Skip to content

Stripe integration

The backend supports per-shop Stripe accounts: every ShopTable row carries its own stripe_secret_key and stripe_public_key. There is no global Stripe key — every API call has to pick the right one for the shop being acted on.

The helper: server/services/stripe_client.py

A small module that centralizes key resolution. It does not wrap the whole Stripe SDK — callers still import stripe and use it directly. The helper exists so stripe.api_key is set in exactly one way.

from server.services import stripe_client

stripe_client.configure_for_shop(shop)         # sets stripe.api_key from shop.stripe_secret_key
customer_id = stripe_client.get_customer_id(account)  # reads account.details["stripe_customer_id"]
customer = stripe_client.fetch_customer(shop, customer_id)  # configure + retrieve as dict

Exceptions

Exception Raised when
StripeNotConfigured The shop has no stripe_secret_key (or shop is None).
StripeCustomerMissing The account's details JSON has no stripe_customer_id key.

The helper does not translate stripe.error.StripeError into HTTP errors — that stays the responsibility of the route handler so each endpoint can pick the right status code (typically 502 for upstream errors).

Where it's used

  • server/api/endpoints/admin_accounts.py — superuser sync + read-through (see Admin accounts).
  • server/api/endpoints/shop_endpoints/stripe.py — payment intents, subscription create/cancel.
  • server/api/endpoints/shop_endpoints/orders.py — auto-creates a Stripe customer when a new account first appears in checkout.

Why module-level, not a StripeClient instance

The Stripe SDK supports both styles. The codebase has historically used the module-level stripe.api_key = ... pattern; the helper preserves that without introducing a new concurrency model. If multi-key concurrency ever becomes a concern (e.g. background workers fanning out across shops), migrating to stripe.StripeClient(...) instances becomes a localized refactor of this one module.

Known follow-ups

  • No webhook handlers yet. Payment / subscription state changes have to be reconciled via the admin sync endpoints. Adding a /stripe/webhook route is the next obvious step.
  • /shops/{shop_id}/stripe/* routes are unauthenticated. Public access is currently required by the frontend checkout flow; revisit once the checkout is reworked to carry a token.
  • shop_endpoints/stripe.py swallows exceptions with except Exception: return e, which then mishandles the response. Tracked separately.