141 lines
5.9 KiB
Go
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
|
|
}
|