95 lines
3.3 KiB
TypeScript
95 lines
3.3 KiB
TypeScript
import { headers } from "next/headers";
|
|
import { NextRequest, NextResponse } from "next/server";
|
|
import { getPrisma } from "../../../../lib/db";
|
|
import { loadSystemConfig } from "../../../../lib/system-config";
|
|
import { getStripe } from "../../../../lib/stripe";
|
|
import { sendEmail } from "../../../../lib/email";
|
|
import { createWebinarCalendarEvent } from "../../../../lib/calendar";
|
|
|
|
export const runtime = "nodejs";
|
|
|
|
export async function POST(req: NextRequest) {
|
|
const prisma = await getPrisma();
|
|
const stripe = await getStripe();
|
|
const cfg = await loadSystemConfig();
|
|
|
|
const secret = cfg.stripe?.webhookSecret || process.env.STRIPE_WEBHOOK_SECRET;
|
|
|
|
if (!prisma || !stripe || !secret) {
|
|
// must still 200 to avoid retries in misconfigured env
|
|
return NextResponse.json({ ok: true });
|
|
}
|
|
|
|
const headersList = await headers();
|
|
const sig = headersList.get("stripe-signature");
|
|
const body = await req.text();
|
|
|
|
if (!sig) return NextResponse.json({ ok: true });
|
|
|
|
let event: any;
|
|
try {
|
|
event = stripe.webhooks.constructEvent(body, sig, secret);
|
|
} catch {
|
|
return NextResponse.json({ ok: true });
|
|
}
|
|
|
|
if (event.type === "checkout.session.completed") {
|
|
const session = event.data.object as any;
|
|
const registrationId = session.metadata?.registrationId as string | undefined;
|
|
const paymentIntent = session.payment_intent as string | undefined;
|
|
|
|
if (registrationId) {
|
|
try {
|
|
await prisma.webinarRegistration.update({
|
|
where: { id: registrationId },
|
|
data: { status: "PAID", stripePaymentIntentId: paymentIntent ?? null },
|
|
});
|
|
|
|
// Send confirmation email with calendar invite
|
|
const registration = await prisma.webinarRegistration.findUnique({
|
|
where: { id: registrationId },
|
|
include: { user: true, webinar: true },
|
|
});
|
|
|
|
if (registration && cfg.email?.enabled) {
|
|
const { icsContent } = await createWebinarCalendarEvent(
|
|
registration.webinar,
|
|
registration.user.email
|
|
);
|
|
|
|
const meetingInfo = registration.webinar.meetingInfo as any;
|
|
const meetingLink = meetingInfo?.meetingLink || "TBD";
|
|
const htmlContent = `
|
|
<h2>Webinar Registration Confirmed</h2>
|
|
<p>Hi ${registration.user.firstName},</p>
|
|
<p>Thank you for registering for <strong>${registration.webinar.title}</strong>.</p>
|
|
<p><strong>Date & Time:</strong> ${new Date(registration.webinar.startAt).toLocaleString()}</p>
|
|
<p><strong>Duration:</strong> ${registration.webinar.duration} minutes</p>
|
|
<p><strong>Join Link:</strong> <a href="${meetingLink}">${meetingLink}</a></p>
|
|
<p>A calendar invitation is attached to this email.</p>
|
|
<p>See you there!</p>
|
|
`;
|
|
|
|
await sendEmail({
|
|
to: registration.user.email,
|
|
subject: `Registration Confirmed: ${registration.webinar.title}`,
|
|
html: htmlContent,
|
|
attachments: [
|
|
{
|
|
filename: "webinar-invite.ics",
|
|
content: icsContent,
|
|
contentType: "text/calendar",
|
|
},
|
|
],
|
|
});
|
|
}
|
|
} catch (error) {
|
|
console.error("[WEBHOOK] Error processing payment:", error);
|
|
// ignore
|
|
}
|
|
}
|
|
}
|
|
|
|
return NextResponse.json({ ok: true });
|
|
}
|