package service import ( "encoding/json" "errors" "math" "strings" "affiliate_dash/internal/model" "github.com/google/uuid" "gorm.io/gorm" "gorm.io/gorm/clause" ) type CreateFulfillmentOrderInput struct { MerchantID uint APIClientID uint ActorUserID uint ClientOrderNo string SKU string Quantity int64 BuyerReference string RequestData interface{} OrderSource string } type CreateFulfillmentOrderResult struct { Order *model.FulfillmentOrder `json:"order"` Idempotent bool `json:"idempotent"` } // CreateManualOrderInput 是商户后台创建真实订单的输入。其账务、库存与回调规则与开放 API 下单一致。 type CreateManualOrderInput struct { MerchantID uint ActorUserID uint ClientOrderNo string SKU string Quantity int64 BuyerReference string RequestData interface{} } func (s *FulfillmentService) CreateManualOrder(in CreateManualOrderInput) (*CreateFulfillmentOrderResult, error) { return s.CreateOrder(CreateFulfillmentOrderInput{ MerchantID: in.MerchantID, ActorUserID: in.ActorUserID, ClientOrderNo: in.ClientOrderNo, SKU: in.SKU, Quantity: in.Quantity, BuyerReference: in.BuyerReference, RequestData: in.RequestData, OrderSource: model.OrderSourceManual, }) } func (s *FulfillmentService) CreateOrder(in CreateFulfillmentOrderInput) (*CreateFulfillmentOrderResult, error) { in.ClientOrderNo = strings.TrimSpace(in.ClientOrderNo) in.SKU = strings.TrimSpace(in.SKU) if in.MerchantID == 0 || (in.APIClientID == 0 && in.ActorUserID == 0) { return nil, errors.New("无效的商户或下单身份") } if in.ClientOrderNo == "" || len(in.ClientOrderNo) > 96 { return nil, errors.New("client_order_no 不能为空且最长 96 位") } if in.SKU == "" { return nil, errors.New("sku 不能为空") } if in.Quantity == 0 { in.Quantity = 1 } if in.Quantity < 1 { return nil, errors.New("quantity 必须大于零") } orderSource := strings.TrimSpace(in.OrderSource) if orderSource == "" { orderSource = model.OrderSourceAPI } switch orderSource { case model.OrderSourceAPI, model.OrderSourceManual: default: return nil, errors.New("无效的订单来源") } requestData := "" if in.RequestData != nil { raw, err := json.Marshal(in.RequestData) if err != nil { return nil, errors.New("订单请求数据无法序列化") } requestData = string(raw) } fingerprint := orderRequestFingerprint(in, requestData) result := &CreateFulfillmentOrderResult{} err := s.db.Transaction(func(tx *gorm.DB) error { var existing model.FulfillmentOrder err := tx.Where("merchant_id = ? AND client_order_no = ?", in.MerchantID, in.ClientOrderNo).First(&existing).Error if err == nil { if err := ensureSameIdempotentOrder(&existing, fingerprint); err != nil { return err } result.Order = &existing result.Idempotent = true return nil } if !errors.Is(err, gorm.ErrRecordNotFound) { return err } var merchant model.Merchant if err := tx.Where("id = ? AND status = ?", in.MerchantID, model.MerchantStatusActive).First(&merchant).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return errors.New("商户不存在或已禁用") } return err } var product model.MerchantProduct if err := tx.Preload("Product").Clauses(clause.Locking{Strength: "UPDATE"}). Where("merchant_id = ? AND sku = ? AND status = ?", in.MerchantID, in.SKU, model.ProductStatusActive). First(&product).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return errors.New("商品不存在或已下架") } return err } if product.Product == nil || product.Product.Status != model.ProductStatusActive { return errors.New("商品目录已下架") } if product.Stock >= 0 && product.Stock < in.Quantity { return errors.New("商品库存不足") } if product.PriceAmount > 0 && in.Quantity > math.MaxInt64/product.PriceAmount { return errors.New("订单金额超出范围") } baseAmount := product.PriceAmount * in.Quantity serviceFee, err := calculateServiceFee(baseAmount, merchant.FeeType, merchant.FeeRateBP, merchant.FeeFixedAmount) if err != nil { return err } if serviceFee > math.MaxInt64-baseAmount { return errors.New("订单金额超出范围") } totalAmount := baseAmount + serviceFee var wallet model.WalletAccount if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}). Where("merchant_id = ?", in.MerchantID).First(&wallet).Error; err != nil { return err } if wallet.AvailableBalance < totalAmount { return errors.New("商户钱包余额不足") } newBalance := wallet.AvailableBalance - totalAmount if err := tx.Model(&wallet).Update("available_balance", newBalance).Error; err != nil { return err } order := &model.FulfillmentOrder{ MerchantID: in.MerchantID, OrderNo: newFulfillmentOrderNo(), ClientOrderNo: in.ClientOrderNo, MerchantProductID: product.ID, ProductSKU: product.SKU, ProductName: fallbackName(product.DisplayName, product.Product.Name), Quantity: in.Quantity, BaseAmount: baseAmount, FeeType: merchant.FeeType, FeeRateBP: merchant.FeeRateBP, FeeFixedAmount: merchant.FeeFixedAmount, ServiceFeeAmount: serviceFee, Amount: totalAmount, Currency: product.Currency, OrderStatus: model.OrderStatusPaid, OrderSource: orderSource, BuyerReference: in.BuyerReference, RequestFingerprint: fingerprint, RequestData: requestData, } if err := tx.Create(order).Error; err != nil { return err } idempotencyKey := in.ClientOrderNo ledgerNote := "开放接口下单扣款(含平台手续费)" if orderSource == model.OrderSourceManual { ledgerNote = "商户后台手动下单扣款(含平台手续费)" } if err := tx.Create(&model.WalletLedgerEntry{ MerchantID: in.MerchantID, WalletAccountID: wallet.ID, EntryNo: "WL" + uuid.NewString(), Type: model.WalletLedgerDebit, Amount: -totalAmount, BalanceAfter: newBalance, ReferenceType: "fulfillment_order", ReferenceNo: order.OrderNo, IdempotencyKey: &idempotencyKey, Note: ledgerNote, }).Error; err != nil { return err } if product.Stock >= 0 { if err := tx.Model(&model.MerchantProduct{}).Where("id = ? AND stock >= ?", product.ID, in.Quantity). Update("stock", gorm.Expr("stock - ?", in.Quantity)).Error; err != nil { return err } } action := "open_order.create" if orderSource == model.OrderSourceManual { action = "merchant_order.manual_create" } if err := writeAudit(tx, &in.MerchantID, optionalUint(in.ActorUserID), optionalUint(in.APIClientID), action, "fulfillment_order", order.OrderNo, map[string]interface{}{"client_order_no": in.ClientOrderNo, "sku": in.SKU, "source": orderSource}); err != nil { return err } if s.callbacks != nil && orderSource != model.OrderSourceManual { if err := s.callbacks.Enqueue(tx, in.MerchantID, "order.created", orderCallbackData(order)); err != nil { return err } } result.Order = order return nil }) if err != nil { // 并发请求恰好同时通过首次查询时,唯一约束冲突后返回既有订单。 if strings.Contains(err.Error(), "duplicate") || strings.Contains(err.Error(), "UNIQUE") { var existing model.FulfillmentOrder if queryErr := s.db.Where("merchant_id = ? AND client_order_no = ?", in.MerchantID, in.ClientOrderNo).First(&existing).Error; queryErr == nil { if sameErr := ensureSameIdempotentOrder(&existing, fingerprint); sameErr != nil { return nil, sameErr } return &CreateFulfillmentOrderResult{Order: &existing, Idempotent: true}, nil } } return nil, err } return result, nil }