app-cloudep-wallet-service/pkg/usecase/wallet_tx_option.go

314 lines
9.6 KiB
Go
Raw Normal View History

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
// }
//}