|
|
@ -3,19 +3,23 @@ package handler |
|
|
import ( |
|
|
import ( |
|
|
"bytes" |
|
|
"bytes" |
|
|
"encoding/json" |
|
|
"encoding/json" |
|
|
|
|
|
"fmt" |
|
|
"io" |
|
|
"io" |
|
|
"mime" |
|
|
"mime" |
|
|
"net/http" |
|
|
"net/http" |
|
|
|
|
|
"strings" |
|
|
|
|
|
|
|
|
"github.com/gin-gonic/gin" |
|
|
"github.com/gin-gonic/gin" |
|
|
"github.com/gin-gonic/gin/binding" |
|
|
|
|
|
|
|
|
|
|
|
"laic-backend/common" |
|
|
"laic-backend/common" |
|
|
|
|
|
"laic-backend/logger" |
|
|
"laic-backend/model" |
|
|
"laic-backend/model" |
|
|
"laic-backend/service" |
|
|
"laic-backend/service" |
|
|
"laic-backend/vo" |
|
|
"laic-backend/vo" |
|
|
) |
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
const maxAGPayCallbackBodyBytes = 64 * 1024 |
|
|
|
|
|
|
|
|
func CreateWechatPayment(c *gin.Context) { |
|
|
func CreateWechatPayment(c *gin.Context) { |
|
|
var req vo.PaymentCreateReq |
|
|
var req vo.PaymentCreateReq |
|
|
if err := c.ShouldBindJSON(&req); err != nil { |
|
|
if err := c.ShouldBindJSON(&req); err != nil { |
|
|
@ -45,29 +49,21 @@ func GetPaymentTransaction(c *gin.Context) { |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
func AGPayCallback(c *gin.Context) { |
|
|
func AGPayCallback(c *gin.Context) { |
|
|
contentType, _, err := mime.ParseMediaType(c.GetHeader("Content-Type")) |
|
|
|
|
|
if err != nil || contentType != "application/json" { |
|
|
|
|
|
c.Status(http.StatusBadRequest) |
|
|
|
|
|
return |
|
|
|
|
|
} |
|
|
|
|
|
c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, 1<<10) |
|
|
|
|
|
|
|
|
c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, maxAGPayCallbackBodyBytes) |
|
|
body, err := c.GetRawData() |
|
|
body, err := c.GetRawData() |
|
|
if err != nil { |
|
|
if err != nil { |
|
|
|
|
|
logger.WARN("拒绝 AGPay 支付回调", "stage", "read_body", "error", err) |
|
|
c.Status(http.StatusBadRequest) |
|
|
c.Status(http.StatusBadRequest) |
|
|
return |
|
|
return |
|
|
} |
|
|
} |
|
|
var callback model.AGPayCallback |
|
|
|
|
|
decoder := json.NewDecoder(bytes.NewReader(body)) |
|
|
|
|
|
decoder.DisallowUnknownFields() |
|
|
|
|
|
if err := decoder.Decode(&callback); err != nil || decoder.Decode(&struct{}{}) != io.EOF { |
|
|
|
|
|
c.Status(http.StatusBadRequest) |
|
|
|
|
|
return |
|
|
|
|
|
} |
|
|
|
|
|
if err := binding.Validator.ValidateStruct(&callback); err != nil { |
|
|
|
|
|
|
|
|
callback, err := parseAGPayCallback(c.GetHeader("Content-Type"), body) |
|
|
|
|
|
if err != nil { |
|
|
|
|
|
logger.WARN("拒绝 AGPay 支付回调", "stage", "parse", "contentType", c.GetHeader("Content-Type"), "bodyBytes", len(body), "error", err) |
|
|
c.Status(http.StatusBadRequest) |
|
|
c.Status(http.StatusBadRequest) |
|
|
return |
|
|
return |
|
|
} |
|
|
} |
|
|
if err := service.DefaultPaymentService.ConfirmAGPayCallback(callback.TradeNo, body); err != nil { |
|
|
if err := service.DefaultPaymentService.ConfirmAGPayCallback(callback.TradeNo, body); err != nil { |
|
|
|
|
|
logger.WARN("拒绝 AGPay 支付回调", "stage", "confirm", "tradeNo", callback.TradeNo, "code", err.Code, "error", err) |
|
|
if err == common.ErrInternal { |
|
|
if err == common.ErrInternal { |
|
|
c.Status(http.StatusInternalServerError) |
|
|
c.Status(http.StatusInternalServerError) |
|
|
return |
|
|
return |
|
|
@ -77,3 +73,29 @@ func AGPayCallback(c *gin.Context) { |
|
|
} |
|
|
} |
|
|
c.JSON(http.StatusOK, gin.H{"code": "SUCCESS"}) |
|
|
c.JSON(http.StatusOK, gin.H{"code": "SUCCESS"}) |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
func parseAGPayCallback(contentType string, body []byte) (model.AGPayCallback, error) { |
|
|
|
|
|
var callback model.AGPayCallback |
|
|
|
|
|
mediaType, _, err := mime.ParseMediaType(contentType) |
|
|
|
|
|
if err != nil || (mediaType != "application/json" && !strings.HasSuffix(mediaType, "+json")) { |
|
|
|
|
|
return callback, fmt.Errorf("unsupported content type %q", contentType) |
|
|
|
|
|
} |
|
|
|
|
|
decoder := json.NewDecoder(bytes.NewReader(body)) |
|
|
|
|
|
// Gateways commonly add payment amount, provider transaction ID, and
|
|
|
|
|
|
// signature fields. Only the merchant trade number is needed here; reject
|
|
|
|
|
|
// malformed payloads, but do not reject otherwise valid gateway metadata.
|
|
|
|
|
|
if err := decoder.Decode(&callback); err != nil { |
|
|
|
|
|
return callback, err |
|
|
|
|
|
} |
|
|
|
|
|
if err := decoder.Decode(&struct{}{}); err != io.EOF { |
|
|
|
|
|
if err == nil { |
|
|
|
|
|
return callback, fmt.Errorf("multiple JSON values") |
|
|
|
|
|
} |
|
|
|
|
|
return callback, err |
|
|
|
|
|
} |
|
|
|
|
|
callback.TradeNo = strings.TrimSpace(callback.TradeNo) |
|
|
|
|
|
if callback.TradeNo == "" || len(callback.TradeNo) > 128 { |
|
|
|
|
|
return callback, fmt.Errorf("invalid tradeNo") |
|
|
|
|
|
} |
|
|
|
|
|
return callback, nil |
|
|
|
|
|
} |
|
|
|