👋You’re viewing the live demo of NextFlow Kit — the SaaS starter template. Everything here is included out of the box.
Nov 8, 2025

How to Add Stripe Payments to Your Next.js App

A clear, step-by-step guide to setting up Stripe Checkout in your Next.js app. From dashboard setup to server routes and webhooks.

#nextjs#stripe#payments#saas

Accepting payments is one of the most important parts of launching a product online. Whether you're selling software, subscriptions, or digital products, Stripe makes it simple, secure, and developer-friendly 💪. This guide shows how to integrate Stripe Checkout into a Next.js app, connect keys safely, and handle webhooks.

💼 Why Stripe

Stripe is trusted by startups and giants alike. It ships:

  • Modern APIs and excellent docs
  • Prebuilt UIs (Checkout) for fast time-to-live
  • 135+ currencies 🌎 and global payment methods
  • Automatic receipts, refunds, and tax options
  • Smooth pairing with Next.js server routes

Docs:

  • https://stripe.com/docs/payments/checkout
  • https://dashboard.stripe.com

⚙️ Step 1: Create a Stripe account & keys

  1. Create an account → https://dashboard.stripe.com/register
  2. Go to Developers → API keys and grab:
    • Publishable key (pk_test_...)
    • Secret key (sk_test_...)
  3. Add to your env (Vercel Project Settings or .env.local):
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_xxx
STRIPE_SECRET_KEY=sk_test_xxx
NEXT_PUBLIC_APP_URL=http://localhost:3000

Keep STRIPE_SECRET_KEY server-only.


🧱 Step 2: Install Stripe SDKs

pnpm add stripe
# Optional if using client-side Elements later:
pnpm add @stripe/stripe-js

🧩 Step 3: Create a Checkout Session (server route)

app/api/create-checkout-session/route.ts

import Stripe from "stripe";
import { NextResponse } from "next/server";

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
  apiVersion: "2025-04-30.basil",
});

export async function POST() {
  try {
    const session = await stripe.checkout.sessions.create({
      mode: "payment",
      line_items: [
        {
          price_data: {
            currency: "usd",
            product_data: { name: "Pro Template License" },
            unit_amount: 2000, // $20.00 (amount is in cents)
          },
          quantity: 1,
        },
      ],
      success_url: `${process.env.NEXT_PUBLIC_APP_URL}/success`,
      cancel_url: `${process.env.NEXT_PUBLIC_APP_URL}/cancel`,
      allow_promotion_codes: true,
    });

    return NextResponse.json({ url: session.url });
  } catch (err) {
    console.error(err);
    return NextResponse.json({ error: "Failed to create session" }, { status: 500 });
  }
}

🧭 Step 4: Trigger Checkout from the client

A simple client component to fetch the session and redirect:

"use client";
import { useState } from "react";

export function CheckoutButton() {
  const [loading, setLoading] = useState(false);

  async function handleCheckout() {
    setLoading(true);
    const res = await fetch("/api/create-checkout-session", { method: "POST" });
    const data = await res.json();
    if (data?.url) window.location.href = data.url;
    setLoading(false);
  }

  return (
    <button
      onClick={handleCheckout}
      disabled={loading}
      className="bg-black text-white px-4 py-2 rounded-md"
      aria-busy={loading}
    >
      {loading ? "Redirecting…" : "Buy Now 💳"}
    </button>
  );
}

🧾 Step 5: Success & cancel pages

app/success/page.tsx

export default function SuccessPage() {
  return (
    <div className="max-w-lg mx-auto text-center py-20">
      <h2 className="text-3xl font-bold mb-4">🎉 Payment Successful!</h2>
      <p className="text-neutral-600">
        Thanks for your purchase! your product is on its way.
      </p>
    </div>
  );
}

app/cancel/page.tsx (optional)

export default function CancelPage() {
  return (
    <div className="max-w-lg mx-auto text-center py-20">
      <h2 className="text-3xl font-bold mb-4">Payment Canceled</h2>
      <p className="text-neutral-600">
        No worries, you can try again whenever you’re ready.
      </p>
    </div>
  );
}

⚡ Step 6: Webhooks (optional but powerful)

Use webhooks to confirm payments server-to-server (e.g., to grant access or email a download link).

  1. In Stripe Dashboard → Developers → Webhooks
  2. Add endpoint: https://yourdomain.com/api/stripe/webhook
  3. Subscribe to events:
    • checkout.session.completed
    • payment_intent.succeeded
    • (for subs) invoice.paid, customer.subscription.created

Docs:

  • https://stripe.com/docs/webhooks
  • Example starter: https://github.com/vercel/nextjs-subscription-payments

🧪 Step 7: Test mode cards

Before going live, test with Stripe’s cards:

  • Success: 4242 4242 4242 4242
  • Declined: 4000 0000 0000 0002

Docs: https://stripe.com/docs/testing


🧠 SEO & conversion tips

  • Unique metadata for pricing/checkout pages (title + description).
  • Add Product/Offer structured data if you show pricing.
  • Use trust signals: testimonials, logos, refund policy.
  • Keep pages fast (Next.js <Image>, next/font) for better Core Web Vitals.

✅ Launch checklist 🚀

  • [ ] Env vars set in Vercel (Preview + Production)
  • [ ] Success/cancel URLs verified
  • [ ] Amounts correct (in cents)
  • [ ] (If used) webhook endpoint tested
  • [ ] Metadata + canonical URLs added
  • [ ] Pages pass Lighthouse > 90

Flip your keys from test_ to live_, redeploy, and you’re ready to accept real payments 🎉

More reading:

  • Checkout: https://stripe.com/docs/payments/checkout
  • Elements: https://stripe.com/docs/elements
  • Next.js deployment: https://nextjs.org/docs/app/building-your-application/deploying