| 
									
										
										
										
											2025-09-21 18:27:25 +08:00
										 |  |  |  | package agent | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | import ( | 
					
						
							|  |  |  |  | 	"context" | 
					
						
							| 
									
										
										
										
											2025-09-30 17:44:18 +08:00
										 |  |  |  | 	"tydata-server/app/main/model" | 
					
						
							|  |  |  |  | 	"tydata-server/common/ctxdata" | 
					
						
							|  |  |  |  | 	"tydata-server/common/xerr" | 
					
						
							|  |  |  |  | 	"tydata-server/pkg/lzkit/lzUtils" | 
					
						
							| 
									
										
										
										
											2025-09-21 18:27:25 +08:00
										 |  |  |  | 	"database/sql" | 
					
						
							|  |  |  |  | 	"fmt" | 
					
						
							|  |  |  |  | 	"time" | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | 	"github.com/cenkalti/backoff/v4" | 
					
						
							|  |  |  |  | 	"github.com/pkg/errors" | 
					
						
							|  |  |  |  | 	"github.com/smartwalle/alipay/v3" | 
					
						
							|  |  |  |  | 	"github.com/zeromicro/go-zero/core/stores/sqlx" | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-30 17:44:18 +08:00
										 |  |  |  | 	"tydata-server/app/main/api/internal/svc" | 
					
						
							|  |  |  |  | 	"tydata-server/app/main/api/internal/types" | 
					
						
							| 
									
										
										
										
											2025-09-21 18:27:25 +08:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  | 	"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 | 
					
						
							|  |  |  |  | } |