mirror of
https://github.com/GMWalletApp/epusdt.git
synced 2026-07-07 10:16:15 +00:00
@@ -4,6 +4,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
"os"
|
"os"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -53,7 +54,7 @@ func Init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func GetAppVersion() string {
|
func GetAppVersion() string {
|
||||||
return "0.0.1"
|
return "0.0.2"
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetAppName() string {
|
func GetAppName() string {
|
||||||
@@ -90,3 +91,8 @@ func GetOrderExpirationTime() int {
|
|||||||
}
|
}
|
||||||
return timer
|
return timer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetOrderExpirationTimeDuration() time.Duration {
|
||||||
|
timer := GetOrderExpirationTime()
|
||||||
|
return time.Minute * time.Duration(timer)
|
||||||
|
}
|
||||||
|
|||||||
+2
-2
@@ -7,8 +7,8 @@ require (
|
|||||||
github.com/go-resty/resty/v2 v2.7.0
|
github.com/go-resty/resty/v2 v2.7.0
|
||||||
github.com/golang-module/carbon/v2 v2.0.1
|
github.com/golang-module/carbon/v2 v2.0.1
|
||||||
github.com/gookit/color v1.5.0
|
github.com/gookit/color v1.5.0
|
||||||
github.com/gookit/goutil v0.4.4
|
github.com/gookit/goutil v0.4.6
|
||||||
github.com/gookit/validate v1.2.11
|
github.com/gookit/validate v1.3.1
|
||||||
github.com/hibiken/asynq v0.22.1
|
github.com/hibiken/asynq v0.22.1
|
||||||
github.com/json-iterator/go v1.1.12
|
github.com/json-iterator/go v1.1.12
|
||||||
github.com/labstack/echo/v4 v4.6.0
|
github.com/labstack/echo/v4 v4.6.0
|
||||||
|
|||||||
@@ -197,8 +197,12 @@ github.com/gookit/filter v1.1.2/go.mod h1:pVXLLDD+A8yH9GRztq2Cp7zwZocnuTUpbZs9Q+
|
|||||||
github.com/gookit/goutil v0.3.12/go.mod h1:ITj7Lw0muhJNOX+QRa+j+HH0+RNoQVuTmZx5d5LE1vE=
|
github.com/gookit/goutil v0.3.12/go.mod h1:ITj7Lw0muhJNOX+QRa+j+HH0+RNoQVuTmZx5d5LE1vE=
|
||||||
github.com/gookit/goutil v0.4.4 h1:18xr8NKbs4LteyOHZfQ8g7JqTqZal801mAT6LERGdpE=
|
github.com/gookit/goutil v0.4.4 h1:18xr8NKbs4LteyOHZfQ8g7JqTqZal801mAT6LERGdpE=
|
||||||
github.com/gookit/goutil v0.4.4/go.mod h1:qlGVh0PI+WnWSjYnIocfz/7tkeogxL6+EDNP1mRe+7o=
|
github.com/gookit/goutil v0.4.4/go.mod h1:qlGVh0PI+WnWSjYnIocfz/7tkeogxL6+EDNP1mRe+7o=
|
||||||
|
github.com/gookit/goutil v0.4.6 h1:LSATnmIyR0rV4BMj3XyaKFYtX/HbRURnPVHt6Zb5ABs=
|
||||||
|
github.com/gookit/goutil v0.4.6/go.mod h1:pq1eTibwb2wN96jrci0xy7xogWzzo9CihOQJEAvz4yQ=
|
||||||
github.com/gookit/validate v1.2.11 h1:zUMsezhMrW3Cy8St3cQJgCKB1ZIbKOWK8e7WMSuVIRc=
|
github.com/gookit/validate v1.2.11 h1:zUMsezhMrW3Cy8St3cQJgCKB1ZIbKOWK8e7WMSuVIRc=
|
||||||
github.com/gookit/validate v1.2.11/go.mod h1:wXo0Vr+AzFUCEUCbTFXgKlPfT+V/V0wPK3zLp49jQq0=
|
github.com/gookit/validate v1.2.11/go.mod h1:wXo0Vr+AzFUCEUCbTFXgKlPfT+V/V0wPK3zLp49jQq0=
|
||||||
|
github.com/gookit/validate v1.3.1 h1:YoxBxG5H+WucKjfEo8X+QvYOzED7ue3Mfpq6jtpIbYs=
|
||||||
|
github.com/gookit/validate v1.3.1/go.mod h1:mzcr3U5XtAlQVAgbW0wbmDGiuefU/MNuDkXFMBNb1ko=
|
||||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
|
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
|
||||||
github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
|
github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
|
||||||
@@ -370,6 +374,7 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5
|
|||||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
|
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
|
||||||
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
||||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||||
|
|||||||
@@ -8,11 +8,11 @@ import (
|
|||||||
"github.com/assimon/luuu/model/request"
|
"github.com/assimon/luuu/model/request"
|
||||||
"github.com/go-redis/redis/v8"
|
"github.com/go-redis/redis/v8"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
CacheWalletAddressToAvailableAmountKey = "WalletAddressToAvailableAmountKey:" // 钱包 - 可用金额 - 过期时间
|
CacheWalletAddressWithAmountToTradeIdKey = "wallet:%s_%v" // 钱包_待支付金额 : 交易号
|
||||||
CacheWalletAddressToOrdersKey = "WalletAddressToOrdersKey:" // 钱包 - 待支付金额 - 订单号
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetOrderInfoByOrderId 通过客户订单号查询订单
|
// GetOrderInfoByOrderId 通过客户订单号查询订单
|
||||||
@@ -78,36 +78,11 @@ func UpdateOrderIsExpirationById(id uint64) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// LockPayCache 锁定支付池
|
// GetTradeIdByWalletAddressAndAmount 通过钱包地址,支付金额获取交易号
|
||||||
func LockPayCache(token, tradeId, amount string, expirationTime int64) error {
|
func GetTradeIdByWalletAddressAndAmount(token string, amount float64) (string, error) {
|
||||||
// 载入Redis
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
pipe := dao.Rdb.TxPipeline()
|
cacheKey := fmt.Sprintf(CacheWalletAddressWithAmountToTradeIdKey, token, amount)
|
||||||
lockAvailableAmountKey := fmt.Sprintf("%s%s", CacheWalletAddressToAvailableAmountKey, token)
|
result, err := dao.Rdb.Get(ctx, cacheKey).Result()
|
||||||
// 占用金额
|
|
||||||
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 {
|
if err == redis.Nil {
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
@@ -117,14 +92,18 @@ func GetTradeIdByAmount(ctx context.Context, token, amount string) (string, erro
|
|||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetExpirationTimeByAmount(ctx context.Context, token, amount string) (string, error) {
|
// LockTransaction 锁定交易
|
||||||
cacheKey := fmt.Sprintf("%s%s", CacheWalletAddressToAvailableAmountKey, token)
|
func LockTransaction(token, tradeId string, amount float64, expirationTime time.Duration) error {
|
||||||
result, err := dao.Rdb.HGet(ctx, cacheKey, amount).Result()
|
ctx := context.Background()
|
||||||
if err == redis.Nil {
|
cacheKey := fmt.Sprintf(CacheWalletAddressWithAmountToTradeIdKey, token, amount)
|
||||||
return "", nil
|
err := dao.Rdb.Set(ctx, cacheKey, tradeId, expirationTime).Err()
|
||||||
}
|
return err
|
||||||
if err != nil {
|
}
|
||||||
return "", err
|
|
||||||
}
|
// UnLockTransaction 解锁交易
|
||||||
return result, nil
|
func UnLockTransaction(token string, amount float64) error {
|
||||||
|
ctx := context.Background()
|
||||||
|
cacheKey := fmt.Sprintf(CacheWalletAddressWithAmountToTradeIdKey, token, amount)
|
||||||
|
err := dao.Rdb.Del(ctx, cacheKey).Err()
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ func (r CreateTransactionRequest) Translates() map[string]string {
|
|||||||
// OrderProcessingRequest 订单处理
|
// OrderProcessingRequest 订单处理
|
||||||
type OrderProcessingRequest struct {
|
type OrderProcessingRequest struct {
|
||||||
Token string
|
Token string
|
||||||
Amount string
|
Amount float64
|
||||||
TradeId string
|
TradeId string
|
||||||
BlockTransactionId string
|
BlockTransactionId string
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package service
|
package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/assimon/luuu/config"
|
"github.com/assimon/luuu/config"
|
||||||
"github.com/assimon/luuu/model/dao"
|
"github.com/assimon/luuu/model/dao"
|
||||||
@@ -11,35 +10,40 @@ import (
|
|||||||
"github.com/assimon/luuu/model/response"
|
"github.com/assimon/luuu/model/response"
|
||||||
"github.com/assimon/luuu/mq"
|
"github.com/assimon/luuu/mq"
|
||||||
"github.com/assimon/luuu/mq/handle"
|
"github.com/assimon/luuu/mq/handle"
|
||||||
"github.com/assimon/luuu/telegram"
|
|
||||||
"github.com/assimon/luuu/util/constant"
|
"github.com/assimon/luuu/util/constant"
|
||||||
|
"github.com/assimon/luuu/util/math"
|
||||||
"github.com/golang-module/carbon/v2"
|
"github.com/golang-module/carbon/v2"
|
||||||
"github.com/gookit/goutil/mathutil"
|
|
||||||
"github.com/hibiken/asynq"
|
"github.com/hibiken/asynq"
|
||||||
"github.com/shopspring/decimal"
|
"github.com/shopspring/decimal"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"strconv"
|
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
CnyMinimumPaymentAmount = 0.01 // cny最低支付金额
|
||||||
|
UsdtMinimumPaymentAmount = 0.001 // usdt最低支付金额
|
||||||
|
UsdtAmountPerIncrement = 0.001 // usdt每次递增金额
|
||||||
|
IncrementalMaximumNumber = 100 // 最大递增次数
|
||||||
|
)
|
||||||
|
|
||||||
var gCreateTransactionLock sync.Mutex
|
var gCreateTransactionLock sync.Mutex
|
||||||
|
|
||||||
// CreateTransaction 创建订单
|
// CreateTransaction 创建订单
|
||||||
func CreateTransaction(req *request.CreateTransactionRequest) (*response.CreateTransactionResponse, error) {
|
func CreateTransaction(req *request.CreateTransactionRequest) (*response.CreateTransactionResponse, error) {
|
||||||
gCreateTransactionLock.Lock()
|
gCreateTransactionLock.Lock()
|
||||||
defer gCreateTransactionLock.Unlock()
|
defer gCreateTransactionLock.Unlock()
|
||||||
// 汇率计算金额
|
payAmount := math.MustParsePrecFloat64(req.Amount, 2)
|
||||||
rmb := decimal.NewFromFloat(req.Amount)
|
// 按照汇率转化USDT
|
||||||
rate := decimal.NewFromFloat(config.GetUsdtRate())
|
decimalPayAmount := decimal.NewFromFloat(payAmount)
|
||||||
amount := rmb.Div(rate).InexactFloat64()
|
decimalRate := decimal.NewFromFloat(config.GetUsdtRate())
|
||||||
actualAmountStr := fmt.Sprintf("%.4f", amount)
|
decimalUsdt := decimalPayAmount.Div(decimalRate)
|
||||||
actualAmountFloat, err := strconv.ParseFloat(actualAmountStr, 64)
|
// cny 是否可以满足最低支付金额
|
||||||
if err != nil {
|
if decimalPayAmount.Cmp(decimal.NewFromFloat(CnyMinimumPaymentAmount)) == -1 {
|
||||||
return nil, err
|
return nil, constant.PayAmountErr
|
||||||
}
|
}
|
||||||
// 是否可以满足最低支付金额
|
// Usdt是否可以满足最低支付金额
|
||||||
if actualAmountFloat <= 0 {
|
if decimalUsdt.Cmp(decimal.NewFromFloat(UsdtMinimumPaymentAmount)) == -1 {
|
||||||
return nil, constant.PayAmountErr
|
return nil, constant.PayAmountErr
|
||||||
}
|
}
|
||||||
// 已经存在了的交易
|
// 已经存在了的交易
|
||||||
@@ -58,23 +62,20 @@ func CreateTransaction(req *request.CreateTransactionRequest) (*response.CreateT
|
|||||||
if len(walletAddress) <= 0 {
|
if len(walletAddress) <= 0 {
|
||||||
return nil, constant.NotAvailableWalletAddress
|
return nil, constant.NotAvailableWalletAddress
|
||||||
}
|
}
|
||||||
availableToken, availableAmountStr, err := CalculateAvailableWalletTokenAndAmount(actualAmountStr, walletAddress)
|
amount := math.MustParsePrecFloat64(decimalUsdt.InexactFloat64(), 3)
|
||||||
|
availableToken, availableAmount, err := CalculateAvailableWalletAndAmount(amount, walletAddress)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if availableToken == "" || availableAmountStr == "" {
|
if availableToken == "" {
|
||||||
return nil, constant.NotAvailableAmountErr
|
return nil, constant.NotAvailableAmountErr
|
||||||
}
|
}
|
||||||
availableAmountFloat, err := strconv.ParseFloat(availableAmountStr, 64)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
tx := dao.Mdb.Begin()
|
tx := dao.Mdb.Begin()
|
||||||
order := &mdb.Orders{
|
order := &mdb.Orders{
|
||||||
TradeId: GenerateCode(),
|
TradeId: GenerateCode(),
|
||||||
OrderId: req.OrderId,
|
OrderId: req.OrderId,
|
||||||
Amount: req.Amount,
|
Amount: req.Amount,
|
||||||
ActualAmount: availableAmountFloat,
|
ActualAmount: availableAmount,
|
||||||
Token: availableToken,
|
Token: availableToken,
|
||||||
Status: mdb.StatusWaitPay,
|
Status: mdb.StatusWaitPay,
|
||||||
NotifyUrl: req.NotifyUrl,
|
NotifyUrl: req.NotifyUrl,
|
||||||
@@ -85,9 +86,8 @@ func CreateTransaction(req *request.CreateTransactionRequest) (*response.CreateT
|
|||||||
tx.Rollback()
|
tx.Rollback()
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
ExpirationTime := carbon.Now().AddMinutes(config.GetOrderExpirationTime()).Timestamp()
|
|
||||||
// 锁定支付池
|
// 锁定支付池
|
||||||
err = data.LockPayCache(availableToken, order.TradeId, availableAmountStr, ExpirationTime)
|
err = data.LockTransaction(availableToken, order.TradeId, availableAmount, config.GetOrderExpirationTimeDuration())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
tx.Rollback()
|
tx.Rollback()
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -95,7 +95,8 @@ func CreateTransaction(req *request.CreateTransactionRequest) (*response.CreateT
|
|||||||
tx.Commit()
|
tx.Commit()
|
||||||
// 超时过期消息队列
|
// 超时过期消息队列
|
||||||
orderExpirationQueue, _ := handle.NewOrderExpirationQueue(order.TradeId)
|
orderExpirationQueue, _ := handle.NewOrderExpirationQueue(order.TradeId)
|
||||||
mq.MClient.Enqueue(orderExpirationQueue, asynq.ProcessIn(time.Minute*time.Duration(config.GetOrderExpirationTime())))
|
mq.MClient.Enqueue(orderExpirationQueue, asynq.ProcessIn(config.GetOrderExpirationTimeDuration()))
|
||||||
|
ExpirationTime := carbon.Now().AddMinutes(config.GetOrderExpirationTime()).Timestamp()
|
||||||
resp := &response.CreateTransactionResponse{
|
resp := &response.CreateTransactionResponse{
|
||||||
TradeId: order.TradeId,
|
TradeId: order.TradeId,
|
||||||
OrderId: order.OrderId,
|
OrderId: order.OrderId,
|
||||||
@@ -125,88 +126,51 @@ func OrderProcessing(req *request.OrderProcessingRequest) error {
|
|||||||
tx.Rollback()
|
tx.Rollback()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = data.ClearPayCache(req.Token, req.Amount)
|
// 解锁交易
|
||||||
tx.Commit()
|
err = data.UnLockTransaction(req.Token, req.Amount)
|
||||||
order, err := data.GetOrderInfoByTradeId(req.TradeId)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
tx.Rollback()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// 回调队列
|
tx.Commit()
|
||||||
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func CalculateAvailableWalletTokenAndAmount(amount string, walletAddress []mdb.WalletAddress) (string, string, error) {
|
// CalculateAvailableWalletAndAmount 计算可用钱包地址和金额
|
||||||
calculateAmountStr := amount
|
func CalculateAvailableWalletAndAmount(amount float64, walletAddress []mdb.WalletAddress) (string, float64, error) {
|
||||||
availableAmountStr := ""
|
|
||||||
availableToken := ""
|
availableToken := ""
|
||||||
for i := 0; i < 100; i++ {
|
availableAmount := amount
|
||||||
token, err := CalculateAvailableWalletToken(calculateAmountStr, walletAddress)
|
calculateAvailableWalletFunc := func(amount float64) (string, error) {
|
||||||
if err != nil {
|
availableWallet := ""
|
||||||
return "", "", err
|
for _, address := range walletAddress {
|
||||||
|
token := address.Token
|
||||||
|
result, err := data.GetTradeIdByWalletAddressAndAmount(token, amount)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if result == "" {
|
||||||
|
availableWallet = token
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// 这个金额没有拿到可用的钱包,重试,金额+0.0001
|
return availableWallet, nil
|
||||||
|
}
|
||||||
|
for i := 0; i < IncrementalMaximumNumber; i++ {
|
||||||
|
token, err := calculateAvailableWalletFunc(availableAmount)
|
||||||
|
if err != nil {
|
||||||
|
return "", 0, err
|
||||||
|
}
|
||||||
|
// 拿不到可用钱包就累加金额
|
||||||
if token == "" {
|
if token == "" {
|
||||||
x, err := decimal.NewFromString(calculateAmountStr)
|
decimalOldAmount := decimal.NewFromFloat(availableAmount)
|
||||||
if err != nil {
|
decimalIncr := decimal.NewFromFloat(UsdtAmountPerIncrement)
|
||||||
return "", "", err
|
availableAmount = decimalOldAmount.Add(decimalIncr).InexactFloat64()
|
||||||
}
|
|
||||||
y, err := decimal.NewFromString("0.0001")
|
|
||||||
if err != nil {
|
|
||||||
return "", "", err
|
|
||||||
}
|
|
||||||
calculateAmountStr = x.Add(y).String()
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
availableAmountStr = calculateAmountStr
|
|
||||||
availableToken = token
|
availableToken = token
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
return availableToken, availableAmountStr, nil
|
return availableToken, availableAmount, 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 订单号生成
|
// GenerateCode 订单号生成
|
||||||
|
|||||||
@@ -1,20 +1,21 @@
|
|||||||
package service
|
package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/assimon/luuu/model/data"
|
"github.com/assimon/luuu/model/data"
|
||||||
"github.com/assimon/luuu/model/request"
|
"github.com/assimon/luuu/model/request"
|
||||||
|
"github.com/assimon/luuu/mq"
|
||||||
|
"github.com/assimon/luuu/mq/handle"
|
||||||
|
"github.com/assimon/luuu/telegram"
|
||||||
"github.com/assimon/luuu/util/http_client"
|
"github.com/assimon/luuu/util/http_client"
|
||||||
"github.com/assimon/luuu/util/json"
|
"github.com/assimon/luuu/util/json"
|
||||||
"github.com/assimon/luuu/util/log"
|
"github.com/assimon/luuu/util/log"
|
||||||
"github.com/golang-module/carbon/v2"
|
"github.com/golang-module/carbon/v2"
|
||||||
"github.com/gookit/goutil/mathutil"
|
|
||||||
"github.com/gookit/goutil/stdutil"
|
"github.com/gookit/goutil/stdutil"
|
||||||
|
"github.com/hibiken/asynq"
|
||||||
"github.com/shopspring/decimal"
|
"github.com/shopspring/decimal"
|
||||||
"net/http"
|
"net/http"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const UsdtTrc20ApiUri = "https://apilist.tronscan.org/api/token_trc20/transfers"
|
const UsdtTrc20ApiUri = "https://apilist.tronscan.org/api/token_trc20/transfers"
|
||||||
@@ -90,38 +91,33 @@ func Trc20CallBack(token string, wg *sync.WaitGroup) {
|
|||||||
if trc20Resp.Total <= 0 {
|
if trc20Resp.Total <= 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ctx := context.Background()
|
|
||||||
nowTime := time.Now().Unix()
|
|
||||||
for _, transfer := range trc20Resp.TokenTransfers {
|
for _, transfer := range trc20Resp.TokenTransfers {
|
||||||
if transfer.ToAddress != token || transfer.FinalResult != "SUCCESS" {
|
if transfer.ToAddress != token || transfer.FinalResult != "SUCCESS" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
x, _ := decimal.NewFromString(transfer.Quant)
|
decimalQuant, err := 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 {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
if result != "" {
|
decimalDivisor := decimal.NewFromFloat(1000000)
|
||||||
expTime := mathutil.MustInt64(result)
|
amount := decimalQuant.Div(decimalDivisor).InexactFloat64()
|
||||||
// 但是过期了
|
tradeId, err := data.GetTradeIdByWalletAddressAndAmount(token, amount)
|
||||||
if expTime < nowTime {
|
|
||||||
// 删掉过期
|
|
||||||
_ = data.ClearPayCache(token, amount)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 该钱包下有无匹配金额订单
|
|
||||||
tradeId, err := data.GetTradeIdByAmount(ctx, token, amount)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
if tradeId == "" {
|
if tradeId == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// 到这一步就匹配到金额了
|
order, err := data.GetOrderInfoByTradeId(tradeId)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
// 区块的确认时间必须在订单创建时间之后
|
||||||
|
createTime := order.CreatedAt.TimestampWithMillisecond()
|
||||||
|
if transfer.BlockTs < createTime {
|
||||||
|
panic("Orders cannot actually be matched")
|
||||||
|
}
|
||||||
|
// 到这一步就完全算是支付成功了
|
||||||
req := &request.OrderProcessingRequest{
|
req := &request.OrderProcessingRequest{
|
||||||
Token: token,
|
Token: token,
|
||||||
TradeId: tradeId,
|
TradeId: tradeId,
|
||||||
@@ -132,5 +128,21 @@ func Trc20CallBack(token string, wg *sync.WaitGroup) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
// 回调队列
|
||||||
|
orderCallbackQueue, _ := handle.NewOrderCallbackQueue(order)
|
||||||
|
mq.MClient.Enqueue(orderCallbackQueue, asynq.MaxRetry(5))
|
||||||
|
// 发送机器人消息
|
||||||
|
msgTpl := `
|
||||||
|
<b>📢📢有新的交易支付成功!</b>
|
||||||
|
<pre>交易号:%s</pre>
|
||||||
|
<pre>订单号:%s</pre>
|
||||||
|
<pre>请求支付金额:%f cny</pre>
|
||||||
|
<pre>实际支付金额:%f 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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package handle
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"github.com/assimon/luuu/model/data"
|
"github.com/assimon/luuu/model/data"
|
||||||
"github.com/assimon/luuu/model/mdb"
|
"github.com/assimon/luuu/model/mdb"
|
||||||
"github.com/hibiken/asynq"
|
"github.com/hibiken/asynq"
|
||||||
@@ -28,8 +27,7 @@ func OrderExpirationHandle(ctx context.Context, t *asynq.Task) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
actualAmount := fmt.Sprintf("%.4f", orderInfo.ActualAmount)
|
err = data.UnLockTransaction(orderInfo.Token, orderInfo.ActualAmount)
|
||||||
err = data.ClearPayCache(orderInfo.Token, actualAmount)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
package task
|
package task
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"github.com/assimon/luuu/config"
|
"github.com/assimon/luuu/config"
|
||||||
"github.com/assimon/luuu/util/http_client"
|
"github.com/assimon/luuu/util/http_client"
|
||||||
"github.com/assimon/luuu/util/json"
|
"github.com/assimon/luuu/util/json"
|
||||||
"github.com/assimon/luuu/util/log"
|
"github.com/assimon/luuu/util/log"
|
||||||
"strconv"
|
"github.com/assimon/luuu/util/math"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -56,10 +55,7 @@ func (r UsdtRateJob) Run() {
|
|||||||
}
|
}
|
||||||
for _, points := range usdtResp.Data.Points {
|
for _, points := range usdtResp.Data.Points {
|
||||||
if len(points.C) > 0 && points.C[0] > 0 {
|
if len(points.C) > 0 && points.C[0] > 0 {
|
||||||
config.UsdtRate, err = strconv.ParseFloat(fmt.Sprintf("%.2f", points.C[0]), 64)
|
config.UsdtRate = math.MustParsePrecFloat64(points.C[0], 2)
|
||||||
if err != nil {
|
|
||||||
log.Sugar.Error("Calculate UsdtRate err:", err.Error())
|
|
||||||
}
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package math
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MustParsePrecFloat64 按小数位截取float
|
||||||
|
func MustParsePrecFloat64(value float64, prec int) float64 {
|
||||||
|
format := fmt.Sprintf("%%.%df", prec)
|
||||||
|
v, err := strconv.ParseFloat(fmt.Sprintf(format, value), 64)
|
||||||
|
if err != nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user