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; |