package handler import ( "encoding/json" "time" "affiliate_dash/internal/pkg/openlog" "affiliate_dash/internal/pkg/response" "affiliate_dash/internal/service" "github.com/gin-gonic/gin" ) // OpenHandler 皮肤源头开放接口(上游 SourceOpen),基于 FulfillmentOrder 履约模型。 type OpenHandler struct { fulfillmentSvc *service.FulfillmentService } func NewOpenHandler(fulfillmentSvc *service.FulfillmentService) *OpenHandler { return &OpenHandler{fulfillmentSvc: fulfillmentSvc} } // QueryOrder GET /api/open/v1/orders/:order_no // 上游输入店铺订单号,查询商品与是否可发货 func (h *OpenHandler) QueryOrder(c *gin.Context) { orderNo := c.Param("order_no") if orderNo == "" { orderNo = c.Query("order_no") } openlog.Info(c, "query_order start order_no=%s", orderNo) data, err := h.fulfillmentSvc.QueryOpenOrder(orderNo) if err != nil { openlog.Warn(c, "query_order fail order_no=%s err=%s", orderNo, err.Error()) if err.Error() == "订单不存在" { response.NotFound(c, err.Error()) return } response.BadRequest(c, err.Error()) return } sku, name, game := "", "", "" if data.Product != nil { sku, name, game = data.Product.SKU, data.Product.Name, data.Product.Game } openlog.Info(c, "query_order ok order_no=%s status=%s can_ship=%v reason=%q sku=%s name=%s game=%s buyer=%s amount=%.2f provider_no=%s shipped_at=%v fail_reason=%q game_channel=%s game_uid=%s role_name=%s pay_score=%d", data.OrderNo, data.Status, data.CanShip, data.CannotShipReason, sku, name, game, data.BuyerName, data.Amount, data.ProviderOrderNo, data.ShippedAt, data.ShipFailReason, data.GameChannel, data.GameUID, data.RoleName, data.PayScore, ) response.OK(c, data) } type shipNotifyReq struct { OrderNo string `json:"order_no" binding:"required"` ShipStatus string `json:"ship_status" binding:"required"` // success/failed/processing ProviderOrderNo string `json:"provider_order_no"` ShippedAt string `json:"shipped_at"` // RFC3339 可选 FailReason string `json:"fail_reason"` GameChannel *string `json:"game_channel"` // 账号区服(安卓/IOS-微信/QQ) GameUID *string `json:"game_uid"` // 游戏角色UUID RoleName *string `json:"role_name"` // 角色名 PayScore *int `json:"pay_score"` // 消耗积分 } // ShipNotify POST /api/open/v1/orders/ship-notify // 上游发货后推送结果,同步订单状态 func (h *OpenHandler) ShipNotify(c *gin.Context) { var req shipNotifyReq if err := c.ShouldBindJSON(&req); err != nil { openlog.Warn(c, "ship_notify bind_fail err=%v", err) response.BadRequest(c, "参数错误:order_no 与 ship_status 必填") return } raw, _ := json.Marshal(req) openlog.Info(c, "ship_notify start order_no=%s ship_status=%s provider_no=%s shipped_at=%s fail_reason=%q payload=%s", req.OrderNo, req.ShipStatus, req.ProviderOrderNo, req.ShippedAt, req.FailReason, openlog.Truncate(string(raw), 800), ) var shippedAt *time.Time if req.ShippedAt != "" { t, err := time.Parse(time.RFC3339, req.ShippedAt) if err != nil { openlog.Warn(c, "ship_notify bad_shipped_at raw=%s err=%v", req.ShippedAt, err) response.BadRequest(c, "shipped_at 格式错误,请使用 RFC3339,如 2026-07-20T16:00:00+08:00") return } shippedAt = &t } result, err := h.fulfillmentSvc.HandleShipNotify(service.ShipNotifyInput{ OrderNo: req.OrderNo, ShipStatus: req.ShipStatus, ProviderOrderNo: req.ProviderOrderNo, ShippedAt: shippedAt, FailReason: req.FailReason, RawPayload: string(raw), GameChannel: req.GameChannel, GameUID: req.GameUID, RoleName: req.RoleName, PayScore: req.PayScore, }) if err != nil { openlog.Warn(c, "ship_notify fail order_no=%s ship_status=%s err=%s", req.OrderNo, req.ShipStatus, err.Error()) if err.Error() == "订单不存在" { response.NotFound(c, err.Error()) return } response.BadRequest(c, err.Error()) return } openlog.Info(c, "ship_notify ok order_no=%s result_status=%s message=%s", result.OrderNo, result.Status, result.Message) response.OK(c, result) }