package order import ( "context" "time" "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" "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/pkg/queue" "github.com/break/junhong_cmp_fiber/pkg/utils" "github.com/break/junhong_cmp_fiber/pkg/wechat" "github.com/bytedance/sonic" "go.uber.org/zap" "gorm.io/gorm" ) type Service struct { db *gorm.DB orderStore *postgres.OrderStore orderItemStore *postgres.OrderItemStore walletStore *postgres.WalletStore purchaseValidationService *purchase_validation.Service allocationConfigStore *postgres.ShopSeriesAllocationConfigStore seriesAllocationStore *postgres.ShopSeriesAllocationStore iotCardStore *postgres.IotCardStore deviceStore *postgres.DeviceStore wechatPayment wechat.PaymentServiceInterface queueClient *queue.Client logger *zap.Logger } func New( db *gorm.DB, orderStore *postgres.OrderStore, orderItemStore *postgres.OrderItemStore, walletStore *postgres.WalletStore, purchaseValidationService *purchase_validation.Service, allocationConfigStore *postgres.ShopSeriesAllocationConfigStore, seriesAllocationStore *postgres.ShopSeriesAllocationStore, iotCardStore *postgres.IotCardStore, deviceStore *postgres.DeviceStore, wechatPayment wechat.PaymentServiceInterface, queueClient *queue.Client, logger *zap.Logger, ) *Service { return &Service{ db: db, orderStore: orderStore, orderItemStore: orderItemStore, walletStore: walletStore, purchaseValidationService: purchaseValidationService, allocationConfigStore: allocationConfigStore, seriesAllocationStore: seriesAllocationStore, iotCardStore: iotCardStore, deviceStore: deviceStore, wechatPayment: wechatPayment, queueClient: queueClient, logger: logger, } } func (s *Service) Create(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 } forceRechargeCheck := s.checkForceRechargeRequirement(validationResult) if forceRechargeCheck.NeedForceRecharge && validationResult.TotalPrice < forceRechargeCheck.ForceRechargeAmount { return nil, errors.New(errors.CodeForceRechargeRequired, "首次购买需满足最低充值要求") } userID := middleware.GetUserIDFromContext(ctx) configVersion := s.snapshotCommissionConfig(ctx, validationResult.Allocation.ID) var seriesID *uint var sellerShopID *uint var sellerCostPrice int64 if validationResult.Allocation != nil { seriesID = &validationResult.Allocation.SeriesID sellerShopID = &validationResult.Allocation.ShopID sellerCostPrice = utils.CalculateCostPrice(validationResult.Allocation, validationResult.TotalPrice) } order := &model.Order{ BaseModel: model.BaseModel{ Creator: userID, Updater: userID, }, OrderNo: s.orderStore.GenerateOrderNo(), OrderType: req.OrderType, BuyerType: buyerType, BuyerID: buyerID, IotCardID: req.IotCardID, DeviceID: req.DeviceID, TotalAmount: validationResult.TotalPrice, PaymentStatus: model.PaymentStatusPending, CommissionStatus: model.CommissionStatusPending, CommissionConfigVersion: configVersion, SeriesID: seriesID, SellerShopID: sellerShopID, SellerCostPrice: sellerCostPrice, } var items []*model.OrderItem for _, pkg := range validationResult.Packages { item := &model.OrderItem{ BaseModel: model.BaseModel{ Creator: userID, Updater: userID, }, PackageID: pkg.ID, PackageName: pkg.PackageName, Quantity: 1, UnitPrice: pkg.SuggestedRetailPrice, Amount: pkg.SuggestedRetailPrice, } items = append(items, item) } if err := s.orderStore.Create(ctx, order, items); err != nil { return nil, err } return s.buildOrderResponse(order, items), nil } func (s *Service) Get(ctx context.Context, id uint) (*dto.OrderResponse, error) { order, items, err := s.orderStore.GetByIDWithItems(ctx, id) if err != nil { if err == gorm.ErrRecordNotFound { return nil, errors.New(errors.CodeNotFound, "订单不存在") } return nil, err } return s.buildOrderResponse(order, items), nil } func (s *Service) List(ctx context.Context, req *dto.OrderListRequest, buyerType string, buyerID uint) (*dto.OrderListResponse, error) { page := req.Page pageSize := req.PageSize if page == 0 { page = 1 } if pageSize == 0 { pageSize = constants.DefaultPageSize } opts := &store.QueryOptions{ Page: page, PageSize: pageSize, } filters := map[string]any{ "buyer_type": buyerType, "buyer_id": buyerID, } if req.PaymentStatus != nil { filters["payment_status"] = *req.PaymentStatus } if req.OrderType != "" { filters["order_type"] = req.OrderType } if req.OrderNo != "" { filters["order_no"] = req.OrderNo } if req.StartTime != nil { filters["start_time"] = req.StartTime } if req.EndTime != nil { filters["end_time"] = req.EndTime } orders, total, err := s.orderStore.List(ctx, opts, filters) if err != nil { return nil, err } var orderIDs []uint for _, o := range orders { orderIDs = append(orderIDs, o.ID) } itemsMap := make(map[uint][]*model.OrderItem) if len(orderIDs) > 0 { allItems, err := s.orderItemStore.ListByOrderIDs(ctx, orderIDs) if err != nil { return nil, err } for _, item := range allItems { itemsMap[item.OrderID] = append(itemsMap[item.OrderID], item) } } var list []*dto.OrderResponse for _, o := range orders { list = append(list, s.buildOrderResponse(o, itemsMap[o.ID])) } totalPages := int(total) / pageSize if int(total)%pageSize > 0 { totalPages++ } return &dto.OrderListResponse{ List: list, Total: total, Page: page, PageSize: pageSize, TotalPages: totalPages, }, nil } func (s *Service) Cancel(ctx context.Context, id uint, buyerType string, buyerID uint) error { order, err := s.orderStore.GetByID(ctx, id) if err != nil { if err == gorm.ErrRecordNotFound { return errors.New(errors.CodeNotFound, "订单不存在") } return err } if order.BuyerType != buyerType || order.BuyerID != buyerID { return errors.New(errors.CodeForbidden, "无权操作此订单") } if order.PaymentStatus != model.PaymentStatusPending { return errors.New(errors.CodeInvalidStatus, "只能取消待支付的订单") } return s.orderStore.UpdatePaymentStatus(ctx, id, model.PaymentStatusCancelled, nil) } func (s *Service) WalletPay(ctx context.Context, orderID uint, buyerType string, buyerID uint) error { order, err := s.orderStore.GetByID(ctx, orderID) if err != nil { if err == gorm.ErrRecordNotFound { return errors.New(errors.CodeNotFound, "订单不存在") } return err } if order.BuyerType != buyerType || order.BuyerID != buyerID { return errors.New(errors.CodeForbidden, "无权操作此订单") } if order.IsPurchaseOnBehalf { return errors.New(errors.CodeInvalidStatus, "代购订单无需支付") } var resourceType string var resourceID uint if buyerType == model.BuyerTypePersonal { if order.OrderType == model.OrderTypeSingleCard && order.IotCardID != nil { resourceType = "iot_card" resourceID = *order.IotCardID } else if order.OrderType == model.OrderTypeDevice && order.DeviceID != nil { resourceType = "device" resourceID = *order.DeviceID } else { return errors.New(errors.CodeInvalidParam, "无法确定钱包归属") } } else if buyerType == model.BuyerTypeAgent { resourceType = "shop" resourceID = buyerID } else { return errors.New(errors.CodeInvalidParam, "不支持的买家类型") } wallet, err := s.walletStore.GetByResourceTypeAndID(ctx, resourceType, resourceID, "main") if err != nil { if err == gorm.ErrRecordNotFound { return errors.New(errors.CodeWalletNotFound, "钱包不存在") } return err } if wallet.Balance < order.TotalAmount { return errors.New(errors.CodeInsufficientBalance, "余额不足") } now := time.Now() err = s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { result := tx.Model(&model.Order{}). Where("id = ? AND payment_status = ?", orderID, model.PaymentStatusPending). Updates(map[string]any{ "payment_status": model.PaymentStatusPaid, "payment_method": model.PaymentMethodWallet, "paid_at": now, }) if result.Error != nil { return errors.Wrap(errors.CodeDatabaseError, result.Error, "更新订单支付状态失败") } if result.RowsAffected == 0 { var currentOrder model.Order if err := tx.First(¤tOrder, orderID).Error; err != nil { return errors.Wrap(errors.CodeDatabaseError, err, "查询订单失败") } switch currentOrder.PaymentStatus { case model.PaymentStatusPaid: return nil case model.PaymentStatusCancelled: return errors.New(errors.CodeInvalidStatus, "订单已取消,无法支付") case model.PaymentStatusRefunded: return errors.New(errors.CodeInvalidStatus, "订单已退款,无法支付") default: return errors.New(errors.CodeInvalidStatus, "订单状态异常") } } walletResult := tx.Model(&model.Wallet{}). Where("id = ? AND balance >= ? AND version = ?", wallet.ID, order.TotalAmount, wallet.Version). Updates(map[string]any{ "balance": gorm.Expr("balance - ?", order.TotalAmount), "version": gorm.Expr("version + 1"), }) if walletResult.Error != nil { return errors.Wrap(errors.CodeDatabaseError, walletResult.Error, "扣减钱包余额失败") } if walletResult.RowsAffected == 0 { return errors.New(errors.CodeInsufficientBalance, "余额不足或并发冲突") } return s.activatePackage(ctx, tx, order) }) if err != nil { return err } s.enqueueCommissionCalculation(ctx, orderID) return nil } func (s *Service) HandlePaymentCallback(ctx context.Context, orderNo string, paymentMethod string) error { order, err := s.orderStore.GetByOrderNo(ctx, orderNo) if err != nil { if err == gorm.ErrRecordNotFound { return errors.New(errors.CodeNotFound, "订单不存在") } return err } now := time.Now() err = s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { result := tx.Model(&model.Order{}). Where("id = ? AND payment_status = ?", order.ID, model.PaymentStatusPending). Updates(map[string]any{ "payment_status": model.PaymentStatusPaid, "payment_method": paymentMethod, "paid_at": now, }) if result.Error != nil { return errors.Wrap(errors.CodeDatabaseError, result.Error, "更新订单支付状态失败") } if result.RowsAffected == 0 { var currentOrder model.Order if err := tx.First(¤tOrder, order.ID).Error; err != nil { return errors.Wrap(errors.CodeDatabaseError, err, "查询订单失败") } switch currentOrder.PaymentStatus { case model.PaymentStatusPaid: return nil case model.PaymentStatusCancelled: return errors.New(errors.CodeInvalidStatus, "订单已取消,无法支付") case model.PaymentStatusRefunded: return errors.New(errors.CodeInvalidStatus, "订单已退款,无法支付") default: return errors.New(errors.CodeInvalidStatus, "订单状态异常") } } return s.activatePackage(ctx, tx, order) }) if err != nil { return err } s.enqueueCommissionCalculation(ctx, order.ID) return nil } func (s *Service) activatePackage(ctx context.Context, tx *gorm.DB, order *model.Order) error { var items []*model.OrderItem if err := tx.Where("order_id = ?", order.ID).Find(&items).Error; err != nil { return errors.Wrap(errors.CodeDatabaseError, err, "查询订单明细失败") } now := time.Now() for _, item := range items { var existingUsage model.PackageUsage err := tx.Where("order_id = ? AND package_id = ?", order.ID, item.PackageID). First(&existingUsage).Error if err == nil { s.logger.Warn("套餐使用记录已存在,跳过创建", zap.Uint("order_id", order.ID), zap.Uint("package_id", item.PackageID)) continue } if err != gorm.ErrRecordNotFound { return errors.Wrap(errors.CodeDatabaseError, err, "检查套餐使用记录失败") } var pkg model.Package if err := tx.First(&pkg, item.PackageID).Error; err != nil { return errors.Wrap(errors.CodeDatabaseError, err, "查询套餐信息失败") } usage := &model.PackageUsage{ BaseModel: model.BaseModel{ Creator: order.Creator, Updater: order.Creator, }, OrderID: order.ID, PackageID: item.PackageID, UsageType: order.OrderType, DataLimitMB: pkg.DataAmountMB, ActivatedAt: now, ExpiresAt: now.AddDate(0, pkg.DurationMonths, 0), Status: 1, } if order.OrderType == model.OrderTypeSingleCard && order.IotCardID != nil { usage.IotCardID = *order.IotCardID } else if order.OrderType == model.OrderTypeDevice && order.DeviceID != nil { usage.DeviceID = *order.DeviceID } if err := tx.Create(usage).Error; err != nil { return errors.Wrap(errors.CodeDatabaseError, err, "创建套餐使用记录失败") } } return nil } func (s *Service) snapshotCommissionConfig(ctx context.Context, allocationID uint) int { if s.allocationConfigStore == nil { return 0 } config, err := s.allocationConfigStore.GetEffective(ctx, allocationID, time.Now()) if err != nil || config == nil { return 0 } return config.Version } func (s *Service) enqueueCommissionCalculation(ctx context.Context, orderID uint) { if s.queueClient == nil { s.logger.Warn("队列客户端未初始化,跳过佣金计算任务入队", zap.Uint("order_id", orderID)) return } payload := map[string]interface{}{ "order_id": orderID, } payloadBytes, err := sonic.Marshal(payload) if err != nil { s.logger.Error("佣金计算任务载荷序列化失败", zap.Uint("order_id", orderID), zap.Error(err)) return } if err := s.queueClient.EnqueueTask(ctx, constants.TaskTypeCommission, payloadBytes); err != nil { s.logger.Error("佣金计算任务入队失败", zap.Uint("order_id", orderID), zap.Error(err), zap.String("task_type", constants.TaskTypeCommission)) return } s.logger.Info("佣金计算任务已入队", zap.Uint("order_id", orderID), zap.String("task_type", constants.TaskTypeCommission)) } func (s *Service) buildOrderResponse(order *model.Order, items []*model.OrderItem) *dto.OrderResponse { var itemResponses []*dto.OrderItemResponse for _, item := range items { itemResponses = append(itemResponses, &dto.OrderItemResponse{ ID: item.ID, PackageID: item.PackageID, PackageName: item.PackageName, Quantity: item.Quantity, UnitPrice: item.UnitPrice, Amount: item.Amount, }) } statusText := "" switch order.PaymentStatus { case model.PaymentStatusPending: statusText = "待支付" case model.PaymentStatusPaid: statusText = "已支付" case model.PaymentStatusCancelled: statusText = "已取消" case model.PaymentStatusRefunded: statusText = "已退款" } return &dto.OrderResponse{ ID: order.ID, OrderNo: order.OrderNo, OrderType: order.OrderType, BuyerType: order.BuyerType, BuyerID: order.BuyerID, IotCardID: order.IotCardID, DeviceID: order.DeviceID, TotalAmount: order.TotalAmount, PaymentMethod: order.PaymentMethod, PaymentStatus: order.PaymentStatus, PaymentStatusText: statusText, PaidAt: order.PaidAt, CommissionStatus: order.CommissionStatus, CommissionConfigVersion: order.CommissionConfigVersion, Items: itemResponses, CreatedAt: order.CreatedAt, UpdatedAt: order.UpdatedAt, } } // WechatPayJSAPI 发起微信 JSAPI 支付 func (s *Service) WechatPayJSAPI(ctx context.Context, orderID uint, openID string, buyerType string, buyerID uint) (*dto.WechatPayJSAPIResponse, error) { if s.wechatPayment == nil { s.logger.Error("微信支付服务未配置") return nil, errors.New(errors.CodeWechatPayFailed, "微信支付服务未配置") } order, err := s.orderStore.GetByID(ctx, orderID) if err != nil { if err == gorm.ErrRecordNotFound { return nil, errors.New(errors.CodeNotFound, "订单不存在") } return nil, errors.Wrap(errors.CodeInternalError, err, "查询订单失败") } if order.BuyerType != buyerType || order.BuyerID != buyerID { return nil, errors.New(errors.CodeForbidden, "无权操作此订单") } if order.PaymentStatus != model.PaymentStatusPending { return nil, errors.New(errors.CodeInvalidStatus, "订单状态不允许支付") } items, err := s.orderItemStore.ListByOrderIDs(ctx, []uint{orderID}) if err != nil { return nil, errors.Wrap(errors.CodeInternalError, err, "查询订单明细失败") } description := "套餐购买" if len(items) > 0 { description = items[0].PackageName } result, err := s.wechatPayment.CreateJSAPIOrder(ctx, order.OrderNo, description, openID, int(order.TotalAmount)) if err != nil { s.logger.Error("创建 JSAPI 支付失败", zap.Uint("order_id", orderID), zap.String("order_no", order.OrderNo), zap.Error(err), ) return nil, err } s.logger.Info("创建 JSAPI 支付成功", zap.Uint("order_id", orderID), zap.String("order_no", order.OrderNo), zap.String("prepay_id", result.PrepayID), ) payConfig, _ := result.PayConfig.(map[string]interface{}) return &dto.WechatPayJSAPIResponse{ PrepayID: result.PrepayID, PayConfig: payConfig, }, nil } // WechatPayH5 发起微信 H5 支付 func (s *Service) WechatPayH5(ctx context.Context, orderID uint, sceneInfo *dto.WechatH5SceneInfo, buyerType string, buyerID uint) (*dto.WechatPayH5Response, error) { if s.wechatPayment == nil { s.logger.Error("微信支付服务未配置") return nil, errors.New(errors.CodeWechatPayFailed, "微信支付服务未配置") } order, err := s.orderStore.GetByID(ctx, orderID) if err != nil { if err == gorm.ErrRecordNotFound { return nil, errors.New(errors.CodeNotFound, "订单不存在") } return nil, errors.Wrap(errors.CodeInternalError, err, "查询订单失败") } if order.BuyerType != buyerType || order.BuyerID != buyerID { return nil, errors.New(errors.CodeForbidden, "无权操作此订单") } if order.PaymentStatus != model.PaymentStatusPending { return nil, errors.New(errors.CodeInvalidStatus, "订单状态不允许支付") } items, err := s.orderItemStore.ListByOrderIDs(ctx, []uint{orderID}) if err != nil { return nil, errors.Wrap(errors.CodeInternalError, err, "查询订单明细失败") } description := "套餐购买" if len(items) > 0 { description = items[0].PackageName } h5SceneInfo := &wechat.H5SceneInfo{ PayerClientIP: sceneInfo.PayerClientIP, H5Type: sceneInfo.H5Info.Type, } result, err := s.wechatPayment.CreateH5Order(ctx, order.OrderNo, description, int(order.TotalAmount), h5SceneInfo) if err != nil { s.logger.Error("创建 H5 支付失败", zap.Uint("order_id", orderID), zap.String("order_no", order.OrderNo), zap.Error(err), ) return nil, err } s.logger.Info("创建 H5 支付成功", zap.Uint("order_id", orderID), zap.String("order_no", order.OrderNo), zap.String("h5_url", result.H5URL), ) return &dto.WechatPayH5Response{ H5URL: result.H5URL, }, 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 }