411 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			411 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package agent
 | ||
| 
 | ||
| import (
 | ||
| 	"context"
 | ||
| 	"database/sql"
 | ||
| 	"fmt"
 | ||
| 	"hm-server/app/main/model"
 | ||
| 	"hm-server/common/ctxdata"
 | ||
| 	"hm-server/common/xerr"
 | ||
| 	"hm-server/pkg/lzkit/lzUtils"
 | ||
| 	"time"
 | ||
| 
 | ||
| 	"github.com/cenkalti/backoff/v4"
 | ||
| 	"github.com/pkg/errors"
 | ||
| 	"github.com/smartwalle/alipay/v3"
 | ||
| 	"github.com/zeromicro/go-zero/core/stores/sqlx"
 | ||
| 
 | ||
| 	"hm-server/app/main/api/internal/svc"
 | ||
| 	"hm-server/app/main/api/internal/types"
 | ||
| 
 | ||
| 	"github.com/zeromicro/go-zero/core/logx"
 | ||
| )
 | ||
| 
 | ||
| // 状态常量
 | ||
| const (
 | ||
| 	StatusProcessing = 1 // 处理中
 | ||
| 	StatusSuccess    = 2 // 成功
 | ||
| 	StatusFailed     = 3 // 失败
 | ||
| )
 | ||
| 
 | ||
| // 前端响应状态
 | ||
| const (
 | ||
| 	WithdrawStatusProcessing = 1
 | ||
| 	WithdrawStatusSuccess    = 2
 | ||
| 	WithdrawStatusFailed     = 3
 | ||
| )
 | ||
| 
 | ||
| type AgentWithdrawalLogic struct {
 | ||
| 	logx.Logger
 | ||
| 	ctx    context.Context
 | ||
| 	svcCtx *svc.ServiceContext
 | ||
| }
 | ||
| 
 | ||
| func NewAgentWithdrawalLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AgentWithdrawalLogic {
 | ||
| 	return &AgentWithdrawalLogic{
 | ||
| 		Logger: logx.WithContext(ctx),
 | ||
| 		ctx:    ctx,
 | ||
| 		svcCtx: svcCtx,
 | ||
| 	}
 | ||
| }
 | ||
| 
 | ||
| func (l *AgentWithdrawalLogic) AgentWithdrawal(req *types.WithdrawalReq) (*types.WithdrawalResp, error) {
 | ||
| 	var (
 | ||
| 		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.DB_ERROR), "查询代理信息失败: %v", err)
 | ||
| 		}
 | ||
| 		agentRealName, err := l.svcCtx.AgentRealNameModel.FindOneByAgentId(l.ctx, agentModel.Id)
 | ||
| 		if err != nil {
 | ||
| 			if errors.Is(err, model.ErrNotFound) {
 | ||
| 				return errors.Wrapf(xerr.NewErrMsg("您未进行实名认证, 无法提现"), "您未进行实名认证")
 | ||
| 			}
 | ||
| 			return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询代理实名信息失败: %v", err)
 | ||
| 		}
 | ||
| 		if agentRealName.Status != model.AgentRealNameStatusApproved {
 | ||
| 			return errors.Wrapf(xerr.NewErrMsg("您的实名认证未通过, 无法提现"), "您的实名认证未通过")
 | ||
| 		}
 | ||
| 		if agentRealName.Name != req.PayeeName {
 | ||
| 			return errors.Wrapf(xerr.NewErrMsg("您的实名认证信息不匹配, 无法提现"), "您的实名认证信息不匹配")
 | ||
| 		}
 | ||
| 		// 查询钱包
 | ||
| 		agentWallet, err := l.svcCtx.AgentWalletModel.FindOneByAgentId(l.ctx, agentModel.Id)
 | ||
| 		if err != nil {
 | ||
| 			return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询代理钱包失败: %v", err)
 | ||
| 		}
 | ||
| 
 | ||
| 		// 校验可提现金额
 | ||
| 		if req.Amount > agentWallet.Balance {
 | ||
| 			return errors.Wrapf(xerr.NewErrMsg("您可提现的余额不足"), "获取用户ID失败")
 | ||
| 		}
 | ||
| 
 | ||
| 		// 生成交易号
 | ||
| 		outBizNo = "W_" + l.svcCtx.AlipayService.GenerateOutTradeNo()
 | ||
| 
 | ||
| 		// 冻结资金(事务内操作)
 | ||
| 		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()))
 | ||
| 		// 统一按6%收取税收,不再有免税额度
 | ||
| 		taxRate := l.svcCtx.Config.TaxConfig.TaxRate
 | ||
| 		var (
 | ||
| 			taxAmount        float64 // 应缴税费
 | ||
| 			taxDeductionPart float64 // 应税金额
 | ||
| 			TaxStatus        int64   // 扣税状态
 | ||
| 			exemptionAmount  float64 // 免税金额(固定为0)
 | ||
| 		)
 | ||
| 
 | ||
| 		// 统一扣税逻辑:所有提现都按6%收取税收
 | ||
