Files
2026-08-14 12:58:20 +08:00

312 lines
10 KiB
Go

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" binding:"max=512"`
}
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, "参数错误:reason 最长 512 字")
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
}