package store_test import ( "testing" "time" "github.com/wangjia/pay/internal/model" "github.com/wangjia/pay/internal/store" ) func seedOrder(t *testing.T, s *store.OrderStore, no string) { t.Helper() if err := s.CreateOrder(&model.OrderV2{ OutTradeNo: no, BizSystem: "pangolin", BizRef: "u-1", ProductID: 3, AmountMinor: 29990000, Currency: "USDT", Status: model.OrderPendingV2, }); err != nil { t.Fatalf("create order: %v", err) } if err := s.CreateAttempt(&model.Attempt{ OutTradeNo: no, Channel: "crypto", AccountID: "usdt-a1", ProviderRef: "P-" + no, RenderType: "crypto_address", AmountMinor: 29990000, Currency: "USDT", Status: model.AttemptPending, ExpiresAt: ptr(time.Now().Add(15 * time.Minute)), }); err != nil { t.Fatalf("create attempt: %v", err) } } func ptr(t time.Time) *time.Time { return &t } func TestMarkPaidIdempotentAndCancel(t *testing.T) { s := store.NewOrderStore(model.OpenTestDB(t)) seedOrder(t, s, "PAY-1") ok, err := s.MarkAttemptPaid("PAY-1", "crypto", "P-PAY-1", time.Now()) if err != nil || !ok { t.Fatalf("mark#1 ok=%v err=%v", ok, err) } // 幂等:再标 → false ok2, _ := s.MarkAttemptPaid("PAY-1", "crypto", "P-PAY-1", time.Now()) if ok2 { t.Fatalf("第二次 MarkAttemptPaid 应 false") } // paid 后不能取消 if okc, _ := s.CancelOrder("PAY-1"); okc { t.Fatalf("paid 单不应可取消") } // 另一单可取消,且列表可见 seedOrder(t, s, "PAY-2") if okc, err := s.CancelOrder("PAY-2"); err != nil || !okc { t.Fatalf("cancel PAY-2 ok=%v err=%v", okc, err) } list, _ := s.ListOrders("pangolin", "u-1", 20) var sawCanceled bool for _, o := range list { if o.OutTradeNo == "PAY-2" && o.Status == model.OrderCanceledV2 { sawCanceled = true } } if !sawCanceled { t.Fatalf("canceled 单应出现在列表") } } func TestMarkAttemptPaidProviderRefMismatchRollsBack(t *testing.T) { db := model.OpenTestDB(t) s := store.NewOrderStore(db) seedOrder(t, s, "PAY-X") ok, err := s.MarkAttemptPaid("PAY-X", "crypto", "P-WRONG", time.Now()) if err == nil { t.Fatalf("providerRef 错配应返回 error, got ok=%v err=%v", ok, err) } if ok { t.Fatalf("providerRef 错配不应返回 ok=true") } var o model.OrderV2 if err := db.Where("out_trade_no = ?", "PAY-X").First(&o).Error; err != nil { t.Fatalf("query order: %v", err) } if o.Status != model.OrderPendingV2 { t.Fatalf("事务应回滚, order status 应仍为 pending, got %v", o.Status) } }