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

1547 lines
42 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
}