From ffd5346effac25d99e82230776cc05856bbc90eb Mon Sep 17 00:00:00 2001 From: lawrencecchen <54008264+lawrencecchen@users.noreply.github.com> Date: Thu, 23 Jul 2026 22:00:22 -0700 Subject: [PATCH 1/2] test initial Pro checkout destination --- web/tests/pro-cta-link.test.tsx | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 web/tests/pro-cta-link.test.tsx diff --git a/web/tests/pro-cta-link.test.tsx b/web/tests/pro-cta-link.test.tsx new file mode 100644 index 000000000000..8a69ed83185b --- /dev/null +++ b/web/tests/pro-cta-link.test.tsx @@ -0,0 +1,30 @@ +import { describe, expect, mock, test } from "bun:test"; +import { renderToStaticMarkup } from "react-dom/server"; + +const capture = mock(() => undefined); + +mock.module("posthog-js", () => ({ + default: { capture }, +})); + +const { ProCtaLink } = await import( + "../app/[locale]/components/pro-cta-link" +); + +describe("Pro pricing CTA", () => { + test("routes the initial click to Stripe checkout", () => { + const html = renderToStaticMarkup( + + Get Pro + , + ); + + expect(html).toContain( + 'href="/api/billing/checkout?plan=pro"', + ); + expect(html).not.toContain('href="/download/confirmation?dl=1"'); + }); +}); From ef420168e1c53d06352ba94b54d100cfaec57991 Mon Sep 17 00:00:00 2001 From: lawrencecchen <54008264+lawrencecchen@users.noreply.github.com> Date: Thu, 23 Jul 2026 22:03:19 -0700 Subject: [PATCH 2/2] route Pro pricing CTA directly to checkout --- docs/pro-badge-options.html | 3 +- scripts/retired-feature-flags.txt | 1 + skills/cmux-billing/SKILL.md | 2 +- web/app/[locale]/components/pro-cta-link.tsx | 35 ++++---------------- web/app/[locale]/pricing/page.tsx | 6 +--- web/app/lib/feature-flags.ts | 8 ----- web/tests/pro-cta-link.test.tsx | 1 - 7 files changed, 10 insertions(+), 46 deletions(-) diff --git a/docs/pro-badge-options.html b/docs/pro-badge-options.html index cb187d3284c9..d2925330afb3 100644 --- a/docs/pro-badge-options.html +++ b/docs/pro-badge-options.html @@ -189,9 +189,8 @@

Feature flags (PostHog project 244066)

pro-upgrade-ui-enabled-release — badge + all Pro entrypoints · ON mobile-connect-button-enabled-release — titlebar iPhone button · ON - pro-checkout-enabled-release — pricing CTA → checkout · OFF
-

Each is registered in code (owner + review date + safe default), gated live from the PostHog dashboard, and enforced by scripts/lint-feature-flags.py in CI against the posthog.com feature-flag-mistakes rules.

+

Each is registered in code (owner + review date + safe default), gated live from the PostHog dashboard, and enforced by scripts/lint-feature-flags.py in CI against the posthog.com feature-flag-mistakes rules. Pricing checkout is launched and no longer feature-gated.

In-app pricing screen (native)

