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
|
2025-04-18 09:10:40 +00:00
|
|
|
|
wSvc := repo.NewWalletService(db, flow.UID, flow.Asset)
|
2025-04-17 09:00:42 +00:00
|
|
|
|
|
|
|
|
|
// 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,
|
2025-04-18 09:10:40 +00:00
|
|
|
|
w.PrepareTransactions(
|
2025-04-17 09:00:42 +00:00
|
|
|
|
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 {
|
2025-04-18 09:10:40 +00:00
|
|
|
|
if err := wSvc.PersistBalances(ctx); err != nil {
|
2025-04-17 09:00:42 +00:00
|
|
|
|
return err
|
|
|
|
|
}
|
2025-04-18 09:10:40 +00:00
|
|
|
|
if err := wSvc.PersistOrderBalances(ctx); err != nil {
|
2025-04-17 09:00:42 +00:00
|
|
|
|
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{}{}
|
|
|
|
|
}
|