Skip to content

[PM-35455] feat: Wire premium subscription data into Plan screen#6819

Merged
SaintPatrck merged 9 commits intomainfrom
premium-upgrade/PM-35455-plan-premium-wiring
Apr 28, 2026
Merged

[PM-35455] feat: Wire premium subscription data into Plan screen#6819
SaintPatrck merged 9 commits intomainfrom
premium-upgrade/PM-35455-plan-premium-wiring

Conversation

@SaintPatrck
Copy link
Copy Markdown
Contributor

@SaintPatrck SaintPatrck commented Apr 21, 2026

🎟️ Tracking

https://bitwarden.atlassian.net/browse/PM-35455

📔 Objective

Wires premium subscription data into the Plan screen so premium users see their actual billed rate, storage cost, discount, estimated tax, and next-charge summary — plus a status badge reflecting their current subscription state.

Stacks on PM-35454. Line items default to a "--" placeholder while the subscription fetch is loading and for any field that resolves to null or 0.00 (e.g. no additional storage, no discount, no tax). Currency is hardcoded to USD for now.

The existing Manage plan / Cancel Premium flows (via the Stripe customer portal) are surfaced here; the cancel button is hidden when status is already CANCELED.

📸 Screenshots

@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 Apr 21, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 21, 2026

Logo
Checkmarx One – Scan Summary & Detailsb55fb6bf-24b3-4a67-8e8b-fcfb0ade9321

Great job! No new security vulnerabilities introduced in this pull request

@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 21, 2026

Codecov Report

❌ Patch coverage is 94.32836% with 19 lines in your changes missing coverage. Please review.
✅ Project coverage is 85.73%. Comparing base (be1dabb) to head (4d73154).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
.../ui/platform/feature/premium/plan/PlanViewModel.kt 90.96% 9 Missing and 5 partials ⚠️
...den/ui/platform/feature/premium/plan/PlanScreen.kt 96.68% 1 Missing and 4 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #6819      +/-   ##
==========================================
- Coverage   85.73%   85.73%   -0.01%     
==========================================
  Files         832      836       +4     
  Lines       58955    59300     +345     
  Branches     8617     8654      +37     
==========================================
+ Hits        50543    50838     +295     
- Misses       5437     5480      +43     
- Partials     2975     2982       +7     
Flag Coverage Δ
app-data 17.39% <0.00%> (-0.10%) ⬇️
app-ui-auth-tools 20.17% <0.00%> (-0.11%) ⬇️
app-ui-platform 15.89% <94.32%> (+0.42%) ⬆️
app-ui-vault 25.73% <0.00%> (-0.14%) ⬇️
authenticator 6.63% <0.00%> (-0.05%) ⬇️
lib-core-network-bridge 4.25% <0.00%> (-0.03%) ⬇️
lib-data-ui 1.02% <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.

@SaintPatrck SaintPatrck force-pushed the premium-upgrade/PM-35455-plan-premium-wiring branch from c3887d5 to 915e983 Compare April 21, 2026 21:48
@SaintPatrck SaintPatrck added ai-review-vnext Request a Claude code review using the vNext workflow labels Apr 21, 2026
@github-actions github-actions Bot removed the ai-review-vnext Request a Claude code review using the vNext workflow label Apr 21, 2026
@SaintPatrck SaintPatrck force-pushed the premium-upgrade/PM-35454-subscription-backend branch from cbf4111 to 0cb83ba Compare April 23, 2026 20:44
@SaintPatrck SaintPatrck force-pushed the premium-upgrade/PM-35455-plan-premium-wiring branch 2 times, most recently from 5230d82 to 06622de Compare April 23, 2026 21:20
Comment thread ui/src/main/res/values/strings.xml Outdated
<string name="subscription_canceled_description">Your subscription was canceled on %1$s. Resubscribe to continue using premium features.</string>
<string name="subscription_overdue_description">We couldn’t process your payment. Update your payment before your subscription ends on %1$s.</string>
<string name="subscription_past_due_description">You have a grace period of %1$d days from your subscription expiration date. Please resolve the past due amount by %2$s.</string>
<string name="subscription_paused_description">Your subscription is paused. Resume it to continue using premium features.</string>
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@RishikaSG-28 is this ok for the "Paused" status message?

Copy link
Copy Markdown

@RishikaSG-28 RishikaSG-28 Apr 23, 2026

Choose a reason for hiding this comment

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

@SaintPatrck For the paused state, here's the copy:
Badge: "Paused" (orange/warning)
Message: "Your subscription is paused. Resume to continue using premium features."

@SaintPatrck SaintPatrck force-pushed the premium-upgrade/PM-35454-subscription-backend branch 2 times, most recently from 049e150 to e50bdb6 Compare April 27, 2026 18:50
@SaintPatrck SaintPatrck force-pushed the premium-upgrade/PM-35455-plan-premium-wiring branch from f383be5 to 5ebe4ce Compare April 27, 2026 19:11
@SaintPatrck SaintPatrck marked this pull request as ready for review April 27, 2026 20:42
@SaintPatrck SaintPatrck requested review from a team and david-livefront as code owners April 27, 2026 20:42
Base automatically changed from premium-upgrade/PM-35454-subscription-backend to main April 27, 2026 20:56
@SaintPatrck SaintPatrck added the ai-review-vnext Request a Claude code review using the vNext workflow label Apr 27, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 28, 2026

🤖 Bitwarden Claude Code Review

Overall Assessment: APPROVE

