128 lines
4.2 KiB
Go
128 lines
4.2 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"
|
||
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{}{}
|
||
}
|