mirror of
https://github.com/rgrgogu/new_starr.git
synced 2026-09-27 00:12:54 +08:00
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
88 lines
3.6 KiB
JavaScript
88 lines
3.6 KiB
JavaScript
/***********************************************************************************************************************************************************************
|
|
* 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,
|
|
};
|