Stripe Billing

Configure subscription prices, idempotent checkout, webhooks, credits, portal safety, and reconciliation.

Synced with starter commit 7d452a6.

Create stable Stripe Prices for Plus and Max, monthly and yearly, then set the STRIPE_PRICE_* IDs, STRIPE_PRIVATE_KEY, and STRIPE_WEBHOOK_SECRET. CNY Price IDs are optional. Billing amounts and catalog metadata live in src/config/billing.ts; Price IDs remain server-only.

Checkout is organization-owner only and requires Idempotency-Key. The service creates a purchase intent/order before Stripe, so retrying the same request returns one effect; a deliberate second purchase needs a new key. Multiple subscriptions can coexist and the entitlement resolver combines them.

Webhook Contract

Expose /api/pay/notify/stripe. At minimum subscribe to checkout completion/expiration, subscription create/update/delete, invoice paid/payment failed, and refund/dispute events. The handler verifies the signature, deduplicates Stripe event IDs, respects event time for out-of-order delivery, and grants each credit benefit once. Renewal invoices produce new orders and monthly credits.

Refunds and disputes are assessed and surfaced for review; the starter does not silently claw back consumed credits. Reconcile uncertain state with the supplied Stripe reconciliation command and the admin event view.

Portal and Local Testing

Create a Stripe Billing Portal configuration and set its ID in production. Subscription updates in the portal must be disabled; the starter fails closed if the configuration is unsafe.

stripe listen --forward-to localhost:3000/api/pay/notify/stripe

Before launch, test duplicate checkout calls, delayed/duplicate webhooks, renewals, failures, cancellation, refunds, and portal access. Use live keys only in production and restrict webhook delivery to live-mode events.

Stripe Billing · Sushi SaaS