add wallet and user wallet
This commit is contained in:
parent
4cb6faefdc
commit
59064b0a69
|
@ -0,0 +1,37 @@
|
||||||
|
package entity
|
||||||
|
|
||||||
|
import (
|
||||||
|
"code.30cm.net/digimon/app-cloudep-wallet-service/pkg/domain/wallet"
|
||||||
|
"github.com/shopspring/decimal"
|
||||||
|
)
|
||||||
|
|
||||||
|
//graph TD
|
||||||
|
// A[使用者下單/轉帳/出金等行為] --> B[建立 Transaction 主交易紀錄]
|
||||||
|
// B --> C1[WalletTransaction:可用餘額 -100]
|
||||||
|
// B --> C2[WalletTransaction:凍結餘額 +100]
|
||||||
|
// C1 --> D1[錢包資料庫:更新可用餘額]
|
||||||
|
// C2 --> D2[錢包資料庫:更新凍結餘額]
|
||||||
|
|
||||||
|
// Transaction 表示一次錢包資金交易的紀錄
|
||||||
|
type Transaction struct {
|
||||||
|
ID int64 `gorm:"column:id"` // 資料表主鍵 ID
|
||||||
|
OrderID string `gorm:"column:order_id"` // 外部訂單編號(例如交易訂單編號)
|
||||||
|
TransactionID string `gorm:"column:transaction_id"` // 此筆資金交易的唯一識別碼
|
||||||
|
Brand string `gorm:"column:brand"` // 品牌或平台識別(用於多租戶區分)
|
||||||
|
UID string `gorm:"column:uid"` // 發起交易的用戶 UID
|
||||||
|
ToUID string `gorm:"column:to_uid"` // 接收方用戶 UID(如為轉帳類型)
|
||||||
|
Type wallet.TransactionType `gorm:"column:type"` // 資金異動類型(如充值、提領、轉帳等)
|
||||||
|
BusinessType int8 `gorm:"column:business_type"` // 業務類型(對應平台不同業務,例如系統轉帳、合約轉帳等)
|
||||||
|
Asset string `gorm:"column:asset"` // 幣種(或平台定義的資產名稱)
|
||||||
|
Amount decimal.Decimal `gorm:"column:amount"` // 異動金額(正負表示收入或支出)
|
||||||
|
Balance decimal.Decimal `gorm:"column:balance"` // 異動後錢包餘額
|
||||||
|
BeforeBalance decimal.Decimal `gorm:"column:before_balance"` // 異動前錢包餘額
|
||||||
|
Status bool `gorm:"column:status"` // 狀態(true 表示成功,false 表示失敗或未確認)
|
||||||
|
CreateAt int64 `gorm:"column:create_at;autoCreateTime"` // 建立時間(Unix timestamp)
|
||||||
|
DueTime int64 `gorm:"column:due_time"` // 到期時間(可用於未完成交易的失效邏輯)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TableName 回傳對應的資料表名稱
|
||||||
|
func (t *Transaction) TableName() string {
|
||||||
|
return "transaction"
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
package entity
|
||||||
|
|
||||||
|
import (
|
||||||
|
"code.30cm.net/digimon/app-cloudep-wallet-service/pkg/domain/wallet"
|
||||||
|
"github.com/shopspring/decimal"
|
||||||
|
)
|
||||||
|
|
||||||
|
// WalletTransaction 表示錢包中每一個餘額類型的交易細節紀錄(例如:可用金額、凍結金額分別異動)
|
||||||
|
type WalletTransaction struct {
|
||||||
|
ID int64 `gorm:"column:id"` // 資料表主鍵 ID
|
||||||
|
TransactionID int64 `gorm:"column:transaction_id"` // 對應的主交易 ID(Transaction.ID)
|
||||||
|
OrderID string `gorm:"column:order_id"` // 對應的訂單編號(可能與主交易相同)
|
||||||
|
Brand string `gorm:"column:brand"` // 品牌識別(用於多租戶區分)
|
||||||
|
UID string `gorm:"column:uid"` // 用戶 UID(這筆交易所屬的使用者)
|
||||||
|
WalletType wallet.Types `gorm:"column:wallet_type"` // 錢包類型(例如:可用、凍結、未確認等)
|
||||||
|
BusinessType int8 `gorm:"column:business_type"` // 業務類型(例如:合約、系統轉帳、分潤等)
|
||||||
|
Asset string `gorm:"column:asset"` // 幣別或資產識別碼
|
||||||
|
Amount decimal.Decimal `gorm:"column:amount"` // 異動金額(正數入帳,負數出帳)
|
||||||
|
Balance decimal.Decimal `gorm:"column:balance"` // 異動後的該錢包類型餘額
|
||||||
|
CreateTime int64 `gorm:"column:create_time;autoCreateTime"` // 創建時間(Unix timestamp)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TableName 指定對應的資料表名稱
|
||||||
|
func (t *WalletTransaction) TableName() string {
|
||||||
|
return "wallet_transaction"
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrRecordNotFound = errors.New("query record not found")
|
||||||
|
ErrBalanceInsufficient = errors.New("balance insufficient")
|
||||||
|
)
|
||||||
|
|
||||||
|
func WrapNotFoundError(err error) error {
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return ErrRecordNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
|
@ -0,0 +1,98 @@
|
||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"code.30cm.net/digimon/app-cloudep-wallet-service/pkg/domain/entity"
|
||||||
|
"code.30cm.net/digimon/app-cloudep-wallet-service/pkg/domain/wallet"
|
||||||
|
"context"
|
||||||
|
"github.com/shopspring/decimal"
|
||||||
|
)
|
||||||
|
|
||||||
|
// WalletReader 定義查詢錢包餘額的操作,純讀取,不涉及鎖或修改。
|
||||||
|
type WalletReader interface {
|
||||||
|
// AllBalances 查詢此用戶單一幣別所有錢包(可用/凍結/...)
|
||||||
|
AllBalances(ctx context.Context) ([]entity.Wallet, error)
|
||||||
|
// Balance 查詢單一餘額種類
|
||||||
|
Balance(ctx context.Context, kind wallet.Types) (entity.Wallet, error)
|
||||||
|
// Balances 查詢多個餘額種類
|
||||||
|
Balances(ctx context.Context, kind []wallet.Types) ([]entity.Wallet, error)
|
||||||
|
// LocalBalance 取得本地記憶體暫存餘額
|
||||||
|
LocalBalance(kind wallet.Types) decimal.Decimal
|
||||||
|
}
|
||||||
|
|
||||||
|
// WalletLocker 提供所有需要「加鎖(XLock)」的錢包操作,用於避免併發交易衝突。
|
||||||
|
// 通常搭配交易流程使用,保證同時只有一個執行緒可以修改該錢包類型或訂單相關餘額。
|
||||||
|
type WalletLocker interface {
|
||||||
|
// BalanceXLock 查詢指定錢包類型的餘額,並加上排他鎖(FOR UPDATE)
|
||||||
|
BalanceXLock(ctx context.Context, kind wallet.Types) (entity.Wallet, error)
|
||||||
|
// BalancesXLock 查詢多個錢包類型的餘額,並加上排他鎖(FOR UPDATE)
|
||||||
|
BalancesXLock(ctx context.Context, kind []wallet.Types) ([]entity.Wallet, error)
|
||||||
|
// XLock 針對指定錢包紀錄 ID 加鎖(FOR UPDATE)
|
||||||
|
XLock(ctx context.Context, id int64) (entity.Wallet, error)
|
||||||
|
// XLocks 針對多筆錢包紀錄 ID 加鎖(FOR UPDATE)
|
||||||
|
XLocks(ctx context.Context, ids []int64) ([]entity.Wallet, error)
|
||||||
|
// GetAvailableBalanceXLock 查詢指定業務邏輯下的可用餘額並加鎖
|
||||||
|
GetAvailableBalanceXLock(ctx context.Context, b wallet.BusinessName) (entity.Wallet, error)
|
||||||
|
// GetFreezeBalanceXLock 查詢指定業務邏輯下的凍結餘額並加鎖
|
||||||
|
GetFreezeBalanceXLock(ctx context.Context, b wallet.BusinessName) (entity.Wallet, error)
|
||||||
|
// GetUnconfirmedBalanceXLock 查詢尚未確認(unconfirmed)的餘額並加鎖
|
||||||
|
GetUnconfirmedBalanceXLock(ctx context.Context) (entity.Wallet, error)
|
||||||
|
// GetOrderBalanceXLock 查詢某筆訂單的餘額資料並加鎖
|
||||||
|
GetOrderBalanceXLock(ctx context.Context, id int64) (entity.Transaction, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WalletOperator 負責錢包的餘額加減操作與訂單金額變動邏輯。
|
||||||
|
// 此介面不包含「資料鎖定(Lock)」與「資料持久化(Execute)」等責任,單純聚焦於記錄錢包與訂單餘額的變動邏輯。
|
||||||
|
type WalletOperator interface {
|
||||||
|
// AddBalance 增加指定錢包類型的餘額(使用前必須已查詢該餘額並存在本地記憶體)
|
||||||
|
AddBalance(kind wallet.Types, amount decimal.Decimal) error
|
||||||
|
// AddBalanceSetOrderID 增加指定錢包類型的餘額並記錄訂單 ID(用於與訂單綁定的交易)
|
||||||
|
AddBalanceSetOrderID(orderID string, kind wallet.Types, amount decimal.Decimal) error
|
||||||
|
// AddAvailable 增加可用餘額(根據業務類型自動判斷使用哪種錢包)
|
||||||
|
AddAvailable(b wallet.BusinessName, amount decimal.Decimal) error
|
||||||
|
// AddFreeze 增加凍結餘額(根據業務類型自動判斷使用哪種錢包)
|
||||||
|
AddFreeze(b wallet.BusinessName, amount decimal.Decimal) error
|
||||||
|
// AddUnconfirmed 增加尚未確認(unconfirmed)的餘額
|
||||||
|
AddUnconfirmed(amount decimal.Decimal) error
|
||||||
|
// AddOrder 增加與訂單相關的餘額(僅適用於交易表中訂單類型紀錄)
|
||||||
|
AddOrder(id int64, amount decimal.Decimal) error
|
||||||
|
// SubAvailable 減少可用餘額(根據業務類型自動判斷使用哪種錢包)
|
||||||
|
SubAvailable(b wallet.BusinessName, amount decimal.Decimal) error
|
||||||
|
// SubFreeze 減少凍結餘額(根據業務類型自動判斷使用哪種錢包)
|
||||||
|
SubFreeze(b wallet.BusinessName, amount decimal.Decimal) error
|
||||||
|
// SubUnconfirmed 減少尚未確認(unconfirmed)的餘額
|
||||||
|
SubUnconfirmed(amount decimal.Decimal) error
|
||||||
|
// GetAvailableBalance 查詢某業務類型對應的可用餘額
|
||||||
|
GetAvailableBalance(ctx context.Context, b wallet.BusinessName) (entity.Wallet, error)
|
||||||
|
// GetFreezeBalance 查詢某業務類型對應的凍結餘額
|
||||||
|
GetFreezeBalance(ctx context.Context, b wallet.BusinessName) (entity.Wallet, error)
|
||||||
|
// GetUnconfirmedBalance 查詢尚未確認的餘額
|
||||||
|
GetUnconfirmedBalance(ctx context.Context) (entity.Wallet, error)
|
||||||
|
// GetOrderBalance 查詢與指定訂單 ID 相關的餘額紀錄(訂單錢包交易)
|
||||||
|
GetOrderBalance(ctx context.Context, orderID string) (entity.Transaction, error)
|
||||||
|
// CheckWallet 確認錢包是否存在(通常用於初始化前檢查)
|
||||||
|
CheckWallet(ctx context.Context) (bool, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WalletTransactional 負責執行錢包與訂單餘額的最終更新(實際寫入資料庫),
|
||||||
|
// 並提供錢包交易紀錄的取得方法,主要用途為提交交易與後續紀錄保存。
|
||||||
|
type WalletTransactional interface {
|
||||||
|
// Execute 實際寫入錢包餘額的變動(例如:可用、凍結、未確認餘額等)
|
||||||
|
// 此方法應在交易邏輯處理完畢後被呼叫,用於同步 local 記憶體中的餘額至資料庫。
|
||||||
|
Execute(ctx context.Context) error
|
||||||
|
// ExecuteOrder 實際寫入訂單餘額的變動(通常指向 Transaction 表中的餘額欄位)
|
||||||
|
// 適用於與特定訂單相關的錢包操作(如訂單鎖定、釋放等)。
|
||||||
|
ExecuteOrder(ctx context.Context) error
|
||||||
|
// Transactions 建立錢包交易紀錄列表,用於最終寫入錢包交易表 wallet_transaction。
|
||||||
|
// 會補齊必要的交易相關欄位(如 txID、orderID、brand、businessType)。
|
||||||
|
Transactions(txID int64, orderID, brand string, businessType wallet.BusinessName) []entity.WalletTransaction
|
||||||
|
// GetTransactions 回傳目前記憶體中所累積的錢包交易紀錄,用於外部查詢或後續處理。
|
||||||
|
GetTransactions() []entity.WalletTransaction
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserWalletService 統整所有錢包相關功能,對外仍可以使用這個 interface。
|
||||||
|
type UserWalletService interface {
|
||||||
|
WalletReader
|
||||||
|
WalletLocker
|
||||||
|
WalletOperator
|
||||||
|
WalletTransactional
|
||||||
|
}
|
|
@ -42,13 +42,13 @@ type WalletRepository interface {
|
||||||
// Transaction 資料庫交易包裝器(確保交易一致性)
|
// Transaction 資料庫交易包裝器(確保交易一致性)
|
||||||
Transaction(fn func(db *gorm.DB) error) error
|
Transaction(fn func(db *gorm.DB) error) error
|
||||||
// InitWallets 初始化使用者的所有錢包類型(如可用、凍結等)
|
// InitWallets 初始化使用者的所有錢包類型(如可用、凍結等)
|
||||||
InitWallets(ctx context.Context, param []Wallet) error
|
InitWallets(ctx context.Context, param Wallet) ([]entity.Wallet, error)
|
||||||
// QueryBalances 查詢特定資產的錢包餘額
|
// QueryBalances 查詢特定資產的錢包餘額
|
||||||
QueryBalances(ctx context.Context, req BalanceQuery) ([]entity.Wallet, error)
|
QueryBalances(ctx context.Context, req BalanceQuery) ([]entity.Wallet, error)
|
||||||
// QueryBalancesByUIDs 查詢多個使用者在特定資產下的錢包餘額
|
// QueryBalancesByUIDs 查詢多個使用者在特定資產下的錢包餘額
|
||||||
QueryBalancesByUIDs(ctx context.Context, uids []string, req BalanceQuery) ([]entity.Wallet, error)
|
QueryBalancesByUIDs(ctx context.Context, uids []string, req BalanceQuery) ([]entity.Wallet, error)
|
||||||
//// GetDailyTxAmount 查詢使用者今日交易總金額(指定類型與業務)
|
// GetDailyTxAmount 查詢使用者今日交易總金額(指定類型與業務)
|
||||||
//GetDailyTxAmount(ctx context.Context, uid string, txTypes []domain.TxType, business wallet.BusinessName) ([]entity.Wallet, error)
|
GetDailyTxAmount(ctx context.Context, uid string, txTypes []wallet.TransactionType, business wallet.BusinessName) ([]entity.Wallet, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// BalanceQuery 是查詢餘額時的篩選條件
|
// BalanceQuery 是查詢餘額時的篩選條件
|
||||||
|
@ -57,31 +57,3 @@ type BalanceQuery struct {
|
||||||
Asset string // 資產類型(Crypto、寶石等)
|
Asset string // 資產類型(Crypto、寶石等)
|
||||||
Kinds []wallet.Types // 錢包類型(如可用、凍結等)
|
Kinds []wallet.Types // 錢包類型(如可用、凍結等)
|
||||||
}
|
}
|
||||||
|
|
||||||
// UserWalletService 專注於某位使用者在單一資產下的錢包操作邏輯
|
|
||||||
type UserWalletService interface {
|
|
||||||
// Init 初始化錢包(如建立可用、凍結、未確認等錢包)
|
|
||||||
Init(ctx context.Context, uid, asset, brand string) ([]entity.Wallet, error)
|
|
||||||
// All 查詢所有錢包餘額
|
|
||||||
All(ctx context.Context) ([]entity.Wallet, error)
|
|
||||||
// Get 查詢單一或多種類型的餘額
|
|
||||||
Get(ctx context.Context, kinds []wallet.Types) ([]entity.Wallet, error)
|
|
||||||
// GetWithLock 查詢鎖定後的錢包(交易使用)
|
|
||||||
GetWithLock(ctx context.Context, kinds []wallet.Types) ([]entity.Wallet, error)
|
|
||||||
// LocalBalance 查詢記憶中的快取值(非查資料庫)
|
|
||||||
LocalBalance(kind wallet.Types) decimal.Decimal
|
|
||||||
// LockByIDs 根據錢包 ID 鎖定(資料一致性用)
|
|
||||||
LockByIDs(ctx context.Context, ids []int64) ([]entity.Wallet, error)
|
|
||||||
// CheckReady 檢查錢包是否已經存在並準備好
|
|
||||||
CheckReady(ctx context.Context) (bool, error)
|
|
||||||
// Add 加值與扣款邏輯(含業務類別)
|
|
||||||
Add(kind wallet.Types, business wallet.BusinessName, amount decimal.Decimal) error
|
|
||||||
Sub(kind wallet.Types, business wallet.BusinessName, amount decimal.Decimal) error
|
|
||||||
// AddTransaction 新增一筆交易紀錄(建立資料)
|
|
||||||
AddTransaction(txID int64, orderID string, brand string, business wallet.BusinessName, kind wallet.Types, amount decimal.Decimal)
|
|
||||||
//// PendingTransactions 查詢尚未執行的交易清單(會在 Execute 中一次提交)
|
|
||||||
//PendingTransactions() []entity.WalletTransaction
|
|
||||||
|
|
||||||
// Commit 提交所有操作(更新錢包與新增交易紀錄)
|
|
||||||
Commit(ctx context.Context) error
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
package wallet
|
package wallet
|
||||||
|
|
||||||
type BusinessName string
|
type BusinessName string
|
||||||
|
|
||||||
|
func (b BusinessName) ToINT8() int8 {
|
||||||
|
return int8(0)
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
package wallet
|
||||||
|
|
||||||
|
type Enable int8
|
||||||
|
|
||||||
|
const (
|
||||||
|
EnableTrue Enable = 1
|
||||||
|
EnableFalse Enable = 2
|
||||||
|
)
|
||||||
|
|
||||||
|
func (e Enable) ToINT8() int8 {
|
||||||
|
return int8(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e Enable) ToBool() bool {
|
||||||
|
return e == EnableTrue
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
package wallet
|
||||||
|
|
||||||
|
// TransactionType 交易類型
|
||||||
|
type TransactionType int8
|
||||||
|
|
||||||
|
// 交易類型
|
||||||
|
const (
|
||||||
|
// Deposit 充值(增加可用餘額)
|
||||||
|
Deposit TransactionType = iota + 1
|
||||||
|
// Withdraw 提現(減少可用餘額)
|
||||||
|
Withdraw
|
||||||
|
// Freeze 凍結(減少可用餘額,加在凍結餘額)
|
||||||
|
Freeze
|
||||||
|
// UnFreeze 解凍(減少凍結餘額)
|
||||||
|
UnFreeze
|
||||||
|
// RollbackFreeze 凍結 (減少凍結餘額,加回可用餘額,不可指定金額)
|
||||||
|
RollbackFreeze
|
||||||
|
// Unconfirmed 限制 (減少凍結餘額,加別人限制餘額)
|
||||||
|
Unconfirmed
|
||||||
|
// CancelFreeze 取消凍結 (減少凍結餘額,加回可用餘額,,可指定金額)
|
||||||
|
CancelFreeze
|
||||||
|
// DepositUnconfirmed 充值 (增加限制餘額)
|
||||||
|
DepositUnconfirmed
|
||||||
|
// AppendFreeze 追加凍結(減少可用餘額,加在凍結餘額)
|
||||||
|
AppendFreeze
|
||||||
|
// RollbackFreezeAddAvailable 凍結(rollback凍結餘額,指定金額加回可用餘額)
|
||||||
|
RollbackFreezeAddAvailable
|
||||||
|
// ContractTransfer 合約劃轉
|
||||||
|
ContractTransfer
|
||||||
|
// FundingFee 合約倉位資金費
|
||||||
|
FundingFee
|
||||||
|
// Distribution 平台分發
|
||||||
|
Distribution
|
||||||
|
// SystemTransfer 系統劃轉
|
||||||
|
SystemTransfer
|
||||||
|
)
|
||||||
|
|
||||||
|
func (t TransactionType) ToInt64() int64 {
|
||||||
|
return int64(t)
|
||||||
|
}
|
|
@ -1,10 +1,12 @@
|
||||||
package wallet
|
package wallet
|
||||||
|
|
||||||
|
import "github.com/shopspring/decimal"
|
||||||
|
|
||||||
type Types int8
|
type Types int8
|
||||||
|
|
||||||
const (
|
const (
|
||||||
TypeAvailable Types = iota + 1 // 可動用金額(使用者可以自由花用的餘額)
|
TypeAvailable Types = iota + 1 // 可動用金額(使用者可以自由花用的餘額)
|
||||||
TypeFreezeType // 被凍結金額(交易進行中或風控鎖住的金額)
|
TypeFreeze // 被凍結金額(交易進行中或風控鎖住的金額)
|
||||||
TypeUnconfirmed // 未確認金額(交易已送出但區塊鏈尚未確認)
|
TypeUnconfirmed // 未確認金額(交易已送出但區塊鏈尚未確認)
|
||||||
// 以下為進階用途:合約或模擬交易錢包
|
// 以下為進階用途:合約或模擬交易錢包
|
||||||
|
|
||||||
|
@ -13,3 +15,18 @@ const (
|
||||||
TypeSimulationAvailable // 模擬交易可用金額(例如沙盒環境)
|
TypeSimulationAvailable // 模擬交易可用金額(例如沙盒環境)
|
||||||
TypeSimulationFreeze // 模擬交易凍結金額
|
TypeSimulationFreeze // 模擬交易凍結金額
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
ContractSimulationAvailable int64 = 100000
|
||||||
|
)
|
||||||
|
|
||||||
|
var AllTypes = []Types{
|
||||||
|
TypeAvailable, TypeFreeze, TypeUnconfirmed,
|
||||||
|
TypeContractAvailable, TypeContractFreeze, TypeSimulationAvailable, TypeSimulationFreeze,
|
||||||
|
}
|
||||||
|
|
||||||
|
var AllAvailableTypes = []Types{
|
||||||
|
TypeAvailable, TypeContractAvailable, TypeSimulationAvailable,
|
||||||
|
}
|
||||||
|
|
||||||
|
var InitContractSimulationAvailable = decimal.NewFromInt(ContractSimulationAvailable)
|
||||||
|
|
|
@ -2,17 +2,505 @@ package repository
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"code.30cm.net/digimon/app-cloudep-wallet-service/pkg/domain/entity"
|
"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/wallet"
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
"github.com/shopspring/decimal"
|
"github.com/shopspring/decimal"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
|
"gorm.io/gorm/clause"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewUserWallet(db *gorm.DB, uid, crypto string) repository.UserWallet {
|
// 用戶某個幣種餘額
|
||||||
|
type userWallet struct {
|
||||||
|
db *gorm.DB
|
||||||
|
uid string
|
||||||
|
asset string
|
||||||
|
// local wallet相關計算的餘額存在這裡
|
||||||
|
localWalletBalance map[wallet.Types]entity.Wallet
|
||||||
|
// local order wallet相關計算的餘額存在這裡
|
||||||
|
localOrderBalance map[int64]decimal.Decimal
|
||||||
|
// local wallet內所有餘額變化紀錄
|
||||||
|
transactions []entity.WalletTransaction
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewUserWallet(db *gorm.DB, uid, asset string) repository.UserWalletService {
|
||||||
return &userWallet{
|
return &userWallet{
|
||||||
db: db,
|
db: db,
|
||||||
uid: uid,
|
uid: uid,
|
||||||
crypto: crypto,
|
asset: asset,
|
||||||
|
localWalletBalance: make(map[wallet.Types]entity.Wallet, len(wallet.AllTypes)),
|
||||||
|
localOrderBalance: make(map[int64]decimal.Decimal, len(wallet.AllTypes)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
localWalletBalance: make(map[domain.WalletType]entity.Wallet, len(domain.WalletAllType)),
|
func (repo *userWallet) Create(ctx context.Context, uid, asset, brand string) ([]entity.Wallet, error) {
|
||||||
localOrderBalance: make(map[int64]decimal.Decimal, len(domain.WalletAllType)),
|
wallets := make([]entity.Wallet, 0, len(wallet.AllTypes))
|
||||||
|
for _, t := range wallet.AllTypes {
|
||||||
|
var balance decimal.Decimal
|
||||||
|
|
||||||
|
// 合約模擬初始資金
|
||||||
|
if t == wallet.TypeSimulationAvailable {
|
||||||
|
balance = wallet.InitContractSimulationAvailable
|
||||||
|
}
|
||||||
|
|
||||||
|
wallets = append(wallets, entity.Wallet{
|
||||||
|
Brand: brand,
|
||||||
|
UID: uid,
|
||||||
|
Asset: asset,
|
||||||
|
Balance: balance,
|
||||||
|
Type: t,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := repo.db.WithContext(ctx).Create(&wallets).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, v := range wallets {
|
||||||
|
repo.localWalletBalance[v.Type] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
return wallets, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *userWallet) AllBalances(ctx context.Context) ([]entity.Wallet, error) {
|
||||||
|
var result []entity.Wallet
|
||||||
|
|
||||||
|
err := repo.walletUserWhere(repo.uid, repo.asset).
|
||||||
|
WithContext(ctx).
|
||||||
|
Select("id, crypto, balance, type").
|
||||||
|
Find(&result).Error
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return []entity.Wallet{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, v := range result {
|
||||||
|
repo.localWalletBalance[v.Type] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *userWallet) Balance(ctx context.Context, kind wallet.Types) (entity.Wallet, error) {
|
||||||
|
var result entity.Wallet
|
||||||
|
|
||||||
|
err := repo.walletUserWhere(repo.uid, repo.asset).
|
||||||
|
WithContext(ctx).
|
||||||
|
Model(&result).
|
||||||
|
Select("id, crypto, balance, type").
|
||||||
|
Where("type = ?", kind).
|
||||||
|
Take(&result).Error
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return entity.Wallet{}, repository.WrapNotFoundError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
repo.localWalletBalance[result.Type] = result
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *userWallet) Balances(ctx context.Context, kind []wallet.Types) ([]entity.Wallet, error) {
|
||||||
|
var wallets []entity.Wallet
|
||||||
|
|
||||||
|
err := repo.walletUserWhere(repo.uid, repo.asset).
|
||||||
|
WithContext(ctx).
|
||||||
|
Model(&entity.Wallet{}).
|
||||||
|
Select("id, crypto, balance, type").
|
||||||
|
Where("type IN ?", kind).
|
||||||
|
Find(&wallets).Error
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return []entity.Wallet{}, repository.WrapNotFoundError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, w := range wallets {
|
||||||
|
repo.localWalletBalance[w.Type] = w
|
||||||
|
}
|
||||||
|
|
||||||
|
return wallets, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *userWallet) LocalBalance(kind wallet.Types) decimal.Decimal {
|
||||||
|
w, ok := repo.localWalletBalance[kind]
|
||||||
|
if !ok {
|
||||||
|
return decimal.Zero
|
||||||
|
}
|
||||||
|
|
||||||
|
return w.Balance
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *userWallet) BalanceXLock(ctx context.Context, kind wallet.Types) (entity.Wallet, error) {
|
||||||
|
var result entity.Wallet
|
||||||
|
|
||||||
|
err := repo.walletUserWhere(repo.uid, repo.asset).
|
||||||
|
WithContext(ctx).
|
||||||
|
Model(&result).
|
||||||
|
Select("id, crypto, balance, type").
|
||||||
|
Where("type = ?", kind).
|
||||||
|
Clauses(clause.Locking{Strength: "UPDATE"}).
|
||||||
|
Take(&result).Error
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return entity.Wallet{}, repository.WrapNotFoundError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
repo.localWalletBalance[result.Type] = result
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *userWallet) BalancesXLock(ctx context.Context, kind []wallet.Types) ([]entity.Wallet, error) {
|
||||||
|
var wallets []entity.Wallet
|
||||||
|
|
||||||
|
err := repo.walletUserWhere(repo.uid, repo.asset).
|
||||||
|
WithContext(ctx).
|
||||||
|
Model(&entity.Wallet{}).
|
||||||
|
Select("id, crypto, balance, type").
|
||||||
|
Where("type IN ?", kind).
|
||||||
|
Clauses(clause.Locking{Strength: "UPDATE"}).
|
||||||
|
Find(&wallets).Error
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return []entity.Wallet{}, repository.WrapNotFoundError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, w := range wallets {
|
||||||
|
repo.localWalletBalance[w.Type] = w
|
||||||
|
}
|
||||||
|
|
||||||
|
return wallets, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *userWallet) XLock(ctx context.Context, id int64) (entity.Wallet, error) {
|
||||||
|
var result entity.Wallet
|
||||||
|
|
||||||
|
err := repo.db.WithContext(ctx).
|
||||||
|
Model(&result).
|
||||||
|
Select("id").
|
||||||
|
Where("id = ?", id).
|
||||||
|
Clauses(clause.Locking{Strength: "UPDATE"}).
|
||||||
|
Take(&result).Error
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return entity.Wallet{}, repository.WrapNotFoundError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
repo.localWalletBalance[result.Type] = result
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *userWallet) XLocks(ctx context.Context, ids []int64) ([]entity.Wallet, error) {
|
||||||
|
var wallets []entity.Wallet
|
||||||
|
|
||||||
|
err := repo.db.WithContext(ctx).
|
||||||
|
Model(&entity.Wallet{}).
|
||||||
|
Select("id, crypto, balance, type").
|
||||||
|
Where("id IN ?", ids).
|
||||||
|
Clauses(clause.Locking{Strength: "UPDATE"}).
|
||||||
|
Find(&wallets).Error
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return []entity.Wallet{}, repository.WrapNotFoundError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, w := range wallets {
|
||||||
|
repo.localWalletBalance[w.Type] = w
|
||||||
|
}
|
||||||
|
|
||||||
|
return wallets, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *userWallet) GetAvailableBalanceXLock(ctx context.Context, _ wallet.BusinessName) (entity.Wallet, error) {
|
||||||
|
//switch b {
|
||||||
|
//case domain.ContractBusinessTypeBusinessName, domain.SystemTransferBusinessTypeBusinessName:
|
||||||
|
// return repo.BalanceXLock(ctx, domain.WalletContractAvailableType)
|
||||||
|
//case domain.ContractSimulationBusinessTypeBusinessName:
|
||||||
|
// return repo.BalanceXLock(ctx, domain.WalletSimulationAvailableType)
|
||||||
|
//}
|
||||||
|
|
||||||
|
return repo.BalanceXLock(ctx, wallet.TypeAvailable)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *userWallet) GetFreezeBalanceXLock(ctx context.Context, _ wallet.BusinessName) (entity.Wallet, error) {
|
||||||
|
//switch b {
|
||||||
|
//case domain.ContractBusinessTypeBusinessName, domain.SystemTransferBusinessTypeBusinessName, domain.SystemTransferCommissionBusinessTypeBusinessName:
|
||||||
|
// return w.Balance(ctx, domain.WalletContractFreezeType)
|
||||||
|
//case domain.ContractSimulationBusinessTypeBusinessName:
|
||||||
|
// return w.Balance(ctx, domain.WalletSimulationFreezeType)
|
||||||
|
//}
|
||||||
|
|
||||||
|
return repo.Balance(ctx, wallet.TypeFreeze)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *userWallet) GetUnconfirmedBalanceXLock(ctx context.Context) (entity.Wallet, error) {
|
||||||
|
return repo.BalanceXLock(ctx, wallet.TypeUnconfirmed)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *userWallet) GetOrderBalanceXLock(ctx context.Context, id int64) (entity.Transaction, error) {
|
||||||
|
var result entity.Transaction
|
||||||
|
|
||||||
|
err := repo.db.WithContext(ctx).
|
||||||
|
Model(&result).
|
||||||
|
Select("id, transaction_id, order_id, uid, crypto, balance, type, business_type").
|
||||||
|
Where("id = ?", id).
|
||||||
|
Clauses(clause.Locking{Strength: "UPDATE"}).
|
||||||
|
Take(&result).Error
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return entity.Transaction{}, repository.WrapNotFoundError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
repo.localOrderBalance[result.ID] = result.Balance
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *userWallet) AddBalance(kind wallet.Types, amount decimal.Decimal) error {
|
||||||
|
return repo.AddBalanceSetOrderID("", kind, amount)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *userWallet) AddBalanceSetOrderID(orderID string, kind wallet.Types, amount decimal.Decimal) error {
|
||||||
|
w, ok := repo.localWalletBalance[kind]
|
||||||
|
if !ok {
|
||||||
|
return repository.ErrRecordNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Balance = w.Balance.Add(amount)
|
||||||
|
if w.Balance.LessThan(decimal.Zero) {
|
||||||
|
return repository.ErrBalanceInsufficient
|
||||||
|
}
|
||||||
|
|
||||||
|
repo.transactions = append(repo.transactions, entity.WalletTransaction{
|
||||||
|
OrderID: orderID,
|
||||||
|
UID: repo.uid,
|
||||||
|
WalletType: kind,
|
||||||
|
Asset: repo.asset,
|
||||||
|
Amount: amount,
|
||||||
|
Balance: w.Balance,
|
||||||
|
})
|
||||||
|
|
||||||
|
repo.localWalletBalance[kind] = w
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *userWallet) AddAvailable(_ wallet.BusinessName, amount decimal.Decimal) error {
|
||||||
|
//switch b {
|
||||||
|
//case domain.ContractBusinessTypeBusinessName, domain.SystemTransferBusinessTypeBusinessName:
|
||||||
|
// return w.AddBalance(domain.WalletContractAvailableType, amount)
|
||||||
|
//case domain.ContractSimulationBusinessTypeBusinessName:
|
||||||
|
// return w.AddBalance(domain.WalletSimulationAvailableType, amount)
|
||||||
|
//case domain.DistributionBusinessTypeBusinessName:
|
||||||
|
// return w.AddBalance(domain.WalletAvailableType, amount)
|
||||||
|
//}
|
||||||
|
|
||||||
|
return repo.AddBalance(wallet.TypeAvailable, amount)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *userWallet) AddFreeze(_ wallet.BusinessName, amount decimal.Decimal) error {
|
||||||
|
//switch b {
|
||||||
|
//case domain.ContractBusinessTypeBusinessName, domain.SystemTransferBusinessTypeBusinessName:
|
||||||
|
// return w.AddBalance(domain.WalletContractFreezeType, amount)
|
||||||
|
//case domain.ContractSimulationBusinessTypeBusinessName:
|
||||||
|
// return w.AddBalance(domain.WalletSimulationFreezeType, amount)
|
||||||
|
//}
|
||||||
|
|
||||||
|
return repo.AddBalance(wallet.TypeFreeze, amount)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *userWallet) AddUnconfirmed(amount decimal.Decimal) error {
|
||||||
|
return repo.AddBalance(wallet.TypeUnconfirmed, amount)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *userWallet) AddOrder(id int64, amount decimal.Decimal) error {
|
||||||
|
return repo.addOrderBalance(id, amount)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *userWallet) SubAvailable(_ wallet.BusinessName, amount decimal.Decimal) error {
|
||||||
|
//switch b {
|
||||||
|
//case domain.ContractBusinessTypeBusinessName, domain.SystemTransferBusinessTypeBusinessName:
|
||||||
|
// return w.AddBalance(domain.WalletContractAvailableType, decimal.Zero.Sub(amount))
|
||||||
|
//case domain.ContractSimulationBusinessTypeBusinessName:
|
||||||
|
// return w.AddBalance(domain.WalletSimulationAvailableType, decimal.Zero.Sub(amount))
|
||||||
|
//}
|
||||||
|
|
||||||
|
return repo.AddBalance(wallet.TypeAvailable, decimal.Zero.Sub(amount))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *userWallet) SubFreeze(_ wallet.BusinessName, amount decimal.Decimal) error {
|
||||||
|
//switch b {
|
||||||
|
//case domain.ContractBusinessTypeBusinessName, domain.SystemTransferBusinessTypeBusinessName, domain.SystemTransferCommissionBusinessTypeBusinessName:
|
||||||
|
// return w.AddBalance(domain.WalletContractFreezeType, decimal.Zero.Sub(amount))
|
||||||
|
//case domain.ContractSimulationBusinessTypeBusinessName:
|
||||||
|
// return w.AddBalance(domain.WalletSimulationFreezeType, decimal.Zero.Sub(amount))
|
||||||
|
//}
|
||||||
|
|
||||||
|
return repo.AddBalance(wallet.TypeFreeze, decimal.Zero.Sub(amount))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *userWallet) SubUnconfirmed(amount decimal.Decimal) error {
|
||||||
|
return repo.AddBalance(wallet.TypeUnconfirmed, decimal.Zero.Sub(amount))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *userWallet) GetAvailableBalance(ctx context.Context, _ wallet.BusinessName) (entity.Wallet, error) {
|
||||||
|
//switch b {
|
||||||
|
//case domain.ContractBusinessTypeBusinessName, domain.SystemTransferBusinessTypeBusinessName:
|
||||||
|
// return w.Balance(ctx, domain.WalletContractAvailableType)
|
||||||
|
//case domain.ContractSimulationBusinessTypeBusinessName:
|
||||||
|
// return w.Balance(ctx, domain.WalletSimulationAvailableType)
|
||||||
|
//case domain.DistributionBusinessTypeBusinessName:
|
||||||
|
// return w.Balance(ctx, domain.WalletAvailableType)
|
||||||
|
//}
|
||||||
|
|
||||||
|
return repo.Balance(ctx, wallet.TypeAvailable)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *userWallet) GetFreezeBalance(ctx context.Context, _ wallet.BusinessName) (entity.Wallet, error) {
|
||||||
|
//switch b {
|
||||||
|
//case domain.ContractBusinessTypeBusinessName, domain.SystemTransferBusinessTypeBusinessName, domain.SystemTransferCommissionBusinessTypeBusinessName:
|
||||||
|
// return w.Balance(ctx, domain.WalletContractFreezeType)
|
||||||
|
//case domain.ContractSimulationBusinessTypeBusinessName:
|
||||||
|
// return w.Balance(ctx, domain.WalletSimulationFreezeType)
|
||||||
|
//}
|
||||||
|
|
||||||
|
return repo.Balance(ctx, wallet.TypeFreeze)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *userWallet) GetUnconfirmedBalance(ctx context.Context) (entity.Wallet, error) {
|
||||||
|
return repo.Balance(ctx, wallet.TypeUnconfirmed)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *userWallet) GetOrderBalance(ctx context.Context, orderID string) (entity.Transaction, error) {
|
||||||
|
var result entity.Transaction
|
||||||
|
|
||||||
|
err := repo.db.WithContext(ctx).
|
||||||
|
Model(&result).
|
||||||
|
Select("id, transaction_id, order_id, uid, crypto, balance, type, business_type").
|
||||||
|
Where("order_id = ?", orderID).
|
||||||
|
Take(&result).Error
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return entity.Transaction{}, repository.WrapNotFoundError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 要確實set這兩個Balance,不然在做計算會有問題
|
||||||
|
repo.localOrderBalance[result.ID] = result.Balance
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *userWallet) CheckWallet(ctx context.Context) (bool, error) {
|
||||||
|
var exists bool
|
||||||
|
err := repo.walletUserWhere(repo.uid, repo.asset).WithContext(ctx).
|
||||||
|
Model(&entity.Wallet{}).
|
||||||
|
Select("1").
|
||||||
|
Where("type = ?", wallet.TypeAvailable).
|
||||||
|
Limit(1).
|
||||||
|
Scan(&exists).Error
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return exists, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *userWallet) Execute(ctx context.Context) error {
|
||||||
|
// 處理wallet table
|
||||||
|
for _, walletType := range wallet.AllTypes {
|
||||||
|
w, ok := repo.localWalletBalance[walletType]
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
rc := &sql.TxOptions{
|
||||||
|
Isolation: sql.LevelReadCommitted,
|
||||||
|
ReadOnly: false,
|
||||||
|
}
|
||||||
|
err := repo.db.Transaction(func(tx *gorm.DB) error {
|
||||||
|
return tx.WithContext(ctx).
|
||||||
|
Model(&entity.Wallet{}).
|
||||||
|
Where("id = ?", w.ID).
|
||||||
|
UpdateColumns(map[string]interface{}{
|
||||||
|
"balance": w.Balance,
|
||||||
|
"update_time": time.Now().UTC().Unix(),
|
||||||
|
}).Error
|
||||||
|
}, rc)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("update uid: %s crypto: %s type: %d wallet error: %w", repo.uid, repo.asset, walletType, err)
|
||||||
|
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *userWallet) ExecuteOrder(ctx context.Context) error {
|
||||||
|
for id, localBalance := range repo.localOrderBalance {
|
||||||
|
rc := &sql.TxOptions{
|
||||||
|
Isolation: sql.LevelReadCommitted,
|
||||||
|
ReadOnly: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
err := repo.db.Transaction(func(tx *gorm.DB) error {
|
||||||
|
return tx.WithContext(ctx).
|
||||||
|
Where("id = ?", id).
|
||||||
|
Model(&entity.Transaction{}).
|
||||||
|
Update("balance", localBalance).Error
|
||||||
|
}, rc)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *userWallet) Transactions(txID int64, orderID, brand string, businessType wallet.BusinessName) []entity.WalletTransaction {
|
||||||
|
for i := range repo.transactions {
|
||||||
|
repo.transactions[i].TransactionID = txID
|
||||||
|
repo.transactions[i].OrderID = orderID
|
||||||
|
repo.transactions[i].Brand = brand
|
||||||
|
repo.transactions[i].BusinessType = businessType.ToINT8()
|
||||||
|
}
|
||||||
|
|
||||||
|
return repo.transactions
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *userWallet) GetTransactions() []entity.WalletTransaction {
|
||||||
|
return repo.transactions
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
func (repo *userWallet) walletUserWhere(uid, asset string) *gorm.DB {
|
||||||
|
return repo.db.Where("uid = ?", uid).
|
||||||
|
Where("asset = ?", asset)
|
||||||
|
}
|
||||||
|
|
||||||
|
// addOrderBalance 新增訂單餘額
|
||||||
|
// 使用前local Balance必須有資料,所以必須執行過GetOrderBalanceXLock才會有資料
|
||||||
|
func (repo *userWallet) addOrderBalance(id int64, amount decimal.Decimal) error {
|
||||||
|
balance, ok := repo.localOrderBalance[id]
|
||||||
|
if !ok {
|
||||||
|
return repository.ErrRecordNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
balance = balance.Add(amount)
|
||||||
|
if balance.LessThan(decimal.Zero) {
|
||||||
|
return repository.ErrBalanceInsufficient
|
||||||
|
}
|
||||||
|
|
||||||
|
repo.localOrderBalance[id] = balance
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
@ -3,8 +3,10 @@ package repository
|
||||||
import (
|
import (
|
||||||
"code.30cm.net/digimon/app-cloudep-wallet-service/pkg/domain/entity"
|
"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/repository"
|
||||||
|
"code.30cm.net/digimon/app-cloudep-wallet-service/pkg/domain/wallet"
|
||||||
"context"
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
"github.com/shopspring/decimal"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -45,18 +47,37 @@ func (repo *WalletRepository) Transaction(fn func(db *gorm.DB) error) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (repo *WalletRepository) Session(uid, asset string) repository.UserWalletService {
|
func (repo *WalletRepository) Session(uid, asset string) repository.UserWalletService {
|
||||||
//TODO implement me
|
return NewUserWallet(repo.DB, uid, asset)
|
||||||
panic("implement me")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (repo *WalletRepository) SessionWithTx(db *gorm.DB, uid, asset string) repository.UserWalletService {
|
func (repo *WalletRepository) SessionWithTx(db *gorm.DB, uid, asset string) repository.UserWalletService {
|
||||||
//TODO implement me
|
return NewUserWallet(db, uid, asset)
|
||||||
panic("implement me")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (repo *WalletRepository) InitWallets(ctx context.Context, param []repository.Wallet) error {
|
func (repo *WalletRepository) InitWallets(ctx context.Context, param repository.Wallet) ([]entity.Wallet, error) {
|
||||||
//TODO implement me
|
w := make([]entity.Wallet, 0, len(wallet.AllTypes))
|
||||||
panic("implement me")
|
for _, t := range wallet.AllTypes {
|
||||||
|
var balance decimal.Decimal
|
||||||
|
|
||||||
|
// 合約模擬初始資金
|
||||||
|
if t == wallet.TypeSimulationAvailable {
|
||||||
|
balance = wallet.InitContractSimulationAvailable
|
||||||
|
}
|
||||||
|
|
||||||
|
w = append(w, entity.Wallet{
|
||||||
|
Brand: param.Brand,
|
||||||
|
UID: param.UID,
|
||||||
|
Asset: param.Asset,
|
||||||
|
Balance: balance,
|
||||||
|
Type: t,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := repo.DB.WithContext(ctx).Create(&w).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return w, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (repo *WalletRepository) QueryBalances(ctx context.Context, req repository.BalanceQuery) ([]entity.Wallet, error) {
|
func (repo *WalletRepository) QueryBalances(ctx context.Context, req repository.BalanceQuery) ([]entity.Wallet, error) {
|
||||||
|
@ -68,3 +89,8 @@ func (repo *WalletRepository) QueryBalancesByUIDs(ctx context.Context, uids []st
|
||||||
//TODO implement me
|
//TODO implement me
|
||||||
panic("implement me")
|
panic("implement me")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (repo *WalletRepository) GetDailyTxAmount(ctx context.Context, uid string, txTypes []wallet.TransactionType, business wallet.BusinessName) ([]entity.Wallet, error) {
|
||||||
|
//TODO implement me
|
||||||
|
panic("implement me")
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue