SwiftCause User Flows v2: Magic Link Model

Revised architecture — no persistent web portal, all off-kiosk interactions via one-time magic links

v2.1 — 30 March 2026 — Magic link architecture + Developer Briefing (merged)

What Changed: Web Portal → Magic Link

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.

Previous Model (v1)

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.

vs
Revised Model (v2)

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.

Why This Is Better

Simpler to Build

1 route
One dynamic page at /link/[token] serves all purposes. The token payload determines what the page shows: Gift Aid form, recurring setup, information update, or a combination.

More Secure

Tokenised
No open public checkout URL. Every link is cryptographically unique, single-use, and expires (30 days). Can’t be guessed, crawled, or abused.

Context-Aware

Pre-loaded
The link already knows: which donation it relates to, the amount, the campaign, and what action is needed. No need for the donor to re-enter anything the system already has.

Less to Maintain

–1 flow
No campaign discovery, no amount selection, no public routing on the web page. The kiosk handles all of that. The magic link just collects what’s missing.

How the Magic Link Works

Magic Link Lifecycle
1Token Created
When: on payment completion (webhook) if Gift Aid wasn’t captured on-screen, OR when admin/kiosk operator triggers a follow-up. Stored in magicLinkTokens collection with: donationId, amount, campaignId, purpose (gift_aid | recurring | info_update | gift_aid_and_recurring), expiresAt, status: pending.
2Link Delivered to Donor
Delivery methods (one or more): QR code on kiosk Thank You screen, NFC push (if supported), printed on receipt, or emailed if donor email was captured at any point. URL format: https://app.swiftcause.com/link/[token]
3Donor Opens Link on Their Phone
Public page, no login needed. Validates token (exists, not expired, not already used). Shows context: “Thank you for your £X donation to [Campaign].”
4Dynamic Content Based on Purpose
The token’s purpose field determines what the page renders. See individual flows below for each scenario.
5Donor Completes Action
Submits form. Token marked as “completed”. Link becomes single-use — revisiting shows a “You’ve already completed this” confirmation page with a summary.
6Confirmation + Email
Thank you page on phone. Confirmation email with Gift Aid declaration summary and/or recurring schedule. Token status: completed.

Token Purpose Types

