Test Plan: Fundraising & Campaigns
♻️ Reconciled against code — 2026-07-09 (MR !280 fundraising money-flow + admin console). Reconciled against
membervu-saas dev @ fb391e60. Fundraising uses the same manual-proof payment rail as billing (upload proof → admin approve/reject) layered on a dedicated Donation/FundraisingCampaign model — it is not auto-settled and there is no live payment gateway (decision #607/#650). This Test Plan is the authoritative reference — where a child TC page conflicts, trust this page + the code.
🆕 New on stage — Fundraising money-flow (#651), admin console (#623), cover-image/aggregates (#661/#662), donor reject/resubmit (#569).
- Donation money-flow (#651/#569/#607): donate →
Donation PENDING+Invoice ISSUED(no auto-pay) → donor uploads proof →Payment PENDING→ admin approve/reject on the same Payment row (never a duplicate). Approve settles the invoice (G9 money-parity) and writes an audit entry. Reject is terminal but retains the proof file and lets the donor resubmit on the same Donation row (G13 idempotency) — see TC-FUND-001. - Admin Fundraising Console (#623): a dedicated pending-proof review queue (
/admin/campaigns/donations/pending) with a live sidebar badge, plus campaign close-out (final totals, donor CSV export, bulk thank-you email) — see TC-FUND-002. - Cover image + aggregates (#661/#662): campaigns can carry a cover image (5MB / png-jpg-jpeg-webp guard, edit-flow isolation on failure); list/detail responses now include
raisedAmountandpendingProofCount; the public campaign page exposes an approved-only donor list and honoursisAnonymous(API-only, no FE checkbox yet) — see TC-FUND-003.
1. Scope
- Donation money-flow — member donates on an ACTIVE campaign, uploads proof, admin approves/rejects, donor resubmits after rejection.
- Admin console — campaign list/create/detail, pending-proof review queue + nav badge, campaign close-out (final totals, donor CSV, thank-you email).
- Cover image, aggregates & public campaign page — cover-image upload, list/detail aggregates (raisedAmount, pendingProofCount), the anonymous public donation feed.
2. Status models
Donation.approvalStatus:PENDING→APPROVED|REJECTED;REJECTED→PENDINGon resubmit. OneDonationrow per invoice — never duplicated (resubmit reuses the same row, G13).Invoice: donating createsInvoice.status = ISSUED(sourceDONATION, invoice number prefixDONATION, due +7 days). No auto-pay (#607/#650) — reachesPAIDonly when admin approves a payment that covers it.Payment: created only when the donor uploads proof (manual-proof rail) →status: PENDING,verificationStatus: PENDING_VERIFICATION. Approve →SUCCEEDED/APPROVED. Reject →FAILED/REJECTED(terminal; proof file retained).FundraisingCampaign.status:DRAFT·ACTIVE·CLOSED·ARCHIVED. OnlyACTIVEcampaigns accept donations.
3. Key routes
| Area | Route |
|---|---|
| Public campaign page (unauth) | /:tenantSlug/campaigns/:slug (backed by GET /api/public/:tenantSlug/campaigns/:slug) |
| Member donate / invoice | Donate from the public campaign page; pay via /invoices/:id (?pay=now) to upload proof |
| Admin campaign list | /admin/campaigns (AdminCampaignsPage) |
| Admin create campaign | /admin/campaigns/new |
| 🆕 Admin pending-proof queue | /admin/campaigns/donations/pending (AdminPendingProofQueuePage) |
| Admin campaign detail | /admin/campaigns/:id |
| Admin campaign updates | /admin/campaigns/:id/updates |
Admin console routes are guarded for roles ADMIN / TREASURER / FINANCE_MANAGER / SUPER_ADMIN.
4. Key API endpoints
| Action | Endpoint | Guard |
|---|---|---|
| Donate | POST /api/campaigns/:campaignId/donations — body {amountCents,currency,donorName?,donorEmail?,isAnonymous?}. Campaign must be ACTIVE else 400 "Campaign is not accepting donations"; else 404. | requireMemberId |
| Approve donation | POST /api/campaigns/admin/donations/:donationId/approve | FINANCE:EDIT:donations |
| Reject donation | POST /api/campaigns/admin/donations/:donationId/reject — body {reason} REQUIRED, trimmed non-empty, ≤1000 chars else 400 | FINANCE:EDIT:donations |
| Resubmit after rejection | POST /api/campaigns/donations/:donationId/resubmit | requireMemberId (donor-scoped) |
| 🆕 List pending donations (queue) | GET /api/campaigns/admin/donations/pending — oldest-first, tenant-wide | FINANCE:VIEW:donations |
| 🆕 Campaign close-out | POST /api/billing/admin/campaigns/:id/close-out — ACTIVE → CLOSED, returns {finalTotal, donationCount}. Idempotent (re-closing an already-CLOSED campaign is a 200 no-op). | FINANCE:EDIT:campaigns |
| 🆕 Donor CSV export | GET /api/billing/admin/campaigns/:id/close-out/donors.csv — APPROVED-only; anonymous rows redact name/email | FINANCE:EDIT:campaigns |
| 🆕 Bulk thank-you email | POST /api/billing/admin/campaigns/:id/close-out/thank-you — returns {total,sent,skipped,failed} | FINANCE:EDIT:campaigns |
| 🆕 Cover image upload (#661) | POST /api/billing/admin/campaigns/:id/cover-image — body {imageData:"data:image/png;base64,..."}. Max 5MB; png/jpg/jpeg/webp only. | FINANCE:EDIT:campaigns |
| Admin campaign list / detail (aggregates, #662) | GET /api/billing/admin/campaigns · GET /api/billing/admin/campaigns/:id — include raisedAmount, pendingProofCount | FINANCE:VIEW:campaigns |
| Public campaign page | GET /api/public/:tenantSlug/campaigns/:slug — no auth; APPROVED donations only feed raisedAmount/donationCount/donor list | public |
5. Known caveats
Refunded donations still count toward
raisedAmount (#662). There is no un-approve / refund transition on Donation — if an APPROVED donation's underlying payment is later refunded elsewhere, raisedAmount does not decrease. Document this if observed; do not assert a refund reduces the campaign total.
isAnonymous has no frontend checkbox today. The donate API accepts isAnonymous:true, and the public campaign page correctly nulls out donorName when set — but the donation UI does not expose a way to set it. It is currently API-only (verify via direct API call in TC-FUND-003).
6. Test users
Donor: testmember@rcme.membervu.com / Member123! (MEMBER). Finance admin: admin@rcme.membervu.com / Admin123! (has FINANCE via ADMIN wildcard). Treasurer: treasurer@demo.membervu.com / Treasurer123! (FINANCE_MANAGER). Tenant: RCME.
7. Test Cases
- TC-FUND-001 Donation Money-Flow (#651/#569/#607) · detailed
- TC-FUND-002 Admin Fundraising Console (#623) · detailed
- TC-FUND-003 Cover Image, Aggregates & Public Campaign Page (#661/#662) · detailed