2026-08-09 10:43:45 +08:00
|
|
|
|
package basic
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
|
"context"
|
|
|
|
|
|
"time"
|
|
|
|
|
|
|
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"
|
|
|
|
|
|
pb "bsm/full/module/finance/wallet/pb"
|
2026-08-09 10:43:45 +08:00
|
|
|
|
"git.apinb.com/bsm-sdk/core/errcode"
|
|
|
|
|
|
"git.apinb.com/bsm-sdk/core/printer"
|
|
|
|
|
|
"git.apinb.com/bsm-sdk/core/service"
|
|
|
|
|
|
"git.apinb.com/bsm-sdk/core/utils"
|
|
|
|
|
|
"git.apinb.com/bsm-sdk/core/vars"
|
2026-09-22 21:15:34 +08:00
|
|
|
|
"gorm.io/gorm"
|
2026-08-09 10:43:45 +08:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// 申请提现
|
2026-09-22 21:15:34 +08:00
|
|
|
|
// 在同一事务内先原子扣减余额/可提现余额,再写入提现单与流水,
|
|
|
|
|
|
// 并通过条件更新(balance >= amount)保证余额不足时不会生成提现单,避免重复提交超额提现。
|
2026-08-09 10:43:45 +08:00
|
|
|
|
func ApplyCash(ctx context.Context, in *pb.ApplyCashRequest) (reply *pb.StatusReply, err error) {
|
|
|
|
|
|
auth, ok := service.ParseMetaCtx(ctx, nil)
|
|
|
|
|
|
if ok != nil {
|
|
|
|
|
|
return nil, ok
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-22 21:15:34 +08:00
|
|
|
|
if in.Amount <= 0 || in.Channel == 0 {
|
2026-08-09 10:43:45 +08:00
|
|
|
|
return nil, errcode.ErrInvalidArgument
|
|
|
|
|
|
}
|
2026-09-22 21:15:34 +08:00
|
|
|
|
// 提现渠道仅支持 1:微信 2:支付宝 3:银行卡
|
|
|
|
|
|
if in.Channel != 1 && in.Channel != 2 && in.Channel != 3 {
|
|
|
|
|
|
return nil, excode.ErrPayChannel
|
|
|
|
|
|
}
|
2026-08-09 10:43:45 +08:00
|
|
|
|
|
|
|
|
|
|
myWallet, err := models.GetWalletByPassportIdentity(auth.ID, auth.Identity)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
printer.Error(err.Error())
|
|
|
|
|
|
return nil, errcode.ErrDB
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-22 21:15:34 +08:00
|
|
|
|
// cashNo 同时作为提现单幂等键与内部流水号
|
|
|
|
|
|
cashNo := utils.UUID()
|
2026-08-09 10:43:45 +08:00
|
|
|
|
|
2026-09-22 21:15:34 +08:00
|
|
|
|
err = impl.DBService.Transaction(func(tx *gorm.DB) error {
|
|
|
|
|
|
// 条件更新:余额与可提现余额均充足且钱包未禁用时才扣减
|
|
|
|
|
|
res := tx.Model(&models.WalletBasic{}).
|
|
|
|
|
|
Where("identity = ? AND status <> -1 AND balance >= ? AND withdrawal_balance >= ?", myWallet.Identity, in.Amount, in.Amount).
|
|
|
|
|
|
UpdateColumns(map[string]interface{}{
|
|
|
|
|
|
"balance": gorm.Expr("balance - ?", in.Amount),
|
|
|
|
|
|
"withdrawal_balance": gorm.Expr("withdrawal_balance - ?", in.Amount),
|
|
|
|
|
|
})
|
|
|
|
|
|
if res.Error != nil {
|
|
|
|
|
|
return res.Error
|
|
|
|
|
|
}
|
|
|
|
|
|
if res.RowsAffected == 0 {
|
|
|
|
|
|
return excode.ErrBalanceNotEnough
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 生成提现单
|
|
|
|
|
|
data := &models.WalletApplyCash{
|
|
|
|
|
|
WalletIdentity: myWallet.Identity,
|
|
|
|
|
|
CashNo: cashNo,
|
|
|
|
|
|
Amount: in.Amount,
|
|
|
|
|
|
Channel: int8(in.Channel),
|
|
|
|
|
|
Remark: in.Remark,
|
|
|
|
|
|
}
|
|
|
|
|
|
data.Identity = utils.UUID()
|
|
|
|
|
|
data.PassportID = auth.ID
|
|
|
|
|
|
data.PassportIdentity = auth.Identity
|
|
|
|
|
|
if err := tx.Create(data).Error; err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
2026-08-09 10:43:45 +08:00
|
|
|
|
|
2026-09-22 21:15:34 +08:00
|
|
|
|
// 同一事务内写入提现流水
|
|
|
|
|
|
record := &models.WalletRecord{
|
|
|
|
|
|
WalletIdentity: myWallet.Identity,
|
|
|
|
|
|
TransType: -1, // 支出
|
|
|
|
|
|
InTradeNo: cashNo, // 内部流水号
|
|
|
|
|
|
Money: in.Amount,
|
|
|
|
|
|
TradeType: 2, // 提现
|
|
|
|
|
|
PayChannel: int8(in.Channel),
|
|
|
|
|
|
PayType: "CASH", // 提现
|
|
|
|
|
|
Remark: in.Remark,
|
|
|
|
|
|
}
|
|
|
|
|
|
record.Identity = utils.UUID()
|
|
|
|
|
|
record.PassportID = auth.ID
|
|
|
|
|
|
record.PassportIdentity = auth.Identity
|
|
|
|
|
|
models.FillRecordYmd(record)
|
|
|
|
|
|
return tx.Create(record).Error
|
|
|
|
|
|
})
|
2026-08-09 10:43:45 +08:00
|
|
|
|
if err != nil {
|
2026-09-22 21:15:34 +08:00
|
|
|
|
if err == excode.ErrBalanceNotEnough {
|
|
|
|
|
|
return nil, excode.ErrBalanceNotEnough
|
|
|
|
|
|
}
|
2026-08-09 10:43:45 +08:00
|
|
|
|
printer.Error(err.Error())
|
|
|
|
|
|
return nil, errcode.ErrDB
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return &pb.StatusReply{
|
|
|
|
|
|
Message: vars.OK,
|
|
|
|
|
|
Timeseq: time.Now().UnixMilli(),
|
|
|
|
|
|
}, nil
|
|
|
|
|
|
}
|