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.
| Code | payments/ (shared client, webhook), store/payments_handlers.py, events/payments_handlers.py, donations/payments_handlers.py |
| Runbooks | payments/README.md, store/README.md (sandbox), donations/README.md (Stripe object setup), docs/donations-go-live.md (live checklist) |
| Env | STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET, STRIPE_API_VERSION (pinned, default 2026-09-30.endive), CHECKOUT_RETURN_URL, STORE_*, EVENTS_*, DONATIONS_* (see Configuration) |
| Modes | Test 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:
| Event | Used by |
|---|---|
checkout.session.completed, checkout.session.async_payment_succeeded, checkout.session.async_payment_failed, checkout.session.expired | store, events, donations |
charge.refunded | store, events, donations |
invoice.paid, invoice.payment_failed | donations (recurring) |
customer.subscription.updated, customer.subscription.deleted | donations (recurring) |
When you update the webhook endpoint in Stripe, enabled_events is replaced wholesale: list all nine.
Per-surface Stripe objects
| Surface | Stripe objects the backend references |
|---|---|
| Store | STORE_PAYMENT_METHOD_CONFIGURATION (pmc_), optional Stripe Tax (STORE_AUTOMATIC_TAX, STORE_PRODUCT_TAX_CODE); products synced with sync_stripe_catalog |
| Events | EVENTS_PAYMENT_METHOD_CONFIGURATION (defaults to the store's) |
| Donations | DONATIONS_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.