LogoLaunchSaaS

支付

在 LaunchSaaS 中集成 Stripe 或 Creem 支付。处理一次性购买、订阅、Webhook 和客户账单,统一 API 接口。

LaunchSaaS 支持多个支付提供商,具有统一、可扩展的架构。你可以轻松切换提供商、添加自定义实现,或保持支付功能未配置。

支持的提供商

  • Stripe - 行业标准的支付处理器
  • Creem - 替代支付提供商
  • Custom - 使用你自己的支付提供商扩展

架构概览

支付系统包含:

  • Order(订单) - 代表已确认的货币交易(已支付/不可逆)
  • Entitlement(权益) - 代表用户对产品的访问权限
  • Payment Hooks(支付钩子) - 用于支付后操作的可扩展钩子
  • Provider Factory(提供商工厂) - 管理支付提供商实例

所有支付提供商都遵循同一套职责拆分:

  • checkout.completed 只为一次性支付创建订单和权益
  • 订阅支付成功事件负责创建订阅订单并激活权益
  • 其他 subscription.* 事件只同步权益状态和周期日期

配置

启用支付提供商

packages/config/src/features/development.tspackages/config/src/features/production.ts 中配置你的支付提供商:

export const features: Features = {
  payment: {
    provider: "stripe", // "stripe" | "creem"
    hooks: {
      github: true, // 启用 GitHub 集成
      email: true, // 启用支付完成邮件
    },
  },
  // ... 其他功能
};

如果你的项目不需要计费功能,可以直接省略 payment 配置段。

设置 Stripe

1. 创建 Stripe 账户

  1. stripe.com 注册
  2. 完成账户验证(生产环境需要)
  3. 你可以立即在测试模式下开始开发

2. 获取 API 密钥

  1. 前往 Stripe DashboardDevelopersAPI keys
  2. 复制你的密钥(测试模式下以 sk_test_ 开头)
  3. 添加到你的 .env 文件:
STRIPE_SECRET_KEY="sk_test_..."

开发环境使用测试密钥(sk_test_...),生产环境使用实时密钥(sk_live_...)。

3. 创建产品和价格

  1. 前往 Stripe DashboardProduct Catalog
  2. 点击 Add product
  3. 填写产品详情:
    • 名称:例如"终身访问"
    • 描述:你的产品描述
    • 价格:设置价格(一次性或定期)
  4. 点击 "Save product"
  5. 复制 Price ID(以 price_ 开头)

4. 配置产品

packages/config/src/product/development.tspackages/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 转发到本地服务器:

  1. 安装 Stripe CLI:
# macOS
brew install stripe/stripe-cli/stripe

# 其他平台:https://stripe.com/docs/stripe-cli
  1. 登录 Stripe:
stripe login
  1. 转发 webhooks:
stripe listen --forward-to localhost:3000/api/payment/stripe/webhook
  1. 复制 webhook secret(以 whsec_ 开头)并添加到你的 .env 文件:
STRIPE_WEBHOOK_SECRET="whsec_..."
在开发时保持 stripe listen 命令运行。

生产环境

  1. 前往 Stripe DashboardDevelopersWebhooks
  2. 点击 Add endpoint
  3. 设置端点 URL:https://yourdomain.com/api/payment/stripe/webhook
  4. 选择要监听的事件:
    • checkout.session.completed
    • invoice.paid
    • customer.subscription.created
    • customer.subscription.updated
    • customer.subscription.deleted
  5. 点击 Add endpoint
  6. 复制 Signing secret 并添加到生产环境

必须监听的事件

在 Stripe Webhook 端点中配置以下事件:

  • checkout.session.completed
  • invoice.paid
  • customer.subscription.created
  • customer.subscription.updated
  • customer.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 账户

  1. creem.io 注册
  2. 完成账户验证
  3. 创建你的产品

2. 获取 API 密钥

  1. 前往 Creem Dashboard → Settings → API Keys
  2. 复制你的 API key 和 webhook secret
  3. 添加到你的 .env 文件:
CREEM_API_KEY="your-api-key"
CREEM_WEBHOOK_SECRET="your-webhook-secret"

3. 配置产品

packages/config/src/product/development.tspackages/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:orgrepo 权限的 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"
  • 包含订阅的周期信息

自定义支付提供商

要添加自定义支付提供商:

  1. packages/payment/src/providers/ 中实现 PaymentProvider 接口
  2. packages/payment/src/factory.ts 中注册
  3. 添加到 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 处理
  }

  // ... 其他必需方法
}

参考资料

下一步