Payments
This page explains what happens under the hood when your storefront takes a payment, so you understand the guarantees you're building on. For the step-by-step SDK calls, see Store; for the business-owner view of getting paid, see How You Get Paid.
You build two things: the UI, and a call to the checkout SDK. Everything after the customer clicks "pay" — the Stripe session, the customer record, the order/subscription row, retries, refunds — BIAB handles server-side.
The model: direct charges on connected accounts
Every org connects one Stripe account through Stripe Connect (Express).
Your storefront's charges are created on that connected account (a "direct
charge"), so the money settles to the org, and BIAB takes a small
application_fee. There are two Stripe accounts in play — the org's connected
account (its revenue) and BIAB's platform account (its own billing) — and the
webhook tells them apart by event.account.
You never touch Stripe keys or the connected account id — the SDK is scoped to the org, and the server resolves the rest.
The checkout flow
The shape is the same for one-time purchases and subscriptions:
-
Your site calls the SDK to start checkout:
// One-time (cart → hosted checkout) const { stripeUrl } = await client.checkout.start(session.id) // Subscription (buyer must be signed in — see below) const { stripeUrl } = await client.subscriptions.startCheckout({ subscriptionId: 'sub-plan-1', }) -
Redirect the browser to
stripeUrl. Stripe collects payment on its hosted page. -
Stripe returns the customer to your
successUrlwith asession_id. -
Your success page confirms the result:
const status = await client.checkout.getStatus(session.id) // → { paymentStatus: 'paid' | 'unpaid' | 'no_payment_required', ... }
You render "thank you" on paid. That's the whole integration.
Source of truth: the webhook re-reads Stripe
BIAB never trusts a redirect or an event payload to decide what you got paid.
When a payment completes, Stripe notifies BIAB's webhook, which re-fetches the
current state directly from Stripe by id and writes the order or subscription
from that. The same sync also runs eagerly when your success page calls
getStatus, so state is usually already correct by the time the customer sees
the page — the webhook is the backstop.
This gives you three guarantees without any work on your side:
- Idempotent. A duplicated or replayed Stripe event is recorded once — inventory won't double-decrement, an invoice won't be marked paid twice.
- Self-healing. A dropped notification is repaired by the next event's full re-sync.
- Correct amounts. What's recorded is what Stripe actually captured, not what a payload claimed.
Customer-first (and why subscriptions require sign-in)
Before a durable purchase, BIAB creates the buyer's Stripe customer on the connected account and remembers the link, so repeat purchases, saved cards, and subscriptions all attach to the same customer.
Because that binding is keyed to a logged-in account, subscription checkout
requires an authenticated buyer — client.subscriptions.startCheckout returns
a 401 for an anonymous visitor. Wire up Auth
first. One-time store checkout still works for guests (the order is the
record); when the buyer is signed in, their purchases attach to their durable
customer automatically.
A subscription CTA should only render for a signed-in visitor (or send them through sign-in first). One-time "Buy" buttons can stay available to guests.
What settles where
| You handle | BIAB handles |
|---|---|
| Storefront UI + the checkout SDK call | Stripe session creation on the connected account |
Redirect to stripeUrl | The Stripe customer + the buyer↔customer binding |
Reading getStatus on your success page | Orders, subscriptions, invoices — written from Stripe's truth |
| Sign-in before subscription checkout | Idempotency, retries, refunds, renewals, cancellations |
The org connects Stripe once under Settings → Payments; after that, store purchases, subscriptions, invoices, bookings, and POS all settle to the same connected account.