This commit is contained in:
David Nguyen
2025-05-07 15:03:20 +10:00
parent 419bc02171
commit 7abfc9e271
390 changed files with 21254 additions and 12607 deletions

View File

@ -6,7 +6,7 @@ import { ZLimitsResponseSchema } from './schema';
export type GetLimitsOptions = {
headers?: Record<string, string>;
teamId?: number | null;
teamId: number | null;
};
export const getLimits = async ({ headers, teamId }: GetLimitsOptions = {}) => {

View File

@ -22,7 +22,7 @@ export const useLimits = () => {
export type LimitsProviderProps = {
initialValue?: TLimitsResponseSchema;
teamId?: number;
teamId: number;
children?: React.ReactNode;
};

View File

@ -12,7 +12,7 @@ import { ZLimitsSchema } from './schema';
export type GetServerLimitsOptions = {
email: string;
teamId?: number | null;
teamId: number | null;
};
export const getServerLimits = async ({

View File

@ -1,20 +1,23 @@
import { STRIPE_CUSTOMER_TYPE } from '@documenso/lib/constants/billing';
import { stripe } from '@documenso/lib/server-only/stripe';
type CreateTeamCustomerOptions = {
type CreateOrganisationCustomerOptions = {
name: string;
email: string;
};
/**
* Create a Stripe customer for a given team.
* Create a Stripe customer for a given Organisation.
*/
export const createTeamCustomer = async ({ name, email }: CreateTeamCustomerOptions) => {
export const createOrganisationCustomer = async ({
name,
email,
}: CreateOrganisationCustomerOptions) => {
return await stripe.customers.create({
name,
email,
metadata: {
type: STRIPE_CUSTOMER_TYPE.TEAM,
type: STRIPE_CUSTOMER_TYPE.ORGANISATION,
},
});
};

View File

@ -27,6 +27,7 @@ export const getStripeCustomerById = async (stripeCustomerId: string) => {
}
};
// Todo: (orgs)
/**
* Get a stripe customer by user.
*

View File

@ -1,128 +0,0 @@
import { type Subscription, type Team, type User } from '@prisma/client';
import type Stripe from 'stripe';
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
import { stripe } from '@documenso/lib/server-only/stripe';
import { subscriptionsContainsActivePlan } from '@documenso/lib/utils/billing';
import { prisma } from '@documenso/prisma';
import { deleteCustomerPaymentMethods } from './delete-customer-payment-methods';
import { getTeamPrices } from './get-team-prices';
import { getTeamRelatedPriceIds } from './get-team-related-prices';
type TransferStripeSubscriptionOptions = {
/**
* The user to transfer the subscription to.
*/
user: User & { subscriptions: Subscription[] };
/**
* The team the subscription is associated with.
*/
team: Team & { subscription?: Subscription | null };
/**
* Whether to clear any current payment methods attached to the team.
*/
clearPaymentMethods: boolean;
};
/**
* Transfer the Stripe Team seats subscription from one user to another.
*
* Will create a new subscription for the new owner and cancel the old one.
*
* Returns the subscription that should be associated with the team, null if
* no subscription is needed (for early adopter plan).
*/
export const transferTeamSubscription = async ({
user,
team,
clearPaymentMethods,
}: TransferStripeSubscriptionOptions) => {
const teamCustomerId = team.customerId;
if (!teamCustomerId) {
throw new AppError(AppErrorCode.NOT_FOUND, {
message: 'Missing customer ID.',
});
}
const [teamRelatedPlanPriceIds, teamSeatPrices] = await Promise.all([
getTeamRelatedPriceIds(),
getTeamPrices(),
]);
const teamSubscriptionRequired = !subscriptionsContainsActivePlan(
user.subscriptions,
teamRelatedPlanPriceIds,
);
let teamSubscription: Stripe.Subscription | null = null;
if (team.subscription) {
teamSubscription = await stripe.subscriptions.retrieve(team.subscription.planId);
if (!teamSubscription) {
throw new Error('Could not find the current subscription.');
}
if (clearPaymentMethods) {
await deleteCustomerPaymentMethods({ customerId: teamCustomerId });
}
}
await stripe.customers.update(teamCustomerId, {
name: user.name ?? team.name,
email: user.email,
});
// If team subscription is required and the team does not have a subscription, create one.
if (teamSubscriptionRequired && !teamSubscription) {
const numberOfSeats = await prisma.teamMember.count({
where: {
teamId: team.id,
},
});
const teamSeatPriceId = teamSeatPrices.monthly.priceId;
teamSubscription = await stripe.subscriptions.create({
customer: teamCustomerId,
items: [
{
price: teamSeatPriceId,
quantity: numberOfSeats,
},
],
metadata: {
teamId: team.id.toString(),
},
});
}
// If no team subscription is required, cancel the current team subscription if it exists.
if (!teamSubscriptionRequired && teamSubscription) {
try {
// Set the quantity to 0 so we can refund/charge the old Stripe customer the prorated amount.
await stripe.subscriptions.update(teamSubscription.id, {
items: teamSubscription.items.data.map((item) => ({
id: item.id,
quantity: 0,
})),
});
await stripe.subscriptions.cancel(teamSubscription.id, {
invoice_now: true,
prorate: false,
});
} catch (e) {
// Do not error out since we can't easily undo the transfer.
// Todo: Teams - Alert us.
}
return null;
}
return teamSubscription;
};

View File

@ -7,7 +7,7 @@ import { prisma } from '@documenso/prisma';
export type OnSubscriptionUpdatedOptions = {
userId?: number;
teamId?: number;
teamId: number;
subscription: Stripe.Subscription;
};

View File

@ -7,7 +7,7 @@ import { getCommunityPlanPriceIds } from '../stripe/get-community-plan-prices';
export type IsCommunityPlanOptions = {
userId: number;
teamId?: number;
teamId: number;
};
/**

View File

@ -8,7 +8,7 @@ import { getEnterprisePlanPriceIds } from '../stripe/get-enterprise-plan-prices'
export type IsUserEnterpriseOptions = {
userId: number;
teamId?: number;
teamId: number;
};
/**