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:
@@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
import React, { useEffect, useMemo, useState } from "react";
|
||||
import { useAuth } from "@/hooks/useAuth";
|
||||
import { apiFetch } from "@/lib/api";
|
||||
import { apiFetch, createFullPaymentCheckout } from "@/lib/api";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { formatDate } from "@/lib/date";
|
||||
import { formatPaymentMethod } from "@/lib/paymentMethod";
|
||||
@@ -312,6 +312,41 @@ export default function UserDashboardPage() {
|
||||
}
|
||||
};
|
||||
|
||||
// Creates a Yoco checkout for the full outstanding balance and redirects there directly —
|
||||
// choosing a partial amount is only available from the supervisor payments dashboard.
|
||||
const payNow = async (registrationId: string) => {
|
||||
if (!token) return;
|
||||
setError(null);
|
||||
setInfo(null);
|
||||
setDialog({ open: true, message: "Creating payment link…", loading: true });
|
||||
try {
|
||||
const res = await createFullPaymentCheckout(token, registrationId);
|
||||
if (res.priceUpdated) {
|
||||
setDialog({ open: false, message: "", loading: false });
|
||||
setError(`${res.message || 'Pricing has changed.'} New total: R ${(res.newTotal ?? 0).toFixed(2)}. Please try again.`);
|
||||
// Refresh this registration's billing so the displayed outstanding reflects the new price
|
||||
try {
|
||||
const now = new Date();
|
||||
const r = await apiFetch<any>(`/api/registrations/${encodeURIComponent(registrationId)}`, { authToken: token });
|
||||
const pays = await apiFetch<any[]>(`/api/payments/registration/${encodeURIComponent(registrationId)}`, { authToken: token });
|
||||
const totalPaid = pays.reduce((s, p) => s + (p.amount || 0), 0);
|
||||
const totalDue = (r.registrationOptions || []).reduce((sum: number, opt: any) => sum + optionUnitPrice(opt, null, now) * (opt.quantity || 0), 0);
|
||||
setBilling(prev => ({ ...prev, [registrationId]: { totalDue, totalPaid, outstanding: Math.max(0, totalDue - totalPaid), payments: pays } }));
|
||||
} catch {}
|
||||
return;
|
||||
}
|
||||
if (!res.redirectUrl) {
|
||||
setDialog({ open: false, message: "", loading: false });
|
||||
setError("Failed to create checkout");
|
||||
return;
|
||||
}
|
||||
window.location.href = res.redirectUrl;
|
||||
} catch (e: any) {
|
||||
setDialog({ open: false, message: "", loading: false });
|
||||
setError(e?.message || "Failed to create checkout");
|
||||
}
|
||||
};
|
||||
|
||||
// Print helpers
|
||||
const buildTicketHtmlCard = (t: any, buyerName?: string) => {
|
||||
const eventTitle = t.event?.title || t.eventId || "Event";
|
||||
@@ -1039,7 +1074,7 @@ export default function UserDashboardPage() {
|
||||
{canModifyActive && activeBill && activeBill.outstanding > 0 ? (
|
||||
<button
|
||||
className="px-3 py-1.5 text-sm bg-green-600 text-white rounded hover:bg-green-700 shadow-sm focus:outline-none focus:ring-2 focus:ring-green-500 focus:ring-offset-1"
|
||||
onClick={() => router.push(`/dashboard/user/pay?registrationId=${encodeURIComponent(activeReg.id)}`)}
|
||||
onClick={() => payNow(activeReg.id)}
|
||||
>Make payment</button>
|
||||
) : (
|
||||
<>
|
||||
|
||||
Reference in New Issue
Block a user