add trial period ending banner + server logic (#11389)
Solution : - if user reaches his workflow usage cap + in trial period > display banner - end trial period if user has payment method (if not, not possible) <img width="941" alt="Screenshot 2025-04-04 at 10 27 32" src="https://github.com/user-attachments/assets/d7a1d5f7-9b12-4a92-a7c7-15ef8847c790" />
This commit is contained in:
@ -137,6 +137,14 @@ export type Billing = {
|
|||||||
trialPeriods: Array<BillingTrialPeriodDto>;
|
trialPeriods: Array<BillingTrialPeriodDto>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type BillingEndTrialPeriodOutput = {
|
||||||
|
__typename?: 'BillingEndTrialPeriodOutput';
|
||||||
|
/** Boolean that confirms if a payment method was found */
|
||||||
|
hasPaymentMethod: Scalars['Boolean'];
|
||||||
|
/** Updated subscription status */
|
||||||
|
status?: Maybe<SubscriptionStatus>;
|
||||||
|
};
|
||||||
|
|
||||||
/** The different billing plans available */
|
/** The different billing plans available */
|
||||||
export enum BillingPlanKey {
|
export enum BillingPlanKey {
|
||||||
ENTERPRISE = 'ENTERPRISE',
|
ENTERPRISE = 'ENTERPRISE',
|
||||||
@ -145,9 +153,9 @@ export enum BillingPlanKey {
|
|||||||
|
|
||||||
export type BillingPlanOutput = {
|
export type BillingPlanOutput = {
|
||||||
__typename?: 'BillingPlanOutput';
|
__typename?: 'BillingPlanOutput';
|
||||||
baseProduct: BillingProductDto;
|
baseProduct: BillingProduct;
|
||||||
meteredProducts: Array<BillingProductDto>;
|
meteredProducts: Array<BillingProduct>;
|
||||||
otherLicensedProducts: Array<BillingProductDto>;
|
otherLicensedProducts: Array<BillingProduct>;
|
||||||
planKey: BillingPlanKey;
|
planKey: BillingPlanKey;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -183,13 +191,26 @@ export enum BillingPriceTiersMode {
|
|||||||
|
|
||||||
export type BillingPriceUnionDto = BillingPriceLicensedDto | BillingPriceMeteredDto;
|
export type BillingPriceUnionDto = BillingPriceLicensedDto | BillingPriceMeteredDto;
|
||||||
|
|
||||||
export type BillingProductDto = {
|
export type BillingProduct = {
|
||||||
__typename?: 'BillingProductDTO';
|
__typename?: 'BillingProduct';
|
||||||
description: Scalars['String'];
|
description: Scalars['String'];
|
||||||
images?: Maybe<Array<Scalars['String']>>;
|
images?: Maybe<Array<Scalars['String']>>;
|
||||||
|
metadata: BillingProductMetadata;
|
||||||
name: Scalars['String'];
|
name: Scalars['String'];
|
||||||
prices: Array<BillingPriceUnionDto>;
|
prices?: Maybe<Array<BillingPriceUnionDto>>;
|
||||||
type: BillingUsageType;
|
};
|
||||||
|
|
||||||
|
/** The different billing products available */
|
||||||
|
export enum BillingProductKey {
|
||||||
|
BASE_PRODUCT = 'BASE_PRODUCT',
|
||||||
|
WORKFLOW_NODE_EXECUTION = 'WORKFLOW_NODE_EXECUTION'
|
||||||
|
}
|
||||||
|
|
||||||
|
export type BillingProductMetadata = {
|
||||||
|
__typename?: 'BillingProductMetadata';
|
||||||
|
planKey: BillingPlanKey;
|
||||||
|
priceUsageBased: BillingUsageType;
|
||||||
|
productKey: BillingProductKey;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type BillingSessionOutput = {
|
export type BillingSessionOutput = {
|
||||||
@ -199,11 +220,19 @@ export type BillingSessionOutput = {
|
|||||||
|
|
||||||
export type BillingSubscription = {
|
export type BillingSubscription = {
|
||||||
__typename?: 'BillingSubscription';
|
__typename?: 'BillingSubscription';
|
||||||
|
billingSubscriptionItems?: Maybe<Array<BillingSubscriptionItem>>;
|
||||||
id: Scalars['UUID'];
|
id: Scalars['UUID'];
|
||||||
interval?: Maybe<SubscriptionInterval>;
|
interval?: Maybe<SubscriptionInterval>;
|
||||||
status: SubscriptionStatus;
|
status: SubscriptionStatus;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type BillingSubscriptionItem = {
|
||||||
|
__typename?: 'BillingSubscriptionItem';
|
||||||
|
billingProduct?: Maybe<BillingProduct>;
|
||||||
|
hasReachedCurrentPeriodCap: Scalars['Boolean'];
|
||||||
|
id: Scalars['UUID'];
|
||||||
|
};
|
||||||
|
|
||||||
export type BillingTrialPeriodDto = {
|
export type BillingTrialPeriodDto = {
|
||||||
__typename?: 'BillingTrialPeriodDTO';
|
__typename?: 'BillingTrialPeriodDTO';
|
||||||
duration: Scalars['Float'];
|
duration: Scalars['Float'];
|
||||||
@ -828,6 +857,7 @@ export type Mutation = {
|
|||||||
editSSOIdentityProvider: EditSsoOutput;
|
editSSOIdentityProvider: EditSsoOutput;
|
||||||
emailPasswordResetLink: EmailPasswordResetLink;
|
emailPasswordResetLink: EmailPasswordResetLink;
|
||||||
enablePostgresProxy: PostgresCredentials;
|
enablePostgresProxy: PostgresCredentials;
|
||||||
|
endSubscriptionTrialPeriod: BillingEndTrialPeriodOutput;
|
||||||
executeOneServerlessFunction: ServerlessFunctionExecutionResult;
|
executeOneServerlessFunction: ServerlessFunctionExecutionResult;
|
||||||
generateApiKeyToken: ApiKeyToken;
|
generateApiKeyToken: ApiKeyToken;
|
||||||
generateTransientToken: TransientToken;
|
generateTransientToken: TransientToken;
|
||||||
@ -2505,7 +2535,7 @@ export type ValidatePasswordResetTokenQuery = { __typename?: 'Query', validatePa
|
|||||||
export type BillingBaseProductPricesQueryVariables = Exact<{ [key: string]: never; }>;
|
export type BillingBaseProductPricesQueryVariables = Exact<{ [key: string]: never; }>;
|
||||||
|
|
||||||
|
|
||||||
export type BillingBaseProductPricesQuery = { __typename?: 'Query', plans: Array<{ __typename?: 'BillingPlanOutput', planKey: BillingPlanKey, baseProduct: { __typename?: 'BillingProductDTO', name: string, prices: Array<{ __typename?: 'BillingPriceLicensedDTO', unitAmount: number, stripePriceId: string, recurringInterval: SubscriptionInterval } | { __typename?: 'BillingPriceMeteredDTO' }> } }> };
|
export type BillingBaseProductPricesQuery = { __typename?: 'Query', plans: Array<{ __typename?: 'BillingPlanOutput', planKey: BillingPlanKey, baseProduct: { __typename?: 'BillingProduct', name: string, prices?: Array<{ __typename?: 'BillingPriceLicensedDTO', unitAmount: number, stripePriceId: string, recurringInterval: SubscriptionInterval } | { __typename?: 'BillingPriceMeteredDTO' }> | null } }> };
|
||||||
|
|
||||||
export type BillingPortalSessionQueryVariables = Exact<{
|
export type BillingPortalSessionQueryVariables = Exact<{
|
||||||
returnUrlPath?: InputMaybe<Scalars['String']>;
|
returnUrlPath?: InputMaybe<Scalars['String']>;
|
||||||
@ -2524,6 +2554,11 @@ export type CheckoutSessionMutationVariables = Exact<{
|
|||||||
|
|
||||||
export type CheckoutSessionMutation = { __typename?: 'Mutation', checkoutSession: { __typename?: 'BillingSessionOutput', url?: string | null } };
|
export type CheckoutSessionMutation = { __typename?: 'Mutation', checkoutSession: { __typename?: 'BillingSessionOutput', url?: string | null } };
|
||||||
|
|
||||||
|
export type EndSubscriptionTrialPeriodMutationVariables = Exact<{ [key: string]: never; }>;
|
||||||
|
|
||||||
|
|
||||||
|
export type EndSubscriptionTrialPeriodMutation = { __typename?: 'Mutation', endSubscriptionTrialPeriod: { __typename?: 'BillingEndTrialPeriodOutput', status?: SubscriptionStatus | null, hasPaymentMethod: boolean } };
|
||||||
|
|
||||||
export type UpdateBillingSubscriptionMutationVariables = Exact<{ [key: string]: never; }>;
|
export type UpdateBillingSubscriptionMutationVariables = Exact<{ [key: string]: never; }>;
|
||||||
|
|
||||||
|
|
||||||
@ -2695,7 +2730,7 @@ export type GetSsoIdentityProvidersQueryVariables = Exact<{ [key: string]: never
|
|||||||
|
|
||||||
export type GetSsoIdentityProvidersQuery = { __typename?: 'Query', getSSOIdentityProviders: Array<{ __typename?: 'FindAvailableSSOIDPOutput', type: IdentityProviderType, id: string, name: string, issuer: string, status: SsoIdentityProviderStatus }> };
|
export type GetSsoIdentityProvidersQuery = { __typename?: 'Query', getSSOIdentityProviders: Array<{ __typename?: 'FindAvailableSSOIDPOutput', type: IdentityProviderType, id: string, name: string, issuer: string, status: SsoIdentityProviderStatus }> };
|
||||||
|
|
||||||
export type UserQueryFragmentFragment = { __typename?: 'User', id: any, firstName: string, lastName: string, email: string, canAccessFullAdminPanel: boolean, canImpersonate: boolean, supportUserHash?: string | null, onboardingStatus?: OnboardingStatus | null, userVars: any, workspaceMember?: { __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, userEmail: string, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } } | null, workspaceMembers?: Array<{ __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, userEmail: string, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | null, currentUserWorkspace?: { __typename?: 'UserWorkspace', settingsPermissions?: Array<SettingPermissionType> | null, objectRecordsPermissions?: Array<PermissionsOnAllObjectRecords> | null } | null, currentWorkspace?: { __typename?: 'Workspace', id: any, displayName?: string | null, logo?: string | null, inviteHash?: string | null, allowImpersonation: boolean, activationStatus: WorkspaceActivationStatus, isPublicInviteLinkEnabled: boolean, isGoogleAuthEnabled: boolean, isMicrosoftAuthEnabled: boolean, isPasswordAuthEnabled: boolean, subdomain: string, hasValidEnterpriseKey: boolean, customDomain?: string | null, isCustomDomainEnabled: boolean, metadataVersion: number, workspaceMembersCount?: number | null, workspaceUrls: { __typename?: 'WorkspaceUrls', subdomainUrl: string, customUrl?: string | null }, featureFlags?: Array<{ __typename?: 'FeatureFlag', id: any, key: FeatureFlagKey, value: boolean, workspaceId: string }> | null, currentBillingSubscription?: { __typename?: 'BillingSubscription', id: any, status: SubscriptionStatus, interval?: SubscriptionInterval | null } | null, billingSubscriptions: Array<{ __typename?: 'BillingSubscription', id: any, status: SubscriptionStatus }>, defaultRole?: { __typename?: 'Role', id: string, label: string, description?: string | null, icon?: string | null, canUpdateAllSettings: boolean, isEditable: boolean, canReadAllObjectRecords: boolean, canUpdateAllObjectRecords: boolean, canSoftDeleteAllObjectRecords: boolean, canDestroyAllObjectRecords: boolean } | null } | null, workspaces: Array<{ __typename?: 'UserWorkspace', workspace?: { __typename?: 'Workspace', id: any, logo?: string | null, displayName?: string | null, subdomain: string, customDomain?: string | null, workspaceUrls: { __typename?: 'WorkspaceUrls', subdomainUrl: string, customUrl?: string | null } } | null }> };
|
export type UserQueryFragmentFragment = { __typename?: 'User', id: any, firstName: string, lastName: string, email: string, canAccessFullAdminPanel: boolean, canImpersonate: boolean, supportUserHash?: string | null, onboardingStatus?: OnboardingStatus | null, userVars: any, workspaceMember?: { __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, userEmail: string, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } } | null, workspaceMembers?: Array<{ __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, userEmail: string, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | null, currentUserWorkspace?: { __typename?: 'UserWorkspace', settingsPermissions?: Array<SettingPermissionType> | null, objectRecordsPermissions?: Array<PermissionsOnAllObjectRecords> | null } | null, currentWorkspace?: { __typename?: 'Workspace', id: any, displayName?: string | null, logo?: string | null, inviteHash?: string | null, allowImpersonation: boolean, activationStatus: WorkspaceActivationStatus, isPublicInviteLinkEnabled: boolean, isGoogleAuthEnabled: boolean, isMicrosoftAuthEnabled: boolean, isPasswordAuthEnabled: boolean, subdomain: string, hasValidEnterpriseKey: boolean, customDomain?: string | null, isCustomDomainEnabled: boolean, metadataVersion: number, workspaceMembersCount?: number | null, workspaceUrls: { __typename?: 'WorkspaceUrls', subdomainUrl: string, customUrl?: string | null }, featureFlags?: Array<{ __typename?: 'FeatureFlag', id: any, key: FeatureFlagKey, value: boolean, workspaceId: string }> | null, currentBillingSubscription?: { __typename?: 'BillingSubscription', id: any, status: SubscriptionStatus, interval?: SubscriptionInterval | null, billingSubscriptionItems?: Array<{ __typename?: 'BillingSubscriptionItem', id: any, hasReachedCurrentPeriodCap: boolean, billingProduct?: { __typename?: 'BillingProduct', name: string, description: string, metadata: { __typename?: 'BillingProductMetadata', planKey: BillingPlanKey, priceUsageBased: BillingUsageType, productKey: BillingProductKey } } | null }> | null } | null, billingSubscriptions: Array<{ __typename?: 'BillingSubscription', id: any, status: SubscriptionStatus }>, defaultRole?: { __typename?: 'Role', id: string, label: string, description?: string | null, icon?: string | null, canUpdateAllSettings: boolean, isEditable: boolean, canReadAllObjectRecords: boolean, canUpdateAllObjectRecords: boolean, canSoftDeleteAllObjectRecords: boolean, canDestroyAllObjectRecords: boolean } | null } | null, workspaces: Array<{ __typename?: 'UserWorkspace', workspace?: { __typename?: 'Workspace', id: any, logo?: string | null, displayName?: string | null, subdomain: string, customDomain?: string | null, workspaceUrls: { __typename?: 'WorkspaceUrls', subdomainUrl: string, customUrl?: string | null } } | null }> };
|
||||||
|
|
||||||
export type DeleteUserAccountMutationVariables = Exact<{ [key: string]: never; }>;
|
export type DeleteUserAccountMutationVariables = Exact<{ [key: string]: never; }>;
|
||||||
|
|
||||||
@ -2712,7 +2747,7 @@ export type UploadProfilePictureMutation = { __typename?: 'Mutation', uploadProf
|
|||||||
export type GetCurrentUserQueryVariables = Exact<{ [key: string]: never; }>;
|
export type GetCurrentUserQueryVariables = Exact<{ [key: string]: never; }>;
|
||||||
|
|
||||||
|
|
||||||
export type GetCurrentUserQuery = { __typename?: 'Query', currentUser: { __typename?: 'User', id: any, firstName: string, lastName: string, email: string, canAccessFullAdminPanel: boolean, canImpersonate: boolean, supportUserHash?: string | null, onboardingStatus?: OnboardingStatus | null, userVars: any, workspaceMember?: { __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, userEmail: string, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } } | null, workspaceMembers?: Array<{ __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, userEmail: string, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | null, currentUserWorkspace?: { __typename?: 'UserWorkspace', settingsPermissions?: Array<SettingPermissionType> | null, objectRecordsPermissions?: Array<PermissionsOnAllObjectRecords> | null } | null, currentWorkspace?: { __typename?: 'Workspace', id: any, displayName?: string | null, logo?: string | null, inviteHash?: string | null, allowImpersonation: boolean, activationStatus: WorkspaceActivationStatus, isPublicInviteLinkEnabled: boolean, isGoogleAuthEnabled: boolean, isMicrosoftAuthEnabled: boolean, isPasswordAuthEnabled: boolean, subdomain: string, hasValidEnterpriseKey: boolean, customDomain?: string | null, isCustomDomainEnabled: boolean, metadataVersion: number, workspaceMembersCount?: number | null, workspaceUrls: { __typename?: 'WorkspaceUrls', subdomainUrl: string, customUrl?: string | null }, featureFlags?: Array<{ __typename?: 'FeatureFlag', id: any, key: FeatureFlagKey, value: boolean, workspaceId: string }> | null, currentBillingSubscription?: { __typename?: 'BillingSubscription', id: any, status: SubscriptionStatus, interval?: SubscriptionInterval | null } | null, billingSubscriptions: Array<{ __typename?: 'BillingSubscription', id: any, status: SubscriptionStatus }>, defaultRole?: { __typename?: 'Role', id: string, label: string, description?: string | null, icon?: string | null, canUpdateAllSettings: boolean, isEditable: boolean, canReadAllObjectRecords: boolean, canUpdateAllObjectRecords: boolean, canSoftDeleteAllObjectRecords: boolean, canDestroyAllObjectRecords: boolean } | null } | null, workspaces: Array<{ __typename?: 'UserWorkspace', workspace?: { __typename?: 'Workspace', id: any, logo?: string | null, displayName?: string | null, subdomain: string, customDomain?: string | null, workspaceUrls: { __typename?: 'WorkspaceUrls', subdomainUrl: string, customUrl?: string | null } } | null }> } };
|
export type GetCurrentUserQuery = { __typename?: 'Query', currentUser: { __typename?: 'User', id: any, firstName: string, lastName: string, email: string, canAccessFullAdminPanel: boolean, canImpersonate: boolean, supportUserHash?: string | null, onboardingStatus?: OnboardingStatus | null, userVars: any, workspaceMember?: { __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, userEmail: string, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } } | null, workspaceMembers?: Array<{ __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, userEmail: string, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | null, currentUserWorkspace?: { __typename?: 'UserWorkspace', settingsPermissions?: Array<SettingPermissionType> | null, objectRecordsPermissions?: Array<PermissionsOnAllObjectRecords> | null } | null, currentWorkspace?: { __typename?: 'Workspace', id: any, displayName?: string | null, logo?: string | null, inviteHash?: string | null, allowImpersonation: boolean, activationStatus: WorkspaceActivationStatus, isPublicInviteLinkEnabled: boolean, isGoogleAuthEnabled: boolean, isMicrosoftAuthEnabled: boolean, isPasswordAuthEnabled: boolean, subdomain: string, hasValidEnterpriseKey: boolean, customDomain?: string | null, isCustomDomainEnabled: boolean, metadataVersion: number, workspaceMembersCount?: number | null, workspaceUrls: { __typename?: 'WorkspaceUrls', subdomainUrl: string, customUrl?: string | null }, featureFlags?: Array<{ __typename?: 'FeatureFlag', id: any, key: FeatureFlagKey, value: boolean, workspaceId: string }> | null, currentBillingSubscription?: { __typename?: 'BillingSubscription', id: any, status: SubscriptionStatus, interval?: SubscriptionInterval | null, billingSubscriptionItems?: Array<{ __typename?: 'BillingSubscriptionItem', id: any, hasReachedCurrentPeriodCap: boolean, billingProduct?: { __typename?: 'BillingProduct', name: string, description: string, metadata: { __typename?: 'BillingProductMetadata', planKey: BillingPlanKey, priceUsageBased: BillingUsageType, productKey: BillingProductKey } } | null }> | null } | null, billingSubscriptions: Array<{ __typename?: 'BillingSubscription', id: any, status: SubscriptionStatus }>, defaultRole?: { __typename?: 'Role', id: string, label: string, description?: string | null, icon?: string | null, canUpdateAllSettings: boolean, isEditable: boolean, canReadAllObjectRecords: boolean, canUpdateAllObjectRecords: boolean, canSoftDeleteAllObjectRecords: boolean, canDestroyAllObjectRecords: boolean } | null } | null, workspaces: Array<{ __typename?: 'UserWorkspace', workspace?: { __typename?: 'Workspace', id: any, logo?: string | null, displayName?: string | null, subdomain: string, customDomain?: string | null, workspaceUrls: { __typename?: 'WorkspaceUrls', subdomainUrl: string, customUrl?: string | null } } | null }> } };
|
||||||
|
|
||||||
export type ActivateWorkflowVersionMutationVariables = Exact<{
|
export type ActivateWorkflowVersionMutationVariables = Exact<{
|
||||||
workflowVersionId: Scalars['String'];
|
workflowVersionId: Scalars['String'];
|
||||||
@ -3036,6 +3071,19 @@ export const UserQueryFragmentFragmentDoc = gql`
|
|||||||
id
|
id
|
||||||
status
|
status
|
||||||
interval
|
interval
|
||||||
|
billingSubscriptionItems {
|
||||||
|
id
|
||||||
|
hasReachedCurrentPeriodCap
|
||||||
|
billingProduct {
|
||||||
|
name
|
||||||
|
description
|
||||||
|
metadata {
|
||||||
|
planKey
|
||||||
|
priceUsageBased
|
||||||
|
productKey
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
billingSubscriptions {
|
billingSubscriptions {
|
||||||
id
|
id
|
||||||
@ -4133,6 +4181,39 @@ export function useCheckoutSessionMutation(baseOptions?: Apollo.MutationHookOpti
|
|||||||
export type CheckoutSessionMutationHookResult = ReturnType<typeof useCheckoutSessionMutation>;
|
export type CheckoutSessionMutationHookResult = ReturnType<typeof useCheckoutSessionMutation>;
|
||||||
export type CheckoutSessionMutationResult = Apollo.MutationResult<CheckoutSessionMutation>;
|
export type CheckoutSessionMutationResult = Apollo.MutationResult<CheckoutSessionMutation>;
|
||||||
export type CheckoutSessionMutationOptions = Apollo.BaseMutationOptions<CheckoutSessionMutation, CheckoutSessionMutationVariables>;
|
export type CheckoutSessionMutationOptions = Apollo.BaseMutationOptions<CheckoutSessionMutation, CheckoutSessionMutationVariables>;
|
||||||
|
export const EndSubscriptionTrialPeriodDocument = gql`
|
||||||
|
mutation EndSubscriptionTrialPeriod {
|
||||||
|
endSubscriptionTrialPeriod {
|
||||||
|
status
|
||||||
|
hasPaymentMethod
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
export type EndSubscriptionTrialPeriodMutationFn = Apollo.MutationFunction<EndSubscriptionTrialPeriodMutation, EndSubscriptionTrialPeriodMutationVariables>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* __useEndSubscriptionTrialPeriodMutation__
|
||||||
|
*
|
||||||
|
* To run a mutation, you first call `useEndSubscriptionTrialPeriodMutation` within a React component and pass it any options that fit your needs.
|
||||||
|
* When your component renders, `useEndSubscriptionTrialPeriodMutation` returns a tuple that includes:
|
||||||
|
* - A mutate function that you can call at any time to execute the mutation
|
||||||
|
* - An object with fields that represent the current status of the mutation's execution
|
||||||
|
*
|
||||||
|
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const [endSubscriptionTrialPeriodMutation, { data, loading, error }] = useEndSubscriptionTrialPeriodMutation({
|
||||||
|
* variables: {
|
||||||
|
* },
|
||||||
|
* });
|
||||||
|
*/
|
||||||
|
export function useEndSubscriptionTrialPeriodMutation(baseOptions?: Apollo.MutationHookOptions<EndSubscriptionTrialPeriodMutation, EndSubscriptionTrialPeriodMutationVariables>) {
|
||||||
|
const options = {...defaultOptions, ...baseOptions}
|
||||||
|
return Apollo.useMutation<EndSubscriptionTrialPeriodMutation, EndSubscriptionTrialPeriodMutationVariables>(EndSubscriptionTrialPeriodDocument, options);
|
||||||
|
}
|
||||||
|
export type EndSubscriptionTrialPeriodMutationHookResult = ReturnType<typeof useEndSubscriptionTrialPeriodMutation>;
|
||||||
|
export type EndSubscriptionTrialPeriodMutationResult = Apollo.MutationResult<EndSubscriptionTrialPeriodMutation>;
|
||||||
|
export type EndSubscriptionTrialPeriodMutationOptions = Apollo.BaseMutationOptions<EndSubscriptionTrialPeriodMutation, EndSubscriptionTrialPeriodMutationVariables>;
|
||||||
export const UpdateBillingSubscriptionDocument = gql`
|
export const UpdateBillingSubscriptionDocument = gql`
|
||||||
mutation UpdateBillingSubscription {
|
mutation UpdateBillingSubscription {
|
||||||
updateBillingSubscription {
|
updateBillingSubscription {
|
||||||
|
|||||||
@ -0,0 +1,10 @@
|
|||||||
|
import { gql } from '@apollo/client';
|
||||||
|
|
||||||
|
export const END_SUBSCRIPTION_TRIAL_PERIOD = gql`
|
||||||
|
mutation EndSubscriptionTrialPeriod {
|
||||||
|
endSubscriptionTrialPeriod {
|
||||||
|
status
|
||||||
|
hasPaymentMethod
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
@ -0,0 +1,71 @@
|
|||||||
|
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
|
||||||
|
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
|
||||||
|
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||||
|
import { t } from '@lingui/core/macro';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { useRecoilState } from 'recoil';
|
||||||
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
|
import { useEndSubscriptionTrialPeriodMutation } from '~/generated/graphql';
|
||||||
|
|
||||||
|
export const useEndSubscriptionTrialPeriod = () => {
|
||||||
|
const { enqueueSnackBar } = useSnackBar();
|
||||||
|
const [endSubscriptionTrialPeriod] = useEndSubscriptionTrialPeriodMutation();
|
||||||
|
const [currentWorkspace, setCurrentWorkspace] = useRecoilState(
|
||||||
|
currentWorkspaceState,
|
||||||
|
);
|
||||||
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
|
||||||
|
const endTrialPeriod = async () => {
|
||||||
|
try {
|
||||||
|
setIsLoading(true);
|
||||||
|
|
||||||
|
const { data } = await endSubscriptionTrialPeriod();
|
||||||
|
const endTrialPeriodOutput = data?.endSubscriptionTrialPeriod;
|
||||||
|
|
||||||
|
const hasPaymentMethod = endTrialPeriodOutput?.hasPaymentMethod;
|
||||||
|
|
||||||
|
if (isDefined(hasPaymentMethod) && hasPaymentMethod === false) {
|
||||||
|
enqueueSnackBar(
|
||||||
|
t`No payment method found. Please update your billing details.`,
|
||||||
|
{
|
||||||
|
variant: SnackBarVariant.Error,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const updatedSubscriptionStatus = endTrialPeriodOutput?.status;
|
||||||
|
if (
|
||||||
|
isDefined(updatedSubscriptionStatus) &&
|
||||||
|
isDefined(currentWorkspace?.currentBillingSubscription)
|
||||||
|
) {
|
||||||
|
setCurrentWorkspace({
|
||||||
|
...currentWorkspace,
|
||||||
|
currentBillingSubscription: {
|
||||||
|
...currentWorkspace?.currentBillingSubscription,
|
||||||
|
status: updatedSubscriptionStatus,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
enqueueSnackBar(t`Subscription activated.`, {
|
||||||
|
variant: SnackBarVariant.Success,
|
||||||
|
});
|
||||||
|
} catch {
|
||||||
|
enqueueSnackBar(
|
||||||
|
t`Error while ending trial period. Please contact Twenty team.`,
|
||||||
|
{
|
||||||
|
variant: SnackBarVariant.Error,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
endTrialPeriod,
|
||||||
|
isLoading,
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -1,8 +1,10 @@
|
|||||||
import { InformationBannerBillingSubscriptionPaused } from '@/information-banner/components/billing/InformationBannerBillingSubscriptionPaused';
|
import { InformationBannerBillingSubscriptionPaused } from '@/information-banner/components/billing/InformationBannerBillingSubscriptionPaused';
|
||||||
|
import { InformationBannerEndTrialPeriod } from '@/information-banner/components/billing/InformationBannerEndTrialPeriod';
|
||||||
import { InformationBannerFailPaymentInfo } from '@/information-banner/components/billing/InformationBannerFailPaymentInfo';
|
import { InformationBannerFailPaymentInfo } from '@/information-banner/components/billing/InformationBannerFailPaymentInfo';
|
||||||
import { InformationBannerNoBillingSubscription } from '@/information-banner/components/billing/InformationBannerNoBillingSubscription';
|
import { InformationBannerNoBillingSubscription } from '@/information-banner/components/billing/InformationBannerNoBillingSubscription';
|
||||||
import { InformationBannerReconnectAccountEmailAliases } from '@/information-banner/components/reconnect-account/InformationBannerReconnectAccountEmailAliases';
|
import { InformationBannerReconnectAccountEmailAliases } from '@/information-banner/components/reconnect-account/InformationBannerReconnectAccountEmailAliases';
|
||||||
import { InformationBannerReconnectAccountInsufficientPermissions } from '@/information-banner/components/reconnect-account/InformationBannerReconnectAccountInsufficientPermissions';
|
import { InformationBannerReconnectAccountInsufficientPermissions } from '@/information-banner/components/reconnect-account/InformationBannerReconnectAccountInsufficientPermissions';
|
||||||
|
import { useIsSomeMeteredProductCapReached } from '@/workspace/hooks/useIsSomeMeteredProductCapReached';
|
||||||
import { useIsWorkspaceActivationStatusEqualsTo } from '@/workspace/hooks/useIsWorkspaceActivationStatusEqualsTo';
|
import { useIsWorkspaceActivationStatusEqualsTo } from '@/workspace/hooks/useIsWorkspaceActivationStatusEqualsTo';
|
||||||
import { useSubscriptionStatus } from '@/workspace/hooks/useSubscriptionStatus';
|
import { useSubscriptionStatus } from '@/workspace/hooks/useSubscriptionStatus';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
@ -24,6 +26,7 @@ export const InformationBannerWrapper = () => {
|
|||||||
const isWorkspaceSuspended = useIsWorkspaceActivationStatusEqualsTo(
|
const isWorkspaceSuspended = useIsWorkspaceActivationStatusEqualsTo(
|
||||||
WorkspaceActivationStatus.SUSPENDED,
|
WorkspaceActivationStatus.SUSPENDED,
|
||||||
);
|
);
|
||||||
|
const isSomeMeteredProductCapReached = useIsSomeMeteredProductCapReached();
|
||||||
|
|
||||||
const displayBillingSubscriptionPausedBanner =
|
const displayBillingSubscriptionPausedBanner =
|
||||||
isWorkspaceSuspended && subscriptionStatus === SubscriptionStatus.Paused;
|
isWorkspaceSuspended && subscriptionStatus === SubscriptionStatus.Paused;
|
||||||
@ -35,6 +38,10 @@ export const InformationBannerWrapper = () => {
|
|||||||
subscriptionStatus === SubscriptionStatus.PastDue ||
|
subscriptionStatus === SubscriptionStatus.PastDue ||
|
||||||
subscriptionStatus === SubscriptionStatus.Unpaid;
|
subscriptionStatus === SubscriptionStatus.Unpaid;
|
||||||
|
|
||||||
|
const displayEndTrialPeriodBanner =
|
||||||
|
isSomeMeteredProductCapReached &&
|
||||||
|
subscriptionStatus === SubscriptionStatus.Trialing;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledInformationBannerWrapper>
|
<StyledInformationBannerWrapper>
|
||||||
<InformationBannerReconnectAccountInsufficientPermissions />
|
<InformationBannerReconnectAccountInsufficientPermissions />
|
||||||
@ -46,6 +53,7 @@ export const InformationBannerWrapper = () => {
|
|||||||
<InformationBannerNoBillingSubscription />
|
<InformationBannerNoBillingSubscription />
|
||||||
)}
|
)}
|
||||||
{displayFailPaymentInfoBanner && <InformationBannerFailPaymentInfo />}
|
{displayFailPaymentInfoBanner && <InformationBannerFailPaymentInfo />}
|
||||||
|
{displayEndTrialPeriodBanner && <InformationBannerEndTrialPeriod />}
|
||||||
</StyledInformationBannerWrapper>
|
</StyledInformationBannerWrapper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -0,0 +1,18 @@
|
|||||||
|
import { useEndSubscriptionTrialPeriod } from '@/billing/hooks/useEndSubscriptionTrialPeriod';
|
||||||
|
import { InformationBanner } from '@/information-banner/components/InformationBanner';
|
||||||
|
import { useLingui } from '@lingui/react/macro';
|
||||||
|
|
||||||
|
export const InformationBannerEndTrialPeriod = () => {
|
||||||
|
const { endTrialPeriod, isLoading } = useEndSubscriptionTrialPeriod();
|
||||||
|
const { t } = useLingui();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<InformationBanner
|
||||||
|
variant="danger"
|
||||||
|
message={t`No free workflow executions left. End trial period and activate your billing to continue.`}
|
||||||
|
buttonTitle={t`Activate`}
|
||||||
|
buttonOnClick={async () => await endTrialPeriod()}
|
||||||
|
isButtonDisabled={isLoading}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -53,6 +53,19 @@ export const USER_QUERY_FRAGMENT = gql`
|
|||||||
id
|
id
|
||||||
status
|
status
|
||||||
interval
|
interval
|
||||||
|
billingSubscriptionItems {
|
||||||
|
id
|
||||||
|
hasReachedCurrentPeriodCap
|
||||||
|
billingProduct {
|
||||||
|
name
|
||||||
|
description
|
||||||
|
metadata {
|
||||||
|
planKey
|
||||||
|
priceUsageBased
|
||||||
|
productKey
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
billingSubscriptions {
|
billingSubscriptions {
|
||||||
id
|
id
|
||||||
|
|||||||
@ -0,0 +1,20 @@
|
|||||||
|
import { useRecoilValue } from 'recoil';
|
||||||
|
|
||||||
|
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
|
||||||
|
import { BillingProductKey } from '~/generated/graphql';
|
||||||
|
|
||||||
|
export const useIsSomeMeteredProductCapReached = (): boolean => {
|
||||||
|
const currentWorkspace = useRecoilValue(currentWorkspaceState);
|
||||||
|
|
||||||
|
const meteredProductKeys = Object.values(BillingProductKey).filter(
|
||||||
|
(productKey) => productKey !== BillingProductKey.BASE_PRODUCT,
|
||||||
|
);
|
||||||
|
|
||||||
|
return meteredProductKeys.some((productKey) => {
|
||||||
|
return (
|
||||||
|
currentWorkspace?.currentBillingSubscription?.billingSubscriptionItems?.find(
|
||||||
|
(item) => item.billingProduct?.metadata.productKey === productKey,
|
||||||
|
)?.hasReachedCurrentPeriodCap ?? false
|
||||||
|
);
|
||||||
|
});
|
||||||
|
};
|
||||||
@ -125,7 +125,7 @@ export const ChooseYourPlan = () => {
|
|||||||
(plan) => plan.planKey === currentPlan,
|
(plan) => plan.planKey === currentPlan,
|
||||||
)?.baseProduct;
|
)?.baseProduct;
|
||||||
|
|
||||||
const baseProductPrice = baseProduct?.prices.find(
|
const baseProductPrice = baseProduct?.prices?.find(
|
||||||
(price): price is BillingPriceLicensedDto =>
|
(price): price is BillingPriceLicensedDto =>
|
||||||
isBillingPriceLicensed(price) &&
|
isBillingPriceLicensed(price) &&
|
||||||
price.recurringInterval === SubscriptionInterval.Month,
|
price.recurringInterval === SubscriptionInterval.Month,
|
||||||
|
|||||||
@ -19,4 +19,5 @@ export enum BillingExceptionCode {
|
|||||||
BILLING_MISSING_REQUEST_BODY = 'BILLING_MISSING_REQUEST_BODY',
|
BILLING_MISSING_REQUEST_BODY = 'BILLING_MISSING_REQUEST_BODY',
|
||||||
BILLING_UNHANDLED_ERROR = 'BILLING_UNHANDLED_ERROR',
|
BILLING_UNHANDLED_ERROR = 'BILLING_UNHANDLED_ERROR',
|
||||||
BILLING_STRIPE_ERROR = 'BILLING_STRIPE_ERROR',
|
BILLING_STRIPE_ERROR = 'BILLING_STRIPE_ERROR',
|
||||||
|
BILLING_SUBSCRIPTION_NOT_IN_TRIAL_PERIOD = 'BILLING_SUBSCRIPTION_NOT_IN_TRIAL_PERIOD',
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import { isDefined } from 'twenty-shared/utils';
|
|||||||
|
|
||||||
import { BillingCheckoutSessionInput } from 'src/engine/core-modules/billing/dtos/inputs/billing-checkout-session.input';
|
import { BillingCheckoutSessionInput } from 'src/engine/core-modules/billing/dtos/inputs/billing-checkout-session.input';
|
||||||
import { BillingSessionInput } from 'src/engine/core-modules/billing/dtos/inputs/billing-session.input';
|
import { BillingSessionInput } from 'src/engine/core-modules/billing/dtos/inputs/billing-session.input';
|
||||||
|
import { BillingEndTrialPeriodOutput } from 'src/engine/core-modules/billing/dtos/outputs/billing-end-trial-period.output';
|
||||||
import { BillingPlanOutput } from 'src/engine/core-modules/billing/dtos/outputs/billing-plan.output';
|
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 { 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 { BillingUpdateOutput } from 'src/engine/core-modules/billing/dtos/outputs/billing-update.output';
|
||||||
@ -130,6 +131,17 @@ export class BillingResolver {
|
|||||||
return plans.map(formatBillingDatabaseProductToGraphqlDTO);
|
return plans.map(formatBillingDatabaseProductToGraphqlDTO);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Mutation(() => BillingEndTrialPeriodOutput)
|
||||||
|
@UseGuards(
|
||||||
|
WorkspaceAuthGuard,
|
||||||
|
SettingsPermissionsGuard(SettingPermissionType.WORKSPACE),
|
||||||
|
)
|
||||||
|
async endSubscriptionTrialPeriod(
|
||||||
|
@AuthWorkspace() workspace: Workspace,
|
||||||
|
): Promise<BillingEndTrialPeriodOutput> {
|
||||||
|
return await this.billingSubscriptionService.endTrialPeriod(workspace);
|
||||||
|
}
|
||||||
|
|
||||||
private async validateCanCheckoutSessionPermissionOrThrow({
|
private async validateCanCheckoutSessionPermissionOrThrow({
|
||||||
workspaceId,
|
workspaceId,
|
||||||
userWorkspaceId,
|
userWorkspaceId,
|
||||||
|
|||||||
@ -5,9 +5,9 @@ import { Field, ObjectType } from '@nestjs/graphql';
|
|||||||
import { BillingPriceLicensedDTO } from 'src/engine/core-modules/billing/dtos/billing-price-licensed.dto';
|
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 { BillingPriceMeteredDTO } from 'src/engine/core-modules/billing/dtos/billing-price-metered.dto';
|
||||||
import { BillingPriceUnionDTO } from 'src/engine/core-modules/billing/dtos/billing-price-union.dto';
|
import { BillingPriceUnionDTO } from 'src/engine/core-modules/billing/dtos/billing-price-union.dto';
|
||||||
import { BillingUsageType } from 'src/engine/core-modules/billing/enums/billing-usage-type.enum';
|
import { BillingProductMetadata } from 'src/engine/core-modules/billing/types/billing-product-metadata.type';
|
||||||
|
|
||||||
@ObjectType()
|
@ObjectType('BillingProduct')
|
||||||
export class BillingProductDTO {
|
export class BillingProductDTO {
|
||||||
@Field(() => String)
|
@Field(() => String)
|
||||||
name: string;
|
name: string;
|
||||||
@ -18,9 +18,9 @@ export class BillingProductDTO {
|
|||||||
@Field(() => [String], { nullable: true })
|
@Field(() => [String], { nullable: true })
|
||||||
images: string[];
|
images: string[];
|
||||||
|
|
||||||
@Field(() => BillingUsageType)
|
@Field(() => [BillingPriceUnionDTO], { nullable: true })
|
||||||
type: BillingUsageType;
|
|
||||||
|
|
||||||
@Field(() => [BillingPriceUnionDTO])
|
|
||||||
prices: Array<BillingPriceLicensedDTO> | Array<BillingPriceMeteredDTO>;
|
prices: Array<BillingPriceLicensedDTO> | Array<BillingPriceMeteredDTO>;
|
||||||
|
|
||||||
|
@Field(() => BillingProductMetadata)
|
||||||
|
metadata: BillingProductMetadata;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,19 @@
|
|||||||
|
/* @license Enterprise */
|
||||||
|
|
||||||
|
import { Field, ObjectType } from '@nestjs/graphql';
|
||||||
|
|
||||||
|
import { SubscriptionStatus } from 'src/engine/core-modules/billing/enums/billing-subscription-status.enum';
|
||||||
|
|
||||||
|
@ObjectType()
|
||||||
|
export class BillingEndTrialPeriodOutput {
|
||||||
|
@Field(() => SubscriptionStatus, {
|
||||||
|
description: 'Updated subscription status',
|
||||||
|
nullable: true,
|
||||||
|
})
|
||||||
|
status: SubscriptionStatus | undefined;
|
||||||
|
|
||||||
|
@Field(() => Boolean, {
|
||||||
|
description: 'Boolean that confirms if a payment method was found',
|
||||||
|
})
|
||||||
|
hasPaymentMethod: boolean;
|
||||||
|
}
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
/* @license Enterprise */
|
||||||
|
|
||||||
|
import { Field, ObjectType } from '@nestjs/graphql';
|
||||||
|
|
||||||
|
import { IDField } from '@ptc-org/nestjs-query-graphql';
|
||||||
|
|
||||||
|
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
|
||||||
|
import { BillingProductDTO } from 'src/engine/core-modules/billing/dtos/billing-product.dto';
|
||||||
|
|
||||||
|
@ObjectType('BillingSubscriptionItem')
|
||||||
|
export class BillingSubscriptionItemDTO {
|
||||||
|
@IDField(() => UUIDScalarType)
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
@Field(() => Boolean)
|
||||||
|
hasReachedCurrentPeriodCap: boolean;
|
||||||
|
|
||||||
|
@Field(() => BillingProductDTO, { nullable: true })
|
||||||
|
billingProduct: BillingProductDTO;
|
||||||
|
}
|
||||||
@ -5,6 +5,7 @@ import {
|
|||||||
Column,
|
Column,
|
||||||
CreateDateColumn,
|
CreateDateColumn,
|
||||||
Entity,
|
Entity,
|
||||||
|
JoinColumn,
|
||||||
ManyToOne,
|
ManyToOne,
|
||||||
PrimaryGeneratedColumn,
|
PrimaryGeneratedColumn,
|
||||||
Relation,
|
Relation,
|
||||||
@ -12,6 +13,7 @@ import {
|
|||||||
UpdateDateColumn,
|
UpdateDateColumn,
|
||||||
} from 'typeorm';
|
} from 'typeorm';
|
||||||
|
|
||||||
|
import { BillingProduct } from 'src/engine/core-modules/billing/entities/billing-product.entity';
|
||||||
import { BillingSubscription } from 'src/engine/core-modules/billing/entities/billing-subscription.entity';
|
import { BillingSubscription } from 'src/engine/core-modules/billing/entities/billing-subscription.entity';
|
||||||
@Entity({ name: 'billingSubscriptionItem', schema: 'core' })
|
@Entity({ name: 'billingSubscriptionItem', schema: 'core' })
|
||||||
@Unique('IndexOnBillingSubscriptionIdAndStripeProductIdUnique', [
|
@Unique('IndexOnBillingSubscriptionIdAndStripeProductIdUnique', [
|
||||||
@ -52,6 +54,13 @@ export class BillingSubscriptionItem {
|
|||||||
)
|
)
|
||||||
billingSubscription: Relation<BillingSubscription>;
|
billingSubscription: Relation<BillingSubscription>;
|
||||||
|
|
||||||
|
@ManyToOne(() => BillingProduct)
|
||||||
|
@JoinColumn({
|
||||||
|
name: 'stripeProductId',
|
||||||
|
referencedColumnName: 'stripeProductId',
|
||||||
|
})
|
||||||
|
billingProduct: Relation<BillingProduct>;
|
||||||
|
|
||||||
@Column({ nullable: false })
|
@Column({ nullable: false })
|
||||||
stripeProductId: string;
|
stripeProductId: string;
|
||||||
|
|
||||||
|
|||||||
@ -18,6 +18,7 @@ import {
|
|||||||
} from 'typeorm';
|
} from 'typeorm';
|
||||||
|
|
||||||
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
|
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
|
||||||
|
import { BillingSubscriptionItemDTO } from 'src/engine/core-modules/billing/dtos/outputs/billing-subscription-item.output';
|
||||||
import { BillingCustomer } from 'src/engine/core-modules/billing/entities/billing-customer.entity';
|
import { BillingCustomer } from 'src/engine/core-modules/billing/entities/billing-customer.entity';
|
||||||
import { BillingSubscriptionItem } from 'src/engine/core-modules/billing/entities/billing-subscription-item.entity';
|
import { BillingSubscriptionItem } from 'src/engine/core-modules/billing/entities/billing-subscription-item.entity';
|
||||||
import { BillingSubscriptionCollectionMethod } from 'src/engine/core-modules/billing/enums/billing-subscription-collection-method.enum';
|
import { BillingSubscriptionCollectionMethod } from 'src/engine/core-modules/billing/enums/billing-subscription-collection-method.enum';
|
||||||
@ -72,6 +73,7 @@ export class BillingSubscription {
|
|||||||
})
|
})
|
||||||
interval: Stripe.Price.Recurring.Interval;
|
interval: Stripe.Price.Recurring.Interval;
|
||||||
|
|
||||||
|
@Field(() => [BillingSubscriptionItemDTO], { nullable: true })
|
||||||
@OneToMany(
|
@OneToMany(
|
||||||
() => BillingSubscriptionItem,
|
() => BillingSubscriptionItem,
|
||||||
(billingSubscriptionItem) => billingSubscriptionItem.billingSubscription,
|
(billingSubscriptionItem) => billingSubscriptionItem.billingSubscription,
|
||||||
|
|||||||
@ -1,6 +1,13 @@
|
|||||||
/* @license Enterprise */
|
/* @license Enterprise */
|
||||||
|
|
||||||
|
import { registerEnumType } from '@nestjs/graphql';
|
||||||
|
|
||||||
export enum BillingProductKey {
|
export enum BillingProductKey {
|
||||||
BASE_PRODUCT = 'BASE_PRODUCT',
|
BASE_PRODUCT = 'BASE_PRODUCT',
|
||||||
WORKFLOW_NODE_EXECUTION = 'WORKFLOW_NODE_EXECUTION',
|
WORKFLOW_NODE_EXECUTION = 'WORKFLOW_NODE_EXECUTION',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
registerEnumType(BillingProductKey, {
|
||||||
|
name: 'BillingProductKey',
|
||||||
|
description: 'The different billing products available',
|
||||||
|
});
|
||||||
|
|||||||
@ -1,6 +1,13 @@
|
|||||||
/* @license Enterprise */
|
/* @license Enterprise */
|
||||||
|
|
||||||
|
import { registerEnumType } from '@nestjs/graphql';
|
||||||
|
|
||||||
export enum BillingUsageType {
|
export enum BillingUsageType {
|
||||||
METERED = 'METERED',
|
METERED = 'METERED',
|
||||||
LICENSED = 'LICENSED',
|
LICENSED = 'LICENSED',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
registerEnumType(BillingUsageType, {
|
||||||
|
name: 'BillingUsageType',
|
||||||
|
description: 'The different billing usage types',
|
||||||
|
});
|
||||||
|
|||||||
@ -48,6 +48,7 @@ export class BillingRestApiExceptionFilter implements ExceptionFilter {
|
|||||||
404,
|
404,
|
||||||
);
|
);
|
||||||
case BillingExceptionCode.BILLING_METER_EVENT_FAILED:
|
case BillingExceptionCode.BILLING_METER_EVENT_FAILED:
|
||||||
|
case BillingExceptionCode.BILLING_SUBSCRIPTION_NOT_IN_TRIAL_PERIOD:
|
||||||
return this.httpExceptionHandlerService.handleError(
|
return this.httpExceptionHandlerService.handleError(
|
||||||
exception,
|
exception,
|
||||||
response,
|
response,
|
||||||
|
|||||||
@ -21,15 +21,15 @@ import { SubscriptionInterval } from 'src/engine/core-modules/billing/enums/bill
|
|||||||
import { SubscriptionStatus } from 'src/engine/core-modules/billing/enums/billing-subscription-status.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 { BillingPlanService } from 'src/engine/core-modules/billing/services/billing-plan.service';
|
||||||
import { BillingProductService } from 'src/engine/core-modules/billing/services/billing-product.service';
|
import { BillingProductService } from 'src/engine/core-modules/billing/services/billing-product.service';
|
||||||
import { StripeSubscriptionItemService } from 'src/engine/core-modules/billing/stripe/services/stripe-subscription-item.service';
|
import { StripeCustomerService } from 'src/engine/core-modules/billing/stripe/services/stripe-customer.service';
|
||||||
import { StripeSubscriptionService } from 'src/engine/core-modules/billing/stripe/services/stripe-subscription.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 { getPlanKeyFromSubscription } from 'src/engine/core-modules/billing/utils/get-plan-key-from-subscription.util';
|
||||||
|
import { getSubscriptionStatus } from 'src/engine/core-modules/billing/webhooks/utils/transform-stripe-subscription-event-to-database-subscription.util';
|
||||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class BillingSubscriptionService {
|
export class BillingSubscriptionService {
|
||||||
protected readonly logger = new Logger(BillingSubscriptionService.name);
|
protected readonly logger = new Logger(BillingSubscriptionService.name);
|
||||||
constructor(
|
constructor(
|
||||||
private readonly stripeSubscriptionItemService: StripeSubscriptionItemService,
|
|
||||||
private readonly stripeSubscriptionService: StripeSubscriptionService,
|
private readonly stripeSubscriptionService: StripeSubscriptionService,
|
||||||
private readonly billingPlanService: BillingPlanService,
|
private readonly billingPlanService: BillingPlanService,
|
||||||
private readonly billingProductService: BillingProductService,
|
private readonly billingProductService: BillingProductService,
|
||||||
@ -37,6 +37,7 @@ export class BillingSubscriptionService {
|
|||||||
private readonly billingEntitlementRepository: Repository<BillingEntitlement>,
|
private readonly billingEntitlementRepository: Repository<BillingEntitlement>,
|
||||||
@InjectRepository(BillingSubscription, 'core')
|
@InjectRepository(BillingSubscription, 'core')
|
||||||
private readonly billingSubscriptionRepository: Repository<BillingSubscription>,
|
private readonly billingSubscriptionRepository: Repository<BillingSubscription>,
|
||||||
|
private readonly stripeCustomerService: StripeCustomerService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async getCurrentBillingSubscriptionOrThrow(criteria: {
|
async getCurrentBillingSubscriptionOrThrow(criteria: {
|
||||||
@ -46,7 +47,10 @@ export class BillingSubscriptionService {
|
|||||||
const notCanceledSubscriptions =
|
const notCanceledSubscriptions =
|
||||||
await this.billingSubscriptionRepository.find({
|
await this.billingSubscriptionRepository.find({
|
||||||
where: { ...criteria, status: Not(SubscriptionStatus.Canceled) },
|
where: { ...criteria, status: Not(SubscriptionStatus.Canceled) },
|
||||||
relations: ['billingSubscriptionItems'],
|
relations: [
|
||||||
|
'billingSubscriptionItems',
|
||||||
|
'billingSubscriptionItems.billingProduct',
|
||||||
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
assert(
|
assert(
|
||||||
@ -195,4 +199,38 @@ export class BillingSubscriptionService {
|
|||||||
|
|
||||||
return subscriptionItemsToUpdate;
|
return subscriptionItemsToUpdate;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async endTrialPeriod(workspace: Workspace) {
|
||||||
|
const billingSubscription = await this.getCurrentBillingSubscriptionOrThrow(
|
||||||
|
{ workspaceId: workspace.id },
|
||||||
|
);
|
||||||
|
|
||||||
|
if (billingSubscription.status !== SubscriptionStatus.Trialing) {
|
||||||
|
throw new BillingException(
|
||||||
|
'Billing subscription is not in trial period',
|
||||||
|
BillingExceptionCode.BILLING_SUBSCRIPTION_NOT_IN_TRIAL_PERIOD,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasPaymentMethod = await this.stripeCustomerService.hasPaymentMethod(
|
||||||
|
billingSubscription.stripeCustomerId,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!hasPaymentMethod) {
|
||||||
|
return { hasPaymentMethod: false, status: undefined };
|
||||||
|
}
|
||||||
|
|
||||||
|
const updatedSubscription =
|
||||||
|
await this.stripeSubscriptionService.updateSubscription(
|
||||||
|
billingSubscription.stripeSubscriptionId,
|
||||||
|
{
|
||||||
|
trial_end: 'now',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
status: getSubscriptionStatus(updatedSubscription.status),
|
||||||
|
hasPaymentMethod: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -32,4 +32,11 @@ export class StripeCustomerService {
|
|||||||
metadata: { workspaceId: workspaceId },
|
metadata: { workspaceId: workspaceId },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async hasPaymentMethod(stripeCustomerId: string) {
|
||||||
|
const { data: paymentMethods } =
|
||||||
|
await this.stripe.customers.listPaymentMethods(stripeCustomerId);
|
||||||
|
|
||||||
|
return paymentMethods.length > 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -76,4 +76,11 @@ export class StripeSubscriptionService {
|
|||||||
items: stripeSubscriptionItemsToUpdate,
|
items: stripeSubscriptionItemsToUpdate,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async updateSubscription(
|
||||||
|
stripeSubscriptionId: string,
|
||||||
|
updateData: Stripe.SubscriptionUpdateParams,
|
||||||
|
): Promise<Stripe.Subscription> {
|
||||||
|
return this.stripe.subscriptions.update(stripeSubscriptionId, updateData);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,14 +1,21 @@
|
|||||||
/* @license Enterprise */
|
/* @license Enterprise */
|
||||||
|
|
||||||
|
import { Field, ObjectType } from '@nestjs/graphql';
|
||||||
|
|
||||||
import { BillingPlanKey } from 'src/engine/core-modules/billing/enums/billing-plan-key.enum';
|
import { BillingPlanKey } from 'src/engine/core-modules/billing/enums/billing-plan-key.enum';
|
||||||
import { BillingProductKey } from 'src/engine/core-modules/billing/enums/billing-product-key.enum';
|
import { BillingProductKey } from 'src/engine/core-modules/billing/enums/billing-product-key.enum';
|
||||||
import { BillingUsageType } from 'src/engine/core-modules/billing/enums/billing-usage-type.enum';
|
import { BillingUsageType } from 'src/engine/core-modules/billing/enums/billing-usage-type.enum';
|
||||||
|
|
||||||
export type BillingProductMetadata =
|
@ObjectType('BillingProductMetadata')
|
||||||
| {
|
export class BillingProductMetadata {
|
||||||
planKey: BillingPlanKey;
|
@Field(() => BillingPlanKey)
|
||||||
priceUsageBased: BillingUsageType;
|
planKey: BillingPlanKey;
|
||||||
productKey: BillingProductKey;
|
|
||||||
[key: string]: string;
|
@Field(() => BillingUsageType)
|
||||||
}
|
priceUsageBased: BillingUsageType;
|
||||||
| Record<string, never>;
|
|
||||||
|
@Field(() => BillingProductKey)
|
||||||
|
productKey: BillingProductKey;
|
||||||
|
|
||||||
|
[key: string]: string;
|
||||||
|
}
|
||||||
|
|||||||
@ -68,6 +68,9 @@ describe('formatBillingDatabaseProductToGraphqlDTO', () => {
|
|||||||
planKey: BillingPlanKey.PRO,
|
planKey: BillingPlanKey.PRO,
|
||||||
baseProduct: {
|
baseProduct: {
|
||||||
id: 'base-1',
|
id: 'base-1',
|
||||||
|
metadata: {
|
||||||
|
priceUsageBased: BillingUsageType.LICENSED,
|
||||||
|
},
|
||||||
name: 'Base Product',
|
name: 'Base Product',
|
||||||
billingPrices: [
|
billingPrices: [
|
||||||
{
|
{
|
||||||
@ -77,7 +80,6 @@ describe('formatBillingDatabaseProductToGraphqlDTO', () => {
|
|||||||
priceUsageType: BillingUsageType.LICENSED,
|
priceUsageType: BillingUsageType.LICENSED,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
type: BillingUsageType.LICENSED,
|
|
||||||
prices: [
|
prices: [
|
||||||
{
|
{
|
||||||
recurringInterval: SubscriptionInterval.Month,
|
recurringInterval: SubscriptionInterval.Month,
|
||||||
@ -90,6 +92,9 @@ describe('formatBillingDatabaseProductToGraphqlDTO', () => {
|
|||||||
otherLicensedProducts: [
|
otherLicensedProducts: [
|
||||||
{
|
{
|
||||||
id: 'licensed-1',
|
id: 'licensed-1',
|
||||||
|
metadata: {
|
||||||
|
priceUsageBased: BillingUsageType.LICENSED,
|
||||||
|
},
|
||||||
name: 'Licensed Product',
|
name: 'Licensed Product',
|
||||||
billingPrices: [
|
billingPrices: [
|
||||||
{
|
{
|
||||||
@ -99,7 +104,6 @@ describe('formatBillingDatabaseProductToGraphqlDTO', () => {
|
|||||||
priceUsageType: BillingUsageType.LICENSED,
|
priceUsageType: BillingUsageType.LICENSED,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
type: BillingUsageType.LICENSED,
|
|
||||||
prices: [
|
prices: [
|
||||||
{
|
{
|
||||||
recurringInterval: SubscriptionInterval.Year,
|
recurringInterval: SubscriptionInterval.Year,
|
||||||
@ -113,6 +117,9 @@ describe('formatBillingDatabaseProductToGraphqlDTO', () => {
|
|||||||
meteredProducts: [
|
meteredProducts: [
|
||||||
{
|
{
|
||||||
id: 'metered-1',
|
id: 'metered-1',
|
||||||
|
metadata: {
|
||||||
|
priceUsageBased: BillingUsageType.METERED,
|
||||||
|
},
|
||||||
name: 'Metered Product',
|
name: 'Metered Product',
|
||||||
billingPrices: [
|
billingPrices: [
|
||||||
{
|
{
|
||||||
@ -129,7 +136,6 @@ describe('formatBillingDatabaseProductToGraphqlDTO', () => {
|
|||||||
priceUsageType: BillingUsageType.METERED,
|
priceUsageType: BillingUsageType.METERED,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
type: BillingUsageType.METERED,
|
|
||||||
prices: [
|
prices: [
|
||||||
{
|
{
|
||||||
tiersMode: BillingPriceTiersMode.GRADUATED,
|
tiersMode: BillingPriceTiersMode.GRADUATED,
|
||||||
@ -191,6 +197,9 @@ describe('formatBillingDatabaseProductToGraphqlDTO', () => {
|
|||||||
planKey: 'empty-plan',
|
planKey: 'empty-plan',
|
||||||
baseProduct: {
|
baseProduct: {
|
||||||
id: 'base-1',
|
id: 'base-1',
|
||||||
|
metadata: {
|
||||||
|
priceUsageBased: BillingUsageType.LICENSED,
|
||||||
|
},
|
||||||
name: 'Base Product',
|
name: 'Base Product',
|
||||||
billingPrices: [
|
billingPrices: [
|
||||||
{
|
{
|
||||||
@ -200,7 +209,6 @@ describe('formatBillingDatabaseProductToGraphqlDTO', () => {
|
|||||||
priceUsageType: BillingUsageType.LICENSED,
|
priceUsageType: BillingUsageType.LICENSED,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
type: BillingUsageType.LICENSED,
|
|
||||||
prices: [
|
prices: [
|
||||||
{
|
{
|
||||||
recurringInterval: SubscriptionInterval.Month,
|
recurringInterval: SubscriptionInterval.Month,
|
||||||
@ -214,6 +222,9 @@ describe('formatBillingDatabaseProductToGraphqlDTO', () => {
|
|||||||
meteredProducts: [
|
meteredProducts: [
|
||||||
{
|
{
|
||||||
id: 'metered-1',
|
id: 'metered-1',
|
||||||
|
metadata: {
|
||||||
|
priceUsageBased: BillingUsageType.METERED,
|
||||||
|
},
|
||||||
name: 'Metered Product',
|
name: 'Metered Product',
|
||||||
billingPrices: [
|
billingPrices: [
|
||||||
{
|
{
|
||||||
@ -224,7 +235,6 @@ describe('formatBillingDatabaseProductToGraphqlDTO', () => {
|
|||||||
priceUsageType: BillingUsageType.METERED,
|
priceUsageType: BillingUsageType.METERED,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
type: BillingUsageType.METERED,
|
|
||||||
prices: [
|
prices: [
|
||||||
{
|
{
|
||||||
tiersMode: null,
|
tiersMode: null,
|
||||||
|
|||||||
@ -16,7 +16,10 @@ export const formatBillingDatabaseProductToGraphqlDTO = (
|
|||||||
planKey: plan.planKey,
|
planKey: plan.planKey,
|
||||||
baseProduct: {
|
baseProduct: {
|
||||||
...plan.baseProduct,
|
...plan.baseProduct,
|
||||||
type: BillingUsageType.LICENSED,
|
metadata: {
|
||||||
|
...plan.baseProduct.metadata,
|
||||||
|
priceUsageBased: BillingUsageType.LICENSED,
|
||||||
|
},
|
||||||
prices: plan.baseProduct.billingPrices.map(
|
prices: plan.baseProduct.billingPrices.map(
|
||||||
formatBillingDatabasePriceToLicensedPriceDTO,
|
formatBillingDatabasePriceToLicensedPriceDTO,
|
||||||
),
|
),
|
||||||
@ -24,7 +27,10 @@ export const formatBillingDatabaseProductToGraphqlDTO = (
|
|||||||
otherLicensedProducts: plan.otherLicensedProducts.map((product) => {
|
otherLicensedProducts: plan.otherLicensedProducts.map((product) => {
|
||||||
return {
|
return {
|
||||||
...product,
|
...product,
|
||||||
type: BillingUsageType.LICENSED,
|
metadata: {
|
||||||
|
...product.metadata,
|
||||||
|
priceUsageBased: BillingUsageType.LICENSED,
|
||||||
|
},
|
||||||
prices: product.billingPrices.map(
|
prices: product.billingPrices.map(
|
||||||
formatBillingDatabasePriceToLicensedPriceDTO,
|
formatBillingDatabasePriceToLicensedPriceDTO,
|
||||||
),
|
),
|
||||||
@ -33,7 +39,10 @@ export const formatBillingDatabaseProductToGraphqlDTO = (
|
|||||||
meteredProducts: plan.meteredProducts.map((product) => {
|
meteredProducts: plan.meteredProducts.map((product) => {
|
||||||
return {
|
return {
|
||||||
...product,
|
...product,
|
||||||
type: BillingUsageType.METERED,
|
metadata: {
|
||||||
|
...product.metadata,
|
||||||
|
priceUsageBased: BillingUsageType.METERED,
|
||||||
|
},
|
||||||
prices: product.billingPrices.map(
|
prices: product.billingPrices.map(
|
||||||
formatBillingDatabasePriceToMeteredPriceDTO,
|
formatBillingDatabasePriceToMeteredPriceDTO,
|
||||||
),
|
),
|
||||||
|
|||||||
@ -7,9 +7,6 @@ import Stripe from 'stripe';
|
|||||||
import { Repository } from 'typeorm';
|
import { Repository } from 'typeorm';
|
||||||
|
|
||||||
import { BillingProduct } from 'src/engine/core-modules/billing/entities/billing-product.entity';
|
import { BillingProduct } from 'src/engine/core-modules/billing/entities/billing-product.entity';
|
||||||
import { BillingPlanKey } from 'src/engine/core-modules/billing/enums/billing-plan-key.enum';
|
|
||||||
import { BillingUsageType } from 'src/engine/core-modules/billing/enums/billing-usage-type.enum';
|
|
||||||
import { BillingProductMetadata } from 'src/engine/core-modules/billing/types/billing-product-metadata.type';
|
|
||||||
import { isStripeValidProductMetadata } from 'src/engine/core-modules/billing/utils/is-stripe-valid-product-metadata.util';
|
import { isStripeValidProductMetadata } from 'src/engine/core-modules/billing/utils/is-stripe-valid-product-metadata.util';
|
||||||
import { transformStripeProductEventToDatabaseProduct } from 'src/engine/core-modules/billing/webhooks/utils/transform-stripe-product-event-to-database-product.util';
|
import { transformStripeProductEventToDatabaseProduct } from 'src/engine/core-modules/billing/webhooks/utils/transform-stripe-product-event-to-database-product.util';
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@ -40,28 +37,4 @@ export class BillingWebhookProductService {
|
|||||||
stripeProductId: data.object.id,
|
stripeProductId: data.object.id,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
isStripeValidProductMetadata(
|
|
||||||
metadata: Stripe.Metadata,
|
|
||||||
): metadata is BillingProductMetadata {
|
|
||||||
if (Object.keys(metadata).length === 0) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
const hasBillingPlanKey = this.isValidBillingPlanKey(metadata?.planKey);
|
|
||||||
const hasPriceUsageBased = this.isValidPriceUsageBased(
|
|
||||||
metadata?.priceUsageBased,
|
|
||||||
);
|
|
||||||
|
|
||||||
return hasBillingPlanKey && hasPriceUsageBased;
|
|
||||||
}
|
|
||||||
|
|
||||||
isValidBillingPlanKey(planKey?: string) {
|
|
||||||
return Object.values(BillingPlanKey).includes(planKey as BillingPlanKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
isValidPriceUsageBased(priceUsageBased?: string) {
|
|
||||||
return Object.values(BillingUsageType).includes(
|
|
||||||
priceUsageBased as BillingUsageType,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -53,7 +53,7 @@ export const transformStripeSubscriptionEventToDatabaseSubscription = (
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const getSubscriptionStatus = (status: Stripe.Subscription.Status) => {
|
export const getSubscriptionStatus = (status: Stripe.Subscription.Status) => {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case 'active':
|
case 'active':
|
||||||
return SubscriptionStatus.Active;
|
return SubscriptionStatus.Active;
|
||||||
|
|||||||
Reference in New Issue
Block a user