package srv_recharge import ( "context" "crypto/md5" "encoding/hex" "encoding/json" "fmt" "strconv" "time" "videoweb/pub/common/log" "videoweb/pub/common/srvdef" "videoweb/pub/common/useragent" "videoweb/pub/dataReport" "videoweb/pub/models/cointransmod" "videoweb/pub/models/eventmod" "videoweb/pub/models/rechargepackagemod" "videoweb/pub/models/usermod" "videoweb/pub/models/vipordermod" "videoweb/pub/payment" "videoweb/pub/pubdef" "github.com/gin-gonic/gin" "go.mongodb.org/mongo-driver/bson" ) // BillCallbackParam 嘉华支付回调参数 type BillCallbackParam struct { TradeNo string `json:"transNo" form:"transNo"` Oid string `json:"oid" form:"oid"` RealAmount int64 `json:"payMoney" form:"payMoney"` PaymentAt time.Time `json:"paymentAt" form:"paymentAt"` SuccessAt time.Time `json:"successAt" form:"successAt"` } // OnPayCenterCallback 统一支付回调入口 // 通过 vip_order 表查询订单,再按 OrderType 分支处理。 func OnPayCenterCallback(ctx *gin.Context, param *BillCallbackParam) *srvdef.SrvErr { log.Info("srv_recharge:OnPayCenterCallback", log.Any("tradeNo", param.TradeNo), log.Any("oid", param.Oid), log.Any("realAmount", param.RealAmount), ) if param.TradeNo == "" { return srvdef.New(srvdef.ErrParamError, "tradeNo 不能为空", "") } order, serr := vipordermod.FindOneVipOrder(ctx, bson.M{ vipordermod.FN.VipOrder.TradeNo: param.TradeNo, }) if serr != nil { log.Error("OnPayCenterCallback FindOneVipOrder failed", log.Any("tradeNo", param.TradeNo), log.E(serr)) return serr } if order.Status == pubdef.VipOrderSuccess { log.Info("OnPayCenterCallback already success", log.Any("tradeNo", param.TradeNo)) return nil } if order.Oid != "" && order.Oid != param.Oid { return srvdef.New(srvdef.ErrParamError, fmt.Sprintf("oid 不匹配: %s vs %s", order.Oid, param.Oid), "") } paymentAt := resolvePaymentAt(param) if serr = vipordermod.UpdateOrderSuccess(ctx, param.TradeNo, param.Oid, param.RealAmount, paymentAt); serr != nil { log.Error("OnPayCenterCallback UpdateOrderSuccess failed", log.Any("tradeNo", param.TradeNo), log.E(serr)) return serr } orderSnap := *order realAmount := param.RealAmount oid := param.Oid switch order.OrderType { case vipordermod.OrderTypeCoin: go handleCoinOrderSuccess(orderSnap, oid, realAmount) default: go handleVipOrderSuccess(orderSnap, oid, realAmount) } return nil } func handleVipOrderSuccess(order vipordermod.VipOrder, oid string, realAmount int64) { ctx := context.Background() pkg, err := findVipPackage(ctx, order.CardId) if err != nil { log.Error("handleVipOrderSuccess find package failed", log.Any("userId", order.UserId), log.Any("packageId", order.CardId), log.E(err)) return } expire, err := payment.ActivateVip(ctx, order.UserId, pkg) if err != nil { log.Error("handleVipOrderSuccess ActivateVip failed, retrying", log.Any("userId", order.UserId), log.Any("packageId", order.CardId), log.E(err)) time.Sleep(3 * time.Second) expire, err = payment.ActivateVip(ctx, order.UserId, pkg) if err != nil { log.Error("handleVipOrderSuccess ActivateVip retry failed", log.Any("userId", order.UserId), log.Any("packageId", order.CardId), log.E(err)) } } log.Info("handleVipOrderSuccess done", log.Any("userId", order.UserId), log.Any("expire", expire)) if pkg.GiftCoin > 0 { _ = payment.WriteCoinTransByUser(ctx, payment.WriteCoinTransOpt{ UserId: order.UserId, FlowDir: cointransmod.FlowIn, TranType: cointransmod.TranTypeVipGiftCoin, Amount: pkg.GiftCoin, OrderNo: order.TradeNo, Remark: payment.CoinTransRemark(cointransmod.TranTypeVipGiftCoin, pkg.Name), }) } reportVipOrderPaid(ctx, &order, oid, realAmount, expire) } func handleCoinOrderSuccess(order vipordermod.VipOrder, oid string, realAmount int64) { ctx := context.Background() if err := addUserCoins(ctx, order.UserId, order.Coin); err != nil { log.Error("handleCoinOrderSuccess addUserCoins failed, retrying", log.Any("userId", order.UserId), log.Any("coin", order.Coin), log.E(err)) time.Sleep(3 * time.Second) if err = addUserCoins(ctx, order.UserId, order.Coin); err != nil { log.Error("handleCoinOrderSuccess addUserCoins retry failed", log.Any("userId", order.UserId), log.Any("coin", order.Coin), log.E(err)) } } log.Info("handleCoinOrderSuccess done", log.Any("userId", order.UserId), log.Any("coin", order.Coin)) _ = payment.WriteCoinTransByUser(ctx, payment.WriteCoinTransOpt{ UserId: order.UserId, FlowDir: cointransmod.FlowIn, TranType: cointransmod.TranTypeCoinRecharge, Amount: order.Coin, OrderNo: order.TradeNo, Remark: payment.CoinTransRemark(cointransmod.TranTypeCoinRecharge, ""), }) reportCoinOrderPaid(ctx, &order, oid, realAmount) } func resolvePaymentAt(param *BillCallbackParam) time.Time { if !param.PaymentAt.IsZero() { return param.PaymentAt } if !param.SuccessAt.IsZero() { return param.SuccessAt } return time.Now() } func findVipPackage(ctx context.Context, packageId int64) (*rechargepackagemod.RechargePackage, *srvdef.SrvErr) { pkg, serr := rechargepackagemod.FindOneRechargePackage(ctx, bson.M{ rechargepackagemod.FN.RechargePackage.Id: packageId, rechargepackagemod.FN.RechargePackage.PackageType: rechargepackagemod.PackageTypeVip, }) if serr != nil || pkg == nil { return nil, srvdef.New(srvdef.ErrNoData, "会员套餐不存在", "") } return pkg, nil } func addUserCoins(ctx context.Context, userId int64, coin int64) *srvdef.SrvErr { return usermod.UpdateOneUser(ctx, bson.M{usermod.FN.User.Id: userId}, bson.M{ "$inc": bson.M{usermod.FN.User.Recharge: coin}, "$set": bson.M{usermod.FN.User.UpdatedAt: time.Now()}, }, ) } func reportVipOrderPaid(ctx context.Context, order *vipordermod.VipOrder, oid string, realAmount int64, vipExpire time.Time) { user, _ := usermod.FindOneUser(ctx, bson.M{usermod.FN.User.Id: order.UserId}) districtCode, ip := "", order.IPAddr if user != nil { districtCode = user.DistrictCode } ua := useragent.UA{SysType: order.SysType} eventId := newEventId(pubdef.TrackerAppId(), order.UserId, order.TradeNo, order.CreatedAt.Unix()) cf := dataReport.NewCommonField(eventId, dataReport.EventTypeOrderPaid, districtCode, ua, strconv.FormatInt(user.Id, 10), ip) var expireTS int64 if !vipExpire.IsZero() { expireTS = vipExpire.Unix() } cf.Payload = dataReport.JsonRawMessage(&dataReport.OrderPaidEvent{ OrderID: order.TradeNo, OrderType: "vip_subscription", ProductID: strconv.FormatInt(order.CardId, 10), Amount: dataReport.JsonNumber(realAmount), Currency: "CNY", VIPExpirationTime: dataReport.JsonNumber(expireTS), PayType: order.PayMode, PayChannel: order.PayChannel, TransactionID: oid, CreateTime: dataReport.JsonNumber(order.CreatedAt.Unix()), }) insertEvent(ctx, cf, order.TradeNo) } func reportCoinOrderPaid(ctx context.Context, order *vipordermod.VipOrder, oid string, realAmount int64) { user, _ := usermod.FindOneUser(ctx, bson.M{usermod.FN.User.Id: order.UserId}) districtCode, ip := "", order.IPAddr if user != nil { districtCode = user.DistrictCode } ua := useragent.UA{SysType: order.SysType} eventId := newEventId(pubdef.TrackerAppId(), order.UserId, order.TradeNo, order.CreatedAt.Unix()) cf := dataReport.NewCommonField(eventId, dataReport.EventTypeOrderPaid, districtCode, ua, strconv.FormatInt(user.Id, 10), ip) cf.Payload = dataReport.JsonRawMessage(&dataReport.OrderPaidEvent{ OrderID: order.TradeNo, OrderType: "coin_purchase", ProductID: strconv.FormatInt(order.CoinCfgId, 10), Amount: dataReport.JsonNumber(realAmount), Currency: "CNY", CoinQuantity: dataReport.JsonNumber(order.Coin), PayType: order.PayMode, PayChannel: order.PayChannel, TransactionID: oid, CreateTime: dataReport.JsonNumber(order.CreatedAt.Unix()), }) insertEvent(ctx, cf, order.TradeNo) } func insertEvent(ctx context.Context, cf dataReport.CommonField, tradeNo string) { data, _ := json.Marshal(cf) if err := eventmod.InsertOneEvent(ctx, &eventmod.Event{ EventId: cf.EventID, EventType: cf.Event, Data: string(data), Status: eventmod.Default, CreatedAt: time.Now(), UpdatedAt: time.Now(), }); err != nil { log.Error("insertEvent failed", log.Any("tradeNo", tradeNo), log.E(err)) } } func newEventId(appId string, uid int64, orderId interface{}, createTime int64) string { input := fmt.Sprintf("%v%v%v%v", appId, uid, orderId, createTime) h := md5.New() h.Write([]byte(input)) return hex.EncodeToString(h.Sum(nil)) }