fix(v2): settleRenewal 重投补入队(通知不再永久丢)+ 无业务方首过返回 Processed(+同型路径核修)

settleRenewal 先 CreateRenewalPaid(建单已 paid)后 Enqueue,首次入队失败后重投走
created=false 的 duplicate 分支旧实现直接 return、不再入队,subscription.renewed
永久丢失(SyncPendingAttempts 救不了)。改为 duplicate 分支也无条件尝试入队,outbox
唯一键 ON CONFLICT DO NOTHING 幂等自愈;顺带对齐无业务方首过应返回 Processed。核查
发现 finalizeCanceled/markSubscriptionPastDue 有同型反纪律,同修。

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 18:30:11 +08:00
parent 62abdc1d65
commit ff65a06bf2
4 changed files with 186 additions and 26 deletions
+13 -1
View File
@@ -35,7 +35,11 @@ func (stubResolver) Resolve(sku, currency string) (int64, string, string, error)
type spyEnqueuer struct {
calls []map[string]any
failNext bool // 置 true 模拟 outbox 入队失败(settle 崩溃窗口测试用)
failNext bool // 置 true 模拟 outbox 入队失败(settle 崩溃窗口测试用)
seen map[string]bool // 镜像真实 WebhookStore.EnqueueDelivery 的 (out_trade_no,event_type,
// refund_id) 唯一键 ON CONFLICT DO NOTHING:重复 key 静默 no-op(不追加 calls,也不算失败)。
// 修复"重投补入队自愈"后,业务代码会在 duplicate/重投分支也调用 Enqueue,若 spy 仍是无脑
// 计数器就会把 outbox 天然幂等的重复行误判成"多发了一次 webhook",这里镜像真实幂等语义。
}
func (s *spyEnqueuer) Enqueue(outTradeNo, bizSystem, eventType, refundID string, data map[string]any) error {
@@ -43,6 +47,14 @@ func (s *spyEnqueuer) Enqueue(outTradeNo, bizSystem, eventType, refundID string,
s.failNext = false
return errors.New("outbox down")
}
key := outTradeNo + "|" + eventType + "|" + refundID
if s.seen == nil {
s.seen = make(map[string]bool)
}
if s.seen[key] {
return nil // 幂等 no-op,行已存在
}
s.seen[key] = true
s.calls = append(s.calls, data)
return nil
}