Files
hadja_server/web/api/web_recharge/new_pay_center.go
T
rootandClaude Opus 4.7 79ac89b458 Initial commit
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-11-07 10:47:18 +08:00

141 lines
5.9 KiB
Go

package web_recharge
import (
"context"
"errors"
"fmt"
"net/http"
"strconv"
"time"
"videoweb/pub/common/log"
"videoweb/pub/common/srvdef"
"videoweb/pub/models/vipordermod"
"videoweb/pub/payCenter"
"videoweb/pub/pubdef"
srv_recharge "videoweb/web/service/srv_recharge"
"github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/binding"
"go.mongodb.org/mongo-driver/bson"
)
// newPayCenterCallbackDeps 将查单和入账边界集中,便于用内存替身验证回调不会错误入账。
type newPayCenterCallbackDeps struct {
findOrder func(context.Context, string) (*vipordermod.VipOrder, *srvdef.SrvErr)
queryOrder func(context.Context, string) (payCenter.QueryOrderInfo, error)
applyRecharge func(*gin.Context, *srv_recharge.BillCallbackParam) *srvdef.SrvErr
}
// NewPayCenterRchgCallBack 接收新支付中心签名充值通知。
// @Tags 支付回调
// @Summary 新支付中心充值回调
// @Description 校验商户号、MD5签名、金额及平台订单号,查单取得支付时间后按原流程入账;重复成功通知直接应答成功。
// @Accept json
// @Produce plain
// @Param request body payCenter.RechargeCallbackResp true "支付中心签名回调"
// @Success 200 {string} string "success"
// @Failure 400 {string} string "fail"
// @Router /api/web/third/newpaycenter/recharge/callback [POST]
func NewPayCenterRchgCallBack(ctx *gin.Context) {
deps := newPayCenterCallbackDeps{
findOrder: func(c context.Context, tradeNo string) (*vipordermod.VipOrder, *srvdef.SrvErr) {
return vipordermod.FindOneVipOrder(c, bson.M{vipordermod.FN.VipOrder.TradeNo: tradeNo})
},
queryOrder: func(c context.Context, tradeNo string) (payCenter.QueryOrderInfo, error) {
return (&payCenter.Recharge{Ctx: c, TradeNo: tradeNo}).QueryOrder(c)
},
applyRecharge: srv_recharge.OnPayCenterCallback,
}
handleNewPayCenterRchgCallBack(ctx, deps)
}
func handleNewPayCenterRchgCallBack(ctx *gin.Context, deps newPayCenterCallbackDeps) {
if err := processNewPayCenterRchgCallBack(ctx, deps); err != nil {
log.Error("【/api/web/third/newpaycenter/recharge/callback】充值回调处理失败", log.E(err))
ctx.String(http.StatusBadRequest, "fail")
return
}
ctx.String(http.StatusOK, "success")
}
func processNewPayCenterRchgCallBack(ctx *gin.Context, deps newPayCenterCallbackDeps) error {
var callback payCenter.RechargeCallbackResp
bindErr := ctx.ShouldBindBodyWith(&callback, binding.JSON)
bodyValue, _ := ctx.Get(gin.BodyBytesKey)
rawBody, _ := bodyValue.([]byte)
log.Info("【/api/web/third/newpaycenter/recharge/callback】收到充值回调",
log.Any("tradeNo", callback.TradeNo),
log.Any("requestBody", string(rawBody)),
log.Any("clientIP", ctx.ClientIP()),
)
if bindErr != nil {
return fmt.Errorf("回调参数解析失败: %w", bindErr)
}
request := &payCenter.Recharge{TradeNo: callback.TradeNo}
if request.GetMercID() == "" || request.GetAppSecret() == "" {
return errors.New("新支付中心回调配置缺少 mercID 或 mercSecret")
}
if callback.Code != http.StatusOK || callback.MercID != request.GetMercID() {
return fmt.Errorf("回调状态或商户不匹配,tradeNo=%s", callback.TradeNo)
}
signSource := strconv.Itoa(callback.Code) + callback.MercID + callback.OID + callback.PayMoney + callback.TradeNo + request.GetAppSecret()
if !payCenter.VerifySign(callback.Sign, signSource) {
return fmt.Errorf("回调验签失败,tradeNo=%s", callback.TradeNo)
}
amount, err := payCenter.ParseYuanToFen(callback.PayMoney)
if err != nil || amount <= 0 {
return fmt.Errorf("回调金额格式错误或非正数,tradeNo=%s", callback.TradeNo)
}
order, serr := deps.findOrder(ctx, callback.TradeNo)
if serr != nil {
return fmt.Errorf("查找本地充值订单失败,tradeNo=%s: %w", callback.TradeNo, serr)
}
if order == nil || order.TradeNo != callback.TradeNo {
return fmt.Errorf("本地充值订单不存在,tradeNo=%s", callback.TradeNo)
}
amountMismatch := amount != order.Amount
oidMismatch := order.Oid != "" && callback.OID != order.Oid
if amountMismatch || oidMismatch {
log.Error("【/api/web/third/newpaycenter/recharge/callback】回调金额或支付中心订单号不匹配",
log.Any("tradeNo", callback.TradeNo),
log.Any("userId", order.UserId),
log.Any("callbackPayMoneyYuan", callback.PayMoney),
log.Any("callbackAmountFen", amount),
log.Any("orderAmountFen", order.Amount),
log.Any("callbackOid", callback.OID),
log.Any("orderOid", order.Oid),
log.Any("orderStatus", order.Status),
log.Any("amountMismatch", amountMismatch),
log.Any("oidMismatch", oidMismatch),
)
return fmt.Errorf("回调金额或支付中心订单号不匹配,tradeNo=%s", callback.TradeNo)
}
if order.Status == pubdef.VipOrderSuccess {
log.Info("【/api/web/third/newpaycenter/recharge/callback】重复成功通知", log.Any("tradeNo", callback.TradeNo))
return nil
}
info, err := deps.queryOrder(ctx, callback.TradeNo)
if err != nil {
return fmt.Errorf("支付中心查单失败,tradeNo=%s: %w", callback.TradeNo, err)
}
if (info.MercID != "" && info.MercID != callback.MercID) || (info.TradeNo != "" && info.TradeNo != callback.TradeNo) {
return fmt.Errorf("支付中心查单返回了其他商户或订单,tradeNo=%s", callback.TradeNo)
}
paymentAt, err := time.ParseInLocation(time.RFC3339, info.PayTime, time.Local)
if err != nil {
return fmt.Errorf("支付中心支付时间无效,tradeNo=%s: %w", callback.TradeNo, err)
}
param := &srv_recharge.BillCallbackParam{
TradeNo: callback.TradeNo,
Oid: callback.OID,
RealAmount: amount,
PaymentAt: paymentAt,
}
if serr := deps.applyRecharge(ctx, param); serr != nil {
return fmt.Errorf("本地充值入账失败,tradeNo=%s: %w", callback.TradeNo, serr)
}
log.Info("【/api/web/third/newpaycenter/recharge/callback】充值入账成功", log.Any("tradeNo", callback.TradeNo), log.Any("userId", order.UserId), log.Any("amount", amount))
return nil
}