-
Notifications
You must be signed in to change notification settings - Fork 769
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add webhook handler for Next JS 14 API router #2258
Conversation
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@AbdullahAhmadAAK Thanks for your contribution! Please let me know if you've any questions about my comments/suggestions. Once resolved we can get this merged.
import { NextRequest, NextResponse } from 'next/server'; | ||
import Stripe from 'stripe'; | ||
import { headers } from 'next/headers' | ||
import { getErrorMessage, logError } from '@/lib/error-methods'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we remove this import as it looks like code specific to your application and may confuse others.
|
||
// Handle the construction of the event | ||
try { | ||
if (!sig) throw new Error("No signature provided") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Redundant as there'll always be a Stripe-Signature header.
const bodyBuffer = Buffer.from(reqArrayBuffer) | ||
event = stripe.webhooks.constructEvent(bodyBuffer, sig, webhookSecret); | ||
} catch (err) { | ||
logError(err) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Application specific – remove.
const reqArrayBuffer = await req.arrayBuffer() | ||
const bodyBuffer = Buffer.from(reqArrayBuffer) | ||
event = stripe.webhooks.constructEvent(bodyBuffer, sig, webhookSecret); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can simplify this:
const reqArrayBuffer = await req.arrayBuffer() | |
const bodyBuffer = Buffer.from(reqArrayBuffer) | |
event = stripe.webhooks.constructEvent(bodyBuffer, sig, webhookSecret); | |
const body = await req.text(); | |
event = stripe.webhooks.constructEvent(body, sig, webhookSecret); |
event = stripe.webhooks.constructEvent(bodyBuffer, sig, webhookSecret); | ||
} catch (err) { | ||
logError(err) | ||
return NextResponse.json({ error: `Webhook Error: ${getErrorMessage(err)}` }, { status: 400 }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we remove getErrorMessage
usage please.
@@ -0,0 +1,3 @@ | |||
export const api = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This file is redundant I believe.
switch (event.type) { | ||
case 'product.updated': { | ||
|
||
} | ||
|
||
case 'product.created': { | ||
|
||
} | ||
|
||
// other cases as you please | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we add a canonical example for event handling? Doesn't need to be extensive
switch (event.type) { | |
case 'product.updated': { | |
} | |
case 'product.created': { | |
} | |
// other cases as you please | |
} | |
ry { | |
switch (event.type) { | |
case "checkout.session.completed": | |
data = event.data.object as Stripe.Checkout.Session; | |
console.log(`💰 CheckoutSession status: ${data.payment_status}`); | |
break; | |
case "payment_intent.payment_failed": | |
data = event.data.object as Stripe.PaymentIntent; | |
console.log(`❌ Payment failed: ${data.last_payment_error?.message}`); | |
break; | |
case "payment_intent.succeeded": | |
data = event.data.object as Stripe.PaymentIntent; | |
console.log(`💰 PaymentIntent status: ${data.status}`); | |
break; | |
default: | |
throw new Error(`Unhandled event: ${event.type}`); | |
} | |
} catch (error) { | |
console.log(error); | |
return NextResponse.json( | |
{ message: "Webhook handler failed" }, | |
{ status: 500 }, | |
); | |
} |
} | ||
|
||
// Return a response to acknowledge receipt of the event | ||
return new Response('Event received', { status: 200 }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's be consistent and use NextResponse
.
return new Response('Event received', { status: 200 }); | |
return NextResponse.json({ message: "Event received" }, { status: 200 }); |
@@ -0,0 +1,43 @@ | |||
import { NextRequest, NextResponse } from 'next/server'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you rename the directory so it resembles the Next.js project structure (app/api/stripe/webhook/route.ts
)?
// Webhook handler | ||
export async function POST(req: NextRequest) { | ||
const webhookSecret: string = process.env.STRIPE_WEBHOOK_SECRET!; | ||
const headersList = headers() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
headers
is an async function ( ref ), and we need to use await
here . For example
const headersList = await headers();
Alternatively, we can get the headers directly from req
const headersList = req.headers;
closing in favor of #2259. Thank you for your interest though! |
Why?
This is to help developers in NextJS 14 by giving them a working example of a webhook handler. That way, they won't be confused as I was for 2-3 hours by not being able to follow the other pages router example.
Addresses #2100