/** * Plans & Billing Page - Unified Subscription & Payment Management * Comprehensive dashboard for all billing-related features * Rich, actionable, data-driven UX following UsageDashboard patterns */ import { useState, useEffect, useRef } from 'react'; import { createPortal } from 'react-dom'; import { Link } from 'react-router-dom'; import { CreditCardIcon, TrendingUpIcon, FileTextIcon, WalletIcon, ArrowUpIcon, Loader2Icon, AlertCircleIcon, CheckCircleIcon, DownloadIcon, ZapIcon, GlobeIcon, UsersIcon, XIcon, CalendarIcon, RefreshCwIcon, ChevronRightIcon, PlusIcon, Building2Icon, TagIcon, LockIcon, ShootingStarIcon, DollarLineIcon, } from '../../icons'; import { Card } from '../../components/ui/card'; import Badge from '../../components/ui/badge/Badge'; import Button from '../../components/ui/button/Button'; import Label from '../../components/form/Label'; import PageMeta from '../../components/common/PageMeta'; import PageHeader from '../../components/common/PageHeader'; import { useToast } from '../../components/ui/toast/ToastContainer'; import { usePageLoading } from '../../context/PageLoadingContext'; import { formatCurrency } from '../../utils'; import { fetchAPI } from '../../services/api'; import { getCreditBalance, getCreditPackages, getInvoices, getAccountPaymentMethods, purchaseCreditPackage, downloadInvoicePDF, getPayments, type CreditBalance, type CreditPackage, type Invoice, type PaymentMethod, type Payment, getPlans, getSubscriptions, createSubscription, cancelSubscription, type Plan, type Subscription, // Payment gateway methods subscribeToPlan, purchaseCredits, openStripeBillingPortal, getAvailablePaymentGateways, type PaymentGateway, } from '../../services/billing.api'; import { useAuthStore } from '../../store/authStore'; import PayInvoiceModal from '../../components/billing/PayInvoiceModal'; import PendingPaymentView from '../../components/billing/PendingPaymentView'; /** * Helper function to determine the effective currency based on billing country and payment method * - PKR for Pakistan users using bank_transfer * - USD for all other cases (Stripe, PayPal, or non-PK countries) */ const getCurrencyForDisplay = (billingCountry: string, paymentMethod?: string): string => { if (billingCountry === 'PK' && paymentMethod === 'bank_transfer') { return 'PKR'; } return 'USD'; }; /** * Convert USD price to PKR using approximate exchange rate * Backend uses 278 PKR per USD * Rounds to nearest thousand for cleaner display */ const convertUSDToPKR = (usdAmount: string | number): number => { const amount = typeof usdAmount === 'string' ? parseFloat(usdAmount) : usdAmount; const pkr = amount * 278; return Math.round(pkr / 1000) * 1000; // Round to nearest thousand }; export default function PlansAndBillingPage() { const { startLoading, stopLoading } = usePageLoading(); const toast = useToast(); const hasLoaded = useRef(false); // FIX: Subscribe to user changes from Zustand store (reactive) const user = useAuthStore((state) => state.user); const refreshUser = useAuthStore((state) => state.refreshUser); const isAwsAdmin = user?.account?.slug === 'aws-admin'; // Track if initial data has been loaded to prevent flash const [initialDataLoaded, setInitialDataLoaded] = useState(false); // Payment processing state - shows beautiful loading UI const [paymentProcessing, setPaymentProcessing] = useState<{ active: boolean; stage: 'verifying' | 'processing' | 'finalizing' | 'activating'; message: string; } | null>(null); // UI States const [error, setError] = useState(''); const [planLoadingId, setPlanLoadingId] = useState(null); const [purchaseLoadingId, setPurchaseLoadingId] = useState(null); const [showCancelConfirm, setShowCancelConfirm] = useState(false); const [showUpgradeModal, setShowUpgradeModal] = useState(false); const [selectedBillingCycle, setSelectedBillingCycle] = useState<'monthly' | 'annual'>('monthly'); const [selectedGateway, setSelectedGateway] = useState('stripe'); const [showPayInvoiceModal, setShowPayInvoiceModal] = useState(false); const [selectedInvoice, setSelectedInvoice] = useState(null); // Data States const [creditBalance, setCreditBalance] = useState(null); const [packages, setPackages] = useState([]); const [invoices, setInvoices] = useState([]); const [payments, setPayments] = useState([]); const [userPaymentMethods, setUserPaymentMethods] = useState([]); const [plans, setPlans] = useState([]); const [subscriptions, setSubscriptions] = useState([]); const [selectedPaymentMethod, setSelectedPaymentMethod] = useState(undefined); const [availableGateways, setAvailableGateways] = useState<{ stripe: boolean; paypal: boolean; manual: boolean }>({ stripe: false, paypal: false, manual: false, // FIX: Initialize as false, will be set based on country }); useEffect(() => { if (hasLoaded.current) return; hasLoaded.current = true; // Handle payment gateway return URLs BEFORE loadData const params = new URLSearchParams(window.location.search); const success = params.get('success'); const sessionId = params.get('session_id'); // Stripe session ID const canceled = params.get('canceled'); const purchase = params.get('purchase'); const paypalStatus = params.get('paypal'); const paypalToken = params.get('token'); // PayPal token from URL const planIdParam = params.get('plan_id'); const packageIdParam = params.get('package_id'); // Don't destructure from getState - use hooks above instead // ============================================================================ // PAYMENT RETURN LOGGING - Comprehensive debug output // ============================================================================ const LOG_PREFIX = '[PAYMENT-RETURN]'; console.group(`${LOG_PREFIX} Payment Return Flow Started`); console.log(`${LOG_PREFIX} Full URL:`, window.location.href); console.log(`${LOG_PREFIX} Session ID:`, sessionId); // Detect which payment flow we're in const paymentFlow = (paypalStatus === 'success' && paypalToken) ? 'PAYPAL_SUCCESS' : paypalStatus === 'cancel' ? 'PAYPAL_CANCEL' : (success === 'true' && sessionId) ? 'STRIPE_SUCCESS_WITH_SESSION' : success === 'true' ? 'STRIPE_SUCCESS_NO_SESSION' : canceled === 'true' ? 'STRIPE_CANCELED' : purchase === 'success' ? 'CREDIT_PURCHASE_SUCCESS' : purchase === 'canceled' ? 'CREDIT_PURCHASE_CANCELED' : 'NO_PAYMENT_RETURN'; console.log(`${LOG_PREFIX} ===== DETECTED PAYMENT FLOW =====`); console.log(`${LOG_PREFIX} Flow type:`, paymentFlow); console.groupEnd(); // Handle PayPal return - Get order_id from localStorage and capture if (paypalStatus === 'success' && paypalToken) { console.group(`${LOG_PREFIX} PayPal Success Flow`); // FIX: Retrieve order_id from localStorage (stored before redirect) const storedOrderId = localStorage.getItem('paypal_order_id'); console.log(`${LOG_PREFIX} PayPal token from URL:`, paypalToken); console.log(`${LOG_PREFIX} Stored order_id from localStorage:`, storedOrderId); console.log(`${LOG_PREFIX} plan_id:`, planIdParam); console.log(`${LOG_PREFIX} package_id:`, packageIdParam); if (!storedOrderId) { console.error(`${LOG_PREFIX} ❌ CRITICAL: No order_id in localStorage!`); console.log(`${LOG_PREFIX} This means order_id was not saved before redirect to PayPal`); console.groupEnd(); toast?.error?.('Payment not captured - order ID missing. Please try again.'); window.history.replaceState({}, '', window.location.pathname); loadData(); // Still load data to show current state return; } console.log(`${LOG_PREFIX} ✓ Order ID found, proceeding to capture...`); // Show payment processing UI for PayPal setPaymentProcessing({ active: true, stage: 'processing', message: 'Completing PayPal payment...' }); // Clean URL immediately window.history.replaceState({}, '', window.location.pathname); // Import and capture PayPal order import('../../services/billing.api').then(async ({ capturePayPalOrder }) => { try { const captureResponse = await capturePayPalOrder(storedOrderId, { plan_id: planIdParam || undefined, package_id: packageIdParam || undefined, }); console.log(`${LOG_PREFIX} ✓ PayPal capture SUCCESS!`, captureResponse); localStorage.removeItem('paypal_order_id'); // Update stage setPaymentProcessing({ active: true, stage: 'activating', message: 'Activating your subscription...' }); // Refresh user data - IMPORTANT: wait for this! try { await refreshUser(); console.log(`${LOG_PREFIX} ✓ User refreshed`); } catch (refreshErr) { console.error(`${LOG_PREFIX} User refresh failed:`, refreshErr); } // Short delay then complete setTimeout(() => { setPaymentProcessing(null); toast?.success?.('Payment completed successfully!'); loadData(); }, 500); } catch (err: any) { console.error(`${LOG_PREFIX} ❌ PayPal capture FAILED:`, err); localStorage.removeItem('paypal_order_id'); setPaymentProcessing(null); toast?.error?.(err?.message || 'Failed to complete PayPal payment'); loadData(); } }); console.groupEnd(); return; // Don't load data yet, wait for capture to complete } else if (paypalStatus === 'cancel') { console.log(`${LOG_PREFIX} PayPal payment was cancelled by user`); localStorage.removeItem('paypal_order_id'); // Clear on cancellation toast?.info?.('PayPal payment was cancelled'); window.history.replaceState({}, '', window.location.pathname); } // Handle Stripe return - Verify payment with backend else if (success === 'true' && sessionId) { console.log(`${LOG_PREFIX} Stripe Success Flow - Session:`, sessionId); // Show beautiful processing UI setPaymentProcessing({ active: true, stage: 'verifying', message: 'Verifying your payment...' }); // Clean URL immediately window.history.replaceState({}, '', window.location.pathname); fetchAPI(`/v1/billing/stripe/verify-return/?session_id=${sessionId}`) .then(async (data) => { console.log(`${LOG_PREFIX} Verification response:`, data); if (data.payment_processed) { // Payment already processed by webhook! setPaymentProcessing({ active: true, stage: 'activating', message: 'Activating your subscription...' }); // Refresh user to get updated account status try { await refreshUser(); console.log(`${LOG_PREFIX} User refreshed successfully`); } catch (err) { console.error(`${LOG_PREFIX} User refresh failed:`, err); } // Short delay for UX, then show success setTimeout(() => { setPaymentProcessing(null); toast?.success?.('Payment successful! Your account is now active.'); loadData(); }, 500); } else if (data.should_poll) { // Webhook hasn't fired yet, poll for status setPaymentProcessing({ active: true, stage: 'processing', message: 'Processing your payment...' }); pollPaymentStatus(sessionId); } else { setPaymentProcessing(null); toast?.warning?.(data.message || 'Payment verification pending'); loadData(); } }) .catch(err => { console.error(`${LOG_PREFIX} Verification failed:`, err); setPaymentProcessing(null); toast?.warning?.('Payment verification pending. Please refresh the page.'); loadData(); }); console.groupEnd(); return; } else if (success === 'true') { // Stripe return without session_id (old flow fallback) console.log(`${LOG_PREFIX} Stripe success without session_id (legacy flow)`); toast?.success?.('Subscription activated successfully!'); refreshUser().catch(() => {}); window.history.replaceState({}, '', window.location.pathname); } else if (canceled === 'true') { console.log(`${LOG_PREFIX} Stripe payment was cancelled`); toast?.info?.('Payment was cancelled'); window.history.replaceState({}, '', window.location.pathname); } else if (purchase === 'success') { console.log(`${LOG_PREFIX} Credit purchase success`); toast?.success?.('Credits purchased successfully!'); refreshUser().catch(() => {}); window.history.replaceState({}, '', window.location.pathname); } else if (purchase === 'canceled') { console.log(`${LOG_PREFIX} Credit purchase cancelled`); toast?.info?.('Credit purchase was cancelled'); window.history.replaceState({}, '', window.location.pathname); } else { console.log(`${LOG_PREFIX} No payment return parameters detected, loading page normally`); } // Helper function to poll payment status with beautiful UI updates async function pollPaymentStatus(sessionId: string, attempts = 0) { const maxAttempts = 15; // Increased to 15 attempts console.log(`${LOG_PREFIX} [POLL] Attempt ${attempts + 1}/${maxAttempts}`); // Update processing stage based on attempt count if (attempts === 3) { setPaymentProcessing({ active: true, stage: 'finalizing', message: 'Finalizing your payment...' }); } if (attempts >= maxAttempts) { console.warn(`${LOG_PREFIX} [POLL] Max attempts reached`); setPaymentProcessing(null); toast?.warning?.('Payment is being processed. Please refresh the page in a moment.'); loadData(); return; } // Faster initial polls (800ms), slower later (1.5s) const pollDelay = attempts < 5 ? 800 : 1500; setTimeout(async () => { try { const data = await fetchAPI(`/v1/billing/stripe/verify-return/?session_id=${sessionId}`); if (data.payment_processed) { console.log(`${LOG_PREFIX} [POLL] Payment processed!`); // Show activating stage setPaymentProcessing({ active: true, stage: 'activating', message: 'Activating your subscription...' }); // Refresh user data try { await refreshUser(); console.log(`${LOG_PREFIX} [POLL] User refreshed`); } catch (err) { console.error(`${LOG_PREFIX} [POLL] User refresh failed:`, err); } // Short delay then complete setTimeout(() => { setPaymentProcessing(null); toast?.success?.('Payment successful! Your account is now active.'); loadData(); }, 500); } else { // Continue polling pollPaymentStatus(sessionId, attempts + 1); } } catch (pollErr) { console.error(`${LOG_PREFIX} [POLL] Error:`, pollErr); setPaymentProcessing(null); toast?.warning?.('Please refresh page to see updated status.'); loadData(); } }, pollDelay); } // Load data after handling return URLs loadData(); console.groupEnd(); }, [refreshUser]); const handleError = (err: any, fallback: string) => { const message = err?.message || fallback; setError(message); toast?.error?.(message); }; const loadData = async (allowRetry = true) => { try { startLoading('Loading billing data...'); const wait = (ms: number) => new Promise((res) => setTimeout(res, ms)); const balanceData = await getCreditBalance(); const packagesPromise = getCreditPackages(); const invoicesPromise = getInvoices({}); const paymentsPromise = getPayments({}); const userMethodsPromise = getAccountPaymentMethods(); const plansData = await getPlans(); await wait(400); let subsData: { results: Subscription[] } = { results: [] }; try { subsData = await getSubscriptions(); } catch (subErr: any) { if (subErr?.status === 429 && allowRetry) { await wait(2500); try { subsData = await getSubscriptions(); } catch { subsData = { results: [] }; } } } const [packagesData, invoicesData, paymentsData, userMethodsData] = await Promise.all([ packagesPromise, invoicesPromise, paymentsPromise, userMethodsPromise ]); setCreditBalance(balanceData); setPackages(packagesData.results || []); setInvoices(invoicesData.results || []); setPayments(paymentsData.results || []); // Load user's verified payment methods (AccountPaymentMethod) const userMethods = (userMethodsData.results || []).filter((m: any) => m.is_enabled !== false); setUserPaymentMethods(userMethods); // Select the user's default/verified payment method if (userMethods.length > 0) { const defaultMethod = userMethods.find((m: any) => m.is_default && m.is_verified) || userMethods.find((m: any) => m.is_verified) || userMethods.find((m: any) => m.is_default) || userMethods[0]; if (defaultMethod) { setSelectedPaymentMethod(defaultMethod.type); } } // Filter plans const activePlans = (plansData.results || []).filter((p) => p.is_active !== false); const filteredPlans = activePlans.filter((p) => { const name = (p.name || '').toLowerCase(); const slug = (p.slug || '').toLowerCase(); const isEnterprise = name.includes('enterprise') || slug === 'enterprise'; return isAwsAdmin ? true : !isEnterprise; }); const accountPlan = user?.account?.plan; if (accountPlan && !filteredPlans.find((p) => p.id === accountPlan.id)) { filteredPlans.push(accountPlan as any); } setPlans(filteredPlans); const subs = subsData.results || []; if (subs.length === 0 && accountPlan) { subs.push({ id: accountPlan.id || 0, plan: accountPlan, status: 'active' } as any); } setSubscriptions(subs); // Load available payment gateways and sync with user's payment method try { // FIX: Pass billing country to filter payment gateways correctly const billingCountry = user?.account?.billing_country || 'US'; const gateways = await getAvailablePaymentGateways(billingCountry); setAvailableGateways(gateways); // Use user's verified payment method to set gateway // userMethods was already loaded above const verifiedMethod = userMethods.find( (m: any) => m.is_verified && m.is_default ) || userMethods.find((m: any) => m.is_verified) || userMethods[0]; let userPreferredGateway: PaymentGateway | null = null; if (verifiedMethod) { if (verifiedMethod.type === 'stripe' && gateways.stripe) { userPreferredGateway = 'stripe'; } else if (verifiedMethod.type === 'paypal' && gateways.paypal) { userPreferredGateway = 'paypal'; } else if (['bank_transfer', 'local_wallet', 'manual'].includes(verifiedMethod.type)) { userPreferredGateway = 'manual'; } } // Set selected gateway based on user preference or available gateways if (userPreferredGateway) { setSelectedGateway(userPreferredGateway); } else if (gateways.stripe) { setSelectedGateway('stripe'); } else if (gateways.paypal) { setSelectedGateway('paypal'); } else { setSelectedGateway('manual'); } } catch { // Non-critical - just keep defaults console.log('Could not load payment gateways, using defaults'); } } catch (err: any) { if (err?.status === 429 && allowRetry) { setError('Request was throttled. Retrying...'); setTimeout(() => loadData(false), 2500); } else if (err?.status !== 429) { setError(err.message || 'Failed to load billing data'); } } finally { stopLoading(); setInitialDataLoaded(true); } }; const handleSelectPlan = async (planId: number) => { try { setPlanLoadingId(planId); // Use payment gateway integration for Stripe/PayPal if (selectedGateway === 'stripe' || selectedGateway === 'paypal') { const { redirect_url } = await subscribeToPlan(planId.toString(), selectedGateway); window.location.href = redirect_url; return; } // Fall back to manual/bank transfer flow await createSubscription({ plan_id: planId, payment_method: selectedPaymentMethod }); toast?.success?.('Plan upgraded successfully!'); setShowUpgradeModal(false); await loadData(); } catch (err: any) { handleError(err, 'Failed to upgrade plan'); } finally { setPlanLoadingId(null); } }; const handleCancelSubscription = async () => { if (!currentSubscription?.id) return; try { setPlanLoadingId(currentSubscription.id); await cancelSubscription(currentSubscription.id); toast?.success?.('Subscription cancelled'); setShowCancelConfirm(false); await loadData(); } catch (err: any) { handleError(err, 'Failed to cancel subscription'); } finally { setPlanLoadingId(null); } }; const handlePurchaseCredits = async (packageId: number) => { try { setPurchaseLoadingId(packageId); // Use payment gateway integration for Stripe/PayPal if (selectedGateway === 'stripe' || selectedGateway === 'paypal') { const { redirect_url } = await purchaseCredits(packageId.toString(), selectedGateway); window.location.href = redirect_url; return; } // Fall back to manual/bank transfer flow await purchaseCreditPackage({ package_id: packageId, payment_method: selectedPaymentMethod as any || 'manual' }); toast?.success?.('Credits purchased successfully!'); await loadData(); } catch (err: any) { handleError(err, 'Failed to purchase credits'); } finally { setPurchaseLoadingId(null); } }; const handleManageSubscription = async () => { try { const { portal_url } = await openStripeBillingPortal(); window.location.href = portal_url; } catch (err: any) { handleError(err, 'Failed to open billing portal'); } }; const handleDownloadInvoice = async (invoiceId: number) => { try { const blob = await downloadInvoicePDF(invoiceId); const url = window.URL.createObjectURL(blob); const link = document.createElement('a'); link.href = url; link.download = `invoice-${invoiceId}.pdf`; document.body.appendChild(link); link.click(); link.remove(); window.URL.revokeObjectURL(url); } catch (err: any) { handleError(err, 'Failed to download invoice'); } }; // Computed values const currentSubscription = subscriptions.find((sub) => sub.status === 'active') || subscriptions[0]; const currentPlanId = typeof currentSubscription?.plan === 'object' ? currentSubscription.plan.id : currentSubscription?.plan; const accountPlanId = user?.account?.plan?.id; const effectivePlanId = currentPlanId || accountPlanId; const currentPlan = plans.find((p) => p.id === effectivePlanId) || user?.account?.plan; // FIX: hasActivePlan should check account status, not just plan existence const accountStatus = user?.account?.status || ''; const hasPendingInvoice = invoices.some((inv) => inv.status === 'pending'); const hasActivePlan = accountStatus === 'active' && effectivePlanId && currentPlan?.slug !== 'free' && !hasPendingInvoice; const hasPendingPayment = payments.some((p) => p.status === 'pending_approval'); // Detect new user pending payment scenario: // - account status is 'pending_payment' // - user has never made a successful payment const hasEverPaid = payments.some((p) => p.status === 'succeeded' || p.status === 'completed'); const isNewUserPendingPayment = accountStatus === 'pending_payment' && !hasEverPaid; const pendingInvoice = invoices.find((inv) => inv.status === 'pending'); const billingCountry = (user?.account as any)?.billing_country || 'US'; // FIX: canManageBilling should check if user actually paid via Stripe const userPaymentMethod = (user?.account as any)?.payment_method || ''; const hasStripeCustomerId = !!(user?.account as any)?.stripe_customer_id; const canManageBilling = userPaymentMethod === 'stripe' && hasStripeCustomerId && hasActivePlan; // Determine effective currency for display based on country and payment method const effectiveCurrency = getCurrencyForDisplay(billingCountry, userPaymentMethod); // Combined check: disable Buy Credits if no active plan OR has pending invoice const canBuyCredits = hasActivePlan && !hasPendingInvoice; // Credit usage percentage const creditUsage = creditBalance && creditBalance.plan_credits_per_month > 0 ? Math.round((creditBalance.credits_used_this_month / creditBalance.plan_credits_per_month) * 100) : 0; // Upgrade plans (exclude current and free) const upgradePlans = plans.filter(p => { const price = Number(p.price) || 0; return price > 0 && p.id !== effectivePlanId; }).sort((a, b) => (Number(a.price) || 0) - (Number(b.price) || 0)); // PAYMENT PROCESSING OVERLAY - Beautiful full-page loading with breathing badge if (paymentProcessing?.active) { const stageConfig = { verifying: { color: 'bg-blue-600', label: 'Verifying Payment' }, processing: { color: 'bg-amber-600', label: 'Processing Payment' }, finalizing: { color: 'bg-purple-600', label: 'Finalizing' }, activating: { color: 'bg-green-600', label: 'Activating Subscription' }, }; const config = stageConfig[paymentProcessing.stage]; // Use Modal-style overlay (matches app's default modal design) return createPortal(
{/* Glass-like backdrop - same as Modal component */}
{/* Content card - Modal style */}
{/* Main Loading Spinner */}
{/* Message */}

{paymentProcessing.message}

Please don't close this page

{/* Stage Badge */}
{config.label}
, document.body ); } // Show loading spinner until initial data is loaded // This prevents the flash of billing dashboard before PendingPaymentView if (!initialDataLoaded) { return (

