125 lines
4.1 KiB
TypeScript
125 lines
4.1 KiB
TypeScript
import { type NextRequest, NextResponse } from "next/server"
|
|
import Stripe from "stripe"
|
|
import { updatePaymentStatus } from "@/lib/google-sheets"
|
|
import { sendPaymentConfirmation } from "@/lib/email"
|
|
import { addToListmonk } from "@/lib/listmonk"
|
|
|
|
// 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}`)
|
|
}
|
|
|
|
// Send payment confirmation email
|
|
if (customerEmail) {
|
|
await sendPaymentConfirmation({
|
|
name: metadata.name || "",
|
|
email: customerEmail,
|
|
amountPaid: session.amount_total
|
|
? `€${(session.amount_total / 100).toFixed(2)}`
|
|
: "",
|
|
paymentMethod: session.payment_method_types?.[0] || "card",
|
|
contributions: metadata.contributions || "",
|
|
dietary: metadata.dietary || "",
|
|
})
|
|
|
|
// Add to Listmonk newsletter
|
|
addToListmonk({
|
|
email: customerEmail,
|
|
name: metadata.name || "",
|
|
attribs: {
|
|
contact: metadata.contact,
|
|
contributions: metadata.contributions,
|
|
expectations: metadata.expectations,
|
|
},
|
|
}).catch((err) => console.error("[Webhook] Listmonk sync failed:", err))
|
|
}
|
|
|
|
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 })
|
|
}
|
|
}
|