package app import ( "encoding/json" "fmt" "net/http" "time" "bindbox-game/internal/code" "bindbox-game/internal/pkg/core" "bindbox-game/internal/pkg/validation" "bindbox-game/internal/repository/mysql/model" ) const ( shippingFeeThreshold = 5 shippingFeeCents = 1000 // 运费金额(分),10 元 shippingFeeSourceType = int32(5) // orders.source_type: 5 = 运费订单 shippingFeeReasonBelowThreshold = "below_threshold" shippingFeeReasonContainsNonFreeShipping = "contains_non_free_shipping_item" ) type shippingFeePreorderRequest struct { InventoryIDs []int64 `json:"inventory_ids"` } type shippingFeePreorderResponse struct { OrderNo string `json:"order_no"` } type shippingFeeCheckResponse struct { NeedFee bool `json:"need_fee"` Reason string `json:"reason,omitempty"` FeeCents int64 `json:"fee_cents"` } func (h *handler) ShippingFeeCheck() core.HandlerFunc { return func(ctx core.Context) { req := new(shippingFeePreorderRequest) rsp := &shippingFeeCheckResponse{FeeCents: shippingFeeCents} if err := ctx.ShouldBindJSON(req); err != nil { ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, validation.Error(err))) return } if len(req.InventoryIDs) == 0 { ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "inventory_ids 不能为空")) return } userID := int64(ctx.SessionUserInfo().Id) needFee, reason, err := h.user.CheckShippingFeeRequirement(ctx.RequestContext(), userID, req.InventoryIDs) if err != nil { ctx.AbortWithError(core.Error(http.StatusBadRequest, 150002, err.Error())) return } rsp.NeedFee = needFee rsp.Reason = reason ctx.Payload(rsp) } } // ShippingFeePreorder 创建运费订单 // @Summary 创建运费订单 // @Description 选中商品命中运费规则时,创建 10 元运费订单并返回 order_no;前端再调用 /pay/wechat/jsapi/preorder 发起支付;无需运费时不应调用 // @Tags APP端.用户 // @Accept json // @Produce json // @Security LoginVerifyToken // @Param user_id path integer true "用户ID" // @Param RequestBody body shippingFeePreorderRequest true "请求参数:资产ID列表" // @Success 200 {object} shippingFeePreorderResponse // @Failure 400 {object} code.Failure // @Router /api/app/users/{user_id}/inventory/shipping-fee/preorder [post] func (h *handler) ShippingFeePreorder() core.HandlerFunc { return func(ctx core.Context) { req := new(shippingFeePreorderRequest) rsp := new(shippingFeePreorderResponse) if err := ctx.ShouldBindJSON(req); err != nil { ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, validation.Error(err))) return } if len(req.InventoryIDs) == 0 { ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "inventory_ids 不能为空")) return } userID := int64(ctx.SessionUserInfo().Id) needFee, _, err := h.user.CheckShippingFeeRequirement(ctx.RequestContext(), userID, req.InventoryIDs) if err != nil { ctx.AbortWithError(core.Error(http.StatusBadRequest, 150002, err.Error())) return } if !needFee { ctx.AbortWithError(core.Error(http.StatusBadRequest, 150001, fmt.Sprintf("件数满 %d 件且均非不包邮分类商品,无需支付运费", shippingFeeThreshold))) return } remarkBytes, _ := json.Marshal(req.InventoryIDs) // 生成运费订单号(SF + 时间戳 + 用户ID后6位,保证唯一) now := time.Now() orderNo := fmt.Sprintf("SF%s%06d", now.Format("20060102150405"), userID%1000000) // 创建运费订单(source_type=5 区分普通商品订单,status=1 待支付) order := &model.Orders{ UserID: userID, OrderNo: orderNo, SourceType: shippingFeeSourceType, TotalAmount: shippingFeeCents, DiscountAmount: 0, PointsAmount: 0, ActualAmount: shippingFeeCents, Status: 1, IsConsumed: 0, Remark: string(remarkBytes), } if err := h.writeDB.Orders.WithContext(ctx.RequestContext()). Omit(h.writeDB.Orders.PaidAt, h.writeDB.Orders.CancelledAt). Create(order); err != nil { ctx.AbortWithError(core.Error(http.StatusBadRequest, 150002, "创建运费订单失败: "+err.Error())) return } rsp.OrderNo = orderNo ctx.Payload(rsp) } }