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 }