Merge pull request #148 from Tria-plc/feat/payment-microservice

refactor: ( payment ) use rabbitmq for webhooks event
This commit is contained in:
Eyob T.
2026-06-14 15:24:25 +03:00
committed by GitHub
17 changed files with 396 additions and 10 deletions

View File

@@ -0,0 +1,47 @@
import { Injectable, Logger } from '@nestjs/common';
import { Nack, RabbitSubscribe } from '@golevelup/nestjs-rabbitmq';
import {
PAYMENT_EVENTS_DLX,
PAYMENT_EVENTS_EXCHANGE,
PAYMENT_QUEUES,
PaymentEvent,
PaymentService,
paymentServiceBindingPattern,
} from '@edr/types';
import { PaymentEventDto } from './internal-payments.dto';
import { PaymentsService } from './payments.service';
const PASSENGER_QUEUE = PAYMENT_QUEUES[PaymentService.PASSENGER];
@Injectable()
export class PaymentEventsConsumer {
private readonly logger = new Logger(PaymentEventsConsumer.name);
constructor(private readonly paymentsService: PaymentsService) {}
@RabbitSubscribe({
exchange: PAYMENT_EVENTS_EXCHANGE,
routingKey: paymentServiceBindingPattern(PaymentService.PASSENGER), // payment.passenger.*
queue: PASSENGER_QUEUE.main,
queueOptions: {
durable: true,
deadLetterExchange: PAYMENT_EVENTS_DLX,
},
})
async handle(event: PaymentEvent): Promise<Nack | void> {
try {
const result = await this.paymentsService.handlePaymentEvent(
event as unknown as PaymentEventDto,
);
this.logger.log(
`processed ${event.eventType} (${event.eventId}) ref=${event.referenceId}: ${JSON.stringify(result)}`,
);
} catch (err) {
const message = err instanceof Error ? err.message : String(err);
this.logger.error(
`DEAD-LETTERING ${event.eventType} (${event.eventId}) ref=${event.referenceId}: ${message}`,
);
return new Nack(false);
}
}
}

View File

@@ -1,25 +1,61 @@
import { Module } from "@nestjs/common";
import { HttpModule } from "@nestjs/axios";
import { ConfigService } from "@nestjs/config";
import { RabbitMQModule } from "@golevelup/nestjs-rabbitmq";
import {
PAYMENT_EVENTS_DLX,
PAYMENT_EVENTS_EXCHANGE,
PAYMENT_QUEUES,
PaymentService,
paymentServiceBindingPattern,
} from "@edr/types";
import { PaymentsController } from "./payments.controller";
import { PaymentsService } from "./payments.service";
import { InternalPaymentsController } from "./internal-payments.controller";
import { PaymentClientService } from "./payment-client.service";
import { PaymentEventsConsumer } from "./payment-events.consumer";
import { ServiceAuthGuard } from "../../common/guards/service-auth.guard";
import { SeatsModule } from "../seats/seats.module";
import { TicketsModule } from "../tickets/tickets.module";
/**
* Post-cutover (docs/payment-service phase 6): provider gateways and webhook handlers live in
* apps/edr-payment-api. This module keeps domain validation, the WALLET flow, the payment
* client, and the idempotent mark-paid consumer.
*/
const PASSENGER_QUEUE = PAYMENT_QUEUES[PaymentService.PASSENGER];
@Module({
imports: [
SeatsModule,
TicketsModule,
HttpModule.register({ timeout: 10_000 }),
RabbitMQModule.forRootAsync({
inject: [ConfigService],
useFactory: (config: ConfigService) => ({
uri: config.get<string>("rabbitmq.url") as string,
exchanges: [
{
name: PAYMENT_EVENTS_EXCHANGE,
type: "topic",
options: { durable: true },
},
{ name: PAYMENT_EVENTS_DLX, type: "topic", options: { durable: true } },
],
queues: [
{
name: PASSENGER_QUEUE.dlq,
exchange: PAYMENT_EVENTS_DLX,
routingKey: paymentServiceBindingPattern(PaymentService.PASSENGER),
options: { durable: true },
},
],
prefetchCount: config.get<number>("rabbitmq.prefetch") ?? 10,
connectionInitOptions: { wait: false },
}),
}),
],
controllers: [PaymentsController, InternalPaymentsController],
providers: [PaymentsService, PaymentClientService, ServiceAuthGuard],
providers: [
PaymentsService,
PaymentClientService,
PaymentEventsConsumer,
ServiceAuthGuard,
],
})
export class PaymentsModule {}