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