In the previous post, we set up Stripe in our project. In this post, we’ll create a webhook to update payment information in the database after the user completes the payment.
We’ll start by defining our webhook endpoint on the Stripe website. First, create a file in src/app/api/webhooks/route.ts
. After that, open the webhooks tab on the Stripe website.
Replace the endpoint with your base URL
and add the event checkout.session.completed
.
Next, we need logic to update the information after the user completes the payment. To do this, we should catch the event from the Stripe checkout session. We will receive the stripe-signature
from the headers and verify it. Here is the logic for the webhook
file. You can also add conditions for several events such as subscription schedules, coupons, etc., using if (event.type === YOUR_SELECTED_EVENT)
.
// file: src/app/api/webhooks/route.ts
import { db } from "@/db";
import { stripe } from "@/lib/stripe";
import { headers } from "next/headers";
import { NextResponse } from "next/server";
import Stripe from "stripe";
export async function POST(req: Request) {
try {
const body = await req.text();
const signature = headers().get("stripe-signature");
if (!signature) {
return new Response("Invalid signature", { status: 400 });
}
const event = stripe.webhooks.constructEvent(
body,
signature,
process.env.STRIPE_WEBHOOK_SECRET!
);
if (event.type === "checkout.session.completed") {
if (!event.data.object.customer_details?.email) {
throw new Error("Missing user email");
}
const session = event.data.object as Stripe.Checkout.Session;
const { userId, orderId } = session.metadata || {
userId: null,
orderId: null,
};
if (!userId || !orderId) {
throw new Error("Invalid request metadata");
}
const billingAddress = session.customer_details!.address;
const shippingAddress = session.shipping_details!.address;
await db.order.update({
where: {
id: orderId,
},
data: {
isPaid: true,
shippingAddress: {
create: {
name: session.customer_details!.name!,
city: shippingAddress!.city!,
country: shippingAddress!.country!,
postalCode: shippingAddress!.postal_code!,
street: shippingAddress!.line1!,
state: shippingAddress!.state!,
},
},
billingAddress: {
create: {
name: session.customer_details!.name!,
city: billingAddress!.city!,
country: billingAddress!.country!,
postalCode: billingAddress!.postal_code!,
street: billingAddress!.line1!,
state: billingAddress!.state!,
},
},
},
});
return NextResponse.json({ result: event, ok: true });
}
} catch (error) {
console.error(error);
return NextResponse.json(
{ message: "Something went worng", ok: false },
{ status: 500 }
);
}
}
Do you remember the success_url
we configured in the stripeSession
in the previous post? This should be the redirected URL after the user completes the payment.
const stripeSession = await stripe.checkout.sessions.create({
success_url: `${process.env.NEXT_PUBLIC_SERVER_URL}/thank-you?orderId=${order.id}`, // will be redirected to this URL if payment succeed
cancel_url: `${process.env.NEXT_PUBLIC_SERVER_URL}/configure/preview?${item.id}`,
payment_method_types: ["card", "link"],
mode: "payment",
shipping_address_collection: {
allowed_countries: ["US", "CA", "ID", "DE"],
},
metadata: {
userId: user.id,
orderId: order.id,
},
line_items: [{ price: product.default_price as string, quantity: 1 }],
});
Hope this helps. See you in the next post!