目录

Stripe 支付全指南:一次性付款与订阅的正确建模(Next.js 全栈实践)

在 SaaS、内容订阅和工具类产品里,Stripe 几乎是事实标准。但它的对象多、概念多,很多团队在 MVP 阶段就踩坑:选错集成方式、订阅建模混乱、Webhook 处理不稳,最后被迫重构。

本文从工程视角出发,给出一套可扩展的 Stripe 支付落地方案,覆盖一次性支付与订阅的核心差异、关键对象与数据库结构。

一、先厘清两个层次:集成方式 vs 业务模式

1. 集成方式(Integration)

你如何把 Stripe 接进网站:

集成方式 说明 推荐度
Checkout Stripe 托管收银台(跳转) ⭐⭐⭐⭐⭐
Payment Element / Elements 自定义支付页面(嵌入) ⭐⭐⭐
Payment Links 无代码支付链接 ⭐⭐
Invoicing 发票/对公 ⭐⭐

结论: 大多数 MVP 或早期 SaaS 优先选择 Checkout,简单、稳定、合规成本低。

2. 业务模式(Billing Model)

用户怎么付钱:

模式 Stripe 对象
一次性支付 PaymentIntent
按月订阅 Subscription + Invoice
按年订阅 Subscription + Invoice

按月和按年本质是同一套系统,只是 Price 的 recurring.interval 不同。

二、一次性支付:模型简单,但要保证可追溯

适用场景:购买单次服务、点数包、一次性交付。

推荐流程(Checkout)

  1. 后端创建 Checkout Session(mode=payment
  2. 前端跳转 session.url
  3. 支付成功后 Webhook 确认
  4. 业务订单状态更新

关键对象

  • Checkout Sessioncs_...
  • PaymentIntentpi_...
  • Chargech_...

要点:不要用前端回调当作支付成功的唯一依据,Webhook 才是事实来源。

三、订阅支付:价格与订阅分离建模

1. Stripe 正确建模方式

  • Product:产品,例如 Pro Plan
  • Price:价格,月付/年付是两个 Price
  • Subscription:用户订阅记录

示例

  • pro_monthlyrecurring.interval = month
  • pro_yearlyrecurring.interval = year

2. 订阅流程(Checkout)

  1. 创建 Checkout Session(mode=subscription
  2. 传入 line_items.price
  3. 用户完成支付
  4. Stripe 创建 Subscription
  5. 每期续费生成 Invoice
  6. Webhook 驱动订阅状态

四、一次性 vs 订阅:核心差异一览

维度 一次性支付 订阅(月/年)
Checkout mode payment subscription
核心对象 PaymentIntent Subscription + Invoice
是否自动续费
是否需要订阅表
Webhook 复杂度

五、关键 Key 与环境变量

1. Secret Key(后端)

STRIPE_SECRET_KEY=sk_...

用途:创建 Session / PaymentIntent / Subscription、退款、查询、处理 Webhook。

2. Publishable Key(前端)

NEXT_PUBLIC_STRIPE_PUBLIC_KEY=pk_...

仅在使用 Payment Element 等前端支付组件时需要。若只使用 Checkout 跳转,通常可以不使用。

3. Webhook Secret(生产必备)

STRIPE_WEBHOOK_SECRET=whsec_...

用于验证 Webhook 签名,防止伪造请求。

六、最小但可持续的数据库设计

推荐 4 张表,避免早期重构:

表名 作用
billing_orders 业务订单(统一入口)
billing_payments 支付流水(对账/退款)
billing_subscriptions 订阅状态(激活/取消/到期)
stripe_events Webhook 幂等与审计

为什么要分表?

  • 订单、支付、订阅是三个维度
  • 订阅会周期性扣款
  • Webhook 可能重复投递

七、Webhook 是支付系统的事实来源

前端成功页 ≠ 支付成功。必须以 Stripe Webhook 事件为准:

一次性支付关注:

  • checkout.session.completed
  • payment_intent.succeeded

订阅关注:

  • customer.subscription.created
  • customer.subscription.updated
  • invoice.paid
  • invoice.payment_failed

建议做法:

  • stripe_events 表做幂等去重
  • 使用 event.id 保证一次处理
  • 处理失败要支持重试

八、Next.js 实践建议(简要)

  • API Route 创建 Checkout Session
  • Server Actions/Route Handler 接收 Webhook
  • 中间层(如 billing service)屏蔽 Stripe 细节
  • 状态机 驱动订单/订阅状态迁移

九、落地清单(检查你的实现是否完整)

  • 是否明确区分一次性支付与订阅
  • 是否用 Checkout 做 MVP
  • 是否记录订单、支付、订阅三类实体
  • 是否由 Webhook 作为最终支付事实
  • 是否做了幂等和失败重试

如果你的业务后续需要支持试用、优惠券、多币种或企业对公开票,建议在现有模型上扩展,而不是推翻重写。