// /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 } = 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); await notifyAction({ actionKey: 'paymentReminder', userId: user._id, phoneNumber: user.phoneNumber, email: user.email, subject: 'یادآوری سررسید پرداخت', body: `یادآوری: مبلغ ${amountDue} تومان تا ${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 };