Skip to content

Commit b2e2375

Browse files
committed
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 0b69cb9 commit b2e2375

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 { taskThreadCoreModule } from "@posthog/core/canvas/taskThread.module";
54
import { inboxCoreModule } from "@posthog/core/inbox/inbox.module";
65
import { githubConnectModule } from "@posthog/core/integrations/githubConnect.module";
@@ -37,7 +36,6 @@ export function registerDesktopContributions(): void {
3736
authUiModule,
3837
autoresearchCoreModule,
3938
billingUiModule,
40-
billingCoreModule,
4139
taskThreadCoreModule,
4240
browserTabsUiModule,
4341
cloneUiModule,

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");
@@ -4452,113 +4434,6 @@ export class PostHogAPIClient {
44524434
return data.results ?? [];
44534435
}
44544436

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

4610-
private throwSeatError(error: unknown): never {
4611-
const parsed = this.parseFetcherError(error);
4612-
4613-
if (parsed) {
4614-
if (
4615-
parsed.status === 400 &&
4616-
typeof parsed.body.redirect_url === "string"
4617-
) {
4618-
throw new SeatSubscriptionRequiredError(parsed.body.redirect_url);
4619-
}
4620-
if (parsed.status === 402) {
4621-
const message =
4622-
typeof parsed.body.error === "string" ? parsed.body.error : undefined;
4623-
throw new SeatPaymentFailedError(message);
4624-
}
4625-
}
4626-
4627-
throw error;
4628-
}
4629-
46304485
/**
46314486
* Check if a feature flag is enabled for the current project.
46324487
* 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)