/** * Admin Payments Page * Tabs: All Payments, Pending Approvals (approve/reject), Payment Methods (country-level configs + per-account methods) */ import { useEffect, useState } from 'react'; import { Filter, Loader2, AlertCircle, Check, X, RefreshCw, Plus, Trash, Star } from 'lucide-react'; import { Card } from '../../components/ui/card'; import Badge from '../../components/ui/badge/Badge'; import { getAdminPayments, getPendingPayments, approvePayment, rejectPayment, getAdminPaymentMethodConfigs, createAdminPaymentMethodConfig, updateAdminPaymentMethodConfig, deleteAdminPaymentMethodConfig, getAdminAccountPaymentMethods, createAdminAccountPaymentMethod, updateAdminAccountPaymentMethod, deleteAdminAccountPaymentMethod, setAdminDefaultAccountPaymentMethod, getAdminUsers, type Payment, type PaymentMethod, type PaymentMethodConfig, type AdminAccountPaymentMethod, type AdminUser, } from '../../services/billing.api'; type AdminPayment = Payment & { account_name?: string }; type TabType = 'all' | 'pending' | 'methods'; export default function AdminAllPaymentsPage() { const [payments, setPayments] = useState([]); const [pendingPayments, setPendingPayments] = useState([]); const [paymentConfigs, setPaymentConfigs] = useState([]); const [accounts, setAccounts] = useState([]); const [accountPaymentMethods, setAccountPaymentMethods] = useState([]); const [accountIdFilter, setAccountIdFilter] = useState(''); const [selectedConfigIdForAccount, setSelectedConfigIdForAccount] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(''); const [statusFilter, setStatusFilter] = useState('all'); const [activeTab, setActiveTab] = useState('all'); const [actionLoadingId, setActionLoadingId] = useState(null); const [rejectNotes, setRejectNotes] = useState>({}); const [newConfig, setNewConfig] = useState<{ country_code: string; payment_method: PaymentMethod['type']; display_name: string; instructions?: string; sort_order?: number; is_enabled?: boolean; }>({ country_code: '*', payment_method: 'bank_transfer', display_name: '', instructions: '', sort_order: 0, is_enabled: true, }); const [editingConfigId, setEditingConfigId] = useState(null); useEffect(() => { loadAll(); }, []); const loadAll = async () => { try { setLoading(true); const [allData, pendingData, configsData, usersData] = await Promise.all([ getAdminPayments(), getPendingPayments(), getAdminPaymentMethodConfigs(), getAdminUsers(), ]); setPayments(allData.results || []); setPendingPayments(pendingData.results || []); setPaymentConfigs(configsData.results || []); setAccounts(usersData.results || []); } catch (err: any) { setError(err.message || 'Failed to load payments'); } finally { setLoading(false); } }; const filteredPayments = payments.filter((payment) => statusFilter === 'all' || payment.status === statusFilter); const getStatusColor = (status: string) => { switch (status) { case 'succeeded': case 'completed': return 'success'; case 'processing': case 'pending': case 'pending_approval': return 'warning'; case 'refunded': return 'info'; default: return 'error'; } }; const handleApprove = async (id: number) => { try { setActionLoadingId(id); await approvePayment(id); await loadAll(); } catch (err: any) { setError(err.message || 'Failed to approve payment'); } finally { setActionLoadingId(null); } }; const handleReject = async (id: number) => { try { setActionLoadingId(id); await rejectPayment(id, { notes: rejectNotes[id] || '' }); await loadAll(); } catch (err: any) { setError(err.message || 'Failed to reject payment'); } finally { setActionLoadingId(null); } }; // Payment method configs (country-level) const handleSaveConfig = async () => { if (!newConfig.display_name.trim()) { setError('Payment method display name is required'); return; } if (!newConfig.payment_method) { setError('Payment method type is required'); return; } try { setActionLoadingId(-1); if (editingConfigId) { await updateAdminPaymentMethodConfig(editingConfigId, { country_code: newConfig.country_code || '*', payment_method: newConfig.payment_method, display_name: newConfig.display_name, instructions: newConfig.instructions, sort_order: newConfig.sort_order, is_enabled: newConfig.is_enabled ?? true, }); } else { await createAdminPaymentMethodConfig({ country_code: newConfig.country_code || '*', payment_method: newConfig.payment_method, display_name: newConfig.display_name, instructions: newConfig.instructions, sort_order: newConfig.sort_order, is_enabled: newConfig.is_enabled ?? true, }); } setNewConfig({ country_code: '*', payment_method: 'bank_transfer', display_name: '', instructions: '', sort_order: 0, is_enabled: true, }); setEditingConfigId(null); const cfgs = await getAdminPaymentMethodConfigs(); setPaymentConfigs(cfgs.results || []); } catch (err: any) { setError(err.message || 'Failed to add payment method config'); } finally { setActionLoadingId(null); } }; const handleToggleConfigEnabled = async (cfg: PaymentMethodConfig) => { try { setActionLoadingId(cfg.id); await updateAdminPaymentMethodConfig(cfg.id, { is_enabled: !cfg.is_enabled }); const cfgs = await getAdminPaymentMethodConfigs(); setPaymentConfigs(cfgs.results || []); } catch (err: any) { setError(err.message || 'Failed to update payment method config'); } finally { setActionLoadingId(null); } }; const handleDeleteConfig = async (id: number) => { try { setActionLoadingId(id); await deleteAdminPaymentMethodConfig(id); const cfgs = await getAdminPaymentMethodConfigs(); setPaymentConfigs(cfgs.results || []); } catch (err: any) { setError(err.message || 'Failed to delete payment method config'); } finally { setActionLoadingId(null); } }; const handleEditConfig = (cfg: PaymentMethodConfig) => { setEditingConfigId(cfg.id); setNewConfig({ country_code: cfg.country_code, payment_method: cfg.payment_method, display_name: cfg.display_name, instructions: cfg.instructions, sort_order: cfg.sort_order, is_enabled: cfg.is_enabled, }); }; const handleCancelConfigEdit = () => { setEditingConfigId(null); setNewConfig({ country_code: '*', payment_method: 'bank_transfer', display_name: '', instructions: '', sort_order: 0, is_enabled: true, }); }; // Account payment methods const handleLoadAccountMethods = async () => { const accountId = accountIdFilter.trim(); if (!accountId) { setAccountPaymentMethods([]); return; } try { setActionLoadingId(-3); const data = await getAdminAccountPaymentMethods({ account_id: Number(accountId) }); setAccountPaymentMethods(data.results || []); } catch (err: any) { setError(err.message || 'Failed to load account payment methods'); } finally { setActionLoadingId(null); } }; // Associate an existing country-level config to the account (one per account) const handleAssociateConfigToAccount = async () => { const accountId = accountIdFilter.trim(); if (!accountId) { setError('Select an account first'); return; } if (!selectedConfigIdForAccount) { setError('Select a payment method config to assign'); return; } const cfg = paymentConfigs.find((c) => c.id === selectedConfigIdForAccount); if (!cfg) { setError('Selected config not found'); return; } try { setActionLoadingId(-2); // Create or replace with the chosen config; treat as association. const created = await createAdminAccountPaymentMethod({ account: Number(accountId), type: cfg.payment_method, display_name: cfg.display_name, instructions: cfg.instructions, is_enabled: cfg.is_enabled, is_default: true, }); // Remove extras if more than one exists for this account to enforce single association. const refreshed = await getAdminAccountPaymentMethods({ account_id: Number(accountId) }); const others = (refreshed.results || []).filter((m) => m.id !== created.id); for (const other of others) { await deleteAdminAccountPaymentMethod(other.id); } await handleLoadAccountMethods(); } catch (err: any) { setError(err.message || 'Failed to assign payment method to account'); } finally { setActionLoadingId(null); } }; const handleDeleteAccountMethod = async (id: number | string) => { try { setActionLoadingId(Number(id)); await deleteAdminAccountPaymentMethod(id); await handleLoadAccountMethods(); } catch (err: any) { setError(err.message || 'Failed to delete account payment method'); } finally { setActionLoadingId(null); } }; const handleSetDefaultAccountMethod = async (id: number | string) => { try { setActionLoadingId(Number(id)); await setAdminDefaultAccountPaymentMethod(id); await handleLoadAccountMethods(); } catch (err: any) { setError(err.message || 'Failed to set default account payment method'); } finally { setActionLoadingId(null); } }; if (loading) { return (
); } const renderPaymentsTable = (rows: AdminPayment[]) => (
{rows.length === 0 ? ( ) : ( rows.map((payment) => ( )) )}
Account Invoice Amount Method Status Date Actions
No payments found
{payment.account_name} {payment.invoice_number || payment.invoice_id || '—'} {payment.currency} {payment.amount} {payment.payment_method.replace('_', ' ')} {payment.status} {new Date(payment.created_at).toLocaleDateString()}
); const renderPendingTable = () => (
{pendingPayments.length === 0 ? ( ) : ( pendingPayments.map((payment) => ( )) )}
Account Invoice Amount Method Reference Actions
No pending payments
{payment.account_name} {payment.invoice_number || payment.invoice_id || '—'} {payment.currency} {payment.amount} {payment.payment_method.replace('_', ' ')} {payment.transaction_reference || '—'}
setRejectNotes({ ...rejectNotes, [payment.id as number]: e.target.value })} />
); return (

Payments

Admin-only billing management

{error && (

{error}

)}
{activeTab === 'all' && (
)}
{activeTab === 'all' && renderPaymentsTable(filteredPayments)} {activeTab === 'pending' && renderPendingTable()} {activeTab === 'methods' && (
{/* Payment Method Configs (country-level) */}

Payment Method Configs (country-level)

setNewConfig({ ...newConfig, country_code: e.target.value })} /> setNewConfig({ ...newConfig, display_name: e.target.value })} /> setNewConfig({ ...newConfig, instructions: e.target.value })} /> setNewConfig({ ...newConfig, sort_order: Number(e.target.value) })} /> {editingConfigId && ( )}
{paymentConfigs.length === 0 ? ( ) : ( paymentConfigs.map((cfg) => ( )) )}
Country Name Type Enabled Instructions Actions
No payment method configs
{cfg.country_code} {cfg.display_name} {cfg.payment_method.replace('_', ' ')} {cfg.instructions || '—'}
{/* Account Payment Methods (associate existing configs only) */}

Account Payment Methods (association)

Only one payment method per account; assigning replaces existing.

{accountPaymentMethods.length === 0 ? ( ) : ( accountPaymentMethods.map((m) => ( )) )}
Account Name Type Enabled Default Instructions Actions
No account payment methods
{m.account} {m.display_name} {m.type.replace('_', ' ')} {m.is_enabled ? 'Yes' : 'No'} {m.is_default ? : '—'} {m.instructions || '—'} {!m.is_default && ( )}
)}
); }