package handler import ( "encoding/json" "errors" "fmt" "gorm.io/gorm/clause" "server/call" "server/common" "server/config" "server/db" "server/modules/web/app" "server/modules/web/values" "server/pb" "server/util" "strings" "time" "github.com/gin-gonic/gin" "github.com/liangdas/mqant/log" "github.com/mitchellh/mapstructure" "gorm.io/gorm" ) func checkWithdrawInfo(uid int, withdrawInfo values.WithdrawInfo) (err error) { log.Debug("check withdraw info, %d %+v", uid, withdrawInfo) if withdrawInfo.AccountNumber == "" && withdrawInfo.UserName == "" && withdrawInfo.Email == "" { err = errors.New("the Bank Card No should not be empty") return } if len(withdrawInfo.AccountNumber) != 11 { err = errors.New("the IFSC Code shall be 11 digital letters") return } // 判断是否是拉黑用户,拉黑用户不让代付 blackData := &common.BlackList{Phone: withdrawInfo.PhoneNumber, PayAccount: withdrawInfo.AccountNumber, Email: withdrawInfo.Email, Name: withdrawInfo.UserName} if call.BlackListAndKick(uid, blackData) { err = common.ErrCodeBlack return } if !call.CheckIFSC(withdrawInfo.IFSCCode) { err = errors.New("the IFSC Code is invalid") return } return nil } func WithdrawInfoSet(c *gin.Context) { a := app.NewApp(c) resp := &values.WithdrawInfoSetResp{} a.Data = resp defer func() { log.Debug("player %v PlayerWithdraw code:%v, msg:%s", a.UID, a.Code, a.Msg) a.Response() }() req := new(values.WithdrawInfoSetReq) if !a.S(req) { return } uid := a.UID log.Debug("uid:%d set withdraw info:%+v", uid, req) ip := a.GetRemoteIP() if req.PhoneNumber == "" { user := common.PlayerDBInfo{Id: uid} err := db.Mysql().Get(&user) if err != nil { log.Error("get user err:%s", err.Error()) a.Code = values.CodeRetry return } req.PhoneNumber = user.Mobile } if err := checkWithdrawInfo(uid, req.WithdrawInfo); err != nil && errors.Is(err, common.ErrCodeBlack) { log.Error("check withdraw info err, %s", err.Error()) a.Code = values.CodeParam return } withdrawInfo := common.WithdrawInfo{ UID: uid, AccountNumber: req.AccountNumber, UserName: req.UserName, IFSCCode: req.IFSCCode, BankName: req.BankName, BankBranchName: req.BankBranchName, PhoneNumber: req.PhoneNumber, Email: req.Email, UpdateIp: ip, UpdatedAt: time.Now().Unix(), } err := db.Mysql().C().Model(&common.WithdrawInfo{}).Clauses(clause.OnConflict{ Columns: []clause.Column{{Name: "uid"}}, DoUpdates: clause.Assignments(map[string]interface{}{ "update_ip": ip, "updated_at": withdrawInfo.UpdatedAt, "account_number": withdrawInfo.AccountNumber, "user_name": withdrawInfo.UserName, "ifsc_code": withdrawInfo.IFSCCode, "bank_name": withdrawInfo.BankName, "bank_branch_name": withdrawInfo.BankBranchName, "phone_number": withdrawInfo.PhoneNumber, "email": withdrawInfo.Email, }), }).Create(&withdrawInfo).Error if err != nil { log.Error("err:%v", err) a.Code = values.CodeRetry return } } func WithdrawInfoGet(c *gin.Context) { a := app.NewApp(c) resp := &values.WithdrawInfoGetResp{} a.Data = resp defer func() { log.Debug("player %v PlayerWithdraw code:%v, msg:%s", a.UID, a.Code, a.Msg) a.Response() }() uid := a.UID var withdrawInfo common.WithdrawInfo err := db.Mysql().C().Model(&common.WithdrawInfo{}).Where("uid = ?", uid).Find(&withdrawInfo).Error if err != nil { log.Error("err:%v", err) a.Code = values.CodeRetry return } if withdrawInfo.ID == 0 { return } resp.BankName = withdrawInfo.BankName resp.BankBranchName = withdrawInfo.BankBranchName resp.PhoneNumber = withdrawInfo.PhoneNumber resp.UserName = withdrawInfo.UserName resp.IFSCCode = withdrawInfo.IFSCCode resp.AccountNumber = withdrawInfo.AccountNumber resp.Email = withdrawInfo.Email } func WithdrawInfo(c *gin.Context) { a := app.NewApp(c) defer func() { a.Response() }() info := new(common.RechargeInfo) info.UID = a.UID if err := db.Mysql().Get(info); err != nil && err != gorm.ErrRecordNotFound { log.Error("err:%v", err) a.Code = values.CodeRetry return } resp := values.WithDrawInfoResp{Tips: call.GetConfigPlatform().WithdrawTips} if util.IsSameDayTimeStamp(time.Now().Unix(), info.LastWithdraw) { resp.WithDrawCount = info.WithdrawCount } else { resp.WithDrawCount = 0 } con := call.GetVipCon(a.UID) if con != nil { resp.TotalWithdrawCount = con.WithdrawCount } resp.Fees = append(resp.Fees, con.Fee) resp.Channels = call.GetConfigWithdrawChannels() list := call.GetConfigWithdrawProduct() for _, v := range list { if v.IfSell == 2 { continue } one := *v if v.Type == common.CurrencyUSDT { for _, j := range resp.Channels { if j.CurrencyType == common.CurrencyUSDT { if j.PayDown <= v.Amount && j.PayUp >= v.Amount { one.Channels = append(one.Channels, j.ChannelID) } } } resp.List = append(resp.List, one) } else if v.Type == common.CurrencyINR { for _, j := range resp.Channels { if j.CurrencyType == common.CurrencyINR { if j.PayDown <= v.Amount && j.PayUp >= v.Amount { one.Channels = append(one.Channels, j.ChannelID) } } } resp.List = append(resp.List, one) } } for i := common.CurrencyTypeZero + 1; i < common.CurrencyAll; i++ { // bet := call.GetPlayerProfileByCurrency(a.UID, i).TotalBet info := call.GetPlayerRechargeInfoByCurrency(a.UID, i) canWithdraw := call.GetUserCurrency(a.UID, i) if canWithdraw < 0 || info.TotalRecharge == 0 { canWithdraw = 0 } resp.Bets = append(resp.Bets, canWithdraw) } resp.NeedBet = call.GetUserNeedBet(a.UID) resp.Cash = int64(call.GetUserCurrencyFloat(a.UID, common.CurrencyType(0), 0)) if resp.NeedBet == 0 { resp.CanWithdraw = resp.Cash } var withdrawInfo common.WithdrawInfo err := db.Mysql().C().Model(&common.WithdrawInfo{}).Where("uid = ?", a.UID).Find(&withdrawInfo).Error if err != nil { log.Error("err:%v", err) a.Code = values.CodeRetry return } if withdrawInfo.ID == 0 { return } resp.WithdrawInfo.BankName = withdrawInfo.BankName resp.WithdrawInfo.BankBranchName = withdrawInfo.BankBranchName resp.WithdrawInfo.PhoneNumber = withdrawInfo.PhoneNumber resp.WithdrawInfo.UserName = withdrawInfo.UserName resp.WithdrawInfo.IFSCCode = withdrawInfo.IFSCCode resp.WithdrawInfo.AccountNumber = withdrawInfo.AccountNumber resp.WithdrawInfo.Email = withdrawInfo.Email a.Data = resp } func WithdrawHistory(c *gin.Context) { a := app.NewApp(c) defer func() { a.Response() }() req := new(values.WithdrawHistoryReq) if !a.S(req) { return } if req.PageSize > 100 { req.PageSize = 100 } // log.Debug("withdraw history req:%+v", *req) ret := []common.WithdrawOrder{} var count int64 var err error count, err = db.Mysql().QueryListW(req.Page, req.PageSize, "create_time desc", &common.WithdrawOrder{UID: a.UID}, &ret, "uid = ? and event = ?", a.UID, common.CurrencyEventWithDraw) if err != nil { log.Error("err:%v", err) a.Code = values.CodeRetry return } for i, v := range ret { if v.Status == common.StatusROrderCreate || v.Status == common.StatusROrderWaitting { ret[i].Status = common.StatusROrderPay } else if v.Status == common.StatusROrderRefuse { ret[i].Status = common.StatusROrderFail } } resp := values.WithdrawHistoryResp{ Count: count, List: ret, } a.Data = resp } func PlayerWithdrawBlock(c *gin.Context) { a := app.NewApp(c) resp := &values.WithdrawResp{} defer func() { a.Data = resp log.Debug("player %v PlayerWithdraw code:%v", a.UID, a.Code) a.Response() }() req := new(values.WithdrawBlockReq) if !a.S(req) { return } if req.CurrencyType != common.CurrencyUSDT { a.Code = values.CodeParam return } uid := a.UID log.Debug("player %v withdrawblock %+v", uid, *req) req.Amount = common.RoundCurrency(req.CurrencyType, req.Amount) // 退出条件限制 if !a.CheckWithdrawCondition(req.Amount, req.CurrencyType) { return } has := call.GetUserCurrency(a.UID, req.CurrencyType) need := req.Amount con := call.GetVipCon(uid) if con != nil && con.Fee > 0 { need += req.Amount * con.Fee / 100 } if has < need { log.Error("err not enough cash:%v,%v", has, need) a.Code = values.CodeWithdrawNotEnough return } // 拉取玩家退出信息 re := call.GetRechargeInfo(a.UID) now := time.Now().Unix() if re.ID == 0 { re.LastWithdraw = now if err := db.Mysql().Create(re); err != nil { a.Code = values.CodeRetry return } } else { u := map[string]interface{}{"last_withdraw": now} if !util.IsSameDayTimeStamp(now, re.LastWithdraw) { u["withdraw_count"] = 0 u["day_withdraw"] = 0 } rows, err := db.Mysql().UpdateRes(&common.RechargeInfo{UID: uid, LastWithdraw: re.LastWithdraw}, u) if err != nil || rows == 0 { a.Code = values.CodeRetry return } } orderID := "USDT" + util.NewOrderID(uid) // 第一步,先扣钱 err := call.MineCurrencyProReal(&common.UpdateCurrency{ CurrencyBalance: &common.CurrencyBalance{ UID: a.UID, Event: common.CurrencyEventWithDraw, Type: common.CurrencyUSDT, Value: -need, Exs1: orderID, ChannelID: a.Channel, }, }).Err if err != nil { log.Error("player %v mines cash err:%v", uid, err) a.Code = values.CodeRetry return } // 直接发起退出 shouldAuto := call.CanAutoWithdraw(uid, re.TotalRecharge, re.TotalWithdraw+re.TotalWithdrawing+need) // 第二步,创建订单 orderStatus := common.StatusROrderCreate if shouldAuto { orderStatus = common.StatusROrderWaitting } order := &common.WithdrawOrder{ UID: uid, OrderID: orderID, APIPayID: "", // ProductID: one.ID, CreateTime: time.Now().Unix(), Amount: req.Amount, WithdrawCash: need, Status: uint8(orderStatus), PaySource: common.PaySourceBlockPay, CurrencyType: req.CurrencyType, Event: int(common.CurrencyEventWithDraw), PayAccount: req.Address, ChannelID: a.Channel, } if err := db.Mysql().Create(order); err != nil { log.Error("player %v create WithdrawBlock order fail err:%v", uid, err) a.Code = values.CodeRetry return } u := map[string]interface{}{} u["withdraw_count"] = gorm.Expr("withdraw_count + ?", 1) db.Mysql().Update(&common.RechargeInfo{UID: uid}, u) call.UpdatePlayerRechargeInfoCurrency(uid, req.CurrencyType, map[string]interface{}{"total_withdrawing": gorm.Expr("total_withdrawing + ?", need)}) // 直接发起退出 // util.Go(func() { // call.SendWithdrawMail(uid, call.MailWithdrawType2) // }) } func PlayerWithdrawCheck(c *gin.Context) { a := app.NewApp(c) defer func() { log.Debug("player %v PlayerWithdrawCheck code:%v,msg:%v", a.UID, a.Code, a.Msg) a.Response() }() req := new(values.WithdrawCheckReq) if !a.S(req) { return } a.CheckWithdrawCondition(req.Amount, common.CurrencyINR) } func PlayerWithdraw(c *gin.Context) { a := app.NewApp(c) resp := &values.WithdrawResp{} a.Data = resp defer func() { log.Debug("player %v PlayerWithdraw code:%v, msg:%s", a.UID, a.Code, a.Msg) a.Response() }() req := new(values.WithdrawReq) if !a.S(req) { return } if req.CurrencyType != common.CurrencyINR { req.CurrencyType = common.CurrencyINR } uid := a.UID log.Debug("player %v withdraw %+v", uid, *req) req.Amount = common.RoundCurrency(req.CurrencyType, req.Amount) // 退出条件限制 if !a.CheckWithdrawCondition(req.Amount, req.CurrencyType) { return } ip := a.GetRemoteIP() payInfo, code := NewWithdraw(req, uid, ip, a.UUID) if code != values.CodeOK { a.Code = code a.Msg = payInfo return } if len(payInfo) > 500 { a.Code = values.CodeParam a.Msg = "Withdrawal information too long." return } user := new(common.PlayerDBInfo) user.Id = uid if err := db.Mysql().Get(user); err != nil { log.Error("err:%v", err) a.Code = values.CodeRetry return } need := req.Amount has := call.GetUserCurrency(a.UID, req.CurrencyType) if need > has { log.Error("err not enough cash:%v,%v", has, need) a.Code = values.CodeWithdrawNotEnough a.Msg = "not enough cash" return } re := call.GetRechargeInfo(a.UID) now := time.Now().Unix() if re.ID == 0 { re.LastWithdraw = now if err := db.Mysql().Create(re); err != nil { a.Code = values.CodeRetry return } } else { u := map[string]interface{}{"last_withdraw": now} if !util.IsSameDayTimeStamp(now, re.LastWithdraw) { u["withdraw_count"] = 0 u["day_withdraw"] = 0 } rows, err := db.Mysql().UpdateRes(&common.RechargeInfo{UID: uid, LastWithdraw: re.LastWithdraw}, u) if err != nil || rows == 0 { a.Code = values.CodeRetry return } } orderID := util.NewOrderID(uid) // 第一步,先扣钱 pro := call.MineCurrencyProReal(&common.UpdateCurrency{ CurrencyBalance: &common.CurrencyBalance{ UID: uid, Value: -need, Event: common.CurrencyEventWithDraw, Type: req.CurrencyType, Exs1: orderID, ChannelID: a.Channel, }, }) if pro.Err != nil { log.Error("err:%v", pro.Err) a.Code = values.CodeRetry return } con := call.GetVipCon(uid) realAmount := need // 实际打款 if con != nil && con.Fee > 0 { realAmount = common.RoundCurrency(req.CurrencyType, (1000-int64(con.Fee))*realAmount/1000) - 600 // 固定税费 } var shouldAuto = false // 在总赠送比配置比例小时才判断个人 // if call.GetTotalRechargePer(realAmount) < config.GetConfig().Web.TotalWithdrawPer { // 直接发起退出 shouldAuto = call.CanAutoWithdraw(uid, re.TotalRecharge, re.TotalWithdraw+re.TotalWithdrawing+realAmount) // } // 第二步,创建订单 withdrawChannel := -1 if req.ChannelID != nil { withdrawChannel = *req.ChannelID } orderStatus := common.StatusROrderCreate if shouldAuto { orderStatus = common.StatusROrderWaitting } order := &common.WithdrawOrder{ UID: uid, OrderID: orderID, APIPayID: "", // ProductID: one.ID, CreateTime: time.Now().Unix(), Amount: realAmount, WithdrawCash: need, Status: uint8(orderStatus), PaySource: common.PaySourceModulePay, Event: int(common.CurrencyEventWithDraw), CurrencyType: req.CurrencyType, PayAccount: payInfo, ChannelID: a.Channel, UPI: withdrawChannel, } if err := db.Mysql().Create(order); err != nil { log.Error("player %v create withdraw order fail err:%v", uid, err) a.Code = values.CodeRetry return } resp.Balance = pro.Balance resp.WithdrawBalance = has - need u := map[string]interface{}{} u["withdrawing_cash"] = gorm.Expr("withdrawing_cash + ?", need) u["withdraw_count"] = gorm.Expr("withdraw_count + ?", 1) u["total_withdrawing"] = gorm.Expr("total_withdrawing + ?", realAmount) u["day_withdraw"] = gorm.Expr("day_withdraw + ?", realAmount) db.Mysql().Update(&common.RechargeInfo{UID: uid}, u) // call.UpdatePlayerRechargeInfoCurrency(uid, req.CurrencyType, map[string]interface{}{"total_withdrawing": gorm.Expr("total_withdrawing + ?", need)}) // 直接发起退出 // util.Go(func() { // call.SendWithdrawMail(uid, call.MailWithdrawType2) // }) } // 返回值在code不为0的时候,代表错误msg func NewWithdraw(req *values.WithdrawReq, uid int, ip string, uuid string) (string, int) { one := common.WithdrawCommon{} err := mapstructure.Decode(req.PayAccount, &one) if err != nil { log.Error("NewWithdrawImp err:%s", err.Error()) return "", values.CodeParam } one.PayType = common.PayTypeBank one.DeviceNo = uuid one.AccountName = strings.TrimSpace(one.AccountName) one.BankCardNo = strings.TrimSpace(one.BankCardNo) one.BankCode = strings.TrimSpace(one.BankCode) one.IP = ip if one.AccountName == "" || one.BankCode == "" { withdrawInfo, err := call.WithdrawInfoGet(uid) if err != nil { log.Error("get withdraw info err, %d %s", uid, err.Error()) return "", values.CodeRetry } if withdrawInfo.ID == 0 { log.Error("get withdraw wrong, %d", uid) return "", values.CodeParam } one.AccountName = withdrawInfo.UserName one.BankCardNo = withdrawInfo.AccountNumber one.BankCode = withdrawInfo.IFSCCode one.Mobile = withdrawInfo.PhoneNumber } if one.PayType == common.PayTypeBank && len(one.BankCode) != 11 { return "The IFSC Code shall be 11 digital letters.", values.CodeParam } if one.PayType == common.PayTypeBank && one.BankCardNo == "" && one.AccountName == "" && one.Email == "" { log.Error("NewWithdrawImp 银行卡支付,银行卡号不能为空 one:%+v", one) return "The Bank Card No should not be empty.", values.CodeParam } // 判断是否是拉黑用户,拉黑用户不让代付 blackData := &common.BlackList{Phone: one.Mobile, PayAccount: one.BankCardNo, Email: one.Email, Name: one.AccountName} if call.BlackListAndKick(uid, blackData) { return "", values.CodeRetry } // 如果是银行那卡代付,验证ifsc if one.PayType == common.PayTypeBank { if !call.CheckIFSC(one.BankCode) { return "The IFSC Code is invalid.", values.CodeParam } } // 查询该银行卡是否已经绑定其他账号 pi := &common.PayInfo{UID: uid} _ = db.Mysql().Get(pi) if one.PayType == common.PayTypeBank { if pi.BankCardNo != one.BankCardNo { sql := fmt.Sprintf("bank_card_no = '%v'", one.BankCardNo) if db.Mysql().Count(&common.PayInfo{}, sql) >= int64(config.GetConfig().Web.MaxBankCardCount) { return "Bank card error", values.CodeBankCardNoLimit } } } else if one.PayType == common.PayTypeUPI { // UPI if pi.BankCode != one.BankCode { sql := fmt.Sprintf("bank_code = '%v'", one.BankCode) if db.Mysql().Count(&common.PayInfo{}, sql) >= int64(config.GetConfig().Web.MaxBankCardCount) { return "Bank card error", values.CodeBankCardNoLimit } } } info := &common.PayInfo{ UID: uid, DeviceNo: one.DeviceNo, PhoneModel: one.Model, OperatorOs: one.OperatorOs, AccountName: one.AccountName, Mobile: one.Mobile, Email: one.Email, BankCardNo: one.BankCardNo, PayType: one.PayType, BankCode: one.BankCode, } if _, err := db.Mysql().Upsert(fmt.Sprintf("uid = %v", uid), info); err != nil { return "", values.CodeParam } ret, err := json.Marshal(one) if err != nil { log.Error("err:%v", err) return "", values.CodeParam } return string(ret), values.CodeOK } func NewWithdrawImp(order *common.WithdrawOrder) *WithdrawImp { base := new(WithdrawImp) // uid := order.UID cid := order.ChannelID base.Channel = call.GetChannelByID(cid) if base.Channel == nil { log.Error("invalid cid:%v", cid) return nil } base.order = order one := new(PayWithdraw) base.SubWithdraw = one one.base = base return base } // WithdrawImp 发起退出对象 type WithdrawImp struct { order *common.WithdrawOrder // tx *gorm.DB SubWithdraw WithdrawInter Channel *common.Channel PayChannel int } func (w *WithdrawImp) BaseWithdraw() error { var err error // uid := w.order.UID defer func() { if err != nil { call.ReturnBackWithdraw(w.order, common.StatusROrderPay, common.StatusROrderFail, w.PayChannel) } }() res := db.Mysql().C().Model(w.order).Where("status = ?", common.StatusROrderCreate).Updates(map[string]interface{}{"status": common.StatusROrderPay}) if res.Error != nil { // w.tx.Rollback() log.Error("sub withdraw err:%v", err) return res.Error } if res.RowsAffected == 0 { // w.tx.Rollback() log.Error("sub withdraw err:%v", err) return errors.New("invalid order") } err = w.SubWithdraw.Withdraw() if err != nil { // w.tx.Rollback() log.Error("sub withdraw err:%v", err) return err } // err = w.tx.Commit().Error // if err != nil { // log.Error("commit BaseWithdraw err:%v", err) // return err // } return nil } func (w *WithdrawImp) AutoWithdraw() error { err := w.SubWithdraw.AutoWithdraw() if err != nil { return err } return nil } type WithdrawInter interface { Withdraw() error AutoWithdraw() error } // PayWithdraw pay模块退出 type PayWithdraw struct { base *WithdrawImp } func (p *PayWithdraw) Withdraw() error { order := p.base.order req := &pb.InnerWithdrawReq{ OrderID: order.OrderID, Amount: order.Amount, UID: uint32(order.UID), Channel: int64(order.UPI), PaySource: uint32(order.PaySource), } if req.PaySource == common.PaySourceModulePay { send := new(common.WithdrawCommon) err := json.Unmarshal([]byte(order.PayAccount), &send) if err != nil { log.Error("withdraw unmarshal err %v", err) return err } req.Phone = send.Mobile req.Name = send.Name req.Email = send.Email req.PayType = int64(send.PayType) req.PayCode = send.BankCode req.CardNo = send.BankCardNo req.Name = send.AccountName } ret, err := call.Withdraw(req) if ret != nil { p.base.PayChannel = int(ret.Channel) } if err != nil { log.Error("err:%v", err) return err } or := &common.WithdrawOrder{ Status: common.StatusROrderPay, APIPayID: ret.APIOrderID, PaySource: common.PaySourceModulePay} or.ID = order.ID res := db.Mysql().C().Model(order).Updates(or) err = res.Error if err != nil { log.Error("update order err:%v", err) return err } if res.RowsAffected == 0 { log.Error("update order fail orderid:%v", order.ID) return errors.New("update order fail") } return nil } func (p *PayWithdraw) AutoWithdraw() error { order := p.base.order req := &pb.InnerWithdrawReq{ OrderID: order.OrderID, Amount: order.Amount, UID: uint32(order.UID), Channel: int64(order.UPI), PaySource: uint32(order.PaySource), } if req.PaySource == common.PaySourceModulePay { send := new(common.WithdrawCommon) err := json.Unmarshal([]byte(order.PayAccount), &send) if err != nil { log.Error("withdraw unmarshal err %v", err) return err } req.Phone = send.Mobile req.Name = send.Name req.Email = send.Email req.PayType = int64(send.PayType) req.BankName = send.BankName req.CardNo = send.BankCode // req.Address = send.Address // req.Number = send.Number } else { req.Address = order.PayAccount } ret, err := call.Withdraw(req) if ret != nil { p.base.PayChannel = int(ret.Channel) } if err != nil { return err } p.base.order.APIPayID = ret.APIOrderID p.base.order.Status = common.StatusROrderPay p.base.order.PayChannel = int(ret.Channel) return nil }