fix: use @polar-sh/sdk validateEvent for webhook verification
This commit is contained in:
@@ -1,10 +1,11 @@
|
|||||||
import { NextRequest, NextResponse } from "next/server";
|
import { NextRequest, NextResponse } from "next/server";
|
||||||
|
import { WebhookVerificationError } from "@polar-sh/sdk/webhooks";
|
||||||
import { Query } from "node-appwrite";
|
import { Query } from "node-appwrite";
|
||||||
|
|
||||||
import { logAudit } from "@/lib/appwrite/audit";
|
import { logAudit } from "@/lib/appwrite/audit";
|
||||||
import { DATABASE_ID, TABLES, type SubscriptionPayment } from "@/lib/appwrite/schema";
|
import { DATABASE_ID, TABLES, type SubscriptionPayment } from "@/lib/appwrite/schema";
|
||||||
import { createAdminClient } from "@/lib/appwrite/server";
|
import { createAdminClient } from "@/lib/appwrite/server";
|
||||||
import { verifyPolarWebhook } from "@/lib/payments/polar";
|
import { verifyAndParsePolarWebhook } from "@/lib/payments/polar";
|
||||||
|
|
||||||
const PRO_VALIDITY_DAYS = 30;
|
const PRO_VALIDITY_DAYS = 30;
|
||||||
|
|
||||||
@@ -16,33 +17,30 @@ export async function POST(req: NextRequest): Promise<NextResponse> {
|
|||||||
return NextResponse.json({ error: "invalid body" }, { status: 400 });
|
return NextResponse.json({ error: "invalid body" }, { status: 400 });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Svix tüm headerları obje olarak alır
|
|
||||||
const headers: Record<string, string> = {};
|
const headers: Record<string, string> = {};
|
||||||
req.headers.forEach((value, key) => { headers[key] = value; });
|
req.headers.forEach((value, key) => { headers[key] = value; });
|
||||||
|
|
||||||
if (!verifyPolarWebhook(headers, rawBody)) {
|
let event: ReturnType<typeof verifyAndParsePolarWebhook>;
|
||||||
|
try {
|
||||||
|
event = verifyAndParsePolarWebhook(headers, rawBody);
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof WebhookVerificationError) {
|
||||||
console.error("[polar/callback] signature mismatch");
|
console.error("[polar/callback] signature mismatch");
|
||||||
return NextResponse.json({ error: "signature mismatch" }, { status: 403 });
|
return NextResponse.json({ error: "signature mismatch" }, { status: 403 });
|
||||||
}
|
}
|
||||||
|
return NextResponse.json({ error: "invalid payload" }, { status: 400 });
|
||||||
let event: { type: string; data: Record<string, unknown> };
|
|
||||||
try {
|
|
||||||
event = JSON.parse(rawBody) as typeof event;
|
|
||||||
} catch {
|
|
||||||
return NextResponse.json({ error: "invalid json" }, { status: 400 });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// order.created veya checkout.updated (status=confirmed) eventlerini işle
|
// order.created veya checkout.updated (status=confirmed) eventlerini işle
|
||||||
const isOrderCreated = event.type === "order.created";
|
const isOrderCreated = event.type === "order.created";
|
||||||
const isCheckoutConfirmed =
|
const isCheckoutConfirmed =
|
||||||
event.type === "checkout.updated" &&
|
event.type === "checkout.updated" && event.data.status === "confirmed";
|
||||||
(event.data as { status?: string }).status === "confirmed";
|
|
||||||
|
|
||||||
if (!isOrderCreated && !isCheckoutConfirmed) {
|
if (!isOrderCreated && !isCheckoutConfirmed) {
|
||||||
return new NextResponse("OK", { status: 200 });
|
return new NextResponse("OK", { status: 200 });
|
||||||
}
|
}
|
||||||
|
|
||||||
const metadata = (event.data as { metadata?: Record<string, string> }).metadata ?? {};
|
const metadata = (event.data.metadata ?? {}) as Record<string, string>;
|
||||||
const crmOrderId = metadata.crm_order_id ?? "";
|
const crmOrderId = metadata.crm_order_id ?? "";
|
||||||
|
|
||||||
if (!crmOrderId) {
|
if (!crmOrderId) {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import "server-only";
|
import "server-only";
|
||||||
|
|
||||||
import { Webhook } from "svix";
|
import { validateEvent, WebhookVerificationError } from "@polar-sh/sdk/webhooks";
|
||||||
|
|
||||||
const POLAR_API_BASE = "https://api.polar.sh";
|
const POLAR_API_BASE = "https://api.polar.sh";
|
||||||
const ACCESS_TOKEN = process.env.POLAR_ACCESS_TOKEN ?? "";
|
const ACCESS_TOKEN = process.env.POLAR_ACCESS_TOKEN ?? "";
|
||||||
@@ -52,19 +52,11 @@ export async function createPolarCheckout(params: {
|
|||||||
return res.json() as Promise<PolarCheckout>;
|
return res.json() as Promise<PolarCheckout>;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Svix kullanarak Polar webhook imzasını doğrula
|
// Polar resmi SDK ile webhook doğrulama ve parse
|
||||||
export function verifyPolarWebhook(
|
// Hata fırlatırsa imza geçersiz demektir
|
||||||
|
export function verifyAndParsePolarWebhook(
|
||||||
headers: Record<string, string>,
|
headers: Record<string, string>,
|
||||||
rawBody: string,
|
rawBody: string,
|
||||||
): boolean {
|
): ReturnType<typeof validateEvent> {
|
||||||
if (!WEBHOOK_SECRET) return false;
|
return validateEvent(rawBody, headers, WEBHOOK_SECRET);
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user