Skip to content

fix: preserve payment recovery rollback behavior - #14746

Open
dante01yoon wants to merge 6 commits into
mainfrom
dante01yoon/payment-recovery-rollback-fix
Open

fix: preserve payment recovery rollback behavior#14746
dante01yoon wants to merge 6 commits into
mainfrom
dante01yoon/payment-recovery-rollback-fix

Conversation

@dante01yoon

@dante01yoon dante01yoon commented Aug 5, 2026

Copy link
Copy Markdown
Collaborator

Summary

Follow-up to #14701 that makes v1_payment_recovery a complete runtime rollback boundary: flag OFF restores the pre-feature billing/payment experience, while flag ON keeps payment recovery unchanged.

Root cause and why this is needed

The original gate controlled which recovery UI was rendered, but not every feature-owned side effect. Legacy paused/payment-failed banners remained hidden behind the older billing_control_enabled derivation, and requests/callbacks started while the new flag was ON could finish after rollback. That allowed stale billing commits, dialog updates, queue mode, focus refreshes, or Stripe portal actions to survive a true → false transition. A status-first portal-return refresh could also cancel its slower balance refresh.

How this changes

  • Restores the exact legacy paused/payment-failed banner copy, date handling, and actions under billing_control_enabled when v1_payment_recovery is OFF.
  • Cancels or session-fences recovery-owned status/balance reads, portal requests, loading/error state, dialogs, and captured callbacks on rollback, workspace changes, and unmount.
  • Restores the queue mode that existed before the recovery lock instead of leaving a feature-owned mutation behind.
  • Prevents stale requests from opening Stripe or mutating a newly re-enabled recovery session.
  • Keeps normal flag-ON portal-return status and balance refreshes independent so both complete.
  • Preserves existing unsignaled legacy manageSubscription() behavior when recovery is already OFF.

Changes

  • What: Complete frontend gate isolation and regression coverage for OFF, true → false, true → false → true, workspace transition, and unmount paths.

Review Focus

  • Flag OFF is behaviorally equivalent to the pre-FE-1251 feat: add flagged payment recovery UI #14701 experience, including billing_control_enabled banners and actions.
  • No feature-owned asynchronous result can cross a rollback/session boundary.
  • Flag ON still completes both status and balance refresh after returning from Stripe.

Screenshots

These are the real cloud-app captures from #14701. This follow-up intentionally adds no new visual state: OFF returns to the original experience and ON retains the recovery experience.

AS IS / rollback target — original paused experience

Original paused experience - Subscribe to Run replaces queue controls

TO BE when flag ON — recovery experience remains unchanged

Flag ON - owner queue lock

Flag ON - owner payment recovery dialog

Validation

  • 241 focused unit/component tests across billing API/facade signal propagation, banner derivation/rendering, workspace billing, queue lock, and cloud run wrapper
  • No new Playwright case: these rollback regressions require deterministically pausing and reordering status, balance, and portal promises while toggling the runtime flag (true → false → true), switching workspaces, and unmounting within the same component lifetime. The cloud browser suite cannot reliably control those in-flight boundaries; colocated Vitest tests exercise each exact abort and stale-response boundary instead, while the existing cloud E2E suite remains green.
  • pnpm typecheck
  • touched-file oxlint --type-aware
  • touched-file ESLint and Stylelint
  • touched-file oxfmt --check
  • pnpm knip (pre-push hook)
  • git diff --check
  • Independent blocker review: SHIP

Rollout / rollback

  • Staging PostHog: active at 100% for validation.
  • Production PostHog: active at 0%; the legacy experience remains the production default.
  • Emergency rollback is a single production flag change to OFF; cloud evaluation also fails closed for anonymous users and evaluation errors via Comfy-Org/cloud#6198.

@dante01yoon
dante01yoon requested a review from a team August 5, 2026 02:23
@dosubot dosubot Bot added the size:L This PR changes 100-499 lines, ignoring generated files. label Aug 5, 2026
@dosubot

dosubot Bot commented Aug 5, 2026

Copy link
Copy Markdown

📄 Knowledge review

Dosu skipped reviewing this PR because your organization has used its 200 included credits for the month. Your usage will reset on 2026-09-01. To have Dosu review this PR before then, ask your organization admin to upgrade to a pro account.


Leave Feedback Ask Dosu about ComfyUI_frontend Add Dosu to your team

@github-actions

github-actions Bot commented Aug 5, 2026

Copy link
Copy Markdown

🎭 Playwright: 🕵🏻 0 passed, 0 failed

📊 Browser Reports
  • chromium: ❌ Deployment failed
  • chromium-2x: ❌ Deployment failed
  • chromium-0.5x: ❌ Deployment failed
  • mobile-chrome: ❌ Deployment failed

🎨 Storybook: ✅ Built — View Storybook

