113 lines
3.8 KiB
JavaScript
113 lines
3.8 KiB
JavaScript
// /jobs/paymentReminderJob.js
|
|
|
|
const cron = require('node-cron');
|
|
const Payment = require('../components/payments/paymentModel');
|
|
const User = require('../components/users/userModel');
|
|
const Course = require('../components/courses/courseModel');
|
|
const eventEmitter = require('../events/eventEmitter');
|
|
const EVENT_NAMES = require('../constants/eventNames');
|
|
const logger = require('../utils/logger');
|
|
const { getPayableAmount, formatPrice } = require('../utils/paymentAmount');
|
|
const { notifyAction } = require('../utils/actionNotify');
|
|
const { sendPaymentReminderSms } = require('../utils/senders/smsMessages');
|
|
const { resolveNotifyFlags } = require('../utils/notifyResolver');
|
|
|
|
const formatDueDate = (date) => {
|
|
if (!date) return '';
|
|
const parsed = new Date(date);
|
|
if (Number.isNaN(parsed.getTime())) return '';
|
|
return parsed.toLocaleDateString('fa-IR');
|
|
};
|
|
|
|
const notifyPaymentReminder = async (payment, user) => {
|
|
const notify = await resolveNotifyFlags({}, 'paymentReminder');
|
|
if (!notify.sms && !notify.email && !notify.bot) return;
|
|
|
|
let courseName = '-';
|
|
if (payment.course) {
|
|
const course = await Course.findById(payment.course).select('title').lean();
|
|
if (course?.title) courseName = course.title;
|
|
}
|
|
|
|
const amountDue = getPayableAmount(payment) - (payment.paidAmount || 0);
|
|
const formattedAmountDue = formatPrice(amountDue);
|
|
|
|
await notifyAction({
|
|
actionKey: 'paymentReminder',
|
|
userId: user._id,
|
|
phoneNumber: user.phoneNumber,
|
|
email: user.email,
|
|
subject: 'یادآوری سررسید پرداخت',
|
|
body: `یادآوری: مبلغ ${formattedAmountDue} تومان تا ${formatDueDate(payment.dueDate)} سررسید دارد. کد صورتحساب: ${payment.uniqueCode || ''}`,
|
|
smsHandler: () => sendPaymentReminderSms(user.phoneNumber, {
|
|
fullName: user.name || '',
|
|
amount: amountDue,
|
|
dueDate: formatDueDate(payment.dueDate),
|
|
invoiceCode: payment.uniqueCode || '',
|
|
course: courseName
|
|
}, user._id)
|
|
});
|
|
};
|
|
|
|
const runPaymentReminderJob = async () => {
|
|
try {
|
|
const now = new Date();
|
|
const threeDaysFromNow = new Date();
|
|
threeDaysFromNow.setDate(now.getDate() + 3);
|
|
|
|
const upcomingPayments = await Payment.find({
|
|
status: { $in: ['pending', 'partial'] },
|
|
dueDate: { $gte: now, $lte: threeDaysFromNow }
|
|
}).populate({ path: 'user', select: 'name phoneNumber email' });
|
|
|
|
for (const payment of upcomingPayments) {
|
|
if (payment.user) {
|
|
eventEmitter.emit(EVENT_NAMES.PAYMENT_REMINDER_DUE, {
|
|
paymentId: payment._id,
|
|
userId: payment.user._id,
|
|
amountDue: getPayableAmount(payment) - (payment.paidAmount || 0),
|
|
dueDate: payment.dueDate
|
|
});
|
|
try {
|
|
await notifyPaymentReminder(payment, payment.user);
|
|
} catch (err) {
|
|
logger.error(`[PaymentReminderJob] Notification failed for payment ${payment._id}: ${err.message}`);
|
|
}
|
|
}
|
|
}
|
|
|
|
const overduePayments = await Payment.find({
|
|
status: { $in: ['pending', 'partial'] },
|
|
dueDate: { $lt: now }
|
|
});
|
|
|
|
for (const payment of overduePayments) {
|
|
const previousStatus = payment.status;
|
|
payment.status = 'overdue';
|
|
await payment.save();
|
|
if (payment.user) {
|
|
eventEmitter.emit(EVENT_NAMES.PAYMENT_STATUS_CHANGED, {
|
|
paymentId: payment._id,
|
|
userId: payment.user,
|
|
oldStatus: previousStatus,
|
|
newStatus: 'overdue'
|
|
});
|
|
}
|
|
}
|
|
} catch (error) {
|
|
logger.error(`[PaymentReminderJob ERROR]: ${error.message}`);
|
|
}
|
|
};
|
|
|
|
const startPaymentReminderJob = () => {
|
|
cron.schedule('0 0 * * *', async () => {
|
|
await runPaymentReminderJob();
|
|
});
|
|
logger.info('[PaymentReminderJob] Scheduled to run daily at midnight.');
|
|
};
|
|
|
|
module.exports = {
|
|
startPaymentReminderJob,
|
|
runPaymentReminderJob
|
|
};
|