2025-04-17 09:00:42 +00:00
|
|
|
|
package usecase
|
|
|
|
|
|
|
|
|
|
import (
|
2025-04-30 01:43:16 +00:00
|
|
|
|
"code.30cm.net/digimon/app-cloudep-wallet-service/pkg/domain/entity"
|
2025-04-17 09:00:42 +00:00
|
|
|
|
"code.30cm.net/digimon/app-cloudep-wallet-service/pkg/domain/repository"
|
|
|
|
|
"code.30cm.net/digimon/app-cloudep-wallet-service/pkg/domain/usecase"
|
|
|
|
|
"code.30cm.net/digimon/app-cloudep-wallet-service/pkg/domain/wallet"
|
|
|
|
|
"code.30cm.net/digimon/library-go/errs"
|
|
|
|
|
"context"
|
2025-04-26 11:12:47 +00:00
|
|
|
|
"errors"
|
|
|
|
|
"github.com/go-sql-driver/mysql"
|
|
|
|
|
"github.com/sirupsen/logrus"
|
2025-04-17 09:00:42 +00:00
|
|
|
|
"sync"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
type WalletUseCaseParam struct {
|
|
|
|
|
WalletRepo repository.WalletRepository
|
|
|
|
|
TransactionRepo repository.TransactionRepository
|
|
|
|
|
WalletTransactionRepo repository.WalletTransactionRepo
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type WalletUseCase struct {
|
|
|
|
|
WalletUseCaseParam
|
|
|
|
|
|
|
|
|
|
// 內存讀寫所記錄有哪些玩家已經確認有存在錢包過了,減少確認錢包是否存在頻率
|
|
|
|
|
sync.RWMutex
|
|
|
|
|
existUIDAsset map[string]struct{}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (use *WalletUseCase) Process(ctx context.Context, req usecase.WalletTransferRequest) error {
|
|
|
|
|
//TODO implement me
|
|
|
|
|
panic("implement me")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Withdraw 提幣
|
|
|
|
|
// 1. 新增一筆提幣交易
|
|
|
|
|
// 2. 錢包減少可用餘額
|
|
|
|
|
// 3. 錢包變化新增一筆減少可用餘額資料
|
|
|
|
|
func (use *WalletUseCase) Withdraw(ctx context.Context, tx usecase.WalletTransferRequest) error {
|
|
|
|
|
if !tx.Amount.IsPositive() {
|
|
|
|
|
return errs.InvalidRange("failed to get correct amount")
|
|
|
|
|
}
|
|
|
|
|
tx.TxType = wallet.Withdraw
|
|
|
|
|
|
|
|
|
|
return use.ProcessTransaction(ctx, tx, userWalletFlow{
|
|
|
|
|
UID: tx.FromUID,
|
|
|
|
|
Asset: tx.Asset,
|
2025-04-21 07:46:43 +00:00
|
|
|
|
Actions: []walletActionOption{use.withLockAvailable(), use.withSubAvailable()},
|
2025-04-17 09:00:42 +00:00
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
2025-04-21 07:46:43 +00:00
|
|
|
|
// Deposit 充值
|
|
|
|
|
// 1. 新增一筆充值交易
|
|
|
|
|
// 2. 錢包增加可用餘額
|
|
|
|
|
// 3. 錢包變化新增一筆增加可用餘額資料
|
2025-04-17 09:00:42 +00:00
|
|
|
|
func (use *WalletUseCase) Deposit(ctx context.Context, tx usecase.WalletTransferRequest) error {
|
2025-04-21 07:46:43 +00:00
|
|
|
|
// 確認錢包新增或減少的餘額是否正確
|
|
|
|
|
if !tx.Amount.IsPositive() {
|
|
|
|
|
return errs.InvalidRange("failed to get correct amount")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
tx.TxType = wallet.Deposit
|
|
|
|
|
|
2025-04-26 11:12:47 +00:00
|
|
|
|
uidAsset := uidAssetKey{
|
|
|
|
|
uid: tx.FromUID,
|
|
|
|
|
asset: tx.Asset,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
exists := use.checkWalletExistence(uidAsset)
|
|
|
|
|
|
|
|
|
|
withLockAvailable := func(ctx context.Context, tx *usecase.WalletTransferRequest, w repository.UserWalletService) error {
|
|
|
|
|
if !exists {
|
|
|
|
|
checkWallet, err := w.HasAvailableBalance(ctx)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 錢包不存在要做新增
|
|
|
|
|
if !checkWallet {
|
|
|
|
|
if _, err := w.InitializeWallets(ctx, tx.Brand); err != nil {
|
|
|
|
|
var mysqlErr *mysql.MySQLError
|
|
|
|
|
// 解析是否被其他 transaction insert 了,是的話嘗試取得 insert 後的鎖,不是的話需要直接回傳錯誤
|
|
|
|
|
if errors.As(err, &mysqlErr) && mysqlErr.Number == 1062 {
|
|
|
|
|
logrus.WithFields(logrus.Fields{
|
|
|
|
|
"err": err,
|
|
|
|
|
"uid": tx.FromUID,
|
|
|
|
|
}).Warn("Deposit.Create.Wallet")
|
|
|
|
|
} else {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// 因為是透過 transaction 新增,所以不用上鎖
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
exists = true
|
|
|
|
|
use.markWalletAsExisting(uidAsset)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 確認有 wallet 再 lock for update,避免 deadlock
|
|
|
|
|
_, err := w.GetBalancesForUpdate(ctx, []wallet.Types{wallet.TypeAvailable})
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2025-04-21 07:46:43 +00:00
|
|
|
|
if err := use.ProcessTransaction(
|
|
|
|
|
ctx, tx, userWalletFlow{
|
|
|
|
|
UID: tx.FromUID,
|
|
|
|
|
Asset: tx.Asset,
|
2025-04-26 11:12:47 +00:00
|
|
|
|
Actions: []walletActionOption{withLockAvailable, use.withAddAvailable()},
|
2025-04-21 07:46:43 +00:00
|
|
|
|
}); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
2025-04-17 09:00:42 +00:00
|
|
|
|
}
|
|
|
|
|
|
2025-04-26 11:12:47 +00:00
|
|
|
|
// DepositUnconfirmed 增加限制餘額
|
|
|
|
|
// 1. 新增一筆充值限制交易
|
|
|
|
|
// 2. 錢包新增限制餘額
|
|
|
|
|
// 3. 錢包變化新增一筆增加限制餘額資料
|
2025-04-17 09:00:42 +00:00
|
|
|
|
func (use *WalletUseCase) DepositUnconfirmed(ctx context.Context, tx usecase.WalletTransferRequest) error {
|
2025-04-26 11:12:47 +00:00
|
|
|
|
// 確認錢包新增或減少的餘額是否正確
|
|
|
|
|
if !tx.Amount.IsPositive() {
|
|
|
|
|
return errs.InvalidRange("failed to get correct amount")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
uidAsset := uidAssetKey{
|
|
|
|
|
uid: tx.FromUID,
|
|
|
|
|
asset: tx.Asset,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
exists := use.checkWalletExistence(uidAsset)
|
|
|
|
|
tx.TxType = wallet.DepositUnconfirmed
|
|
|
|
|
|
|
|
|
|
withLockUnconfirmed := func(ctx context.Context, tx *usecase.WalletTransferRequest, w repository.UserWalletService) error {
|
|
|
|
|
if !exists {
|
|
|
|
|
checkWallet, err := w.HasAvailableBalance(ctx)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 錢包不存在要做新增
|
|
|
|
|
if !checkWallet {
|
|
|
|
|
if _, err := w.InitializeWallets(ctx, tx.Brand); err != nil {
|
|
|
|
|
var mysqlErr *mysql.MySQLError
|
|
|
|
|
// 解析是否被其他 transaction insert 了,是的話嘗試取得 insert 後的鎖,不是的話需要直接回傳錯誤
|
|
|
|
|
if errors.As(err, &mysqlErr) && mysqlErr.Number == 1062 {
|
|
|
|
|
logrus.WithFields(logrus.Fields{
|
|
|
|
|
"err": err,
|
|
|
|
|
"uid": tx.FromUID,
|
|
|
|
|
}).Warn("Deposit.Create.Wallet")
|
|
|
|
|
} else {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// 因為是透過 transaction 新增,所以不用上鎖
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
exists = true
|
|
|
|
|
use.markWalletAsExisting(uidAsset)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 確認有 wallet 再 lock for update,避免 deadlock
|
|
|
|
|
_, err := w.GetBalancesForUpdate(ctx, []wallet.Types{wallet.TypeUnconfirmed})
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if err := use.ProcessTransaction(
|
|
|
|
|
ctx, tx, userWalletFlow{
|
|
|
|
|
UID: tx.FromUID,
|
|
|
|
|
Asset: tx.Asset,
|
|
|
|
|
Actions: []walletActionOption{withLockUnconfirmed, use.withAddUnconfirmed()},
|
|
|
|
|
}); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
2025-04-17 09:00:42 +00:00
|
|
|
|
}
|
|
|
|
|
|
2025-04-26 11:12:47 +00:00
|
|
|
|
// Freeze 凍結
|
|
|
|
|
// 1. 新增一筆凍結交易
|
|
|
|
|
// 2. 錢包減少可用餘額
|
|
|
|
|
// 3. 錢包增加凍結餘額
|
|
|
|
|
// 4. 錢包變化新增一筆減少可用餘額資料
|
|
|
|
|
// 5. 錢包變化新增一筆增加凍結餘額資料
|
|
|
|
|
// 6. 訂單錢包新增一筆資料,餘額是凍結金額
|
2025-04-17 09:00:42 +00:00
|
|
|
|
func (use *WalletUseCase) Freeze(ctx context.Context, tx usecase.WalletTransferRequest) error {
|
2025-04-26 11:12:47 +00:00
|
|
|
|
// 確認錢包新增或減少的餘額是否正確
|
|
|
|
|
if !tx.Amount.IsPositive() {
|
|
|
|
|
return errs.InvalidRange("failed to get correct amount")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
tx.TxType = wallet.DepositUnconfirmed
|
|
|
|
|
|
|
|
|
|
return use.ProcessTransaction(ctx, tx, userWalletFlow{
|
|
|
|
|
UID: tx.FromUID,
|
|
|
|
|
Asset: tx.Asset,
|
|
|
|
|
Actions: []walletActionOption{use.withLockAvailableAndFreeze(), use.withSubAvailable(), use.withAddFreeze()},
|
|
|
|
|
})
|
2025-04-17 09:00:42 +00:00
|
|
|
|
}
|
|
|
|
|
|
2025-04-26 11:12:47 +00:00
|
|
|
|
// AppendFreeze 追加凍結金額
|
|
|
|
|
// 1. 新增一筆凍結交易
|
|
|
|
|
// 2. 錢包減少可用餘額
|
|
|
|
|
// 3. 錢包增加凍結餘額
|
|
|
|
|
// 4. 錢包變化新增一筆減少可用餘額資料
|
|
|
|
|
// 5. 錢包變化新增一筆增加凍結餘額資料
|
|
|
|
|
// 6. 原凍結金額上追加凍結金額
|
2025-04-17 09:00:42 +00:00
|
|
|
|
func (use *WalletUseCase) AppendFreeze(ctx context.Context, tx usecase.WalletTransferRequest) error {
|
2025-04-26 11:12:47 +00:00
|
|
|
|
// 確認錢包新增或減少的餘額是否正確
|
|
|
|
|
if !tx.Amount.IsPositive() {
|
|
|
|
|
return errs.InvalidRange("failed to get correct amount")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
tx.TxType = wallet.DepositUnconfirmed
|
|
|
|
|
|
|
|
|
|
return use.ProcessTransaction(ctx, tx, userWalletFlow{
|
|
|
|
|
UID: tx.FromUID,
|
|
|
|
|
Asset: tx.Asset,
|
|
|
|
|
Actions: []walletActionOption{use.withLockAvailableAndFreeze(), use.withSubAvailable(), use.withAddFreeze()},
|
|
|
|
|
})
|
2025-04-17 09:00:42 +00:00
|
|
|
|
}
|
|
|
|
|
|
2025-04-26 12:01:05 +00:00
|
|
|
|
// UnFreeze 解凍
|
|
|
|
|
// 1. 新增一筆解凍交易
|
|
|
|
|
// 2. 減少order餘額
|
|
|
|
|
// 3. 錢包減少凍結餘額
|
|
|
|
|
// 4. 錢包變化新增一筆減少凍結餘額資料
|
2025-04-17 09:00:42 +00:00
|
|
|
|
func (use *WalletUseCase) UnFreeze(ctx context.Context, tx usecase.WalletTransferRequest) error {
|
2025-04-26 12:01:05 +00:00
|
|
|
|
// 確認錢包新增或減少的餘額是否正確
|
|
|
|
|
if !tx.Amount.IsPositive() {
|
|
|
|
|
return errs.InvalidRange("failed to get correct amount")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
tx.TxType = wallet.UnFreeze
|
|
|
|
|
|
|
|
|
|
return use.ProcessTransaction(ctx, tx, userWalletFlow{
|
|
|
|
|
UID: tx.FromUID,
|
|
|
|
|
Asset: tx.Asset,
|
|
|
|
|
Actions: []walletActionOption{use.withSubOrder(nil), use.withLockFreeze(), use.withSubFreeze()},
|
|
|
|
|
})
|
2025-04-17 09:00:42 +00:00
|
|
|
|
}
|
|
|
|
|
|
2025-04-30 01:43:16 +00:00
|
|
|
|
// RollbackFreeze Rollback 凍結,不可指定金額(剩餘order凍結金額)
|
|
|
|
|
// 1. 新增一筆 Rollback 凍結交易
|
|
|
|
|
// 2. 減少 order 餘額
|
|
|
|
|
// 3. 錢包減少凍結餘額
|
|
|
|
|
// 4. 錢包增加可用餘額
|
|
|
|
|
// 5. 錢包變化新增一筆減少凍結餘額資料
|
|
|
|
|
// 6. 錢包變化新增一筆增加可用餘額資料
|
2025-04-17 09:00:42 +00:00
|
|
|
|
func (use *WalletUseCase) RollbackFreeze(ctx context.Context, tx usecase.WalletTransferRequest) error {
|
2025-04-30 01:43:16 +00:00
|
|
|
|
tx.TxType = wallet.RollbackFreeze
|
|
|
|
|
|
|
|
|
|
return use.ProcessTransaction(ctx, tx, userWalletFlow{
|
|
|
|
|
UID: tx.FromUID,
|
|
|
|
|
Asset: tx.Asset,
|
|
|
|
|
Actions: []walletActionOption{use.withSubOrder(func(tx *usecase.Transaction, order entity.Transaction) {
|
|
|
|
|
tx.Amount = order.Balance
|
|
|
|
|
}), use.withLockAvailableAndFreeze(), use.withSubFreeze(), use.withAddAvailable()},
|
|
|
|
|
})
|
|
|
|
|
|
2025-04-17 09:00:42 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (use *WalletUseCase) RollbackFreezeAddAvailable(ctx context.Context, tx usecase.WalletTransferRequest) error {
|
|
|
|
|
//TODO implement me
|
|
|
|
|
panic("implement me")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (use *WalletUseCase) CancelFreeze(ctx context.Context, tx usecase.WalletTransferRequest) error {
|
|
|
|
|
//TODO implement me
|
|
|
|
|
panic("implement me")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (use *WalletUseCase) Unconfirmed(ctx context.Context, tx usecase.WalletTransferRequest) error {
|
|
|
|
|
//TODO implement me
|
|
|
|
|
panic("implement me")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (use *WalletUseCase) Balance(ctx context.Context, req usecase.BalanceReq) ([]usecase.Balance, error) {
|
|
|
|
|
//TODO implement me
|
|
|
|
|
panic("implement me")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (use *WalletUseCase) HistoryBalance(ctx context.Context, req usecase.BalanceReq) ([]usecase.Balance, error) {
|
|
|
|
|
//TODO implement me
|
|
|
|
|
panic("implement me")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (use *WalletUseCase) BalanceByAssets(ctx context.Context, uid, cryptoCode string, walletTypes []wallet.Types) (usecase.BalanceAssetsResp, error) {
|
|
|
|
|
//TODO implement me
|
|
|
|
|
panic("implement me")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (use *WalletUseCase) CheckBalance(ctx context.Context, tx usecase.WalletTransferRequest) error {
|
|
|
|
|
//TODO implement me
|
|
|
|
|
panic("implement me")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (use *WalletUseCase) GetTodayWithdraw(ctx context.Context, uid, toCrypto string) (usecase.TodayWithdrawResp, error) {
|
|
|
|
|
//TODO implement me
|
|
|
|
|
panic("implement me")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func MustWalletUseCase(param WalletUseCaseParam) usecase.WalletTransferUseCase {
|
|
|
|
|
return &WalletUseCase{
|
|
|
|
|
WalletUseCaseParam: param,
|
|
|
|
|
existUIDAsset: make(map[string]struct{}),
|
|
|
|
|
}
|
|
|
|
|
}
|