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 }