diff --git a/packages/twenty-server/src/engine/core-modules/billing/billing.controller.ts b/packages/twenty-server/src/engine/core-modules/billing/billing.controller.ts index 2c8c85c1aa..9cac8ec106 100644 --- a/packages/twenty-server/src/engine/core-modules/billing/billing.controller.ts +++ b/packages/twenty-server/src/engine/core-modules/billing/billing.controller.ts @@ -10,7 +10,11 @@ import { import { Response } from 'express'; -import { WebhookEvent } from 'src/engine/core-modules/billing/enums/webhook-events.enum'; +import { + BillingException, + BillingExceptionCode, +} from 'src/engine/core-modules/billing/billing.exception'; +import { WebhookEvent } from 'src/engine/core-modules/billing/enums/billing-webhook-events.enum'; import { BillingSubscriptionService } from 'src/engine/core-modules/billing/services/billing-subscription.service'; import { BillingWebhookService } from 'src/engine/core-modules/billing/services/billing-webhook.service'; import { StripeService } from 'src/engine/core-modules/billing/stripe/stripe.service'; @@ -62,15 +66,20 @@ export class BillingController { event.data, ); } - if (event.type === WebhookEvent.CUSTOMER_ACTIVE_ENTITLEMENT) { + if ( + event.type === WebhookEvent.CUSTOMER_ACTIVE_ENTITLEMENT_SUMMARY_UPDATED + ) { try { await this.billingWehbookService.processCustomerActiveEntitlement( event.data, ); } catch (error) { - res.status(500).end(); - - return; + if ( + error instanceof BillingException && + error.code === BillingExceptionCode.BILLING_CUSTOMER_NOT_FOUND + ) { + res.status(404).end(); + } } } res.status(200).end(); diff --git a/packages/twenty-server/src/engine/core-modules/billing/billing.exception.ts b/packages/twenty-server/src/engine/core-modules/billing/billing.exception.ts new file mode 100644 index 0000000000..10e34c755e --- /dev/null +++ b/packages/twenty-server/src/engine/core-modules/billing/billing.exception.ts @@ -0,0 +1,14 @@ +/* @license Enterprise */ + +import { CustomException } from 'src/utils/custom-exception'; + +export class BillingException extends CustomException { + code: BillingExceptionCode; + constructor(message: string, code: BillingExceptionCode) { + super(message, code); + } +} + +export enum BillingExceptionCode { + BILLING_CUSTOMER_NOT_FOUND = 'BILLING_CUSTOMER_NOT_FOUND', +} diff --git a/packages/twenty-server/src/engine/core-modules/billing/billing.resolver.ts b/packages/twenty-server/src/engine/core-modules/billing/billing.resolver.ts index 1997606cbc..c7076bdc2d 100644 --- a/packages/twenty-server/src/engine/core-modules/billing/billing.resolver.ts +++ b/packages/twenty-server/src/engine/core-modules/billing/billing.resolver.ts @@ -7,7 +7,7 @@ import { ProductPricesEntity } from 'src/engine/core-modules/billing/dto/product import { ProductInput } from 'src/engine/core-modules/billing/dto/product.input'; import { SessionEntity } from 'src/engine/core-modules/billing/dto/session.entity'; import { UpdateBillingEntity } from 'src/engine/core-modules/billing/dto/update-billing.entity'; -import { AvailableProduct } from 'src/engine/core-modules/billing/enums/available-product.enum'; +import { AvailableProduct } from 'src/engine/core-modules/billing/enums/billing-available-product.enum'; import { BillingPortalWorkspaceService } from 'src/engine/core-modules/billing/services/billing-portal.workspace-service'; import { BillingSubscriptionService } from 'src/engine/core-modules/billing/services/billing-subscription.service'; import { StripeService } from 'src/engine/core-modules/billing/stripe/stripe.service'; diff --git a/packages/twenty-server/src/engine/core-modules/billing/dto/checkout-session.input.ts b/packages/twenty-server/src/engine/core-modules/billing/dto/checkout-session.input.ts index 41f75e59ca..9371eee8f2 100644 --- a/packages/twenty-server/src/engine/core-modules/billing/dto/checkout-session.input.ts +++ b/packages/twenty-server/src/engine/core-modules/billing/dto/checkout-session.input.ts @@ -3,7 +3,7 @@ import { ArgsType, Field } from '@nestjs/graphql'; import { IsNotEmpty, IsOptional, IsString } from 'class-validator'; import Stripe from 'stripe'; -import { SubscriptionInterval } from 'src/engine/core-modules/billing/enums/subcription-interval.enum'; +import { SubscriptionInterval } from 'src/engine/core-modules/billing/enums/billing-subscription-interval.enum'; @ArgsType() export class CheckoutSessionInput { diff --git a/packages/twenty-server/src/engine/core-modules/billing/dto/product-price.entity.ts b/packages/twenty-server/src/engine/core-modules/billing/dto/product-price.entity.ts index 3ea7a6c835..011d880b2a 100644 --- a/packages/twenty-server/src/engine/core-modules/billing/dto/product-price.entity.ts +++ b/packages/twenty-server/src/engine/core-modules/billing/dto/product-price.entity.ts @@ -2,7 +2,7 @@ import { Field, ObjectType } from '@nestjs/graphql'; import Stripe from 'stripe'; -import { SubscriptionInterval } from 'src/engine/core-modules/billing/enums/subcription-interval.enum'; +import { SubscriptionInterval } from 'src/engine/core-modules/billing/enums/billing-subscription-interval.enum'; @ObjectType() export class ProductPriceEntity { @Field(() => SubscriptionInterval) diff --git a/packages/twenty-server/src/engine/core-modules/billing/dto/product.input.ts b/packages/twenty-server/src/engine/core-modules/billing/dto/product.input.ts index 56d1cceaf8..126e1351d0 100644 --- a/packages/twenty-server/src/engine/core-modules/billing/dto/product.input.ts +++ b/packages/twenty-server/src/engine/core-modules/billing/dto/product.input.ts @@ -2,7 +2,7 @@ import { ArgsType, Field } from '@nestjs/graphql'; import { IsNotEmpty, IsString } from 'class-validator'; -import { AvailableProduct } from 'src/engine/core-modules/billing/enums/available-product.enum'; +import { AvailableProduct } from 'src/engine/core-modules/billing/enums/billing-available-product.enum'; @ArgsType() export class ProductInput { diff --git a/packages/twenty-server/src/engine/core-modules/billing/entities/billing-entitlement.entity.ts b/packages/twenty-server/src/engine/core-modules/billing/entities/billing-entitlement.entity.ts index 8e5947daee..4bb831fdcb 100644 --- a/packages/twenty-server/src/engine/core-modules/billing/entities/billing-entitlement.entity.ts +++ b/packages/twenty-server/src/engine/core-modules/billing/entities/billing-entitlement.entity.ts @@ -14,7 +14,7 @@ import { } from 'typeorm'; import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars'; -import { FeatureStripeLookupKey } from 'src/engine/core-modules/billing/enums/feature-stripe-lookup-key.enum'; +import { BillingEntitlementKey } from 'src/engine/core-modules/billing/enums/billing-entitlement-key.enum'; import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; @Entity({ name: 'billingEntitlement', schema: 'core' }) @ObjectType('billingEntitlement') @@ -26,7 +26,7 @@ export class BillingEntitlement { @Field(() => String) @Column({ nullable: false, type: 'text' }) - key: FeatureStripeLookupKey; + key: BillingEntitlementKey; @Field() @Column({ nullable: false, type: 'uuid' }) diff --git a/packages/twenty-server/src/engine/core-modules/billing/entities/billing-subscription.entity.ts b/packages/twenty-server/src/engine/core-modules/billing/entities/billing-subscription.entity.ts index b22ef7a7b4..711c3f5275 100644 --- a/packages/twenty-server/src/engine/core-modules/billing/entities/billing-subscription.entity.ts +++ b/packages/twenty-server/src/engine/core-modules/billing/entities/billing-subscription.entity.ts @@ -16,8 +16,8 @@ import { import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars'; import { BillingSubscriptionItem } from 'src/engine/core-modules/billing/entities/billing-subscription-item.entity'; -import { SubscriptionInterval } from 'src/engine/core-modules/billing/enums/subcription-interval.enum'; -import { SubscriptionStatus } from 'src/engine/core-modules/billing/enums/subcription-status.enum'; +import { SubscriptionInterval } from 'src/engine/core-modules/billing/enums/billing-subscription-interval.enum'; +import { SubscriptionStatus } from 'src/engine/core-modules/billing/enums/billing-subscription-status.enum'; import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; registerEnumType(SubscriptionStatus, { name: 'SubscriptionStatus' }); diff --git a/packages/twenty-server/src/engine/core-modules/billing/enums/available-product.enum.ts b/packages/twenty-server/src/engine/core-modules/billing/enums/billing-available-product.enum.ts similarity index 100% rename from packages/twenty-server/src/engine/core-modules/billing/enums/available-product.enum.ts rename to packages/twenty-server/src/engine/core-modules/billing/enums/billing-available-product.enum.ts diff --git a/packages/twenty-server/src/engine/core-modules/billing/enums/billing-entitlement-key.enum.ts b/packages/twenty-server/src/engine/core-modules/billing/enums/billing-entitlement-key.enum.ts new file mode 100644 index 0000000000..1547e5c529 --- /dev/null +++ b/packages/twenty-server/src/engine/core-modules/billing/enums/billing-entitlement-key.enum.ts @@ -0,0 +1,3 @@ +export enum BillingEntitlementKey { + SSO = 'sso_feat', +} diff --git a/packages/twenty-server/src/engine/core-modules/billing/enums/subcription-interval.enum.ts b/packages/twenty-server/src/engine/core-modules/billing/enums/billing-subscription-interval.enum.ts similarity index 100% rename from packages/twenty-server/src/engine/core-modules/billing/enums/subcription-interval.enum.ts rename to packages/twenty-server/src/engine/core-modules/billing/enums/billing-subscription-interval.enum.ts diff --git a/packages/twenty-server/src/engine/core-modules/billing/enums/subcription-status.enum.ts b/packages/twenty-server/src/engine/core-modules/billing/enums/billing-subscription-status.enum.ts similarity index 100% rename from packages/twenty-server/src/engine/core-modules/billing/enums/subcription-status.enum.ts rename to packages/twenty-server/src/engine/core-modules/billing/enums/billing-subscription-status.enum.ts diff --git a/packages/twenty-server/src/engine/core-modules/billing/enums/webhook-events.enum.ts b/packages/twenty-server/src/engine/core-modules/billing/enums/billing-webhook-events.enum.ts similarity index 74% rename from packages/twenty-server/src/engine/core-modules/billing/enums/webhook-events.enum.ts rename to packages/twenty-server/src/engine/core-modules/billing/enums/billing-webhook-events.enum.ts index 467b7802fd..efb1e5f571 100644 --- a/packages/twenty-server/src/engine/core-modules/billing/enums/webhook-events.enum.ts +++ b/packages/twenty-server/src/engine/core-modules/billing/enums/billing-webhook-events.enum.ts @@ -3,5 +3,5 @@ export enum WebhookEvent { CUSTOMER_SUBSCRIPTION_UPDATED = 'customer.subscription.updated', CUSTOMER_SUBSCRIPTION_DELETED = 'customer.subscription.deleted', SETUP_INTENT_SUCCEEDED = 'setup_intent.succeeded', - CUSTOMER_ACTIVE_ENTITLEMENT = 'entitlements.active_entitlement_summary.updated', + CUSTOMER_ACTIVE_ENTITLEMENT_SUMMARY_UPDATED = 'entitlements.active_entitlement_summary.updated', } diff --git a/packages/twenty-server/src/engine/core-modules/billing/enums/feature-stripe-lookup-key.enum.ts b/packages/twenty-server/src/engine/core-modules/billing/enums/feature-stripe-lookup-key.enum.ts deleted file mode 100644 index 991676f802..0000000000 --- a/packages/twenty-server/src/engine/core-modules/billing/enums/feature-stripe-lookup-key.enum.ts +++ /dev/null @@ -1,3 +0,0 @@ -export enum FeatureStripeLookupKey { - SSO = 'sso_feat', -} diff --git a/packages/twenty-server/src/engine/core-modules/billing/services/billing-subscription.service.ts b/packages/twenty-server/src/engine/core-modules/billing/services/billing-subscription.service.ts index 2d27d67c3f..f2ebf2d07a 100644 --- a/packages/twenty-server/src/engine/core-modules/billing/services/billing-subscription.service.ts +++ b/packages/twenty-server/src/engine/core-modules/billing/services/billing-subscription.service.ts @@ -9,10 +9,10 @@ import { Not, Repository } from 'typeorm'; import { BillingEntitlement } from 'src/engine/core-modules/billing/entities/billing-entitlement.entity'; import { BillingSubscription } from 'src/engine/core-modules/billing/entities/billing-subscription.entity'; -import { AvailableProduct } from 'src/engine/core-modules/billing/enums/available-product.enum'; -import { FeatureStripeLookupKey } from 'src/engine/core-modules/billing/enums/feature-stripe-lookup-key.enum'; -import { SubscriptionInterval } from 'src/engine/core-modules/billing/enums/subcription-interval.enum'; -import { SubscriptionStatus } from 'src/engine/core-modules/billing/enums/subcription-status.enum'; +import { AvailableProduct } from 'src/engine/core-modules/billing/enums/billing-available-product.enum'; +import { BillingEntitlementKey } from 'src/engine/core-modules/billing/enums/billing-entitlement-key.enum'; +import { SubscriptionInterval } from 'src/engine/core-modules/billing/enums/billing-subscription-interval.enum'; +import { SubscriptionStatus } from 'src/engine/core-modules/billing/enums/billing-subscription-status.enum'; import { StripeService } from 'src/engine/core-modules/billing/stripe/stripe.service'; import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service'; @@ -99,19 +99,19 @@ export class BillingSubscriptionService { async getWorkspaceEntitlementByKey( workspaceId: string, - lookupKey: FeatureStripeLookupKey, + key: BillingEntitlementKey, ) { const entitlement = await this.billingEntitlementRepository.findOneBy({ workspaceId, - key: lookupKey, + key, value: true, }); - if (!entitlement?.value) { + if (!entitlement) { return false; } - return true; + return entitlement.value; } async applyBillingSubscription(user: User) { diff --git a/packages/twenty-server/src/engine/core-modules/billing/services/billing-webhook.service.ts b/packages/twenty-server/src/engine/core-modules/billing/services/billing-webhook.service.ts index 4d58dacbca..fc6c7d2b4c 100644 --- a/packages/twenty-server/src/engine/core-modules/billing/services/billing-webhook.service.ts +++ b/packages/twenty-server/src/engine/core-modules/billing/services/billing-webhook.service.ts @@ -4,11 +4,15 @@ import { InjectRepository } from '@nestjs/typeorm'; import Stripe from 'stripe'; import { Repository } from 'typeorm'; +import { + BillingException, + BillingExceptionCode, +} from 'src/engine/core-modules/billing/billing.exception'; import { BillingEntitlement } from 'src/engine/core-modules/billing/entities/billing-entitlement.entity'; import { BillingSubscriptionItem } from 'src/engine/core-modules/billing/entities/billing-subscription-item.entity'; import { BillingSubscription } from 'src/engine/core-modules/billing/entities/billing-subscription.entity'; -import { FeatureStripeLookupKey } from 'src/engine/core-modules/billing/enums/feature-stripe-lookup-key.enum'; -import { SubscriptionStatus } from 'src/engine/core-modules/billing/enums/subcription-status.enum'; +import { BillingEntitlementKey } from 'src/engine/core-modules/billing/enums/billing-entitlement-key.enum'; +import { SubscriptionStatus } from 'src/engine/core-modules/billing/enums/billing-subscription-status.enum'; import { Workspace, WorkspaceActivationStatus, @@ -103,22 +107,30 @@ export class BillingWebhookService { data: Stripe.EntitlementsActiveEntitlementSummaryUpdatedEvent.Data, ) { const billingSubscription = - await this.billingSubscriptionRepository.findOneOrFail({ + await this.billingSubscriptionRepository.findOne({ where: { stripeCustomerId: data.object.customer }, }); + + if (!billingSubscription) { + throw new BillingException( + 'Billing customer not found', + BillingExceptionCode.BILLING_CUSTOMER_NOT_FOUND, + ); + } + const workspaceId = billingSubscription.workspaceId; const stripeCustomerId = data.object.customer; - const currentEntitlements = data.object.entitlements.data.map( - (item) => item.lookup_key, + const activeEntitlementsKeys = data.object.entitlements.data.map( + (entitlement) => entitlement.lookup_key, ); await this.billingEntitlementRepository.upsert( - Object.values(FeatureStripeLookupKey).map((key) => { + Object.values(BillingEntitlementKey).map((key) => { return { workspaceId, key, - value: currentEntitlements.includes(key), + value: activeEntitlementsKeys.includes(key), stripeCustomerId, }; }), diff --git a/packages/twenty-server/src/engine/core-modules/billing/services/billing.service.ts b/packages/twenty-server/src/engine/core-modules/billing/services/billing.service.ts index 79bce812d2..27cc61bb4a 100644 --- a/packages/twenty-server/src/engine/core-modules/billing/services/billing.service.ts +++ b/packages/twenty-server/src/engine/core-modules/billing/services/billing.service.ts @@ -2,8 +2,8 @@ import { Injectable, Logger } from '@nestjs/common'; import { isDefined } from 'class-validator'; -import { FeatureStripeLookupKey } from 'src/engine/core-modules/billing/enums/feature-stripe-lookup-key.enum'; -import { SubscriptionStatus } from 'src/engine/core-modules/billing/enums/subcription-status.enum'; +import { BillingEntitlementKey } from 'src/engine/core-modules/billing/enums/billing-entitlement-key.enum'; +import { SubscriptionStatus } from 'src/engine/core-modules/billing/enums/billing-subscription-status.enum'; import { BillingSubscriptionService } from 'src/engine/core-modules/billing/services/billing-subscription.service'; import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service'; import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum'; @@ -56,7 +56,7 @@ export class BillingService { async verifyWorkspaceEntitlement( workspaceId: string, - entitlementKey: FeatureStripeLookupKey, + entitlementKey: BillingEntitlementKey, ) { const isBillingEnabled = this.isBillingEnabled(); diff --git a/packages/twenty-server/src/engine/core-modules/billing/stripe/stripe.service.ts b/packages/twenty-server/src/engine/core-modules/billing/stripe/stripe.service.ts index 4d7695e9b8..cff2572444 100644 --- a/packages/twenty-server/src/engine/core-modules/billing/stripe/stripe.service.ts +++ b/packages/twenty-server/src/engine/core-modules/billing/stripe/stripe.service.ts @@ -4,7 +4,7 @@ import Stripe from 'stripe'; import { ProductPriceEntity } from 'src/engine/core-modules/billing/dto/product-price.entity'; import { BillingSubscriptionItem } from 'src/engine/core-modules/billing/entities/billing-subscription-item.entity'; -import { AvailableProduct } from 'src/engine/core-modules/billing/enums/available-product.enum'; +import { AvailableProduct } from 'src/engine/core-modules/billing/enums/billing-available-product.enum'; import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service'; import { User } from 'src/engine/core-modules/user/user.entity'; diff --git a/packages/twenty-server/src/engine/core-modules/sso/services/sso.service.ts b/packages/twenty-server/src/engine/core-modules/sso/services/sso.service.ts index 12b69ec16c..0d5d90a0be 100644 --- a/packages/twenty-server/src/engine/core-modules/sso/services/sso.service.ts +++ b/packages/twenty-server/src/engine/core-modules/sso/services/sso.service.ts @@ -6,7 +6,7 @@ import { InjectRepository } from '@nestjs/typeorm'; import { Issuer } from 'openid-client'; import { Repository } from 'typeorm'; -import { FeatureStripeLookupKey } from 'src/engine/core-modules/billing/enums/feature-stripe-lookup-key.enum'; +import { BillingEntitlementKey } from 'src/engine/core-modules/billing/enums/billing-entitlement-key.enum'; import { BillingService } from 'src/engine/core-modules/billing/services/billing.service'; import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service'; import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum'; @@ -30,7 +30,7 @@ import { User } from 'src/engine/core-modules/user/user.entity'; @Injectable() export class SSOService { - private readonly featureLookUpKey = 'sso_feat' as FeatureStripeLookupKey; + private readonly featureLookUpKey = 'sso_feat' as BillingEntitlementKey; constructor( @InjectRepository(FeatureFlagEntity, 'core') private readonly featureFlagRepository: Repository,