176 lines
5.8 KiB
Go
176 lines
5.8 KiB
Go
package data
|
|
|
|
import (
|
|
"errors"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/assimon/luuu/model/dao"
|
|
"github.com/assimon/luuu/model/mdb"
|
|
"github.com/assimon/luuu/model/request"
|
|
"github.com/shopspring/decimal"
|
|
"gorm.io/gorm"
|
|
"gorm.io/gorm/clause"
|
|
)
|
|
|
|
var ErrTransactionLocked = errors.New("transaction amount is already locked")
|
|
|
|
func normalizeLockAmount(amount float64) (int64, string) {
|
|
value := decimal.NewFromFloat(amount).Round(2)
|
|
return value.Shift(2).IntPart(), value.StringFixed(2)
|
|
}
|
|
|
|
func normalizeLockToken(token string) string {
|
|
return strings.ToUpper(strings.TrimSpace(token))
|
|
}
|
|
|
|
// GetOrderInfoByOrderId fetches an order by merchant order id.
|
|
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 fetches an order by epusdt trade id.
|
|
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 creates an order in the active database transaction.
|
|
func CreateOrderWithTransaction(tx *gorm.DB, order *mdb.Orders) error {
|
|
return tx.Model(order).Create(order).Error
|
|
}
|
|
|
|
// GetOrderByBlockIdWithTransaction fetches an order by blockchain tx id.
|
|
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 marks an order as paid only if it is still waiting for payment.
|
|
func OrderSuccessWithTransaction(tx *gorm.DB, req *request.OrderProcessingRequest) (bool, error) {
|
|
result := tx.Model(&mdb.Orders{}).
|
|
Where("trade_id = ?", req.TradeId).
|
|
Where("status = ?", mdb.StatusWaitPay).
|
|
Updates(map[string]interface{}{
|
|
"block_transaction_id": req.BlockTransactionId,
|
|
"status": mdb.StatusPaySuccess,
|
|
"callback_confirm": mdb.CallBackConfirmNo,
|
|
})
|
|
return result.RowsAffected > 0, result.Error
|
|
}
|
|
|
|
// GetPendingCallbackOrders returns orders that still need callback delivery.
|
|
func GetPendingCallbackOrders(maxRetry int, limit int) ([]mdb.Orders, error) {
|
|
var orders []mdb.Orders
|
|
query := dao.Mdb.Model(&mdb.Orders{}).
|
|
Where("callback_num <= ?", maxRetry).
|
|
Where("callback_confirm = ?", mdb.CallBackConfirmNo).
|
|
Where("status = ?", mdb.StatusPaySuccess).
|
|
Order("updated_at asc")
|
|
if limit > 0 {
|
|
query = query.Limit(limit)
|
|
}
|
|
err := query.Find(&orders).Error
|
|
return orders, err
|
|
}
|
|
|
|
// SaveCallBackOrdersResp persists a callback attempt result.
|
|
func SaveCallBackOrdersResp(order *mdb.Orders) error {
|
|
return dao.Mdb.Model(order).
|
|
Where("id = ?", order.ID).
|
|
Where("callback_confirm = ?", mdb.CallBackConfirmNo).
|
|
Updates(map[string]interface{}{
|
|
"callback_num": gorm.Expr("callback_num + ?", 1),
|
|
"callback_confirm": order.CallBackConfirm,
|
|
}).Error
|
|
}
|
|
|
|
// UpdateOrderIsExpirationById expires an order only if it is still pending and already timed out.
|
|
func UpdateOrderIsExpirationById(id uint64, expirationCutoff time.Time) (bool, error) {
|
|
result := dao.Mdb.Model(mdb.Orders{}).
|
|
Where("id = ?", id).
|
|
Where("status = ?", mdb.StatusWaitPay).
|
|
Where("created_at <= ?", expirationCutoff).
|
|
Update("status", mdb.StatusExpired)
|
|
return result.RowsAffected > 0, result.Error
|
|
}
|
|
|
|
// GetTradeIdByWalletAddressAndAmountAndToken resolves the reserved trade id by address, token and amount.
|
|
func GetTradeIdByWalletAddressAndAmountAndToken(address string, token string, amount float64) (string, error) {
|
|
scaledAmount, _ := normalizeLockAmount(amount)
|
|
var lock mdb.TransactionLock
|
|
err := dao.RuntimeDB.Model(&mdb.TransactionLock{}).
|
|
Where("address = ?", address).
|
|
Where("token = ?", normalizeLockToken(token)).
|
|
Where("amount_scaled = ?", scaledAmount).
|
|
Where("expires_at > ?", time.Now()).
|
|
Limit(1).
|
|
Find(&lock).Error
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if lock.ID <= 0 {
|
|
return "", nil
|
|
}
|
|
return lock.TradeId, nil
|
|
}
|
|
|
|
// LockTransaction reserves an address+token+amount pair in sqlite until expiration.
|
|
func LockTransaction(address, token, tradeID string, amount float64, expirationTime time.Duration) error {
|
|
scaledAmount, amountText := normalizeLockAmount(amount)
|
|
normalizedToken := normalizeLockToken(token)
|
|
now := time.Now()
|
|
lock := &mdb.TransactionLock{
|
|
Address: address,
|
|
Token: normalizedToken,
|
|
AmountScaled: scaledAmount,
|
|
AmountText: amountText,
|
|
TradeId: tradeID,
|
|
ExpiresAt: now.Add(expirationTime),
|
|
}
|
|
|
|
return dao.RuntimeDB.Transaction(func(tx *gorm.DB) error {
|
|
if err := tx.Where("address = ?", address).
|
|
Where("token = ?", normalizedToken).
|
|
Where("amount_scaled = ?", scaledAmount).
|
|
Where("expires_at <= ?", now).
|
|
Delete(&mdb.TransactionLock{}).Error; err != nil {
|
|
return err
|
|
}
|
|
if err := tx.Where("trade_id = ?", tradeID).Delete(&mdb.TransactionLock{}).Error; err != nil {
|
|
return err
|
|
}
|
|
|
|
result := tx.Clauses(clause.OnConflict{DoNothing: true}).Create(lock)
|
|
if result.Error != nil {
|
|
return result.Error
|
|
}
|
|
if result.RowsAffected == 0 {
|
|
return ErrTransactionLocked
|
|
}
|
|
return nil
|
|
})
|
|
}
|
|
|
|
// UnLockTransaction releases the reservation for address+token+amount.
|
|
func UnLockTransaction(address string, token string, amount float64) error {
|
|
scaledAmount, _ := normalizeLockAmount(amount)
|
|
return dao.RuntimeDB.
|
|
Where("address = ?", address).
|
|
Where("token = ?", normalizeLockToken(token)).
|
|
Where("amount_scaled = ?", scaledAmount).
|
|
Delete(&mdb.TransactionLock{}).Error
|
|
}
|
|
|
|
func UnLockTransactionByTradeId(tradeID string) error {
|
|
return dao.RuntimeDB.Where("trade_id = ?", tradeID).Delete(&mdb.TransactionLock{}).Error
|
|
}
|
|
|
|
func CleanupExpiredTransactionLocks() error {
|
|
return dao.RuntimeDB.Where("expires_at <= ?", time.Now()).Delete(&mdb.TransactionLock{}).Error
|
|
}
|