PurposeWhat It ShowsWhen CreatedDonor Action
gift_aidGift Aid declaration form only (name, address, postcode, HMRC consent)After kiosk tap-to-pay when donor didn’t complete Option AFill in details, tick declaration, submit
recurringRecurring setup: frequency selector + Payment Element to store payment methodDonor expressed interest in recurring on kiosk, or admin sends invitationChoose frequency, enter card/wallet, confirm
gift_aid_and_recurringCombined: Gift Aid form + recurring setup + Payment ElementKiosk flow where both are needed (most common for engaged donors)Fill Gift Aid, choose frequency, pay, submit all
info_updatePre-filled donor details with edit capabilityAdmin sends update request, or donor requests via earlier linkReview, edit if needed, confirm
Key Architectural DecisionOne route (/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.

Flow 1: Kiosk Tap-to-Pay

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.

Dev Work: #issue = GitHub issue to build  |  P0 = Security/compliance blocker  |  PR = Pull request
AS-IS — Today
1Campaign ListBUILT
2Campaign Detail + AmountBUILT
3Gift Aid Boost PanelBUILT
“Boost your donation” — Yes/No.
Donor taps “Yes, Boost”?
Yes → Option A
Email gate → Gift Aid form → declaration saved.
No → Skip
Straight to payment. No Gift Aid. No follow-up.
4Stripe Terminal PaymentBUILT
5Basic Result Screen
Success/fail only. No QR. No options. Flow ends.
ProblemIf the donor skips Gift Aid (or the admin has Option B+ configured), there is no way to capture it after payment. The donation is anonymous forever. Money left on the table.
TO-BE — With Magic Link
1Campaign ListNO CHANGE
2Campaign Detail + AmountNO CHANGE
3Flow RouterUPDATED
Reads donationFlowConfig from campaign. Routes to Option A (on-screen GA), Option B+ (pay first, magic link after), or Hybrid (donor chooses).
Flow type?
Option A
Same: email gate → Gift Aid form (with new fields) → declaration saved before payment.
Option B+
Skip form. Straight to Terminal. Gift Aid via magic link after.
4Stripe Terminal PaymentNO CHANGE
5Webhook + Magic Link TokenUPDATED
On payment success: if Gift Aid NOT captured on-screen, create magic link token with purpose gift_aid (or gift_aid_and_recurring if recurring was selected).
6Thank You Screen + Magic LinkNEW
4-region kiosk layout: celebration message, QR code (encodes magic link URL), “Done” button, countdown timer. QR sized 200×200px minimum for easy scanning.
7Donor Scans → Magic Link FlowMAGIC LINK
Opens on donor’s phone. See Flow 2 or Flow 3 depending on token purpose.
ResultEvery kiosk donation now has a path to Gift Aid capture — either on-screen (Option A) or via magic link (Option B+). The magic link also enables recurring setup, which was impossible on Terminal alone.

Flow 2: Magic Link — Gift Aid Capture

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.

Dev Work: #issue = GitHub issue to build  |  P0 = Security/compliance blocker  |  PR = Pull request
Key ConstraintThis is a post-payment flow. The donor has already paid on the kiosk via Stripe Terminal. No second payment happens here. The magic link page collects HMRC-required donor identity and declaration only.
Magic Link: Gift Aid Capture (purpose: gift_aid)
1Donor Opens Magic Link
URL: https://app.swiftcause.com/link/[token]. Opens in phone browser (Safari/Chrome). No app needed. No login.
2Token ValidationNEW
Backend checks: token exists, status is “pending”, not expired. Returns donation context: amount, campaign name, charity name. If invalid: friendly error (“This link has expired or already been used”).
3Context ScreenNEW
“Thank you for your £25.00 donation to [Campaign]! You can boost your donation by 25% at no cost to you through Gift Aid.” Shows Gift Aid value: £6.25. CTA: “Add Gift Aid”.
Is this a returning donor? (email lookup)
Yes — Profile Found
4aConfirmation ScreenRETURNING
“We have your Gift Aid details from [date]. Still correct?” Shows name + address summary. Two options: “Yes, use these” or “Update my details”.
No — New Donor
4bGift Aid FormNEW
Full HMRC-compliant form: name, email, house number, address, town, postcode (with lookup), HMRC declaration text, consent checkbox, GDPR checkbox, declarationType selector.
5Declaration Created + Token CompletedNEW
Creates Gift Aid declaration in Firestore linked to original donationId. Sets captureMethod: POST_DONATION_MAGIC_LINK. Marks token as “completed”. Updates donation doc: isGiftAidCaptured: true.
6Thank You + Confirmation EmailNEW
“Your £25.00 donation now qualifies for £6.25 Gift Aid!” Confirmation email with declaration text, charity name, HMRC responsibility notice (GA-04).
If the Donor Doesn’t ScanThe token stays “pending” for 30 days. If the charity captured the donor’s email during any prior interaction, an automated follow-up email can be sent after 24/48 hours with the same magic link. If the token expires unused, the donation remains GASDS-eligible (if ≤£30) but not Gift Aid-eligible.

Flow 3: Magic Link — Recurring Setup

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).

issue = GitHub issue (to build)PR = Pull request (in progress / merged)P0 = Security / compliance blocker
This is the only way to get recurring from a kiosk flow.Terminal = card-present = no stored payment method. Payment Element = card-not-present = stored payment method = recurring possible.
Magic Link: Gift Aid + Recurring (purpose: gift_aid_and_recurring)
1Donor Opens Magic Link
Same URL pattern. Token has purpose: gift_aid_and_recurring (or just recurring if Gift Aid was already captured on-screen).
#589 /link/[token] page — routes by token purpose
2Context + Recurring PitchNEW
“Thank you for your £25.00 donation! Would you like to make this a regular gift? Monthly donors help [Campaign] plan ahead and have twice the impact.”
#589 Context screen inside /link/[token]
Token purpose includes Gift Aid?
Yes — gift_aid_and_recurring
Gift Aid form shown first (same as Flow 2 step 4). declarationType defaults to “past_and_future” since this is a recurring setup.
#590 Gift Aid form component #585 declarationType radio
No — recurring only
Skip Gift Aid form. Existing declaration already covers future donations.
3Recurring Frequency SelectorNEW
Monthly (default), Quarterly, Yearly. Amount pre-filled from original donation. Donor can adjust.
#592 Recurring via magic link — frequency UI
4Stripe Payment ElementNEW
Payment Element on donor’s phone. Apple Pay / Google Pay / card entry. Stores payment method for future charges. Creates Stripe Customer + Subscription.
#592 Payment Element + subscription creation #521#522 Subscription auth (P0 blocker)
5Subscription Created + Token CompletedNEW
Backend creates Stripe subscription. Gift Aid declaration (if created) has declarationType: “past_and_future” — covers all future invoices. Token marked completed.
#592 Backend subscription + token completion #587 Token lifecycle management
6ConfirmationNEW
“You’re now giving £25/month to [Campaign]. With Gift Aid, that’s £31.25/month of impact!” Email with schedule, next charge date, cancellation info.
#593 Confirmation emails

