Test Plan: Billing & Invoicing
♻️ Reconciled against current code — 2026-07-01. Key corrections carried forward: invoices live under a Finance sidebar group (not "Billing"); list filters are Outstanding / Overdue / Paid / Cancelled; TraxionPay is fully removed (Paynamics + OTC + manual only); API base is
/api/billing (PDF at /api/statements) — there is no /api/invoices top-level; the UI still says "Invoice" (the planned "Statement" rename hasn't reached the frontend). New this pass: "Simulate Payment" is removed — testers use Record Payment for manual payments; one non-compliant status-label surface remains: components/InvoiceTable.tsx still renders the raw inv.status enum directly (does not go through the new humanized-status util — #502 gap). See the green banner below for everything new on stage since Jun 19. Trust this page + the code over child TC pages.
🆕 New on stage — 2026-07-08 (payment/billing sweep, !283). See the Jul 8 release note for full test steps.
- "Paid" total counts only settled payments (#614, money · P1): the statement/receipt Paid figure no longer sums
FAILED/CANCELLEDattempts or approved-then-REFUNDED/REVERSEDpayments. Re-check any statement whose Paid total looked inflated. - New-invoice email on ISSUED (#627): creating/issuing an invoice now emails the member a notification with amount + link (check MailHog).
- Duplicate-invoice confirm dialog (#630): a colliding invoice now shows a friendly "create anyway?" confirm instead of the raw
confirmDuplicate: trueerror text. - Org-profile logo defers to Save (#615): logo upload/remove now stages locally and commits only on Save Changes (no immediate API call, no blob-URL leak).
🆕 New on stage — 2026-06-19 → 2026-07-01.
- Canonical invoice-totals projector
projectInvoiceTotals(#443/#450, Jun 25 release): a single money projector now feeds invoice detail, listing, reporting exports, and the finance summary. If a total is wrong, it will be wrong everywhere consistently — cross-check the same invoice (ideally one with discounts + line items + credits) across all four surfaces. See TC-BIL-008. - Apply-Discount control + discount line + ₱0-invoice visibility + settled-only discount cap (#494/#495/#497/#498, Jun 29 release & Jul 1 release): admins can apply a PERCENTAGE or FIXED discount to an OUTSTANDING invoice (
POST /api/billing/admin/invoices/:invoiceId/discount) — a discount line renders and the balance drops by the exact amount. A fully-discounted (₱0) invoice still appears in the invoice list (previously hidden). The discount cap (0 ≤ discountCents ≤ amountCentsand net ≥ already-paid) now counts only SETTLED allocations — aPENDING/UNDER_REVIEWallocation must NOT inflateamountPaidCentsor let a discount slip below what's genuinely paid. See TC-BIL-007. - Invoice line-item editing + itemized CSV export + reminder reflects line items (#259/#260, Jun 29 release): line items can now be edited (not just added on create); the CSV export is itemized; a dues/event reminder email reflects the actual line items.
- Multi-invoice payment initiation (#149, Jun 29 release): real endpoint
POST /api/billing/member/invoices/bulk-pay/paynamics— select several outstanding invoices, initiate ONE payment (GCash/Maya wallet channels only; CREDITCARD is deferred — GR150), and the allocation lands on each invoice correctly. See TC-BIL-008. - Rejected manual payment keeps invoice visible + reverts to full balance (#469, Jun 29 release): rejecting a manual payment no longer hides the invoice or leaves a stale partial balance — the invoice stays listed and its balance is restored to the pre-payment amount.
- Payment rows show payment ref + gateway ref (#490, Jun 29 release): payment list/detail rows now display both the internal payment reference and the gateway reference.
- Invoice/statement search by full name (#476, Jun 29 release): searching "First Last" (two tokens) now finds the member's invoices, not just a single-token substring match.
- Soft duplicate-invoice warning on create (#489, Jun 29 release): creating a manual invoice that matches an existing non-voided invoice (same member + amount + description + due-date day, issued within 90 days) returns
409 DUPLICATE_INVOICE_WARNINGwith the existing invoice's id/number — it warns, does not hard-block. Retry withconfirmDuplicate: truein the body to proceed intentionally (e.g. a re-bill/split-bill). It does not silently create a second invoice. - Payment-confirmation email durability via reconcile job (#118, Jun 29 release): a successful dues payment that misses its immediate confirmation email is caught by a background reconcile job (
Payment.confirmationEmailSentAt) within ~10 minutes — allow that window before flagging a missed email as a bug. - Custom date-range picker for statements + export honors range (#480, Jun 29 release): statements support a custom start/end date range; both the on-screen list and the CSV export honor it.
- Removed misleading "Skip" on bank-transfer proof (#467, Jun 29 release):
SubmitPaymentProofModal.tsxno longer has a "Skip" button — the field is labeled "Proof of Payment (Screenshot) *" (required for bank transfer) and the submit action reads "Submit for Approval". - Void-invoice lifted to all sources (#503, Jul 1 release): voiding is no longer restricted to a subset of invoice origins — dues, event, and manual invoices can all be voided (subject to the existing guard: an invoice with a
SUCCEEDEDpayment allocation requires refund-first; credit-only allocations remain reversible). - Humanized invoice status labels via single util (#502, Jul 1 release):
frontend/pwa-app/src/utils/invoiceStatus.ts→getInvoiceStatusLabel()now renders human text (DRAFT→"Draft",PARTIALLY_PAID→"Partially Paid",VOID→"Void", etc.) consistently acrossInvoiceListCard,InvoiceDetailHeader,BillingTab,InvoicePrintView, andMemberInvoiceDetailPage. ⚠️ One known gap:components/InvoiceTable.tsxstill renders the rawinv.statusenum — flag if you spot a raw enum string there, it's a known non-compliant surface, not a new bug. - PHP peso (₱) in Admin Billing Overview (#493, Jun 29 release):
frontend/admin-portal/src/pages/OrgControlsPage.tsxnow renders amounts with₱viaen-PHlocale formatting, not$. createManualInvoicewith a badmemberIdnow returns404, not500(#432, Jun 25 release): the handler catchesMemberNotFoundErrorand returns a proper404.
1. Invoice model
InvoiceStatus: DRAFT · ISSUED · PARTIALLY_PAID · PAID · OVERDUE · VOID · FAILED. Invoice source (free-text): DUES / EVENT / DONATION / OTHER. Invoice numbers: RCME-DUES-0001, RCME-EVT-0001, RCME-DON-0001, RCME-OTH-0001. Balance is computed from Allocation records (no stored balanceCents).
2. Routes & nav
| Area | Route / how to reach |
|---|---|
| Admin invoices | /admin/invoices (split-view). Sidebar group Finance → Invoices. Page title "Invoices". |
| Admin payments | /admin/payments — sidebar Finance → Payments. |
| Finance dashboard | /admin/finance — sidebar Finance → Dashboard. |
| Member "My Invoices" | /invoices (page "My Invoices & Payments") · detail /invoices/:id · pay /invoices/:id/pay. |
| Member payment history | /payments. |
| Invoice/payment settings | Finance & Billing settings: /admin/settings/finance; payment channels + bank accounts under Finance. |
3. Invoice list filters (exact)
- Status: All Status /
Outstanding/Overdue/Paid/Cancelled(not "Pending/Voided") - Source: All Source / Dues / Donation / Event / Other
- Period: All Time / YTD / 12 Months / 30 Days · free-text search by invoice #, member, event.
4. Key API endpoints (mounted at /api/billing)
| Action | Endpoint | Guard |
|---|---|---|
| List invoices (admin) | GET /api/billing/invoices/tenant | FINANCE:VIEW:invoices |
| Invoice detail (admin) | GET /api/billing/admin/invoices/:id | FINANCE:VIEW:invoices |
| Create invoice | POST /api/billing/invoices | FINANCE:CREATE:invoices |
| Void invoice | POST /api/billing/admin/invoices/:invoiceId/void | FINANCE:EDIT:invoices |
| Mark paid | POST /api/billing/invoices/:id/mark-paid | FINANCE:EDIT:invoices |
| Record manual payment | POST /api/billing/admin/payments/manual (multipart, proof file) | FINANCE:CREATE:payments |
| Approve / reject manual payment | POST /api/billing/admin/payments/:id/approve · /reject | FINANCE:EDIT:payments |
| Member: own invoices | GET /api/billing/invoices | requireMemberId |
| Member: pay invoice | POST /api/billing/member/invoices/:invoiceId/pay/paynamics · multi-invoice POST /api/billing/member/invoices/bulk-pay/paynamics (#149 — GCash/Maya only, CREDITCARD deferred) | requireLoggedInAuth |
| Invoice PDF | GET /api/statements/:id/pdf (A-010 canonical) · legacy /api/billing/invoices/:id/pdf | requireMemberId |
| Donations | POST /api/billing/donations · report GET /api/billing/admin/donations/report | requireMemberId / FINANCE:VIEW:donations |
| Public invoice pay (guest token) | GET/POST /api/public/:tenantSlug/invoices/:invoiceNumber | token / publicActionLimiter |
5. Payment channels / gateway
TraxionPay is fully removed. Sole gateway is Paynamics. Member-selectable channel codes:
GCASH, MAYA, CREDITCARD, OTC_7ELEVEN, OTC_MLHUILLIER, OTC_CEBUANA, OTC_PALAWAN, OTC_BAYAD. Admin-recorded payment channels: MANUAL_CASH, MANUAL_BANK, MANUAL_OTHER, SIMULATED. Do not test for BDO/BPI/TraxionPay/Cash channel codes — they don't exist.
⏸ Live Paynamics payment is PARKED (no prod creds). You can exercise invoice creation, manual-payment recording/approval, and member "Pay Now" up to the gateway hand-off, but completing a real online payment is not testable on stage.
6. Test data (RCME seed)
- Membership types: Regular ₱15,000 · Associate ₱10,000 · Student ₱5,000.
- Dues invoices: 100% member coverage, all
source=DUES, "Annual Membership Dues 2025"; ~60% PAID, ~20% ISSUED (future), ~20% past-due (computed OVERDUE). NumbersRCME-DUES-0001…. (Old guide's "INV-2024-000X" don't exist.) - Finance roles:
treasurer@demo.membervu.com/Treasurer123!(FINANCE_MANAGER) oradmin@rcme.membervu.com/Admin123!.
7. Now on stage — accounting milestone (was mile/accounting)
Updated 2026-06-15: invoice discount (#152), the late-fee engine (#153/#158), and line items (#213) have landed on dev/stage via the accounting milestone (!136) — the discount/late-fee/line-item schema and fields now exist on dev and are testable. See the Jun 15 release note for scope & the #213 update-path caveat. The payment gateway is still inert (no live online checkout/webhooks).
8. Test Cases
| TC | Topic | Detailed |
|---|---|---|
| TC-BIL-001 | Invoice Creation | detailed |
| TC-BIL-002 | Invoice Listing & Filtering | detailed |
| TC-BIL-003 | Invoice Status / Details | detailed |
| TC-BIL-004 | Invoice Actions (void/send/PDF) | detailed |
| TC-BIL-005 | Notifications / Reminders | detailed |
| TC-BIL-006 | Bulk / Export / Donations | detailed |
| TC-BIL-007 | 🆕 Discounts, Adjustments & Void | detailed |
| TC-BIL-008 | 🆕 Total Parity & Payment Reconciliation | detailed |