Skip to content

Commit 017ed7e

Browse files
authored
chore(billing): delete the dead seat-based billing feature
The usage-based billing cutover shipped (#3485, #3487, #3488) and the seat model is dead code: nothing rendered useSeat, the seat store was only fed by auth/onboarding sync calls, and the /api/seats/* client methods have no remaining product surface. Removed: shared seat types/plan keys, core SeatService/seatView/ seatErrors + tokens, the UI seat store/client/hook, api-client seat methods and seat error classes, the SUBSCRIPTION_STARTED/CANCELLED analytics events, the orphaned "upgrade_dialog" surface literal, and the gateway invalidate-plan-cache chain (router procedure, service method, endpoint, URL helper) whose only caller was the seat upgrade flow. Auth identity sync keeps the usage-snapshot cache clear (still load-bearing for usage billing); the llmGateway query invalidation it carried was a no-op (no queries live under that key) and is dropped. Generated-By: PostHog Code Task-Id: 1039ea23-9930-44e2-9888-b05cf8b129ec
1 parent 1082a7c commit 017ed7e

26 files changed

Lines changed: 10 additions & 1250 deletions

apps/code/src/main/di/container.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import { readFile as fsReadFile, stat as fsStat } from "node:fs/promises";
44
import { TypedContainer } from "@inversifyjs/strongly-typed";
55
import { DEFAULT_GATEWAY_MODEL } from "@posthog/agent/gateway-models";
66
import {
7-
getGatewayInvalidatePlanCacheUrl,
87
getGatewayUsageUrl,
98
getLlmGatewayUrl,
109
} from "@posthog/agent/posthog-api";
@@ -491,8 +490,6 @@ container.bind(LLM_GATEWAY_HOST).toDynamicValue((ctx) => {
491490
messagesUrl: (apiHost: string) =>
492491
`${getLlmGatewayUrl(apiHost)}/v1/messages`,
493492
usageUrl: (apiHost: string) => getGatewayUsageUrl(apiHost),
494-
invalidatePlanCacheUrl: (apiHost: string) =>
495-
getGatewayInvalidatePlanCacheUrl(apiHost),
496493
defaultModel: DEFAULT_GATEWAY_MODEL,
497494
};
498495
});

