diff --git a/src/app/api/payments/polar/callback/route.ts b/src/app/api/payments/polar/callback/route.ts index d14a81c..e7f5120 100644 --- a/src/app/api/payments/polar/callback/route.ts +++ b/src/app/api/payments/polar/callback/route.ts @@ -1,10 +1,11 @@ import { NextRequest, NextResponse } from "next/server"; +import { WebhookVerificationError } from "@polar-sh/sdk/webhooks"; import { Query } from "node-appwrite"; import { logAudit } from "@/lib/appwrite/audit"; import { DATABASE_ID, TABLES, type SubscriptionPayment } from "@/lib/appwrite/schema"; import { createAdminClient } from "@/lib/appwrite/server"; -import { verifyPolarWebhook } from "@/lib/payments/polar"; +import { verifyAndParsePolarWebhook } from "@/lib/payments/polar"; const PRO_VALIDITY_DAYS = 30; @@ -16,33 +17,30 @@ export async function POST(req: NextRequest): Promise { return NextResponse.json({ error: "invalid body" }, { status: 400 }); } - // Svix tüm headerları obje olarak alır const headers: Record = {}; req.headers.forEach((value, key) => { headers[key] = value; }); - if (!verifyPolarWebhook(headers, rawBody)) { - console.error("[polar/callback] signature mismatch"); - return NextResponse.json({ error: "signature mismatch" }, { status: 403 }); - } - - let event: { type: string; data: Record }; + let event: ReturnType; try { - event = JSON.parse(rawBody) as typeof event; - } catch { - return NextResponse.json({ error: "invalid json" }, { status: 400 }); + event = verifyAndParsePolarWebhook(headers, rawBody); + } catch (e) { + if (e instanceof WebhookVerificationError) { + console.error("[polar/callback] signature mismatch"); + return NextResponse.json({ error: "signature mismatch" }, { status: 403 }); + } + return NextResponse.json({ error: "invalid payload" }, { status: 400 }); } // order.created veya checkout.updated (status=confirmed) eventlerini işle const isOrderCreated = event.type === "order.created"; const isCheckoutConfirmed = - event.type === "checkout.updated" && - (event.data as { status?: string }).status === "confirmed"; + event.type === "checkout.updated" && event.data.status === "confirmed"; if (!isOrderCreated && !isCheckoutConfirmed) { return new NextResponse("OK", { status: 200 }); } - const metadata = (event.data as { metadata?: Record }).metadata ?? {}; + const metadata = (event.data.metadata ?? {}) as Record; const crmOrderId = metadata.crm_order_id ?? ""; if (!crmOrderId) { diff --git a/src/lib/payments/polar.ts b/src/lib/payments/polar.ts index e962c09..630b992 100644 --- a/src/lib/payments/polar.ts +++ b/src/lib/payments/polar.ts @@ -1,6 +1,6 @@ import "server-only"; -import { Webhook } from "svix"; +import { validateEvent, WebhookVerificationError } from "@polar-sh/sdk/webhooks"; const POLAR_API_BASE = "https://api.polar.sh"; const ACCESS_TOKEN = process.env.POLAR_ACCESS_TOKEN ?? ""; @@ -52,19 +52,11 @@ export async function createPolarCheckout(params: { return res.json() as Promise; } -// Svix kullanarak Polar webhook imzasını doğrula -export function verifyPolarWebhook( +// Polar resmi SDK ile webhook doğrulama ve parse +// Hata fırlatırsa imza geçersiz demektir +export function verifyAndParsePolarWebhook( headers: Record, rawBody: string, -): boolean { - if (!WEBHOOK_SECRET) return false; - try { - // Svix whsec_ prefix bekler; polar_whs_ → whsec_ dönüşümü - const secret = WEBHOOK_SECRET.replace(/^polar_whs_/, "whsec_"); - const wh = new Webhook(secret); - wh.verify(rawBody, headers); - return true; - } catch { - return false; - } +): ReturnType { + return validateEvent(rawBody, headers, WEBHOOK_SECRET); }