Files
ycc-proxy-server/app/main/api/internal/logic/agent/applywithdrawallogic.go

227 lines
6.8 KiB
Go
Raw Normal View History

2025-11-27 13:09:54 +08:00
package agent
import (
"context"
"fmt"
"time"
"ycc-server/app/main/model"
"ycc-server/common/ctxdata"
"ycc-server/common/globalkey"
"ycc-server/common/xerr"
"ycc-server/pkg/lzkit/lzUtils"
2025-12-09 18:55:28 +08:00
"github.com/google/uuid"
2025-11-27 13:09:54 +08:00
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/stores/sqlx"
"ycc-server/app/main/api/internal/svc"
"ycc-server/app/main/api/internal/types"
"github.com/zeromicro/go-zero/core/logx"
)
type ApplyWithdrawalLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewApplyWithdrawalLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ApplyWithdrawalLogic {
return &ApplyWithdrawalLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *ApplyWithdrawalLogic) ApplyWithdrawal(req *types.ApplyWithdrawalReq) (resp *types.ApplyWithdrawalResp, err error) {
userID, err := ctxdata.GetUidFromCtx(l.ctx)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取用户信息失败, %v", err)
}
// 1. 获取代理信息
agent, err := l.svcCtx.AgentModel.FindOneByUserId(l.ctx, userID)
if err != nil {
if errors.Is(err, model.ErrNotFound) {
return nil, errors.Wrapf(xerr.NewErrMsg("您不是代理"), "")
}
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询代理信息失败, %v", err)
}
// 2. 验证实名认证
realName, err := l.svcCtx.AgentRealNameModel.FindOneByAgentId(l.ctx, agent.Id)
if err != nil {
if errors.Is(err, model.ErrNotFound) {
return nil, errors.Wrapf(xerr.NewErrMsg("请先完成实名认证"), "")
}
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询实名认证失败, %v", err)
}
// 检查是否已通过三要素核验verify_time不为空表示已通过
if !realName.VerifyTime.Valid {
return nil, errors.Wrapf(xerr.NewErrMsg("请先完成实名认证"), "")
}
// 3. 验证提现金额
if req.Amount <= 0 {
return nil, errors.Wrapf(xerr.NewErrMsg("提现金额必须大于0"), "")
}
// 4. 获取钱包信息
wallet, err := l.svcCtx.AgentWalletModel.FindOneByAgentId(l.ctx, agent.Id)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询钱包失败, %v", err)
}
// 5. 验证余额
if wallet.Balance < req.Amount {
return nil, errors.Wrapf(xerr.NewErrMsg(fmt.Sprintf("余额不足,当前余额:%.2f", wallet.Balance)), "")
}
// 6. 计算税费
yearMonth := int64(time.Now().Year()*100 + int(time.Now().Month()))
taxInfo, err := l.calculateTax(l.ctx, agent.Id, req.Amount, yearMonth)
if err != nil {
return nil, errors.Wrapf(err, "计算税费失败")
}
// 7. 生成提现单号
withdrawNo := fmt.Sprintf("WD%d%d", time.Now().Unix(), agent.Id)
// 8. 使用事务处理提现申请
2025-12-09 18:55:28 +08:00
var withdrawalId string
2025-11-27 13:09:54 +08:00
err = l.svcCtx.AgentWalletModel.Trans(l.ctx, func(transCtx context.Context, session sqlx.Session) error {
// 8.1 冻结余额
wallet.FrozenBalance += req.Amount
wallet.Balance -= req.Amount
if err := l.svcCtx.AgentWalletModel.UpdateWithVersion(transCtx, session, wallet); err != nil {
return errors.Wrapf(err, "冻结余额失败")
}
// 8.2 创建提现记录
withdrawal := &model.AgentWithdrawal{
2025-12-09 18:55:28 +08:00
Id: uuid.New().String(),
2025-11-27 13:09:54 +08:00
AgentId: agent.Id,
WithdrawNo: withdrawNo,
PayeeAccount: req.PayeeAccount,
PayeeName: req.PayeeName,
Amount: req.Amount,
ActualAmount: taxInfo.ActualAmount,
TaxAmount: taxInfo.TaxAmount,
Status: 1, // 处理中(待审核)
}
2025-12-09 18:55:28 +08:00
_, err := l.svcCtx.AgentWithdrawalModel.Insert(transCtx, session, withdrawal)
2025-11-27 13:09:54 +08:00
if err != nil {
return errors.Wrapf(err, "创建提现记录失败")
}
2025-12-09 18:55:28 +08:00
withdrawalId = withdrawal.Id
2025-11-27 13:09:54 +08:00
// 8.3 创建扣税记录
taxRecord := &model.AgentWithdrawalTax{
AgentId: agent.Id,
WithdrawalId: withdrawalId,
YearMonth: yearMonth,
WithdrawalAmount: req.Amount,
TaxableAmount: taxInfo.TaxableAmount,
TaxRate: taxInfo.TaxRate,
TaxAmount: taxInfo.TaxAmount,
ActualAmount: taxInfo.ActualAmount,
TaxStatus: 1, // 待扣税
Remark: lzUtils.StringToNullString("提现申请"),
}
if _, err := l.svcCtx.AgentWithdrawalTaxModel.Insert(transCtx, session, taxRecord); err != nil {
return errors.Wrapf(err, "创建扣税记录失败")
}
return nil
})
if err != nil {
return nil, err
}
return &types.ApplyWithdrawalResp{
WithdrawalId: withdrawalId,
WithdrawalNo: withdrawNo,
}, nil
}
// TaxInfo 税费信息
type TaxInfo struct {
TaxableAmount float64 // 应税金额
TaxRate float64 // 税率
TaxAmount float64 // 税费金额
ActualAmount float64 // 实际到账金额
}
// calculateTax 计算税费
2025-12-09 18:55:28 +08:00
func (l *ApplyWithdrawalLogic) calculateTax(ctx context.Context, agentId string, amount float64, yearMonth int64) (*TaxInfo, error) {
2025-11-27 13:09:54 +08:00
// 获取税率配置默认6%
taxRate := 0.06
config, err := l.svcCtx.AgentConfigModel.FindOneByConfigKey(ctx, "tax_rate")
if err == nil {
if parsedRate, parseErr := l.parseFloat(config.ConfigValue); parseErr == nil {
taxRate = parsedRate
}
}
// 查询本月已提现金额
builder := l.svcCtx.AgentWithdrawalTaxModel.SelectBuilder().
Where("agent_id = ? AND year_month = ? AND del_state = ?", agentId, yearMonth, globalkey.DelStateNo)
taxRecords, err := l.svcCtx.AgentWithdrawalTaxModel.FindAll(ctx, builder, "")
if err != nil {
return nil, errors.Wrapf(err, "查询月度提现记录失败")
}
// 计算本月累计提现金额
monthlyTotal := 0.0
for _, record := range taxRecords {
monthlyTotal += record.WithdrawalAmount
}
// 获取免税额度配置默认0即无免税额度
exemptionAmount := 0.0
exemptionConfig, err := l.svcCtx.AgentConfigModel.FindOneByConfigKey(ctx, "tax_exemption_amount")
if err == nil {
if parsedAmount, parseErr := l.parseFloat(exemptionConfig.ConfigValue); parseErr == nil {
exemptionAmount = parsedAmount
}
}
// 计算应税金额
// 如果本月累计 + 本次提现金额 <= 免税额度,则本次提现免税
// 否则,应税金额 = 本次提现金额 - (免税额度 - 本月累计)(如果免税额度 > 本月累计)
taxableAmount := amount
if exemptionAmount > 0 {
remainingExemption := exemptionAmount - monthlyTotal
if remainingExemption > 0 {
if amount <= remainingExemption {
// 本次提现完全免税
taxableAmount = 0
} else {
// 部分免税
taxableAmount = amount - remainingExemption
}
}
}
// 计算税费
taxAmount := taxableAmount * taxRate
actualAmount := amount - taxAmount
return &TaxInfo{
TaxableAmount: taxableAmount,
TaxRate: taxRate,
TaxAmount: taxAmount,
ActualAmount: actualAmount,
}, nil
}
// parseFloat 解析浮点数
func (l *ApplyWithdrawalLogic) parseFloat(s string) (float64, error) {
var result float64
_, err := fmt.Sscanf(s, "%f", &result)
return result, err
}