# Billing accounts, subscriptions, and entitlements

> Supacharger separates identity, billing, and application access.

# Billing accounts, subscriptions, and entitlements

Supacharger separates identity, billing, and application access.

## Relationship model

```text
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](../Application%20Development/login-redirects-and-paywalling.md) 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](./stripe.md) for configuration, security, processing, and release checks.
