Files
calendar/packages/features/ee/billing/api/webhook/_customer.subscription.deleted.ts
T
aa48e72c12 feat: inbound calls in cal ai (#23890)
* feat: lang support

* fix: type errors

* feat: select voice agent

* refactor: address feedback

* refactor: address feedback

* refactor: missing import

* fix: types

* feat: add inbound calls

* chore: formatting

* chore

* feat: finish inbound call

* chore: formatting

* fix: update bug

* fix: types

* refactor: Agent Configuration Sheet (#23930)

* refactor: agent configuration sheet

* chore: use default phone numbre

* refactor: improvements

* refactor: improvements

* fix: types

* fix: feedback

* chore:

* fix: feedback

* fix: prompt

* fix: review

* fix: review

* refactor: class

* refactor: class

* refactor: rename

* Update apps/web/public/static/locales/en/common.json

* Update apps/web/public/static/locales/en/common.json

* chore: update set value

* fix: remove index

* fix: type error

* fix: update tetss

* fix: use logger

* refactor: don't use static

* fix: type

* fix: schema

* refactor:

---------

Co-authored-by: Carina Wollendorfer <30310907+CarinaWolli@users.noreply.github.com>
2025-10-09 11:40:37 +01:00

99 lines
3.4 KiB
TypeScript

import { createDefaultAIPhoneServiceProvider } from "@calcom/features/calAIPhone";
import { PrismaPhoneNumberRepository } from "@calcom/lib/server/repository/PrismaPhoneNumberRepository";
import prisma from "@calcom/prisma";
import type { LazyModule, SWHMap } from "./__handler";
import { HttpCode } from "./__handler";
type Data = SWHMap["customer.subscription.deleted"]["data"];
type Handlers = Record<`prod_${string}`, () => LazyModule<Data>>;
const STRIPE_TEAM_PRODUCT_ID = process.env.STRIPE_TEAM_PRODUCT_ID || "";
const stripeWebhookProductHandler = (handlers: Handlers) => async (data: Data) => {
const subscription = data.object;
const phoneNumberRepo = new PrismaPhoneNumberRepository(prisma);
const phoneNumber = await phoneNumberRepo.findByStripeSubscriptionId({
stripeSubscriptionId: subscription.id,
});
if (phoneNumber) {
return await handleCalAIPhoneNumberSubscriptionDeleted(subscription, phoneNumber);
}
// Fall back to product-based handling for other subscriptions
let productId: string | null = null;
// @ts-expect-error - support legacy just in case.
if (subscription.plan) {
// @ts-expect-error - we know subscription.plan.product is defined when unsubscribing
productId = subscription.plan.product; // prod_xxxxx
} else {
const subscriptionItem = subscription.items?.data?.[0];
if (!subscriptionItem) {
throw new Error("Subscription item and plan missing");
}
const product = subscription.items.data[0]?.plan.product;
if (product) {
productId = typeof product === "string" ? product : product.id;
}
}
if (typeof productId !== "string") {
throw new Error(`Unable to determine Product ID from subscription: ${subscription.id}`);
}
const handlerGetter = handlers[productId as any];
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) {
console.log("No product handler found for product", productId);
return {
success: false,
message: `No product handler found for product: ${productId}`,
};
}
return await handler(data);
};
async function handleCalAIPhoneNumberSubscriptionDeleted(
subscription: Data["object"],
phoneNumber: NonNullable<Awaited<ReturnType<PrismaPhoneNumberRepository["findByStripeSubscriptionId"]>>>
) {
if (!subscription.id) {
throw new HttpCode(400, "Subscription ID not found");
}
if (!phoneNumber.userId) {
throw new HttpCode(400, "Phone number does not belong to a user");
}
try {
if (phoneNumber.subscriptionStatus === "CANCELLED") {
return { success: true, subscriptionId: subscription.id, skipped: true };
}
const aiService = createDefaultAIPhoneServiceProvider();
await aiService.cancelPhoneNumberSubscription({
phoneNumberId: phoneNumber.id,
userId: phoneNumber.userId,
teamId: phoneNumber.teamId ?? undefined,
});
return { success: true, subscriptionId: subscription.id };
} catch (error) {
console.error("Failed to update phone number subscription:", error);
throw new HttpCode(500, "Failed to update phone number subscription");
}
}
export default stripeWebhookProductHandler({
[STRIPE_TEAM_PRODUCT_ID]: () => import("./_customer.subscription.deleted.team-plan"),
});