Simplify self-service payments to full-amount checkout, add manual-page payment tab

Self-service "pay now" flows (registration success + user dashboard) now go
straight to a Yoco checkout for the full outstanding balance instead of
prompting for a partial amount; that page has been removed. Partial-amount
payment links remain supervisor/admin-only via the Payments dashboard.

Also adds a "Record Payment" tab to the supervisor manual registration page,
pre-filled with the most recently created registration, so staff can capture
a payment right after registering someone without leaving the page.
This commit is contained in:
2026-07-27 10:14:49 +02:00
parent c710da4dff
commit 36b61d968c
7 changed files with 321 additions and 216 deletions
+28 -4
View File
@@ -22,6 +22,7 @@ function RegistrationSuccessContent() {
const [loading, setLoading] = React.useState(false);
const [error, setError] = React.useState<string | null>(null);
const [info, setInfo] = React.useState<string | null>(null);
const [payLoading, setPayLoading] = React.useState(false);
React.useEffect(() => {
(async () => {
@@ -79,9 +80,32 @@ function RegistrationSuccessContent() {
}
}, [reg, totalDue, router]);
const goPay = () => {
const goPay = async () => {
if (!registrationId) return;
router.push(`/dashboard/user/pay?registrationId=${encodeURIComponent(registrationId)}`);
const token = (typeof window !== 'undefined') ? localStorage.getItem('token') : null;
if (!token) { setError('Please login to pay.'); return; }
try {
setError(null);
setPayLoading(true);
const { createFullPaymentCheckout } = await import('@/lib/api');
const res = await createFullPaymentCheckout(token, registrationId);
if (res.priceUpdated) {
// Early-bird price changed since registration — surface the new total and
// reload the registration so the displayed totalDue reflects it, instead of redirecting.
setError(`${res.message || 'Pricing has changed.'} New total: R ${(res.newTotal ?? 0).toFixed(2)}. Please try again.`);
try {
const r = await (await import('@/lib/api')).apiFetch(`/api/registrations/${encodeURIComponent(registrationId)}`, { authToken: token });
if (r) setReg(r);
} catch {}
return;
}
if (!res.redirectUrl) { setError('Failed to create checkout'); return; }
window.location.href = res.redirectUrl;
} catch (e: any) {
setError(e?.message || 'Failed to create checkout');
} finally {
setPayLoading(false);
}
};
const goDashboard = () => {
@@ -108,9 +132,9 @@ function RegistrationSuccessContent() {
{(totalDue > 0 || (!reg && fallbackTotalDue > 0)) && (
<button
onClick={goPay}
disabled={!registrationId}
disabled={!registrationId || payLoading}
className="px-4 py-2 rounded bg-green-600 text-white disabled:opacity-60"
>Pay with Yoco</button>
>{payLoading ? "Creating checkout..." : "Pay with Yoco"}</button>
)}
<button
onClick={goDashboard}