Skip to content

[PM-37084] Business Aware Schedule Recovery and Cancellation#7686

Open
sbrown-livefront wants to merge 20 commits into
mainfrom
billing/pm-37084/business-aware-schedule-recovery
Open

[PM-37084] Business Aware Schedule Recovery and Cancellation#7686
sbrown-livefront wants to merge 20 commits into
mainfrom
billing/pm-37084/business-aware-schedule-recovery

Conversation

@sbrown-livefront
Copy link
Copy Markdown
Collaborator

@sbrown-livefront sbrown-livefront commented May 20, 2026

🎟️ Tracking

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

📔 Objective

Introduces a new unified scheduling path for price increases on subscriptions, enabling both personal and business (organization) subscription reinstatements to be handled by a single method. The main change is the addition of the ScheduleForSubscription method in PriceIncreaseScheduler, which intelligently routes scheduling based on the subscription owner type. This supports new business plan price migration scenarios and improves maintainability by consolidating logic. The changes also include expanded test coverage to ensure correct routing and handling for various subscription and organization scenarios.

Core functionality changes:

  • Added ScheduleForSubscription to IPriceIncreaseScheduler and implemented it in PriceIncreaseScheduler, which dispatches scheduling to the correct path (personal, business, or provider) based on the subscription owner. This method includes error handling and logging for unsupported or invalid cases.
  • Implemented DispatchOrganizationScheduleAsync to handle business plan migrations, including checks for organization existence, plan type, cohort assignment, cohort status, and migration path validity.
  • Updated dependencies and constructor for PriceIncreaseScheduler to include organization and cohort repositories, enabling the new scheduling logic.

Integration and behavior updates:

  • Replaced calls to SchedulePersonalPriceIncrease with ScheduleForSubscription in business logic (SubscriptionUpdatedHandler, ReinstateSubscriptionCommand) and associated tests, ensuring all scheduling now uses the unified path.

Metadata and Stripe API Improvements

  • Improved Stripe API usage by expanding the list of fields included when retrieving subscriptions to ensure all necessary metadata is available, especially for discounts and customer details.
  • Added a new metadata key, CancellingUserId, to MetadataKeys and updated all relevant code to use this constant, ensuring consistency and clarity.

Test Clock and Testing Improvements

  • Enhanced the handling of Stripe test clocks by adding logic to wait for the test clock to advance before scheduling price migrations, improving test reliability for time-dependent billing scenarios.
  • Updated and expanded unit tests to cover the new scheduling flow and ensure correct invocation of the new methods.

📸 Screenshots

Screen.Recording.2026-05-21.at.2.42.23.PM.mov

@sbrown-livefront sbrown-livefront changed the title Billing/pm 37084/business aware schedule recovery [PM-37084] Business Aware Schedule Recovery May 20, 2026
@codecov
Copy link
Copy Markdown

codecov Bot commented May 20, 2026

Codecov Report

❌ Patch coverage is 54.83871% with 42 lines in your changes missing coverage. Please review.
✅ Project coverage is 60.43%. Comparing base (dcf4c48) to head (97a2fb4).

Files with missing lines Patch % Lines
src/Core/Billing/Pricing/PriceIncreaseScheduler.cs 56.25% 23 Missing and 5 partials ⚠️
...Services/Implementations/UpcomingInvoiceHandler.cs 0.00% 13 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #7686      +/-   ##
==========================================
- Coverage   60.43%   60.43%   -0.01%     
==========================================
  Files        2140     2140              
  Lines       94628    94712      +84     
  Branches     8445     8456      +11     
==========================================
+ Hits        57193    57239      +46     
- Misses      35430    35462      +32     
- Partials     2005     2011       +6     

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

@sbrown-livefront sbrown-livefront self-assigned this May 20, 2026
@sbrown-livefront sbrown-livefront added the ai-review Request a Claude code review label 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 unified ScheduleForSubscription dispatcher that routes price-increase scheduling to the correct path (personal, business, or provider) based on subscription owner. Verified the new DispatchOrganizationScheduleAsync flow with its plan-type, cohort, and migration-path validation, the CancellingUserId metadata constant extraction and clearing on reinstatement, the customer/customer.discount expansions added at all dispatching call sites, and the new test coverage. Previously flagged findings around missing Stripe expansions and a stale test name were addressed in commits b1bb09916, 18ecb4d2b, and the test rename. No new findings warranting inline comments.

Code Review Details

No additional findings beyond the three threads already resolved on this PR.

Comment thread src/Billing/Services/Implementations/SubscriptionUpdatedHandler.cs
Comment thread test/Billing.Test/Services/SubscriptionUpdatedHandlerTests.cs
@sonarqubecloud
Copy link
Copy Markdown

Comment thread src/Billing/Services/Implementations/UpcomingInvoiceHandler.cs
@sbrown-livefront sbrown-livefront marked this pull request as ready for review May 21, 2026 18:53
@sbrown-livefront sbrown-livefront requested a review from a team as a code owner May 21, 2026 18:53
@sbrown-livefront sbrown-livefront changed the title [PM-37084] Business Aware Schedule Recovery [PM-37084] Business Aware Schedule Recovery and Cancellation May 21, 2026
Copy link
Copy Markdown
Contributor

@amorask-bitwarden amorask-bitwarden left a comment

Choose a reason for hiding this comment

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

Looks great - just left a question on a refactoring opp we have here. Let me know if you want to discuss further.

var subscription = await stripeAdapter.GetSubscriptionAsync(
subscriber.GatewaySubscriptionId,
new SubscriptionGetOptions { Expand = ["discounts"] });
new SubscriptionGetOptions { Expand = ["discounts", "customer", "customer.discount"] });
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.

⛏️ Expanding customer.discount necessarily expands customer.

public async Task HandleAsync(Event parsedEvent)
{
var subscription = await _stripeEventService.GetSubscription(parsedEvent, true, ["customer", "discounts", "latest_invoice", "test_clock"]);
var subscription = await _stripeEventService.GetSubscription(parsedEvent, true, ["customer", "customer.discount", "discounts", "latest_invoice", "test_clock"]);
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.

⛏ Same thing here.

/// <param name="subscription"> The subscription to schedule a price increase for. </param>
/// <param name="organizationId"> The ID of the organization associated with the subscription. </param>
/// <returns> True if the schedule was dispatched successfully, false otherwise. </returns>
private async Task<bool> DispatchOrganizationScheduleAsync(Subscription subscription, Guid organizationId)
Copy link
Copy Markdown
Contributor

@amorask-bitwarden amorask-bitwarden May 21, 2026

Choose a reason for hiding this comment

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

I think we have a refactoring opportunity here that I overlooked in the original implementation. This is almost the same exact logic that's in the UpcomingInvoiceHandler.ScheduleBusinessPlanPriceMigration. It seems like we're going to need the same logic in multiple areas, but I'm not positive they're supposed to behave exactly the same way between the original scheduling and a reinstatement. For example, some of the validation checks such as assignment.ScheduledAt is not null might not translate. Wanna give this a whirl or discuss further?

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants