fix: 修复代理钱包订单创建逻辑,拆分后台/H5端下单方法并归档变更
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 6m54s
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 6m54s
- 拆分订单创建为 CreateAdminOrder(后台一步支付)和 CreateH5Order(H5 两步支付) - 新增 CreateAdminOrderRequest DTO,后台仅允许 wallet/offline 支付方式 - 同步 delta specs 到主规格(order-payment 更新 + admin-order-creation 新增) - 归档 fix-agent-wallet-order-creation 变更 - 新增 implement-order-expiration 变更提案
This commit is contained in:
@@ -46,7 +46,7 @@ func initHandlers(svc *services, deps *Dependencies) *Handlers {
|
||||
ShopPackageAllocation: admin.NewShopPackageAllocationHandler(svc.ShopPackageAllocation),
|
||||
ShopPackageBatchAllocation: admin.NewShopPackageBatchAllocationHandler(svc.ShopPackageBatchAllocation),
|
||||
ShopPackageBatchPricing: admin.NewShopPackageBatchPricingHandler(svc.ShopPackageBatchPricing),
|
||||
AdminOrder: admin.NewOrderHandler(svc.Order),
|
||||
AdminOrder: admin.NewOrderHandler(svc.Order, validate),
|
||||
H5Order: h5.NewOrderHandler(svc.Order),
|
||||
H5Recharge: h5.NewRechargeHandler(svc.Recharge),
|
||||
PaymentCallback: callback.NewPaymentHandler(svc.Order, svc.Recharge, deps.WechatPayment),
|
||||
|
||||
@@ -3,6 +3,7 @@ package admin
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"github.com/go-playground/validator/v10"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||
@@ -14,24 +15,35 @@ import (
|
||||
"github.com/break/junhong_cmp_fiber/pkg/response"
|
||||
)
|
||||
|
||||
// OrderHandler 后台订单处理器
|
||||
type OrderHandler struct {
|
||||
service *orderService.Service
|
||||
service *orderService.Service
|
||||
validator *validator.Validate
|
||||
}
|
||||
|
||||
func NewOrderHandler(service *orderService.Service) *OrderHandler {
|
||||
return &OrderHandler{service: service}
|
||||
// NewOrderHandler 创建后台订单处理器
|
||||
func NewOrderHandler(service *orderService.Service, validator *validator.Validate) *OrderHandler {
|
||||
return &OrderHandler{service: service, validator: validator}
|
||||
}
|
||||
|
||||
// Create 创建后台订单
|
||||
// POST /api/admin/orders
|
||||
func (h *OrderHandler) Create(c *fiber.Ctx) error {
|
||||
var req dto.CreateOrderRequest
|
||||
var req dto.CreateAdminOrderRequest
|
||||
if err := c.BodyParser(&req); err != nil {
|
||||
return errors.New(errors.CodeInvalidParam, "请求参数解析失败")
|
||||
}
|
||||
|
||||
// 验证请求参数(payment_method 必须为 wallet 或 offline)
|
||||
if err := h.validator.Struct(&req); err != nil {
|
||||
return errors.New(errors.CodeInvalidParam)
|
||||
}
|
||||
|
||||
ctx := c.UserContext()
|
||||
userType := middleware.GetUserTypeFromContext(ctx)
|
||||
shopID := middleware.GetShopIDFromContext(ctx)
|
||||
|
||||
// 线下支付仅限平台用户
|
||||
if req.PaymentMethod == model.PaymentMethodOffline {
|
||||
if userType != constants.UserTypeSuperAdmin && userType != constants.UserTypePlatform {
|
||||
return errors.New(errors.CodeForbidden, "只有平台可以使用线下支付")
|
||||
@@ -40,6 +52,9 @@ func (h *OrderHandler) Create(c *fiber.Ctx) error {
|
||||
if userType != constants.UserTypeAgent && userType != constants.UserTypePlatform && userType != constants.UserTypeSuperAdmin {
|
||||
return errors.New(errors.CodeForbidden, "无权创建订单")
|
||||
}
|
||||
} else {
|
||||
// 防御性分支:DTO 验证已限制,此处兜底
|
||||
return errors.New(errors.CodeInvalidParam, "后台仅支持钱包支付和线下支付")
|
||||
}
|
||||
|
||||
buyerType := ""
|
||||
@@ -49,7 +64,7 @@ func (h *OrderHandler) Create(c *fiber.Ctx) error {
|
||||
buyerID = shopID
|
||||
}
|
||||
|
||||
order, err := h.service.Create(ctx, &req, buyerType, buyerID)
|
||||
order, err := h.service.CreateAdminOrder(ctx, &req, buyerType, buyerID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ func (h *OrderHandler) Create(c *fiber.Ctx) error {
|
||||
return errors.New(errors.CodeForbidden, "不支持的用户类型")
|
||||
}
|
||||
|
||||
order, err := h.service.Create(ctx, &req, buyerType, buyerID)
|
||||
order, err := h.service.CreateH5Order(ctx, &req, buyerType, buyerID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -3,6 +3,15 @@ package dto
|
||||
import "time"
|
||||
|
||||
type CreateOrderRequest 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列表"`
|
||||
PaymentMethod string `json:"payment_method" validate:"required,oneof=wallet wechat alipay" required:"true" description:"支付方式 (wallet:钱包支付, wechat:微信支付, alipay:支付宝支付)"`
|
||||
}
|
||||
|
||||
// CreateAdminOrderRequest 后台订单创建请求(仅允许 wallet/offline)
|
||||
type CreateAdminOrderRequest 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(设备购买时必填)"`
|
||||
|
||||
@@ -85,7 +85,9 @@ func New(
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Service) Create(ctx context.Context, req *dto.CreateOrderRequest, buyerType string, buyerID uint) (*dto.OrderResponse, error) {
|
||||
// CreateLegacy 创建订单(已废弃)
|
||||
// Deprecated: 使用 CreateAdminOrder 或 CreateH5Order 替代。保留用于回滚。
|
||||
func (s *Service) CreateLegacy(ctx context.Context, req *dto.CreateOrderRequest, buyerType string, buyerID uint) (*dto.OrderResponse, error) {
|
||||
var validationResult *purchase_validation.PurchaseValidationResult
|
||||
var err error
|
||||
|
||||
@@ -317,6 +319,475 @@ func (s *Service) Create(ctx context.Context, req *dto.CreateOrderRequest, buyer
|
||||
}
|
||||
}
|
||||
|
||||
// CreateAdminOrder 后台订单创建(仅支持 wallet/offline,立即扣款或激活)
|
||||
// 与 CreateH5Order 的核心区别:后台订单不创建待支付状态,wallet 立即扣款,offline 立即激活
|
||||
// POST /api/admin/orders
|
||||
func (s *Service) CreateAdminOrder(ctx context.Context, req *dto.CreateAdminOrderRequest, buyerType string, buyerID uint) (*dto.OrderResponse, error) {
|
||||
var validationResult *purchase_validation.PurchaseValidationResult
|
||||
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)
|
||||
} 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)
|
||||
} else {
|
||||
return nil, errors.New(errors.CodeInvalidParam, "无效的订单类型")
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 下单阶段校验混买限制:禁止同一订单同时包含正式套餐和加油包
|
||||
if err := validatePackageTypeMixFromPackages(validationResult.Packages); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 幂等性检查:防止同一买家对同一载体短时间内重复下单
|
||||
carrierType, carrierID := resolveAdminCarrierInfo(req)
|
||||
existingOrderID, err := s.checkOrderIdempotency(ctx, buyerType, buyerID, req.OrderType, carrierType, carrierID, req.PackageIDs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if existingOrderID > 0 {
|
||||
return s.Get(ctx, existingOrderID)
|
||||
}
|
||||
// 获取到分布式锁后,确保无论成功还是失败都释放
|
||||
lockKey := constants.RedisOrderCreateLockKey(carrierType, carrierID)
|
||||
defer s.redis.Del(ctx, lockKey)
|
||||
|
||||
forceRechargeCheck := s.checkForceRechargeRequirement(ctx, validationResult)
|
||||
if forceRechargeCheck.NeedForceRecharge && validationResult.TotalPrice < forceRechargeCheck.ForceRechargeAmount {
|
||||
return nil, errors.New(errors.CodeForceRechargeRequired, "首次购买需满足最低充值要求")
|
||||
}
|
||||
|
||||
userID := middleware.GetUserIDFromContext(ctx)
|
||||
|
||||
// 提取资源所属店铺ID
|
||||
var resourceShopID *uint
|
||||
var seriesID *uint
|
||||
if validationResult.Card != nil {
|
||||
resourceShopID = validationResult.Card.ShopID
|
||||
seriesID = validationResult.Card.SeriesID
|
||||
} else if validationResult.Device != nil {
|
||||
resourceShopID = validationResult.Device.ShopID
|
||||
seriesID = validationResult.Device.SeriesID
|
||||
}
|
||||
|
||||
// 初始化订单字段
|
||||
orderBuyerType := buyerType
|
||||
orderBuyerID := buyerID
|
||||
totalAmount := validationResult.TotalPrice
|
||||
paymentMethod := req.PaymentMethod
|
||||
paymentStatus := model.PaymentStatusPaid
|
||||
var paidAt *time.Time
|
||||
now := time.Now()
|
||||
isPurchaseOnBehalf := false
|
||||
var operatorID *uint
|
||||
operatorType := ""
|
||||
var actualPaidAmount *int64
|
||||
purchaseRole := ""
|
||||
var sellerShopID *uint = resourceShopID
|
||||
var sellerCostPrice int64
|
||||
|
||||
// 根据支付方式分别处理
|
||||
if req.PaymentMethod == model.PaymentMethodOffline {
|
||||
// ==== 场景 1:平台代购(offline)====
|
||||
purchaseBuyerID, buyerCostPrice, purchasePaidAt, err := s.resolvePurchaseOnBehalfInfo(ctx, validationResult)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
orderBuyerType = model.BuyerTypeAgent
|
||||
orderBuyerID = purchaseBuyerID
|
||||
totalAmount = buyerCostPrice
|
||||
paymentMethod = model.PaymentMethodOffline
|
||||
paymentStatus = model.PaymentStatusPaid
|
||||
paidAt = purchasePaidAt
|
||||
isPurchaseOnBehalf = true
|
||||
sellerCostPrice = buyerCostPrice
|
||||
|
||||
// 设置操作者信息(平台代购)
|
||||
operatorID = nil
|
||||
operatorType = constants.OwnerTypePlatform
|
||||
purchaseRole = model.PurchaseRolePurchasedByPlatform
|
||||
actualPaidAmount = nil
|
||||
|
||||
} else if req.PaymentMethod == model.PaymentMethodWallet {
|
||||
// ==== 场景 2:代理钱包支付(wallet)====
|
||||
// 只有代理账号可以使用钱包支付
|
||||
if buyerType != model.BuyerTypeAgent {
|
||||
return nil, errors.New(errors.CodeInvalidParam, "只有代理账号可以使用钱包支付")
|
||||
}
|
||||
operatorShopID := buyerID
|
||||
|
||||
// 判断资源是否属于操作者
|
||||
if resourceShopID == nil {
|
||||
return nil, errors.New(errors.CodeInternalError, "资源店铺ID为空")
|
||||
}
|
||||
|
||||
// 获取第一个套餐ID用于查询成本价
|
||||
if len(validationResult.Packages) == 0 {
|
||||
return nil, errors.New(errors.CodeInternalError, "套餐列表为空")
|
||||
}
|
||||
firstPackageID := validationResult.Packages[0].ID
|
||||
|
||||
if *resourceShopID == operatorShopID {
|
||||
// ==== 子场景 2.1:代理自购 ====
|
||||
costPrice, err := s.getCostPrice(ctx, operatorShopID, firstPackageID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
orderBuyerType = model.BuyerTypeAgent
|
||||
orderBuyerID = operatorShopID
|
||||
totalAmount = costPrice
|
||||
paymentMethod = model.PaymentMethodWallet
|
||||
paymentStatus = model.PaymentStatusPaid
|
||||
paidAt = &now
|
||||
isPurchaseOnBehalf = false
|
||||
|
||||
operatorID = &operatorShopID
|
||||
operatorType = "agent"
|
||||
actualPaidAmountVal := costPrice
|
||||
actualPaidAmount = &actualPaidAmountVal
|
||||
purchaseRole = model.PurchaseRoleSelfPurchase
|
||||
sellerCostPrice = costPrice
|
||||
|
||||
} else {
|
||||
// ==== 子场景 2.2:代理代购(给下级购买)====
|
||||
// 获取买家成本价
|
||||
buyerCostPrice, err := s.getCostPrice(ctx, *resourceShopID, firstPackageID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 获取操作者成本价
|
||||
operatorCostPrice, err := s.getCostPrice(ctx, operatorShopID, firstPackageID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
orderBuyerType = model.BuyerTypeAgent
|
||||
orderBuyerID = *resourceShopID
|
||||
totalAmount = buyerCostPrice
|
||||
paymentMethod = model.PaymentMethodWallet
|
||||
paymentStatus = model.PaymentStatusPaid
|
||||
paidAt = &now
|
||||
isPurchaseOnBehalf = true
|
||||
|
||||
operatorID = &operatorShopID
|
||||
operatorType = "agent"
|
||||
actualPaidAmount = &operatorCostPrice
|
||||
purchaseRole = model.PurchaseRolePurchaseForSubordinate
|
||||
sellerCostPrice = buyerCostPrice
|
||||
}
|
||||
} else {
|
||||
// 兜底检查:后台不支持其他支付方式(DTO 验证已拒绝,此为防御性编程)
|
||||
return nil, errors.New(errors.CodeInvalidParam, "后台仅支持钱包支付或线下支付")
|
||||
}
|
||||
|
||||
order := &model.Order{
|
||||
BaseModel: model.BaseModel{
|
||||
Creator: userID,
|
||||
Updater: userID,
|
||||
},
|
||||
OrderNo: s.orderStore.GenerateOrderNo(),
|
||||
OrderType: req.OrderType,
|
||||
BuyerType: orderBuyerType,
|
||||
BuyerID: orderBuyerID,
|
||||
IotCardID: req.IotCardID,
|
||||
DeviceID: req.DeviceID,
|
||||
TotalAmount: totalAmount,
|
||||
PaymentMethod: paymentMethod,
|
||||
PaymentStatus: paymentStatus,
|
||||
PaidAt: paidAt,
|
||||
CommissionStatus: model.CommissionStatusPending,
|
||||
CommissionConfigVersion: 0,
|
||||
SeriesID: seriesID,
|
||||
SellerShopID: sellerShopID,
|
||||
SellerCostPrice: sellerCostPrice,
|
||||
IsPurchaseOnBehalf: isPurchaseOnBehalf,
|
||||
OperatorID: operatorID,
|
||||
OperatorType: operatorType,
|
||||
ActualPaidAmount: actualPaidAmount,
|
||||
PurchaseRole: purchaseRole,
|
||||
}
|
||||
|
||||
items := s.buildOrderItems(userID, validationResult.Packages)
|
||||
|
||||
idempotencyKey := buildOrderIdempotencyKey(buyerType, buyerID, req.OrderType, carrierType, carrierID, req.PackageIDs)
|
||||
|
||||
// 根据支付方式选择创建订单的方式
|
||||
if req.PaymentMethod == model.PaymentMethodOffline {
|
||||
// 平台代购:创建订单并立即激活套餐
|
||||
if err := s.createOrderWithActivation(ctx, order, items); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.enqueueCommissionCalculation(ctx, order.ID)
|
||||
s.markOrderCreated(ctx, idempotencyKey, order.ID)
|
||||
return s.buildOrderResponse(order, items), nil
|
||||
|
||||
} else if req.PaymentMethod == model.PaymentMethodWallet {
|
||||
// 钱包支付:创建订单、扣款、激活套餐(在事务中完成)
|
||||
if operatorID == nil {
|
||||
return nil, errors.New(errors.CodeInternalError, "钱包支付场景下 operatorID 不能为空")
|
||||
}
|
||||
operatorShopID := *operatorID
|
||||
buyerShopID := orderBuyerID
|
||||
|
||||
if err := s.createOrderWithWalletPayment(ctx, order, items, operatorShopID, buyerShopID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.markOrderCreated(ctx, idempotencyKey, order.ID)
|
||||
return s.buildOrderResponse(order, items), nil
|
||||
|
||||
} else {
|
||||
// 不应该到这里(DTO 验证已拒绝其他支付方式)
|
||||
return nil, errors.New(errors.CodeInvalidParam, "后台仅支持钱包支付或线下支付")
|
||||
}
|
||||
}
|
||||
|
||||
// CreateH5Order H5 端订单创建(支持 wallet/wechat/alipay,支持待支付状态)
|
||||
// 保留原 Create() 方法的完整逻辑,H5 端行为不变
|
||||
// POST /api/h5/orders
|
||||
func (s *Service) CreateH5Order(ctx context.Context, req *dto.CreateOrderRequest, buyerType string, buyerID uint) (*dto.OrderResponse, error) {
|
||||
var validationResult *purchase_validation.PurchaseValidationResult
|
||||
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)
|
||||
} 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)
|
||||
} else {
|
||||
return nil, errors.New(errors.CodeInvalidParam, "无效的订单类型")
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 下单阶段校验混买限制:禁止同一订单同时包含正式套餐和加油包
|
||||
if err := validatePackageTypeMixFromPackages(validationResult.Packages); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 幂等性检查:防止同一买家对同一载体短时间内重复下单
|
||||
carrierType, carrierID := resolveCarrierInfo(req)
|
||||
existingOrderID, err := s.checkOrderIdempotency(ctx, buyerType, buyerID, req.OrderType, carrierType, carrierID, req.PackageIDs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if existingOrderID > 0 {
|
||||
return s.Get(ctx, existingOrderID)
|
||||
}
|
||||
// 获取到分布式锁后,确保无论成功还是失败都释放
|
||||
lockKey := constants.RedisOrderCreateLockKey(carrierType, carrierID)
|
||||
defer s.redis.Del(ctx, lockKey)
|
||||
|
||||
forceRechargeCheck := s.checkForceRechargeRequirement(ctx, validationResult)
|
||||
if forceRechargeCheck.NeedForceRecharge && validationResult.TotalPrice < forceRechargeCheck.ForceRechargeAmount {
|
||||
return nil, errors.New(errors.CodeForceRechargeRequired, "首次购买需满足最低充值要求")
|
||||
}
|
||||
|
||||
userID := middleware.GetUserIDFromContext(ctx)
|
||||
|
||||
// 提取资源所属店铺ID
|
||||
var resourceShopID *uint
|
||||
var seriesID *uint
|
||||
if validationResult.Card != nil {
|
||||
resourceShopID = validationResult.Card.ShopID
|
||||
seriesID = validationResult.Card.SeriesID
|
||||
} else if validationResult.Device != nil {
|
||||
resourceShopID = validationResult.Device.ShopID
|
||||
seriesID = validationResult.Device.SeriesID
|
||||
}
|
||||
|
||||
// 初始化订单字段
|
||||
orderBuyerType := buyerType
|
||||
orderBuyerID := buyerID
|
||||
totalAmount := validationResult.TotalPrice
|
||||
paymentMethod := req.PaymentMethod
|
||||
paymentStatus := model.PaymentStatusPending
|
||||
var paidAt *time.Time
|
||||
now := time.Now()
|
||||
isPurchaseOnBehalf := false
|
||||
var operatorID *uint
|
||||
operatorType := ""
|
||||
var actualPaidAmount *int64
|
||||
purchaseRole := ""
|
||||
var sellerShopID *uint = resourceShopID
|
||||
var sellerCostPrice int64
|
||||
|
||||
// 场景判断:offline(平台代购)、wallet(代理钱包支付)、其他(待支付)
|
||||
if req.PaymentMethod == model.PaymentMethodOffline {
|
||||
// ==== 场景 1:平台代购(offline)====
|
||||
purchaseBuyerID, buyerCostPrice, purchasePaidAt, err := s.resolvePurchaseOnBehalfInfo(ctx, validationResult)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
orderBuyerType = model.BuyerTypeAgent
|
||||
orderBuyerID = purchaseBuyerID
|
||||
totalAmount = buyerCostPrice
|
||||
paymentMethod = model.PaymentMethodOffline
|
||||
paymentStatus = model.PaymentStatusPaid
|
||||
paidAt = purchasePaidAt
|
||||
isPurchaseOnBehalf = true
|
||||
sellerCostPrice = buyerCostPrice
|
||||
|
||||
// 设置操作者信息(平台代购)
|
||||
operatorID = nil
|
||||
operatorType = constants.OwnerTypePlatform
|
||||
purchaseRole = model.PurchaseRolePurchasedByPlatform
|
||||
actualPaidAmount = nil
|
||||
|
||||
} else if req.PaymentMethod == model.PaymentMethodWallet {
|
||||
// ==== 场景 2:代理钱包支付(wallet)====
|
||||
// 只有代理账号可以使用钱包支付
|
||||
if buyerType != model.BuyerTypeAgent {
|
||||
return nil, errors.New(errors.CodeInvalidParam, "只有代理账号可以使用钱包支付")
|
||||
}
|
||||
operatorShopID := buyerID
|
||||
|
||||
// 判断资源是否属于操作者
|
||||
if resourceShopID == nil {
|
||||
return nil, errors.New(errors.CodeInternalError, "资源店铺ID为空")
|
||||
}
|
||||
|
||||
// 获取第一个套餐ID用于查询成本价
|
||||
if len(validationResult.Packages) == 0 {
|
||||
return nil, errors.New(errors.CodeInternalError, "套餐列表为空")
|
||||
}
|
||||
firstPackageID := validationResult.Packages[0].ID
|
||||
|
||||
if *resourceShopID == operatorShopID {
|
||||
// ==== 子场景 2.1:代理自购 ====
|
||||
costPrice, err := s.getCostPrice(ctx, operatorShopID, firstPackageID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
orderBuyerType = model.BuyerTypeAgent
|
||||
orderBuyerID = operatorShopID
|
||||
totalAmount = costPrice
|
||||
paymentMethod = model.PaymentMethodWallet
|
||||
paymentStatus = model.PaymentStatusPaid
|
||||
paidAt = &now
|
||||
isPurchaseOnBehalf = false
|
||||
|
||||
operatorID = &operatorShopID
|
||||
operatorType = "agent"
|
||||
actualPaidAmountVal := costPrice
|
||||
actualPaidAmount = &actualPaidAmountVal
|
||||
purchaseRole = model.PurchaseRoleSelfPurchase
|
||||
sellerCostPrice = costPrice
|
||||
|
||||
} else {
|
||||
// ==== 子场景 2.2:代理代购(给下级购买)====
|
||||
// 获取买家成本价
|
||||
buyerCostPrice, err := s.getCostPrice(ctx, *resourceShopID, firstPackageID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 获取操作者成本价
|
||||
operatorCostPrice, err := s.getCostPrice(ctx, operatorShopID, firstPackageID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
orderBuyerType = model.BuyerTypeAgent
|
||||
orderBuyerID = *resourceShopID
|
||||
totalAmount = buyerCostPrice
|
||||
paymentMethod = model.PaymentMethodWallet
|
||||
paymentStatus = model.PaymentStatusPaid
|
||||
paidAt = &now
|
||||
isPurchaseOnBehalf = true
|
||||
|
||||
operatorID = &operatorShopID
|
||||
operatorType = "agent"
|
||||
actualPaidAmount = &operatorCostPrice
|
||||
purchaseRole = model.PurchaseRolePurchaseForSubordinate
|
||||
sellerCostPrice = buyerCostPrice
|
||||
}
|
||||
}
|
||||
|
||||
order := &model.Order{
|
||||
BaseModel: model.BaseModel{
|
||||
Creator: userID,
|
||||
Updater: userID,
|
||||
},
|
||||
OrderNo: s.orderStore.GenerateOrderNo(),
|
||||
OrderType: req.OrderType,
|
||||
BuyerType: orderBuyerType,
|
||||
BuyerID: orderBuyerID,
|
||||
IotCardID: req.IotCardID,
|
||||
DeviceID: req.DeviceID,
|
||||
TotalAmount: totalAmount,
|
||||
PaymentMethod: paymentMethod,
|
||||
PaymentStatus: paymentStatus,
|
||||
PaidAt: paidAt,
|
||||
CommissionStatus: model.CommissionStatusPending,
|
||||
CommissionConfigVersion: 0,
|
||||
SeriesID: seriesID,
|
||||
SellerShopID: sellerShopID,
|
||||
SellerCostPrice: sellerCostPrice,
|
||||
IsPurchaseOnBehalf: isPurchaseOnBehalf,
|
||||
OperatorID: operatorID,
|
||||
OperatorType: operatorType,
|
||||
ActualPaidAmount: actualPaidAmount,
|
||||
PurchaseRole: purchaseRole,
|
||||
}
|
||||
|
||||
items := s.buildOrderItems(userID, validationResult.Packages)
|
||||
|
||||
idempotencyKey := buildOrderIdempotencyKey(buyerType, buyerID, req.OrderType, carrierType, carrierID, req.PackageIDs)
|
||||
|
||||
// 根据支付方式选择创建订单的方式
|
||||
if req.PaymentMethod == model.PaymentMethodOffline {
|
||||
// 平台代购:创建订单并立即激活套餐
|
||||
if err := s.createOrderWithActivation(ctx, order, items); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.enqueueCommissionCalculation(ctx, order.ID)
|
||||
s.markOrderCreated(ctx, idempotencyKey, order.ID)
|
||||
return s.buildOrderResponse(order, items), nil
|
||||
|
||||
} else if req.PaymentMethod == model.PaymentMethodWallet {
|
||||
// 钱包支付:创建订单、扣款、激活套餐(在事务中完成)
|
||||
if operatorID == nil {
|
||||
return nil, errors.New(errors.CodeInternalError, "钱包支付场景下 operatorID 不能为空")
|
||||
}
|
||||
operatorShopID := *operatorID
|
||||
buyerShopID := orderBuyerID
|
||||
|
||||
if err := s.createOrderWithWalletPayment(ctx, order, items, operatorShopID, buyerShopID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.markOrderCreated(ctx, idempotencyKey, order.ID)
|
||||
return s.buildOrderResponse(order, items), nil
|
||||
|
||||
} else {
|
||||
// 其他支付方式:创建待支付订单(H5 端支持 wechat/alipay)
|
||||
if err := s.orderStore.Create(ctx, order, items); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.markOrderCreated(ctx, idempotencyKey, order.ID)
|
||||
return s.buildOrderResponse(order, items), nil
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Service) resolvePurchaseOnBehalfInfo(ctx context.Context, result *purchase_validation.PurchaseValidationResult) (uint, int64, *time.Time, error) {
|
||||
var resourceShopID *uint
|
||||
var seriesID *uint
|
||||
@@ -1013,6 +1484,17 @@ func resolveCarrierInfo(req *dto.CreateOrderRequest) (carrierType string, carrie
|
||||
return "", 0
|
||||
}
|
||||
|
||||
// resolveAdminCarrierInfo 从后台订单请求中提取载体类型和ID
|
||||
func resolveAdminCarrierInfo(req *dto.CreateAdminOrderRequest) (carrierType string, carrierID uint) {
|
||||
if req.OrderType == model.OrderTypeSingleCard && req.IotCardID != nil {
|
||||
return "iot_card", *req.IotCardID
|
||||
}
|
||||
if req.OrderType == model.OrderTypeDevice && req.DeviceID != nil {
|
||||
return "device", *req.DeviceID
|
||||
}
|
||||
return "", 0
|
||||
}
|
||||
|
||||
// buildOrderIdempotencyKey 生成订单创建的幂等性业务键
|
||||
// 格式: {buyer_type}:{buyer_id}:{order_type}:{carrier_type}:{carrier_id}:{sorted_package_ids}
|
||||
func buildOrderIdempotencyKey(buyerType string, buyerID uint, orderType string, carrierType string, carrierID uint, packageIDs []uint) string {
|
||||
|
||||
Reference in New Issue
Block a user