Loading billing information...

); } // NEW USER PENDING PAYMENT - Show full-page payment view // This is the simplified flow for users who just signed up with a paid plan if (isNewUserPendingPayment && pendingInvoice) { const planName = currentPlan?.name || pendingInvoice.subscription?.plan?.name || 'Selected Plan'; const invoiceCurrency = pendingInvoice.currency || 'USD'; // Get USD price from plan, PKR price from invoice const planUSDPrice = currentPlan?.price || pendingInvoice.subscription?.plan?.price || '0'; const invoicePKRPrice = invoiceCurrency === 'PKR' ? (pendingInvoice.total_amount || pendingInvoice.total || '0') : undefined; // Check if user has a pending bank transfer (status = pending_approval with payment_method = bank_transfer) const hasPendingBankTransfer = payments.some( (p) => p.status === 'pending_approval' && (p.payment_method === 'bank_transfer' || p.payment_method === 'manual') ); // Debug log for payment view console.log('[PlansAndBillingPage] Rendering PendingPaymentView:', { billingCountry, invoiceCurrency, planUSDPrice, invoicePKRPrice, planName, hasPendingBankTransfer }); return ( { // Refresh user and billing data const { refreshUser } = useAuthStore.getState(); refreshUser().catch(() => {}); loadData(); }} /> ); } // EXISTING USER - Show normal billing dashboard return ( <> , color: 'blue' }} actions={ } /> {/* Alerts */} {!hasActivePlan && (

