stripe-integration

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Stripe Integration for SaaS

面向SaaS的Stripe集成

Setup

配置

Environment Variables

环境变量

env
STRIPE_SECRET_KEY=sk_...
STRIPE_PUBLISHABLE_KEY=pk_...
STRIPE_WEBHOOK_SECRET=whsec_...
env
STRIPE_SECRET_KEY=sk_...
STRIPE_PUBLISHABLE_KEY=pk_...
STRIPE_WEBHOOK_SECRET=whsec_...

Install

安装

bash
pnpm add stripe @stripe/stripe-js
bash
pnpm add stripe @stripe/stripe-js

Server Client

服务端客户端

typescript
// lib/stripe.ts
import Stripe from 'stripe';

export const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
  apiVersion: '2024-11-20.acacia',
  typescript: true,
});
typescript
// 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

结账会话

typescript
// 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 });
}
typescript
// 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)

Webhook处理程序(关键)

See templates/webhook_handler.ts for complete implementation.
完整实现可查看templates/webhook_handler.ts文件。

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
  1. ✅ 使用
    stripe.webhooks.constructEvent()
    验证签名
  2. ✅ 使用原始请求体(而非解析后的JSON)
  3. ✅ 快速返回200状态码,异步处理业务逻辑
  4. ✅ 处理幂等性(检查事件是否已被处理过)
  5. ✅ 记录Webhook事件便于调试

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
  • checkout.session.completed
    - 首次购买
  • customer.subscription.created
    - 新订阅创建
  • customer.subscription.updated
    - 套餐变更
  • customer.subscription.deleted
    - 订阅取消
  • invoice.payment_failed
    - 支付失败
  • invoice.paid
    - 支付成功

Testing

测试

bash
undefined
bash
undefined

Forward webhooks to local

将Webhook转发到本地环境

stripe listen --forward-to localhost:3000/api/webhooks/stripe
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
undefined
stripe trigger checkout.session.completed stripe trigger customer.subscription.updated stripe trigger invoice.payment_failed
undefined

Common Errors

常见错误

"No signatures found matching"

"未找到匹配的签名"

  • Check STRIPE_WEBHOOK_SECRET is correct
  • Ensure using raw body:
    await request.text()
  • 检查STRIPE_WEBHOOK_SECRET是否正确
  • 确保使用原始请求体:
    await request.text()

"Webhook timeout"

"Webhook超时"

  • Process heavy work async
  • Return 200 immediately, use queue for processing
  • 异步处理耗时任务
  • 立即返回200状态码,使用队列处理后续逻辑