251 lines
7.3 KiB
Go
251 lines
7.3 KiB
Go
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
|
|
}
|