Files
jurong_circle_black/routes/agent-withdrawals.js
2025-08-26 10:06:23 +08:00

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;