Stripe Payment 服务
充值单与 Checkout
为什么 Stripe 一次性充值应该建立在 balance topup 之上,而不是直接改余额。
@downcity/services 这一版不是 subscription 或 entitlement 系统,它的核心是:
让 Stripe 一次性支付去完成一笔已经存在的 topup。
为什么必须先有 topup
因为“用户说我要充钱”和“系统确认已经到账”不是一回事。
在 Downcity 里,更稳定的建模方式是:
- 先创建
pending topup - 再去 Stripe 支付
- 支付成功后完成 topup
这样做的好处是:
- 充值金额的可信源在 City,不在前端
- Stripe 支付记录和内部充值单可以稳定一一对应
- 到账动作统一收口到
balance.finishTopup()
最常见链路
const topup = await user.service("balance").action("topups/create").invoke({
credits: 500_000_000,
note: "recharge",
});
const checkout = await user.service("payment").action("checkout/create").invoke({
method_id: "stripe",
topup_id: topup.topup_id,
});前端跳转到 checkout.checkout_url 后,用户就在 Stripe 完成支付。
并发创建行为
同一个 topup_id + method_id 同时收到多个创建请求时,PaymentService 只会向 Provider 创建一次 Checkout。首个请求持有短租约;租约期间的并发请求返回 409,前端稍后重试即可。Checkout 已创建后,重复请求会返回同一笔 payment 和 checkout_url。
服务会把稳定的 payment_id 作为 Stripe 幂等键。即使创建请求在网络返回前中断,后续接管也不会生成第二笔 Stripe Checkout。
为什么不让 Stripe 服务直接收金额
如果让前端直接把金额传给 Stripe 服务,再由它独立发起支付,会有几个问题:
- 前端金额更容易被篡改
- 内部没有统一的充值单记录
- 后续到账、失败、过期都很难对齐
所以这层的关键不是“怎么收钱”,而是“先让 City 拿到可信 topup,再让 Stripe 去完成它”。
常用入口
POST /v1/balance/topups/createPOST /v1/payment/checkout/create