Skip to content

Commit f65a67e

Browse files
authored
fix(billing): key the free-tier surfaces off code_usage_subscribed
Follow the field rename from posthog#71315: isCodeUsageUnbilled becomes isCodeUsageFreeTier and reads code_usage_subscribed, matching the wire name and the plans-page card language. Generated-By: PostHog Code Task-Id: 1039ea23-9930-44e2-9888-b05cf8b129ec
1 parent 3a63683 commit f65a67e

4 files changed

Lines changed: 28 additions & 30 deletions

File tree

packages/core/src/billing/usageDisplay.test.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { describe, expect, it } from "vitest";
22
import type { UsageOutput } from "../usage/schemas";
33
import {
44
formatResetTime,
5-
isCodeUsageUnbilled,
5+
isCodeUsageFreeTier,
66
isUsageExceeded,
77
} from "./usageDisplay";
88

@@ -57,19 +57,21 @@ describe("isUsageExceeded", () => {
5757
});
5858
});
5959

60-
describe("isCodeUsageUnbilled", () => {
60+
describe("isCodeUsageFreeTier", () => {
6161
it.each([
6262
[false, true],
6363
[true, false],
6464
// Absent means unknown, never free.
6565
[undefined, false],
66-
] as const)("code_usage_billed=%s -> %s", (billed, expected) => {
67-
expect(isCodeUsageUnbilled({ code_usage_billed: billed })).toBe(expected);
66+
] as const)("code_usage_subscribed=%s -> %s", (subscribed, expected) => {
67+
expect(isCodeUsageFreeTier({ code_usage_subscribed: subscribed })).toBe(
68+
expected,
69+
);
6870
});
6971

7072
it("treats missing usage as not confirmed free", () => {
71-
expect(isCodeUsageUnbilled(null)).toBe(false);
72-
expect(isCodeUsageUnbilled(undefined)).toBe(false);
73+
expect(isCodeUsageFreeTier(null)).toBe(false);
74+
expect(isCodeUsageFreeTier(undefined)).toBe(false);
7375
});
7476
});
7577

packages/core/src/billing/usageDisplay.ts

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,10 @@
11
import type { UsageOutput } from "../usage/schemas";
22

3-
/**
4-
* The org is confirmed on the free tier. False when billed OR unknown —
5-
* `code_usage_billed` is absent on gateways predating the field, and absence
6-
* must never read as free.
7-
*/
8-
export function isCodeUsageUnbilled(
9-
usage: Pick<UsageOutput, "code_usage_billed"> | null | undefined,
3+
/** Confirmed free tier only — an absent `code_usage_subscribed` is unknown, never free. */
4+
export function isCodeUsageFreeTier(
5+
usage: Pick<UsageOutput, "code_usage_subscribed"> | null | undefined,
106
): boolean {
11-
return usage?.code_usage_billed === false;
7+
return usage?.code_usage_subscribed === false;
128
}
139

1410
export function isUsageExceeded(usage: UsageOutput): boolean {

packages/ui/src/features/billing/useFreeUsage.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { isCodeUsageUnbilled } from "@posthog/core/billing/usageDisplay";
1+
import { isCodeUsageFreeTier } from "@posthog/core/billing/usageDisplay";
22
import type { UsageOutput } from "@posthog/core/usage/schemas";
33
import { useUsage } from "./useUsage";
44

@@ -13,9 +13,9 @@ export function useFreeUsage(billingEnabled: boolean): FreeUsageResult {
1313
const { usage, isLoading } = useUsage({ enabled: billingEnabled });
1414

1515
if (!billingEnabled) return { usage: null, isLoading: false };
16-
// Only confirmed-unbilled orgs have a meaningful free-tier meter — billed
17-
// orgs have no per-user caps, and unknown must not render as free.
18-
if (!isCodeUsageUnbilled(usage)) {
16+
// Only confirmed free-tier orgs have a meaningful free-tier meter —
17+
// subscribed orgs have no per-user caps, and unknown must not render as free.
18+
if (!isCodeUsageFreeTier(usage)) {
1919
return { usage: null, isLoading: usage ? false : isLoading };
2020
}
2121
return { usage: usage ?? null, isLoading };

packages/ui/src/features/settings/sections/PlanUsageSettings.tsx

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import {
33
CreditCard,
44
WarningCircle,
55
} from "@phosphor-icons/react";
6-
import { isCodeUsageUnbilled } from "@posthog/core/billing/usageDisplay";
6+
import { isCodeUsageFreeTier } from "@posthog/core/billing/usageDisplay";
77
import {
88
Empty,
99
EmptyDescription,
@@ -29,7 +29,7 @@ import { useEffect } from "react";
2929
* Plan & usage under usage-based billing: no seats or plans to manage in-app —
3030
* payment methods, spend limits, and org-level usage live on the PostHog
3131
* billing page. The free-tier meters show the per-user allowance for
32-
* confirmed-unbilled orgs; billed orgs have no per-user caps to meter.
32+
* confirmed free-tier orgs; subscribed orgs have no per-user caps to meter.
3333
*/
3434
export function PlanUsageSettings() {
3535
const billingEnabled = useFeatureFlag(BILLING_FLAG);
@@ -56,9 +56,9 @@ export function PlanUsageSettings() {
5656
burstUsedPercent: usage?.burst.used_percent ?? null,
5757
});
5858

59-
// Tri-state: unknown (absent field) must render as billed, never as free.
60-
const unbilled = isCodeUsageUnbilled(usage);
61-
const billed = usage?.code_usage_billed === true;
59+
// Tri-state: unknown (absent field) must render as subscribed, never free.
60+
const freeTier = isCodeUsageFreeTier(usage);
61+
const subscribed = usage?.code_usage_subscribed === true;
6262
const orgLimitReached = usage?.ai_credits?.exhausted === true;
6363

6464
const openBilling = () => {
@@ -121,26 +121,26 @@ export function PlanUsageSettings() {
121121
<Flex align="center" justify="between">
122122
<Flex direction="column" gap="1">
123123
<Text className="font-bold text-base">
124-
{unbilled ? "Free tier" : "Usage-based billing"}
124+
{freeTier ? "Free tier" : "Usage-based billing"}
125125
</Text>
126126
<Text className="text-(--gray-11) text-sm">
127-
{unbilled
127+
{freeTier
128128
? "Your organization's first $20 of usage each month is included, with access to open models. Add a payment method to unlock premium models — you only pay for what you use."
129129
: "Your organization pays for PostHog Code usage at cost — no seats, no subscriptions. The first $20 each month is included."}
130130
</Text>
131131
</Flex>
132-
{billed && (
132+
{subscribed && (
133133
<Badge variant="soft" color="green" radius="full">
134134
Active
135135
</Badge>
136136
)}
137137
</Flex>
138138
<Button
139139
size="1"
140-
variant={unbilled ? "solid" : "outline"}
140+
variant={freeTier ? "solid" : "outline"}
141141
disabled={!billingUrl}
142142
onClick={() => {
143-
if (unbilled) {
143+
if (freeTier) {
144144
track(ANALYTICS_EVENTS.UPGRADE_PROMPT_CLICKED, {
145145
surface: "plan_page_card",
146146
});
@@ -149,7 +149,7 @@ export function PlanUsageSettings() {
149149
}}
150150
className="self-start"
151151
>
152-
{unbilled
152+
{freeTier
153153
? "Add payment method"
154154
: "Manage billing and spend limits"}
155155
<ArrowSquareOut size={12} />
@@ -167,7 +167,7 @@ export function PlanUsageSettings() {
167167
>
168168
<Spinner size="2" />
169169
</Flex>
170-
) : unbilled && usage ? (
170+
) : freeTier && usage ? (
171171
<Flex direction="column" gap="3">
172172
<UsageMeter
173173
label="Monthly free usage"

0 commit comments

Comments
 (0)