From 4d7e52ef25a81a82fa3ea5d0cfabd38a8756ee08 Mon Sep 17 00:00:00 2001 From: Ana Sofia Marin Alexandre <61988046+anamarn@users.noreply.github.com> Date: Wed, 5 Mar 2025 11:27:34 -0300 Subject: [PATCH] deprocate getProductPrices query in front end (#10397) **TLDR:** Deprecate getProductPrices in the frontEnd and replace it with BillingBaseProductPrices. **In order to test:** - Have the environment variable IS_BILLING_ENABLED set to true and add the other required environment variables for Billing to work - Do a database reset (to ensure that the new feature flag is properly added and that the billing tables are created) - Run the command: npx nx run twenty-server:command billing:sync-plans-data (if you don't do that the products and prices will not be present in the database) - Run the server , the frontend, the worker, and the stripe listen command (stripe listen --forward-to http://localhost:3000/billing/webhooks) - Buy a subscription for acme workspace the choose your plan should be using the new front end endpoint --- .../twenty-front/src/generated/graphql.tsx | 116 ++++++++---------- .../graphql/billingBaseProductPrices.ts | 18 +++ .../billing/graphql/getProductPrices.ts | 14 --- .../billing/utils/isBillingPriceLicensed.ts | 7 ++ .../src/pages/onboarding/ChooseYourPlan.tsx | 33 +++-- .../__stories__/ChooseYourPlan.stories.tsx | 48 ++++---- .../core-modules/billing/billing.resolver.ts | 19 +-- .../dtos/billing-price-licensed.dto.ts | 4 + .../billing/dtos/billing-price-metered.dto.ts | 4 + .../billing/dtos/billing-price-union.dto.ts | 4 +- .../billing/dtos/billing-product.dto.ts | 4 +- .../outputs/billing-product-prices.output.ts | 14 --- .../billing/services/billing-plan.service.ts | 2 +- .../services/billing-subscription.service.ts | 4 +- ...tabase-product-to-graphql-dto.util.spec.ts | 15 +++ ...at-database-product-to-graphql-dto.util.ts | 2 + .../get-plan-key-from-subscription.util.ts | 17 +++ 17 files changed, 174 insertions(+), 151 deletions(-) create mode 100644 packages/twenty-front/src/modules/billing/graphql/billingBaseProductPrices.ts delete mode 100644 packages/twenty-front/src/modules/billing/graphql/getProductPrices.ts create mode 100644 packages/twenty-front/src/modules/billing/utils/isBillingPriceLicensed.ts delete mode 100644 packages/twenty-server/src/engine/core-modules/billing/dtos/outputs/billing-product-prices.output.ts create mode 100644 packages/twenty-server/src/engine/core-modules/billing/utils/get-plan-key-from-subscription.util.ts diff --git a/packages/twenty-front/src/generated/graphql.tsx b/packages/twenty-front/src/generated/graphql.tsx index 52a4a5993..d50492535 100644 --- a/packages/twenty-front/src/generated/graphql.tsx +++ b/packages/twenty-front/src/generated/graphql.tsx @@ -1,5 +1,5 @@ -import { gql } from '@apollo/client'; import * as Apollo from '@apollo/client'; +import { gql } from '@apollo/client'; export type Maybe = T | null; export type InputMaybe = Maybe; export type Exact = { [K in keyof T]: T[K] }; @@ -161,6 +161,7 @@ export type BillingPlanOutput = { export type BillingPriceLicensedDto = { __typename?: 'BillingPriceLicensedDTO'; + priceUsageType: BillingUsageType; recurringInterval: SubscriptionInterval; stripePriceId: Scalars['String']; unitAmount: Scalars['Float']; @@ -168,6 +169,7 @@ export type BillingPriceLicensedDto = { export type BillingPriceMeteredDto = { __typename?: 'BillingPriceMeteredDTO'; + priceUsageType: BillingUsageType; recurringInterval: SubscriptionInterval; stripePriceId: Scalars['String']; tiers?: Maybe>; @@ -194,24 +196,10 @@ export type BillingProductDto = { description: Scalars['String']; images?: Maybe>; name: Scalars['String']; - prices: Array>; + prices: Array; type: BillingUsageType; }; -export type BillingProductPriceDto = { - __typename?: 'BillingProductPriceDTO'; - created: Scalars['Float']; - recurringInterval: SubscriptionInterval; - stripePriceId: Scalars['String']; - unitAmount: Scalars['Float']; -}; - -export type BillingProductPricesOutput = { - __typename?: 'BillingProductPricesOutput'; - productPrices: Array; - totalNumberOfPrices: Scalars['Int']; -}; - export type BillingSessionOutput = { __typename?: 'BillingSessionOutput'; url?: Maybe; @@ -1306,7 +1294,6 @@ export type Query = { getEnvironmentVariablesGrouped: EnvironmentVariablesOutput; getIndicatorHealthStatus: AdminPanelHealthServiceData; getPostgresCredentials?: Maybe; - getProductPrices: BillingProductPricesOutput; getPublicWorkspaceDataByDomain: PublicWorkspaceDataOutput; getQueueMetrics: QueueMetricsData; getRoles: Array; @@ -2356,6 +2343,11 @@ export type ValidatePasswordResetTokenQueryVariables = Exact<{ export type ValidatePasswordResetTokenQuery = { __typename?: 'Query', validatePasswordResetToken: { __typename?: 'ValidatePasswordResetToken', id: string, email: string } }; +export type BillingBaseProductPricesQueryVariables = Exact<{ [key: string]: never; }>; + + +export type BillingBaseProductPricesQuery = { __typename?: 'Query', plans: Array<{ __typename?: 'BillingPlanOutput', planKey: BillingPlanKey, baseProduct: { __typename?: 'BillingProductDTO', prices: Array<{ __typename?: 'BillingPriceLicensedDTO', unitAmount: number, stripePriceId: string, recurringInterval: SubscriptionInterval } | { __typename?: 'BillingPriceMeteredDTO' }> } }> }; + export type BillingPortalSessionQueryVariables = Exact<{ returnUrlPath?: InputMaybe; }>; @@ -2373,13 +2365,6 @@ export type CheckoutSessionMutationVariables = Exact<{ export type CheckoutSessionMutation = { __typename?: 'Mutation', checkoutSession: { __typename?: 'BillingSessionOutput', url?: string | null } }; -export type GetProductPricesQueryVariables = Exact<{ - product: Scalars['String']; -}>; - - -export type GetProductPricesQuery = { __typename?: 'Query', getProductPrices: { __typename?: 'BillingProductPricesOutput', productPrices: Array<{ __typename?: 'BillingProductPriceDTO', created: number, recurringInterval: SubscriptionInterval, stripePriceId: string, unitAmount: number }> } }; - export type UpdateBillingSubscriptionMutationVariables = Exact<{ [key: string]: never; }>; @@ -3817,6 +3802,49 @@ export function useValidatePasswordResetTokenLazyQuery(baseOptions?: Apollo.Lazy export type ValidatePasswordResetTokenQueryHookResult = ReturnType; export type ValidatePasswordResetTokenLazyQueryHookResult = ReturnType; export type ValidatePasswordResetTokenQueryResult = Apollo.QueryResult; +export const BillingBaseProductPricesDocument = gql` + query billingBaseProductPrices { + plans { + planKey + baseProduct { + prices { + ... on BillingPriceLicensedDTO { + unitAmount + stripePriceId + recurringInterval + } + } + } + } +} + `; + +/** + * __useBillingBaseProductPricesQuery__ + * + * To run a query within a React component, call `useBillingBaseProductPricesQuery` and pass it any options that fit your needs. + * When your component renders, `useBillingBaseProductPricesQuery` returns an object from Apollo Client that contains loading, error, and data properties + * you can use to render your UI. + * + * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; + * + * @example + * const { data, loading, error } = useBillingBaseProductPricesQuery({ + * variables: { + * }, + * }); + */ +export function useBillingBaseProductPricesQuery(baseOptions?: Apollo.QueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useQuery(BillingBaseProductPricesDocument, options); + } +export function useBillingBaseProductPricesLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useLazyQuery(BillingBaseProductPricesDocument, options); + } +export type BillingBaseProductPricesQueryHookResult = ReturnType; +export type BillingBaseProductPricesLazyQueryHookResult = ReturnType; +export type BillingBaseProductPricesQueryResult = Apollo.QueryResult; export const BillingPortalSessionDocument = gql` query BillingPortalSession($returnUrlPath: String) { billingPortalSession(returnUrlPath: $returnUrlPath) { @@ -3893,46 +3921,6 @@ export function useCheckoutSessionMutation(baseOptions?: Apollo.MutationHookOpti export type CheckoutSessionMutationHookResult = ReturnType; export type CheckoutSessionMutationResult = Apollo.MutationResult; export type CheckoutSessionMutationOptions = Apollo.BaseMutationOptions; -export const GetProductPricesDocument = gql` - query GetProductPrices($product: String!) { - getProductPrices(product: $product) { - productPrices { - created - recurringInterval - stripePriceId - unitAmount - } - } -} - `; - -/** - * __useGetProductPricesQuery__ - * - * To run a query within a React component, call `useGetProductPricesQuery` and pass it any options that fit your needs. - * When your component renders, `useGetProductPricesQuery` returns an object from Apollo Client that contains loading, error, and data properties - * you can use to render your UI. - * - * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; - * - * @example - * const { data, loading, error } = useGetProductPricesQuery({ - * variables: { - * product: // value for 'product' - * }, - * }); - */ -export function useGetProductPricesQuery(baseOptions: Apollo.QueryHookOptions) { - const options = {...defaultOptions, ...baseOptions} - return Apollo.useQuery(GetProductPricesDocument, options); - } -export function useGetProductPricesLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions) { - const options = {...defaultOptions, ...baseOptions} - return Apollo.useLazyQuery(GetProductPricesDocument, options); - } -export type GetProductPricesQueryHookResult = ReturnType; -export type GetProductPricesLazyQueryHookResult = ReturnType; -export type GetProductPricesQueryResult = Apollo.QueryResult; export const UpdateBillingSubscriptionDocument = gql` mutation UpdateBillingSubscription { updateBillingSubscription { diff --git a/packages/twenty-front/src/modules/billing/graphql/billingBaseProductPrices.ts b/packages/twenty-front/src/modules/billing/graphql/billingBaseProductPrices.ts new file mode 100644 index 000000000..1f0df15fc --- /dev/null +++ b/packages/twenty-front/src/modules/billing/graphql/billingBaseProductPrices.ts @@ -0,0 +1,18 @@ +import { gql } from '@apollo/client'; + +export const BILLING_BASE_PRODUCT_PRICES = gql` + query billingBaseProductPrices { + plans { + planKey + baseProduct { + prices { + ... on BillingPriceLicensedDTO { + unitAmount + stripePriceId + recurringInterval + } + } + } + } + } +`; diff --git a/packages/twenty-front/src/modules/billing/graphql/getProductPrices.ts b/packages/twenty-front/src/modules/billing/graphql/getProductPrices.ts deleted file mode 100644 index 353e75512..000000000 --- a/packages/twenty-front/src/modules/billing/graphql/getProductPrices.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { gql } from '@apollo/client'; - -export const GET_PRODUCT_PRICES = gql` - query GetProductPrices($product: String!) { - getProductPrices(product: $product) { - productPrices { - created - recurringInterval - stripePriceId - unitAmount - } - } - } -`; diff --git a/packages/twenty-front/src/modules/billing/utils/isBillingPriceLicensed.ts b/packages/twenty-front/src/modules/billing/utils/isBillingPriceLicensed.ts new file mode 100644 index 000000000..23bfb3a87 --- /dev/null +++ b/packages/twenty-front/src/modules/billing/utils/isBillingPriceLicensed.ts @@ -0,0 +1,7 @@ +import { BillingPriceLicensedDto } from '~/generated/graphql'; + +export const isBillingPriceLicensed = ( + price: T, +): price is T & BillingPriceLicensedDto => { + return price?.__typename === 'BillingPriceLicensedDTO'; +}; diff --git a/packages/twenty-front/src/pages/onboarding/ChooseYourPlan.tsx b/packages/twenty-front/src/pages/onboarding/ChooseYourPlan.tsx index e1ed93733..9e13063ea 100644 --- a/packages/twenty-front/src/pages/onboarding/ChooseYourPlan.tsx +++ b/packages/twenty-front/src/pages/onboarding/ChooseYourPlan.tsx @@ -6,6 +6,7 @@ import { SubscriptionBenefit } from '@/billing/components/SubscriptionBenefit'; import { SubscriptionPrice } from '@/billing/components/SubscriptionPrice'; import { TrialCard } from '@/billing/components/TrialCard'; import { useHandleCheckoutSession } from '@/billing/hooks/useHandleCheckoutSession'; +import { isBillingPriceLicensed } from '@/billing/utils/isBillingPriceLicensed'; import { billingState } from '@/client-config/states/billingState'; import styled from '@emotion/styled'; import { Trans, useLingui } from '@lingui/react/macro'; @@ -18,8 +19,12 @@ import { Loader, MainButton, } from 'twenty-ui'; -import { SubscriptionInterval } from '~/generated-metadata/graphql'; -import { useGetProductPricesQuery } from '~/generated/graphql'; +import { + BillingPlanKey, + BillingPriceLicensedDto, + SubscriptionInterval, + useBillingBaseProductPricesQuery, +} from '~/generated/graphql'; const StyledSubscriptionContainer = styled.div<{ withLongerMarginBottom: boolean; @@ -92,13 +97,15 @@ export const ChooseYourPlan = () => { t`1 000 workflow node executions`, ]; - const { data: prices } = useGetProductPricesQuery({ - variables: { product: 'base-plan' }, - }); + const { data: plans } = useBillingBaseProductPricesQuery(); - const price = prices?.getProductPrices?.productPrices.find( - (productPrice) => - productPrice.recurringInterval === SubscriptionInterval.Month, + const baseProduct = plans?.plans.find( + (plan) => plan.planKey === BillingPlanKey.PRO, + )?.baseProduct; + const baseProductPrice = baseProduct?.prices.find( + (price): price is BillingPriceLicensedDto => + isBillingPriceLicensed(price) && + price.recurringInterval === SubscriptionInterval.Month, ); const hasWithoutCreditCardTrialPeriod = billing?.trialPeriods.some( @@ -122,12 +129,12 @@ export const ChooseYourPlan = () => { const handleTrialPeriodChange = (withCreditCard: boolean) => { return () => { if ( - isDefined(price) && + isDefined(baseProductPrice) && billingCheckoutSession.requirePaymentMethod !== withCreditCard ) { setBillingCheckoutSession({ plan: billingCheckoutSession.plan, - interval: price.recurringInterval, + interval: baseProductPrice.recurringInterval, requirePaymentMethod: withCreditCard, }); } @@ -139,7 +146,7 @@ export const ChooseYourPlan = () => { const withCreditCardTrialPeriodDuration = withCreditCardTrialPeriod?.duration; return ( - isDefined(price) && + isDefined(baseProductPrice) && isDefined(billing) && ( <> @@ -163,8 +170,8 @@ export const ChooseYourPlan = () => { > <StyledSubscriptionPriceContainer> <SubscriptionPrice - type={price.recurringInterval} - price={price.unitAmount / 100} + type={baseProductPrice.recurringInterval} + price={baseProductPrice.unitAmount / 100} /> </StyledSubscriptionPriceContainer> <StyledBenefitsContainer> diff --git a/packages/twenty-front/src/pages/onboarding/__stories__/ChooseYourPlan.stories.tsx b/packages/twenty-front/src/pages/onboarding/__stories__/ChooseYourPlan.stories.tsx index 83b7298dd..b03abb181 100644 --- a/packages/twenty-front/src/pages/onboarding/__stories__/ChooseYourPlan.stories.tsx +++ b/packages/twenty-front/src/pages/onboarding/__stories__/ChooseYourPlan.stories.tsx @@ -3,9 +3,14 @@ import { Meta, StoryObj } from '@storybook/react'; import { within } from '@storybook/testing-library'; import { HttpResponse, graphql } from 'msw'; +import { BILLING_BASE_PRODUCT_PRICES } from '@/billing/graphql/billingBaseProductPrices'; import { AppPath } from '@/types/AppPath'; import { GET_CURRENT_USER } from '@/users/graphql/queries/getCurrentUser'; -import { OnboardingStatus } from '~/generated/graphql'; +import { + BillingPlanKey, + OnboardingStatus, + SubscriptionInterval, +} from '~/generated/graphql'; import { ChooseYourPlan } from '~/pages/onboarding/ChooseYourPlan'; import { PageDecorator, @@ -31,31 +36,30 @@ const meta: Meta<PageDecoratorArgs> = { }, }); }), - graphql.query('GetProductPrices', () => { - return HttpResponse.json({ - data: { - getProductPrices: { - __typename: 'ProductPricesEntity', - productPrices: [ + graphql.query( + getOperationName(BILLING_BASE_PRODUCT_PRICES) ?? '', + () => { + return HttpResponse.json({ + data: { + plans: [ { - __typename: 'ProductPriceEntity', - created: 1699860608, - recurringInterval: 'Month', - stripePriceId: 'monthly8usd', - unitAmount: 900, - }, - { - __typename: 'ProductPriceEntity', - created: 1701874964, - recurringInterval: 'Year', - stripePriceId: 'priceId', - unitAmount: 9000, + planKey: BillingPlanKey.PRO, + baseProduct: { + prices: [ + { + __typename: 'BillingPriceLicensedDTO', + unitAmount: 900, + stripePriceId: 'monthly8usd', + recurringInterval: SubscriptionInterval.Month, + }, + ], + }, }, ], }, - }, - }); - }), + }); + }, + ), ...graphqlMocks.handlers, ], }, diff --git a/packages/twenty-server/src/engine/core-modules/billing/billing.resolver.ts b/packages/twenty-server/src/engine/core-modules/billing/billing.resolver.ts index aaf3bc4a2..918ddd318 100644 --- a/packages/twenty-server/src/engine/core-modules/billing/billing.resolver.ts +++ b/packages/twenty-server/src/engine/core-modules/billing/billing.resolver.ts @@ -7,10 +7,8 @@ import { GraphQLError } from 'graphql'; import { isDefined } from 'twenty-shared'; import { BillingCheckoutSessionInput } from 'src/engine/core-modules/billing/dtos/inputs/billing-checkout-session.input'; -import { BillingProductInput } from 'src/engine/core-modules/billing/dtos/inputs/billing-product.input'; import { BillingSessionInput } from 'src/engine/core-modules/billing/dtos/inputs/billing-session.input'; import { BillingPlanOutput } from 'src/engine/core-modules/billing/dtos/outputs/billing-plan.output'; -import { BillingProductPricesOutput } from 'src/engine/core-modules/billing/dtos/outputs/billing-product-prices.output'; import { BillingSessionOutput } from 'src/engine/core-modules/billing/dtos/outputs/billing-session.output'; import { BillingUpdateOutput } from 'src/engine/core-modules/billing/dtos/outputs/billing-update.output'; import { AvailableProduct } from 'src/engine/core-modules/billing/enums/billing-available-product.enum'; @@ -48,28 +46,13 @@ export class BillingResolver { constructor( private readonly billingSubscriptionService: BillingSubscriptionService, private readonly billingPortalWorkspaceService: BillingPortalWorkspaceService, - private readonly stripePriceService: StripePriceService, private readonly billingPlanService: BillingPlanService, + private readonly stripePriceService: StripePriceService, private readonly featureFlagService: FeatureFlagService, private readonly billingService: BillingService, private readonly permissionsService: PermissionsService, ) {} - @Query(() => BillingProductPricesOutput) - @UseGuards(WorkspaceAuthGuard) - async getProductPrices( - @AuthWorkspace() workspace: Workspace, - @Args() { product }: BillingProductInput, - ) { - const productPrices = - await this.stripePriceService.getStripePrices(product); - - return { - totalNumberOfPrices: productPrices.length, - productPrices, - }; - } - @Query(() => BillingSessionOutput) @UseGuards( WorkspaceAuthGuard, diff --git a/packages/twenty-server/src/engine/core-modules/billing/dtos/billing-price-licensed.dto.ts b/packages/twenty-server/src/engine/core-modules/billing/dtos/billing-price-licensed.dto.ts index 793ce0764..345585bfb 100644 --- a/packages/twenty-server/src/engine/core-modules/billing/dtos/billing-price-licensed.dto.ts +++ b/packages/twenty-server/src/engine/core-modules/billing/dtos/billing-price-licensed.dto.ts @@ -3,6 +3,7 @@ import { Field, ObjectType } from '@nestjs/graphql'; import { SubscriptionInterval } from 'src/engine/core-modules/billing/enums/billing-subscription-interval.enum'; +import { BillingUsageType } from 'src/engine/core-modules/billing/enums/billing-usage-type.enum'; @ObjectType() export class BillingPriceLicensedDTO { @@ -14,4 +15,7 @@ export class BillingPriceLicensedDTO { @Field(() => String) stripePriceId: string; + + @Field(() => BillingUsageType) + priceUsageType: BillingUsageType.LICENSED; } diff --git a/packages/twenty-server/src/engine/core-modules/billing/dtos/billing-price-metered.dto.ts b/packages/twenty-server/src/engine/core-modules/billing/dtos/billing-price-metered.dto.ts index daa6f1fc5..91bd16163 100644 --- a/packages/twenty-server/src/engine/core-modules/billing/dtos/billing-price-metered.dto.ts +++ b/packages/twenty-server/src/engine/core-modules/billing/dtos/billing-price-metered.dto.ts @@ -5,6 +5,7 @@ import { Field, ObjectType } from '@nestjs/graphql'; import { BillingPriceTierDTO } from 'src/engine/core-modules/billing/dtos/billing-price-tier.dto'; import { BillingPriceTiersMode } from 'src/engine/core-modules/billing/enums/billing-price-tiers-mode.enum'; import { SubscriptionInterval } from 'src/engine/core-modules/billing/enums/billing-subscription-interval.enum'; +import { BillingUsageType } from 'src/engine/core-modules/billing/enums/billing-usage-type.enum'; @ObjectType() export class BillingPriceMeteredDTO { @@ -19,4 +20,7 @@ export class BillingPriceMeteredDTO { @Field(() => String) stripePriceId: string; + + @Field(() => BillingUsageType) + priceUsageType: BillingUsageType.METERED; } diff --git a/packages/twenty-server/src/engine/core-modules/billing/dtos/billing-price-union.dto.ts b/packages/twenty-server/src/engine/core-modules/billing/dtos/billing-price-union.dto.ts index 86db64124..6bf7f7332 100644 --- a/packages/twenty-server/src/engine/core-modules/billing/dtos/billing-price-union.dto.ts +++ b/packages/twenty-server/src/engine/core-modules/billing/dtos/billing-price-union.dto.ts @@ -4,12 +4,12 @@ import { createUnionType } from '@nestjs/graphql'; import { BillingPriceLicensedDTO } from 'src/engine/core-modules/billing/dtos/billing-price-licensed.dto'; import { BillingPriceMeteredDTO } from 'src/engine/core-modules/billing/dtos/billing-price-metered.dto'; - +import { BillingUsageType } from 'src/engine/core-modules/billing/enums/billing-usage-type.enum'; export const BillingPriceUnionDTO = createUnionType({ name: 'BillingPriceUnionDTO', types: () => [BillingPriceLicensedDTO, BillingPriceMeteredDTO], resolveType(value) { - if ('unitAmount' in value) { + if (value.priceUsageType === BillingUsageType.LICENSED) { return BillingPriceLicensedDTO; } diff --git a/packages/twenty-server/src/engine/core-modules/billing/dtos/billing-product.dto.ts b/packages/twenty-server/src/engine/core-modules/billing/dtos/billing-product.dto.ts index 199ab6012..619583f80 100644 --- a/packages/twenty-server/src/engine/core-modules/billing/dtos/billing-product.dto.ts +++ b/packages/twenty-server/src/engine/core-modules/billing/dtos/billing-product.dto.ts @@ -21,6 +21,6 @@ export class BillingProductDTO { @Field(() => BillingUsageType) type: BillingUsageType; - @Field(() => [BillingPriceUnionDTO], { nullable: 'items' }) - prices: Array<BillingPriceLicensedDTO | BillingPriceMeteredDTO>; + @Field(() => [BillingPriceUnionDTO]) + prices: Array<BillingPriceLicensedDTO> | Array<BillingPriceMeteredDTO>; } diff --git a/packages/twenty-server/src/engine/core-modules/billing/dtos/outputs/billing-product-prices.output.ts b/packages/twenty-server/src/engine/core-modules/billing/dtos/outputs/billing-product-prices.output.ts deleted file mode 100644 index 2108dab38..000000000 --- a/packages/twenty-server/src/engine/core-modules/billing/dtos/outputs/billing-product-prices.output.ts +++ /dev/null @@ -1,14 +0,0 @@ -/* @license Enterprise */ - -import { Field, Int, ObjectType } from '@nestjs/graphql'; - -import { BillingProductPriceDTO } from 'src/engine/core-modules/billing/dtos/billing-product-price.dto'; - -@ObjectType() -export class BillingProductPricesOutput { - @Field(() => Int) - totalNumberOfPrices: number; - - @Field(() => [BillingProductPriceDTO]) - productPrices: BillingProductPriceDTO[]; -} diff --git a/packages/twenty-server/src/engine/core-modules/billing/services/billing-plan.service.ts b/packages/twenty-server/src/engine/core-modules/billing/services/billing-plan.service.ts index 10fdc1d05..c56b52377 100644 --- a/packages/twenty-server/src/engine/core-modules/billing/services/billing-plan.service.ts +++ b/packages/twenty-server/src/engine/core-modules/billing/services/billing-plan.service.ts @@ -85,7 +85,7 @@ export class BillingPlanService { if (!baseProduct) { throw new BillingException( - 'Base product not found', + 'Base product not found, did you run the billing:sync-products command?', BillingExceptionCode.BILLING_PRODUCT_NOT_FOUND, ); } diff --git a/packages/twenty-server/src/engine/core-modules/billing/services/billing-subscription.service.ts b/packages/twenty-server/src/engine/core-modules/billing/services/billing-subscription.service.ts index 2009e7127..6a7e1d793 100644 --- a/packages/twenty-server/src/engine/core-modules/billing/services/billing-subscription.service.ts +++ b/packages/twenty-server/src/engine/core-modules/billing/services/billing-subscription.service.ts @@ -26,6 +26,7 @@ import { BillingProductService } from 'src/engine/core-modules/billing/services/ import { StripePriceService } from 'src/engine/core-modules/billing/stripe/services/stripe-price.service'; import { StripeSubscriptionItemService } from 'src/engine/core-modules/billing/stripe/services/stripe-subscription-item.service'; import { StripeSubscriptionService } from 'src/engine/core-modules/billing/stripe/services/stripe-subscription.service'; +import { getPlanKeyFromSubscription } from 'src/engine/core-modules/billing/utils/get-plan-key-from-subscription.util'; import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service'; import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum'; import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service'; @@ -176,8 +177,9 @@ export class BillingSubscriptionService { ); if (isBillingPlansEnabled) { + const planKey = getPlanKeyFromSubscription(billingSubscription); const billingProductsByPlan = - await this.billingProductService.getProductsByPlan(BillingPlanKey.PRO); + await this.billingProductService.getProductsByPlan(planKey); const pricesPerPlanArray = this.billingProductService.getProductPricesByInterval({ interval: newInterval, diff --git a/packages/twenty-server/src/engine/core-modules/billing/utils/__tests__/format-database-product-to-graphql-dto.util.spec.ts b/packages/twenty-server/src/engine/core-modules/billing/utils/__tests__/format-database-product-to-graphql-dto.util.spec.ts index 14cac3be6..fe3a2ef4d 100644 --- a/packages/twenty-server/src/engine/core-modules/billing/utils/__tests__/format-database-product-to-graphql-dto.util.spec.ts +++ b/packages/twenty-server/src/engine/core-modules/billing/utils/__tests__/format-database-product-to-graphql-dto.util.spec.ts @@ -19,6 +19,7 @@ describe('formatBillingDatabaseProductToGraphqlDTO', () => { interval: SubscriptionInterval.Month, unitAmount: 1000, stripePriceId: 'price_base1', + priceUsageType: BillingUsageType.LICENSED, }, ], }, @@ -31,6 +32,7 @@ describe('formatBillingDatabaseProductToGraphqlDTO', () => { interval: SubscriptionInterval.Year, unitAmount: 2000, stripePriceId: 'price_licensed1', + priceUsageType: BillingUsageType.LICENSED, }, ], }, @@ -51,6 +53,7 @@ describe('formatBillingDatabaseProductToGraphqlDTO', () => { }, ], stripePriceId: 'price_metered1', + priceUsageType: BillingUsageType.METERED, }, ], }, @@ -71,6 +74,7 @@ describe('formatBillingDatabaseProductToGraphqlDTO', () => { interval: SubscriptionInterval.Month, unitAmount: 1000, stripePriceId: 'price_base1', + priceUsageType: BillingUsageType.LICENSED, }, ], type: BillingUsageType.LICENSED, @@ -79,6 +83,7 @@ describe('formatBillingDatabaseProductToGraphqlDTO', () => { recurringInterval: SubscriptionInterval.Month, unitAmount: 1000, stripePriceId: 'price_base1', + priceUsageType: BillingUsageType.LICENSED, }, ], }, @@ -91,6 +96,7 @@ describe('formatBillingDatabaseProductToGraphqlDTO', () => { interval: SubscriptionInterval.Year, unitAmount: 2000, stripePriceId: 'price_licensed1', + priceUsageType: BillingUsageType.LICENSED, }, ], type: BillingUsageType.LICENSED, @@ -99,6 +105,7 @@ describe('formatBillingDatabaseProductToGraphqlDTO', () => { recurringInterval: SubscriptionInterval.Year, unitAmount: 2000, stripePriceId: 'price_licensed1', + priceUsageType: BillingUsageType.LICENSED, }, ], }, @@ -119,6 +126,7 @@ describe('formatBillingDatabaseProductToGraphqlDTO', () => { }, ], stripePriceId: 'price_metered1', + priceUsageType: BillingUsageType.METERED, }, ], type: BillingUsageType.METERED, @@ -134,6 +142,7 @@ describe('formatBillingDatabaseProductToGraphqlDTO', () => { ], recurringInterval: SubscriptionInterval.Month, stripePriceId: 'price_metered1', + priceUsageType: BillingUsageType.METERED, }, ], }, @@ -152,6 +161,7 @@ describe('formatBillingDatabaseProductToGraphqlDTO', () => { interval: null, unitAmount: null, stripePriceId: null, + priceUsageType: BillingUsageType.LICENSED, }, ], }, @@ -166,6 +176,7 @@ describe('formatBillingDatabaseProductToGraphqlDTO', () => { tiersMode: null, tiers: null, stripePriceId: null, + priceUsageType: BillingUsageType.METERED, }, ], }, @@ -186,6 +197,7 @@ describe('formatBillingDatabaseProductToGraphqlDTO', () => { interval: null, unitAmount: null, stripePriceId: null, + priceUsageType: BillingUsageType.LICENSED, }, ], type: BillingUsageType.LICENSED, @@ -194,6 +206,7 @@ describe('formatBillingDatabaseProductToGraphqlDTO', () => { recurringInterval: SubscriptionInterval.Month, unitAmount: 0, stripePriceId: null, + priceUsageType: BillingUsageType.LICENSED, }, ], }, @@ -208,6 +221,7 @@ describe('formatBillingDatabaseProductToGraphqlDTO', () => { tiersMode: null, tiers: null, stripePriceId: null, + priceUsageType: BillingUsageType.METERED, }, ], type: BillingUsageType.METERED, @@ -217,6 +231,7 @@ describe('formatBillingDatabaseProductToGraphqlDTO', () => { tiers: [], recurringInterval: SubscriptionInterval.Month, stripePriceId: null, + priceUsageType: BillingUsageType.METERED, }, ], }, diff --git a/packages/twenty-server/src/engine/core-modules/billing/utils/format-database-product-to-graphql-dto.util.ts b/packages/twenty-server/src/engine/core-modules/billing/utils/format-database-product-to-graphql-dto.util.ts index 55fe85b72..07e0a15e2 100644 --- a/packages/twenty-server/src/engine/core-modules/billing/utils/format-database-product-to-graphql-dto.util.ts +++ b/packages/twenty-server/src/engine/core-modules/billing/utils/format-database-product-to-graphql-dto.util.ts @@ -58,6 +58,7 @@ const formatBillingDatabasePriceToMeteredPriceDTO = ( })) ?? [], recurringInterval: billingPrice?.interval ?? SubscriptionInterval.Month, stripePriceId: billingPrice?.stripePriceId, + priceUsageType: BillingUsageType.METERED, }; }; @@ -68,5 +69,6 @@ const formatBillingDatabasePriceToLicensedPriceDTO = ( recurringInterval: billingPrice?.interval ?? SubscriptionInterval.Month, unitAmount: billingPrice?.unitAmount ?? 0, stripePriceId: billingPrice?.stripePriceId, + priceUsageType: BillingUsageType.LICENSED, }; }; diff --git a/packages/twenty-server/src/engine/core-modules/billing/utils/get-plan-key-from-subscription.util.ts b/packages/twenty-server/src/engine/core-modules/billing/utils/get-plan-key-from-subscription.util.ts new file mode 100644 index 000000000..cf26b8433 --- /dev/null +++ b/packages/twenty-server/src/engine/core-modules/billing/utils/get-plan-key-from-subscription.util.ts @@ -0,0 +1,17 @@ +import { BillingSubscription } from 'src/engine/core-modules/billing/entities/billing-subscription.entity'; +import { BillingPlanKey } from 'src/engine/core-modules/billing/enums/billing-plan-key.enum'; + +export const getPlanKeyFromSubscription = ( + subscription: BillingSubscription, +): BillingPlanKey => { + const planKey = subscription.metadata?.planKey; + + switch (planKey) { + case 'PRO': + return BillingPlanKey.PRO; + case 'ENTERPRISE': + return BillingPlanKey.ENTERPRISE; + default: + return BillingPlanKey.PRO; + } +};