User Donation Flows
This document describes the step-by-step user experience for different donation scenarios.
Prerequisites
All donation flows start at the donation homepage, typically accessed via subdomain:
- Production:
https://donations.aleteia.org - Staging:
https://ga-reports-staging.herokuapp.com
The homepage loads a React single-page application (DonationsApp) that handles the entire donation experience.
Single Card Donation Flow
Step 1: Landing Page
The user arrives at the donation page and sees:
- Email input field (required)
- Currency selector (EUR, USD, or enabled optional currencies)
- Amount selector with preset buttons (e.g., €10, €30, €50, €100)
- Option to enter custom amount
- "Single" vs "Monthly" toggle for donation type
- Campaign parameter (optional, passed via URL query string)
Step 2: User Input
- User enters their email address (e.g.,
[email protected]) - User selects currency (defaults to EUR)
- User chooses donation amount:
- Click preset button (e.g., €30)
- Or enter custom amount (minimum €1.00 equivalent)
- User ensures "Single" is selected (not "Monthly")
Step 3: Payment Method Selection
User clicks one of the payment buttons:
- "Confirm my selection" - Opens Stripe Checkout (legacy)
- "Pay with Card" - Uses Payment Intent flow with Stripe Elements
- "Pay with SEPA" - SEPA Direct Debit (EUR only)
- "Pay with P24" - Przelewy24 (Polish payment method)
For card payment, the modern flow uses Payment Intent with Stripe Elements.
Step 4: Stripe Elements Payment Form
- Frontend calls
/donations/payment_intentAPI endpoint - Server creates
Stripe::PaymentIntentwith:- Amount and currency
- Customer ID (existing or newly created Stripe Customer)
- Metadata (locale, campaign, IP geolocation)
- Frontend receives
client_secret - Modal/embedded form displays Stripe Payment Element
- User enters card details:
- Card number (e.g., test card:
4242 4242 4242 4242) - Expiry date (e.g.,
12/34) - CVC (e.g.,
123) - Billing postal code
- Card number (e.g., test card:
Step 5: 3D Secure Authentication (if required)
For cards requiring Strong Customer Authentication (SCA):
- Stripe redirects to card issuer's authentication page
- User enters authentication code or approves via mobile app
- Authentication result sent back to Stripe
- Payment confirmed or declined
Step 6: Payment Confirmation
- Stripe confirms the payment
- Frontend receives success status
- Success panel displayed: "Thank you for your donation!"
- Page may redirect to Aleteia site (configurable via settings)
Step 7: Backend Processing
- Stripe sends
charge.succeededwebhook to/donations/event ProcessStripeEventJobprocesses the event:- Creates
Donations::Notificationrecord - Creates
Donations::Transactionrecord with:- Amount
- Currency
- Transaction ID (Stripe charge ID)
- Payment date
- Associates transaction with campaign (if specified)
- Creates
- Thank you email sent to donor
- Slack notification sent (if configured)
Recurring Donation Flow
Step 1-2: Same as Single Donation
User enters email, selects currency and amount.
Step 3: Select Monthly
User toggles from "Single" to "Monthly" to create a recurring donation.
Step 4: Payment Method Setup
For recurring donations, Stripe requires a Setup Intent (not Payment Intent) to authorize future charges.
For Cards:
- Frontend calls
/donations/payment_intentwithperiod: 'month' - Server creates
Stripe::SetupIntent(no amount specified) - User enters card details in Stripe Elements
- Card is authorized for future charges
For SEPA:
- User clicks "Pay with SEPA" button
- Modal displays SEPA mandate form
- User enters:
- IBAN (e.g.,
DE89370400440532013000) - Accepts SEPA mandate terms
- Frontend creates SetupIntent and confirms payment method
Step 5: Subscription Creation
- Frontend calls
/donations/create_subscriptionwithpayment_methodID - Server finds or creates Stripe Plan:
- Plan ID format:
{amount_cents}-{currency}-month - Example:
3000-EUR-monthfor €30/month
- Plan ID format:
- Server creates
Stripe::Subscription:- Customer ID
- Plan ID
- Payment method
- Metadata (locale, campaign)
- Subscription immediately active in Stripe
Step 6: Confirmation & First Charge
- Stripe creates subscription and immediately attempts first charge
- Two webhooks sent:
customer.subscription.createdcharge.succeeded(for first payment)
- Backend processes both events:
Subscriptionrecord createdTransactionrecord created for first charge- Thank you email sent
Step 7: Recurring Charges
- Stripe automatically charges on monthly anniversary
- Each charge triggers
charge.succeededwebhook - New
Transactionrecord created each month - Invoice attached to charge (distinguishes recurring vs one-time)
SEPA Direct Debit Flow
SEPA is only available for EUR currency.
Step 1-3: Standard Setup
User enters email, selects EUR currency, selects amount.
Step 4: SEPA Form
- User clicks "Pay with SEPA" button
- Modal displays with SEPA payment form
- User enters:
- Email address
- IBAN (International Bank Account Number)
- SEPA mandate text displayed (legally required)
- User clicks "Donate Now"
Step 5: Payment Confirmation
For Single Donation:
- Frontend creates
PaymentIntentwithpayment_method_types: ['sepa_debit'] - SEPA mandate accepted
- Browser may show native bank authentication (optional)
- Payment confirmed
- Success message displayed
For Recurring Donation:
- Frontend creates
SetupIntentfor mandate authorization - After confirmation, creates
Subscription - First SEPA debit processed
- Monthly recurring charges automatically handled
Step 6: SEPA Processing Timeline
- SEPA debits take 5-7 business days to process
- Payment appears as "pending" initially
- Confirmation sent via webhook when funds settled
- Donor may receive email when payment confirmed
Przelewy24 (P24) Flow
P24 is a Polish payment method, enabled via feature flag.
Step 1-3: Standard Setup
User enters email, selects currency (usually PLN), selects amount.
Step 4: P24 Checkout
- User clicks "Pay with P24" button
- Frontend creates Stripe Checkout Session with
payment_method_types: ['p24'] - User redirected to Stripe-hosted checkout page
- P24 payment form displayed
Step 5: P24 Bank Selection
- User selects their Polish bank from list
- User enters P24 credentials/authorizes payment
- Bank processes payment
Step 6: Return to Application
- After payment authorized: redirected to success URL
- After payment failed/canceled: redirected to cancel URL
- Appropriate success/failure message displayed
Step 7: Webhook Processing
- Stripe sends
charge.succeededwebhook - P24 source status changes to "consumed" (can't be recharged)
- Transaction record created
Failed Payment Flow
Card Declined Scenario
- User enters card details and submits
- Stripe attempts to charge card
- Card issuer declines (insufficient funds, invalid card, etc.)
- Stripe returns error to frontend
- Error message displayed to user
- User can retry with different card or payment method
Webhook Failure Handling
When charge.failed webhook received:
ProcessStripeEventJobprocesses the event- Failure recorded in
Notificationstable - Check for fraud indicators:
- Multiple failed attempts (>3 in 30 minutes)
- Fraud detection flags from Stripe
- If suspicious: cancel payment intent to prevent retries
- Email sent to donor explaining failure
- Staff notification email sent with error details
Retry Logic
- User can retry same payment intent up to 3 times
- After 3 failures: payment intent canceled automatically
- User must start new donation flow
- Payment intents expire after 30 minutes (via
ExpirePaymentIntentJob)
Canceling Recurring Subscription
From Donor Profile
- Donor receives email with authentication link
- Clicks link with authentication token
- Redirected to subscription management page
- Donor views active subscription details
- Clicks "Cancel Subscription" button
- Confirmation prompt displayed
- Donor confirms cancellation
Backend Processing
Subscriptionrecord updated withended_attimestampCancelStripeSubscriptionJobenqueued- Job calls Stripe API to cancel subscription
- Stripe sends
customer.subscription.deletedwebhook - Cancellation email sent to donor
Important Notes
- Cancellation takes effect immediately (no prorated refund)
- Donor will not be charged again
- Already-processed charges remain (no refunds)
- Donor can create new subscription anytime
Error Scenarios & Recovery
Server-Side Validation Errors
Scenario: Invalid email, amount too small, unsupported currency
- Error displayed in UI modal
- User can correct input and retry
- No Stripe API calls made
Network Errors
Scenario: Connection timeout, API unavailable
- Generic error message displayed
- User advised to try again later
- Support email provided
reCAPTCHA Failure
Scenario: reCAPTCHA score too low (when enabled)
- Request blocked with 403 Forbidden
- User sees "reCAPTCHA verification failed" message
- User cannot proceed (anti-fraud measure)
Payment Intent Expiration
Scenario: User abandons form after creating payment intent
- After 30 minutes:
ExpirePaymentIntentJobruns - Payment intent canceled in Stripe
- Prevents stale payment attempts
- User must start fresh donation
Multi-Language Support
All donation flows support localization via URL parameter or browser language:
- Example:
?locale=itfor Italian - Stripe Checkout sessions localized automatically
- Email notifications sent in donor's preferred language
- Language stored in Stripe Customer metadata
Supported languages:
- English (en) - default
- Italian (it)
- Spanish (es)
- French (fr)
- Portuguese (pt)
- Polish (pl)
- Arabic (ar)
- Slovenian (si)
Campaign-Specific Donations
Campaigns allow tracking donations for specific fundraising drives.
Usage
- Append
?campaign=Spring2018to donation URL - Campaign name stored in metadata
- Transactions associated with campaign record
- Reports can filter by campaign
- Campaign progress tracked toward target
Campaign Configuration
- Created in admin interface (Avo)
- Each campaign has:
- Unique name (e.g., "Spring2018", "Christmas2023")
- Target amount in default currency
- Optional end date
- Transactions linked via
donations_campaign_idforeign key
Testing with Stripe Test Cards
For testing in development/staging environments:
Successful Payments
- Visa:
4242 4242 4242 4242 - Mastercard:
5555 5555 5555 4444 - Amex:
3782 822463 10005
3D Secure Authentication
- Requires Auth:
4000 0027 6000 3184 - Use any future expiry date and any CVC
Failed Payments
- Card Declined:
4000 0000 0000 0002 - Insufficient Funds:
4000 0000 0000 9995 - Fraud Prevention:
4100 0000 0000 0019
SEPA Test IBANs
- Successful:
DE89370400440532013000 - Failed:
DE62370400440532013001
All test cards require:
- Any future expiry date (e.g.,
12/34) - Any 3-digit CVC (e.g.,
123) - Any billing postal code (e.g.,
12345)
Next Steps
- Technical Integration - API details and code examples
- Webhooks - Event handling and processing
- Email Notifications - Email templates and triggers