This commit is contained in:
Ashang
2022-04-04 16:01:36 +08:00
commit 20c576b9bc
72 changed files with 4680 additions and 0 deletions
+47
View File
@@ -0,0 +1,47 @@
package dao
import (
"github.com/assimon/luuu/config"
"github.com/assimon/luuu/util/log"
"github.com/gookit/color"
"github.com/spf13/viper"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/logger"
"gorm.io/gorm/schema"
"time"
)
var Mdb *gorm.DB
// MysqlInit 数据库初始化
func MysqlInit() {
var err error
Mdb, err = gorm.Open(mysql.Open(config.MysqlDns), &gorm.Config{
NamingStrategy: schema.NamingStrategy{
TablePrefix: viper.GetString("mysql_table_prefix"),
SingularTable: true,
},
Logger: logger.Default.LogMode(logger.Error),
})
if err != nil {
panic(err)
}
if config.AppDebug {
Mdb = Mdb.Debug()
}
sqlDB, err := Mdb.DB()
if err != nil {
color.Red.Printf("[store_db] mysql get DB,err=%s\n", err)
panic(err)
}
sqlDB.SetMaxIdleConns(viper.GetInt("mysql_max_idle_conns"))
sqlDB.SetMaxOpenConns(viper.GetInt("mysql_max_open_conns"))
sqlDB.SetConnMaxLifetime(time.Hour * time.Duration(viper.GetInt("mysql_max_life_time")))
err = sqlDB.Ping()
if err != nil {
color.Red.Printf("[store_db] mysql connDB err:%s", err.Error())
panic(err)
}
log.Sugar.Debug("[store_db] mysql connDB success")
}
+41
View File
@@ -0,0 +1,41 @@
package dao
import (
"context"
"fmt"
"github.com/assimon/luuu/util/log"
"github.com/go-redis/redis/v8"
"github.com/gookit/color"
"github.com/spf13/viper"
"time"
)
var Rdb *redis.Client
func RedisInit() {
options := redis.Options{
Addr: fmt.Sprintf(
"%s:%s",
viper.GetString("redis_host"),
viper.GetString("redis_port")), // Redis地址
DB: viper.GetInt("redis_db"), // Redis库
PoolSize: viper.GetInt("redis_pool_size"), // Redis连接池大小
MaxRetries: viper.GetInt("redis_max_retries"), // 最大重试次数
IdleTimeout: time.Second * time.Duration(viper.GetInt("redis_idle_timeout")), // 空闲链接超时时间
}
if viper.GetString("redis_passwd") != "" {
options.Password = viper.GetString("redis_passwd")
}
Rdb = redis.NewClient(&options)
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
pong, err := Rdb.Ping(ctx).Result()
if err == redis.Nil {
log.Sugar.Debug("[store_redis] Nil reply returned by Rdb when key does not exist.")
} else if err != nil {
color.Red.Printf("[store_redis] redis connRdb err,err=%s", err)
panic(err)
} else {
log.Sugar.Debug("[store_redis] redis connRdb success,suc=%s", pong)
}
}
+130
View File
@@ -0,0 +1,130 @@
package data
import (
"context"
"fmt"
"github.com/assimon/luuu/model/dao"
"github.com/assimon/luuu/model/mdb"
"github.com/assimon/luuu/model/request"
"github.com/go-redis/redis/v8"
"gorm.io/gorm"
)
var (
CacheWalletAddressToAvailableAmountKey = "WalletAddressToAvailableAmountKey:" // 钱包 - 可用金额 - 过期时间
CacheWalletAddressToOrdersKey = "WalletAddressToOrdersKey:" // 钱包 - 待支付金额 - 订单号
)
// GetOrderInfoByOrderId 通过客户订单号查询订单
func GetOrderInfoByOrderId(orderId string) (*mdb.Orders, error) {
order := new(mdb.Orders)
err := dao.Mdb.Model(order).Limit(1).Find(order, "order_id = ?", orderId).Error
return order, err
}
// GetOrderInfoByTradeId 通过交易号查询订单
func GetOrderInfoByTradeId(tradeId string) (*mdb.Orders, error) {
order := new(mdb.Orders)
err := dao.Mdb.Model(order).Limit(1).Find(order, "trade_id = ?", tradeId).Error
return order, err
}
// CreateOrderWithTransaction 事务创建订单
func CreateOrderWithTransaction(tx *gorm.DB, order *mdb.Orders) error {
err := tx.Model(order).Create(order).Error
return err
}
// GetOrderByBlockIdWithTransaction 通过区块获取订单
func GetOrderByBlockIdWithTransaction(tx *gorm.DB, blockId string) (*mdb.Orders, error) {
order := new(mdb.Orders)
err := tx.Model(order).Limit(1).Find(order, "block_transaction_id = ?", blockId).Error
return order, err
}
// OrderSuccessWithTransaction 事务支付成功
func OrderSuccessWithTransaction(tx *gorm.DB, req *request.OrderProcessingRequest) error {
err := tx.Model(&mdb.Orders{}).Where("trade_id = ?", req.TradeId).Updates(map[string]interface{}{
"block_transaction_id": req.BlockTransactionId,
"status": mdb.StatusPaySuccess,
"callback_confirm": mdb.CallBackConfirmNo,
}).Error
return err
}
// GetPendingCallbackOrders 查询出等待回调的订单
func GetPendingCallbackOrders() ([]mdb.Orders, error) {
var orders []mdb.Orders
err := dao.Mdb.Model(orders).
Where("callback_num < ?", 5).
Where("callback_confirm = ?", mdb.CallBackConfirmNo).
Where("status = ?", mdb.StatusPaySuccess).
Find(&orders).Error
return orders, err
}
// SaveCallBackOrdersResp 保存订单回调结果
func SaveCallBackOrdersResp(order *mdb.Orders) error {
err := dao.Mdb.Model(order).Where("id = ?", order.ID).Updates(map[string]interface{}{
"callback_num": gorm.Expr("callback_num + ?", 1),
"callback_confirm": order.CallBackConfirm,
}).Error
return err
}
// UpdateOrderIsExpirationById 通过id设置订单过期
func UpdateOrderIsExpirationById(id uint64) error {
err := dao.Mdb.Model(mdb.Orders{}).Where("id = ?", id).Update("status", mdb.StatusExpired).Error
return err
}
// LockPayCache 锁定支付池
func LockPayCache(token, tradeId, amount string, expirationTime int64) error {
// 载入Redis
ctx := context.Background()
pipe := dao.Rdb.TxPipeline()
lockAvailableAmountKey := fmt.Sprintf("%s%s", CacheWalletAddressToAvailableAmountKey, token)
// 占用金额
pipe.HSet(ctx, lockAvailableAmountKey, amount, expirationTime)
// 标记金额对应订单号
lockWalletOrdersKey := fmt.Sprintf("%s%s", CacheWalletAddressToOrdersKey, token)
pipe.HSet(ctx, lockWalletOrdersKey, amount, tradeId)
_, err := pipe.Exec(ctx)
return err
}
// ClearPayCache 清理支付池
func ClearPayCache(token, amount string) error {
ctx := context.Background()
lockAvailableAmountKey := fmt.Sprintf("%s%s", CacheWalletAddressToAvailableAmountKey, token)
lockWalletOrdersKey := fmt.Sprintf("%s%s", CacheWalletAddressToOrdersKey, token)
pipe := dao.Rdb.TxPipeline()
pipe.HDel(ctx, lockAvailableAmountKey, amount)
pipe.HDel(ctx, lockWalletOrdersKey, amount)
_, err := pipe.Exec(ctx)
return err
}
func GetTradeIdByAmount(ctx context.Context, token, amount string) (string, error) {
cacheKey := fmt.Sprintf("%s%s", CacheWalletAddressToOrdersKey, token)
result, err := dao.Rdb.HGet(ctx, cacheKey, amount).Result()
if err == redis.Nil {
return "", nil
}
if err != nil {
return "", err
}
return result, nil
}
func GetExpirationTimeByAmount(ctx context.Context, token, amount string) (string, error) {
cacheKey := fmt.Sprintf("%s%s", CacheWalletAddressToAvailableAmountKey, token)
result, err := dao.Rdb.HGet(ctx, cacheKey, amount).Result()
if err == redis.Nil {
return "", nil
}
if err != nil {
return "", err
}
return result, nil
}
+64
View File
@@ -0,0 +1,64 @@
package data
import (
"github.com/assimon/luuu/model/dao"
"github.com/assimon/luuu/model/mdb"
"github.com/assimon/luuu/util/constant"
)
// AddWalletAddress 创建钱包
func AddWalletAddress(token string) (*mdb.WalletAddress, error) {
exist, err := GetWalletAddressByToken(token)
if err != nil {
return nil, err
}
if exist.ID > 0 {
return nil, constant.WalletAddressAlreadyExists
}
walletAddress := &mdb.WalletAddress{
Token: token,
Status: mdb.TokenStatusEnable,
}
err = dao.Mdb.Create(walletAddress).Error
return walletAddress, err
}
// GetWalletAddressByToken 通过钱包地址获取token
func GetWalletAddressByToken(token string) (*mdb.WalletAddress, error) {
walletAddress := new(mdb.WalletAddress)
err := dao.Mdb.Model(walletAddress).Limit(1).Find(walletAddress, "token = ?", token).Error
return walletAddress, err
}
// GetWalletAddressById 通过id获取钱包
func GetWalletAddressById(id uint64) (*mdb.WalletAddress, error) {
walletAddress := new(mdb.WalletAddress)
err := dao.Mdb.Model(walletAddress).Limit(1).Find(walletAddress, id).Error
return walletAddress, err
}
// DeleteWalletAddressById 通过id删除钱包
func DeleteWalletAddressById(id uint64) error {
err := dao.Mdb.Where("id = ?", id).Delete(&mdb.WalletAddress{}).Error
return err
}
// GetAvailableWalletAddress 获得所有可用的钱包地址
func GetAvailableWalletAddress() ([]mdb.WalletAddress, error) {
var WalletAddressList []mdb.WalletAddress
err := dao.Mdb.Model(WalletAddressList).Where("status = ?", mdb.TokenStatusEnable).Find(&WalletAddressList).Error
return WalletAddressList, err
}
// GetAllWalletAddress 获得所有钱包地址
func GetAllWalletAddress() ([]mdb.WalletAddress, error) {
var WalletAddressList []mdb.WalletAddress
err := dao.Mdb.Model(WalletAddressList).Find(&WalletAddressList).Error
return WalletAddressList, err
}
// ChangeWalletAddressStatus 启用禁用钱包
func ChangeWalletAddressStatus(id uint64, status int) error {
err := dao.Mdb.Model(&mdb.WalletAddress{}).Where("id = ?", id).Update("status", status).Error
return err
}
+13
View File
@@ -0,0 +1,13 @@
package mdb
import (
"github.com/golang-module/carbon/v2"
"gorm.io/gorm"
)
type BaseModel struct {
ID uint64 `gorm:"column:id;primary_key" json:"id"`
CreatedAt carbon.Time `gorm:"column:created_at" json:"created_at"`
UpdatedAt carbon.Time `gorm:"column:updated_at" json:"updated_at"`
DeletedAt gorm.DeletedAt `gorm:"index" json:"deleted_at"`
}
+29
View File
@@ -0,0 +1,29 @@
package mdb
const (
StatusWaitPay = 1
StatusPaySuccess = 2
StatusExpired = 3
CallBackConfirmOk = 1
CallBackConfirmNo = 2
)
type Orders struct {
TradeId string `gorm:"column:trade_id" json:"trade_id"` // epusdt订单号
OrderId string `gorm:"column:order_id" json:"order_id"` // 客户交易id
BlockTransactionId string `gorm:"column:block_transaction_id" json:"block_transaction_id"` // 区块id
Amount float64 `gorm:"column:amount" json:"amount"` // 订单金额,保留4位小数
ActualAmount float64 `gorm:"column:actual_amount" json:"actual_amount"` // 订单实际需要支付的金额,保留4位小数
Token string `gorm:"column:token" json:"token"` // 所属钱包地址
Status int `gorm:"column:status" json:"status"` // 1:等待支付,2:支付成功,3:已过期
NotifyUrl string `gorm:"column:notify_url" json:"notify_url"` // 异步回调地址
RedirectUrl string `gorm:"column:redirect_url" json:"redirect_url"` // 同步回调地址
CallbackNum int `gorm:"column:callback_num" json:"callback_num"` // 回调次数
CallBackConfirm int `gorm:"column:callback_confirm" json:"callback_confirm"` // 回调是否已确认 1是 2否
BaseModel
}
// TableName sets the insert table name for this struct type
func (o *Orders) TableName() string {
return "orders"
}
+18
View File
@@ -0,0 +1,18 @@
package mdb
const (
TokenStatusEnable = 1
TokenStatusDisable = 2
)
// WalletAddress 钱包表
type WalletAddress struct {
Token string `gorm:"column:token" json:"token"` // 钱包token
Status int64 `gorm:"column:status" json:"status"` // 1:启用 2:禁用
BaseModel
}
// TableName sets the insert table name for this struct type
func (w *WalletAddress) TableName() string {
return "wallet_address"
}
+15
View File
@@ -0,0 +1,15 @@
package request
const (
OrderByFuncDesc = "DESC"
OrderByFuncAsc = "OrderByFuncASC"
)
var OrderByFuncList = []string{OrderByFuncDesc, OrderByFuncAsc}
type BaseRequest struct {
Page int `json:"page"` // 页数
PageSize int `json:"page_size"` // 每页条数
OrderField string `json:"order_field"` // 排序字段
OrderFunc string `json:"order_func"` // 排序方法
}
+29
View File
@@ -0,0 +1,29 @@
package request
import "github.com/gookit/validate"
// CreateTransactionRequest 创建交易请求
type CreateTransactionRequest struct {
OrderId string `json:"order_id" validate:"required|maxLen:32"`
Amount float64 `json:"amount" validate:"required|isFloat|gt:0.01"`
NotifyUrl string `json:"notify_url" validate:"required"`
Signature string `json:"signature" validate:"required"`
RedirectUrl string `json:"redirect_url"`
}
func (r CreateTransactionRequest) Translates() map[string]string {
return validate.MS{
"OrderId": "订单号",
"Amount": "支付金额",
"NotifyUrl": "异步回调网址",
"Signature": "签名",
}
}
// OrderProcessingRequest 订单处理
type OrderProcessingRequest struct {
Token string
Amount string
TradeId string
BlockTransactionId string
}
+24
View File
@@ -0,0 +1,24 @@
package response
// CreateTransactionResponse 创建订单成功返回
type CreateTransactionResponse struct {
TradeId string `json:"trade_id"` // epusdt订单号
OrderId string `json:"order_id"` // 客户交易id
Amount float64 `json:"amount"` // 订单金额,保留4位小数
ActualAmount float64 `json:"actual_amount"` // 订单实际需要支付的金额,保留4位小数
Token string `json:"token"` // 收款钱包地址
ExpirationTime int64 `json:"expiration_time"` // 过期时间 时间戳
PaymentUrl string `json:"payment_url"` // 收银台地址
}
// OrderNotifyResponse 订单异步回调结构体
type OrderNotifyResponse struct {
TradeId string `json:"trade_id"` // epusdt订单号
OrderId string `json:"order_id"` // 客户交易id
Amount float64 `json:"amount"` // 订单金额,保留4位小数
ActualAmount float64 `json:"actual_amount"` // 订单实际需要支付的金额,保留4位小数
Token string `json:"token"` // 收款钱包地址
BlockTransactionId string `json:"block_transaction_id"` // 区块id
Signature string `json:"signature"` // 签名
Status int `json:"status"` // 1:等待支付,2:支付成功,3:已过期
}
+14
View File
@@ -0,0 +1,14 @@
package response
type CheckoutCounterResponse struct {
TradeId string `json:"trade_id"` // epusdt订单号
ActualAmount float64 `json:"actual_amount"` // 订单实际需要支付的金额,保留4位小数
Token string `json:"token"` // 收款钱包地址
ExpirationTime int64 `json:"expiration_time"` // 过期时间 时间戳
RedirectUrl string `json:"redirect_url"`
}
type CheckStatusResponse struct {
TradeId string `json:"trade_id"` // epusdt订单号
Status int `json:"status"`
}
+230
View File
@@ -0,0 +1,230 @@
package service
import (
"context"
"fmt"
"github.com/assimon/luuu/config"
"github.com/assimon/luuu/model/dao"
"github.com/assimon/luuu/model/data"
"github.com/assimon/luuu/model/mdb"
"github.com/assimon/luuu/model/request"
"github.com/assimon/luuu/model/response"
"github.com/assimon/luuu/mq"
"github.com/assimon/luuu/mq/handle"
"github.com/assimon/luuu/telegram"
"github.com/assimon/luuu/util/constant"
"github.com/golang-module/carbon/v2"
"github.com/gookit/goutil/mathutil"
"github.com/hibiken/asynq"
"github.com/shopspring/decimal"
"math/rand"
"strconv"
"sync"
"time"
)
var gCreateTransactionLock sync.Mutex
// CreateTransaction 创建订单
func CreateTransaction(req *request.CreateTransactionRequest) (*response.CreateTransactionResponse, error) {
gCreateTransactionLock.Lock()
defer gCreateTransactionLock.Unlock()
// 汇率计算金额
rmb := decimal.NewFromFloat(req.Amount)
rate := decimal.NewFromFloat(config.GetUsdtRate())
amount := rmb.Div(rate).InexactFloat64()
actualAmountStr := fmt.Sprintf("%.4f", amount)
actualAmountFloat, err := strconv.ParseFloat(actualAmountStr, 64)
if err != nil {
return nil, err
}
// 是否可以满足最低支付金额
if actualAmountFloat <= 0 {
return nil, constant.PayAmountErr
}
// 已经存在了的交易
exist, err := data.GetOrderInfoByOrderId(req.OrderId)
if err != nil {
return nil, err
}
if exist.ID > 0 {
return nil, constant.OrderAlreadyExists
}
// 有无可用钱包
walletAddress, err := data.GetAvailableWalletAddress()
if err != nil {
return nil, err
}
if len(walletAddress) <= 0 {
return nil, constant.NotAvailableWalletAddress
}
availableToken, availableAmountStr, err := CalculateAvailableWalletTokenAndAmount(actualAmountStr, walletAddress)
if err != nil {
return nil, err
}
if availableToken == "" || availableAmountStr == "" {
return nil, constant.NotAvailableAmountErr
}
availableAmountFloat, err := strconv.ParseFloat(availableAmountStr, 64)
if err != nil {
return nil, err
}
tx := dao.Mdb.Begin()
order := &mdb.Orders{
TradeId: GenerateCode(),
OrderId: req.OrderId,
Amount: req.Amount,
ActualAmount: availableAmountFloat,
Token: availableToken,
Status: mdb.StatusWaitPay,
NotifyUrl: req.NotifyUrl,
RedirectUrl: req.RedirectUrl,
}
err = data.CreateOrderWithTransaction(tx, order)
if err != nil {
tx.Rollback()
return nil, err
}
ExpirationTime := carbon.Now().AddMinutes(config.GetOrderExpirationTime()).Timestamp()
// 锁定支付池
err = data.LockPayCache(availableToken, order.TradeId, availableAmountStr, ExpirationTime)
if err != nil {
tx.Rollback()
return nil, err
}
tx.Commit()
// 超时过期消息队列
orderExpirationQueue, _ := handle.NewOrderExpirationQueue(order.TradeId)
mq.MClient.Enqueue(orderExpirationQueue, asynq.ProcessIn(time.Minute*time.Duration(config.GetOrderExpirationTime())))
resp := &response.CreateTransactionResponse{
TradeId: order.TradeId,
OrderId: order.OrderId,
Amount: order.Amount,
ActualAmount: order.ActualAmount,
Token: order.Token,
ExpirationTime: ExpirationTime,
PaymentUrl: fmt.Sprintf("%s/pay/checkout-counter/%s", config.GetAppUri(), order.TradeId),
}
return resp, nil
}
// OrderProcessing 成功处理订单
func OrderProcessing(req *request.OrderProcessingRequest) error {
tx := dao.Mdb.Begin()
exist, err := data.GetOrderByBlockIdWithTransaction(tx, req.BlockTransactionId)
if err != nil {
return err
}
if exist.ID > 0 {
tx.Rollback()
return constant.OrderBlockAlreadyProcess
}
// 标记订单成功
err = data.OrderSuccessWithTransaction(tx, req)
if err != nil {
tx.Rollback()
return err
}
err = data.ClearPayCache(req.Token, req.Amount)
tx.Commit()
order, err := data.GetOrderInfoByTradeId(req.TradeId)
if err != nil {
return err
}
// 回调队列
orderCallbackQueue, _ := handle.NewOrderCallbackQueue(order)
mq.MClient.Enqueue(orderCallbackQueue, asynq.MaxRetry(5))
// 发送机器人消息
msgTpl := `
<b>📢📢有新的交易支付成功!</b>
<pre>交易号:%s</pre>
<pre>订单号:%s</pre>
<pre>请求支付金额:%.4f cny</pre>
<pre>实际支付金额:%.4f usdt</pre>
<pre>钱包地址:%s</pre>
<pre>订单创建时间:%s</pre>
<pre>支付成功时间:%s</pre>
`
msg := fmt.Sprintf(msgTpl, order.TradeId, order.OrderId, order.Amount, order.ActualAmount, order.Token, order.CreatedAt.ToDateTimeString(), carbon.Now().ToDateTimeString())
telegram.SendToBot(msg)
return nil
}
func CalculateAvailableWalletTokenAndAmount(amount string, walletAddress []mdb.WalletAddress) (string, string, error) {
calculateAmountStr := amount
availableAmountStr := ""
availableToken := ""
for i := 0; i < 100; i++ {
token, err := CalculateAvailableWalletToken(calculateAmountStr, walletAddress)
if err != nil {
return "", "", err
}
// 这个金额没有拿到可用的钱包,重试,金额+0.0001
if token == "" {
x, err := decimal.NewFromString(calculateAmountStr)
if err != nil {
return "", "", err
}
y, err := decimal.NewFromString("0.0001")
if err != nil {
return "", "", err
}
calculateAmountStr = x.Add(y).String()
continue
}
availableAmountStr = calculateAmountStr
availableToken = token
break
}
return availableToken, availableAmountStr, nil
}
// CalculateAvailableWalletToken 计算可用钱包token
func CalculateAvailableWalletToken(payAmount string, walletAddress []mdb.WalletAddress) (string, error) {
nowTime := time.Now().Unix()
ctx := context.Background()
walletToken := ""
for _, address := range walletAddress {
result, err := data.GetExpirationTimeByAmount(ctx, address.Token, payAmount)
if err != nil {
return "", err
}
// 这个钱包金额被占用了
if result != "" {
endTime := mathutil.MustInt64(result)
// 但是过期了
if endTime < nowTime {
// 删掉过期,还能继续用这个地址
err = data.ClearPayCache(address.Token, payAmount)
if err != nil {
return "", err
}
} else {
continue
}
}
walletToken = address.Token
break
}
return walletToken, nil
}
// GenerateCode 订单号生成
func GenerateCode() string {
date := time.Now().Format("20060102")
r := rand.Intn(1000)
code := fmt.Sprintf("%s%d%03d", date, time.Now().UnixNano()/1e6, r)
return code
}
// GetOrderInfoByTradeId 通过交易号获取订单
func GetOrderInfoByTradeId(tradeId string) (*mdb.Orders, error) {
order, err := data.GetOrderInfoByTradeId(tradeId)
if err != nil {
return nil, err
}
if order.ID <= 0 {
return nil, constant.OrderNotExists
}
return order, nil
}
+28
View File
@@ -0,0 +1,28 @@
package service
import (
"errors"
"github.com/assimon/luuu/config"
"github.com/assimon/luuu/model/data"
"github.com/assimon/luuu/model/mdb"
"github.com/assimon/luuu/model/response"
)
// GetCheckoutCounterByTradeId 获取收银台详情,通过订单
func GetCheckoutCounterByTradeId(tradeId string) (*response.CheckoutCounterResponse, error) {
orderInfo, err := data.GetOrderInfoByTradeId(tradeId)
if err != nil {
return nil, err
}
if orderInfo.ID <= 0 || orderInfo.Status != mdb.StatusWaitPay {
return nil, errors.New("不存在待支付订单或已过期!")
}
resp := &response.CheckoutCounterResponse{
TradeId: orderInfo.TradeId,
ActualAmount: orderInfo.ActualAmount,
Token: orderInfo.Token,
ExpirationTime: orderInfo.CreatedAt.AddMinutes(config.GetOrderExpirationTime()).TimestampWithMillisecond(),
RedirectUrl: orderInfo.RedirectUrl,
}
return resp, nil
}
+136
View File
@@ -0,0 +1,136 @@
package service
import (
"context"
"fmt"
"github.com/assimon/luuu/model/data"
"github.com/assimon/luuu/model/request"
"github.com/assimon/luuu/util/http_client"
"github.com/assimon/luuu/util/json"
"github.com/assimon/luuu/util/log"
"github.com/golang-module/carbon/v2"
"github.com/gookit/goutil/mathutil"
"github.com/gookit/goutil/stdutil"
"github.com/shopspring/decimal"
"net/http"
"sync"
"time"
)
const UsdtTrc20ApiUri = "https://apilist.tronscan.org/api/token_trc20/transfers"
type UsdtTrc20Resp struct {
Total int `json:"total"`
RangeTotal int `json:"rangeTotal"`
TokenTransfers []TokenTransfers `json:"token_transfers"`
}
type TokenInfo struct {
TokenID string `json:"tokenId"`
TokenAbbr string `json:"tokenAbbr"`
TokenName string `json:"tokenName"`
TokenDecimal int `json:"tokenDecimal"`
TokenCanShow int `json:"tokenCanShow"`
TokenType string `json:"tokenType"`
TokenLogo string `json:"tokenLogo"`
TokenLevel string `json:"tokenLevel"`
Vip bool `json:"vip"`
}
type TokenTransfers struct {
TransactionID string `json:"transaction_id"`
BlockTs int64 `json:"block_ts"`
FromAddress string `json:"from_address"`
ToAddress string `json:"to_address"`
Block int `json:"block"`
ContractAddress string `json:"contract_address"`
Quant string `json:"quant"`
ApprovalAmount string `json:"approval_amount"`
EventType string `json:"event_type"`
ContractType string `json:"contract_type"`
Confirmed bool `json:"confirmed"`
ContractRet string `json:"contractRet"`
FinalResult string `json:"finalResult"`
TokenInfo TokenInfo `json:"tokenInfo"`
FromAddressIsContract bool `json:"fromAddressIsContract"`
ToAddressIsContract bool `json:"toAddressIsContract"`
Revert bool `json:"revert"`
}
// Trc20CallBack trc20回调
func Trc20CallBack(token string, wg *sync.WaitGroup) {
defer wg.Done()
defer func() {
if err := recover(); err != nil {
log.Sugar.Error(err)
}
}()
client := http_client.GetHttpClient()
startTime := carbon.Now().AddHours(-24).TimestampWithMillisecond()
endTime := carbon.Now().TimestampWithMillisecond()
resp, err := client.R().SetQueryParams(map[string]string{
"limit": "200",
"start": "0",
"direction": "in",
"tokens": "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t",
"relatedAddress": token,
"start_timestamp": stdutil.ToString(startTime),
"end_timestamp": stdutil.ToString(endTime),
}).Get(UsdtTrc20ApiUri)
if err != nil {
panic(err)
}
if resp.StatusCode() != http.StatusOK {
panic(err)
}
var trc20Resp UsdtTrc20Resp
err = json.Cjson.Unmarshal(resp.Body(), &trc20Resp)
if err != nil {
panic(err)
}
if trc20Resp.Total <= 0 {
return
}
ctx := context.Background()
nowTime := time.Now().Unix()
for _, transfer := range trc20Resp.TokenTransfers {
if transfer.ToAddress != token || transfer.FinalResult != "SUCCESS" {
continue
}
x, _ := decimal.NewFromString(transfer.Quant)
y, _ := decimal.NewFromString("1000000")
quant := x.Div(y).InexactFloat64()
amount := fmt.Sprintf("%.4f", quant)
result, err := data.GetExpirationTimeByAmount(ctx, token, amount)
if err != nil {
panic(err)
}
if result != "" {
expTime := mathutil.MustInt64(result)
// 但是过期了
if expTime < nowTime {
// 删掉过期
_ = data.ClearPayCache(token, amount)
continue
}
}
// 该钱包下有无匹配金额订单
tradeId, err := data.GetTradeIdByAmount(ctx, token, amount)
if err != nil {
panic(err)
}
if tradeId == "" {
continue
}
// 到这一步就匹配到金额了
req := &request.OrderProcessingRequest{
Token: token,
TradeId: tradeId,
Amount: amount,
BlockTransactionId: transfer.TransactionID,
}
err = OrderProcessing(req)
if err != nil {
panic(err)
}
}
}