Skip to content

Commit a978944

Browse files
dante01yoonampagent
andcommitted
fix(billing): preserve local invoice actions
Amp-Thread-ID: https://ampcode.com/threads/T-019fc87c-d73e-7635-83db-1f7c0e65e42d Co-authored-by: Amp <amp@ampcode.com>
1 parent 59b3f6b commit a978944

5 files changed

Lines changed: 117 additions & 8 deletions

File tree

src/locales/en/main.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2642,6 +2642,7 @@
26422642
"changesToPlanOnDate": "Changes to {plan} on {date}",
26432643
"manageSubscription": "Manage subscription",
26442644
"billingAndInvoices": "Billing & invoices",
2645+
"manageBilling": "Manage billing",
26452646
"changePlan": "Change plan",
26462647
"cancelPlan": "Cancel plan",
26472648
"canceled": "Canceled",
@@ -2748,6 +2749,7 @@
27482749
"saveYearly": "Save 20%",
27492750
"tierNameYearly": "{name} Yearly",
27502751
"messageSupport": "Message support",
2752+
"invoiceHistory": "Invoice history",
27512753
"refreshCredits": "Refresh credits",
27522754
"benefits": {
27532755
"benefit1": "Monthly credits for Partner Nodes — top up when needed",

src/platform/cloud/subscription/components/SubscriptionFooterLinks.test.ts

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,24 @@ import { createI18n } from 'vue-i18n'
77
import SubscriptionFooterLinks from './SubscriptionFooterLinks.vue'
88

99
const state = vi.hoisted(() => ({
10+
isCloud: true,
11+
manageSubscription: vi.fn(),
1012
handleLearnMoreClick: vi.fn(),
1113
handleMessageSupport: vi.fn()
1214
}))
1315

16+
vi.mock('@/platform/distribution/types', () => ({
17+
get isCloud() {
18+
return state.isCloud
19+
}
20+
}))
21+
22+
vi.mock('@/composables/billing/useBillingContext', () => ({
23+
useBillingContext: () => ({
24+
manageSubscription: state.manageSubscription
25+
})
26+
}))
27+
1428
vi.mock('@/composables/useExternalLink', () => ({
1529
useExternalLink: () => ({
1630
buildDocsUrl: vi.fn(() => 'https://docs.comfy.org/partner-nodes'),
@@ -37,14 +51,16 @@ const i18n = createI18n({
3751
subscription: {
3852
learnMore: 'Learn more',
3953
partnerNodesPricingTable: 'Partner Nodes pricing',
40-
messageSupport: 'Message support'
54+
messageSupport: 'Message support',
55+
invoiceHistory: 'Invoice history'
4156
}
4257
}
4358
}
4459
})
4560

46-
function renderComponent() {
61+
function renderComponent(showInvoiceHistory?: boolean) {
4762
return render(SubscriptionFooterLinks, {
63+
props: showInvoiceHistory === undefined ? {} : { showInvoiceHistory },
4864
global: {
4965
plugins: [i18n],
5066
stubs: {
@@ -61,6 +77,7 @@ function renderComponent() {
6177
describe('SubscriptionFooterLinks', () => {
6278
beforeEach(() => {
6379
vi.clearAllMocks()
80+
state.isCloud = true
6481
})
6582

6683
afterEach(() => {
@@ -99,4 +116,24 @@ describe('SubscriptionFooterLinks', () => {
99116
'_blank'
100117
)
101118
})
119+
120+
it('keeps Invoice history working outside the cloud distribution', async () => {
121+
const user = userEvent.setup()
122+
state.isCloud = false
123+
renderComponent()
124+
125+
await user.click(screen.getByRole('button', { name: 'Invoice history' }))
126+
127+
expect(state.manageSubscription).toHaveBeenCalledOnce()
128+
})
129+
130+
it('hides Invoice history from local users without billing permission', () => {
131+
state.isCloud = false
132+
renderComponent(false)
133+
134+
expect(
135+
screen.queryByRole('button', { name: 'Invoice history' })
136+
).not.toBeInTheDocument()
137+
expect(state.manageSubscription).not.toHaveBeenCalled()
138+
})
102139
})

src/platform/cloud/subscription/components/SubscriptionFooterLinks.vue

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,19 +29,42 @@
2929
{{ $t('subscription.messageSupport') }}
3030
</Button>
3131
</div>
32+
33+
<Button
34+
v-if="!isCloud && showInvoiceHistory"
35+
variant="muted-textonly"
36+
class="text-xs text-text-secondary"
37+
@click="handleInvoiceHistory"
38+
>
39+
{{ $t('subscription.invoiceHistory') }}
40+
<i class="pi pi-external-link text-xs text-text-secondary" />
41+
</Button>
3242
</div>
3343
</template>
3444

3545
<script setup lang="ts">
3646
import Button from '@/components/ui/button/Button.vue'
47+
import { useBillingContext } from '@/composables/billing/useBillingContext'
3748
import { useExternalLink } from '@/composables/useExternalLink'
3849
import { useSubscriptionActions } from '@/platform/cloud/subscription/composables/useSubscriptionActions'
50+
import { isCloud } from '@/platform/distribution/types'
51+
52+
const { showInvoiceHistory = true } = defineProps<{
53+
showInvoiceHistory?: boolean
54+
}>()
3955
4056
const { buildDocsUrl, docsPaths } = useExternalLink()
4157
58+
const { manageSubscription } = useBillingContext()
59+
4260
const { isLoadingSupport, handleMessageSupport, handleLearnMoreClick } =
4361
useSubscriptionActions()
4462
63+
async function handleInvoiceHistory() {
64+
if (!showInvoiceHistory) return
65+
await manageSubscription()
66+
}
67+
4568
function handleOpenPartnerNodesInfo() {
4669
window.open(
4770
buildDocsUrl(docsPaths.partnerNodesPricing, { includeLocale: true }),

src/platform/workspace/components/SubscriptionPanelContentWorkspace.test.ts

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,13 @@ const { mockIsSettingUp, mockSubscriptionActionOperation } = vi.hoisted(() => ({
2424
value: undefined as { actionUrl: string } | undefined
2525
}
2626
}))
27+
const mockDistributionState = vi.hoisted(() => ({ isCloud: true }))
28+
29+
vi.mock('@/platform/distribution/types', () => ({
30+
get isCloud() {
31+
return mockDistributionState.isCloud
32+
}
33+
}))
2734

2835
const RENEWAL_DATE_ISO = '2026-06-20T12:00:00Z'
2936
const END_DATE_ISO = '2026-01-20T12:00:00Z'
@@ -265,7 +272,9 @@ const ButtonStub = {
265272
}
266273

267274
const SubscriptionFooterLinksStub = {
268-
template: '<div data-testid="subscription-footer-links" />'
275+
props: ['showInvoiceHistory'],
276+
template:
277+
'<div data-testid="subscription-footer-links" :data-show-invoice-history="String(showInvoiceHistory)" />'
269278
}
270279

271280
const DropdownMenuStub = {
@@ -297,6 +306,7 @@ describe('SubscriptionPanelContentWorkspace', () => {
297306

298307
beforeEach(() => {
299308
vi.clearAllMocks()
309+
mockDistributionState.isCloud = true
300310
mockSubscriptionStatus.value = 'active'
301311
mockBillingStatus.value = 'paid'
302312
mockRenewalDate.value = RENEWAL_DATE_ISO
@@ -372,6 +382,10 @@ describe('SubscriptionPanelContentWorkspace', () => {
372382
screen.getByText(`Renews on ${formatPanelDate(RENEWAL_DATE_ISO)}`)
373383
).toBeInTheDocument()
374384
expect(screen.getByTestId('subscription-footer-links')).toBeInTheDocument()
385+
expect(screen.getByTestId('subscription-footer-links')).toHaveAttribute(
386+
'data-show-invoice-history',
387+
'true'
388+
)
375389
})
376390

377391
it('shows a scheduled plan change instead of the renewal date', () => {
@@ -471,6 +485,22 @@ describe('SubscriptionPanelContentWorkspace', () => {
471485
expect(mockShowSubscriptionDialog).toHaveBeenCalledOnce()
472486
})
473487

488+
it('preserves local Manage billing and Invoice history actions', async () => {
489+
const user = userEvent.setup()
490+
mockDistributionState.isCloud = false
491+
renderComponent()
492+
493+
expect(
494+
screen.queryByRole('button', { name: 'Billing & invoices' })
495+
).not.toBeInTheDocument()
496+
await user.click(screen.getByRole('button', { name: 'Manage billing' }))
497+
expect(mockManageSubscription).toHaveBeenCalledOnce()
498+
expect(screen.getByTestId('subscription-footer-links')).toHaveAttribute(
499+
'data-show-invoice-history',
500+
'true'
501+
)
502+
})
503+
474504
it('keeps a Personal workspace Team-plan member view read-only', () => {
475505
mockIsInPersonalWorkspace.value = true
476506
mockCanManageSubscription.value = false
@@ -494,6 +524,10 @@ describe('SubscriptionPanelContentWorkspace', () => {
494524
screen.getByRole('button', { name: 'Leave Workspace' })
495525
).toBeInTheDocument()
496526
expect(screen.getByText('Invite members')).toBeInTheDocument()
527+
expect(screen.getByTestId('subscription-footer-links')).toHaveAttribute(
528+
'data-show-invoice-history',
529+
'false'
530+
)
497531
})
498532

499533
it('uses Team-plan change copy in a Personal workspace', () => {

src/platform/workspace/components/SubscriptionPanelContentWorkspace.vue

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@
108108
</div>
109109
<div class="flex flex-wrap gap-2 md:ml-auto">
110110
<Button
111-
v-if="permissions.canManageSubscription"
111+
v-if="isCloud && permissions.canManageSubscription"
112112
size="lg"
113113
variant="secondary"
114114
class="rounded-lg bg-interface-menu-component-surface-selected px-4 text-sm font-normal text-text-primary"
@@ -174,7 +174,7 @@
174174
</div>
175175
<div class="flex flex-wrap gap-2 md:ml-auto">
176176
<Button
177-
v-if="permissions.canManageSubscription"
177+
v-if="isCloud && permissions.canManageSubscription"
178178
size="lg"
179179
variant="secondary"
180180
class="rounded-lg bg-interface-menu-component-surface-selected px-4 text-sm font-normal text-text-primary"
@@ -236,13 +236,22 @@
236236
class="flex flex-wrap gap-2 md:ml-auto"
237237
>
238238
<Button
239-
v-if="permissions.canManageSubscription"
239+
v-if="
240+
permissions.canManageSubscription &&
241+
(isCloud || !isFreeTierPlan)
242+
"
240243
size="lg"
241244
variant="secondary"
242245
class="rounded-lg bg-interface-menu-component-surface-selected px-4 text-sm font-normal text-text-primary"
243246
@click="manageSubscription"
244247
>
245-
{{ $t('subscription.billingAndInvoices') }}
248+
{{
249+
$t(
250+
isCloud
251+
? 'subscription.billingAndInvoices'
252+
: 'subscription.manageBilling'
253+
)
254+
}}
246255
</Button>
247256
<Button
248257
v-if="
@@ -377,7 +386,10 @@
377386
</Button>
378387
</div>
379388

380-
<SubscriptionFooterLinks class="mt-auto pt-6" />
389+
<SubscriptionFooterLinks
390+
class="mt-auto pt-6"
391+
:show-invoice-history="permissions.canManageSubscription"
392+
/>
381393
</template>
382394
</div>
383395
</template>
@@ -398,6 +410,7 @@ import { useSubscriptionDialog } from '@/platform/cloud/subscription/composables
398410
import { useFreeTierQuota } from '@/platform/cloud/subscription/composables/useFreeTierQuota'
399411
import type { TierBenefit } from '@/platform/cloud/subscription/utils/tierBenefits'
400412
import { getCommonTierBenefits } from '@/platform/cloud/subscription/utils/tierBenefits'
413+
import { isCloud } from '@/platform/distribution/types'
401414
import { useResubscribe } from '@/platform/workspace/composables/useResubscribe'
402415
import { useWorkspaceMenuItems } from '@/platform/workspace/composables/useWorkspaceMenuItems'
403416
import { useWorkspacePlanPricing } from '@/platform/workspace/composables/useWorkspacePlanPricing'

0 commit comments

Comments
 (0)