A port, not an SDK
The heart of the module is an interface, PaymentGateway. Every operation the app needs to take money is declared on it: create a customer, capture a card without charging, create a payment intent, open a hosted checkout, charge off-session, verify a webhook. Routes, crons and public pages depend on this interface, never on hard-coded stripe.* calls. Adding a provider means writing a class that implements the port and registering it, without touching anything else.
Stripe Connect per workspace
Each workspace connects its own Stripe account. The platform holds one secret key and acts on behalf of the connected account by passing its identifier (acct_...) on every Stripe call. The connected account secret is never stored: Connect Standard relies on the platform auth plus the account header. A workspace with no connected account falls back to the platform account (legacy mono-tenant behavior), but a new workspace must connect Stripe before it can take payments.
- Account resolved from workspace_stripe_accounts, cached for 60 seconds
- The connected flag says whether the workspace wired its own account or falls back to the platform
- The connected account publishable key is what public pages use to mount Stripe Elements
What the gateway can do
The port covers the full lifecycle of a payment. Each operation returns a normalized, PSP-agnostic type, so the caller never writes Stripe-specific logic.
- PSP customer: fetched or recreated if the stored id is dead
- Card capture (SetupIntent): saves a payment method without a charge
- On-session payment (PaymentIntent): the client pays on the page, with 3DS handled
- Hosted checkout: the PSP hosts the page for a one-off payment, redirects to success or cancel
- Off-session charge: debits a saved card without the client present, with an idempotency key against double charges
- Webhook verification: checks the signature and returns a normalized event
Where the money comes in
The Payments layer has no screen of its own. It is consumed by the surfaces that sell. An invoice payment opens a branded checkout page that mounts Stripe Elements and keeps the client on your domain. A product checkout creates an order, applies shipping and coupons, charges through Connect. A subscription to a Collection bills the end-user on top. A booking deposit runs through the same path. On platform flows, a commission (application fee) can be taken on each session.
One PSP written today
The design is provider-agnostic, but to be honest: only Stripe is actually implemented behind the port. PayPal, Mollie and Adyen are planned identifiers, not written gateways. A provider that is chosen but not implemented fails loudly, it never silently falls back to Stripe: taking money through the wrong PSP would be worse than not taking it. And until Stripe is connected at the workspace level, this whole layer stays inert.