Skip to main content

Stripe

Stripe processes every payment on the platform: book orders, event registrations and donations. The website mounts Stripe Elements for Checkout Sessions (ui_mode=elements); the backend creates the sessions and reacts to webhooks. Card data never touches EMAW systems.

Codepayments/ (shared client, webhook), store/payments_handlers.py, events/payments_handlers.py, donations/payments_handlers.py
Runbookspayments/README.md, store/README.md (sandbox), donations/README.md (Stripe object setup), docs/donations-go-live.md (live checklist)
EnvSTRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET, STRIPE_API_VERSION (pinned, default 2026-09-30.endive), CHECKOUT_RETURN_URL, STORE_*, EVENTS_*, DONATIONS_* (see Configuration)
ModesTest keys in dev (test cards work, nothing is charged); live keys in production

Flow​

The success page is shared: /checkout/success?token=…&type=store|events|donations.

Webhook​

Single endpoint POST /rest/stripe/webhook/ (payments/views.py). It checks the signature with STRIPE_WEBHOOK_SECRET, stores the event id in Redis for 24 hours to drop replays, then calls <app>.payments_handlers.handle_stripe_event based on metadata.app.

Handled event types:

EventUsed by
checkout.session.completed, checkout.session.async_payment_succeeded, checkout.session.async_payment_failed, checkout.session.expiredstore, events, donations
charge.refundedstore, events, donations
invoice.paid, invoice.payment_faileddonations (recurring)
customer.subscription.updated, customer.subscription.deleteddonations (recurring)

When you update the webhook endpoint in Stripe, enabled_events is replaced wholesale: list all nine.

Per-surface Stripe objects​

SurfaceStripe objects the backend references
StoreSTORE_PAYMENT_METHOD_CONFIGURATION (pmc_), optional Stripe Tax (STORE_AUTOMATIC_TAX, STORE_PRODUCT_TAX_CODE); products synced with sync_stripe_catalog
EventsEVENTS_PAYMENT_METHOD_CONFIGURATION (defaults to the store's)
DonationsDONATIONS_PAYMENT_METHOD_CONFIGURATION (card + US bank account), DONATIONS_STRIPE_PRODUCT_ID (one product every gift hangs off), DONATIONS_PORTAL_CONFIGURATION (bpc_, billing portal for managing recurring gifts)

These are created with the Stripe CLI or dashboard once per mode (test, then live). The donations README has the exact CLI calls.

Shipping is server-side​

Hosted and embedded Checkout cannot do dynamic shipping, which is why the website uses Elements mode: each completed address calls POST /rest/store/checkout/<token>/shipping-options/, the backend quotes (Shipping) and updates the session. Do not switch modes.

Local development​

Webhooks reach a laptop through the dev tunnel without extra setup. For host-run Django, bridge with the Stripe CLI:

stripe listen --forward-to https://dev.emaw-app.com/rest/stripe/webhook/ # or http://localhost:8000/...
# copy the printed whsec_ into STRIPE_WEBHOOK_SECRET
stripe trigger checkout.session.completed

Test cards​

See Operations → Stripe for the success and decline card tables and the go-live checklist.