package withdrawser import ( "91porn-server/app/service/messageser" "91porn-server/common/stderr" "errors" "fmt" "strconv" "time" "91porn-server/app/appg" "91porn-server/common" "91porn-server/common/constant/redisconst" "91porn-server/common/db" "91porn-server/common/log" "91porn-server/common/rchgutil" "91porn-server/common/redis" "91porn-server/models/v/txnactmod" "91porn-server/models/v/txnmod" "91porn-server/models/v/walletmod" "91porn-server/models/v/wdchannmod" "91porn-server/models/v/wdordmod" "91porn-server/models/v/wdtaxmod" "91porn-server/skd/skdg" "91porn-server/web/webg" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/bson/primitive" "go.mongodb.org/mongo-driver/mongo/options" ) // ExchangeAllow 提现通过 func ExchangeAllow(id string, usdtRate float64, operator string) (err error) { // 查询订单信息 withdrawOrder, ec := wdordmod.FindWithdrawOrderByID(id) if ec != nil { return ec } now := time.Now() set := wdordmod.EditSelector{ CheckedAt: &now, ProgressAt: &now, Status: wdordmod.PROCESSING, StatusDesc: wdordmod.PROCESSINGError, Operator: &operator, } content := "提现审核已通过,预计1个工作日内到账。" // 特殊处理usdt提现审核 if usdtRate > 0 { if withdrawOrder.PayType != wdordmod.TypeUsdt || withdrawOrder.Status != wdordmod.DEFAULT { return fmt.Errorf("订单异常") } // 更新订单状态 if ec = wdordmod.UpdateOne(nil, bson.M{"_id": withdrawOrder.ID}, bson.M{ "checkedAt": now, "progressAt": now, "successAt": now, "updatedAt": now, "receivedAt": now, "status": wdordmod.SUCCESS, "statusDesc": wdordmod.SUCCESSStatus, "usdtRate": usdtRate, "operator": operator, }); ec != nil { return ec } go func() { content = "USDT" + content // 处理消息 log.Info(fmt.Sprintf("HandleOfficialMsg pass withdraw start content:%v,uid:%v", content, withdrawOrder.UID)) code := messageser.HandleOfficialMsg(withdrawOrder.UID, content, 2) if code != stderr.Success { log.Error(fmt.Sprintf("HandleOfficialMsg pass withdraw error:%+v:", err), log.Any("uid", withdrawOrder.UID)) } }() return nil } //获取银行卡编码 if withdrawOrder.PayType == wdordmod.TypeBankCard && withdrawOrder.BankCode == "" { withdrawOrder.BankCode = GetBankInfo(withdrawOrder.Act) if withdrawOrder.BankCode == "" { log.Warn("GetBankInfo BankCode == ''", log.Any("uid", withdrawOrder.UID), log.Any("bankAct", withdrawOrder.Act)) return errors.New("获取银行编码异常,请重试") } } w, err := wdordmod.FindOneAndUpate(id, wdordmod.DEFAULT, set) if err != nil { if w.ID.IsZero() || w.ActName == "" { return err } return err } var msg rchgutil.ExchgMsg var tradeNo string if w.ProductType == 0 { tradeNo = rchgutil.EChgIDAssemble(w.ID.Hex()) } if w.ProductType == 1 { tradeNo = rchgutil.EChgWLIDAssemble(w.ID.Hex()) } if w.PayType == wdordmod.TypeBankCard && w.BankCode == "" { typeAct := txnactmod.Bank act, err := txnactmod.FindOne(txnactmod.Query{ Act: &(w.Act), AType: &typeAct, }) if err != nil { return err } w.BankCode = act.BankCode } req := &rchgutil.Exchg{ TransNo: tradeNo, UID: strconv.FormatUint(w.UID, 10), Name: w.Name, PayMoney: w.PayMoney, PayType: w.PayType, ActName: w.ActName, Act: w.Act, UserIP: w.UserIP, DeviceType: w.DeviceType, DevID: w.DevID, CheckedAt: now, ProductType: w.ProductType, BankCode: w.BankCode, } if w.ProductType == 1 { req.Rate = 3 } msg = req.ToPay() set.OID = &(msg.OID) if msg.Code != 200 { if msg.Code == rchgutil.UNKNOWError { set.Status = wdordmod.UNKNOWN set.StatusDesc = wdordmod.UNKNOWNError set.FailureAt = &now if err = wdordmod.Update(nil, id, set); err != nil { return err } return errors.New(set.StatusDesc) } //发生错误,执行退款程序 set.FailureAt = &now set.Status = wdordmod.FAILURE set.StatusDesc = wdordmod.ServerError + "[第三方返回: " + msg.Err + "]" if err = refusedWirhdraw(w, set, "服务器请求平台失败"); err != nil { return err } return nil } common.Go(func() { if err := CacheProcessingOrder(w.UID); err != nil { log.Error("CacheProcessingUser", log.Any("order id", id), log.E(err)) } }) return nil } // ExchangeRefuseByHand 手动对特定状态的提现订单进行退款 func ExchangeRefuseByHand(id string, status int, desc string) error { w, err := wdordmod.FindWithdrawOrderByIDAndStatus(id, status) //防重入 if err != nil { return err } //发生错误,执行退款程序 now := time.Now() set := wdordmod.EditSelector{ Status: wdordmod.REFUSE, StatusDesc: desc, CheckedAt: &now, } if err = refusedWirhdraw(w, set, desc); err != nil { return err } //棋牌usdt兑换拒绝增加usdt可提现额度 if w.ProductType == 1 && w.PayType == wdordmod.TypeUsdt { if _, err = walletmod.IncUsdtAmount(w.UID, w.Money); err != nil { return err } } return nil } // ExchangeRefuse 提现拒绝 func ExchangeRefuse(id string, statusDesc, operator string) error { w, err := wdordmod.FindWithdrawOrderByIDAndStatus(id, wdordmod.DEFAULT) //防重入 if err != nil { return err } //发生错误,执行退款程序 now := time.Now() set := wdordmod.EditSelector{ Status: wdordmod.REFUSE, StatusDesc: statusDesc, Operator: &operator, CheckedAt: &now, } if err = refusedWirhdraw(w, set, statusDesc); err != nil { return err } //棋牌usdt兑换拒绝增加usdt可提现额度 if w.ProductType == 1 && w.PayType == wdordmod.TypeUsdt { if _, err = walletmod.IncUsdtAmount(w.UID, w.Money); err != nil { return err } } go func() { content := "提现审核未通过," + statusDesc + "。" if w.PayType == wdordmod.TypeUsdt { content = "USDT" + content } if w.PayType == wdordmod.TypeAlipay { content = "支付宝" + content } if w.PayType == wdordmod.TypeBankCard { content = "银行卡" + content } // 处理消息 log.Info(fmt.Sprintf("HandleOfficialMsg refused withdraw start content:%v,uid:%v", content, w.UID)) code := messageser.HandleOfficialMsg(w.UID, content, 2) if code != stderr.Success { log.Error(fmt.Sprintf("HandleOfficialMsg refused withdraw error:%+v:", err), log.Any("uid", w.UID)) } }() return nil } // GetAllOrders 分页获取订单, func GetAllOrders(cond primitive.M, opt *options.FindOptions) (total int64, data []*wdordmod.WithdrawOrder, err error) { if _, ok := cond["uid"]; !ok { if b, ok := cond["filterChecked"].(bool); ok && b { uids, err := GetAllProcessingOrders() if err != nil { log.Error("GetAllProcessingUser", log.E(err)) } if len(uids) > 0 { cond["uid"] = bson.M{"$nin": uids} } } } delete(cond, "filterChecked") return wdordmod.FindOrders(cond, opt) } // GetAllProcessingOrders 获取过去15分钟内缓存的所有审核过的订单 func GetAllProcessingOrders() (uids []uint64, err error) { redisc := getRedis() if redisc != nil { now := time.Now() start := fmt.Sprintf("%d", now.Add(-redisconst.WithdrawOrderDuration).Unix()) end := fmt.Sprintf("%d", now.Unix()) uidStrs, err := redisc.ZRangeByScore(redisconst.WithdrawOrderKey, start, end) if err != nil { return uids, err } uids = make([]uint64, 0, len(uidStrs)) for _, str := range uidStrs { uid, err := strconv.ParseUint(str, 0, 64) if err != nil { log.Error("strconv.ParseUint", log.Any("str", str), log.E(err)) continue } uids = append(uids, uid) } common.Go(func() { DelCacheProcessingOrders() }) } return } // DelCacheProcessingOrders 删除过期数据(超过配置时间窗口的数据:-inf~前15分钟) func DelCacheProcessingOrders() { now := time.Now() start := "-inf" end := fmt.Sprintf("(%d", now.Add(-redisconst.WithdrawOrderDuration).Unix()) removed, err := getRedis().ZRemRangeByScore(redisconst.WithdrawOrderKey, start, end) if err != nil { log.Error("redisc.ZRemRangeByScore", log.Any("start", start), log.Any("end", end), log.E(err)) } log.Info("redisc.ZRemRangeByScore removed success", log.Any("start", start), log.Any("end", end), log.Any("number", removed)) } // CacheProcessingOrder 缓存正在审核中的订单 func CacheProcessingOrder(uid uint64) error { redisc := getRedis() if redisc != nil { _, err := redisc.ZAdd(redisconst.WithdrawOrderKey, redis.Member{Score: float64(time.Now().Unix()), Member: uid}) return err } return errors.New("nil redis client") } func getRedis() *redis.Client { if appg.Redis != nil { return appg.Redis } if webg.Redis != nil { return webg.Redis } if skdg.Redis != nil { return skdg.Redis } return nil } // ExchgCallBack 提现回调 func ExchgCallBack(tradeNo string, oid string, money int64, status string, mark string) (err error) { w, err := wdordmod.FindWithdrawOrderByID(tradeNo) if err != nil { return } if w.Status == wdordmod.SUCCESS || w.Status == wdordmod.FAILURE { return } if w.Status != wdordmod.PROCESSING && w.Status != wdordmod.UNKNOWN { err = errors.New("Wtihdraw status error") log.Error(fmt.Sprintf("Wtihdraw status error ,order:%+v:", w)) return } now := time.Now() set := wdordmod.EditSelector{ ReceivedAt: &now, OID: &oid, } switch status { case "paid": //已经打款 //修改订单状态 set.PayMoney = &money set.SuccessAt = &now set.Status = wdordmod.SUCCESS set.StatusDesc = wdordmod.SUCCESSStatus set.SuccessAt = &now if err = wdordmod.Update(nil, tradeNo, set); err != nil { return } case "refused": //拒绝 set.Status = wdordmod.FAILURE set.StatusDesc = mark set.FailureAt = &now if err = refusedWirhdraw(w, set, "提现被平台拒绝"); err != nil { return } } return } func refusedWirhdraw(w wdordmod.WithdrawOrder, set wdordmod.EditSelector, mark string) error { if w.ID.IsZero() { return errors.New("id is invalid") } if w.ProductType == 1 { return errors.New("瓦力游戏已下架") } return webg.VideoDB.Trans(func(t *db.MongoTool) error { switch w.WithdrawType { case wdordmod.GoldWithdraw: wallet, err := walletmod.CreditIncome(t, w.Amount, w.UID) if err != nil { return err } //插入收支流水 tk := txnmod.TransactionLog{ TransNo: w.ID, UID: w.UID, Amount: w.Amount, TranType: txnmod.WithdrawRefund.Key(), TranTypeInt: int64(txnmod.WithdrawRefund), ChannelType: w.PayType, Desc: "用户提现失败,退款" + strconv.FormatInt(w.Amount, 10), DiscDoc: w.DiscDoc, SysType: w.DeviceType, ActualAmount: float64(w.Amount), RealAmount: walletmod.GetRealAmount(wallet), } //插入一条支出流水 if err = txnmod.InsertTransactionLog(t, &tk); err != nil { return err } case wdordmod.AgentWithdraw: if err := walletmod.CreditMoney(t, w.Money, w.UID); err != nil { return err } case wdordmod.DiscWithdraw: // err = discWirhdrawRefund(t, w, mark) // if err != nil { // log.Error("discWirhdrawRefund failed", log.E(err), log.Any("witOrd", w)) // return err // } default: return errors.New("invalid type") } return wdordmod.Update(t, w.ID.Hex(), set) }) } // WithdrawType 获取提现方式 func WithdrawType() (res []*wdchannmod.WithdrawChannelRes, err error) { wt, err := wdtaxmod.GetWithDrawTariff() if err != nil { return } wcs, err := wdchannmod.GetWithdrawChannels() if err != nil { return } res = make([]*wdchannmod.WithdrawChannelRes, len(wcs)) for i, v := range wcs { res[i] = &wdchannmod.WithdrawChannelRes{ WithdrawChannel: *v, WithdrawTariff: wt, } } return } type WithdrawMethod struct { ChannelName string //提现渠道名字 CID string //渠道id PayType string //支付方式 MinMoney int //支持最小支付金额 QpMinMoney int //支持棋牌最小支付金额 MaxMoney int //支持最大支付金额 Weight int //权重 Active bool //是否激活 CashTax int //代理提现比率 CoinTax int //金币提现比率 DiscCashTax int //商区提现比率 } func (w WithdrawMethod) IsValid() bool { return w.ChannelName != "" && w.CID != "" } // GetWithdrawMethod 获取提现方法 func GetWithdrawMethod(paymentMethod string) (WithdrawMethod, error) { active := true mats := []wdchannmod.Matcher{ (&wdchannmod.ActiveMatch{Active: &active}).New(), (&wdchannmod.PayTypeMatch{PayType: &paymentMethod}).New(), } wcList, err := wdchannmod.List(nil, nil, nil, mats...) if err != nil { log.Error("GetWithdrawChannel failed", log.E(err)) return WithdrawMethod{}, err } wc := wcList.WeightedRandom() if wc.ID.IsZero() { return WithdrawMethod{}, nil } var method WithdrawMethod method.ChannelName = wc.ChannelName method.CID = wc.CID method.PayType = wc.PayType method.MinMoney = wc.MinMoney method.QpMinMoney = wc.QpMinMoney method.MaxMoney = wc.MaxMoney method.Weight = wc.Weight method.Active = wc.Active wt, err := wdtaxmod.GetWithDrawTariff() if err != nil { log.Error("GetWithDrawTariff failed", log.E(err)) return WithdrawMethod{}, err } method.CashTax = wt.CashTax method.CoinTax = wt.CoinTax method.DiscCashTax = wt.DiscCashTax return method, nil }