Billing

Every limit lives in one file, features/billing/plans.ts, and no service hard-codes a number — they all ask getLimit, which reads that catalogue. A subscription grants a plan while it is live and lapses on its own; a one-time purchase grants one permanently. Whichever applies, the caller's entitlement resolves for the billing subject, which is their organization when one is active and otherwise the person.

One catalogue, one question, one answer.

Where the numbers live

features/billing/plans.ts is the single source of truth for every limit. A service never hard-codes a number; it asks:

const max = await getLimit(db, ctx, "projects.max");
await assertWithinLimit(db, ctx, "projects.max", current);

assertWithinLimit throws a typed EntitlementExceeded (HTTP 402) naming the limit and where to change it. Adding a new limit will not compile until you have priced it on every plan — the catalogue is typed Record<LimitKey, number> on purpose.

Subscriptions and purchases

Two different things, deliberately kept apart:

  • A subscription renews, has a period end, and lapses on its own when the period runs out or the status stops entitling. past_due still entitles — dunning is not eviction.
  • A one-time purchase grants a plan permanently. It writes no subscription row, so there is nothing to renew and nothing to cancel, and the billing page does not offer the customer portal for it.

getEntitlement answers both "what plan" and "can this be cancelled" together, so the two can never disagree. A live subscription wins; a purchase is the floor it falls back to.

Who is billed

The billing subject is the organization when a session is acting inside one, and the person otherwise. That is why an organization can hold its own subscription separately from the person who bought it.

Trying it without Stripe

With no STRIPE_SECRET_KEY the kit runs an in-memory provider with deterministic ids, and the whole upgrade path — checkout, webhook, entitlement — works end to end. It is fake mode, not a broken configuration.

← All Documentation