fix: restrict individual plans to upgrade only (#1900)

Prevent users from creating a separate organisation for individual
plans. Only applies to users who have 1 personal organisation and are
subscribing to the "Individual" plan.

The reason for this change is to keep the layout in the "Personal" mode
which means it doesn't show a bunch of unusable "organisation" related
UI.
This commit is contained in:
David Nguyen
2025-07-16 14:35:42 +10:00
committed by GitHub
parent f9d7fd7d9a
commit e5aaa17545
5 changed files with 114 additions and 14 deletions

View File

@ -1,8 +1,9 @@
import { SubscriptionStatus } from '@prisma/client';
import { OrganisationType, SubscriptionStatus } from '@prisma/client';
import { match } from 'ts-pattern';
import { createOrganisationClaimUpsertData } from '@documenso/lib/server-only/organisation/create-organisation';
import { type Stripe, stripe } from '@documenso/lib/server-only/stripe';
import { INTERNAL_CLAIM_ID } from '@documenso/lib/types/subscription';
import { prisma } from '@documenso/prisma';
export type OnSubscriptionUpdatedOptions = {
@ -92,6 +93,22 @@ export const onSubscriptionUpdated = async ({
? new Date(subscription.trial_end * 1000)
: new Date(subscription.current_period_end * 1000);
// Migrate the organisation type if it is no longer an individual plan.
if (
updatedSubscriptionClaim.id !== INTERNAL_CLAIM_ID.INDIVIDUAL &&
updatedSubscriptionClaim.id !== INTERNAL_CLAIM_ID.FREE &&
organisation.type === OrganisationType.PERSONAL
) {
await prisma.organisation.update({
where: {
id: organisation.id,
},
data: {
type: OrganisationType.ORGANISATION,
},
});
}
await prisma.$transaction(async (tx) => {
await tx.subscription.update({
where: {

View File

@ -12,7 +12,7 @@ import { ZCreateSubscriptionRequestSchema } from './create-subscription.types';
export const createSubscriptionRoute = authenticatedProcedure
.input(ZCreateSubscriptionRequestSchema)
.mutation(async ({ ctx, input }) => {
const { organisationId, priceId } = input;
const { organisationId, priceId, isPersonalLayoutMode } = input;
ctx.logger.info({
input: {
@ -70,10 +70,14 @@ export const createSubscriptionRoute = authenticatedProcedure
});
}
const returnUrl = isPersonalLayoutMode
? `${NEXT_PUBLIC_WEBAPP_URL()}/settings/billing`
: `${NEXT_PUBLIC_WEBAPP_URL()}/o/${organisation.url}/settings/billing`;
const redirectUrl = await createCheckoutSession({
customerId,
priceId,
returnUrl: `${NEXT_PUBLIC_WEBAPP_URL()}/o/${organisation.url}/settings/billing`,
returnUrl,
});
if (!redirectUrl) {

View File

@ -3,4 +3,5 @@ import { z } from 'zod';
export const ZCreateSubscriptionRequestSchema = z.object({
organisationId: z.string().describe('The organisation to create the subscription for'),
priceId: z.string().describe('The price to create the subscription for'),
isPersonalLayoutMode: z.boolean().optional(),
});