How Future Invoices Are Handled

Recurring Invoice Processing (Automatic)
1Stripe Charges Next Invoice
Automatic monthly/quarterly/yearly charge on stored payment method.
#592 Stripe subscription config
2handleInvoicePaid Webhook
Creates donation doc for this invoice. Checks for existing Gift Aid declaration with declarationType: “past_and_future”.
#525 Webhook idempotency (P0 blocker)
3Link Donation to Existing Declaration
Sets giftAidDeclarationId on new donation doc pointing to original declaration. No new declaration created. One declaration, many donations.
#592 Declaration linking logic
4CSV Export Includes All Linked Donations
When treasurer generates R68 CSV, all donations linked to the declaration appear as separate rows with the same donor details.
#592 R68 export update for recurring

Flow 4: Returning Donor Recognition

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.

issue = GitHub issue (to build)PR = Pull request (in progress / merged)P0 = Security / compliance blocker
AS-IS — Today
1Email Gate (Kiosk)BUILT
After “Yes, Boost” — asks for email.
2Profile LookupBUILT
getReusableGiftAidProfileByEmail() queries declarations by normalised email. Returns most recent with valid consent + address.
!Auto-Submit Without ConfirmationLEGAL RISK
GiftAidPage.tsx auto-creates GiftAidDetails and calls onAcceptGiftAid() directly. Hardcodes dataProcessingConsent: true, homeAddressConfirmed: true. No “still correct?” screen. If donor has moved house or changed tax status, this creates an invalid HMRC declaration.
#591 Returning donor consent fix (P0 — HMRC compliance)
TO-BE — With OTP Verification + Confirmation
1Email EntryNO CHANGE
On kiosk (email gate) or on magic link page (email field).
2Email OTP VerificationNEW — 13 Apr Decision
CRITICAL (13 Apr standup decision): 6-digit OTP sent to entered email. Donor enters OTP on same page (no context switch). Firebase Auth native support. 10-minute expiry.

