next-stripe-helper
Advanced tools
Comparing version 1.1.25 to 1.1.26
import Stripe from 'stripe'; | ||
export interface ManageSubscriptionChangeFunction { | ||
(subscriptionId: string, customerId: string, client_reference_id: string | null, isCreated: boolean): Promise<void>; | ||
(subscriptionId: string, customerId: string, isCreated: boolean): Promise<void>; | ||
} | ||
export interface ManageCustomerDetailsChangeFunction { | ||
(customerId: string, paymentMethodId: string | Stripe.PaymentMethod | null, client_reference_id: string | null): Promise<void>; | ||
(customer: Stripe.Customer | Stripe.DeletedCustomer): Promise<void>; | ||
} | ||
export declare function handleCheckoutSessionCompleted(checkoutSession: Stripe.Checkout.Session, manageSubscriptionChange: ManageSubscriptionChangeFunction, manageCustomerDetailsChange: ManageCustomerDetailsChangeFunction): Promise<void>; |
@@ -6,3 +6,2 @@ "use strict"; | ||
async function handleCheckoutSessionCompleted(checkoutSession, manageSubscriptionChange, manageCustomerDetailsChange) { | ||
const client_reference_id = checkoutSession.client_reference_id; | ||
if (checkoutSession.mode === 'subscription') { | ||
@@ -14,3 +13,3 @@ const subscriptionId = checkoutSession.subscription; | ||
: checkoutSession.customer.id; // Assuming checkoutSession.customer is a Customer object | ||
await manageSubscriptionChange(subscriptionId, customerId, client_reference_id, true); | ||
await manageSubscriptionChange(subscriptionId, customerId, true); | ||
} | ||
@@ -34,3 +33,3 @@ else { | ||
// Set the payment method as the default for the customer | ||
await stripe_1.stripe.customers.update(checkoutSession.customer, { | ||
const customer = await stripe_1.stripe.customers.update(checkoutSession.customer, { | ||
invoice_settings: { | ||
@@ -40,3 +39,3 @@ default_payment_method: setupIntent.payment_method, | ||
}); | ||
await manageCustomerDetailsChange(checkoutSession.customer, setupIntent.payment_method, client_reference_id); | ||
await manageCustomerDetailsChange(customer); | ||
} | ||
@@ -43,0 +42,0 @@ catch (error) { |
@@ -55,2 +55,7 @@ "use strict"; | ||
break; | ||
case 'customer.created': | ||
case 'customer.deleted': | ||
case 'customer.updated': | ||
await manageCustomerDetailsChange(stripeEvent.data.object); | ||
break; | ||
case 'customer.subscription.created': | ||
@@ -61,8 +66,7 @@ case 'customer.subscription.updated': | ||
if (typeof subscription.customer === 'string') { | ||
await manageSubscriptionChange(subscription.id, subscription.customer, null, stripeEvent.type === 'customer.subscription.created'); | ||
await manageCustomerDetailsChange(subscription.customer, subscription.default_payment_method, null); | ||
await manageSubscriptionChange(subscription.id, subscription.customer, stripeEvent.type === 'customer.subscription.created'); | ||
} | ||
else if (subscription.customer && 'id' in subscription.customer) { | ||
await manageSubscriptionChange(subscription.id, subscription.customer.id, null, stripeEvent.type === 'customer.subscription.created'); | ||
await manageCustomerDetailsChange(subscription.customer.id, subscription.default_payment_method, null); | ||
await manageSubscriptionChange(subscription.id, subscription.customer.id, stripeEvent.type === 'customer.subscription.created'); | ||
await manageCustomerDetailsChange(subscription.customer); | ||
} | ||
@@ -69,0 +73,0 @@ else { |
{ | ||
"name": "next-stripe-helper", | ||
"version": "1.1.25", | ||
"version": "1.1.26", | ||
"description": "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!", | ||
@@ -5,0 +5,0 @@ "main": "dist/index.js", |
498
README.md
@@ -454,273 +454,291 @@ # next-stripe-helper | ||
Examples of Webhook functions you could use with a MongoDB Database. | ||
Examples of Webhook helper functions you could use with a MongoDB Database. | ||
``` javascript | ||
import clientPromise from '@/lib/mongodb'; //assuming you have a funciton to get your clientPromise for your DB. | ||
import { ObjectId } from 'mongodb'; | ||
import { ObjectId } from "mongodb"; | ||
import Stripe from "stripe"; | ||
const dbName = process.env.MONGODB_DB; //assuming you are using the db name. | ||
import clientPromise from "@/lib/mongodb"; //assuming you have a function to get your DB clientPromise | ||
import { convertToNumberOrBoolean } from "./utils"; //simple utility to convert a string to a number or boolean or remain string; | ||
//this will GET the active Stripe products and related Prices in your DB | ||
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() | ||
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 [] | ||
} | ||
} | ||
return data || []; | ||
} catch (error) { | ||
console.log(error.message); | ||
return []; | ||
} | ||
}; | ||
//this will GET the active Stripe products in your DB | ||
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() | ||
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 [] | ||
} | ||
} | ||
return data || []; | ||
} catch (error) { | ||
console.log(error.message); | ||
return []; | ||
} | ||
}; | ||
//this will keep the active Stripe products updated in your DB | ||
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 } | ||
) | ||
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}`) | ||
} | ||
} | ||
if (result.upsertedCount || result.modifiedCount) { | ||
console.log(`Product inserted/updated: ${product.id}`); | ||
} | ||
}; | ||
//this will keep the active Stripe prices updated in your DB | ||
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 } | ||
) | ||
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}`) | ||
} | ||
} | ||
if (result.upsertedCount || result.modifiedCount) { | ||
console.log(`Price inserted/updated: ${price.id}`); | ||
} | ||
}; | ||
//this will keep your user billing details updated in your DB | ||
const copyBillingDetailsToCustomer = async (uuid, payment_method) => { | ||
const customer = payment_method.customer | ||
const { name, phone, address } = payment_method.billing_details | ||
if (!name || !phone || !address) return | ||
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 client = await clientPromise | ||
const result = await client.db(dbName).collection('users').findOneAndUpdate( | ||
{ _id: uuid }, | ||
{ | ||
$set: { | ||
billing_address: { ...address }, | ||
payment_method: { ...payment_method[payment_method.type] }, | ||
}, | ||
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] }, | ||
}, | ||
{ returnOriginal: false } | ||
) | ||
if (!result.value) { | ||
throw new Error('Error updating user billing details') | ||
} | ||
} | ||
}, | ||
{ returnDocument: "after" } | ||
); | ||
if (!result) { | ||
console.error("Error updating user billing details"); | ||
throw new Error("Error updating user billing details"); | ||
} | ||
}; | ||
//this will keep the customer subscriptions updated in your DB | ||
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 | ||
subscriptionId, | ||
customerId, | ||
createAction = false | ||
) => { | ||
console.log('manageSubscriptionStatusChange: Started.') | ||
console.log(customerId) | ||
const client = await clientPromise | ||
const customerData = await client | ||
.db(dbName) | ||
.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 customer = await getCustomer(customerId); | ||
const uuid = customerData._id.toString() | ||
const client = await clientPromise; | ||
const user = await client.db(dbName).collection("users").findOne({ email: customer.email }); | ||
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 (!user) { | ||
console.log("manageSubscriptionStatusChange: Customer not found. id:", customerId); | ||
throw new Error("Customer not found"); | ||
} else { | ||
console.log("manageSubscriptionStatusChange: Customer found and Updated: ", customerId); | ||
} | ||
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 uuid = user._id.toString(); | ||
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 client | ||
.db(dbName) | ||
.collection('subscriptions') | ||
.updateOne( | ||
{ user_id: new ObjectId(uuid), _id: subscription.id }, | ||
{ $set: subscriptionData }, | ||
{ upsert: true } | ||
) | ||
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 (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 (!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, | ||
})); | ||
if (createAction && subscription.default_payment_method && uuid) { | ||
await copyBillingDetailsToCustomer( | ||
uuid, | ||
subscription.default_payment_method | ||
) | ||
} | ||
} | ||
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 client | ||
.db(dbName) | ||
.collection("subscriptions") | ||
.updateOne({ user_id: new ObjectId(uuid) }, { $set: subscriptionData }, { upsert: true }); | ||
//this will keep the customer id and thier paymentID updated in your DB | ||
export async function manageCustomerDetailsChange( | ||
customerId: string, | ||
defaultPaymentMethodId: string, | ||
) { | ||
// Your code to update the user document in your database | ||
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) { | ||
try { | ||
const client = await clientPromise; | ||
await client.db(dbName).collection("users").findOneAndUpdate({ email: stripeCustomer.email },{ | ||
$set: {customerId: stripeCustomer.id} | ||
}); | ||
} catch (error) { | ||
throw error | ||
} | ||
} | ||
@@ -727,0 +745,0 @@ ``` |
@@ -5,7 +5,7 @@ import Stripe from 'stripe'; | ||
export interface ManageSubscriptionChangeFunction { | ||
(subscriptionId: string, customerId: string, client_reference_id: string | null, isCreated: boolean): Promise<void>; | ||
(subscriptionId: string, customerId: string, isCreated: boolean): Promise<void>; | ||
} | ||
export interface ManageCustomerDetailsChangeFunction { | ||
(customerId: string, paymentMethodId: string | Stripe.PaymentMethod | null, client_reference_id: string | null): Promise<void>; | ||
(customer: Stripe.Customer | Stripe.DeletedCustomer): Promise<void>; | ||
} | ||
@@ -18,3 +18,2 @@ | ||
): Promise<void> { | ||
const client_reference_id = checkoutSession.client_reference_id; | ||
if (checkoutSession.mode === 'subscription') { | ||
@@ -29,3 +28,2 @@ const subscriptionId = checkoutSession.subscription as string; | ||
customerId, | ||
client_reference_id, | ||
true | ||
@@ -55,3 +53,3 @@ ); | ||
// Set the payment method as the default for the customer | ||
await stripe.customers.update(checkoutSession.customer, { | ||
const customer = await stripe.customers.update(checkoutSession.customer, { | ||
invoice_settings: { | ||
@@ -62,7 +60,3 @@ default_payment_method: setupIntent.payment_method, | ||
await manageCustomerDetailsChange( | ||
checkoutSession.customer, | ||
setupIntent.payment_method, | ||
client_reference_id | ||
); | ||
await manageCustomerDetailsChange(customer); | ||
} catch (error) { | ||
@@ -69,0 +63,0 @@ console.error("Failed to update customer's default payment method:", error); |
@@ -78,2 +78,7 @@ import Stripe from 'stripe'; | ||
break; | ||
case 'customer.created': | ||
case 'customer.deleted': | ||
case 'customer.updated': | ||
await manageCustomerDetailsChange(stripeEvent.data.object as Stripe.Customer); | ||
break; | ||
case 'customer.subscription.created': | ||
@@ -87,10 +92,5 @@ case 'customer.subscription.updated': | ||
subscription.customer, | ||
null, | ||
stripeEvent.type === 'customer.subscription.created' | ||
); | ||
await manageCustomerDetailsChange( | ||
subscription.customer, | ||
subscription.default_payment_method, | ||
null | ||
); | ||
} else if (subscription.customer && 'id' in subscription.customer) { | ||
@@ -100,10 +100,5 @@ await manageSubscriptionChange( | ||
subscription.customer.id, | ||
null, | ||
stripeEvent.type === 'customer.subscription.created' | ||
); | ||
await manageCustomerDetailsChange( | ||
subscription.customer.id, | ||
subscription.default_payment_method, | ||
null | ||
); | ||
await manageCustomerDetailsChange(subscription.customer); | ||
} else { | ||
@@ -110,0 +105,0 @@ console.error('Error: Customer ID is not a string or Customer object on subscription deletion'); |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
772
111945
1681