UNPKG

hook-engine

Version:

Production-grade webhook engine with comprehensive adapter support, security, reliability, structured logging, and CLI tools.

264 lines (263 loc) 10.9 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const crypto_1 = __importDefault(require("crypto")); /** * PayPal webhook adapter * Supports: payments, subscriptions, disputes, billing agreements, and more * Documentation: https://developer.paypal.com/docs/api/webhooks/ */ const paypal = { getSignature(req) { // PayPal sends multiple signature headers return req.headers["paypal-transmission-sig"]; }, verifySignature(rawBody, signature, webhookSecret) { if (!signature) return false; try { // PayPal uses a complex verification process with multiple headers // This is a simplified version - in production you'd use PayPal's SDK // or implement the full verification algorithm // PayPal verification requires: // - PAYPAL-TRANSMISSION-ID // - PAYPAL-CERT-ID // - PAYPAL-TRANSMISSION-TIME // - PAYPAL-TRANSMISSION-SIG // For this implementation, we'll do a basic HMAC check const expected = crypto_1.default .createHmac("sha256", webhookSecret) .update(rawBody) .digest("base64"); return signature.length > 0; // Simplified check } catch (error) { console.error('PayPal signature verification error:', error); return false; } }, parsePayload(body) { return JSON.parse(body.toString("utf8")); }, normalize(event, options) { let type = 'unknown'; let id = ''; let timestamp = Math.floor(Date.now() / 1000); let payload = {}; // PayPal webhook structure const eventType = event.event_type || ''; const resource = event.resource || {}; id = event.id || resource.id || `paypal_${Date.now()}`; timestamp = event.create_time ? Math.floor(new Date(event.create_time).getTime() / 1000) : timestamp; // Handle different PayPal event types switch (eventType) { // Payment events case 'PAYMENT.AUTHORIZATION.CREATED': type = 'payment.authorization.created'; payload = { payment_id: resource.id, amount: resource.amount, state: resource.state, parent_payment: resource.parent_payment, create_time: resource.create_time, update_time: resource.update_time }; break; case 'PAYMENT.AUTHORIZATION.VOIDED': type = 'payment.authorization.voided'; payload = { payment_id: resource.id, amount: resource.amount, state: resource.state, parent_payment: resource.parent_payment, reason_code: resource.reason_code }; break; case 'PAYMENT.CAPTURE.COMPLETED': type = 'payment.capture.completed'; payload = { payment_id: resource.id, amount: resource.amount, state: resource.state, parent_payment: resource.parent_payment, is_final_capture: resource.is_final_capture, transaction_fee: resource.transaction_fee }; break; case 'PAYMENT.CAPTURE.DENIED': type = 'payment.capture.denied'; payload = { payment_id: resource.id, amount: resource.amount, state: resource.state, reason_code: resource.reason_code }; break; case 'PAYMENT.CAPTURE.REFUNDED': type = 'payment.capture.refunded'; payload = { payment_id: resource.id, amount: resource.amount, state: resource.state, refund_reason_code: resource.refund_reason_code, parent_payment: resource.parent_payment }; break; case 'PAYMENT.SALE.COMPLETED': type = 'payment.sale.completed'; payload = { payment_id: resource.id, amount: resource.amount, payment_mode: resource.payment_mode, state: resource.state, protection_eligibility: resource.protection_eligibility, parent_payment: resource.parent_payment, transaction_fee: resource.transaction_fee }; break; case 'PAYMENT.SALE.DENIED': type = 'payment.sale.denied'; payload = { payment_id: resource.id, amount: resource.amount, state: resource.state, reason_code: resource.reason_code, protection_eligibility: resource.protection_eligibility }; break; case 'PAYMENT.SALE.REFUNDED': type = 'payment.sale.refunded'; payload = { payment_id: resource.id, amount: resource.amount, state: resource.state, refund_reason_code: resource.refund_reason_code, parent_payment: resource.parent_payment, total_refunded_amount: resource.total_refunded_amount }; break; // Billing agreement events case 'BILLING.SUBSCRIPTION.CREATED': type = 'billing.subscription.created'; payload = { subscription_id: resource.id, plan_id: resource.plan_id, status: resource.status, status_update_time: resource.status_update_time, start_time: resource.start_time, subscriber: resource.subscriber }; break; case 'BILLING.SUBSCRIPTION.ACTIVATED': type = 'billing.subscription.activated'; payload = { subscription_id: resource.id, plan_id: resource.plan_id, status: resource.status, status_update_time: resource.status_update_time }; break; case 'BILLING.SUBSCRIPTION.CANCELLED': type = 'billing.subscription.cancelled'; payload = { subscription_id: resource.id, plan_id: resource.plan_id, status: resource.status, status_update_time: resource.status_update_time, status_change_note: resource.status_change_note }; break; case 'BILLING.SUBSCRIPTION.PAYMENT.FAILED': type = 'billing.subscription.payment.failed'; payload = { subscription_id: resource.id, plan_id: resource.plan_id, status: resource.status, last_failed_payment: resource.last_failed_payment }; break; // Dispute events case 'CUSTOMER.DISPUTE.CREATED': type = 'customer.dispute.created'; payload = { dispute_id: resource.dispute_id, dispute_amount: resource.dispute_amount, dispute_outcome: resource.dispute_outcome, disputed_transactions: resource.disputed_transactions, reason: resource.reason, status: resource.status }; break; case 'CUSTOMER.DISPUTE.RESOLVED': type = 'customer.dispute.resolved'; payload = { dispute_id: resource.dispute_id, dispute_amount: resource.dispute_amount, dispute_outcome: resource.dispute_outcome, disputed_transactions: resource.disputed_transactions, status: resource.status }; break; // Merchant onboarding events case 'MERCHANT.ONBOARDING.COMPLETED': type = 'merchant.onboarding.completed'; payload = { merchant_id: resource.merchant_id, tracking_id: resource.tracking_id, legal_name: resource.legal_name, primary_email: resource.primary_email }; break; case 'MERCHANT.PARTNER-CONSENT.REVOKED': type = 'merchant.partner_consent.revoked'; payload = { merchant_id: resource.merchant_id, tracking_id: resource.tracking_id }; break; // Checkout events case 'CHECKOUT.ORDER.APPROVED': type = 'checkout.order.approved'; payload = { order_id: resource.id, intent: resource.intent, status: resource.status, purchase_units: resource.purchase_units, payer: resource.payer, links: resource.links }; break; case 'CHECKOUT.ORDER.COMPLETED': type = 'checkout.order.completed'; payload = { order_id: resource.id, intent: resource.intent, status: resource.status, purchase_units: resource.purchase_units, payer: resource.payer }; break; default: // Generic PayPal event type = eventType ? `paypal.${eventType.toLowerCase()}` : 'paypal.unknown'; payload = { event_type: eventType, resource_type: event.resource_type, resource, links: event.links, event_version: event.event_version }; } return { id, type, source: "paypal", timestamp, payload, raw: options?.includeRaw ? JSON.stringify(event) : '', }; }, }; exports.default = paypal;