diff --git a/apps/api/v2/src/modules/billing/controllers/billing.controller.e2e-spec.ts b/apps/api/v2/src/modules/billing/controllers/billing.controller.e2e-spec.ts index 2c59d2c1b4..e41ff969f6 100644 --- a/apps/api/v2/src/modules/billing/controllers/billing.controller.e2e-spec.ts +++ b/apps/api/v2/src/modules/billing/controllers/billing.controller.e2e-spec.ts @@ -116,6 +116,7 @@ describe("Platform Billing Controller (e2e)", () => { return request(app.getHttpServer()) .post("/v2/billing/webhook") + .set("stripe-signature", "t=1234567890,v1=random_signature_for_e2e_test") .expect(200) .then(async (res) => { const billing = await platformBillingRepositoryFixture.get(organization.id); @@ -143,6 +144,7 @@ describe("Platform Billing Controller (e2e)", () => { ); return request(app.getHttpServer()) .post("/v2/billing/webhook") + .set("stripe-signature", "t=1234567890,v1=random_signature_for_e2e_test") .expect(200) .then(async (res) => { const billing = await platformBillingRepositoryFixture.get(organization.id); @@ -172,6 +174,7 @@ describe("Platform Billing Controller (e2e)", () => { return request(app.getHttpServer()) .post("/v2/billing/webhook") + .set("stripe-signature", "t=1234567890,v1=random_signature_for_e2e_test") .expect(200) .then(async (res) => { const billing = await platformBillingRepositoryFixture.get(organization.id); @@ -204,6 +207,7 @@ describe("Platform Billing Controller (e2e)", () => { return request(app.getHttpServer()) .post("/v2/billing/webhook") + .set("stripe-signature", "t=1234567890,v1=random_signature_for_e2e_test") .expect(200) .then(async (res) => { const billing = await platformBillingRepositoryFixture.get(organization.id); diff --git a/apps/api/v2/src/modules/billing/controllers/billing.controller.ts b/apps/api/v2/src/modules/billing/controllers/billing.controller.ts index 4898014e61..38d9467f29 100644 --- a/apps/api/v2/src/modules/billing/controllers/billing.controller.ts +++ b/apps/api/v2/src/modules/billing/controllers/billing.controller.ts @@ -21,10 +21,12 @@ import { HttpStatus, Logger, Delete, + BadRequestException, } from "@nestjs/common"; import { ConfigService } from "@nestjs/config"; import { ApiExcludeController } from "@nestjs/swagger"; import { Request } from "express"; +import Stripe from "stripe"; import { ApiResponse } from "@calcom/platform-types"; @@ -116,35 +118,59 @@ export class BillingController { @Req() request: Request, @Headers("stripe-signature") stripeSignature: string ): Promise { - const event = await this.billingService.stripeService - .getStripe() - .webhooks.constructEventAsync(request.body, stripeSignature, this.stripeWhSecret); + try { + if (!stripeSignature) { + this.logger.warn("Missing stripe-signature header in webhook request"); + return { + status: "success", + }; + } - switch (event.type) { - case "checkout.session.completed": - await this.billingService.handleStripeCheckoutEvents(event); - break; - case "customer.subscription.updated": - await this.billingService.handleStripePaymentPastDue(event); - break; - case "customer.subscription.deleted": - await this.billingService.handleStripeSubscriptionDeleted(event); - break; - case "invoice.created": - await this.billingService.handleStripeSubscriptionForActiveManagedUsers(event); - break; - case "invoice.payment_failed": - await this.billingService.handleStripePaymentFailed(event); - break; - case "invoice.payment_succeeded": - await this.billingService.handleStripePaymentSuccess(event); - break; - default: - break; + if (!this.stripeWhSecret) { + this.logger.error("Missing STRIPE_WEBHOOK_SECRET configuration"); + return { + status: "success", + }; + } + + const event = await this.billingService.stripeService + .getStripe() + .webhooks.constructEventAsync(request.body, stripeSignature, this.stripeWhSecret); + + switch (event.type) { + case "checkout.session.completed": + await this.billingService.handleStripeCheckoutEvents(event); + break; + case "customer.subscription.updated": + await this.billingService.handleStripePaymentPastDue(event); + break; + case "customer.subscription.deleted": + await this.billingService.handleStripeSubscriptionDeleted(event); + break; + case "invoice.created": + await this.billingService.handleStripeSubscriptionForActiveManagedUsers(event); + break; + case "invoice.payment_failed": + await this.billingService.handleStripePaymentFailed(event); + break; + case "invoice.payment_succeeded": + await this.billingService.handleStripePaymentSuccess(event); + break; + default: + break; + } + + return { + status: "success", + }; + } catch (error) { + if (error instanceof Stripe.errors.StripeSignatureVerificationError) { + this.logger.error("Webhook signature validation failed", error); + return { + status: "success", + }; + } + throw error; } - - return { - status: "success", - }; } }