refactor(backend): 清理 settle 残单预查死代码块——入参金额直接回填,fail-closed 语义不变

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
wangjia
2026-07-11 01:31:39 +08:00
parent 3b5d84a7e3
commit 18b97d5d35
2 changed files with 26 additions and 37 deletions
+9 -19
View File
@@ -284,25 +284,15 @@ func (s *PayService) replayed(nonce string) bool {
return false
}
// settle 入账:幂等(同 out_trade_no 只续一次)+ 金额核对(残单先兜底回填)+ 同事务续期。
// webhook 与查单兜底共用此入口。
// settle 入账:幂等(同 out_trade_no 只续一次)+ 金额核对(残单用入参权威金额回填)+ 同事务续期。
// webhook 与查单兜底reconcileOnce)共用此入口,两个调用方都自带权威金额(webhook 签名负载 /
// reconcile 自身 queryOrder 结果),恒非零。
//
// 残单兜底(D1)的查单外呼是只读操作,不需要持行锁:调用方(webhook 签名负载 /
// reconcile 自身查单结果)若已经带来非零金额,直接复用,不重复外呼 pay;只有调用方
// 也不知道金额(amountMinor==0)时,才在事务外先轻量读一次购买单(无锁 First),
// 若其 amount_minor 也是 0 才现查一次权威价。事务内仍以 FOR UPDATE 读到的行值为准做
// 短路与核对,若行内金额仍为 0 才回填;补查失败/仍未知 → 核对不过 → ErrPayAmountfail-closed)。
// 事务内以 FOR UPDATE 读到的行值为准:若行内 amount_minor 仍为 0(残单,建单时回填曾失败),
// 直接用入参 amountMinor 回填落库;若行内已有金额,则入参必须与之一致(防篡改)。核对逻辑
// p.AmountMinor==0 || amountMinor != p.AmountMinor || currency 不一致 → ErrPayAmount)对两种
// 情形都成立:入参为 0(不应出现于真实流)→ 回填后行内仍为 0 → 核对不过,fail-closed。
func (s *PayService) settle(outTradeNo, bizCode string, amountMinor int64, currency, channel string, paidAt time.Time) error {
fillMinor, fillCurrency := amountMinor, currency
if fillMinor == 0 {
var pre model.LicensePurchase
if err := s.db.Where("out_trade_no = ?", outTradeNo).First(&pre).Error; err == nil && pre.AmountMinor == 0 {
if st, qerr := s.queryOrder(outTradeNo); qerr == nil && st.AmountMinor > 0 {
fillMinor, fillCurrency = st.AmountMinor, st.Currency
}
}
}
var shopID uint64
err := s.db.Transaction(func(tx *gorm.DB) error {
var p model.LicensePurchase
@@ -316,8 +306,8 @@ func (s *PayService) settle(outTradeNo, bizCode string, amountMinor int64, curre
if p.Status == "paid" { // 幂等:pay 会重发
return nil
}
if p.AmountMinor == 0 && fillMinor > 0 { // 行内金额仍为 0 才回填,事务外查到的权威价直接落库
p.AmountMinor, p.Currency = fillMinor, fillCurrency
if p.AmountMinor == 0 && amountMinor > 0 { // 行内金额仍为 0 才回填,入参权威金额直接落库
p.AmountMinor, p.Currency = amountMinor, currency
if err := tx.Model(&model.LicensePurchase{}).Where("id = ?", p.ID).
Updates(map[string]any{"amount_minor": p.AmountMinor, "currency": p.Currency}).Error; err != nil {
return err