Billing and entitlements
Synchronize subscription state, project purchased access into stable grants, and enforce capabilities without scattering plan-name checks through application code.
Subscription lifecycle · Billing operations
Create hosted subscription and one-time checkout, then reconcile verified Stripe events into separate billing and payment projections.
Visit Stripe ↗Synchronize subscription state, project purchased access into stable grants, and enforce capabilities without scattering plan-name checks through application code.
Subscription lifecycle · Billing operations
Compose server-authored products, one-time checkout, finite stock, immutable orders, and physical fulfillment without turning one provider into the domain model.
Catalog and quotes · One-time checkout · Refunds and reconciliation
Verify provider requests from raw bytes, claim events transactionally, enqueue normalized work exactly once, and keep provider parsing outside domain handlers.
Raw-request verification · Provider normalization
@playstack/billing/stripe maps configured Playstack plans to Stripe prices, creates hosted checkout and customer-portal sessions, and normalizes provider subscription state into the portable billing projection.
import { createBilling, definePlans } from '@playstack/billing'
import { stripeBilling } from '@playstack/billing/stripe'
const billing = createBilling({
provider: stripeBilling(stripe, { instance: 'subscriptions' }),
persistence,
entitlements,
entitlementConsistency: 'transactional',
events,
clock,
plans: definePlans({
pro: {
prices: {
monthly: {
provider: 'stripe',
instance: 'subscriptions',
id: 'price_pro_monthly',
},
},
entitlements: [{ entitlement: 'packages.pro', value: true }],
},
}),
})Checkout and portal creation belong behind authenticated application routes. Plan names remain application configuration, while product code authorizes stable entitlements rather than Stripe price or subscription identifiers.
stripeWebhookProvider() from @playstack/webhooks verifies the exact raw body, signature, timestamp tolerance, and provider event ID before the application passes a normalized event to billing.handleWebhook(). The billing projection compares both event ID and provider occurrence time, so exact retries and delayed older events cannot replace newer state. Reconciliation is safe to repeat; unverified request bodies never enter billing policy.
Stripe invoice payment and payment failure, cancellation and resumption, scheduled plan transitions, and provider trial-ending signals become provider-neutral Playstack events. Current subscription queries, immutable revision history, and explicit reconciliation support billing settings and operator tooling without treating the local projection as authoritative.
The injected Stripe client remains available through billing.provider.client for provider-specific operations outside the portable subscription surface.
@playstack/payments/stripe uses hosted Checkout in payment mode and projects payments, refunds, and disputes without changing subscription state. @playstack/catalog stores the provider price reference for each immutable offer, and @playstack/commerce snapshots the resolved terms into an order before checkout begins.
const payments = createPayments({
providers: [
stripePayments({ client: storeStripe, instance: 'store' }),
stripePayments({ client: euStripe, instance: 'eu' }),
],
persistence,
events,
ids,
clock,
})The configured instance is part of every provider identity, so multiple Stripe accounts cannot exchange customer, price, checkout, or webhook IDs accidentally. Verified one-time events flow to payments.handleWebhook(); verified subscription events flow to billing.handleWebhook().
Billing checkout uses an explicit durable attempt ID before Stripe I/O, preserving immutable request identity and unknown outcomes. Reconcile and retry the same attempt after ambiguous success; do not automatically issue another checkout. Subscription updates, history and transactional entitlement projection share customer locking.
Meter reporting has its own idempotent usage/claim contract. Product prices, allowances, refund/dispute effects and access grants remain application policy. Raw-body signature verification belongs to Webhooks and the host HTTP composition, not a browser return URL.