Skip to main content

Billing accounts, subscriptions, and entitlements

Supacharger separates identity, billing, and application access.

Relationship model

billing account
├── members (owner, billing administrator, member)
├── Stripe Customer mapping (per account and live/test mode)
├── Subscriptions
│ └── Subscription Items → Prices → Products
└── entitlement grants → stable feature lookup keys

A personal account can use the authenticated user's UUID as its subject, but this is a subject reference—not proof that every billable party is a user. Organisation/team applications create a billing account for that domain object and manage user authority through membership.

Billing acquisition boundary

When BILLING_ACCESS.REQUIRED is enabled, full product routes use requireAppAccess(), which evaluates onboarding before billing. The configured acquisition page must instead use requireOnboardedUser(): unauthenticated users are sent to login, incomplete profiles are sent to setup, and an onboarded user without billing access may render Checkout. Never place the acquisition page beneath requireAppAccess(), and never treat Checkout query parameters or return state as proof of entitlement.

Multiple subscriptions and items

A Stripe Customer can hold multiple independent Subscriptions with different periods and lifecycle states. A Subscription can combine multiple fixed, quantity/seat, and metered Prices. Flexible billing mode also supports mixed intervals where Stripe account capabilities permit them.

The canonical read model is subscriptions[] → items[]. The old subscriptions.price_id and quantity columns are temporary compatibility aliases to the first Item. New features must never read them as the complete plan.

Entitlement-based access

Prices answer “what is billed”; entitlements answer “what can this account use”. Stable feature lookup keys let pricing change without changing application authorisation.

Sources include:

  • Stripe Entitlements attached to Stripe Products;
  • application-maintained subscription/Product mappings;
  • manual administrator grants; and
  • promotional grants with optional start/end dates.

Stripe summary events trigger a complete paginated refresh. Runtime access reads the local projection, avoiding a live Stripe dependency on every request. Configure SC_CONFIG.BILLING_ACCESS.FEATURE_LOOKUP_KEY for feature access. Leave it null only when the product intentionally grants broad access for any trialing or active Subscription.

SC_CONFIG.BILLING_ACCESS.REQUIRED controls a post-authentication callback detour; it is not, by itself, a universal paywall. See Login redirects and subscription paywalls before enabling it, and enforce entitlements at every protected server boundary.

Lifecycle states

The projection recognises trialing, active, canceled, incomplete, incomplete_expired, past_due, unpaid, and paused. Applications must explicitly decide which entitlements remain active during payment failure, grace periods, pauses, scheduled cancellation, and manual support overrides.

Do not infer access solely from a successful Checkout redirect, a client timer, or a database error. Webhooks and reconciliation populate the projection; the server-side access function evaluates it.

Upgrades and scheduled changes

Simple changes can be enabled in Customer Portal. For application-driven changes:

  • preview prorations before confirmation;
  • use pending updates when access must not change until payment succeeds;
  • use Subscription Schedules for future-dated phases;
  • retain Item IDs when replacing Prices; and
  • reconcile after complex or operator-created Dashboard changes.

One-time payments

One-time fulfilment remains outside the canonical recurring-billing contract. Add it only with an Order/Payment projection, fulfilment idempotency, async payment handling, refunds/disputes, customer communication, and reconciliation. Do not overload Subscriptions or entitlement grants to represent unimplemented order fulfilment.

Shared and application billing tests

The CLI-managed test/billing-schema-contract.test.mjs protects the reusable account-based billing model, Stripe customer mapping identifiers, shared RPC argument and grant conventions, generated type alignment, and entitlement-based access. It remains byte-identical across CLI-managed applications.

Your product catalogue, application-only billing tables, RPCs, policies, and entitlement definitions are not Core behaviour. Put their assertions in test/project-billing-schema-contract.test.mjs and extend the merge-managed test:billing-schema package script to run both files. Core updates then refresh the shared contract test while preserving the application's product coverage.

See Stripe setup and operations for configuration, security, processing, and release checks.

Build Supacharger with SpecdriveKeep the specification, infrastructure and agent work in one project context.