Test Cases: Donation Money-Flow (#651 / #569 / #607)
Module: Fundraising & Campaigns — Donation Money-Flow (G9 money-parity, G13 idempotency)
Endpoints: POST /api/campaigns/:campaignId/donations (donate) · POST /api/campaigns/admin/donations/:donationId/approve · POST /api/campaigns/admin/donations/:donationId/reject (body {reason}) · POST /api/campaigns/donations/:donationId/resubmit.
Frontend: donate from the public campaign page; pay via the resulting invoice's Upload Proof flow (/invoices/:id?pay=now); admin acts from the Pending Proof Review queue (TC-FUND-002).
Prerequisites
- An ACTIVE fundraising campaign for RCME. Login as
testmember@rcme.membervu.com/Member123!to donate. Login asadmin@rcme.membervu.com/Admin123!to approve/reject.
Test Cases
TC-FUND-MONEY-01: Happy Path — Donate → Proof → Approve (G9)
| Step | Action | Expected Result |
|---|---|---|
| 1 | Member donates a fixed amount on an ACTIVE campaign. | POST /api/campaigns/:campaignId/donations → Donation.approvalStatus = PENDING + a new Invoice.status = ISSUED (source DONATION, number prefixed DONATION, due +7d). No Payment row exists yet. "Donation Received" email arrives in MailHog. |
| 2 | Donor opens the invoice and uploads payment proof for the full amount. | A Payment row is created: status: PENDING, verificationStatus: PENDING_VERIFICATION. |
| 3 | Admin opens the Pending Proof Review queue, finds the donation, clicks Approve. | POST /api/campaigns/admin/donations/:donationId/approve. Assert on data: the same Payment row flips to SUCCEEDED/APPROVED (no second Payment row created); an Allocation covers the invoice; Invoice.status → PAID; a money-flow audit entry (APPROVED) is written; Donation.approvalStatus → APPROVED. |
| 4 | Check MailHog and the campaign totals. | "Donation Approved" email sent to the donor. Campaign raisedAmount increases by exactly the donation amount. |
TC-FUND-MONEY-02: Under-Covered Proof Blocks Approval (rollback)
| Step | Action | Expected Result |
|---|---|---|
| 1 | Donate, then upload proof for LESS than the invoice amount. | Payment created PENDING as usual. |
| 2 | Admin attempts to Approve. | 409 {"error":{"code":"PAYMENT_AMOUNT_INSUFFICIENT"}}. Assert everything rolled back — Payment stays PENDING, Invoice stays ISSUED, Donation stays PENDING. Nothing settled. |
| 3 | (Contrast) donate + overpay the proof amount, then approve. | Overpayment is allowed — invoice still reaches PAID; the excess is issued as a Credit with status AVAILABLE. |
TC-FUND-MONEY-03: Approve With No Proof Uploaded
| Step | Action | Expected Result |
|---|---|---|
| 1 | Donate, do NOT upload proof. | Donation PENDING, no Payment row. |
| 2 | Admin attempts to Approve. | 409 {"error":{"code":"NO_PROOF_TO_APPROVE"}}. |
TC-FUND-MONEY-04: Reject Retains Proof + Donor-Safe Copy (#569)
| Step | Action | Expected Result |
|---|---|---|
| 1 | Donate, upload proof, then as admin click Reject with a reason (e.g. "Amount does not match proof"). | POST /api/campaigns/admin/donations/:donationId/reject body {reason}. Payment → FAILED/REJECTED (terminal). Proof file is RETAINED — proofUrl still present on the Payment, S3 object not deleted. Donation → REJECTED. Invoice reverts to unsettled. |
| 2 | Try rejecting with an empty/whitespace-only reason. | 400 — reason is required, trimmed non-empty, ≤1000 chars. |
| 3 | Check MailHog. | "Donation Rejected" email carries the reason. |
| 4 | Donor reopens the invoice. | The DonationActionNeededCard is shown. Assert its copy NEVER uses the word "Invoice", "Receipt", or "OR" — it uses "gift" / "donation" / "proof of payment", and labels the field "Gift reference". |
TC-FUND-MONEY-05: Resubmit After Rejection Reuses Same Row (G13)
| Step | Action | Expected Result |
|---|---|---|
| 1 | From TC-FUND-MONEY-04's rejected donation, donor clicks Resubmit. | POST /api/campaigns/donations/:donationId/resubmit. Donation.approvalStatus REJECTED → PENDING on the SAME row — assert Donation.count({invoiceId}) is exactly 1 (not a new donation created). |
| 2 | Call resubmit again while the donation is already PENDING. | Idempotent 200 no-op — NOT an error. |
| 3 | Donor uploads a fresh proof. | A NEW Payment row is created (the old rejected one is FAILED, so the duplicate-payment guard does not 409). |
| 4 | Admin approves the new proof. | Same as TC-FUND-MONEY-01 step 3 — assert Payment → SUCCEEDED, Invoice → PAID, Donation → APPROVED. |