stripe-integration

SKILL.md

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

  1. ✅ Verify signature with stripe.webhooks.constructEvent()
  2. ✅ Use raw body (not parsed JSON)
  3. ✅ Return 200 quickly, process async
  4. ✅ Handle idempotency (check if already processed)
  5. ✅ Log webhook events for debugging

Event Types to Handle

  • checkout.session.completed - Initial purchase
  • customer.subscription.created - New subscription
  • customer.subscription.updated - Plan change
  • customer.subscription.deleted - Cancellation
  • invoice.payment_failed - Failed payment
  • invoice.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
Weekly Installs
13
First Seen
Jan 25, 2026
Installed on
opencode13
gemini-cli13
antigravity13
claude-code13
windsurf13
codex13