From d8aecc4092f236814e3c831d3bbad0c57f5b643d Mon Sep 17 00:00:00 2001 From: apoorv taneja Date: Thu, 25 Jan 2024 13:21:55 +0530 Subject: [PATCH 1/6] fixed undo operation on signature pad --- packages/ui/primitives/signature-pad/signature-pad.tsx | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/ui/primitives/signature-pad/signature-pad.tsx b/packages/ui/primitives/signature-pad/signature-pad.tsx index 80bac0e18..e6f844b52 100644 --- a/packages/ui/primitives/signature-pad/signature-pad.tsx +++ b/packages/ui/primitives/signature-pad/signature-pad.tsx @@ -26,7 +26,7 @@ export const SignaturePad = ({ ...props }: SignaturePadProps) => { const $el = useRef(null); - + const defaultImageRef = useRef(null); const [isPressed, setIsPressed] = useState(false); const [lines, setLines] = useState([]); const [currentLine, setCurrentLine] = useState([]); @@ -161,6 +161,7 @@ export const SignaturePad = ({ const ctx = $el.current.getContext('2d'); ctx?.clearRect(0, 0, $el.current.width, $el.current.height); + defaultImageRef.current = null; } onChange?.(null); @@ -181,8 +182,11 @@ export const SignaturePad = ({ // Clear the canvas if ($el.current) { const ctx = $el.current.getContext('2d'); + const { width, height } = $el.current; ctx?.clearRect(0, 0, $el.current.width, $el.current.height); - + if (typeof defaultValue === 'string' && defaultImageRef.current) { + ctx?.putImageData(defaultImageRef.current, 0, 0); + } newLines.forEach((line) => { const pathData = new Path2D(getSvgPathFromStroke(getStroke(line, perfectFreehandOptions))); ctx?.fill(pathData); @@ -207,6 +211,8 @@ export const SignaturePad = ({ img.onload = () => { ctx?.drawImage(img, 0, 0, Math.min(width, img.width), Math.min(height, img.height)); + const defaultImageData = ctx?.getImageData(0, 0, width, height) || null; + defaultImageRef.current = defaultImageData; }; img.src = defaultValue; From 142c1c003ebbdb7bf39d18f59f78a6a47e278f2e Mon Sep 17 00:00:00 2001 From: apoorv taneja Date: Fri, 2 Feb 2024 18:16:54 +0530 Subject: [PATCH 2/6] changed useEffect variables --- packages/ui/primitives/signature-pad/signature-pad.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ui/primitives/signature-pad/signature-pad.tsx b/packages/ui/primitives/signature-pad/signature-pad.tsx index cb546538c..b52626026 100644 --- a/packages/ui/primitives/signature-pad/signature-pad.tsx +++ b/packages/ui/primitives/signature-pad/signature-pad.tsx @@ -219,7 +219,7 @@ export const SignaturePad = ({ img.src = defaultValue; } - }, [defaultValue]); + }, []); return (
Date: Fri, 2 Feb 2024 19:32:39 +0530 Subject: [PATCH 3/6] fixed variable declaration --- packages/ui/primitives/signature-pad/signature-pad.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ui/primitives/signature-pad/signature-pad.tsx b/packages/ui/primitives/signature-pad/signature-pad.tsx index b52626026..c38c69fb2 100644 --- a/packages/ui/primitives/signature-pad/signature-pad.tsx +++ b/packages/ui/primitives/signature-pad/signature-pad.tsx @@ -185,7 +185,7 @@ export const SignaturePad = ({ if ($el.current) { const ctx = $el.current.getContext('2d'); const { width, height } = $el.current; - ctx?.clearRect(0, 0, $el.current.width, $el.current.height); + ctx?.clearRect(0, 0, width, height); if (typeof defaultValue === 'string' && defaultImageRef.current) { ctx?.putImageData(defaultImageRef.current, 0, 0); } From c970abc871d1b3f961717d42a6d4eed1bc3602df Mon Sep 17 00:00:00 2001 From: apoorv taneja Date: Fri, 2 Feb 2024 20:46:54 +0530 Subject: [PATCH 4/6] added onchange handler --- packages/ui/primitives/signature-pad/signature-pad.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ui/primitives/signature-pad/signature-pad.tsx b/packages/ui/primitives/signature-pad/signature-pad.tsx index c38c69fb2..ad6f92e91 100644 --- a/packages/ui/primitives/signature-pad/signature-pad.tsx +++ b/packages/ui/primitives/signature-pad/signature-pad.tsx @@ -134,7 +134,6 @@ export const SignaturePad = ({ }); onChange?.($el.current.toDataURL()); - ctx.save(); } } @@ -193,6 +192,7 @@ export const SignaturePad = ({ const pathData = new Path2D(getSvgPathFromStroke(getStroke(line, perfectFreehandOptions))); ctx?.fill(pathData); }); + onChange?.($el.current.toDataURL()); } }; From d83769b4104c9fbe4e74625486328712e16c4ca2 Mon Sep 17 00:00:00 2001 From: Lucas Smith Date: Fri, 16 Feb 2024 11:56:02 +0000 Subject: [PATCH 5/6] chore: use unsafe effect --- .../lib/client-only/hooks/use-effect-once.ts | 13 ++++++++++ .../signature-pad/signature-pad.tsx | 25 ++++++++++++------- 2 files changed, 29 insertions(+), 9 deletions(-) create mode 100644 packages/lib/client-only/hooks/use-effect-once.ts diff --git a/packages/lib/client-only/hooks/use-effect-once.ts b/packages/lib/client-only/hooks/use-effect-once.ts new file mode 100644 index 000000000..dc6d062dd --- /dev/null +++ b/packages/lib/client-only/hooks/use-effect-once.ts @@ -0,0 +1,13 @@ +import type { EffectCallback } from 'react'; +import { useEffect } from 'react'; + +/** + * Dangerously runs an effect "once" by ignoring the depedencies of a given effect. + * + * DANGER: The effect will run twice in concurrent react and development environments. + */ +export const unsafe_useEffectOnce = (callback: EffectCallback) => { + // Intentionally avoiding exhaustive deps and rule of hooks here + // eslint-disable-next-line react-hooks/exhaustive-deps, react-hooks/rules-of-hooks + return useEffect(callback, []); +}; diff --git a/packages/ui/primitives/signature-pad/signature-pad.tsx b/packages/ui/primitives/signature-pad/signature-pad.tsx index ad6f92e91..8524450fc 100644 --- a/packages/ui/primitives/signature-pad/signature-pad.tsx +++ b/packages/ui/primitives/signature-pad/signature-pad.tsx @@ -7,6 +7,8 @@ import { Undo2 } from 'lucide-react'; import type { StrokeOptions } from 'perfect-freehand'; import { getStroke } from 'perfect-freehand'; +import { unsafe_useEffectOnce } from '@documenso/lib/client-only/hooks/use-effect-once'; + import { cn } from '../../lib/utils'; import { getSvgPathFromStroke } from './helper'; import { Point } from './point'; @@ -28,7 +30,8 @@ export const SignaturePad = ({ ...props }: SignaturePadProps) => { const $el = useRef(null); - const defaultImageRef = useRef(null); + const $imageData = useRef(null); + const [isPressed, setIsPressed] = useState(false); const [lines, setLines] = useState([]); const [currentLine, setCurrentLine] = useState([]); @@ -162,7 +165,7 @@ export const SignaturePad = ({ const ctx = $el.current.getContext('2d'); ctx?.clearRect(0, 0, $el.current.width, $el.current.height); - defaultImageRef.current = null; + $imageData.current = null; } onChange?.(null); @@ -176,8 +179,7 @@ export const SignaturePad = ({ return; } - const newLines = [...lines]; - newLines.pop(); // Remove the last line + const newLines = lines.slice(0, -1); setLines(newLines); // Clear the canvas @@ -185,13 +187,16 @@ export const SignaturePad = ({ const ctx = $el.current.getContext('2d'); const { width, height } = $el.current; ctx?.clearRect(0, 0, width, height); - if (typeof defaultValue === 'string' && defaultImageRef.current) { - ctx?.putImageData(defaultImageRef.current, 0, 0); + + if (typeof defaultValue === 'string' && $imageData.current) { + ctx?.putImageData($imageData.current, 0, 0); } + newLines.forEach((line) => { const pathData = new Path2D(getSvgPathFromStroke(getStroke(line, perfectFreehandOptions))); ctx?.fill(pathData); }); + onChange?.($el.current.toDataURL()); } }; @@ -203,7 +208,7 @@ export const SignaturePad = ({ } }, []); - useEffect(() => { + unsafe_useEffectOnce(() => { if ($el.current && typeof defaultValue === 'string') { const ctx = $el.current.getContext('2d'); @@ -213,13 +218,15 @@ export const SignaturePad = ({ img.onload = () => { ctx?.drawImage(img, 0, 0, Math.min(width, img.width), Math.min(height, img.height)); + const defaultImageData = ctx?.getImageData(0, 0, width, height) || null; - defaultImageRef.current = defaultImageData; + + $imageData.current = defaultImageData; }; img.src = defaultValue; } - }, []); + }); return (
Date: Sat, 17 Feb 2024 12:42:00 +1100 Subject: [PATCH 6/6] feat: add enterprise billing (#939) ## Description Add support for enterprise billing plans. Enterprise billing plans by default get access to everything early adopters do: - Unlimited teams - Unlimited documents They will also get additional features in the future. ## Notes Pending webhook updates to support enterprise onboarding. Rolled back env changes `NEXT_PUBLIC_PROJECT` since it doesn't seem to work. --- .../app/(dashboard)/settings/billing/page.tsx | 16 ++++++++-------- .../tables/teams-member-page-data-table.tsx | 2 +- packages/ee/server-only/limits/server.ts | 8 ++++---- .../stripe/get-document-related-prices.ts.ts | 10 ++++++++++ .../stripe/get-enterprise-plan-prices.ts | 13 +++++++++++++ .../ee/server-only/stripe/get-prices-by-plan.ts | 14 +++++++++----- .../stripe/get-primary-account-plan-prices.ts | 10 ++++++++++ .../stripe/get-team-related-prices.ts | 17 +++++++++++++++++ .../stripe/transfer-team-subscription.ts | 12 ++++++------ packages/lib/constants/app.ts | 9 ++++----- packages/lib/constants/billing.ts | 3 +-- packages/lib/server-only/team/create-team.ts | 13 ++++++------- packages/lib/utils/billing.ts | 9 ++++----- 13 files changed, 93 insertions(+), 43 deletions(-) create mode 100644 packages/ee/server-only/stripe/get-document-related-prices.ts.ts create mode 100644 packages/ee/server-only/stripe/get-enterprise-plan-prices.ts create mode 100644 packages/ee/server-only/stripe/get-primary-account-plan-prices.ts create mode 100644 packages/ee/server-only/stripe/get-team-related-prices.ts diff --git a/apps/web/src/app/(dashboard)/settings/billing/page.tsx b/apps/web/src/app/(dashboard)/settings/billing/page.tsx index cee2aa2f1..7865e2b5c 100644 --- a/apps/web/src/app/(dashboard)/settings/billing/page.tsx +++ b/apps/web/src/app/(dashboard)/settings/billing/page.tsx @@ -5,7 +5,7 @@ import { match } from 'ts-pattern'; import { getStripeCustomerByUser } from '@documenso/ee/server-only/stripe/get-customer'; import { getPricesByInterval } from '@documenso/ee/server-only/stripe/get-prices-by-interval'; -import { getPricesByPlan } from '@documenso/ee/server-only/stripe/get-prices-by-plan'; +import { getPrimaryAccountPlanPrices } from '@documenso/ee/server-only/stripe/get-primary-account-plan-prices'; import { getProductByPriceId } from '@documenso/ee/server-only/stripe/get-product-by-price-id'; import { STRIPE_PLAN_TYPE } from '@documenso/lib/constants/billing'; import { getRequiredServerComponentSession } from '@documenso/lib/next-auth/get-server-component-session'; @@ -37,23 +37,23 @@ export default async function BillingSettingsPage() { user = await getStripeCustomerByUser(user).then((result) => result.user); } - const [subscriptions, prices, communityPlanPrices] = await Promise.all([ + const [subscriptions, prices, primaryAccountPlanPrices] = await Promise.all([ getSubscriptionsByUserId({ userId: user.id }), getPricesByInterval({ plan: STRIPE_PLAN_TYPE.COMMUNITY }), - getPricesByPlan(STRIPE_PLAN_TYPE.COMMUNITY), + getPrimaryAccountPlanPrices(), ]); - const communityPlanPriceIds = communityPlanPrices.map(({ id }) => id); + const primaryAccountPlanPriceIds = primaryAccountPlanPrices.map(({ id }) => id); let subscriptionProduct: Stripe.Product | null = null; - const communityPlanUserSubscriptions = subscriptions.filter(({ priceId }) => - communityPlanPriceIds.includes(priceId), + const primaryAccountPlanSubscriptions = subscriptions.filter(({ priceId }) => + primaryAccountPlanPriceIds.includes(priceId), ); const subscription = - communityPlanUserSubscriptions.find(({ status }) => status === SubscriptionStatus.ACTIVE) ?? - communityPlanUserSubscriptions[0]; + primaryAccountPlanSubscriptions.find(({ status }) => status === SubscriptionStatus.ACTIVE) ?? + primaryAccountPlanSubscriptions[0]; if (subscription?.priceId) { subscriptionProduct = await getProductByPriceId({ priceId: subscription.priceId }).catch( diff --git a/apps/web/src/components/(teams)/tables/teams-member-page-data-table.tsx b/apps/web/src/components/(teams)/tables/teams-member-page-data-table.tsx index 316c4373f..24d4089b2 100644 --- a/apps/web/src/components/(teams)/tables/teams-member-page-data-table.tsx +++ b/apps/web/src/components/(teams)/tables/teams-member-page-data-table.tsx @@ -67,7 +67,7 @@ export const TeamsMemberPageDataTable = ({ - All + Active diff --git a/packages/ee/server-only/limits/server.ts b/packages/ee/server-only/limits/server.ts index 4904f0271..abed86da7 100644 --- a/packages/ee/server-only/limits/server.ts +++ b/packages/ee/server-only/limits/server.ts @@ -1,11 +1,10 @@ import { DateTime } from 'luxon'; import { IS_BILLING_ENABLED } from '@documenso/lib/constants/app'; -import { STRIPE_PLAN_TYPE } from '@documenso/lib/constants/billing'; import { prisma } from '@documenso/prisma'; import { SubscriptionStatus } from '@documenso/prisma/client'; -import { getPricesByPlan } from '../stripe/get-prices-by-plan'; +import { getDocumentRelatedPrices } from '../stripe/get-document-related-prices.ts'; import { FREE_PLAN_LIMITS, SELFHOSTED_PLAN_LIMITS, TEAM_PLAN_LIMITS } from './constants'; import { ERROR_CODES } from './errors'; import { ZLimitsSchema } from './schema'; @@ -56,10 +55,11 @@ const handleUserLimits = async ({ email }: HandleUserLimitsOptions) => { ); if (activeSubscriptions.length > 0) { - const communityPlanPrices = await getPricesByPlan(STRIPE_PLAN_TYPE.COMMUNITY); + const documentPlanPrices = await getDocumentRelatedPrices(); for (const subscription of activeSubscriptions) { - const price = communityPlanPrices.find((price) => price.id === subscription.priceId); + const price = documentPlanPrices.find((price) => price.id === subscription.priceId); + if (!price || typeof price.product === 'string' || price.product.deleted) { continue; } diff --git a/packages/ee/server-only/stripe/get-document-related-prices.ts.ts b/packages/ee/server-only/stripe/get-document-related-prices.ts.ts new file mode 100644 index 000000000..81b32a7b9 --- /dev/null +++ b/packages/ee/server-only/stripe/get-document-related-prices.ts.ts @@ -0,0 +1,10 @@ +import { STRIPE_PLAN_TYPE } from '@documenso/lib/constants/billing'; + +import { getPricesByPlan } from './get-prices-by-plan'; + +/** + * Returns the Stripe prices of items that affect the amount of documents a user can create. + */ +export const getDocumentRelatedPrices = async () => { + return await getPricesByPlan([STRIPE_PLAN_TYPE.COMMUNITY, STRIPE_PLAN_TYPE.ENTERPRISE]); +}; diff --git a/packages/ee/server-only/stripe/get-enterprise-plan-prices.ts b/packages/ee/server-only/stripe/get-enterprise-plan-prices.ts new file mode 100644 index 000000000..ec67fe163 --- /dev/null +++ b/packages/ee/server-only/stripe/get-enterprise-plan-prices.ts @@ -0,0 +1,13 @@ +import { STRIPE_PLAN_TYPE } from '@documenso/lib/constants/billing'; + +import { getPricesByPlan } from './get-prices-by-plan'; + +export const getEnterprisePlanPrices = async () => { + return await getPricesByPlan(STRIPE_PLAN_TYPE.ENTERPRISE); +}; + +export const getEnterprisePlanPriceIds = async () => { + const prices = await getEnterprisePlanPrices(); + + return prices.map((price) => price.id); +}; diff --git a/packages/ee/server-only/stripe/get-prices-by-plan.ts b/packages/ee/server-only/stripe/get-prices-by-plan.ts index 5c390b35a..45906d54a 100644 --- a/packages/ee/server-only/stripe/get-prices-by-plan.ts +++ b/packages/ee/server-only/stripe/get-prices-by-plan.ts @@ -1,14 +1,18 @@ import type { STRIPE_PLAN_TYPE } from '@documenso/lib/constants/billing'; import { stripe } from '@documenso/lib/server-only/stripe'; -export const getPricesByPlan = async ( - plan: (typeof STRIPE_PLAN_TYPE)[keyof typeof STRIPE_PLAN_TYPE], -) => { +type PlanType = (typeof STRIPE_PLAN_TYPE)[keyof typeof STRIPE_PLAN_TYPE]; + +export const getPricesByPlan = async (plan: PlanType | PlanType[]) => { + const planTypes = typeof plan === 'string' ? [plan] : plan; + + const query = planTypes.map((planType) => `metadata['plan']:'${planType}'`).join(' OR '); + const { data: prices } = await stripe.prices.search({ - query: `metadata['plan']:'${plan}' type:'recurring'`, + query, expand: ['data.product'], limit: 100, }); - return prices; + return prices.filter((price) => price.type === 'recurring'); }; diff --git a/packages/ee/server-only/stripe/get-primary-account-plan-prices.ts b/packages/ee/server-only/stripe/get-primary-account-plan-prices.ts new file mode 100644 index 000000000..0eb368ce7 --- /dev/null +++ b/packages/ee/server-only/stripe/get-primary-account-plan-prices.ts @@ -0,0 +1,10 @@ +import { STRIPE_PLAN_TYPE } from '@documenso/lib/constants/billing'; + +import { getPricesByPlan } from './get-prices-by-plan'; + +/** + * Returns the prices of items that count as the account's primary plan. + */ +export const getPrimaryAccountPlanPrices = async () => { + return await getPricesByPlan([STRIPE_PLAN_TYPE.COMMUNITY, STRIPE_PLAN_TYPE.ENTERPRISE]); +}; diff --git a/packages/ee/server-only/stripe/get-team-related-prices.ts b/packages/ee/server-only/stripe/get-team-related-prices.ts new file mode 100644 index 000000000..b10ab06f4 --- /dev/null +++ b/packages/ee/server-only/stripe/get-team-related-prices.ts @@ -0,0 +1,17 @@ +import { STRIPE_PLAN_TYPE } from '@documenso/lib/constants/billing'; + +import { getPricesByPlan } from './get-prices-by-plan'; + +/** + * Returns the Stripe prices of items that affect the amount of teams a user can create. + */ +export const getTeamRelatedPrices = async () => { + return await getPricesByPlan([STRIPE_PLAN_TYPE.COMMUNITY, STRIPE_PLAN_TYPE.ENTERPRISE]); +}; + +/** + * Returns the Stripe price IDs of items that affect the amount of teams a user can create. + */ +export const getTeamRelatedPriceIds = async () => { + return await getTeamRelatedPrices().then((prices) => prices.map((price) => price.id)); +}; diff --git a/packages/ee/server-only/stripe/transfer-team-subscription.ts b/packages/ee/server-only/stripe/transfer-team-subscription.ts index b4e0bd59a..953efcaf4 100644 --- a/packages/ee/server-only/stripe/transfer-team-subscription.ts +++ b/packages/ee/server-only/stripe/transfer-team-subscription.ts @@ -2,13 +2,13 @@ import type Stripe from 'stripe'; import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error'; import { stripe } from '@documenso/lib/server-only/stripe'; -import { subscriptionsContainsActiveCommunityPlan } from '@documenso/lib/utils/billing'; +import { subscriptionsContainsActivePlan } from '@documenso/lib/utils/billing'; import { prisma } from '@documenso/prisma'; import { type Subscription, type Team, type User } from '@documenso/prisma/client'; import { deleteCustomerPaymentMethods } from './delete-customer-payment-methods'; -import { getCommunityPlanPriceIds } from './get-community-plan-prices'; import { getTeamPrices } from './get-team-prices'; +import { getTeamRelatedPriceIds } from './get-team-related-prices'; type TransferStripeSubscriptionOptions = { /** @@ -46,14 +46,14 @@ export const transferTeamSubscription = async ({ throw new AppError(AppErrorCode.NOT_FOUND, 'Missing customer ID.'); } - const [communityPlanIds, teamSeatPrices] = await Promise.all([ - getCommunityPlanPriceIds(), + const [teamRelatedPlanPriceIds, teamSeatPrices] = await Promise.all([ + getTeamRelatedPriceIds(), getTeamPrices(), ]); - const teamSubscriptionRequired = !subscriptionsContainsActiveCommunityPlan( + const teamSubscriptionRequired = !subscriptionsContainsActivePlan( user.Subscription, - communityPlanIds, + teamRelatedPlanPriceIds, ); let teamSubscription: Stripe.Subscription | null = null; diff --git a/packages/lib/constants/app.ts b/packages/lib/constants/app.ts index 1adb4effb..c17193677 100644 --- a/packages/lib/constants/app.ts +++ b/packages/lib/constants/app.ts @@ -3,18 +3,17 @@ import { env } from 'next-runtime-env'; export const APP_DOCUMENT_UPLOAD_SIZE_LIMIT = Number(process.env.NEXT_PUBLIC_DOCUMENT_SIZE_UPLOAD_LIMIT) || 50; -export const NEXT_PUBLIC_PROJECT = () => env('NEXT_PUBLIC_PROJECT'); export const NEXT_PUBLIC_WEBAPP_URL = () => env('NEXT_PUBLIC_WEBAPP_URL'); export const NEXT_PUBLIC_MARKETING_URL = () => env('NEXT_PUBLIC_MARKETING_URL'); -export const IS_APP_MARKETING = () => NEXT_PUBLIC_PROJECT() === 'marketing'; -export const IS_APP_WEB = () => NEXT_PUBLIC_PROJECT() === 'web'; +export const IS_APP_MARKETING = process.env.NEXT_PUBLIC_PROJECT === 'marketing'; +export const IS_APP_WEB = process.env.NEXT_PUBLIC_PROJECT === 'web'; export const IS_BILLING_ENABLED = () => env('NEXT_PUBLIC_FEATURE_BILLING_ENABLED') === 'true'; -export const APP_FOLDER = () => (IS_APP_MARKETING() ? 'marketing' : 'web'); +export const APP_FOLDER = () => (IS_APP_MARKETING ? 'marketing' : 'web'); export const APP_BASE_URL = () => - IS_APP_WEB() ? NEXT_PUBLIC_WEBAPP_URL() : NEXT_PUBLIC_MARKETING_URL(); + IS_APP_WEB ? NEXT_PUBLIC_WEBAPP_URL() : NEXT_PUBLIC_MARKETING_URL(); export const WEBAPP_BASE_URL = NEXT_PUBLIC_WEBAPP_URL() ?? 'http://localhost:3000'; export const MARKETING_BASE_URL = NEXT_PUBLIC_MARKETING_URL() ?? 'http://localhost:3001'; diff --git a/packages/lib/constants/billing.ts b/packages/lib/constants/billing.ts index e6d897af8..0d8dee6e2 100644 --- a/packages/lib/constants/billing.ts +++ b/packages/lib/constants/billing.ts @@ -6,6 +6,5 @@ export enum STRIPE_CUSTOMER_TYPE { export enum STRIPE_PLAN_TYPE { TEAM = 'team', COMMUNITY = 'community', + ENTERPRISE = 'enterprise', } - -export const TEAM_BILLING_DOMAIN = 'billing.team.documenso.com'; diff --git a/packages/lib/server-only/team/create-team.ts b/packages/lib/server-only/team/create-team.ts index 4d26a161a..3461d49bf 100644 --- a/packages/lib/server-only/team/create-team.ts +++ b/packages/lib/server-only/team/create-team.ts @@ -2,11 +2,11 @@ import type Stripe from 'stripe'; import { z } from 'zod'; import { createTeamCustomer } from '@documenso/ee/server-only/stripe/create-team-customer'; -import { getCommunityPlanPriceIds } from '@documenso/ee/server-only/stripe/get-community-plan-prices'; +import { getTeamRelatedPrices } from '@documenso/ee/server-only/stripe/get-team-related-prices'; import { mapStripeSubscriptionToPrismaUpsertAction } from '@documenso/ee/server-only/stripe/webhook/on-subscription-updated'; import { IS_BILLING_ENABLED } from '@documenso/lib/constants/app'; import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error'; -import { subscriptionsContainsActiveCommunityPlan } from '@documenso/lib/utils/billing'; +import { subscriptionsContainsActivePlan } from '@documenso/lib/utils/billing'; import { prisma } from '@documenso/prisma'; import { Prisma, TeamMemberRole } from '@documenso/prisma/client'; @@ -61,13 +61,12 @@ export const createTeam = async ({ let customerId: string | null = null; if (IS_BILLING_ENABLED()) { - const communityPlanPriceIds = await getCommunityPlanPriceIds(); - - isPaymentRequired = !subscriptionsContainsActiveCommunityPlan( - user.Subscription, - communityPlanPriceIds, + const teamRelatedPriceIds = await getTeamRelatedPrices().then((prices) => + prices.map((price) => price.id), ); + isPaymentRequired = !subscriptionsContainsActivePlan(user.Subscription, teamRelatedPriceIds); + customerId = await createTeamCustomer({ name: user.name ?? teamName, email: user.email, diff --git a/packages/lib/utils/billing.ts b/packages/lib/utils/billing.ts index ca85addbb..048fa6ee0 100644 --- a/packages/lib/utils/billing.ts +++ b/packages/lib/utils/billing.ts @@ -2,15 +2,14 @@ import type { Subscription } from '.prisma/client'; import { SubscriptionStatus } from '.prisma/client'; /** - * Returns true if there is a subscription that is active and is a community plan. + * Returns true if there is a subscription that is active and is one of the provided price IDs. */ -export const subscriptionsContainsActiveCommunityPlan = ( +export const subscriptionsContainsActivePlan = ( subscriptions: Subscription[], - communityPlanPriceIds: string[], + priceIds: string[], ) => { return subscriptions.some( (subscription) => - subscription.status === SubscriptionStatus.ACTIVE && - communityPlanPriceIds.includes(subscription.priceId), + subscription.status === SubscriptionStatus.ACTIVE && priceIds.includes(subscription.priceId), ); };