diff --git a/packages/twenty-server/src/database/typeorm/core/migrations/billing/1739981257356-addOnUpdateInEntitlements.ts b/packages/twenty-server/src/database/typeorm/core/migrations/billing/1739981257356-addOnUpdateInEntitlements.ts new file mode 100644 index 000000000..b8bd5756d --- /dev/null +++ b/packages/twenty-server/src/database/typeorm/core/migrations/billing/1739981257356-addOnUpdateInEntitlements.ts @@ -0,0 +1,25 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddOnUpdateInEntitlements1739981257356 + implements MigrationInterface +{ + name = 'AddOnUpdateInEntitlements1739981257356'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "core"."billingEntitlement" DROP CONSTRAINT "FK_766a1918aa3dbe0d67d3df62356"`, + ); + await queryRunner.query( + `ALTER TABLE "core"."billingEntitlement" ADD CONSTRAINT "FK_766a1918aa3dbe0d67d3df62356" FOREIGN KEY ("stripeCustomerId") REFERENCES "core"."billingCustomer"("stripeCustomerId") ON DELETE CASCADE ON UPDATE CASCADE`, + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "core"."billingEntitlement" DROP CONSTRAINT "FK_766a1918aa3dbe0d67d3df62356"`, + ); + await queryRunner.query( + `ALTER TABLE "core"."billingEntitlement" ADD CONSTRAINT "FK_766a1918aa3dbe0d67d3df62356" FOREIGN KEY ("stripeCustomerId") REFERENCES "core"."billingCustomer"("stripeCustomerId") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + } +} 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 c579fcb86..3b7f35d0f 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 @@ -47,23 +47,34 @@ export class BillingController { @Res() res: Response, ) { if (!req.rawBody) { - res.status(400).end(); - - return; + throw new BillingException( + 'Missing request body', + BillingExceptionCode.BILLING_MISSING_REQUEST_BODY, + ); } - const event = this.stripeWebhookService.constructEventFromPayload( - signature, - req.rawBody, - ); try { + const event = this.stripeWebhookService.constructEventFromPayload( + signature, + req.rawBody, + ); const result = await this.handleStripeEvent(event); res.status(200).send(result).end(); } catch (error) { - if (error instanceof BillingException) { - res.status(404).end(); + if ( + error instanceof BillingException || + error instanceof Stripe.errors.StripeError + ) { + throw error; } + const errorMessage = + error instanceof Error ? error.message : JSON.stringify(error); + + throw new BillingException( + errorMessage, + BillingExceptionCode.BILLING_UNHANDLED_ERROR, + ); } } 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 index fceca015c..35b26ea27 100644 --- a/packages/twenty-server/src/engine/core-modules/billing/billing.exception.ts +++ b/packages/twenty-server/src/engine/core-modules/billing/billing.exception.ts @@ -16,4 +16,7 @@ export enum BillingExceptionCode { BILLING_SUBSCRIPTION_EVENT_WORKSPACE_NOT_FOUND = 'BILLING_SUBSCRIPTION_EVENT_WORKSPACE_NOT_FOUND', BILLING_ACTIVE_SUBSCRIPTION_NOT_FOUND = 'BILLING_ACTIVE_SUBSCRIPTION_NOT_FOUND', BILLING_METER_EVENT_FAILED = 'BILLING_METER_EVENT_FAILED', + BILLING_MISSING_REQUEST_BODY = 'BILLING_MISSING_REQUEST_BODY', + BILLING_UNHANDLED_ERROR = 'BILLING_UNHANDLED_ERROR', + BILLING_STRIPE_ERROR = 'BILLING_STRIPE_ERROR', } 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 f89153cc7..b093064b8 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 @@ -54,6 +54,7 @@ export class BillingEntitlement { (billingCustomer) => billingCustomer.billingEntitlements, { onDelete: 'CASCADE', + onUpdate: 'CASCADE', }, ) @JoinColumn({ diff --git a/packages/twenty-server/src/engine/core-modules/billing/filters/billing-api-exception.filter.ts b/packages/twenty-server/src/engine/core-modules/billing/filters/billing-api-exception.filter.ts index 020949e01..444605e1a 100644 --- a/packages/twenty-server/src/engine/core-modules/billing/filters/billing-api-exception.filter.ts +++ b/packages/twenty-server/src/engine/core-modules/billing/filters/billing-api-exception.filter.ts @@ -17,22 +17,41 @@ export class BillingRestApiExceptionFilter implements ExceptionFilter { private readonly httpExceptionHandlerService: HttpExceptionHandlerService, ) {} - catch(exception: BillingException, host: ArgumentsHost) { + catch( + exception: BillingException | Stripe.errors.StripeError, + host: ArgumentsHost, + ) { const ctx = host.switchToHttp(); const response = ctx.getResponse(); + if (exception instanceof Stripe.errors.StripeError) { + return this.httpExceptionHandlerService.handleError( + { + code: BillingExceptionCode.BILLING_STRIPE_ERROR, + message: exception.message, + name: 'StripeError', + }, + response, + 400, + ); + } + switch (exception.code) { case BillingExceptionCode.BILLING_CUSTOMER_NOT_FOUND: + case BillingExceptionCode.BILLING_ACTIVE_SUBSCRIPTION_NOT_FOUND: + case BillingExceptionCode.BILLING_SUBSCRIPTION_EVENT_WORKSPACE_NOT_FOUND: + case BillingExceptionCode.BILLING_PRODUCT_NOT_FOUND: + case BillingExceptionCode.BILLING_PLAN_NOT_FOUND: return this.httpExceptionHandlerService.handleError( exception, response, 404, ); - case BillingExceptionCode.BILLING_PRODUCT_NOT_FOUND: + case BillingExceptionCode.BILLING_METER_EVENT_FAILED: return this.httpExceptionHandlerService.handleError( exception, response, - 404, + 400, ); default: return this.httpExceptionHandlerService.handleError(