diff --git a/CHANGELOG.md b/CHANGELOG.md index d6f34c7..6ec83c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,7 +14,7 @@ and this project follows [Semantic Versioning](https://semver.org/). ### 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: `GET /api/payments/mypayments` now normalizes `method` to the fixed set cash/card/eft/voucher/other — any gateway-reported value outside that set (apple_pay, google_pay, yoco, etc.) is reported and filterable as "other" — instead of exposing raw, inconsistent gateway strings the filter dropdown didn't know about. +- User dashboard payment history: `GET /api/payments/mypayments` now normalizes `method` to the fixed set cash/card/eft/voucher/other. Card-network wallet payments (`apple_pay`, `google_pay`) are reported and filterable as "card"; any other gateway-reported value falls under "other" — instead of exposing raw, inconsistent gateway strings the filter dropdown didn't know about. ## [1.0.1] - 2026-07-23 diff --git a/backend/src/controllers/paymentController.js b/backend/src/controllers/paymentController.js index cd75a53..da273e4 100644 --- a/backend/src/controllers/paymentController.js +++ b/backend/src/controllers/paymentController.js @@ -350,14 +350,18 @@ const getPayments = async (req, res) => { }; // Payment.method is free-text — online checkouts get tagged with whatever wallet type the -// gateway reports (apple_pay, google_pay, yoco, ...), not just the manual-entry methods below. -// For the user-facing dashboard we don't want to expose every raw gateway string, so anything -// that isn't one of the methods staff can manually choose is just shown/filtered as "other". +// gateway reports (apple_pay, google_pay, ...), not just the manual-entry methods below. +// For the user-facing dashboard: card-network wallets count as "card" (same settlement, no +// separate float); anything else unrecognized falls into "other". const USER_FACING_METHODS = ['cash', 'card', 'eft', 'voucher']; +const CARD_ALIASES = ['apple_pay', 'google_pay']; +const KNOWN_METHODS = [...USER_FACING_METHODS, ...CARD_ALIASES]; function normalizeUserMethod(method) { const m = String(method || '').toLowerCase(); - return USER_FACING_METHODS.includes(m) ? m : 'other'; + if (USER_FACING_METHODS.includes(m)) return m; + if (CARD_ALIASES.includes(m)) return 'card'; + return 'other'; } // @desc Get user payments (paginated, excludes donations, supports date range/method/kind filters) @@ -381,11 +385,16 @@ const getUserPayments = async (req, res) => { if (req.query.method) { const requested = String(req.query.method).toLowerCase(); - if (requested === 'other') { - // "Other" covers every method that isn't one of the four explicit buckets — - // e.g. gateway-reported wallet types like apple_pay, google_pay, yoco, etc. + if (requested === 'card') { + // "Card" also covers card-network wallet types (apple_pay, google_pay) — same + // settlement as a card payment, no separate float to reconcile. + where.AND = [{ + OR: ['card', ...CARD_ALIASES].map(m => ({ method: { equals: m, mode: 'insensitive' } })) + }]; + } else if (requested === 'other') { + // "Other" covers every method that isn't one of the recognized buckets above. where.NOT = { - OR: USER_FACING_METHODS.map(m => ({ method: { equals: m, mode: 'insensitive' } })) + OR: KNOWN_METHODS.map(m => ({ method: { equals: m, mode: 'insensitive' } })) }; } else if (USER_FACING_METHODS.includes(requested)) { where.method = { equals: requested, mode: 'insensitive' }; diff --git a/backend/src/index.js b/backend/src/index.js index b84aa9e..255bb15 100644 --- a/backend/src/index.js +++ b/backend/src/index.js @@ -543,7 +543,7 @@ app.get('/docs', async (req, res) => { responses:[ { status:201, desc:'Recorded', body:{ id:'pay-uuid-...', amount:450, method:'cash', status:'succeeded', createdAt:'2025-06-10T09:00:00.000Z' }}, ]}, - { method:'GET', path:'/api/payments/mypayments', auth:'user+', desc:'Get own payment history (paginated, excludes donations). Returned method is normalized to cash|card|eft|voucher|other — any gateway-reported wallet type (apple_pay, google_pay, yoco, ...) not in that set is reported as "other"', + { method:'GET', path:'/api/payments/mypayments', auth:'user+', desc:'Get own payment history (paginated, excludes donations). Returned method is normalized to cash|card|eft|voucher|other — apple_pay/google_pay report as "card", any other gateway-reported value reports as "other"', queryParams:{ page:'Page (default 1)', limit:'Per page (default 25, max 25)', startDate:'ISO date, filters createdAt >=', endDate:'ISO date, filters createdAt <=', method:'Filter by normalized method: cash|card|eft|voucher|other', kind:'payment|refund — filters by amount sign' }, responses:[{ status:200, desc:'Success', body:{ data:[{ id:'pay-uuid-...', amount:450, method:'card', status:'succeeded', createdAt:'2025-06-01T11:00:00.000Z' }], total:1, page:1, limit:25, pages:1 }}]}, { method:'GET', path:'/api/payments', auth:'supervisor+', desc:'List all payments',