Files
tyc-server-v2/app/main/api/internal/logic/admin_agent/adminreviewbankcardwithdrawallogic.go
2026-02-01 17:35:35 +08:00

488 lines
20 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package admin_agent
import (
"context"
"database/sql"
"math"
"time"
"tyc-server/app/main/model"
"tyc-server/common/xerr"
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/stores/sqlx"
"tyc-server/app/main/api/internal/svc"
"tyc-server/app/main/api/internal/types"
"github.com/zeromicro/go-zero/core/logx"
)
// 审核操作常量
const (
ReviewActionApprove = 1 // 确认
ReviewActionReject = 2 // 拒绝
)
// 状态常量
const (
StatusPending = 1 // 申请中/处理中
StatusSuccess = 2 // 成功
StatusFailed = 3 // 失败
)
// 提现类型常量
const (
WithdrawTypeAlipay = 1 // 支付宝提现
WithdrawTypeBankCard = 2 // 银行卡提现
)
type AdminReviewBankCardWithdrawalLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewAdminReviewBankCardWithdrawalLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminReviewBankCardWithdrawalLogic {
return &AdminReviewBankCardWithdrawalLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *AdminReviewBankCardWithdrawalLogic) AdminReviewBankCardWithdrawal(req *types.AdminReviewBankCardWithdrawalReq) (resp *types.AdminReviewBankCardWithdrawalResp, err error) {
// 验证操作类型
if req.Action != ReviewActionApprove && req.Action != ReviewActionReject {
return nil, errors.Wrapf(xerr.NewErrMsg("操作类型不正确"), "操作类型验证失败")
}
// 拒绝操作必须填写备注
if req.Action == ReviewActionReject && req.Remark == "" {
return nil, errors.Wrapf(xerr.NewErrMsg("拒绝提现必须填写拒绝原因"), "拒绝原因验证失败")
}
resp = &types.AdminReviewBankCardWithdrawalResp{
Success: false,
}
// 使用事务处理审核操作
err = l.svcCtx.AgentModel.Trans(l.ctx, func(ctx context.Context, session sqlx.Session) error {
// 获取提现记录
record, err := l.svcCtx.AgentWithdrawalModel.FindOne(ctx, req.WithdrawalId)
if err != nil {
if errors.Is(err, model.ErrNotFound) {
return errors.Wrapf(xerr.NewErrMsg("提现记录不存在"), "提现记录不存在")
}
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询提现记录失败: %v", err)
}
// 验证提现记录状态必须是申请中
if record.Status != StatusPending {
return errors.Wrapf(xerr.NewErrMsg("该提现记录已处理,无法重复操作"), "状态验证失败")
}
// 验证提现类型(支持银行卡和支付宝提现)
if record.WithdrawType != WithdrawTypeBankCard && record.WithdrawType != WithdrawTypeAlipay {
return errors.Wrapf(xerr.NewErrMsg("提现类型不正确"), "提现类型验证失败")
}
// 确认时使用的扣税比例:请求中传入则用传入值,否则默认 6%
taxRate := 0.06
if req.Action == ReviewActionApprove && req.TaxRate != nil {
taxRate = *req.TaxRate
if taxRate < 0 || taxRate > 1 {
return errors.Wrapf(xerr.NewErrMsg("扣税比例必须在 0100% 之间"), "扣税比例验证失败")
}
}
if req.Action == ReviewActionApprove {
// 确认提现(可带自定义扣税比例)
return l.approveWithdrawal(ctx, session, record, taxRate)
} else {
// 拒绝提现
return l.rejectWithdrawal(ctx, session, record, req.Remark)
}
})
if err != nil {
return nil, err
}
resp.Success = true
return resp, nil
}
// 确认提现taxRate 为扣税比例,如 0.06 表示 6%
func (l *AdminReviewBankCardWithdrawalLogic) approveWithdrawal(ctx context.Context, session sqlx.Session, record *model.AgentWithdrawal, taxRate float64) error {
// 按审核时选择的扣税比例重新计算并更新提现记录与扣税记录
if err := l.applyReviewTaxRate(ctx, session, record, taxRate); err != nil {
return err
}
// 根据提现类型执行不同的操作
if record.WithdrawType == WithdrawTypeAlipay {
// 支付宝提现:按更新后的实际到账金额调用支付宝转账
return l.approveAlipayWithdrawal(ctx, session, record)
} else {
// 银行卡提现:直接更新状态为成功(线下转账)
return l.approveBankCardWithdrawal(ctx, session, record)
}
}
// applyReviewTaxRate 按审核时选择的扣税比例更新提现记录与扣税记录
func (l *AdminReviewBankCardWithdrawalLogic) applyReviewTaxRate(ctx context.Context, session sqlx.Session, record *model.AgentWithdrawal, taxRate float64) error {
// 金额保留两位小数,避免浮点误差并与前端/支付宝一致
newTaxAmount := math.Round(record.Amount*taxRate*100) / 100
newActualAmount := math.Round((record.Amount-newTaxAmount)*100) / 100
record.TaxAmount = newTaxAmount
record.ActualAmount = newActualAmount
if _, err := l.svcCtx.AgentWithdrawalModel.Update(ctx, session, record); err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新提现记录扣税金额失败: %v", err)
}
taxModel, err := l.svcCtx.AgentWithdrawalTaxModel.FindOneByWithdrawalId(ctx, record.Id)
if err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询扣税记录失败: %v", err)
}
taxModel.TaxRate = taxRate
taxModel.TaxAmount = newTaxAmount
taxModel.ActualAmount = newActualAmount
taxModel.TaxableAmount = record.Amount
if err := l.svcCtx.AgentWithdrawalTaxModel.UpdateWithVersion(ctx, session, taxModel); err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新扣税记录失败: %v", err)
}
return nil
}
// 确认支付宝提现
func (l *AdminReviewBankCardWithdrawalLogic) approveAlipayWithdrawal(ctx context.Context, session sqlx.Session, record *model.AgentWithdrawal) error {
// 同步调用支付宝转账
transferResp, err := l.svcCtx.AlipayService.AliTransfer(ctx, record.PayeeAccount, record.PayeeName.String, record.ActualAmount, "公司提现", record.WithdrawNo)
if err != nil {
l.Logger.Errorf("【支付宝转账失败】withdrawNo:%s error:%v", record.WithdrawNo, err)
return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "支付宝接口调用失败: %v", err)
}
switch {
case transferResp.Status == "SUCCESS":
// 立即处理成功状态
return l.completeWithdrawalSuccess(ctx, session, record)
case transferResp.Status == "FAIL" || transferResp.SubCode != "":
// 处理明确失败
errorMsg := l.mapAlipayError(transferResp.SubCode)
return l.completeWithdrawalFailure(ctx, session, record, errorMsg)
case transferResp.Status == "DEALING":
// 处理中状态,更新为处理中但不标记为最终状态
record.Remark = sql.NullString{String: "支付宝转账处理中", Valid: true}
if _, err := l.svcCtx.AgentWithdrawalModel.Update(ctx, session, record); err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新提现记录失败: %v", err)
}
l.Logger.Infof("支付宝提现审核通过,转账处理中 withdrawalId:%d withdrawNo:%s", record.Id, record.WithdrawNo)
return nil
default:
// 未知状态按失败处理
return l.completeWithdrawalFailure(ctx, session, record, "支付宝返回未知状态")
}
}
// 确认银行卡提现
func (l *AdminReviewBankCardWithdrawalLogic) approveBankCardWithdrawal(ctx context.Context, session sqlx.Session, record *model.AgentWithdrawal) error {
// 更新提现记录状态为成功
record.Status = StatusSuccess
record.Remark = sql.NullString{String: "管理员确认提现", Valid: true}
if _, err := l.svcCtx.AgentWithdrawalModel.Update(ctx, session, record); err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新提现记录失败: %v", err)
}
// 解冻资金并扣除FrozenBalance -= amount, Balance不变
wallet, err := l.svcCtx.AgentWalletModel.FindOneByAgentId(ctx, record.AgentId)
if err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询钱包失败: %v", err)
}
// 记录变动前的冻结余额
frozenBalanceBefore := wallet.FrozenBalance
// 更新钱包(减少冻结余额)
wallet.FrozenBalance -= record.Amount
if err := l.svcCtx.AgentWalletModel.UpdateWithVersion(ctx, session, wallet); err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新钱包失败: %v", err)
}
// 记录交易流水(提现成功)
err = l.svcCtx.AgentService.CreateWalletTransaction(
ctx,
session,
wallet.AgentId,
model.WalletTransactionTypeWithdraw,
-record.Amount, // 变动金额(负数表示减少)
wallet.Balance, // 变动前余额(不变)
wallet.Balance, // 变动后余额(不变)
frozenBalanceBefore, // 变动前冻结余额
wallet.FrozenBalance, // 变动后冻结余额
record.WithdrawNo, // 关联交易ID
0, // 关联用户ID
"提现审核通过", // 备注
)
if err != nil {
return err
}
// 更新扣税记录状态为成功
taxModel, err := l.svcCtx.AgentWithdrawalTaxModel.FindOneByWithdrawalId(ctx, record.Id)
if err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询扣税记录失败: %v", err)
}
if taxModel.TaxStatus == model.TaxStatusPending {
taxModel.TaxStatus = model.TaxStatusSuccess // 扣税状态 = 成功
taxModel.TaxTime = sql.NullTime{Time: time.Now(), Valid: true}
if err := l.svcCtx.AgentWithdrawalTaxModel.UpdateWithVersion(ctx, session, taxModel); err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新扣税记录失败: %v", err)
}
}
// 提现成功后,给上级代理发放提现奖励
withdrawRewardErr := l.svcCtx.AgentService.GiveWithdrawReward(ctx, record.AgentId, record.Amount, session)
if withdrawRewardErr != nil {
l.Logger.Errorf("发放提现奖励失败代理ID%d提现金额%f错误%+v", record.AgentId, record.Amount, withdrawRewardErr)
// 提现奖励失败不影响主流程,只记录日志
} else {
l.Logger.Infof("发放提现奖励成功代理ID%d提现金额%f", record.AgentId, record.Amount)
}
l.Logger.Infof("银行卡提现确认成功 withdrawalId:%d amount:%f", record.Id, record.Amount)
return nil
}
// 拒绝提现
func (l *AdminReviewBankCardWithdrawalLogic) rejectWithdrawal(ctx context.Context, session sqlx.Session, record *model.AgentWithdrawal, remark string) error {
// 更新提现记录状态为失败
record.Status = StatusFailed
record.Remark = sql.NullString{String: "管理员拒绝:" + remark, Valid: true}
if _, err := l.svcCtx.AgentWithdrawalModel.Update(ctx, session, record); err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新提现记录失败: %v", err)
}
// 解冻资金FrozenBalance -= amount, Balance += amount
wallet, err := l.svcCtx.AgentWalletModel.FindOneByAgentId(ctx, record.AgentId)
if err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询钱包失败: %v", err)
}
// 记录变动前的余额
balanceBefore := wallet.Balance
frozenBalanceBefore := wallet.FrozenBalance
// 更新钱包(余额增加,冻结余额减少)
wallet.Balance += record.Amount
wallet.FrozenBalance -= record.Amount
if err := l.svcCtx.AgentWalletModel.UpdateWithVersion(ctx, session, wallet); err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新钱包失败: %v", err)
}
// 记录交易流水(解冻)
err = l.svcCtx.AgentService.CreateWalletTransaction(
ctx,
session,
wallet.AgentId,
model.WalletTransactionTypeUnfreeze,
record.Amount, // 变动金额(正数表示增加)
balanceBefore, // 变动前余额
wallet.Balance, // 变动后余额
frozenBalanceBefore, // 变动前冻结余额
wallet.FrozenBalance, // 变动后冻结余额
record.WithdrawNo, // 关联交易ID
0, // 关联用户ID
"提现拒绝,解冻资金", // 备注
)
if err != nil {
return err
}
// 更新扣税记录状态为失败
taxModel, err := l.svcCtx.AgentWithdrawalTaxModel.FindOneByWithdrawalId(ctx, record.Id)
if err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询扣税记录失败: %v", err)
}
if taxModel.TaxStatus == model.TaxStatusPending {
taxModel.TaxStatus = model.TaxStatusFailed // 扣税状态 = 失败
taxModel.TaxTime = sql.NullTime{Time: time.Now(), Valid: true}
if err := l.svcCtx.AgentWithdrawalTaxModel.UpdateWithVersion(ctx, session, taxModel); err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新扣税记录失败: %v", err)
}
}
l.Logger.Infof("银行卡提现拒绝 withdrawalId:%d amount:%f reason:%s", record.Id, record.Amount, remark)
return nil
}
// 完成提现成功(支付宝转账成功后调用)
func (l *AdminReviewBankCardWithdrawalLogic) completeWithdrawalSuccess(ctx context.Context, session sqlx.Session, record *model.AgentWithdrawal) error {
// 更新提现记录状态为成功
record.Status = StatusSuccess
record.Remark = sql.NullString{String: "支付宝转账成功", Valid: true}
if _, err := l.svcCtx.AgentWithdrawalModel.Update(ctx, session, record); err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新提现记录失败: %v", err)
}
// 解冻资金并扣除FrozenBalance -= amount, Balance不变
wallet, err := l.svcCtx.AgentWalletModel.FindOneByAgentId(ctx, record.AgentId)
if err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询钱包失败: %v", err)
}
// 记录变动前的冻结余额
frozenBalanceBefore := wallet.FrozenBalance
// 更新钱包(减少冻结余额)
wallet.FrozenBalance -= record.Amount
if err := l.svcCtx.AgentWalletModel.UpdateWithVersion(ctx, session, wallet); err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新钱包失败: %v", err)
}
// 记录交易流水(提现成功)
err = l.svcCtx.AgentService.CreateWalletTransaction(
ctx,
session,
wallet.AgentId,
model.WalletTransactionTypeWithdraw,
-record.Amount, // 变动金额(负数表示减少)
wallet.Balance, // 变动前余额(不变)
wallet.Balance, // 变动后余额(不变)
frozenBalanceBefore, // 变动前冻结余额
wallet.FrozenBalance, // 变动后冻结余额
record.WithdrawNo, // 关联交易ID
0, // 关联用户ID
"提现成功", // 备注
)
if err != nil {
return err
}
// 更新扣税记录状态为成功
taxModel, err := l.svcCtx.AgentWithdrawalTaxModel.FindOneByWithdrawalId(ctx, record.Id)
if err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询扣税记录失败: %v", err)
}
if taxModel.TaxStatus == model.TaxStatusPending {
taxModel.TaxStatus = model.TaxStatusSuccess // 扣税状态 = 成功
taxModel.TaxTime = sql.NullTime{Time: time.Now(), Valid: true}
if err := l.svcCtx.AgentWithdrawalTaxModel.UpdateWithVersion(ctx, session, taxModel); err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新扣税记录失败: %v", err)
}
}
// 提现成功后,给上级代理发放提现奖励
withdrawRewardErr := l.svcCtx.AgentService.GiveWithdrawReward(ctx, record.AgentId, record.Amount, session)
if withdrawRewardErr != nil {
l.Logger.Errorf("发放提现奖励失败代理ID%d提现金额%f错误%+v", record.AgentId, record.Amount, withdrawRewardErr)
// 提现奖励失败不影响主流程,只记录日志
} else {
l.Logger.Infof("发放提现奖励成功代理ID%d提现金额%f", record.AgentId, record.Amount)
}
l.Logger.Infof("支付宝提现成功 withdrawalId:%d withdrawNo:%s", record.Id, record.WithdrawNo)
return nil
}
// 完成提现失败(支付宝转账失败后调用)
func (l *AdminReviewBankCardWithdrawalLogic) completeWithdrawalFailure(ctx context.Context, session sqlx.Session, record *model.AgentWithdrawal, errorMsg string) error {
// 更新提现记录状态为失败
record.Status = StatusFailed
record.Remark = sql.NullString{String: errorMsg, Valid: true}
if _, err := l.svcCtx.AgentWithdrawalModel.Update(ctx, session, record); err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新提现记录失败: %v", err)
}
// 解冻资金FrozenBalance -= amount, Balance += amount
wallet, err := l.svcCtx.AgentWalletModel.FindOneByAgentId(ctx, record.AgentId)
if err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询钱包失败: %v", err)
}
// 记录变动前的余额
balanceBefore := wallet.Balance
frozenBalanceBefore := wallet.FrozenBalance
// 更新钱包(余额增加,冻结余额减少)
wallet.Balance += record.Amount
wallet.FrozenBalance -= record.Amount
if err := l.svcCtx.AgentWalletModel.UpdateWithVersion(ctx, session, wallet); err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新钱包失败: %v", err)
}
// 记录交易流水(解冻)
err = l.svcCtx.AgentService.CreateWalletTransaction(
ctx,
session,
wallet.AgentId,
model.WalletTransactionTypeUnfreeze,
record.Amount, // 变动金额(正数表示增加)
balanceBefore, // 变动前余额
wallet.Balance, // 变动后余额
frozenBalanceBefore, // 变动前冻结余额
wallet.FrozenBalance, // 变动后冻结余额
record.WithdrawNo, // 关联交易ID
0, // 关联用户ID
"提现失败,解冻资金", // 备注
)
if err != nil {
return err
}
// 更新扣税记录状态为失败
taxModel, err := l.svcCtx.AgentWithdrawalTaxModel.FindOneByWithdrawalId(ctx, record.Id)
if err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询扣税记录失败: %v", err)
}
if taxModel.TaxStatus == model.TaxStatusPending {
taxModel.TaxStatus = model.TaxStatusFailed // 扣税状态 = 失败
taxModel.TaxTime = sql.NullTime{Time: time.Now(), Valid: true}
if err := l.svcCtx.AgentWithdrawalTaxModel.UpdateWithVersion(ctx, session, taxModel); err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新扣税记录失败: %v", err)
}
}
l.Logger.Infof("支付宝提现失败 withdrawalId:%d withdrawNo:%s reason:%s", record.Id, record.WithdrawNo, errorMsg)
return nil
}
// 错误类型映射
func (l *AdminReviewBankCardWithdrawalLogic) mapAlipayError(code string) string {
errorMapping := map[string]string{
// 账户存在性错误
"PAYEE_ACCOUNT_NOT_EXSIT": "收款账户不存在,请检查账号是否正确",
"PAYEE_NOT_EXIST": "收款账户不存在或姓名有误,请核实信息",
"PAYEE_ACC_OCUPIED": "收款账号存在多个账户,无法确认唯一性",
"PAYEE_MID_CANNOT_SAME": "收款方和中间方不能是同一个人,请修改收款方或者中间方信息",
// 实名认证问题
"PAYEE_CERTIFY_LEVEL_LIMIT": "收款方未完成实名认证",
"PAYEE_NOT_RELNAME_CERTIFY": "收款方未完成实名认证",
"PAYEE_CERT_INFO_ERROR": "收款方证件信息不匹配",
// 账户状态异常
"PAYEE_ACCOUNT_STATUS_ERROR": "收款账户状态异常,请更换账号",
"PAYEE_USERINFO_STATUS_ERROR": "收款账户状态异常,无法收款",
"PERMIT_LIMIT_PAYEE": "收款账户异常,请更换账号",
"BLOCK_USER_FORBBIDEN_RECIEVE": "账户冻结无法收款",
"PAYEE_TRUSTEESHIP_ACC_OVER_LIMIT": "收款方托管子户累计收款金额超限",
// 账户信息错误
"PAYEE_USERINFO_ERROR": "收款方姓名或信息不匹配",
"PAYEE_CARD_INFO_ERROR": "收款支付宝账号及户名不一致",
"PAYEE_IDENTITY_NOT_MATCH": "收款方身份信息不匹配",
"PAYEE_USER_IS_INST": "收款方为金融机构,不能使用提现功能,请更换收款账号",
"PAYEE_USER_TYPE_ERROR": "该支付宝账号类型不支持提现,请更换收款账号",
// 权限与限制
"PAYEE_RECEIVE_COUNT_EXCEED_LIMIT": "收款次数超限,请明日再试",
"PAYEE_OUT_PERMLIMIT_CHECK_FAILURE": "收款方权限校验不通过",
"PERMIT_NON_BANK_LIMIT_PAYEE": "收款方未完善身份信息,无法收款",
}
if msg, ok := errorMapping[code]; ok {
return msg
}
return "系统错误,请联系客服"
}