TC-FUND-001: Donation Money-Flow (Detailed) — #651 / #569 / #607
ASSERT ON DATA, NOT "IT WORKED". This is a G9 money-parity flow — every step below checks a concrete DB field/enum value (Donation.approvalStatus, Invoice.status, Payment.status/verificationStatus), never just a toast message. Approve/Reject act on the same Payment/Donation row — a passing test that silently created a duplicate row is a FAIL even if the UI looked fine.
TC-FUND-MONEY-01: Happy Path — Donate → Proof → Approve (G9)
| Step | Action | Expected Result | Test Value |
|---|---|---|---|
| 1 | Log in as the donor member. | Dashboard loads. | testmember@rcme.membervu.com / Member123! |
| 2 | Navigate to the public/member campaign page for an ACTIVE campaign and donate a fixed amount. | POST /api/campaigns/:campaignId/donations returns 200/201. Toast confirms the donation. |
500.00 PHP |
| 3 | Note the donation's linked invoice ID (visible in the response / "View Invoice" link / My Donations list). | Assert on data: Donation.approvalStatus = PENDING; a new Invoice exists with status = ISSUED, source = DONATION, invoice number prefixed DONATION, due date = today + 7 days. No Payment row exists for this invoice yet. |
— |
| 4 | Open MailHog. | A "Donation Received" email is present, addressed to testmember@rcme.membervu.com. |
https://stg-webmail.membervu.com |
| 5 | Back in the app, open the donation's invoice (/invoices/:id?pay=now) and upload a payment proof image for the FULL donation amount. |
A Payment row is created: status: PENDING, verificationStatus: PENDING_VERIFICATION, proofUrl populated. |
any JPG/PNG receipt image |
| 6 | Log out. Log in as the finance admin. | Admin portal loads. | admin@rcme.membervu.com / Admin123! |
| 7 | Navigate to Pending Proof Review (/admin/campaigns/donations/pending) and find this donation. |
The donation appears in the queue with the proof image visible. | /admin/campaigns/donations/pending |
| 8 | Click Approve. | POST /api/campaigns/admin/donations/:donationId/approve returns 200. Assert on data: the SAME Payment row (from step 5) flips to SUCCEEDED/APPROVED — do NOT see a second Payment row for this invoice. An Allocation now covers the invoice. Invoice.status → PAID. A money-flow audit entry with action APPROVED exists. Donation.approvalStatus → APPROVED. |
— |
| 9 | Check MailHog again. | "Donation Approved" email sent to the donor. | https://stg-webmail.membervu.com |
| 10 | Reload the campaign page (public or admin detail) and note raisedAmount before/after. |
raisedAmount increases by exactly the donation amount (500.00). |
— |
TC-FUND-MONEY-02: Under-Covered Proof Blocks Approval (Rollback)
| Step | Action | Expected Result | Test Value |
|---|---|---|---|
| 1 | As donor, make a new donation on the ACTIVE campaign. | Donation PENDING + Invoice ISSUED created. |
1000.00 PHP |
| 2 | Upload a proof, but for LESS than the invoice amount (use a payment note/annotation indicating a partial amount, or use a proof-amount field if the UI has one). | Payment row created, status: PENDING. |
e.g. proof for 600.00 against a 1000.00 invoice |
| 3 | As admin, attempt to Approve this donation. | 409 response: {"error":{"code":"PAYMENT_AMOUNT_INSUFFICIENT"}}. |
— |
| 4 | Re-check the Donation, Invoice, and Payment records. | Assert everything rolled back: Payment.status still PENDING; Invoice.status still ISSUED; Donation.approvalStatus still PENDING. Nothing was partially settled. |
— |
| 5 | (Contrast case) make another donation and upload proof for MORE than the invoice amount, then approve. | Approval succeeds — Invoice.status → PAID. The overpaid excess is issued as a Credit with status: AVAILABLE for the donor. |
e.g. proof for 1200.00 against a 1000.00 invoice |
TC-FUND-MONEY-03: Approve With No Proof Uploaded
| Step | Action | Expected Result | Test Value |
|---|---|---|---|
| 1 | As donor, make a new donation. Do NOT upload any proof. | Donation PENDING, no Payment row exists for the invoice. |
250.00 PHP |
| 2 | As admin, attempt to Approve this donation from the pending queue (or via direct API call if the UI hides the button for no-proof donations — note either behavior). | 409 response: {"error":{"code":"NO_PROOF_TO_APPROVE"}}. |
— |
TC-FUND-MONEY-04: Reject Retains Proof + Donor-Safe Copy (#569)
| Step | Action | Expected Result | Test Value |
|---|---|---|---|
| 1 | As donor, make a new donation and upload a proof. | Payment PENDING, proofUrl set. |
300.00 PHP |
| 2 | Note the proofUrl value (e.g. from admin queue image src or API response) before rejecting. |
URL recorded for comparison in step 4. | — |
| 3 | As admin, click Reject and try submitting with an EMPTY reason field. | 400 — reason is required (trimmed non-empty, ≤1000 chars). |
(blank) |
| 4 | Now submit Reject with a real reason. | POST /api/campaigns/admin/donations/:donationId/reject returns 200. Assert on data: Payment.status → FAILED/REJECTED (terminal); proofUrl is STILL present on the Payment record (compare to step 2 — the S3 object is NOT deleted); Donation.approvalStatus → REJECTED; Invoice.status reverts to unsettled (not PAID). |
Amount does not match uploaded proof |
| 5 | Check MailHog. | "Donation Rejected" email sent, body includes the reason text from step 4. | https://stg-webmail.membervu.com |
| 6 | As donor, reopen the invoice for the rejected donation. | The DonationActionNeededCard is displayed. Assert its copy NEVER contains the words "Invoice", "Receipt", or "OR" — only donor-safe terms ("gift", "donation", "proof of payment"). The upload field is labeled "Gift reference", not "Invoice Number" / "OR Number". | — |
TC-FUND-MONEY-05: Resubmit After Rejection Reuses Same Row (G13)
| Step | Action | Expected Result | Test Value |
|---|---|---|---|
| 1 | Continuing from TC-FUND-MONEY-04's rejected donation, note the donationId and the invoice's total Donation row count. |
Donation.count({invoiceId}) = 1. |
— |
| 2 | As donor, click Resubmit on the DonationActionNeededCard. | POST /api/campaigns/donations/:donationId/resubmit returns 200. Assert on data: Donation.approvalStatus REJECTED → PENDING on the SAME row (same donationId as step 1). Re-check Donation.count({invoiceId}) — must STILL be 1 (not 2). |
— |
| 3 | Call resubmit a second time while the donation is already PENDING (re-click the button, or repeat the API call). |
Idempotent 200 no-op — NOT an error, no state change beyond staying PENDING. |
— |
| 4 | Upload a fresh proof for the resubmitted donation. | A NEW Payment row is created for the invoice (the old rejected Payment stays FAILED — the duplicate-payment guard does not block this because the prior payment is terminal). |
any JPG/PNG receipt image |
| 5 | As admin, approve the new proof. | Same assertions as TC-FUND-MONEY-01 step 8: the new Payment → SUCCEEDED, Invoice.status → PAID, Donation.approvalStatus → APPROVED. Still only ONE Donation row for this invoice throughout the entire flow. |
— |
CLEANUP
Data created during this test:
- 5 donations by testmember@rcme.membervu.com on the target ACTIVE campaign (500, 1000, 1000/overpay-1200, 250, 300 PHP) — safe to leave; they exercise real money-flow states useful for TC-FUND-002/003 regression.
- One
APPROVEDdonation with aCredit(from the overpay contrast case) — note the Credit ID if you plan to test credit application elsewhere. - Campaign
raisedAmountwas increased — if you need a clean baseline for TC-FUND-003 aggregate checks, use a fresh campaign instead of resetting this one.