From a09dfe37a1f011307ede1839715ef0622507bc45 Mon Sep 17 00:00:00 2001 From: sean-brydon <55134778+sean-brydon@users.noreply.github.com> Date: Wed, 26 Nov 2025 09:54:16 +0000 Subject: [PATCH] fix: redirect when creating orgs onboarding v3 (#25390) * Fix redirect * fix redirect --- .../organizations/payment-redirect/route.ts | 84 +++++++++++++++++++ .../lib/OrganizationPaymentService.ts | 4 +- 2 files changed, 86 insertions(+), 2 deletions(-) create mode 100644 apps/web/app/api/organizations/payment-redirect/route.ts diff --git a/apps/web/app/api/organizations/payment-redirect/route.ts b/apps/web/app/api/organizations/payment-redirect/route.ts new file mode 100644 index 0000000000..fadadc5a47 --- /dev/null +++ b/apps/web/app/api/organizations/payment-redirect/route.ts @@ -0,0 +1,84 @@ +import { defaultResponderForAppDir } from "app/api/defaultResponderForAppDir"; +import type { NextRequest } from "next/server"; +import { NextResponse } from "next/server"; +import { z } from "zod"; + +import stripe from "@calcom/features/ee/payments/server/stripe"; +import { FeaturesRepository } from "@calcom/features/flags/features.repository"; +import { WEBAPP_URL } from "@calcom/lib/constants"; +import { HttpError } from "@calcom/lib/http-error"; +import { prisma } from "@calcom/prisma"; + +const querySchema = z.object({ + session_id: z.string().min(1), + paymentStatus: z.enum(["success", "failed"]), +}); + +async function getHandler(req: NextRequest) { + try { + const searchParams = req.nextUrl.searchParams; + const { session_id, paymentStatus } = querySchema.parse({ + session_id: searchParams.get("session_id"), + paymentStatus: searchParams.get("paymentStatus"), + }); + + // Retrieve checkout session from Stripe to get metadata + const checkoutSession = await stripe.checkout.sessions.retrieve(session_id); + if (!checkoutSession) { + throw new HttpError({ statusCode: 404, message: "Checkout session not found" }); + } + + // Extract organizationOnboardingId from metadata + const organizationOnboardingId = checkoutSession.metadata?.organizationOnboardingId; + + // Check if onboarding-v3 feature flag is enabled + const featuresRepository = new FeaturesRepository(prisma); + const isOnboardingV3Enabled = await featuresRepository.checkIfFeatureIsEnabledGlobally("onboarding-v3"); + + // Build query params to preserve + const params = new URLSearchParams({ + session_id, + paymentStatus, + }); + + // If onboarding-v3 is enabled AND organizationOnboardingId exists, redirect to onboarding flow + if (isOnboardingV3Enabled && organizationOnboardingId) { + const redirectUrl = new URL( + `/onboarding/personal/settings?${params.toString()}`, + WEBAPP_URL + ).toString(); + return NextResponse.redirect(redirectUrl); + } + + // Otherwise, redirect to the current flow + // Preserve any additional query params that were passed + searchParams.forEach((value, key) => { + if (key !== "session_id" && key !== "paymentStatus") { + params.append(key, value); + } + }); + + const redirectUrl = new URL( + `/settings/organizations/new/status?${params.toString()}`, + WEBAPP_URL + ).toString(); + return NextResponse.redirect(redirectUrl); + } catch (error) { + if (error instanceof HttpError) { + throw error; + } + // If there's an error, fall back to the default status page + const searchParams = req.nextUrl.searchParams; + const fallbackParams = new URLSearchParams({ + session_id: searchParams.get("session_id") || "", + paymentStatus: searchParams.get("paymentStatus") || "failed", + }); + const fallbackUrl = new URL( + `/settings/organizations/new/status?${fallbackParams.toString()}`, + WEBAPP_URL + ).toString(); + return NextResponse.redirect(fallbackUrl); + } +} + +export const GET = defaultResponderForAppDir(getHandler); diff --git a/packages/features/ee/organizations/lib/OrganizationPaymentService.ts b/packages/features/ee/organizations/lib/OrganizationPaymentService.ts index 334f960dcc..ceb9e4725e 100644 --- a/packages/features/ee/organizations/lib/OrganizationPaymentService.ts +++ b/packages/features/ee/organizations/lib/OrganizationPaymentService.ts @@ -287,8 +287,8 @@ export class OrganizationPaymentService { return this.billingService.createSubscriptionCheckout({ customerId: stripeCustomerId, - successUrl: `${WEBAPP_URL}/settings/organizations/new/status?session_id={CHECKOUT_SESSION_ID}&paymentStatus=success&${params.toString()}`, - cancelUrl: `${WEBAPP_URL}/settings/organizations/new/status?session_id={CHECKOUT_SESSION_ID}&paymentStatus=failed&${params.toString()}`, + successUrl: `${WEBAPP_URL}/api/organizations/payment-redirect?session_id={CHECKOUT_SESSION_ID}&paymentStatus=success&${params.toString()}`, + cancelUrl: `${WEBAPP_URL}/api/organizations/payment-redirect?session_id={CHECKOUT_SESSION_ID}&paymentStatus=failed&${params.toString()}`, priceId, quantity: config.seats, metadata: {