| name | stripe-integration |
| description | Stripe payment integration for SaaS. Use when implementing Stripe checkout, webhooks, subscriptions, or payment flows. Includes secure patterns for Next.js. |
Stripe Integration for SaaS
Setup
Environment Variables
STRIPE_SECRET_KEY=sk_...
STRIPE_PUBLISHABLE_KEY=pk_...
STRIPE_WEBHOOK_SECRET=whsec_...
Install
pnpm add stripe @stripe/stripe-js
Server Client
// lib/stripe.ts
import Stripe from 'stripe';
export const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
apiVersion: '2024-11-20.acacia',
typescript: true,
});
Checkout Session
// app/api/checkout/route.ts
import { stripe } from '@/lib/stripe';
import { auth } from '@/lib/auth';
export async function POST(request: Request) {
const user = await auth();
if (!user) return new Response('Unauthorized', { status: 401 });
const { priceId } = await request.json();
// Validate price ID against allowed list
const allowedPrices = ['price_xxx', 'price_yyy'];
if (!allowedPrices.includes(priceId)) {
return new Response('Invalid price', { status: 400 });
}
const session = await stripe.checkout.sessions.create({
customer: user.stripeCustomerId,
mode: 'subscription',
line_items: [{ price: priceId, quantity: 1 }],
success_url: `${process.env.NEXT_PUBLIC_URL}/dashboard?success=true`,
cancel_url: `${process.env.NEXT_PUBLIC_URL}/pricing`,
subscription_data: {
metadata: { userId: user.id },
},
});
return Response.json({ url: session.url });
}
Webhook Handler (CRITICAL)
See templates/webhook_handler.ts for complete implementation.
Security Requirements
- ✅ Verify signature with
stripe.webhooks.constructEvent() - ✅ Use raw body (not parsed JSON)
- ✅ Return 200 quickly, process async
- ✅ Handle idempotency (check if already processed)
- ✅ Log webhook events for debugging
Event Types to Handle
checkout.session.completed- Initial purchasecustomer.subscription.created- New subscriptioncustomer.subscription.updated- Plan changecustomer.subscription.deleted- Cancellationinvoice.payment_failed- Failed paymentinvoice.paid- Successful payment
Testing
# Forward webhooks to local
stripe listen --forward-to localhost:3000/api/webhooks/stripe
# Trigger test events
stripe trigger checkout.session.completed
stripe trigger customer.subscription.updated
stripe trigger invoice.payment_failed
Common Errors
"No signatures found matching"
- Check STRIPE_WEBHOOK_SECRET is correct
- Ensure using raw body:
await request.text()
"Webhook timeout"
- Process heavy work async
- Return 200 immediately, use queue for processing