314 lines
9.6 KiB
Go
314 lines
9.6 KiB
Go
package usecase
|
||
|
||
import (
|
||
"code.30cm.net/digimon/app-cloudep-wallet-service/pkg/domain/entity"
|
||
"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) {
|
||
// 找不到錢包存不存在
|
||
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})
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
return nil
|
||
}
|
||
}
|
||
|
||
// withSubAvailable 減少用戶可用餘額
|
||
func (use *WalletUseCase) withSubAvailable() walletActionOption {
|
||
return func(_ context.Context, tx *usecase.WalletTransferRequest, w repository.UserWalletService) error {
|
||
if err := w.DecreaseBalance(wallet.TypeAvailable, tx.ReferenceOrderID, tx.Amount); err != nil {
|
||
if errors.Is(err, repository.ErrBalanceInsufficient) {
|
||
// todo 錯誤要看怎麼給(餘額不足)
|
||
return fmt.Errorf("balance insufficient")
|
||
}
|
||
|
||
return err
|
||
}
|
||
|
||
return nil
|
||
}
|
||
}
|
||
|
||
// 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
|
||
}
|
||
}
|
||
|
||
// 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
|
||
}
|
||
}
|
||
|
||
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
|
||
}
|
||
}
|
||
|
||
//// 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
|
||
// }
|
||
//}
|
||
|
||
//// 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
|
||
// }
|
||
//}
|