接入 Stripe 支付

配置 Stripe 产品、价格和 Webhook,连接购买流程与权益发放。

上一篇已经定义套餐和权益,并实现使用限制。这一篇配置 Stripe,让购买、支付通知和权益发放形成完整流程。

接入支付系统

我们已经在前面完成 products.ts 中的产品配置,现在需要接入真实的支付系统。

这里以 Stripe 为例,假设你已经完成注册并开通了收款权限。第一次接入时,建议先在测试模式下跑通 Checkout 和 Webhook,再切换到正式模式。两个环境各自使用一套商品、Price ID 和密钥,不能混用。

创建产品

接下来可以让 AI 创建网站的 Logo 图片。我一般直接把商品图片设置为网站 Logo;如果不想这样做,也可以根据 products.ts 中的产品配置单独生成商品图片。

然后再让 AI 根据配置生成对应的名称和描述,方便在 Stripe 中添加产品。例如下面是我的提示词:

我要将产品发布到 Stripe 上,你帮我生成对应的 name 和 description,积分包也要有。

AI 给我生成了下面的表格:

产品NameDescription
Pro 会员Webpage to PDF ProGet 60 minutes of webpage conversion time every day across Quick Convert, Custom, and Visual Editor, plus 500 API conversion credits each month. Failed conversions do not use your allowance.
500 积分包Webpage to PDF API Credits — 500500 prepaid credits for successful API conversions. One successful conversion uses one credit. Purchased credits do not expire, and failed conversions do not use credits.
2,000 积分包Webpage to PDF API Credits — 2,0002,000 prepaid credits for successful API conversions. One successful conversion uses one credit. Purchased credits do not expire, and failed conversions do not use credits.
10,000 积分包Webpage to PDF API Credits — 10,00010,000 prepaid credits for successful API conversions. One successful conversion uses one credit. Purchased credits do not expire, and failed conversions do not use credits.

虽然首版不会在网站上展示积分包,但可以先在 Stripe 中把这些商品建好,等 API 功能开放后再启用对应套餐。

打开 Stripe 的 Product catalog 页面,点击右上角的 Create product,将上面的表格数据填入对应输入框,然后点击 Add product 完成创建。

Stripe 产品创建

创建完成后的产品列表如下:

Stripe 产品列表

接下来就是将产品和积分包对应的 Price ID 配置到 config/products.ts 中,我们这里拿 Pro 会员为例,点击刚刚创建的 Pro 会员产品,在打开的页面中可以看到该产品的 Pricing 列表:

Stripe 产品 Pricing 列表

你可以点击这些列表项查看这些 Pricing 项目的详情,又或者打开右侧的操作菜单,在下拉列表中选择 Copy price ID 复制对应的 Price ID。

Copy Price ID

按照上面的步骤,补全 config/products.ts 中的产品配置,核心是替换配置中的 priceId 字段,注意每个套餐配置应填写对应的 Price ID,Pro 月付和年付分别使用各自的 Price ID,不要混用,否则会导致支付系统无法正确扣费。

配置 Webhook

Stripe 提供了 Webhook 功能,可以让我们在用户支付成功后收到通知,我们根据这些通知来更新数据库中的订单状态,以及对应的权益和额度。

设置 Webhook 的步骤很简单。首先在 Stripe 后台页面的下方点击 Developers 菜单:

Stripe Developers

选择 Webhooks 面板:

Stripe Webhooks

点击页面中间的 Add destination,进入 Create an event destination 页面:

Stripe Create Event Destination

接下来的操作就是选择当前网站关注的事件列表,目前的 Saavo 项目中关注的事件列表如下:

checkout.session.completed
checkout.session.async_payment_succeeded
checkout.session.async_payment_failed
checkout.session.expired
customer.subscription.created
customer.subscription.updated
customer.subscription.paused
customer.subscription.resumed
customer.subscription.deleted
invoice.finalized
invoice.finalization_failed
invoice.paid
invoice.payment_failed
invoice.payment_action_required
invoice.marked_uncollectible
invoice.voided
refund.created
refund.updated
refund.failed

一共 19 个事件:

Stripe Webhook Events

点击 Continue,在新页面中选择 Webhook endpoint 类型。确认后按页面提示填写表单并完成创建。

Stripe Create Webhook

复制密钥到本地

创建好 Webhook 后,Stripe 会生成一个 Webhook Secret,需要将它保存到本地环境变量中。该密钥以 whsec_ 开头。

Webhook Secret

复制后保存到项目根目录的 .env.production 文件中:

STRIPE_WEBHOOK_SECRET="whsec_xxxxxxxxxxxxxxxxxxxxxx"

如果没找到 .env.production 文件,可以创建一个,不影响后续使用。

