// Package basic 钱包基础业务逻辑 package basic import ( "context" "bsm/full/module/finance/wallet/internal/excode" "bsm/full/module/finance/wallet/internal/impl" "bsm/full/module/finance/wallet/internal/models" "git.apinb.com/bsm-sdk/core/errcode" "git.apinb.com/bsm-sdk/core/utils" "golang.org/x/crypto/bcrypt" "gorm.io/gorm" ) // WalletPay 钱包支付结构体 type WalletPay struct { ctx context.Context Body *models.WalletBasic } // NewWallet 创建钱包支付实例 // 验证用户身份、钱包状态、支付密码和余额 func NewWallet(passportIdentity, pwd string, amount int64) (*WalletPay, error) { // 检查钱包是否存在 wallet := models.WalletExists(passportIdentity) if wallet == nil { return nil, errcode.ErrNotFound(404, "wallet") } // 检查钱包状态 if wallet.Status == -1 { return nil, errcode.ErrPermissionDenied } // 校验支付密码 if !VerifyPayPassword(wallet.PayPassword, pwd) { return nil, errcode.ErrPassword } // 检查金额合法性 if amount <= 0 { return nil, excode.ErrAmount } // 检查余额是否足够 if amount > wallet.Balance { return nil, excode.ErrBalanceNotEnough } return &WalletPay{ Body: wallet, }, nil } // TradeConsum 执行交易消费 // 使用单条条件更新原子扣减余额与可提现余额,避免"读后写"造成的并发双花; // 余额变更与消费流水写入放在同一事务内,保证账实一致。 func (srv *WalletPay) TradeConsum(amount int64) error { if amount <= 0 { return excode.ErrAmount } return impl.DBService.Transaction(func(tx *gorm.DB) error { // 条件更新:余额与可提现余额均充足且钱包未禁用时才扣减 res := tx.Model(&models.WalletBasic{}). Where("identity = ? AND status <> -1 AND balance >= ? AND withdrawal_balance >= ?", srv.Body.Identity, amount, amount). UpdateColumns(map[string]interface{}{ "balance": gorm.Expr("balance - ?", amount), "withdrawal_balance": gorm.Expr("withdrawal_balance - ?", amount), }) if res.Error != nil { return res.Error } if res.RowsAffected == 0 { return excode.ErrBalanceNotEnough } // 同一事务内写入消费流水 record := &models.WalletRecord{ WalletIdentity: srv.Body.Identity, TransType: -1, // 支出 InTradeNo: utils.UUID(), Money: amount, TradeType: 3, // 消费 PayChannel: 3, // 钱包余额 PayType: "BALANCE", // 余额支付 } record.Identity = utils.UUID() record.PassportID = srv.Body.PassportID record.PassportIdentity = srv.Body.PassportIdentity models.FillRecordYmd(record) return tx.Create(record).Error }) } // EncodePassword 使用 bcrypt 生成支付密码哈希 func EncodePassword(pwd string) (string, error) { hashed, err := bcrypt.GenerateFromPassword([]byte(pwd), bcrypt.DefaultCost) if err != nil { return "", err } return string(hashed), nil } // VerifyPayPassword 校验支付密码 // 仅接受 bcrypt 哈希;历史 SHA256 摘要无法校验,需通过重置流程重新设置。 func VerifyPayPassword(hashed, pwd string) bool { if hashed == "" || pwd == "" { return false } return bcrypt.CompareHashAndPassword([]byte(hashed), []byte(pwd)) == nil }