import { Injectable, Logger } from '@nestjs/common'; import { Cron } from '@nestjs/schedule'; import { EntityManager } from '@mikro-orm/postgresql'; import { Payment } from '../../payments/entities/payment.entity'; import { PaymentMethodEnum, PaymentStatusEnum } from '../../payments/interface/payment'; import { InventoryService } from '../../inventory/inventory.service'; import { OrderStatus } from '../interface/order.interface'; @Injectable() export class OrdersCrone { private readonly logger = new Logger(OrdersCrone.name); constructor( private readonly em: EntityManager, private readonly inventoryService: InventoryService, ) {} // run every minute and fail pending online payments older than 15 minutes @Cron('*/1 * * * *', { name: 'failOldOnlinePayments', timeZone: 'UTC', }) async handleCron() { try { const cutoff = new Date(Date.now() - 15 * 60 * 1000); this.logger.debug('Searching for pending online payments older than 15 minutes'); const payments = await this.em.find( Payment, { method: PaymentMethodEnum.Online, status: PaymentStatusEnum.Pending, createdAt: { $lte: cutoff }, }, { populate: ['order', 'order.items', 'order.items.food'] }, ); if (!payments || payments.length === 0) { return; } this.logger.log(`Found ${payments.length} stale pending online payments`); for (const p of payments) { try { await this.em.transactional(async em => { // reload inside transaction to avoid concurrency issues const payment = await em.findOne( Payment, { id: p.id }, { populate: ['order', 'order.items', 'order.items.food'] }, ); if (!payment) return; if (payment.status !== PaymentStatusEnum.Pending) return; payment.status = PaymentStatusEnum.Failed; payment.failedAt = new Date(); if (payment.order) { payment.order.status = OrderStatus.FAILED; // prepare restore payload const items = (payment.order as any).items || []; const restorePayload = { items: items.map((it: any) => ({ foodId: it.food.id, quantity: it.quantity })), }; if (restorePayload.items.length > 0) { await this.inventoryService.restoreToInventory(em, restorePayload); } } em.persist(payment); if (payment.order) em.persist(payment.order); await em.flush(); this.logger.log( `Marked payment ${payment.id} and order ${payment.order?.id} as failed and restored inventory`, ); }); } catch (err) { this.logger.error(`Error processing payment ${p.id}: ${err.message}`, err.stack); } } } catch (err) { this.logger.error(`OrdersCrone failed: ${err.message}`, err.stack); } } }