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
- Create an account → https://dashboard.stripe.com/register
- Go to Developers → API keys and grab:
- Publishable key (
pk_test_...) - Secret key (
sk_test_...)
- Publishable key (
- 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).
- In Stripe Dashboard → Developers → Webhooks
- Add endpoint:
https://yourdomain.com/api/stripe/webhook - Subscribe to events:
checkout.session.completedpayment_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