除了 Webhook 密钥,还需要将 Stripe 的连接 ID 和 Secret Key 配置到本地环境变量中。

STRIPE_CONNECTION_ID="webpage_to_pdf"
STRIPE_SECRET_KEY="sk_live_xxxxxxxxxxxxxxxxxxxxxx"

其中,STRIPE_SECRET_KEY 可以在 Stripe 后台找到,正式密钥以 sk_live_ 开头。

Stripe Secret Key

STRIPE_CONNECTION_ID 是自己定义的连接 ID,用来区分不同的支付连接。

使用脚本快速设置

前面演示的是手动创建方式,Webhook 也可以使用项目脚本快速设置。这个脚本不会替你申请 Stripe 密钥,只是代替“配置 Webhook”小节中的后台操作。

使用前先运行 npm run,确认项目包含这些命令。旧模板可能需要同时更新脚本入口和实现,参见项目命令。

生产环境执行 npm run webhook:stripe:prod,开发环境执行 npm run webhook:stripe:dev。下面演示的是开发环境的设置过程。

执行前,需要先配置对应环境的 STRIPE_SECRET_KEY,否则脚本会报错:

C:\code\webpagetopdf>npm run webhook:stripe:dev

> webpagetopdf@0.0.1 webhook:stripe:dev
> tsx scripts/stripe/setup.ts development

Webhook site origin [http://127.0.0.1:5173]: https://7c7d-2605-52c0-2-1ebf-be24-11ff-fe62-cadd.ngrok-free.app
✔ Select the webhook event API version 2026-08-26.dahlia (project default)
✔ Select Stripe events checkout.session.completed, checkout.session.async_payment_succeeded, checkout.session.async_payment_failed, checkout.session.expired, customer.subscription.created, customer.subscription.updated, customer.subscription.paused, customer.subscription.resumed, customer.subscription.deleted, invoice.finalized, invoice.finalization_failed, invoice.paid, invoice.payment_failed, invoice.payment_action_required, invoice.marked_uncollectible, invoice.voided, refund.created, refund.updated, refund.failed
Environment: development
Destination: https://7c7d-2605-52c0-2-1ebf-be24-11ff-fe62-cadd.ngrok-free.app/api/webhooks/stripe
Webhook API version: 2026-08-26.dahlia
Selected events: 19
Create this Stripe webhook destination? [Y/n]:
Stripe webhook destination created: we_1UEnlFLG8YPa5bzGMCs1REYC
Configuration file: C:\code\webpagetopdf\.env
The new signing secret was saved without being printed.
Restart the development server if the signing secret changed.

完善支付逻辑

Saavo 已经把支付相关的底层逻辑处理好了,这里不需要再自己调用 Stripe SDK。我们只需要根据 products.ts 中配置的产品和套餐,把 Pricing 页面和购买入口接上。

整体流程大概是这样:

  1. Pricing 页面读取产品和套餐配置。用户点击购买后,将 productId 和 planId 提交到 /api/payments/checkout/:productId/:planId。
  2. 后端根据这两个参数重新读取套餐配置,检查用户是否登录、套餐是否存在、是否允许重复购买,然后使用套餐中的 priceId 创建 Stripe Checkout Session。
  3. 接口返回 Stripe Checkout 地址,前端跳转过去完成付款。
  4. Stripe 在支付、退款、订阅状态变化时,会向 /api/webhooks/stripe 发送 Webhook。后端使用 STRIPE_WEBHOOK_SECRET 验证签名,再更新订单、交易、订阅和退款记录。
  5. 系统根据 Stripe 返回的 Price ID 找到 products.ts 中对应的套餐,再读取 access.roles 和 access.entitlements。
  6. 一次性付款成功后会触发 payment_succeeded。订阅创建、更新或终止时,也会触发对应事件,角色和权益会跟着自动更新。

有一点需要注意,不能根据前端是否跳转成功来判断付款结果。用户可能中途关闭页面,也可以自己构造请求,最终的支付状态要以 Stripe Webhook 为准。

Saavo 已经处理了 Webhook 持久化和重复事件。某次处理失败时,接口会返回失败状态,Stripe 后面还会继续重试。我们真正需要改的主要还是前端 UI,包括套餐展示、调用 Checkout 接口、处理登录状态和接口错误等等。

订单、订阅、角色和权益这些数据都交给后端 Webhook 更新,前端不要自己改,否则会导致数据不一致。

简单来说,可以把 Pricing 页面和购买入口交给 AI 完成,让它按照当前的 products.ts 配置处理即可。

本篇检查

完成这一篇后,应在测试模式下走通购买、Webhook 回调和权益发放,再为正式环境准备对应的配置。

教程总览 · 上一篇:设计套餐与接入权益 · 下一篇:完善网站与部署上线