Set up Stripe
Wire up Stripe end-to-end — products, checkout, webhook, and access control.
NoteThis tutorial uses Stripe. The flow for Lemon Squeezy is nearly identical — just swap the provider in config and follow the Payments feature guide.
1. Set the provider
// src/config.ts
paymentProvider: "stripe",2. Add your Stripe keys
# .env.local
STRIPE_SECRET_KEY=sk_test_...
STRIPE_WEBHOOK_SECRET=whsec_... # fill in after step 5Find your secret key in Stripe Dashboard → Developers → API Keys.
3. Create products in Stripe
- Stripe Dashboard → Product catalog → Add product.
- For each plan, create a price — choose One time or Recurring.
- Copy each price ID — it starts with
price_.
4. Paste the price IDs into config
// src/config.ts
stripe: {
plans: [
{ name: "Starter", priceId: "price_xxx", mode: "payment" },
{ name: "Advanced", priceId: "price_yyy", mode: "subscription" },
{ name: "Pro", priceId: "price_zzz", mode: "subscription" },
],
},mode: "payment" is a one-time charge. mode: "subscription" is recurring billing.
WarningEach
name must match a planName in config.pricing.cards exactly — this is how checkout buttons know which product to charge for.5. Register the webhook
- Stripe Dashboard → Developers → Webhooks → Add endpoint.
- URL:
https://yourdomain.com/api/webhook/stripe - Events to listen for:
checkout.session.completed,customer.subscription.deleted,customer.subscription.updated,invoice.payment_failed - Copy the signing secret and add it as
STRIPE_WEBHOOK_SECRET.
6. Test locally with Stripe CLI
stripe login
stripe listen --forward-to localhost:3000/api/webhook/stripeThe CLI outputs a temporary webhook secret for local dev. In another terminal:
stripe trigger checkout.session.completedAfter the event fires, check the profilestable in Supabase — the test user's row should show has_access = true.
7. Gate your product behind access
Inside any server component or API route, check the has_access flag:
import { createClient } from "@/libs/supabase/server";
const supabase = await createClient();
const { data: profile } = await supabase
.from("profiles")
.select("has_access")
.single();
if (!profile?.has_access) {
redirect("/");
}