Dev Pattern Labs logo
Dev Pattern Labs Blog
Automation 2 min readOct 27, 2026

Next.js 15 Stripe Subscriptions & Billing Customer Portal Guide

Build a SaaS subscription engine in Next.js 15 with Stripe Billing, Checkout Sessions, Customer Portal redirects, and webhook lifecycle event handlers.

Dev Pattern Labs Author

Dev Pattern Labs

Peer-reviewed technical tutorial · Production tested

Credit card and financial analytics screen representing SaaS subscription billing
Executive Summary & Key Insight

Build a SaaS subscription engine in Next.js 15 with Stripe Billing, Checkout Sessions, Customer Portal redirects, and webhook lifecycle event handlers. This practical guide covers step-by-step setup, real-world code snippets, security hardening, and common production edge cases.

Executive Summary & Key Takeaway (AEO)
Engineering SaaS recurring billing in Next.js 15 App Router requires three components: creating Stripe Checkout Sessions for initial subscription signups, generating Stripe Billing Customer Portal sessions for self-service tier upgrades/cancellations, and maintaining a background webhook receiver handling customer.subscription.updated and invoice.payment_failed events.


Architectural Sequence

sequenceDiagram
    participant User
    participant NextJS as Next.js 15 App Router
    participant Stripe as Stripe Billing API
    participant DB as Postgres / Supabase

    User->>NextJS: Clicks "Upgrade to Pro ($49/mo)"
    NextJS->>Stripe: stripe.checkout.sessions.create()
    Stripe-->>NextJS: returns session.url
    NextJS-->>User: Redirects to Stripe Checkout
    User->>Stripe: Enters Card & Completes Payment
    Stripe->>NextJS: Webhook: checkout.session.completed
    NextJS->>DB: Updates user.subscription_status = 'active'
    Stripe-->>User: Redirects back to /dashboard

Step 1: Creating Stripe Checkout Sessions via Server Actions

Create app/actions/stripe.ts:

'use server';

import Stripe from 'stripe';
import { redirect } from 'next/navigation';
import { getServerSession } from 'next-auth';
import { authOptions } from '@/lib/auth';

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
  apiVersion: '2024-12-18.acacia' as any,
});

export async function createCheckoutSession(priceId: string) {
  const session = await getServerSession(authOptions);
  if (!session?.user?.email) {
    redirect('/api/auth/signin');
  }

  const siteUrl = process.env.NEXT_PUBLIC_SITE_URL || 'https://blogs.devpatternlabs.com';

  const checkoutSession = await stripe.checkout.sessions.create({
    mode: 'subscription',
    payment_method_types: ['card'],
    customer_email: session.user.email,
    line_items: [{ price: priceId, quantity: 1 }],
    success_url: `${siteUrl}/dashboard?session_id={CHECKOUT_SESSION_ID}`,
    cancel_url: `${siteUrl}/pricing`,
    metadata: {
      userId: session.user.email,
    },
  });

  if (!checkoutSession.url) {
    throw new Error('Failed to generate checkout session');
  }

  redirect(checkoutSession.url);
}

Step 2: Creating a Self-Service Customer Portal Session

Allow users to update credit cards, switch tiers, or cancel subscriptions without custom billing UI:

export async function createCustomerPortalSession(stripeCustomerId: string) {
  const siteUrl = process.env.NEXT_PUBLIC_SITE_URL || 'https://blogs.devpatternlabs.com';

  const portalSession = await stripe.billingPortal.sessions.create({
    customer: stripeCustomerId,
    return_url: `${siteUrl}/dashboard`,
  });

  redirect(portalSession.url);
}

Step 3: Handling Webhook Lifecycle Events

Add subscription listeners inside your webhook route:

switch (event.type) {
  case 'customer.subscription.created':
  case 'customer.subscription.updated': {
    const sub = event.data.object as Stripe.Subscription;
    console.log(`User subscription updated: ${sub.id}, Status: ${sub.status}`);
    // Update local database record with active tier
    break;
  }
  case 'customer.subscription.deleted': {
    const sub = event.data.object as Stripe.Subscription;
    console.log(`Subscription cancelled: ${sub.id}`);
    // Revoke access in database
    break;
  }
}

Tags:#Next.js#Stripe#subscriptions#SaaS#webhooks#customer portal
Dev Pattern Labs Engineering Team

Written by Dev Pattern LabsCore Engineering

Dev Pattern Labs is an engineering lab and digital consultancy specializing in custom Next.js web applications, WooCommerce REST API integrations, and workflow automation for growing businesses.

Recommended Companion Articles