/** * PaymentProvider — module-local state for the customer Payments portal. * * Invoices and transactions are sourced from the shared paymentStore (localStorage) * so the customer sees payment requests created on the owner side, and payments * made here flow back to the owner's Payment Management page. The server-side * (webhook-written) transaction history is merged in on top when available. */ import React, { createContext, useContext, useState, useEffect, useCallback } from 'react'; import { stripeConfig } from '../config/stripeConfig'; import { loadInvoices, loadTransactions, saveTransactions, subscribe, } from '../data/paymentStore'; const mergeById = (a, b) => { const map = new Map(); [...a, ...b].forEach((t) => { if (t && t.id) map.set(t.id, { ...map.get(t.id), ...t }); }); return Array.from(map.values()).sort( (x, y) => new Date(y.createdAt || 0) - new Date(x.createdAt || 0) ); }; const PaymentContext = createContext(null); export const PaymentProvider = ({ children }) => { const [invoices, setInvoices] = useState(loadInvoices); const [transactions, setTransactions] = useState(loadTransactions); // Re-read from the shared store whenever the owner adds an invoice, marks // one paid, or a payment is recorded (same-tab and cross-tab). useEffect( () => subscribe(() => { setInvoices(loadInvoices()); setTransactions((prev) => mergeById(prev, loadTransactions())); }), [] ); // Pull the server-side (webhook-written) history and merge it in. useEffect(() => { let cancelled = false; (async () => { try { const res = await fetch(`${stripeConfig.apiBase}/transactions`); if (!res.ok) return; const { transactions: server = [] } = await res.json(); if (cancelled || !server.length) return; setTransactions((prev) => mergeById(prev, server)); } catch { /* offline / endpoint unavailable — local history is fine. */ } })(); return () => { cancelled = true; }; }, []); /** Record a transaction confirmed on the success return page. */ const recordTransaction = useCallback((txn) => { if (!txn?.id) return; setTransactions((prev) => { const next = mergeById(prev, [txn]); saveTransactions(next); // persists to the shared store + notifies the owner side return next; }); }, []); const value = { invoices, transactions, recordTransaction }; return {children}; }; export const usePayments = () => { const ctx = useContext(PaymentContext); if (!ctx) throw new Error('usePayments must be used within a .'); return ctx; };