Skip to content

Commit a9d96c6

Browse files
dante01yoonampagent
andcommitted
fix: identify past-due subscriptions precisely
Amp-Thread-ID: https://ampcode.com/threads/T-01a001d5-47e3-720d-8e82-82c45995251e Co-authored-by: Amp <amp@ampcode.com>
1 parent 0b3b6c4 commit a9d96c6

5 files changed

Lines changed: 55 additions & 13 deletions

File tree

browser_tests/tests/dialogs/creditsTile.spec.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ const endedPersonalBillingStatus: BillingStatusResponse = {
8585
has_funds: true
8686
}
8787

88-
const paymentFailedBillingStatus: BillingStatusResponse = {
88+
const pastDueBillingStatus: BillingStatusResponse = {
8989
...mockBillingStatus,
9090
is_active: false,
9191
billing_status: 'payment_failed'
@@ -282,12 +282,12 @@ test.describe('Credits tile (Plan & Credits)', { tag: '@cloud' }, () => {
282282
.toBe('https://billing.example/portal')
283283
})
284284

285-
test('keeps billing management available when payment fails', async ({
285+
test('keeps billing management available for a past-due subscription', async ({
286286
page
287287
}) => {
288288
test.setTimeout(60_000)
289289

290-
await mockCloudBoot(page, true, paymentFailedBillingStatus)
290+
await mockCloudBoot(page, true, pastDueBillingStatus)
291291

292292
const content = await openPlanAndCredits(page)
293293
await expect(

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

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ const state = vi.hoisted(() => ({
1616
canAccessSubscriptionFeatures: true,
1717
isFreeTier: false,
1818
isCancelled: false,
19+
planSlug: 'pro-monthly' as string | null,
1920
canTopUp: false,
2021
canManageSubscription: false,
2122
canManageSubscriptionLifecycle: false,
@@ -60,7 +61,8 @@ vi.mock('@/composables/billing/useBillingContext', () => ({
6061
),
6162
isFreeTier: computed(() => state.isFreeTier),
6263
subscription: computed(() => ({
63-
isCancelled: state.isCancelled
64+
isCancelled: state.isCancelled,
65+
planSlug: state.planSlug
6466
})),
6567
balance: ref({ amountMicros: 100 }),
6668
isLoading: ref(false),
@@ -163,6 +165,7 @@ describe('CurrentUserPopoverWorkspace', () => {
163165
state.canAccessSubscriptionFeatures = true
164166
state.isFreeTier = false
165167
state.isCancelled = false
168+
state.planSlug = 'pro-monthly'
166169
state.canTopUp = false
167170
state.canManageSubscription = false
168171
state.canManageSubscriptionLifecycle = false
@@ -253,7 +256,7 @@ describe('CurrentUserPopoverWorkspace', () => {
253256
})
254257

255258
it.for(['payment_failed', 'paused'])(
256-
'keeps Manage plan available instead of Subscribe when billing is %s',
259+
'keeps Manage plan available for an existing %s subscription',
257260
(billingStatus) => {
258261
state.billingStatus = billingStatus
259262
state.canAccessSubscriptionFeatures = false
@@ -268,6 +271,22 @@ describe('CurrentUserPopoverWorkspace', () => {
268271
}
269272
)
270273

274+
it('shows Subscribe instead of Manage plan when payment_failed has no plan', () => {
275+
state.billingStatus = 'payment_failed'
276+
state.canAccessSubscriptionFeatures = false
277+
state.canManageSubscription = true
278+
state.planSlug = null
279+
280+
renderComponent('team')
281+
282+
expect(
283+
screen.queryByTestId('manage-plan-menu-item')
284+
).not.toBeInTheDocument()
285+
expect(
286+
screen.getByRole('button', { name: 'Subscribe' })
287+
).toBeInTheDocument()
288+
})
289+
271290
it.for([
272291
{
273292
name: 'allows a lifecycle manager to resubscribe a cancelled plan',

src/platform/workspace/components/CurrentUserPopoverWorkspace.vue

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -306,19 +306,22 @@ const displayedCredits = computed(() => {
306306
const showPlansAndPricing = computed(
307307
() => permissions.value.canManageSubscription
308308
)
309+
const hasDelinquentSubscription = computed(
310+
() =>
311+
(billingStatus.value === 'payment_failed' ||
312+
billingStatus.value === 'paused') &&
313+
Boolean(subscription.value?.planSlug)
314+
)
309315
const showManagePlan = computed(
310316
() =>
311317
permissions.value.canManageSubscription &&
312-
(canAccessSubscriptionFeatures.value ||
313-
billingStatus.value === 'payment_failed' ||
314-
billingStatus.value === 'paused')
318+
(canAccessSubscriptionFeatures.value || hasDelinquentSubscription.value)
315319
)
316320
const showSubscribeAction = computed(
317321
() =>
318322
(isCancelled.value && permissions.value.canManageSubscriptionLifecycle) ||
319323
(!canAccessSubscriptionFeatures.value &&
320-
billingStatus.value !== 'payment_failed' &&
321-
billingStatus.value !== 'paused' &&
324+
!hasDelinquentSubscription.value &&
322325
permissions.value.canManageSubscription)
323326
)
324327

src/platform/workspace/composables/useWorkspaceMenuItems.test.ts

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ const state = vi.hoisted(() => ({
1212
isDeleteDisabled: false,
1313
isFreeTier: false,
1414
isInPersonalWorkspace: false,
15+
planSlug: 'pro-monthly' as string | null,
1516
isSubscriptionCancelled: false
1617
}))
1718

@@ -30,7 +31,10 @@ vi.mock('@/composables/billing/useBillingContext', () => ({
3031
useBillingContext: () => ({
3132
billingStatus: computed(() => state.billingStatus),
3233
isFreeTier: computed(() => state.isFreeTier),
33-
subscription: computed(() => ({ endDate: '2026-08-01T00:00:00Z' }))
34+
subscription: computed(() => ({
35+
endDate: '2026-08-01T00:00:00Z',
36+
planSlug: state.planSlug
37+
}))
3438
})
3539
}))
3640

@@ -76,6 +80,7 @@ describe('useWorkspaceMenuItems', () => {
7680
state.isDeleteDisabled = false
7781
state.isFreeTier = false
7882
state.isInPersonalWorkspace = false
83+
state.planSlug = 'pro-monthly'
7984
state.isSubscriptionCancelled = false
8085
})
8186

@@ -128,13 +133,26 @@ describe('useWorkspaceMenuItems', () => {
128133
)
129134
})
130135

131-
it('withholds cancellation while a plan is paused', () => {
136+
it('allows cancellation while an existing plan is paused', () => {
132137
state.billingStatus = 'paused'
133138
state.canManageSubscriptionLifecycle = true
134139
state.isActiveSubscription = false
135140

136141
const { menuItems } = useWorkspaceMenuItems()
137142

143+
expect(menuItems.value.map((item) => item.label)).toContain(
144+
'subscription.cancelPlan'
145+
)
146+
})
147+
148+
it('withholds cancellation when payment_failed has no subscription plan', () => {
149+
state.billingStatus = 'payment_failed'
150+
state.canManageSubscriptionLifecycle = true
151+
state.isActiveSubscription = false
152+
state.planSlug = null
153+
154+
const { menuItems } = useWorkspaceMenuItems()
155+
138156
expect(menuItems.value.map((item) => item.label)).not.toContain(
139157
'subscription.cancelPlan'
140158
)

src/platform/workspace/composables/useWorkspaceMenuItems.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,9 @@ export function useWorkspaceMenuItems() {
6666
() =>
6767
permissions.value.canManageSubscriptionLifecycle &&
6868
(isActiveSubscription.value ||
69-
billingStatus.value === 'payment_failed') &&
69+
((billingStatus.value === 'payment_failed' ||
70+
billingStatus.value === 'paused') &&
71+
Boolean(subscription.value?.planSlug))) &&
7072
!isSubscriptionCancelled.value &&
7173
!isFreeTier.value
7274
)

0 commit comments

Comments
 (0)