Skip to content

[PM-33901] Implement schedule-aware tax handling#7319

Merged
cturnbull-bitwarden merged 1 commit intomainfrom
billing/PM-33901/schedule-aware-tax
Mar 27, 2026
Merged

[PM-33901] Implement schedule-aware tax handling#7319
cturnbull-bitwarden merged 1 commit intomainfrom
billing/PM-33901/schedule-aware-tax

Conversation

@cturnbull-bitwarden
Copy link
Copy Markdown
Contributor

🎟️ Tracking

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

📔 Objective

Make tax-related subscription updates schedule-aware during the ~15-day window between invoice.upcoming and renewal. When a subscription schedule is present and the PM32645_DeferPriceMigrationToRenewal feature flag is enabled, updates default_settings.automatic_tax on the schedule instead of the subscription directly.

Changes

  • UpcomingInvoiceHandler: AlignOrganizationTaxConcernsAsync and AlignPremiumUsersTaxConcernsAsync now delegate to a shared EnableAutomaticTaxAsync helper that checks for an active schedule
  • UpdateBillingAddressCommand: EnableAutomaticTaxAsync updated with the same schedule-aware pattern; IFeatureService added to constructor
  • Tests covering schedule-present and no-schedule paths for both org and premium tax alignment, and billing address tax enablement

@cturnbull-bitwarden cturnbull-bitwarden added the ai-review-vnext Request a Claude code review using the vNext workflow label Mar 26, 2026
Comment on lines +569 to +602
private async Task EnableAutomaticTaxAsync(Subscription subscription)
{
if (featureService.IsEnabled(FeatureFlagKeys.PM32645_DeferPriceMigrationToRenewal))
{
var schedules = await stripeAdapter.ListSubscriptionSchedulesAsync(
new SubscriptionScheduleListOptions { Customer = subscription.CustomerId });

var activeSchedule = schedules.Data.FirstOrDefault(s =>
s.SubscriptionId == subscription.Id && s.Status == SubscriptionScheduleStatus.Active);

if (activeSchedule != null)
{
await stripeAdapter.UpdateSubscriptionScheduleAsync(activeSchedule.Id,
new SubscriptionScheduleUpdateOptions
{
DefaultSettings = new SubscriptionScheduleDefaultSettingsOptions
{
AutomaticTax = new SubscriptionScheduleDefaultSettingsAutomaticTaxOptions
{
Enabled = true
}
}
});
return;
}
}

await stripeAdapter.UpdateSubscriptionAsync(subscription.Id,
new SubscriptionUpdateOptions
{
AutomaticTax = new SubscriptionAutomaticTaxOptions { Enabled = true }
});
}

Copy link
Copy Markdown
Contributor Author

@cturnbull-bitwarden cturnbull-bitwarden Mar 26, 2026

Choose a reason for hiding this comment

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

This shared helper is duplicated in UpdateBillingAddressCommand.EnableAutomaticTaxAsync. I considered extracting it to PriceIncreaseScheduler or a standalone service, but:

  1. Tax enablement isn't conceptually "price increase scheduling" as it doesn't fit IPriceIncreaseScheduler's responsibility
  2. A new ISubscriptionScheduleService for a single method felt like over-abstraction
  3. Two call sites with ~10 duplicated lines felt acceptable given the above

If a third schedule-aware call site with this same shape appears, maybe this is worth revisiting. Feel free to disagree!

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.

Yeah I agree, I think it's good for now but worth revisiting if we see it pop up again.