apps/code/src/renderer/desktop-contributions.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { agentChatCoreModule } from "@posthog/core/agent-chat/agentChat.module";
22
import { autoresearchCoreModule } from "@posthog/core/autoresearch/autoresearch.module";
3-
import { billingCoreModule } from "@posthog/core/billing/billing.module";
43
import { inboxCoreModule } from "@posthog/core/inbox/inbox.module";
54
import { githubConnectModule } from "@posthog/core/integrations/githubConnect.module";
65
import { localMcpCoreModule } from "@posthog/core/local-mcp/local-mcp.module";
@@ -36,7 +35,6 @@ export function registerDesktopContributions(): void {
3635
authUiModule,
3736
autoresearchCoreModule,
3837
billingUiModule,
39-
billingCoreModule,
4038
browserTabsUiModule,
4139
cloneUiModule,
4240
connectivityUiModule,

packages/agent/src/posthog-api.ts

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,9 @@ import type {
77
TaskRun,
88
TaskRunArtifact,
99
} from "./types";
10-
import {
11-
getGatewayInvalidatePlanCacheUrl,
12-
getGatewayUsageUrl,
13-
getLlmGatewayUrl,
14-
} from "./utils/gateway";
15-
16-
export {
17-
getGatewayInvalidatePlanCacheUrl,
18-
getGatewayUsageUrl,
19-
getLlmGatewayUrl,
20-
};
10+
import { getGatewayUsageUrl, getLlmGatewayUrl } from "./utils/gateway";
11+
12+
export { getGatewayUsageUrl, getLlmGatewayUrl };
2113

2214
const DEFAULT_USER_AGENT = `posthog/agent.hog.dev; version: ${packageJson.version}`;
2315

packages/agent/src/utils/gateway.ts

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -92,10 +92,3 @@ export function getGatewayUsageUrl(
9292
): string {
9393
return `${getGatewayBaseUrl(posthogHost)}/v1/usage/${product}`;
9494
}
95-
96-
export function getGatewayInvalidatePlanCacheUrl(
97-
posthogHost: string,
98-
product: GatewayProduct = "posthog_code",
99-
): string {
100-
return `${getGatewayBaseUrl(posthogHost)}/v1/usage/${product}/invalidate-plan-cache`;
101-
}

packages/api-client/src/posthog-client.ts

Lines changed: 0 additions & 145 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,13 @@ import type {
77
CloudRunSource,
88
ExecutionMode,
99
PrAuthorshipMode,
10-
SeatData,
1110
StoredLogEntry,
1211
TaskRunArtifactMetadata,
1312
} from "@posthog/shared";
1413
import {
1514
DISMISSAL_REASON_OPTIONS,
1615
type DismissalReasonOptionValue,
1716
resolveCloudInitialPermissionMode,
18-
SEAT_PRODUCT_KEY,
1917
} from "@posthog/shared";
2018
import type {
2119
AgentAnalyticsData,
@@ -119,22 +117,6 @@ export function setPosthogApiClientAppVersion(version: string): void {
119117
clientAppVersion = version;
120118
}
121119

122-
export class SeatSubscriptionRequiredError extends Error {
123-
redirectUrl: string;
124-
constructor(redirectUrl: string) {
125-
super("Billing subscription required");
126-
this.name = "SeatSubscriptionRequiredError";
127-
this.redirectUrl = redirectUrl;
128-
}
129-
}
130-
131-
export class SeatPaymentFailedError extends Error {
132-
constructor(message?: string) {
133-
super(message ?? "Payment failed");
134-
this.name = "SeatPaymentFailedError";
135-
}
136-
}
137-
138120
export class SandboxCustomImagesDisabledError extends Error {
139121
constructor(message?: string) {
140122
super(message ?? "Custom sandbox images are not enabled");
@@ -4444,113 +4426,6 @@ export class PostHogAPIClient {
44444426
return data.results ?? [];
44454427
}
44464428

4447-
async getMySeat(
4448-
options: { best?: boolean } = { best: true },
4449-
): Promise<SeatData | null> {
4450-
try {
4451-
const url = new URL(`${this.api.baseUrl}/api/seats/me/`);
4452-
url.searchParams.set("product_key", SEAT_PRODUCT_KEY);
4453-
if (options.best) {
4454-
url.searchParams.set("best", "true");
4455-
}
4456-
const response = await this.api.fetcher.fetch({
4457-
method: "get",
4458-
url,
4459-
path: "/api/seats/me/",
4460-
});
4461-
return (await response.json()) as SeatData;
4462-
} catch (error) {
4463-
if (this.isFetcherStatusError(error, 404)) {
4464-
return null;
4465-
}
4466-
throw error;
4467-
}
4468-
}
4469-
4470-
async createSeat(planKey: string): Promise<SeatData> {
4471-
try {
4472-
const user = await this.getCurrentUser();
4473-
const distinctId = user.distinct_id;
4474-
if (!distinctId) {
4475-
throw new Error("Cannot create seat: user has no distinct_id");
4476-
}
4477-
const url = new URL(`${this.api.baseUrl}/api/seats/`);
4478-
const response = await this.api.fetcher.fetch({
4479-
method: "post",
4480-
url,
4481-
path: "/api/seats/",
4482-
overrides: {
4483-
body: JSON.stringify({
4484-
product_key: SEAT_PRODUCT_KEY,
4485-
plan_key: planKey,
4486-
user_distinct_id: distinctId,
4487-
}),
4488-
},
4489-
});
4490-
return (await response.json()) as SeatData;
4491-
} catch (error) {
4492-
this.throwSeatError(error);
4493-
}
4494-
}
4495-
4496-
async upgradeSeat(planKey: string): Promise<SeatData> {
4497-
try {
4498-
const url = new URL(`${this.api.baseUrl}/api/seats/me/`);
4499-
const response = await this.api.fetcher.fetch({
4500-
method: "patch",
4501-
url,
4502-
path: "/api/seats/me/",
4503-
overrides: {
4504-
body: JSON.stringify({
4505-
product_key: SEAT_PRODUCT_KEY,
4506-
plan_key: planKey,
4507-
}),
4508-
},
4509-
});
4510-
return (await response.json()) as SeatData;
4511-
} catch (error) {
4512-
this.throwSeatError(error);
4513-
}
4514-
}
4515-
4516-
async cancelSeat(): Promise<void> {
4517-
try {
4518-
const url = new URL(`${this.api.baseUrl}/api/seats/me/`);
4519-
url.searchParams.set("product_key", SEAT_PRODUCT_KEY);
4520-
await this.api.fetcher.fetch({
4521-
method: "delete",
4522-
url,
4523-
path: "/api/seats/me/",
4524-
});
4525-
} catch (error) {
4526-
if (this.isFetcherStatusError(error, 204)) {
4527-
return;
4528-
}
4529-
this.throwSeatError(error);
4530-
}
4531-
}
4532-
4533-
async reactivateSeat(): Promise<SeatData> {
4534-
try {
4535-
const url = new URL(`${this.api.baseUrl}/api/seats/me/reactivate/`);
4536-
const response = await this.api.fetcher.fetch({
4537-
method: "post",
4538-
url,
4539-
path: "/api/seats/me/reactivate/",
4540-
overrides: {
4541-
body: JSON.stringify({ product_key: SEAT_PRODUCT_KEY }),
4542-
},
4543-
});
4544-
return (await response.json()) as SeatData;
4545-
} catch (error) {
4546-
this.throwSeatError(error);
4547-
}
4548-
}
4549-
4550-
private isFetcherStatusError(error: unknown, status: number): boolean {
4551-
return error instanceof Error && error.message.includes(`[${status}]`);
4552-
}
4553-
45544429
private parseFetcherError(error: unknown): {
45554430
status: number;
45564431
body: Record<string, unknown>;
@@ -4599,26 +4474,6 @@ export class PostHogAPIClient {
45994474
}
46004475
}
46014476

4602-
private throwSeatError(error: unknown): never {
4603-
const parsed = this.parseFetcherError(error);
4604-
4605-
if (parsed) {
4606-
if (
4607-
parsed.status === 400 &&
4608-
typeof parsed.body.redirect_url === "string"
4609-
) {
4610-
throw new SeatSubscriptionRequiredError(parsed.body.redirect_url);
4611-
}
4612-
if (parsed.status === 402) {
4613-
const message =
4614-
typeof parsed.body.error === "string" ? parsed.body.error : undefined;
4615-
throw new SeatPaymentFailedError(message);
4616-
}
4617-
}
4618-
4619-
throw error;
4620-
}
4621-
46224477
/**
46234478
* Check if a feature flag is enabled for the current project.
46244479
* Returns true if the flag exists and is active, false otherwise.

packages/core/src/billing/billing.module.ts

Lines changed: 0 additions & 8 deletions
This file was deleted.

packages/core/src/billing/identifiers.ts

Lines changed: 0 additions & 27 deletions
This file was deleted.

packages/core/src/billing/seatErrors.ts

Lines changed: 0 additions & 24 deletions
This file was deleted.

0 commit comments

Comments
 (0)