/** * Billing API Service * Uses STANDARD billing endpoints from /v1/billing and /v1/admin/billing */ import { fetchAPI } from './api'; // Coalesce concurrent credit balance requests to avoid hitting throttle for normal users let creditBalanceInFlight: Promise | null = null; // ============================================================================ // TYPES // ============================================================================ export interface CreditBalance { credits: number; // Current balance plan_credits_per_month: number; // Monthly allocation from plan credits_used_this_month: number; // Used this billing period credits_remaining: number; // Remaining credits } export interface CreditTransaction { id: number; amount: number; transaction_type: 'purchase' | 'subscription' | 'refund' | 'deduction' | 'adjustment' | 'grant'; description: string; created_at: string; reference_id?: string; metadata?: Record; balance_after?: number; } export interface CreditUsageLog { id: number; operation_type: string; credits_used: number; cost_usd: string; model_used?: string; tokens_input?: number; tokens_output?: number; created_at: string; metadata?: Record; } export interface AdminBillingStats { total_accounts: number; active_subscriptions: number; total_revenue: string; revenue_this_month?: string; new_accounts_this_month?: number; active_accounts?: number; credits_issued_30d?: number; credits_used_30d?: number; pending_approvals?: number; invoices_pending?: number; invoices_overdue?: number; system_health?: { status: string; last_check: string; }; recent_activity?: Array<{ id: number; type: string; account_name: string; amount: string; currency: string; timestamp: string; description: string; }>; } export interface CreditCostConfig { id: number; operation_type: string; credits_cost: number; unit: string; display_name: string; description: string; is_active: boolean; updated_at: string; } export interface AdminUser { id: number; email: string; account_name: string; account_id: number; role: string; is_active: boolean; credit_balance: number; plan_name?: string; created_at: string; last_login?: string; } export interface Invoice { id: number; invoice_number: string; status: 'draft' | 'pending' | 'paid' | 'void' | 'uncollectible'; total_amount: string; subtotal: string; tax_amount: string; currency: string; created_at: string; paid_at?: string; due_date?: string; line_items: Array<{ description: string; amount: string; quantity?: number; }>; billing_email?: string; notes?: string; stripe_invoice_id?: string; billing_period_start?: string; billing_period_end?: string; account_name?: string; } export interface Payment { id: number; invoice_id: number; invoice_number?: string; amount: string; currency: string; status: 'pending' | 'processing' | 'succeeded' | 'failed' | 'refunded' | 'cancelled' | 'pending_approval' | 'completed'; payment_method: 'stripe' | 'paypal' | 'bank_transfer' | 'local_wallet' | 'manual'; created_at: string; processed_at?: string; failed_at?: string; refunded_at?: string; failure_reason?: string; manual_reference?: string; manual_notes?: string; transaction_reference?: string; } export interface CreditPackage { id: number; name: string; slug: string; credits: number; price: string; discount_percentage: number; is_featured: boolean; description: string; display_order: number; is_active?: boolean; sort_order?: number; stripe_price_id?: string | null; } export interface TeamMember { id: number; email: string; first_name: string; last_name: string; role: string; is_active: boolean; is_staff?: boolean; created_at: string; date_joined?: string; last_login?: string; } export interface PaymentMethod { id: string; type: 'stripe' | 'paypal' | 'bank_transfer' | 'local_wallet' | 'manual'; display_name: string; name?: string; is_enabled: boolean; is_default?: boolean; instructions?: string; country_code?: string; bank_details?: { bank_name?: string; account_number?: string; routing_number?: string; swift_code?: string; }; wallet_details?: { wallet_type?: string; wallet_id?: string; }; } export interface PendingPayment extends Payment { account_name: string; user_email: string; invoice_number?: string; admin_notes?: string; } // ============================================================================ // CREDIT BALANCE & TRANSACTIONS // ============================================================================ export async function getCreditBalance(): Promise { // Use canonical balance endpoint (credits/balance) per unified API structure if (creditBalanceInFlight) { return creditBalanceInFlight; } creditBalanceInFlight = fetchAPI('/v1/billing/credits/balance/'); try { return await creditBalanceInFlight; } finally { creditBalanceInFlight = null; } } export async function getCreditTransactions(): Promise<{ results: CreditTransaction[]; count: number; current_balance?: number; }> { return fetchAPI('/v1/billing/credits/transactions/'); } // ============================================================================ // CREDIT USAGE LOGS // ============================================================================ export async function getCreditUsage(params?: { operation_type?: string; start_date?: string; end_date?: string; }): Promise<{ results: CreditUsageLog[]; count: number; }> { const queryParams = new URLSearchParams(); if (params?.operation_type) queryParams.append('operation_type', params.operation_type); if (params?.start_date) queryParams.append('start_date', params.start_date); if (params?.end_date) queryParams.append('end_date', params.end_date); const url = `/v1/billing/credits/usage/${queryParams.toString() ? '?' + queryParams.toString() : ''}`; return fetchAPI(url); } export async function getCreditUsageSummary(params?: { start_date?: string; end_date?: string; }): Promise<{ total_credits_used: number; total_cost_usd: string; by_operation: Record; by_model: Record; }> { const queryParams = new URLSearchParams(); if (params?.start_date) queryParams.append('start_date', params.start_date); if (params?.end_date) queryParams.append('end_date', params.end_date); const url = `/v1/billing/credits/usage/summary/${queryParams.toString() ? '?' + queryParams.toString() : ''}`; return fetchAPI(url); } export async function getCreditUsageLimits(): Promise<{ plan_name: string; plan_credits_per_month: number; credits_used_this_month: number; credits_remaining: number; percentage_used: number; approaching_limit: boolean; }> { return fetchAPI('/v1/billing/credits/usage/limits/'); } // ============================================================================ // ADMIN - BILLING STATS & MANAGEMENT // ============================================================================ export async function getAdminBillingStats(): Promise { // Admin billing dashboard metrics // Use business billing stats endpoint to include revenue, accounts, credits, and recent payments return fetchAPI('/v1/admin/billing/stats/'); } export async function getAdminInvoices(params?: { status?: string; account_id?: number; search?: string }): Promise<{ results: Invoice[]; count: number; }> { const queryParams = new URLSearchParams(); if (params?.status) queryParams.append('status', params.status); if (params?.account_id) queryParams.append('account_id', String(params.account_id)); if (params?.search) queryParams.append('search', params.search); const url = `/v1/admin/billing/invoices/${queryParams.toString() ? '?' + queryParams.toString() : ''}`; return fetchAPI(url); } export async function getAdminPayments(params?: { status?: string; account_id?: number; payment_method?: string }): Promise<{ results: Payment[]; count: number; }> { const queryParams = new URLSearchParams(); if (params?.status) queryParams.append('status', params.status); if (params?.account_id) queryParams.append('account_id', String(params.account_id)); if (params?.payment_method) queryParams.append('payment_method', params.payment_method); const url = `/v1/admin/billing/payments/${queryParams.toString() ? '?' + queryParams.toString() : ''}`; return fetchAPI(url); } // Admin payment method configs (country-level) export interface PaymentMethodConfig { id: number; country_code: string; payment_method: PaymentMethod['type']; display_name: string; is_enabled: boolean; instructions?: string; sort_order?: number; } export async function getAdminPaymentMethodConfigs(params?: { country_code?: string; payment_method?: string }): Promise<{ results: PaymentMethodConfig[]; count: number; }> { const queryParams = new URLSearchParams(); if (params?.country_code) queryParams.append('country_code', params.country_code); if (params?.payment_method) queryParams.append('payment_method', params.payment_method); const url = `/v1/admin/billing/payment-method-configs/${queryParams.toString() ? '?' + queryParams.toString() : ''}`; return fetchAPI(url); } export async function createAdminPaymentMethodConfig(data: Partial): Promise { return fetchAPI('/v1/admin/billing/payment-method-configs/', { method: 'POST', body: JSON.stringify(data), }); } export async function updateAdminPaymentMethodConfig(id: number, data: Partial): Promise { return fetchAPI(`/v1/admin/billing/payment-method-configs/${id}/`, { method: 'PATCH', body: JSON.stringify(data), }); } export async function deleteAdminPaymentMethodConfig(id: number): Promise { await fetchAPI(`/v1/admin/billing/payment-method-configs/${id}/`, { method: 'DELETE', }); } // Admin account payment methods (cross-account) export interface AdminAccountPaymentMethod extends PaymentMethod { account: number; metadata?: Record; } export async function getAdminAccountPaymentMethods(params?: { account_id?: number }): Promise<{ results: AdminAccountPaymentMethod[]; count: number; }> { const queryParams = new URLSearchParams(); if (params?.account_id) queryParams.append('account_id', String(params.account_id)); const url = `/v1/admin/billing/account-payment-methods/${queryParams.toString() ? '?' + queryParams.toString() : ''}`; return fetchAPI(url); } export async function createAdminAccountPaymentMethod(data: Partial): Promise { return fetchAPI('/v1/admin/billing/account-payment-methods/', { method: 'POST', body: JSON.stringify(data), }); } export async function updateAdminAccountPaymentMethod(id: number | string, data: Partial): Promise { return fetchAPI(`/v1/admin/billing/account-payment-methods/${id}/`, { method: 'PATCH', body: JSON.stringify(data), }); } export async function deleteAdminAccountPaymentMethod(id: number | string): Promise { await fetchAPI(`/v1/admin/billing/account-payment-methods/${id}/`, { method: 'DELETE', }); } export async function setAdminDefaultAccountPaymentMethod(id: number | string): Promise<{ message: string; id: number | string }> { return fetchAPI(`/v1/admin/billing/account-payment-methods/${id}/set_default/`, { method: 'POST', }); } export async function getAdminUsers(params?: { search?: string; role?: string; is_active?: boolean; }): Promise<{ results: AdminUser[]; count: number; }> { const queryParams = new URLSearchParams(); if (params?.search) queryParams.append('search', params.search); if (params?.role) queryParams.append('role', params.role); if (params?.is_active !== undefined) queryParams.append('is_active', String(params.is_active)); const url = `/v1/admin/users/${queryParams.toString() ? '?' + queryParams.toString() : ''}`; return fetchAPI(url); } export async function adjustUserCredits( userId: number, data: { amount: number; reason: string; } ): Promise<{ message: string; new_balance: number; }> { return fetchAPI(`/v1/admin/users/${userId}/adjust-credits/`, { method: 'POST', body: JSON.stringify(data), }); } // ============================================================================ // ADMIN - CREDIT COSTS CONFIGURATION // ============================================================================ export async function getCreditCosts(): Promise<{ results: CreditCostConfig[]; count: number; }> { // credit costs are served from the admin namespace (modules billing) return fetchAPI('/v1/admin/credit-costs/'); } export async function updateCreditCosts( costs: Array<{ operation_type: string; credits_cost: number; }> ): Promise<{ message: string; updated_count: number; }> { return fetchAPI('/v1/admin/credit-costs/', { method: 'POST', body: JSON.stringify({ costs }), }); } // ============================================================================ // INVOICES // ============================================================================ export async function getInvoices(params?: { status?: string; }): Promise<{ results: Invoice[]; count: number; }> { const queryParams = new URLSearchParams(); if (params?.status) queryParams.append('status', params.status); const url = `/v1/billing/invoices/${queryParams.toString() ? '?' + queryParams.toString() : ''}`; return fetchAPI(url); } export async function getInvoiceDetail(invoiceId: number): Promise { return fetchAPI(`/v1/billing/invoices/${invoiceId}/`); } export async function downloadInvoicePDF(invoiceId: number): Promise { const response = await fetch(`/v1/billing/invoices/${invoiceId}/download_pdf/`, { headers: { 'Authorization': `Bearer ${localStorage.getItem('access_token')}`, }, }); if (!response.ok) { throw new Error('Failed to download invoice PDF'); } return response.blob(); } // ============================================================================ // PAYMENTS // ============================================================================ export async function getPayments(params?: { status?: string; invoice_id?: number; }): Promise<{ results: Payment[]; count: number; }> { const queryParams = new URLSearchParams(); if (params?.status) queryParams.append('status', params.status); if (params?.invoice_id) queryParams.append('invoice_id', String(params.invoice_id)); const url = `/v1/billing/payments/${queryParams.toString() ? '?' + queryParams.toString() : ''}`; return fetchAPI(url); } export async function submitManualPayment(data: { invoice_id?: number; payment_method: 'bank_transfer' | 'local_wallet' | 'manual'; amount: string; currency?: string; reference?: string; notes?: string; proof_file?: File; }): Promise<{ id: number; status: string; message: string; }> { return fetchAPI('/v1/billing/payments/manual/', { method: 'POST', body: JSON.stringify(data), }); } // ============================================================================ // CREDIT PACKAGES // ============================================================================ export async function getCreditPackages(): Promise<{ results: CreditPackage[]; count: number; }> { return fetchAPI('/v1/billing/credit-packages/'); } // Admin credit packages (CRUD capable backend; currently read-only here) export async function getAdminCreditPackages(): Promise<{ results: CreditPackage[]; count: number; }> { // Backend does not expose a dedicated admin credit-packages list; fall back to the shared packages list return fetchAPI('/v1/billing/credit-packages/'); } export async function createAdminCreditPackage(data: { name: string; slug?: string; credits: number; price: string | number; discount_percentage?: number; description?: string; is_active?: boolean; is_featured?: boolean; sort_order?: number; }): Promise<{ package: CreditPackage; message?: string }> { return fetchAPI('/v1/billing/credit-packages/', { method: 'POST', body: JSON.stringify(data), }); } export async function updateAdminCreditPackage( id: number, data: Partial<{ name: string; slug: string; credits: number; price: string | number; discount_percentage: number; description: string; is_active: boolean; is_featured: boolean; sort_order: number; }> ): Promise<{ package: CreditPackage; message?: string }> { return fetchAPI(`/v1/billing/credit-packages/${id}/`, { method: 'PATCH', body: JSON.stringify(data), }); } export async function deleteAdminCreditPackage(id: number): Promise<{ message?: string }> { return fetchAPI(`/v1/billing/credit-packages/${id}/`, { method: 'DELETE', }); } export async function purchaseCreditPackage(data: { package_id: number; payment_method: 'stripe' | 'paypal' | 'bank_transfer' | 'local_wallet'; }): Promise<{ invoice_id?: number; invoice_number?: string; total_amount?: string; status?: string; message?: string; next_action?: string; stripe_client_secret?: string; paypal_order_id?: string; }> { return fetchAPI(`/v1/billing/credit-packages/${data.package_id}/purchase/`, { method: 'POST', body: JSON.stringify({ payment_method: data.payment_method }), }); } // ============================================================================ // TEAM MANAGEMENT // ============================================================================ export async function getTeamMembers(): Promise<{ results: TeamMember[]; count: number; }> { return fetchAPI('/v1/account/team/'); } export async function inviteTeamMember(data: { email: string; first_name?: string; last_name?: string; role?: string; }): Promise<{ message: string; member?: TeamMember; }> { return fetchAPI('/v1/account/team/invite/', { method: 'POST', body: JSON.stringify(data), }); } export async function removeTeamMember(memberId: number): Promise<{ message: string; }> { return fetchAPI(`/v1/account/team/${memberId}/`, { method: 'DELETE', }); } // ============================================================================ // PAYMENT METHODS // ============================================================================ // Account payment methods (CRUD) export async function getAvailablePaymentMethods(): Promise<{ results: PaymentMethod[]; count: number; }> { const response = await fetchAPI('/v1/billing/payment-methods/'); // Frontend guard: only allow the simplified set we currently support const allowed = new Set(['bank_transfer', 'manual']); const results = Array.isArray(response.results) ? response.results : []; const filtered = results.filter((m: PaymentMethod) => allowed.has(m.type)); return { results: filtered, count: filtered.length }; } export async function createPaymentMethod(data: { type: 'stripe' | 'paypal' | 'bank_transfer' | 'local_wallet' | 'manual'; display_name: string; instructions?: string; metadata?: Record; country_code?: string; is_default?: boolean; }): Promise<{ payment_method: PaymentMethod; message?: string }> { return fetchAPI('/v1/billing/payment-methods/', { method: 'POST', body: JSON.stringify(data), }); } export async function deletePaymentMethod(id: string): Promise<{ message?: string }> { return fetchAPI(`/v1/billing/payment-methods/${id}/`, { method: 'DELETE', }); } export async function updatePaymentMethod(id: string, data: Partial): Promise<{ payment_method?: PaymentMethod; message?: string }> { return fetchAPI(`/v1/billing/payment-methods/${id}/`, { method: 'PATCH', body: JSON.stringify(data), }); } export async function setDefaultPaymentMethod(id: string): Promise<{ payment_method?: PaymentMethod; message?: string }> { return fetchAPI(`/v1/billing/payment-methods/${id}/set_default/`, { method: 'POST', }); } // Country/config-driven available methods (legacy) export async function getPaymentMethodConfigs(): Promise<{ results: PaymentMethod[]; count: number; }> { return fetchAPI('/v1/billing/payment-methods/available/'); } // Get payment methods for a specific country export async function getPaymentMethodsByCountry(countryCode?: string): Promise<{ success: boolean; results: PaymentMethodConfig[]; count: number; }> { const params = countryCode ? `?country=${countryCode}` : ''; return fetchAPI(`/v1/billing/admin/payment-methods/${params}`); } // Confirm manual payment (submit proof of payment) export async function confirmPayment(data: { invoice_id: number; payment_method: string; amount: string; manual_reference: string; manual_notes?: string; proof_url?: string; }): Promise<{ success: boolean; message: string; payment: Payment; }> { return fetchAPI('/v1/billing/admin/payments/confirm/', { method: 'POST', body: JSON.stringify(data), }); } export async function createManualPayment(data: { invoice_id?: number; amount: string; payment_method: string; reference: string; notes?: string; }): Promise<{ id: number; status: string; message: string; }> { return fetchAPI('/v1/billing/payments/manual/', { method: 'POST', body: JSON.stringify(data), }); } // ============================================================================ // ADMIN - PAYMENT APPROVALS // ============================================================================ export async function getPendingPayments(): Promise<{ results: PendingPayment[]; count: number; }> { return fetchAPI('/v1/admin/billing/pending_payments/'); } export async function approvePayment(paymentId: number, data?: { notes?: string; }): Promise<{ message: string; payment: Payment; }> { return fetchAPI(`/v1/admin/billing/${paymentId}/approve_payment/`, { method: 'POST', body: JSON.stringify(data || {}), }); } export async function rejectPayment(paymentId: number, data: { reason: string; notes?: string; }): Promise<{ message: string; payment: Payment; }> { return fetchAPI(`/v1/admin/billing/${paymentId}/reject_payment/`, { method: 'POST', body: JSON.stringify(data), }); } // ============================================================================ // ACCOUNT MANAGEMENT // ============================================================================ export interface AccountSettings { id: number; name: string; slug: string; billing_address_line1?: string; billing_address_line2?: string; billing_city?: string; billing_state?: string; billing_postal_code?: string; billing_country?: string; tax_id?: string; billing_email?: string; credit_balance: number; created_at: string; updated_at: string; } export async function getAccountSettings(): Promise { return fetchAPI('/v1/account/settings/'); } export async function updateAccountSettings(data: Partial): Promise<{ message: string; account: AccountSettings; }> { return fetchAPI('/v1/account/settings/', { method: 'PATCH', body: JSON.stringify(data), }); } export interface UsageAnalytics { period_days: number; start_date: string; end_date: string; current_balance: number; usage_by_type: Array<{ transaction_type: string; total: number; count: number; }>; purchases_by_type: Array<{ transaction_type: string; total: number; count: number; }>; daily_usage: Array<{ date: string; usage: number; purchases: number; net: number; }>; total_usage: number; total_purchases: number; } export async function getUsageAnalytics(days: number = 30): Promise { return fetchAPI(`/v1/account/usage/analytics/?days=${days}`); } // ============================================================================ // PLANS & SUBSCRIPTIONS (TENANT) // ============================================================================ export interface Plan { id: number; name: string; slug?: string; price?: number | string; currency?: string; interval?: 'month' | 'year'; description?: string; is_active?: boolean; is_featured?: boolean; annual_discount_percent?: number; features?: string[]; limits?: Record; display_order?: number; // Hard Limits max_sites?: number; max_users?: number; max_keywords?: number; max_clusters?: number; // Monthly Limits max_content_ideas?: number; max_content_words?: number; max_images_basic?: number; max_images_premium?: number; max_image_prompts?: number; included_credits?: number; } export interface LimitUsage { display_name: string; current: number; limit: number; remaining: number; percentage_used: number; } export interface UsageSummary { account_id: number; account_name: string; plan_name: string; period_start: string; period_end: string; days_until_reset: number; hard_limits: { sites?: LimitUsage; users?: LimitUsage; keywords?: LimitUsage; clusters?: LimitUsage; }; monthly_limits: { content_ideas?: LimitUsage; content_words?: LimitUsage; images_basic?: LimitUsage; images_premium?: LimitUsage; image_prompts?: LimitUsage; }; } export interface Subscription { id: number; plan: Plan | number; status: string; current_period_start?: string; current_period_end?: string; cancel_at_period_end?: boolean; created_at?: string; } export async function getPlans(): Promise<{ results: Plan[] }> { // Coalesce concurrent plan fetches to avoid 429s on first load if (!(getPlans as any)._inFlight) { (getPlans as any)._inFlight = fetchAPI('/v1/auth/plans/').finally(() => { (getPlans as any)._inFlight = null; }); } return (getPlans as any)._inFlight; } export async function getSubscriptions(): Promise<{ results: Subscription[] }> { const now = Date.now(); const self: any = getSubscriptions as any; // Return cached result if fetched within 5s to avoid hitting throttle if (self._lastResult && self._lastFetched && now - self._lastFetched < 5000) { return self._lastResult; } // Respect cooldown if previous call was throttled if (self._cooldownUntil && now < self._cooldownUntil) { if (self._lastResult) return self._lastResult; return { results: [] }; } // Coalesce concurrent subscription fetches if (!self._inFlight) { self._inFlight = fetchAPI('/v1/auth/subscriptions/') .then((res) => { self._lastResult = res; self._lastFetched = Date.now(); return res; }) .catch((err) => { if (err?.status === 429) { // Set a short cooldown to prevent immediate re-hits self._cooldownUntil = Date.now() + 5000; // Return cached or empty to avoid surfacing the 429 upstream return self._lastResult || { results: [] }; } throw err; }) .finally(() => { self._inFlight = null; }); } return self._inFlight; } export async function createSubscription(data: { plan_id: number; payment_method?: string; }): Promise<{ message?: string; subscription?: Subscription }> { return fetchAPI('/v1/auth/subscriptions/', { method: 'POST', body: JSON.stringify(data), }); } export async function cancelSubscription(subscriptionId: number): Promise<{ message?: string }> { return fetchAPI(`/v1/auth/subscriptions/${subscriptionId}/cancel/`, { method: 'POST', }); } // ============================================================================ // USAGE SUMMARY (PLAN LIMITS) // ============================================================================ export async function getUsageSummary(): Promise { return fetchAPI('/v1/billing/usage-summary/'); }