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

128 lines
4.2 KiB
Go
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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