-
Notifications
You must be signed in to change notification settings - Fork 672
feat(billing): render the Enterprise tier and hide its self-serve pricing surfaces #15403
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 5 commits
7209fe4
5ee9b28
ca29a67
fe0a575
1d9543b
b849cd0
af2f2a8
2b8fe82
844e4c6
5e9d79b
d296689
d222055
bb0bc30
8743dc5
8bf9f76
b38acfa
32c7f10
2e89c37
9fd27ea
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,10 @@ | ||
| import { useRoute, useRouter } from 'vue-router' | ||
|
|
||
| import { useBillingContext } from '@/composables/billing/useBillingContext' | ||
| import { | ||
| isEnterprisePlanSlug, | ||
| isEnterpriseTier | ||
| } from '@/platform/cloud/subscription/constants/tierPricing' | ||
| import { useSubscriptionDialog } from '@/platform/cloud/subscription/composables/useSubscriptionDialog' | ||
| import { | ||
| clearPreservedQuery, | ||
|
|
@@ -102,7 +106,7 @@ | |
| const route = useRoute() | ||
| const router = useRouter() | ||
| const subscriptionDialog = useSubscriptionDialog() | ||
| const { teamCreditStops, fetchPlans } = useBillingContext() | ||
| const { subscription, teamCreditStops, fetchPlans } = useBillingContext() | ||
| const { permissions } = useWorkspaceUI() | ||
|
|
||
| /** Reads `?pricing=`, strips it, and opens the table when the gate allows. */ | ||
|
|
@@ -139,6 +143,14 @@ | |
| if (typeof param !== 'string' || !param) return | ||
|
|
||
| if (!permissions.value.canManageSubscription) return | ||
| // Enterprise is sales-managed: the pricing table never opens for it, even | ||
| // from a deep link. The param was already stripped above. | ||
| if ( | ||
| isEnterpriseTier(subscription.value?.tier) || | ||
|
Check failure on line 149 in src/platform/cloud/subscription/composables/usePricingTableUrlLoader.ts
|
||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟠 High — The Enterprise gate reads |
||
| isEnterprisePlanSlug(subscription.value?.planSlug) | ||
| ) { | ||
| return | ||
| } | ||
|
|
||
| const teamCheckoutRequest = getTeamCheckoutRequest( | ||
| param, | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -58,7 +58,21 @@ export const DEFAULT_TIER_KEY: TierKey = 'standard' | |
|
|
||
| // TEAM is workspace-level, so it maps to no key in this personal plan catalog. | ||
| export function toTierKey(tier: IngestSubscriptionTier): TierKey | null { | ||
| return tier === 'TEAM' ? null : TIER_TO_KEY[tier] | ||
| return tier === 'TEAM' ? null : (TIER_TO_KEY[tier] ?? null) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟢 Low — |
||
| } | ||
|
|
||
| // ENTERPRISE is a sales-managed workspace tier: absent from the personal | ||
| // catalog and (until the ingest enum ships it) from the generated types, so it | ||
| // is matched as a runtime string. It never self-serves plan changes. | ||
| const ENTERPRISE_TIER = 'ENTERPRISE' | ||
| const ENTERPRISE_PLAN_SLUG_PREFIX = 'enterprise' | ||
|
|
||
| export function isEnterpriseTier(tier: string | null | undefined): boolean { | ||
| return tier?.toUpperCase() === ENTERPRISE_TIER | ||
| } | ||
|
|
||
| export function isEnterprisePlanSlug(slug: string | null | undefined): boolean { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟢 Low — |
||
| return slug?.toLowerCase().startsWith(ENTERPRISE_PLAN_SLUG_PREFIX) === true | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This predicate encodes server-owned business policy in the frontend. |
||
| } | ||
|
|
||
| // Includes the workspace-level TEAM, which toTierKey maps to null: a catalog | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -229,6 +229,10 @@ import { useCurrentUser } from '@/composables/auth/useCurrentUser' | |
|
|
||
| import { useExternalLink } from '@/composables/useExternalLink' | ||
| import { useBillingContext } from '@/composables/billing/useBillingContext' | ||
| import { | ||
| isEnterprisePlanSlug, | ||
| isEnterpriseTier | ||
| } from '@/platform/cloud/subscription/constants/tierPricing' | ||
| import SubscribeButton from '@/platform/cloud/subscription/components/SubscribeButton.vue' | ||
| import { useSubscriptionDialog } from '@/platform/cloud/subscription/composables/useSubscriptionDialog' | ||
| import { isCloud } from '@/platform/distribution/types' | ||
|
|
@@ -303,8 +307,13 @@ const displayedCredits = computed(() => { | |
| }) | ||
| }) | ||
|
|
||
| const isEnterprisePlan = computed( | ||
| () => | ||
| isEnterpriseTier(subscription.value?.tier) || | ||
| isEnterprisePlanSlug(subscription.value?.planSlug) | ||
| ) | ||
| const showPlansAndPricing = computed( | ||
| () => permissions.value.canManageSubscription | ||
| () => permissions.value.canManageSubscription && !isEnterprisePlan.value | ||
|
coderabbitai[bot] marked this conversation as resolved.
Outdated
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟠 High — Hiding |
||
| ) | ||
| const hasDelinquentSubscription = computed( | ||
| () => | ||
|
|
@@ -319,10 +328,11 @@ const showManagePlan = computed( | |
| ) | ||
| const showSubscribeAction = computed( | ||
| () => | ||
| (isCancelled.value && permissions.value.canManageSubscriptionLifecycle) || | ||
| (!canAccessSubscriptionFeatures.value && | ||
| !hasDelinquentSubscription.value && | ||
| permissions.value.canManageSubscription) | ||
| !isEnterprisePlan.value && | ||
| ((isCancelled.value && permissions.value.canManageSubscriptionLifecycle) || | ||
| (!canAccessSubscriptionFeatures.value && | ||
| !hasDelinquentSubscription.value && | ||
| permissions.value.canManageSubscription)) | ||
| ) | ||
|
|
||
| const handleOpenUserSettings = () => { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🟡 Medium — The Enterprise branch sits after the
canAccessSubscriptionFeaturesearly return, so a lapsed or paused Enterprise plan withisTeamPlanfalse resolves toWithoutActiveSubscriptioninstead of the team-flavored state; that policy setsshowsSubscribeUpsellUI: true, rendering the "Upgrade to add credits" button inCreditsTilewhich callsshowPricingTable— the exact self-serve entry point this PR blocks elsewhere. The check also ignoresplanSlug, so the slug-only Enterprise shape falls through entirely. Raised by 4 of 8 reviewers (gpt-5.6-sol-max adversarial, gpt-5.6-sol-max edge-case, claude-opus-5-thinking-max edge-case, gemini-3.1-pro edge-case).