diff --git a/scripts/retired-feature-flags.txt b/scripts/retired-feature-flags.txt index 9498d88d16cf..6523936d26ba 100644 --- a/scripts/retired-feature-flags.txt +++ b/scripts/retired-feature-flags.txt @@ -2,3 +2,4 @@ # from code. Keys listed here must NEVER be reused for new functionality — # reusing a stale key is how Knight Capital lost $440M in 45 minutes. # scripts/lint-feature-flags.py fails CI if any of these reappear. +pro-checkout-enabled-release diff --git a/skills/cmux-billing/SKILL.md b/skills/cmux-billing/SKILL.md index c9833d7f0f22..a45af793fd2e 100644 --- a/skills/cmux-billing/SKILL.md +++ b/skills/cmux-billing/SKILL.md @@ -40,7 +40,7 @@ Use this skill before changing billing, pricing, Stripe, Pro entitlement, checko ## Feature Flags - `pro-upgrade-ui-enabled-release` (PostHog id `741838`) gates all Pro UI and stays OFF in release until launch. DEBUG builds default the UI on. -- `pro-checkout-enabled-release` (PostHog id `741839`) routes the public pricing CTA. +- Public Pro and Team pricing CTAs always route through `/api/billing/checkout`; do not fall back to the download confirmation page. - `cmux __internal_flags`, once merged, inspects and overrides flags locally. ## Prod Runbook diff --git a/web/app/[locale]/components/pro-cta-link.tsx b/web/app/[locale]/components/pro-cta-link.tsx index fb8ec87968ee..9834aa21b9db 100644 --- a/web/app/[locale]/components/pro-cta-link.tsx +++ b/web/app/[locale]/components/pro-cta-link.tsx @@ -9,51 +9,28 @@ import { CheckoutPendingContent, useCheckoutRedirect, } from "../../components/checkout-navigation"; -import { - isClientConfigFlagEnabled, - useClientConfigFlag, -} from "../../lib/client-config-flags"; -import { FEATURE_FLAGS } from "../../lib/feature-flags"; - -// Single evaluation site for the pro-checkout flag (lint-enforced). -// Resolution: build-time env force (local dev / previews), then the PostHog -// flag, then the registry's safe default (the download link always works, -// including while flags are still loading — no checkout flicker). -const FORCE = process.env.NEXT_PUBLIC_CMUX_CHECKOUT_ENABLED; -const FORCED_ON = FORCE === "1" || (FORCE === undefined && process.env.NODE_ENV === "development"); -const FORCED_OFF = FORCE === "0"; export function ProCtaLink({ checkoutHref, - fallbackHref, children, size = "default", location = "pricing_page", }: { checkoutHref: string; - fallbackHref: string; children: React.ReactNode; size?: PricingActionSize; location?: string; }) { - const flagEnabled = useClientConfigFlag(FEATURE_FLAGS.proCheckout.key); - const checkout = - !FORCED_OFF && - (FORCED_ON || - isClientConfigFlagEnabled( - flagEnabled, - FEATURE_FLAGS.proCheckout.defaultWhenUnavailable, - )); - const href = checkout ? checkoutHref : fallbackHref; const { pending, start } = useCheckoutRedirect(); return ( { - posthog.capture("cmuxterm_pro_cta_clicked", { location, checkout }); - // Only the checkout href is intercepted for the spinner; the download - // fallback navigates normally. - start(href, event); + posthog.capture("cmuxterm_pro_cta_clicked", { + location, + checkout: true, + }); + start(checkoutHref, event); }} aria-busy={pending} className={`${pricingActionClassName("primary", size)} relative`} diff --git a/web/app/[locale]/pricing/page.tsx b/web/app/[locale]/pricing/page.tsx index e1efda6c64e4..f6c8d9bd50c9 100644 --- a/web/app/[locale]/pricing/page.tsx +++ b/web/app/[locale]/pricing/page.tsx @@ -36,9 +36,6 @@ import { } from "../../components/pricing-shared"; import { CheckoutButton } from "../../components/checkout-navigation"; -// The Pro CTA destination is decided at runtime by the proCheckout PostHog -// flag inside (see app/lib/feature-flags.ts); the download -// link is the safe fallback. const ENTERPRISE_CTA_URL = "/enterprise"; const ANONYMOUS_IF_EXISTS = "anonymous-if-exists[deprecated]" as const; @@ -152,7 +149,7 @@ export default async function PricingPage({ ) : ( - + {t("pro.cta")} )} @@ -216,7 +213,6 @@ export default async function PricingPage({ ) : ( diff --git a/web/app/lib/feature-flags.ts b/web/app/lib/feature-flags.ts index 46009f8d979f..36a3fce4c419 100644 --- a/web/app/lib/feature-flags.ts +++ b/web/app/lib/feature-flags.ts @@ -32,12 +32,4 @@ export const FEATURE_FLAGS = { reviewBy: "2026-10-01", defaultWhenUnavailable: false, }, - proCheckout: { - key: "pro-checkout-enabled-release", - owner: "lawrencecchen", - description: - "Points the pricing page Pro CTA at /api/billing/checkout instead of the download link. Off until prod Stripe is live.", - reviewBy: "2026-10-01", - defaultWhenUnavailable: false, - }, } as const satisfies Record; diff --git a/web/tests/pro-cta-link.test.tsx b/web/tests/pro-cta-link.test.tsx index 8a69ed83185b..d29e687eae35 100644 --- a/web/tests/pro-cta-link.test.tsx +++ b/web/tests/pro-cta-link.test.tsx @@ -16,7 +16,6 @@ describe("Pro pricing CTA", () => { const html = renderToStaticMarkup( Get Pro ,