package service // 服务层白盒测试:验证支付回调落授权核心链路(无真实商户,绕过 SDK 调用)。 // 运行方式: cd server && GF_GCFG_FILE=biz/service/testdata/config.yml go test ./biz/service/ import ( "context" "strconv" "testing" "time" "github.com/gogf/gf/v2/database/gdb" "github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/os/gtime" "observer-server/biz/consts" "observer-server/biz/dao" "observer-server/biz/model/dto" "observer-server/biz/model/entity" "observer-server/common" ) func ctx() context.Context { return context.Background() } // uniquePhone / uniqueOrderId 每次运行生成唯一值,避免测试库残留数据冲突 func uniquePhone() string { return "138" + strconv.FormatInt(time.Now().UnixNano()%100000000, 10) } func uniqueOrderId(prefix string) string { return prefix + strconv.FormatInt(time.Now().UnixNano()%100000000, 10) } // insertOrder 直插一条 created 订单(模拟下单链路产出) func insertOrder(t *testing.T, o *entity.PaymentOrder) { t.Helper() err := g.DB().Transaction(ctx(), func(ctx context.Context, tx gdb.TX) error { return dao.PaymentOrder.InsertOneInTx(ctx, tx, o) }) if err != nil { t.Fatalf("insert order: %v", err) } } // TestPersistPaid 回调落授权:created → paid + 授权生效,重复回调幂等 func TestPersistPaid(t *testing.T) { phone := uniquePhone() orderId := uniqueOrderId("O-test-persist-") wxTrade := uniqueOrderId("wx-trade-") insertOrder(t, &entity.PaymentOrder{ OrderId: orderId, PhoneNum: phone, PlanId: "week", Channel: consts.ChannelWechat, AmountCents: 5600, Status: consts.OrderStatusCreated, CreatedAt: gtime.Now(), }) if err := Payment.persistPaid(ctx(), orderId, wxTrade, consts.ChannelWechat, 0); err != nil { t.Fatalf("persistPaid: %v", err) } // 订单已支付 o, err := dao.PaymentOrder.GetByPk(ctx(), orderId) if err != nil { t.Fatal(err) } if o.Status != consts.OrderStatusPaid || o.WxTradeNo == nil || *o.WxTradeNo != wxTrade { t.Fatalf("order not paid: status=%s wx=%v", o.Status, o.WxTradeNo) } // 授权生效:week = 今天 0 点 + 7 天 lic, err := dao.License.GetByPhone(ctx(), phone) if err != nil { t.Fatal(err) } if lic == nil || lic.ExpiresAt == nil { t.Fatal("license not granted") } want := time.Date(time.Now().Year(), time.Now().Month(), time.Now().Day(), 0, 0, 0, 0, time.Local).AddDate(0, 0, 7) if !lic.ExpiresAt.Time.Equal(want) { t.Fatalf("expiresAt = %s, want %s", lic.ExpiresAt.Time, want) } // 授权查询接口返回 active licRes, err := License.Get(common.WithPhone(ctx(), phone), &dto.LicenseReq{}) if err != nil { t.Fatal(err) } if !licRes.Active { t.Fatalf("license res = %+v", licRes) } // confirm 返回 paid conf, err := Order.Confirm(ctx(), &dto.ConfirmOrderReq{OrderId: orderId}) if err != nil { t.Fatal(err) } if conf.Status != "paid" { t.Fatalf("confirm status = %s, want paid", conf.Status) } // 重复回调幂等:不报错、不重复累加授权 before := lic.ExpiresAt if err := Payment.persistPaid(ctx(), orderId, wxTrade, consts.ChannelWechat, 0); err != nil { t.Fatalf("repeat persistPaid: %v", err) } lic2, _ := dao.License.GetByPhone(ctx(), phone) if !lic2.ExpiresAt.Time.Equal(before.Time) { t.Fatalf("idempotency broken: %s -> %s", before, lic2.ExpiresAt) } } // TestPersistPaidRenewal 续费叠加:未过期顺延(base=现有到期),已过期从当前时间起算 func TestPersistPaidRenewal(t *testing.T) { phone := uniquePhone() order1 := uniqueOrderId("O-test-renew-1-") insertOrder(t, &entity.PaymentOrder{ OrderId: order1, PhoneNum: phone, PlanId: "week", Channel: consts.ChannelAlipay, AmountCents: 5600, Status: consts.OrderStatusCreated, CreatedAt: gtime.Now(), }) if err := Payment.persistPaid(ctx(), order1, uniqueOrderId("ali-trade-"), consts.ChannelAlipay, 5600); err != nil { t.Fatalf("persistPaid 1: %v", err) } lic, _ := dao.License.GetByPhone(ctx(), phone) // 第二次购买 month:base = 现有到期(未过期),30 天叠加 order2 := uniqueOrderId("O-test-renew-2-") insertOrder(t, &entity.PaymentOrder{ OrderId: order2, PhoneNum: phone, PlanId: "month", Channel: consts.ChannelWechat, AmountCents: 18000, Status: consts.OrderStatusCreated, CreatedAt: gtime.Now(), }) if err := Payment.persistPaid(ctx(), order2, uniqueOrderId("wx-trade-"), consts.ChannelWechat, 0); err != nil { t.Fatalf("persistPaid 2: %v", err) } lic2, _ := dao.License.GetByPhone(ctx(), phone) want := lic.ExpiresAt.Time.AddDate(0, 0, 30) if !lic2.ExpiresAt.Time.Equal(want) { t.Fatalf("renewal expiresAt = %s, want %s (base %s)", lic2.ExpiresAt, want, lic.ExpiresAt) } // 购买的套餐记录在 payment_order,license 只留到期时间(此处验证第二次下单的订单快照) o2, _ := dao.PaymentOrder.GetByPk(ctx(), order2) if o2 == nil || o2.PlanId != "month" { t.Fatalf("order plan snapshot = %v, want month", o2) } // 支付宝回调金额不匹配 → 拒绝 order3 := uniqueOrderId("O-test-renew-3-") insertOrder(t, &entity.PaymentOrder{ OrderId: order3, PhoneNum: uniquePhone(), PlanId: "day", Channel: consts.ChannelAlipay, AmountCents: 1000, Status: consts.OrderStatusCreated, CreatedAt: gtime.Now(), }) if err := Payment.persistPaid(ctx(), order3, uniqueOrderId("ali-trade-"), consts.ChannelAlipay, 999); err == nil { t.Fatal("amount mismatch should fail") } } // TestCreateSequence 订单创建串行序列:惰性关闭超时订单 + 2 小时内复用 created 订单 func TestCreateSequence(t *testing.T) { phone := uniquePhone() orderStale := uniqueOrderId("O-test-stale-") orderRecent := uniqueOrderId("O-test-recent-") // 3 小时前的 stale 订单 + 1 分钟前的有效订单 now := gtime.Now() insertOrder(t, &entity.PaymentOrder{ OrderId: orderStale, PhoneNum: phone, PlanId: "day", Channel: consts.ChannelWechat, AmountCents: 1000, Status: consts.OrderStatusCreated, CreatedAt: gtime.New(now.Time.Add(-3 * time.Hour)), }) insertOrder(t, &entity.PaymentOrder{ OrderId: orderRecent, PhoneNum: phone, PlanId: "day", Channel: consts.ChannelWechat, AmountCents: 1000, Status: consts.OrderStatusCreated, CreatedAt: gtime.New(now.Time.Add(-1 * time.Minute)), }) // 复现 Create 的串行事务序列 var recent *entity.PaymentOrder err := common.Serial().Submit(ctx(), func() error { return g.DB().Transaction(ctx(), func(ctx context.Context, tx gdb.TX) error { window := gtime.New(now.Time.Add(-consts.PendingOrderWindowSeconds * time.Second)) if err := dao.PaymentOrder.CloseStaleCreatedInTx(ctx, tx, phone, window); err != nil { return err } var err error recent, err = dao.PaymentOrder.GetRecentCreatedInTx(ctx, tx, phone, window) return err }) }) if err != nil { t.Fatal(err) } stale, _ := dao.PaymentOrder.GetByPk(ctx(), orderStale) if stale.Status != consts.OrderStatusClosed { t.Fatalf("stale order status = %s, want closed", stale.Status) } if recent == nil || recent.OrderId != orderRecent { t.Fatalf("recent = %+v, want %s", recent, orderRecent) } } // TestTradeNoNullable 未支付订单 trade_no 必须落 NULL:UNIQUE 约束下多个 created 订单(同渠道不同账号)可共存 func TestTradeNoNullable(t *testing.T) { for _, channel := range []string{consts.ChannelAlipay, consts.ChannelAlipay, consts.ChannelWechat, consts.ChannelWechat} { insertOrder(t, &entity.PaymentOrder{ OrderId: uniqueOrderId("O-test-trade-"), PhoneNum: uniquePhone(), PlanId: "day", Channel: channel, AmountCents: 1000, Status: consts.OrderStatusCreated, CreatedAt: gtime.Now(), }) } } // TestCalcExpiresAt 自然日语义:day 当天 24 点失效、week/month 自生效日起累计 func TestCalcExpiresAt(t *testing.T) { base := time.Date(2026, 8, 22, 15, 30, 0, 0, time.Local) start := time.Date(2026, 8, 22, 0, 0, 0, 0, time.Local) cases := []struct { days int want time.Time }{ {1, start.AddDate(0, 0, 1)}, // 当天 24:00 {7, start.AddDate(0, 0, 7)}, // 第 7 天 24:00 {30, start.AddDate(0, 0, 30)}, } for _, c := range cases { got := License.CalcExpiresAt(base, c.days) if !got.Time.Equal(c.want) { t.Fatalf("CalcExpiresAt(%d) = %s, want %s", c.days, got, c.want) } } }