Z Image Turbo 积分充值与 Stripe 后台配置指南
Z Image Turbo 积分充值与 Stripe 后台配置指南
作者:Z Image Turbo Team | 发布时间:2026年7月3日 | 阅读时间:约 8 分钟
Z Image Turbo 现在使用一次性积分充值,不做月订阅。用户按需购买积分,生成图片时扣除积分。本文说明当前积分包规则、Stripe 后台如何创建产品和价格、Webhook 如何配置,以及上线前需要检查哪些环境变量和数据库迁移。
当前积分包
当前使用一个基础积分包,通过 Stripe Checkout 的数量倍数实现不同金额:
| 用户看到的价格 | Stripe 数量 | 到账积分 |
|---|---|---|
| $10 | 1 | 500 积分 |
| $20 | 2 | 1,000 积分 |
| $50 | 5 | 2,500 积分 |
| $100 | 10 | 5,000 积分 |
当前每次图像生成消耗 1 积分。如果生成因为服务错误失败,系统会自动退回本次消耗的积分。
Stripe 后台命名规范
建议只创建一个一次性 Price,不要创建四个不同商品。当前代码使用同一个 Price ID,并通过 quantity=1,2,5,10 来得到不同充值档位。
推荐命名如下:
| Stripe 对象 | 推荐名称 |
|---|---|
| Product name | Z Image Turbo Credits |
| Product description | Credits for AI image generation on Z Image Turbo |
| Price nickname | Credit Pack 500 - $10 |
| Price type | One-time |
| Unit amount | $10.00 USD |
Metadata app |
z-image-turbo |
Metadata kind |
credit_pack |
Metadata base_credits |
500 |
注意:代码需要的是 price_... 开头的 Price ID,不是 prod_... 开头的 Product ID。
Stripe 后台创建 Product 和 Price
在 Stripe Dashboard 中按下面步骤操作:
- 进入 Product catalog。
- 点击 Add product。
- Product name 填:
Z Image Turbo Credits。 - Description 填:
Credits for AI image generation on Z Image Turbo。 - Pricing model 选择标准一次性价格。
- Price type 选择 One-time,不要选择 Recurring。
- Unit amount 填
$10.00,Currency 选择USD。 - Price nickname 填:
Credit Pack 500 - $10。 - 保存后复制
price_...开头的 Price ID。 - 把这个 Price ID 填到环境变量
STRIPE_PRICE_CREDIT_PACK_500。
当前代码会把这个 $10 Price 当成基础单元。用户买 $20 时,系统传给 Stripe 的 quantity 是 2;用户买 $50 时,quantity 是 5。
需要配置的环境变量
生产环境至少需要这些变量:
STRIPE_SECRET_KEY=sk_live_xxx
STRIPE_WEBHOOK_SECRET=whsec_xxx
STRIPE_PRICE_CREDIT_PACK_500=price_xxx
CREDIT_PACK_BASE_CREDITS=500
CREDIT_PACK_BASE_USD_CENTS=1000
CREDIT_PACK_ALLOWED_QUANTITIES=1,2,5,10
含义如下:
| 变量 | 含义 |
|---|---|
STRIPE_SECRET_KEY |
Stripe Secret key,生产环境使用 sk_live_... |
STRIPE_WEBHOOK_SECRET |
Webhook signing secret,通常是 whsec_... |
STRIPE_PRICE_CREDIT_PACK_500 |
$10 / 500 积分的 Price ID |
CREDIT_PACK_BASE_CREDITS |
一个基础包包含多少积分 |
CREDIT_PACK_BASE_USD_CENTS |
一个基础包价格,单位是美分,1000 表示 $10 |
CREDIT_PACK_ALLOWED_QUANTITIES |
允许购买的数量倍数 |
推荐把 STRIPE_SECRET_KEY 和 STRIPE_WEBHOOK_SECRET 放到 Cloudflare secret,不要写进公开配置文件:
bunx wrangler secret put STRIPE_SECRET_KEY
bunx wrangler secret put STRIPE_WEBHOOK_SECRET
STRIPE_PRICE_CREDIT_PACK_500 可以放在 wrangler.jsonc 的 vars 中,也可以按你的发布流程用环境变量注入。
Webhook 后台配置
在 Stripe Dashboard 中配置 Webhook:
- 进入 Developers。
- 进入 Webhooks。
- 点击 Add endpoint。
- Endpoint URL 填:
https://z-image-turbo.com/api/stripe/webhook
- 当前一次性积分充值只选择下面这些事件:
checkout.session.completed
checkout.session.async_payment_succeeded
checkout.session.async_payment_failed
charge.refunded
refund.updated
当前项目没有月订阅逻辑,暂时不要配置 customer.subscription.deleted、customer.subscription.updated、invoice.payment_failed、invoice.payment_succeeded。这些事件属于订阅续费场景,当前代码不会消费它们。
- 创建后进入该 webhook 详情页。
- 复制 Signing secret,它通常是
whsec_...。 - 把它配置为
STRIPE_WEBHOOK_SECRET。
Webhook 是发放积分的关键入口。用户支付成功后,Stripe 会调用这个地址,系统验证签名后才会给用户加积分。
charge.refunded 和 refund.updated 用于同步在 Stripe 后台或 Z Image Turbo 后台发起的退款状态。积分扣回仍建议通过 Z Image Turbo 后台的退款确认表单执行,避免用户余额被自动扣成负数。
当前需要配置的事件
| 事件 | 当前用途 | 是否配置 |
|---|---|---|
checkout.session.completed |
用户完成 Checkout 后发放积分 | 必须 |
checkout.session.async_payment_succeeded |
异步支付最终成功后发放积分 | 建议保留 |
checkout.session.async_payment_failed |
异步支付失败后标记订单失败 | 建议保留 |
charge.refunded |
Stripe 退款后同步订单退款状态 | 建议保留 |
refund.updated |
Stripe 退款状态变化后同步订单 | 建议保留 |
未来做订阅时再配置的事件
如果后续重新加入月订阅或自动续费积分,再增加下面这些事件,并同步新增订阅表、订阅状态同步、续费发积分和失败续费处理逻辑:
checkout.session.completed
customer.subscription.created
customer.subscription.updated
customer.subscription.deleted
invoice.payment_succeeded
invoice.payment_failed
如果一次性积分包和订阅同时存在,当前这组一次性支付事件仍然保留:
checkout.session.async_payment_succeeded
checkout.session.async_payment_failed
charge.refunded
refund.updated
使用 Stripe CLI 创建生产 Webhook
如果不想在 Stripe Dashboard 手动创建,也可以用 Stripe CLI 创建真实环境的 webhook endpoint。下面命令对应当前一次性积分充值事件。这里建议显式传入 sk_live_...,这样最不容易误建到测试环境:
stripe webhook_endpoints create \
--api-key sk_live_xxx \
--url="https://z-image-turbo.com/api/stripe/webhook" \
-d "description=Z Image Turbo production webhook" \
-d "enabled_events[0]=checkout.session.completed" \
-d "enabled_events[1]=checkout.session.async_payment_succeeded" \
-d "enabled_events[2]=checkout.session.async_payment_failed" \
-d "enabled_events[3]=charge.refunded" \
-d "enabled_events[4]=refund.updated"
如果本机已经通过 stripe login 登录到正确的 Stripe 账户,也可以使用 live 模式:
stripe webhook_endpoints create --live \
--url="https://z-image-turbo.com/api/stripe/webhook" \
-d "description=Z Image Turbo production webhook" \
-d "enabled_events[0]=checkout.session.completed" \
-d "enabled_events[1]=checkout.session.async_payment_succeeded" \
-d "enabled_events[2]=checkout.session.async_payment_failed" \
-d "enabled_events[3]=charge.refunded" \
-d "enabled_events[4]=refund.updated"
需要注意:--live 只表示请求 live mode,不代表当前 CLI key 一定有足够权限。stripe login 生成的 CLI key 通常是 rk_live_... 这种 restricted key。如果这个 key 没有 webhook endpoint 写权限,Stripe 会返回类似下面的错误:
{
"error": {
"message": "The provided key 'rk_live_...' does not have the required permissions for this endpoint..."
}
}
遇到这个错误时,不是项目代码问题,而是 Stripe API key 权限不足。最稳的处理方式是在 Stripe Dashboard 手动创建 webhook;如果仍想用 CLI,就改用有权限的 sk_live_...,或者在 Dashboard 中给对应 restricted key 增加 webhook endpoint 创建权限。
创建成功后,Stripe 返回结果里会包含 secret: whsec_...。这个值就是生产环境要使用的 STRIPE_WEBHOOK_SECRET,建议立刻写入 Cloudflare secret:
bunx wrangler secret put STRIPE_WEBHOOK_SECRET
注意不要把 stripe listen 当成生产 webhook 配置。stripe listen 主要用于本地开发转发,例如把 Stripe 测试事件转发到 http://127.0.0.1:5187/api/stripe/webhook。生产环境需要的是上面这种持久的 webhook endpoint。
如果命令中使用的是 sk_test_...,或者没有明确使用 live 模式,就会创建测试环境 webhook,真实付款不会打到这个 endpoint。创建后建议在 Stripe Dashboard 的 Developers → Webhooks 里确认该 endpoint 的模式是 live,URL 是 https://z-image-turbo.com/api/stripe/webhook,事件列表只包含当前需要的一次性积分充值事件。
参考官方文档:
- Stripe CLI resource commands: https://docs.stripe.com/cli/resources
- Create a webhook endpoint: https://docs.stripe.com/api/webhook_endpoints/create?lang=cli
- Stripe CLI listen: https://docs.stripe.com/cli/listen
Customer Portal 配置
当前账户页会在用户已有 Stripe Customer 后显示 Manage billing 入口。使用前需要在 Stripe Dashboard 中启用 Customer Portal:
- 进入 Settings。
- 进入 Billing 下的 Customer portal。
- 开启 invoice/payment history 相关能力。
- 保存配置。
当前项目只做一次性积分充值,不需要启用订阅管理能力。如果后续重新加入订阅,再在 Customer Portal 中打开 subscription update/cancel 相关配置。
支付到账逻辑
当前实现流程如下:
- 用户在账户页选择积分包。
- 前端请求
/api/billing/checkout,传入 quantity。 - 后端校验 quantity 是否在
1,2,5,10之内。 - 后端创建
billing_order订单,状态为pending。 - 后端创建 Stripe Checkout Session,使用
mode=payment。 - 用户跳转到 Stripe 完成支付。
- Stripe 发送 webhook 到
/api/stripe/webhook。 - 后端验证 Stripe 签名。
- 后端找到对应的
billing_order。 - 后端给用户增加积分,并写入
credit_ledger。 - 订单状态更新为
paid。
防止重复到账
Stripe 可能会重试 webhook,所以发积分必须幂等。
当前系统使用 Stripe 支付引用作为积分流水引用:
ref_type = stripe_payment_intent
ref_id = pi_xxx
如果没有 PaymentIntent,则回退为:
ref_type = stripe_checkout_session
ref_id = cs_xxx
系统在发放积分前会检查 credit_ledger 中是否已经存在同一引用。即使 Stripe 重试 webhook,同一笔支付也不会重复到账。
数据库迁移
上线前需要在远程 D1 执行迁移,创建 billing_order 表:
bunx wrangler d1 migrations apply z-image-turbo-prod --remote
如果只在本地测试,可以执行:
bun run db:migrate:local
没有执行迁移时,支付订单无法写入数据库,Checkout 会失败。
本地测试建议
本地测试 Stripe 时建议使用 Stripe 测试模式:
- Stripe Dashboard 切到 test mode。
- 创建 test mode 下的 Product 和 Price。
- 使用
sk_test_...和 test mode 的price_...。 - 使用 Stripe CLI 转发 webhook:
stripe listen --forward-to http://127.0.0.1:5187/api/stripe/webhook
- 把 CLI 输出的
whsec_...填到本地STRIPE_WEBHOOK_SECRET。 - 使用 Stripe 测试卡完成支付:
4242 4242 4242 4242
任意未来有效期
任意 CVC
注意:本地如果需要 D1/R2 绑定,应使用 Cloudflare 本地开发方式或确保当前开发环境能访问对应绑定。
上线检查清单
上线前逐项确认:
- Stripe Product 名称是
Z Image Turbo Credits。 - Stripe Price 是 One-time,不是 Recurring。
- Price ID 是
price_...,不是prod_...。 -
STRIPE_SECRET_KEY使用 live secret key。 -
STRIPE_WEBHOOK_SECRET使用生产 webhook 的 signing secret。 -
STRIPE_PRICE_CREDIT_PACK_500使用生产 Price ID。 - 已执行远程 D1 迁移。
- Webhook endpoint 是
https://z-image-turbo.com/api/stripe/webhook。 - Webhook 事件包含
checkout.session.completed、checkout.session.async_payment_succeeded、checkout.session.async_payment_failed、charge.refunded、refund.updated。 - 当前没有启用订阅事件:
customer.subscription.*、invoice.payment_*。 - Stripe Customer Portal 已启用 invoice/payment history。
- 账户页能显示积分包。
- 支付成功后
billing_order状态变为paid。 -
credit_ledger出现credit_pack_purchase记录。 - 用户
credit_balance增加对应积分。
常见错误
填了 Product ID 而不是 Price ID
STRIPE_PRICE_CREDIT_PACK_500 必须是 price_...,不是 prod_...。
创建成了订阅价格
当前代码只处理一次性支付。Stripe Price 必须是 One-time。如果创建成 Recurring,Checkout 或 webhook 逻辑会和当前实现不匹配。
test 和 live 混用
sk_test_... 只能配 test mode 的 price_...。sk_live_... 只能配 live mode 的 price_...。Webhook secret 也分 test/live,不能混用。
Webhook 没配置签名 secret
没有正确的 STRIPE_WEBHOOK_SECRET 时,系统会拒绝 webhook,请求会返回签名错误,积分不会到账。
Stripe CLI 报 rk_live 权限不足
如果执行 stripe webhook_endpoints create --live ... 时看到:
The provided key 'rk_live_...' does not have the required permissions for this endpoint
说明当前 Stripe CLI 使用的是 live restricted key,但这个 key 没有创建 webhook endpoint 的权限。rk_live_... 是受限密钥,权限由 Stripe Dashboard 控制;--live 只切换到真实环境,不会自动提升权限。
推荐处理顺序:
- 优先在 Stripe Dashboard 的 Developers → Webhooks → Add endpoint 手动创建生产 webhook。
- 或者用有权限的
sk_live_...执行 CLI 命令,并用--api-key sk_live_xxx显式指定。 - 或者在 Dashboard 的 Developers → API keys → Restricted keys 中找到当前 CLI key,给它增加 webhook endpoint 创建权限。
生产 webhook 创建成功后,仍然要复制该 endpoint 的 whsec_...,并把它配置成 STRIPE_WEBHOOK_SECRET。
没有执行 D1 迁移
如果没有 billing_order 表,支付订单无法创建。上线前必须执行远程迁移。
退款处理建议
当前系统已经有后台退款入口:
- 进入
/admin/billing。 - 找到状态为
paid的订单。 - 在退款区域输入
REFUND确认。 - 默认勾选“扣回本订单积分”。
- 提交后系统调用 Stripe Refund API。
- 如果退款成功,
billing_order会标记为refunded,并写入stripe_refund_id。 - 如果勾选扣回积分,系统最多扣回用户当前可用余额,不会把余额扣成负数。
如果直接在 Stripe Dashboard 发起退款,Webhook 会同步订单退款状态,但不会自动扣回积分。遇到这类订单时建议在后台审计日志和用户积分流水中人工复核。
为什么现在不做月订阅
积分包更简单,也更适合当前阶段:
- 用户只在需要时付费。
- 没有自动续费争议。
- 余额和消耗清晰可查。
- Stripe webhook 逻辑更简单。
- 后台订单和积分流水更容易对账。
等积分充值稳定后,再考虑自动充值或订阅会更稳妥。