Files
edr-platform/apps/edr-payment-api/src/modules/outbox/payment-event.factory.ts
2026-06-11 15:25:24 +03:00

67 lines
2.0 KiB
TypeScript

import { randomUUID } from "node:crypto";
import {
PaymentEvent,
PaymentFailedEvent,
PaymentSucceededEvent,
} from "@edr/types";
import { PaymentIntent } from "../intents/entities/payment-intent.entity";
import { NotificationOutbox } from "./entities/notification-outbox.entity";
/**
* Build a ready-to-insert outbox row for a terminal intent. Pure (no DI) so the intents
* state machine can insert it inside its own DB transaction without a module cycle.
* The row id is generated here because the event envelope embeds it as `eventId`.
*/
export function buildOutboxRow(
intent: PaymentIntent,
terminal:
| { eventType: "payment.succeeded"; providerTxnId?: string; paidAt: Date }
| {
eventType: "payment.failed";
failureCode?: string;
failureMessage?: string;
},
): Partial<NotificationOutbox> {
const id = randomUUID();
const base = {
version: 1 as const,
eventId: id,
occurredAt: new Date().toISOString(),
service: intent.service,
intentId: intent.id,
referenceType: intent.referenceType,
referenceId: intent.referenceId,
merchantOrderId: intent.merchantOrderId,
provider: intent.provider,
amountMinor: intent.amountMinor,
currency: intent.currency,
};
const event: PaymentEvent =
terminal.eventType === "payment.succeeded"
? ({
...base,
eventType: "payment.succeeded",
providerTxnId: terminal.providerTxnId,
paidAt: terminal.paidAt.toISOString(),
} satisfies PaymentSucceededEvent)
: ({
...base,
eventType: "payment.failed",
failureCode: terminal.failureCode,
failureMessage: terminal.failureMessage,
} satisfies PaymentFailedEvent);
return {
id,
eventType: event.eventType,
service: intent.service,
intentId: intent.id,
referenceType: intent.referenceType,
referenceId: intent.referenceId,
payload: event,
status: "PENDING",
attempts: 0,
};
}