Files
sean-brydonGitHubcoderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
224e606439 feat: pbac org billing (#23709)
* refactor layout to not check session

* add actions for orgs + org admins

* update types on actions to be correctly non nullable

* Add tests for utils

* Apply suggestion from @coderabbitai[bot]

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* restore lock file

* add permission check action for org authentication managment

* WIP BRANCH

* Git merge fix conflicts

* Fix imports

* intro to pbac team billing

* restore log

* refactor to be a billing portal factory service

* fix merge conflict

* Fix merge conflicts

* Passing test with non hardcoded vars

* fix migration

* Wip

* remove layout permision checks

* Fix type check

* remove logs

* improve error handling and logs

* Fix nits

* nits

* Use push instead of slice for billing tab

* Add permissions to memo

* Fix credits handlers to use PBAC also

* fix imports

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2025-09-23 14:01:21 +01:00

60 lines
1.9 KiB
TypeScript

import type { NextApiRequest, NextApiResponse } from "next";
import type { Session } from "next-auth";
import { WEBAPP_URL } from "@calcom/lib/constants";
import { getSafeRedirectUrl } from "@calcom/lib/getSafeRedirectUrl";
import { BillingPortalServiceFactory } from "../lib/BillingPortalService";
interface AuthenticatedUser {
id: number;
}
interface RequestWithSession extends NextApiRequest {
session?: Session | null;
}
export const validateAuthentication = (req: NextApiRequest): AuthenticatedUser | null => {
const userId = (req as RequestWithSession).session?.user?.id;
if (!userId) return null;
return { id: userId };
};
export const buildReturnUrl = (returnTo?: string): string => {
const defaultUrl = `${WEBAPP_URL}/settings/billing`;
if (typeof returnTo !== "string") return defaultUrl;
const safeRedirectUrl = getSafeRedirectUrl(returnTo);
return safeRedirectUrl || defaultUrl;
};
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
if (req.method !== "POST" && req.method !== "GET") {
return res.status(405).json({ message: "Method not allowed" });
}
const user = validateAuthentication(req);
if (!user) {
return res.status(401).json({ message: "Not authenticated" });
}
const teamId = req.query.teamId ? parseInt(req.query.teamId as string) : null;
const returnUrl = buildReturnUrl(req.query.returnTo as string);
try {
if (!teamId) {
const userService = BillingPortalServiceFactory.createUserService();
return await userService.processBillingPortal(user.id, returnUrl, res);
}
const billingService = await BillingPortalServiceFactory.createService(teamId);
return await billingService.processBillingPortal(user.id, teamId, returnUrl, res);
} catch (error) {
if (error instanceof Error && error.message === "Team not found") {
return res.status(404).json({ message: "Team not found" });
}
throw error;
}
}