import { createHmac, timingSafeEqual } from "node:crypto"; const PAYMENT_GATEWAY_URL = process.env.EGBE_PAYMENT_GATEWAY_URL ?? ""; const GATEWAY_TOKEN = process.env.EGBE_GATEWAYS_TOKEN ?? ""; const WEBHOOK_SECRET = process.env.EGBE_PAYMENT_GATEWAY_WEBHOOK_SECRET ?? ""; const WEBHOOK_HEADER = process.env.EGBE_PAYMENT_GATEWAY_WEBHOOK_HEADER ?? "x-egbe-payment-signature"; export function isBillingConfigured(): boolean { return Boolean( PAYMENT_GATEWAY_URL && GATEWAY_TOKEN && process.env.PRO_STRIPE_PRICE_ID ); } export type CheckoutSessionArgs = { userId: string; customerEmail?: string; appSlug: string; baseUrl: string; }; export async function createCheckoutSession(args: CheckoutSessionArgs) { const priceId = process.env.PRO_STRIPE_PRICE_ID; if (!priceId) { throw new Error("PRO_STRIPE_PRICE_ID not set"); } if (!(PAYMENT_GATEWAY_URL && GATEWAY_TOKEN)) { throw new Error("Payment gateway not configured"); } const body = { price: priceId, mode: "subscription", quantity: 1, successUrl: `${args.baseUrl}/pricing?upgraded=1`, cancelUrl: `${args.baseUrl}/pricing?canceled=1`, customerEmail: args.customerEmail, appSlug: args.appSlug, webhookPath: "/api/billing/webhook", metadata: { userId: args.userId }, }; const response = await fetch(`${PAYMENT_GATEWAY_URL}/v1/checkout-sessions`, { method: "POST", headers: { Authorization: `Bearer ${GATEWAY_TOKEN}`, "Content-Type": "application/json", }, body: JSON.stringify(body), }); if (!response.ok) { const text = await response.text(); throw new Error(`Payment gateway error ${response.status}: ${text}`); } return response.json() as Promise<{ id: string; url: string }>; } export const webhookHeaderName = WEBHOOK_HEADER; export function verifyWebhookSignature( rawBody: string, signatureHeader: string | null, toleranceSeconds = 300 ): boolean { if (!(WEBHOOK_SECRET && signatureHeader)) { return false; } const parts = Object.fromEntries( signatureHeader.split(",").map((part) => { const idx = part.indexOf("="); return idx === -1 ? [part, ""] : [part.slice(0, idx), part.slice(idx + 1)]; }) ); const timestamp = parts.t; const v1 = parts.v1; if (!(timestamp && v1)) { return false; } const tsNum = Number(timestamp); if (Number.isFinite(tsNum)) { const ageSeconds = Math.abs(Date.now() / 1000 - tsNum); if (ageSeconds > toleranceSeconds) { return false; } } const expected = createHmac("sha256", WEBHOOK_SECRET) .update(`${timestamp}.${rawBody}`) .digest("hex"); try { return timingSafeEqual( Buffer.from(expected, "hex"), Buffer.from(v1, "hex") ); } catch { return false; } }