From 9fc5be1c4cb342160a2f700f9804b7a03dc1e12e Mon Sep 17 00:00:00 2001 From: Etienne <45695613+etiennejouan@users.noreply.github.com> Date: Thu, 7 May 2026 17:42:11 +0200 Subject: [PATCH] Billing - Migrate from Stripe metering (#20298) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **Overall strategy** **1. Introduce “Billing V2” behind a workspace flag** Gate the new model with FeatureFlagKey.IS_BILLING_V2_ENABLED so existing workspaces stay on the old behavior until they’re migrated or explicitly on V2. **2. Replace workflow metered SKUs with a resource-credit product** Conceptually, billable “workflow execution” usage is not the primary subscription line item anymore. Add a RESOURCE_CREDIT product (and keep WORKFLOW_NODE_EXECUTION as deprecated for the transition). Usage and limits are expressed through credit buckets (e.g. price metadata like credit_amount), so one product can represent pooled credits instead of a narrow workflow-only meter. **3. Migrate subscriptions in two layers** Schema/catalog: persist extra price metadata (instance upgrade) so the server knows credit amounts and can match Stripe prices to the new model. Per workspace: the registered workspace command upgrade:2-2:migrate-to-billing-v2 finds subscriptions that still have WORKFLOW_NODE_EXECUTION, swaps those items to the right RESOURCE_CREDIT prices (using existing Stripe schedule + BillingSubscriptionUpdateService stack), then treats the workspace as V2 (flag). Workspaces without that legacy item or without a subscription are skipped. **4. Unify subscription lifecycle + usage on the server** **5. Refresh the product surface in Settings** Test : - [x] Subscribe v1 + Update subscribe + Migrate - [x] Subscribe v2 + Update subscribe --- .../src/metadata/generated/schema.graphql | 6 +- .../src/metadata/generated/schema.ts | 16 +- .../src/metadata/generated/types.ts | 8 +- .../src/generated-admin/graphql.ts | 1 + .../src/generated-metadata/graphql.ts | 14 +- .../AIChatNoMoreBillingCreditsBanner.tsx | 57 +- .../SettingsAdminWorkspaceBillingContent.tsx | 4 +- .../components/SettingsBillingContent.tsx | 13 +- .../SettingsBillingCreditsSection.tsx | 69 ++- .../SettingsBillingSubscriptionInfo.tsx | 141 +++-- .../internal/MeteredPriceSelector.tsx | 32 +- .../internal/ResourceCreditPriceSelector.tsx | 209 +++++++ .../fragments/billingPriceLicensedFragment.ts | 1 + .../billing/graphql/queries/listPlans.ts | 17 +- .../billing/hooks/useAllBillingPrices.ts | 10 +- .../billing/hooks/useBaseProductByPlanKey.ts | 2 +- .../billing/hooks/useBillingWording.ts | 10 +- .../billing/hooks/useCurrentMetered.ts | 32 +- .../billing/hooks/useCurrentResourceCredit.ts | 51 ++ .../hooks/useGetNextResourceCreditPrice.ts | 67 +++ .../hooks/useGetResourceCreditUsage.ts | 43 ++ .../settings/billing/hooks/useListProducts.ts | 3 +- .../billing/hooks/useNextBillingSeats.ts | 10 +- .../settings/billing/hooks/useNextPlan.ts | 8 +- .../billing/hooks/usePlanByPriceId.ts | 5 +- .../hooks/useSplitPhaseItemsInPrices.ts | 31 +- .../twenty-front/src/testing/graphqlMocks.ts | 38 ++ .../src/testing/mock-data/billing-plans.ts | 10 +- .../src/testing/mock-data/users.ts | 2 +- ...100000000-add-metadata-to-billing-price.ts | 37 ++ .../2-4/2-4-upgrade-version-command.module.ts | 18 +- ...000001000-migrate-to-billing-v2.command.ts | 409 +++++++++++++ .../instance-commands.constant.ts | 8 +- .../billing-webhook-invoice.service.ts | 58 +- .../billing-webhook-subscription.service.ts | 15 - ...price-event-to-database-price.util.spec.ts | 1 + ...ripe-price-event-to-database-price.util.ts | 1 + .../app-billing/app-billing.controller.ts | 2 +- .../billing/app-billing/app-billing.module.ts | 4 + .../app-billing/app-billing.service.ts | 25 +- .../core-modules/billing/billing.module.ts | 1 + .../core-modules/billing/billing.resolver.ts | 44 +- .../core-modules/billing/billing.validate.ts | 55 +- .../__test__/enforce-usage-cap.job.spec.ts | 18 +- .../billing/crons/enforce-usage-cap.job.ts | 38 +- .../billing/dtos/billing-plan.dto.ts | 5 +- .../dtos/billing-price-licensed.dto.ts | 3 + .../billing/entities/billing-price.entity.ts | 4 + .../billing/enums/billing-product-key.enum.ts | 2 + .../listeners/billing-usage-event.listener.ts | 13 + .../billing-credit-rollover.service.spec.ts | 7 + ...illing-subscription-update.service.spec.ts | 78 ++- .../billing-usage-cap.service.spec.ts | 22 +- .../__test__/utils/mock-builders.util.ts | 38 +- .../billing-credit-rollover.service.ts | 29 + .../billing/services/billing-plan.service.ts | 33 +- .../billing-portal.workspace-service.ts | 95 ++- .../billing/services/billing-price.service.ts | 63 ++ .../services/billing-product.service.ts | 6 +- .../billing-subscription-item.service.ts | 60 +- .../billing-subscription-phase.service.ts | 131 ++++- .../billing-subscription-update.service.ts | 549 ++++++++++++++---- .../services/billing-subscription.service.ts | 71 +-- .../services/billing-usage-cap.service.ts | 57 +- .../billing/services/billing-usage.service.ts | 177 +++++- .../services/metered-credit.service.ts | 70 +++ .../stripe/services/stripe-invoice.service.ts | 33 ++ .../stripe-subscription-schedule.service.ts | 5 +- .../types/billing-get-plan-result.type.ts | 3 +- ...billing-get-prices-per-plan-result.type.ts | 5 +- ...portal-checkout-session-parameters.type.ts | 2 +- .../types/billing-price-metadata.type.ts | 11 + .../types/billing-subscription-update.type.ts | 5 + ...e-subscription-update-options.util.spec.ts | 4 +- ...tabase-product-to-graphql-dto.util.spec.ts | 10 +- ...tripe-price-to-database-price.util.spec.ts | 1 + ...ompute-subscription-update-options.util.ts | 7 +- ...at-database-product-to-graphql-dto.util.ts | 22 +- ...product-subscription-item-or-throw.util.ts | 19 + ...-credit-subscription-item-or-throw.util.ts | 18 + ...orm-stripe-price-to-database-price.util.ts | 1 + .../logic-function-executor.service.ts | 25 +- .../core-modules/message-queue/jobs.module.ts | 2 + .../twenty-config/config-variables.ts | 8 + .../ai/ai-billing/ai-billing.module.ts | 8 +- .../__tests__/ai-billing.service.spec.ts | 11 + .../ai-billing/services/ai-billing.service.ts | 51 +- .../workspace-entity-manager.spec.ts | 2 + .../constant/default-feature-flags.ts | 1 + .../workflow-executor.module.ts | 2 + ...orkflow-executor.workspace-service.spec.ts | 13 + .../workflow-executor.workspace-service.ts | 26 +- .../twenty-shared/src/types/FeatureFlagKey.ts | 1 + 93 files changed, 2939 insertions(+), 514 deletions(-) create mode 100644 packages/twenty-front/src/modules/settings/billing/components/internal/ResourceCreditPriceSelector.tsx create mode 100644 packages/twenty-front/src/modules/settings/billing/hooks/useCurrentResourceCredit.ts create mode 100644 packages/twenty-front/src/modules/settings/billing/hooks/useGetNextResourceCreditPrice.ts create mode 100644 packages/twenty-front/src/modules/settings/billing/hooks/useGetResourceCreditUsage.ts create mode 100644 packages/twenty-server/src/database/commands/upgrade-version-command/2-4/2-4-instance-command-fast-1777100000000-add-metadata-to-billing-price.ts create mode 100644 packages/twenty-server/src/database/commands/upgrade-version-command/2-4/2-4-workspace-command-1797000001000-migrate-to-billing-v2.command.ts create mode 100644 packages/twenty-server/src/engine/core-modules/billing/types/billing-price-metadata.type.ts create mode 100644 packages/twenty-server/src/engine/core-modules/billing/utils/get-base-product-subscription-item-or-throw.util.ts create mode 100644 packages/twenty-server/src/engine/core-modules/billing/utils/get-resource-credit-subscription-item-or-throw.util.ts diff --git a/packages/twenty-client-sdk/src/metadata/generated/schema.graphql b/packages/twenty-client-sdk/src/metadata/generated/schema.graphql index 8cdaa85113a..1a2d277b4a7 100644 --- a/packages/twenty-client-sdk/src/metadata/generated/schema.graphql +++ b/packages/twenty-client-sdk/src/metadata/generated/schema.graphql @@ -1484,6 +1484,7 @@ enum BillingUsageType { """The different billing products available""" enum BillingProductKey { BASE_PRODUCT + RESOURCE_CREDIT WORKFLOW_NODE_EXECUTION } @@ -1492,6 +1493,7 @@ type BillingPriceLicensed { unitAmount: Float! stripePriceId: String! priceUsageType: BillingUsageType! + creditAmount: Float } enum SubscriptionInterval { @@ -1590,7 +1592,8 @@ type BillingMeteredProductUsage { type BillingPlan { planKey: BillingPlanKey! - licensedProducts: [BillingLicensedProduct!]! + baseProducts: [BillingLicensedProduct!]! + resourceCreditProducts: [BillingLicensedProduct!]! meteredProducts: [BillingMeteredProduct!]! } @@ -1754,6 +1757,7 @@ enum FeatureFlagKey { IS_RICH_TEXT_V1_MIGRATED IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED IS_DATASOURCE_MIGRATED + IS_BILLING_V2_ENABLED } type WorkspaceUrls { diff --git a/packages/twenty-client-sdk/src/metadata/generated/schema.ts b/packages/twenty-client-sdk/src/metadata/generated/schema.ts index 3e83953d551..6a72a63d328 100644 --- a/packages/twenty-client-sdk/src/metadata/generated/schema.ts +++ b/packages/twenty-client-sdk/src/metadata/generated/schema.ts @@ -1145,13 +1145,14 @@ export type BillingUsageType = 'METERED' | 'LICENSED' /** The different billing products available */ -export type BillingProductKey = 'BASE_PRODUCT' | 'WORKFLOW_NODE_EXECUTION' +export type BillingProductKey = 'BASE_PRODUCT' | 'RESOURCE_CREDIT' | 'WORKFLOW_NODE_EXECUTION' export interface BillingPriceLicensed { recurringInterval: SubscriptionInterval unitAmount: Scalars['Float'] stripePriceId: Scalars['String'] priceUsageType: BillingUsageType + creditAmount?: Scalars['Float'] __typename: 'BillingPriceLicensed' } @@ -1244,7 +1245,8 @@ export interface BillingMeteredProductUsage { export interface BillingPlan { planKey: BillingPlanKey - licensedProducts: BillingLicensedProduct[] + baseProducts: BillingLicensedProduct[] + resourceCreditProducts: BillingLicensedProduct[] meteredProducts: BillingMeteredProduct[] __typename: 'BillingPlan' } @@ -1386,7 +1388,7 @@ export interface FeatureFlag { __typename: 'FeatureFlag' } -export type FeatureFlagKey = 'IS_UNIQUE_INDEXES_ENABLED' | 'IS_JSON_FILTER_ENABLED' | 'IS_COMMAND_MENU_ITEM_ENABLED' | 'IS_MARKETPLACE_SETTING_TAB_VISIBLE' | 'IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED' | 'IS_PUBLIC_DOMAIN_ENABLED' | 'IS_EMAILING_DOMAIN_ENABLED' | 'IS_JUNCTION_RELATIONS_ENABLED' | 'IS_CONNECTED_ACCOUNT_MIGRATED' | 'IS_RICH_TEXT_V1_MIGRATED' | 'IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED' | 'IS_DATASOURCE_MIGRATED' +export type FeatureFlagKey = 'IS_UNIQUE_INDEXES_ENABLED' | 'IS_JSON_FILTER_ENABLED' | 'IS_COMMAND_MENU_ITEM_ENABLED' | 'IS_MARKETPLACE_SETTING_TAB_VISIBLE' | 'IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED' | 'IS_PUBLIC_DOMAIN_ENABLED' | 'IS_EMAILING_DOMAIN_ENABLED' | 'IS_JUNCTION_RELATIONS_ENABLED' | 'IS_CONNECTED_ACCOUNT_MIGRATED' | 'IS_RICH_TEXT_V1_MIGRATED' | 'IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED' | 'IS_DATASOURCE_MIGRATED' | 'IS_BILLING_V2_ENABLED' export interface WorkspaceUrls { customUrl?: Scalars['String'] @@ -4079,6 +4081,7 @@ export interface BillingPriceLicensedGenqlSelection{ unitAmount?: boolean | number stripePriceId?: boolean | number priceUsageType?: boolean | number + creditAmount?: boolean | number __typename?: boolean | number __scalar?: boolean | number } @@ -4177,7 +4180,8 @@ export interface BillingMeteredProductUsageGenqlSelection{ export interface BillingPlanGenqlSelection{ planKey?: boolean | number - licensedProducts?: BillingLicensedProductGenqlSelection + baseProducts?: BillingLicensedProductGenqlSelection + resourceCreditProducts?: BillingLicensedProductGenqlSelection meteredProducts?: BillingMeteredProductGenqlSelection __typename?: boolean | number __scalar?: boolean | number @@ -8629,6 +8633,7 @@ export const enumBillingUsageType = { export const enumBillingProductKey = { BASE_PRODUCT: 'BASE_PRODUCT' as const, + RESOURCE_CREDIT: 'RESOURCE_CREDIT' as const, WORKFLOW_NODE_EXECUTION: 'WORKFLOW_NODE_EXECUTION' as const } @@ -8690,7 +8695,8 @@ export const enumFeatureFlagKey = { IS_CONNECTED_ACCOUNT_MIGRATED: 'IS_CONNECTED_ACCOUNT_MIGRATED' as const, IS_RICH_TEXT_V1_MIGRATED: 'IS_RICH_TEXT_V1_MIGRATED' as const, IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED: 'IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED' as const, - IS_DATASOURCE_MIGRATED: 'IS_DATASOURCE_MIGRATED' as const + IS_DATASOURCE_MIGRATED: 'IS_DATASOURCE_MIGRATED' as const, + IS_BILLING_V2_ENABLED: 'IS_BILLING_V2_ENABLED' as const } export const enumIdentityProviderType = { diff --git a/packages/twenty-client-sdk/src/metadata/generated/types.ts b/packages/twenty-client-sdk/src/metadata/generated/types.ts index 5eed38d2b00..1251f03c64f 100644 --- a/packages/twenty-client-sdk/src/metadata/generated/types.ts +++ b/packages/twenty-client-sdk/src/metadata/generated/types.ts @@ -2956,6 +2956,9 @@ export default { "priceUsageType": [ 136 ], + "creditAmount": [ + 11 + ], "__typename": [ 1 ] @@ -3143,7 +3146,10 @@ export default { "planKey": [ 135 ], - "licensedProducts": [ + "baseProducts": [ + 143 + ], + "resourceCreditProducts": [ 143 ], "meteredProducts": [ diff --git a/packages/twenty-front/src/generated-admin/graphql.ts b/packages/twenty-front/src/generated-admin/graphql.ts index 19fb87c0622..ff6de35d187 100644 --- a/packages/twenty-front/src/generated-admin/graphql.ts +++ b/packages/twenty-front/src/generated-admin/graphql.ts @@ -270,6 +270,7 @@ export type FeatureFlag = { }; export enum FeatureFlagKey { + IS_BILLING_V2_ENABLED = 'IS_BILLING_V2_ENABLED', IS_COMMAND_MENU_ITEM_ENABLED = 'IS_COMMAND_MENU_ITEM_ENABLED', IS_CONNECTED_ACCOUNT_MIGRATED = 'IS_CONNECTED_ACCOUNT_MIGRATED', IS_DATASOURCE_MIGRATED = 'IS_DATASOURCE_MIGRATED', diff --git a/packages/twenty-front/src/generated-metadata/graphql.ts b/packages/twenty-front/src/generated-metadata/graphql.ts index 7bedf272ecc..39e8a9291ab 100644 --- a/packages/twenty-front/src/generated-metadata/graphql.ts +++ b/packages/twenty-front/src/generated-metadata/graphql.ts @@ -594,9 +594,10 @@ export type BillingMeteredProductUsage = { export type BillingPlan = { __typename?: 'BillingPlan'; - licensedProducts: Array; + baseProducts: Array; meteredProducts: Array; planKey: BillingPlanKey; + resourceCreditProducts: Array; }; /** The different billing plans available */ @@ -607,6 +608,7 @@ export enum BillingPlanKey { export type BillingPriceLicensed = { __typename?: 'BillingPriceLicensed'; + creditAmount?: Maybe; priceUsageType: BillingUsageType; recurringInterval: SubscriptionInterval; stripePriceId: Scalars['String']; @@ -646,6 +648,7 @@ export type BillingProductDto = { /** The different billing products available */ export enum BillingProductKey { BASE_PRODUCT = 'BASE_PRODUCT', + RESOURCE_CREDIT = 'RESOURCE_CREDIT', WORKFLOW_NODE_EXECUTION = 'WORKFLOW_NODE_EXECUTION' } @@ -1614,6 +1617,7 @@ export type FeatureFlag = { }; export enum FeatureFlagKey { + IS_BILLING_V2_ENABLED = 'IS_BILLING_V2_ENABLED', IS_COMMAND_MENU_ITEM_ENABLED = 'IS_COMMAND_MENU_ITEM_ENABLED', IS_CONNECTED_ACCOUNT_MIGRATED = 'IS_CONNECTED_ACCOUNT_MIGRATED', IS_DATASOURCE_MIGRATED = 'IS_DATASOURCE_MIGRATED', @@ -6993,7 +6997,7 @@ export type ApplicationConnectionProvidersQueryVariables = Exact<{ export type ApplicationConnectionProvidersQuery = { __typename?: 'Query', applicationConnectionProviders: Array<{ __typename?: 'ApplicationConnectionProvider', id: string, applicationId: string, type: string, name: string, displayName: string, oauth?: { __typename?: 'ApplicationConnectionProviderOAuthConfig', scopes: Array, isClientCredentialsConfigured: boolean } | null }> }; -export type BillingPriceLicensedFragmentFragment = { __typename?: 'BillingPriceLicensed', stripePriceId: string, unitAmount: number, recurringInterval: SubscriptionInterval, priceUsageType: BillingUsageType }; +export type BillingPriceLicensedFragmentFragment = { __typename?: 'BillingPriceLicensed', stripePriceId: string, unitAmount: number, recurringInterval: SubscriptionInterval, priceUsageType: BillingUsageType, creditAmount?: number | null }; export type BillingPriceMeteredFragmentFragment = { __typename?: 'BillingPriceMetered', priceUsageType: BillingUsageType, recurringInterval: SubscriptionInterval, stripePriceId: string, tiers: Array<{ __typename?: 'BillingPriceTier', flatAmount?: number | null, unitAmount?: number | null, upTo?: number | null }> }; @@ -7063,7 +7067,7 @@ export type GetMeteredProductsUsageQuery = { __typename?: 'Query', getMeteredPro export type ListPlansQueryVariables = Exact<{ [key: string]: never; }>; -export type ListPlansQuery = { __typename?: 'Query', listPlans: Array<{ __typename?: 'BillingPlan', planKey: BillingPlanKey, licensedProducts: Array<{ __typename?: 'BillingLicensedProduct', name: string, description: string, images?: Array | null, prices?: Array<{ __typename?: 'BillingPriceLicensed', stripePriceId: string, unitAmount: number, recurringInterval: SubscriptionInterval, priceUsageType: BillingUsageType }> | null, metadata: { __typename?: 'BillingProductMetadata', productKey: BillingProductKey, planKey: BillingPlanKey, priceUsageBased: BillingUsageType } }>, meteredProducts: Array<{ __typename?: 'BillingMeteredProduct', name: string, description: string, images?: Array | null, prices?: Array<{ __typename?: 'BillingPriceMetered', priceUsageType: BillingUsageType, recurringInterval: SubscriptionInterval, stripePriceId: string, tiers: Array<{ __typename?: 'BillingPriceTier', flatAmount?: number | null, unitAmount?: number | null, upTo?: number | null }> }> | null, metadata: { __typename?: 'BillingProductMetadata', productKey: BillingProductKey, planKey: BillingPlanKey, priceUsageBased: BillingUsageType } }> }> }; +export type ListPlansQuery = { __typename?: 'Query', listPlans: Array<{ __typename?: 'BillingPlan', planKey: BillingPlanKey, baseProducts: Array<{ __typename?: 'BillingLicensedProduct', name: string, description: string, images?: Array | null, prices?: Array<{ __typename?: 'BillingPriceLicensed', stripePriceId: string, unitAmount: number, recurringInterval: SubscriptionInterval, priceUsageType: BillingUsageType, creditAmount?: number | null }> | null, metadata: { __typename?: 'BillingProductMetadata', productKey: BillingProductKey, planKey: BillingPlanKey, priceUsageBased: BillingUsageType } }>, resourceCreditProducts: Array<{ __typename?: 'BillingLicensedProduct', name: string, description: string, images?: Array | null, prices?: Array<{ __typename?: 'BillingPriceLicensed', stripePriceId: string, unitAmount: number, recurringInterval: SubscriptionInterval, priceUsageType: BillingUsageType, creditAmount?: number | null }> | null, metadata: { __typename?: 'BillingProductMetadata', productKey: BillingProductKey, planKey: BillingPlanKey, priceUsageBased: BillingUsageType } }>, meteredProducts: Array<{ __typename?: 'BillingMeteredProduct', name: string, description: string, images?: Array | null, prices?: Array<{ __typename?: 'BillingPriceMetered', priceUsageType: BillingUsageType, recurringInterval: SubscriptionInterval, stripePriceId: string, tiers: Array<{ __typename?: 'BillingPriceTier', flatAmount?: number | null, unitAmount?: number | null, upTo?: number | null }> }> | null, metadata: { __typename?: 'BillingProductMetadata', productKey: BillingProductKey, planKey: BillingPlanKey, priceUsageBased: BillingUsageType } }> }> }; export type ApiKeyFragmentFragment = { __typename?: 'ApiKey', id: string, name: string, expiresAt: string, revokedAt?: string | null, role: { __typename?: 'Role', id: string, label: string, icon?: string | null } }; @@ -7870,7 +7874,7 @@ export const NavigationMenuItemFieldsFragmentDoc = {"kind":"Document","definitio export const NavigationMenuItemQueryFieldsFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"NavigationMenuItemQueryFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"NavigationMenuItem"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"NavigationMenuItemFields"}},{"kind":"Field","name":{"kind":"Name","value":"targetRecordIdentifier"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"labelIdentifier"}},{"kind":"Field","name":{"kind":"Name","value":"imageIdentifier"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"NavigationMenuItemFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"NavigationMenuItem"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"userWorkspaceId"}},{"kind":"Field","name":{"kind":"Name","value":"targetRecordId"}},{"kind":"Field","name":{"kind":"Name","value":"targetObjectMetadataId"}},{"kind":"Field","name":{"kind":"Name","value":"viewId"}},{"kind":"Field","name":{"kind":"Name","value":"folderId"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"link"}},{"kind":"Field","name":{"kind":"Name","value":"icon"}},{"kind":"Field","name":{"kind":"Name","value":"color"}},{"kind":"Field","name":{"kind":"Name","value":"pageLayoutId"}},{"kind":"Field","name":{"kind":"Name","value":"position"}},{"kind":"Field","name":{"kind":"Name","value":"applicationId"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]} as unknown as DocumentNode; export const PublicConnectionParamsFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"PublicConnectionParams"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"PublicConnectionParametersOutput"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"host"}},{"kind":"Field","name":{"kind":"Name","value":"port"}},{"kind":"Field","name":{"kind":"Name","value":"secure"}},{"kind":"Field","name":{"kind":"Name","value":"username"}}]}}]} as unknown as DocumentNode; export const ApplicationRegistrationFragmentFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ApplicationRegistrationFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ApplicationRegistration"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"universalIdentifier"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"logoUrl"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthClientId"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthRedirectUris"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthScopes"}},{"kind":"Field","name":{"kind":"Name","value":"sourceType"}},{"kind":"Field","name":{"kind":"Name","value":"sourcePackage"}},{"kind":"Field","name":{"kind":"Name","value":"latestAvailableVersion"}},{"kind":"Field","name":{"kind":"Name","value":"isListed"}},{"kind":"Field","name":{"kind":"Name","value":"isFeatured"}},{"kind":"Field","name":{"kind":"Name","value":"isPreInstalled"}},{"kind":"Field","name":{"kind":"Name","value":"isConfigured"}},{"kind":"Field","name":{"kind":"Name","value":"ownerWorkspaceId"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]} as unknown as DocumentNode; -export const BillingPriceLicensedFragmentFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"BillingPriceLicensedFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"BillingPriceLicensed"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"stripePriceId"}},{"kind":"Field","name":{"kind":"Name","value":"unitAmount"}},{"kind":"Field","name":{"kind":"Name","value":"recurringInterval"}},{"kind":"Field","name":{"kind":"Name","value":"priceUsageType"}}]}}]} as unknown as DocumentNode; +export const BillingPriceLicensedFragmentFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"BillingPriceLicensedFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"BillingPriceLicensed"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"stripePriceId"}},{"kind":"Field","name":{"kind":"Name","value":"unitAmount"}},{"kind":"Field","name":{"kind":"Name","value":"recurringInterval"}},{"kind":"Field","name":{"kind":"Name","value":"priceUsageType"}},{"kind":"Field","name":{"kind":"Name","value":"creditAmount"}}]}}]} as unknown as DocumentNode; export const BillingPriceMeteredFragmentFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"BillingPriceMeteredFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"BillingPriceMetered"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"priceUsageType"}},{"kind":"Field","name":{"kind":"Name","value":"recurringInterval"}},{"kind":"Field","name":{"kind":"Name","value":"stripePriceId"}},{"kind":"Field","name":{"kind":"Name","value":"tiers"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"flatAmount"}},{"kind":"Field","name":{"kind":"Name","value":"unitAmount"}},{"kind":"Field","name":{"kind":"Name","value":"upTo"}}]}}]}}]} as unknown as DocumentNode; export const ApiKeyFragmentFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ApiKeyFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ApiKey"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"expiresAt"}},{"kind":"Field","name":{"kind":"Name","value":"revokedAt"}},{"kind":"Field","name":{"kind":"Name","value":"role"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"icon"}}]}}]}}]} as unknown as DocumentNode; export const WebhookFragmentFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"WebhookFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Webhook"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"targetUrl"}},{"kind":"Field","name":{"kind":"Name","value":"operations"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"secret"}}]}}]} as unknown as DocumentNode; @@ -8046,7 +8050,7 @@ export const SwitchBillingPlanDocument = {"kind":"Document","definitions":[{"kin export const SwitchSubscriptionIntervalDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"SwitchSubscriptionInterval"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"switchSubscriptionInterval"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"currentBillingSubscription"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"CurrentBillingSubscriptionFragment"}}]}},{"kind":"Field","name":{"kind":"Name","value":"billingSubscriptions"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"BillingSubscriptionFragment"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"BillingSubscriptionSchedulePhaseItemFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"BillingSubscriptionSchedulePhaseItem"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"price"}},{"kind":"Field","name":{"kind":"Name","value":"quantity"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"BillingSubscriptionSchedulePhaseFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"BillingSubscriptionSchedulePhase"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"start_date"}},{"kind":"Field","name":{"kind":"Name","value":"end_date"}},{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"BillingSubscriptionSchedulePhaseItemFragment"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"CurrentBillingSubscriptionFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"BillingSubscription"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"interval"}},{"kind":"Field","name":{"kind":"Name","value":"metadata"}},{"kind":"Field","name":{"kind":"Name","value":"currentPeriodEnd"}},{"kind":"Field","name":{"kind":"Name","value":"phases"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"BillingSubscriptionSchedulePhaseFragment"}}]}},{"kind":"Field","name":{"kind":"Name","value":"billingSubscriptionItems"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"hasReachedCurrentPeriodCap"}},{"kind":"Field","name":{"kind":"Name","value":"quantity"}},{"kind":"Field","name":{"kind":"Name","value":"stripePriceId"}},{"kind":"Field","name":{"kind":"Name","value":"billingProduct"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"images"}},{"kind":"Field","name":{"kind":"Name","value":"metadata"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"productKey"}},{"kind":"Field","name":{"kind":"Name","value":"planKey"}},{"kind":"Field","name":{"kind":"Name","value":"priceUsageBased"}}]}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"BillingSubscriptionFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"BillingSubscription"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"metadata"}},{"kind":"Field","name":{"kind":"Name","value":"phases"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"BillingSubscriptionSchedulePhaseFragment"}}]}}]}}]} as unknown as DocumentNode; export const BillingPortalSessionDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"BillingPortalSession"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"returnUrlPath"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"billingPortalSession"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"returnUrlPath"},"value":{"kind":"Variable","name":{"kind":"Name","value":"returnUrlPath"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"url"}}]}}]}}]} as unknown as DocumentNode; export const GetMeteredProductsUsageDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetMeteredProductsUsage"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"getMeteredProductsUsage"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"productKey"}},{"kind":"Field","name":{"kind":"Name","value":"usedCredits"}},{"kind":"Field","name":{"kind":"Name","value":"grantedCredits"}},{"kind":"Field","name":{"kind":"Name","value":"rolloverCredits"}},{"kind":"Field","name":{"kind":"Name","value":"totalGrantedCredits"}},{"kind":"Field","name":{"kind":"Name","value":"unitPriceCents"}}]}}]}}]} as unknown as DocumentNode; -export const ListPlansDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"listPlans"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"listPlans"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"planKey"}},{"kind":"Field","name":{"kind":"Name","value":"licensedProducts"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"images"}},{"kind":"Field","name":{"kind":"Name","value":"metadata"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"productKey"}},{"kind":"Field","name":{"kind":"Name","value":"planKey"}},{"kind":"Field","name":{"kind":"Name","value":"priceUsageBased"}}]}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"BillingLicensedProduct"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"prices"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"BillingPriceLicensedFragment"}}]}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"meteredProducts"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"images"}},{"kind":"Field","name":{"kind":"Name","value":"metadata"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"productKey"}},{"kind":"Field","name":{"kind":"Name","value":"planKey"}},{"kind":"Field","name":{"kind":"Name","value":"priceUsageBased"}}]}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"BillingMeteredProduct"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"prices"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"BillingPriceMeteredFragment"}}]}}]}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"BillingPriceLicensedFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"BillingPriceLicensed"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"stripePriceId"}},{"kind":"Field","name":{"kind":"Name","value":"unitAmount"}},{"kind":"Field","name":{"kind":"Name","value":"recurringInterval"}},{"kind":"Field","name":{"kind":"Name","value":"priceUsageType"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"BillingPriceMeteredFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"BillingPriceMetered"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"priceUsageType"}},{"kind":"Field","name":{"kind":"Name","value":"recurringInterval"}},{"kind":"Field","name":{"kind":"Name","value":"stripePriceId"}},{"kind":"Field","name":{"kind":"Name","value":"tiers"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"flatAmount"}},{"kind":"Field","name":{"kind":"Name","value":"unitAmount"}},{"kind":"Field","name":{"kind":"Name","value":"upTo"}}]}}]}}]} as unknown as DocumentNode; +export const ListPlansDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"listPlans"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"listPlans"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"planKey"}},{"kind":"Field","name":{"kind":"Name","value":"baseProducts"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"images"}},{"kind":"Field","name":{"kind":"Name","value":"metadata"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"productKey"}},{"kind":"Field","name":{"kind":"Name","value":"planKey"}},{"kind":"Field","name":{"kind":"Name","value":"priceUsageBased"}}]}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"BillingLicensedProduct"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"prices"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"BillingPriceLicensedFragment"}}]}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"resourceCreditProducts"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"images"}},{"kind":"Field","name":{"kind":"Name","value":"metadata"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"productKey"}},{"kind":"Field","name":{"kind":"Name","value":"planKey"}},{"kind":"Field","name":{"kind":"Name","value":"priceUsageBased"}}]}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"BillingLicensedProduct"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"prices"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"BillingPriceLicensedFragment"}}]}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"meteredProducts"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"images"}},{"kind":"Field","name":{"kind":"Name","value":"metadata"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"productKey"}},{"kind":"Field","name":{"kind":"Name","value":"planKey"}},{"kind":"Field","name":{"kind":"Name","value":"priceUsageBased"}}]}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"BillingMeteredProduct"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"prices"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"BillingPriceMeteredFragment"}}]}}]}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"BillingPriceLicensedFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"BillingPriceLicensed"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"stripePriceId"}},{"kind":"Field","name":{"kind":"Name","value":"unitAmount"}},{"kind":"Field","name":{"kind":"Name","value":"recurringInterval"}},{"kind":"Field","name":{"kind":"Name","value":"priceUsageType"}},{"kind":"Field","name":{"kind":"Name","value":"creditAmount"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"BillingPriceMeteredFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"BillingPriceMetered"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"priceUsageType"}},{"kind":"Field","name":{"kind":"Name","value":"recurringInterval"}},{"kind":"Field","name":{"kind":"Name","value":"stripePriceId"}},{"kind":"Field","name":{"kind":"Name","value":"tiers"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"flatAmount"}},{"kind":"Field","name":{"kind":"Name","value":"unitAmount"}},{"kind":"Field","name":{"kind":"Name","value":"upTo"}}]}}]}}]} as unknown as DocumentNode; export const AssignRoleToApiKeyDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"AssignRoleToApiKey"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"apiKeyId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UUID"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"roleId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UUID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"assignRoleToApiKey"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"apiKeyId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"apiKeyId"}}},{"kind":"Argument","name":{"kind":"Name","value":"roleId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"roleId"}}}]}]}}]} as unknown as DocumentNode; export const CreateApiKeyDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreateApiKey"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"CreateApiKeyInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"createApiKey"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ApiKeyFragment"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ApiKeyFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ApiKey"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"expiresAt"}},{"kind":"Field","name":{"kind":"Name","value":"revokedAt"}},{"kind":"Field","name":{"kind":"Name","value":"role"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"icon"}}]}}]}}]} as unknown as DocumentNode; export const CreateWebhookDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreateWebhook"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"CreateWebhookInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"createWebhook"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"WebhookFragment"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"WebhookFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Webhook"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"targetUrl"}},{"kind":"Field","name":{"kind":"Name","value":"operations"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"secret"}}]}}]} as unknown as DocumentNode; diff --git a/packages/twenty-front/src/modules/ai/components/AIChatNoMoreBillingCreditsBanner.tsx b/packages/twenty-front/src/modules/ai/components/AIChatNoMoreBillingCreditsBanner.tsx index abf02e0c879..3427e9ffe9b 100644 --- a/packages/twenty-front/src/modules/ai/components/AIChatNoMoreBillingCreditsBanner.tsx +++ b/packages/twenty-front/src/modules/ai/components/AIChatNoMoreBillingCreditsBanner.tsx @@ -2,17 +2,20 @@ import { AiChatBanner } from '@/ai/components/AiChatBanner'; import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; import { useNumberFormat } from '@/localization/hooks/useNumberFormat'; import { useEndSubscriptionTrialPeriod } from '@/settings/billing/hooks/useEndSubscriptionTrialPeriod'; +import { useGetNextResourceCreditPrice } from '@/settings/billing/hooks/useGetNextResourceCreditPrice'; import { useGetNextMeteredBillingPrice } from '@/settings/billing/hooks/useGetNextMeteredBillingPrice'; import { usePermissionFlagMap } from '@/settings/roles/hooks/usePermissionFlagMap'; import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal'; import { useModal } from '@/ui/layout/modal/hooks/useModal'; import { useAtomState } from '@/ui/utilities/state/jotai/hooks/useAtomState'; +import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; import { useSubscriptionStatus } from '@/workspace/hooks/useSubscriptionStatus'; import { useMutation } from '@apollo/client/react'; import { t } from '@lingui/core/macro'; import { isDefined } from 'twenty-shared/utils'; import { + FeatureFlagKey, PermissionFlagType, SetMeteredSubscriptionPriceDocument, SubscriptionInterval, @@ -29,7 +32,14 @@ export const AIChatNoMoreBillingCreditsBanner = () => { const { openModal } = useModal(); const { endTrialPeriod, isLoading: isEndTrialLoading } = useEndSubscriptionTrialPeriod(); + + const isV2 = useIsFeatureEnabled(FeatureFlagKey.IS_BILLING_V2_ENABLED); + const nextMeteredBillingPrice = useGetNextMeteredBillingPrice(); + const nextResourceCreditPrice = useGetNextResourceCreditPrice(); + + const nextPrice = isV2 ? nextResourceCreditPrice : nextMeteredBillingPrice; + const { formatNumber } = useNumberFormat(); const { enqueueSuccessSnackBar, enqueueErrorSnackBar } = useSnackBar(); @@ -50,46 +60,65 @@ export const AIChatNoMoreBillingCreditsBanner = () => { const isTrialing = subscriptionStatus === SubscriptionStatus.Trialing; - const nextTierCredits = isDefined(nextMeteredBillingPrice) - ? formatNumber(nextMeteredBillingPrice.tiers[0].upTo, { - abbreviate: true, - decimals: 2, - }) + const nextTierCredits = isDefined(nextPrice) + ? isV2 + ? formatNumber( + (nextPrice as typeof nextResourceCreditPrice)?.creditAmount ?? 0, + { + abbreviate: true, + decimals: 2, + }, + ) + : formatNumber( + (nextPrice as typeof nextMeteredBillingPrice)?.tiers?.[0]?.upTo ?? 0, + { + abbreviate: true, + decimals: 2, + }, + ) : null; - const nextTierPrice = isDefined(nextMeteredBillingPrice) - ? formatNumber(nextMeteredBillingPrice.tiers[0].flatAmount / 100) + const nextTierPrice = isDefined(nextPrice) + ? isV2 + ? formatNumber( + ((nextPrice as typeof nextResourceCreditPrice)?.unitAmount ?? 0) / + 100, + ) + : formatNumber( + ((nextPrice as typeof nextMeteredBillingPrice)?.tiers?.[0] + ?.flatAmount ?? 0) / 100, + ) : null; - const nextTierInterval = isDefined(nextMeteredBillingPrice) - ? nextMeteredBillingPrice.recurringInterval === SubscriptionInterval.Month + const nextTierInterval = isDefined(nextPrice) + ? nextPrice.recurringInterval === SubscriptionInterval.Month ? t`month` : t`year` : null; const message = isTrialing ? t`You've hit your usage limit. Subscribe for more usage.` - : isDefined(nextMeteredBillingPrice) + : isDefined(nextPrice) ? t`You've hit your usage limit. \nUpgrade to ${nextTierCredits} credits for $${nextTierPrice}/${nextTierInterval}.` : t`You've hit your usage limit. \nReach to our support team to upgrade.`; const buttonTitle = isTrialing ? t`Subscribe Now` - : isDefined(nextMeteredBillingPrice) + : isDefined(nextPrice) ? t`Upgrade` : undefined; const handleButtonClick = isTrialing ? () => openModal(AI_CHAT_END_TRIAL_PERIOD_MODAL_ID) - : isDefined(nextMeteredBillingPrice) + : isDefined(nextPrice) ? () => openModal(AI_CHAT_UPGRADE_CREDIT_PLAN_MODAL_ID) : undefined; const handleUpgradeConfirm = async () => { - if (!isDefined(nextMeteredBillingPrice)) return; + if (!isDefined(nextPrice)) return; try { const { data } = await setMeteredSubscriptionPrice({ - variables: { priceId: nextMeteredBillingPrice.stripePriceId }, + variables: { priceId: nextPrice.stripePriceId }, }); if ( isDefined( diff --git a/packages/twenty-front/src/modules/settings/admin-panel/components/SettingsAdminWorkspaceBillingContent.tsx b/packages/twenty-front/src/modules/settings/admin-panel/components/SettingsAdminWorkspaceBillingContent.tsx index 37e3ad7f9b2..5d4a5706c27 100644 --- a/packages/twenty-front/src/modules/settings/admin-panel/components/SettingsAdminWorkspaceBillingContent.tsx +++ b/packages/twenty-front/src/modules/settings/admin-panel/components/SettingsAdminWorkspaceBillingContent.tsx @@ -38,6 +38,7 @@ import { const STRIPE_DASHBOARD_BASE_URL = 'https://dashboard.stripe.com'; const BASE_PRODUCT_KEY = 'BASE_PRODUCT'; const METERED_PRODUCT_KEY = 'WORKFLOW_NODE_EXECUTION'; +const RESOURCE_CREDIT_KEY = 'RESOURCE_CREDIT'; const EM_DASH = '\u2014'; type SettingsAdminWorkspaceBillingContentProps = { @@ -319,7 +320,8 @@ export const SettingsAdminWorkspaceBillingContent = ({ Icon: item.productKey === BASE_PRODUCT_KEY ? IconUsers - : item.productKey === METERED_PRODUCT_KEY + : item.productKey === METERED_PRODUCT_KEY || + item.productKey === RESOURCE_CREDIT_KEY ? IconCoins : IconBox, label: item.productName || t`Unnamed product`, diff --git a/packages/twenty-front/src/modules/settings/billing/components/SettingsBillingContent.tsx b/packages/twenty-front/src/modules/settings/billing/components/SettingsBillingContent.tsx index 197d0754b06..4a618a921c0 100644 --- a/packages/twenty-front/src/modules/settings/billing/components/SettingsBillingContent.tsx +++ b/packages/twenty-front/src/modules/settings/billing/components/SettingsBillingContent.tsx @@ -4,9 +4,11 @@ import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; import { useRedirect } from '@/domain-manager/hooks/useRedirect'; import { SettingsBillingCreditsSection } from '@/settings/billing/components/SettingsBillingCreditsSection'; import { SettingsBillingSubscriptionInfo } from '@/settings/billing/components/SettingsBillingSubscriptionInfo'; +import { useGetResourceCreditUsage } from '@/settings/billing/hooks/useGetResourceCreditUsage'; import { useGetWorkflowNodeExecutionUsage } from '@/settings/billing/hooks/useGetWorkflowNodeExecutionUsage'; import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer'; import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue'; +import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; import { useSubscriptionStatus } from '@/workspace/hooks/useSubscriptionStatus'; import { useQuery } from '@apollo/client/react'; import { isDefined } from 'twenty-shared/utils'; @@ -15,9 +17,9 @@ import { Button } from 'twenty-ui/input'; import { Section } from 'twenty-ui/layout'; import { BillingPortalSessionDocument, + FeatureFlagKey, SubscriptionStatus, } from '~/generated-metadata/graphql'; - export const SettingsBillingContent = () => { const { t } = useLingui(); @@ -31,8 +33,15 @@ export const SettingsBillingContent = () => { const subscriptionStatus = useSubscriptionStatus(); + const isV2 = useIsFeatureEnabled(FeatureFlagKey.IS_BILLING_V2_ENABLED); + const { isGetMeteredProductsUsageQueryLoaded } = useGetWorkflowNodeExecutionUsage(); + const { isGetResourceCreditUsageQueryLoaded } = useGetResourceCreditUsage(); + + const isUsageQueryLoaded = isV2 + ? isGetResourceCreditUsageQueryLoaded + : isGetMeteredProductsUsageQueryLoaded; const hasNotCanceledCurrentSubscription = isDefined(subscriptionStatus) && @@ -69,7 +78,7 @@ export const SettingsBillingContent = () => { {hasNotCanceledCurrentSubscription && currentWorkspace && currentWorkspace.currentBillingSubscription && - isGetMeteredProductsUsageQueryLoaded && ( + isUsageQueryLoaded && (
@@ -128,20 +143,27 @@ export const SettingsBillingCreditsSection = ({ })} /> )} - - - - + {!isV2 && ( + <> + + + + + + )} )} @@ -170,10 +192,17 @@ export const SettingsBillingCreditsSection = ({
- + {isV2 ? ( + + ) : ( + + )}
); diff --git a/packages/twenty-front/src/modules/settings/billing/components/SettingsBillingSubscriptionInfo.tsx b/packages/twenty-front/src/modules/settings/billing/components/SettingsBillingSubscriptionInfo.tsx index 285fbfa07ce..5bb19072e78 100644 --- a/packages/twenty-front/src/modules/settings/billing/components/SettingsBillingSubscriptionInfo.tsx +++ b/packages/twenty-front/src/modules/settings/billing/components/SettingsBillingSubscriptionInfo.tsx @@ -9,27 +9,31 @@ import { currentWorkspaceState, } from '@/auth/states/currentWorkspaceState'; +import { useNumberFormat } from '@/localization/hooks/useNumberFormat'; import { PlansTags } from '@/settings/billing/components/internal/PlansTags'; import { useBillingWording } from '@/settings/billing/hooks/useBillingWording'; import { useCurrentBillingFlags } from '@/settings/billing/hooks/useCurrentBillingFlags'; import { useCurrentMetered } from '@/settings/billing/hooks/useCurrentMetered'; import { useCurrentPlan } from '@/settings/billing/hooks/useCurrentPlan'; +import { useCurrentResourceCredit } from '@/settings/billing/hooks/useCurrentResourceCredit'; import { useEndSubscriptionTrialPeriod } from '@/settings/billing/hooks/useEndSubscriptionTrialPeriod'; +import { useGetResourceCreditUsage } from '@/settings/billing/hooks/useGetResourceCreditUsage'; import { useGetWorkflowNodeExecutionUsage } from '@/settings/billing/hooks/useGetWorkflowNodeExecutionUsage'; import { useHasNextBillingPhase } from '@/settings/billing/hooks/useHasNextBillingPhase'; import { useNextBillingPhase } from '@/settings/billing/hooks/useNextBillingPhase'; import { useNextBillingSeats } from '@/settings/billing/hooks/useNextBillingSeats'; import { useNextPlan } from '@/settings/billing/hooks/useNextPlan'; import { useSplitPhaseItemsInPrices } from '@/settings/billing/hooks/useSplitPhaseItemsInPrices'; -import { useNumberFormat } from '@/localization/hooks/useNumberFormat'; import { usePermissionFlagMap } from '@/settings/roles/hooks/usePermissionFlagMap'; import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal'; import { useModal } from '@/ui/layout/modal/hooks/useModal'; +import { useSetAtomState } from '@/ui/utilities/state/jotai/hooks/useSetAtomState'; +import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; import { useSubscriptionStatus } from '@/workspace/hooks/useSubscriptionStatus'; +import { useMutation } from '@apollo/client/react'; import { styled } from '@linaria/react'; import { useLingui } from '@lingui/react/macro'; -import { useSetAtomState } from '@/ui/utilities/state/jotai/hooks/useSetAtomState'; import { useMemo, useState } from 'react'; import { isDefined } from 'twenty-shared/utils'; import { @@ -46,16 +50,16 @@ import { import { Button } from 'twenty-ui/input'; import { Section } from 'twenty-ui/layout'; import { themeCssVariables } from 'twenty-ui/theme-constants'; -import { useMutation } from '@apollo/client/react'; import { BillingPlanKey, BillingProductKey, - PermissionFlagType, - SubscriptionInterval, - SubscriptionStatus, CancelSwitchBillingIntervalDocument, CancelSwitchBillingPlanDocument, CancelSwitchMeteredPriceDocument, + FeatureFlagKey, + PermissionFlagType, + SubscriptionInterval, + SubscriptionStatus, SwitchBillingPlanDocument, SwitchSubscriptionIntervalDocument, } from '~/generated-metadata/graphql'; @@ -103,11 +107,19 @@ export const SettingsBillingSubscriptionInfo = ({ const { openModal } = useModal(); + const isV2 = useIsFeatureEnabled(FeatureFlagKey.IS_BILLING_V2_ENABLED); + const { refetchMeteredProductsUsage } = useGetWorkflowNodeExecutionUsage(); + const { refetchResourceCreditUsage } = useGetResourceCreditUsage(); + + const refetchUsage = isV2 + ? refetchResourceCreditUsage + : refetchMeteredProductsUsage; const { enqueueSuccessSnackBar, enqueueErrorSnackBar } = useSnackBar(); const { currentMeteredBillingPrice } = useCurrentMetered(); + const { currentResourceCreditBillingPrice } = useCurrentResourceCredit(); const { currentPlan, oppositPlan } = useCurrentPlan(); const { isEnterprisePlan, isYearlyPlan, isMonthlyPlan, isProPlan } = @@ -119,10 +131,25 @@ export const SettingsBillingSubscriptionInfo = ({ const { nextBillingSeats } = useNextBillingSeats(); const { nextBillingPhase } = useNextBillingPhase(); const nextInterval = - splitedPhaseItemsInPrices?.nextLicensedPrice?.recurringInterval; - const nextMeteredBillingPrice = splitedPhaseItemsInPrices.nextMereredPrice; + splitedPhaseItemsInPrices?.nextBasePrice?.recurringInterval; + const nextMeteredBillingPrice = splitedPhaseItemsInPrices.nextMeteredPrice; + const nextResourceCreditPrice = + splitedPhaseItemsInPrices.nextResourceCreditPrice; const subscriptionStatus = useSubscriptionStatus(); + const currentInterval = isV2 + ? currentBillingSubscription.interval + : currentMeteredBillingPrice?.recurringInterval; + + const currentCreditsByPeriod = isV2 + ? (currentResourceCreditBillingPrice?.creditAmount ?? null) + : ((currentMeteredBillingPrice as { tiers?: { upTo: number }[] } | null) + ?.tiers?.[0]?.upTo ?? null); + + const nextCreditsByPeriod = isV2 + ? (nextResourceCreditPrice?.creditAmount ?? null) + : (nextMeteredBillingPrice?.tiers?.[0]?.upTo ?? null); + const { getIntervalLabelAsAdjectiveCapitalize, confirmationModalSwitchToProMessage, @@ -210,7 +237,7 @@ export const SettingsBillingSubscriptionInfo = ({ currentBillingSubscription, billingSubscriptions, }); - refetchMeteredProductsUsage(); + refetchUsage(); }; const switchInterval = async () => { @@ -337,11 +364,15 @@ export const SettingsBillingSubscriptionInfo = ({ } enqueueSuccessSnackBar({ - message: t`Metered tier switching has been cancelled.`, + message: isV2 + ? t`Credit pack switching has been cancelled.` + : t`Metered tier switching has been cancelled.`, }); } catch { enqueueErrorSnackBar({ - message: t`Error while cancelling metered tier switching.`, + message: isV2 + ? t`Error while cancelling credit pack switching.` + : t`Error while cancelling metered tier switching.`, }); } finally { setIsCancellingMeteredSwitch(false); @@ -375,8 +406,7 @@ export const SettingsBillingSubscriptionInfo = ({ label={t`Billing interval`} Icon={IconCalendarEvent} currentValue={getIntervalLabelAsAdjectiveCapitalize( - currentMeteredBillingPrice.recurringInterval === - SubscriptionInterval.Month, + currentInterval === SubscriptionInterval.Month, )} nextValue={ nextInterval @@ -407,13 +437,17 @@ export const SettingsBillingSubscriptionInfo = ({ )} - {nextInterval && - currentMeteredBillingPrice.recurringInterval !== nextInterval && ( -