From 90bea253382a9463b7971d55e2f02a6a7d7c5dcc Mon Sep 17 00:00:00 2001 From: joshua Date: Fri, 24 Jul 2026 08:28:59 +0200 Subject: [PATCH] Fix payment method handling for non-default types (apple_pay, google_pay) Yoco webhook payments are tagged with whatever wallet type the gateway reports, not just cash/card/eft/voucher. Cashup/report totals were silently dropping those into the "other" bucket instead of "card", and the new user payment history page couldn't filter by them and showed raw snake_case values. Co-Authored-By: Claude Sonnet 5 --- CHANGELOG.md | 5 ++++ backend/src/utils/cashupUtils.js | 4 ++- .../src/app/dashboard/user/payments/page.tsx | 26 +++++++++++++++++-- 3 files changed, 32 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7bd3d05..a326bbb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,11 @@ and this project follows [Semantic Versioning](https://semver.org/). - User dashboard: new "Payment history" page listing the user's own payments (donations excluded), with server-side pagination (25 per page), date range, method, and payment/refund filters. +### Fixed + +- Cashup/reports: payments tagged with a digital wallet method (e.g. `apple_pay`, `google_pay` from online checkouts) are now bucketed as "card" for reconciliation instead of silently falling into "other". +- User dashboard payment history: the method filter and display now recognize Apple Pay / Google Pay instead of only cash/card/EFT/voucher, and unrecognized method values are shown with a readable label instead of a raw snake_case/camelCase string. + ## [1.0.1] - 2026-07-23 ### Added diff --git a/backend/src/utils/cashupUtils.js b/backend/src/utils/cashupUtils.js index b08a92d..33259fb 100644 --- a/backend/src/utils/cashupUtils.js +++ b/backend/src/utils/cashupUtils.js @@ -11,11 +11,13 @@ function emptyByMethod(fill = 0) { } // Normalizes a free-text Payment.method into one of the fixed cashup buckets. +// Card-network wallets (Apple Pay, Google Pay, etc.) settle exactly like a card payment — +// no separate float to reconcile — so they belong in the 'card' bucket, not 'other'. function bucketForMethod(method) { const m = String(method || '').toLowerCase(); if (m.includes('cash')) return 'cash'; if (m.includes('eft')) return 'eft'; - if (m.includes('card') || m.includes('yoco')) return 'card'; + if (m.includes('card') || m.includes('yoco') || m.includes('pay')) return 'card'; return 'other'; } diff --git a/frontend/src/app/dashboard/user/payments/page.tsx b/frontend/src/app/dashboard/user/payments/page.tsx index 3c5ec47..83b3fbd 100644 --- a/frontend/src/app/dashboard/user/payments/page.tsx +++ b/frontend/src/app/dashboard/user/payments/page.tsx @@ -18,6 +18,26 @@ interface PaymentItem { const formatRand = (n: number) => `R ${Math.abs(n).toFixed(2)}`; +// Payment.method is free-text — online payments arrive tagged with whatever wallet type the +// gateway reports (e.g. apple_pay, google_pay), not just the manual-entry methods below. +const METHOD_LABELS: Record = { + cash: "Cash", + card: "Card", + eft: "EFT", + voucher: "Voucher", + apple_pay: "Apple Pay", + google_pay: "Google Pay", +}; + +const formatMethod = (method: string | null | undefined) => { + if (!method) return "Payment"; + if (METHOD_LABELS[method]) return METHOD_LABELS[method]; + return method + .replace(/[_-]+/g, " ") + .replace(/([a-z])([A-Z])/g, "$1 $2") + .replace(/\b\w/g, c => c.toUpperCase()); +}; + export default function UserPaymentsPage() { const { user, loading, token } = useAuth(); const router = useRouter(); @@ -37,7 +57,7 @@ export default function UserPaymentsPage() { // Filters const [startDate, setStartDate] = useState(""); const [endDate, setEndDate] = useState(""); - const [method, setMethod] = useState<"" | "cash" | "card" | "eft" | "voucher">(""); + const [method, setMethod] = useState<"" | "cash" | "card" | "eft" | "voucher" | "apple_pay" | "google_pay">(""); const [kind, setKind] = useState<"" | "payment" | "refund">(""); const buildQuery = useCallback((p: number) => { @@ -110,6 +130,8 @@ export default function UserPaymentsPage() { + +
@@ -147,7 +169,7 @@ export default function UserPaymentsPage() {
{formatDateTime(p.createdAt)}
-
Method: {p.method || "payment"}
+
Method: {formatMethod(p.method)}
{eventTitle &&
Event: {eventTitle}
} );