tkcashgame_v4/app/eonline/internal/biz/eonline.go

1547 lines
42 KiB
Go
Raw Normal View History

2025-10-22 10:01:11 +00:00
package biz
import (
"context"
"encoding/json"
"fmt"
"strconv"
"strings"
"time"
"sandc/app/eonline/internal/conf"
"sandc/app/eonline/internal/config"
"sandc/pkg/geo"
"sandc/pkg/middleware/xhttp"
"sandc/pkg/utils"
"github.com/go-kratos/kratos/v2/log"
"github.com/hibiken/asynq"
"github.com/tx7do/kratos-transport/broker"
)
type Eonline struct {
Hello string
}
type EonlineRepo interface {
CreateEonline(context.Context, *Eonline) error
UpdateEonline(context.Context, *Eonline) error
GetUserByUuid(ctx context.Context, uuid string) (*PayoutUser, error)
CreatePayoutUser(ctx context.Context, item *PayoutUser) (*PayoutUser, error)
SearchPayoutRecord(ctx context.Context, opts *SearchPayoutRecordReq) (*ListPayoutRecord, error)
GetPayoutLeftum(ctx context.Context, uuid string) (int, error)
GetPayoutRecordCountByAccount(ctx context.Context, account string) (int, error)
CreatePayoutRecord(ctx context.Context, item *PayoutRecord) (*PayoutRecord, error)
UpdatePayoutRecordNotify(ctx context.Context, recordNo string, payoutId string, status uint8, fail string) error
GetPayoutRecordListByUuid(ctx context.Context, uuid string) ([]*PayoutRecord, error)
// GetPayoutRecordByRecordNo 获取提现记录
GetPayoutRecordByRecordNo(ctx context.Context, recordNo string) (*PayoutRecord, error)
// 根据account、status 获取提现记录
GetPayoutRecordByAccountStatus(ctx context.Context, account string, status, pageIndex, pageSize int) ([]*PayoutRecord, error)
// UpdatePayoutUserLoginDays 更新用户登录天数
UpdatePayoutUserLoginDays(ctx context.Context, uuid string, loginDays uint) error
// 更新 客户端上传的、需要保持的数据
UpdatePayoutUserClientData(ctx context.Context, uuid string, clientData string) error
CreateBonusRecord(ctx context.Context, item *BonusRecord) (*BonusRecord, error)
UpdateBonusCur(ctx context.Context, id uint32, bonusCur uint32) error
}
type EonlineUsecase struct {
repo EonlineRepo
conf *conf.Bootstrap
log *log.Helper
tx Transaction
pscli *PagsmileClient
cache Cache
geocli *geo.GeoClient
}
func NewEonlineUsecase(repo EonlineRepo, conf *conf.Bootstrap, tx Transaction, logger log.Logger, cache Cache) *EonlineUsecase {
return &EonlineUsecase{
repo: repo,
tx: tx,
log: log.NewHelper(logger),
pscli: NewPagsmileClient(&PayoutClient{
AppId: conf.Pagsmile.Payout.AppId,
AppKey: conf.Pagsmile.Payout.AppKey,
ApiUrl: conf.Pagsmile.Payout.ApiUrl,
NotifyUrl: conf.Pagsmile.Payout.NotifyUrl,
}, logger),
conf: conf,
cache: cache,
geocli: geo.NewGeoClient(conf.Server.GeoFile),
}
}
func (uc *EonlineUsecase) Create(ctx context.Context, g *Eonline) error {
return uc.repo.CreateEonline(ctx, g)
}
func (uc *EonlineUsecase) Update(ctx context.Context, g *Eonline) error {
return uc.repo.UpdateEonline(ctx, g)
}
func (uc *EonlineUsecase) KakfaExampleConsumer(_ context.Context, topic string, headers broker.Headers, msg *ExampleStruct) error {
log.Infof("Topic %s, Headers: %+v, Payload: %+v\n", topic, headers, msg)
return nil
}
const EonlineTaskName = "eonline:task"
// AsynqEonlineTaskHandler Asynq handler task
func (uc *EonlineUsecase) AsynqEonlineTaskHandler(ctx context.Context, t *asynq.Task) error {
// do something
// do some else logging
log.Info("AsynqEonlineTaskHandle: ", string(t.Payload()))
return nil
}
// PayInitReq 支付初始化请求
type PayInitReq struct {
DeviceId string
}
// PayInitReply 支付初始化响应
type PayInitReply struct {
UUID string
ClientData string
Days uint32 // 登录天数
Items []*PayoutItem
}
// PayoutItem 支付项目
type PayoutItem struct {
Id uint
Amount float64
Status uint8
}
// PayInit
func (uc *EonlineUsecase) PayInit(ctx context.Context, req *PayoutAddtionalReq) (*PayInitReply, error) {
// 生成唯一用户ID
uuid := GenPayoutUuid(req.DeviceId)
err := uc.initUserLoginDays(ctx, uuid, req)
if err != nil {
return nil, fmt.Errorf("init user login days error: %v", err)
}
// 判断是否提现
payoutValidRes, err, _ := uc.checkPayoutValid(ctx, uuid, req)
if err != nil {
// 如果不允许提现,状态全部更改为不可提现
payoutValidRes.LeftNum[uint(PayoutItemId1)] = -1
payoutValidRes.LeftNum[uint(PayoutItemId2)] = -1
payoutValidRes.LeftNum[uint(PayoutItemId3)] = -1
payoutValidRes.LeftNum[uint(PayoutItemId4)] = -1
}
items := make([]*PayoutItem, 0)
for k, v := range PayoutItemIdAmountes {
if k == 0 {
continue
}
// 生成对应的payoutItem
payoutItem := &PayoutItem{
Id: uint(k),
Amount: v,
Status: uint8(uc.getPayoutItemStatus(ctx, uuid, req.Ts, uint(k), payoutValidRes)),
}
items = append(items, payoutItem)
}
reply := &PayInitReply{
UUID: uuid,
ClientData: payoutValidRes.ClientData,
Days: payoutValidRes.Days,
Items: items,
}
return reply, nil
}
// initUserLoginDays 初始化用户登录天数
func (uc *EonlineUsecase) initUserLoginDays(ctx context.Context, uuid string, req *PayoutAddtionalReq) error {
// 插入用户登录信息
// 判断当前用户是否存在
user, err := uc.repo.GetUserByUuid(ctx, uuid)
if err != nil {
return err
}
// 定义用户登录redis key
keyDay := ""
if uc.conf.Server.Env == "qa" {
ts, _ := strconv.ParseInt(req.Ts, 10, 64)
keyDay = time.Unix(ts, 0).Format("20060102")
}
userLoginRedisKey := GetUserLoginRedisKey(uuid, keyDay)
// 创建用户
if user.Id == 0 {
// 记录登录用户
_, err = uc.repo.CreatePayoutUser(ctx, &PayoutUser{
Uuid: uuid,
DeviceId: req.DeviceId,
Platform: req.Platform,
Version: req.Version,
Country: req.Country,
Ip: req.Ip,
LoginDays: 1,
})
if err != nil {
return err
}
// 是否已记录当前登录行为
err = uc.cache.WriteValue(ctx, userLoginRedisKey, "1", 24*3600)
if err != nil {
return fmt.Errorf("write redis error: %v", err)
}
} else {
// 更新登录天数
currentValue, err := uc.cache.GetValue(ctx, userLoginRedisKey)
if err != nil {
return fmt.Errorf("get redis value error: %v", err)
}
if currentValue == "" {
// 更新登录天数
loginDays := user.LoginDays + 1
err = uc.repo.UpdatePayoutUserLoginDays(ctx, uuid, loginDays)
if err != nil {
return fmt.Errorf("update payout user login days error: %v", err)
}
// 记录当前登录行为
err = uc.cache.WriteValue(ctx, userLoginRedisKey, "1", 24*3600)
if err != nil {
return fmt.Errorf("write redis error: %v", err)
}
}
}
return nil
}
func (uc *EonlineUsecase) updateUserClientData(ctx context.Context, uuid string, clientData string) error {
err := uc.repo.UpdatePayoutUserClientData(ctx, uuid, clientData)
if err != nil {
return fmt.Errorf("update payout user clientData error: %v", err)
}
return nil
}
// getPayoutItemStatus 获取提现项目状态
func (uc *EonlineUsecase) getPayoutItemStatus(ctx context.Context, uuid, ts string, itemId uint, payoutValidRes *PayoutValidRes) PayoutItemStatus {
leftNum, ok := payoutValidRes.LeftNum[itemId]
if !ok || leftNum <= 0 {
return PayoutItemStatusForbidden
}
if _, ok = payoutValidRes.RecordStatusMap[itemId]; ok {
switch payoutValidRes.RecordStatusMap[itemId] {
// 提现成功的状态,返回已提现
case uint8(PayoutStatusPayouted):
return PayoutItemStatusPayouted
// 提现失败的状态,返回可提现
case uint8(PayoutStatusPayoutFailed):
return PayoutItemStatusAvailable
// 提现中的状态,返回提现中
case uint8(PayoutStatusPayouting):
return PayoutItemStatusPayouting
default:
return PayoutItemStatusForbidden
}
}
// 如果没有提现记录,返回可提现
if itemId == 1 {
return PayoutItemStatusAvailable
}
// 如果没有提现记录,返回可提现
// 根据uuid查询用户是否满足第二天的提现条件
// user, err := uc.repo.GetUserByUuid(ctx, uuid)
// if err != nil {
// return PayoutItemStatusForbidden
// }
//
// if user.LoginDays >= 3 && payoutValidRes.LeftNum[itemId] == 1 {
// return PayoutItemStatusAvailable
// } else {
// return PayoutItemStatusUnfinished
// }
keyDay := ""
if uc.conf.Server.Env == "qa" {
ts, _ := strconv.ParseInt(ts, 10, 64)
keyDay = time.Unix(ts, 0).Format("20060102")
}
uuidRedisKey := GetUuidPayoutRedisKey(uuid, keyDay, uint32(itemId))
v, err := uc.cache.GetValue(ctx, uuidRedisKey)
if err != nil {
// uc.log.WithContext(ctx).Errorf("get redis value error: %v", err)
log.Infof("get redis value error: %v", err)
return PayoutItemStatusUnfinished
}
if v == "" && leftNum > 0 {
return PayoutItemStatusAvailable
} else {
return PayoutItemStatusUnfinished
}
}
// PayoutValidRes 提现是否有效响应
type PayoutValidRes struct {
Days uint32
LeftNum map[uint]int
RecordStatusMap map[uint]uint8
ClientData string
}
// true表示可以提现的国家
func canPayoutCountry(country string) bool {
if country == "US" || country == "CA" || country == "IN" || country == "PH" || country == "ID" || country == "VN" || country == "TH" || country == "BR" {
return true
}
return false
}
// 转换国家代码
func convertCountry(country string) string {
if country == "US" { // 美国
return "USA"
} else if country == "CA" { // 加拿大
return "CAN"
} else if country == "IN" { // 印度
return "IND"
} else if country == "PH" { // 菲律宾
return "PHL"
} else if country == "ID" { // 印度尼西亚
return "IDN"
} else if country == "VN" { // 越南
return "VNM"
} else if country == "TH" { // 泰国
return "THA"
} else if country == "BR" { // 巴西
return "BRA"
}
log.Infof("convertCountry error: country[%s]", country)
return country
}
// checkPayoutValid 检查提现是否有效,1+2位数
func (uc *EonlineUsecase) checkPayoutValid(ctx context.Context, uuid string, opts *PayoutAddtionalReq) (*PayoutValidRes, error, int32) {
payoutValidRes := &PayoutValidRes{
Days: 0,
LeftNum: make(map[uint]int),
RecordStatusMap: make(map[uint]uint8),
}
// 判断是否可以提现,国家判断
// if opts.Country != "US" && opts.Country != "CA" && opts.Country != "IN" && opts.Country != "PH" {
// if !canPayoutCountry(opts.Country) {
// return payoutValidRes, fmt.Errorf("country [%s] not support", opts.Country), 101
// }
if opts.ItemId > 0 {
if opts.ItemId <= uint32(len(PayoutItemIdAmountes)) {
// 判断金额是否一致
if int32(opts.Amount*100) != int32(PayoutItemIdAmountes[int(opts.ItemId)]*100) {
return payoutValidRes, fmt.Errorf("amount error"), 102
}
} else {
return payoutValidRes, fmt.Errorf("ItemId error"), 103
}
}
user, err := uc.repo.GetUserByUuid(ctx, uuid)
if err != nil {
return payoutValidRes, err, 1
}
payoutValidRes.Days = uint32(user.LoginDays)
payoutValidRes.ClientData = user.ClientData
records, err := uc.repo.GetPayoutRecordListByUuid(ctx, uuid)
if err != nil {
return payoutValidRes, err, 1
}
var num1, num2, num3, num4 int
var lastest1, lastest2, lastest3, lastest4 *PayoutRecord
var leftAmount float64
for _, record := range records {
if record.ItemId == uint(PayoutItemId4) {
continue
}
switch record.ItemId {
case uint(PayoutItemId1):
if record.Status == uint8(PayoutStatusPayouted) || record.Status == uint8(PayoutStatusPayouting) {
num1++
leftAmount += record.Amount
}
if lastest1 != nil {
if lastest1.CreatedAt.Compare(record.CreatedAt) < 0 {
lastest1 = record
}
} else {
lastest1 = record
}
case uint(PayoutItemId2):
if record.Status == uint8(PayoutStatusPayouted) || record.Status == uint8(PayoutStatusPayouting) {
num2++
leftAmount += record.Amount
}
if lastest2 != nil {
if lastest2.CreatedAt.Compare(record.CreatedAt) < 0 {
lastest2 = record
}
} else {
lastest2 = record
}
case uint(PayoutItemId3):
if record.Status == uint8(PayoutStatusPayouted) || record.Status == uint8(PayoutStatusPayouting) {
num3++
leftAmount += record.Amount
}
if lastest3 != nil {
if lastest3.CreatedAt.Compare(record.CreatedAt) < 0 {
lastest3 = record
}
} else {
lastest3 = record
}
case uint(PayoutItemId4):
if record.Status == uint8(PayoutStatusPayouted) || record.Status == uint8(PayoutStatusPayouting) {
num4++
}
if lastest4 != nil {
if lastest4.CreatedAt.Compare(record.CreatedAt) < 0 {
lastest4 = record
}
} else {
lastest4 = record
}
default:
log.Infof("checkPayoutValid error: uuid[%s] record.ItemId[%d] not dispatch No.1", uuid, record.ItemId)
}
}
ts, _ := strconv.ParseInt(opts.Ts, 10, 64)
keyDay := ""
if uc.conf.Server.Env == "qa" {
keyDay = time.Unix(ts, 0).Format("20060102")
}
var now time.Time
now = time.Unix(ts, 0)
recordStatusMap := make(map[uint]uint8)
if lastest1 != nil {
if isSameDay(now, lastest1.CreatedAt) {
recordStatusMap[lastest1.ItemId] = lastest1.Status
}
}
if lastest2 != nil {
if isSameDay(now, lastest2.CreatedAt) {
recordStatusMap[lastest2.ItemId] = lastest2.Status
}
}
if lastest3 != nil {
if isSameDay(now, lastest3.CreatedAt) {
recordStatusMap[lastest3.ItemId] = lastest3.Status
}
}
if lastest4 != nil {
if isSameDay(now, lastest4.CreatedAt) {
recordStatusMap[lastest4.ItemId] = lastest4.Status
}
}
// true表示今天已提现过
// callCheckTodayPayoutStatus := func(itemId PayoutItemId) bool {
// uuidRedisKey := GetUuidPayoutRedisKey(uuid, keyDay, uint32(itemId))
// value, err := uc.cache.GetValue(ctx, uuidRedisKey)
// if err == nil {
// if value != "" {
// return true
// }
// }
// return false
// }
// 检查已提现次数
if num1 < PayoutRecordLimit1 {
payoutValidRes.LeftNum[uint(PayoutItemId1)] = PayoutRecordLimit1 - num1
} else {
payoutValidRes.LeftNum[uint(PayoutItemId1)] = -1
}
if num2 < PayoutRecordLimit2 {
payoutValidRes.LeftNum[uint(PayoutItemId2)] = PayoutRecordLimit2 - num2
} else {
payoutValidRes.LeftNum[uint(PayoutItemId2)] = -1
}
if num3 < PayoutRecordLimit3 {
payoutValidRes.LeftNum[uint(PayoutItemId3)] = PayoutRecordLimit3 - num3
} else {
payoutValidRes.LeftNum[uint(PayoutItemId3)] = -1
}
if num4 < PayoutRecordLimit4 {
payoutValidRes.LeftNum[uint(PayoutItemId4)] = PayoutRecordLimit4 - num4
} else {
payoutValidRes.LeftNum[uint(PayoutItemId4)] = -1
}
payoutValidRes.RecordStatusMap = recordStatusMap
// 判断金额
leftAmount += opts.Amount
if leftAmount > PayoutAmountLimit {
return payoutValidRes, fmt.Errorf("amount limit"), 104
}
if opts.ItemId > 0 {
// 首次提现必须是0.1
if num1+num2+num3+num4 <= 0 {
if opts.ItemId != uint32(PayoutItemId1) {
return payoutValidRes, fmt.Errorf("ItemId error"), 108
}
}
switch opts.ItemId {
case 1:
if num1 >= PayoutRecordLimit1 {
return payoutValidRes, fmt.Errorf("no payout chance"), 105
}
case 2:
if num2 >= PayoutRecordLimit2 {
return payoutValidRes, fmt.Errorf("no payout chance"), 105
}
case 3:
if num3 >= PayoutRecordLimit3 {
return payoutValidRes, fmt.Errorf("no payout chance"), 105
}
case 4:
if num4 >= PayoutRecordLimit4 {
return payoutValidRes, fmt.Errorf("no payout chance"), 105
}
default:
log.Infof("checkPayoutValid error: uuid[%s] record.ItemId[%d] not dispatch No.2", uuid, opts.ItemId)
return payoutValidRes, fmt.Errorf("no payout chance"), 105
}
} else {
}
// if leftNum1 <= 0 {
// return payoutValidRes, fmt.Errorf("no payout chance"), 105
// }
if opts.ItemId > 0 {
uuidRedisKey := GetUuidPayoutRedisKey(uuid, keyDay, opts.ItemId)
value, err := uc.cache.GetValue(ctx, uuidRedisKey)
if err != nil {
return payoutValidRes, err, 1
}
// 次日才能进行第二次提现(逻辑保留)
if value != "" {
return payoutValidRes, fmt.Errorf("no payout chance, please try tomorrow"), 106
}
}
// 登录第三日才能体现第二次
// if leftNum1 == 1 && user.LoginDays < 3 && opts.ItemId > 0 {
// return payoutValidRes, fmt.Errorf("no payout chance, please try tomorrow")
// }
if len(opts.Account) > 0 {
// 如果是qa环境test账号不限制提现次数
// if uc.conf.Server.Env == "qa" && opts.Account == "pagsmile_test_success@pagsmile.com" {
if uc.conf.Server.Env == "qa" && strings.Contains(opts.Account, "@pagsmile.com") {
return payoutValidRes, nil, 0
}
// 一个账号只能提现2次
count, err := uc.repo.GetPayoutRecordCountByAccount(ctx, opts.Account)
if err != nil {
return payoutValidRes, err, 1
}
if count >= PayoutRecordLimit1+PayoutRecordLimit2+PayoutRecordLimit3+PayoutRecordLimit4 {
return payoutValidRes, fmt.Errorf("account payouted limit"), 107
}
}
return payoutValidRes, nil, 0
}
// 1+2位数
func (uc *EonlineUsecase) checkPayoutValidNew(ctx context.Context, uuid string, opts *PayoutAddtionalReq) (*PayoutValidRes, error, int32) {
x, err := uc.CheckInfo(ctx, &CheckInfoReq{
Uuid: uuid,
Ts: opts.Ts,
})
if err != nil {
return nil, fmt.Errorf("error: %w", err), 1
}
if x.CanCheckPayOut == 1 {
return nil, fmt.Errorf("can't payout"), 150
}
payoutValidRes := &PayoutValidRes{
LeftNum: make(map[uint]int),
RecordStatusMap: make(map[uint]uint8),
}
// 判断是否可以提现,国家判断
// if opts.Country != "US" && opts.Country != "CA" && opts.Country != "IN" && opts.Country != "PH" {
// if !canPayoutCountry(opts.Country) {
// return payoutValidRes, fmt.Errorf("country [%s] not support", opts.Country), 101
// }
// 判断金额是否一致
if int32(opts.Amount*100) != config.PublicCheckCoin {
return payoutValidRes, fmt.Errorf("amount error"), 102
}
records, err := uc.repo.GetPayoutRecordListByUuid(ctx, uuid)
if err != nil {
return payoutValidRes, err, 1
}
recordStatusMap := make(map[uint]uint8)
for _, record := range records {
if _, ok := recordStatusMap[record.ItemId]; !ok {
recordStatusMap[record.ItemId] = record.Status
}
}
payoutValidRes.RecordStatusMap = recordStatusMap
status, ok := recordStatusMap[uint(PayoutItemId4)]
if ok {
if status == uint8(PayoutStatusPayouted) || status == uint8(PayoutStatusPayouting) {
return payoutValidRes, fmt.Errorf("no payout chance"), 105
}
}
// keyDay := ""
// if uc.conf.Server.Env == "qa" {
// ts, _ := strconv.ParseInt(opts.Ts, 10, 64)
// keyDay = time.Unix(ts, 0).Format("20060102")
// }
//
// uuidRedisKey := GetUuidPayoutRedisKey(uuid, keyDay)
// value, err := uc.cache.GetValue(ctx, uuidRedisKey)
// if err != nil {
// return payoutValidRes, err
// }
//
// // 次日才能进行第二次体现(逻辑保留)
// if value != "" && opts.ItemId > 0 {
// return payoutValidRes, fmt.Errorf("no payout chance, please try tomorrow")
// }
return payoutValidRes, nil, 0
}
// PayoutAddtionalReq 支付附加请求
type PayoutAddtionalReq struct {
Platform string
DeviceId string
Version string
EcpmLvl uint32
ItemId uint32
Ip string
Amount float64
Country string
Account string
Ts string
Email string
}
// Payout 支付请求
func (uc *EonlineUsecase) Payout(ctx context.Context, uuid string, opts *PayoutAddtionalReq, req *PayoutReq) (*PayoutReplyData, error, int32) {
// 定义一个uuid的redis key
keyDay := ""
if uc.conf.Server.Env == "qa" {
ts, _ := strconv.ParseInt(opts.Ts, 10, 64)
keyDay = time.Unix(ts, 0).Format("20060102")
}
uuidRedisKey := GetUuidPayoutRedisKey(uuid, keyDay, opts.ItemId)
// 定义一个uuid的redis lock key, 用于锁定, 防止并发
uuidRedisLockKey := GetUuidLockRedisKey(uuid, keyDay)
lock, _ := uc.cache.RedisLock(ctx, uuidRedisLockKey, 1, 20)
if !lock {
return nil, fmt.Errorf("lock error"), 1
}
defer func() {
uc.cache.RedisUnLock(ctx, uuidRedisLockKey)
}()
country, currentIp := uc.GetCountryCodeByIp(ctx, opts.Ip)
opts.Country = country
opts.Ip = currentIp
// 验证提现是否有效
var err error
var errCode int32
if opts.ItemId == uint32(PayoutItemId4) {
_, err, errCode = uc.checkPayoutValidNew(ctx, uuid, opts)
} else {
_, err, errCode = uc.checkPayoutValid(ctx, uuid, opts)
}
if err != nil {
switch errCode {
case 101:
errCode = 5
case 102:
errCode = 6
case 103:
errCode = 7
case 104:
errCode = 8
case 105:
errCode = 9
case 106:
errCode = 10
case 107:
errCode = 11
case 150:
errCode = 12
default:
log.Infof("checkPayoutValid error: errCode[%d]", errCode)
errCode = 1
}
return nil, err, errCode
}
// 判断当前用户是否存在
user, err := uc.repo.GetUserByUuid(ctx, uuid)
if err != nil {
return nil, err, 1
}
// 支付的时候需要替换掉country code
// if country == "US" { // 美国
// req.Country = "USA"
// } else if country == "CA" { // 加拿大
// req.Country = "CAN"
// } else if country == "IN" { // 印度
// req.Country = "IND"
// } else if country == "PH" { // 菲律宾
// req.Country = "PHL"
// }
req.Country = convertCountry(country)
// 创建提现用户
if user.Id == 0 {
// 记录提现的用户
_, err = uc.repo.CreatePayoutUser(ctx, &PayoutUser{
Uuid: uuid,
DeviceId: opts.DeviceId,
Platform: opts.Platform,
Version: opts.Version,
Country: country,
Ip: opts.Ip,
LoginDays: 1,
})
if err != nil {
return nil, err, 1
}
}
// 记录提现及操作
var payoutReplyData PayoutReplyData
var payoutStatus uint8 // 记录提现状态
err = uc.tx.InTx(ctx, func(ctx context.Context) error {
// 创建提现记录
// 以下参数固定
req.AccountType = "EMAIL"
req.Method = "WALLET"
req.Channel = "PayPal"
req.FeeBear = "merchant" // beneficiary , merchant
// 如果金额大于等于0.3 收款方承担手续费
if opts.Amount > 0.3 {
req.FeeBear = "beneficiary"
}
req.SourceCurrency = "USD"
req.ArrivalCurrency = "USD"
reqMd5 := utils.MD5Any(req)
customCode := fmt.Sprintf("PGS_%s", utils.MD5Any(fmt.Sprintf("%s-%d", reqMd5, time.Now().Unix())))
// 类似唯一订单号
req.CustomCode = customCode
record, err := uc.repo.CreatePayoutRecord(ctx, &PayoutRecord{
Uuid: uuid,
Channel: req.Channel,
ItemId: uint(opts.ItemId),
Amount: opts.Amount,
Account: req.Account,
Currency: req.SourceCurrency,
Status: uint8(PayoutStatusPayouting),
RecordNo: req.CustomCode,
Version: opts.Version,
Email: opts.Email,
})
if err != nil {
return fmt.Errorf("create payout record error: %v", err)
}
// 第三方提现
x, err := uc.pscli.out.Payout(ctx, req)
if err != nil {
return err
}
payoutReplyData = *x
payoutStatus = uint8(PayoutStatusPayouting)
// 等待回调更新状态
err = uc.repo.UpdatePayoutRecordNotify(ctx, payoutReplyData.CustomCode, payoutReplyData.Id, payoutStatus, "")
if err != nil {
return fmt.Errorf("update payout record_%d status error: %v", record.Id, err)
}
return nil
})
if err != nil {
return nil, err, 1
}
// 记录此次提现,用于判断是否可以继续提现
uc.cache.WriteValue(ctx, uuidRedisKey, "1", 24*3600)
return &payoutReplyData, nil, 0
}
// PayoutBrazil 支付请求
func (uc *EonlineUsecase) PayoutBrazil(ctx context.Context, uuid string, opts *PayoutAddtionalReq, req *PayoutReq) (*PayoutReplyData, error, int32) {
// 定义一个uuid的redis key
keyDay := ""
if uc.conf.Server.Env == "qa" {
ts, _ := strconv.ParseInt(opts.Ts, 10, 64)
keyDay = time.Unix(ts, 0).Format("20060102")
}
uuidRedisKey := GetUuidPayoutRedisKey(uuid, keyDay, opts.ItemId)
// 定义一个uuid的redis lock key, 用于锁定, 防止并发
uuidRedisLockKey := GetUuidLockRedisKey(uuid, keyDay)
lock, _ := uc.cache.RedisLock(ctx, uuidRedisLockKey, 1, 20)
if !lock {
return nil, fmt.Errorf("lock error"), 1
}
defer func() {
uc.cache.RedisUnLock(ctx, uuidRedisLockKey)
}()
country, currentIp := uc.GetCountryCodeByIp(ctx, opts.Ip)
if country != "BR" {
// return nil, fmt.Errorf("Not Brazil")
country = "BR"
}
opts.Country = country
opts.Ip = currentIp
// 验证提现是否有效
var err error
var errCode int32
if opts.ItemId == uint32(PayoutItemId4) {
_, err, errCode = uc.checkPayoutValidNew(ctx, uuid, opts)
} else {
_, err, errCode = uc.checkPayoutValid(ctx, uuid, opts)
}
if err != nil {
switch errCode {
case 101:
errCode = 5
case 102:
errCode = 6
case 103:
errCode = 7
case 104:
errCode = 8
case 105:
errCode = 9
case 106:
errCode = 10
case 107:
errCode = 11
case 108:
errCode = 25
case 150:
errCode = 12
default:
log.Infof("checkPayoutValid error: errCode[%d]", errCode)
errCode = 1
}
return nil, err, errCode
}
// 判断当前用户是否存在
user, err := uc.repo.GetUserByUuid(ctx, uuid)
if err != nil {
return nil, err, 1
}
// 支付的时候需要替换掉country code
req.Country = convertCountry(country)
// 创建提现用户
if user.Id == 0 {
// 记录提现的用户
_, err = uc.repo.CreatePayoutUser(ctx, &PayoutUser{
Uuid: uuid,
DeviceId: opts.DeviceId,
Platform: opts.Platform,
Version: opts.Version,
Country: country,
Ip: opts.Ip,
LoginDays: 1,
})
if err != nil {
return nil, err, 1
}
}
if len(req.ClientData) > 0 {
uc.updateUserClientData(ctx, uuid, req.ClientData)
}
// 记录提现及操作
var payoutReplyData PayoutReplyData
var payoutStatus uint8 // 记录提现状态
err = uc.tx.InTx(ctx, func(ctx context.Context) error {
// 创建提现记录
// 以下参数固定
req.Method = "PIX"
// req.Channel = "PayPal"
req.FeeBear = "merchant" // beneficiary , merchant
// 如果金额大于等于0.3 收款方承担手续费
if opts.Amount > 0.3 {
req.FeeBear = "beneficiary"
}
req.SourceCurrency = "USD"
req.ArrivalCurrency = "BRL"
reqMd5 := utils.MD5Any(req)
customCode := fmt.Sprintf("PGS_%s", utils.MD5Any(fmt.Sprintf("%s-%d", reqMd5, time.Now().Unix())))
// 类似唯一订单号
req.CustomCode = customCode
record, err := uc.repo.CreatePayoutRecord(ctx, &PayoutRecord{
Uuid: uuid,
Channel: req.Channel,
ItemId: uint(opts.ItemId),
Amount: opts.Amount,
Account: req.Account,
Currency: req.SourceCurrency,
Status: uint8(PayoutStatusPayouting),
RecordNo: req.CustomCode,
Version: opts.Version,
})
if err != nil {
return fmt.Errorf("create payout record error: %v", err)
}
// 第三方提现
x, err := uc.pscli.out.Payout(ctx, req)
if err != nil {
return err
}
payoutReplyData = *x
payoutStatus = uint8(PayoutStatusPayouting)
// 等待回调更新状态
err = uc.repo.UpdatePayoutRecordNotify(ctx, payoutReplyData.CustomCode, payoutReplyData.Id, payoutStatus, "")
if err != nil {
return fmt.Errorf("update payout record_%d status error: %v", record.Id, err)
}
return nil
})
if err != nil {
return nil, err, 1
}
// 记录此次提现,用于判断是否可以继续提现
uc.cache.WriteValue(ctx, uuidRedisKey, "1", 24*3600)
orderId := GetPayoutOrderIdRedisKey(payoutReplyData.Id)
uc.cache.WriteValue(ctx, orderId, uuidRedisKey, 15*3600)
return &payoutReplyData, nil, 0
}
// PayoutNotify 支付回调
func (uc *EonlineUsecase) PayoutNotify(ctx context.Context, req *PayoutNotifyReq) error {
value, _ := json.Marshal(req)
fmt.Printf("[%s] payout notify: %s \n", time.Now().Format("2006-01-02 15:04:05"), string(value))
log.Infof("[%s] payout notify: %s \n", time.Now().Format("2006-01-02 15:04:05"), string(value))
err := uc.pscli.out.PayoutNotify(ctx, req)
if err != nil {
return err
}
// 测试用代码
// callTest := func() {
// uuidRedisKey := "pt_6b59480226f21f10a8939807dded2679_20250518_1"
// orderTs := "TS202505080744025sxBs5nA47qPB"
// err = uc.cache.WriteValue(ctx, uuidRedisKey, "1", 24*3600)
// if err != nil {
// err = nil
// }
// orderId := GetPayoutOrderIdRedisKey(orderTs)
// err = uc.cache.WriteValue(ctx, orderId, uuidRedisKey, 15*3600)
// if err != nil {
// err = nil
// }
//
// // 查找删除
// ctx2 := ctx
// orderId = GetPayoutOrderIdRedisKey(orderTs)
// value2, err2 := uc.cache.GetValue(ctx2, orderId)
// if err2 == nil {
// if value2 != "" {
// err2 = uc.cache.DelValue(ctx2, value2)
// if err2 == nil {
// log.Infof("del GetUuidPayoutRedisKey: %s", value2)
//
// err2 = uc.cache.DelValue(ctx2, orderId)
// if err2 == nil {
// log.Infof("del redis key orderId: %s", orderId)
// } else {
// log.Infof("del redis key orderId: %s err[%v]", orderId, err2)
// }
// } else {
// log.Infof("del GetUuidPayoutRedisKey error: %s err[%v]", value2, err2)
// }
// } else {
// log.Infof("GetPayoutOrderIdRedisKey error: key[%s] value2 is null", orderId)
// }
// } else {
// log.Infof("GetPayoutOrderIdRedisKey error: key[%s] err[%v]", orderId, err2)
// }
// }
// 判断回调是否有效
_, err = uc.repo.GetPayoutRecordByRecordNo(ctx, req.CustomCode)
if err != nil {
return fmt.Errorf("get payout record error: %v", err)
}
// callTest()
reportData := GetReportData(req.PayoutId)
// 更新提现记录状态
var payoutStatus uint8
switch req.Status {
case "PAID":
payoutStatus = uint8(PayoutStatusPayouted)
if reportData != nil && reportData.Adjust != nil && reportData.ShuShu != nil {
adjust := GetAdjustData2(reportData.Adjust, "")
shuShu := GetShuShuData2(reportData.ShuShu, "")
SendReport(ctx, adjust, shuShu)
}
case "REJECTED":
payoutStatus = uint8(PayoutStatusPayoutFailed)
ctx2 := ctx
// ctx2 := context.Background()
orderId := GetPayoutOrderIdRedisKey(req.PayoutId)
value2, err2 := uc.cache.GetValue(ctx2, orderId)
if err2 == nil {
if value2 != "" {
err2 = uc.cache.DelValue(ctx2, value2)
if err2 == nil {
log.Infof("del GetUuidPayoutRedisKey: %s", value2)
// err2 = uc.cache.DelValue(ctx2, orderId)
// if err2 == nil {
// log.Infof("del redis key orderId: %s", orderId)
// } else {
// log.Infof("del redis key orderId: %s err[%v]", orderId, err2)
// }
} else {
log.Infof("del GetUuidPayoutRedisKey error: %s err[%v]", value2, err2)
}
} else {
log.Infof("GetPayoutOrderIdRedisKey error: key[%s] value2 is null", orderId)
}
} else {
log.Infof("GetPayoutOrderIdRedisKey error: key[%s] err[%v]", orderId, err2)
}
if reportData != nil && reportData.Adjust != nil && reportData.ShuShu != nil {
adjust := GetAdjustData2(reportData.Adjust, req.Msg)
shuShu := GetShuShuData2(reportData.ShuShu, req.Msg)
SendReport(ctx, adjust, shuShu)
}
default:
payoutStatus = uint8(PayoutStatusPayouting)
}
err = uc.repo.UpdatePayoutRecordNotify(ctx, req.CustomCode, req.PayoutId, payoutStatus, req.Msg)
if err != nil {
return fmt.Errorf("update payout record status error: %v", err)
}
// //存入redis给请求提现的用户
// payoutNotifyRedisKey := GetPayoutNotifyRedisKey(req.PayoutId)
// err = uc.cache.WriteValue(ctx, payoutNotifyRedisKey, string(value), 24*3600)
// if err != nil {
// return fmt.Errorf("write redis error: %v", err)
// }
return nil
}
// PayoutNotify 支付回调
func (uc *EonlineUsecase) PayoutNotify2(ctx context.Context, req *PayoutNotifyReq) error {
value, _ := json.Marshal(req)
fmt.Printf("[%s] payout notify: %s \n", time.Now().Format("2006-01-02 15:04:05"), string(value))
log.Infof("[%s] payout notify: %s \n", time.Now().Format("2006-01-02 15:04:05"), string(value))
// err := uc.pscli.out.PayoutNotify(ctx, req)
// if err != nil {
// return err
// }
// 判断回调是否有效
_, err := uc.repo.GetPayoutRecordByRecordNo(ctx, req.CustomCode)
if err != nil {
return fmt.Errorf("get payout record error: %v", err)
}
// callTest()
reportData := GetReportData(req.PayoutId)
// 更新提现记录状态
var payoutStatus uint8
switch req.Status {
case "PAID":
payoutStatus = uint8(PayoutStatusPayouted)
if reportData != nil && reportData.Adjust != nil && reportData.ShuShu != nil {
adjust := GetAdjustData2(reportData.Adjust, "")
shuShu := GetShuShuData2(reportData.ShuShu, "")
SendReport(ctx, adjust, shuShu)
}
case "REJECTED":
payoutStatus = uint8(PayoutStatusPayoutFailed)
ctx2 := ctx
// ctx2 := context.Background()
orderId := GetPayoutOrderIdRedisKey(req.PayoutId)
value2, err2 := uc.cache.GetValue(ctx2, orderId)
if err2 == nil {
if value2 != "" {
err2 = uc.cache.DelValue(ctx2, value2)
if err2 == nil {
log.Infof("del GetUuidPayoutRedisKey: %s", value2)
// err2 = uc.cache.DelValue(ctx2, orderId)
// if err2 == nil {
// log.Infof("del redis key orderId: %s", orderId)
// } else {
// log.Infof("del redis key orderId: %s err[%v]", orderId, err2)
// }
} else {
log.Infof("del GetUuidPayoutRedisKey error: %s err[%v]", value2, err2)
}
} else {
log.Infof("GetPayoutOrderIdRedisKey error: key[%s] value2 is null", orderId)
}
} else {
log.Infof("GetPayoutOrderIdRedisKey error: key[%s] err[%v]", orderId, err2)
}
if reportData != nil && reportData.Adjust != nil && reportData.ShuShu != nil {
adjust := GetAdjustData2(reportData.Adjust, req.Msg)
shuShu := GetShuShuData2(reportData.ShuShu, req.Msg)
SendReport(ctx, adjust, shuShu)
}
default:
payoutStatus = uint8(PayoutStatusPayouting)
}
err = uc.repo.UpdatePayoutRecordNotify(ctx, req.CustomCode, req.PayoutId, payoutStatus, req.Msg)
if err != nil {
return fmt.Errorf("update payout record status error: %v", err)
}
return nil
}
// ParseIp 解析IP
func (uc *EonlineUsecase) ParseIp(ctx context.Context, ip string) (*geo.GeoInfo, error) {
return uc.geocli.Parse(ip)
}
// 通过IP获取对应country code 和 country ip, 如果IP为空从服务端获取
func (uc *EonlineUsecase) GetCountryCodeByIp(ctx context.Context, ip string) (string, string) {
var country string
var currentIp string
currentIp = ip
// 获取国家
// 强制通过服务端获取IP不支持客户端传递, QA环境仍然支持客户端传递
if ip == "" || uc.conf.Server.Env != "qa" {
httpCtx := xhttp.RequestFromContext(ctx)
if httpCtx != nil {
// 获取当前的IP地址
currentIp = utils.GetClientPublicIP(httpCtx.Request())
} else {
uc.log.WithContext(ctx).Errorf("get http context error")
return "", ""
}
}
// 解析IP地址
geonInfo, err := uc.geocli.Parse(currentIp)
if err != nil {
uc.log.WithContext(ctx).Errorf("parse ip(%s) error: %v", currentIp, err)
return currentIp, ""
}
country = geonInfo.Country.ISOCode
fmt.Println("parse ip success: ", currentIp, country)
return country, currentIp
}
// PayoutCheck 检查提现状态
func (uc *EonlineUsecase) PayoutCheck(ctx context.Context, recordNo string) (uint8, error) {
record, err := uc.repo.GetPayoutRecordByRecordNo(ctx, recordNo)
if err != nil {
return 0, err
}
return record.Status, nil
}
// PayoutCheck 检查提现状态
func (uc *EonlineUsecase) GetPayoutRecordByAccount(ctx context.Context, account string, status, pageIndex, pageSize int) ([]*PayoutRecord, error) {
record, err := uc.repo.GetPayoutRecordByAccountStatus(ctx, account, int(status), int(pageIndex), int(pageSize))
if err != nil {
return nil, err
}
return record, nil
}
type SubmitCheckReq struct {
Account string
Uuid string
}
type SubmitCheckReply struct {
Result int32 // 0成功1失败2以前提交过还没审核结果3以前提交过并审核通过以前提交过但审核失败的可以继续提交
}
const (
StringNull = "" // 审核无结果
CheckResultFail = "0" // 审核没通过
CheckResultSuccess = "1" // 审核通过
)
// 提交审核0成功1失败2以前提交过还没审核结果3以前提交过并审核通过以前提交过但审核失败的可以继续提交
func (uc *EonlineUsecase) SubmitCheck(ctx context.Context, req *SubmitCheckReq) (*SubmitCheckReply, error) {
reply := &SubmitCheckReply{}
// 查询是否已经提交过
v, err := uc.getCheckSubmit(ctx, req.Account)
if err != nil {
reply.Result = 1
return reply, err
}
if v == 1 {
// 提交过,检查审核结果
v2, err := uc.getCheckResult(ctx, req.Account)
if err != nil {
reply.Result = 1
return reply, err
}
// 还没审核结果
if v2 == 0 {
reply.Result = 2
return reply, nil
}
// 审核通过的
if v2 == 2 {
reply.Result = 3
return reply, nil
}
}
// 检查提交人数
// v3, err := uc.getCheckSubmitNum(ctx)
// _ = v3
// 提交计数
err = uc.increaseCheckSubmitNum(ctx)
if err != nil {
reply.Result = 1
return reply, err
}
// 建立[uuid, paypal]
err = uc.setUuid2Paypal(ctx, req.Account, req.Uuid)
if err != nil {
reply.Result = 1
return reply, err
}
// 不加redis锁并发写入提交记录也没什么影响
// 存入redis
key := GetCheckSubmitRedisKey(req.Account)
uc.cache.WriteValue(ctx, key, req.Uuid, 0)
return reply, nil
}
type CheckInfoReq struct {
Uuid string
Ts string
}
type CheckInfoReply struct {
CanCheckSubmit uint32
CheckSubmit int32
CheckResult int32
CheckPayout int32
CanCheckPayOut int32
CheckResultFailedDesc string
}
func (uc *EonlineUsecase) CheckInfo(ctx context.Context, req *CheckInfoReq) (*CheckInfoReply, error) {
reply := &CheckInfoReply{}
acc, err := uc.getUuid2Paypal(ctx, req.Uuid)
if err != nil {
return nil, err
}
if acc == StringNull {
reply.CheckSubmit = 0
reply.CheckResult = 0
reply.CheckPayout = 0
} else {
// 提交身份文件验证情况
v, err := uc.getCheckSubmit(ctx, acc)
if err != nil {
return nil, err
}
if v == 0 {
reply.CheckSubmit = 0
} else {
reply.CheckSubmit = 1
}
// 身份文件审核有反馈的情况
v2, err := uc.getCheckResult(ctx, acc)
if err != nil {
return nil, err
}
if v2 != 0 {
if v2 == 2 {
reply.CheckResult = 2
} else {
reply.CheckResult = 1
reply.CheckResultFailedDesc, err = uc.getCheckResultFailedDesc(ctx, acc)
if err != nil {
return nil, err
}
}
}
// 提交身份文件奖励5美元领取的情况0没有提现记录1提现中2提现成功3提现失败
records, err := uc.repo.GetPayoutRecordListByUuid(ctx, req.Uuid)
if err != nil {
return nil, err
}
for _, record := range records {
if record.ItemId == uint(PayoutItemId4) {
switch record.Status {
case uint8(PayoutStatusPayouting):
reply.CheckPayout = 1
case uint8(PayoutStatusPayouted):
reply.CheckPayout = 2
case uint8(PayoutStatusPayoutFailed):
reply.CheckPayout = 3
}
break
}
}
}
// 获得当前提交审核计数
v3, err := uc.getCheckSubmitNum(ctx)
if err != nil {
return nil, err
}
reply.CanCheckSubmit = v3
log.Infof("CheckInfo: getCheckSubmitNum[%d]", v3)
if reply.CanCheckSubmit < config.PublicCheckNum {
reply.CanCheckSubmit = 1
} else {
reply.CanCheckSubmit = 0
}
var resultTixian int
reply.CanCheckPayOut = 1 // 身份文件审核过的提现奖励5美元 能否 提现0能提现1条件不满足不能提现2当天已经提现过不能再次提现
if reply.CheckSubmit == 1 && reply.CheckResult == 2 && reply.CheckPayout == 0 {
resultTixian, err = uc.GetCashStatus(ctx, req.Uuid, req.Ts, uint32(PayoutItemId4))
if err != nil {
return nil, fmt.Errorf("error: %w", err)
}
if resultTixian == 1 {
reply.CanCheckPayOut = 2
} else {
reply.CanCheckPayOut = 0
}
}
return reply, nil
}
// 获取已经提交身份审核计数
func (uc *EonlineUsecase) getCheckSubmitNum(ctx context.Context) (uint32, error) {
key := GetCheckSubmitNumRedisKey()
v, err := uc.cache.GetValue(ctx, key)
if err != nil {
return 0, err
}
if v == StringNull {
return 0, nil
} else {
x, err := strconv.Atoi(v)
if err != nil {
return 0, err
}
return uint32(x), nil
}
}
// 提交身份审核计数 自增
func (uc *EonlineUsecase) increaseCheckSubmitNum(ctx context.Context) error {
key := GetCheckSubmitNumRedisKey()
v, err := uc.cache.GetValue(ctx, key)
if err != nil {
return err
}
var x int
if v != StringNull {
x, err = strconv.Atoi(v)
if err != nil {
return err
}
}
err = uc.cache.WriteValue(ctx, key, x+1, 0)
if err != nil {
return err
}
return nil
}
// 提交身份文件验证情况0没有提交1提交过
func (uc *EonlineUsecase) getCheckSubmit(ctx context.Context, account string) (int32, error) {
key := GetCheckSubmitRedisKey(account)
v, err := uc.cache.GetValue(ctx, key)
if err != nil {
return 0, err
}
if v == StringNull {
return 0, nil
} else {
return 1, nil
}
}
// 身份文件审核有反馈的情况0没有记录1审核没通过2审核通过
func (uc *EonlineUsecase) getCheckResult(ctx context.Context, account string) (int32, error) {
key := GetCheckResultRedisKey(account)
v, err := uc.cache.GetValue(ctx, key)
if err != nil {
return 0, err
}
if v == StringNull {
return 0, nil
}
if v == CheckResultSuccess {
return 2, nil
} else {
return 1, nil
}
}
// 提交身份文件奖励5美元领取的情况0没有领取记录1有领取记录
func (uc *EonlineUsecase) getCheckPayout(ctx context.Context, account string) (int32, error) {
return 0, nil
}
// 存储[uuid, paypal]
func (uc *EonlineUsecase) setUuid2Paypal(ctx context.Context, account, uuid string) error {
key := GetUuid2PaypalRedisKey(uuid)
err := uc.cache.WriteValue(ctx, key, account, 0)
if err != nil {
return err
}
return nil
}
// 根据uuid获取paypal
func (uc *EonlineUsecase) getUuid2Paypal(ctx context.Context, uuid string) (string, error) {
key := GetUuid2PaypalRedisKey(uuid)
v, err := uc.cache.GetValue(ctx, key)
if err != nil {
return StringNull, err
}
return v, nil
}
// 查询当天是否有提现记录0没有1有
func (uc *EonlineUsecase) GetCashStatus(ctx context.Context, uuid, ts string, itemId uint32) (int, error) {
keyDay := ""
if uc.conf.Server.Env == "qa" {
ts, _ := strconv.ParseInt(ts, 10, 64)
keyDay = time.Unix(ts, 0).Format("20060102")
}
uuidRedisKey := GetUuidPayoutRedisKey(uuid, keyDay, itemId)
value, err := uc.cache.GetValue(ctx, uuidRedisKey)
if err != nil {
return 0, err
}
if value != "" {
return 1, nil
}
return 0, nil
}
// 获取审核没通过的原因描述信息
func (uc *EonlineUsecase) getCheckResultFailedDesc(ctx context.Context, account string) (string, error) {
key := GetCheckResultFailRedisKey(account)
v, err := uc.cache.GetValue(ctx, key)
if err != nil {
return "", err
}
return v, nil
}