feat(backend): 购买单取消透传 pay v2 cancel
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -646,3 +646,94 @@ func TestReconcileOnce_WithinWindowSkipped(t *testing.T) {
|
||||
require.NoError(t, db.Where("out_trade_no = ?", "yanmei-otn-reconcile-fresh").First(&p).Error)
|
||||
assert.Equal(t, "pending", p.Status, "未超窗口的单本轮不应被对账")
|
||||
}
|
||||
|
||||
// ---------- 取消透传(Task 5:防 pending 堆积挤占 reconcile 每轮 50 条限额)----------
|
||||
|
||||
// cancelMux 建一个只服务 POST /api/v2/orders/:no/cancel 的假 pay,固定回给定 canceled 值,
|
||||
// 并统计外呼次数(供"非 pending 单不外呼"断言)。
|
||||
func cancelMux(t *testing.T, otn string, canceled bool, callCount *int) *httptest.Server {
|
||||
t.Helper()
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("POST /api/v2/orders/"+otn+"/cancel", func(w http.ResponseWriter, r *http.Request) {
|
||||
if callCount != nil {
|
||||
*callCount++
|
||||
}
|
||||
fmt.Fprintf(w, `{"data":{"canceled":%v}}`, canceled)
|
||||
})
|
||||
return httptest.NewServer(mux)
|
||||
}
|
||||
|
||||
func TestCancelPurchase_PayCanceledMarksFailed(t *testing.T) {
|
||||
db := testutil.SetupTestDB()
|
||||
shop := testutil.CreateTestShop(db, "PAY022")
|
||||
createPendingPurchase(t, db, shop.ID, "monthly_standard", 29900, "CNY", "yanmei-otn-cancel-1")
|
||||
|
||||
var calls int
|
||||
payServer := cancelMux(t, "yanmei-otn-cancel-1", true, &calls)
|
||||
defer payServer.Close()
|
||||
|
||||
svc := newTestPaySvc(db, payServer.URL)
|
||||
canceled, err := svc.CancelPurchase(shop.ID, "yanmei-otn-cancel-1")
|
||||
require.NoError(t, err)
|
||||
assert.True(t, canceled)
|
||||
assert.Equal(t, 1, calls, "应外呼 pay 一次")
|
||||
|
||||
var p model.LicensePurchase
|
||||
require.NoError(t, db.Where("out_trade_no = ?", "yanmei-otn-cancel-1").First(&p).Error)
|
||||
assert.Equal(t, "failed", p.Status, "pay 回 canceled=true 应本地标 failed")
|
||||
}
|
||||
|
||||
// TestCancelPurchase_PayAlreadyPaidKeepsPending:pay 回 canceled=false(已支付竞态:
|
||||
// 取消请求到达 pay 时单已被支付)→ 本地必须保持 pending,等 webhook/reconcile 正常入账,
|
||||
// 不得被误标 failed(否则钱已收但门店权益丢失)。
|
||||
func TestCancelPurchase_PayAlreadyPaidKeepsPending(t *testing.T) {
|
||||
db := testutil.SetupTestDB()
|
||||
shop := testutil.CreateTestShop(db, "PAY023")
|
||||
createPendingPurchase(t, db, shop.ID, "monthly_standard", 29900, "CNY", "yanmei-otn-cancel-2")
|
||||
|
||||
payServer := cancelMux(t, "yanmei-otn-cancel-2", false, nil)
|
||||
defer payServer.Close()
|
||||
|
||||
svc := newTestPaySvc(db, payServer.URL)
|
||||
canceled, err := svc.CancelPurchase(shop.ID, "yanmei-otn-cancel-2")
|
||||
require.NoError(t, err)
|
||||
assert.False(t, canceled)
|
||||
|
||||
var p model.LicensePurchase
|
||||
require.NoError(t, db.Where("out_trade_no = ?", "yanmei-otn-cancel-2").First(&p).Error)
|
||||
assert.Equal(t, "pending", p.Status, "已支付竞态本地应保持 pending,等 webhook/reconcile 入账")
|
||||
}
|
||||
|
||||
func TestCancelPurchase_OtherShopNotFound(t *testing.T) {
|
||||
db := testutil.SetupTestDB()
|
||||
shop := testutil.CreateTestShop(db, "PAY024")
|
||||
other := testutil.CreateTestShop(db, "PAY025")
|
||||
createPendingPurchase(t, db, shop.ID, "monthly_standard", 29900, "CNY", "yanmei-otn-cancel-3")
|
||||
|
||||
svc := newTestPaySvc(db, "http://pay.invalid")
|
||||
_, err := svc.CancelPurchase(other.ID, "yanmei-otn-cancel-3")
|
||||
assert.ErrorIs(t, err, ErrPurchaseNotFound, "跨店不可见")
|
||||
}
|
||||
|
||||
// TestCancelPurchase_NonPendingNoOpNoExternalCall:非 pending 单(已 paid)直接返回 false,
|
||||
// 且不外呼 pay(避免对已终态单做无意义/有风险的取消请求)。
|
||||
func TestCancelPurchase_NonPendingNoOpNoExternalCall(t *testing.T) {
|
||||
db := testutil.SetupTestDB()
|
||||
shop := testutil.CreateTestShop(db, "PAY026")
|
||||
p := createPendingPurchase(t, db, shop.ID, "monthly_standard", 29900, "CNY", "yanmei-otn-cancel-4")
|
||||
require.NoError(t, db.Model(p).Update("status", "paid").Error)
|
||||
|
||||
var calls int
|
||||
payServer := cancelMux(t, "yanmei-otn-cancel-4", true, &calls)
|
||||
defer payServer.Close()
|
||||
|
||||
svc := newTestPaySvc(db, payServer.URL)
|
||||
canceled, err := svc.CancelPurchase(shop.ID, "yanmei-otn-cancel-4")
|
||||
require.NoError(t, err)
|
||||
assert.False(t, canceled)
|
||||
assert.Equal(t, 0, calls, "非 pending 单不应外呼 pay")
|
||||
|
||||
var got model.LicensePurchase
|
||||
require.NoError(t, db.Where("out_trade_no = ?", "yanmei-otn-cancel-4").First(&got).Error)
|
||||
assert.Equal(t, "paid", got.Status, "非 pending 单状态不变")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user