TC-PAY-008: Proof Storage, Walk-in Records & Payment Refs (Detailed)
URLs for this test:
Frontend:
Member invoices:
Admin payments:
Admin events / ticket types:
Event check-in (walk-in):
MailHog:
Frontend:
https://stg-rcme.membervu.com/loginMember invoices:
https://stg-rcme.membervu.com/invoicesAdmin payments:
https://stg-rcme.membervu.com/admin/paymentsAdmin events / ticket types:
https://stg-rcme.membervu.com/admin/eventsEvent check-in (walk-in):
https://stg-rcme.membervu.com/admin/events/check-inMailHog:
https://stg-webmail.membervu.com
Payment gateway is INERT on staging (Paynamics). Every "paid" step below (proof submission, walk-in, memberOnly registration) creates or checks real Invoice/Payment/Registration records only — none of them make a live gateway call. Do not expect a redirect to an actual GCash/Maya/card checkout.
Step 1: Submit bank-transfer proof → confirm bare-key storage + renders after refresh (PAY8-01)
| Step | Action (EXACT clicks/typing) | Expected Result (EXACT text/behavior) | Test Value |
|---|---|---|---|
| 1 | Go to https://stg-rcme.membervu.com/login; sign in. | Member dashboard loads. | testmember@rcme.membervu.com / Member123! |
| 2 | Go to My Invoices (/invoices). Open any OUTSTANDING dues invoice, choose Pay → Bank Transfer. | The SubmitPaymentProofModal opens. Field is labeled "Proof of Payment (Screenshot) *" — no "Skip" button (#467). | — |
| 3 | Upload a small PNG/JPG screenshot as proof. Click "Submit for Approval". | Payment record created with verificationStatus: PENDING. Success toast/redirect to invoice detail. | Any small .png/.jpg |
| 4 | As treasurer@demo.membervu.com / Treasurer123!, go to Finance → Payments (/admin/payments), find this pending payment, open its proof/detail view. | The proof image renders inline. The underlying proofUrl is a bare storage key (e.g. payments/proofs/<tenantId>/…) — NOT a full http:///https:// URL. Retrieval goes through GET /api/billing/admin/payments/:paymentId/proof, which streams the file (or, only for legacy absolute-URL proofs, redirects to an allow-listed host). | — |
| 5 | Hard-refresh the payment detail page (Cmd/Ctrl+Shift+R). | The proof image still renders after refresh — no broken image icon, no 404 on the proof route. | — |
Data assertion: if you can inspect the network request for the proof, confirm the request path is
/api/billing/admin/payments/:paymentId/proof (not a direct S3/LocalStack URL embedded in the page) and the response is 200 with a Content-Type: image/* header — that's the #451 key-only + proxy-serve behavior, not a coincidence.
Step 2: Reject that manual payment → invoice stays listed + balance reverts (PAY8-02)
| Step | Action | Expected Result | Test Value |
|---|---|---|---|
| 1 | Note the invoice's outstanding balance BEFORE the Step 1 payment (e.g. its full dues amount) — record the exact figure. | Recorded for cross-check. | — |
| 2 | Still as Treasurer, on the pending payment from Step 1, click Reject, enter a reason. | POST /api/billing/admin/payments/:paymentId/reject → 200. Payment status becomes FAILED (verificationStatus: REJECTED). | Reason QA PAY-008 reject test |
| 3 | Go back to the invoice list (Outstanding filter), search for this invoice. | The invoice still appears in the Outstanding list — rejection did NOT remove/hide it. | — |
| 4 | Open the invoice detail. | Balance is the exact full pre-payment amount you recorded in row 1 — not ₱0, not a stale partial reflecting the rejected payment. recomputeAndPersistInvoiceStatus re-runs on rejection (#469) specifically so a rejected payment's allocation no longer counts toward paid. | — |
Step 3: Payment row shows both payment ref + gateway ref (PAY8-03)
| Step | Action | Expected Result | Test Value |
|---|---|---|---|
| 1 | Locate (or create + approve) a SETTLED manual payment with a reference number entered (e.g. a bank-transfer proof approved, or a Record Payment with a reference filled in). | Payment status SUCCEEDED. | Reference QA-PAY8-REF-001 |
| 2 | Open the invoice's Payments tab (components/invoices/tabs/PaymentsTab.tsx — visible on the invoice detail view, both admin and member sides where applicable). | The payment row shows "Ref: QA-PAY8-REF-001" (from payment.reference). | — |
| 3 | If this payment also has a gateway-assigned reference (payment.externalReference — e.g. from a Paynamics-initiated payment, even if inert/uncaptured), check the same row. | The row ALSO shows "Gateway: <externalReference>" alongside the internal ref — both visible simultaneously, one does not overwrite the other. | — |
Step 4: Paid walk-in creates real Invoice + Payment records, gateway inert (PAY8-04)
| Step | Action | Expected Result | Test Value |
|---|---|---|---|
| 1 | Log in as events@demo.membervu.com / Events123!. Go to Events → Check-in (/admin/events/check-in). Select any live/upcoming event with registrationMode: PAY_NOW and a positive price (e.g. a seeded dinner/gala/workshop event). | Check-in portal loads for that event. | Any seeded PAY_NOW event |
| 2 | Open the Walk-in tab. Enter a new attendee's name/email and submit. | Because this is a paid event, the UI shows "This is a paid event. Amount due: …" and prompts to confirm payment was collected offline. | Name QA Walk-in Payer |
| 3 | Confirm "Payment collected offline" (optionally add a payment note), then submit. | POST /api/events/:eventId/checkin/walk-in → 201. Response includes paid: true. A shell guest Member (status GUEST_ATTENDEE) is created to satisfy the non-nullable memberId FKs. | Note QA PAY-008 walk-in |
| 4 | As Treasurer, find the resulting invoice (source EVENT) in Finance → Invoices, and its payment in Finance → Payments. | A real Invoice exists for the walk-in's ticket amount, status PAID. A real Payment exists with channel: MANUAL_OTHER, status: SUCCEEDED, verificationStatus: NOT_REQUIRED — created directly, not via a gateway callback. | — |
| 5 | Confirm no live gateway interaction occurred (no Paynamics redirect at any point in steps 2–3; no webhook-dependent state). | Correct — the walk-in paid path never calls Paynamics. It is a records-only offline-payment flow (#424/#425). | — |
| 6 | Check https://stg-webmail.membervu.com for a welcome email to the walk-in's email address. | A welcome email arrives — this is best-effort/async (queued after the 201 response), so allow a short delay. A missing email does NOT mean the check-in/payment records failed — verify the records (step 4) independently of the email. | — |
Step 5: memberOnly ticket rejects a non-member paid registration (PAY8-05)
| Step | Action | Expected Result | Test Value |
|---|---|---|---|
| 1 | As admin@rcme.membervu.com / Admin123!, open a PAY_NOW event's ticket types and create (or edit) one with the Member Only flag enabled. | A ticket type with memberOnly: true exists on this event. | Ticket name QA Members-Only Ticket |
| 2 | Log out. Log in as guest@demo.membervu.com / Guest123! (a non-ACTIVE / non-member-status account) and attempt to self-register for this event, selecting the Member Only ticket. | Registration is rejected with a clear message — "Ticket type "…" is for active members only. Your membership must be active to purchase this ticket." — not a silent success, not a generic 500. | — |
| 3 | Log in as testmember@rcme.membervu.com / Member123! (a seeded ACTIVE member) and self-register for the same Member Only ticket. | Registration succeeds — the memberOnly check passes for an ACTIVE member on a SELF registration. | — |
| 4 | (Optional, if the UI supports it) Have the ACTIVE member invite a GUEST to the same Member Only ticket. | The GUEST registration is exempt from the memberOnly check — the invited guest need not themselves be a member, because the responsible party is the inviting active member. | — |
CLEANUP
- Confirm the rejected payment from Step 2 stays rejected (do not re-approve unless intentionally re-testing).
- Void or flag the walk-in Invoice/Payment created in Step 4 so it isn't mistaken for a real attendee obligation in later regression passes.
- Remove or deactivate the QA Member Only ticket type from Step 5 if it was created directly on a real seeded event (to avoid it appearing in later member-facing regression runs).
- Cancel the guest-shell registration/member record created by the Step 4 walk-in if your tenant's regression process tracks shell-member counts.