Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions src/composables/useFeatureFlags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export enum ServerFeatureFlag {
UNIFIED_CLOUD_AUTH = 'unified_cloud_auth',
CONSOLIDATED_BILLING_ENABLED = 'consolidated_billing_enabled',
BILLING_CONTROL_ENABLED = 'billing_control_enabled',
EMBEDDED_CHECKOUT_ENABLED = 'embedded_checkout_enabled',
FREE_TIER_JOB_ALLOWANCE_ENABLED = 'free_tier_job_allowance_enabled',
SIGNUP_TURNSTILE = 'signup_turnstile',
SUPPORTS_MODEL_TYPE_TAGS = 'supports_model_type_tags'
Expand Down Expand Up @@ -221,6 +222,13 @@ export function useFeatureFlags() {
cachedBillingControlEnabled
)
},
get embeddedCheckoutEnabled() {
return resolveFlag(
ServerFeatureFlag.EMBEDDED_CHECKOUT_ENABLED,
remoteConfig.value.embedded_checkout_enabled,
false
)
},
get freeTierJobAllowanceEnabled() {
const config = remoteConfig.value as typeof remoteConfig.value & {
free_tier_job_allowance_enabled?: boolean
Expand Down
1 change: 1 addition & 0 deletions src/platform/remoteConfig/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ export type RemoteConfig = {
unified_cloud_auth?: boolean
consolidated_billing_enabled?: boolean
billing_control_enabled?: boolean
embedded_checkout_enabled?: boolean
sentry_dsn?: string
turnstile_sitekey?: string
// Raw, unvalidated wire value (a server typo like 'enfroce' is possible).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,13 @@ import SubscriptionRequiredDialogContentUnified from './SubscriptionRequiredDial
const mockHandleSubscribeTeamClick = vi.fn()
const mockHandleSubscribeClick = vi.fn()
const mockIsInPersonalWorkspace = ref(false)
const mockCheckoutStep = ref('pricing')
const mockPreviewVariant = ref<string | null>(null)
const mockEmbeddedCheckoutEnabled = ref(false)

vi.mock('@/platform/workspace/composables/useSubscriptionCheckout', () => ({
useSubscriptionCheckout: () => ({
checkoutStep: ref('pricing'),
checkoutStep: mockCheckoutStep,
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
isLoadingPreview: ref(false),
loadingTier: ref(null),
isSubscribing: ref(false),
Expand All @@ -24,18 +27,30 @@ vi.mock('@/platform/workspace/composables/useSubscriptionCheckout', () => ({
activeCheckoutActionUrl: ref(null),
isPolling: ref(false),
isTeamCheckout: computed(() => false),
previewVariant: computed(() => null),
previewVariant: computed(() => mockPreviewVariant.value),
handleSubscribeClick: mockHandleSubscribeClick,
handleSubscribeTeamClick: mockHandleSubscribeTeamClick,
handleBackToPricing: vi.fn(),
handleSuccessClose: vi.fn(),
handleAddCreditCard: vi.fn(),
handleConfirmTransition: vi.fn(),
handleTeamSubscribe: vi.fn(),
handleSubscriptionPayment: vi.fn(),
handleTeamSubscriptionPayment: vi.fn(),
handleResubscribe: vi.fn()
})
}))

vi.mock('@/composables/useFeatureFlags', () => ({
useFeatureFlags: () => ({
flags: {
get embeddedCheckoutEnabled() {
return mockEmbeddedCheckoutEnabled.value
}
}
})
}))

vi.mock('@/platform/workspace/stores/teamWorkspaceStore', () => ({
useTeamWorkspaceStore: () => ({
get isInPersonalWorkspace() {
Expand Down Expand Up @@ -71,14 +86,22 @@ const UnifiedPricingTableStub = {
}
}

const SubscriptionAddPaymentPreviewWorkspaceStub = {
name: 'SubscriptionAddPaymentPreviewWorkspace',
props: ['usePaymentElement'],
template:
'<div data-testid="payment-preview">{{ String(usePaymentElement) }}</div>'
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

function renderComponent(props: Record<string, unknown> = {}) {
return render(SubscriptionRequiredDialogContentUnified, {
props: { onClose: vi.fn(), ...props },
global: {
plugins: [i18n],
stubs: {
UnifiedPricingTable: UnifiedPricingTableStub,
SubscriptionAddPaymentPreviewWorkspace: { template: '<div />' },
SubscriptionAddPaymentPreviewWorkspace:
SubscriptionAddPaymentPreviewWorkspaceStub,
SubscriptionTransitionPreviewWorkspace: { template: '<div />' },
SubscriptionSuccessWorkspace: { template: '<div />' }
}
Expand All @@ -90,6 +113,10 @@ describe('SubscriptionRequiredDialogContentUnified team-plan subscribe', () => {
beforeEach(() => {
vi.clearAllMocks()
mockIsInPersonalWorkspace.value = false
mockCheckoutStep.value = 'pricing'
mockPreviewVariant.value = null
mockEmbeddedCheckoutEnabled.value = false
vi.stubEnv('VITE_STRIPE_PUBLISHABLE_KEY', 'pk_test')
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
})

it('advances to team checkout from a team workspace', async () => {
Expand Down Expand Up @@ -152,4 +179,23 @@ describe('SubscriptionRequiredDialogContentUnified team-plan subscribe', () => {
})
expect(mockHandleSubscribeClick).not.toHaveBeenCalled()
})

it('keeps the legacy checkout when the embedded checkout flag is disabled', () => {
mockCheckoutStep.value = 'preview'
mockPreviewVariant.value = 'personal-new'

renderComponent()

expect(screen.getByTestId('payment-preview')).toHaveTextContent('false')
})

it('uses the embedded checkout when the flag is enabled', () => {
mockCheckoutStep.value = 'preview'
mockPreviewVariant.value = 'personal-new'
mockEmbeddedCheckoutEnabled.value = true

renderComponent()

expect(screen.getByTestId('payment-preview')).toHaveTextContent('true')
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@ import { useEventListener } from '@vueuse/core'
import { computed, nextTick, onMounted, ref, watch } from 'vue'

import Button from '@/components/ui/button/Button.vue'
import { useFeatureFlags } from '@/composables/useFeatureFlags'
import type { PaymentIntentSource } from '@/platform/telemetry/types'
import type { SubscriptionCheckoutSelection } from '@/platform/workspace/composables/useSubscriptionCheckout'
import { useSubscriptionCheckout } from '@/platform/workspace/composables/useSubscriptionCheckout'
Expand All @@ -183,8 +184,11 @@ const emit = defineEmits<{
close: [subscribed: boolean]
}>()

const stripePaymentElementEnabled = Boolean(
import.meta.env.VITE_STRIPE_PUBLISHABLE_KEY
const { flags } = useFeatureFlags()
const stripePaymentElementEnabled = computed(
() =>
flags.embeddedCheckoutEnabled &&
Boolean(import.meta.env.VITE_STRIPE_PUBLISHABLE_KEY)
)

// The embedded-payment confirm step keeps the pricing table's dialog
Expand All @@ -198,7 +202,7 @@ const savedMethodsForConfirm = ref<SavedPaymentMethod[] | null>(null)
const isEmbeddedPaymentStep = computed(
() =>
checkoutStep.value === 'preview' &&
stripePaymentElementEnabled &&
stripePaymentElementEnabled.value &&
!savedMethodsForConfirm.value?.length &&
(previewVariant.value === 'team-new' ||
previewVariant.value === 'personal-new')
Expand All @@ -210,7 +214,7 @@ const isEmbeddedPaymentStep = computed(
const isEmbeddedConfirmStep = computed(
() =>
checkoutStep.value === 'preview' &&
stripePaymentElementEnabled &&
stripePaymentElementEnabled.value &&
(previewVariant.value === 'team-change' ||
previewVariant.value === 'personal-change' ||
(!!savedMethodsForConfirm.value?.length &&
Expand All @@ -223,7 +227,7 @@ const isEmbeddedConfirmStep = computed(
const isEmbeddedSuccessStep = computed(
() =>
checkoutStep.value === 'success' &&
stripePaymentElementEnabled &&
stripePaymentElementEnabled.value &&
(previewVariant.value === 'team-new' ||
previewVariant.value === 'personal-new')
)
Expand Down Expand Up @@ -273,7 +277,7 @@ watch(
[checkoutStep, () => !!savedMethodsForConfirm.value?.length] as const,
async ([next, nextSaved], [prev, prevSaved]) => {
const el = contentRoot.value
if (!el || !stripePaymentElementEnabled) return
if (!el || !stripePaymentElementEnabled.value) return
const between = (a: string, b: string) =>
(prev === a && next === b) || (prev === b && next === a)
const savedFlip =
Expand Down
Loading