85 lines
2.9 KiB
React
85 lines
2.9 KiB
React
/**
|
|
* 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 <PaymentContext.Provider value={value}>{children}</PaymentContext.Provider>;
|
|
};
|
|
|
|
export const usePayments = () => {
|
|
const ctx = useContext(PaymentContext);
|
|
if (!ctx) throw new Error('usePayments must be used within a <PaymentProvider>.');
|
|
return ctx;
|
|
};
|