Details

⏰ Completed at: 08/07/2026, 10:59:28 PM UTC

Links

📦 Bundle Size

⚠️ Size data collection failed. Check the CI workflow logs.

⚡ Performance

⚠️ Performance tests failed. Check the CI workflow logs.

@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Payment recovery now uses abortable billing requests, session validation, and stale-response protection. Recovery banners and dialogs react to feature-flag and permission changes. Queue controls restore their previous mode after recovery locks end.

Changes

Payment recovery lifecycle

Layer / File(s) Summary
Abort signal contracts and API forwarding
src/composables/billing/types.ts, src/composables/billing/useBillingContext.ts, src/platform/workspace/api/workspaceApi.ts, tests
Billing actions and workspace API methods accept optional abort signals and forward them to Axios.
Stale-safe billing request handling
src/platform/workspace/composables/useWorkspaceBilling.ts, tests
Status, balance, and portal operations ignore aborted, stale, or cross-workspace responses. Shared loading cleanup tracks concurrent operations.
Feature-flagged recovery banners
src/platform/workspace/components/dialogs/settings/*, src/platform/workspace/composables/*, src/locales/en/main.json
Recovery banners use dedicated translations and abort active refreshes or subscription requests when recovery becomes unavailable.
Session-aware dialog and queue recovery
src/components/actionbar/ComfyRunButton/*
Recovery dialogs validate active sessions and ignore obsolete callbacks. Queue controls restore their previous mode after recovery locks clear or unmount.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant CloudRunButtonWrapper
  participant useWorkspaceBilling
  participant workspaceApi
  User->>CloudRunButtonWrapper: Start payment recovery
  CloudRunButtonWrapper->>useWorkspaceBilling: manageSubscription(signal)
  useWorkspaceBilling->>workspaceApi: getPaymentPortalUrl(signal)
  workspaceApi-->>useWorkspaceBilling: Portal response or abort
  useWorkspaceBilling-->>CloudRunButtonWrapper: Open only current portal
  CloudRunButtonWrapper-->>User: Update recovery dialog
Loading

Possibly related PRs

Suggested reviewers: christian-byrne

🚥 Pre-merge checks | ✅ 6
✅ Passed checks (6 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes preserving payment recovery rollback behavior, which is the main change.
Description check ✅ Passed The description includes the required summary, changes, review focus, screenshots, and detailed validation information.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
End-To-End Regression Coverage For Fixes ✅ Passed The title uses “fix” and changes frontend files under src/ without browser_tests/, but the description gives a concrete reason Playwright was not practical and cites focused Vitest coverage.
Adr Compliance For Entity/Litegraph Changes ✅ Passed The changed-file list contains no files under src/lib/litegraph/, src/ecs/, or graph-entity-related files, so this check does not apply.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch dante01yoon/payment-recovery-rollback-fix

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@src/platform/workspace/components/dialogs/settings/BillingStatusBanner.test.ts`:
- Around line 338-352: Add a colocated Vitest test covering the unmount abort
path in the BillingStatusBanner test suite: start an unresolved
manageSubscription request, capture its AbortSignal, unmount the rendered
banner, and assert the signal is aborted. Keep the existing flag-rollback test
unchanged and target the component’s unmount cleanup behavior.

In `@src/platform/workspace/composables/deriveBillingBanner.test.ts`:
- Around line 57-78: Add a test in the deriveBillingBanner suite covering
billingControlEnabled: false with v1PaymentRecovery: true, using the existing
paymentFailed and paused fixtures to verify recovery banners remain visible
under the recovery-only rollout state. Keep the assertions aligned with the
expected banner values and existing test style.

In `@src/platform/workspace/composables/useBillingBanner.ts`:
- Around line 103-126: Update useBillingBanner to import and register
onScopeDispose, aborting paymentRefreshController and clearing it when the
shared composable scope is disposed. Preserve the existing flag-watcher cleanup
and ensure both fetchStatus/fetchBalance requests are canceled on flag changes
and final consumer unmount.

In `@src/platform/workspace/composables/useWorkspaceBilling.ts`:
- Around line 87-99: Update clearLoadingOnAbort and the surrounding billing
operation loading flow to track active operations centrally with loading tokens
or equivalent ownership state. Ensure aborting one request only releases that
request’s token and sets isLoading to false after the final pending operation
completes or aborts, including concurrent subscribe(), topup(), and refresh
operations. Add a regression test covering a pending billing operation alongside
an aborted refresh.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 25f46090-7d6d-4f75-9a2d-7a1cd2b74714

📥 Commits

Reviewing files that changed from the base of the PR and between cc2bff0 and 3c30f8a.

📒 Files selected for processing (16)
  • src/components/actionbar/ComfyRunButton/CloudRunButtonWrapper.test.ts
  • src/components/actionbar/ComfyRunButton/CloudRunButtonWrapper.vue
  • src/components/actionbar/ComfyRunButton/ComfyQueueButton.test.ts
  • src/components/actionbar/ComfyRunButton/ComfyQueueButton.vue
  • src/composables/billing/types.ts
  • src/composables/billing/useBillingContext.ts
  • src/locales/en/main.json
  • src/platform/workspace/api/workspaceApi.test.ts
  • src/platform/workspace/api/workspaceApi.ts
  • src/platform/workspace/components/dialogs/settings/BillingStatusBanner.test.ts
  • src/platform/workspace/components/dialogs/settings/BillingStatusBanner.vue
  • src/platform/workspace/composables/deriveBillingBanner.test.ts
  • src/platform/workspace/composables/useBillingBanner.test.ts
  • src/platform/workspace/composables/useBillingBanner.ts
  • src/platform/workspace/composables/useWorkspaceBilling.test.ts
  • src/platform/workspace/composables/useWorkspaceBilling.ts

Comment thread src/platform/workspace/composables/deriveBillingBanner.test.ts
Comment thread src/platform/workspace/composables/useBillingBanner.ts
Comment thread src/platform/workspace/composables/useWorkspaceBilling.ts Outdated
@codecov

codecov Bot commented Aug 5, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 99.51456% with 1 line in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
...tform/workspace/composables/useWorkspaceBilling.ts 98.59% 1 Missing ⚠️
@@            Coverage Diff             @@
##             main   #14746      +/-   ##
==========================================
+ Coverage   79.51%   79.72%   +0.20%     
==========================================
  Files        1795     1804       +9     
  Lines      116642   117464     +822     
  Branches    34027    33648     -379     
==========================================
+ Hits        92751    93645     +894     
+ Misses      23336    23261      -75     
- Partials      555      558       +3     
Flag Coverage Δ
unit 71.59% <99.51%> (+0.60%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
...actionbar/ComfyRunButton/CloudRunButtonWrapper.vue 100.00% <100.00%> (ø)
...ents/actionbar/ComfyRunButton/ComfyQueueButton.vue 85.52% <100.00%> (+1.06%) ⬆️
src/composables/billing/useBillingContext.ts 80.66% <100.00%> (ø)
src/platform/workspace/api/workspaceApi.ts 48.58% <100.00%> (+0.64%) ⬆️
...omponents/dialogs/settings/BillingStatusBanner.vue 97.80% <100.00%> (+1.08%) ⬆️
...platform/workspace/composables/useBillingBanner.ts 100.00% <100.00%> (ø)
...tform/workspace/composables/useWorkspaceBilling.ts 71.89% <98.59%> (+3.10%) ⬆️

... and 56 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/platform/workspace/composables/useWorkspaceBilling.ts (1)

354-357: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Forward the portal abort signal to the API.

manageSubscription() passes the signal only to local cleanup and stale-response checks. workspaceApi.getPaymentPortalUrl(returnUrl) still makes the HTTP request without cancellation, so aborting recovery or stopping the scope leaves that request running. Update getPaymentPortalUrl() to accept an optional signal, pass it to workspaceApiClient.post(...), and add a regression test verifying the API request receives and honors it.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/platform/workspace/composables/useWorkspaceBilling.ts` around lines 354 -
357, Update manageSubscription to pass its AbortSignal into
workspaceApi.getPaymentPortalUrl, extend getPaymentPortalUrl to accept an
optional signal and forward it to workspaceApiClient.post, and add a regression
test confirming the request receives and honors cancellation.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/platform/workspace/composables/useWorkspaceBilling.ts`:
- Around line 193-197: Update fetchBalance and fetchStatus in
useWorkspaceBilling to capture the active workspace ID when each request starts,
then verify it still matches before publishing either success data or errors,
alongside the existing request/abort guards. Add a regression test that switches
workspaces while a billing read is pending and confirms the old workspace cannot
update the new workspace’s balance, status, or error state.

---

Outside diff comments:
In `@src/platform/workspace/composables/useWorkspaceBilling.ts`:
- Around line 354-357: Update manageSubscription to pass its AbortSignal into
workspaceApi.getPaymentPortalUrl, extend getPaymentPortalUrl to accept an
optional signal and forward it to workspaceApiClient.post, and add a regression
test confirming the request receives and honors cancellation.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: c542749c-ac46-4f90-b5af-e94096b95f52

📥 Commits

Reviewing files that changed from the base of the PR and between 3c30f8a and c6d1f26.

📒 Files selected for processing (6)
  • src/platform/workspace/components/dialogs/settings/BillingStatusBanner.test.ts
  • src/platform/workspace/composables/deriveBillingBanner.test.ts
  • src/platform/workspace/composables/useBillingBanner.test.ts
  • src/platform/workspace/composables/useBillingBanner.ts
  • src/platform/workspace/composables/useWorkspaceBilling.test.ts
  • src/platform/workspace/composables/useWorkspaceBilling.ts

Comment thread src/platform/workspace/composables/useWorkspaceBilling.ts
@dante01yoon

Copy link
Copy Markdown
Collaborator Author

Addressed the outside-diff portal cancellation finding in f052aa7: manageSubscription() now forwards its AbortSignal through getPaymentPortalUrl() to the Axios request. API and composable tests verify the same signal reaches the HTTP request and is aborted during rollback.

@dante01yoon dante01yoon added needs-backport Fix/change that needs to be cherry-picked to the current feature freeze branch core/1.49 Backport PRs for core 1.49 cloud/1.49 Backport PRs for cloud 1.49 labels Aug 5, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/platform/workspace/components/dialogs/settings/BillingStatusBanner.vue (1)

143-155: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Hide and block recovery payment updates for non-managers.

When kind.value is paymentFailed, Line 155 always exposes updatePayment. A member can then click the button after canManage.value becomes false, and Line 196 starts manageSubscription() anyway. The watcher only aborts an existing request.

Gate the recovery warning body and action on canManage.value. Preserve the legacy behavior when v1PaymentRecovery is disabled. Add a regression test for permission loss in the paymentFailed state.

Also applies to: 187-200

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/platform/workspace/components/dialogs/settings/BillingStatusBanner.vue`
around lines 143 - 155, Update the paymentFailed branch in
BillingStatusBanner.vue so the recovery warning body and action are only exposed
when canManage.value is true, while keeping the existing legacy
v1PaymentRecovery-disabled behavior unchanged. Adjust the action returned from
the state logic that feeds the updatePayment flow and ensure
manageSubscription() is not reachable after permission is lost; also add a
regression test covering permission loss while kind.value remains paymentFailed.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@src/platform/workspace/components/dialogs/settings/BillingStatusBanner.vue`:
- Around line 143-155: Update the paymentFailed branch in
BillingStatusBanner.vue so the recovery warning body and action are only exposed
when canManage.value is true, while keeping the existing legacy
v1PaymentRecovery-disabled behavior unchanged. Adjust the action returned from
the state logic that feeds the updatePayment flow and ensure
manageSubscription() is not reachable after permission is lost; also add a
regression test covering permission loss while kind.value remains paymentFailed.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 2fcfe877-ad5d-4878-8ce0-b0d4580717b9

📥 Commits

Reviewing files that changed from the base of the PR and between f052aa7 and e12d1fa.

📒 Files selected for processing (4)
  • src/components/actionbar/ComfyRunButton/CloudRunButtonWrapper.test.ts
  • src/composables/billing/useBillingContext.test.ts
  • src/platform/workspace/components/dialogs/settings/BillingStatusBanner.test.ts
  • src/platform/workspace/components/dialogs/settings/BillingStatusBanner.vue

coderabbitai[bot]
coderabbitai Bot previously approved these changes Aug 5, 2026
@dante01yoon

Copy link
Copy Markdown
Collaborator Author

Addressed the latest permission-loss finding in 73a5b71f0: recovery-mode payment-failed banners now hide for non-managers, and the click handler re-checks permission so a stale button cannot start the portal request before Vue removes it. Legacy behavior with v1_payment_recovery disabled is unchanged. Added a regression test for the permission-loss race.

@dante01yoon
dante01yoon requested a review from huang47 August 7, 2026 16:10
@huang47

huang47 commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Current-head visual evidence for 73a5b71f0d.

Route-mocked cloud app. The red plans-loading toast is capture-fixture noise from the billing-plans mock, not PR behavior.

Flag OFF — legacy rollback target Flag ON — payment recovery
Restores “Payment declined” and renewal-date copy. Retains “Payment failed” recovery copy.
eabe6489e3bc-evidence

@huang47 huang47 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Current-head rollback comparison: #14746 (comment)

Comment thread src/platform/workspace/api/workspaceApi.ts
@dante01yoon
dante01yoon added this pull request to the merge queue Aug 7, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Aug 7, 2026
@dante01yoon
dante01yoon added this pull request to the merge queue Aug 7, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Aug 7, 2026
@dante01yoon
dante01yoon added this pull request to the merge queue Aug 7, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to a conflict with the base branch Aug 7, 2026
@dante01yoon
dante01yoon added this pull request to the merge queue Aug 7, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Aug 7, 2026

@christian-byrne christian-byrne left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are failing tests and merge conflicts.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cloud/1.49 Backport PRs for cloud 1.49 core/1.49 Backport PRs for core 1.49 needs-backport Fix/change that needs to be cherry-picked to the current feature freeze branch size:L This PR changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants