From 4be75fb7da8ab7cc3c5f34eec9926f3b735af706 Mon Sep 17 00:00:00 2001 From: Ana Sofia Marin Alexandre <61988046+anamarn@users.noreply.github.com> Date: Fri, 7 Mar 2025 07:46:22 -0300 Subject: [PATCH] eliminate IS_BILLING_PLANS_ENABLED feature flag (#10678) Solves: https://github.com/twentyhq/core-team-issues/issues/527 **TLDR:** Basically the title. Fetches the product and prices from the database instead of the environment variables. **What this means:** - new subscriptions in twenty will be hybrid (per seat subscription plus an usage base product) - right now the price for the usage base product is 0$ per unit - The existing subscription will work normally, however we will need to update their subscription items in order to contain the usage base product (remember that the pricing intervals like monthly or yearly should match in all the subscription items) - The previous point can be done using Stripe Postman **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 deleted 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 - Update the quantity of members in a workspace (add or delete) - Change the subscription interval --------- Co-authored-by: Etienne <45695613+etiennejouan@users.noreply.github.com> --- .../src/generated-metadata/graphql.ts | 24 +---- .../twenty-front/src/generated/graphql.tsx | 8 +- .../typeorm-seeds/core/feature-flags.ts | 5 -- .../core-modules/billing/billing.resolver.ts | 42 ++------- .../billing/dtos/billing-product-price.dto.ts | 21 ----- .../billing-portal.workspace-service.ts | 26 +----- .../services/billing-subscription.service.ts | 88 +++++-------------- .../billing/services/billing-usage.service.ts | 16 ---- .../services/stripe-checkout.service.ts | 6 +- .../stripe/services/stripe-price.service.ts | 53 ----------- .../stripe-subscription-item.service.ts | 17 ---- ...portal-checkout-session-parameters.type.ts | 1 - .../enums/feature-flag-key.enum.ts | 1 - 13 files changed, 33 insertions(+), 275 deletions(-) delete mode 100644 packages/twenty-server/src/engine/core-modules/billing/dtos/billing-product-price.dto.ts diff --git a/packages/twenty-front/src/generated-metadata/graphql.ts b/packages/twenty-front/src/generated-metadata/graphql.ts index 596056585..241964966 100644 --- a/packages/twenty-front/src/generated-metadata/graphql.ts +++ b/packages/twenty-front/src/generated-metadata/graphql.ts @@ -170,6 +170,7 @@ export type BillingPlanOutput = { export type BillingPriceLicensedDto = { __typename?: 'BillingPriceLicensedDTO'; + priceUsageType: BillingUsageType; recurringInterval: SubscriptionInterval; stripePriceId: Scalars['String']['output']; unitAmount: Scalars['Float']['output']; @@ -177,6 +178,7 @@ export type BillingPriceLicensedDto = { export type BillingPriceMeteredDto = { __typename?: 'BillingPriceMeteredDTO'; + priceUsageType: BillingUsageType; recurringInterval: SubscriptionInterval; stripePriceId: Scalars['String']['output']; tiers?: Maybe>; @@ -203,24 +205,10 @@ export type BillingProductDto = { description: Scalars['String']['output']; images?: Maybe>; name: Scalars['String']['output']; - prices: Array>; + prices: Array; type: BillingUsageType; }; -export type BillingProductPriceDto = { - __typename?: 'BillingProductPriceDTO'; - created: Scalars['Float']['output']; - recurringInterval: SubscriptionInterval; - stripePriceId: Scalars['String']['output']; - unitAmount: Scalars['Float']['output']; -}; - -export type BillingProductPricesOutput = { - __typename?: 'BillingProductPricesOutput'; - productPrices: Array; - totalNumberOfPrices: Scalars['Int']['output']; -}; - export type BillingSessionOutput = { __typename?: 'BillingSessionOutput'; url?: Maybe; @@ -560,7 +548,6 @@ export enum FeatureFlagKey { IsAirtableIntegrationEnabled = 'IsAirtableIntegrationEnabled', IsAnalyticsV2Enabled = 'IsAnalyticsV2Enabled', IsApprovedAccessDomainsEnabled = 'IsApprovedAccessDomainsEnabled', - IsBillingPlansEnabled = 'IsBillingPlansEnabled', IsCommandMenuV2Enabled = 'IsCommandMenuV2Enabled', IsCopilotEnabled = 'IsCopilotEnabled', IsCustomDomainEnabled = 'IsCustomDomainEnabled', @@ -1444,7 +1431,6 @@ export type Query = { getEnvironmentVariablesGrouped: EnvironmentVariablesOutput; getIndicatorHealthStatus: AdminPanelHealthServiceData; getPostgresCredentials?: Maybe; - getProductPrices: BillingProductPricesOutput; getPublicWorkspaceDataByDomain: PublicWorkspaceDataOutput; getRoles: Array; getSSOIdentityProviders: Array; @@ -1527,10 +1513,6 @@ export type QueryGetIndicatorHealthStatusArgs = { }; -export type QueryGetProductPricesArgs = { - product: Scalars['String']['input']; -}; - export type QueryGetServerlessFunctionSourceCodeArgs = { input: GetServerlessFunctionSourceCodeInput; diff --git a/packages/twenty-front/src/generated/graphql.tsx b/packages/twenty-front/src/generated/graphql.tsx index 19db5d21b..2a197d303 100644 --- a/packages/twenty-front/src/generated/graphql.tsx +++ b/packages/twenty-front/src/generated/graphql.tsx @@ -1,5 +1,5 @@ -import * as Apollo from '@apollo/client'; import { gql } from '@apollo/client'; +import * as Apollo from '@apollo/client'; export type Maybe = T | null; export type InputMaybe = Maybe; export type Exact = { [K in keyof T]: T[K] }; @@ -491,7 +491,6 @@ export enum FeatureFlagKey { IsAirtableIntegrationEnabled = 'IsAirtableIntegrationEnabled', IsAnalyticsV2Enabled = 'IsAnalyticsV2Enabled', IsApprovedAccessDomainsEnabled = 'IsApprovedAccessDomainsEnabled', - IsBillingPlansEnabled = 'IsBillingPlansEnabled', IsCommandMenuV2Enabled = 'IsCommandMenuV2Enabled', IsCopilotEnabled = 'IsCopilotEnabled', IsCustomDomainEnabled = 'IsCustomDomainEnabled', @@ -1390,11 +1389,6 @@ export type QueryGetIndicatorHealthStatusArgs = { }; -export type QueryGetProductPricesArgs = { - product: Scalars['String']; -}; - - export type QueryGetQueueMetricsArgs = { queueName: Scalars['String']; timeRange?: InputMaybe; diff --git a/packages/twenty-server/src/database/typeorm-seeds/core/feature-flags.ts b/packages/twenty-server/src/database/typeorm-seeds/core/feature-flags.ts index ada5fac4e..afdd4ff88 100644 --- a/packages/twenty-server/src/database/typeorm-seeds/core/feature-flags.ts +++ b/packages/twenty-server/src/database/typeorm-seeds/core/feature-flags.ts @@ -55,11 +55,6 @@ export const seedFeatureFlags = async ( workspaceId: workspaceId, value: true, }, - { - key: FeatureFlagKey.IsBillingPlansEnabled, - workspaceId: workspaceId, - value: true, - }, { key: FeatureFlagKey.IsUniqueIndexesEnabled, workspaceId: workspaceId, 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 918ddd318..85b9b9a73 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 @@ -3,7 +3,6 @@ import { UseFilters, UseGuards } from '@nestjs/common'; import { Args, Mutation, Query, Resolver } from '@nestjs/graphql'; -import { GraphQLError } from 'graphql'; import { isDefined } from 'twenty-shared'; import { BillingCheckoutSessionInput } from 'src/engine/core-modules/billing/dtos/inputs/billing-checkout-session.input'; @@ -11,13 +10,11 @@ import { BillingSessionInput } from 'src/engine/core-modules/billing/dtos/inputs import { BillingPlanOutput } from 'src/engine/core-modules/billing/dtos/outputs/billing-plan.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'; import { BillingPlanKey } from 'src/engine/core-modules/billing/enums/billing-plan-key.enum'; import { BillingPlanService } from 'src/engine/core-modules/billing/services/billing-plan.service'; import { BillingPortalWorkspaceService } from 'src/engine/core-modules/billing/services/billing-portal.workspace-service'; import { BillingSubscriptionService } from 'src/engine/core-modules/billing/services/billing-subscription.service'; import { BillingService } from 'src/engine/core-modules/billing/services/billing.service'; -import { StripePriceService } from 'src/engine/core-modules/billing/stripe/services/stripe-price.service'; import { BillingPortalCheckoutSessionParameters } from 'src/engine/core-modules/billing/types/billing-portal-checkout-session-parameters.type'; import { formatBillingDatabaseProductToGraphqlDTO } from 'src/engine/core-modules/billing/utils/format-database-product-to-graphql-dto.util'; import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum'; @@ -47,7 +44,6 @@ export class BillingResolver { private readonly billingSubscriptionService: BillingSubscriptionService, private readonly billingPortalWorkspaceService: BillingPortalWorkspaceService, private readonly billingPlanService: BillingPlanService, - private readonly stripePriceService: StripePriceService, private readonly featureFlagService: FeatureFlagService, private readonly billingService: BillingService, private readonly permissionsService: PermissionsService, @@ -90,11 +86,6 @@ export class BillingResolver { userWorkspaceId, isExecutedByApiKey: isDefined(apiKey), }); - const isBillingPlansEnabled = - await this.featureFlagService.isFeatureEnabled( - FeatureFlagKey.IsBillingPlansEnabled, - workspace.id, - ); const checkoutSessionParams: BillingPortalCheckoutSessionParameters = { user, @@ -104,37 +95,16 @@ export class BillingResolver { requirePaymentMethod, }; - if (isBillingPlansEnabled) { - const billingPricesPerPlan = - await this.billingPlanService.getPricesPerPlan({ - planKey: checkoutSessionParams.plan, - interval: recurringInterval, - }); - const checkoutSessionURL = - await this.billingPortalWorkspaceService.computeCheckoutSessionURL({ - ...checkoutSessionParams, - billingPricesPerPlan, - }); - - return { - url: checkoutSessionURL, - }; - } - - const productPrice = await this.stripePriceService.getStripePrice( - AvailableProduct.BasePlan, - recurringInterval, + const billingPricesPerPlan = await this.billingPlanService.getPricesPerPlan( + { + planKey: checkoutSessionParams.plan, + interval: recurringInterval, + }, ); - - if (!productPrice) { - throw new GraphQLError( - 'Product price not found for the given recurring interval', - ); - } const checkoutSessionURL = await this.billingPortalWorkspaceService.computeCheckoutSessionURL({ ...checkoutSessionParams, - priceId: productPrice.stripePriceId, + billingPricesPerPlan, }); return { diff --git a/packages/twenty-server/src/engine/core-modules/billing/dtos/billing-product-price.dto.ts b/packages/twenty-server/src/engine/core-modules/billing/dtos/billing-product-price.dto.ts deleted file mode 100644 index 085dafea5..000000000 --- a/packages/twenty-server/src/engine/core-modules/billing/dtos/billing-product-price.dto.ts +++ /dev/null @@ -1,21 +0,0 @@ -/* @license Enterprise */ - -import { Field, ObjectType } from '@nestjs/graphql'; - -import Stripe from 'stripe'; - -import { SubscriptionInterval } from 'src/engine/core-modules/billing/enums/billing-subscription-interval.enum'; -@ObjectType() -export class BillingProductPriceDTO { - @Field(() => SubscriptionInterval) - recurringInterval: Stripe.Price.Recurring.Interval; - - @Field(() => Number) - unitAmount: number; - - @Field(() => Number) - created: number; - - @Field(() => String) - stripePriceId: string; -} diff --git a/packages/twenty-server/src/engine/core-modules/billing/services/billing-portal.workspace-service.ts b/packages/twenty-server/src/engine/core-modules/billing/services/billing-portal.workspace-service.ts index d6ff677c0..8667a08ef 100644 --- a/packages/twenty-server/src/engine/core-modules/billing/services/billing-portal.workspace-service.ts +++ b/packages/twenty-server/src/engine/core-modules/billing/services/billing-portal.workspace-service.ts @@ -17,8 +17,6 @@ import { StripeCheckoutService } from 'src/engine/core-modules/billing/stripe/se import { BillingGetPricesPerPlanResult } from 'src/engine/core-modules/billing/types/billing-get-prices-per-plan-result.type'; import { BillingPortalCheckoutSessionParameters } from 'src/engine/core-modules/billing/types/billing-portal-checkout-session-parameters.type'; import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.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'; import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-workspace.entity'; import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; import { assert } from 'src/utils/assert'; @@ -30,7 +28,6 @@ export class BillingPortalWorkspaceService { private readonly stripeCheckoutService: StripeCheckoutService, private readonly stripeBillingPortalService: StripeBillingPortalService, private readonly domainManagerService: DomainManagerService, - private readonly featureFlagService: FeatureFlagService, @InjectRepository(BillingSubscription, 'core') private readonly billingSubscriptionRepository: Repository, @InjectRepository(UserWorkspace, 'core') @@ -43,7 +40,6 @@ export class BillingPortalWorkspaceService { billingPricesPerPlan, successUrlPath, plan, - priceId, requirePaymentMethod, }: BillingPortalCheckoutSessionParameters): Promise { const frontBaseUrl = this.domainManagerService.buildWorkspaceURL({ @@ -65,18 +61,11 @@ export class BillingPortalWorkspaceService { }); const stripeCustomerId = subscription?.stripeCustomerId; - const isBillingPlansEnabled = - await this.featureFlagService.isFeatureEnabled( - FeatureFlagKey.IsBillingPlansEnabled, - workspace.id, - ); const stripeSubscriptionLineItems = await this.getStripeSubscriptionLineItems({ quantity, - isBillingPlansEnabled, billingPricesPerPlan, - priceId, }); const checkoutSession = @@ -90,7 +79,6 @@ export class BillingPortalWorkspaceService { plan, requirePaymentMethod, withTrialPeriod: !isDefined(subscription), - isBillingPlansEnabled, }); assert(checkoutSession.url, 'Error: missing checkout.session.url'); @@ -139,16 +127,12 @@ export class BillingPortalWorkspaceService { private getStripeSubscriptionLineItems({ quantity, - isBillingPlansEnabled, billingPricesPerPlan, - priceId, }: { quantity: number; - isBillingPlansEnabled: boolean; billingPricesPerPlan?: BillingGetPricesPerPlanResult; - priceId?: string; }): Stripe.Checkout.SessionCreateParams.LineItem[] { - if (isBillingPlansEnabled && billingPricesPerPlan) { + if (billingPricesPerPlan) { return [ { price: billingPricesPerPlan.baseProductPrice.stripePriceId, @@ -160,14 +144,8 @@ export class BillingPortalWorkspaceService { ]; } - if (priceId && !isBillingPlansEnabled) { - return [{ price: priceId, quantity }]; - } - throw new BillingException( - isBillingPlansEnabled - ? 'Missing Billing prices per plan' - : 'Missing price id', + 'Missing Billing prices per plan', BillingExceptionCode.BILLING_PRICE_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 6a7e1d793..f045556a4 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 @@ -16,31 +16,21 @@ import { BillingEntitlement } from 'src/engine/core-modules/billing/entities/bil import { BillingPrice } from 'src/engine/core-modules/billing/entities/billing-price.entity'; import { BillingSubscriptionItem } from 'src/engine/core-modules/billing/entities/billing-subscription-item.entity'; import { BillingSubscription } from 'src/engine/core-modules/billing/entities/billing-subscription.entity'; -import { AvailableProduct } from 'src/engine/core-modules/billing/enums/billing-available-product.enum'; import { BillingEntitlementKey } from 'src/engine/core-modules/billing/enums/billing-entitlement-key.enum'; import { BillingPlanKey } from 'src/engine/core-modules/billing/enums/billing-plan-key.enum'; import { SubscriptionInterval } from 'src/engine/core-modules/billing/enums/billing-subscription-interval.enum'; import { SubscriptionStatus } from 'src/engine/core-modules/billing/enums/billing-subscription-status.enum'; import { BillingPlanService } from 'src/engine/core-modules/billing/services/billing-plan.service'; import { BillingProductService } from 'src/engine/core-modules/billing/services/billing-product.service'; -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'; import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; @Injectable() export class BillingSubscriptionService { protected readonly logger = new Logger(BillingSubscriptionService.name); constructor( private readonly stripeSubscriptionService: StripeSubscriptionService, - private readonly stripePriceService: StripePriceService, - private readonly stripeSubscriptionItemService: StripeSubscriptionItemService, private readonly billingPlanService: BillingPlanService, - private readonly environmentService: EnvironmentService, - private readonly featureFlagService: FeatureFlagService, private readonly billingProductService: BillingProductService, @InjectRepository(BillingEntitlement, 'core') private readonly billingEntitlementRepository: Repository, @@ -68,24 +58,14 @@ export class BillingSubscriptionService { async getBaseProductCurrentBillingSubscriptionItemOrThrow( workspaceId: string, - stripeBaseProductId = this.environmentService.get( - 'BILLING_STRIPE_BASE_PLAN_PRODUCT_ID', - ), ) { - const isBillingPlansEnabled = - await this.featureFlagService.isFeatureEnabled( - FeatureFlagKey.IsBillingPlansEnabled, - workspaceId, - ); - const billingSubscription = await this.getCurrentBillingSubscriptionOrThrow( { workspaceId }, ); - const getStripeProductId = isBillingPlansEnabled - ? (await this.billingPlanService.getPlanBaseProduct(BillingPlanKey.PRO)) - ?.stripeProductId - : stripeBaseProductId; + const getStripeProductId = ( + await this.billingPlanService.getPlanBaseProduct(BillingPlanKey.PRO) + )?.stripeProductId; if (!getStripeProductId) { throw new BillingException( @@ -136,7 +116,7 @@ export class BillingSubscriptionService { return { handleUnpaidInvoiceStripeSubscriptionId: - billingSubscription.stripeSubscriptionId, + billingSubscription?.stripeSubscriptionId, }; } @@ -161,57 +141,29 @@ export class BillingSubscriptionService { const billingSubscription = await this.getCurrentBillingSubscriptionOrThrow( { workspaceId: workspace.id }, ); - const isBillingPlansEnabled = - await this.featureFlagService.isFeatureEnabled( - FeatureFlagKey.IsBillingPlansEnabled, - workspace.id, - ); const newInterval = billingSubscription?.interval === SubscriptionInterval.Year ? SubscriptionInterval.Month : SubscriptionInterval.Year; - const billingBaseProductSubscriptionItem = - await this.getBaseProductCurrentBillingSubscriptionItemOrThrow( - workspace.id, - ); + const planKey = getPlanKeyFromSubscription(billingSubscription); + const billingProductsByPlan = + await this.billingProductService.getProductsByPlan(planKey); + const pricesPerPlanArray = + this.billingProductService.getProductPricesByInterval({ + interval: newInterval, + billingProductsByPlan, + }); - if (isBillingPlansEnabled) { - const planKey = getPlanKeyFromSubscription(billingSubscription); - const billingProductsByPlan = - await this.billingProductService.getProductsByPlan(planKey); - const pricesPerPlanArray = - this.billingProductService.getProductPricesByInterval({ - interval: newInterval, - billingProductsByPlan, - }); + const subscriptionItemsToUpdate = this.getSubscriptionItemsToUpdate( + billingSubscription, + pricesPerPlanArray, + ); - const subscriptionItemsToUpdate = this.getSubscriptionItemsToUpdate( - billingSubscription, - pricesPerPlanArray, - ); - - await this.stripeSubscriptionService.updateSubscriptionItems( - billingSubscription.stripeSubscriptionId, - subscriptionItemsToUpdate, - ); - } else { - const productPrice = await this.stripePriceService.getStripePrice( - AvailableProduct.BasePlan, - newInterval, - ); - - if (!productPrice) { - throw new Error( - `Cannot find product price for product ${AvailableProduct.BasePlan} and interval ${newInterval}`, - ); - } - - await this.stripeSubscriptionItemService.updateBillingSubscriptionItem( - billingBaseProductSubscriptionItem, - productPrice.stripePriceId, - ); - } + await this.stripeSubscriptionService.updateSubscriptionItems( + billingSubscription.stripeSubscriptionId, + subscriptionItemsToUpdate, + ); } private getSubscriptionItemsToUpdate( diff --git a/packages/twenty-server/src/engine/core-modules/billing/services/billing-usage.service.ts b/packages/twenty-server/src/engine/core-modules/billing/services/billing-usage.service.ts index 870d965de..fb7d2732f 100644 --- a/packages/twenty-server/src/engine/core-modules/billing/services/billing-usage.service.ts +++ b/packages/twenty-server/src/engine/core-modules/billing/services/billing-usage.service.ts @@ -13,9 +13,6 @@ import { BillingCustomer } from 'src/engine/core-modules/billing/entities/billin import { BillingSubscriptionService } from 'src/engine/core-modules/billing/services/billing-subscription.service'; import { StripeBillingMeterEventService } from 'src/engine/core-modules/billing/stripe/services/stripe-billing-meter-event.service'; import { BillingUsageEvent } from 'src/engine/core-modules/billing/types/billing-usage-event.type'; -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'; @Injectable() export class BillingUsageService { @@ -23,24 +20,11 @@ export class BillingUsageService { constructor( @InjectRepository(BillingCustomer, 'core') private readonly billingCustomerRepository: Repository, - private readonly featureFlagService: FeatureFlagService, private readonly billingSubscriptionService: BillingSubscriptionService, - private readonly environmentService: EnvironmentService, private readonly stripeBillingMeterEventService: StripeBillingMeterEventService, ) {} async canFeatureBeUsed(workspaceId: string): Promise { - const isBillingEnabled = this.environmentService.get('IS_BILLING_ENABLED'); - const isBillingPlansEnabled = - await this.featureFlagService.isFeatureEnabled( - FeatureFlagKey.IsBillingPlansEnabled, - workspaceId, - ); - - if (!isBillingPlansEnabled || !isBillingEnabled) { - return true; - } - const billingSubscription = await this.billingSubscriptionService.getCurrentBillingSubscriptionOrThrow( { diff --git a/packages/twenty-server/src/engine/core-modules/billing/stripe/services/stripe-checkout.service.ts b/packages/twenty-server/src/engine/core-modules/billing/stripe/services/stripe-checkout.service.ts index 1d68299d7..57b9f9199 100644 --- a/packages/twenty-server/src/engine/core-modules/billing/stripe/services/stripe-checkout.service.ts +++ b/packages/twenty-server/src/engine/core-modules/billing/stripe/services/stripe-checkout.service.ts @@ -36,7 +36,6 @@ export class StripeCheckoutService { plan = BillingPlanKey.PRO, requirePaymentMethod = true, withTrialPeriod, - isBillingPlansEnabled = false, }: { user: User; workspaceId: string; @@ -47,7 +46,6 @@ export class StripeCheckoutService { plan?: BillingPlanKey; requirePaymentMethod?: boolean; withTrialPeriod: boolean; - isBillingPlansEnabled: boolean; }): Promise { return await this.stripe.checkout.sessions.create({ line_items: stripeSubscriptionLineItems, @@ -66,9 +64,7 @@ export class StripeCheckoutService { ), trial_settings: { end_behavior: { - missing_payment_method: isBillingPlansEnabled - ? 'create_invoice' - : 'pause', + missing_payment_method: 'pause', }, }, } diff --git a/packages/twenty-server/src/engine/core-modules/billing/stripe/services/stripe-price.service.ts b/packages/twenty-server/src/engine/core-modules/billing/stripe/services/stripe-price.service.ts index 32b34531e..877516913 100644 --- a/packages/twenty-server/src/engine/core-modules/billing/stripe/services/stripe-price.service.ts +++ b/packages/twenty-server/src/engine/core-modules/billing/stripe/services/stripe-price.service.ts @@ -4,8 +4,6 @@ import { Injectable, Logger } from '@nestjs/common'; import Stripe from 'stripe'; -import { BillingProductPriceDTO } from 'src/engine/core-modules/billing/dtos/billing-product-price.dto'; -import { AvailableProduct } from 'src/engine/core-modules/billing/enums/billing-available-product.enum'; import { StripeSDKService } from 'src/engine/core-modules/billing/stripe/stripe-sdk/services/stripe-sdk.service'; import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service'; @@ -26,57 +24,6 @@ export class StripePriceService { ); } - async getStripePrices(product: AvailableProduct) { - const stripeProductId = this.getStripeProductId(product); - - const prices = await this.stripe.prices.search({ - query: `product: '${stripeProductId}'`, - }); - - return this.formatProductPrices(prices.data); - } - - async getStripePrice(product: AvailableProduct, recurringInterval: string) { - const productPrices = await this.getStripePrices(product); - - return productPrices.find( - (price) => price.recurringInterval === recurringInterval, - ); - } - - getStripeProductId(product: AvailableProduct) { - if (product === AvailableProduct.BasePlan) { - return this.environmentService.get('BILLING_STRIPE_BASE_PLAN_PRODUCT_ID'); - } - } - - formatProductPrices(prices: Stripe.Price[]): BillingProductPriceDTO[] { - const productPrices: BillingProductPriceDTO[] = Object.values( - prices - .filter((item) => item.recurring?.interval && item.unit_amount) - .reduce((acc, item: Stripe.Price) => { - const interval = item.recurring?.interval; - - if (!interval || !item.unit_amount) { - return acc; - } - - if (!acc[interval] || item.created > acc[interval].created) { - acc[interval] = { - unitAmount: item.unit_amount, - recurringInterval: interval, - created: item.created, - stripePriceId: item.id, - }; - } - - return acc satisfies Record; - }, {}), - ); - - return productPrices.sort((a, b) => a.unitAmount - b.unitAmount); - } - async getPricesByProductId(productId: string) { const prices = await this.stripe.prices.list({ product: productId, diff --git a/packages/twenty-server/src/engine/core-modules/billing/stripe/services/stripe-subscription-item.service.ts b/packages/twenty-server/src/engine/core-modules/billing/stripe/services/stripe-subscription-item.service.ts index 98e351a53..1f7a40b40 100644 --- a/packages/twenty-server/src/engine/core-modules/billing/stripe/services/stripe-subscription-item.service.ts +++ b/packages/twenty-server/src/engine/core-modules/billing/stripe/services/stripe-subscription-item.service.ts @@ -4,7 +4,6 @@ import { Injectable, Logger } from '@nestjs/common'; import Stripe from 'stripe'; -import { BillingSubscriptionItem } from 'src/engine/core-modules/billing/entities/billing-subscription-item.entity'; import { StripeSDKService } from 'src/engine/core-modules/billing/stripe/stripe-sdk/services/stripe-sdk.service'; import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service'; @@ -28,20 +27,4 @@ export class StripeSubscriptionItemService { async updateSubscriptionItem(stripeItemId: string, quantity: number) { await this.stripe.subscriptionItems.update(stripeItemId, { quantity }); } - - async updateBillingSubscriptionItem( - stripeSubscriptionItem: BillingSubscriptionItem, - stripePriceId: string, - ) { - await this.stripe.subscriptionItems.update( - stripeSubscriptionItem.stripeSubscriptionItemId, - { - price: stripePriceId, - quantity: - stripeSubscriptionItem.quantity === null - ? undefined - : stripeSubscriptionItem.quantity, - }, - ); - } } diff --git a/packages/twenty-server/src/engine/core-modules/billing/types/billing-portal-checkout-session-parameters.type.ts b/packages/twenty-server/src/engine/core-modules/billing/types/billing-portal-checkout-session-parameters.type.ts index d5abacdef..a3da4752a 100644 --- a/packages/twenty-server/src/engine/core-modules/billing/types/billing-portal-checkout-session-parameters.type.ts +++ b/packages/twenty-server/src/engine/core-modules/billing/types/billing-portal-checkout-session-parameters.type.ts @@ -11,6 +11,5 @@ export type BillingPortalCheckoutSessionParameters = { billingPricesPerPlan?: BillingGetPricesPerPlanResult; successUrlPath?: string; plan: BillingPlanKey; - priceId?: string; requirePaymentMethod?: boolean; }; diff --git a/packages/twenty-server/src/engine/core-modules/feature-flag/enums/feature-flag-key.enum.ts b/packages/twenty-server/src/engine/core-modules/feature-flag/enums/feature-flag-key.enum.ts index 40943e009..704bb80a5 100644 --- a/packages/twenty-server/src/engine/core-modules/feature-flag/enums/feature-flag-key.enum.ts +++ b/packages/twenty-server/src/engine/core-modules/feature-flag/enums/feature-flag-key.enum.ts @@ -12,7 +12,6 @@ export enum FeatureFlagKey { IsJsonFilterEnabled = 'IS_JSON_FILTER_ENABLED', IsCustomDomainEnabled = 'IS_CUSTOM_DOMAIN_ENABLED', IsApprovedAccessDomainsEnabled = 'IS_APPROVED_ACCESS_DOMAINS_ENABLED', - IsBillingPlansEnabled = 'IS_BILLING_PLANS_ENABLED', IsNewRelationEnabled = 'IS_NEW_RELATION_ENABLED', IsPermissionsEnabled = 'IS_PERMISSIONS_ENABLED', }