2025-04-17 09:00:42 +00:00
|
|
|
|
package usecase
|
|
|
|
|
|
|
|
|
|
import (
|
2025-04-26 12:01:05 +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"
|
|
|
|
|
"context"
|
|
|
|
|
"errors"
|
|
|
|
|
"fmt"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
type uidAssetKey struct {
|
|
|
|
|
uid string
|
|
|
|
|
asset string
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// walletActionOption 表示一個「錢包操作函式」,可在錢包流程中插入自定義動作(例如:扣款、加值、凍結)
|
|
|
|
|
type walletActionOption func(
|
|
|
|
|
ctx context.Context,
|
|
|
|
|
tx *usecase.WalletTransferRequest,
|
|
|
|
|
wallet repository.UserWalletService,
|
|
|
|
|
) error
|
|
|
|
|
|
|
|
|
|
// withLockAvailable 鎖定用戶可用餘額
|
|
|
|
|
func (use *WalletUseCase) withLockAvailable() walletActionOption {
|
|
|
|
|
return func(ctx context.Context, tx *usecase.WalletTransferRequest, w repository.UserWalletService) error {
|
|
|
|
|
uidAsset := uidAssetKey{
|
|
|
|
|
uid: tx.FromUID,
|
|
|
|
|
asset: tx.Asset,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if !use.checkWalletExistence(uidAsset) {
|
|
|
|
|
// 找不到錢包存不存在
|
2025-04-18 09:10:40 +00:00
|
|
|
|
wStatus, err := w.HasAvailableBalance(ctx)
|
2025-04-17 09:00:42 +00:00
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("failed to check wallet: %w", err)
|
|
|
|
|
}
|
|
|
|
|
// 錢包不存在要做新增
|
|
|
|
|
if !wStatus {
|
|
|
|
|
//// 是合約模擬交易或帳變且錢包不存在才建立錢包
|
|
|
|
|
//if !(tx.Business == wa.ContractSimulationBusinessTypeBusinessName || tx.BusinessType == domain.DistributionBusinessTypeBusinessName) {
|
|
|
|
|
// // 新增錢包有命中 UK 不需要額外上鎖
|
|
|
|
|
// return use.translateError(err)
|
|
|
|
|
//}
|
|
|
|
|
|
2025-04-18 09:10:40 +00:00
|
|
|
|
if _, err := w.InitializeWallets(ctx, tx.Brand); err != nil {
|
2025-04-17 09:00:42 +00:00
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
use.markWalletAsExisting(uidAsset)
|
|
|
|
|
}
|
|
|
|
|
|
2025-04-18 09:10:40 +00:00
|
|
|
|
_, err := w.GetBalancesForUpdate(ctx, []wallet.Types{wallet.TypeAvailable})
|
2025-04-17 09:00:42 +00:00
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-04-21 07:46:43 +00:00
|
|
|
|
// withSubAvailable 減少用戶可用餘額
|
2025-04-17 09:00:42 +00:00
|
|
|
|
func (use *WalletUseCase) withSubAvailable() walletActionOption {
|
|
|
|
|
return func(_ context.Context, tx *usecase.WalletTransferRequest, w repository.UserWalletService) error {
|
2025-04-18 09:10:40 +00:00
|
|
|
|
if err := w.DecreaseBalance(wallet.TypeAvailable, tx.ReferenceOrderID, tx.Amount); err != nil {
|
2025-04-17 09:00:42 +00:00
|
|
|
|
if errors.Is(err, repository.ErrBalanceInsufficient) {
|
|
|
|
|
// todo 錯誤要看怎麼給(餘額不足)
|
|
|
|
|
return fmt.Errorf("balance insufficient")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-04-21 07:46:43 +00:00
|
|
|
|
|
|
|
|
|
// withAddAvailable 增加用戶可用餘額
|
|
|
|
|
func (use *WalletUseCase) withAddAvailable() walletActionOption {
|
|
|
|
|
return func(_ context.Context, tx *usecase.WalletTransferRequest, w repository.UserWalletService) error {
|
|
|
|
|
if err := w.IncreaseBalance(wallet.TypeAvailable, tx.ReferenceOrderID, tx.Amount); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// withAddFreeze 增加用戶凍結餘額
|
|
|
|
|
func (use *WalletUseCase) withAddFreeze() walletActionOption {
|
|
|
|
|
return func(_ context.Context, tx *usecase.WalletTransferRequest, w repository.UserWalletService) error {
|
|
|
|
|
if err := w.IncreaseBalance(wallet.TypeFreeze, tx.ReferenceOrderID, tx.Amount); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
// 訂單可以做解凍,解凍最大上限金額來自當初凍結金額,所以在每一筆tx可以設定Balance
|
|
|
|
|
// 後續tx需要依據其他tx做交易時能有所依據
|
|
|
|
|
tx.PostTransferBalance = tx.Amount
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-04-26 11:12:47 +00:00
|
|
|
|
// withAddUnconfirmed 增加用戶限制餘額
|
|
|
|
|
func (use *WalletUseCase) withAddUnconfirmed() walletActionOption {
|
|
|
|
|
return func(_ context.Context, tx *usecase.WalletTransferRequest, w repository.UserWalletService) error {
|
|
|
|
|
err := w.IncreaseBalance(wallet.TypeUnconfirmed, tx.ReferenceOrderID, tx.Amount)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// withLockAvailableAndFreeze 用戶可用與凍結餘額
|
|
|
|
|
func (use *WalletUseCase) withLockAvailableAndFreeze() walletActionOption {
|
|
|
|
|
return func(ctx context.Context, tx *usecase.WalletTransferRequest, w repository.UserWalletService) error {
|
|
|
|
|
uidAsset := uidAssetKey{
|
|
|
|
|
uid: tx.FromUID,
|
|
|
|
|
asset: tx.Asset,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if !use.checkWalletExistence(uidAsset) {
|
|
|
|
|
// 找不到錢包存不存在
|
|
|
|
|
wStatus, err := w.HasAvailableBalance(ctx)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("failed to check wallet: %w", err)
|
|
|
|
|
}
|
|
|
|
|
// 錢包不存在要做新增
|
|
|
|
|
if !wStatus {
|
|
|
|
|
//// 是合約模擬交易或帳變且錢包不存在才建立錢包
|
|
|
|
|
//if !(tx.Business == wa.ContractSimulationBusinessTypeBusinessName || tx.BusinessType == domain.DistributionBusinessTypeBusinessName) {
|
|
|
|
|
// // 新增錢包有命中 UK 不需要額外上鎖
|
|
|
|
|
// return use.translateError(err)
|
|
|
|
|
//}
|
|
|
|
|
|
|
|
|
|
if _, err := w.InitializeWallets(ctx, tx.Brand); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
use.markWalletAsExisting(uidAsset)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_, err := w.GetBalancesForUpdate(ctx, []wallet.Types{wallet.TypeAvailable, wallet.TypeFreeze})
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// appendFreeze 追加用戶原凍結餘額
|
|
|
|
|
func (use *WalletUseCase) withAppendFreeze() walletActionOption {
|
|
|
|
|
return func(ctx context.Context, tx *usecase.WalletTransferRequest, w repository.UserWalletService) error {
|
|
|
|
|
order, err := w.GetOrderBalance(ctx, tx.ReferenceOrderID)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 以id來做lock更可以確保只lock到該筆,而不會因為index關係lock到多筆導致死鎖
|
|
|
|
|
// 而且先不lock把資料先拉出來判斷餘額是否足夠,在不足夠時可以直接return而不用lock減少開銷
|
|
|
|
|
order, err = w.GetOrderBalanceForUpdate(ctx, tx.ReferenceOrderID)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
tx.Asset = order.Asset
|
|
|
|
|
tx.FromUID = order.UID
|
|
|
|
|
|
|
|
|
|
//w.IncreaseBalance()
|
|
|
|
|
//if err := w.AddOrder(order.ID, tx.Amount); err != nil {
|
|
|
|
|
// return use.translateError(err)
|
|
|
|
|
//}
|
|
|
|
|
//
|
|
|
|
|
//if err := wallet.AddFreeze(tx.BusinessType, tx.Amount); err != nil {
|
|
|
|
|
// return use.translateError(err)
|
|
|
|
|
//}
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-04-26 12:01:05 +00:00
|
|
|
|
func (use *WalletUseCase) withSubOrder(fTx func(*usecase.WalletTransferRequest, entity.Transaction)) walletActionOption {
|
|
|
|
|
return func(ctx context.Context, tx *usecase.WalletTransferRequest, w repository.UserWalletService) error {
|
|
|
|
|
order, err := w.GetOrderBalance(ctx, tx.ReferenceOrderID)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 檢查order wallet 餘額<=0
|
|
|
|
|
if !order.Amount.IsPositive() {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 以id來做lock更可以確保只lock到該筆,而不會因為index關係lock到多筆導致死鎖
|
|
|
|
|
// 而且先不lock把資料先拉出來判斷餘額是否足夠,在不足夠時可以直接return而不用lock減少開銷
|
|
|
|
|
order, err = w.GetOrderBalanceForUpdate(ctx, order.OrderID)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
tx.Asset = order.Asset
|
|
|
|
|
tx.FromUID = order.UID
|
|
|
|
|
|
|
|
|
|
//// 解凍BusinessType,必須跟建立凍結訂單的BusinessType一致
|
|
|
|
|
//if tx.Business != domain.SystemTransferCommissionBusinessTypeBusinessName {
|
|
|
|
|
// // 只有系統劃轉會有轉入錢包不同的狀況
|
|
|
|
|
// tx.BusinessType = domain.BusinessTypeToString(order.BusinessType)
|
|
|
|
|
//}
|
|
|
|
|
|
|
|
|
|
if fTx != nil {
|
|
|
|
|
fTx(tx, order)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if err := w.AddOrderBalance(ctx, order.OrderID, tx.Amount.Neg()); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (use *WalletUseCase) withLockFreeze() walletActionOption {
|
|
|
|
|
return func(ctx context.Context, tx *usecase.WalletTransferRequest, w repository.UserWalletService) error {
|
|
|
|
|
_, err := w.GetBalancesForUpdate(ctx, []wallet.Types{wallet.TypeFreeze})
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (use *WalletUseCase) withSubFreeze() walletActionOption {
|
|
|
|
|
return func(ctx context.Context, tx *usecase.WalletTransferRequest, w repository.UserWalletService) error {
|
|
|
|
|
if err := w.DecreaseBalance(wallet.TypeFreeze, tx.ReferenceOrderID, tx.Amount); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-04-26 11:12:47 +00:00
|
|
|
|
//// withSubFreeze 減少用戶凍結餘額
|
|
|
|
|
//func (use *WalletUseCase) withSubFreeze() walletActionOption {
|
|
|
|
|
// return func(_ context.Context, tx *usecase.Transaction, wallet repository.UserWallet) error {
|
|
|
|
|
// if err := wallet.SubFreeze(tx.BusinessType, tx.Amount); err != nil {
|
|
|
|
|
// if errors.Is(err, repository.ErrBalanceInsufficient) {
|
|
|
|
|
// return usecase.BalanceInsufficientError{
|
|
|
|
|
// Amount: tx.Amount.Neg(),
|
|
|
|
|
// Balance: wallet.LocalBalance(domain.WalletFreezeType.ToBusiness(tx.BusinessType)),
|
|
|
|
|
// }
|
|
|
|
|
// }
|
|
|
|
|
//
|
|
|
|
|
// return use.translateError(err)
|
|
|
|
|
// }
|
|
|
|
|
//
|
|
|
|
|
// return nil
|
|
|
|
|
// }
|
|
|
|
|
//}
|
|
|
|
|
//
|
|
|
|
|
//// addFreeze 增加用戶凍結餘額
|
|
|
|
|
//func (use *walletUseCase) addFreeze() walletActionOption {
|
|
|
|
|
// return func(_ context.Context, tx *usecase.Transaction, wallet repository.UserWallet) error {
|
|
|
|
|
// if err := wallet.AddFreeze(tx.BusinessType, tx.Amount); err != nil {
|
|
|
|
|
// return use.translateError(err)
|
|
|
|
|
// }
|
|
|
|
|
//
|
|
|
|
|
// // 訂單可以做解凍,解凍最大上限金額來自當初凍結金額,所以在每一筆tx可以設定Balance
|
|
|
|
|
// // 後續tx需要依據其他tx做交易時能有所依據
|
|
|
|
|
// tx.Balance = tx.Amount
|
|
|
|
|
//
|
|
|
|
|
// return nil
|
|
|
|
|
// }
|
|
|
|
|
//}
|
|
|
|
|
|
2025-04-21 07:46:43 +00:00
|
|
|
|
//// WithAppendFreeze 追加用戶原凍結餘額
|
|
|
|
|
//func (use *WalletUseCase) withAppendFreeze() walletActionOption {
|
|
|
|
|
// return func(ctx context.Context, tx *usecase.WalletTransferRequest, w repository.UserWalletService) error {
|
|
|
|
|
// order, err := wallet.GetOrderBalance(ctx, tx.ReferenceOrderIDs)
|
|
|
|
|
// if err != nil {
|
|
|
|
|
// return use.translateError(err)
|
|
|
|
|
// }
|
|
|
|
|
//
|
|
|
|
|
// // 以id來做lock更可以確保只lock到該筆,而不會因為index關係lock到多筆導致死鎖
|
|
|
|
|
// // 而且先不lock把資料先拉出來判斷餘額是否足夠,在不足夠時可以直接return而不用lock減少開銷
|
|
|
|
|
// order, err = wallet.GetOrderBalanceXLock(ctx, order.ID)
|
|
|
|
|
// if err != nil {
|
|
|
|
|
// return use.translateError(err)
|
|
|
|
|
// }
|
|
|
|
|
//
|
|
|
|
|
// tx.Crypto = order.Crypto
|
|
|
|
|
// tx.UID = order.UID
|
|
|
|
|
//
|
|
|
|
|
// if err := wallet.AddOrder(order.ID, tx.Amount); err != nil {
|
|
|
|
|
// return use.translateError(err)
|
|
|
|
|
// }
|
|
|
|
|
//
|
|
|
|
|
// if err := wallet.AddFreeze(tx.BusinessType, tx.Amount); err != nil {
|
|
|
|
|
// return use.translateError(err)
|
|
|
|
|
// }
|
|
|
|
|
//
|
|
|
|
|
// return nil
|
|
|
|
|
// }
|
|
|
|
|
//}
|