Stripe Integration

Tiered subscription system with checkout, webhooks, and billing management.

Free

$0

Basic features

Pro

$20/mo

Advanced features

Ultra

$50/mo

Enterprise features

Setup

.env
# API Keys
STRIPE_SECRET_KEY="sk_test_..."
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY="pk_test_..."
STRIPE_WEBHOOK_SECRET="whsec_..."
# Price IDs (from Stripe Dashboard)
STRIPE_PRICE_PRO_MONTHLY="price_..."
STRIPE_PRICE_PRO_YEARLY="price_..."
STRIPE_PRICE_ULTRA_MONTHLY="price_..."
STRIPE_PRICE_ULTRA_YEARLY="price_..."

Create products and prices in Stripe Dashboard, then copy the price IDs.

Key Files

src/lib/subscriptions.tsPlan definitions, pricing, features
src/app/api/checkout/route.tsCreates checkout sessions
src/app/api/webhooks/stripe/route.tsHandles Stripe events
src/server/actions/stripe.tsServer actions for billing

Checkout Flow

1// Create checkout session
2 const checkoutSession = await stripe.checkout.sessions.create({
3 customer_email: session.user.email,
4 line_items: [{ price: priceId, quantity: 1 }],
5 mode: "subscription",
6 success_url: `${origin}/account?success=true`,
7 cancel_url: `${origin}/account?canceled=true`,
8 metadata: { userId: session.user.id, plan },
9});
10
11return NextResponse.json({ url: checkoutSession.url });

Webhooks

Configure webhook at https://yourdomain.com/api/webhooks/stripe

Required events:

  • customer.subscription.created
  • customer.subscription.updated
  • customer.subscription.deleted
1// Verify webhook signature
2 const event = stripe.webhooks.constructEvent(
3 body,
4 signature,
5 env.STRIPE_WEBHOOK_SECRET
6 );
7
8 switch (event.type) {
9 case "customer.subscription.created":
10 await handleSubscriptionCreated(event.data.object);
11 break;
12 case "customer.subscription.updated":
13 await handleSubscriptionUpdated(event.data.object);
14 break;
15 case "customer.subscription.deleted":
16 await handleSubscriptionDeleted(event.data.object);
17 break;
18}

Customer Portal

1const portalSession = await stripe.billingPortal.sessions.create({
2 customer: subscription.stripeCustomerId,
3 return_url: `${process.env.NEXTAUTH_URL}/account`,
4 });
5
6return portalSession.url;

Allows users to update payment methods and download invoices.

Customizing Plans

Change Prices/Features

Edit the single source of truth:

1// src/lib/subscriptions.ts
2export const SUBSCRIPTION_PLANS = {
3 PRO_MONTHLY: {
4 priceValue: 2000, // $20.00 in cents
5 features: [
6 "Unlimited rankings",
7 "Advanced analytics",
8 // Add/remove features
9 ],
10 },
11};

Add New Tier

  1. 1. Add type to prisma/schema.prisma enum
  2. 2. Run pnpm db:generate
  3. 3. Add plan to src/lib/subscriptions.ts
  4. 4. Create product/prices in Stripe Dashboard
  5. 5. Add price IDs to .env and webhook mapping

Local Testing

Test Cards

  • 4242424242424242 — Success
  • 4000000000000002 — Declined

Stripe CLI Setup

Install the Stripe CLI to test webhooks locally:

# macOS
brew install stripe/stripe-cli/stripe
# Windows (scoop)
scoop install stripe
# Linux / WSL
curl -s https://packages.stripe.dev/api/security/keypair/stripe-cli-gpg/public | gpg --dearmor | sudo tee /usr/share/keyrings/stripe.gpg
echo "deb [signed-by=/usr/share/keyrings/stripe.gpg] https://packages.stripe.dev/stripe-cli-debian-local stable main" | sudo tee -a /etc/apt/sources.list.d/stripe.list
sudo apt update && sudo apt install stripe

Authenticate

Log in to your Stripe account (opens browser):

stripe login

Forward Webhooks

Run this in a separate terminal while developing:

stripe listen --forward-to localhost:3000/api/webhooks/stripe

Copy the webhook signing secret (whsec_...) and add it to your .env as STRIPE_WEBHOOK_SECRET.

Next: Email System

Transactional emails with Resend

Continue →