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
+17 -18
View File
@@ -167,20 +167,15 @@ func TestHandleCallback_UnknownEventAcked(t *testing.T) {
}
// TestHandleCallback_ResidualAmountBackfill:残单兜底——建单时金额回填失败(amount_minor=0),
// 入账前补查权威价成功 → 核对通过并回填
// webhook 自带权威金额(真实流恒非零)→ settle 事务内直接用入参回填并核对通过、入账续期
// settle 不再外呼 queryOrder,此处特意不配置 pay mock server(传 http://pay.invalid),
// 证明回填全程无需外部查单。
func TestHandleCallback_ResidualAmountBackfill(t *testing.T) {
db := testutil.SetupTestDB()
shop := testutil.CreateTestShop(db, "PAY015")
createPendingPurchase(t, db, shop.ID, "annual_standard", 0, "", "yanmei-otn-residual")
mux := http.NewServeMux()
mux.HandleFunc("GET /api/v2/orders/yanmei-otn-residual", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, `{"data":{"order_no":"yanmei-otn-residual","status":"paid","amount_minor":299900,"currency":"CNY"}}`)
})
payServer := httptest.NewServer(mux)
defer payServer.Close()
svc := newTestPaySvc(db, payServer.URL)
svc := newTestPaySvc(db, "http://pay.invalid")
body := callbackBody("yanmei-otn-residual", "annual_standard", 299900)
ts, nonce, sign := signedCallbackArgs(body)
require.NoError(t, svc.HandleCallback(body, ts, nonce, sign))
@@ -188,21 +183,24 @@ func TestHandleCallback_ResidualAmountBackfill(t *testing.T) {
var p model.LicensePurchase
require.NoError(t, db.Where("out_trade_no = ?", "yanmei-otn-residual").First(&p).Error)
assert.Equal(t, "paid", p.Status)
assert.Equal(t, int64(299900), p.AmountMinor, "残单兜底应回填金额")
assert.Equal(t, int64(299900), p.AmountMinor, "残单兜底应用 webhook 权威金额回填")
assert.Equal(t, "CNY", p.Currency)
assert.NotNil(t, p.RenewedTo, "入账应落库续期后到期日")
}
// TestSettle_ResidualQueryFailFailsClosed残单(amount_minor=0)且调用方自己也不知道金额
// amountMinor==0,如 reconcile 遇到 pay 侧字段异常)时,settle 内补查权威价若也失败
// pay 查单 500),必须 fail-closed不入账、不续期、购买单维持 pending。
func TestSettle_ResidualQueryFailFailsClosed(t *testing.T) {
// TestSettle_ZeroAmountInputFailsClosedsettle 入参 amountMinor==0(真实流不应出现,
// 防御性场景)时必须 fail-closedguard 的 p.AmountMinor==0 分支兜底命中 ErrPayAmount
// 不入账、不续期、购买单维持 pending。settle 不再外呼 queryOrder 兜底补查,
// 用调用计数断言零调用,防止该死代码路径回归。
func TestSettle_ZeroAmountInputFailsClosed(t *testing.T) {
db := testutil.SetupTestDB()
shop := testutil.CreateTestShop(db, "PAY016")
createPendingPurchase(t, db, shop.ID, "annual_standard", 0, "", "yanmei-otn-residual-fail")
var queryCalls int
mux := http.NewServeMux()
mux.HandleFunc("GET /api/v2/orders/yanmei-otn-residual-fail", func(w http.ResponseWriter, r *http.Request) {
queryCalls++
w.WriteHeader(http.StatusInternalServerError)
})
payServer := httptest.NewServer(mux)
@@ -210,16 +208,17 @@ func TestSettle_ResidualQueryFailFailsClosed(t *testing.T) {
svc := newTestPaySvc(db, payServer.URL)
err := svc.settle("yanmei-otn-residual-fail", "annual_standard", 0, "", "alipay", time.Now())
assert.ErrorIs(t, err, ErrPayAmount, "补查也失败应 fail-closed")
assert.ErrorIs(t, err, ErrPayAmount, "入参金额为 0 应 fail-closed")
assert.Equal(t, 0, queryCalls, "settle 不应再外呼查单")
var p model.LicensePurchase
require.NoError(t, db.Where("out_trade_no = ?", "yanmei-otn-residual-fail").First(&p).Error)
assert.Equal(t, "pending", p.Status, "补查失败不得入账")
assert.Nil(t, p.RenewedTo, "补查失败不得续期")
assert.Equal(t, "pending", p.Status, "入参金额为 0 不得入账")
assert.Nil(t, p.RenewedTo, "入参金额为 0 不得续期")
var licCount int64
require.NoError(t, db.Model(&model.License{}).Where("shop_id = ?", shop.ID).Count(&licCount).Error)
assert.Equal(t, int64(0), licCount, "补查失败不得续期")
assert.Equal(t, int64(0), licCount, "入参金额为 0 不得续期")
}
// ---------- 回调:入账 ----------