暂存一下,防止丢失
This commit is contained in:
2
internal/domain/wallet/doc.go
Normal file
2
internal/domain/wallet/doc.go
Normal file
@@ -0,0 +1,2 @@
|
||||
// Package wallet 定义代理主钱包的资金边界与信用额度不变量。
|
||||
package wallet
|
||||
250
internal/domain/wallet/wallet.go
Normal file
250
internal/domain/wallet/wallet.go
Normal file
@@ -0,0 +1,250 @@
|
||||
package wallet
|
||||
|
||||
import (
|
||||
"math"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/pkg/constants"
|
||||
appErrors "github.com/break/junhong_cmp_fiber/pkg/errors"
|
||||
)
|
||||
|
||||
// AgentWallet 表示代理钱包聚合的资金状态。
|
||||
//
|
||||
// 当前聚合统一维护扣款、冻结、完成扣除、正向入账、退款回充与调额不变量。
|
||||
type AgentWallet struct {
|
||||
ID uint
|
||||
ShopID uint
|
||||
WalletType string
|
||||
Balance int64
|
||||
FrozenBalance int64
|
||||
CreditEnabled bool
|
||||
CreditLimit int64
|
||||
Status int
|
||||
Version int
|
||||
}
|
||||
|
||||
// Debit 从代理主钱包扣减指定金额,并保证扣款后总可用金额不为负数。
|
||||
func (w *AgentWallet) Debit(amount int64) error {
|
||||
if err := w.validateMainWalletMutation(amount); err != nil {
|
||||
return err
|
||||
}
|
||||
candidate := *w
|
||||
balance, ok := safeSub(candidate.Balance, amount)
|
||||
if !ok {
|
||||
return appErrors.New(appErrors.CodeInvalidParam, "扣减钱包余额时发生整数溢出")
|
||||
}
|
||||
candidate.Balance = balance
|
||||
if err := candidate.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
w.Balance = balance
|
||||
return nil
|
||||
}
|
||||
|
||||
// Credit 向代理主钱包增加账面余额,负余额会自然表现为欠款减少。
|
||||
func (w *AgentWallet) Credit(amount int64) error {
|
||||
if err := w.validateMainWalletMutation(amount); err != nil {
|
||||
return err
|
||||
}
|
||||
balance, ok := safeAdd(w.Balance, amount)
|
||||
if !ok {
|
||||
return appErrors.New(appErrors.CodeInvalidParam, "增加钱包余额时发生整数溢出")
|
||||
}
|
||||
candidate := *w
|
||||
candidate.Balance = balance
|
||||
if err := candidate.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
w.Balance = balance
|
||||
return nil
|
||||
}
|
||||
|
||||
// Freeze 预占代理主钱包资金,冻结金额会占用现金和信用但不直接形成欠款。
|
||||
func (w *AgentWallet) Freeze(amount int64) error {
|
||||
if err := w.validateMainWalletMutation(amount); err != nil {
|
||||
return err
|
||||
}
|
||||
candidate := *w
|
||||
frozen, ok := safeAdd(candidate.FrozenBalance, amount)
|
||||
if !ok {
|
||||
return appErrors.New(appErrors.CodeInvalidParam, "增加钱包冻结金额时发生整数溢出")
|
||||
}
|
||||
candidate.FrozenBalance = frozen
|
||||
if err := candidate.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
w.FrozenBalance = frozen
|
||||
return nil
|
||||
}
|
||||
|
||||
// Release 释放已经预占的代理主钱包资金。
|
||||
func (w *AgentWallet) Release(amount int64) error {
|
||||
if err := w.validateMainWalletMutation(amount); err != nil {
|
||||
return err
|
||||
}
|
||||
if w.FrozenBalance < amount {
|
||||
return appErrors.New(appErrors.CodeInsufficientBalance, "钱包冻结金额不足")
|
||||
}
|
||||
w.FrozenBalance -= amount
|
||||
return w.Validate()
|
||||
}
|
||||
|
||||
// CompleteReserved 完成冻结资金扣除,同时减少账面余额和冻结金额。
|
||||
func (w *AgentWallet) CompleteReserved(amount int64) error {
|
||||
if err := w.validateMainWalletMutation(amount); err != nil {
|
||||
return err
|
||||
}
|
||||
if w.FrozenBalance < amount {
|
||||
return appErrors.New(appErrors.CodeInsufficientBalance, "钱包冻结金额不足")
|
||||
}
|
||||
balance, ok := safeSub(w.Balance, amount)
|
||||
if !ok {
|
||||
return appErrors.New(appErrors.CodeInvalidParam, "完成冻结资金扣除时发生整数溢出")
|
||||
}
|
||||
candidate := *w
|
||||
candidate.Balance = balance
|
||||
candidate.FrozenBalance -= amount
|
||||
if err := candidate.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
w.Balance = candidate.Balance
|
||||
w.FrozenBalance = candidate.FrozenBalance
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w AgentWallet) validateMainWalletMutation(amount int64) error {
|
||||
if amount <= 0 {
|
||||
return appErrors.New(appErrors.CodeInvalidParam, "钱包变更金额必须大于零")
|
||||
}
|
||||
if w.WalletType != constants.AgentWalletTypeMain {
|
||||
return appErrors.New(appErrors.CodeInvalidParam, "仅代理主钱包支持该资金操作")
|
||||
}
|
||||
if w.Status != constants.AgentWalletStatusNormal {
|
||||
return appErrors.New(appErrors.CodeInvalidStatus, "当前钱包状态不允许资金操作")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ChangeCredit 调整主钱包实际信用额度并重新校验完整资金边界。
|
||||
func (w *AgentWallet) ChangeCredit(enabled bool, limit int64) error {
|
||||
candidate := *w
|
||||
candidate.CreditEnabled = enabled
|
||||
candidate.CreditLimit = limit
|
||||
if err := candidate.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
w.CreditEnabled = enabled
|
||||
w.CreditLimit = limit
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate 校验钱包类型、信用配置、版本与总可用金额不变量。
|
||||
func (w AgentWallet) Validate() error {
|
||||
if w.WalletType != constants.AgentWalletTypeMain && w.WalletType != constants.AgentWalletTypeCommission {
|
||||
return appErrors.New(appErrors.CodeInvalidParam, "代理钱包类型无效")
|
||||
}
|
||||
if w.Version < 0 {
|
||||
return appErrors.New(appErrors.CodeInvalidParam, "钱包版本不能为负数")
|
||||
}
|
||||
if w.FrozenBalance < 0 {
|
||||
return appErrors.New(appErrors.CodeInvalidParam, "冻结金额不能为负数")
|
||||
}
|
||||
if err := validateCredit(w.WalletType, w.CreditEnabled, w.CreditLimit); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := w.CashAvailableBalance(); err != nil {
|
||||
return err
|
||||
}
|
||||
available, err := w.AvailableBalance()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if available < 0 {
|
||||
return appErrors.New(appErrors.CodeInsufficientBalance, "钱包总可用金额不能为负数")
|
||||
}
|
||||
if _, err := w.DebtAmount(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// EffectiveCredit 返回当前实际生效的信用额度。
|
||||
func (w AgentWallet) EffectiveCredit() int64 {
|
||||
if !w.CreditEnabled {
|
||||
return 0
|
||||
}
|
||||
return w.CreditLimit
|
||||
}
|
||||
|
||||
// CashAvailableBalance 返回现金可用金额,即账面余额减冻结金额。
|
||||
func (w AgentWallet) CashAvailableBalance() (int64, error) {
|
||||
available, ok := safeSub(w.Balance, w.FrozenBalance)
|
||||
if !ok {
|
||||
return 0, appErrors.New(appErrors.CodeInvalidParam, "计算现金可用金额时发生整数溢出")
|
||||
}
|
||||
return available, nil
|
||||
}
|
||||
|
||||
// AvailableBalance 返回总可用金额,即现金可用金额加有效信用额度。
|
||||
func (w AgentWallet) AvailableBalance() (int64, error) {
|
||||
cashAvailable, err := w.CashAvailableBalance()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
available, ok := safeAdd(cashAvailable, w.EffectiveCredit())
|
||||
if !ok {
|
||||
return 0, appErrors.New(appErrors.CodeInvalidParam, "计算钱包总可用金额时发生整数溢出")
|
||||
}
|
||||
return available, nil
|
||||
}
|
||||
|
||||
// IsInDebt 返回账面余额是否已经形成欠款。
|
||||
func (w AgentWallet) IsInDebt() bool {
|
||||
return w.Balance < 0
|
||||
}
|
||||
|
||||
// DebtAmount 返回欠款金额;冻结金额不直接计入欠款。
|
||||
func (w AgentWallet) DebtAmount() (int64, error) {
|
||||
if !w.IsInDebt() {
|
||||
return 0, nil
|
||||
}
|
||||
if w.Balance == math.MinInt64 {
|
||||
return 0, appErrors.New(appErrors.CodeInvalidParam, "计算钱包欠款金额时发生整数溢出")
|
||||
}
|
||||
return -w.Balance, nil
|
||||
}
|
||||
|
||||
func validateCredit(walletType string, enabled bool, limit int64) error {
|
||||
if limit < 0 {
|
||||
return appErrors.New(appErrors.CodeInvalidParam, "信用额度不能为负数")
|
||||
}
|
||||
if !enabled && limit != 0 {
|
||||
return appErrors.New(appErrors.CodeInvalidParam, "关闭信用时信用额度必须为零")
|
||||
}
|
||||
if enabled && limit == 0 {
|
||||
return appErrors.New(appErrors.CodeInvalidParam, "启用信用时信用额度必须大于零")
|
||||
}
|
||||
if walletType != constants.AgentWalletTypeMain && (enabled || limit != 0) {
|
||||
return appErrors.New(appErrors.CodeInvalidParam, "只有代理主钱包可以启用信用额度")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func safeAdd(left, right int64) (int64, bool) {
|
||||
if right > 0 && left > math.MaxInt64-right {
|
||||
return 0, false
|
||||
}
|
||||
if right < 0 && left < math.MinInt64-right {
|
||||
return 0, false
|
||||
}
|
||||
return left + right, true
|
||||
}
|
||||
|
||||
func safeSub(left, right int64) (int64, bool) {
|
||||
if right > 0 && left < math.MinInt64+right {
|
||||
return 0, false
|
||||
}
|
||||
if right < 0 && left > math.MaxInt64+right {
|
||||
return 0, false
|
||||
}
|
||||
return left - right, true
|
||||
}
|
||||
Reference in New Issue
Block a user