Files
jurong_circle_black/services/transferService.js

1115 lines
49 KiB
JavaScript
Raw Normal View History

2025-08-26 10:06:23 +08:00
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);
2025-08-28 09:14:56 +08:00
console.log(transferStatuses,'transferStatuses');
2025-08-26 10:06:23 +08:00
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 = {}) {
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 = [];
// 构建查询条件
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
);
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 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 updateUserBalance(transfer) {
const {from_user_id, to_user_id, amount, transfer_type} = transfer;
const db = getDB();
// 扣除发送方余额(使用行锁防止并发问题)
if (transfer_type === TRANSFER_TYPES.USER_TO_USER || transfer_type === TRANSFER_TYPES.USER_TO_SYSTEM) {
if (from_user_id) {
await db.execute(
'UPDATE users SET balance = balance - ? WHERE id = ?',
[amount, from_user_id]
);
}
}
// 增加接收方余额
if (transfer_type === TRANSFER_TYPES.SYSTEM_TO_USER || transfer_type === TRANSFER_TYPES.USER_TO_USER) {
await db.execute(
'UPDATE users SET balance = balance + ? WHERE id = ?',
[amount, to_user_id]
);
}
}
// 使用指定连接更新用户余额(用于事务处理)
async updateUserBalanceWithConnection(transfer, connection) {
const {from_user_id, to_user_id, amount, transfer_type} = transfer;
// 扣除发送方余额(使用行锁防止并发问题)
if (transfer_type === TRANSFER_TYPES.USER_TO_USER || transfer_type === TRANSFER_TYPES.USER_TO_SYSTEM) {
if (from_user_id) {
await connection.execute(
'UPDATE users SET balance = balance - ? WHERE id = ?',
[amount, from_user_id]
);
}
}
// 增加接收方余额
if (transfer_type === TRANSFER_TYPES.SYSTEM_TO_USER || transfer_type === TRANSFER_TYPES.USER_TO_USER) {
await connection.execute(
'UPDATE users SET balance = balance + ? WHERE id = ?',
[amount, to_user_id]
);
}
}
// 用户确认收到转账
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);
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 = '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();