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