90 lines
3.0 KiB
TypeScript
90 lines
3.0 KiB
TypeScript
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);
|
|
}
|
|
}
|
|
}
|