Skip to content

Commit 75c7aaf

Browse files
authored
fix(billing): resume a pending top-up as a top-up, not a subscription (#14715)
## Description Billing status can return an in-flight billing operation (`pending_billing_op_id` + `action_url`) so a client that lost its local reference can resume the payment — a reload, a cleared browser, a different device. The recovery path in `useWorkspaceBilling` assumed that operation was always a subscription and passed a hardcoded `'subscription'`. That assumption is a latent bug regardless of what the server sends today: any non-subscription operation surfaced there takes the wrong branch throughout. - `isSettingUp` turns true, so `SubscriptionPanelContentWorkspace` replaces the whole panel with a "setting up your subscription" spinner — hiding the user's actual plan for as long as the operation is pending - `topupActionOperation` requires `type === 'topup'`, so the top-up dialog's own recovery prompt never matches - `handleSuccess` runs `reconcileSubscriptionSuccess()` instead of `fetchStatus()` + `fetchBalance()`, so credits just purchased never appear - the toast and telemetry describe a subscription checkout Uses the operation type when the API reports one, falling back to `'subscription'` when it does not — so this is inert against a server that never sends the field, and correct the moment one does. **Status:** the server side does not populate a top-up here yet. This is the client half, landing first because it is safe in isolation; without it, a server that starts reporting top-ups would drive the wrong UI. Treat it as removing a hardcoded assumption, not as shipping a user-visible fix on its own. ## Checklist - [x] Test added — `recovers a pending top-up as a top-up, not a subscription`, confirmed to fail without the fix - [x] `pnpm test:unit` (72 passed in the touched suite), `pnpm typecheck`, `pnpm lint`, `pnpm format` ## Follow-up (not in this PR) `billingOperationStore` gives a top-up 120s to discover an authentication URL (`TIMEOUT_MS`) while a subscription gets 5min (`SUBSCRIPTION_ACTION_DISCOVERY_TIMEOUT_MS`). Only bites when the payment page is slow to appear, so it is kept separate.
1 parent b1861ab commit 75c7aaf

3 files changed

Lines changed: 23 additions & 1 deletion

File tree

src/platform/workspace/api/workspaceApi.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,7 @@ export interface BillingStatusResponse {
294294
change_at?: string
295295
billing_status?: BillingStatus
296296
pending_billing_op_id?: string
297+
pending_billing_op_type?: 'subscription' | 'topup'
297298
action_url?: string
298299
has_funds: boolean
299300
cancel_at?: string

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

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,27 @@ describe('useWorkspaceBilling', () => {
291291
)
292292
})
293293

294+
it('recovers a pending top-up as a top-up, not a subscription', async () => {
295+
const actionUrl = 'https://invoice.stripe.com/sensitive-token'
296+
mockWorkspaceApi.getBillingStatus.mockResolvedValue({
297+
...activeStatus,
298+
billing_status: 'pending_payment',
299+
pending_billing_op_id: 'op-topup',
300+
pending_billing_op_type: 'topup',
301+
action_url: actionUrl
302+
} satisfies BillingStatusResponse)
303+
304+
const billing = setupBilling()
305+
await billing.fetchStatus()
306+
307+
expect(mockStartOperation).toHaveBeenCalledWith(
308+
'op-topup',
309+
'topup',
310+
undefined,
311+
actionUrl
312+
)
313+
})
314+
294315
it('does not restart an operation recovered by an earlier status read', async () => {
295316
mockWorkspaceApi.getBillingStatus.mockResolvedValue({
296317
...activeStatus,

src/platform/workspace/composables/useWorkspaceBilling.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,7 @@ export function useWorkspaceBilling(): BillingState & BillingActions {
210210
) {
211211
void billingOperationStore.startOperation(
212212
status.pending_billing_op_id,
213-
'subscription',
213+
status.pending_billing_op_type === 'topup' ? 'topup' : 'subscription',
214214
undefined,
215215
status.action_url
216216
)

0 commit comments

Comments
 (0)