Skip to content

[PM-40440] fix: Include Secrets Manager and storage in plan-change tax preview#7995

Open
amorask-bitwarden wants to merge 4 commits into
mainfrom
billing/PM-40440/teams-2020-upgrade-migration-defect
Open

[PM-40440] fix: Include Secrets Manager and storage in plan-change tax preview#7995
amorask-bitwarden wants to merge 4 commits into
mainfrom
billing/PM-40440/teams-2020-upgrade-migration-defect

Conversation

@amorask-bitwarden

@amorask-bitwarden amorask-bitwarden commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

🎟️ Tracking

PM-40440

📔 Objective

In the organization plan-change tax preview (PreviewOrganizationTaxCommand, has-subscription branch), preview line items are rebuilt from the org's current Stripe subscription and re-priced at the target plan. Password Manager seats correctly look themselves up by the current plan's price IDs, but Secrets Manager seats, service accounts, and storage were looked up by the new plan's price IDs. Those IDs change across a tier boundary (Teams↔Enterprise), across the service-account "2024" version bump, and for storage between Families (personal-storage-gb-*) and org plans (storage-gb-*) — so the lookups missed, and the preview Total silently omitted Secrets Manager and storage cost, under-quoting the upgrade (e.g. ~$1,440+ on a Teams 2020 annual org with 10 SM seats moving to Enterprise annual).

This aligns Secrets Manager and storage with the existing Password-Manager-seat pattern already in this branch: match existing items on the current subscription by the current plan's price IDs, then re-price at the target plan. Null guards are added for plans without Secrets Manager (Families/Free) and without a storage add-on.

Covered by five new tests (SM tier-change regression, Families null-guard, monthly tier change, no-SM regression, Families-with-storage regression) plus an updated existing test whose mock subscription is now keyed by the current plan's IDs. The SM and storage regressions were verified to fail against the pre-fix lookups.

Ship-together dependency: the corresponding client change (change-plan dialog switching its displayed total to this command's Total) must not merge ahead of this PR, or Secrets-Manager orgs would see a real under-quote in the upgrade dialog. Recommended merge order: server first (or coordinated), then client.

clients PR: bitwarden/clients#21902

…x preview

The plan-change tax preview rebuilt line items from the current subscription but looked up Secrets Manager seats, service accounts, and storage by the target plan's price IDs. Those IDs change across a tier boundary (and the service-account 2024 bump), and Families storage uses a distinct personal-storage ID, so the lookups missed and the preview omitted SM and storage cost, under-quoting the upgrade. Match existing items by the current plan's price IDs and re-price at the target plan, following the password-manager-seat pattern already in the command.
@amorask-bitwarden amorask-bitwarden added the ai-review Request a Claude code review label Jul 16, 2026
@github-actions

github-actions Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

🤖 Bitwarden Claude Code Review

Overall Assessment: APPROVE

Reviewed the plan-change tax preview fix in PreviewOrganizationTaxCommand (has-subscription branch), which aligns Secrets Manager seat, service-account, and storage lookups with the existing Password-Manager-seat pattern — matching existing subscription items by the current plan's Stripe price IDs, then re-pricing at the target plan. The change adds correct null guards for plans without Secrets Manager (Families/Free) and without a storage add-on, and the additive IsFromSchedule flag on BillingCustomerDiscount cleanly distinguishes schedule-derived coupons from genuine discounts. Test coverage is thorough: five new regression tests plus an updated existing test whose mock subscription is now keyed by the current plan's IDs, exercising tier-boundary, monthly, no-SM, and Families-storage cases.

Code Review Details

No findings. The lookup fix correctly mirrors the established Password-Manager-seat pattern, the null guards match the plan-definition semantics (Families SecretsManager is null; storage IDs differ between personal-storage-gb-* and storage-gb-*), and the IsFromSchedule flag defaults to false so existing serialization/clients are unaffected. The bundled coupon-flag change is part of the same PM-40440 task and is independently covered by test assertions on both the genuine-discount and schedule-derived paths.

@amorask-bitwarden amorask-bitwarden added the t:bugfix Change Type - Bugfix label Jul 16, 2026
@codecov

codecov Bot commented Jul 16, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 62.15%. Comparing base (5e3ce1c) to head (fe68d7c).
⚠️ Report is 2 commits behind head on main.

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #7995   +/-   ##
=======================================
  Coverage   62.15%   62.15%           
=======================================
  Files        2284     2284           
  Lines       99649    99661   +12     
  Branches     9012     9012           
=======================================
+ Hits        61934    61946   +12     
- Misses      35538    35539    +1     
+ Partials     2177     2176    -1     

☔ View full report in Codecov by Harness.
📢 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.

amorask-bitwarden and others added 2 commits July 16, 2026 12:33
When an org has an active price-migration schedule, its Phase 2 coupon is
surfaced as the org's CustomerDiscount for the subscription page. The web
change-plan dialog couldn't tell that schedule-derived coupon apart from a
genuine customer/SM discount, causing it to mis-render Enterprise upgrades.
Add an additive IsFromSchedule flag, serialized to the API response, so the
client can make that distinction. No server behavior changes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ai-review Request a Claude code review t:bugfix Change Type - Bugfix

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant