475 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
		
		
			
		
	
	
			475 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
|  | const express = require('express'); | ||
|  | const router = express.Router(); | ||
|  | const { getDB } = require('../database'); | ||
|  | const { auth } = require('../middleware/auth'); | ||
|  | const multer = require('multer'); | ||
|  | const path = require('path'); | ||
|  | const fs = require('fs'); | ||
|  | 
 | ||
|  | // 配置multer用于文件上传
 | ||
|  | const storage = multer.diskStorage({ | ||
|  |   destination: function (req, file, cb) { | ||
|  |     const uploadDir = 'uploads/qr-codes'; | ||
|  |     // 确保上传目录存在
 | ||
|  |     if (!fs.existsSync(uploadDir)) { | ||
|  |       fs.mkdirSync(uploadDir, { recursive: true }); | ||
|  |     } | ||
|  |     cb(null, uploadDir); | ||
|  |   }, | ||
|  |   filename: function (req, file, cb) { | ||
|  |     // 生成唯一文件名
 | ||
|  |     const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9); | ||
|  |     cb(null, 'qr-code-' + uniqueSuffix + path.extname(file.originalname)); | ||
|  |   } | ||
|  | }); | ||
|  | 
 | ||
|  | // 文件过滤器
 | ||
|  | const fileFilter = (req, file, cb) => { | ||
|  |   // 只允许图片文件
 | ||
|  |   if (file.mimetype.startsWith('image/')) { | ||
|  |     cb(null, true); | ||
|  |   } else { | ||
|  |     cb(new Error('只允许上传图片文件'), false); | ||
|  |   } | ||
|  | }; | ||
|  | 
 | ||
|  | const upload = multer({  | ||
|  |   storage: storage, | ||
|  |   fileFilter: fileFilter, | ||
|  |   limits: { | ||
|  |     fileSize: 5 * 1024 * 1024 // 限制文件大小为5MB
 | ||
|  |   } | ||
|  | }); | ||
|  | 
 | ||
|  | // 获取数据库连接
 | ||
|  | const db = { | ||
|  |   query: async (sql, params = []) => { | ||
|  |     const connection = getDB(); | ||
|  |     const [rows] = await connection.execute(sql, params); | ||
|  |     return rows; | ||
|  |   } | ||
|  | }; | ||
|  | 
 | ||
|  | /** | ||
|  |  * 检查用户是否为代理商 | ||
|  |  */ | ||
|  | const requireAgent = async (req, res, next) => { | ||
|  |   try { | ||
|  |     const userId = req.user.id; | ||
|  |      | ||
|  |     // 查询用户是否为代理商
 | ||
|  |     const agentResult = await db.query( | ||
|  |       'SELECT * FROM regional_agents WHERE user_id = ? AND status = "active"', | ||
|  |       [userId] | ||
|  |     ); | ||
|  |      | ||
|  |     if (!agentResult || agentResult.length === 0) { | ||
|  |       return res.status(403).json({ success: false, message: '您不是活跃的代理商' }); | ||
|  |     } | ||
|  |      | ||
|  |     req.agent = agentResult[0]; | ||
|  |     next(); | ||
|  |   } catch (error) { | ||
|  |     console.error('检查代理商身份失败:', error); | ||
|  |     res.status(500).json({ success: false, message: '检查代理商身份失败' }); | ||
|  |   } | ||
|  | }; | ||
|  | 
 | ||
|  | /** | ||
|  |  * 获取代理商佣金统计信息 | ||
|  |  */ | ||
|  | router.get('/stats', auth, requireAgent, async (req, res) => { | ||
|  |   try { | ||
|  |     const agentId = req.agent.id; | ||
|  |      | ||
|  |     // 查询佣金统计
 | ||
|  |     const statsQuery = `
 | ||
|  |       SELECT  | ||
|  |         CAST(COALESCE(commission_sum.total_commission, 0) AS DECIMAL(10,2)) as total_commission, | ||
|  |         CAST(COALESCE(ra.withdrawn_amount, 0) AS DECIMAL(10,2)) as withdrawn_amount, | ||
|  |         CAST(COALESCE(ra.pending_withdrawal, 0) AS DECIMAL(10,2)) as pending_withdrawal, | ||
|  |         CAST(COALESCE(commission_sum.total_commission, 0) - COALESCE(ra.withdrawn_amount, 0) - COALESCE(ra.pending_withdrawal, 0) AS DECIMAL(10,2)) as available_amount | ||
|  |       FROM regional_agents ra | ||
|  |       LEFT JOIN ( | ||
|  |         SELECT agent_id, SUM(commission_amount) as total_commission | ||
|  |         FROM agent_commission_records | ||
|  |         WHERE agent_id = ? | ||
|  |         GROUP BY agent_id | ||
|  |       ) commission_sum ON ra.id = commission_sum.agent_id | ||
|  |       WHERE ra.id = ? | ||
|  |     `;
 | ||
|  |      | ||
|  |     const statsResult = await db.query(statsQuery, [agentId, agentId]); | ||
|  |     const stats = statsResult && statsResult.length > 0 ? statsResult[0] : { | ||
|  |       total_commission: 0, | ||
|  |       withdrawn_amount: 0, | ||
|  |       pending_withdrawal: 0, | ||
|  |       available_amount: 0 | ||
|  |     }; | ||
|  |      | ||
|  |     // 查询代理商信息包括收款方式
 | ||
|  |     const agentInfo = await db.query( | ||
|  |       'SELECT payment_type, bank_name, account_number, account_holder, qr_code_url, bank_account FROM regional_agents WHERE id = ?', | ||
|  |       [agentId] | ||
|  |     ); | ||
|  |      | ||
|  |     const agent = agentInfo[0] || {}; | ||
|  |      | ||
|  |     // 构建收款方式信息,兼容旧数据
 | ||
|  |     const paymentInfo = { | ||
|  |       payment_type: agent.payment_type || 'bank', | ||
|  |       bank_name: agent.bank_name || '', | ||
|  |       account_number: agent.account_number || agent.bank_account, // 兼容旧字段
 | ||
|  |       account_holder: agent.account_holder, | ||
|  |       qr_code_url: agent.qr_code_url || '' | ||
|  |     }; | ||
|  |      | ||
|  |     // 兼容旧的bankInfo字段
 | ||
|  |     const bankInfo = { | ||
|  |       bank_name: agent.bank_name || '', | ||
|  |       bank_account: agent.bank_account || agent.account_number, | ||
|  |       account_holder: agent.account_holder | ||
|  |     }; | ||
|  |      | ||
|  |     res.json({ | ||
|  |       success: true, | ||
|  |       data: { | ||
|  |         ...stats, | ||
|  |         paymentInfo: paymentInfo, | ||
|  |         bank_info: bankInfo // 保持向后兼容
 | ||
|  |       } | ||
|  |     }); | ||
|  |   } catch (error) { | ||
|  |     console.error('获取佣金统计失败:', error); | ||
|  |     res.status(500).json({ success: false, message: '获取佣金统计失败' }); | ||
|  |   } | ||
|  | }); | ||
|  | 
 | ||
|  | /** | ||
|  |  * 更新收款方式信息 | ||
|  |  */ | ||
|  | router.put('/payment-info', auth, requireAgent, async (req, res) => { | ||
|  |   try { | ||
|  |     const { payment_type, bank_name, account_number, account_holder, qr_code_url } = req.body; | ||
|  |     const agentId = req.agent.id; | ||
|  | 
 | ||
|  |     // 验证收款方式类型
 | ||
|  |     const validPaymentTypes = ['bank', 'wechat', 'alipay', 'unionpay']; | ||
|  |     if (!validPaymentTypes.includes(payment_type)) { | ||
|  |       return res.status(400).json({ success: false, message: '收款方式类型不正确' }); | ||
|  |     } | ||
|  | 
 | ||
|  |     // 根据收款方式类型进行不同的验证
 | ||
|  |     if (payment_type === 'bank') { | ||
|  |       // 银行卡验证
 | ||
|  |       if (!bank_name || !account_number || !account_holder) { | ||
|  |         return res.status(400).json({ success: false, message: '银行信息不完整' }); | ||
|  |       } | ||
|  |       // 验证银行账号格式(简单验证)
 | ||
|  |       if (!/^\d{10,25}$/.test(account_number.replace(/\s/g, ''))) { | ||
|  |         return res.status(400).json({ success: false, message: '银行账号格式不正确' }); | ||
|  |       } | ||
|  |     } else { | ||
|  |       // 收款码验证
 | ||
|  |       if (!account_holder || !qr_code_url) { | ||
|  |         return res.status(400).json({ success: false, message: '收款码信息不完整' }); | ||
|  |       } | ||
|  |     } | ||
|  | 
 | ||
|  |     // 更新收款方式信息
 | ||
|  |     await db.query( | ||
|  |       'UPDATE regional_agents SET payment_type = ?, bank_name = ?, account_number = ?, account_holder = ?, qr_code_url = ? WHERE id = ?', | ||
|  |       [payment_type, bank_name, account_number, account_holder, qr_code_url, agentId] | ||
|  |     ); | ||
|  | 
 | ||
|  |     res.json({ | ||
|  |       success: true, | ||
|  |       message: '收款方式信息更新成功' | ||
|  |     }); | ||
|  |   } catch (error) { | ||
|  |     console.error('更新收款方式信息失败:', error); | ||
|  |     res.status(500).json({ success: false, message: '更新收款方式信息失败' }); | ||
|  |   } | ||
|  | }); | ||
|  | 
 | ||
|  | /** | ||
|  |  * 上传收款码图片 | ||
|  |  */ | ||
|  | router.post('/upload-qr-code', auth, requireAgent, upload.single('qrCode'), async (req, res) => { | ||
|  |   try { | ||
|  |     if (!req.file) { | ||
|  |       return res.status(400).json({ success: false, message: '请选择要上传的图片' }); | ||
|  |     } | ||
|  | 
 | ||
|  |     // 构建文件访问URL
 | ||
|  |     const fileUrl = `/uploads/qr-codes/${req.file.filename}`; | ||
|  | 
 | ||
|  |     res.json({ | ||
|  |       success: true, | ||
|  |       message: '收款码上传成功', | ||
|  |       data: { | ||
|  |         url: fileUrl, | ||
|  |         filename: req.file.filename | ||
|  |       } | ||
|  |     }); | ||
|  |   } catch (error) { | ||
|  |     console.error('上传收款码失败:', error); | ||
|  |     res.status(500).json({ success: false, message: '上传收款码失败' }); | ||
|  |   } | ||
|  | }); | ||
|  | 
 | ||
|  | /** | ||
|  |  * 兼容旧的银行信息接口 | ||
|  |  */ | ||
|  | router.put('/bank-info', auth, requireAgent, async (req, res) => { | ||
|  |   try { | ||
|  |     const agentId = req.agent.id; | ||
|  |     const { bank_name, bank_account, account_holder } = req.body; | ||
|  |      | ||
|  |     // 验证必填字段
 | ||
|  |     if (!bank_name || !bank_account || !account_holder) { | ||
|  |       return res.status(400).json({ success: false, message: '银行信息不完整' }); | ||
|  |     } | ||
|  |      | ||
|  |     // 验证银行账号格式(简单验证)
 | ||
|  |     if (!/^\d{10,25}$/.test(bank_account)) { | ||
|  |       return res.status(400).json({ success: false, message: '银行账号格式不正确' }); | ||
|  |     } | ||
|  |      | ||
|  |     // 更新银行信息
 | ||
|  |     await db.query( | ||
|  |       'UPDATE regional_agents SET payment_type = "bank", bank_name = ?, account_number = ?, account_holder = ?, bank_account = ? WHERE id = ?', | ||
|  |       [bank_name, bank_account, account_holder, bank_account, agentId] | ||
|  |     ); | ||
|  |      | ||
|  |     res.json({ | ||
|  |       success: true, | ||
|  |       message: '银行信息更新成功' | ||
|  |     }); | ||
|  |   } catch (error) { | ||
|  |     console.error('更新银行信息失败:', error); | ||
|  |     res.status(500).json({ success: false, message: '更新银行信息失败' }); | ||
|  |   } | ||
|  | }); | ||
|  | 
 | ||
|  | /** | ||
|  |  * 申请提现 | ||
|  |  */ | ||
|  | router.post('/apply', auth, requireAgent, async (req, res) => { | ||
|  |   try { | ||
|  |     const agentId = req.agent.id; | ||
|  |     const { amount, apply_note } = req.body; | ||
|  |      | ||
|  |     // 验证提现金额
 | ||
|  |     if (!amount || amount <= 0) { | ||
|  |       return res.status(400).json({ success: false, message: '提现金额必须大于0' }); | ||
|  |     } | ||
|  |      | ||
|  |     if (amount < 10) { | ||
|  |       return res.status(400).json({ success: false, message: '最低提现金额为100元' }); | ||
|  |     } | ||
|  |      | ||
|  |     // 查询代理商信息和可提现金额
 | ||
|  |     const agentQuery = `
 | ||
|  |       SELECT  | ||
|  |         ra.*, | ||
|  |         CAST(COALESCE(SUM(acr.commission_amount), 0) - COALESCE(ra.withdrawn_amount, 0) - COALESCE(ra.pending_withdrawal, 0) AS DECIMAL(10,2)) as available_amount | ||
|  |       FROM regional_agents ra | ||
|  |       LEFT JOIN agent_commission_records acr ON ra.id = acr.agent_id | ||
|  |       WHERE ra.id = ? | ||
|  |       GROUP BY ra.id | ||
|  |     `;
 | ||
|  |      | ||
|  |     const agentResult = await db.query(agentQuery, [agentId]); | ||
|  |      | ||
|  |     if (!agentResult || agentResult.length === 0) { | ||
|  |       return res.status(404).json({ success: false, message: '代理商信息不存在' }); | ||
|  |     } | ||
|  |      | ||
|  |     const agent = agentResult[0]; | ||
|  |      | ||
|  |     // 检查收款方式信息是否完整
 | ||
|  |     const paymentType = agent.payment_type || 'bank'; | ||
|  |      | ||
|  |     if (paymentType === 'bank') { | ||
|  |       // 银行卡收款方式验证
 | ||
|  |       if (!agent.bank_name || !agent.account_number || !agent.account_holder) { | ||
|  |         return res.status(400).json({ success: false, message: '请先完善银行信息' }); | ||
|  |       } | ||
|  |     } else { | ||
|  |       // 收款码收款方式验证
 | ||
|  |       if (!agent.account_holder || !agent.qr_code_url) { | ||
|  |         return res.status(400).json({ success: false, message: '请先完善收款码信息' }); | ||
|  |       } | ||
|  |     } | ||
|  |      | ||
|  |     // 检查可提现金额
 | ||
|  |     if (amount > agent.available_amount) { | ||
|  |       return res.status(400).json({  | ||
|  |         success: false,  | ||
|  |         message: `可提现金额不足,当前可提现:¥${agent.available_amount}`  | ||
|  |       }); | ||
|  |     } | ||
|  |      | ||
|  |     // 开始事务
 | ||
|  |     const pool = getDB(); | ||
|  |     const connection = await pool.getConnection(); | ||
|  |     await connection.beginTransaction(); | ||
|  |      | ||
|  |     try { | ||
|  |       // 创建提现申请
 | ||
|  |       await connection.execute( | ||
|  |         'INSERT INTO agent_withdrawals (agent_id, amount, payment_type, bank_name, account_number, account_holder, qr_code_url, apply_note, bank_account) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)', | ||
|  |         [agentId, amount, paymentType, agent.bank_name || '', agent.account_number, agent.account_holder, agent.qr_code_url, apply_note || null, agent.account_number] | ||
|  |       ); | ||
|  |        | ||
|  |       // 更新代理的待提现金额
 | ||
|  |       await connection.execute( | ||
|  |         'UPDATE regional_agents SET pending_withdrawal = pending_withdrawal + ? WHERE id = ?', | ||
|  |         [amount, agentId] | ||
|  |       ); | ||
|  |        | ||
|  |       await connection.commit(); | ||
|  |       connection.release(); // 释放连接回连接池
 | ||
|  |        | ||
|  |       res.json({ | ||
|  |         success: true, | ||
|  |         message: '提现申请提交成功,请等待审核', | ||
|  |         data: { | ||
|  |           paymentType: paymentType | ||
|  |         } | ||
|  |       }); | ||
|  |     } catch (error) { | ||
|  |       await connection.rollback(); | ||
|  |       connection.release(); // 释放连接回连接池
 | ||
|  |       throw error; | ||
|  |     } | ||
|  |   } catch (error) { | ||
|  |     console.error('申请提现失败:', error); | ||
|  |     res.status(500).json({ success: false, message: '申请提现失败' }); | ||
|  |   } | ||
|  | }); | ||
|  | 
 | ||
