const User = require('../models/User'); const Withdrawal = require('../models/Withdrawal'); const { sequelize } = require('../config/database'); const { Op } = require('sequelize'); const logger = require('../config/logger'); /** * Admin Withdrawal Service * Handles withdrawal management operations for admin panel */ /** * Get paginated withdrawal list with search and filters * @param {Object} options - Query options * @returns {Promise} Withdrawal list with pagination */ const getWithdrawalList = async (options = {}) => { const { page = 1, limit = 20, status = '', search = '', sortBy = 'createdAt', sortOrder = 'DESC', } = options; const offset = (page - 1) * limit; // Build where clause const where = {}; // Filter by status if (status) { where.status = status; } // Search by withdrawal number or user info let userIds = []; if (search) { // Search for users matching the search term const users = await User.findAll({ where: { [Op.or]: [ { nickname: { [Op.like]: `%${search}%` } }, { realName: { [Op.like]: `%${search}%` } }, { phone: { [Op.like]: `%${search}%` } }, ], }, attributes: ['id'], }); userIds = users.map(u => u.id); // Also search by withdrawal number where[Op.or] = [ { withdrawalNo: { [Op.like]: `%${search}%` } }, ...(userIds.length > 0 ? [{ userId: { [Op.in]: userIds } }] : []), ]; } // Query withdrawals with user information const { count, rows: withdrawals } = await Withdrawal.findAndCountAll({ where, limit: parseInt(limit), offset, order: [[sortBy, sortOrder]], include: [ { model: User, as: 'user', attributes: ['id', 'nickname', 'realName', 'phone', 'whatsapp', 'wechatId'], }, ], }); // Format response const formattedWithdrawals = withdrawals.map(withdrawal => ({ id: withdrawal.id, withdrawalNo: withdrawal.withdrawalNo, amount: parseFloat(withdrawal.amount).toFixed(2), paymentMethod: withdrawal.paymentMethod, paymentDetails: withdrawal.paymentDetails, status: withdrawal.status, reviewedBy: withdrawal.reviewedBy, reviewedAt: withdrawal.reviewedAt, rejectionReason: withdrawal.rejectionReason, completedAt: withdrawal.completedAt, createdAt: withdrawal.createdAt, updatedAt: withdrawal.updatedAt, user: withdrawal.user ? { id: withdrawal.user.id, nickname: withdrawal.user.nickname, realName: withdrawal.user.realName, phone: withdrawal.user.phone, whatsapp: withdrawal.user.whatsapp, wechatId: withdrawal.user.wechatId, } : null, })); return { withdrawals: formattedWithdrawals, pagination: { total: count, page: parseInt(page), limit: parseInt(limit), totalPages: Math.ceil(count / limit), }, }; }; /** * Approve withdrawal request * @param {String} withdrawalId - Withdrawal ID * @param {String} adminId - Admin ID performing the action * @param {String} notes - Optional notes * @returns {Promise} Updated withdrawal */ const approveWithdrawal = async (withdrawalId, adminId, notes = '') => { const transaction = await sequelize.transaction(); try { // Get withdrawal with lock const withdrawal = await Withdrawal.findByPk(withdrawalId, { transaction, lock: transaction.LOCK.UPDATE, include: [ { model: User, as: 'user', attributes: ['id', 'balance', 'balancePeso'], }, ], }); if (!withdrawal) { throw new Error('Withdrawal not found'); } // Check if withdrawal is in waiting status if (withdrawal.status !== 'waiting') { throw new Error(`Cannot approve withdrawal with status: ${withdrawal.status}`); } // Update withdrawal status (balance was already deducted when user submitted the request) await withdrawal.update({ status: 'completed', reviewedBy: adminId, reviewedAt: new Date(), completedAt: new Date(), }, { transaction }); await transaction.commit(); logger.info(`Withdrawal ${withdrawal.withdrawalNo} approved by admin ${adminId}`); // Get current user balance for response const user = withdrawal.user; const currency = withdrawal.currency || 'CNY'; const currentBalance = currency === 'PHP' ? parseFloat(user?.balancePeso || 0).toFixed(2) : parseFloat(user?.balance || 0).toFixed(2); return { id: withdrawal.id, withdrawalNo: withdrawal.withdrawalNo, amount: parseFloat(withdrawal.amount).toFixed(2), currency: currency, status: withdrawal.status, reviewedBy: withdrawal.reviewedBy, reviewedAt: withdrawal.reviewedAt, completedAt: withdrawal.completedAt, userBalance: currentBalance, }; } catch (error) { await transaction.rollback(); logger.error('Approve withdrawal error:', error); throw error; } }; /** * Reject withdrawal request * @param {String} withdrawalId - Withdrawal ID * @param {String} adminId - Admin ID performing the action * @param {String} reason - Rejection reason (required) * @returns {Promise} Updated withdrawal */ const rejectWithdrawal = async (withdrawalId, adminId, reason) => { if (!reason || reason.trim() === '') { throw new Error('Rejection reason is required'); } const transaction = await sequelize.transaction(); try { // Get withdrawal with lock const withdrawal = await Withdrawal.findByPk(withdrawalId, { transaction, lock: transaction.LOCK.UPDATE, }); if (!withdrawal) { throw new Error('Withdrawal not found'); } // Check if withdrawal is in waiting status if (withdrawal.status !== 'waiting') { throw new Error(`Cannot reject withdrawal with status: ${withdrawal.status}`); } // Get user with lock to refund the balance const user = await User.findByPk(withdrawal.userId, { transaction, lock: transaction.LOCK.UPDATE, }); if (!user) { throw new Error('User not found'); } // Refund the withdrawal amount to user balance based on currency const withdrawalAmount = parseFloat(withdrawal.amount); const currency = withdrawal.currency || 'CNY'; if (currency === 'PHP') { user.balancePeso = parseFloat(user.balancePeso || 0) + withdrawalAmount; } else { user.balance = parseFloat(user.balance || 0) + withdrawalAmount; } await user.save({ transaction }); // Update withdrawal status await withdrawal.update({ status: 'rejected', reviewedBy: adminId, reviewedAt: new Date(), rejectionReason: reason, }, { transaction }); await transaction.commit(); logger.info(`Withdrawal ${withdrawal.withdrawalNo} rejected by admin ${adminId}, amount refunded to user`); return { id: withdrawal.id, withdrawalNo: withdrawal.withdrawalNo, amount: parseFloat(withdrawal.amount).toFixed(2), currency: currency, status: withdrawal.status, reviewedBy: withdrawal.reviewedBy, reviewedAt: withdrawal.reviewedAt, rejectionReason: withdrawal.rejectionReason, }; } catch (error) { await transaction.rollback(); logger.error('Reject withdrawal error:', error); throw error; } }; /** * Get withdrawal details * @param {String} withdrawalId - Withdrawal ID * @returns {Promise} Withdrawal details with user info */ const getWithdrawalDetails = async (withdrawalId) => { const withdrawal = await Withdrawal.findByPk(withdrawalId, { include: [ { model: User, as: 'user', attributes: ['id', 'nickname', 'realName', 'phone', 'whatsapp', 'wechatId', 'balance'], }, ], }); if (!withdrawal) { throw new Error('Withdrawal not found'); } return { id: withdrawal.id, withdrawalNo: withdrawal.withdrawalNo, amount: parseFloat(withdrawal.amount).toFixed(2), paymentMethod: withdrawal.paymentMethod, paymentDetails: withdrawal.paymentDetails, status: withdrawal.status, reviewedBy: withdrawal.reviewedBy, reviewedAt: withdrawal.reviewedAt, rejectionReason: withdrawal.rejectionReason, completedAt: withdrawal.completedAt, createdAt: withdrawal.createdAt, updatedAt: withdrawal.updatedAt, user: withdrawal.user ? { id: withdrawal.user.id, nickname: withdrawal.user.nickname, realName: withdrawal.user.realName, phone: withdrawal.user.phone, whatsapp: withdrawal.user.whatsapp, wechatId: withdrawal.user.wechatId, balance: parseFloat(withdrawal.user.balance).toFixed(2), } : null, }; }; module.exports = { getWithdrawalList, approveWithdrawal, rejectWithdrawal, getWithdrawalDetails, };