package handler import ( "encoding/json" "net/http" "strconv" "affiliate_dash/internal/middleware" "affiliate_dash/internal/model" "affiliate_dash/internal/pkg/openlog" "affiliate_dash/internal/pkg/response" "affiliate_dash/internal/service" "github.com/gin-gonic/gin" ) // OpenV1Handler 提供面向商户系统的客户侧开放接口。 type OpenV1Handler struct { merchantSvc *service.MerchantService fulfillmentSvc *service.FulfillmentService deliverySvc *service.DeliveryService } func NewOpenV1Handler(merchantSvc *service.MerchantService, fulfillmentSvc *service.FulfillmentService, deliverySvc *service.DeliveryService) *OpenV1Handler { return &OpenV1Handler{merchantSvc: merchantSvc, fulfillmentSvc: fulfillmentSvc, deliverySvc: deliverySvc} } func (h *OpenV1Handler) ListProducts(c *gin.Context) { openlog.SetAction(c, openlog.ActionList) page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) size, _ := strconv.Atoi(c.DefaultQuery("size", "20")) openlog.Info(c, "list_products start page=%d size=%d", page, size) list, total, err := h.merchantSvc.ListMerchantProducts(middleware.GetMerchantID(c), page, size, true) if err != nil { openlog.Warn(c, "list_products fail err=%v", err) response.ServerError(c, err.Error()) return } openlog.Info(c, "list_products ok total=%d returned=%d", total, len(list)) response.Page(c, list, total, page, size) } type openCreateOrderReq struct { ClientOrderNo string `json:"client_order_no" binding:"required"` SKU string `json:"sku" binding:"required"` Quantity int64 `json:"quantity"` BuyerReference string `json:"buyer_reference"` Data json.RawMessage `json:"data"` } func (h *OpenV1Handler) CreateOrder(c *gin.Context) { openlog.SetAction(c, openlog.ActionCreate) var req openCreateOrderReq if err := c.ShouldBindJSON(&req); err != nil { openlog.Warn(c, "create_order bind_fail err=%v", err) response.BadRequest(c, "参数错误:client_order_no 与 sku 必填") return } var data interface{} if len(req.Data) > 0 { if len(req.Data) > 2048 { openlog.Warn(c, "create_order data_too_large size=%d", len(req.Data)) response.BadRequest(c, "data 最长 2048 字节") return } var decoded interface{} if err := json.Unmarshal(req.Data, &decoded); err != nil { openlog.Warn(c, "create_order bad_data err=%v", err) response.BadRequest(c, "data 必须是有效 JSON") return } data = decoded } client := middleware.GetAPIClient(c) openlog.Info(c, "create_order start client_order_no=%s sku=%s quantity=%d buyer=%s", req.ClientOrderNo, req.SKU, req.Quantity, req.BuyerReference) result, err := h.fulfillmentSvc.CreateOrder(service.CreateFulfillmentOrderInput{ MerchantID: middleware.GetMerchantID(c), APIClientID: client.ID, ClientOrderNo: req.ClientOrderNo, SKU: req.SKU, Quantity: req.Quantity, BuyerReference: req.BuyerReference, RequestData: data, }) if err != nil { openlog.Warn(c, "create_order fail client_order_no=%s sku=%s err=%v", req.ClientOrderNo, req.SKU, err) response.BadRequest(c, err.Error()) return } status := http.StatusOK if !result.Idempotent { status = http.StatusCreated } openlog.Info(c, "create_order ok order_no=%s idempotent=%v amount=%d", result.Order.OrderNo, result.Idempotent, result.Order.Amount) response.WithStatus(c, status, gin.H{ "order": buildOpenOrderResponse(result.Order), "idempotent": result.Idempotent, }) } func (h *OpenV1Handler) QueryOrder(c *gin.Context) { openlog.SetAction(c, openlog.ActionQuery) orderNo := c.Param("order_no") openlog.Info(c, "query_order start order_no=%s", orderNo) order, err := h.fulfillmentSvc.GetOrder(middleware.GetMerchantID(c), orderNo) if err != nil { openlog.Warn(c, "query_order fail order_no=%s err=%v", orderNo, err) if err.Error() == "订单不存在" { response.NotFound(c, err.Error()) return } response.ServerError(c, err.Error()) return } openlog.Info(c, "query_order ok order_no=%s status=%s", order.OrderNo, order.OrderStatus) response.OK(c, buildOpenOrderResponse(order)) } type openCancelOrderReq struct { Reason string `json:"reason"` } func (h *OpenV1Handler) CancelOrder(c *gin.Context) { openlog.SetAction(c, openlog.ActionCancel) var req openCancelOrderReq if err := c.ShouldBindJSON(&req); err != nil { openlog.Warn(c, "cancel_order bind_fail err=%v", err) response.BadRequest(c, "参数错误") return } orderNo := c.Param("order_no") openlog.Info(c, "cancel_order start order_no=%s reason=%s", orderNo, req.Reason) client := middleware.GetAPIClient(c) order, err := h.fulfillmentSvc.CancelOrder(middleware.GetMerchantID(c), client.ID, orderNo, req.Reason) if err != nil { openlog.Warn(c, "cancel_order fail order_no=%s err=%v", orderNo, err) response.BadRequest(c, err.Error()) return } openlog.Info(c, "cancel_order ok order_no=%s", order.OrderNo) response.OK(c, buildOpenOrderResponse(order)) } func (h *OpenV1Handler) GetWallet(c *gin.Context) { openlog.SetAction(c, openlog.ActionWallet) openlog.Info(c, "get_wallet start") wallet, err := h.fulfillmentSvc.GetWallet(middleware.GetMerchantID(c)) if err != nil { openlog.Warn(c, "get_wallet fail err=%v", err) response.ServerError(c, err.Error()) return } openlog.Info(c, "get_wallet ok balance=%d frozen=%d", wallet.AvailableBalance, wallet.FrozenBalance) response.OK(c, wallet) } func (h *OpenV1Handler) GetDeliveryLink(c *gin.Context) { openlog.SetAction(c, openlog.ActionQuery) orderNo := c.Param("order_no") openlog.Info(c, "delivery_link start order_no=%s", orderNo) link, err := h.deliverySvc.GetOrCreateDeliveryLink(middleware.GetMerchantID(c), orderNo, requestBaseURL(c)) if err != nil { openlog.Warn(c, "delivery_link fail order_no=%s err=%v", orderNo, err) if sc, ok := err.(interface{ HTTPStatus() int }); ok { response.Fail(c, sc.HTTPStatus(), sc.HTTPStatus(), err.Error()) return } if err.Error() == "订单不存在" { response.NotFound(c, err.Error()) return } response.BadRequest(c, err.Error()) return } response.OK(c, link) } func (h *OpenV1Handler) GetDeliveryOrder(c *gin.Context) { openlog.SetAction(c, openlog.ActionQuery) orderNo := c.Param("order_no") openlog.Info(c, "delivery_get start order_no=%s", orderNo) order, err := h.deliverySvc.GetMerchantOrder(middleware.GetMerchantID(c), orderNo) if err != nil { openlog.Warn(c, "delivery_get fail order_no=%s err=%v", orderNo, err) if err.Error() == "订单不存在" { response.NotFound(c, err.Error()) return } if sc, ok := err.(interface{ HTTPStatus() int }); ok { response.Fail(c, sc.HTTPStatus(), sc.HTTPStatus(), err.Error()) return } response.BadRequest(c, err.Error()) return } response.OK(c, order) } type openDeliveryBindReq struct { GameAccount string `json:"game_account" binding:"required"` } func (h *OpenV1Handler) BindDeliveryOrder(c *gin.Context) { openlog.SetAction(c, openlog.ActionCreate) var req openDeliveryBindReq if err := c.ShouldBindJSON(&req); err != nil { response.BadRequest(c, "请输入玩家编号") return } orderNo := c.Param("order_no") openlog.Info(c, "delivery_bind start order_no=%s", orderNo) result, err := h.deliverySvc.BindForMerchant(middleware.GetMerchantID(c), orderNo, req.GameAccount) if err != nil { openlog.Warn(c, "delivery_bind fail order_no=%s err=%v", orderNo, err) if sc, ok := err.(interface{ HTTPStatus() int }); ok { response.Fail(c, sc.HTTPStatus(), sc.HTTPStatus(), err.Error()) return } response.BadRequest(c, err.Error()) return } response.OK(c, result) } // GetDeliveryBindResult GET /api/client/v1/orders/:order_no/delivery/bind-result // 查询绑定结果与角色信息(自建发货页轮询绑定状态用)。 func (h *OpenV1Handler) GetDeliveryBindResult(c *gin.Context) { openlog.SetAction(c, openlog.ActionQuery) orderNo := c.Param("order_no") bindUUID := c.Query("bind_uuid") openlog.Info(c, "delivery_bind_result start order_no=%s bind_uuid=%s", orderNo, bindUUID) result, err := h.deliverySvc.GetBindResult(middleware.GetMerchantID(c), orderNo, bindUUID) if err != nil { openlog.Warn(c, "delivery_bind_result fail order_no=%s err=%v", orderNo, err) response.BadRequest(c, err.Error()) return } response.OK(c, result) } type openDeliverySubmitReq struct { GameAccount string `json:"game_account" binding:"required"` BindUUID string `json:"bind_uuid" binding:"required"` } func (h *OpenV1Handler) SubmitDeliveryOrder(c *gin.Context) { openlog.SetAction(c, openlog.ActionCreate) var req openDeliverySubmitReq if err := c.ShouldBindJSON(&req); err != nil { response.BadRequest(c, "玩家编号和绑定凭证不能为空") return } orderNo := c.Param("order_no") openlog.Info(c, "delivery_submit start order_no=%s", orderNo) client := middleware.GetAPIClient(c) result, err := h.deliverySvc.SubmitForMerchant(middleware.GetMerchantID(c), client.ID, orderNo, req.GameAccount, req.BindUUID) if err != nil { openlog.Warn(c, "delivery_submit fail order_no=%s err=%v", orderNo, err) if sc, ok := err.(interface{ HTTPStatus() int }); ok { response.Fail(c, sc.HTTPStatus(), sc.HTTPStatus(), err.Error()) return } if err.Error() == "订单不存在" { response.NotFound(c, err.Error()) return } response.BadRequest(c, err.Error()) return } response.OK(c, result) } func buildOpenOrderResponse(order *model.FulfillmentOrder) gin.H { canShip, reason := service.CanFulfill(order) data := gin.H{ "order_no": order.OrderNo, "client_order_no": order.ClientOrderNo, "order_status": order.OrderStatus, "order_source": order.OrderSource, "can_ship": canShip, "cannot_ship_reason": reason, "product": gin.H{ "sku": order.ProductSKU, "name": order.ProductName, }, "quantity": order.Quantity, "base_amount": order.BaseAmount, "fee_type": order.FeeType, "service_fee_amount": order.ServiceFeeAmount, "amount": order.Amount, "currency": order.Currency, "buyer_reference": order.BuyerReference, "provider_order_no": order.ProviderOrderNo, "failure_reason": order.FailureReason, "created_at": order.CreatedAt, "delivered_at": order.DeliveredAt, "cancelled_at": order.CancelledAt, } if json.Valid([]byte(order.RequestData)) { data["data"] = json.RawMessage(order.RequestData) } if json.Valid([]byte(order.ResultData)) { data["result"] = json.RawMessage(order.ResultData) } return data }