fix: reconcile ambiguous Stripe event ordering
Some checks failed
CI / verify (push) Has been cancelled
Some checks failed
CI / verify (push) Has been cancelled
This commit is contained in:
@@ -130,14 +130,18 @@ RETURNING used_units;
|
||||
- `payments.provider_payment_id` ↔ Stripe `payment_intent.id`(或 charge id,按实现选)
|
||||
|
||||
### 4.2 Checkout / Portal
|
||||
- Checkout:后端创建 Stripe Checkout Session,前端跳转 `checkout_url`。
|
||||
- Checkout:后端按用户行锁串行创建 Stripe Checkout Session,前端跳转 `checkout_url`。服务端使用稳定 Customer 幂等键和待支付记录 ID 对应的 Session 幂等键,不依赖客户端 `Idempotency-Key`。
|
||||
- 每个用户只能映射一个非空 Stripe Customer,同时只能存在一个未过期待支付记录和一个未取消 Stripe 订阅。并发请求复用同一 Session;Customer 映射未持久化时禁止返回或创建 Session。
|
||||
- 已有未取消 Stripe 订阅的用户不能再次进入订阅 Checkout,升级、降级、续费和取消统一走 Portal,避免多重周期扣费。
|
||||
- Portal:后端创建 Stripe Billing Portal Session,前端跳转管理支付方式/取消订阅。
|
||||
|
||||
### 4.3 Stripe Webhook(商用必须)
|
||||
要求:
|
||||
- **验签**:使用 `STRIPE_WEBHOOK_SECRET` 校验 `Stripe-Signature`。
|
||||
- **事件幂等**:按 `provider_event_id` 去重(落库 `webhook_events`)。
|
||||
- **乱序容忍**:订阅对象按 `(event.created, 事件优先级, event.id)` 保存独立水位;`deleted` 即使先到也会保留 tombstone,旧 `created/updated` 不得恢复已取消订阅。
|
||||
- **乱序容忍**:订阅对象按 `(event.created, 事件优先级)` 保存独立水位;`deleted` 即使先到也会保留 tombstone,旧 `created/updated` 不得恢复已取消订阅。
|
||||
- **同秒歧义**:两个不同事件具有相同 `(event.created, 事件优先级)` 时,不能用不透明的 Event ID 排序,必须从 Stripe 拉取当前订阅快照并以快照响应时间推进水位。
|
||||
- **迁移对账**:历史版本用本地 `subscriptions.updated_at` 播种的非终态水位会标记为待对账;API 后台任务持租约获取 Stripe 快照,成功后才清除标记。未映射 Customer 或 Price 的受管订阅事件返回失败并等待重试,不能标记为已处理。
|
||||
- **并发一致性**:`subscriptions(provider, provider_subscription_id)` 唯一,订阅业务写入与 `webhook_events=processed` 在同一事务提交。
|
||||
- **可重放**:保存原始 payload(脱敏)用于排查。
|
||||
|
||||
|
||||
Reference in New Issue
Block a user