mirror of
https://github.com/documenso/documenso.git
synced 2025-11-14 08:42:12 +10:00
feat: add multi subscription support (#734)
## Description Previously we assumed that there can only be 1 subscription per user. However, that will soon no longer the case with the introduction of the Teams subscription. This PR will apply the required migrations to support multiple subscriptions. ## Changes Made - Updated the Prisma schema to allow for multiple `Subscriptions` per `User` - Added a Stripe `customerId` field to the `User` model - Updated relevant billing sections to support multiple subscriptions ## Testing Performed - Tested running the Prisma migration on a demo database created on the main branch Will require a lot of additional testing. ## Checklist - [ ] I have tested these changes locally and they work as expected. - [ ] I have added/updated tests that prove the effectiveness of these changes. - [X] I have followed the project's coding style guidelines. ## Additional Notes Added the following custom SQL statement to the migration: > DELETE FROM "Subscription" WHERE "planId" IS NULL OR "priceId" IS NULL; Prior to deployment this will require changes to Stripe products: - Adding `type` meta attribute --------- Co-authored-by: Lucas Smith <me@lucasjamessmith.me>
This commit is contained in:
@ -75,23 +75,23 @@ export const stripeWebhookHandler = async (
|
||||
|
||||
// Finally, attempt to get the user ID from the subscription within the database.
|
||||
if (!userId && customerId) {
|
||||
const result = await prisma.subscription.findFirst({
|
||||
const result = await prisma.user.findFirst({
|
||||
select: {
|
||||
userId: true,
|
||||
id: true,
|
||||
},
|
||||
where: {
|
||||
customerId,
|
||||
},
|
||||
});
|
||||
|
||||
if (!result?.userId) {
|
||||
if (!result?.id) {
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: 'User not found',
|
||||
});
|
||||
}
|
||||
|
||||
userId = result.userId;
|
||||
userId = result.id;
|
||||
}
|
||||
|
||||
const subscriptionId =
|
||||
@ -124,23 +124,23 @@ export const stripeWebhookHandler = async (
|
||||
? subscription.customer
|
||||
: subscription.customer.id;
|
||||
|
||||
const result = await prisma.subscription.findFirst({
|
||||
const result = await prisma.user.findFirst({
|
||||
select: {
|
||||
userId: true,
|
||||
id: true,
|
||||
},
|
||||
where: {
|
||||
customerId,
|
||||
},
|
||||
});
|
||||
|
||||
if (!result?.userId) {
|
||||
if (!result?.id) {
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: 'User not found',
|
||||
});
|
||||
}
|
||||
|
||||
await onSubscriptionUpdated({ userId: result.userId, subscription });
|
||||
await onSubscriptionUpdated({ userId: result.id, subscription });
|
||||
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
@ -182,23 +182,23 @@ export const stripeWebhookHandler = async (
|
||||
});
|
||||
}
|
||||
|
||||
const result = await prisma.subscription.findFirst({
|
||||
const result = await prisma.user.findFirst({
|
||||
select: {
|
||||
userId: true,
|
||||
id: true,
|
||||
},
|
||||
where: {
|
||||
customerId,
|
||||
},
|
||||
});
|
||||
|
||||
if (!result?.userId) {
|
||||
if (!result?.id) {
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: 'User not found',
|
||||
});
|
||||
}
|
||||
|
||||
await onSubscriptionUpdated({ userId: result.userId, subscription });
|
||||
await onSubscriptionUpdated({ userId: result.id, subscription });
|
||||
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
@ -233,23 +233,23 @@ export const stripeWebhookHandler = async (
|
||||
});
|
||||
}
|
||||
|
||||
const result = await prisma.subscription.findFirst({
|
||||
const result = await prisma.user.findFirst({
|
||||
select: {
|
||||
userId: true,
|
||||
id: true,
|
||||
},
|
||||
where: {
|
||||
customerId,
|
||||
},
|
||||
});
|
||||
|
||||
if (!result?.userId) {
|
||||
if (!result?.id) {
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: 'User not found',
|
||||
});
|
||||
}
|
||||
|
||||
await onSubscriptionUpdated({ userId: result.userId, subscription });
|
||||
await onSubscriptionUpdated({ userId: result.id, subscription });
|
||||
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { Stripe } from '@documenso/lib/server-only/stripe';
|
||||
import type { Stripe } from '@documenso/lib/server-only/stripe';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { SubscriptionStatus } from '@documenso/prisma/client';
|
||||
|
||||
@ -7,12 +7,9 @@ export type OnSubscriptionDeletedOptions = {
|
||||
};
|
||||
|
||||
export const onSubscriptionDeleted = async ({ subscription }: OnSubscriptionDeletedOptions) => {
|
||||
const customerId =
|
||||
typeof subscription.customer === 'string' ? subscription.customer : subscription.customer?.id;
|
||||
|
||||
await prisma.subscription.update({
|
||||
where: {
|
||||
customerId,
|
||||
planId: subscription.id,
|
||||
},
|
||||
data: {
|
||||
status: SubscriptionStatus.INACTIVE,
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { match } from 'ts-pattern';
|
||||
|
||||
import { Stripe } from '@documenso/lib/server-only/stripe';
|
||||
import type { Stripe } from '@documenso/lib/server-only/stripe';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { SubscriptionStatus } from '@documenso/prisma/client';
|
||||
|
||||
@ -13,9 +13,6 @@ export const onSubscriptionUpdated = async ({
|
||||
userId,
|
||||
subscription,
|
||||
}: OnSubscriptionUpdatedOptions) => {
|
||||
const customerId =
|
||||
typeof subscription.customer === 'string' ? subscription.customer : subscription.customer?.id;
|
||||
|
||||
const status = match(subscription.status)
|
||||
.with('active', () => SubscriptionStatus.ACTIVE)
|
||||
.with('past_due', () => SubscriptionStatus.PAST_DUE)
|
||||
@ -23,22 +20,22 @@ export const onSubscriptionUpdated = async ({
|
||||
|
||||
await prisma.subscription.upsert({
|
||||
where: {
|
||||
customerId,
|
||||
planId: subscription.id,
|
||||
},
|
||||
create: {
|
||||
customerId,
|
||||
status: status,
|
||||
planId: subscription.id,
|
||||
priceId: subscription.items.data[0].price.id,
|
||||
periodEnd: new Date(subscription.current_period_end * 1000),
|
||||
userId,
|
||||
cancelAtPeriodEnd: subscription.cancel_at_period_end,
|
||||
},
|
||||
update: {
|
||||
customerId,
|
||||
status: status,
|
||||
planId: subscription.id,
|
||||
priceId: subscription.items.data[0].price.id,
|
||||
periodEnd: new Date(subscription.current_period_end * 1000),
|
||||
cancelAtPeriodEnd: subscription.cancel_at_period_end,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user