Skip to content

Instantly share code, notes, and snippets.

@gragland
Last active January 18, 2024 22:20
Show Gist options
  • Star 13 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save gragland/f6d1c2df86bdf53880c6bae161bf28c4 to your computer and use it in GitHub Desktop.
Save gragland/f6d1c2df86bdf53880c6bae161bf28c4 to your computer and use it in GitHub Desktop.
Next.js Stripe Webhook from divjoy.com
const getRawBody = require("raw-body");
const { updateUserByCustomerId } = require("./_db.js");
const stripe = require("stripe")(process.env.STRIPE_SECRET_KEY, {
apiVersion: process.env.STRIPE_API_VERSION,
});
// Disable next.js body parsing (stripe needs the raw body to validate the event)
export const config = {
api: {
bodyParser: false,
},
};
export default async (req, res) => {
const headers = req.headers;
try {
const rawBody = await getRawBody(req);
const stripeEvent = stripe.webhooks.constructEvent(
rawBody,
headers["stripe-signature"],
process.env.STRIPE_WEBHOOK_SECRET
);
console.log(`stripeEvent: ${stripeEvent.type}`);
// Get the object from stripeEvent
const object = stripeEvent.data.object;
switch (stripeEvent.type) {
case "checkout.session.completed":
// Fetch subscription
const subscription = await stripe.subscriptions.retrieve(
object.subscription
);
// Update the current user
await updateUserByCustomerId(object.customer, {
stripeSubscriptionId: subscription.id,
// Store the priceId (or "plan") for this subscription
stripePriceId: subscription.items.data[0].price.id,
// Store the subscription status ("active" or "trialing")
stripeSubscriptionStatus: subscription.status,
});
break;
case "invoice.payment_succeeded":
// If a payment succeeded we update stored subscription status to "active"
// in case it was previously "trialing" or "past_due".
// We skip if amount due is 0 as that's the case at start of trial period.
if (object.amount_due > 0) {
await updateUserByCustomerId(object.customer, {
stripeSubscriptionStatus: "active",
});
}
break;
case "invoice.payment_failed":
// If a payment failed we update stored subscription status to "past_due"
await updateUserByCustomerId(object.customer, {
stripeSubscriptionStatus: "past_due",
});
break;
case "customer.subscription.updated":
await updateUserByCustomerId(object.customer, {
stripePriceId: object.items.data[0].price.id,
stripeSubscriptionStatus: object.status,
});
// 💡 You could also read "cancel_at_period_end" if you'd like to email user and learn why they cancelled
// or convince them to renew before their subscription is deleted at end of payment period.
break;
case "customer.subscription.deleted":
// If a subscription was deleted update stored subscription status to "canceled".
// Keep in mind this won't be called right away if "Cancel at end of billing period" is selected
// in Billing Portal settings (https://dashboard.stripe.com/settings/billing/portal). Instead you'll
// get a "customer.subscription.updated" event with a cancel_at_period_end value.
await updateUserByCustomerId(object.customer, {
stripeSubscriptionStatus: "canceled",
});
break;
case "customer.subscription.trial_will_end":
// This event happens 3 days before a trial ends
// 💡 You could email user letting them know their trial will end or you can have Stripe do that
// automatically 7 days in advance: https://dashboard.stripe.com/settings/billing/automatic
break;
// no default
}
// Send success response
res.send({ status: "success" });
} catch (error) {
console.log("stripe webhook error", error);
// Send error response
res.send({ status: "error", code: error.code, message: error.message });
}
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment