Revised architecture — no persistent web portal, all off-kiosk interactions via one-time magic links
Developer comments: rather than building and maintaining a permanent public donation portal, all off-kiosk donor interactions happen through one-time magic links. This is a simpler, more secure architecture that solves the same problems.
3 separate flows: Kiosk Tap-to-Pay, QR Follow-Up page, and a permanent Web Portal at /donate/[campaignId]. The web portal was a standalone responsive checkout page that anyone could visit directly.
2 flows, 1 link pattern: Kiosk Tap-to-Pay (same), plus a Magic Link that serves every off-kiosk need — Gift Aid capture, recurring setup, donor information updates. No persistent public URL. Every link is tokenised, one-time use, and expires.
/link/[token] serves all purposes. The token payload determines what the page shows: Gift Aid form, recurring setup, information update, or a combination.magicLinkTokens collection with: donationId, amount, campaignId, purpose (gift_aid | recurring | info_update | gift_aid_and_recurring), expiresAt, status: pending.https://app.swiftcause.com/link/[token]purpose field determines what the page renders. See individual flows below for each scenario.| Purpose | What It Shows | When Created | Donor Action |
|---|---|---|---|
| gift_aid | Gift Aid declaration form only (name, address, postcode, HMRC consent) | After kiosk tap-to-pay when donor didn’t complete Option A | Fill in details, tick declaration, submit |
| recurring | Recurring setup: frequency selector + Payment Element to store payment method | Donor expressed interest in recurring on kiosk, or admin sends invitation | Choose frequency, enter card/wallet, confirm |
| gift_aid_and_recurring | Combined: Gift Aid form + recurring setup + Payment Element | Kiosk flow where both are needed (most common for engaged donors) | Fill Gift Aid, choose frequency, pay, submit all |
| info_update | Pre-filled donor details with edit capability | Admin sends update request, or donor requests via earlier link | Review, edit if needed, confirm |
/link/[token]), one page component, one backend validator. The token payload drives what renders. No separate pages for Gift Aid vs recurring vs updates. This replaces both the old “QR Follow-Up” flow AND the “Web Portal” flow with a single, simpler system.The primary high-footfall flow. Donor at a kiosk (Android handset or fixed device). The kiosk flow itself is largely unchanged — the key difference is what happens after payment when the donor hasn’t completed Gift Aid on-screen.
donationFlowConfig from campaign. Routes to Option A (on-screen GA), Option B+ (pay first, magic link after), or Hybrid (donor chooses).gift_aid (or gift_aid_and_recurring if recurring was selected).Donor scans QR on kiosk Thank You screen (or receives link via NFC/email/receipt). Opens a one-time page on their phone to provide Gift Aid details for the donation they just made. This is the most common magic link use case.
https://app.swiftcause.com/link/[token]. Opens in phone browser (Safari/Chrome). No app needed. No login.Stripe Terminal cannot store payment methods for future charges, so recurring donations are impossible on-kiosk. The magic link solves this: the donor taps once on the kiosk (one-time payment), then opens the magic link on their phone to set up recurring via Payment Element (which can store payment methods).
gift_aid_and_recurring (or just recurring if Gift Aid was already captured on-screen).When a donor who has previously given through SwiftCause returns — either on the kiosk or via a magic link — the system recognises them by email and can pre-fill or auto-apply their existing Gift Aid consent.
The complete picture: every donor path from kiosk to outcome, showing how the magic link model connects them.
Updated to reflect the magic link model. The old “Web Portal” column is removed. Magic link columns replace it.
| Data Field | HMRC Required? | Kiosk Option A (on-screen) | Kiosk Option B+ (at kiosk) | Magic Link: Gift Aid | Magic Link: Recurring | Magic Link: GA + Recurring |
|---|---|---|---|---|---|---|
| First Name | Yes | Form | No | Form | Not needed | Form |
| Surname | Yes | Form | No | Form | Not needed | Form |
| Recommended | Form | No | Form | For subscription | Form | |
| Home Address | Yes | Form | No | Form + Lookup | Not needed | Form + Lookup |
| Postcode | Yes | Form | No | Form + PAF | Not needed | Form + PAF |
| Gift Aid Consent | Yes | Checkbox | No | Checkbox | N/A | Checkbox |
| UK Taxpayer Confirm | Yes | Combined | No | Checkbox | N/A | Checkbox |
| HMRC Declaration Text | Ch.3 | Shown | No | Shown | N/A | Shown |
| Declaration Type | Yes | Missing (fix) | No | Radio | N/A | Auto: past_and_future |
| GDPR Consent | GDPR | Missing (fix) | No | Checkbox | Checkbox | Checkbox |
| Future Consent | Optional | Missing (fix) | No | Checkbox | N/A | Auto: true |
| Payment Method | No | Terminal | Terminal | Already paid | Payment Element | Payment Element |
| Stored PM (recurring) | No | Impossible | Impossible | N/A | Yes | Yes |
| Recurring Frequency | No | N/A | N/A | N/A | Selector | Selector |
| IP Address | Audit | Missing (fix) | No | Server-side | Server-side | Server-side |
| Capture Method | Audit | Missing (fix) | N/A | POST_DONATION_MAGIC_LINK | N/A | POST_DONATION_MAGIC_LINK |
| Donation Amount | Yes (pence) | Auto | Auto | From token | Pre-filled | From token |
| Tax Year | Yes | Bug (GA-01) | Bug | Fixed helper | Fixed helper | Fixed helper |
| Item | v1 (Previous) | v2 (Magic Link Model) |
|---|---|---|
| Off-kiosk flows | 2 separate: QR Follow-Up + Web Portal | 1 unified: Magic Link |
| Routes to build | /gift-aid/token/[id] + /donate/[campaignId] | /link/[token] only |
| Public campaign checkout | Yes — anyone can visit | No — token required (more secure) |
| Recurring setup | Separate from Gift Aid capture | Combined in one magic link if needed |
| Token purposes | 1 (gift_aid only) | 4 (gift_aid, recurring, gift_aid_and_recurring, info_update) |
| Backend handlers | 2 (createWebPaymentIntent + token validator) | 1 (magic link resolver that handles all purposes) |
| Wallet pre-fill (name/email) | Web portal only | Magic link recurring flow (Payment Element) |
| Campaign discovery on web | Yes (via /donate/[campaignId]) | No — all discovery happens on kiosk or via charity’s own website |
Phased plan to ship SwiftCause v1 with full kiosk-to-magic-link flow. Each phase unlocks the next. Issues and PRs are mapped to the specific v2 flow steps they resolve. Repo: YNVSolutions/SwiftCause_Web
Must-fix before taking real money. These are pre-existing P0/P1 issues that block every subsequent phase. No feature work until these land.
Also in Phase 0 (P1 — parallel track):
Build the token system that everything else depends on. Backend-heavy. Can start frontend work in parallel once the API contract is defined.
magicLinkTokens collection, token creation in payment webhook, public validation endpoint, token completion endpoint./link/[token], token validation on load, context screen (“Thank you for £X”), purpose-driven content switching, error states, mobile-first design.Connect the kiosk to the magic link. Build the shared Gift Aid form. After this phase, the core “kiosk → QR → phone → Gift Aid” journey works end-to-end.
donationFlowConfig on campaign doc, kiosk Flow Router component.captureMethod tracking. Used on BOTH kiosk (Option A) and magic link page.The revenue multiplier. After this phase, kiosk donors can become recurring givers through the same magic link. This is impossible with Terminal alone — the magic link unlocks it.
handleInvoicePaid webhook linking future donations to Gift Aid declaration.gift_aid_and_recurring: Gift Aid form (from #590) renders first, then recurring frequency + Payment Element. Declaration type auto-set to past_and_future. One submission covers both.Harden, test, and prepare for production launch. No new features — this is about confidence and operational readiness.
Separate phase. Some backend work overlaps with magic link infrastructure already built. Not required for go-live.
Also deferred: #577–#583 (admin pagination, skeleton loading, dashboard performance).
| Issue | Phase | Blocked By | Resolves Flow |
|---|---|---|---|
| #525 Webhook idempotency | Phase 0 | None | Architecture (token creation safety) |
| #524 Abuse protection | Phase 0 | None | All public endpoints |
| #521/522 Subscription auth | Phase 0 | None | Flow 3 (recurring safety) |
| #591 Returning donor consent fix | Phase 0 | #590 | Flow 4, step 3 |
| #585 Data capture gap fixes | Phase 0 | None | Data Capture Matrix |
| #587 Magic link token system | Phase 1 | #525 | Architecture steps 1–6 |
| #589 /link/[token] page | Phase 1 | #587 | Flow 2 steps 1–3, Flow 3 steps 1–2 |
| #586 Flow Router + config | Phase 2 | None | Flow 1, step 3 |
| #590 Gift Aid form component | Phase 2 | #585 | Flow 2 step 4b, Matrix |
| #588 QR Thank You screen | Phase 2 | #587, #586 | Flow 1, step 6 |
| #592 Recurring via magic link | Phase 3 | #587, #589, #521/522 | Flow 3, all steps |
| #531 Automated tests | Phase 4 | All above | Go-live confidence |
| #467 Gift Aid classification | Phase 4 | #590 | R68 CSV accuracy |
| #593 Confirmation emails | Phase 4 | #587, #590, #592 | Flow 2 step 6, Flow 3 step 6 |
| #594 Admin unclaimed tokens view | Phase 4 | #587 | Combined Journey — “Donor doesn’t scan” step C |
| #595 Automated email follow-up | Phase 4 | #587 | Combined Journey — “Donor doesn’t scan” step B |
What to build, in what order, and why — based on the confirmed v2 Magic Link plan. 30 March 2026 — From: Qamar (PM)
PR #567 — Kiosk sync fix
Across Phase 0–4 + Deferred
0 of 7 P0 issues have PRs or assignees
The team confirmed the v2 Magic Link flows on 25 March. The documentation gate is closed. We are now cleared to begin development sprints. This briefing tells you exactly what to build.
Go-live requires Phases 0 through 4 to be complete. Phase 2 is the minimum viable product — after Phase 2, the core kiosk-to-magic-link journey works end-to-end. Phase 3 adds recurring. Phase 4 adds email, testing, and admin tools.
This is the longest dependency chain. A delay to any issue on this chain delays go-live by the same amount.
Parallel chain (converges at Phase 3): #521/#522 (subscription auth) → #592 (recurring via magic link). This also depends on #587 and #589 from the main chain.
| Issue | Title | Depends On | Blocks | Can Start Now |
|---|---|---|---|---|
| #525 P0 | Make Stripe webhook idempotency atomic | Nothing | #587, #592, all of Phase 1+ | YES |
| #524 P0 | Add abuse protection to public payment endpoints | Nothing | Magic link page security | YES |
| #521/#522 P0 | Enforce AuthN/AuthZ on recurring subscription endpoints | Nothing | #592 (recurring via magic link) | YES |
| #585 P0 | Fix Gift Aid data capture gaps (GDPR, declaration type, IP, capture method) | Nothing | #590, #591 | YES |
| #598 P0 NEW | Extend GiftAidDeclaration model + DonorRecord collection + skip logic | Nothing | #590, #591, #467 | YES |
| #596 P0 NEW | Preserve donor form input on payment failure | Nothing | Go-live | YES |
| #597 P0 NEW | Campaign should not auto-stop at 100% funding | Nothing | Go-live | YES |
| Issue | Title | Depends On | Blocks | Can Start Now |
|---|---|---|---|---|
| #590 P0 | HMRC-compliant Gift Aid form component (shared: kiosk + magic link) | #585, #598 | #591, Flow 2/4 | No — needs #585, #598 first |
| #591 P0 | Fix returning donor auto-consent (LEGAL RISK) | #590 | Flow 4 | No — needs #590 first |
| Issue | Title | Status |
|---|---|---|
| #568 | Change Password flow | PR open — needs review NOW |
| #526 | Replace permissive CORS with explicit allowlist | Open, unassigned |
| #527 | Remove or redact PII from backend logs | Open, unassigned |
| #276 | Implement logging utility (suppress logs in prod) | Open, unassigned |
| #603 NEW | Verify Stripe SCA/3DS for Terminal + Payment Element | Open, unassigned |
| #604 INFRA NEW | Set up Firebase test/staging environment | Open, unassigned |
| #605 INFRA NEW | Set up production SendGrid account + email templates | Open, unassigned |
| #602 NEW | Admin KYC status screen + manual approval gate | Open, unassigned |
| Issue | Title | Depends On | What It Delivers |
|---|---|---|---|
| #587 | Magic Link token model, creation on webhook, validation API | #525 | Firestore magicLinkTokens collection, token creation in payment webhook, public validation endpoint, token completion endpoint, 30-day expiry lifecycle |
| #589 | /link/[token] page — route, context screen, dynamic renderer | #587 | Public route /link/[token], token validation on load, context screen, purpose-driven content switching, completed/expired/invalid states, mobile-first design |
Exit criteria: Token lifecycle works end-to-end. /link/[token] page loads and validates.
| Issue | Title | Depends On | What It Delivers |
|---|---|---|---|
| #586 | Add donationFlowConfig and Flow Router to campaign settings | Nothing (can start in Phase 1) | Admin UI to configure flow type per campaign (Option A / B+ / Hybrid), kiosk Flow Router component, QR enable/disable toggle |
| #590 | HMRC-compliant Gift Aid form component (shared: kiosk + magic link) | #585, #598 | Reusable form: all HMRC fields, postcode lookup (#599), declaration type selector, GDPR consent, IP capture, captureMethod tracking |
| #599 NEW | Postcode lookup API integration | Nothing (can start any time) | PostcodeLookup React component, backend proxy, fallback to manual entry |
| #588 | Kiosk Thank You screen with QR code | #587, #586 | 4-region Thank You layout: celebration, QR code (200×200px min), instructions, Done + countdown |
| #591 | Returning donor confirmation screen | #590 | “Welcome back!” screen, address summary, “Yes, these are correct” / “Update my details” buttons, fresh consent |
| Issue | Title | Depends On | What It Delivers |
|---|---|---|---|
| #592 | Magic link recurring setup — frequency, Payment Element, subscription | #587, #589, #521/#522, #525 | Frequency selector (monthly/quarterly/yearly), Stripe Payment Element on phone, Apple Pay/Google Pay, subscription creation, handleInvoicePaid webhook linking future donations to existing Gift Aid declaration |
| Issue | Title | Depends On |
|---|---|---|
| #531 | Automated tests for critical auth/payment/email flows | All above + #604 (test environment) |
| #467 | Gift Aid classification tracks & threshold-based extraction (incl. GASDS auto-classify for unclaimed ≤£30) | #590 |
| #600 NEW | Admin HMRC Gift Aid export — manual Excel/XML, 999-record batch limit | #467, #585, #598 |
| #601 NEW | Gift Aid health check admin screen | #585, #467, #587 |
| #593 | Confirmation emails for magic link completions | #587, #590, #592, #605 |
| #594 | Admin view of unclaimed tokens + manual re-send | #587, #605 |
| #595 | Automated email follow-up for pending tokens (24/48hr) | #587, #605 |
These issues have zero upstream dependencies. They are ready for assignment and development right now.
| Issue | Title | Type | Impact |
|---|---|---|---|
| #525 | Webhook idempotency | P0 | Unblocks entire critical path |
| #585 | Gift Aid data capture gaps | P0 | Unblocks Gift Aid form (#590) |
| #598 | GiftAidDeclaration model + DonorRecord collection | P0 | Unblocks #590, #591, #467 |
| #524 | Abuse protection on public endpoints | P0 | Secures magic link page |
| #521/#522 | Subscription auth hardening | P0 | Unblocks recurring (#592) |
| #596 | Preserve donor input on payment failure | P0 | Go-live blocker |
| #597 | Campaign auto-stop bug at 100% | P0 | Go-live blocker |
| #604 | Firebase test/staging environment | INFRA | Unblocks E2E testing (#531) |
| #605 | SendGrid production setup | INFRA | Unblocks all email issues |
| #603 | Stripe SCA/3DS verification | P1 | Unblocks recurring (#592) |
| #599 | Postcode lookup API | P1 | Needed by Gift Aid form (#590) |
| #586 | Flow Router + donationFlowConfig | P1 | Admin config for flow type per campaign |
| #602 | KYC admin screen + manual approval | P1 | Compliance for org onboarding |
| Document | Where | What It Contains |
|---|---|---|
| v2 Magic Link User Flows | See tabs: Architecture Change through Sprint Plan | All 4 flows, data capture matrix, dependency map, sprint plan, combined journey map |
| Gift Aid Data Model Spec | Slack #charity_project, 13 March 2026 | GiftAidDeclaration interface, DonorRecord collection, declarationType, skip logic. Must read before starting #585, #598, or #590. |
| Recurring Donations Handover | Slack #charity_project, 7 January 2026 (Yuvraj) | Full backend/frontend architecture for subscriptions, webhook handlers, branch: customAmountRecurringDonations. Must read before starting #525 or #592. |
| 13 Feb Meeting Notes | Slack #charity_project, 13 February 2026 | KYC decision, Gift Aid GASDS/Standard/Future tracks, HMRC 999-record batch limit, P0 bugs (#596, #597) |
| Issues | Description | Why Deferred |
|---|---|---|
| #569–#576 | Donor portal (magic link login, subscription management, donation history, Stripe customer portal) | The v2 architecture deliberately removed the persistent web portal. Go-live uses magic links only. The donor portal is a separate product phase that reuses magic link infrastructure (#587) once it is built and proven. Building it now would split development capacity across two surfaces when the team needs to focus on one. |
| #577–#583 | Admin performance (cursor-based pagination, skeleton loading, dashboard optimisation) | These are quality-of-life improvements for the admin panel. The admin panel works today — it is slow with large datasets but functional. At go-live with the first 10 customers (per 22 March meeting), data volumes will be small. These become necessary at scale, not at launch. |
| #528–#530 | Firestore rules CI, Cloud Functions runtime unification, hardcoded URL removal | Technical debt cleanup. Important for long-term maintainability but does not affect any user-facing flow or compliance requirement. Doing this now risks destabilising the codebase before go-live (the 22 March meeting explicitly said stabilisation first, then structural changes). |
| #532–#534 | Admin subscription cancellation workflow (backend, UI, notifications) | Subscription cancellation requires the subscription system to be live first (#592). Even after #592 ships, cancellation can be handled manually via the Stripe dashboard for the initial customer cohort. A dedicated admin workflow is a Phase 2 admin feature, not a go-live requirement. |
| NFC delivery | Magic link via NFC push from handset (noted on #588, future enhancement) | QR code is the primary delivery method and is simpler to implement and universally supported. NFC requires device compatibility checks and adds complexity. It can be layered on after QR is proven. Noted on #588 for future pickup. |