Files
edr-platform/apps/edr-payment-api/src/modules/outbox/test-events.controller.ts
2026-06-30 15:51:47 +03:00

89 lines
3.2 KiB
TypeScript

import { randomUUID } from "node:crypto";
import { Body, Controller, Inject, Logger, Post } from "@nestjs/common";
import { ApiOperation, ApiTags } from "@nestjs/swagger";
import {
PaymentEvent,
PaymentReferenceType,
PaymentService,
ProviderMethod,
paymentRoutingKey,
} from "@edr/types";
import {
PAYMENT_EVENT_PUBLISHER,
PaymentEventPublisher,
} from "./publisher/payment-event-publisher";
import { TestPaymentEventDto } from "./dto/test-payment-event.dto";
/**
* DEV-ONLY test harness. Publishes a synthetic payment event through the real
* PaymentEventPublisher (RabbitMQ in dev), so the passenger/freight consumer receives it
* exactly as in production — without creating an intent or going through a booking + provider
* flow. Registered only when NODE_ENV !== "production" (see OutboxModule); never reachable in prod.
*
* Quick check (no body): POST /test/payment-event -> publishes payment.passenger.succeeded.
* Real side effects: pass a real bookingId as `referenceId`.
*/
@ApiTags("Dev test (non-production)")
@Controller("test")
export class TestEventsController {
private readonly logger = new Logger(TestEventsController.name);
constructor(
@Inject(PAYMENT_EVENT_PUBLISHER)
private readonly publisher: PaymentEventPublisher,
) {}
@Post("payment-event")
@ApiOperation({
summary:
"DEV ONLY: publish a synthetic payment event to the broker (passenger/freight consumes it)",
description:
"Bypasses intents/booking. Empty body publishes a payment.succeeded for PASSENGER. " +
"Set referenceId to a real bookingId to trigger the consumer's seat/ticket side effects.",
})
async publishTestEvent(
@Body() dto: TestPaymentEventDto,
): Promise<{ published: true; routingKey: string; event: PaymentEvent }> {
const eventType = dto.eventType ?? "payment.succeeded";
const service = dto.service ?? PaymentService.PASSENGER;
const now = new Date().toISOString();
const base = {
version: 1 as const,
eventId: randomUUID(),
occurredAt: now,
service,
intentId: dto.intentId ?? randomUUID(),
referenceType: dto.referenceType ?? PaymentReferenceType.BOOKING,
referenceId: dto.referenceId ?? randomUUID(),
merchantOrderId: dto.merchantOrderId ?? `test-${randomUUID().slice(0, 8)}`,
provider: dto.provider ?? ProviderMethod.WAAFI,
amountMinor: dto.amountMinor ?? 10_000,
currency: dto.currency ?? "ETB",
};
const event: PaymentEvent =
eventType === "payment.failed"
? {
...base,
eventType: "payment.failed",
failureCode: dto.failureCode ?? "TEST_DECLINED",
failureMessage: dto.failureMessage ?? "Synthetic test failure",
}
: {
...base,
eventType: "payment.succeeded",
providerTxnId: dto.providerTxnId ?? `TEST-${randomUUID().slice(0, 8)}`,
paidAt: now,
};
await this.publisher.publish(event);
const routingKey = paymentRoutingKey(event.service, event.eventType);
this.logger.log(
`published TEST ${event.eventType} (${event.eventId}) ref=${event.referenceId} -> ${routingKey}`,
);
return { published: true, routingKey, event };
}
}