| 		exemptionAmount = 0                              // 免税金额 = 0
 | ||
| 		TaxStatus = model.TaxStatusPending               // 扣税状态 = 待扣税
 | ||
| 		taxDeductionPart = req.Amount                    // 应税金额 = 提现金额
 | ||
| 		taxAmount = taxDeductionPart * taxRate           // 应缴税费 = 应税金额 * 税率
 | ||
| 		finalWithdrawAmount = req.Amount - taxAmount     // 实际到账金额 = 提现金额 - 应缴税费
 | ||
| 
 | ||
| 		// 创建提现记录(初始状态为处理中)
 | ||
| 		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: 0, // 不再使用免税额度记录
 | ||
| 		}
 | ||
| 		_, err = l.svcCtx.AgentWithdrawalTaxModel.Insert(ctx, session, taxModel)
 | ||
| 		if err != nil {
 | ||
| 			return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "创建扣税记录失败: %v", err)
 | ||
| 		}
 | ||
| 		return nil
 | ||
| 	})
 | ||
| 
 | ||
| 	if err != nil {
 | ||
| 		return nil, err
 | ||
| 	}
 | ||
| 
 | ||
| 	// 同步调用支付宝转账
 | ||
| 	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)
 | ||
| 	}
 | ||
| 
 | ||
| 	switch {
 | ||
| 	case transferResp.Status == "SUCCESS":
 | ||
| 		// 立即处理成功状态
 | ||
| 		l.handleTransferSuccess(outBizNo, transferResp)
 | ||
| 		withdrawRes.Status = WithdrawStatusSuccess
 | ||
| 	case transferResp.Status == "FAIL" || transferResp.SubCode != "":
 | ||
| 		// 处理明确失败
 | ||
| 		errorMsg := l.mapAlipayError(transferResp.SubCode)
 | ||
| 		l.handleTransferFailure(outBizNo, transferResp)
 | ||
| 		withdrawRes.Status = WithdrawStatusFailed
 | ||
| 		withdrawRes.FailMsg = errorMsg
 | ||
| 	case transferResp.Status == "DEALING":
 | ||
| 		// 处理中状态,启动异步轮询
 | ||
| 		go l.startAsyncPolling(outBizNo)
 | ||
| 		withdrawRes.Status = WithdrawStatusProcessing
 | ||
| 	default:
 | ||
| 		// 未知状态按失败处理
 | ||
| 		l.handleTransferError(outBizNo, fmt.Errorf("未知状态:%s", transferResp.Status), "支付宝返回未知状态")
 | ||
| 		return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "支付宝接口调用失败: %v", err)
 | ||
| 	}
 | ||
| 	return withdrawRes, nil
 | ||
| }
 | ||
| 
 | ||
| // 错误类型映射
 | ||
| func (l *AgentWithdrawalLogic) 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 "系统错误,请联系客服"
 | ||
| }
 | ||
| 
 | ||
| // 创建提现记录(事务内操作)
 | ||
| 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: payeeAccount,
 | ||
| 		Amount:       amount,
 | ||
| 		ActualAmount: finalWithdrawAmount,
 | ||
| 		TaxAmount:    taxAmount,
 | ||
| 		Status:       StatusProcessing,
 | ||
| 	}
 | ||
| 
 | ||
| 	result, err := l.svcCtx.AgentWithdrawalModel.Insert(l.ctx, session, record)
 | ||
| 	if err != nil {
 | ||
| 		return 0, err
 | ||
| 	}
 | ||
| 	return result.LastInsertId()
 | ||
| }
 | ||
| 
 | ||
| // 冻结资金(事务内操作)
 | ||
| func (l *AgentWithdrawalLogic) freezeFunds(session sqlx.Session, wallet *model.AgentWallet, amount float64) error {
 | ||
| 	wallet.Balance -= amount
 | ||
| 	wallet.FrozenBalance += amount
 | ||
| 	err := l.svcCtx.AgentWalletModel.UpdateWithVersion(l.ctx, session, wallet)
 | ||
| 	if err != nil {
 | ||
| 		return err
 | ||
| 	}
 | ||
| 
 | ||
| 	return nil
 | ||
| }
 | ||
| 
 | ||
| // 处理异步轮询
 | ||
