package admin_agent import ( "context" "database/sql" "fmt" "os" "time" "ycc-server/common/xerr" "ycc-server/pkg/lzkit/lzUtils" "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 AdminAuditWithdrawalLogic struct { logx.Logger ctx context.Context svcCtx *svc.ServiceContext } func NewAdminAuditWithdrawalLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminAuditWithdrawalLogic { return &AdminAuditWithdrawalLogic{ Logger: logx.WithContext(ctx), ctx: ctx, svcCtx: svcCtx, } } // parseFloat 解析配置中的浮点数 func (l *AdminAuditWithdrawalLogic) parseFloat(s string) (float64, error) { var result float64 _, err := fmt.Sscanf(s, "%f", &result) return result, err } func (l *AdminAuditWithdrawalLogic) AdminAuditWithdrawal(req *types.AdminAuditWithdrawalReq) (resp *types.AdminAuditWithdrawalResp, err error) { // 1. 查询提现记录 withdrawal, err := l.svcCtx.AgentWithdrawalModel.FindOne(l.ctx, req.WithdrawalId) if err != nil { return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询提现记录失败, %v", err) } // 3. 检查状态(必须是待审核状态) if withdrawal.Status != 1 { return nil, errors.Wrapf(xerr.NewErrMsg("该提现记录已处理"), "") } // 4. 使用事务处理审核 err = l.svcCtx.AgentWithdrawalModel.Trans(l.ctx, func(transCtx context.Context, session sqlx.Session) error { switch req.Status { case 2: // 审核通过 // 4.1 根据提现方式处理 switch withdrawal.WithdrawalType { case 1: // 支付宝提现:审核通过前再次校验月度额度,避免一次性通过多笔超限 now := time.Now() monthStart := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, now.Location()) nextMonthStart := monthStart.AddDate(0, 1, 0) // 获取支付宝月度额度配置(默认 800 元) alipayQuota := 800.0 if cfg, cfgErr := l.svcCtx.AgentConfigModel.FindOneByConfigKey(transCtx, "alipay_month_quota"); cfgErr == nil { if parsed, parseErr := l.parseFloat(cfg.ConfigValue); parseErr == nil && parsed > 0 { alipayQuota = parsed } } // 统计本月已成功的支付宝提现金额(status=5) withdrawBuilder := l.svcCtx.AgentWithdrawalModel.SelectBuilder(). Where("agent_id = ? AND withdrawal_type = ? AND status = ? AND create_time >= ? AND create_time < ?", withdrawal.AgentId, 1, 5, monthStart, nextMonthStart) usedAmount, sumErr := l.svcCtx.AgentWithdrawalModel.FindSum(transCtx, withdrawBuilder, "amount") if sumErr != nil { return errors.Wrapf(sumErr, "查询本月支付宝提现额度使用情况失败") } if usedAmount+withdrawal.Amount > alipayQuota { // 超出额度,不允许通过,保持待审核状态并提示原因 withdrawal.Status = 1 withdrawal.Remark = sql.NullString{ String: fmt.Sprintf("超过本月支付宝提现额度(限额:%.2f 元,已用:%.2f 元),请使用银行卡提现或调整金额", alipayQuota, usedAmount), Valid: true, } if err := l.svcCtx.AgentWithdrawalModel.UpdateWithVersion(transCtx, session, withdrawal); err != nil { return errors.Wrapf(err, "更新提现记录失败") } return errors.Wrapf(xerr.NewErrMsg("超过本月支付宝提现额度,无法通过该笔提现"), "") } // 支付宝提现:开发环境下做模拟,不调用真实支付宝转账 mockTransferStatus := "SUCCESS" var transferResp struct { Status string SubCode string } if os.Getenv("ENV") == "development" { transferResp.Status = mockTransferStatus logx.Infof("【DEV】模拟支付宝转账成功,withdrawNo=%s, amount=%.2f, payee=%s", withdrawal.WithdrawNo, withdrawal.ActualAmount, withdrawal.PayeeAccount) } else { // 生产环境:同步调用支付宝转账接口 outBizNo := withdrawal.WithdrawNo resp, err := l.svcCtx.AlipayService.AliTransfer(transCtx, withdrawal.PayeeAccount, withdrawal.PayeeName, withdrawal.ActualAmount, "代理提现", outBizNo) if err != nil { // 调用失败:保持状态为待审核(1),只记录备注,方便管理员重试 withdrawal.Status = 1 // 待审核 withdrawal.Remark = sql.NullString{String: fmt.Sprintf("支付宝转账调用失败: %v", err), Valid: true} _ = l.svcCtx.AgentWithdrawalModel.UpdateWithVersion(transCtx, session, withdrawal) return errors.Wrapf(err, "支付宝转账失败") } transferResp.Status = resp.Status transferResp.SubCode = resp.SubCode } // 4.2 根据转账结果更新状态 switch transferResp.Status { case "SUCCESS": // 转账成功 withdrawal.Status = 5 // 提现成功 if err := l.svcCtx.AgentWithdrawalModel.UpdateWithVersion(transCtx, session, withdrawal); err != nil { return errors.Wrapf(err, "更新提现记录失败") } // 更新钱包(解冻并扣除) wallet, err := l.svcCtx.AgentWalletModel.FindOneByAgentId(transCtx, withdrawal.AgentId) if err != nil { return errors.Wrapf(err, "查询钱包失败") } wallet.FrozenBalance -= withdrawal.Amount wallet.WithdrawnAmount += withdrawal.Amount if err := l.svcCtx.AgentWalletModel.UpdateWithVersion(transCtx, session, wallet); err != nil { return errors.Wrapf(err, "更新钱包失败") } // 更新扣税记录状态 taxBuilder := l.svcCtx.AgentWithdrawalTaxModel.SelectBuilder(). Where("withdrawal_id = ?", withdrawal.Id) taxRecords, err := l.svcCtx.AgentWithdrawalTaxModel.FindAll(transCtx, taxBuilder, "") if err == nil && len(taxRecords) > 0 { taxRecord := taxRecords[0] taxRecord.TaxStatus = 2 // 已扣税 taxRecord.TaxTime = lzUtils.TimeToNullTime(time.Now()) l.svcCtx.AgentWithdrawalTaxModel.UpdateWithVersion(transCtx, session, taxRecord) } case "FAIL": // 转账失败:保持待审核状态,方便人工处理或重试 withdrawal.Status = 1 // 待审核 errorMsg := l.mapAlipayError(transferResp.SubCode) withdrawal.Remark = sql.NullString{String: errorMsg, Valid: true} if err := l.svcCtx.AgentWithdrawalModel.UpdateWithVersion(transCtx, session, withdrawal); err != nil { return errors.Wrapf(err, "更新提现记录失败") } case "DEALING": // 处理中:同样保持待审核状态(1),由管理员后续确认 withdrawal.Status = 1 withdrawal.Remark = sql.NullString{String: "支付宝处理中,请稍后重试或联系平台", Valid: true} if err := l.svcCtx.AgentWithdrawalModel.UpdateWithVersion(transCtx, session, withdrawal); err != nil { return errors.Wrapf(err, "更新提现记录失败") } } case 2: // 银行卡提现:审核通过即视为提现成功(线下已/将立即打款) withdrawal.Status = 5 // 提现成功 withdrawal.Remark = sql.NullString{String: req.Remark, Valid: true} if err := l.svcCtx.AgentWithdrawalModel.UpdateWithVersion(transCtx, session, withdrawal); err != nil { return errors.Wrapf(err, "更新提现记录失败") } // 更新钱包(解冻并扣除) wallet, err := l.svcCtx.AgentWalletModel.FindOneByAgentId(transCtx, withdrawal.AgentId) if err != nil { return errors.Wrapf(err, "查询钱包失败") } wallet.FrozenBalance -= withdrawal.Amount wallet.WithdrawnAmount += withdrawal.Amount if err := l.svcCtx.AgentWalletModel.UpdateWithVersion(transCtx, session, wallet); err != nil { return errors.Wrapf(err, "更新钱包失败") } // 更新扣税记录状态 taxBuilder := l.svcCtx.AgentWithdrawalTaxModel.SelectBuilder(). Where("withdrawal_id = ?", withdrawal.Id) taxRecords, err := l.svcCtx.AgentWithdrawalTaxModel.FindAll(transCtx, taxBuilder, "") if err == nil && len(taxRecords) > 0 { taxRecord := taxRecords[0] taxRecord.TaxStatus = 2 // 已扣税 taxRecord.TaxTime = lzUtils.TimeToNullTime(time.Now()) l.svcCtx.AgentWithdrawalTaxModel.UpdateWithVersion(transCtx, session, taxRecord) } } case 3: // 审核拒绝 // 4.1 更新提现记录状态为拒绝 withdrawal.Status = 3 // 审核拒绝 withdrawal.Remark = sql.NullString{String: req.Remark, Valid: true} if err := l.svcCtx.AgentWithdrawalModel.UpdateWithVersion(transCtx, session, withdrawal); err != nil { return errors.Wrapf(err, "更新提现记录失败") } // 4.2 解冻余额 wallet, err := l.svcCtx.AgentWalletModel.FindOneByAgentId(transCtx, withdrawal.AgentId) if err != nil { return errors.Wrapf(err, "查询钱包失败") } wallet.FrozenBalance -= withdrawal.Amount wallet.Balance += withdrawal.Amount if err := l.svcCtx.AgentWalletModel.UpdateWithVersion(transCtx, session, wallet); err != nil { return errors.Wrapf(err, "更新钱包失败") } } return nil }) if err != nil { return nil, err } return &types.AdminAuditWithdrawalResp{ Success: true, }, nil } // mapAlipayError 映射支付宝错误码 func (l *AdminAuditWithdrawalLogic) mapAlipayError(code string) string { errorMapping := map[string]string{ "PAYEE_USERINFO_ERROR": "收款方姓名或信息不匹配", "PAYEE_CARD_INFO_ERROR": "收款支付宝账号及户名不一致", "PAYEE_IDENTITY_NOT_MATCH": "收款方身份信息不匹配", "PAYEE_USER_IS_INST": "收款方为金融机构,不能使用提现功能", "PAYEE_USER_TYPE_ERROR": "该支付宝账号类型不支持提现", } if msg, ok := errorMapping[code]; ok { return msg } return "系统错误,请联系客服" }