Security News
PyPI’s New Archival Feature Closes a Major Security Gap
PyPI now allows maintainers to archive projects, improving security and helping users make informed decisions about their dependencies.
next-stripe-helper
Advanced tools
Easily add Stripe boilerplate code to Nextjs, like webhook handling, and subscription updates. This package provides a thin wrapper around the Stripe API, and makes integrating Stripe and NextJS a lot faster!
next-stripe-helper
is a module designed to simplify the integration of Stripe's functionality into Next.js applications. Whether you're looking to process payments, manage customers, or handle subscriptions, this utility aims to streamline those interactions.
This utility is perfect for developers building e-commerce platforms, subscription-based services, or any other application that requires payment functionalities within a JS or TS ecosystem.
Includes a smart webhook handler that will automatically keep your database up to date with current plans, pricing, and subscriptions. I also included a few helper function examples below.
If you would like to contribute or report an error, the github repo is here. Please star and follow if you find this tool helpful!!
npm install next-stripe-helper
Ensure you've set up Stripe and have an error handler in your project, as this utility relies on those components.
You will likely want to start in TEST MODE!! Make sure you use the test mode switch to turn Test Mode on before proceeding. Once you are setup and ready to take live payments, turn off test mode, get your LIVE stripe keys, and setup your LIVE webhooks secret and endpoint.
Ensure you have set the STRIPE_SECRET_LIVE
or STRIPE_SECRET
environment variables in your .env.local
(or other environment-specific .env
files).
Ensure you have set the STRIPE_WEBHOOK_SECRET_LIVE
or STRIPE_WEBHOOK_SECRET
environment variables in your .env.local
(or other environment-specific .env
files).
If you would like to log Stripe Webhook Events for debugging purposes you can add NEXT_STRIPE_HELPER_DEBUG
in your .env.local
(or other environment-specific .env
files).
# Stripe configuration
STRIPE_SECRET=your_stripe_TEST_secret_key
STRIPE_WEBHOOK_SECRET=your_TEST_stripe_webhook_secret
STRIPE_SECRET_LIVE=your_LIVE_stripe_secret_key
STRIPE_WEBHOOK_SECRET_LIVE=your_LIVE_stripe_webhook_secret
Make sure you complete your checkout settings from within the Stripe dashboard before using any checkout functions. Setup Checkout Settings
Make sure you add your webhooks endpoint URL!! Setup Webhook URL
First, ensure that you've imported the necessary functions from the package:
import { createCheckoutSession, createCheckoutSessionForSavingCard } from 'next-stripe-helper';
Create a checkout session with Stripe.
const session = await createCheckoutSession({
success_url: 'https://example.com/success',
cancel_url: 'https://example.com/canceled',
line_items: [
{price: 'price_idhere', quantity: 1},
],
mode: 'subscription',
client_reference_id: 'your_user_id',
additionalParams: {}
});
Parameters:
success_url
(required): The URL to redirect upon successful payment.cancel_url
(optional, default ""
): The URL to redirect upon payment cancellation.line_items
(required unless setup mode): An array of line items for the checkout.mode
(optional, default subscription
): The mode of the checkout session (subscription
or payment
).customer
(optional): The Stripe customer ID. A new Customer will be created if no ID is provided.client_reference_id
(optional): Your User ID. This ID is from your own DB, and is returned via webhook so you can use it to track the session and user in your app.additionalParams
(optional): Additional parameters can be found in the stripe api docs.Create a checkout session with Stripe for the purpose of saving a card.
const sessionId = await createCheckoutSessionForSavingCard(
'customer_id',
'https://your-success-url.com',
'https://your-cancel-url.com'
);
Parameters:
customerId
(required): The Stripe customer ID.successUrl
(required): The URL to redirect upon successful card saving.cancelUrl
(required): The URL to redirect upon cancellation.Import the required functions:
import { createCustomer, getCustomer, updateCustomer } from 'next-stripe-helper';
Create a new customer in Stripe using their email address.
const newCustomer = await createCustomer('example@email.com');
Parameters:
email
(required): The email address of the new customer.Get a customer in Stripe using their email address.
const customerData = await getCustomerByEmail(email: string, limit: number = 1)
Parameters:
email
(required): The email address of the new customer.limit
(optional): Limit the number of returned customer data. If limit is higher than 1, then an array will be returned.Fetch the details of a specific customer using their Stripe customer ID.
const customerDetails = await getCustomer('customer_id');
Parameters:
customerId
(required): The Stripe customer ID.Update the details of a customer in Stripe.
const updates = {
// Your update parameters here (e.g., email, name, etc.)
};
const updatedCustomer = await updateCustomer('customer_id', updates);
Parameters:
customerId
(required): The Stripe customer ID.updates
(required): An object containing key-value pairs of the properties to be updated.Get the payment details of a customer in Stripe.
const paymentMethods = await getCustomerPaymentMethods('customer_id');
Parameters:
customerId
(required): The Stripe customer ID.The next-stripe-helper
also facilitates easy interaction with Stripe's billing portal.
Firstly, remember to import the necessary function:
import { createPortalLink } from 'next-stripe-helper';
Generate a link for your users to access Stripe's billing portal where they can manage their billing details.
const portalUrl = await createPortalLink({customer:'customerId', returnUrl:'https://your-return-url.com'});
Parameters:
customer
(required): The Stripe customer ID.configuration
(optional): The ID of an existing configuration to use for this session, describing its functionality and features. If not specified, the session uses the default configuration.flow_data
(optional): Information about a specific flow for the customer to go through. See the stripe docs to learn more about using customer portal deep links and flows.returnUrl
(required): The URL where the user will be redirected after exiting the billing portal.For more params see the stripe docs.
The next-stripe-helper
provides utilities to help with the creation of products and their associated prices on Stripe.
Start by importing the functions you need:
import { createProduct, createPrice } from 'next-stripe-helper';
To create a new product in Stripe, use:
const newProduct = await createProduct('Product Name', 'Product Description');
Parameters:
name
(required): The name of the product.description
(required): A brief description of the product.Once you have a product, you can set its price:
const newPrice = await createPrice('product_id', 1000, 'usd', { optionalMetadata: 'value' });
Parameters:
productID
(required): The ID of the product for which the price is being set.amount
(required): The amount to be charged for the product, in the smallest unit for the currency (e.g., cents for USD).currency
(required): The three-letter ISO currency code. (e.g., 'usd', 'eur', 'gbp').metadata
(optional): A key-value store for any additional information you want to store.Note: The price is set up as recurring, with a monthly interval. If you wish to modify the recurrence, you would need to adjust the utility function accordingly.
Create a connected account with Stripe.
const account = await createConnectedAccount({
country: 'US',
email: 'affiliate@example.com',
type: 'express', // Optional, defaults to 'express'
capabilities: { // Optional, defaults to requesting 'transfers'
card_payments: { requested: true },
transfers: { requested: true },
},
// ...any other Stripe AccountCreateParams
});
Parameters:
country
(required): The country of the connected account.email
(required): The email address of the connected account.type
(optional, default 'express'
): The type of the connected account ('express'
, 'custom'
, or 'standard'
).capabilities
(optional, default { card_payments: { requested: true }, transfers: { requested: true } }
): The capabilities to request for the connected account.Initiates the Stripe OAuth flow by constructing the OAuth URL.
const oauthURL = startOAuthFlow('your-stripe-client-id', 'https://your-redirect-uri.com/api/stripe/callback');
Parameters:
clientId
(required): Your Stripe client ID. The clientId in the context of the Stripe OAuth flow is not the Stripe customer ID. Instead, it refers to the "Client ID" of your Stripe Connect application.When you set up Stripe Connect in your Stripe Dashboard, you'll be provided with a set of API keys specific to your Connect application. One of these keys is the "Client ID." This ID is used to initiate the OAuth flow for connecting other Stripe accounts to your platform.
redirectUri
(required): The URL where Stripe should redirect the user after completing the OAuth flow. Our example shows using an api route that you would create.Returns:
Process the OAuth callback by exchanging the authorization code for a connected account ID.
const connectedAccountId = await handleOAuthCallback(authorizationCode);
Parameters:
authorizationCode
(required): The authorization code returned by Stripe after the user completes the OAuth flow.Returns:
Create a payout to a connected account.
const payout = await createPayout({
amount: 1000, // amount in cents
currency: 'usd',
destination: 'acct_123456789', // the ID of the connected account
});
Parameters:
amount
(required): The amount to payout, in the smallest currency unit (e.g., cents for USD).currency
(required): The currency of the payout.destination
(required): The ID of the connected account to which the payout will be sent.Creates an account link for onboarding or updating a connected account.
const accountLink = await createAccountLink({
accountId: 'acct_123456789',
refreshUrl: 'https://example.com/reauth',
returnUrl: 'https://example.com/return',
type: 'account_onboarding'
});
Parameters:
accountId
(required): The ID of the connected account.refreshUrl
(required): The URL to redirect to if the user's session expires.returnUrl
(required): The URL to redirect to upon completion.type
(optional, default 'account_onboarding'
): The type of account link. Possible values are:
account_onboarding
: Provides a form for inputting outstanding requirements.account_update
: Displays the fields that are already populated on the account object and allows editing.Returns:
Creates a Connect Express login link for a connected account.
const loginLinkUrl = await createConnectExpressLoginLink(
'acct_123456789', // Replace with your users connected account ID
'sk_YourStripeSecretKey' // Replace with your Stripe secret key
);
Parameters:
accountId
(required): The ID of the connected account.secretKey
(required): Your Stripe secret key.Returns:
The next-stripe-helper
package offers a suite of utilities designed to streamline interactions with Stripe subscriptions.
Before you can use these utilities, you need to import them.
Here is a list of available subscription functions:
import {
createSubscription,
getUserFirstActivePlan,
getUserSubscription,
getUserSubscriptions,
getUserSubscriptionDetails,
addItemToSubscription,
updateItemQuantity,
updateItemQuantityByPriceId,
removeItemsFromSubscription,
removeItemsByPriceId,
updateUserSubscriptionMetadata,
listUserSubscriptions,
changeSubscriptionPlan,
updateSubscriptionPlan,
cancelUserSubscription,
getSubscriptionPeriod,
getProductMetadataFromSubscription
} from 'next-stripe-helper';
Create a subscription for a customer using the Price ID:
const subscription = await createSubscription('customer_id', 'price_id');
Parameters:
customerID
(required): The Customer ID of the user.priceId
(required): The Price ID of the plan.Fetch details of a users first plan using the Customer ID:
const plan = await getUserFirstActivePlan('customer__id');
Parameters:
customerId
(required): The Customer ID of the user.Returns: object {subscription, plan}
Fetch details of a specific subscription using its Stripe ID:
const subscriptionDetails = await getUserSubscription('subscription_id');
Parameters:
subscriptionID
(required): The Stripe ID of the subscription.Update a users existing subscription:
This will use the current payment method by default. Customer must have an existing subscription.
const subscriptionDetails = await updateSubscriptionPlan('subscription_id', 'options object')
Parameters:
subscriptionID
(required): The Stripe ID of the existing subscription.options
: The Stripe api subscription update parameters.Change a users existing subscription first item product/price using its Stripe ID:
Deletes the old one plan/price and adds the new one to the subscription item. This will use the current payment method by default. Customer must have an existing subscription.
const subscriptionDetails = await changeSubscriptionPlan('subscription_id', 'item_id', 'new_price_id')
Parameters:
subscriptionID
(required): The Stripe subscription ID of the existing subscription.item_id
(required): The Stripe Item ID of the plan.new_price_id
(required): The Stripe Price ID of the new plan (price_id).Add a quantity of an item to a users existing subscription:
This will use the current payment method by default. Customer must have an existing subscription.
const subscriptionItem = await addItemToSubscription("subscriptionId", "priceId", "quantity", "proration_behavior")
Parameters:
subscriptionID
(required): The Stripe ID of the existing subscription.priceId
(required): The Stripe Price ID of the item.quantity
(optional): The quantity of the item you are adding. Defaults to 1. Will add to existing quantity or add the item to the subsdcription if it doesnt exist.proration_behavior
(optional): Determines how to handle prorations when the billing cycle changes (e.g., when switching plans, resetting billing_cycle_anchor=now, or starting a trial), or if an item’s quantity changes. The default value is always_invoice. ('create_prorations', 'none', 'always_invoice')Update a subscription items quantity on a users existing subscription:
This will use the current payment method by default. Customer must have an existing subscription.
const updatedSubscriptionItem = await updateItemQuantity("subscriptionItemId", "newQuantity", "proration_behavior")
Parameters:
subscriptionItemId
(required): The ID of the existing ITEM in the subscription (not the subscription ID).newQuantity
(required): The new quanity of the item. Does not add or subtract, it updates the quantity to the new number.proration_behavior
(optional): Determines how to handle prorations when the billing cycle changes (e.g., when switching plans, resetting billing_cycle_anchor=now, or starting a trial), or if an item’s quantity changes. The default value is always_invoice. ('create_prorations', 'none', 'always_invoice')Update a subscription items quantity on a users existing subscription:
This function will ADD an item to a subscription, or UPDATE the quantity if an item is already present on the subscription.
If the quantity is 0, the item will be DELETED from the subscription.
This will use the current payment method by default. Customer must have an existing subscription.
const updatedSubscriptionItem = await updateItemQuantityByPriceId("subscriptionId", "priceId", "newQuantity", "proration_behavior")
Parameters:
subscriptionItemId
(required): The ID of the existing ITEM in the subscription (not the subscription ID).priceId
(required): The Stripe Price ID of the item.newQuantity
(required): The new quanity of the item. Does not add or subtract, it updates the quantity to the new number.proration_behavior
(optional): Determines how to handle prorations when the billing cycle changes (e.g., when switching plans, resetting billing_cycle_anchor=now, or starting a trial), or if an item’s quantity changes. The default value is always_invoice. ('create_prorations', 'none', 'always_invoice')Update a subscription items quantity on a users existing subscription by removing a certain number:
This will use the current payment method by default. Customer must have an existing subscription.
const updatedSubscriptionItem = await removeItemsFromSubscription("subscriptionItemId", "removeQuantity", "proration_behavior")
Parameters:
subscriptionItemId
(required): The ID of the existing ITEM in the subscription (not the subscription ID).removeQuantity
(required): The number you wish to remove of item. Subtracts the given quantity from the existing amount.proration_behavior
(optional): Determines how to handle prorations when the billing cycle changes (e.g., when switching plans, resetting billing_cycle_anchor=now, or starting a trial), or if an item’s quantity changes. The default value is always_invoice. ('create_prorations', 'none', 'always_invoice')Update a subscription items quantity on a users existing subscription by removing a certain number:
This will use the current payment method by default. Customer must have an existing subscription.
const updatedSubscriptionItem = await removeItemsByPriceId("subscriptionId", "priceId", "removeQuantity", "proration_behavior")
Parameters:
subscriptionId
(required): The subscriptionID of the subscription.priceId
(required): The Stripe Price ID of the item.removeQuantity
(required): The number you wish to remove of item. Subtracts the given quantity from the existing amount.proration_behavior
(optional): Determines how to handle prorations when the billing cycle changes (e.g., when switching plans, resetting billing_cycle_anchor=now, or starting a trial), or if an item’s quantity changes. The default value is always_invoice. ('create_prorations', 'none', 'always_invoice')Retrieve a subscription item on a users existing subscription by using the price id:
Customer must have an existing subscription. No item returns null.
const subscriptionItem = await getASubscriptionItemByPriceId("subscriptionId", "priceId")
Parameters:
subscriptionId
(required): The subscriptionID of the subscription.priceId
(required): The Stripe Price ID of the item.Retrieve a list of all the subscription items on a users existing subscription:
Customer must have an existing subscription.
const subscriptionItem = await getASubscriptionItemByPriceId("subscriptionID")
Parameters:
subscriptionID
(required): The ID of the users subscription.To fetch comprehensive details about a subscription, including its associated price metadata:
const detailedInfo = await getUserSubscriptionDetails('subscription_id');
Parameters:
subscriptionID
(required): The Stripe ID of the subscription.If you wish to modify the metadata of a subscription:
const updatedSubscription = await updateUserSubscriptionMetadata('subscription_id', { key: 'newValue' });
Parameters:
subscriptionID
(required): The Stripe ID of the subscription.metadata
(required): An object containing key-value pairs for metadata updates.Retrieve a list of all active subscriptions associated with a particular customer:
const activeSubscriptions = await listUserSubscriptions('customer_id');
Parameters:
customerID
(required): The Stripe ID of the customer.To cancel a subscription:
const cancelledSubscription = await cancelUserSubscription('subscription_id');
Parameters:
subscriptionID
(required): The Stripe ID of the subscription.To cancel a subscription:
const periodData = await getSubscriptionPeriod('subscription_id');
Parameters:
subscriptionID
(required): The Stripe ID of the subscription.Returns:
start
period start JS Dateend
period end JS DateIn the Next.js 13 environment, API routes provide a solution to build your backend functionality. The next-stripe-helper
comes equipped with a webhook handler specifically designed for easy integration with Next.js API routes.
If you add the webhookHandler to an api route, your Database can automatically stay in sync with Stripe Products, Prices, and Subscriptions.
First create your DB functions (upsertProductRecord, upsertPriceRecord, manageSubscriptionStatusChange, manageCustomerDetailsChange), then use them with the webhookHandler function in an api endpoint.
async function upsertProductRecord(product)
Returns:
product
Stripe Product Dataasync function upsertPriceRecord(price)
Returns:
price
Stripe Price Dataasync function manageSubscriptionStatusChange(subscriptionId, customerId, isCreated)
Returns:
subscriptionId
string - Stripe Subcription IDcustomerId
string - Stripe Customer IDisCreated
string - is newly createdasync function manageCustomerDetailsChange(stripeCustomer, eventType)
Returns:
stripeCustomer
object - Stripe Customer dataeventType
string - 'created', 'updated', 'deleted'You can find example functions below that use MongoDb, but it can be used with any DB type.
First, you'll need to import the webhook handler into your API route:
import { webhookHandler } from 'stripe-next-helper';
Then, set up an API route in Next.js to handle the Stripe webhook:
// pages/api/stripe/webhook/route.js
import { webhookHandler } from 'next-stripe-helper';
import {
manageSubscriptionStatusChange,
manageCustomerDetailsChange,
upsertPriceRecord,
upsertProductRecord
} from '@/lib/stripe';
import { headers } from "next/headers"
export async function POST(req) {
try {
const body = await req.text();
const signature = headers().get("Stripe-Signature");
if (!signature) {
throw new Error('Stripe signature missing from headers');
}
await webhookHandler(
upsertProductRecord,
upsertPriceRecord,
manageSubscriptionStatusChange,
manageCustomerDetailsChange,
{ body, signature }
);
return new Response(null, { status: 200 });
} catch (error) {
console.error(error);
return new Response(JSON.stringify({ error: error.message }), { status: 500 });
}
}
Examples of Webhook helper functions you could use with a MongoDB Database.
import { ObjectId } from "mongodb";
import Stripe from "stripe";
import clientPromise from "@/lib/mongodb";
import { convertToNumberOrBoolean } from "./utils";
import { getCustomer } from "next-stripe-helper";
const stripeSecret = process.env.STRIPE_SECRET_LIVE || process.env.STRIPE_SECRET || "";
/**
* Initialize the Stripe SDK with the secret key.
*/
const stripe = new Stripe(stripeSecret, {
apiVersion: "2023-08-16",
});
const dbName = process.env.MONGODB_DB;
export const getActiveProductsWithPrices = async () => {
try {
const client = await clientPromise;
const data = await client
.db(dbName)
.collection("products")
.aggregate([
{
$lookup: {
from: "prices",
localField: "_id",
foreignField: "product_id",
as: "prices",
},
},
{ $match: { active: true, "prices.active": true } },
{ $sort: { "metadata.index": 1, "prices.unit_amount": 1 } },
])
.toArray();
return data || [];
} catch (error) {
console.log(error.message);
return [];
}
};
export const getActiveApiProductsWithPrices = async () => {
try {
const client = await clientPromise;
const data = await client
.db(dbName)
.collection("products")
.aggregate([
{
$lookup: {
from: "prices",
localField: "_id",
foreignField: "product_id",
as: "prices",
},
},
{
$match: {
active: true,
"prices.active": true,
"metadata.is_api_product": "true",
},
},
{ $sort: { "metadata.index": 1, "prices.unit_amount": 1 } },
])
.toArray();
return data || [];
} catch (error) {
console.log(error.message);
return [];
}
};
export const upsertProductRecord = async (product) => {
const productData = {
_id: product.id,
active: product.active,
name: product.name,
description: product.description ?? undefined,
image: product.images?.[0] ?? null,
metadata: product.metadata,
};
const client = await clientPromise;
const result = await client
.db(dbName)
.collection("products")
.updateOne({ _id: productData._id }, { $set: productData }, { upsert: true });
if (result.upsertedCount || result.modifiedCount) {
console.log(`Product inserted/updated: ${product.id}`);
}
};
export const upsertPriceRecord = async (price) => {
const priceData = {
_id: price.id,
product_id: typeof price.product === "string" ? price.product : "",
active: price.active,
currency: price.currency,
description: price.nickname ?? undefined,
type: price.type,
unit_amount: price.unit_amount ?? undefined,
interval: price.recurring?.interval,
interval_count: price.recurring?.interval_count,
trial_period_days: price.recurring?.trial_period_days,
metadata: price.metadata,
};
const client = await clientPromise;
const result = await client
.db(dbName)
.collection("prices")
.updateOne({ _id: priceData._id }, { $set: priceData }, { upsert: true });
if (result.upsertedCount || result.modifiedCount) {
console.log(`Price inserted/updated: ${price.id}`);
}
};
const copyBillingDetailsToCustomer = async (uuid, payment_method) => {
const customer = payment_method.customer;
const { name, phone, address } = payment_method.billing_details;
if (!name || !phone || !address) return;
//optionally you can add illing details to your stripe customer
await stripe.customers.update(customer, { name, phone, address });
const client = await clientPromise;
const result = await client
.db(dbName)
.collection("users")
.findOneAndUpdate(
{ _id: new ObjectId(uuid) },
{
$set: {
billing_address: { ...address },
payment_method: { ...payment_method[payment_method.type] },
},
},
{ returnDocument: "after" }
);
if (!result) {
console.error("Error updating user billing details");
throw new Error("Error updating user billing details");
}
};
export const checkSubscriptionLimit = async (collectionKey, metadataKey, userId) => {
if (!collectionKey || !metadataKey || !userId) return;
const client = await clientPromise;
const subscription = await client
.db(dbName)
.collection("subscriptions")
.findOne({ user_id: new ObjectId(userId), status: "active" });
if (!subscription) {
return false;
}
const product = await client
.db(dbName)
.collection("products")
.findOne({ _id: subscription.items[0].product_id });
if (!product) {
return false;
}
let limit = convertToNumberOrBoolean(product.metadata[metadataKey]);
let result;
let allowed = false;
if (typeof limit !== "boolean") {
const count = await client
.db(dbName)
.collection(collectionKey)
.countDocuments({ userId: new ObjectId(userId) });
allowed = count < limit;
result = count;
} else {
allowed = limit;
limit = 0;
result = 0;
}
return { allowed, result, limit };
};
export const manageSubscriptionStatusChange = async (
subscriptionId,
customerId,
createAction = false
) => {
const customer = await getCustomer(customerId);
const client = await clientPromise;
const user = await client.db(dbName).collection("users").findOne({ email: customer.email });
if (!user) {
console.log("manageSubscriptionStatusChange: Customer not found. id:", customerId);
throw new Error("Customer not found");
} else {
console.log("manageSubscriptionStatusChange: Customer found: ", customerId);
}
const uuid = user._id.toString();
let subscription;
try {
subscription = await stripe.subscriptions.retrieve(subscriptionId, {
expand: ["default_payment_method"],
});
} catch (error) {
console.log("manageSubscriptionStatusChange: Stripe Error: ", error);
throw new Error("Stripe error retrieving subscription in manageSubscriptionStatusChange.");
}
if (!subscription) {
throw new Error(
"Stripe error retrieving subscription in manageSubscriptionStatusChange.",
subscription
);
} else {
console.log("manageSubscriptionStatusChange: Stripe subscription found.", subscription.id);
}
const subscriptionItems = subscription.items.data.map((item) => ({
price_id: item.price.id,
product_id: item.price.product,
quantity: item.quantity,
}));
const existingSubscription = await client
.db(dbName)
.collection("subscriptions")
.findOne({ user_id: new ObjectId(uuid) });
let subscriptionData;
if (!existingSubscription) {
// If the subscription doesn't exist, include the _id field for the new document
subscriptionData = {
_id: subscription.id,
user_id: new ObjectId(uuid),
team_id: new ObjectId(subscription.metadata.team_id),
metadata: subscription.metadata,
status: subscription.status,
price_id: subscription.items.data[0].price.id,
items: subscriptionItems,
cancel_at_period_end: subscription.cancel_at_period_end,
cancel_at: subscription.cancel_at ? new Date(subscription.cancel_at * 1000) : null,
canceled_at: subscription.canceled_at ? new Date(subscription.canceled_at * 1000) : null,
current_period_start: new Date(subscription.current_period_start * 1000),
current_period_end: new Date(subscription.current_period_end * 1000),
created: new Date(subscription.created * 1000),
ended_at: subscription.ended_at ? new Date(subscription.ended_at * 1000) : null,
trial_start: subscription.trial_start ? new Date(subscription.trial_start * 1000) : null,
trial_end: subscription.trial_end ? new Date(subscription.trial_end * 1000) : null,
};
} else {
// If the subscription exists, omit the _id field to avoid the immutable field error
subscriptionData = {
user_id: new ObjectId(uuid),
team_id: new ObjectId(subscription.metadata.team_id),
metadata: subscription.metadata,
status: subscription.status,
price_id: subscription.items.data[0].price.id,
items: subscriptionItems,
cancel_at_period_end: subscription.cancel_at_period_end,
cancel_at: subscription.cancel_at ? new Date(subscription.cancel_at * 1000) : null,
canceled_at: subscription.canceled_at ? new Date(subscription.canceled_at * 1000) : null,
current_period_start: new Date(subscription.current_period_start * 1000),
current_period_end: new Date(subscription.current_period_end * 1000),
created: new Date(subscription.created * 1000),
ended_at: subscription.ended_at ? new Date(subscription.ended_at * 1000) : null,
trial_start: subscription.trial_start ? new Date(subscription.trial_start * 1000) : null,
trial_end: subscription.trial_end ? new Date(subscription.trial_end * 1000) : null,
};
}
const result = await client
.db(dbName)
.collection("subscriptions")
.updateOne({ user_id: new ObjectId(uuid) }, { $set: subscriptionData }, { upsert: true });
if (result.upsertedCount || result.modifiedCount) {
console.log(`Inserted/updated subscription [${subscriptionId}] for user [${uuid}]`);
} else {
console.error(
`manageSubscriptionStatusChange: Subscription for user [${uuid}] was not updated.`,
result
);
}
if (createAction && subscription.default_payment_method && uuid) {
await copyBillingDetailsToCustomer(uuid, subscription.default_payment_method);
}
};
export async function manageCustomerDetailsChange(stripeCustomer, eventType) {
if(eventType !== 'deleted'){
try {
const client = await clientPromise;
await client.db(dbName).collection("users").findOneAndUpdate({ email: stripeCustomer.email },{
$set: {customerId: stripeCustomer.id}
});
} catch (error) {
throw error
}
}
}
Environment Variables: Ensure you have set the STRIPE_WEBHOOK_SECRET_LIVE
or STRIPE_WEBHOOK_SECRET
environment variables in your .env.local
(or other environment-specific .env
files). With Next.js, you can access these environment variables using process.env
. Remember best practice is giving the api key the least amount of access needed.
Stripe Dashboard: Configure your Stripe dashboard to send webhooks to https://your-domain.com/api/stripe-webhook
.
As with the general use-case, you need to provide the upsertProduct
, upsertPrice
, manageSubscriptionChange
, and manageCustomerDetailsChange
callback functions. These functions will handle the various events as they occur on Stripe.
Always handle errors gracefully. The provided webhook handler has built-in error handling, but you may want to extend or customize this for your specific needs.
When deploying your Next.js application, make sure to include your Stripe webhook secret in your production environment variables or secrets management solution. Never expose the secret or api keys.
All utility functions incorporate internal error handling. You can catch these errors using try/catch. Ensure your project provides meaningful and appropriate error handling based on your application's needs.
Feel free to contribute to the project by submitting a pull request or raising an issue. Next Stripe Helper Repo
MIT License
FAQs
Easily add Stripe boilerplate code to Nextjs, like webhook handling, and subscription updates. This package provides a thin wrapper around the Stripe API, and makes integrating Stripe and NextJS a lot faster!
The npm package next-stripe-helper receives a total of 179 weekly downloads. As such, next-stripe-helper popularity was classified as not popular.
We found that next-stripe-helper demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
PyPI now allows maintainers to archive projects, improving security and helping users make informed decisions about their dependencies.
Research
Security News
Malicious npm package postcss-optimizer delivers BeaverTail malware, targeting developer systems; similarities to past campaigns suggest a North Korean connection.
Security News
CISA's KEV data is now on GitHub, offering easier access, API integration, commit history tracking, and automated updates for security teams and researchers.