From b4aad453cd45f6bfe249c0ca23058f95e48ffe6e Mon Sep 17 00:00:00 2001 From: Benny Joo Date: Mon, 17 Feb 2025 06:37:13 +0000 Subject: [PATCH] chore: migrate api for team upgrade to App Router (#19100) * remove old pages router api * add new app router api * fix * fix redirect * refactor --- .../web/app/api/teams/[team]/upgrade/route.ts | 104 ++++++++++++++++++ apps/web/pages/api/teams/[team]/upgrade.ts | 1 - packages/features/ee/teams/api/upgrade.ts | 89 --------------- 3 files changed, 104 insertions(+), 90 deletions(-) create mode 100644 apps/web/app/api/teams/[team]/upgrade/route.ts delete mode 100644 apps/web/pages/api/teams/[team]/upgrade.ts delete mode 100644 packages/features/ee/teams/api/upgrade.ts diff --git a/apps/web/app/api/teams/[team]/upgrade/route.ts b/apps/web/app/api/teams/[team]/upgrade/route.ts new file mode 100644 index 0000000000..b7ae67c54d --- /dev/null +++ b/apps/web/app/api/teams/[team]/upgrade/route.ts @@ -0,0 +1,104 @@ +import { cookies, headers } from "next/headers"; +import type { NextRequest } from "next/server"; +import { NextResponse } from "next/server"; +import type Stripe from "stripe"; +import { z } from "zod"; + +import { getRequestedSlugError } from "@calcom/app-store/stripepayment/lib/team-billing"; +import { getServerSession } from "@calcom/features/auth/lib/getServerSession"; +import stripe from "@calcom/features/ee/payments/server/stripe"; +import { WEBAPP_URL } from "@calcom/lib/constants"; +import { HttpError } from "@calcom/lib/http-error"; +import prisma from "@calcom/prisma"; +import { teamMetadataSchema } from "@calcom/prisma/zod-utils"; + +import { buildLegacyRequest } from "@lib/buildLegacyCtx"; + +const querySchema = z.object({ + team: z.string().transform((val) => parseInt(val)), + session_id: z.string().min(1), +}); + +export async function GET(req: NextRequest, { params }: { params: { team: string } }) { + try { + const searchParams = req.nextUrl.searchParams; + const { team: id, session_id } = querySchema.parse({ + team: params.team, + session_id: searchParams.get("session_id"), + }); + + const checkoutSession = await stripe.checkout.sessions.retrieve(session_id, { + expand: ["subscription"], + }); + if (!checkoutSession) { + throw new HttpError({ statusCode: 404, message: "Checkout session not found" }); + } + + const subscription = checkoutSession.subscription as Stripe.Subscription; + if (checkoutSession.payment_status !== "paid") { + throw new HttpError({ statusCode: 402, message: "Payment required" }); + } + + let team = await prisma.team.findFirst({ + where: { metadata: { path: ["paymentId"], equals: checkoutSession.id } }, + }); + + let metadata; + + if (!team) { + const prevTeam = await prisma.team.findFirstOrThrow({ where: { id } }); + + metadata = teamMetadataSchema.safeParse(prevTeam.metadata); + if (!metadata.success) { + throw new HttpError({ statusCode: 400, message: "Invalid team metadata" }); + } + + const { requestedSlug, ...newMetadata } = metadata.data || {}; + team = await prisma.team.update({ + where: { id }, + data: { + metadata: { + ...newMetadata, + paymentId: checkoutSession.id, + subscriptionId: subscription.id || null, + subscriptionItemId: subscription.items.data[0].id || null, + }, + }, + }); + + const slug = prevTeam.slug || requestedSlug; + if (slug) { + try { + team = await prisma.team.update({ where: { id }, data: { slug } }); + } catch (error) { + const { message, statusCode } = getRequestedSlugError(error, slug); + return NextResponse.json({ message }, { status: statusCode }); + } + } + } + + if (!metadata) { + metadata = teamMetadataSchema.safeParse(team.metadata); + if (!metadata.success) { + throw new HttpError({ statusCode: 400, message: "Invalid team metadata" }); + } + } + + const session = await getServerSession({ req: buildLegacyRequest(headers(), cookies()) }); + + if (!session) { + return NextResponse.json({ message: "Team upgraded successfully" }); + } + + const redirectUrl = team?.isOrganization + ? `${WEBAPP_URL}/settings/organizations/profile?upgraded=true` + : `${WEBAPP_URL}/settings/teams/${team.id}/profile?upgraded=true`; + + return NextResponse.redirect(redirectUrl); + } catch (error) { + if (error instanceof HttpError) { + throw error; + } + return NextResponse.json({ error: "Internal Server Error" }, { status: 500 }); + } +} diff --git a/apps/web/pages/api/teams/[team]/upgrade.ts b/apps/web/pages/api/teams/[team]/upgrade.ts deleted file mode 100644 index 1c36cdd9b0..0000000000 --- a/apps/web/pages/api/teams/[team]/upgrade.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from "@calcom/features/ee/teams/api/upgrade"; diff --git a/packages/features/ee/teams/api/upgrade.ts b/packages/features/ee/teams/api/upgrade.ts deleted file mode 100644 index dacb0f17f7..0000000000 --- a/packages/features/ee/teams/api/upgrade.ts +++ /dev/null @@ -1,89 +0,0 @@ -import type { NextApiRequest, NextApiResponse } from "next"; -import type Stripe from "stripe"; -import { z } from "zod"; - -import { getRequestedSlugError } from "@calcom/app-store/stripepayment/lib/team-billing"; -import { getServerSession } from "@calcom/features/auth/lib/getServerSession"; -import stripe from "@calcom/features/ee/payments/server/stripe"; -import { WEBAPP_URL } from "@calcom/lib/constants"; -import { HttpError } from "@calcom/lib/http-error"; -import { defaultHandler, defaultResponder } from "@calcom/lib/server"; -import prisma from "@calcom/prisma"; -import { teamMetadataSchema } from "@calcom/prisma/zod-utils"; - -const querySchema = z.object({ - team: z.string().transform((val) => parseInt(val)), - session_id: z.string().min(1), -}); - -async function handler(req: NextApiRequest, res: NextApiResponse) { - const { team: id, session_id } = querySchema.parse(req.query); - - const checkoutSession = await stripe.checkout.sessions.retrieve(session_id, { - expand: ["subscription"], - }); - if (!checkoutSession) throw new HttpError({ statusCode: 404, message: "Checkout session not found" }); - - const subscription = checkoutSession.subscription as Stripe.Subscription; - if (checkoutSession.payment_status !== "paid") - throw new HttpError({ statusCode: 402, message: "Payment required" }); - - /* Check if a team was already upgraded with this payment intent */ - let team = await prisma.team.findFirst({ - where: { metadata: { path: ["paymentId"], equals: checkoutSession.id } }, - }); - - let metadata; - - if (!team) { - const prevTeam = await prisma.team.findFirstOrThrow({ where: { id } }); - - metadata = teamMetadataSchema.safeParse(prevTeam.metadata); - if (!metadata.success) throw new HttpError({ statusCode: 400, message: "Invalid team metadata" }); - - const { requestedSlug, ...newMetadata } = metadata.data || {}; - /** We save the metadata first to prevent duplicate payments */ - team = await prisma.team.update({ - where: { id }, - data: { - metadata: { - ...newMetadata, - paymentId: checkoutSession.id, - subscriptionId: subscription.id || null, - subscriptionItemId: subscription.items.data[0].id || null, - }, - }, - }); - /** Legacy teams already have a slug, this will allow them to upgrade as well */ - const slug = prevTeam.slug || requestedSlug; - if (slug) { - try { - /** Then we try to upgrade the slug, which may fail if a conflict came up since team creation */ - team = await prisma.team.update({ where: { id }, data: { slug } }); - } catch (error) { - const { message, statusCode } = getRequestedSlugError(error, slug); - return res.status(statusCode).json({ message }); - } - } - } - - if (!metadata) { - metadata = teamMetadataSchema.safeParse(team.metadata); - if (!metadata.success) throw new HttpError({ statusCode: 400, message: "Invalid team metadata" }); - } - - const session = await getServerSession({ req }); - - if (!session) return { message: "Team upgraded successfully" }; - - const redirectUrl = team?.isOrganization - ? `${WEBAPP_URL}/settings/organizations/profile?upgraded=true` - : `${WEBAPP_URL}/settings/teams/${team.id}/profile?upgraded=true`; - - // redirect to team screen - res.redirect(302, redirectUrl); -} - -export default defaultHandler({ - GET: Promise.resolve({ default: defaultResponder(handler) }), -});