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 { CreditCard, Plus, Loader2, AlertCircle } from 'lucide-react'; import { toast } from 'sonner'; import api from '../utils/api'; import PaymentMethodCard from './PaymentMethodCard'; import AddPaymentMethodDialog from './AddPaymentMethodDialog'; import ConfirmationDialog from './ConfirmationDialog'; // Initialize Stripe with publishable key from environment const stripePromise = loadStripe(process.env.REACT_APP_STRIPE_PUBLISHABLE_KEY); /** * PaymentMethodsSection - Manages user payment methods * * Features: * - List saved payment methods * - Add new payment method via Stripe SetupIntent * - Set default payment method * - Delete payment methods */ const PaymentMethodsSection = () => { const [paymentMethods, setPaymentMethods] = useState([]); const [loading, setLoading] = useState(true); const [actionLoading, setActionLoading] = useState(false); const [error, setError] = useState(null); // Dialog states const [addDialogOpen, setAddDialogOpen] = useState(false); const [clientSecret, setClientSecret] = useState(null); const [deleteConfirmOpen, setDeleteConfirmOpen] = useState(false); const [methodToDelete, setMethodToDelete] = useState(null); /** * Fetch payment methods from API */ const fetchPaymentMethods = useCallback(async () => { try { setLoading(true); setError(null); const response = await api.get('/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); } }, []); useEffect(() => { fetchPaymentMethods(); }, [fetchPaymentMethods]); /** * Create SetupIntent and open add dialog */ const handleAddNew = async () => { try { setActionLoading(true); const response = await api.post('/payment-methods/setup-intent'); setClientSecret(response.data.client_secret); setAddDialogOpen(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 payment method addition */ const handleAddSuccess = () => { setAddDialogOpen(false); setClientSecret(null); fetchPaymentMethods(); }; /** * Set a payment method as default */ const handleSetDefault = async (methodId) => { try { setActionLoading(true); await api.put(`/payment-methods/${methodId}/default`); toast.success('Default payment method updated'); fetchPaymentMethods(); } catch (err) { const errorMessage = err.response?.data?.detail || 'Failed to update default payment method'; toast.error(errorMessage); console.error('Failed to set default:', err); } finally { setActionLoading(false); } }; /** * Open delete confirmation dialog */ const handleDeleteClick = (methodId) => { setMethodToDelete(methodId); setDeleteConfirmOpen(true); }; /** * Confirm and delete payment method */ const handleDeleteConfirm = async () => { if (!methodToDelete) return; try { setActionLoading(true); await api.delete(`/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); console.error('Failed to delete payment method:', err); } finally { setActionLoading(false); } }; // Stripe Elements options - simplified for CardElement const elementsOptions = { appearance: { theme: 'stripe', variables: { colorPrimary: '#6b5b95', colorBackground: '#ffffff', colorText: '#2d2a4a', colorDanger: '#ef4444', fontFamily: "'Nunito Sans', sans-serif", borderRadius: '12px', }, }, }; return ( <> {/* Header */} Payment Methods {actionLoading ? ( ) : ( <> Add > )} {/* Loading State */} {loading && ( Loading payment methods... )} {/* Error State */} {error && !loading && ( {error} Retry )} {/* Payment Methods List */} {!loading && !error && ( {paymentMethods.length === 0 ? ( No payment methods saved Add a card to make payments easier {actionLoading ? ( <> Setting up... > ) : ( <> Add Payment Method > )} ) : ( paymentMethods.map((method) => ( )) )} )} {/* Info Text */} {!loading && paymentMethods.length > 0 && ( Your default payment method will be used for subscription renewals and donations. )} {/* Add Payment Method Dialog */} {clientSecret && stripePromise && ( { setAddDialogOpen(open); if (!open) setClientSecret(null); }} onSuccess={handleAddSuccess} clientSecret={clientSecret} /> )} {/* Delete Confirmation Dialog */} > ); }; export default PaymentMethodsSection;
{error}
No payment methods saved
Add a card to make payments easier
Your default payment method will be used for subscription renewals and donations.