Repository: calcom/cal.com
Pull request #20397: fix: Stop throwing 202 for stripe handler
Author: login: sean-brydon
Labels: name: billing
description: area: billing, stripe, payments, paypal, get paid
name: core
description: area: core, team members only
name: consumer
name: ready-for-e2e
Created: 2025-03-27T10:09:55Z | Merged: 2025-03-27T15:06:15Z

## What does this PR do?

Sentry sees any throws as an error. Constantly flooding sentry



Diff:
diff --git a/packages/features/ee/billing/api/webhook/__handler.test.ts b/packages/features/ee/billing/api/webhook/__handler.test.ts
index c2b151f2a6730d..848b5978e7837d 100644
--- a/packages/features/ee/billing/api/webhook/__handler.test.ts
+++ b/packages/features/ee/billing/api/webhook/__handler.test.ts
@@ -53,7 +53,7 @@ describe("stripeWebhookHandler", () => {
     await expect(handler(req)).rejects.toThrow(new HttpCode(500, "Missing STRIPE_WEBHOOK_SECRET"));
   });
 
-  it("should throw an error if event type is unhandled", async () => {
+  it("should return success false if event type is unhandled", async () => {
     const { req, res } = createMocks<CustomNextApiRequest, CustomNextApiResponse>({
       method: "POST",
       headers: {
@@ -65,9 +65,11 @@ describe("stripeWebhookHandler", () => {
     (stripe.webhooks.constructEvent as any).mockReturnValueOnce({ type: "unhandled_event" });
 
     const handler = stripeWebhookHandler({});
-    await expect(handler(req)).rejects.toThrow(
-      new HttpCode(202, "Unhandled Stripe Webhook event type unhandled_event")
-    );
+    const response = await handler(req);
+    expect(response).toEqual({
+      success: false,
+      message: "Unhandled Stripe Webhook event type unhandled_event",
+    });
   });
 
   it("should call the appropriate handler for a valid event", async () => {
diff --git a/packages/features/ee/billing/api/webhook/__handler.ts b/packages/features/ee/billing/api/webhook/__handler.ts
index f67be10ea1fba3..266389c4b825be 100644
--- a/packages/features/ee/billing/api/webhook/__handler.ts
+++ b/packages/features/ee/billing/api/webhook/__handler.ts
@@ -54,10 +54,22 @@ export const stripeWebhookHandler = (handlers: SWHandlers) => async (req: NextAp
     STRIPE_WEBHOOK_SECRET
   ) as Stripe.DiscriminatedEvent;
   const handlerGetter = handlers[event.type];
-  if (!handlerGetter) throw new HttpCode(202, `Unhandled Stripe Webhook event type ${event.type}`);
+  if (!handlerGetter) {
+    console.log("Unhandled Stripe Webhook event type", event.type);
+    return {
+      success: false,
+      message: `Unhandled Stripe Webhook event type ${event.type}`,
+    };
+  }
   const handler = (await handlerGetter())?.default;
   // auto catch unsupported Stripe events.
-  if (!handler) throw new HttpCode(202, `Unhandled Stripe Webhook event type ${event.type}`);
+  if (!handler) {
+    console.log("Unhandled Stripe Webhook event type", event.type);
+    return {
+      success: false,
+      message: `Unhandled Stripe Webhook event type ${event.type}`,
+    };
+  }
   // @ts-expect-error - we know the handler is defined and accepts the data type
   return await handler(event.data);
 };
diff --git a/packages/features/ee/billing/api/webhook/_customer.subscription.deleted.ts b/packages/features/ee/billing/api/webhook/_customer.subscription.deleted.ts
index 0e3c5bdd2b4323..ec04137915d4ef 100644
--- a/packages/features/ee/billing/api/webhook/_customer.subscription.deleted.ts
+++ b/packages/features/ee/billing/api/webhook/_customer.subscription.deleted.ts
@@ -1,4 +1,3 @@
-import { HttpCode } from "./__handler";
 import type { LazyModule, SWHMap } from "./__handler";
 
 type Data = SWHMap["customer.subscription.deleted"]["data"];
@@ -28,10 +27,22 @@ const stripeWebhookProductHandler = (handlers: Handlers) => async (data: Data) =
     throw new Error(`Unable to determine Product ID from subscription: ${subscription.id}`);
   }
   const handlerGetter = handlers[productId as any];
-  if (!handlerGetter) throw new HttpCode(202, `No product handler found for product: ${productId}`);
+  if (!handlerGetter) {
+    console.log("No product handler found for product", productId);
+    return {
+      success: false,
+      message: `No product handler found for product: ${productId}`,
+    };
+  }
   const handler = (await handlerGetter())?.default;
   // auto catch unsupported Stripe products.
-  if (!handler) throw new HttpCode(202, `No product handler found for product: ${productId}`);
+  if (!handler) {
+    console.log("No product handler found for product", productId);
+    return {
+      success: false,
+      message: `No product handler found for product: ${productId}`,
+    };
+  }
   return await handler(data);
 };
 
diff --git a/packages/features/ee/billing/api/webhook/_invoice.paid.ts b/packages/features/ee/billing/api/webhook/_invoice.paid.ts
index 6d524fb2963e6d..d3a7b44ec44dc1 100644
--- a/packages/features/ee/billing/api/webhook/_invoice.paid.ts
+++ b/packages/features/ee/billing/api/webhook/_invoice.paid.ts
@@ -16,7 +16,7 @@ const stripeWebhookProductHandler = (handlers: Handlers) => async (data: Data) =
   // Only handle subscription invoices
   if (!invoice.subscription) {
     log.warn("Not a subscription invoice, skipping");
-    return { success: true };
+    return { success: false, message: "Not a subscription invoice, skipping" };
   }
 
   // Get the product ID from the first subscription item
@@ -24,8 +24,8 @@ const stripeWebhookProductHandler = (handlers: Handlers) => async (data: Data) =
   const productId = firstItem?.price?.product as string; // prod_xxxxx
 
   if (!productId) {
-    log.error("No product ID found in invoice, skipping");
-    return { success: true };
+    log.warn("No product ID found in invoice, skipping");
+    return { success: false, message: "No product ID found in invoice, skipping" };
   }
 
   const handlerGetter = handlers[productId as keyof typeof handlers];
@@ -34,14 +34,14 @@ const stripeWebhookProductHandler = (handlers: Handlers) => async (data: Data) =
    * If no handler is found, we skip the product. A handler could be null if we don't need webhooks to handle the business logic for the product.
    */
   if (!handlerGetter) {
-    log.error(`Skipping product: ${productId} because no handler found`);
-    return { success: true };
+    log.warn(`Skipping product: ${productId} because no handler found`);
+    return { success: false, message: `Skipping product: ${productId} because no handler found` };
   }
   const handler = (await handlerGetter())?.default;
   // auto catch unsupported Stripe products.
   if (!handler) {
-    log.error(`Skipping product: ${productId} because no handler found`);
-    return { success: true };
+    log.warn(`Skipping product: ${productId} because no handler found`);
+    return { success: false, message: `Skipping product: ${productId} because no handler found` };
   }
   return await handler(data);
 };


Reviews:
user: login: emrysal
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-27T10:49:49Z
user: login: keithwillcode
body: What’s the status code that is returned now that you are returning success: false? 
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-27T10:49:50Z
user: login: keithwillcode
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-27T14:45:26Z
user: login: keithwillcode
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-27T14:45:47Z
user: login: keithwillcode
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-27T14:46:23Z
user: login: zomars
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-27T14:48:10Z

Review comments:
diff_hunk: @@ -25,7 +25,7 @@ const stripeWebhookProductHandler = (handlers: Handlers) => async (data: Data) =
 
   if (!productId) {
     log.error("No product ID found in invoice, skipping");
path: packages/features/ee/billing/api/webhook/_invoice.paid.ts
user: login: emrysal
body: Is this an error?
created_at: 2025-03-27T10:49:49Z
author_association: CONTRIBUTOR
diff_hunk: @@ -25,7 +25,7 @@ const stripeWebhookProductHandler = (handlers: Handlers) => async (data: Data) =
 
   if (!productId) {
     log.error("No product ID found in invoice, skipping");
path: packages/features/ee/billing/api/webhook/_invoice.paid.ts
user: login: keithwillcode
body: Good point. It could be manual invoices sent out and not attached to a product. can just be log
created_at: 2025-03-27T14:45:26Z
author_association: CONTRIBUTOR
diff_hunk: @@ -25,7 +25,7 @@ const stripeWebhookProductHandler = (handlers: Handlers) => async (data: Data) =
 
   if (!productId) {
     log.error("No product ID found in invoice, skipping");
path: packages/features/ee/billing/api/webhook/_invoice.paid.ts
user: login: keithwillcode
body: ```suggestion
    log.warn("No product ID found in invoice, skipping");
```
created_at: 2025-03-27T14:45:47Z
author_association: CONTRIBUTOR

PR comments:
user: login: graphite-app[bot]
created_at: 2025-03-27T10:10:11Z
body: ### Graphite Automations
<details><summary><a href="https://app.graphite.dev/automations/?org=calcom&ruleId=qoTwHtX8MnP39HKxIt4V&ref=gt-automation-github-comment">"Add consumer team as reviewer"</a> took an action on this PR • (03/27/25)</summary><p>1 reviewer was added to this PR based on Keith Williams's automation.</p></details>
<details><summary><a href="https://app.graphite.dev/automations/?org=calcom&ruleId=tJdOAwF9biWptZ2BLCTk&ref=gt-automation-github-comment">"Add ready-for-e2e label"</a> took an action on this PR • (03/27/25)</summary><p>1 label was added to this PR based on Keith Williams's automation.</p></details>
author_association: NONE
user: login: vercel[bot]
created_at: 2025-03-27T10:10:58Z
body: [vc]: #3HCDH0wgLGbtOeeDnbHGm1kBaKI+z5vdFwBiKdIYgAY=:eyJpc01vbm9yZXBvIjp0cnVlLCJ0eXBlIjoiZ2l0aHViIiwicHJvamVjdHMiOlt7Im5hbWUiOiJjYWwiLCJyb290RGlyZWN0b3J5IjoiYXBwcy93ZWIiLCJsaXZlRmVlZGJhY2siOnsicmVzb2x2ZWQiOjAsInVucmVzb2x2ZWQiOjAsInRvdGFsIjowLCJsaW5rIjoiIn0sImluc3BlY3RvclVybCI6Imh0dHBzOi8vdmVyY2VsLmNvbS9jYWwvY2FsL0Vqajc2OG16UFhMTEpFWVRZM2M4YmpMRmg5VVkiLCJuZXh0Q29tbWl0U3RhdHVzIjoiSUdOT1JFRCIsInByZXZpZXdVcmwiOiJjYWwtZ2l0LWZpeC1zdHJpcGUtMjAyLXRocm93bi1jYWwudmVyY2VsLmFwcCJ9LHsibmFtZSI6ImNhbGNvbS13ZWItY2FuYXJ5Iiwicm9vdERpcmVjdG9yeSI6ImFwcHMvd2ViIiwiaW5zcGVjdG9yVXJsIjoiaHR0cHM6Ly92ZXJjZWwuY29tL2NhbC9jYWxjb20td2ViLWNhbmFyeS9QclNOMnlhb05VdllWYnlBc1ZDTEFGdjhNM2JYIiwicHJldmlld1VybCI6ImNhbGNvbS13ZWItY2FuYXJ5LWdpdC1maXgtc3RyaXBlLTIwMi10aHJvd24tY2FsLnZlcmNlbC5hcHAiLCJuZXh0Q29tbWl0U3RhdHVzIjoiSUdOT1JFRCIsImxpdmVGZWVkYmFjayI6eyJyZXNvbHZlZCI6MCwidW5yZXNvbHZlZCI6MCwidG90YWwiOjAsImxpbmsiOiIifX1dfQ==
**The latest updates on your projects**. Learn more about [Vercel for Git ↗︎](https://vercel.link/github-learn-more)


<details><summary>2 Skipped Deployments</summary>

| Name | Status | Preview | Comments | Updated (UTC) |
| :--- | :----- | :------ | :------- | :------ |
| **cal** | ⬜️ Ignored ([Inspect](https://vercel.com/cal/cal/Ejj768mzPXLLJEYTY3c8bjLFh9UY)) | [Visit Preview](https://cal-git-fix-stripe-202-thrown-cal.vercel.app) |  | Mar 27, 2025 2:52pm |
| **calcom-web-canary** | ⬜️ Ignored ([Inspect](https://vercel.com/cal/calcom-web-canary/PrSN2yaoNUvYVbyAsVCLAFv8M3bX)) | [Visit Preview](https://calcom-web-canary-git-fix-stripe-202-thrown-cal.vercel.app) |  | Mar 27, 2025 2:52pm |
</details>



author_association: NONE
user: login: github-actions[bot]
created_at: 2025-03-27T15:13:24Z
body: <!-- GENERATED-E2E-RESULTS -->
## E2E results are ready!
- [Workflow #54298.1 latest results](https://calcom.github.io/test-results/reports/fix/stripe-202-thrown/14109523345/1)
author_association: CONTRIBUTOR