This PR wires premium subscription data into the Plan screen, replacing the previous placeholder. The wiring follows the established UDF/BaseViewModel patterns: Premium view state is now a data class, dialog states are sealed, and BillingRepository.getSubscription() / getPortalUrl() are surfaced through dedicated Internal actions. Error, loading, cancel-confirmation, and portal-error flows are all covered. Test coverage is broad — PlanScreenTest and PlanViewModelTest exercise each subscription status, both cadences, null/zero line items, the retry path, and the manage/cancel portal flows.

Code Review Details
  • 🎨 : Discount color check is coupled to the duplicated "--" literal; consider an explicit hasDiscount flag on the view state
    • app/src/main/kotlin/com/x8bit/bitwarden/ui/platform/feature/premium/plan/PlanScreen.kt:478

@SaintPatrck SaintPatrck force-pushed the premium-upgrade/PM-35455-plan-premium-wiring branch from 2c65004 to f1c6fa8 Compare April 28, 2026 16:49
PremiumSubscriptionStatus.ACTIVE -> BitwardenString.subscription_status_active
PremiumSubscriptionStatus.CANCELED -> BitwardenString.subscription_status_canceled
PremiumSubscriptionStatus.OVERDUE_PAYMENT ->
BitwardenString.subscription_status_overdue_payment
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.

Wanna wrap this in curly braces

PremiumSubscriptionStatus.OVERDUE_PAYMENT,
PremiumSubscriptionStatus.PAST_DUE,
PremiumSubscriptionStatus.PAUSED,
-> BitwardenTheme.colorScheme.statusBadge.warning
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.

Ditto

.cardStyle(
cardStyle = CardStyle.Full,
// Override bottom padding; the final row owns its own spacing.
paddingBottom = 0.dp,
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.

👍

paddingBottom = 0.dp,
),
) {
Column(
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.

Do we need this outer column?

Row(
modifier = modifier
.fillMaxWidth()
.standardHorizontalMargin()
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.

Can we move the width and standard margin up to be passed in.

}

private fun Instant.toLocalizedDate(): String =
dateFormatter.format(this.atZone(clock.zone))
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.

Can we use the helper functions for this?

this.toFormattedDateStyle(
    dateStyle = FormatStyle.LONG,
    locale = Locale.US,
    clock = clock,
)

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.

Isn't this already here?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yup. Had to rebase for it to stop showing up.

Renders a premium user's actual billed rate, storage cost, discount, estimated tax, and next-charge summary in the Plan screen, along with the subscription status badge and the existing Manage plan / Cancel Premium actions. Line items default to a "--" placeholder while the subscription fetch is loading and for any field that resolves to null or 0.00.
Renders the paused status as a warning badge with a generic resume message
and swaps the Plan screen's currency formatting helpers off Double now that
SubscriptionInfo is BigDecimal-backed.
Backfill the Compose UI tests for the premium subscription card, status
badge variants, line items, action buttons, and portal/subscription dialog
states that Codecov flagged as uncovered.
Adds tests for non-ACTIVE subscription statuses (overdue, past due
with and without grace period, paused), monthly cadence formatting,
and the placeholder fallbacks for null or zero line items so a
regression in any of those user-facing strings can no longer ship
silently.
The previous isDigitsOnly() check on the formatted discount string
was always false, so the success color never rendered. Switch to a
direct comparison against the placeholder string instead.
Wrap multi-line arrow bodies in braces, drop the redundant header
Column wrapper, hoist row sizing modifiers to the call site, and
swap the ad-hoc DateTimeFormatter for the toFormattedDateStyle helper.
@SaintPatrck SaintPatrck force-pushed the premium-upgrade/PM-35455-plan-premium-wiring branch from a12ce02 to 4d73154 Compare April 28, 2026 19:49
@SaintPatrck SaintPatrck enabled auto-merge April 28, 2026 19:51
SubscriptionLineItem(
label = stringResource(id = BitwardenString.discount),
value = viewState.discountAmountText,
valueColor = if (viewState.discountAmountText == "--") {
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.

🎨 SUGGESTED: Discount color check is coupled to a duplicated "--" literal.

Details and fix

PLACEHOLDER_TEXT = "--" is private to PlanViewModel.kt, but here the screen hard-codes the same literal to decide the color. If PLACEHOLDER_TEXT is ever renamed, localized, or swapped (e.g. to an em-dash), the discount row will silently render the placeholder in success-green or color real values in primary text without any compile-time signal.

Consider modeling the "no discount" condition explicitly on the view state instead of inferring it from the formatted string — for example a hasDiscount: Boolean on PlanState.ViewState.Premium, set in toPremiumViewState() alongside discountAmountText. The screen would then use viewState.hasDiscount for the color choice and presentation/data concerns stay separated.

@SaintPatrck SaintPatrck added this pull request to the merge queue Apr 28, 2026
Merged via the queue into main with commit 32b704c Apr 28, 2026
38 checks passed
@SaintPatrck SaintPatrck deleted the premium-upgrade/PM-35455-plan-premium-wiring branch April 28, 2026 20:19
<string name="premium_next_charge_summary">Your next charge is for %1$s USD due on %2$s.</string>
<string name="subscription_canceled_description">Your subscription was canceled on %1$s. Resubscribe to continue using premium features.</string>
<string name="subscription_overdue_description">We couldn’t process your payment. Update your payment before your subscription ends on %1$s.</string>
<string name="subscription_past_due_description">You have a grace period of %1$d days from your subscription expiration date. Please resolve the past due amount by %2$s.</string>
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.

It looks like you forgot to add this string as a plural string.

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.

Thanks for catching that @mKoonrad

I've opened a new PR here to address it.

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.

4 participants