All articles
Tutorial

How to Add Stripe Subscriptions to Next.js in Under 2 Hours

February 5, 202612 min read
#stripe#nextjs#typescript#payments
TA

Muhammad Tanveer Abbas

Solo SaaS Builder ยท 6 Products Shipped ยท The MVP Guy

I build production SaaS MVPs in 14 days for non-technical founders. Writing about what actually works โ€“ no fluff.

Stripe Payment Flow

๐Ÿ‘ค
User
โ†’
๐Ÿ›’
Checkout
โ†’
๐Ÿ’ณ
Stripe
โ†’
๐Ÿ””
Webhook
โ†’
๐Ÿ—„๏ธ
DB
โ†’
โœ…
Access

Stripe subscriptions trip up almost every developer the first time. Here's the complete guide webhooks, customer portal, free tiers, and the edge cases that bite you in production.

Step 1: Create Products and Prices in Stripe Dashboard

Before writing any code, set up your products in the Stripe dashboard. Create a product for each tier. For each product, create a recurring price. Copy the price IDs you'll need them.

Use Stripe's test mode for everything during development. Never use live keys locally.

Step 2: Create a Checkout Session

// app/api/checkout/route.ts
export async function POST(req: Request) {
  const { priceId, userId } = await req.json();
  const session = await stripe.checkout.sessions.create({
    mode: "subscription",
    payment_method_types: ["card"],
    line_items: [{ price: priceId, quantity: 1 }],
    success_url: process.env.NEXT_PUBLIC_URL + "/dashboard?success=true",
    cancel_url: process.env.NEXT_PUBLIC_URL + "/pricing",
    metadata: { user_id: userId },
  });
  return Response.json({ url: session.url });
}

Step 3: Handle Webhooks

This is where most developers get it wrong. You must verify the webhook signature. Handle at minimum: checkout.session.completed, customer.subscription.updated, and customer.subscription.deleted.

Always make webhook handlers idempotent. Stripe can send the same event multiple times. Use the event ID to deduplicate in your database.

Step 4: Customer Portal

Don't build your own subscription management UI. Stripe's customer portal handles plan upgrades, downgrades, cancellations, and payment method updates. It takes 10 minutes to set up.

const portalSession = await stripe.billingPortal.sessions.create({
  customer: customerId,
  return_url: process.env.NEXT_PUBLIC_URL + "/dashboard",
});
redirect(portalSession.url);
The full Stripe integration checkout, webhooks, portal, and free tier logic takes about 2 hours if you follow this pattern. I've done it 4 times.

Need Stripe integrated into your MVP? I ship it in 14 days.

Building something similar?

I ship production MVPs in 14 days auth, payments, and everything in between.

Share:

Related Posts