package pay import ( "encoding/json" "errors" "fmt" "server/call" "server/common" "server/config" "server/db" "server/modules/pay/allpay" "server/modules/pay/base" "server/modules/pay/values" "server/pb" "server/util" "sort" "sync" "time" "github.com/gogo/protobuf/proto" "github.com/liangdas/mqant/log" ) var ( withdrawGroup sync.WaitGroup ) func initTimer() { // StartPayCheckTimer() WithdrawTimer() } func StartPayCheckTimer() { values.PayStatus, _ = db.Redis().GetInt(common.RedisKeyPayStatus) if values.PayStatus != values.PayStatusAuto { values.PayStatus = values.PayStatusAuto util.Go(func() { db.Redis().SetData(common.RedisKeyPayStatus, values.PayStatusAuto) }) } t := config.GetConfig().Pay.PayCheckTime if t <= 0 { t = 2 } time.AfterFunc(time.Duration(t)*time.Minute, func() { PayCheck() StartPayCheckTimer() }) } var ( totalAll int64 = 20 // 当前时间段计算的总单数 ) // 每隔一段时间检测渠道成功率,切换渠道 func PayCheck() { log.Debug("pay check start...... ") defer func() { log.Debug("pay check finish...... ") log.Debug("status:%v", values.PayStatus) for _, v := range call.ConfigPayChannels { log.Debug("%+v", *v) } }() log.Debug("limit:%v", config.GetConfig().Pay.CheckLimit) // t := time.Now().Add(-5 * time.Minute).Format("2006-01-02 15:04:05") // 当前成功率没有达到预设值,触发重置权重 // totalAll := getPayCount(t, -1, false) // if totalAll == 0 { // return // } // success := getPayCount(t, -1, true) // if success*100/totalAll >= int64(config.GetConfig().Pay.CheckLimit) { // return // } // 检查是否有渠道的成功率达标 pays := []*common.ConfigPayChannels{} values.PayWeightLock.RLock() for _, v := range call.ConfigPayChannels { if v.PayPer > 0 { pays = append(pays, v) } } values.PayWeightLock.RUnlock() // choosePay := -1 // var bestPer int64 successPer := map[int]int64{} successTotalPer := map[int]int64{} // var totalSuccess int64 for i, v := range pays { per := getSuccessPer(v.ChannelID, i) successPer[v.ChannelID] = per // if per >= int64(config.GetConfig().Pay.CheckLimit) && per > bestPer { // choosePay = v.ChannelID // bestPer = per // } tPer := getSuccessPerTotal(v.ChannelID) successTotalPer[v.ChannelID] = tPer // totalSuccess += tPer } log.Debug("successPer:%v,successTotalPer:%v", successPer, successTotalPer) // 当前有达标的渠道选择,直接切换渠道权重 values.PayWeightLock.Lock() defer values.PayWeightLock.Unlock() // 全部按成功率重置权重 for _, v := range pays { st := successTotalPer[v.ChannelID] if st == 0 { st = int64(config.GetConfig().Pay.BaseSuccess) } per := successPer[v.ChannelID]*500 + st*100 + 100 // if totalSuccess > 0 { // per += successTotalPer[v.ChannelID] * 1000 / totalSuccess // } for _, c := range call.ConfigPayChannels { if c.ChannelID == v.ChannelID { c.PayPer = int(per) break } } cid := v.ChannelID util.Go(func() { values.SetPayChannel(cid, int(per)) }) } // if choosePay > -1 { // // 切换为主渠道模式 // values.PayStatus = values.PayStatusChannel // util.Go(func() { // db.Redis().SetData(common.RedisKeyPayStatus, values.PayStatusChannel) // }) // } else { // values.PayStatus = values.PayStatusAuto // util.Go(func() { // db.Redis().SetData(common.RedisKeyPayStatus, values.PayStatusAuto) // }) // 常驻渠道 // for _, v := range config.GetConfig().Pay.RootChannel { // for _, c := range call.ConfigPayChannels { // if v == c.ChannelID { // if c.PayPer <= 0 { // c.PayPer = 500 // cid := c.ChannelID // util.Go(func() { // values.SetPayChannel(cid, 500) // }) // } // break // } // } // } // } sort.Slice(call.ConfigPayChannels, func(i, j int) bool { return call.ConfigPayChannels[i].PayPer > call.ConfigPayChannels[j].PayPer }) } func getPayCount(start, end int64, channel int, success bool) int64 { sql := fmt.Sprintf("event = %v and create_time >= %d and create_time < %d and upi >= 0", common.CurrencyEventReCharge, start, end) if channel >= 0 { sql += fmt.Sprintf(" and pay_channel = %v", channel) } if success { sql += fmt.Sprintf(" and status = %v", common.StatusROrderPay) } return db.Mysql().Count(&common.RechargeOrder{}, sql) } func getSuccessPer(channel, index int) int64 { now := time.Now() start := now.Add(-6 * time.Minute).Unix() end := now.Add(-1 * time.Minute).Unix() total := getPayCount(start, end, channel, false) success := getPayCount(start, end, channel, true) if index == 0 { // 当前最高权重的渠道 if total < 20 { // 如果最高权重的渠道总单数少于20,降低最低单数判断标准 totalAll = 10 } else { totalAll = 20 } } printTotal := total if total < totalAll { total = totalAll } per := success * 100 / total log.Debug("channel:%v,total:%v,totalAll:%v,success:%v,per:%v", channel, printTotal, totalAll, success, per) return per } func getSuccessPerTotal(channel int) int64 { now := time.Now() start := now.Unix() end := now.AddDate(0, 0, 1).Unix() total := getPayCount(start, end, channel, false) if total == 0 { return 0 } success := getPayCount(start, end, channel, true) if success == 0 && total > 50 { return 1 } per := success * 100 / total log.Debug("total:channel:%v,total:%v,success:%v,per:%v", channel, total, success, per) return per } func WithdrawTimer() { list := []*common.WithdrawOrder{} // startData := time.Now().AddDate(0, 0, -1).Unix() db.Mysql().QueryAll(fmt.Sprintf("status = %d and pay_source = %d", common.StatusROrderWaitting, common.PaySourceModulePay), "", &common.WithdrawOrder{}, &list) l := len(list) if l > 0 { log.Debug("start scan orders:%v", l) withdrawGroup.Add(l) for _, v := range list { // 提交订单 res, err := db.Mysql().UpdateRes(&common.WithdrawOrder{OrderID: v.OrderID, Status: common.StatusROrderWaitting, PaySource: common.PaySourceModulePay}, map[string]interface{}{"status": common.StatusROrderPay}) if err != nil { log.Error("err:%v", err) withdrawGroup.Done() continue } if res == 0 { withdrawGroup.Done() continue } or := v log.Debug("trying:%v", or.OrderID) send := new(common.WithdrawCommon) if err := json.Unmarshal([]byte(or.PayAccount), &send); err != nil { log.Error("withdraw unmarshal err %v", err) call.ReturnBackWithdraw(or, common.StatusROrderPay, common.StatusROrderFail) return } util.Go(func() { TryWithdraw(or) withdrawGroup.Done() }) } } t := config.GetConfig().Pay.WithdrawScanTime if t <= 0 { t = 10 } time.AfterFunc(time.Duration(t)*time.Second, WithdrawTimer) } func TryWithdraw(or *common.WithdrawOrder) { send := new(common.WithdrawCommon) if err := json.Unmarshal([]byte(or.PayAccount), &send); err != nil { log.Error("withdraw unmarshal err %v", err) call.ReturnBackWithdraw(or, common.StatusROrderPay, common.StatusROrderFail) return } req := &pb.InnerWithdrawReq{ OrderID: or.OrderID, Amount: or.Amount, Phone: send.Mobile, Name: send.Name, Email: send.Email, PayType: int64(send.PayType), // Number: send.Number, UID: uint32(or.UID), Channel: int64(or.UPI), Address: send.Address, IP: send.IP, } channel := call.GetConfigWithdrawChannelsByID(int(req.Channel), common.CurrencyINR) if channel == nil || channel.WithdrawPer <= 0 { con := call.GetConfigWithdrawChannelsBest(common.CurrencyINR) if con == nil { return } req.Channel = int64(con.ChannelID) } var ret []byte var err error base := base.NewWithdrawBase(req) allpay.NewSub(base, int(req.Channel)) if base.Sub != nil { ret, err = base.Req() } else { ret, err = nil, errors.New("inner error") } log.Debug("order:%v,err:%v", req.OrderID, err) if err != nil { log.Debug("order:%+v,err:%v", or, err) call.ReturnBackWithdraw(or, common.StatusROrderPay, common.StatusROrderFail, int(req.Channel)) return } data := &pb.InnerWithdrawResp{} proto.Unmarshal(ret, data) log.Debug("withdraw resp:%+v", *data) if data.APIOrderID != "" { db.Mysql().Update(&common.WithdrawOrder{ID: or.ID}, map[string]interface{}{"apipayid": data.APIOrderID}) } }