import React, { useState, useEffect, useCallback } from 'react'; import { loadStripe } from '@stripe/stripe-js'; import { Elements } from '@stripe/react-stripe-js'; import { Card } from '../ui/card'; import { Button } from '../ui/button'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '../ui/select'; import { Textarea } from '../ui/textarea'; import { Label } from '../ui/label'; import { CreditCard, Plus, Loader2, AlertCircle, Eye, Banknote, Building2, FileCheck, Trash2, Star, } from 'lucide-react'; import { toast } from 'sonner'; import api from '../../utils/api'; import ConfirmationDialog from '../ConfirmationDialog'; import PasswordConfirmDialog from '../PasswordConfirmDialog'; import AddPaymentMethodDialog from '../AddPaymentMethodDialog'; // Initialize Stripe with publishable key from environment const stripePromise = loadStripe(process.env.REACT_APP_STRIPE_PUBLISHABLE_KEY); /** * Get icon for payment method type */ const getPaymentTypeIcon = (paymentType) => { switch (paymentType) { case 'cash': return Banknote; case 'bank_transfer': return Building2; case 'check': return FileCheck; default: return CreditCard; } }; /** * Format payment type for display */ const formatPaymentType = (paymentType) => { switch (paymentType) { case 'cash': return 'Cash'; case 'bank_transfer': return 'Bank Transfer'; case 'check': return 'Check'; case 'card': return 'Card'; default: return paymentType; } }; /** * AdminPaymentMethodsPanel - Admin panel for managing user payment methods */ const AdminPaymentMethodsPanel = ({ userId, userName }) => { const [paymentMethods, setPaymentMethods] = useState([]); const [loading, setLoading] = useState(true); const [actionLoading, setActionLoading] = useState(false); const [error, setError] = useState(null); // Dialog states const [addCardDialogOpen, setAddCardDialogOpen] = useState(false); const [addManualDialogOpen, setAddManualDialogOpen] = useState(false); const [clientSecret, setClientSecret] = useState(null); const [deleteConfirmOpen, setDeleteConfirmOpen] = useState(false); const [methodToDelete, setMethodToDelete] = useState(null); const [revealDialogOpen, setRevealDialogOpen] = useState(false); const [revealedData, setRevealedData] = useState(null); // Manual payment form state const [manualPaymentType, setManualPaymentType] = useState('cash'); const [manualNotes, setManualNotes] = useState(''); const [manualSetDefault, setManualSetDefault] = useState(false); /** * Fetch payment methods from API */ const fetchPaymentMethods = useCallback(async () => { try { setLoading(true); setError(null); const response = await api.get(`/admin/users/${userId}/payment-methods`); setPaymentMethods(response.data); } catch (err) { const errorMessage = err.response?.data?.detail || 'Failed to load payment methods'; setError(errorMessage); console.error('Failed to fetch payment methods:', err); } finally { setLoading(false); } }, [userId]); useEffect(() => { if (userId) { fetchPaymentMethods(); } }, [userId, fetchPaymentMethods]); /** * Create SetupIntent for adding a card */ const handleAddCard = async () => { try { setActionLoading(true); const response = await api.post(`/admin/users/${userId}/payment-methods/setup-intent`); setClientSecret(response.data.client_secret); setAddCardDialogOpen(true); } catch (err) { const errorMessage = err.response?.data?.detail || 'Failed to initialize payment setup'; toast.error(errorMessage); console.error('Failed to create setup intent:', err); } finally { setActionLoading(false); } }; /** * Handle successful card addition */ const handleCardAddSuccess = () => { setAddCardDialogOpen(false); setClientSecret(null); fetchPaymentMethods(); }; /** * Save manual payment method */ const handleSaveManualPayment = async () => { try { setActionLoading(true); await api.post(`/admin/users/${userId}/payment-methods/manual`, { payment_type: manualPaymentType, manual_notes: manualNotes || null, set_as_default: manualSetDefault, }); toast.success('Manual payment method recorded'); setAddManualDialogOpen(false); setManualPaymentType('cash'); setManualNotes(''); setManualSetDefault(false); fetchPaymentMethods(); } catch (err) { const errorMessage = err.response?.data?.detail || 'Failed to record payment method'; toast.error(errorMessage); console.error('Failed to save manual payment:', err); } finally { setActionLoading(false); } }; /** * Set a payment method as default */ const handleSetDefault = async (methodId) => { try { setActionLoading(true); await api.put(`/admin/users/${userId}/payment-methods/${methodId}/default`); toast.success('Default payment method updated'); fetchPaymentMethods(); } catch (err) { const errorMessage = err.response?.data?.detail || 'Failed to update default'; toast.error(errorMessage); } finally { setActionLoading(false); } }; /** * Confirm and delete payment method */ const handleDeleteConfirm = async () => { if (!methodToDelete) return; try { setActionLoading(true); await api.delete(`/admin/users/${userId}/payment-methods/${methodToDelete}`); toast.success('Payment method removed'); setDeleteConfirmOpen(false); setMethodToDelete(null); fetchPaymentMethods(); } catch (err) { const errorMessage = err.response?.data?.detail || 'Failed to remove payment method'; toast.error(errorMessage); } finally { setActionLoading(false); } }; /** * Reveal sensitive payment details with password confirmation */ const handleRevealDetails = async (password) => { try { setActionLoading(true); const response = await api.post(`/admin/users/${userId}/payment-methods/reveal`, { password, }); setRevealedData(response.data); setRevealDialogOpen(false); toast.success('Sensitive details revealed'); } catch (err) { const errorMessage = err.response?.data?.detail || 'Failed to reveal details'; throw new Error(errorMessage); } finally { setActionLoading(false); } }; // Stripe Elements options - simplified for CardElement const elementsOptions = { appearance: { theme: 'stripe', variables: { colorPrimary: '#6b5b95', colorBackground: '#ffffff', colorText: '#2d2a4a', fontFamily: "'Nunito Sans', sans-serif", borderRadius: '12px', }, }, }; return ( <> {/* Header */}

Payment Methods

{/* Loading State */} {loading && (
)} {/* Error State */} {error && !loading && (

{error}

)} {/* Payment Methods List */} {!loading && !error && (
{paymentMethods.length === 0 ? (

No payment methods on file for this user.

) : ( (revealedData || paymentMethods).map((method) => { const PaymentIcon = getPaymentTypeIcon(method.payment_type); return (
{method.payment_type === 'card' ? ( <>
{method.card_brand ? method.card_brand.charAt(0).toUpperCase() + method.card_brand.slice(1) : 'Card'}{' '} •••• {method.card_last4 || '****'} {method.is_default && ( Default )}

Expires {method.card_exp_month?.toString().padStart(2, '0')}/ {method.card_exp_year?.toString().slice(-2)} {revealedData && method.stripe_payment_method_id && ( {method.stripe_payment_method_id} )}

) : ( <>
{formatPaymentType(method.payment_type)} {method.is_default && ( Default )}
{method.manual_notes && (

{method.manual_notes}

)} )}
{/* Actions */}
{!method.is_default && ( )}
); }) )}
)}
{/* Add Card Dialog */} {clientSecret && stripePromise && ( { setAddCardDialogOpen(open); if (!open) setClientSecret(null); }} onSuccess={handleCardAddSuccess} clientSecret={clientSecret} saveEndpoint={`/admin/users/${userId}/payment-methods`} /> )} {/* Add Manual Payment Method Dialog */}