Feat/workspaces 6 billing (#8508)

## Summary

Implements billing infrastructure for team workspaces, separate from
legacy personal billing.

## Changes

- **Billing abstraction**: New `useBillingContext` composable that
switches between legacy (personal) and workspace billing based on
context
- **Workspace subscription flows**: Pricing tables, plan transitions,
cancellation dialogs, and payment preview components for workspace
billing
- **Top-up credits**: Workspace-specific top-up dialog with polling for
payment confirmation
- **Workspace API**: Extended with billing endpoints (subscriptions,
invoices, payment methods, credits top-up)
- **Workspace switcher**: Now displays tier badges for each workspace
- **Subscribe polling**: Added polling mechanisms
(`useSubscribePolling`, `useTopupPolling`) for async payment flows

## Review Focus

- Billing flow correctness for workspace vs legacy contexts
- Polling timeout and error handling in payment flows

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8508-Feat-workspaces-6-billing-2f96d73d365081f69f65c1ddf369010d)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Simula_r
2026-02-06 20:52:53 -08:00
committed by GitHub
parent 030d4fd4d5
commit c5431de123
54 changed files with 4861 additions and 568 deletions

View File

@@ -4,10 +4,12 @@ import type { Component } from 'vue'
import ConfirmationDialogContent from '@/components/dialog/content/ConfirmationDialogContent.vue'
import ErrorDialogContent from '@/components/dialog/content/ErrorDialogContent.vue'
import PromptDialogContent from '@/components/dialog/content/PromptDialogContent.vue'
import TopUpCreditsDialogContentLegacy from '@/components/dialog/content/TopUpCreditsDialogContentLegacy.vue'
import TopUpCreditsDialogContentWorkspace from '@/components/dialog/content/TopUpCreditsDialogContentWorkspace.vue'
import { t } from '@/i18n'
import { useTelemetry } from '@/platform/telemetry'
import { isCloud } from '@/platform/distribution/types'
import { useSubscription } from '@/platform/cloud/subscription/composables/useSubscription'
import { useBillingContext } from '@/composables/billing/useBillingContext'
import { useDialogStore } from '@/stores/dialogStore'
import type {
DialogComponentProps,
@@ -34,8 +36,6 @@ const lazyApiNodesSignInContent = () =>
import('@/components/dialog/content/ApiNodesSignInContent.vue')
const lazySignInContent = () =>
import('@/components/dialog/content/SignInContent.vue')
const lazyTopUpCreditsDialogContent = () =>
import('@/components/dialog/content/TopUpCreditsDialogContent.vue')
const lazyUpdatePasswordContent = () =>
import('@/components/dialog/content/UpdatePasswordContent.vue')
const lazyComfyOrgHeader = () =>
@@ -399,15 +399,17 @@ export const useDialogService = () => {
async function showTopUpCreditsDialog(options?: {
isInsufficientCredits?: boolean
}) {
const { isActiveSubscription } = useSubscription()
const { isActiveSubscription, type } = useBillingContext()
if (!isActiveSubscription.value) return
const { default: TopUpCreditsDialogContent } =
await lazyTopUpCreditsDialogContent()
const component =
type.value === 'workspace'
? TopUpCreditsDialogContentWorkspace
: TopUpCreditsDialogContentLegacy
return dialogStore.showDialog({
key: 'top-up-credits',
component: TopUpCreditsDialogContent,
component,
props: options,
dialogComponentProps: {
headless: true,
@@ -729,6 +731,23 @@ export const useDialogService = () => {
})
}
async function showCancelSubscriptionDialog(cancelAt?: string) {
const { default: component } =
await import('@/components/dialog/content/subscription/CancelSubscriptionDialogContent.vue')
return dialogStore.showDialog({
key: 'cancel-subscription',
component,
props: { cancelAt },
dialogComponentProps: {
...workspaceDialogPt,
pt: {
...workspaceDialogPt.pt,
root: { class: 'rounded-2xl max-w-[400px] w-full' }
}
}
})
}
return {
showLoadWorkflowWarning,
showMissingModelsWarning,
@@ -754,6 +773,7 @@ export const useDialogService = () => {
showRemoveMemberDialog,
showRevokeInviteDialog,
showInviteMemberDialog,
showBillingComingSoonDialog
showBillingComingSoonDialog,
showCancelSubscriptionDialog
}
}