|  | /** | ||
|  |  * 获取提现记录 | ||
|  |  */ | ||
|  | router.get('/records', auth, requireAgent, async (req, res) => { | ||
|  |   try { | ||
|  |     const agentId = req.agent.id; | ||
|  |     const { page = 1, limit = 20, status } = req.query; | ||
|  |     const pageNum = parseInt(page) || 1; | ||
|  |     const limitNum = parseInt(limit) || 20; | ||
|  |     const offset = (pageNum - 1) * limitNum; | ||
|  |      | ||
|  |     // 构建查询条件
 | ||
|  |     let whereConditions = ['agent_id = ?']; | ||
|  |     let queryParams = [agentId]; | ||
|  |      | ||
|  |     if (status) { | ||
|  |       whereConditions.push('status = ?'); | ||
|  |       queryParams.push(status); | ||
|  |     } | ||
|  |      | ||
|  |     const whereClause = whereConditions.join(' AND '); | ||
|  |      | ||
|  |     // 查询提现记录
 | ||
|  |     const recordsQuery = `
 | ||
|  |       SELECT  | ||
|  |         id, | ||
|  |         amount, | ||
|  |         payment_type, | ||
|  |         bank_name, | ||
|  |         account_number, | ||
|  |         account_holder, | ||
|  |         qr_code_url, | ||
|  |         status, | ||
|  |         apply_note, | ||
|  |         admin_note, | ||
|  |         created_at, | ||
|  |         processed_at, | ||
|  |         bank_account | ||
|  |       FROM agent_withdrawals | ||
|  |       WHERE ${whereClause} | ||
|  |       ORDER BY created_at DESC | ||
|  |       LIMIT ${limitNum} OFFSET ${offset} | ||
|  |     `;
 | ||
|  |      | ||
|  |     const records = await db.query(recordsQuery, queryParams); | ||
|  |      | ||
|  |     // 处理记录数据,兼容旧格式
 | ||
|  |     const processedRecords = records.map(record => ({ | ||
|  |       ...record, | ||
|  |       payment_type: record.payment_type || 'bank', | ||
|  |       account_number: record.account_number || record.bank_account, | ||
|  |       qr_code_url: record.qr_code_url || '', | ||
|  |       // 保持向后兼容
 | ||
|  |       bank_account: record.bank_account || record.account_number | ||
|  |     })); | ||
|  |      | ||
|  |     // 查询总数
 | ||
|  |     const totalResult = await db.query( | ||
|  |       `SELECT COUNT(*) as total FROM agent_withdrawals WHERE ${whereClause}`, | ||
|  |       queryParams | ||
|  |     ); | ||
|  |     const total = totalResult && totalResult.length > 0 ? totalResult[0].total : 0; | ||
|  |      | ||
|  |     res.json({ | ||
|  |       success: true, | ||
|  |       data: { | ||
|  |         records: processedRecords, | ||
|  |         total: parseInt(total) | ||
|  |       } | ||
|  |     }); | ||
|  |   } catch (error) { | ||
|  |     console.error('获取提现记录失败:', error); | ||
|  |     res.status(500).json({ success: false, message: '获取提现记录失败' }); | ||
|  |   } | ||
|  | }); | ||
|  | 
 | ||
|  | /** | ||
|  |  * 获取佣金明细 | ||
|  |  */ | ||
|  | router.get('/commissions', auth, requireAgent, async (req, res) => { | ||
|  |   try { | ||
|  |     const agentId = req.agent.id; | ||
|  |     const { page = 1, limit = 20 } = req.query; | ||
|  |     const pageNum = parseInt(page) || 1; | ||
|  |     const limitNum = parseInt(limit) || 20; | ||
|  |     const offset = (pageNum - 1) * limitNum; | ||
|  |      | ||
|  |     // 查询佣金记录
 | ||
|  |     const commissionsQuery = `
 | ||
|  |       SELECT  | ||
|  |         acr.*, | ||
|  |         u.real_name as merchant_name, | ||
|  |         CONCAT(SUBSTRING(u.phone, 1, 3), '****', SUBSTRING(u.phone, -4)) as merchant_phone_masked | ||
|  |       FROM agent_commission_records acr | ||
|  |       JOIN users u ON acr.merchant_id = u.id | ||
|  |       WHERE acr.agent_id = ? | ||
|  |       ORDER BY acr.created_at DESC | ||
|  |       LIMIT ${limitNum} OFFSET ${offset} | ||
|  |     `;
 | ||
|  |      | ||
|  |     const commissions = await db.query(commissionsQuery, [agentId]); | ||
|  |      | ||
|  |     // 查询总数
 | ||
|  |     const totalResult = await db.query( | ||
|  |       'SELECT COUNT(*) as total FROM agent_commission_records WHERE agent_id = ?', | ||
|  |       [agentId] | ||
|  |     ); | ||
|  |     const total = totalResult && totalResult.length > 0 ? totalResult[0].total : 0; | ||
|  |      | ||
|  |     res.json({ | ||
|  |       success: true, | ||
|  |       data: { | ||
|  |         commissions, | ||
|  |         total: parseInt(total) | ||
|  |       } | ||
|  |     }); | ||
|  |   } catch (error) { | ||
|  |     console.error('获取佣金明细失败:', error); | ||
|  |     res.status(500).json({ success: false, message: '获取佣金明细失败' }); | ||
|  |   } | ||
|  | }); | ||
|  | 
 | ||
|  | module.exports = router; |