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 { 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, }; }