Limited offer: $250 off for the first 200 customers!Claim discount

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/stripe

The CLI prints a temporary signing secret to use as STRIPE_WEBHOOK_SECRET during development. Trigger test events with:

stripe trigger checkout.session.completed

What the webhook does

  • checkout.session.completed — sets has_access = true, stores customer_id and plan_id on the profile, and sends an order confirmation email.
  • customer.subscription.deleted — sets has_access = false and sends a cancellation email.
  • customer.subscription.updated — updates the plan_id when 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.