|
|
|
|
@@ -2,6 +2,7 @@ package agent
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
|
|
|
|
"database/sql"
|
|
|
|
|
"fmt"
|
|
|
|
|
"qnc-server/app/main/model"
|
|
|
|
|
"qnc-server/common/ctxdata"
|
|
|
|
|
@@ -53,16 +54,18 @@ func (l *AgentWithdrawalLogic) AgentWithdrawal(req *types.WithdrawalReq) (*types
|
|
|
|
|
outBizNo string
|
|
|
|
|
withdrawRes = &types.WithdrawalResp{}
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
var finalWithdrawAmount float64 // 实际到账金额
|
|
|
|
|
// 使用事务处理核心操作
|
|
|
|
|
err := l.svcCtx.AgentModel.Trans(l.ctx, func(ctx context.Context, session sqlx.Session) error {
|
|
|
|
|
userID, err := ctxdata.GetUidFromCtx(l.ctx)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取用户ID失败: %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 查询代理信息
|
|
|
|
|
agentModel, err := l.svcCtx.AgentModel.FindOneByUserId(l.ctx, userID)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询代理信息失败: %v", err)
|
|
|
|
|
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询代理信息失败: %v", err)
|
|
|
|
|
}
|
|
|
|
|
agentRealName, err := l.svcCtx.AgentRealNameModel.FindOneByAgentId(l.ctx, agentModel.Id)
|
|
|
|
|
if err != nil {
|
|
|
|
|
@@ -77,7 +80,6 @@ func (l *AgentWithdrawalLogic) AgentWithdrawal(req *types.WithdrawalReq) (*types
|
|
|
|
|
if agentRealName.Name != req.PayeeName {
|
|
|
|
|
return errors.Wrapf(xerr.NewErrMsg("您的实名认证信息不匹配, 无法提现"), "您的实名认证信息不匹配")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 查询钱包
|
|
|
|
|
agentWallet, err := l.svcCtx.AgentWalletModel.FindOneByAgentId(l.ctx, agentModel.Id)
|
|
|
|
|
if err != nil {
|
|
|
|
|
@@ -92,16 +94,82 @@ func (l *AgentWithdrawalLogic) AgentWithdrawal(req *types.WithdrawalReq) (*types
|
|
|
|
|
// 生成交易号
|
|
|
|
|
outBizNo = "W_" + l.svcCtx.AlipayService.GenerateOutTradeNo()
|
|
|
|
|
|
|
|
|
|
// 创建提现记录(初始状态为处理中)
|
|
|
|
|
if err = l.createWithdrawalRecord(session, agentModel.Id, req, outBizNo); err != nil {
|
|
|
|
|
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "创建提现记录失败: %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 冻结资金(事务内操作)
|
|
|
|
|
if err = l.freezeFunds(session, agentWallet, req.Amount); err != nil {
|
|
|
|
|
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "资金冻结失败: %v", err)
|
|
|
|
|
}
|
|
|
|
|
yearMonth := int64(time.Now().Year()*100 + int(time.Now().Month()))
|
|
|
|
|
// 计算税务额度
|
|
|
|
|
taxExemption, err := l.svcCtx.AgentWithdrawalTaxExemptionModel.FindOneByAgentIdYearMonth(l.ctx, agentModel.Id, yearMonth)
|
|
|
|
|
if err != nil {
|
|
|
|
|
if errors.Is(err, model.ErrNotFound) {
|
|
|
|
|
taxExemption, err = l.createMonthlyExemption(session, agentModel.Id, yearMonth)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "创建代理税务额度失败: %v", err)
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取代理税务额度失败: %v", err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
var taxAmount float64 // 应缴税费
|
|
|
|
|
var taxDeductionPart float64 // 应税金额
|
|
|
|
|
var TaxStatus int64 // 扣税状态
|
|
|
|
|
var exemptionAmount float64 // 免税金额
|
|
|
|
|
taxRate := l.svcCtx.Config.TaxConfig.TaxRate
|
|
|
|
|
|
|
|
|
|
if taxExemption.RemainingExemptionAmount < req.Amount {
|
|
|
|
|
// 超过免税额度需要扣税
|
|
|
|
|
exemptionAmount = taxExemption.RemainingExemptionAmount // 免税金额 = 剩余免税额度
|
|
|
|
|
TaxStatus = model.TaxStatusPending // 扣税状态 = 待扣税
|
|
|
|
|
taxDeductionPart = req.Amount - taxExemption.RemainingExemptionAmount // 应税金额 = 提现金额 - 剩余免税额度
|
|
|
|
|
taxAmount = taxDeductionPart * taxRate // 应缴税费 = 应税金额 * 税率
|
|
|
|
|
finalWithdrawAmount = req.Amount - taxAmount // 实际到账金额 = 提现金额 - 应缴税费
|
|
|
|
|
|
|
|
|
|
taxExemption.UsedExemptionAmount += exemptionAmount // 已使用免税额度 = 已使用免税额度 + 免税金额
|
|
|
|
|
taxExemption.RemainingExemptionAmount -= exemptionAmount // 剩余免税额度 = 剩余免税额度 - 免税金额
|
|
|
|
|
err = l.svcCtx.AgentWithdrawalTaxExemptionModel.UpdateWithVersion(l.ctx, session, taxExemption)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新代理税务额度失败: %v", err)
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// 未超过免税额度,免税
|
|
|
|
|
exemptionAmount = req.Amount // 免税金额 = 提现金额
|
|
|
|
|
TaxStatus = model.TaxStatusExempt // 扣税状态 = 免税
|
|
|
|
|
taxDeductionPart = 0 // 应税金额 = 0
|
|
|
|
|
finalWithdrawAmount = req.Amount // 实际到账金额 = 提现金额
|
|
|
|
|
taxAmount = 0 // 应缴税费 = 0
|
|
|
|
|
taxExemption.UsedExemptionAmount += exemptionAmount // 已使用免税额度 = 已使用免税额度 + 免税金额
|
|
|
|
|
taxExemption.RemainingExemptionAmount -= exemptionAmount // 剩余免税额度 = 剩余免税额度 - 免税金额
|
|
|
|
|
err = l.svcCtx.AgentWithdrawalTaxExemptionModel.UpdateWithVersion(l.ctx, session, taxExemption)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新代理税务额度失败: %v", err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 创建提现记录(初始状态为处理中)
|
|
|
|
|
withdrawalID, err := l.createWithdrawalRecord(session, agentModel.Id, req.PayeeAccount, req.Amount, finalWithdrawAmount, taxAmount, outBizNo)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "创建提现记录失败: %v", err)
|
|
|
|
|
}
|
|
|
|
|
// 扣税记录
|
|
|
|
|
taxModel := &model.AgentWithdrawalTax{
|
|
|
|
|
AgentId: agentModel.Id,
|
|
|
|
|
YearMonth: yearMonth,
|
|
|
|
|
WithdrawalId: withdrawalID,
|
|
|
|
|
WithdrawalAmount: req.Amount,
|
|
|
|
|
ExemptionAmount: exemptionAmount,
|
|
|
|
|
TaxableAmount: taxDeductionPart,
|
|
|
|
|
TaxRate: taxRate,
|
|
|
|
|
TaxAmount: taxAmount,
|
|
|
|
|
ActualAmount: finalWithdrawAmount,
|
|
|
|
|
TaxStatus: TaxStatus,
|
|
|
|
|
Remark: sql.NullString{String: "提现成功自动扣税", Valid: true},
|
|
|
|
|
ExemptionRecordId: taxExemption.Id,
|
|
|
|
|
}
|
|
|
|
|
_, err = l.svcCtx.AgentWithdrawalTaxModel.Insert(ctx, session, taxModel)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "创建扣税记录失败: %v", err)
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
@@ -110,8 +178,9 @@ func (l *AgentWithdrawalLogic) AgentWithdrawal(req *types.WithdrawalReq) (*types
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 同步调用支付宝转账
|
|
|
|
|
transferResp, err := l.svcCtx.AlipayService.AliTransfer(l.ctx, req.PayeeAccount, req.PayeeName, req.Amount, "代理提现", outBizNo)
|
|
|
|
|
transferResp, err := l.svcCtx.AlipayService.AliTransfer(l.ctx, req.PayeeAccount, req.PayeeName, finalWithdrawAmount, "代理提现", outBizNo)
|
|
|
|
|
if err != nil {
|
|
|
|
|
l.Logger.Errorf("【支付宝转账失败】outBizNo:%s error:%v", outBizNo, err)
|
|
|
|
|
l.handleTransferError(outBizNo, err, "支付宝接口调用失败")
|
|
|
|
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "支付宝接口调用失败: %v", err)
|
|
|
|
|
}
|
|
|
|
|
@@ -179,17 +248,22 @@ func (l *AgentWithdrawalLogic) mapAlipayError(code string) string {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 创建提现记录(事务内操作)
|
|
|
|
|
func (l *AgentWithdrawalLogic) createWithdrawalRecord(session sqlx.Session, agentID int64, req *types.WithdrawalReq, outBizNo string) error {
|
|
|
|
|
func (l *AgentWithdrawalLogic) createWithdrawalRecord(session sqlx.Session, agentID int64, payeeAccount string, amount float64, finalWithdrawAmount float64, taxAmount float64, outBizNo string) (int64, error) {
|
|
|
|
|
record := &model.AgentWithdrawal{
|
|
|
|
|
AgentId: agentID,
|
|
|
|
|
WithdrawNo: outBizNo,
|
|
|
|
|
PayeeAccount: req.PayeeAccount,
|
|
|
|
|
Amount: req.Amount,
|
|
|
|
|
PayeeAccount: payeeAccount,
|
|
|
|
|
Amount: amount,
|
|
|
|
|
ActualAmount: finalWithdrawAmount,
|
|
|
|
|
TaxAmount: taxAmount,
|
|
|
|
|
Status: StatusProcessing,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_, err := l.svcCtx.AgentWithdrawalModel.Insert(l.ctx, session, record)
|
|
|
|
|
return err
|
|
|
|
|
result, err := l.svcCtx.AgentWithdrawalModel.Insert(l.ctx, session, record)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return 0, err
|
|
|
|
|
}
|
|
|
|
|
return result.LastInsertId()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 冻结资金(事务内操作)
|
|
|
|
|
@@ -264,7 +338,6 @@ func (l *AgentWithdrawalLogic) updateWithdrawalStatus(outBizNo string, status in
|
|
|
|
|
if _, err = l.svcCtx.AgentWithdrawalModel.Update(ctx, session, record); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 失败时解冻资金
|
|
|
|
|
if status == StatusFailed {
|
|
|
|
|
wallet, err := l.svcCtx.AgentWalletModel.FindOneByAgentId(ctx, record.AgentId)
|
|
|
|
|
@@ -277,6 +350,27 @@ func (l *AgentWithdrawalLogic) updateWithdrawalStatus(outBizNo string, status in
|
|
|
|
|
if err := l.svcCtx.AgentWalletModel.UpdateWithVersion(ctx, session, wallet); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
taxModel, err := l.svcCtx.AgentWithdrawalTaxModel.FindOneByWithdrawalId(ctx, record.Id)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return 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)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
taxExemption, err := l.svcCtx.AgentWithdrawalTaxExemptionModel.FindOne(ctx, taxModel.ExemptionRecordId)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取代理税务额度失败: %v", err)
|
|
|
|
|
}
|
|
|
|
|
taxExemption.UsedExemptionAmount -= taxModel.ExemptionAmount
|
|
|
|
|
taxExemption.RemainingExemptionAmount += taxModel.ExemptionAmount
|
|
|
|
|
if err := l.svcCtx.AgentWithdrawalTaxExemptionModel.UpdateWithVersion(ctx, session, taxExemption); err != nil {
|
|
|
|
|
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新代理税务额度失败: %v", err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if status == StatusSuccess {
|
|
|
|
|
wallet, err := l.svcCtx.AgentWalletModel.FindOneByAgentId(ctx, record.AgentId)
|
|
|
|
|
@@ -288,6 +382,17 @@ func (l *AgentWithdrawalLogic) updateWithdrawalStatus(outBizNo string, status in
|
|
|
|
|
if err := l.svcCtx.AgentWalletModel.UpdateWithVersion(ctx, session, wallet); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
taxModel, err := l.svcCtx.AgentWithdrawalTaxModel.FindOneByWithdrawalId(ctx, record.Id)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return 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)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
})
|
|
|
|
|
@@ -311,7 +416,6 @@ func (l *AgentWithdrawalLogic) handleTransferFailure(outBizNo string, rsp interf
|
|
|
|
|
}
|
|
|
|
|
l.updateWithdrawalStatus(outBizNo, StatusFailed, errorMsg)
|
|
|
|
|
l.Logger.Errorf("提现失败 outBizNo:%s reason:%s", outBizNo, errorMsg)
|
|
|
|
|
l.Logger.Errorf("错误响应 rsp:%+v", rsp)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 超时处理
|
|
|
|
|
@@ -325,3 +429,22 @@ func (l *AgentWithdrawalLogic) handleTransferError(outBizNo string, err error, c
|
|
|
|
|
l.updateWithdrawalStatus(outBizNo, StatusFailed, "系统处理异常")
|
|
|
|
|
l.Logger.Errorf("%s outBizNo:%s error:%v", contextMsg, outBizNo, err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (l *AgentWithdrawalLogic) createMonthlyExemption(session sqlx.Session, agentId int64, yearMonth int64) (*model.AgentWithdrawalTaxExemption, error) {
|
|
|
|
|
exemption := &model.AgentWithdrawalTaxExemption{
|
|
|
|
|
AgentId: agentId,
|
|
|
|
|
YearMonth: yearMonth,
|
|
|
|
|
TotalExemptionAmount: l.svcCtx.Config.TaxConfig.TaxExemptionAmount,
|
|
|
|
|
UsedExemptionAmount: 0.00,
|
|
|
|
|
RemainingExemptionAmount: l.svcCtx.Config.TaxConfig.TaxExemptionAmount,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
result, err := l.svcCtx.AgentWithdrawalTaxExemptionModel.Insert(l.ctx, session, exemption)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
id, _ := result.LastInsertId()
|
|
|
|
|
exemption.Id = id
|
|
|
|
|
return exemption, nil
|
|
|
|
|
}
|
|
|
|
|
|