You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
307 lines
8.3 KiB
307 lines
8.3 KiB
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() { |
|
var 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 / common.DecimalDigits, |
|
Phone: send.Mobile, |
|
Name: send.AccountName, |
|
CardNo: send.BankCardNo, |
|
PayCode: send.BankCode, |
|
PayType: int64(send.PayType), |
|
UID: uint32(or.UID), |
|
Channel: int64(or.UPI), |
|
Email: send.Email, |
|
BankName: send.BankName, |
|
BankBranchName: send.BankBranchName, |
|
DeviceID: send.DeviceNo, |
|
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) |
|
u := map[string]interface{}{"pay_channel": req.Channel} |
|
if data.APIOrderID != "" { |
|
u["apipayid"] = data.APIOrderID |
|
} |
|
db.Mysql().Update(&common.WithdrawOrder{ID: or.ID}, u) |
|
}
|
|
|