262 lines
8.6 KiB
Go
262 lines
8.6 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
|
|
}
|
|
|
|
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
|
|
}
|