{
try
{
await stripeAdapter.UpdateSubscriptionAsync(subscription.Id,
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.

Intentionally not schedule-aware. Provider subscriptions manage Teams/Enterprise orgs, which are unaffected by this price migration. PriceIncreaseScheduler.Schedule() explicitly skips providers (returns null), so a provider subscription will never have an active schedule from this epic.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 26, 2026

Logo
Checkmarx One – Scan Summary & Detailse4fea07a-ef70-45be-8779-675d7869dd91

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

@cturnbull-bitwarden cturnbull-bitwarden marked this pull request as ready for review March 26, 2026 20:23
@cturnbull-bitwarden cturnbull-bitwarden requested a review from a team as a code owner March 26, 2026 20:23
@cturnbull-bitwarden cturnbull-bitwarden removed the ai-review-vnext Request a Claude code review using the vNext workflow label Mar 27, 2026
Make tax-related subscription updates schedule-aware during the ~15-day
window between invoice.upcoming and renewal. When a subscription schedule
is present and the feature flag is enabled, update default_settings.automatic_tax
on the schedule instead of the subscription directly.

Modified paths:
- UpcomingInvoiceHandler: AlignOrganizationTaxConcernsAsync,
  AlignPremiumUsersTaxConcernsAsync, new shared EnableAutomaticTaxAsync helper
- UpdateBillingAddressCommand: EnableAutomaticTaxAsync, added IFeatureService
@cturnbull-bitwarden cturnbull-bitwarden force-pushed the billing/PM-33901/schedule-aware-tax branch from bd0ada9 to 845658b Compare March 27, 2026 14:45
@sonarqubecloud
Copy link
Copy Markdown

Copy link
Copy Markdown
Collaborator

@sbrown-livefront sbrown-livefront left a comment

Choose a reason for hiding this comment

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

Thanks for calling those out. This looks good ✅

Comment on lines +569 to +602
private async Task EnableAutomaticTaxAsync(Subscription subscription)
{
if (featureService.IsEnabled(FeatureFlagKeys.PM32645_DeferPriceMigrationToRenewal))
{
var schedules = await stripeAdapter.ListSubscriptionSchedulesAsync(
new SubscriptionScheduleListOptions { Customer = subscription.CustomerId });

var activeSchedule = schedules.Data.FirstOrDefault(s =>
s.SubscriptionId == subscription.Id && s.Status == SubscriptionScheduleStatus.Active);

if (activeSchedule != null)
{
await stripeAdapter.UpdateSubscriptionScheduleAsync(activeSchedule.Id,
new SubscriptionScheduleUpdateOptions
{
DefaultSettings = new SubscriptionScheduleDefaultSettingsOptions
{
AutomaticTax = new SubscriptionScheduleDefaultSettingsAutomaticTaxOptions
{
Enabled = true
}
}
});
return;
}
}

await stripeAdapter.UpdateSubscriptionAsync(subscription.Id,
new SubscriptionUpdateOptions
{
AutomaticTax = new SubscriptionAutomaticTaxOptions { Enabled = true }
});
}

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.

Yeah I agree, I think it's good for now but worth revisiting if we see it pop up again.

@codecov
Copy link
Copy Markdown

codecov bot commented Mar 27, 2026

Codecov Report

❌ Patch coverage is 96.15385% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 57.93%. Comparing base (5833e34) to head (845658b).
⚠️ Report is 8 commits behind head on main.

Files with missing lines Patch % Lines
...Services/Implementations/UpcomingInvoiceHandler.cs 96.66% 0 Missing and 1 partial ⚠️
...ng/Payment/Commands/UpdateBillingAddressCommand.cs 95.45% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #7319      +/-   ##
==========================================
+ Coverage   57.91%   57.93%   +0.01%     
==========================================
  Files        2045     2045              
  Lines       90235    90278      +43     
  Branches     8024     8030       +6     
==========================================
+ Hits        52263    52304      +41     
  Misses      36105    36105              
- Partials     1867     1869       +2     

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

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.

Disregard previous comment - I read something incorrectly.

@cturnbull-bitwarden cturnbull-bitwarden merged commit c604379 into main Mar 27, 2026
55 of 56 checks passed
@cturnbull-bitwarden cturnbull-bitwarden deleted the billing/PM-33901/schedule-aware-tax branch March 27, 2026 18:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants