package reconcile_test import ( "context" "testing" "time" "github.com/wangjia/pay/config" "github.com/wangjia/pay/internal/accounts" "github.com/wangjia/pay/internal/gateway" "github.com/wangjia/pay/internal/model" "github.com/wangjia/pay/internal/provider" "github.com/wangjia/pay/internal/provider/fake" "github.com/wangjia/pay/internal/reconcile" "github.com/wangjia/pay/internal/store" ) type stubResolver struct{} func (stubResolver) Resolve(sku, currency string) (int64, string, string, error) { return 29990000, "Pro", "pro_year", nil } type nopEnq struct{} // Enqueue 签名依当前仓库 gateway.WebhookEnqueuer(4 个 string + map;brief 草稿只写 3 个 — // P4 T2 之后加了 refundID 参数,此处适配现状,见 task-5-report.md 记录的漂移)。 func (nopEnq) Enqueue(_, _, _, _ string, _ map[string]any) error { return nil } func TestSyncPendingTaskSettlesViaQuery(t *testing.T) { db := model.OpenTestDB(t) orders := store.NewOrderStore(db) refunds := store.NewRefundStore(db) preg := provider.NewRegistry() fp := fake.New() preg.Register(fp) areg := accounts.New([]config.AccountConfig{{AccountID: "fake-a1", Channel: "fake", Region: "global", Enabled: true}}) gw := gateway.New(orders, refunds, preg, accounts.NewRouter(areg, nil, nil), stubResolver{}, nopEnq{}, "global", store.NewSubscriptionStore(db), store.NewChargebackStore(db)) res, _ := gw.CreateOrder(context.Background(), gateway.CreateOrderInput{SKU: "pro_year", Method: "fake", BizSystem: "pangolin", BizRef: "u-1"}) atts, _ := orders.ListAttemptsByStatus(model.AttemptPending, 10) fp.SetQueryResult(atts[0].ProviderRef, provider.PaidEvent{ ProviderRef: atts[0].ProviderRef, Status: provider.PaidSucceeded, PaidAmountMinor: 29990000, PaidCurrency: "USDT"}) task := reconcile.SyncPendingTask(gw, 50) if err := task(context.Background()); err != nil { t.Fatalf("task: %v", err) } o, _ := orders.GetOrder(res.OrderNo) if o.Status != model.OrderPaidV2 { t.Fatalf("查单对账后应 paid, got %v", o.Status) } } func TestPaidSpotCheckTaskRunsCleanOnConsistent(t *testing.T) { db := model.OpenTestDB(t) orders := store.NewOrderStore(db) preg := provider.NewRegistry() fp := fake.New() preg.Register(fp) now := time.Date(2026, 7, 10, 12, 0, 0, 0, time.UTC) paid := now.Add(-10 * time.Minute) _ = orders.CreateAttempt(&model.Attempt{OutTradeNo: "O1", Channel: "fake", ProviderRef: "R-O1", AmountMinor: 100, Currency: "USDT", Status: model.AttemptPaid, PaidAt: &paid}) // 渠道侧查单仍报 succeeded 同额 → 一致,无告警。 fp.SetQueryResult("R-O1", provider.PaidEvent{ProviderRef: "R-O1", Status: provider.PaidSucceeded, PaidAmountMinor: 100, PaidCurrency: "USDT"}) task := reconcile.PaidSpotCheckTask(orders, preg, time.Hour, func() time.Time { return now }) if err := task(context.Background()); err != nil { t.Fatalf("spotcheck: %v", err) // 只求不报错、不 panic;漂移检测走日志 } }