Spaces:
Sleeping
Sleeping
| import { NextRequest, NextResponse } from "next/server"; | |
| import Razorpay from "razorpay"; | |
| import { createClient } from "@supabase/supabase-js"; | |
| const supabase = createClient( | |
| process.env.NEXT_PUBLIC_SUPABASE_URL!, | |
| process.env.SUPABASE_SERVICE_ROLE_KEY! | |
| ); | |
| let resend: any = null; | |
| if (process.env.RESEND_API_KEY) { | |
| import("resend").then(({ Resend }) => { resend = new Resend(process.env.RESEND_API_KEY); }); | |
| } | |
| export async function POST(req: NextRequest) { | |
| const rawBody = await req.text(); | |
| const signature = req.headers.get("x-razorpay-signature") || ""; | |
| const webhookSecret = process.env.RAZORPAY_WEBHOOK_SECRET!; | |
| // Verify signature | |
| const isValid = Razorpay.validateWebhookSignature(rawBody, signature, webhookSecret); | |
| if (!isValid) { | |
| return NextResponse.json({ error: "Invalid signature" }, { status: 400 }); | |
| } | |
| const event = JSON.parse(rawBody); | |
| const eventType: string = event.event; | |
| switch (eventType) { | |
| case "subscription.activated": { | |
| const sub = event.payload.subscription.entity; | |
| const plan = sub.notes?.plan || "pro"; | |
| const userId = sub.notes?.user_id; | |
| if (userId) { | |
| const { data } = await supabase | |
| .from("profiles") | |
| .update({ | |
| plan, | |
| razorpay_subscription_id: sub.id, | |
| updated_at: new Date().toISOString(), | |
| }) | |
| .eq("id", userId) | |
| .select("email") | |
| .single(); | |
| // Send welcome email | |
| if (data?.email && resend) { | |
| await resend.emails.send({ | |
| from: "ClauseGuard <noreply@clauseguardweb.netlify.app>", | |
| to: [data.email], | |
| subject: `Welcome to ClauseGuard ${plan.charAt(0).toUpperCase() + plan.slice(1)}`, | |
| html: `<p>Your ${plan} subscription is active. You now have unlimited scans.</p><p><a href="https://clauseguardweb.netlify.app/dashboard-pages/dashboard">Go to dashboard</a></p>`, | |
| }); | |
| } | |
| } | |
| break; | |
| } | |
| case "subscription.charged": { | |
| // Recurring payment succeeded — nothing to update, plan already active | |
| break; | |
| } | |
| case "subscription.cancelled": | |
| case "subscription.completed": { | |
| const sub = event.payload.subscription.entity; | |
| const userId = sub.notes?.user_id; | |
| if (userId) { | |
| await supabase | |
| .from("profiles") | |
| .update({ | |
| plan: "free", | |
| razorpay_subscription_id: null, | |
| updated_at: new Date().toISOString(), | |
| }) | |
| .eq("id", userId); | |
| } | |
| break; | |
| } | |
| case "subscription.halted": { | |
| const sub = event.payload.subscription.entity; | |
| const userId = sub.notes?.user_id; | |
| if (userId) { | |
| const { data } = await supabase | |
| .from("profiles") | |
| .select("email") | |
| .eq("id", userId) | |
| .single(); | |
| if (data?.email && resend) { | |
| await resend.emails.send({ | |
| from: "ClauseGuard <noreply@clauseguardweb.netlify.app>", | |
| to: [data.email], | |
| subject: "Payment failed — subscription paused", | |
| html: "<p>Your payment failed and your subscription has been paused. Please update your payment method to continue.</p>", | |
| }); | |
| } | |
| } | |
| break; | |
| } | |
| case "payment.failed": { | |
| // Razorpay auto-retries for subscriptions — log for monitoring | |
| const payment = event.payload.payment.entity; | |
| console.warn("Payment failed:", payment.id, payment.error_description); | |
| break; | |
| } | |
| } | |
| return NextResponse.json({ status: "ok" }); | |
| } | |