fix(billing): complete billing module — fix tier change, platform support, merchant portal

- Fix admin tier change: resolve tier_code→tier_id in update_subscription(),
  delegate to billing_service.change_tier() for Stripe-connected subs
- Add platform support to admin tiers page: platform column, filter dropdown,
  platform selector in create/edit modal, platform_name in tier API response
- Filter used platforms in create subscription modal on merchant detail page
- Enrich merchant portal API responses with tier code, tier_name, platform_name
- Add eager-load of platform relationship in get_merchant_subscription()
- Remove stale store_name/store_code references from merchant templates
- Add merchant tier change endpoint (POST /change-tier) and tier selector UI
  replacing broken requestUpgrade() button
- Fix subscription detail link to use platform_id instead of sub.id

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-10 20:49:48 +01:00
parent 0b37274140
commit d1fe3584ff
54 changed files with 222 additions and 52 deletions

View File

@@ -22,6 +22,8 @@ function adminSubscriptionTiers() {
tiers: [],
stats: null,
includeInactive: false,
platforms: [],
filterPlatformId: '',
// Feature management
features: [],
@@ -51,7 +53,8 @@ function adminSubscriptionTiers() {
stripe_product_id: '',
stripe_price_monthly_id: '',
is_active: true,
is_public: true
is_public: true,
platform_id: null
},
async init() {
@@ -67,7 +70,8 @@ function adminSubscriptionTiers() {
await Promise.all([
this.loadTiers(),
this.loadStats(),
this.loadFeatures()
this.loadFeatures(),
this.loadPlatforms()
]);
tiersLog.info('=== SUBSCRIPTION TIERS PAGE INITIALIZED ===');
} catch (error) {
@@ -92,6 +96,7 @@ function adminSubscriptionTiers() {
params.append('include_inactive', this.includeInactive);
if (this.sortBy) params.append('sort_by', this.sortBy);
if (this.sortOrder) params.append('sort_order', this.sortOrder);
if (this.filterPlatformId) params.append('platform_id', this.filterPlatformId);
const data = await apiClient.get(`/admin/subscriptions/tiers?${params}`);
this.tiers = data.tiers || [];
@@ -125,6 +130,22 @@ function adminSubscriptionTiers() {
}
},
async loadPlatforms() {
try {
const data = await apiClient.get('/admin/platforms');
this.platforms = (data.platforms || []).map(p => ({ id: p.id, name: p.name }));
tiersLog.info(`Loaded ${this.platforms.length} platforms`);
} catch (error) {
tiersLog.error('Failed to load platforms:', error);
}
},
getPlatformName(platformId) {
if (!platformId) return 'Global';
const platform = this.platforms.find(p => p.id === platformId);
return platform ? platform.name : `Platform #${platformId}`;
},
openCreateModal() {
this.editingTier = null;
this.formData = {
@@ -137,7 +158,8 @@ function adminSubscriptionTiers() {
stripe_product_id: '',
stripe_price_monthly_id: '',
is_active: true,
is_public: true
is_public: true,
platform_id: null
};
this.showModal = true;
},
@@ -154,7 +176,8 @@ function adminSubscriptionTiers() {
stripe_product_id: tier.stripe_product_id || '',
stripe_price_monthly_id: tier.stripe_price_monthly_id || '',
is_active: tier.is_active,
is_public: tier.is_public
is_public: tier.is_public,
platform_id: tier.platform_id || null
};
this.showModal = true;
},