package agent import ( "context" "database/sql" "regexp" "time" "tydata-server/app/main/model" "tydata-server/common/ctxdata" "tydata-server/common/xerr" "github.com/pkg/errors" "github.com/zeromicro/go-zero/core/stores/sqlx" "tydata-server/app/main/api/internal/svc" "tydata-server/app/main/api/internal/types" "github.com/zeromicro/go-zero/core/logx" ) // 提现类型常量 const ( WithdrawTypeAlipay = 1 // 支付宝提现 WithdrawTypeBankCard = 2 // 银行卡提现 ) type BankCardWithdrawalLogic struct { logx.Logger ctx context.Context svcCtx *svc.ServiceContext } func NewBankCardWithdrawalLogic(ctx context.Context, svcCtx *svc.ServiceContext) *BankCardWithdrawalLogic { return &BankCardWithdrawalLogic{ Logger: logx.WithContext(ctx), ctx: ctx, svcCtx: svcCtx, } } func (l *BankCardWithdrawalLogic) BankCardWithdrawal(req *types.BankCardWithdrawalReq) (resp *types.WithdrawalResp, err error) { var ( outBizNo string withdrawRes = &types.WithdrawalResp{} agentID int64 ) var finalWithdrawAmount float64 // 实际到账金额 // 验证银行卡号格式(16-19位数字) bankCardNoRegex := regexp.MustCompile(`^\d{16,19}$`) if !bankCardNoRegex.MatchString(req.BankCardNo) { return nil, errors.Wrapf(xerr.NewErrMsg("银行卡号格式不正确,请输入16-19位数字"), "银行卡号格式验证失败") } // 验证开户支行不能为空 if req.BankName == "" { return nil, errors.Wrapf(xerr.NewErrMsg("开户支行不能为空"), "开户支行验证失败") } // 使用事务处理核心操作 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) } agentID = agentModel.Id // 保存agentId用于日志 // 查询实名认证信息 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("您的实名认证未通过, 无法提现"), "您的实名认证未通过") } // 查询钱包 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("您可提现的余额不足"), "余额不足") } // 最低提现金额验证 if req.Amount < 50 { return errors.Wrapf(xerr.NewErrMsg("提现金额不能低于50元"), "金额验证失败") } // 生成交易号 outBizNo = "BC_" + 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.createBankCardWithdrawalRecord(session, agentModel.Id, req.BankCardNo, req.BankName, agentRealName.Name, 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 } // 银行卡提现不需要调用支付接口,直接返回申请中状态 withdrawRes.Status = WithdrawStatusProcessing withdrawRes.FailMsg = "" l.Logger.Infof("银行卡提现申请成功 outBizNo:%s agentId:%d amount:%f", outBizNo, agentID, req.Amount) return withdrawRes, nil } // 创建银行卡提现记录(事务内操作) func (l *BankCardWithdrawalLogic) createBankCardWithdrawalRecord(session sqlx.Session, agentID int64, bankCardNo string, bankName string, payeeName string, amount float64, finalWithdrawAmount float64, taxAmount float64, outBizNo string) (int64, error) { record := &model.AgentWithdrawal{ AgentId: agentID, WithdrawType: WithdrawTypeBankCard, // 银行卡提现 WithdrawNo: outBizNo, PayeeAccount: bankCardNo, // 银行卡号存储在PayeeAccount字段 Amount: amount, ActualAmount: finalWithdrawAmount, TaxAmount: taxAmount, Status: StatusProcessing, // 申请中状态 BankCardNo: sql.NullString{String: bankCardNo, Valid: true}, BankName: sql.NullString{String: bankName, Valid: true}, PayeeName: sql.NullString{String: payeeName, Valid: true}, } result, err := l.svcCtx.AgentWithdrawalModel.Insert(l.ctx, session, record) if err != nil { return 0, err } return result.LastInsertId() } // 冻结资金(事务内操作) func (l *BankCardWithdrawalLogic) 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 }