Skip to content

Commit 34d8e03

Browse files
authored
fix(billing): correct URL construction and prevent double-redirecting (#2034)
1 parent 6462dc9 commit 34d8e03

3 files changed

Lines changed: 67 additions & 6 deletions

File tree

apps/code/src/renderer/features/settings/components/sections/PlanUsageSettings.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import {
2323
import { Tooltip } from "@renderer/components/ui/Tooltip";
2424
import { PLAN_PRO_ALPHA } from "@shared/types/seat";
2525
import { logger } from "@utils/logger";
26-
import { getPostHogUrl } from "@utils/urls";
26+
import { getBillingUrl, getPostHogUrl } from "@utils/urls";
2727
import { useEffect, useState } from "react";
2828

2929
const log = logger.scope("plan-usage");
@@ -39,7 +39,7 @@ async function openBillingPage(orgId: string | null): Promise<void> {
3939
log.warn("Failed to switch org before opening billing", err);
4040
}
4141
}
42-
const url = getPostHogUrl("/organization/billing");
42+
const url = getBillingUrl();
4343
if (url) window.open(url, "_blank");
4444
}
4545

@@ -70,9 +70,9 @@ export function PlanUsageSettings() {
7070
const { fetchSeat, upgradeToPro, cancelSeat, reactivateSeat, clearError } =
7171
useSeatStore();
7272
const cloudRegion = useAuthStateValue((state) => state.cloudRegion);
73-
const billingUrl = getPostHogUrl("/organization/billing", cloudRegion);
73+
const billingUrl = getBillingUrl(cloudRegion);
7474
const redirectFullUrl = redirectUrl
75-
? getPostHogUrl(redirectUrl, cloudRegion)
75+
? (getPostHogUrl(redirectUrl, cloudRegion) ?? billingUrl)
7676
: null;
7777
const [showUpgradeDialog, setShowUpgradeDialog] = useState(false);
7878

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { describe, expect, it, vi } from "vitest";
2+
3+
vi.mock("@features/auth/hooks/authQueries", () => ({
4+
getCachedAuthState: () => ({ cloudRegion: null }),
5+
}));
6+
7+
import { getBillingUrl, getPostHogUrl } from "./urls";
8+
9+
describe("getPostHogUrl", () => {
10+
it("returns null when no region is available and the input is a path", () => {
11+
expect(getPostHogUrl("/foo")).toBeNull();
12+
});
13+
14+
it.each([
15+
["us", "/foo", "https://us.posthog.com/foo"],
16+
["us", "foo", "https://us.posthog.com/foo"],
17+
["eu", "/foo", "https://eu.posthog.com/foo"],
18+
] as const)(
19+
"joins base and path for %s region (path=%s)",
20+
(region, path, expected) => {
21+
expect(getPostHogUrl(path, region)).toBe(expected);
22+
},
23+
);
24+
25+
it.each([
26+
"https://app.posthog.com/organization/billing",
27+
"http://localhost:8000/checkout",
28+
"HTTPS://us.posthog.com/foo",
29+
])("passes absolute URLs through unchanged: %s", (url) => {
30+
expect(getPostHogUrl(url, "us")).toBe(url);
31+
});
32+
});
33+
34+
describe("getBillingUrl", () => {
35+
it.each([
36+
["us", "https://us.posthog.com/organization/billing/overview"],
37+
["eu", "https://eu.posthog.com/organization/billing/overview"],
38+
] as const)(
39+
"points at /organization/billing/overview on %s",
40+
(region, expected) => {
41+
expect(getBillingUrl(region)).toBe(expected);
42+
},
43+
);
44+
45+
it("returns null when no region is available", () => {
46+
expect(getBillingUrl()).toBeNull();
47+
});
48+
49+
it("does not produce the malformed double-scheme URL we used to ship", () => {
50+
const url = getBillingUrl("us");
51+
expect(url).not.toMatch(/https?:\/\/[^/]+\/https?:/);
52+
expect(url).not.toContain("/project/");
53+
});
54+
});

apps/code/src/renderer/utils/urls.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,18 @@ import type { CloudRegion } from "@shared/types/regions";
33
import { getCloudUrlFromRegion } from "@shared/utils/urls";
44

55
export function getPostHogUrl(
6-
path: string,
6+
pathOrUrl: string,
77
regionOverride?: CloudRegion | null,
88
): string | null {
9+
if (/^https?:\/\//i.test(pathOrUrl)) return pathOrUrl;
910
const region = regionOverride ?? getCachedAuthState().cloudRegion;
1011
if (!region) return null;
1112
const base = getCloudUrlFromRegion(region);
12-
return `${base}${path.startsWith("/") ? path : `/${path}`}`;
13+
return `${base}${pathOrUrl.startsWith("/") ? pathOrUrl : `/${pathOrUrl}`}`;
14+
}
15+
16+
export function getBillingUrl(
17+
regionOverride?: CloudRegion | null,
18+
): string | null {
19+
return getPostHogUrl("/organization/billing/overview", regionOverride);
1320
}

0 commit comments

Comments
 (0)