Stripe Setup
Configure Stripe Checkout, prices, webhooks, subscriptions, and sandbox verification.
Stripe is one of the two built-in payment adapters. The current integration uses hosted Checkout, scheduled subscription cancellation, Customer Portal, and signed webhooks.
1. Create products and prices
Create recurring prices for every subscription period displayed by the starter and one-time prices for all displayed credit packs. Copy Stripe Price IDs (price_...), not Product IDs (prod_...).
The default visible catalog requires:
STRIPE_PRICE_STARTER_MONTHLY=price_xxx
STRIPE_PRICE_STARTER_ANNUAL=price_xxx
STRIPE_PRICE_PRO_MONTHLY=price_xxx
STRIPE_PRICE_PRO_ANNUAL=price_xxx
STRIPE_PRICE_CREDITS_SMALL=price_xxx
STRIPE_PRICE_CREDITS_MEDIUM=price_xxx
STRIPE_PRICE_CREDITS_LARGE=price_xxxLegacy STRIPE_PRICE_STARTER and STRIPE_PRICE_PRO variables are still accepted as monthly fallbacks, but new installations should use the explicit _MONTHLY names.
Environment names for Enterprise and landing products exist as extension points. Enterprise is not in the built-in plan catalog, and the example landing entitlements do not have complete checkout fulfillment. Do not expose those SKUs until you implement and test their product behavior.
2. Configure Stripe
NEXT_PUBLIC_ENABLE_PAYMENTS=true
PAYMENTS_PROVIDER=stripe
STRIPE_SECRET_KEY=sk_test_xxx
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_xxx
STRIPE_WEBHOOK_SECRET=whsec_xxx
STRIPE_PRICE_STARTER_MONTHLY=price_xxx
STRIPE_PRICE_STARTER_ANNUAL=price_xxx
STRIPE_PRICE_PRO_MONTHLY=price_xxx
STRIPE_PRICE_PRO_ANNUAL=price_xxx
STRIPE_PRICE_CREDITS_SMALL=price_xxx
STRIPE_PRICE_CREDITS_MEDIUM=price_xxx
STRIPE_PRICE_CREDITS_LARGE=price_xxx
CRON_SECRET=replace_with_a_strong_random_server_secretKeep the complete set in one mode:
- test secret and publishable keys;
- test-mode Price IDs;
- a signing secret from the test endpoint or local Stripe listener.
Use the equivalent live values together only when production acceptance is complete. Never expose STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET, or CRON_SECRET to browser code.
The built-in Stripe Checkout adapter currently requests card payments explicitly. Add and test asynchronous payment events before enabling payment methods that can complete later.
3. Configure the webhook
Create a Stripe webhook endpoint at:
https://your-domain.com/api/payments/webhookSubscribe to:
checkout.session.completedcustomer.subscription.createdcustomer.subscription.updatedcustomer.subscription.deletedinvoice.paidinvoice.payment_succeededinvoice.payment_failed
The route verifies Stripe-Signature against the raw request body. Copy the endpoint signing secret into STRIPE_WEBHOOK_SECRET.
For local forwarding:
stripe login
stripe listen --forward-to http://localhost:3000/api/payments/webhookUse the signing secret printed by that listener only for the local environment.
4. Checkout and lifecycle behavior
Use the unified routes rather than provider-specific routes:
POST /api/payments/checkout
Content-Type: application/json
{ "skuKey": "credits_small" }POST /api/payments/subscription/checkout
Content-Type: application/json
{ "planKey": "starter", "period": "month" }The checkout adapter attaches the user ID and catalog metadata to the Checkout Session and, for subscriptions, to subscription_data. Subscription checkout uses a server-generated idempotency key.
Cancellation updates the Stripe subscription with cancel_at_period_end: true. Customer Portal is created with the customer ID stored on the subscription record. These operations continue to use Stripe even if the deployment's active provider is later changed.
The success redirect shows a generic success notification. It does not query Stripe or grant credits. Wait for webhook processing and verify durable state.
5. Verify in test mode
Run static and automated checks first:
npm run preflight
npm run test:unitThe opt-in hosted-checkout smoke creates real Stripe test Checkout Sessions without submitting a payment:
PAYMENT_PROVIDER_SANDBOX_E2E=true \
E2E_TEST_EMAIL=<existing-test-user-email> \
E2E_TEST_PASSWORD=<test-user-password> \
PLAYWRIGHT_BASE_URL=https://<preview-or-test-origin> \
npm run e2e -- tests/e2e/payment-provider-sandbox.spec.ts --project=chromiumFor a local server, set PLAYWRIGHT_BASE_URL=http://localhost:3000. The test deliberately skips when either test credential is missing, so a green command is not enough: acceptance requires the summary to report 2 passed and 0 skipped.
Complete acceptance still requires:
- a Stripe test payment;
- delivery to the public HTTPS webhook;
- a subscription or credit-ledger database read-back;
- duplicate-event replay with no duplicate fulfillment;
- scheduled cancellation and Customer Portal verification.
See Webhooks, subscriptions, and credits for the durable fulfillment model.