No Active Plan

Choose a plan below to activate your account and unlock all features.

)} {hasPendingPayment && (

Payment Pending Review

Your payment is being processed. Credits will be added once approved.

)} {error && (

{error}

)}
{/* SECTION 1: Current Plan Hero */}
{/* Main Plan Card */}

{currentPlan?.name || 'No Plan'}

{hasActivePlan ? 'Active' : 'Inactive'}

{currentPlan?.description || 'Select a plan to unlock features'}

{canManageBilling && ( )}
{/* Quick Stats */}
Credits
{creditBalance?.credits?.toLocaleString() || 0}
Available now
Used
{creditBalance?.credits_used_this_month?.toLocaleString() || 0}
This month ({creditUsage}%)
Renews
{currentSubscription?.current_period_end ? new Date(currentSubscription.current_period_end).toLocaleDateString('en-US', { month: 'short', day: 'numeric' }) : '—'}
Next billing
Monthly
{formatCurrency(Number(currentPlan?.price || 0), 'USD')}
{billingCountry === 'PK' ? ( <>≈ PKR {convertUSDToPKR(Number(currentPlan?.price || 0)).toLocaleString()}/mo ) : ( 'Per month' )}
{/* Credit Usage Bar */}
Credit Usage {creditBalance?.credits_used_this_month?.toLocaleString() || 0} / {creditBalance?.plan_credits_per_month?.toLocaleString() || 0}
= 90 ? 'bg-error-500' : creditUsage >= 70 ? 'bg-warning-500' : 'bg-gradient-to-r from-brand-500 to-purple-500' }`} style={{ width: `${Math.min(creditUsage, 100)}%` }} />
{/* Plan Limits Summary */}

Plan Limits

View Usage
Sites
{currentPlan?.max_sites === 9999 ? '∞' : currentPlan?.max_sites || 0}
Team Members
{currentPlan?.max_users === 9999 ? '∞' : currentPlan?.max_users || 0}
Keywords
{currentPlan?.max_keywords ? currentPlan.max_keywords.toLocaleString() : 0}
Monthly Credits
{currentPlan?.included_credits?.toLocaleString() || 0}
{hasActivePlan && ( )}
{/* SECTION 2: Buy Credits + Quick Upgrade */}
{/* Buy Additional Credits */}

Buy Credits

Top up your credit balance

{/* Show message if no active plan */} {!hasActivePlan ? (

Subscribe to a plan first to purchase additional credits

) : hasPendingInvoice ? (

Please pay your pending invoice first

Credit purchases are disabled until your outstanding balance is settled

) : ( <> {/* Payment Method Selector - Clear buttons */} {(availableGateways.stripe || availableGateways.paypal) && (
{availableGateways.stripe && ( )} {availableGateways.paypal && ( )}
)}
{packages.slice(0, 4).map((pkg) => ( ))}
)}
{/* Quick Upgrade Options */}

Upgrade Plan

Get more features & credits

{upgradePlans.slice(0, 3).map((plan) => (
{plan.name}
{plan.included_credits?.toLocaleString() || 0} credits/mo
{formatCurrency(plan.price, 'USD')}/mo
{billingCountry === 'PK' && (
≈ PKR {convertUSDToPKR(plan.price).toLocaleString()}/mo
)}
))} {upgradePlans.length === 0 && (

You're on the best plan!

)}
{/* SECTION 3: Billing History */}

Billing History

Recent invoices and transactions

{invoices.length === 0 ? ( ) : ( invoices.slice(0, 5).map((invoice) => ( )) )}
Invoice Date Amount Status Actions

No invoices yet

Your billing history will appear here

{invoice.invoice_number} {new Date(invoice.created_at).toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' })}
{formatCurrency(invoice.total_amount, 'USD')}
{billingCountry === 'PK' && invoice.currency === 'USD' && (
≈ PKR {convertUSDToPKR(invoice.total_amount).toLocaleString()}
)}
{invoice.status}
{invoice.status === 'pending' && ( )}
{/* SECTION 4: Payment Methods - User's verified payment methods */}

Payment Methods

Your saved payment methods

{userPaymentMethods .filter((method: any) => { // Bank transfer is only available for Pakistani users if (method.type === 'bank_transfer' && billingCountry !== 'PK') { return false; } return true; }) .map((method: any) => (
{method.type === 'bank_transfer' || method.type === 'local_wallet' ? ( ) : method.type === 'paypal' ? ( ) : ( )}
{method.display_name}
{method.type?.replace('_', ' ')}
{method.is_verified && ( Verified )} {method.is_default && ( Default )}
{selectedPaymentMethod !== method.type ? ( ) : (
Selected for payment
)}
))} {userPaymentMethods.length === 0 && (

No payment methods saved yet.

Complete a payment to save your method.

)}
{/* Upgrade Modal */} {showUpgradeModal && (

Choose Your Plan

Select the plan that fits your needs

{/* Billing Toggle & Payment Gateway */}
{/* Billing Cycle Toggle */}
{/* Payment Gateway Selector */} {(availableGateways.stripe || availableGateways.paypal) && (
{availableGateways.stripe && ( )} {availableGateways.paypal && ( )} {availableGateways.manual && ( )}
)}
{/* Plans Grid */}
{upgradePlans.map((plan, index) => { const isPopular = index === 1; const planPrice = Number(plan.price) || 0; const annualPrice = planPrice * 0.8 * 12; const displayPrice = selectedBillingCycle === 'annual' ? (annualPrice / 12).toFixed(0) : planPrice; return (
{isPopular && (
Most Popular
)}

{plan.name}

${displayPrice} /mo
{selectedBillingCycle === 'annual' && (
${annualPrice.toFixed(0)} billed annually
)}
  • {plan.included_credits?.toLocaleString() || 0} credits/month
  • {plan.max_sites === 9999 ? 'Unlimited' : plan.max_sites} sites
  • {plan.max_users === 9999 ? 'Unlimited' : plan.max_users} team members
  • {plan.max_keywords?.toLocaleString() || 0} keywords
); })}
{/* Policy Info */}
Flexible billing: Upgrades take effect immediately with prorated billing. Downgrades apply at the end of your billing period. Cancel anytime with no penalties.
)} {/* Cancel Confirmation Modal */} {showCancelConfirm && (

Cancel Subscription

Are you sure?

Your subscription will remain active until the end of your billing period.

  • You'll lose access to premium features
  • Remaining credits preserved for 30 days
  • Resubscribe anytime to restore access
)} {/* Pay Invoice Modal */} {showPayInvoiceModal && selectedInvoice && ( { setShowPayInvoiceModal(false); setSelectedInvoice(null); }} onSuccess={async () => { setShowPayInvoiceModal(false); setSelectedInvoice(null); // Refresh user and billing data const { refreshUser } = useAuthStore.getState(); await refreshUser(); await loadData(); toast?.success?.('Payment processed successfully!'); }} invoice={selectedInvoice} userCountry={(user?.account as any)?.billing_country || 'US'} defaultPaymentMethod={selectedPaymentMethod} /> )} ); }