"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.processWebhookOnce = processWebhookOnce; exports.getWebhookEventId = getWebhookEventId; const crypto_1 = __importDefault(require("crypto")); function payloadHash(rawBody) { return crypto_1.default.createHash('sha256').update(rawBody).digest('hex'); } function getWebhookEventModel() { try { // Lazy load keeps isolated unit tests from requiring a generated Prisma client // before `prisma generate` has run in the local sandbox. // eslint-disable-next-line @typescript-eslint/no-var-requires const { prisma } = require('../lib/prisma'); return prisma.webhookEvent; } catch { return undefined; } } async function findExisting(model, provider, providerEventId) { return model.findUnique({ where: { provider_providerEventId: { provider, providerEventId } }, }); } async function mark(model, id, status, errorMessage) { await model.update({ where: { id }, data: { status, processedAt: status === 'PROCESSED' ? new Date() : undefined, errorMessage: errorMessage?.slice(0, 1000), }, }); } async function processWebhookOnce({ provider, providerEventId, eventType, rawBody, handle, }) { const model = getWebhookEventModel(); if (!model) { const result = await handle(); return { duplicate: false, result }; } const hash = payloadHash(rawBody); let eventRecord; try { eventRecord = await model.create({ data: { provider, providerEventId, eventType, status: 'PROCESSING', payloadHash: hash, }, }); } catch (err) { const existing = await findExisting(model, provider, providerEventId); if (existing?.status === 'PROCESSED') return { duplicate: true }; if (existing?.payloadHash && existing.payloadHash !== hash) { throw new Error('Webhook event ID replayed with a different payload hash'); } if (existing?.status === 'PROCESSING') return { duplicate: true }; eventRecord = existing; } try { const result = await handle(); if (eventRecord?.id) await mark(model, eventRecord.id, 'PROCESSED'); return { duplicate: false, result }; } catch (err) { if (eventRecord?.id) await mark(model, eventRecord.id, 'FAILED', err?.message); throw err; } } function getWebhookEventId(provider, payload) { if (provider === 'paypal') { return String(payload.id ?? payload.event_id ?? `${payload.event_type ?? 'unknown'}:${payload.resource?.id ?? 'missing'}`); } return String(payload.event_id ?? payload.id ?? payload.transaction_id ?? 'missing'); } //# sourceMappingURL=webhookIdempotency.js.map