This commit is contained in:
Mrx
2026-03-23 17:13:49 +08:00
parent e10c69fe30
commit c4c9722250
8 changed files with 229 additions and 174 deletions

View File

@@ -4,10 +4,10 @@ import (
"context"
"database/sql"
"fmt"
"time"
"qnc-server/common/globalkey"
"qnc-server/common/xerr"
"qnc-server/pkg/lzkit/lzUtils"
"time"
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/stores/sqlx"
@@ -24,6 +24,11 @@ type AdminAuditWithdrawalLogic struct {
svcCtx *svc.ServiceContext
}
const (
withdrawMethodAlipay int64 = 1
withdrawMethodBankCard int64 = 2
)
func NewAdminAuditWithdrawalLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminAuditWithdrawalLogic {
return &AdminAuditWithdrawalLogic{
Logger: logx.WithContext(ctx),
@@ -47,84 +52,61 @@ func (l *AdminAuditWithdrawalLogic) AdminAuditWithdrawal(req *types.AdminAuditWi
// 4. 使用事务处理审核
err = l.svcCtx.AgentWithdrawalModel.Trans(l.ctx, func(transCtx context.Context, session sqlx.Session) error {
if req.Status == 2 { // 审核通过
// 4.1 更新提现记录状态为提现中
withdrawal.Status = 4 // 提现中
withdrawal.Remark = sql.NullString{String: req.Remark, Valid: true}
// 4.1 支付宝提现自动打款;银行卡提现为手动打款(审核时仅确认已人工打款)
if withdrawal.WithdrawMethod == withdrawMethodAlipay {
outBizNo := withdrawal.WithdrawNo
transferResp, err := l.svcCtx.AlipayService.AliTransfer(transCtx, withdrawal.PayeeAccount, withdrawal.PayeeName, withdrawal.ActualAmount, "代理提现", outBizNo)
if err != nil {
withdrawal.Status = 6 // 提现失败
withdrawal.Remark = sql.NullString{String: fmt.Sprintf("支付宝转账失败: %v", err), Valid: true}
_ = l.svcCtx.AgentWithdrawalModel.UpdateWithVersion(transCtx, session, withdrawal)
return errors.Wrapf(err, "支付宝转账失败")
}
if transferResp.Status != "SUCCESS" {
withdrawal.Status = 6 // 提现失败
withdrawal.Remark = sql.NullString{String: l.mapAlipayError(transferResp.SubCode), Valid: true}
if err := l.svcCtx.AgentWithdrawalModel.UpdateWithVersion(transCtx, session, withdrawal); err != nil {
return errors.Wrapf(err, "更新提现记录失败")
}
return nil
}
}
if withdrawal.WithdrawMethod == withdrawMethodBankCard {
withdrawal.Remark = sql.NullString{String: req.Remark, Valid: req.Remark != ""}
}
// 4.2 审核通过后从冻结余额扣减
wallet, err := l.svcCtx.AgentWalletModel.FindOneByAgentId(transCtx, withdrawal.AgentId)
if err != nil {
return errors.Wrapf(err, "查询钱包失败")
}
if wallet.FrozenBalance < withdrawal.Amount {
withdrawal.Status = 6
withdrawal.Remark = sql.NullString{String: "审核通过失败:冻结余额不足", Valid: true}
_ = l.svcCtx.AgentWithdrawalModel.UpdateWithVersion(transCtx, session, withdrawal)
return errors.Wrapf(xerr.NewErrMsg("冻结余额不足,无法通过审核"), "")
}
wallet.FrozenBalance -= withdrawal.Amount
wallet.WithdrawnAmount += withdrawal.Amount
if err := l.svcCtx.AgentWalletModel.UpdateWithVersion(transCtx, session, wallet); err != nil {
return errors.Wrapf(err, "更新钱包失败")
}
// 4.3 更新提现记录状态为成功
withdrawal.Status = 5
if err := l.svcCtx.AgentWithdrawalModel.UpdateWithVersion(transCtx, session, withdrawal); err != nil {
return errors.Wrapf(err, "更新提现记录失败")
}
// 4.2 调用支付宝转账接口
outBizNo := withdrawal.WithdrawNo
transferResp, err := l.svcCtx.AlipayService.AliTransfer(transCtx, withdrawal.PayeeAccount, withdrawal.PayeeName, withdrawal.ActualAmount, "代理提现", outBizNo)
if err != nil {
// 转账失败,更新状态为失败
withdrawal.Status = 6 // 提现失败
withdrawal.Remark = sql.NullString{String: fmt.Sprintf("转账失败: %v", err), Valid: true}
l.svcCtx.AgentWithdrawalModel.UpdateWithVersion(transCtx, session, withdrawal)
// 解冻余额
wallet, err := l.svcCtx.AgentWalletModel.FindOneByAgentId(transCtx, withdrawal.AgentId)
if err == nil {
wallet.FrozenBalance -= withdrawal.Amount
wallet.Balance += withdrawal.Amount
l.svcCtx.AgentWalletModel.UpdateWithVersion(transCtx, session, wallet)
}
return errors.Wrapf(err, "支付宝转账失败")
}
// 4.3 根据转账结果更新状态
switch transferResp.Status {
case "SUCCESS":
// 转账成功
withdrawal.Status = 5 // 提现成功
if err := l.svcCtx.AgentWithdrawalModel.UpdateWithVersion(transCtx, session, withdrawal); err != nil {
return errors.Wrapf(err, "更新提现记录失败")
}
// 更新钱包(解冻并扣除)
wallet, err := l.svcCtx.AgentWalletModel.FindOneByAgentId(transCtx, withdrawal.AgentId)
if err != nil {
return errors.Wrapf(err, "查询钱包失败")
}
wallet.FrozenBalance -= withdrawal.Amount
wallet.WithdrawnAmount += withdrawal.Amount
if err := l.svcCtx.AgentWalletModel.UpdateWithVersion(transCtx, session, wallet); err != nil {
return errors.Wrapf(err, "更新钱包失败")
}
// 更新扣税记录状态
taxBuilder := l.svcCtx.AgentWithdrawalTaxModel.SelectBuilder().
Where("withdrawal_id = ? AND del_state = ?", withdrawal.Id, globalkey.DelStateNo)
taxRecords, err := l.svcCtx.AgentWithdrawalTaxModel.FindAll(transCtx, taxBuilder, "")
if err == nil && len(taxRecords) > 0 {
taxRecord := taxRecords[0]
taxRecord.TaxStatus = 2 // 已扣税
taxRecord.TaxTime = lzUtils.TimeToNullTime(time.Now())
l.svcCtx.AgentWithdrawalTaxModel.UpdateWithVersion(transCtx, session, taxRecord)
}
case "FAIL":
// 转账失败
withdrawal.Status = 6 // 提现失败
errorMsg := l.mapAlipayError(transferResp.SubCode)
withdrawal.Remark = sql.NullString{String: errorMsg, Valid: true}
if err := l.svcCtx.AgentWithdrawalModel.UpdateWithVersion(transCtx, session, withdrawal); err != nil {
return errors.Wrapf(err, "更新提现记录失败")
}
// 解冻余额
wallet, err := l.svcCtx.AgentWalletModel.FindOneByAgentId(transCtx, withdrawal.AgentId)
if err == nil {
wallet.FrozenBalance -= withdrawal.Amount
wallet.Balance += withdrawal.Amount
l.svcCtx.AgentWalletModel.UpdateWithVersion(transCtx, session, wallet)
}
case "DEALING":
// 处理中,保持提现中状态,后续通过轮询更新
// 状态已经是4提现中无需更新
// 4.4 更新扣税记录状态
taxBuilder := l.svcCtx.AgentWithdrawalTaxModel.SelectBuilder().
Where("withdrawal_id = ? AND del_state = ?", withdrawal.Id, globalkey.DelStateNo)
taxRecords, err := l.svcCtx.AgentWithdrawalTaxModel.FindAll(transCtx, taxBuilder, "")
if err == nil && len(taxRecords) > 0 {
taxRecord := taxRecords[0]
taxRecord.TaxStatus = 2 // 已扣税
taxRecord.TaxTime = lzUtils.TimeToNullTime(time.Now())
_ = l.svcCtx.AgentWithdrawalTaxModel.UpdateWithVersion(transCtx, session, taxRecord)
}
} else if req.Status == 3 { // 审核拒绝
@@ -135,7 +117,7 @@ func (l *AdminAuditWithdrawalLogic) AdminAuditWithdrawal(req *types.AdminAuditWi
return errors.Wrapf(err, "更新提现记录失败")
}
// 4.2 解冻余额
// 4.2 审核拒绝:解冻余额
wallet, err := l.svcCtx.AgentWalletModel.FindOneByAgentId(transCtx, withdrawal.AgentId)
if err != nil {
return errors.Wrapf(err, "查询钱包失败")

View File

@@ -7,6 +7,8 @@ import (
"qnc-server/common/ctxdata"
"qnc-server/common/xerr"
"qnc-server/pkg/lzkit/lzUtils"
"regexp"
"strings"
"time"
"github.com/google/uuid"
@@ -25,6 +27,11 @@ type ApplyWithdrawalLogic struct {
svcCtx *svc.ServiceContext
}
const (
withdrawMethodAlipay int64 = 1
withdrawMethodBankCard int64 = 2
)
func NewApplyWithdrawalLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ApplyWithdrawalLogic {
return &ApplyWithdrawalLogic{
Logger: logx.WithContext(ctx),
@@ -65,6 +72,32 @@ func (l *ApplyWithdrawalLogic) ApplyWithdrawal(req *types.ApplyWithdrawalReq) (r
if req.Amount <= 0 {
return nil, errors.Wrapf(xerr.NewErrMsg("提现金额必须大于0"), "")
}
if req.WithdrawMethod != withdrawMethodAlipay && req.WithdrawMethod != withdrawMethodBankCard {
return nil, errors.Wrapf(xerr.NewErrMsg("提现方式不支持"), "")
}
if strings.TrimSpace(req.PayeeName) == "" {
return nil, errors.Wrapf(xerr.NewErrMsg("收款人姓名不能为空"), "")
}
if req.WithdrawMethod == withdrawMethodAlipay {
if strings.TrimSpace(req.PayeeAccount) == "" {
return nil, errors.Wrapf(xerr.NewErrMsg("支付宝账号不能为空"), "")
}
} else {
if strings.TrimSpace(req.BankName) == "" {
return nil, errors.Wrapf(xerr.NewErrMsg("开户行不能为空"), "")
}
bankCardNo := strings.TrimSpace(req.BankCardNo)
if bankCardNo == "" {
return nil, errors.Wrapf(xerr.NewErrMsg("银行卡号不能为空"), "")
}
if !regexp.MustCompile(`^\d{13,19}$`).MatchString(bankCardNo) {
return nil, errors.Wrapf(xerr.NewErrMsg("银行卡号格式不正确"), "")
}
if !regexp.MustCompile(`^1[3-9]\d{9}$`).MatchString(strings.TrimSpace(req.BankReservedMobile)) {
return nil, errors.Wrapf(xerr.NewErrMsg("银行预留手机号格式不正确"), "")
}
}
// 4. 获取钱包信息
wallet, err := l.svcCtx.AgentWalletModel.FindOneByAgentId(l.ctx, agent.Id)
@@ -85,9 +118,9 @@ func (l *ApplyWithdrawalLogic) ApplyWithdrawal(req *types.ApplyWithdrawalReq) (r
}
// 7. 生成提现单号
withdrawNo := fmt.Sprintf("WD%d%d", time.Now().Unix(), agent.Id)
withdrawNo := fmt.Sprintf("WD%d%s", time.Now().Unix(), agent.Id)
// 8. 使用事务处理提现申请
// 8. 使用事务处理提现申请(申请时冻结余额,审核通过后扣减冻结余额)
var withdrawalId string
err = l.svcCtx.AgentWalletModel.Trans(l.ctx, func(transCtx context.Context, session sqlx.Session) error {
// 8.1 冻结余额
@@ -99,15 +132,19 @@ func (l *ApplyWithdrawalLogic) ApplyWithdrawal(req *types.ApplyWithdrawalReq) (r
// 8.2 创建提现记录
withdrawal := &model.AgentWithdrawal{
Id: uuid.New().String(),
AgentId: agent.Id,
WithdrawNo: withdrawNo,
PayeeAccount: req.PayeeAccount,
PayeeName: req.PayeeName,
Amount: req.Amount,
ActualAmount: taxInfo.ActualAmount,
TaxAmount: taxInfo.TaxAmount,
Status: 1, // 处理中(待审核)
Id: uuid.New().String(),
AgentId: agent.Id,
WithdrawNo: withdrawNo,
WithdrawMethod: req.WithdrawMethod,
PayeeAccount: strings.TrimSpace(req.PayeeAccount),
PayeeName: strings.TrimSpace(req.PayeeName),
BankName: strings.TrimSpace(req.BankName),
BankCardNo: strings.TrimSpace(req.BankCardNo),
BankReservedMobile: strings.TrimSpace(req.BankReservedMobile),
Amount: req.Amount,
ActualAmount: taxInfo.ActualAmount,
TaxAmount: taxInfo.TaxAmount,
Status: 1, // 待审核
}
_, err := l.svcCtx.AgentWithdrawalModel.Insert(transCtx, session, withdrawal)

View File

@@ -82,16 +82,20 @@ func (l *GetWithdrawalListLogic) GetWithdrawalList(req *types.GetWithdrawalListR
}
list = append(list, types.WithdrawalItem{
Id: withdrawal.Id,
WithdrawalNo: withdrawal.WithdrawNo,
Amount: withdrawal.Amount,
TaxAmount: withdrawal.TaxAmount,
ActualAmount: withdrawal.ActualAmount,
Status: withdrawal.Status,
PayeeAccount: withdrawal.PayeeAccount,
PayeeName: withdrawal.PayeeName,
Remark: remark,
CreateTime: withdrawal.CreateTime.Format("2006-01-02 15:04:05"),
Id: withdrawal.Id,
WithdrawalNo: withdrawal.WithdrawNo,
WithdrawMethod: withdrawal.WithdrawMethod,
Amount: withdrawal.Amount,
TaxAmount: withdrawal.TaxAmount,
ActualAmount: withdrawal.ActualAmount,
Status: withdrawal.Status,
PayeeAccount: withdrawal.PayeeAccount,
PayeeName: withdrawal.PayeeName,
BankName: withdrawal.BankName,
BankCardNo: withdrawal.BankCardNo,
BankReservedMobile: withdrawal.BankReservedMobile,
Remark: remark,
CreateTime: withdrawal.CreateTime.Format("2006-01-02 15:04:05"),
})
}

View File

@@ -5,6 +5,7 @@ import (
"database/sql"
"encoding/hex"
"fmt"
"os"
"qnc-server/app/main/model"
"qnc-server/common/ctxdata"
"qnc-server/common/xerr"
@@ -61,37 +62,40 @@ func (l *RealNameAuthLogic) RealNameAuth(req *types.RealNameAuthReq) (resp *type
return nil, errors.Wrapf(xerr.NewErrMsg("手机号与代理注册手机号不匹配"), "")
}
// 3. 验证验证码
encryptedMobile, err := crypto.EncryptMobile(req.Mobile, l.svcCtx.Config.Encrypt.SecretKey)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "加密手机号失败, %v", err)
}
redisKey := fmt.Sprintf("realName:%s", encryptedMobile)
cacheCode, err := l.svcCtx.Redis.Get(redisKey)
if err != nil {
if errors.Is(err, redis.Nil) {
return nil, errors.Wrapf(xerr.NewErrMsg("验证码已过期"), "")
// 3. 开发环境下跳过验证码和三要素核
if os.Getenv("ENV") != "development" {
// 3.1 验证验证码
encryptedMobile, err := crypto.EncryptMobile(req.Mobile, l.svcCtx.Config.Encrypt.SecretKey)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "加密手机号失败, %v", err)
}
redisKey := fmt.Sprintf("realName:%s", encryptedMobile)
cacheCode, err := l.svcCtx.Redis.Get(redisKey)
if err != nil {
if errors.Is(err, redis.Nil) {
return nil, errors.Wrapf(xerr.NewErrMsg("验证码已过期"), "")
}
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "读取验证码失败, %v", err)
}
if cacheCode != req.Code {
return nil, errors.Wrapf(xerr.NewErrMsg("验证码不正确"), "")
}
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "读取验证码失败, %v", err)
}
if cacheCode != req.Code {
return nil, errors.Wrapf(xerr.NewErrMsg("验证码不正确"), "")
}
// 4. 三要素核验(姓名、身份证号、手机号)
verification, err := l.svcCtx.VerificationService.ThreeFactorVerification(service.ThreeFactorVerificationRequest{
Name: req.Name,
IDCard: req.IdCard,
Mobile: req.Mobile,
})
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "三要素核验失败: %v", err)
}
if !verification.Passed {
if verification.Err != nil {
return nil, errors.Wrapf(xerr.NewErrMsg(verification.Err.Error()), "三要素核验不通过")
// 3.2 三要素核验(姓名、身份证号、手机号)
verification, err := l.svcCtx.VerificationService.ThreeFactorVerification(service.ThreeFactorVerificationRequest{
Name: req.Name,
IDCard: req.IdCard,
Mobile: req.Mobile,
})
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "三要素核验失败: %v", err)
}
if !verification.Passed {
if verification.Err != nil {
return nil, errors.Wrapf(xerr.NewErrMsg(verification.Err.Error()), "三要素核验不通过")
}
return nil, errors.Wrapf(xerr.NewErrMsg("三要素核验不通过"), "")
}
return nil, errors.Wrapf(xerr.NewErrMsg("三要素核验不通过"), "")
}
// 5. 检查是否已有实名认证记录