| func (l *AgentWithdrawalLogic) startAsyncPolling(outBizNo string) {
 | ||
| 	go func() {
 | ||
| 		detachedCtx := context.WithoutCancel(l.ctx)
 | ||
| 		retryConfig := &backoff.ExponentialBackOff{
 | ||
| 			InitialInterval:     10 * time.Second,
 | ||
| 			RandomizationFactor: 0.5, // 增加随机因子防止惊群
 | ||
| 			Multiplier:          2,
 | ||
| 			MaxInterval:         30 * time.Second,
 | ||
| 			MaxElapsedTime:      5 * time.Minute, // 缩短总超时
 | ||
| 			Clock:               backoff.SystemClock,
 | ||
| 		}
 | ||
| 		retryConfig.Reset()
 | ||
| 		operation := func() error {
 | ||
| 			statusRsp, err := l.svcCtx.AlipayService.QueryTransferStatus(detachedCtx, outBizNo)
 | ||
| 			if err != nil {
 | ||
| 				return err // 触发重试
 | ||
| 			}
 | ||
| 
 | ||
| 			switch statusRsp.Status {
 | ||
| 			case "SUCCESS":
 | ||
| 				l.handleTransferSuccess(outBizNo, statusRsp)
 | ||
| 				return nil
 | ||
| 			case "FAIL":
 | ||
| 				l.handleTransferFailure(outBizNo, statusRsp)
 | ||
| 				return nil
 | ||
| 			default:
 | ||
| 				return fmt.Errorf("转账处理中")
 | ||
| 			}
 | ||
| 		}
 | ||
| 
 | ||
| 		err := backoff.RetryNotify(operation,
 | ||
| 			backoff.WithContext(retryConfig, detachedCtx),
 | ||
| 			func(err error, duration time.Duration) {
 | ||
| 				l.Logger.Infof("轮询延迟 outBizNo:%s 等待:%v", outBizNo, duration)
 | ||
| 			})
 | ||
| 
 | ||
| 		if err != nil {
 | ||
| 			l.handleTransferTimeout(outBizNo)
 | ||
| 		}
 | ||
| 	}()
 | ||
| }
 | ||
| 
 | ||
| // 统一状态更新
 | ||
| func (l *AgentWithdrawalLogic) updateWithdrawalStatus(outBizNo string, status int64, errorMsg string) {
 | ||
| 	detachedCtx := context.WithoutCancel(l.ctx)
 | ||
| 
 | ||
| 	err := l.svcCtx.AgentModel.Trans(detachedCtx, func(ctx context.Context, session sqlx.Session) error {
 | ||
| 		// 获取提现记录
 | ||
| 		record, err := l.svcCtx.AgentWithdrawalModel.FindOneByWithdrawNo(l.ctx, outBizNo)
 | ||
| 		if err != nil {
 | ||
| 			return err
 | ||
| 		}
 | ||
| 
 | ||
| 		// 更新状态
 | ||
| 		record.Status = status
 | ||
| 		record.Remark = lzUtils.StringToNullString(errorMsg)
 | ||
| 		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)
 | ||
| 			if err != nil {
 | ||
| 				return err
 | ||
| 			}
 | ||
| 
 | ||
| 			wallet.Balance += record.Amount
 | ||
| 			wallet.FrozenBalance -= record.Amount
 | ||
| 			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)
 | ||
| 				}
 | ||
| 			}
 | ||
| 			// 不再需要恢复免税额度,因为统一按6%收取税收
 | ||
| 		}
 | ||
| 		if status == StatusSuccess {
 | ||
| 			wallet, err := l.svcCtx.AgentWalletModel.FindOneByAgentId(ctx, record.AgentId)
 | ||
| 			if err != nil {
 | ||
| 				return err
 | ||
| 			}
 | ||
| 
 | ||
| 			wallet.FrozenBalance -= record.Amount
 | ||
| 			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
 | ||
| 	})
 | ||
| 
 | ||
| 	if err != nil {
 | ||
| 		l.Logger.Errorf("状态更新失败 outBizNo:%s error:%v", outBizNo, err)
 | ||
| 	}
 | ||
| }
 | ||
| 
 | ||
| // 成功处理
 | ||
| func (l *AgentWithdrawalLogic) handleTransferSuccess(outBizNo string, rsp interface{}) {
 | ||
| 	l.updateWithdrawalStatus(outBizNo, StatusSuccess, "")
 | ||
| 	l.Logger.Infof("提现成功 outBizNo:%s", outBizNo)
 | ||
| }
 | ||
| 
 | ||
| // 失败处理
 | ||
| func (l *AgentWithdrawalLogic) handleTransferFailure(outBizNo string, rsp interface{}) {
 | ||
| 	var errorMsg string
 | ||
| 	if resp, ok := rsp.(*alipay.FundTransUniTransferRsp); ok {
 | ||
| 		errorMsg = l.mapAlipayError(resp.SubCode)
 | ||
| 	}
 | ||
| 	l.updateWithdrawalStatus(outBizNo, StatusFailed, errorMsg)
 | ||
| 	l.Logger.Errorf("提现失败 outBizNo:%s reason:%s", outBizNo, errorMsg)
 | ||
| }
 | ||
| 
 | ||
| // 超时处理
 | ||
| func (l *AgentWithdrawalLogic) handleTransferTimeout(outBizNo string) {
 | ||
| 	l.updateWithdrawalStatus(outBizNo, StatusFailed, "系统处理超时")
 | ||
| 	l.Logger.Errorf("轮询超时 outBizNo:%s", outBizNo)
 | ||
| }
 | ||
| 
 | ||
| // 错误处理
 | ||
| func (l *AgentWithdrawalLogic) handleTransferError(outBizNo string, err error, contextMsg string) {
 | ||
| 	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
 | ||
| }
 |