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 think wrapper around the Stripe API.
A utility module to simplify Stripe checkout session interactions in a Next.js environment.
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 the Next.js ecosystem.
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). With Next.js, you can access these environment variables using process.env
.
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
.
# 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. 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 Checkout Session
Create a checkout session with Stripe.
const session = await createCheckoutSession(
'https://your-success-url.com',
'https://your-cancel-url.com',
itemsArray, // Your array of line items
'subscription',
'customer_id',
{} // Additional optional parameters
);
Parameters:
successUrl
(required): The URL to redirect upon successful payment.cancelUrl
(optional, default ""
): The URL to redirect upon payment cancellation.itemsArray
(required): An array of line items for the checkout.mode
(optional, default subscription
): The mode of the checkout session (subscription
or payment
).customerId
(required): The Stripe customer ID.additionalParams
(optional, default {}
): Any other additional parameters.Create Checkout Session for Saving Card
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 Stripe Customer
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.Retrieve Customer Details
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 a Customer's Details
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.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_id', 'https://your-return-url.com');
Parameters:
customer
(required): The Stripe customer ID.returnUrl
(required): The URL where the user will be redirected after exiting the billing portal.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.
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:
import {
getUserSubscription,
getUserSubscriptionDetails,
updateUserSubscriptionMetadata,
listUserSubscriptions,
cancelUserSubscription
} from 'next-stripe-helper';
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.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.In the Next.js 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.
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 'stripe-next-helper';
// Import or define upsertProduct, upsertPrice, and manageSubscriptionChange here. These are functions you create to handle the Database updates.
// Ensure you have error handling and necessary functions.
export async function POST(req) {
try {
// Validate if the request is legitimate (e.g., you might want to check if there's a user session, but for a stripe webhook, it might not be necessary.)
const handleWebhook = webhookHandler(
async (product) => {
// upsertProduct logic here
},
async (price) => {
// upsertPrice logic here
},
async (subscriptionId, customerId, isNew) => {
// manageSubscriptionChange logic here
}
);
// Call the handler with the adapted request/response format
const response = await handleWebhook(req, {
status: (statusCode) => ({ statusCode }),
json: (body) => new Response(JSON.stringify(body)),
setHeader: (name, value) => {}, // You can extend this if needed
end: (text) => new Response(text)
});
return response;
} catch (error) {
// Handle your errors. If it's a validation error, return a 422 status. Otherwise, return a 500 status.
if (/* it's a validation error */) {
return new Response(JSON.stringify(/* error details */), { status: 422 });
}
return new Response(null, { status: 500 });
}
}
Here's an example of Stripe Helper Functions for a MongoDB Database.
import clientPromise from '@/lib/mongodb'
import { ObjectId } from 'mongodb'
export const getActiveProductsWithPrices = async () => {
try {
const data = await clientPromise
.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 data = await clientPromise
.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 result = await clientPromise
.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 result = await clientPromise
.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
await stripe.customers.update(customer, { name, phone, address })
const { db } = await connectToDatabase()
const result = await db.collection('users').findOneAndUpdate(
{ _id: uuid },
{
$set: {
billing_address: { ...address },
payment_method: { ...payment_method[payment_method.type] },
},
},
{ returnOriginal: false }
)
if (!result.value) {
throw new Error('Error updating user billing details')
}
}
export const manageSubscriptionStatusChange = async (
subscriptionId,
customerId,
createAction = false
) => {
console.log('manageSubscriptionStatusChange: Started.')
console.log(customerId)
const customerData = await clientPromise
.collection('customers')
.findOne({ stripe_customer_id: customerId })
if (!customerData) {
console.log(
'manageSubscriptionStatusChange: Customer not found. id:',
customerId
)
throw new Error('Customer not found')
} else {
console.log(
'manageSubscriptionStatusChange: Customer found: ',
customerData
)
}
const uuid = customerData._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,
}))
// console.log('subscriptionItems',subscriptionItems)
const 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,
}
const result = await clientPromise
.collection('subscriptions')
.updateOne(
{ user_id: new ObjectId(uuid), _id: subscription.id },
{ $set: subscriptionData },
{ upsert: true }
)
console.log('manageSubscriptionStatusChange: update: ', result)
if (result.upsertedCount || result.modifiedCount) {
console.log(
`Inserted/updated subscription [${subscription.id}] for user [${uuid}]`
)
} else {
console.log(
`manageSubscriptionStatusChange: Subscription for user [${uuid}] was not updated.`,
result
)
}
if (createAction && subscription.default_payment_method && uuid) {
await copyBillingDetailsToCustomer(
uuid,
subscription.default_payment_method
)
}
}
To ensure that your local database reflects your current Stripe products, you can utilize the provided syncing utility. First create DB functions, then use them with the syncWithStripe function in an api endpoint. You can then create a simple button in your app that triggers the sync function.
Set up an API route in your Next.js project that uses the sync utility:
// pages/api/syncStripe.js
import { syncWithStripe } from 'next-stripe-helper';
const dbOperations = {
getAllLocalProducts: async () => { /* fetch all products from the DB */ },
addProduct: async (product) => { /* add new product to the DB */ },
updateProduct: async (product) => { /* update existing product in the DB */ },
};
export default async (req, res) => {
if (req.method === 'POST') {
const result = await syncWithStripe(dbOperations);
if (result.success) {
res.status(200).json({ message: result.message });
} else {
res.status(500).json({ error: result.error, details: result.details });
}
} else {
res.status(405).end(); // Method Not Allowed
}
};
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
.
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
, and manageSubscriptionChange
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.
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.
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.