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 } func NewOpenV1Handler(merchantSvc *service.MerchantService, fulfillmentSvc *service.FulfillmentService) *OpenV1Handler { return &OpenV1Handler{merchantSvc: merchantSvc, fulfillmentSvc: fulfillmentSvc} } func (h *OpenV1Handler) ListProducts(c *gin.Context) { 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) { 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 } if key := c.GetHeader("Idempotency-Key"); key != "" && key != req.ClientOrderNo { openlog.Warn(c, "create_order idempotency_key_mismatch header=%s body=%s", key, req.ClientOrderNo) response.BadRequest(c, "Idempotency-Key 必须与 client_order_no 一致") return } var data interface{} if len(req.Data) > 0 { 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) c.JSON(status, response.Body{ Code: 0, Message: "ok", Data: gin.H{ "order": buildOpenOrderResponse(result.Order), "idempotent": result.Idempotent, }, }) } func (h *OpenV1Handler) QueryOrder(c *gin.Context) { 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 payment=%s", order.OrderNo, order.FulfillmentStatus, order.PaymentStatus) response.OK(c, buildOpenOrderResponse(order)) } type openCancelOrderReq struct { Reason string `json:"reason"` } func (h *OpenV1Handler) CancelOrder(c *gin.Context) { 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)) } type openShipNotifyReq struct { OrderNo string `json:"order_no"` ShipStatus string `json:"ship_status" binding:"required"` ProviderOrderNo string `json:"provider_order_no"` FailReason string `json:"fail_reason"` Result json.RawMessage `json:"result"` } func (h *OpenV1Handler) ShipNotify(c *gin.Context) { var req openShipNotifyReq if err := c.ShouldBindJSON(&req); err != nil { openlog.Warn(c, "client_ship_notify bind_fail err=%v", err) response.BadRequest(c, "参数错误:ship_status 必填") return } status := "" switch req.ShipStatus { case "processing": status = model.FulfillmentStatusProcessing case "success": status = model.FulfillmentStatusSucceeded case "failed": status = model.FulfillmentStatusFailed default: openlog.Warn(c, "client_ship_notify bad_status=%s", req.ShipStatus) response.BadRequest(c, "ship_status 仅支持 processing、success、failed") return } var result interface{} if len(req.Result) > 0 { if err := json.Unmarshal(req.Result, &result); err != nil { openlog.Warn(c, "client_ship_notify bad_result err=%v", err) response.BadRequest(c, "result 必须是有效 JSON") return } } client := middleware.GetAPIClient(c) orderNo := c.Param("order_no") if orderNo == "" { orderNo = req.OrderNo } if orderNo == "" { openlog.Warn(c, "client_ship_notify missing_order_no") response.BadRequest(c, "order_no 必填") return } openlog.Info(c, "client_ship_notify start order_no=%s ship_status=%s provider_no=%s", orderNo, req.ShipStatus, req.ProviderOrderNo) order, err := h.fulfillmentSvc.UpdateFulfillment(service.FulfillmentUpdateInput{ MerchantID: middleware.GetMerchantID(c), APIClientID: client.ID, OrderNo: orderNo, Status: status, ProviderOrderNo: req.ProviderOrderNo, FailureReason: req.FailReason, ResultData: result, }) if err != nil { openlog.Warn(c, "client_ship_notify fail order_no=%s ship_status=%s err=%v", orderNo, req.ShipStatus, err) response.BadRequest(c, err.Error()) return } openlog.Info(c, "client_ship_notify ok order_no=%s status=%s", order.OrderNo, order.FulfillmentStatus) response.OK(c, buildOpenOrderResponse(order)) } func (h *OpenV1Handler) GetWallet(c *gin.Context) { 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 buildOpenOrderResponse(order *model.FulfillmentOrder) gin.H { canFulfill, reason := service.CanFulfill(order) data := gin.H{ "order_no": order.OrderNo, "client_order_no": order.ClientOrderNo, "payment_status": order.PaymentStatus, "fulfillment_status": order.FulfillmentStatus, "can_fulfill": canFulfill, "cannot_fulfill_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 }