Skip to main content

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

  1. User enters their email address (e.g., donor@example.com)
  2. User selects currency (defaults to EUR)
  3. User chooses donation amount:
    • Click preset button (e.g., €30)
    • Or enter custom amount (minimum €1.00 equivalent)
  4. 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

  1. Frontend calls /donations/payment_intent API endpoint
  2. Server creates Stripe::PaymentIntent with:
    • Amount and currency
    • Customer ID (existing or newly created Stripe Customer)
    • Metadata (locale, campaign, IP geolocation)
  3. Frontend receives client_secret
  4. Modal/embedded form displays Stripe Payment Element
  5. 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

Step 5: 3D Secure Authentication (if required)

For cards requiring Strong Customer Authentication (SCA):

  1. Stripe redirects to card issuer's authentication page
  2. User enters authentication code or approves via mobile app
  3. Authentication result sent back to Stripe
  4. Payment confirmed or declined

Step 6: Payment Confirmation

  1. Stripe confirms the payment
  2. Frontend receives success status
  3. Success panel displayed: "Thank you for your donation!"
  4. Page may redirect to Aleteia site (configurable via settings)

Step 7: Backend Processing

  1. Stripe sends charge.succeeded webhook to /donations/event
  2. ProcessStripeEventJob processes the event:
    • Creates Donations::Notification record
    • Creates Donations::Transaction record with:
      • Amount
      • Currency
      • Transaction ID (Stripe charge ID)
      • Payment date
    • Associates transaction with campaign (if specified)
  3. Thank you email sent to donor
  4. 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:

  1. Frontend calls /donations/payment_intent with period: 'month'
  2. Server creates Stripe::SetupIntent (no amount specified)
  3. User enters card details in Stripe Elements
  4. Card is authorized for future charges

For SEPA:

  1. User clicks "Pay with SEPA" button
  2. Modal displays SEPA mandate form
  3. User enters:
    • Email
    • IBAN (e.g., DE89370400440532013000)
    • Accepts SEPA mandate terms
  4. Frontend creates SetupIntent and confirms payment method

Step 5: Subscription Creation

  1. Frontend calls /donations/create_subscription with payment_method ID
  2. Server finds or creates Stripe Plan:
    • Plan ID format: {amount_cents}-{currency}-month
    • Example: 3000-EUR-month for €30/month
  3. Server creates Stripe::Subscription:
    • Customer ID
    • Plan ID
    • Payment method
    • Metadata (locale, campaign)
  4. Subscription immediately active in Stripe

Step 6: Confirmation & First Charge

  1. Stripe creates subscription and immediately attempts first charge
  2. Two webhooks sent:
    • customer.subscription.created
    • charge.succeeded (for first payment)
  3. Backend processes both events:
    • Subscription record created
    • Transaction record created for first charge
    • Thank you email sent

Step 7: Recurring Charges

  • Stripe automatically charges on monthly anniversary
  • Each charge triggers charge.succeeded webhook
  • New Transaction record 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

  1. User clicks "Pay with SEPA" button
  2. Modal displays with SEPA payment form
  3. User enters:
    • Email address
    • IBAN (International Bank Account Number)
  4. SEPA mandate text displayed (legally required)
  5. User clicks "Donate Now"

Step 5: Payment Confirmation

For Single Donation:

  1. Frontend creates PaymentIntent with payment_method_types: ['sepa_debit']
  2. SEPA mandate accepted
  3. Browser may show native bank authentication (optional)
  4. Payment confirmed
  5. Success message displayed

For Recurring Donation:

  1. Frontend creates SetupIntent for mandate authorization
  2. After confirmation, creates Subscription
  3. First SEPA debit processed
  4. 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

  1. User clicks "Pay with P24" button
  2. Frontend creates Stripe Checkout Session with payment_method_types: ['p24']
  3. User redirected to Stripe-hosted checkout page
  4. P24 payment form displayed

Step 5: P24 Bank Selection

  1. User selects their Polish bank from list
  2. User enters P24 credentials/authorizes payment
  3. Bank processes payment

Step 6: Return to Application

  1. After payment authorized: redirected to success URL
  2. After payment failed/canceled: redirected to cancel URL
  3. Appropriate success/failure message displayed

Step 7: Webhook Processing

  • Stripe sends charge.succeeded webhook
  • P24 source status changes to "consumed" (can't be recharged)
  • Transaction record created

Failed Payment Flow

Card Declined Scenario

  1. User enters card details and submits
  2. Stripe attempts to charge card
  3. Card issuer declines (insufficient funds, invalid card, etc.)
  4. Stripe returns error to frontend
  5. Error message displayed to user
  6. User can retry with different card or payment method

Webhook Failure Handling

When charge.failed webhook received:

  1. ProcessStripeEventJob processes the event
  2. Failure recorded in Notifications table
  3. Check for fraud indicators:
    • Multiple failed attempts (>3 in 30 minutes)
    • Fraud detection flags from Stripe
  4. If suspicious: cancel payment intent to prevent retries
  5. Email sent to donor explaining failure
  6. 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

  1. Donor receives email with authentication link
  2. Clicks link with authentication token
  3. Redirected to subscription management page
  4. Donor views active subscription details
  5. Clicks "Cancel Subscription" button
  6. Confirmation prompt displayed
  7. Donor confirms cancellation

Backend Processing

  1. Subscription record updated with ended_at timestamp
  2. CancelStripeSubscriptionJob enqueued
  3. Job calls Stripe API to cancel subscription
  4. Stripe sends customer.subscription.deleted webhook
  5. 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: ExpirePaymentIntentJob runs
  • 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=it for 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

  1. Append ?campaign=Spring2018 to donation URL
  2. Campaign name stored in metadata
  3. Transactions associated with campaign record
  4. Reports can filter by campaign
  5. 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_id foreign 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