If verified: proceed to profile lookup + confirmation screen.
If skipped/failed: treat as new donor — blank form only. Gift Aid still claimable (HMRC doesn't require email verification). No profile update, no returning donor recognition.

Why OTP: Current magic link page exposes stored donor data to anyone who enters an email — GDPR/data-protection risk. OTP gates access to stored data without adding friction to new donors.
3Profile Lookup (verified only)UPDATED
Only runs if email verified via OTP. Unverified emails always see blank form.
4Confirmation Screen (verified returning donors)NEW
“Welcome back! We have your Gift Aid details from [date]:”
John Smith
123 Main Street, London, SW1A 1AA

Two buttons: “Yes, these are correct” (submits with re-confirmed consent) and “Update my details” (opens pre-filled editable form).
#591 New confirmation screen — replaces auto-submit
Donor response?
Confirmed
Declaration created with fresh timestamp and explicit re-consent. Old details reused. Fast path — 2 taps total.
#591 Re-consent with fresh timestamp
Update
Pre-filled form opens. Donor edits address/name. Old declaration stays valid for old donations. New declaration created for this donation.
#590 Gift Aid form (pre-filled edit mode)
Works on Both SurfacesThe returning donor confirmation screen is the same component whether rendered on the kiosk (Option A flow) or on the donor’s phone (magic link flow). Build once, use everywhere.

Combined Journey Map

The complete picture: every donor path from kiosk to outcome, showing how the magic link model connects them.

issue = GitHub issue (to build)PR = Pull request (in progress / merged)P0 = Security / compliance blocker
Full Donor Journey — All Paths
1Donor arrives at kiosk
2Selects campaign + amount
#586 Campaign-level Option B toggle (admin)
Admin-configured flow type for this campaign?
#586 Admin toggle determines which path
Option A: On-Screen Gift Aid
3aGift Aid boost → form → declaration saved
Full on-screen capture before payment. Works for patient donors. High footfall = fewer completions.
#585 declarationType radio (missing from kiosk)
4aTerminal payment
5aDone. Gift Aid captured.
Gift Aid: Yes
Option B+: Pay First, Link After
3bSkip Gift Aid form
#586 Option B flow bypass
4bTerminal payment (fast)
5bThank You screen + QR magic link
#588 QR code on Thank You screen #587 Token generation backend
6bDonor scans → phone page
Gift Aid form + optional recurring setup on donor’s own device. No queue pressure.
#589 /link/[token] page #590 Gift Aid form #592 Recurring setup
Gift Aid: Via linkRecurring: Via link
Hybrid: Donor Chooses
3c“Add Gift Aid now or on your phone?”
#586 Hybrid mode config
If “Now”: follows Option A path.
If “On my phone”: follows Option B+ path.
Best of both worlds but slightly more complex UI.
Gift Aid: Either wayRecurring: Via link only

What Happens If Donor Doesn’t Scan the QR?

AToken stays pending (30 days)
The donation is recorded. If ≤£30, it’s automatically GASDS-eligible.
#587 Token lifecycle — 30-day expiry
BOptional: Email follow-up (if email known)
If the charity has the donor’s email from any prior interaction, an automated email can be sent 24–48 hours later containing the same magic link.
#595 Automated email follow-up (Phase 4)
COptional: Admin retroactive invite
Treasurer can see “unclaimed” donations in admin and manually trigger magic link emails if they later obtain the donor’s contact details.
#594 Admin unclaimed tokens view (Phase 4)

Data Capture Matrix (Revised)

Updated to reflect the magic link model. The old “Web Portal” column is removed. Magic link columns replace it.

Data FieldHMRC Required?Kiosk Option A
(on-screen)
Kiosk Option B+
(at kiosk)
Magic Link:
Gift Aid
Magic Link:
Recurring
Magic Link:
GA + Recurring
First NameYesFormNoFormNot neededForm
SurnameYesFormNoFormNot neededForm
EmailRecommendedFormNoFormFor subscriptionForm
Home AddressYesFormNoForm + LookupNot neededForm + Lookup
PostcodeYesFormNoForm + PAFNot neededForm + PAF
Gift Aid ConsentYesCheckboxNoCheckboxN/ACheckbox
UK Taxpayer ConfirmYesCombinedNoCheckboxN/ACheckbox
HMRC Declaration TextCh.3ShownNoShownN/AShown
Declaration TypeYesMissing (fix)NoRadioN/AAuto: past_and_future
GDPR ConsentGDPRMissing (fix)NoCheckboxCheckboxCheckbox
Future ConsentOptionalMissing (fix)NoCheckboxN/AAuto: true
Payment MethodNoTerminalTerminalAlready paidPayment ElementPayment Element
Stored PM (recurring)NoImpossibleImpossibleN/AYesYes
Recurring FrequencyNoN/AN/AN/ASelectorSelector
IP AddressAuditMissing (fix)NoServer-sideServer-sideServer-side
Capture MethodAuditMissing (fix)N/APOST_DONATION_MAGIC_LINKN/APOST_DONATION_MAGIC_LINK
Donation AmountYes (pence)AutoAutoFrom tokenPre-filledFrom token
Tax YearYesBug (GA-01)BugFixed helperFixed helperFixed helper
Simplification WinThe magic link model means we only need one off-kiosk page that dynamically shows the right combination of forms. The data capture is context-driven by the token’s purpose field, not by which URL the donor landed on.

What Changed from v1

Itemv1 (Previous)v2 (Magic Link Model)
Off-kiosk flows2 separate: QR Follow-Up + Web Portal1 unified: Magic Link
Routes to build/gift-aid/token/[id] + /donate/[campaignId]/link/[token] only
Public campaign checkoutYes — anyone can visitNo — token required (more secure)
Recurring setupSeparate from Gift Aid captureCombined in one magic link if needed
Token purposes1 (gift_aid only)4 (gift_aid, recurring, gift_aid_and_recurring, info_update)
Backend handlers2 (createWebPaymentIntent + token validator)1 (magic link resolver that handles all purposes)
Wallet pre-fill (name/email)Web portal onlyMagic link recurring flow (Payment Element)
Campaign discovery on webYes (via /donate/[campaignId])No — all discovery happens on kiosk or via charity’s own website

Critical-Path Sprint Plan — Go-Live: Kiosk + Magic Link

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

Go-Live Definition (Option B)Kiosk Tap-to-Pay → Payment → QR Magic Link → Donor opens on phone → Gift Aid captured (and optionally recurring setup). Every kiosk donation has a second-chance path to Gift Aid. Donor portal is a separate later phase.
Phase 0: Security & Compliance Foundation

Must-fix before taking real money. These are pre-existing P0/P1 issues that block every subsequent phase. No feature work until these land.

P0#525 — Make Stripe webhook idempotency atomicBLOCKER
Blocks: Magic link token creation (if webhook fires twice, duplicate tokens). Blocks #587 and all of Phase 2.
P0#524 — Add abuse protection to public payment endpointsBLOCKER
Blocks: The magic link page is a public endpoint. Without rate limiting, it’s vulnerable.
P0#521/#522 — Enforce AuthN/AuthZ on recurring subscription endpointsBLOCKER
Blocks: Recurring via magic link (#592). Without auth, anyone could create subscriptions.
P0#591 — Fix returning donor auto-consent (LEGAL RISK)COMPLIANCE
Resolves: Flow 4, step 3. Currently auto-submits Gift Aid without donor confirmation. HMRC non-compliant.
P0#585 — Fix Gift Aid data capture gaps (GDPR, declaration type, IP, capture method)COMPLIANCE
Resolves: Data Capture Matrix “Missing (fix)” cells. Required for valid HMRC declarations on both kiosk and magic link.

Also in Phase 0 (P1 — parallel track):

P1#526 — Replace permissive CORS with explicit allowlist
P1#527 — Remove or redact PII from backend logs
P1#276 — Implement logging utility (suppress logs in prod)
PR#568 — Change Password flow (awaiting review — merge anytime)IN REVIEW
Exit: Webhooks idempotentExit: Endpoints protectedExit: HMRC-compliant declarations
Phase 1: Magic Link Infrastructure

Build the token system that everything else depends on. Backend-heavy. Can start frontend work in parallel once the API contract is defined.

1#587 — Magic Link token model, creation on webhook, validation APICORE
Resolves: Architecture Change — “How the Magic Link Works” steps 1–3, 5–6.
Delivers: Firestore magicLinkTokens collection, token creation in payment webhook, public validation endpoint, token completion endpoint.
Depends on: #525 (webhook idempotency)
2#589 — Magic link /link/[token] page — route, context screen, dynamic rendererCORE
Resolves: Flow 2 steps 1–3, Flow 3 steps 1–2.
Delivers: Public route /link/[token], token validation on load, context screen (“Thank you for £X”), purpose-driven content switching, error states, mobile-first design.
Depends on: #587 (token validation API)
Exit: Token lifecycle works end-to-endExit: /link/[token] page loads and validates
Phase 2: Kiosk Flow Enhancement + Gift Aid Form

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.

1#586 — Add donationFlowConfig and Flow Router to campaign settingsNEW
Resolves: Flow 1, step 3 (TO-BE). The decision point: Option A vs B+ vs Hybrid.
Delivers: Admin UI to configure flow type per campaign, donationFlowConfig on campaign doc, kiosk Flow Router component.
Parallel with: #590, #588
2#590 — HMRC-compliant Gift Aid form component (shared: kiosk + magic link)NEW
Resolves: Flow 2 step 4b, Data Capture Matrix gaps.
Delivers: Reusable form with all HMRC fields, postcode lookup, declaration type selector, GDPR consent, IP capture, captureMethod tracking. Used on BOTH kiosk (Option A) and magic link page.
Depends on: #585 (data capture gap fixes)
3#588 — Kiosk Thank You screen with QR codeNEW
Resolves: Flow 1, step 6 (TO-BE).
Delivers: 4-region Thank You screen (celebration, QR code 200×200px, instructions, Done + countdown), conditional display based on flow type.
Depends on: #587 (token URL to encode), #586 (flow type determines variant)
4#591 — Returning donor confirmation screenFIX
Resolves: Flow 4, step 3 (TO-BE). Fixes the legal risk.
Delivers: “Welcome back!” screen with address summary, “Yes, these are correct” / “Update my details” buttons. Fresh declaration with explicit re-consent. Works on kiosk AND magic link.
Depends on: #590 (Gift Aid form for “Update my details” path)
Milestone: End of Phase 2The complete kiosk → payment → QR → phone → Gift Aid capture journey works. This is the minimum viable go-live. A charity can deploy kiosks, collect tap-to-pay donations, and capture Gift Aid via magic link for every donor who skips it on-screen.
Exit: Flow 1 complete (kiosk end-to-end)Exit: Flow 2 complete (magic link Gift Aid)Exit: Flow 4 complete (returning donor safe)
Phase 3: Recurring via Magic Link

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.

1#592 — Magic link recurring setup (frequency, Payment Element, subscription)NEW
Resolves: Flow 3, all steps. Combined Journey Map — Option B+ path, step 6b.
Delivers: Frequency selector (monthly/quarterly/yearly), Stripe Payment Element on phone, Apple Pay/Google Pay support, subscription creation backend, handleInvoicePaid webhook linking future donations to Gift Aid declaration.
Depends on: #587 (token infra), #589 (magic link page), #521/#522 (auth on subscription endpoints), #525 (webhook idempotency)
2Combined Gift Aid + Recurring flow integrationPART OF #592
When token purpose is 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.
Exit: Flow 3 complete (recurring via magic link)Exit: Combined GA + recurring worksExit: Future invoices auto-link to declarations
Phase 4: Polish, Testing & Go-Live Readiness

Harden, test, and prepare for production launch. No new features — this is about confidence and operational readiness.

1#531 — Add automated tests for critical auth/payment/email flows
End-to-end test coverage for: kiosk payment → webhook → token creation → magic link validation → Gift Aid submit → recurring setup.
2#467 — Gift Aid Classification Tracks & Threshold-Based Extraction
R68 CSV export accuracy, GASDS vs Gift Aid classification, threshold handling.
3#593 — Confirmation emails for magic link completions
Gift Aid confirmation email with declaration summary (GA-04 notice). Recurring confirmation with schedule, next charge date, cancellation info.
4#594 — Admin view of unclaimed tokens + manual re-send
Treasurer can see donations with pending/expired magic link tokens. Can manually trigger email with the magic link.
5#595 — Automated email follow-up for pending tokens (24/48hr)
If donor email is known from prior interaction, auto-send reminder with same magic link after 24–48 hours.
Exit: E2E tests passExit: Emails workingExit: Admin tools readyGO LIVE
Deferred: Donor Portal (Post Go-Live)

Separate phase. Some backend work overlaps with magic link infrastructure already built. Not required for go-live.

-#569 — Donor portal v1 epic (magic link login + Stripe subscription management)
-#570 — Donor magic link authentication flow (shared infra — partially built by #587)
-#571 — Normalize donor identity data + Stripe customer mapping
-#572 — Donor-authorized API for subscription/donation history
-#573 — Donor-authorized Stripe Customer Portal session endpoint
-#574 — Webhook reconciliation for donor portal visibility
-#575 — Standalone donor login at /donor
-#576 — Donor dashboard (subscription history, donation history)

Also deferred: #577–#583 (admin pagination, skeleton loading, dashboard performance).

Dependency Map

IssuePhaseBlocked ByResolves Flow
#525 Webhook idempotencyPhase 0NoneArchitecture (token creation safety)
#524 Abuse protectionPhase 0NoneAll public endpoints
#521/522 Subscription authPhase 0NoneFlow 3 (recurring safety)
#591 Returning donor consent fixPhase 0#590Flow 4, step 3
#585 Data capture gap fixesPhase 0NoneData Capture Matrix
#587 Magic link token systemPhase 1#525Architecture steps 1–6
#589 /link/[token] pagePhase 1#587Flow 2 steps 1–3, Flow 3 steps 1–2
#586 Flow Router + configPhase 2NoneFlow 1, step 3
#590 Gift Aid form componentPhase 2#585Flow 2 step 4b, Matrix
#588 QR Thank You screenPhase 2#587, #586Flow 1, step 6
#592 Recurring via magic linkPhase 3#587, #589, #521/522Flow 3, all steps
#531 Automated testsPhase 4All aboveGo-live confidence
#467 Gift Aid classificationPhase 4#590R68 CSV accuracy
#593 Confirmation emailsPhase 4#587, #590, #592Flow 2 step 6, Flow 3 step 6
#594 Admin unclaimed tokens viewPhase 4#587Combined Journey — “Donor doesn’t scan” step C
#595 Automated email follow-upPhase 4#587Combined Journey — “Donor doesn’t scan” step B

How Each Flow Gets Resolved

Flow 1: Kiosk Tap-to-Pay

Phase 0–2
Steps 1–4: Already built
Step 3 (Flow Router): #586
Step 5 (webhook + token): #587
Step 6 (QR screen): #588
Step 7 (donor scans): #589

Flow 2: Magic Link (Gift Aid)

Phase 1–2
Steps 1–3 (open + validate + context): #589
Step 4a (returning donor): #591
Step 4b (Gift Aid form): #590
Step 5 (declaration + complete): #587
Step 6 (confirmation email): Phase 4

Flow 3: Magic Link (Recurring)

Phase 3
Steps 1–2 (open + pitch): #589
Step 3 (frequency selector): #592
Step 4 (Payment Element): #592
Step 5 (subscription created): #592
Step 6 (confirmation): Phase 4

Flow 4: Returning Donor

Phase 0
Steps 1–2 (lookup): Already built
Step 3 (confirmation screen): #591
“Update my details” path: #590
End of Phase 2 = Minimum Viable Go-LiveAt this point all 4 flows work. Phase 3 (recurring) and Phase 4 (polish) add significant value but are not required to start accepting donations and capturing Gift Aid through the magic link. If timelines are tight, you could go live after Phase 2 and ship recurring as a fast follow.

Developer Briefing — Sprint Kickoff

What to build, in what order, and why — based on the confirmed v2 Magic Link plan. 30 March 2026 — From: Qamar (PM)

1. Where We Are

Last Commit on Main

23 Mar 2026

PR #567 — Kiosk sync fix

Open PR

1

#568 — Change Password (open 5+ days, needs review)

Open Issues

52

Across Phase 0–4 + Deferred

Critical Path Status

Blocked

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.

2. What Go-Live Means

Go-Live Definition (Option B)Kiosk Tap-to-Pay → Payment → QR Magic Link → Donor opens on phone → Gift Aid captured (and optionally recurring setup). Every kiosk donation has a second-chance path to Gift Aid. Donor portal is a separate later phase.

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.

3. The Critical Path

This is the longest dependency chain. A delay to any issue on this chain delays go-live by the same amount.

#525 Webhook Idempotency#587 Token System#589 /link/[token] Page#590 Gift Aid Form#591 Returning Donor Fix#531 E2E TestsGO LIVE

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.

4. Rules for This Sprint

No feature work until Phase 0 P0s land. The v2 plan is explicit: security and compliance issues must be resolved before building magic link features.
Stabilisation PRs first, then new changes. Per 22 March meeting — merge existing fixes and tech debt before adding new functionality.
Recurring stays behind feature toggle. Per 22 January meeting — recurring donation features must not be on the release branch until tested.
PR #568 must be reviewed immediately. It has been open 5+ days with no reviews. It is on the Phase 0 parallel track.

5. Phase 0: Security & Compliance Foundation

Must-fix before taking real money.No feature work until these land. Exit criteria: webhooks idempotent, endpoints protected, HMRC-compliant declarations possible.

P0 — Blockers (must be done first)

IssueTitleDepends OnBlocksCan Start Now
#525 P0Make Stripe webhook idempotency atomicNothing#587, #592, all of Phase 1+YES
#524 P0Add abuse protection to public payment endpointsNothingMagic link page securityYES
#521/#522 P0Enforce AuthN/AuthZ on recurring subscription endpointsNothing#592 (recurring via magic link)YES
#585 P0Fix Gift Aid data capture gaps (GDPR, declaration type, IP, capture method)Nothing#590, #591YES
#598 P0 NEWExtend GiftAidDeclaration model + DonorRecord collection + skip logicNothing#590, #591, #467YES
#596 P0 NEWPreserve donor form input on payment failureNothingGo-liveYES
#597 P0 NEWCampaign should not auto-stop at 100% fundingNothingGo-liveYES

Phase 2: P0 (depends on Phase 1)

IssueTitleDepends OnBlocksCan Start Now
#590 P0HMRC-compliant Gift Aid form component (shared: kiosk + magic link)#585, #598#591, Flow 2/4No — needs #585, #598 first
#591 P0Fix returning donor auto-consent (LEGAL RISK)#590Flow 4No — needs #590 first

P1 — Parallel Track (work alongside P0s)

IssueTitleStatus
#568Change Password flowPR open — needs review NOW
#526Replace permissive CORS with explicit allowlistOpen, unassigned
#527Remove or redact PII from backend logsOpen, unassigned
#276Implement logging utility (suppress logs in prod)Open, unassigned
#603 NEWVerify Stripe SCA/3DS for Terminal + Payment ElementOpen, unassigned
#604 INFRA NEWSet up Firebase test/staging environmentOpen, unassigned
#605 INFRA NEWSet up production SendGrid account + email templatesOpen, unassigned
#602 NEWAdmin KYC status screen + manual approval gateOpen, unassigned

6. Phase 1: Magic Link Infrastructure

Build the token system that everything else depends on.Backend-heavy. Can start frontend work in parallel once the API contract is defined. Cannot start until #525 (webhook idempotency) lands.
IssueTitleDepends OnWhat It Delivers
#587Magic Link token model, creation on webhook, validation API#525Firestore 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#587Public 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.

7. Phase 2: Kiosk Flow Enhancement + Gift Aid Form

End of Phase 2 = Minimum Viable Go-Live.The complete kiosk → payment → QR → phone → Gift Aid journey works. A charity can deploy kiosks, collect tap-to-pay donations, and capture Gift Aid via magic link.
IssueTitleDepends OnWhat It Delivers
#586Add donationFlowConfig and Flow Router to campaign settingsNothing (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
#590HMRC-compliant Gift Aid form component (shared: kiosk + magic link)#585, #598Reusable form: all HMRC fields, postcode lookup (#599), declaration type selector, GDPR consent, IP capture, captureMethod tracking
#599 NEWPostcode lookup API integrationNothing (can start any time)PostcodeLookup React component, backend proxy, fallback to manual entry
#588Kiosk Thank You screen with QR code#587, #5864-region Thank You layout: celebration, QR code (200×200px min), instructions, Done + countdown
#591Returning donor confirmation screen#590“Welcome back!” screen, address summary, “Yes, these are correct” / “Update my details” buttons, fresh consent

8. Phase 3: Recurring via Magic Link

The revenue multiplier.Stripe Terminal cannot store payment methods (card-present). The magic link is the only way to convert a kiosk donor into a recurring giver. This phase unlocks that.
IssueTitleDepends OnWhat It Delivers
#592Magic link recurring setup — frequency, Payment Element, subscription#587, #589, #521/#522, #525Frequency 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

9. Phase 4: Polish, Testing & Go-Live Readiness

IssueTitleDepends On
#531Automated tests for critical auth/payment/email flowsAll above + #604 (test environment)
#467Gift Aid classification tracks & threshold-based extraction (incl. GASDS auto-classify for unclaimed ≤£30)#590
#600 NEWAdmin HMRC Gift Aid export — manual Excel/XML, 999-record batch limit#467, #585, #598
#601 NEWGift Aid health check admin screen#585, #467, #587
#593Confirmation emails for magic link completions#587, #590, #592, #605
#594Admin view of unclaimed tokens + manual re-send#587, #605
#595Automated email follow-up for pending tokens (24/48hr)#587, #605

10. What You Can Start Today

These issues have zero upstream dependencies. They are ready for assignment and development right now.

IssueTitleTypeImpact
#525Webhook idempotencyP0Unblocks entire critical path
#585Gift Aid data capture gapsP0Unblocks Gift Aid form (#590)
#598GiftAidDeclaration model + DonorRecord collectionP0Unblocks #590, #591, #467
#524Abuse protection on public endpointsP0Secures magic link page
#521/#522Subscription auth hardeningP0Unblocks recurring (#592)
#596Preserve donor input on payment failureP0Go-live blocker
#597Campaign auto-stop bug at 100%P0Go-live blocker
#604Firebase test/staging environmentINFRAUnblocks E2E testing (#531)
#605SendGrid production setupINFRAUnblocks all email issues
#603Stripe SCA/3DS verificationP1Unblocks recurring (#592)
#599Postcode lookup APIP1Needed by Gift Aid form (#590)
#586Flow Router + donationFlowConfigP1Admin config for flow type per campaign
#602KYC admin screen + manual approvalP1Compliance for org onboarding

11. Key Reference Material

DocumentWhereWhat It Contains
v2 Magic Link User FlowsSee tabs: Architecture Change through Sprint PlanAll 4 flows, data capture matrix, dependency map, sprint plan, combined journey map
Gift Aid Data Model SpecSlack #charity_project, 13 March 2026GiftAidDeclaration interface, DonorRecord collection, declarationType, skip logic. Must read before starting #585, #598, or #590.
Recurring Donations HandoverSlack #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 NotesSlack #charity_project, 13 February 2026KYC decision, Gift Aid GASDS/Standard/Future tracks, HMRC 999-record batch limit, P0 bugs (#596, #597)

12. Explicitly Deferred — Do NOT Work On These

These are parked until after go-live. Do not let them creep into current sprint scope.
IssuesDescriptionWhy Deferred
#569–#576Donor 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–#583Admin 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–#530Firestore rules CI, Cloud Functions runtime unification, hardcoded URL removalTechnical 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–#534Admin 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 deliveryMagic 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.

13. Immediate Actions

1. Assign and progress PR #568. Change Password flow — open since 21 March, needs a reviewer assigned to move forward.
2. Assign the 7 P0 issues. #525, #524, #521/#522, #585, #598, #596, #597 — all unassigned, all can start now.
3. Read the Slack specs before starting work. The 13 March Gift Aid data model spec and the 7 January recurring handover contain implementation details not in the GitHub issues.
4. Start infrastructure in parallel. #604 (Firebase staging) and #605 (SendGrid) are not on the critical path but block Phase 4. Getting them done early avoids a bottleneck later.
5. Pick up #586 (Flow Router) and #599 (Postcode Lookup) early. They have no upstream dependencies and will be needed the moment Phase 2 starts.