// app/modules/billing/static/store/js/billing.js // Store billing and subscription management const billingLog = window.LogConfig?.createLogger('BILLING') || console; function storeBilling() { return { // Inherit base data (dark mode, sidebar, store info, etc.) ...data(), currentPage: 'billing', // State loading: true, subscription: null, tiers: [], addons: [], myAddons: [], invoices: [], usageMetrics: [], // UI state showTiersModal: false, showAddonsModal: false, showCancelModal: false, showCancelAddonConfirm: false, pendingCancelAddon: null, showSuccessMessage: false, showCancelMessage: false, showAddonSuccessMessage: false, cancelReason: '', purchasingAddon: null, // Initialize async init() { // Load i18n translations await I18n.loadModule('billing'); // Guard against multiple initialization if (window._storeBillingInitialized) return; window._storeBillingInitialized = true; // IMPORTANT: Call parent init first to set storeCode from URL const parentInit = data().init; if (parentInit) { await parentInit.call(this); } try { // Check URL params for success/cancel const params = new URLSearchParams(window.location.search); if (params.get('success') === 'true') { this.showSuccessMessage = true; window.history.replaceState({}, document.title, window.location.pathname); } if (params.get('cancelled') === 'true') { this.showCancelMessage = true; window.history.replaceState({}, document.title, window.location.pathname); } if (params.get('addon_success') === 'true') { this.showAddonSuccessMessage = true; window.history.replaceState({}, document.title, window.location.pathname); } await this.loadData(); } catch (error) { billingLog.error('Failed to initialize billing page:', error); } }, async loadData() { this.loading = true; try { // Load all data in parallel const [subscriptionRes, tiersRes, addonsRes, myAddonsRes, invoicesRes, usageRes] = await Promise.all([ apiClient.get('/store/billing/subscription'), apiClient.get('/store/billing/tiers'), apiClient.get('/store/billing/addons'), apiClient.get('/store/billing/my-addons'), apiClient.get('/store/billing/invoices?limit=5'), apiClient.get('/store/billing/usage').catch(() => ({ usage: [] })), ]); this.subscription = subscriptionRes; this.tiers = tiersRes.tiers || []; this.addons = addonsRes || []; this.myAddons = myAddonsRes || []; this.invoices = invoicesRes.invoices || []; this.usageMetrics = usageRes.usage || usageRes || []; } catch (error) { billingLog.error('Error loading billing data:', error); Utils.showToast(I18n.t('billing.messages.failed_to_load_billing_data'), 'error'); } finally { this.loading = false; } }, async selectTier(tier) { if (tier.is_current) return; try { const response = await apiClient.post('/store/billing/checkout', { tier_code: tier.code, is_annual: false }); if (response.checkout_url) { window.location.href = response.checkout_url; } } catch (error) { billingLog.error('Error creating checkout:', error); Utils.showToast(I18n.t('billing.messages.failed_to_create_checkout_session'), 'error'); } }, async openPortal() { try { const response = await apiClient.post('/store/billing/portal', {}); if (response.portal_url) { window.location.href = response.portal_url; } } catch (error) { billingLog.error('Error opening portal:', error); Utils.showToast(I18n.t('billing.messages.failed_to_open_payment_portal'), 'error'); } }, async cancelSubscription() { try { await apiClient.post('/store/billing/cancel', { reason: this.cancelReason, immediately: false }); this.showCancelModal = false; Utils.showToast(I18n.t('billing.messages.subscription_cancelled_you_have_access_u'), 'success'); await this.loadData(); } catch (error) { billingLog.error('Error cancelling subscription:', error); Utils.showToast(I18n.t('billing.messages.failed_to_cancel_subscription'), 'error'); } }, async reactivate() { try { await apiClient.post('/store/billing/reactivate', {}); Utils.showToast(I18n.t('billing.messages.subscription_reactivated'), 'success'); await this.loadData(); } catch (error) { billingLog.error('Error reactivating subscription:', error); Utils.showToast(I18n.t('billing.messages.failed_to_reactivate_subscription'), 'error'); } }, async purchaseAddon(addon) { this.purchasingAddon = addon.code; try { const response = await apiClient.post('/store/billing/addons/purchase', { addon_code: addon.code, quantity: 1 }); if (response.checkout_url) { window.location.href = response.checkout_url; } } catch (error) { billingLog.error('Error purchasing addon:', error); Utils.showToast(I18n.t('billing.messages.failed_to_purchase_addon'), 'error'); } finally { this.purchasingAddon = null; } }, async cancelAddon(addon) { try { await apiClient.delete(`/store/billing/addons/${addon.id}`); Utils.showToast(I18n.t('billing.messages.addon_cancelled_successfully'), 'success'); await this.loadData(); } catch (error) { billingLog.error('Error cancelling addon:', error); Utils.showToast(I18n.t('billing.messages.failed_to_cancel_addon'), 'error'); } }, // Check if addon is already purchased isAddonPurchased(addonCode) { return this.myAddons.some(a => a.addon_code === addonCode && a.status === 'active'); }, // Formatters formatDate(dateString) { if (!dateString) return '-'; const date = new Date(dateString); const locale = window.STORE_CONFIG?.locale || 'en-GB'; return date.toLocaleDateString(locale, { year: 'numeric', month: 'short', day: 'numeric' }); }, formatCurrency(cents, currency = 'EUR') { if (cents === null || cents === undefined) return '-'; const amount = cents / 100; const locale = window.STORE_CONFIG?.locale || 'en-GB'; const currencyCode = window.STORE_CONFIG?.currency || currency; return new Intl.NumberFormat(locale, { style: 'currency', currency: currencyCode }).format(amount); } }; }