1192 lines
52 KiB
JavaScript
1192 lines
52 KiB
JavaScript
const {getDB} = require('../database');
|
||
const {logger, auditLogger} = require('../config/logger');
|
||
const {AppError} = require('../middleware/errorHandler');
|
||
const {TRANSFER_TYPES, TRANSFER_STATUS, ERROR_CODES, HTTP_STATUS} = require('../config/constants');
|
||
|
||
class TransferService {
|
||
// 创建转账记录
|
||
async createTransfer(fromUserId, transferData) {
|
||
const {to_user_id, amount, transfer_type, description, voucher_url} = transferData;
|
||
const db = getDB();
|
||
|
||
try {
|
||
// 验证用户是否存在
|
||
await this.validateUser(to_user_id);
|
||
|
||
// 验证转账类型
|
||
if (!Object.values(TRANSFER_TYPES).includes(transfer_type)) {
|
||
throw new AppError('无效的转账类型', HTTP_STATUS.BAD_REQUEST, ERROR_CODES.VALIDATION_ERROR);
|
||
}
|
||
|
||
// 检查余额(如果是用户转账)- 允许负余额转账
|
||
if (transfer_type === TRANSFER_TYPES.USER_TO_USER || transfer_type === TRANSFER_TYPES.USER_TO_SYSTEM) {
|
||
if (!fromUserId) {
|
||
throw new AppError('用户转账必须指定发送方用户', HTTP_STATUS.BAD_REQUEST, ERROR_CODES.VALIDATION_ERROR);
|
||
}
|
||
// 获取当前余额但不检查是否足够,允许负余额转账
|
||
await this.checkUserBalance(fromUserId, amount);
|
||
}
|
||
|
||
// 系统转账时,from_user_id 设为 null
|
||
const actualFromUserId = transfer_type === TRANSFER_TYPES.SYSTEM_TO_USER ? null : fromUserId;
|
||
|
||
// 生成批次ID
|
||
const batch_id = this.generateBatchId();
|
||
|
||
// 插入转账记录
|
||
const currentTime = new Date();
|
||
const [result] = await db.execute(
|
||
`INSERT INTO transfers (from_user_id, to_user_id, amount, transfer_type, status, description,
|
||
voucher_url, batch_id, created_at)
|
||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||
[actualFromUserId, to_user_id, amount, transfer_type, TRANSFER_STATUS.PENDING, description || null, voucher_url || null, batch_id, currentTime]
|
||
);
|
||
|
||
const transferId = result.insertId;
|
||
|
||
// 记录审计日志
|
||
auditLogger.info('Transfer created', {
|
||
transferId,
|
||
fromUserId,
|
||
toUserId: to_user_id,
|
||
amount,
|
||
transferType: transfer_type,
|
||
batchId: batch_id
|
||
});
|
||
|
||
logger.info('Transfer created successfully', {transferId, fromUserId, amount});
|
||
|
||
return {
|
||
transfer_id: transferId,
|
||
batch_id,
|
||
status: TRANSFER_STATUS.PENDING
|
||
};
|
||
} catch (error) {
|
||
logger.error('Failed to create transfer', {
|
||
error: error.message,
|
||
fromUserId,
|
||
transferData
|
||
});
|
||
throw error;
|
||
}
|
||
}
|
||
|
||
// 管理员解除坏账
|
||
async removeBadDebt(transferId, adminId, reason) {
|
||
const db = getDB();
|
||
|
||
try {
|
||
// 获取转账记录
|
||
const transfer = await this.getTransferById(transferId);
|
||
|
||
if (!transfer) {
|
||
throw new AppError('转账记录不存在', HTTP_STATUS.NOT_FOUND, ERROR_CODES.NOT_FOUND);
|
||
}
|
||
|
||
if (!transfer.is_bad_debt) {
|
||
throw new AppError('该转账未被标记为坏账', HTTP_STATUS.BAD_REQUEST, ERROR_CODES.VALIDATION_ERROR);
|
||
}
|
||
|
||
// 解除坏账标记
|
||
await db.execute(
|
||
'UPDATE transfers SET is_bad_debt = 0 WHERE id = ?',
|
||
[transferId]
|
||
);
|
||
|
||
// 记录管理员操作日志
|
||
await db.execute(
|
||
`INSERT INTO admin_operation_logs (admin_id, operation_type, target_type, target_id, description,
|
||
created_at)
|
||
VALUES (?, 'remove_bad_debt', 'transfer', ?, ?, NOW())`,
|
||
[adminId, transferId, reason || `管理员解除转账${transferId}的坏账标记`]
|
||
);
|
||
|
||
// 记录审计日志
|
||
auditLogger.info('Bad debt removed by admin', {
|
||
transferId,
|
||
adminId,
|
||
fromUserId: transfer.from_user_id,
|
||
toUserId: transfer.to_user_id,
|
||
amount: transfer.amount,
|
||
reason
|
||
});
|
||
|
||
logger.info('Bad debt removed successfully', {transferId, adminId, reason});
|
||
|
||
return {success: true};
|
||
} catch (error) {
|
||
logger.error('Failed to remove bad debt', {
|
||
error: error.message,
|
||
transferId,
|
||
adminId
|
||
});
|
||
throw error;
|
||
}
|
||
}
|
||
|
||
// 确认转账
|
||
async confirmTransfer(transferId, note, operatorId) {
|
||
const mysql = require('mysql2/promise');
|
||
const {dbConfig} = require('../database');
|
||
|
||
// 创建单独的连接用于事务处理
|
||
const connection = await mysql.createConnection(dbConfig);
|
||
|
||
try {
|
||
// 获取转账记录
|
||
const transfer = await this.getTransferById(transferId);
|
||
|
||
if (!transfer) {
|
||
throw new AppError('转账记录不存在', HTTP_STATUS.NOT_FOUND, ERROR_CODES.NOT_FOUND);
|
||
}
|
||
|
||
if (transfer.status !== TRANSFER_STATUS.PENDING) {
|
||
throw new AppError('转账记录状态不允许确认', HTTP_STATUS.BAD_REQUEST, ERROR_CODES.VALIDATION_ERROR);
|
||
}
|
||
|
||
// 检查是否为坏账
|
||
if (transfer.is_bad_debt) {
|
||
throw new AppError('该转账已被标记为坏账,无法确认。请联系管理员处理', HTTP_STATUS.BAD_REQUEST, ERROR_CODES.VALIDATION_ERROR);
|
||
}
|
||
|
||
// 开始事务
|
||
await connection.beginTransaction();
|
||
|
||
try {
|
||
// 更新转账状态
|
||
await connection.execute(
|
||
'UPDATE transfers SET status = ? WHERE id = ?',
|
||
[TRANSFER_STATUS.CONFIRMED, transferId]
|
||
);
|
||
|
||
// 如果存在匹配订单ID,更新匹配订单状态
|
||
if (transfer.matching_order_id) {
|
||
// 查询该匹配订单下所有transfers的状态
|
||
const [allTransfers] = await connection.execute(
|
||
`SELECT status
|
||
FROM transfers
|
||
WHERE matching_order_id = ?`,
|
||
[transfer.matching_order_id]
|
||
);
|
||
|
||
let matchingOrderStatus;
|
||
|
||
// 根据所有相关transfers的状态来决定matching_order的状态
|
||
const transferStatuses = allTransfers.map(t => t.status);
|
||
console.log(transferStatuses, 'transferStatuses');
|
||
|
||
if (transferStatuses.every(status => status === 'cancelled' || status === 'rejected' || status === 'not_received')) {
|
||
// 如果所有transfers都被取消/拒绝/未收到,匹配订单标记为已完成
|
||
matchingOrderStatus = 'completed';
|
||
} else if (transferStatuses.every(status => status === 'received')) {
|
||
// 如果所有transfers都已收到,匹配订单完成
|
||
matchingOrderStatus = 'completed';
|
||
} else if (transferStatuses.includes('cancelled') || transferStatuses.includes('rejected') || transferStatuses.includes('not_received') || transferStatuses.some(status => status === 'confirmed' || status === 'received')) {
|
||
// 如果有任何一个transfer被取消/拒绝/未收到,或者有transfers已确认或已收到,匹配订单为进行中状态
|
||
matchingOrderStatus = 'matching';
|
||
} else {
|
||
// 其他情况为待处理状态
|
||
matchingOrderStatus = 'matching';
|
||
}
|
||
|
||
await connection.execute(
|
||
`UPDATE matching_orders
|
||
SET status = ?,
|
||
updated_at = NOW()
|
||
WHERE id = ?`,
|
||
[matchingOrderStatus, transfer.matching_order_id]
|
||
);
|
||
|
||
logger.info('Matching order status updated after transfer confirmation', {
|
||
matchingOrderId: transfer.matching_order_id,
|
||
transferId: transferId,
|
||
newMatchingOrderStatus: matchingOrderStatus,
|
||
allTransferStatuses: transferStatuses
|
||
});
|
||
}
|
||
|
||
// 注意:发送方余额将在接收方确认收款时扣除,而不是在确认转账时扣除
|
||
// 这样可以避免资金被锁定但收款方未确认的情况
|
||
|
||
await connection.commit();
|
||
|
||
// 记录审计日志
|
||
auditLogger.info('Transfer confirmed', {
|
||
transferId,
|
||
operatorId,
|
||
fromUserId: transfer.from_user_id,
|
||
toUserId: transfer.to_user_id,
|
||
amount: transfer.amount
|
||
});
|
||
|
||
logger.info('Transfer confirmed successfully', {transferId, operatorId});
|
||
|
||
return {success: true};
|
||
} catch (error) {
|
||
await connection.rollback();
|
||
throw error;
|
||
}
|
||
} catch (error) {
|
||
logger.error('Failed to confirm transfer', {
|
||
error: error.message,
|
||
transferId,
|
||
operatorId
|
||
});
|
||
throw error;
|
||
} finally {
|
||
await connection.end();
|
||
}
|
||
}
|
||
|
||
// 获取转账列表
|
||
async getTransfers(filters = {}, pagination = {}, user_type = 'user_to_user') {
|
||
const db = getDB();
|
||
const {page = 1, limit = 10, sort = 'created_at', order = 'desc'} = pagination;
|
||
const pageNum = parseInt(page, 10) || 1;
|
||
const limitNum = parseInt(limit, 10) || 10;
|
||
const offset = (pageNum - 1) * limitNum;
|
||
|
||
let whereClause = 'WHERE ';
|
||
const params = [];
|
||
whereClause += `t.transfer_type='${user_type}'`;
|
||
// 构建查询条件
|
||
if (filters.user_id) {
|
||
whereClause += ' AND (t.from_user_id = ? OR t.to_user_id = ?)';
|
||
params.push(filters.user_id, filters.user_id);
|
||
}
|
||
|
||
if (filters.status) {
|
||
whereClause += ' AND t.status = ?';
|
||
params.push(filters.status);
|
||
}
|
||
|
||
if (filters.transfer_type) {
|
||
whereClause += ' AND t.transfer_type = ?';
|
||
params.push(filters.transfer_type);
|
||
}
|
||
|
||
if (filters.start_date) {
|
||
whereClause += ' AND t.created_at >= ?';
|
||
params.push(filters.start_date);
|
||
}
|
||
|
||
if (filters.end_date) {
|
||
whereClause += ' AND t.created_at <= ?';
|
||
params.push(filters.end_date);
|
||
}
|
||
|
||
if (filters.search) {
|
||
whereClause += ' AND (fu.username LIKE ? OR fu.real_name LIKE ? OR tu.username LIKE ? OR tu.real_name LIKE ?)';
|
||
const searchPattern = `%${filters.search}%`;
|
||
params.push(searchPattern, searchPattern, searchPattern, searchPattern);
|
||
}
|
||
|
||
// 构建排序子句
|
||
const validSortFields = ['id', 'amount', 'created_at', 'updated_at', 'status'];
|
||
const sortField = validSortFields.includes(sort) ? sort : 'created_at';
|
||
const sortOrder = order && order.toLowerCase() === 'asc' ? 'ASC' : 'DESC';
|
||
|
||
const orderClause = `ORDER BY t.${sortField} ${sortOrder}`;
|
||
|
||
try {
|
||
// 获取总数
|
||
const [countResult] = await db.execute(
|
||
`SELECT COUNT(*) as total
|
||
FROM transfers t
|
||
LEFT JOIN users fu ON t.from_user_id = fu.id
|
||
LEFT JOIN users tu ON t.to_user_id = tu.id
|
||
${whereClause}`,
|
||
params
|
||
);
|
||
const total = countResult[0].total;
|
||
// 获取数据
|
||
const [transfers] = await db.execute(
|
||
`SELECT t.*,
|
||
fu.username as from_username,
|
||
fu.real_name as from_real_name,
|
||
tu.username as to_username,
|
||
tu.real_name as to_real_name,
|
||
f_p.name as from_province,
|
||
f_c.name as from_city,
|
||
f_d.name as from_district,
|
||
t_p.name as to_province,
|
||
t_c.name as to_city,
|
||
t_d.name as to_district
|
||
FROM transfers t
|
||
LEFT JOIN users fu ON t.from_user_id = fu.id
|
||
LEFT JOIN users tu ON t.to_user_id = tu.id
|
||
LEFT JOIN china_regions f_p ON f_p.code = fu.province
|
||
LEFT JOIN china_regions f_c ON f_c.code = fu.city
|
||
LEFT JOIN china_regions f_d ON f_d.code = fu.district_id
|
||
LEFT JOIN china_regions t_p ON t_p.code = tu.province
|
||
LEFT JOIN china_regions t_c ON t_c.code = tu.city
|
||
LEFT JOIN china_regions t_d ON t_d.code = tu.district_id
|
||
${whereClause} ${orderClause}
|
||
LIMIT ${limitNum}`,
|
||
params
|
||
);
|
||
|
||
return {
|
||
transfers,
|
||
pagination: {
|
||
page: pageNum,
|
||
limit: limitNum,
|
||
total,
|
||
pages: Math.ceil(total / limitNum)
|
||
}
|
||
};
|
||
} catch (error) {
|
||
logger.error('Failed to get transfers', {error: error.message, filters});
|
||
throw error;
|
||
}
|
||
}
|
||
|
||
async getTransfersHistory(filters = {}, pagination = {}, user_type = 'manual') {
|
||
const db = getDB();
|
||
const {page = 1, limit = 10, sort = 'created_at', order = 'desc'} = pagination;
|
||
const pageNum = parseInt(page, 10) || 1;
|
||
const limitNum = parseInt(limit, 10) || 10;
|
||
const offset = (pageNum - 1) * limitNum;
|
||
|
||
let whereClause = 'WHERE 1=1 ';
|
||
const params = [];
|
||
whereClause += `AND source_type != '${user_type}'`;
|
||
// 构建查询条件
|
||
if (filters.user_id) {
|
||
whereClause += ' AND (from_user_id = ? OR to_user_id = ?)';
|
||
params.push(filters.user_id, filters.user_id);
|
||
}
|
||
|
||
if (filters.status) {
|
||
whereClause += ' AND status = ?';
|
||
params.push(filters.status);
|
||
}
|
||
|
||
if (filters.transfer_type) {
|
||
whereClause += ' AND transfer_type = ?';
|
||
params.push(filters.transfer_type);
|
||
}
|
||
|
||
if (filters.start_date) {
|
||
whereClause += ' AND created_at >= ?';
|
||
params.push(filters.start_date);
|
||
}
|
||
|
||
if (filters.end_date) {
|
||
whereClause += ' AND created_at <= ?';
|
||
params.push(filters.end_date);
|
||
}
|
||
|
||
if (filters.search) {
|
||
whereClause += ' AND (fu.username LIKE ? OR fu.real_name LIKE ? OR tu.username LIKE ? OR tu.real_name LIKE ?)';
|
||
const searchPattern = `%${filters.search}%`;
|
||
params.push(searchPattern, searchPattern, searchPattern, searchPattern);
|
||
}
|
||
|
||
// 构建排序子句
|
||
const validSortFields = ['id', 'amount', 'created_at', 'updated_at', 'status'];
|
||
const sortField = validSortFields.includes(sort) ? sort : 'created_at';
|
||
const sortOrder = order && order.toLowerCase() === 'asc' ? 'ASC' : 'DESC';
|
||
|
||
const orderClause = `ORDER BY t.${sortField} ${sortOrder}`;
|
||
|
||
try {
|
||
// 获取总数
|
||
const [countResult] = await db.execute(
|
||
`SELECT COUNT(*) as total
|
||
FROM transfers t
|
||
LEFT JOIN users fu ON t.from_user_id = fu.id
|
||
LEFT JOIN users tu ON t.to_user_id = tu.id
|
||
${whereClause}`,
|
||
params
|
||
);
|
||
const total = countResult[0].total;
|
||
|
||
// 获取数据
|
||
const [transfers] = await db.execute(
|
||
`SELECT t.*,
|
||
fu.username as from_username,
|
||
fu.real_name as from_real_name,
|
||
tu.username as to_username,
|
||
tu.real_name as to_real_name
|
||
FROM transfers t
|
||
LEFT JOIN users fu ON t.from_user_id = fu.id
|
||
LEFT JOIN users tu ON t.to_user_id = tu.id
|
||
${whereClause} ${orderClause}
|
||
LIMIT ${limitNum} OFFSET ${offset}`,
|
||
params
|
||
);
|
||
//获取总数
|
||
const stats = {};
|
||
//获取系统转给融豆的总数
|
||
let [total_to_admin] = await db.execute(`SELECT SUM(t.amount) as total FROM transfers t WHERE t.source_type = 'system'`)
|
||
stats.total_to_admin = total_to_admin[0].total || 0
|
||
//转给代理的融豆总数
|
||
let [total_to_agent] = await db.execute(`SELECT SUM(t.amount) as total FROM transfers t WHERE t.source_type = 'agent'`)
|
||
stats.total_to_agent = total_to_agent[0].total || 0
|
||
//转给直营代理的融豆数量
|
||
let [total_to_agent_directly] = await db.execute(`SELECT SUM(t.amount) as total FROM transfers t WHERE t.source_type = 'operated_agent'`)
|
||
stats.total_to_agent_directly = total_to_agent_directly[0].total || 0
|
||
//转给直营的融豆总数
|
||
let [total_to_directly_operated] = await db.execute(`SELECT SUM(t.amount) as total FROM transfers t WHERE t.source_type = 'directly_operated'`)
|
||
stats.total_to_directly_operated = total_to_directly_operated[0].total || 0
|
||
//提现总数
|
||
let [total_get] = await db.execute(`SELECT SUM(t.amount) as total FROM transfers t WHERE t.source_type = 'withdraw'`)
|
||
stats.total_get = total_get[0].total || 0
|
||
return {
|
||
transfers,
|
||
stats,
|
||
pagination: {
|
||
page: pageNum,
|
||
limit: limitNum,
|
||
total,
|
||
pages: Math.ceil(total / limitNum)
|
||
}
|
||
};
|
||
} catch (error) {
|
||
logger.error('Failed to get transfers', {error: error.message, filters});
|
||
throw error;
|
||
}
|
||
}
|
||
|
||
// 验证用户是否存在
|
||
async validateUser(userId) {
|
||
const db = getDB();
|
||
const [users] = await db.execute('SELECT id FROM users WHERE id = ?', [userId]);
|
||
if (users.length === 0) {
|
||
throw new AppError('用户不存在', HTTP_STATUS.BAD_REQUEST, ERROR_CODES.VALIDATION_ERROR);
|
||
}
|
||
}
|
||
|
||
// 检查用户余额(现在检查balance字段,允许负数)
|
||
async checkUserBalance(userId, amount) {
|
||
const db = getDB();
|
||
const [users] = await db.execute('SELECT balance FROM users WHERE id = ?', [userId]);
|
||
if (users.length === 0) {
|
||
throw new AppError('用户不存在', HTTP_STATUS.BAD_REQUEST, ERROR_CODES.VALIDATION_ERROR);
|
||
}
|
||
// 余额可以为负数,所以不需要检查余额不足
|
||
return users[0].balance;
|
||
}
|
||
|
||
// 获取转账记录
|
||
async getTransferById(transferId) {
|
||
const db = getDB();
|
||
const [transfers] = await db.execute('SELECT * FROM transfers WHERE id = ?', [transferId]);
|
||
return transfers[0] || null;
|
||
}
|
||
|
||
|
||
// 用户确认收到转账
|
||
async confirmReceived(transferId, userId) {
|
||
const mysql = require('mysql2/promise');
|
||
const {dbConfig} = require('../database');
|
||
|
||
// 创建单独的连接用于事务处理
|
||
const connection = await mysql.createConnection(dbConfig);
|
||
|
||
try {
|
||
// 获取转账记录
|
||
const transfer = await this.getTransferById(transferId);
|
||
|
||
if (!transfer) {
|
||
throw new AppError('转账记录不存在', HTTP_STATUS.NOT_FOUND, ERROR_CODES.NOT_FOUND);
|
||
}
|
||
|
||
// 检查用户权限:必须是收款方本人或管理员
|
||
const [userRows] = await connection.execute(
|
||
'SELECT role FROM users WHERE id = ?',
|
||
[userId]
|
||
);
|
||
|
||
const isAdmin = userRows[0]?.role === 'admin';
|
||
const isRecipient = transfer.to_user_id === userId;
|
||
|
||
if (!isRecipient && !isAdmin) {
|
||
throw new AppError('无权限操作此转账', HTTP_STATUS.FORBIDDEN, ERROR_CODES.VALIDATION_ERROR);
|
||
}
|
||
|
||
if (transfer.status !== TRANSFER_STATUS.CONFIRMED) {
|
||
throw new AppError('转账状态不允许确认收款', HTTP_STATUS.BAD_REQUEST, ERROR_CODES.VALIDATION_ERROR);
|
||
}
|
||
|
||
// 检查是否为坏账
|
||
if (transfer.is_bad_debt) {
|
||
throw new AppError('该转账已被标记为坏账,无法确认收款。请联系管理员处理', HTTP_STATUS.BAD_REQUEST, ERROR_CODES.VALIDATION_ERROR);
|
||
}
|
||
|
||
// 开始事务
|
||
await connection.beginTransaction();
|
||
|
||
try {
|
||
// 更新转账状态为已收到
|
||
await connection.execute(
|
||
'UPDATE transfers SET status = ? WHERE id = ?',
|
||
[TRANSFER_STATUS.RECEIVED, transferId]
|
||
);
|
||
|
||
// 扣除发送方余额(在接收方确认收款时扣除)
|
||
if (transfer.from_user_id) {
|
||
await connection.execute(
|
||
'UPDATE users SET balance = balance - ? WHERE id = ?',
|
||
[transfer.amount, transfer.from_user_id]
|
||
);
|
||
}
|
||
|
||
// 所有类型的转账都需要在接收方确认收到时增加接收方余额
|
||
// 这与 confirmTransfer 方法的修改保持一致
|
||
await connection.execute(
|
||
'UPDATE users SET balance = balance + ? WHERE id = ?',
|
||
[transfer.amount, transfer.to_user_id]
|
||
);
|
||
|
||
// 给发起人发放相应的积分(转账金额 = 积分数量)
|
||
await connection.execute(
|
||
'UPDATE users SET points = points + ? WHERE id = ?',
|
||
[transfer.amount, transfer.from_user_id]
|
||
);
|
||
|
||
// 记录积分历史
|
||
await connection.execute(
|
||
`INSERT INTO points_history (user_id, amount, type, description, created_at)
|
||
VALUES (?, ?, 'earn', ?, NOW())`,
|
||
[transfer.from_user_id, transfer.amount, `转账确认收款奖励积分,转账ID: ${transferId}`]
|
||
);
|
||
|
||
// 记录详细的余额变更审计日志
|
||
auditLogger.info('Balance adjustment - confirm received', {
|
||
transferId: transferId,
|
||
fromUserId: transfer.from_user_id,
|
||
toUserId: transfer.to_user_id,
|
||
amount: transfer.amount,
|
||
operation: 'add_receiver_balance',
|
||
operatorId: userId,
|
||
operatorType: isAdmin ? 'admin' : 'user',
|
||
timestamp: new Date().toISOString()
|
||
});
|
||
|
||
await connection.commit();
|
||
|
||
// 记录审计日志
|
||
auditLogger.info('Transfer received confirmed', {
|
||
transferId,
|
||
userId,
|
||
amount: transfer.amount,
|
||
pointsAwarded: transfer.amount,
|
||
pointsAwardedTo: transfer.from_user_id
|
||
});
|
||
|
||
logger.info('Transfer received confirmed successfully', {
|
||
transferId,
|
||
userId,
|
||
pointsAwarded: transfer.amount,
|
||
pointsAwardedTo: transfer.from_user_id
|
||
});
|
||
|
||
// 检查并处理代理佣金(转账完成后)
|
||
try {
|
||
const matchingService = require('./matchingService');
|
||
await matchingService.checkAndProcessAgentCommission(transfer.from_user_id);
|
||
logger.info('Agent commission check completed', {
|
||
transferId,
|
||
fromUserId: transfer.from_user_id
|
||
});
|
||
} catch (commissionError) {
|
||
// 代理佣金处理失败不影响主流程
|
||
logger.error('Agent commission processing failed', {
|
||
transferId,
|
||
fromUserId: transfer.from_user_id,
|
||
error: commissionError.message
|
||
});
|
||
}
|
||
|
||
return {success: true};
|
||
} catch (error) {
|
||
await connection.rollback();
|
||
throw error;
|
||
}
|
||
} catch (error) {
|
||
logger.error('Failed to confirm received transfer', {
|
||
error: error.message,
|
||
transferId,
|
||
userId
|
||
});
|
||
throw error;
|
||
} finally {
|
||
await connection.end();
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 用户确认未收到转账
|
||
* 当用户确认未收到款项时,将转账状态改为not_received并回滚发送方余额
|
||
* @param {number} transferId - 转账ID
|
||
* @param {number} userId - 操作用户ID
|
||
* @returns {Object} 操作结果
|
||
*/
|
||
async confirmNotReceived(transferId, userId) {
|
||
const db = getDB();
|
||
const connection = await db.getConnection();
|
||
|
||
try {
|
||
// 获取转账记录
|
||
const transfer = await this.getTransferById(transferId);
|
||
|
||
if (!transfer) {
|
||
throw new AppError('转账记录不存在', HTTP_STATUS.NOT_FOUND, ERROR_CODES.NOT_FOUND);
|
||
}
|
||
|
||
// 检查用户权限:必须是收款方本人或管理员
|
||
const [userRows] = await db.execute(
|
||
'SELECT role FROM users WHERE id = ?',
|
||
[userId]
|
||
);
|
||
|
||
const isAdmin = userRows[0]?.role === 'admin';
|
||
const isRecipient = transfer.to_user_id === userId;
|
||
|
||
if (!isRecipient && !isAdmin) {
|
||
throw new AppError('无权限操作此转账', HTTP_STATUS.FORBIDDEN, ERROR_CODES.VALIDATION_ERROR);
|
||
}
|
||
|
||
if (transfer.status !== TRANSFER_STATUS.CONFIRMED) {
|
||
throw new AppError('转账状态不允许确认未收款', HTTP_STATUS.BAD_REQUEST, ERROR_CODES.VALIDATION_ERROR);
|
||
}
|
||
|
||
// 开始事务
|
||
await connection.beginTransaction();
|
||
|
||
try {
|
||
// 更新转账状态为未收到
|
||
await connection.execute(
|
||
'UPDATE transfers SET status = ? WHERE id = ?',
|
||
[TRANSFER_STATUS.NOT_RECEIVED, transferId]
|
||
);
|
||
|
||
// 注意:在新逻辑下,CONFIRMED状态时发送方余额还没有被扣除,所以无需回滚
|
||
logger.info('Transfer marked as not received - no balance adjustment needed', {
|
||
transferId,
|
||
userId: transfer.from_user_id,
|
||
amount: transfer.amount,
|
||
operatorId: userId,
|
||
note: 'Sender balance was not deducted in confirmed status under new logic'
|
||
});
|
||
|
||
await connection.commit();
|
||
|
||
// 记录审计日志
|
||
auditLogger.info('Transfer not received confirmed', {
|
||
transferId,
|
||
userId,
|
||
amount: transfer.amount,
|
||
fromUserId: transfer.from_user_id,
|
||
balanceRestored: false,
|
||
note: 'No balance restoration needed under new logic'
|
||
});
|
||
|
||
logger.info('Transfer not received confirmed successfully', {
|
||
transferId,
|
||
userId,
|
||
balanceRestored: false,
|
||
note: 'No balance restoration needed under new logic'
|
||
});
|
||
|
||
return {success: true};
|
||
} catch (error) {
|
||
await connection.rollback();
|
||
throw error;
|
||
} finally {
|
||
connection.release();
|
||
}
|
||
} catch (error) {
|
||
logger.error('Failed to confirm not received transfer', {
|
||
error: error.message,
|
||
transferId,
|
||
userId
|
||
});
|
||
throw error;
|
||
}
|
||
}
|
||
|
||
// 拒绝转账
|
||
async rejectTransfer(transferId, note, operatorId) {
|
||
const db = getDB();
|
||
let connection;
|
||
|
||
try {
|
||
// 从连接池获取连接用于事务处理
|
||
connection = await db.getConnection();
|
||
|
||
try {
|
||
// 获取转账记录
|
||
const transfer = await this.getTransferById(transferId);
|
||
|
||
if (!transfer) {
|
||
throw new AppError('转账记录不存在', HTTP_STATUS.NOT_FOUND, ERROR_CODES.NOT_FOUND);
|
||
}
|
||
|
||
if (transfer.status !== TRANSFER_STATUS.PENDING) {
|
||
throw new AppError('转账记录状态不允许拒绝', HTTP_STATUS.BAD_REQUEST, ERROR_CODES.VALIDATION_ERROR);
|
||
}
|
||
|
||
// 检查是否已超时
|
||
if (transfer.is_overdue) {
|
||
throw new AppError('已超时的转账不能拒绝,异常状态只能后台解除', HTTP_STATUS.BAD_REQUEST, ERROR_CODES.VALIDATION_ERROR);
|
||
}
|
||
|
||
// 检查是否有截止时间且已过期
|
||
if (transfer.deadline_at) {
|
||
const deadline = new Date(transfer.deadline_at);
|
||
const now = new Date();
|
||
if (now > deadline) {
|
||
throw new AppError('已超时的转账不能拒绝,异常状态只能后台解除', HTTP_STATUS.BAD_REQUEST, ERROR_CODES.VALIDATION_ERROR);
|
||
}
|
||
}
|
||
|
||
// 开始事务
|
||
await connection.beginTransaction();
|
||
|
||
try {
|
||
// 更新转账状态
|
||
await connection.execute(
|
||
'UPDATE transfers SET status = ? WHERE id = ?',
|
||
[TRANSFER_STATUS.REJECTED, transferId]
|
||
);
|
||
|
||
// 注意:在新逻辑下,CONFIRMED状态时发送方余额还没有被扣除,所以无需回滚
|
||
// 只有在RECEIVED状态时才需要回滚余额,但RECEIVED状态的转账不应该被拒绝
|
||
logger.info('Transfer rejected - no balance adjustment needed', {
|
||
transferId,
|
||
userId: transfer.from_user_id,
|
||
amount: transfer.amount,
|
||
message: 'Sender balance was not deducted in confirmed status under new logic'
|
||
});
|
||
|
||
// 如果是分配类型的转账,需要更新对应的matching_order状态
|
||
if (transfer.matching_order_id) {
|
||
// 查询该matching_order下所有source_type为allocation的transfers状态
|
||
const [allTransfers] = await connection.execute(
|
||
`SELECT status
|
||
FROM transfers
|
||
WHERE matching_order_id = ?
|
||
AND source_type = 'allocation'`,
|
||
[transfer.matching_order_id]
|
||
);
|
||
|
||
// 统计各种状态的数量
|
||
const statusCounts = {
|
||
cancelled: 0,
|
||
rejected: 0,
|
||
not_received: 0,
|
||
confirmed: 0,
|
||
received: 0,
|
||
pending: 0
|
||
};
|
||
|
||
allTransfers.forEach(t => {
|
||
if (statusCounts.hasOwnProperty(t.status)) {
|
||
statusCounts[t.status]++;
|
||
}
|
||
});
|
||
|
||
const totalTransfers = allTransfers.length;
|
||
const problemTransfers = statusCounts.cancelled + statusCounts.rejected + statusCounts.not_received;
|
||
const completedTransfers = statusCounts.received;
|
||
const activeTransfers = statusCounts.confirmed + statusCounts.received;
|
||
|
||
let matchingOrderStatus;
|
||
if (problemTransfers === totalTransfers) {
|
||
// 所有transfers都是问题状态,matching_order为已完成
|
||
matchingOrderStatus = 'completed';
|
||
} else if (completedTransfers === totalTransfers) {
|
||
// 所有transfers都已收到,matching_order为已完成
|
||
matchingOrderStatus = 'completed';
|
||
} else if (problemTransfers > 0 || activeTransfers > 0) {
|
||
// 有问题transfers或有活跃transfers,matching_order为进行中
|
||
matchingOrderStatus = 'matching';
|
||
} else {
|
||
// 其他情况为等待中
|
||
matchingOrderStatus = 'pending';
|
||
}
|
||
|
||
// 更新matching_order状态
|
||
await connection.execute(
|
||
`UPDATE matching_orders
|
||
SET status = ?,
|
||
updated_at = NOW()
|
||
WHERE id = ?`,
|
||
[matchingOrderStatus, transfer.matching_order_id]
|
||
);
|
||
|
||
logger.info('Updated matching_order status after transfer rejection', {
|
||
matchingOrderId: transfer.matching_order_id,
|
||
newStatus: matchingOrderStatus,
|
||
transferId,
|
||
statusCounts
|
||
});
|
||
}
|
||
|
||
await connection.commit();
|
||
|
||
// 记录审计日志
|
||
auditLogger.info('Transfer rejected', {
|
||
transferId,
|
||
operatorId,
|
||
fromUserId: transfer.from_user_id,
|
||
toUserId: transfer.to_user_id,
|
||
amount: transfer.amount,
|
||
note,
|
||
balanceRestored: false, // 在新逻辑下无需回滚余额
|
||
balanceRestoredNote: 'No balance restoration needed under new logic'
|
||
});
|
||
|
||
logger.info('Transfer rejected successfully', {transferId, operatorId});
|
||
|
||
return {success: true};
|
||
} catch (error) {
|
||
await connection.rollback();
|
||
throw error;
|
||
}
|
||
} catch (error) {
|
||
logger.error('Failed to reject transfer', {
|
||
error: error.message,
|
||
transferId,
|
||
operatorId
|
||
});
|
||
throw error;
|
||
} finally {
|
||
if (connection) {
|
||
connection.release(); // 释放连接回连接池
|
||
}
|
||
}
|
||
} catch (error) {
|
||
logger.error('Failed to get database connection for reject transfer', {
|
||
error: error.message,
|
||
transferId,
|
||
operatorId
|
||
});
|
||
throw error;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 强制变更转账状态(管理员权限)
|
||
* 用于处理货款纠纷等异常情况
|
||
* @param {number} transferId - 转账ID
|
||
* @param {string} newStatus - 新状态
|
||
* @param {string} reason - 变更原因
|
||
* @param {number} adminId - 管理员ID
|
||
* @param {boolean} adjust_balance - 是否调整余额
|
||
*/
|
||
async forceChangeTransferStatus(transferId, newStatus, reason, adminId, adjust_balance = false) {
|
||
const db = getDB();
|
||
let connection;
|
||
|
||
try {
|
||
// 从连接池获取连接用于事务处理
|
||
connection = await db.getConnection();
|
||
|
||
try {
|
||
// 获取转账记录
|
||
const transfer = await this.getTransferById(transferId);
|
||
|
||
if (!transfer) {
|
||
throw new AppError('转账记录不存在', HTTP_STATUS.NOT_FOUND, ERROR_CODES.NOT_FOUND);
|
||
}
|
||
|
||
const oldStatus = transfer.status;
|
||
|
||
// 验证新状态
|
||
const validStatuses = [
|
||
TRANSFER_STATUS.PENDING,
|
||
TRANSFER_STATUS.CONFIRMED,
|
||
TRANSFER_STATUS.RECEIVED,
|
||
TRANSFER_STATUS.REJECTED,
|
||
TRANSFER_STATUS.CANCELLED,
|
||
TRANSFER_STATUS.NOT_RECEIVED
|
||
];
|
||
if (!validStatuses.includes(newStatus)) {
|
||
throw new AppError('无效的转账状态', HTTP_STATUS.BAD_REQUEST, ERROR_CODES.VALIDATION_ERROR);
|
||
}
|
||
|
||
// 开始事务
|
||
await connection.beginTransaction();
|
||
|
||
try {
|
||
// 更新转账状态
|
||
await connection.execute(
|
||
`UPDATE transfers
|
||
SET status = ?,
|
||
admin_note = ?,
|
||
admin_modified_at = NOW(),
|
||
admin_modified_by = ?
|
||
WHERE id = ?`,
|
||
[newStatus, reason, adminId, transferId]
|
||
);
|
||
|
||
// 同步更新matching_orders表的状态
|
||
if (transfer.matching_order_id) {
|
||
// 查询该匹配订单下所有transfers的状态
|
||
const [allTransfers] = await connection.execute(
|
||
`SELECT status
|
||
FROM transfers
|
||
WHERE matching_order_id = ?`,
|
||
[transfer.matching_order_id]
|
||
);
|
||
|
||
let matchingOrderStatus;
|
||
|
||
// 根据所有相关transfers的状态来决定matching_order的状态
|
||
const transferStatuses = allTransfers.map(t => t.status);
|
||
console.log(transferStatuses, 'transferStatuses');
|
||
|
||
if (transferStatuses.every(status => status === 'cancelled' || status === 'rejected' || status === 'not_received' || status === 'confirmed' || status === 'received')) {
|
||
// 如果所有transfers都被取消/拒绝/未收到,匹配订单标记为已完成
|
||
matchingOrderStatus = 'completed';
|
||
} else if (transferStatuses.every(status => status === 'received')) {
|
||
// 如果所有transfers都已收到,匹配订单完成
|
||
matchingOrderStatus = 'completed';
|
||
} else if (transferStatuses.includes('cancelled') || transferStatuses.includes('rejected') || transferStatuses.includes('not_received')) {
|
||
// 如果有任何一个transfer被取消/拒绝/未收到,或者有transfers已确认或已收到,匹配订单为进行中状态
|
||
matchingOrderStatus = 'matching';
|
||
} else {
|
||
// 其他情况为待处理状态
|
||
matchingOrderStatus = 'pending';
|
||
}
|
||
console.log('matchingOrderStatus', matchingOrderStatus);
|
||
|
||
await connection.execute(
|
||
`UPDATE matching_orders
|
||
SET status = ?,
|
||
updated_at = NOW()
|
||
WHERE id = ?`,
|
||
[matchingOrderStatus, transfer.matching_order_id]
|
||
);
|
||
|
||
logger.info('Matching order status updated based on all transfers', {
|
||
matchingOrderId: transfer.matching_order_id,
|
||
transferId: transferId,
|
||
oldTransferStatus: oldStatus,
|
||
newTransferStatus: newStatus,
|
||
allTransferStatuses: transferStatuses,
|
||
newMatchingOrderStatus: matchingOrderStatus,
|
||
adminId
|
||
});
|
||
}
|
||
|
||
// 根据状态变更调整余额
|
||
if (adjust_balance && transfer.from_user_id) {
|
||
if (oldStatus === TRANSFER_STATUS.CONFIRMED && (newStatus === TRANSFER_STATUS.REJECTED || newStatus === TRANSFER_STATUS.CANCELLED || newStatus === TRANSFER_STATUS.NOT_RECEIVED)) {
|
||
// 从已确认变为拒绝/取消/未收到:由于新逻辑下CONFIRMED状态时发送方余额未扣除,所以无需回滚
|
||
logger.info('Status change from confirmed to rejected/cancelled/not_received - no balance adjustment needed', {
|
||
transferId,
|
||
userId: transfer.from_user_id,
|
||
amount: transfer.amount,
|
||
oldStatus,
|
||
newStatus,
|
||
note: 'Sender balance was not deducted in confirmed status under new logic'
|
||
});
|
||
|
||
// 记录详细的余额变更审计日志
|
||
auditLogger.info('Balance adjustment - status change (no action needed)', {
|
||
transferId: transferId,
|
||
fromUserId: transfer.from_user_id,
|
||
toUserId: transfer.to_user_id,
|
||
amount: transfer.amount,
|
||
operation: 'no_action_needed',
|
||
oldStatus: oldStatus,
|
||
newStatus: newStatus,
|
||
adminId: adminId,
|
||
reason: reason,
|
||
note: 'Sender balance was not deducted in confirmed status under new logic',
|
||
timestamp: new Date().toISOString()
|
||
});
|
||
} else if ((oldStatus === TRANSFER_STATUS.PENDING || oldStatus === TRANSFER_STATUS.REJECTED || oldStatus === TRANSFER_STATUS.CANCELLED || oldStatus === TRANSFER_STATUS.NOT_RECEIVED) && newStatus === TRANSFER_STATUS.CONFIRMED) {
|
||
// 从待处理/拒绝/取消/未收到变为确认:新逻辑下CONFIRMED状态不扣除发送方余额
|
||
logger.info('Status change to confirmed - no balance deduction needed', {
|
||
transferId,
|
||
userId: transfer.from_user_id,
|
||
amount: transfer.amount,
|
||
oldStatus,
|
||
newStatus,
|
||
note: 'Sender balance will be deducted when receiver confirms receipt'
|
||
});
|
||
} else if ((oldStatus === TRANSFER_STATUS.PENDING || oldStatus === TRANSFER_STATUS.REJECTED || oldStatus === TRANSFER_STATUS.CANCELLED || oldStatus === TRANSFER_STATUS.NOT_RECEIVED) && newStatus === TRANSFER_STATUS.RECEIVED) {
|
||
// 从待处理/拒绝/取消/未收到变为已收到:扣除发送方余额和积分
|
||
await connection.execute(
|
||
'UPDATE users SET balance = balance - ?, points = points + ? WHERE id = ?',
|
||
[transfer.amount, transfer.amount, transfer.from_user_id]
|
||
);
|
||
|
||
logger.info('Balance and points deducted due to status change to received', {
|
||
transferId,
|
||
userId: transfer.from_user_id,
|
||
amount: transfer.amount,
|
||
oldStatus,
|
||
newStatus
|
||
});
|
||
} else if (oldStatus === TRANSFER_STATUS.RECEIVED && (newStatus === TRANSFER_STATUS.REJECTED || newStatus === TRANSFER_STATUS.CANCELLED || newStatus === TRANSFER_STATUS.NOT_RECEIVED)) {
|
||
// 从已收到变为拒绝/取消/未收到:需要从接收方扣除余额和积分,并回滚发送方余额和积分
|
||
if (transfer.to_user_id) {
|
||
await connection.execute(
|
||
'UPDATE users SET balance = balance - ? WHERE id = ?',
|
||
[transfer.amount, transfer.to_user_id]
|
||
);
|
||
|
||
logger.info('Receiver balance and points deducted due to status change', {
|
||
transferId,
|
||
userId: transfer.to_user_id,
|
||
amount: transfer.amount,
|
||
oldStatus,
|
||
newStatus
|
||
});
|
||
}
|
||
|
||
await connection.execute(
|
||
'UPDATE users SET balance = balance + ?, points = points - ? WHERE id = ?',
|
||
[transfer.amount, transfer.amount, transfer.from_user_id]
|
||
);
|
||
|
||
logger.info('Sender balance and points restored due to status change', {
|
||
transferId,
|
||
userId: transfer.from_user_id,
|
||
amount: transfer.amount,
|
||
oldStatus,
|
||
newStatus
|
||
});
|
||
|
||
// 记录详细的余额变更审计日志
|
||
auditLogger.info('Balance adjustment - status change deduction', {
|
||
transferId: transferId,
|
||
fromUserId: transfer.from_user_id,
|
||
toUserId: transfer.to_user_id,
|
||
amount: transfer.amount,
|
||
operation: 'deduct_sender_balance',
|
||
oldStatus: oldStatus,
|
||
newStatus: newStatus,
|
||
adminId: adminId,
|
||
reason: reason,
|
||
timestamp: new Date().toISOString()
|
||
});
|
||
} else if (oldStatus === TRANSFER_STATUS.RECEIVED && newStatus === TRANSFER_STATUS.CONFIRMED) {
|
||
// 从已收到变为已确认:需要从接收方扣除余额和积分(因为confirmed状态下接收方不应该有余额)
|
||
if (transfer.to_user_id) {
|
||
await connection.execute(
|
||
'UPDATE users SET balance = balance - ? WHERE id = ?',
|
||
[transfer.amount, transfer.to_user_id]
|
||
);
|
||
|
||
logger.info('Receiver balance and points deducted due to status change from received to confirmed', {
|
||
transferId,
|
||
userId: transfer.to_user_id,
|
||
amount: transfer.amount,
|
||
oldStatus,
|
||
newStatus
|
||
});
|
||
}
|
||
} else if (oldStatus === TRANSFER_STATUS.CONFIRMED && newStatus === TRANSFER_STATUS.RECEIVED) {
|
||
// 从已确认变为已收到:新逻辑下需要扣除发送方余额和积分(因为CONFIRMED状态下未扣除)
|
||
await connection.execute(
|
||
'UPDATE users SET balance = balance - ?, points = points + ? WHERE id = ?',
|
||
[transfer.amount, transfer.amount, transfer.from_user_id]
|
||
);
|
||
|
||
logger.info('Status change from confirmed to received - sender balance and points deducted', {
|
||
transferId,
|
||
userId: transfer.from_user_id,
|
||
amount: transfer.amount,
|
||
oldStatus,
|
||
newStatus,
|
||
note: 'Sender balance and points deducted as per new logic'
|
||
});
|
||
}
|
||
}
|
||
|
||
// 如果变更为received状态,需要增加接收方余额和积分
|
||
if (adjust_balance && newStatus === TRANSFER_STATUS.RECEIVED && oldStatus !== TRANSFER_STATUS.RECEIVED && transfer.to_user_id) {
|
||
await connection.execute(
|
||
'UPDATE users SET balance = balance + ? WHERE id = ?',
|
||
[transfer.amount, transfer.to_user_id]
|
||
);
|
||
|
||
logger.info('Receiver balance and points increased due to status change', {
|
||
transferId,
|
||
userId: transfer.to_user_id,
|
||
amount: transfer.amount,
|
||
oldStatus,
|
||
newStatus
|
||
});
|
||
|
||
// 记录详细的余额变更审计日志
|
||
auditLogger.info('Balance adjustment - receiver balance and points increase', {
|
||
transferId: transferId,
|
||
fromUserId: transfer.from_user_id,
|
||
toUserId: transfer.to_user_id,
|
||
amount: transfer.amount,
|
||
operation: 'add_receiver_balance_and_points',
|
||
oldStatus: oldStatus,
|
||
newStatus: newStatus,
|
||
adminId: adminId,
|
||
reason: reason,
|
||
timestamp: new Date().toISOString()
|
||
});
|
||
}
|
||
|
||
await connection.commit();
|
||
|
||
// 记录审计日志
|
||
auditLogger.info('Transfer status force changed by admin', {
|
||
transferId,
|
||
adminId,
|
||
oldStatus,
|
||
newStatus,
|
||
reason,
|
||
adjust_balance,
|
||
fromUserId: transfer.from_user_id,
|
||
toUserId: transfer.to_user_id,
|
||
amount: transfer.amount
|
||
});
|
||
|
||
logger.info('Transfer status force changed successfully', {
|
||
transferId,
|
||
adminId,
|
||
oldStatus,
|
||
newStatus
|
||
});
|
||
|
||
return {success: true, oldStatus, newStatus};
|
||
} catch (error) {
|
||
await connection.rollback();
|
||
throw error;
|
||
}
|
||
} catch (error) {
|
||
logger.error('Failed to force change transfer status', {
|
||
error: error.message,
|
||
transferId,
|
||
adminId,
|
||
newStatus
|
||
});
|
||
throw error;
|
||
} finally {
|
||
if (connection) {
|
||
connection.release(); // 释放连接回连接池
|
||
}
|
||
}
|
||
} catch (error) {
|
||
logger.error('Failed to get database connection for force change transfer status', {
|
||
error: error.message,
|
||
transferId,
|
||
adminId,
|
||
newStatus
|
||
});
|
||
throw error;
|
||
}
|
||
}
|
||
|
||
// 生成批次ID
|
||
generateBatchId() {
|
||
return `T${Date.now()}${Math.random().toString(36).substr(2, 9)}`;
|
||
}
|
||
}
|
||
|
||
module.exports = new TransferService(); |