fix(v2): stripe sessionToEvent 回填 SubscriptionRef(订阅首期激活主链路生产可用)

checkout.session.completed 的 sessionToEvent 从不读 sess.Subscription,导致真实
Stripe 路径下 PaidEvent.SubscriptionRef 恒为空,settle.go 的激活门(SubscriptionRef
!= "")恒 false,订阅创建→首付→激活链路在生产环境整体不工作。fake provider 测试
直塞 SubscriptionRef,掩盖了这个接缝。

补 sess.Subscription != nil 时回填 ID(与既有 inv.Subscription 同套路);Kind 保持
零值 EventPayment——分派门只看 SubscriptionRef,不看 Kind。新增 adapter 测试先红
后绿,验证 VerifyCallback 真实路径产出正确 SubscriptionRef。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013nMthbVEmQquxBRKb9Fj8u
This commit is contained in:
wangjia
2026-07-10 19:12:33 +08:00
parent 2a4be0117d
commit 4a1a431f62
2 changed files with 38 additions and 1 deletions
+10 -1
View File
@@ -315,11 +315,20 @@ func sessionToEvent(sess *gostripe.CheckoutSession, raw []byte) *provider.PaidEv
case gostripe.CheckoutSessionPaymentStatusUnpaid:
status = provider.PaidPending
}
return &provider.PaidEvent{
ev := &provider.PaidEvent{
ProviderRef: sess.ID,
Status: status,
PaidAmountMinor: sess.AmountTotal, // cent
PaidCurrency: strings.ToUpper(string(sess.Currency)),
Raw: string(raw),
}
// mode=subscription 的 Checkout 完成时,sess.Subscription 必被 Stripe 填充
// (*Subscription;字符串 id 会 unmarshal 成 &Subscription{ID:...})。一次性单
// (mode=payment)天然为 nil,不影响既有语义。Kind 保持零值 EventPayment——本仓
// 分派门(settle.go)靠 SubscriptionRef != "" 而非 Kind 判断是否触发订阅激活,
// 见 internal/gateway/settle.go 的 onSubscriptionActivated 调用点。
if sess.Subscription != nil {
ev.SubscriptionRef = sess.Subscription.ID
}
return ev
}