package order import ( "context" "testing" "github.com/break/junhong_cmp_fiber/internal/model" "github.com/break/junhong_cmp_fiber/internal/model/dto" "github.com/break/junhong_cmp_fiber/internal/service/purchase_validation" "github.com/break/junhong_cmp_fiber/internal/store/postgres" "github.com/break/junhong_cmp_fiber/pkg/constants" "github.com/break/junhong_cmp_fiber/pkg/errors" "github.com/break/junhong_cmp_fiber/pkg/middleware" "github.com/break/junhong_cmp_fiber/tests/testutils" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.uber.org/zap" ) type testEnv struct { ctx context.Context svc *Service card *model.IotCard device *model.Device pkg *model.Package shop *model.Shop wallet *model.Wallet allocation *model.ShopSeriesAllocation } func setupOrderTestEnv(t *testing.T) *testEnv { tx := testutils.NewTestTransaction(t) rdb := testutils.GetTestRedis(t) testutils.CleanTestRedisKeys(t, rdb) iotCardStore := postgres.NewIotCardStore(tx, rdb) deviceStore := postgres.NewDeviceStore(tx, rdb) packageStore := postgres.NewPackageStore(tx) seriesAllocationStore := postgres.NewShopSeriesAllocationStore(tx) packageSeriesStore := postgres.NewPackageSeriesStore(tx) carrierStore := postgres.NewCarrierStore(tx) shopStore := postgres.NewShopStore(tx, rdb) orderStore := postgres.NewOrderStore(tx, rdb) orderItemStore := postgres.NewOrderItemStore(tx, rdb) walletStore := postgres.NewWalletStore(tx, rdb) ctx := context.Background() carrier := &model.Carrier{ CarrierCode: "TEST_CARRIER_ORDER", CarrierName: "测试运营商", CarrierType: constants.CarrierTypeCMCC, Status: constants.StatusEnabled, } require.NoError(t, carrierStore.Create(ctx, carrier)) shop := &model.Shop{ ShopName: "测试店铺ORDER", ShopCode: "TEST_SHOP_ORDER", Level: 1, Status: constants.StatusEnabled, BaseModel: model.BaseModel{Creator: 1, Updater: 1}, } require.NoError(t, shopStore.Create(ctx, shop)) series := &model.PackageSeries{ SeriesCode: "TEST_SERIES_ORDER", SeriesName: "测试套餐系列", Description: "测试用", Status: constants.StatusEnabled, BaseModel: model.BaseModel{Creator: 1, Updater: 1}, } require.NoError(t, packageSeriesStore.Create(ctx, series)) allocation := &model.ShopSeriesAllocation{ ShopID: shop.ID, SeriesID: series.ID, Status: constants.StatusEnabled, BaseModel: model.BaseModel{Creator: 1, Updater: 1}, } require.NoError(t, seriesAllocationStore.Create(ctx, allocation)) pkg := &model.Package{ PackageCode: "TEST_PKG_ORDER", PackageName: "测试套餐", SeriesID: series.ID, PackageType: "formal", DurationMonths: 1, DataAmountMB: 1024, SuggestedRetailPrice: 9900, Status: constants.StatusEnabled, ShelfStatus: constants.ShelfStatusOn, BaseModel: model.BaseModel{Creator: 1, Updater: 1}, } require.NoError(t, packageStore.Create(ctx, pkg)) shopIDPtr := &shop.ID card := &model.IotCard{ ICCID: "89860000000000000002", ShopID: shopIDPtr, CarrierID: carrier.ID, SeriesAllocationID: &allocation.ID, Status: constants.StatusEnabled, BaseModel: model.BaseModel{Creator: 1, Updater: 1}, } require.NoError(t, iotCardStore.Create(ctx, card)) device := &model.Device{ DeviceNo: "DEV_TEST_ORDER_001", ShopID: shopIDPtr, SeriesAllocationID: &allocation.ID, Status: constants.StatusEnabled, BaseModel: model.BaseModel{Creator: 1, Updater: 1}, } require.NoError(t, deviceStore.Create(ctx, device)) wallet := &model.Wallet{ ResourceType: "shop", ResourceID: shop.ID, WalletType: "main", Balance: 100000, Version: 1, BaseModel: model.BaseModel{Creator: 1, Updater: 1}, } require.NoError(t, tx.Create(wallet).Error) purchaseValidationSvc := purchase_validation.New(tx, iotCardStore, deviceStore, packageStore, seriesAllocationStore) logger := zap.NewNop() orderSvc := New(tx, orderStore, orderItemStore, walletStore, purchaseValidationSvc, nil, seriesAllocationStore, iotCardStore, deviceStore, nil, nil, logger) userCtx := middleware.SetUserContext(ctx, &middleware.UserContextInfo{ UserID: 1, UserType: constants.UserTypeAgent, ShopID: shop.ID, }) return &testEnv{ ctx: userCtx, svc: orderSvc, card: card, device: device, pkg: pkg, shop: shop, wallet: wallet, allocation: allocation, } } func TestOrderService_Create(t *testing.T) { env := setupOrderTestEnv(t) t.Run("创建单卡订单成功", func(t *testing.T) { req := &dto.CreateOrderRequest{ OrderType: model.OrderTypeSingleCard, IotCardID: &env.card.ID, PackageIDs: []uint{env.pkg.ID}, } resp, err := env.svc.Create(env.ctx, req, model.BuyerTypeAgent, env.shop.ID) require.NoError(t, err) assert.NotZero(t, resp.ID) assert.Contains(t, resp.OrderNo, "ORD") assert.Equal(t, model.OrderTypeSingleCard, resp.OrderType) assert.Equal(t, model.BuyerTypeAgent, resp.BuyerType) assert.Equal(t, env.shop.ID, resp.BuyerID) assert.Equal(t, env.pkg.SuggestedRetailPrice, resp.TotalAmount) assert.Equal(t, model.PaymentStatusPending, resp.PaymentStatus) assert.Len(t, resp.Items, 1) }) t.Run("创建设备订单成功", func(t *testing.T) { req := &dto.CreateOrderRequest{ OrderType: model.OrderTypeDevice, DeviceID: &env.device.ID, PackageIDs: []uint{env.pkg.ID}, } resp, err := env.svc.Create(env.ctx, req, model.BuyerTypeAgent, env.shop.ID) require.NoError(t, err) assert.NotZero(t, resp.ID) assert.Equal(t, model.OrderTypeDevice, resp.OrderType) }) t.Run("单卡订单缺少卡ID", func(t *testing.T) { req := &dto.CreateOrderRequest{ OrderType: model.OrderTypeSingleCard, PackageIDs: []uint{env.pkg.ID}, } _, err := env.svc.Create(env.ctx, req, model.BuyerTypeAgent, env.shop.ID) require.Error(t, err) appErr, ok := err.(*errors.AppError) require.True(t, ok) assert.Equal(t, errors.CodeInvalidParam, appErr.Code) }) t.Run("设备订单缺少设备ID", func(t *testing.T) { req := &dto.CreateOrderRequest{ OrderType: model.OrderTypeDevice, PackageIDs: []uint{env.pkg.ID}, } _, err := env.svc.Create(env.ctx, req, model.BuyerTypeAgent, env.shop.ID) require.Error(t, err) appErr, ok := err.(*errors.AppError) require.True(t, ok) assert.Equal(t, errors.CodeInvalidParam, appErr.Code) }) } func TestOrderService_Get(t *testing.T) { env := setupOrderTestEnv(t) req := &dto.CreateOrderRequest{ OrderType: model.OrderTypeSingleCard, IotCardID: &env.card.ID, PackageIDs: []uint{env.pkg.ID}, } created, err := env.svc.Create(env.ctx, req, model.BuyerTypeAgent, env.shop.ID) require.NoError(t, err) t.Run("获取订单成功", func(t *testing.T) { resp, err := env.svc.Get(env.ctx, created.ID) require.NoError(t, err) assert.Equal(t, created.OrderNo, resp.OrderNo) assert.Len(t, resp.Items, 1) }) t.Run("订单不存在", func(t *testing.T) { _, err := env.svc.Get(env.ctx, 99999) require.Error(t, err) appErr, ok := err.(*errors.AppError) require.True(t, ok) assert.Equal(t, errors.CodeNotFound, appErr.Code) }) } func TestOrderService_List(t *testing.T) { env := setupOrderTestEnv(t) for i := 0; i < 3; i++ { req := &dto.CreateOrderRequest{ OrderType: model.OrderTypeSingleCard, IotCardID: &env.card.ID, PackageIDs: []uint{env.pkg.ID}, } _, err := env.svc.Create(env.ctx, req, model.BuyerTypeAgent, env.shop.ID) require.NoError(t, err) } t.Run("列表查询", func(t *testing.T) { listReq := &dto.OrderListRequest{ Page: 1, PageSize: 10, } resp, err := env.svc.List(env.ctx, listReq, model.BuyerTypeAgent, env.shop.ID) require.NoError(t, err) assert.GreaterOrEqual(t, resp.Total, int64(3)) assert.GreaterOrEqual(t, len(resp.List), 3) }) t.Run("按支付状态过滤", func(t *testing.T) { status := model.PaymentStatusPending listReq := &dto.OrderListRequest{ Page: 1, PageSize: 10, PaymentStatus: &status, } resp, err := env.svc.List(env.ctx, listReq, model.BuyerTypeAgent, env.shop.ID) require.NoError(t, err) for _, o := range resp.List { assert.Equal(t, model.PaymentStatusPending, o.PaymentStatus) } }) } func TestOrderService_Cancel(t *testing.T) { env := setupOrderTestEnv(t) req := &dto.CreateOrderRequest{ OrderType: model.OrderTypeSingleCard, IotCardID: &env.card.ID, PackageIDs: []uint{env.pkg.ID}, } created, err := env.svc.Create(env.ctx, req, model.BuyerTypeAgent, env.shop.ID) require.NoError(t, err) t.Run("取消订单成功", func(t *testing.T) { err := env.svc.Cancel(env.ctx, created.ID, model.BuyerTypeAgent, env.shop.ID) require.NoError(t, err) order, err := env.svc.Get(env.ctx, created.ID) require.NoError(t, err) assert.Equal(t, model.PaymentStatusCancelled, order.PaymentStatus) }) t.Run("订单不存在", func(t *testing.T) { err := env.svc.Cancel(env.ctx, 99999, model.BuyerTypeAgent, env.shop.ID) require.Error(t, err) appErr, ok := err.(*errors.AppError) require.True(t, ok) assert.Equal(t, errors.CodeNotFound, appErr.Code) }) t.Run("无权操作", func(t *testing.T) { newReq := &dto.CreateOrderRequest{ OrderType: model.OrderTypeSingleCard, IotCardID: &env.card.ID, PackageIDs: []uint{env.pkg.ID}, } newOrder, err := env.svc.Create(env.ctx, newReq, model.BuyerTypeAgent, env.shop.ID) require.NoError(t, err) err = env.svc.Cancel(env.ctx, newOrder.ID, model.BuyerTypeAgent, 99999) require.Error(t, err) appErr, ok := err.(*errors.AppError) require.True(t, ok) assert.Equal(t, errors.CodeForbidden, appErr.Code) }) } func TestOrderService_WalletPay(t *testing.T) { env := setupOrderTestEnv(t) t.Run("钱包支付成功", func(t *testing.T) { req := &dto.CreateOrderRequest{ OrderType: model.OrderTypeSingleCard, IotCardID: &env.card.ID, PackageIDs: []uint{env.pkg.ID}, } created, err := env.svc.Create(env.ctx, req, model.BuyerTypeAgent, env.shop.ID) require.NoError(t, err) err = env.svc.WalletPay(env.ctx, created.ID, model.BuyerTypeAgent, env.shop.ID) require.NoError(t, err) order, err := env.svc.Get(env.ctx, created.ID) require.NoError(t, err) assert.Equal(t, model.PaymentStatusPaid, order.PaymentStatus) assert.Equal(t, model.PaymentMethodWallet, order.PaymentMethod) assert.NotNil(t, order.PaidAt) }) t.Run("订单不存在", func(t *testing.T) { err := env.svc.WalletPay(env.ctx, 99999, model.BuyerTypeAgent, env.shop.ID) require.Error(t, err) appErr, ok := err.(*errors.AppError) require.True(t, ok) assert.Equal(t, errors.CodeNotFound, appErr.Code) }) t.Run("无权操作", func(t *testing.T) { req := &dto.CreateOrderRequest{ OrderType: model.OrderTypeSingleCard, IotCardID: &env.card.ID, PackageIDs: []uint{env.pkg.ID}, } created, err := env.svc.Create(env.ctx, req, model.BuyerTypeAgent, env.shop.ID) require.NoError(t, err) err = env.svc.WalletPay(env.ctx, created.ID, model.BuyerTypeAgent, 99999) require.Error(t, err) appErr, ok := err.(*errors.AppError) require.True(t, ok) assert.Equal(t, errors.CodeForbidden, appErr.Code) }) t.Run("重复支付-幂等成功", func(t *testing.T) { req := &dto.CreateOrderRequest{ OrderType: model.OrderTypeSingleCard, IotCardID: &env.card.ID, PackageIDs: []uint{env.pkg.ID}, } created, err := env.svc.Create(env.ctx, req, model.BuyerTypeAgent, env.shop.ID) require.NoError(t, err) err = env.svc.WalletPay(env.ctx, created.ID, model.BuyerTypeAgent, env.shop.ID) require.NoError(t, err) err = env.svc.WalletPay(env.ctx, created.ID, model.BuyerTypeAgent, env.shop.ID) require.NoError(t, err) }) t.Run("已取消订单无法支付", func(t *testing.T) { req := &dto.CreateOrderRequest{ OrderType: model.OrderTypeSingleCard, IotCardID: &env.card.ID, PackageIDs: []uint{env.pkg.ID}, } created, err := env.svc.Create(env.ctx, req, model.BuyerTypeAgent, env.shop.ID) require.NoError(t, err) err = env.svc.Cancel(env.ctx, created.ID, model.BuyerTypeAgent, env.shop.ID) require.NoError(t, err) err = env.svc.WalletPay(env.ctx, created.ID, model.BuyerTypeAgent, env.shop.ID) require.Error(t, err) appErr, ok := err.(*errors.AppError) require.True(t, ok) assert.Equal(t, errors.CodeInvalidStatus, appErr.Code) }) } func TestOrderService_HandlePaymentCallback(t *testing.T) { env := setupOrderTestEnv(t) t.Run("支付回调成功", func(t *testing.T) { req := &dto.CreateOrderRequest{ OrderType: model.OrderTypeSingleCard, IotCardID: &env.card.ID, PackageIDs: []uint{env.pkg.ID}, } created, err := env.svc.Create(env.ctx, req, model.BuyerTypeAgent, env.shop.ID) require.NoError(t, err) err = env.svc.HandlePaymentCallback(env.ctx, created.OrderNo, model.PaymentMethodWechat) require.NoError(t, err) order, err := env.svc.Get(env.ctx, created.ID) require.NoError(t, err) assert.Equal(t, model.PaymentStatusPaid, order.PaymentStatus) assert.Equal(t, model.PaymentMethodWechat, order.PaymentMethod) }) t.Run("幂等处理-已支付订单", func(t *testing.T) { req := &dto.CreateOrderRequest{ OrderType: model.OrderTypeSingleCard, IotCardID: &env.card.ID, PackageIDs: []uint{env.pkg.ID}, } created, err := env.svc.Create(env.ctx, req, model.BuyerTypeAgent, env.shop.ID) require.NoError(t, err) err = env.svc.HandlePaymentCallback(env.ctx, created.OrderNo, model.PaymentMethodAlipay) require.NoError(t, err) err = env.svc.HandlePaymentCallback(env.ctx, created.OrderNo, model.PaymentMethodAlipay) require.NoError(t, err) }) t.Run("订单不存在", func(t *testing.T) { err := env.svc.HandlePaymentCallback(env.ctx, "NOT_EXISTS_ORDER_NO", model.PaymentMethodWechat) require.Error(t, err) appErr, ok := err.(*errors.AppError) require.True(t, ok) assert.Equal(t, errors.CodeNotFound, appErr.Code) }) } func TestOrderService_IdempotencyAndConcurrency(t *testing.T) { tx := testutils.NewTestTransaction(t) rdb := testutils.GetTestRedis(t) testutils.CleanTestRedisKeys(t, rdb) iotCardStore := postgres.NewIotCardStore(tx, rdb) deviceStore := postgres.NewDeviceStore(tx, rdb) packageStore := postgres.NewPackageStore(tx) seriesAllocationStore := postgres.NewShopSeriesAllocationStore(tx) packageSeriesStore := postgres.NewPackageSeriesStore(tx) carrierStore := postgres.NewCarrierStore(tx) shopStore := postgres.NewShopStore(tx, rdb) orderStore := postgres.NewOrderStore(tx, rdb) orderItemStore := postgres.NewOrderItemStore(tx, rdb) walletStore := postgres.NewWalletStore(tx, rdb) ctx := context.Background() carrier := &model.Carrier{ CarrierCode: "TEST_CARRIER_IDEM", CarrierName: "测试运营商幂等", CarrierType: constants.CarrierTypeCMCC, Status: constants.StatusEnabled, } require.NoError(t, carrierStore.Create(ctx, carrier)) shop := &model.Shop{ ShopName: "测试店铺IDEM", ShopCode: "TEST_SHOP_IDEM", Level: 1, Status: constants.StatusEnabled, BaseModel: model.BaseModel{Creator: 1, Updater: 1}, } require.NoError(t, shopStore.Create(ctx, shop)) series := &model.PackageSeries{ SeriesCode: "TEST_SERIES_IDEM", SeriesName: "测试套餐系列幂等", Description: "测试用", Status: constants.StatusEnabled, BaseModel: model.BaseModel{Creator: 1, Updater: 1}, } require.NoError(t, packageSeriesStore.Create(ctx, series)) allocation := &model.ShopSeriesAllocation{ ShopID: shop.ID, SeriesID: series.ID, Status: constants.StatusEnabled, BaseModel: model.BaseModel{Creator: 1, Updater: 1}, } require.NoError(t, seriesAllocationStore.Create(ctx, allocation)) pkg := &model.Package{ PackageCode: "TEST_PKG_IDEM", PackageName: "测试套餐幂等", SeriesID: series.ID, PackageType: "formal", DurationMonths: 1, DataAmountMB: 1024, SuggestedRetailPrice: 9900, Status: constants.StatusEnabled, ShelfStatus: constants.ShelfStatusOn, BaseModel: model.BaseModel{Creator: 1, Updater: 1}, } require.NoError(t, packageStore.Create(ctx, pkg)) shopIDPtr := &shop.ID card := &model.IotCard{ ICCID: "89860000000000000099", ShopID: shopIDPtr, CarrierID: carrier.ID, SeriesAllocationID: &allocation.ID, Status: constants.StatusEnabled, BaseModel: model.BaseModel{Creator: 1, Updater: 1}, } require.NoError(t, iotCardStore.Create(ctx, card)) wallet := &model.Wallet{ ResourceType: "shop", ResourceID: shop.ID, WalletType: "main", Balance: 1000000, Version: 1, BaseModel: model.BaseModel{Creator: 1, Updater: 1}, } require.NoError(t, tx.Create(wallet).Error) purchaseValidationSvc := purchase_validation.New(tx, iotCardStore, deviceStore, packageStore, seriesAllocationStore) logger := zap.NewNop() orderSvc := New(tx, orderStore, orderItemStore, walletStore, purchaseValidationSvc, nil, seriesAllocationStore, iotCardStore, deviceStore, nil, nil, logger) userCtx := middleware.SetUserContext(ctx, &middleware.UserContextInfo{ UserID: 1, UserType: constants.UserTypeAgent, ShopID: shop.ID, }) t.Run("串行幂等支付测试", func(t *testing.T) { req := &dto.CreateOrderRequest{ OrderType: model.OrderTypeSingleCard, IotCardID: &card.ID, PackageIDs: []uint{pkg.ID}, } created, err := orderSvc.Create(userCtx, req, model.BuyerTypeAgent, shop.ID) require.NoError(t, err) err = orderSvc.WalletPay(userCtx, created.ID, model.BuyerTypeAgent, shop.ID) require.NoError(t, err) err = orderSvc.WalletPay(userCtx, created.ID, model.BuyerTypeAgent, shop.ID) require.NoError(t, err) err = orderSvc.WalletPay(userCtx, created.ID, model.BuyerTypeAgent, shop.ID) require.NoError(t, err) var usageCount int64 err = tx.Model(&model.PackageUsage{}).Where("order_id = ?", created.ID).Count(&usageCount).Error require.NoError(t, err) assert.Equal(t, int64(1), usageCount) order, err := orderSvc.Get(userCtx, created.ID) require.NoError(t, err) assert.Equal(t, model.PaymentStatusPaid, order.PaymentStatus) }) t.Run("串行回调幂等测试", func(t *testing.T) { req := &dto.CreateOrderRequest{ OrderType: model.OrderTypeSingleCard, IotCardID: &card.ID, PackageIDs: []uint{pkg.ID}, } created, err := orderSvc.Create(userCtx, req, model.BuyerTypeAgent, shop.ID) require.NoError(t, err) err = orderSvc.HandlePaymentCallback(userCtx, created.OrderNo, model.PaymentMethodWechat) require.NoError(t, err) err = orderSvc.HandlePaymentCallback(userCtx, created.OrderNo, model.PaymentMethodWechat) require.NoError(t, err) err = orderSvc.HandlePaymentCallback(userCtx, created.OrderNo, model.PaymentMethodWechat) require.NoError(t, err) var usageCount int64 err = tx.Model(&model.PackageUsage{}).Where("order_id = ?", created.ID).Count(&usageCount).Error require.NoError(t, err) assert.Equal(t, int64(1), usageCount) }) t.Run("已取消订单回调测试", func(t *testing.T) { req := &dto.CreateOrderRequest{ OrderType: model.OrderTypeSingleCard, IotCardID: &card.ID, PackageIDs: []uint{pkg.ID}, } created, err := orderSvc.Create(userCtx, req, model.BuyerTypeAgent, shop.ID) require.NoError(t, err) err = orderSvc.Cancel(userCtx, created.ID, model.BuyerTypeAgent, shop.ID) require.NoError(t, err) err = orderSvc.HandlePaymentCallback(userCtx, created.OrderNo, model.PaymentMethodWechat) require.Error(t, err) appErr, ok := err.(*errors.AppError) require.True(t, ok) assert.Equal(t, errors.CodeInvalidStatus, appErr.Code) var usageCount int64 err = tx.Model(&model.PackageUsage{}).Where("order_id = ?", created.ID).Count(&usageCount).Error require.NoError(t, err) assert.Equal(t, int64(0), usageCount) }) } func TestOrderService_ForceRechargeValidation(t *testing.T) { tx := testutils.NewTestTransaction(t) rdb := testutils.GetTestRedis(t) testutils.CleanTestRedisKeys(t, rdb) iotCardStore := postgres.NewIotCardStore(tx, rdb) deviceStore := postgres.NewDeviceStore(tx, rdb) packageStore := postgres.NewPackageStore(tx) seriesAllocationStore := postgres.NewShopSeriesAllocationStore(tx) packageSeriesStore := postgres.NewPackageSeriesStore(tx) carrierStore := postgres.NewCarrierStore(tx) shopStore := postgres.NewShopStore(tx, rdb) orderStore := postgres.NewOrderStore(tx, rdb) orderItemStore := postgres.NewOrderItemStore(tx, rdb) walletStore := postgres.NewWalletStore(tx, rdb) ctx := context.Background() carrier := &model.Carrier{ CarrierCode: "TEST_CARRIER_FR", CarrierName: "测试运营商强充", CarrierType: constants.CarrierTypeCMCC, Status: constants.StatusEnabled, } require.NoError(t, carrierStore.Create(ctx, carrier)) shop := &model.Shop{ ShopName: "测试店铺FR", ShopCode: "TEST_SHOP_FR", Level: 1, Status: constants.StatusEnabled, BaseModel: model.BaseModel{Creator: 1, Updater: 1}, } require.NoError(t, shopStore.Create(ctx, shop)) series := &model.PackageSeries{ SeriesCode: "TEST_SERIES_FR", SeriesName: "测试套餐系列强充", Description: "测试用", Status: constants.StatusEnabled, BaseModel: model.BaseModel{Creator: 1, Updater: 1}, } require.NoError(t, packageSeriesStore.Create(ctx, series)) allocation := &model.ShopSeriesAllocation{ ShopID: shop.ID, SeriesID: series.ID, Status: constants.StatusEnabled, EnableOneTimeCommission: true, OneTimeCommissionTrigger: model.OneTimeCommissionTriggerSingleRecharge, OneTimeCommissionThreshold: 20000, BaseModel: model.BaseModel{Creator: 1, Updater: 1}, } require.NoError(t, seriesAllocationStore.Create(ctx, allocation)) cheapPkg := &model.Package{ PackageCode: "TEST_PKG_CHEAP", PackageName: "便宜套餐", SeriesID: series.ID, PackageType: "formal", DurationMonths: 1, DataAmountMB: 512, SuggestedRetailPrice: 5000, Status: constants.StatusEnabled, ShelfStatus: constants.ShelfStatusOn, BaseModel: model.BaseModel{Creator: 1, Updater: 1}, } require.NoError(t, packageStore.Create(ctx, cheapPkg)) expensivePkg := &model.Package{ PackageCode: "TEST_PKG_EXP", PackageName: "高价套餐", SeriesID: series.ID, PackageType: "formal", DurationMonths: 12, DataAmountMB: 10240, SuggestedRetailPrice: 25000, Status: constants.StatusEnabled, ShelfStatus: constants.ShelfStatusOn, BaseModel: model.BaseModel{Creator: 1, Updater: 1}, } require.NoError(t, packageStore.Create(ctx, expensivePkg)) shopIDPtr := &shop.ID card := &model.IotCard{ ICCID: "89860000000000000FR1", ShopID: shopIDPtr, CarrierID: carrier.ID, SeriesAllocationID: &allocation.ID, Status: constants.StatusEnabled, FirstCommissionPaid: false, BaseModel: model.BaseModel{Creator: 1, Updater: 1}, } require.NoError(t, iotCardStore.Create(ctx, card)) purchaseValidationSvc := purchase_validation.New(tx, iotCardStore, deviceStore, packageStore, seriesAllocationStore) logger := zap.NewNop() orderSvc := New(tx, orderStore, orderItemStore, walletStore, purchaseValidationSvc, nil, seriesAllocationStore, iotCardStore, deviceStore, nil, nil, logger) userCtx := middleware.SetUserContext(ctx, &middleware.UserContextInfo{ UserID: 1, UserType: constants.UserTypeAgent, ShopID: shop.ID, }) t.Run("强充验证-金额不足拒绝", func(t *testing.T) { req := &dto.CreateOrderRequest{ OrderType: model.OrderTypeSingleCard, IotCardID: &card.ID, PackageIDs: []uint{cheapPkg.ID}, } _, err := orderSvc.Create(userCtx, req, model.BuyerTypeAgent, shop.ID) require.Error(t, err) appErr, ok := err.(*errors.AppError) require.True(t, ok) assert.Equal(t, errors.CodeForceRechargeRequired, appErr.Code) }) t.Run("强充验证-金额足够通过", func(t *testing.T) { req := &dto.CreateOrderRequest{ OrderType: model.OrderTypeSingleCard, IotCardID: &card.ID, PackageIDs: []uint{expensivePkg.ID}, } resp, err := orderSvc.Create(userCtx, req, model.BuyerTypeAgent, shop.ID) require.NoError(t, err) assert.NotZero(t, resp.ID) assert.Equal(t, expensivePkg.SuggestedRetailPrice, resp.TotalAmount) }) t.Run("已付佣金-跳过强充验证", func(t *testing.T) { card2 := &model.IotCard{ ICCID: "89860000000000000FR2", ShopID: shopIDPtr, CarrierID: carrier.ID, SeriesAllocationID: &allocation.ID, Status: constants.StatusEnabled, FirstCommissionPaid: true, BaseModel: model.BaseModel{Creator: 1, Updater: 1}, } require.NoError(t, iotCardStore.Create(ctx, card2)) req := &dto.CreateOrderRequest{ OrderType: model.OrderTypeSingleCard, IotCardID: &card2.ID, PackageIDs: []uint{cheapPkg.ID}, } resp, err := orderSvc.Create(userCtx, req, model.BuyerTypeAgent, shop.ID) require.NoError(t, err) assert.NotZero(t, resp.ID) }) } func TestOrderService_GetPurchaseCheck(t *testing.T) { tx := testutils.NewTestTransaction(t) rdb := testutils.GetTestRedis(t) testutils.CleanTestRedisKeys(t, rdb) iotCardStore := postgres.NewIotCardStore(tx, rdb) deviceStore := postgres.NewDeviceStore(tx, rdb) packageStore := postgres.NewPackageStore(tx) seriesAllocationStore := postgres.NewShopSeriesAllocationStore(tx) packageSeriesStore := postgres.NewPackageSeriesStore(tx) carrierStore := postgres.NewCarrierStore(tx) shopStore := postgres.NewShopStore(tx, rdb) orderStore := postgres.NewOrderStore(tx, rdb) orderItemStore := postgres.NewOrderItemStore(tx, rdb) walletStore := postgres.NewWalletStore(tx, rdb) ctx := context.Background() carrier := &model.Carrier{ CarrierCode: "TEST_CARRIER_PC", CarrierName: "测试运营商预检", CarrierType: constants.CarrierTypeCMCC, Status: constants.StatusEnabled, } require.NoError(t, carrierStore.Create(ctx, carrier)) shop := &model.Shop{ ShopName: "测试店铺PC", ShopCode: "TEST_SHOP_PC", Level: 1, Status: constants.StatusEnabled, BaseModel: model.BaseModel{Creator: 1, Updater: 1}, } require.NoError(t, shopStore.Create(ctx, shop)) series := &model.PackageSeries{ SeriesCode: "TEST_SERIES_PC", SeriesName: "测试套餐系列预检", Description: "测试用", Status: constants.StatusEnabled, BaseModel: model.BaseModel{Creator: 1, Updater: 1}, } require.NoError(t, packageSeriesStore.Create(ctx, series)) allocation := &model.ShopSeriesAllocation{ ShopID: shop.ID, SeriesID: series.ID, Status: constants.StatusEnabled, EnableOneTimeCommission: true, OneTimeCommissionTrigger: model.OneTimeCommissionTriggerSingleRecharge, OneTimeCommissionThreshold: 10000, BaseModel: model.BaseModel{Creator: 1, Updater: 1}, } require.NoError(t, seriesAllocationStore.Create(ctx, allocation)) pkg := &model.Package{ PackageCode: "TEST_PKG_PC", PackageName: "测试套餐预检", SeriesID: series.ID, PackageType: "formal", DurationMonths: 1, DataAmountMB: 1024, SuggestedRetailPrice: 5000, Status: constants.StatusEnabled, ShelfStatus: constants.ShelfStatusOn, BaseModel: model.BaseModel{Creator: 1, Updater: 1}, } require.NoError(t, packageStore.Create(ctx, pkg)) shopIDPtr := &shop.ID card := &model.IotCard{ ICCID: "89860000000000000PC1", ShopID: shopIDPtr, CarrierID: carrier.ID, SeriesAllocationID: &allocation.ID, Status: constants.StatusEnabled, FirstCommissionPaid: false, BaseModel: model.BaseModel{Creator: 1, Updater: 1}, } require.NoError(t, iotCardStore.Create(ctx, card)) purchaseValidationSvc := purchase_validation.New(tx, iotCardStore, deviceStore, packageStore, seriesAllocationStore) logger := zap.NewNop() orderSvc := New(tx, orderStore, orderItemStore, walletStore, purchaseValidationSvc, nil, seriesAllocationStore, iotCardStore, deviceStore, nil, nil, logger) t.Run("预检-需要强充", func(t *testing.T) { req := &dto.PurchaseCheckRequest{ OrderType: model.OrderTypeSingleCard, ResourceID: card.ID, PackageIDs: []uint{pkg.ID}, } resp, err := orderSvc.GetPurchaseCheck(ctx, req) require.NoError(t, err) assert.Equal(t, pkg.SuggestedRetailPrice, resp.TotalPackageAmount) assert.True(t, resp.NeedForceRecharge) assert.Equal(t, allocation.OneTimeCommissionThreshold, resp.ForceRechargeAmount) assert.Equal(t, allocation.OneTimeCommissionThreshold, resp.ActualPayment) assert.NotEmpty(t, resp.Message) }) t.Run("预检-无需强充", func(t *testing.T) { card2 := &model.IotCard{ ICCID: "89860000000000000PC2", ShopID: shopIDPtr, CarrierID: carrier.ID, SeriesAllocationID: &allocation.ID, Status: constants.StatusEnabled, FirstCommissionPaid: true, BaseModel: model.BaseModel{Creator: 1, Updater: 1}, } require.NoError(t, iotCardStore.Create(ctx, card2)) req := &dto.PurchaseCheckRequest{ OrderType: model.OrderTypeSingleCard, ResourceID: card2.ID, PackageIDs: []uint{pkg.ID}, } resp, err := orderSvc.GetPurchaseCheck(ctx, req) require.NoError(t, err) assert.Equal(t, pkg.SuggestedRetailPrice, resp.TotalPackageAmount) assert.False(t, resp.NeedForceRecharge) assert.Equal(t, pkg.SuggestedRetailPrice, resp.ActualPayment) assert.Empty(t, resp.Message) }) } func TestOrderService_CreatePurchaseOnBehalf(t *testing.T) { tx := testutils.NewTestTransaction(t) rdb := testutils.GetTestRedis(t) testutils.CleanTestRedisKeys(t, rdb) iotCardStore := postgres.NewIotCardStore(tx, rdb) deviceStore := postgres.NewDeviceStore(tx, rdb) packageStore := postgres.NewPackageStore(tx) seriesAllocationStore := postgres.NewShopSeriesAllocationStore(tx) packageSeriesStore := postgres.NewPackageSeriesStore(tx) carrierStore := postgres.NewCarrierStore(tx) shopStore := postgres.NewShopStore(tx, rdb) orderStore := postgres.NewOrderStore(tx, rdb) orderItemStore := postgres.NewOrderItemStore(tx, rdb) walletStore := postgres.NewWalletStore(tx, rdb) ctx := context.Background() carrier := &model.Carrier{ CarrierCode: "TEST_CARRIER_POB", CarrierName: "测试运营商代购", CarrierType: constants.CarrierTypeCMCC, Status: constants.StatusEnabled, } require.NoError(t, carrierStore.Create(ctx, carrier)) shop := &model.Shop{ ShopName: "测试店铺POB", ShopCode: "TEST_SHOP_POB", Level: 1, Status: constants.StatusEnabled, BaseModel: model.BaseModel{Creator: 1, Updater: 1}, } require.NoError(t, shopStore.Create(ctx, shop)) series := &model.PackageSeries{ SeriesCode: "TEST_SERIES_POB", SeriesName: "测试套餐系列代购", Description: "测试用", Status: constants.StatusEnabled, BaseModel: model.BaseModel{Creator: 1, Updater: 1}, } require.NoError(t, packageSeriesStore.Create(ctx, series)) allocation := &model.ShopSeriesAllocation{ ShopID: shop.ID, SeriesID: series.ID, AllocatorShopID: 0, BaseCommissionMode: model.CommissionModePercent, BaseCommissionValue: 100, Status: constants.StatusEnabled, BaseModel: model.BaseModel{Creator: 1, Updater: 1}, } require.NoError(t, seriesAllocationStore.Create(ctx, allocation)) pkg := &model.Package{ PackageCode: "TEST_PKG_POB", PackageName: "测试套餐代购", SeriesID: series.ID, PackageType: "formal", DurationMonths: 1, DataAmountMB: 1024, SuggestedRetailPrice: 10000, Status: constants.StatusEnabled, ShelfStatus: constants.ShelfStatusOn, BaseModel: model.BaseModel{Creator: 1, Updater: 1}, } require.NoError(t, packageStore.Create(ctx, pkg)) shopIDPtr := &shop.ID card := &model.IotCard{ ICCID: "89860000000000000POB", ShopID: shopIDPtr, CarrierID: carrier.ID, SeriesAllocationID: &allocation.ID, Status: constants.StatusEnabled, BaseModel: model.BaseModel{Creator: 1, Updater: 1}, } require.NoError(t, iotCardStore.Create(ctx, card)) purchaseValidationSvc := purchase_validation.New(tx, iotCardStore, deviceStore, packageStore, seriesAllocationStore) logger := zap.NewNop() orderSvc := New(tx, orderStore, orderItemStore, walletStore, purchaseValidationSvc, nil, seriesAllocationStore, iotCardStore, deviceStore, nil, nil, logger) t.Run("代购订单创建成功", func(t *testing.T) { req := &dto.CreatePurchaseOnBehalfRequest{ OrderType: model.OrderTypeSingleCard, IotCardID: &card.ID, PackageIDs: []uint{pkg.ID}, } resp, err := orderSvc.CreatePurchaseOnBehalf(ctx, req, 1) require.NoError(t, err) assert.NotZero(t, resp.ID) assert.Equal(t, model.PaymentStatusPaid, resp.PaymentStatus) assert.Equal(t, model.PaymentMethodOffline, resp.PaymentMethod) assert.Equal(t, model.BuyerTypeAgent, resp.BuyerType) assert.Equal(t, shop.ID, resp.BuyerID) assert.NotNil(t, resp.PaidAt) expectedCostPrice := pkg.SuggestedRetailPrice - (pkg.SuggestedRetailPrice * allocation.BaseCommissionValue / 1000) assert.Equal(t, expectedCostPrice, resp.TotalAmount) var usageCount int64 err = tx.Model(&model.PackageUsage{}).Where("order_id = ?", resp.ID).Count(&usageCount).Error require.NoError(t, err) assert.Equal(t, int64(1), usageCount) }) t.Run("代购订单-资源未分配拒绝", func(t *testing.T) { cardNoShop := &model.IotCard{ ICCID: "89860000000000NOSHOP", ShopID: nil, CarrierID: carrier.ID, SeriesAllocationID: &allocation.ID, Status: constants.StatusEnabled, BaseModel: model.BaseModel{Creator: 1, Updater: 1}, } require.NoError(t, iotCardStore.Create(ctx, cardNoShop)) req := &dto.CreatePurchaseOnBehalfRequest{ OrderType: model.OrderTypeSingleCard, IotCardID: &cardNoShop.ID, PackageIDs: []uint{pkg.ID}, } _, err := orderSvc.CreatePurchaseOnBehalf(ctx, req, 1) require.Error(t, err) appErr, ok := err.(*errors.AppError) require.True(t, ok) assert.Equal(t, errors.CodeInvalidParam, appErr.Code) }) } func TestOrderService_WalletPay_PurchaseOnBehalf(t *testing.T) { tx := testutils.NewTestTransaction(t) rdb := testutils.GetTestRedis(t) testutils.CleanTestRedisKeys(t, rdb) iotCardStore := postgres.NewIotCardStore(tx, rdb) deviceStore := postgres.NewDeviceStore(tx, rdb) packageStore := postgres.NewPackageStore(tx) seriesAllocationStore := postgres.NewShopSeriesAllocationStore(tx) packageSeriesStore := postgres.NewPackageSeriesStore(tx) carrierStore := postgres.NewCarrierStore(tx) shopStore := postgres.NewShopStore(tx, rdb) orderStore := postgres.NewOrderStore(tx, rdb) orderItemStore := postgres.NewOrderItemStore(tx, rdb) walletStore := postgres.NewWalletStore(tx, rdb) ctx := context.Background() carrier := &model.Carrier{ CarrierCode: "TEST_CARRIER_WP", CarrierName: "测试运营商WP", CarrierType: constants.CarrierTypeCMCC, Status: constants.StatusEnabled, } require.NoError(t, carrierStore.Create(ctx, carrier)) shop := &model.Shop{ ShopName: "测试店铺WP", ShopCode: "TEST_SHOP_WP", Level: 1, Status: constants.StatusEnabled, BaseModel: model.BaseModel{Creator: 1, Updater: 1}, } require.NoError(t, shopStore.Create(ctx, shop)) series := &model.PackageSeries{ SeriesCode: "TEST_SERIES_WP", SeriesName: "测试套餐系列WP", Description: "测试用", Status: constants.StatusEnabled, BaseModel: model.BaseModel{Creator: 1, Updater: 1}, } require.NoError(t, packageSeriesStore.Create(ctx, series)) allocation := &model.ShopSeriesAllocation{ ShopID: shop.ID, SeriesID: series.ID, AllocatorShopID: 0, BaseCommissionMode: model.CommissionModePercent, BaseCommissionValue: 100, Status: constants.StatusEnabled, BaseModel: model.BaseModel{Creator: 1, Updater: 1}, } require.NoError(t, seriesAllocationStore.Create(ctx, allocation)) pkg := &model.Package{ PackageCode: "TEST_PKG_WP", PackageName: "测试套餐WP", SeriesID: series.ID, PackageType: "formal", DurationMonths: 1, DataAmountMB: 1024, SuggestedRetailPrice: 10000, Status: constants.StatusEnabled, ShelfStatus: constants.ShelfStatusOn, BaseModel: model.BaseModel{Creator: 1, Updater: 1}, } require.NoError(t, packageStore.Create(ctx, pkg)) wallet := &model.Wallet{ ResourceType: "shop", ResourceID: shop.ID, WalletType: "main", Balance: 100000, Version: 1, BaseModel: model.BaseModel{Creator: 1, Updater: 1}, } require.NoError(t, tx.Create(wallet).Error) shopIDPtr := &shop.ID card := &model.IotCard{ ICCID: "89860000000000000WP1", ShopID: shopIDPtr, CarrierID: carrier.ID, SeriesAllocationID: &allocation.ID, Status: constants.StatusEnabled, BaseModel: model.BaseModel{Creator: 1, Updater: 1}, } require.NoError(t, iotCardStore.Create(ctx, card)) purchaseValidationSvc := purchase_validation.New(tx, iotCardStore, deviceStore, packageStore, seriesAllocationStore) logger := zap.NewNop() orderSvc := New(tx, orderStore, orderItemStore, walletStore, purchaseValidationSvc, nil, seriesAllocationStore, iotCardStore, deviceStore, nil, nil, logger) t.Run("代购订单无法进行钱包支付", func(t *testing.T) { req := &dto.CreatePurchaseOnBehalfRequest{ OrderType: model.OrderTypeSingleCard, IotCardID: &card.ID, PackageIDs: []uint{pkg.ID}, } created, err := orderSvc.CreatePurchaseOnBehalf(ctx, req, 1) require.NoError(t, err) err = orderSvc.WalletPay(ctx, created.ID, model.BuyerTypeAgent, shop.ID) require.Error(t, err) appErr, ok := err.(*errors.AppError) require.True(t, ok) assert.Equal(t, errors.CodeInvalidStatus, appErr.Code) }) }