2026-08-09 10:43:45 +08:00
|
|
|
// Package basic 钱包基础业务逻辑
|
|
|
|
|
package basic
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
|
|
|
|
|
2026-08-09 12:41:08 +08:00
|
|
|
"bsm/full/module/finance/wallet/internal/excode"
|
|
|
|
|
"bsm/full/module/finance/wallet/internal/impl"
|
|
|
|
|
"bsm/full/module/finance/wallet/internal/models"
|
2026-08-09 10:43:45 +08:00
|
|
|
"git.apinb.com/bsm-sdk/core/errcode"
|
|
|
|
|
"git.apinb.com/bsm-sdk/core/utils"
|
2026-09-22 21:15:34 +08:00
|
|
|
"golang.org/x/crypto/bcrypt"
|
|
|
|
|
"gorm.io/gorm"
|
2026-08-09 10:43:45 +08:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// 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
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-22 21:15:34 +08:00
|
|
|
// 校验支付密码
|
|
|
|
|
if !VerifyPayPassword(wallet.PayPassword, pwd) {
|
2026-08-09 10:43:45 +08:00
|
|
|
return nil, errcode.ErrPassword
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-22 21:15:34 +08:00
|
|
|
// 检查金额合法性
|
|
|
|
|
if amount <= 0 {
|
|
|
|
|
return nil, excode.ErrAmount
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-09 10:43:45 +08:00
|
|
|
// 检查余额是否足够
|
|
|
|
|
if amount > wallet.Balance {
|
|
|
|
|
return nil, excode.ErrBalanceNotEnough
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return &WalletPay{
|
|
|
|
|
Body: wallet,
|
|
|
|
|
}, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TradeConsum 执行交易消费
|
2026-09-22 21:15:34 +08:00
|
|
|
// 使用单条条件更新原子扣减余额与可提现余额,避免"读后写"造成的并发双花;
|
|
|
|
|
// 余额变更与消费流水写入放在同一事务内,保证账实一致。
|
2026-08-09 10:43:45 +08:00
|
|
|
func (srv *WalletPay) TradeConsum(amount int64) error {
|
2026-09-22 21:15:34 +08:00
|
|
|
if amount <= 0 {
|
|
|
|
|
return excode.ErrAmount
|
2026-08-09 10:43:45 +08:00
|
|
|
}
|
2026-09-22 21:15:34 +08:00
|
|
|
|
|
|
|
|
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)
|
2026-08-09 10:43:45 +08:00
|
|
|
if err != nil {
|
2026-09-22 21:15:34 +08:00
|
|
|
return "", err
|
2026-08-09 10:43:45 +08:00
|
|
|
}
|
2026-09-22 21:15:34 +08:00
|
|
|
return string(hashed), nil
|
2026-08-09 10:43:45 +08:00
|
|
|
}
|
|
|
|
|
|
2026-09-22 21:15:34 +08:00
|
|
|
// VerifyPayPassword 校验支付密码
|
|
|
|
|
// 仅接受 bcrypt 哈希;历史 SHA256 摘要无法校验,需通过重置流程重新设置。
|
|
|
|
|
func VerifyPayPassword(hashed, pwd string) bool {
|
|
|
|
|
if hashed == "" || pwd == "" {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
return bcrypt.CompareHashAndPassword([]byte(hashed), []byte(pwd)) == nil
|
2026-08-09 10:43:45 +08:00
|
|
|
}
|