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

128 lines
4.2 KiB
Go
Raw Normal View History

2025-04-17 09:00:42 +00:00
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"
repo "code.30cm.net/digimon/app-cloudep-wallet-service/pkg/repository"
"context"
"fmt"
"github.com/google/uuid"
"gorm.io/gorm"
)
// userWalletFlow 表示一組要對某使用者、某資產執行的錢包操作(動作串列)
// 這些操作通常會在轉帳流程中依序套用,例如:從主錢包扣款 → 加到對方凍結錢包
type userWalletFlow struct {
UID string // 目標使用者 UID
Asset string // 目標資產代號(如 BTC、ETH、TWD
Actions []walletActionOption // 要依序執行的錢包操作
}
// ProcessTransaction 處理一次完整的「錢包 + 訂單」交易流程:
// 1. 透過 Repository 開啟 DB 事務
// 2. 依序對每個 userWalletFlow 建立對應的 UserWalletService 實例
// 3. 依序執行每個 flow.Actions扣款、加值、凍結、解凍…
// 4. 建立一條 Transaction 記錄並寫進 transactionRepository
// 5. 將所有 walletTransactions 寫進 walletTransactionRepository
// 6. 最後在同一事務中執行每個 wallet 的 Execute / ExecuteOrder
// 7. 特別注意 flow 會按照順序做,所以順序是重要的
func (use *WalletUseCase) ProcessTransaction(
ctx context.Context,
req usecase.WalletTransferRequest,
flows ...userWalletFlow,
) error {
return use.WalletRepo.Transaction(func(db *gorm.DB) error {
// 暫存所有建立好的 UserWalletService
wallets := make([]repository.UserWalletService, 0, len(flows))
// flows 會按照順序做.順序是重要的
for _, flow := range flows {
// 1⃣ 建立針對該使用者+資產的 UserWalletService
wSvc := repo.NewUserWallet(db, flow.UID, flow.Asset)
// 2⃣ 依序執行所有定義好的錢包操作
for _, action := range flow.Actions {
if err := action(ctx, &req, wSvc); err != nil {
return err
}
}
wallets = append(wallets, wSvc)
}
// 3⃣ 準備寫入 Transaction 主檔
txRecord := &entity.Transaction{
OrderID: req.ReferenceOrderID,
TransactionID: uuid.New().String(),
UID: req.FromUID,
ToUID: req.ToUID,
Asset: req.Asset,
TxType: req.TxType,
Amount: req.Amount,
Brand: req.Brand,
PostTransferBalance: req.PostTransferBalance,
BusinessType: req.Business.ToInt8(),
Status: wallet.EnableTrue,
DueTime: 0,
}
// 4⃣ TODO 計算 DueTime T+N 結算時間)
// 5⃣ 寫入 Transaction 主檔
if err := use.TransactionRepo.Insert(ctx, txRecord); err != nil {
return fmt.Errorf("TransactionRepo.Insert 失敗: %w", err)
}
// 6⃣ 聚合所有 wallet 內的交易歷程
var walletTxs []entity.WalletTransaction
for _, w := range wallets {
walletTxs = append(
walletTxs,
w.Transactions(
txRecord.ID,
txRecord.OrderID,
req.Brand,
req.Business,
)...,
)
}
// 7⃣ 批次寫入所有 WalletTransaction
if err := use.WalletTransactionRepo.Create(ctx, db, walletTxs); err != nil {
return fmt.Errorf("WalletTransactionRepository.Create 失敗: %w", err)
}
// 8⃣ 最後才真正把錢包的餘額更新到資料庫(同一事務)
for _, wSvc := range wallets {
if err := wSvc.Commit(ctx); err != nil {
return err
}
if err := wSvc.CommitOrder(ctx); err != nil {
return err
}
}
return nil
})
}
// checkWalletExistence 檢查錢包是否存在於內存中
func (use *WalletUseCase) checkWalletExistence(uidAsset uidAssetKey) bool {
use.RLock()
defer use.RUnlock()
rk := fmt.Sprintf("%s-%s", uidAsset.uid, uidAsset.asset)
_, exists := use.existUIDAsset[rk]
return exists
}
// markWalletAsExisting 標記錢包為存在
func (use *WalletUseCase) markWalletAsExisting(uidAsset uidAssetKey) {
use.Lock()
defer use.Unlock()
rk := fmt.Sprintf("%s-%s", uidAsset.uid, uidAsset.asset)
use.existUIDAsset[rk] = struct{}{}
}