revise ads and alerts

This commit is contained in:
2026-08-03 22:48:06 +08:00
parent 32c72f0b39
commit 573cfb98af
16 changed files with 414 additions and 17 deletions
+3
View File
@@ -18,6 +18,7 @@
* - expireUserTiers (cron/jobs/expire_user_tiers.cron.js)
* - taskDueSoon (cron/jobs/task_due_soon.cron.js)
* - expireAdvertisements (cron/jobs/expire_advertisements.cron.js) — plain
* - failStalePayments (cron/jobs/fail_stale_payments.cron.js) — plain
*
* Author: Kenneth Obsequio (@lash0000)
* Date Created: Jun. 17, 2026
@@ -28,6 +29,7 @@ const issueCertificates = require('./jobs/issue_certificates.cron');
const expireUserTiers = require('./jobs/expire_user_tiers.cron');
const taskDueSoon = require('./jobs/task_due_soon.cron');
const expireAdvertisements = require('./jobs/expire_advertisements.cron');
const failStalePayments = require('./jobs/fail_stale_payments.cron');
const { startSettingsBackedJobs } = require('./cronRegistry.util');
// ─── Registry — add future client-side cron jobs here ────────────────────────
@@ -41,6 +43,7 @@ const settingsBackedJobs = [
// Plain hardcoded-schedule jobs (not tied to any notification setting).
const plainJobs = [
expireAdvertisements,
failStalePayments,
];
// ─── Boot all registered client-side jobs ─────────────────────────────────────
+87
View File
@@ -0,0 +1,87 @@
/***********************************************************************************************************************************************************************
* File Name : fail_stale_payments.cron.js
* Type : Cron Job
* Description : Marks abandoned checkout attempts as 'failed' instead of
* leaving them stuck on 'pending' forever.
*
* A payment/purchase row is created as 'pending' the moment
* PayPal's create-order call succeeds (createOrder / createCourseOrder),
* before the buyer ever reaches PayPal's approval page. If PayPal's
* own hosted checkout then fails to load ("Things don't appear to
* be working at the moment") or the buyer just abandons the tab,
* the browser never gets redirected back to our return_url/cancel_url —
* so captureOrder/cancelOrder is never called, and the row sits as
* 'pending' indefinitely even though no money ever moved.
*
* This job sweeps 'pending' rows older than STALE_MINUTES and
* marks them 'failed', so payment history correctly reflects
* that nothing was charged, instead of silently doing nothing.
*
* Schedule : Every 10 minutes ("*\/10 * * * *"). Registered by cron/client.cron.js.
*
* Author: Kenneth Obsequio (@lash0000)
* Date Created: Aug. 3, 2026
***********************************************************************************************************************************************************************/
'use strict';
const { Op } = require('sequelize');
const mdl_Payments = require('../../models/tiers/payments.mdl');
const mdl_CoursePurchase = require('../../models/courses/course_purchases.mdl');
const STALE_MINUTES = 30;
async function run() {
const cutoff = new Date(Date.now() - STALE_MINUTES * 60_000);
try {
const stalePayments = await mdl_Payments.findAll({
where: { status: 'pending', createdAt: { [Op.lt]: cutoff } },
});
for (const payment of stalePayments) {
await payment.update({
status: 'failed',
provider_payload: {
...payment.provider_payload,
failed_reason: 'abandoned_checkout',
marked_failed_at: new Date().toISOString(),
},
});
}
if (stalePayments.length) {
console.log(`[CRON][FAIL STALE PAYMENTS] Marked ${stalePayments.length} abandoned tier payment(s) as failed.`);
}
} catch (err) {
console.error('[CRON][FAIL STALE PAYMENTS] Failed sweeping payments:', err);
}
try {
const stalePurchases = await mdl_CoursePurchase.findAll({
where: { status: 'pending', createdAt: { [Op.lt]: cutoff } },
});
for (const purchase of stalePurchases) {
await purchase.update({
status: 'failed',
provider_payload: {
...purchase.provider_payload,
failed_reason: 'abandoned_checkout',
marked_failed_at: new Date().toISOString(),
},
});
}
if (stalePurchases.length) {
console.log(`[CRON][FAIL STALE PAYMENTS] Marked ${stalePurchases.length} abandoned course purchase(s) as failed.`);
}
} catch (err) {
console.error('[CRON][FAIL STALE PAYMENTS] Failed sweeping course purchases:', err);
}
}
module.exports = {
name: 'failStalePayments',
schedule: '*/10 * * * *',
run,
};