Payments
One-time payments and subscriptions with a complete webhook pipeline.
WarningSwitching payment providers is supported. Keep the previous provider's keys and webhook secret active so existing users on that provider keep billing/portal access.
Step 1 — Set the provider
// src/config.ts
paymentProvider: "stripe",Step 2 — Add your keys
STRIPE_SECRET_KEY=sk_live_...
STRIPE_WEBHOOK_SECRET=whsec_...Find your secret key in your Stripe dashboard. The webhook secret is generated in Step 5 when you register the endpoint.
Step 3 — Create products in Stripe
- In your Stripe dashboard, add a product.
- Create a price for each plan. Choose One time or Recurring.
- Copy each price ID — it starts with
price_.
Step 4 — Wire up config.ts
The name in config.stripe.plans[n] must match config.pricing.cards[n].planName exactly.
stripe: {
plans: [
{
name: "Starter",
priceId: "price_xxx",
mode: "payment",
},
{
name: "Advanced",
priceId: "price_yyy",
mode: "subscription",
},
]
}Use mode: "payment" for one-time purchases and mode: "subscription" for recurring plans.
TipOnly offering one or two plans? Comment out the extra entries in
config.stripe.plans and their matching cards in config.pricing.cards. The checkout button will wire up to whichever plan name matches the card — no other changes needed.Step 5 — Register the webhook
- In your Stripe dashboard, add a new webhook endpoint.
- URL:
https://yourdomain.com/api/webhook/stripe - Events:
checkout.session.completed,customer.subscription.deleted,customer.subscription.updated,invoice.payment_failed - Copy the signing secret and set it as
STRIPE_WEBHOOK_SECRET.
Local testing with Stripe CLI
stripe login
stripe listen --forward-to localhost:3000/api/webhook/stripeThe CLI prints a temporary signing secret to use as STRIPE_WEBHOOK_SECRET during development. Trigger test events with:
stripe trigger checkout.session.completedWhat the webhook does
- checkout.session.completed — sets
has_access = true, storescustomer_idandplan_idon the profile, and sends an order confirmation email. - customer.subscription.deleted — sets
has_access = falseand sends a cancellation email. - customer.subscription.updated — updates the
plan_idwhen the user switches plans.
Billing portal
Enable the Customer Portal in your Stripe dashboard billing settings. The ButtonBillingPortal component redirects users there to manage subscriptions and payment methods.