Skip to content

[PM-37691] feat: Surface pending cancellation status on Premium plan view#6957

Merged
SaintPatrck merged 1 commit into
mainfrom
premium-upgrade/pm-37691-pending-cancellation-status
May 21, 2026
Merged

[PM-37691] feat: Surface pending cancellation status on Premium plan view#6957
SaintPatrck merged 1 commit into
mainfrom
premium-upgrade/pm-37691-pending-cancellation-status

Conversation

@SaintPatrck
Copy link
Copy Markdown
Contributor

@SaintPatrck SaintPatrck commented May 20, 2026

🎟️ Tracking

📔 Objective

After canceling a subscription via the Stripe customer portal, the badge on the Premium plan screen stayed on "Active" until the user manually refreshed. The /account/billing/vnext/subscription payload at that point reports status: active with cancelAt: <future-instant>, and the app never sees a push notification for the transition. Two gaps caused this:

  • The portal launch went through intentManager.launchUri(...) (plain Chrome Custom Tab) with no return signal.
  • The status mapping only looked at the raw status field, so active + cancelAt collapsed to PremiumSubscriptionStatus.ACTIVE.

This PR routes the portal launch through startAuthTab with a dedicated stripePortalLauncher, so closing the tab fires MainAction.StripePortalResultSpecialCircumstance.StripePortal → a fresh /subscription fetch. A new PremiumSubscriptionStatus.PENDING_CANCELLATION is derived when status ∈ {active, trialing} and cancelAt is non-null, rendering a warning badge plus "Your subscription is scheduled to cancel on {date}. You can reinstate it anytime before then."

📸 Screenshots

Figma Actual

@SaintPatrck SaintPatrck added the ai-review-vnext Request a Claude code review using the vNext workflow label May 20, 2026
@github-actions github-actions Bot added app:password-manager Bitwarden Password Manager app context app:authenticator Bitwarden Authenticator app context t:feature Change Type - Feature Development labels May 20, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 20, 2026

Bitwarden Claude Code Review

Overall Assessment: APPROVE

Reviewed the introduction of PremiumSubscriptionStatus.PENDING_CANCELLATION and the routing of the Stripe customer portal launch through startAuthTab so the return-to-app signal can drive a subscription refetch. Changes include the JSON-to-domain mapping (deriving pending cancellation when status ∈ {active, trialing} and cancelAt is non-null), the new SpecialCircumstance.StripePortal plumbing across MainViewModel/RootNavViewModel/PlanViewModel, the Plan screen badge/description/cancel-button updates, and accompanying string resources. No security, correctness, or breaking-change concerns were identified; behavior is consistent with the existing premium checkout pattern.

Code Review Details

No findings — implementation, state handling, and test coverage all look in order.

Observations recorded for context (not findings):

  • All five PremiumSubscriptionStatus when sites were updated to handle the new enum value: isInTroubleState() (false), canBeCanceled() (false), isPremiumViewEligible() (true), badgeColors() (warning), and labelRes(). Exhaustive enum checks would have failed to compile otherwise.
  • Mapping precedence in BitwardenSubscriptionResponseJsonExtensions.toPremiumSubscriptionStatus() correctly preserves CANCELED/INCOMPLETE_EXPIRED over cancelAt (verified by the new maps CANCELED with cancelAt still to CANCELED test).
  • RootNavViewModel groups SpecialCircumstance.StripePortal with the other inert vault-unlocked circumstances and whitelists it for shouldShowVaultMigration, matching the PremiumCheckout treatment.
  • The new subscription_pending_cancellation_description string follows the description-suffix naming convention used by neighbors (subscription_canceled_description, subscription_update_payment_description) and applies the same nested <annotation emphasis="bold"><annotation arg="0"> markup the other descriptions use to bold the date.
  • Test coverage spans the JSON mapping (4 new cases), the MainViewModel action, the Plan screen badge rendering and bold-span assertion, and three PlanViewModel scenarios (loading + refetch, post-portal PENDING_CANCELLATION view state, and free-account → premium-view routing).

@codecov
Copy link
Copy Markdown

codecov Bot commented May 20, 2026

Codecov Report

❌ Patch coverage is 95.65217% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 86.46%. Comparing base (31011b5) to head (1c35c7c).

Files with missing lines Patch % Lines
...en/ui/platform/feature/rootnav/RootNavViewModel.kt 0.00% 0 Missing and 2 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #6957      +/-   ##
==========================================
+ Coverage   85.49%   86.46%   +0.96%     
==========================================
  Files        1023      870     -153     
  Lines       66231    63487    -2744     
  Branches     9308     9211      -97     
==========================================
- Hits        56623    54891    -1732     
+ Misses       6422     5432     -990     
+ Partials     3186     3164      -22     
Flag Coverage Δ
app-data 17.04% <22.50%> (+0.18%) ⬆️
app-ui-auth-tools 19.14% <10.00%> (-0.23%) ⬇️
app-ui-platform 16.44% <67.39%> (-0.49%) ⬇️
app-ui-vault 27.89% <0.00%> (-0.59%) ⬇️
authenticator 6.23% <0.00%> (-0.01%) ⬇️
lib-core-network-bridge 4.06% <0.00%> (-0.01%) ⬇️
lib-data-ui 1.15% <0.00%> (-0.01%) ⬇️

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

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 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.

…view

Closing the Stripe customer portal previously left the app unaware the
user had returned, and the subscription payload's `active + cancelAt`
combination collapsed to ACTIVE — so a scheduled cancellation was never
surfaced until period-end. Route the portal launch through AuthTab via a
dedicated launcher to detect return, map `cancelAt != null` on active or
trialing subscriptions to a new PENDING_CANCELLATION status, and refetch
the subscription on portal close so the badge reconciles immediately.
@SaintPatrck SaintPatrck force-pushed the premium-upgrade/pm-37691-pending-cancellation-status branch from 9fa9d28 to 1c35c7c Compare May 20, 2026 21:11
@SaintPatrck SaintPatrck marked this pull request as ready for review May 20, 2026 21:44
@SaintPatrck SaintPatrck requested review from a team and david-livefront as code owners May 20, 2026 21:44
}

private val authTabLaunchers by lazy {
AuthTabLaunchers(
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I assume this is to ensure that we only build them once?

@SaintPatrck SaintPatrck added this pull request to the merge queue May 21, 2026
Merged via the queue into main with commit 5096045 May 21, 2026
32 checks passed
@SaintPatrck SaintPatrck deleted the premium-upgrade/pm-37691-pending-cancellation-status branch May 21, 2026 12:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ai-review-vnext Request a Claude code review using the vNext workflow app:authenticator Bitwarden Authenticator app context app:password-manager Bitwarden Password Manager app context t:feature Change Type - Feature Development

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants