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 }