crypto-commons-gather.ing-w.../app/api/webhook/route.ts

100 lines
3.1 KiB
TypeScript

import { type NextRequest, NextResponse } from "next/server"
import Stripe from "stripe"
import { updatePaymentStatus } from "@/lib/google-sheets"
// Lazy initialization to avoid build-time errors
let stripe: Stripe | null = null
function getStripe() {
if (!stripe) {
stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
apiVersion: "2024-12-18.acacia",
})
}
return stripe
}
function getWebhookSecret() {
return process.env.STRIPE_WEBHOOK_SECRET!
}
export async function POST(request: NextRequest) {
try {
const body = await request.text()
const signature = request.headers.get("stripe-signature")!
let event: Stripe.Event
try {
event = getStripe().webhooks.constructEvent(body, signature, getWebhookSecret())
} catch (err) {
console.error("[Webhook] Signature verification failed:", err)
return NextResponse.json({ error: "Invalid signature" }, { status: 400 })
}
// Handle the event
switch (event.type) {
case "checkout.session.completed": {
const session = event.data.object as Stripe.Checkout.Session
console.log("[Webhook] Payment successful:", session.id)
// Extract registration data from metadata
const metadata = session.metadata || {}
const customerEmail = session.customer_details?.email || ""
// Update Google Sheet with payment confirmation
const updated = await updatePaymentStatus({
name: metadata.name || "",
email: customerEmail,
stripeSessionId: session.id,
paymentStatus: "Paid",
paymentMethod: session.payment_method_types?.[0] || "unknown",
amountPaid: session.amount_total
? `${(session.amount_total / 100).toFixed(2)}`
: "",
paymentDate: new Date().toISOString(),
})
if (updated) {
console.log(`[Webhook] Google Sheet updated for ${metadata.name}`)
} else {
console.error(`[Webhook] Failed to update Google Sheet for ${metadata.name}`)
}
// TODO: Send confirmation email
break
}
case "payment_intent.succeeded": {
const paymentIntent = event.data.object as Stripe.PaymentIntent
console.log("[Webhook] PaymentIntent successful:", paymentIntent.id)
break
}
case "payment_intent.payment_failed": {
const paymentIntent = event.data.object as Stripe.PaymentIntent
console.error("[Webhook] Payment failed:", paymentIntent.id)
// Optionally update sheet to mark as failed
const failedMetadata = paymentIntent.metadata || {}
if (failedMetadata.name) {
await updatePaymentStatus({
name: failedMetadata.name,
stripeSessionId: paymentIntent.id,
paymentStatus: "Failed",
paymentDate: new Date().toISOString(),
})
}
break
}
default:
console.log("[Webhook] Unhandled event type:", event.type)
}
return NextResponse.json({ received: true })
} catch (err) {
console.error("[Webhook] Error:", err)
return NextResponse.json({ error: "Webhook error" }, { status: 500 })
}
}