import "server-only"; import { createHmac, timingSafeEqual } from "crypto"; const POLAR_API_BASE = "https://api.polar.sh"; const ACCESS_TOKEN = process.env.POLAR_ACCESS_TOKEN ?? ""; const WEBHOOK_SECRET = process.env.POLAR_WEBHOOK_SECRET ?? ""; export const POLAR_PRODUCT_ID = process.env.POLAR_PRODUCT_ID ?? ""; export function isPolarEnabled(): boolean { return ( process.env.PAYMENT_PROVIDER === "polar" && Boolean(ACCESS_TOKEN) && Boolean(POLAR_PRODUCT_ID) ); } export type PolarCheckout = { id: string; url: string; }; export async function createPolarCheckout(params: { orderId: string; tenantId: string; userEmail: string; successUrl: string; }): Promise { const res = await fetch(`${POLAR_API_BASE}/v1/checkouts/`, { method: "POST", headers: { Authorization: `Bearer ${ACCESS_TOKEN}`, "Content-Type": "application/json", }, body: JSON.stringify({ products: [POLAR_PRODUCT_ID], customer_email: params.userEmail, success_url: params.successUrl, metadata: { crm_order_id: params.orderId, tenant_id: params.tenantId, }, }), }); if (!res.ok) { const text = await res.text(); throw new Error(`Polar checkout oluşturulamadı: ${text}`); } return res.json() as Promise; } // Standard Webhooks imza doğrulama // Header: webhook-id, webhook-timestamp, webhook-signature // Signed content: "{webhook-id}.{webhook-timestamp}.{rawBody}" // Signature: "v1," + base64(HMAC-SHA256(secret, signedContent)) export function verifyPolarWebhook( webhookId: string, webhookTimestamp: string, webhookSignature: string, rawBody: string, ): boolean { if (!WEBHOOK_SECRET) return false; // Timestamp replay koruması (1 saat — Polar retry aralığı uzun olabilir) const ts = parseInt(webhookTimestamp, 10); if (isNaN(ts) || Math.abs(Date.now() / 1000 - ts) > 3600) return false; const signedContent = `${webhookId}.${webhookTimestamp}.${rawBody}`; // Polar secret: "polar_whs_" — prefix soyulup base64 decode edilir let secretBytes: Buffer; try { const raw = WEBHOOK_SECRET.replace(/^(whsec_|polar_whs_)/, ""); secretBytes = Buffer.from(raw, "base64"); } catch { secretBytes = Buffer.from(WEBHOOK_SECRET); } const expected = createHmac("sha256", secretBytes).update(signedContent).digest("base64"); const expectedFull = `v1,${expected}`; // Header birden fazla imza içerebilir (space ile ayrılmış) const signatures = webhookSignature.split(" "); return signatures.some((sig) => { try { return timingSafeEqual(Buffer.from(expectedFull), Buffer.from(sig)); } catch { return false; } }); }