支付
在 LaunchSaaS 中集成 Stripe 或 Creem 支付。处理一次性购买、订阅、Webhook 和客户账单,统一 API 接口。
LaunchSaaS 支持多个支付提供商,具有统一、可扩展的架构。你可以轻松切换提供商、添加自定义实现,或保持支付功能未配置。
支持的提供商
- Stripe - 行业标准的支付处理器
- Creem - 替代支付提供商
- Custom - 使用你自己的支付提供商扩展
架构概览
支付系统包含:
- Order(订单) - 代表已确认的货币交易(已支付/不可逆)
- Entitlement(权益) - 代表用户对产品的访问权限
- Payment Hooks(支付钩子) - 用于支付后操作的可扩展钩子
- Provider Factory(提供商工厂) - 管理支付提供商实例
所有支付提供商都遵循同一套职责拆分:
checkout.completed只为一次性支付创建订单和权益- 订阅支付成功事件负责创建订阅订单并激活权益
- 其他
subscription.*事件只同步权益状态和周期日期
配置
启用支付提供商
在 packages/config/src/features/development.ts 或 packages/config/src/features/production.ts 中配置你的支付提供商:
export const features: Features = {
payment: {
provider: "stripe", // "stripe" | "creem"
hooks: {
github: true, // 启用 GitHub 集成
email: true, // 启用支付完成邮件
},
},
// ... 其他功能
};如果你的项目不需要计费功能,可以直接省略 payment 配置段。
设置 Stripe
1. 创建 Stripe 账户
- 在 stripe.com 注册
- 完成账户验证(生产环境需要)
- 你可以立即在测试模式下开始开发
2. 获取 API 密钥
- 前往 Stripe Dashboard →
Developers→API keys - 复制你的密钥(测试模式下以
sk_test_开头) - 添加到你的
.env文件:
STRIPE_SECRET_KEY="sk_test_..."开发环境使用测试密钥(sk_test_...),生产环境使用实时密钥(sk_live_...)。
3. 创建产品和价格
- 前往 Stripe Dashboard →
Product Catalog - 点击
Add product - 填写产品详情:
- 名称:例如"终身访问"
- 描述:你的产品描述
- 价格:设置价格(一次性或定期)
- 点击 "Save product"
- 复制 Price ID(以
price_开头)
4. 配置产品
在 packages/config/src/product/development.ts 或 packages/config/src/product/production.ts 中更新产品配置:
const prod: ProviderProductConfiguration = {
stripe: {
onetime: [
{
id: "price_1SWtddRwnUQyjRPerb8ge9xD",
name: "终身访问",
hooks: ["github-integration", "payment-completed-email"],
},
],
subscription: [
{
id: "price_1SOwEE2Kn68A5jDtD9vcpirC",
name: "专业版月付",
hooks: ["payment-completed-email"],
},
],
pricingPageProduct: {
type: "onetime",
id: "price_1SWtddRwnUQyjRPerb8ge9xD",
},
},
};5. 设置 Webhooks
Webhooks 实时通知你的应用程序支付事件。
开发环境(本地测试)
使用 Stripe CLI 将 webhooks 转发到本地服务器:
- 安装 Stripe CLI:
# macOS
brew install stripe/stripe-cli/stripe
# 其他平台:https://stripe.com/docs/stripe-cli- 登录 Stripe:
stripe login- 转发 webhooks:
stripe listen --forward-to localhost:3000/api/payment/stripe/webhook- 复制 webhook secret(以
whsec_开头)并添加到你的.env文件:
STRIPE_WEBHOOK_SECRET="whsec_..."stripe listen 命令运行。生产环境
- 前往 Stripe Dashboard →
Developers→Webhooks - 点击
Add endpoint - 设置端点 URL:
https://yourdomain.com/api/payment/stripe/webhook - 选择要监听的事件:
checkout.session.completedinvoice.paidcustomer.subscription.createdcustomer.subscription.updatedcustomer.subscription.deleted
- 点击
Add endpoint - 复制 Signing secret 并添加到生产环境
必须监听的事件
在 Stripe Webhook 端点中配置以下事件:
checkout.session.completedinvoice.paidcustomer.subscription.createdcustomer.subscription.updatedcustomer.subscription.deleted
Stripe Webhook 流程
LaunchSaaS 使用分离事件模型,三条职责线完全独立,不受事件投递顺序影响。
一次性支付流程
订阅流程
事件职责
| 事件 | 操作 |
|---|---|
checkout.session.completed (mode=payment) | 创建订单 + 激活权益(一次性) |
checkout.session.completed (mode=subscription) | 忽略——订阅由 invoice.paid 处理 |
invoice.paid | 创建订单 + upsert 权益为 active |
customer.subscription.created(试用期) | 创建权益,状态为 trialing |
customer.subscription.created/updated | 更新权益:周期、状态、取消信息(若不存在则为空操作) |
customer.subscription.deleted | 更新权益状态为 canceled |
为什么这样设计
checkout.session.completed(mode=payment) 是一次性购买的明确信号,mode字段无歧义。invoice.paid是订阅付款成功的权威信号——无论其他事件是否已到达,它都会 upsert 权益为active。customer.subscription.*分两种情况:对于trialing订阅,它负责创建权益(试用期间不会触发invoice.paid);对于其他状态,它只更新已有记录,永不将订阅升级为active,因此即使在invoice.paid之前到达也不会产生错误的访问授权。
这确保即使 Stripe 乱序投递事件,权益更新也始终正确。
幂等性
| 记录 | 键 |
|---|---|
| 订单(一次性) | stripe_${payment_intent.id} |
| 订单(订阅) | stripe_${invoice.id} |
| 权益(一次性) | stripe_pi_${payment_intent.id} |
| 权益(订阅) | stripe_${subscription.id} |
重复投递的事件是安全的——重复插入会被静默忽略。
测试卡
用于测试 Stripe 集成的测试信用卡:
| 卡号 | 描述 |
|---|---|
4242 4242 4242 4242 | 成功支付 |
4000 0000 0000 3220 | 需要 3D 验证 |
4000 0000 0000 9995 | 余额不足 |
使用任何未来的过期日期和任何 3 位 CVC。
设置 Creem
1. 创建 Creem 账户
- 在 creem.io 注册
- 完成账户验证
- 创建你的产品
2. 获取 API 密钥
- 前往 Creem Dashboard → Settings → API Keys
- 复制你的 API key 和 webhook secret
- 添加到你的
.env文件:
CREEM_API_KEY="your-api-key"
CREEM_WEBHOOK_SECRET="your-webhook-secret"3. 配置产品
在 packages/config/src/product/development.ts 或 packages/config/src/product/production.ts 中更新产品配置:
const prod: ProviderProductConfiguration = {
creem: {
onetime: [
{
id: "prod_3h218lKmpAIM9xTv97Mdy7",
name: "终身访问",
hooks: ["github-integration", "payment-completed-email"],
},
],
pricingPageProduct: {
type: "onetime",
id: "prod_3h218lKmpAIM9xTv97Mdy7",
},
},
};4. 设置 Webhooks
在 Creem Dashboard 中配置 webhook 端点:
- URL:
https://yourdomain.com/api/payment/creem/webhook - Events: 所有支付相关事件
如果你正在设置环境,现在可以返回环境设置指南继续。
支付钩子
LaunchSaaS 提供可扩展的钩子系统用于支付后操作。钩子在 packages/config/src/features/ 中配置。
内置钩子
GitHub 集成
支付后自动将客户添加为 GitHub 协作者。
在 features.ts 中启用:
payment: {
hooks: {
github: true,
},
}配置环境变量:
GITHUB_TOKEN="your-personal-access-token"
GITHUB_REPO="owner/repo"在 GitHub Settings 生成具有 admin:org 和 repo 权限的 GitHub Personal Access Token
支付完成邮件
成功支付后发送确认邮件。
在 features.ts 中启用:
payment: {
hooks: {
email: true,
},
}自定义钩子
通过在 packages/payment/src/hooks/ 中实现 PaymentHook 接口来创建自定义支付钩子:
import { PaymentHook, AfterHookContext } from "@/lib/features/payment/hook";
export class CustomHook implements PaymentHook {
name = "custom-hook";
async onCheckoutComplete(context: AfterHookContext): Promise<void> {
// 你的自定义逻辑
console.log(`产品 ${context.productId} 的支付已完成`);
}
}在 packages/payment/src/hook-register.ts 中注册你的钩子:
if (features.payment.hooks.custom) {
hooks.push(new CustomHook());
}数据库架构
Order 表
代表已确认的货币交易:
- 支付确认时创建
- 一次性产品在结账完成时创建订单
- 订阅产品在每次成功支付时创建订单
Entitlement 表
代表用户对产品的访问权限:
- 订单支付时授予/延长
- 一次性产品创建状态为 "active" 的权益
- 订阅产品创建状态为 "pending" 的权益,首次支付后变为 "active"
- 包含订阅的周期信息
自定义支付提供商
要添加自定义支付提供商:
- 在
packages/payment/src/providers/中实现PaymentProvider接口 - 在
packages/payment/src/factory.ts中注册 - 添加到
packages/config/src/schemas/site-configuration.ts中的Features类型
示例:
export class CustomProvider implements PaymentProvider {
readonly name = "custom";
async createCheckout(options: CheckoutOptions): Promise<CheckoutResult> {
// 实现结账创建
}
async handleWebhook(request: Request): Promise<Response> {
// 实现 webhook 处理
}
// ... 其他必需方法
}