feat(order): 支持代购订单和强充要求检查
- OrderService 新增代购订单支持 - 强充要求检查(首次购买最低充值) - 代购订单支付限制(无需支付) - 强充金额验证 - 新增 OrderDTO 请求/响应结构 - PurchaseCheckRequest/Response(购买预检) - CreatePurchaseOnBehalfRequest(代购订单创建) - Order 模型新增支付方式 - PaymentMethodOffline(线下支付,仅平台代购使用) - OrderService 依赖注入扩展 - 新增 SeriesAllocationStore、IotCardStore、DeviceStore - 支持强充要求检查逻辑 - 完整的集成测试覆盖(534 行) - 代购订单创建、强充验证、支付限制等场景 Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -72,3 +72,25 @@ type PayOrderParams struct {
|
|||||||
ID uint `path:"id" description:"订单ID" required:"true"`
|
ID uint `path:"id" description:"订单ID" required:"true"`
|
||||||
PayOrderRequest
|
PayOrderRequest
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type PurchaseCheckRequest struct {
|
||||||
|
OrderType string `json:"order_type" validate:"required,oneof=single_card device" required:"true" description:"订单类型 (single_card:单卡购买, device:设备购买)"`
|
||||||
|
ResourceID uint `json:"resource_id" validate:"required,min=1" required:"true" description:"资源ID (IoT卡ID或设备ID)"`
|
||||||
|
PackageIDs []uint `json:"package_ids" validate:"required,min=1,max=10,dive,min=1" required:"true" minItems:"1" maxItems:"10" description:"套餐ID列表"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type PurchaseCheckResponse struct {
|
||||||
|
TotalPackageAmount int64 `json:"total_package_amount" description:"套餐总价(分)"`
|
||||||
|
NeedForceRecharge bool `json:"need_force_recharge" description:"是否需要强充"`
|
||||||
|
ForceRechargeAmount int64 `json:"force_recharge_amount" description:"强充金额(分)"`
|
||||||
|
ActualPayment int64 `json:"actual_payment" description:"实际支付金额(分)"`
|
||||||
|
WalletCredit int64 `json:"wallet_credit" description:"钱包到账金额(分)"`
|
||||||
|
Message string `json:"message" description:"提示信息"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CreatePurchaseOnBehalfRequest struct {
|
||||||
|
OrderType string `json:"order_type" validate:"required,oneof=single_card device" required:"true" description:"订单类型 (single_card:单卡购买, device:设备购买)"`
|
||||||
|
IotCardID *uint `json:"iot_card_id" validate:"required_if=OrderType single_card" description:"IoT卡ID(单卡购买时必填)"`
|
||||||
|
DeviceID *uint `json:"device_id" validate:"required_if=OrderType device" description:"设备ID(设备购买时必填)"`
|
||||||
|
PackageIDs []uint `json:"package_ids" validate:"required,min=1,max=10,dive,min=1" required:"true" minItems:"1" maxItems:"10" description:"套餐ID列表"`
|
||||||
|
}
|
||||||
|
|||||||
@@ -63,9 +63,10 @@ const (
|
|||||||
|
|
||||||
// 支付方式常量
|
// 支付方式常量
|
||||||
const (
|
const (
|
||||||
PaymentMethodWallet = "wallet" // 钱包支付
|
PaymentMethodWallet = "wallet" // 钱包支付
|
||||||
PaymentMethodWechat = "wechat" // 微信支付
|
PaymentMethodWechat = "wechat" // 微信支付
|
||||||
PaymentMethodAlipay = "alipay" // 支付宝支付
|
PaymentMethodAlipay = "alipay" // 支付宝支付
|
||||||
|
PaymentMethodOffline = "offline" // 线下支付(仅平台代购使用)
|
||||||
)
|
)
|
||||||
|
|
||||||
// 支付状态常量
|
// 支付状态常量
|
||||||
|
|||||||
@@ -27,6 +27,9 @@ type Service struct {
|
|||||||
walletStore *postgres.WalletStore
|
walletStore *postgres.WalletStore
|
||||||
purchaseValidationService *purchase_validation.Service
|
purchaseValidationService *purchase_validation.Service
|
||||||
allocationConfigStore *postgres.ShopSeriesAllocationConfigStore
|
allocationConfigStore *postgres.ShopSeriesAllocationConfigStore
|
||||||
|
seriesAllocationStore *postgres.ShopSeriesAllocationStore
|
||||||
|
iotCardStore *postgres.IotCardStore
|
||||||
|
deviceStore *postgres.DeviceStore
|
||||||
wechatPayment wechat.PaymentServiceInterface
|
wechatPayment wechat.PaymentServiceInterface
|
||||||
queueClient *queue.Client
|
queueClient *queue.Client
|
||||||
logger *zap.Logger
|
logger *zap.Logger
|
||||||
@@ -39,6 +42,9 @@ func New(
|
|||||||
walletStore *postgres.WalletStore,
|
walletStore *postgres.WalletStore,
|
||||||
purchaseValidationService *purchase_validation.Service,
|
purchaseValidationService *purchase_validation.Service,
|
||||||
allocationConfigStore *postgres.ShopSeriesAllocationConfigStore,
|
allocationConfigStore *postgres.ShopSeriesAllocationConfigStore,
|
||||||
|
seriesAllocationStore *postgres.ShopSeriesAllocationStore,
|
||||||
|
iotCardStore *postgres.IotCardStore,
|
||||||
|
deviceStore *postgres.DeviceStore,
|
||||||
wechatPayment wechat.PaymentServiceInterface,
|
wechatPayment wechat.PaymentServiceInterface,
|
||||||
queueClient *queue.Client,
|
queueClient *queue.Client,
|
||||||
logger *zap.Logger,
|
logger *zap.Logger,
|
||||||
@@ -50,6 +56,9 @@ func New(
|
|||||||
walletStore: walletStore,
|
walletStore: walletStore,
|
||||||
purchaseValidationService: purchaseValidationService,
|
purchaseValidationService: purchaseValidationService,
|
||||||
allocationConfigStore: allocationConfigStore,
|
allocationConfigStore: allocationConfigStore,
|
||||||
|
seriesAllocationStore: seriesAllocationStore,
|
||||||
|
iotCardStore: iotCardStore,
|
||||||
|
deviceStore: deviceStore,
|
||||||
wechatPayment: wechatPayment,
|
wechatPayment: wechatPayment,
|
||||||
queueClient: queueClient,
|
queueClient: queueClient,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
@@ -78,6 +87,11 @@ func (s *Service) Create(ctx context.Context, req *dto.CreateOrderRequest, buyer
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
forceRechargeCheck := s.checkForceRechargeRequirement(validationResult)
|
||||||
|
if forceRechargeCheck.NeedForceRecharge && validationResult.TotalPrice < forceRechargeCheck.ForceRechargeAmount {
|
||||||
|
return nil, errors.New(errors.CodeForceRechargeRequired, "首次购买需满足最低充值要求")
|
||||||
|
}
|
||||||
|
|
||||||
userID := middleware.GetUserIDFromContext(ctx)
|
userID := middleware.GetUserIDFromContext(ctx)
|
||||||
configVersion := s.snapshotCommissionConfig(ctx, validationResult.Allocation.ID)
|
configVersion := s.snapshotCommissionConfig(ctx, validationResult.Allocation.ID)
|
||||||
|
|
||||||
@@ -254,6 +268,10 @@ func (s *Service) WalletPay(ctx context.Context, orderID uint, buyerType string,
|
|||||||
return errors.New(errors.CodeForbidden, "无权操作此订单")
|
return errors.New(errors.CodeForbidden, "无权操作此订单")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if order.IsPurchaseOnBehalf {
|
||||||
|
return errors.New(errors.CodeInvalidStatus, "代购订单无需支付")
|
||||||
|
}
|
||||||
|
|
||||||
var resourceType string
|
var resourceType string
|
||||||
var resourceID uint
|
var resourceID uint
|
||||||
|
|
||||||
@@ -646,3 +664,206 @@ func (s *Service) WechatPayH5(ctx context.Context, orderID uint, sceneInfo *dto.
|
|||||||
H5URL: result.H5URL,
|
H5URL: result.H5URL,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ForceRechargeRequirement struct {
|
||||||
|
NeedForceRecharge bool
|
||||||
|
ForceRechargeAmount int64
|
||||||
|
TriggerType string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) checkForceRechargeRequirement(result *purchase_validation.PurchaseValidationResult) *ForceRechargeRequirement {
|
||||||
|
if result.Allocation == nil {
|
||||||
|
return &ForceRechargeRequirement{NeedForceRecharge: false}
|
||||||
|
}
|
||||||
|
|
||||||
|
allocation := result.Allocation
|
||||||
|
if !allocation.EnableOneTimeCommission {
|
||||||
|
return &ForceRechargeRequirement{NeedForceRecharge: false}
|
||||||
|
}
|
||||||
|
|
||||||
|
var firstCommissionPaid bool
|
||||||
|
if result.Card != nil {
|
||||||
|
firstCommissionPaid = result.Card.FirstCommissionPaid
|
||||||
|
} else if result.Device != nil {
|
||||||
|
firstCommissionPaid = result.Device.FirstCommissionPaid
|
||||||
|
}
|
||||||
|
|
||||||
|
if firstCommissionPaid {
|
||||||
|
return &ForceRechargeRequirement{NeedForceRecharge: false}
|
||||||
|
}
|
||||||
|
|
||||||
|
if allocation.OneTimeCommissionTrigger == model.OneTimeCommissionTriggerSingleRecharge {
|
||||||
|
return &ForceRechargeRequirement{
|
||||||
|
NeedForceRecharge: true,
|
||||||
|
ForceRechargeAmount: allocation.OneTimeCommissionThreshold,
|
||||||
|
TriggerType: model.OneTimeCommissionTriggerSingleRecharge,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if allocation.EnableForceRecharge {
|
||||||
|
forceAmount := allocation.ForceRechargeAmount
|
||||||
|
if forceAmount == 0 {
|
||||||
|
forceAmount = allocation.OneTimeCommissionThreshold
|
||||||
|
}
|
||||||
|
return &ForceRechargeRequirement{
|
||||||
|
NeedForceRecharge: true,
|
||||||
|
ForceRechargeAmount: forceAmount,
|
||||||
|
TriggerType: model.OneTimeCommissionTriggerAccumulatedRecharge,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &ForceRechargeRequirement{NeedForceRecharge: false}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) GetPurchaseCheck(ctx context.Context, req *dto.PurchaseCheckRequest) (*dto.PurchaseCheckResponse, error) {
|
||||||
|
var validationResult *purchase_validation.PurchaseValidationResult
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if req.OrderType == model.OrderTypeSingleCard {
|
||||||
|
validationResult, err = s.purchaseValidationService.ValidateCardPurchase(ctx, req.ResourceID, req.PackageIDs)
|
||||||
|
} else if req.OrderType == model.OrderTypeDevice {
|
||||||
|
validationResult, err = s.purchaseValidationService.ValidateDevicePurchase(ctx, req.ResourceID, req.PackageIDs)
|
||||||
|
} else {
|
||||||
|
return nil, errors.New(errors.CodeInvalidParam, "无效的订单类型")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
forceRechargeCheck := s.checkForceRechargeRequirement(validationResult)
|
||||||
|
|
||||||
|
response := &dto.PurchaseCheckResponse{
|
||||||
|
TotalPackageAmount: validationResult.TotalPrice,
|
||||||
|
NeedForceRecharge: forceRechargeCheck.NeedForceRecharge,
|
||||||
|
ForceRechargeAmount: forceRechargeCheck.ForceRechargeAmount,
|
||||||
|
ActualPayment: validationResult.TotalPrice,
|
||||||
|
WalletCredit: validationResult.TotalPrice,
|
||||||
|
}
|
||||||
|
|
||||||
|
if forceRechargeCheck.NeedForceRecharge {
|
||||||
|
if validationResult.TotalPrice < forceRechargeCheck.ForceRechargeAmount {
|
||||||
|
response.ActualPayment = forceRechargeCheck.ForceRechargeAmount
|
||||||
|
response.WalletCredit = forceRechargeCheck.ForceRechargeAmount
|
||||||
|
response.Message = "首次购买需满足最低充值要求"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return response, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) CreatePurchaseOnBehalf(ctx context.Context, req *dto.CreatePurchaseOnBehalfRequest, operatorID uint) (*dto.OrderResponse, error) {
|
||||||
|
var validationResult *purchase_validation.PurchaseValidationResult
|
||||||
|
var resourceShopID *uint
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if req.OrderType == model.OrderTypeSingleCard {
|
||||||
|
if req.IotCardID == nil {
|
||||||
|
return nil, errors.New(errors.CodeInvalidParam, "单卡购买必须指定IoT卡ID")
|
||||||
|
}
|
||||||
|
validationResult, err = s.purchaseValidationService.ValidateCardPurchase(ctx, *req.IotCardID, req.PackageIDs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if validationResult.Card != nil {
|
||||||
|
resourceShopID = validationResult.Card.ShopID
|
||||||
|
}
|
||||||
|
} else if req.OrderType == model.OrderTypeDevice {
|
||||||
|
if req.DeviceID == nil {
|
||||||
|
return nil, errors.New(errors.CodeInvalidParam, "设备购买必须指定设备ID")
|
||||||
|
}
|
||||||
|
validationResult, err = s.purchaseValidationService.ValidateDevicePurchase(ctx, *req.DeviceID, req.PackageIDs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if validationResult.Device != nil {
|
||||||
|
resourceShopID = validationResult.Device.ShopID
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return nil, errors.New(errors.CodeInvalidParam, "无效的订单类型")
|
||||||
|
}
|
||||||
|
|
||||||
|
if resourceShopID == nil || *resourceShopID == 0 {
|
||||||
|
return nil, errors.New(errors.CodeInvalidParam, "资源未分配给代理商,无法代购")
|
||||||
|
}
|
||||||
|
|
||||||
|
buyerID := *resourceShopID
|
||||||
|
buyerAllocation, err := s.seriesAllocationStore.GetByShopAndSeries(ctx, buyerID, validationResult.Allocation.SeriesID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New(errors.CodeInvalidParam, "买家没有该套餐系列的分配配置")
|
||||||
|
}
|
||||||
|
|
||||||
|
buyerCostPrice := utils.CalculateCostPrice(buyerAllocation, validationResult.TotalPrice)
|
||||||
|
|
||||||
|
configVersion := s.snapshotCommissionConfig(ctx, validationResult.Allocation.ID)
|
||||||
|
|
||||||
|
var seriesID *uint
|
||||||
|
var sellerShopID *uint
|
||||||
|
if validationResult.Allocation != nil {
|
||||||
|
seriesID = &validationResult.Allocation.SeriesID
|
||||||
|
sellerShopID = &validationResult.Allocation.ShopID
|
||||||
|
}
|
||||||
|
|
||||||
|
now := time.Now()
|
||||||
|
order := &model.Order{
|
||||||
|
BaseModel: model.BaseModel{
|
||||||
|
Creator: operatorID,
|
||||||
|
Updater: operatorID,
|
||||||
|
},
|
||||||
|
OrderNo: s.orderStore.GenerateOrderNo(),
|
||||||
|
OrderType: req.OrderType,
|
||||||
|
BuyerType: model.BuyerTypeAgent,
|
||||||
|
BuyerID: buyerID,
|
||||||
|
IotCardID: req.IotCardID,
|
||||||
|
DeviceID: req.DeviceID,
|
||||||
|
TotalAmount: buyerCostPrice,
|
||||||
|
PaymentMethod: model.PaymentMethodOffline,
|
||||||
|
PaymentStatus: model.PaymentStatusPaid,
|
||||||
|
PaidAt: &now,
|
||||||
|
CommissionStatus: model.CommissionStatusPending,
|
||||||
|
CommissionConfigVersion: configVersion,
|
||||||
|
SeriesID: seriesID,
|
||||||
|
SellerShopID: sellerShopID,
|
||||||
|
SellerCostPrice: buyerCostPrice,
|
||||||
|
IsPurchaseOnBehalf: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
var items []*model.OrderItem
|
||||||
|
for _, pkg := range validationResult.Packages {
|
||||||
|
item := &model.OrderItem{
|
||||||
|
BaseModel: model.BaseModel{
|
||||||
|
Creator: operatorID,
|
||||||
|
Updater: operatorID,
|
||||||
|
},
|
||||||
|
PackageID: pkg.ID,
|
||||||
|
PackageName: pkg.PackageName,
|
||||||
|
Quantity: 1,
|
||||||
|
UnitPrice: pkg.SuggestedRetailPrice,
|
||||||
|
Amount: pkg.SuggestedRetailPrice,
|
||||||
|
}
|
||||||
|
items = append(items, item)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||||
|
if err := tx.Create(order).Error; err != nil {
|
||||||
|
return errors.Wrap(errors.CodeDatabaseError, err, "创建代购订单失败")
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, item := range items {
|
||||||
|
item.OrderID = order.ID
|
||||||
|
}
|
||||||
|
if err := tx.CreateInBatches(items, 100).Error; err != nil {
|
||||||
|
return errors.Wrap(errors.CodeDatabaseError, err, "创建订单明细失败")
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.activatePackage(ctx, tx, order)
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
s.enqueueCommissionCalculation(ctx, order.ID)
|
||||||
|
|
||||||
|
return s.buildOrderResponse(order, items), nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -126,7 +126,7 @@ func setupOrderTestEnv(t *testing.T) *testEnv {
|
|||||||
|
|
||||||
purchaseValidationSvc := purchase_validation.New(tx, iotCardStore, deviceStore, packageStore, seriesAllocationStore)
|
purchaseValidationSvc := purchase_validation.New(tx, iotCardStore, deviceStore, packageStore, seriesAllocationStore)
|
||||||
logger := zap.NewNop()
|
logger := zap.NewNop()
|
||||||
orderSvc := New(tx, orderStore, orderItemStore, walletStore, purchaseValidationSvc, nil, nil, nil, logger)
|
orderSvc := New(tx, orderStore, orderItemStore, walletStore, purchaseValidationSvc, nil, seriesAllocationStore, iotCardStore, deviceStore, nil, nil, logger)
|
||||||
|
|
||||||
userCtx := middleware.SetUserContext(ctx, &middleware.UserContextInfo{
|
userCtx := middleware.SetUserContext(ctx, &middleware.UserContextInfo{
|
||||||
UserID: 1,
|
UserID: 1,
|
||||||
@@ -536,7 +536,7 @@ func TestOrderService_IdempotencyAndConcurrency(t *testing.T) {
|
|||||||
|
|
||||||
purchaseValidationSvc := purchase_validation.New(tx, iotCardStore, deviceStore, packageStore, seriesAllocationStore)
|
purchaseValidationSvc := purchase_validation.New(tx, iotCardStore, deviceStore, packageStore, seriesAllocationStore)
|
||||||
logger := zap.NewNop()
|
logger := zap.NewNop()
|
||||||
orderSvc := New(tx, orderStore, orderItemStore, walletStore, purchaseValidationSvc, nil, nil, nil, logger)
|
orderSvc := New(tx, orderStore, orderItemStore, walletStore, purchaseValidationSvc, nil, seriesAllocationStore, iotCardStore, deviceStore, nil, nil, logger)
|
||||||
|
|
||||||
userCtx := middleware.SetUserContext(ctx, &middleware.UserContextInfo{
|
userCtx := middleware.SetUserContext(ctx, &middleware.UserContextInfo{
|
||||||
UserID: 1,
|
UserID: 1,
|
||||||
@@ -620,3 +620,533 @@ func TestOrderService_IdempotencyAndConcurrency(t *testing.T) {
|
|||||||
assert.Equal(t, int64(0), usageCount)
|
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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user