Initial commit

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-15 13:57:10 +08:00
co-authored by Claude Opus 5
commit 8679200f41
1897 changed files with 257900 additions and 0 deletions
+471
View File
@@ -0,0 +1,471 @@
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
}