// static/vendor/js/billing.js // Vendor billing and subscription management const billingLog = window.LogConfig?.createLogger('BILLING') || console; function vendorBilling() { return { // Inherit base data (dark mode, sidebar, vendor info, etc.) ...data(), currentPage: 'billing', // State loading: true, subscription: null, tiers: [], addons: [], myAddons: [], invoices: [], // UI state showTiersModal: false, showAddonsModal: false, showCancelModal: false, showSuccessMessage: false, showCancelMessage: false, showAddonSuccessMessage: false, cancelReason: '', purchasingAddon: null, // Initialize async init() { // Guard against multiple initialization if (window._vendorBillingInitialized) return; window._vendorBillingInitialized = true; 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] = await Promise.all([ apiClient.get('/vendor/billing/subscription'), apiClient.get('/vendor/billing/tiers'), apiClient.get('/vendor/billing/addons'), apiClient.get('/vendor/billing/my-addons'), apiClient.get('/vendor/billing/invoices?limit=5'), ]); this.subscription = subscriptionRes; this.tiers = tiersRes.tiers || []; this.addons = addonsRes || []; this.myAddons = myAddonsRes || []; this.invoices = invoicesRes.invoices || []; } catch (error) { billingLog.error('Error loading billing data:', error); Utils.showToast('Failed to load billing data', 'error'); } finally { this.loading = false; } }, async selectTier(tier) { if (tier.is_current) return; try { const response = await apiClient.post('/vendor/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('Failed to create checkout session', 'error'); } }, async openPortal() { try { const response = await apiClient.post('/vendor/billing/portal', {}); if (response.portal_url) { window.location.href = response.portal_url; } } catch (error) { billingLog.error('Error opening portal:', error); Utils.showToast('Failed to open payment portal', 'error'); } }, async cancelSubscription() { try { await apiClient.post('/vendor/billing/cancel', { reason: this.cancelReason, immediately: false }); this.showCancelModal = false; Utils.showToast('Subscription cancelled. You have access until the end of your billing period.', 'success'); await this.loadData(); } catch (error) { billingLog.error('Error cancelling subscription:', error); Utils.showToast('Failed to cancel subscription', 'error'); } }, async reactivate() { try { await apiClient.post('/vendor/billing/reactivate', {}); Utils.showToast('Subscription reactivated!', 'success'); await this.loadData(); } catch (error) { billingLog.error('Error reactivating subscription:', error); Utils.showToast('Failed to reactivate subscription', 'error'); } }, async purchaseAddon(addon) { this.purchasingAddon = addon.code; try { const response = await apiClient.post('/vendor/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('Failed to purchase add-on', 'error'); } finally { this.purchasingAddon = null; } }, async cancelAddon(addon) { if (!confirm(`Are you sure you want to cancel ${addon.addon_name}?`)) { return; } try { await apiClient.delete(`/vendor/billing/addons/${addon.id}`); Utils.showToast('Add-on cancelled successfully', 'success'); await this.loadData(); } catch (error) { billingLog.error('Error cancelling addon:', error); Utils.showToast('Failed to cancel add-on', '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); return date.toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric' }); }, formatCurrency(cents, currency = 'EUR') { if (cents === null || cents === undefined) return '-'; const amount = cents / 100; return new Intl.NumberFormat('en-US', { style: 'currency', currency: currency }).format(amount); } }; }