You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
101 lines
2.9 KiB
101 lines
2.9 KiB
package handler
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"mime"
|
|
"net/http"
|
|
"strings"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
|
|
"laic-backend/common"
|
|
"laic-backend/logger"
|
|
"laic-backend/model"
|
|
"laic-backend/service"
|
|
"laic-backend/vo"
|
|
)
|
|
|
|
const maxAGPayCallbackBodyBytes = 64 * 1024
|
|
|
|
func CreateWechatPayment(c *gin.Context) {
|
|
var req vo.PaymentCreateReq
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
common.FailWithBindError(c, common.ErrParam, err)
|
|
return
|
|
}
|
|
data, e := service.DefaultPaymentService.CreateWechatPayment(common.GetUserId(c), &req)
|
|
if e != nil {
|
|
common.FailWithBusiError(c, e)
|
|
return
|
|
}
|
|
common.OKWithData(c, data)
|
|
}
|
|
|
|
func GetPaymentTransaction(c *gin.Context) {
|
|
id, e := parseID(c)
|
|
if e != nil {
|
|
common.FailWithBusiError(c, e)
|
|
return
|
|
}
|
|
data, e := service.DefaultPaymentService.GetTransaction(common.GetUserId(c), id)
|
|
if e != nil {
|
|
common.FailWithBusiError(c, e)
|
|
return
|
|
}
|
|
common.OKWithData(c, data)
|
|
}
|
|
|
|
func AGPayCallback(c *gin.Context) {
|
|
c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, maxAGPayCallbackBodyBytes)
|
|
body, err := c.GetRawData()
|
|
if err != nil {
|
|
logger.WARN("拒绝 AGPay 支付回调", "stage", "read_body", "error", err)
|
|
c.Status(http.StatusBadRequest)
|
|
return
|
|
}
|
|
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)
|
|
return
|
|
}
|
|
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 {
|
|
c.Status(http.StatusInternalServerError)
|
|
return
|
|
}
|
|
c.Status(http.StatusBadRequest)
|
|
return
|
|
}
|
|
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
|
|
}
|
|
|