crone for failed payment orders

This commit is contained in:
2025-12-18 18:55:10 +03:30
parent 60c0bfb8d2
commit 0362540875
8 changed files with 138 additions and 25 deletions
+85 -2
View File
@@ -1,6 +1,89 @@
import { Injectable } from "@nestjs/common";
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);
}
}
}
+2 -4
View File
@@ -12,7 +12,6 @@ import {
type EventArgs,
} from '@mikro-orm/core';
import { BaseEntity } from '../../../common/entities/base.entity';
import { PaymentStatusEnum } from '../../payments/interface/payment';
import { OrderCouponDetail, OrderStatus } from '../interface/order.interface';
import { User } from '../../users/entities/user.entity';
import { Restaurant } from '../../restaurants/entities/restaurant.entity';
@@ -27,7 +26,6 @@ import { OrderUserAddress, OrderCarAddress } from '../interface/order.interface'
@Index({ properties: ['user', 'status'] })
@Index({ properties: ['restaurant', 'orderNumber'] })
@Index({ properties: ['status'] })
@Index({ properties: ['paymentStatus'] })
export class Order extends BaseEntity {
@ManyToOne(() => User)
user!: User;
@@ -92,8 +90,8 @@ export class Order extends BaseEntity {
@Enum(() => OrderStatus)
status!: OrderStatus;
@Enum(() => PaymentStatusEnum)
paymentStatus!: PaymentStatusEnum;
// @Enum(() => PaymentStatusEnum)
// paymentStatus!: PaymentStatusEnum;
@BeforeCreate()
async generateOrderNumber(args: EventArgs<Order>) {
@@ -19,7 +19,6 @@ export interface OrderCarAddress {
}
export enum OrderStatus {
NEW = 'new',
PENDING_PAYMENT = 'pendingPayment',
PAID = 'paid',
CONFIRMED = 'confirmed',
+4 -3
View File
@@ -16,10 +16,11 @@ import { PaymentsModule } from '../payments/payments.module';
import { JwtModule } from '@nestjs/jwt';
import { OrderRepository } from './repositories/order.repository';
import { OrderListeners } from './listeners/order.listeners';
import { OrdersCrone } from './crone/order.crone';
import { AdminModule } from '../admin/admin.module';
import { NotificationsModule } from '../notifications/notifications.module';
import { InventoryModule } from '../inventory/inventory.module';
@Module({
imports: [
MikroOrmModule.forFeature([Order, OrderItem, User, Restaurant, Food, UserAddress, PaymentMethod]),
@@ -33,7 +34,7 @@ import { InventoryModule } from '../inventory/inventory.module';
InventoryModule
],
controllers: [OrdersController],
providers: [OrdersService, OrderRepository, OrderListeners],
providers: [OrdersService, OrderRepository, OrderListeners, OrdersCrone],
exports: [OrderRepository],
})
export class OrdersModule {}
export class OrdersModule { }