Files
jurong_circle_black/services/transferService.js
2025-09-17 14:00:46 +08:00

1192 lines
52 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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或有活跃transfersmatching_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();