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

314 lines
9.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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