Skip to content

Commit c667b3e

Browse files
cturnbull-bitwardenike-kottlowski
authored andcommitted
[PM-31909] Remove m3 flagged logic (#7352)
* Remove pm-26462-milestone-3 flag from PricingClient Simplify FamiliesAnnually2025 lookup to always return "families-2025". Remove PreProcessFamiliesPreMigrationPlan deployment safeguard method and its call sites. Remove unused IFeatureService constructor dependency. * Remove pm-26462-milestone-3 flag from UpcomingInvoiceHandler.HandleForOrganizationAsync Remove milestone3 variable and parameter from AlignOrganizationSubscriptionConcernsAsync. Simplify guard clause to only check plan type. * Update PricingClientTests after pm-26462-milestone-3 removal Remove IFeatureService from test setup. Delete tests for flag-disabled safeguard behavior. Clean up remaining test names. * Update UpcomingInvoiceHandlerTests after pm-26462-milestone-3 removal Remove all feature flag mock setup lines for PM26462_Milestone_3. Delete test methods that verified flag-disabled behavior. * Merge GetPlan lookup key test regions into one
1 parent aaf3034 commit c667b3e

4 files changed

Lines changed: 24 additions & 370 deletions

File tree

src/Billing/Services/Implementations/UpcomingInvoiceHandler.cs

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -112,14 +112,11 @@ private async Task HandleOrganizationUpcomingInvoiceAsync(
112112

113113
var plan = await pricingClient.GetPlanOrThrow(organization.PlanType);
114114

115-
var milestone3 = featureService.IsEnabled(FeatureFlagKeys.PM26462_Milestone_3);
116-
117115
var subscriptionAligned = await AlignOrganizationSubscriptionConcernsAsync(
118116
organization,
119117
@event,
120118
subscription,
121-
plan,
122-
milestone3);
119+
plan);
123120

124121
/*
125122
* Subscription alignment sends out a different version of our Upcoming Invoice email, so we don't need to continue
@@ -210,17 +207,14 @@ await stripeAdapter.UpdateCustomerAsync(subscription.CustomerId,
210207
/// <param name="event">The Stripe event associated with this operation.</param>
211208
/// <param name="subscription">The organization's subscription.</param>
212209
/// <param name="plan">The organization's current plan.</param>
213-
/// <param name="milestone3">A flag indicating whether the third milestone is enabled.</param>
214210
/// <returns>Whether the operation resulted in an updated subscription.</returns>
215211
private async Task<bool> AlignOrganizationSubscriptionConcernsAsync(
216212
Organization organization,
217213
Event @event,
218214
Subscription subscription,
219-
Plan plan,
220-
bool milestone3)
215+
Plan plan)
221216
{
222-
// currently these are the only plans that need aligned and both require the same flag and share most of the logic
223-
if (!milestone3 || plan.Type is not (PlanType.FamiliesAnnually2019 or PlanType.FamiliesAnnually2025))
217+
if (plan.Type is not (PlanType.FamiliesAnnually2019 or PlanType.FamiliesAnnually2025))
224218
{
225219
return false;
226220
}

src/Core/Billing/Pricing/PricingClient.cs

Lines changed: 3 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
using Bit.Core.Billing.Enums;
44
using Bit.Core.Billing.Pricing.Organizations;
55
using Bit.Core.Exceptions;
6-
using Bit.Core.Services;
76
using Bit.Core.Settings;
87
using Microsoft.Extensions.Logging;
98

@@ -13,7 +12,6 @@ namespace Bit.Core.Billing.Pricing;
1312
using PremiumPlan = Premium.Plan;
1413

1514
public class PricingClient(
16-
IFeatureService featureService,
1715
GlobalSettings globalSettings,
1816
HttpClient httpClient,
1917
ILogger<PricingClient> logger) : IPricingClient
@@ -40,7 +38,7 @@ public class PricingClient(
4038
var plan = await response.Content.ReadFromJsonAsync<Plan>();
4139
return plan == null
4240
? throw new BillingException(message: "Deserialization of Pricing Service response resulted in null")
43-
: new PlanAdapter(PreProcessFamiliesPreMigrationPlan(plan));
41+
: new PlanAdapter(plan);
4442
}
4543

4644
if (response.StatusCode == HttpStatusCode.NotFound)
@@ -74,7 +72,7 @@ public async Task<List<OrganizationPlan>> ListPlans()
7472
var plans = await response.Content.ReadFromJsonAsync<List<Plan>>();
7573
return plans == null
7674
? throw new BillingException(message: "Deserialization of Pricing Service response resulted in null")
77-
: plans.Select(OrganizationPlan (plan) => new PlanAdapter(PreProcessFamiliesPreMigrationPlan(plan))).ToList();
75+
: plans.Select(OrganizationPlan (plan) => new PlanAdapter(plan)).ToList();
7876
}
7977

8078
throw new BillingException(
@@ -121,10 +119,7 @@ public async Task<List<PremiumPlan>> ListPremiumPlans()
121119
PlanType.EnterpriseMonthly2020 => "enterprise-monthly-2020",
122120
PlanType.EnterpriseMonthly2023 => "enterprise-monthly-2023",
123121
PlanType.FamiliesAnnually => "families",
124-
PlanType.FamiliesAnnually2025 =>
125-
featureService.IsEnabled(FeatureFlagKeys.PM26462_Milestone_3)
126-
? "families-2025"
127-
: "families",
122+
PlanType.FamiliesAnnually2025 => "families-2025",
128123
PlanType.FamiliesAnnually2019 => "families-2019",
129124
PlanType.Free => "free",
130125
PlanType.TeamsAnnually => "teams-annually",
@@ -139,19 +134,4 @@ public async Task<List<PremiumPlan>> ListPremiumPlans()
139134
PlanType.TeamsStarter2023 => "teams-starter-2023",
140135
_ => null
141136
};
142-
143-
/// <summary>
144-
/// Safeguard used until the feature flag is enabled. Pricing service will return the
145-
/// 2025PreMigration plan with "families" lookup key. When that is detected and the FF
146-
/// is still disabled, set the lookup key to families-2025 so PlanAdapter will assign
147-
/// the correct plan.
148-
/// </summary>
149-
/// <param name="plan">The plan to preprocess</param>
150-
private Plan PreProcessFamiliesPreMigrationPlan(Plan plan)
151-
{
152-
if (plan.LookupKey == "families" && !featureService.IsEnabled(FeatureFlagKeys.PM26462_Milestone_3))
153-
plan.LookupKey = "families-2025";
154-
return plan;
155-
}
156-
157137
}

test/Billing.Test/Services/UpcomingInvoiceHandlerTests.cs

Lines changed: 0 additions & 171 deletions
Original file line numberDiff line numberDiff line change
@@ -1419,7 +1419,6 @@ public async Task HandleAsync_WhenMilestone3Enabled_AndFamilies2019Plan_UpdatesS
14191419
_organizationRepository.GetByIdAsync(_organizationId).Returns(organization);
14201420
_pricingClient.GetPlanOrThrow(PlanType.FamiliesAnnually2019).Returns(families2019Plan);
14211421
_pricingClient.GetPlanOrThrow(PlanType.FamiliesAnnually).Returns(familiesPlan);
1422-
_featureService.IsEnabled(FeatureFlagKeys.PM26462_Milestone_3).Returns(true);
14231422
_stripeEventUtilityService.IsSponsoredSubscription(subscription).Returns(false);
14241423

14251424
// Act
@@ -1527,7 +1526,6 @@ public async Task HandleAsync_WhenMilestone3Enabled_AndFamilies2019Plan_WithoutP
15271526
_organizationRepository.GetByIdAsync(_organizationId).Returns(organization);
15281527
_pricingClient.GetPlanOrThrow(PlanType.FamiliesAnnually2019).Returns(families2019Plan);
15291528
_pricingClient.GetPlanOrThrow(PlanType.FamiliesAnnually).Returns(familiesPlan);
1530-
_featureService.IsEnabled(FeatureFlagKeys.PM26462_Milestone_3).Returns(true);
15311529
_stripeEventUtilityService.IsSponsoredSubscription(subscription).Returns(false);
15321530

15331531
// Act
@@ -1558,88 +1556,6 @@ await _stripeAdapter.DidNotReceive().UpdateCustomerAsync(
15581556
Arg.Any<CustomerUpdateOptions>());
15591557
}
15601558

1561-
[Fact]
1562-
public async Task HandleAsync_WhenMilestone3Disabled_AndFamilies2019Plan_DoesNotUpdateSubscription()
1563-
{
1564-
// Arrange
1565-
var parsedEvent = new Event { Id = "evt_123", Type = "invoice.upcoming" };
1566-
var customerId = "cus_123";
1567-
var subscriptionId = "sub_123";
1568-
var passwordManagerItemId = "si_pm_123";
1569-
1570-
var invoice = new Invoice
1571-
{
1572-
CustomerId = customerId,
1573-
AmountDue = 40000,
1574-
NextPaymentAttempt = DateTime.UtcNow.AddDays(7),
1575-
Lines = new StripeList<InvoiceLineItem>
1576-
{
1577-
Data = [new() { Description = "Test Item" }]
1578-
}
1579-
};
1580-
1581-
var families2019Plan = new Families2019Plan();
1582-
1583-
var subscription = new Subscription
1584-
{
1585-
Id = subscriptionId,
1586-
CustomerId = customerId,
1587-
Items = new StripeList<SubscriptionItem>
1588-
{
1589-
Data =
1590-
[
1591-
new()
1592-
{
1593-
Id = passwordManagerItemId,
1594-
Price = new Price { Id = families2019Plan.PasswordManager.StripePlanId }
1595-
}
1596-
]
1597-
},
1598-
AutomaticTax = new SubscriptionAutomaticTax { Enabled = true },
1599-
Metadata = new Dictionary<string, string>()
1600-
};
1601-
1602-
var customer = new Customer
1603-
{
1604-
Id = customerId,
1605-
Subscriptions = new StripeList<Subscription> { Data = [subscription] },
1606-
Address = new Address { Country = "US" }
1607-
};
1608-
1609-
var organization = new Organization
1610-
{
1611-
Id = _organizationId,
1612-
BillingEmail = "org@example.com",
1613-
PlanType = PlanType.FamiliesAnnually2019
1614-
};
1615-
1616-
_stripeEventService.GetInvoice(parsedEvent).Returns(invoice);
1617-
_stripeAdapter.GetCustomerAsync(customerId, Arg.Any<CustomerGetOptions>()).Returns(customer);
1618-
_stripeEventUtilityService
1619-
.GetIdsFromMetadata(subscription.Metadata)
1620-
.Returns(new Tuple<Guid?, Guid?, Guid?>(_organizationId, null, null));
1621-
_organizationRepository.GetByIdAsync(_organizationId).Returns(organization);
1622-
_pricingClient.GetPlanOrThrow(PlanType.FamiliesAnnually2019).Returns(families2019Plan);
1623-
_featureService.IsEnabled(FeatureFlagKeys.PM26462_Milestone_3).Returns(false);
1624-
_stripeEventUtilityService.IsSponsoredSubscription(subscription).Returns(false);
1625-
1626-
// Act
1627-
await _sut.HandleAsync(parsedEvent);
1628-
1629-
// Assert - should not update subscription or organization when feature flag is disabled
1630-
await _stripeAdapter.DidNotReceive().UpdateSubscriptionAsync(
1631-
Arg.Any<string>(),
1632-
Arg.Is<SubscriptionUpdateOptions>(o => o.Discounts != null));
1633-
1634-
await _organizationRepository.DidNotReceive().ReplaceAsync(
1635-
Arg.Is<Organization>(org => org.PlanType == PlanType.FamiliesAnnually));
1636-
1637-
// Families plan is excluded from tax exempt alignment
1638-
await _stripeAdapter.DidNotReceive().UpdateCustomerAsync(
1639-
Arg.Any<string>(),
1640-
Arg.Any<CustomerUpdateOptions>());
1641-
}
1642-
16431559
[Fact]
16441560
public async Task HandleAsync_WhenMilestone3Enabled_ButNotFamilies2019Plan_DoesNotUpdateSubscription()
16451561
{
@@ -1697,7 +1613,6 @@ public async Task HandleAsync_WhenMilestone3Enabled_ButNotFamilies2019Plan_DoesN
16971613
.Returns(new Tuple<Guid?, Guid?, Guid?>(_organizationId, null, null));
16981614
_organizationRepository.GetByIdAsync(_organizationId).Returns(organization);
16991615
_pricingClient.GetPlanOrThrow(PlanType.FamiliesAnnually).Returns(familiesPlan);
1700-
_featureService.IsEnabled(FeatureFlagKeys.PM26462_Milestone_3).Returns(true);
17011616
_stripeEventUtilityService.IsSponsoredSubscription(subscription).Returns(false);
17021617

17031618
// Act
@@ -1772,7 +1687,6 @@ public async Task HandleAsync_WhenMilestone3Enabled_AndPasswordManagerItemNotFou
17721687
.Returns(new Tuple<Guid?, Guid?, Guid?>(_organizationId, null, null));
17731688
_organizationRepository.GetByIdAsync(_organizationId).Returns(organization);
17741689
_pricingClient.GetPlanOrThrow(PlanType.FamiliesAnnually2019).Returns(families2019Plan);
1775-
_featureService.IsEnabled(FeatureFlagKeys.PM26462_Milestone_3).Returns(true);
17761690
_stripeEventUtilityService.IsSponsoredSubscription(subscription).Returns(false);
17771691

17781692
// Act
@@ -1860,7 +1774,6 @@ public async Task HandleAsync_WhenMilestone3Enabled_AndUpdateFails_LogsErrorAndS
18601774
_organizationRepository.GetByIdAsync(_organizationId).Returns(organization);
18611775
_pricingClient.GetPlanOrThrow(PlanType.FamiliesAnnually2019).Returns(families2019Plan);
18621776
_pricingClient.GetPlanOrThrow(PlanType.FamiliesAnnually).Returns(familiesPlan);
1863-
_featureService.IsEnabled(FeatureFlagKeys.PM26462_Milestone_3).Returns(true);
18641777
_stripeEventUtilityService.IsSponsoredSubscription(subscription).Returns(false);
18651778

18661779
// Simulate update failure
@@ -1958,7 +1871,6 @@ public async Task HandleAsync_WhenMilestone3Enabled_AndCouponNotFound_LogsErrorA
19581871
_organizationRepository.GetByIdAsync(_organizationId).Returns(organization);
19591872
_pricingClient.GetPlanOrThrow(PlanType.FamiliesAnnually2019).Returns(families2019Plan);
19601873
_pricingClient.GetPlanOrThrow(PlanType.FamiliesAnnually).Returns(familiesPlan);
1961-
_featureService.IsEnabled(FeatureFlagKeys.PM26462_Milestone_3).Returns(true);
19621874
_stripeEventUtilityService.IsSponsoredSubscription(subscription).Returns(false);
19631875
_stripeAdapter.GetCouponAsync(CouponIDs.Milestone3SubscriptionDiscount).Returns((Coupon)null);
19641876
_stripeAdapter.UpdateSubscriptionAsync(Arg.Any<string>(), Arg.Any<SubscriptionUpdateOptions>())
@@ -2058,7 +1970,6 @@ public async Task HandleAsync_WhenMilestone3Enabled_AndCouponPercentOffIsNull_Lo
20581970
_organizationRepository.GetByIdAsync(_organizationId).Returns(organization);
20591971
_pricingClient.GetPlanOrThrow(PlanType.FamiliesAnnually2019).Returns(families2019Plan);
20601972
_pricingClient.GetPlanOrThrow(PlanType.FamiliesAnnually).Returns(familiesPlan);
2061-
_featureService.IsEnabled(FeatureFlagKeys.PM26462_Milestone_3).Returns(true);
20621973
_stripeEventUtilityService.IsSponsoredSubscription(subscription).Returns(false);
20631974
_stripeAdapter.GetCouponAsync(CouponIDs.Milestone3SubscriptionDiscount).Returns(coupon);
20641975
_stripeAdapter.UpdateSubscriptionAsync(Arg.Any<string>(), Arg.Any<SubscriptionUpdateOptions>())
@@ -2163,7 +2074,6 @@ public async Task HandleAsync_WhenMilestone3Enabled_AndSeatAddOnExists_DeletesIt
21632074
_organizationRepository.GetByIdAsync(_organizationId).Returns(organization);
21642075
_pricingClient.GetPlanOrThrow(PlanType.FamiliesAnnually2019).Returns(families2019Plan);
21652076
_pricingClient.GetPlanOrThrow(PlanType.FamiliesAnnually).Returns(familiesPlan);
2166-
_featureService.IsEnabled(FeatureFlagKeys.PM26462_Milestone_3).Returns(true);
21672077
_stripeEventUtilityService.IsSponsoredSubscription(subscription).Returns(false);
21682078

21692079
// Act
@@ -2277,7 +2187,6 @@ public async Task HandleAsync_WhenMilestone3Enabled_AndSeatAddOnWithQuantityOne_
22772187
_organizationRepository.GetByIdAsync(_organizationId).Returns(organization);
22782188
_pricingClient.GetPlanOrThrow(PlanType.FamiliesAnnually2019).Returns(families2019Plan);
22792189
_pricingClient.GetPlanOrThrow(PlanType.FamiliesAnnually).Returns(familiesPlan);
2280-
_featureService.IsEnabled(FeatureFlagKeys.PM26462_Milestone_3).Returns(true);
22812190
_stripeEventUtilityService.IsSponsoredSubscription(subscription).Returns(false);
22822191

22832192
// Act
@@ -2398,7 +2307,6 @@ public async Task HandleAsync_WhenMilestone3Enabled_WithPremiumAccessAndSeatAddO
23982307
_organizationRepository.GetByIdAsync(_organizationId).Returns(organization);
23992308
_pricingClient.GetPlanOrThrow(PlanType.FamiliesAnnually2019).Returns(families2019Plan);
24002309
_pricingClient.GetPlanOrThrow(PlanType.FamiliesAnnually).Returns(familiesPlan);
2401-
_featureService.IsEnabled(FeatureFlagKeys.PM26462_Milestone_3).Returns(true);
24022310
_stripeEventUtilityService.IsSponsoredSubscription(subscription).Returns(false);
24032311

24042312
// Act
@@ -2503,7 +2411,6 @@ public async Task HandleAsync_WhenMilestone3Enabled_AndFamilies2025Plan_UpdatesS
25032411
_organizationRepository.GetByIdAsync(_organizationId).Returns(organization);
25042412
_pricingClient.GetPlanOrThrow(PlanType.FamiliesAnnually2025).Returns(families2025Plan);
25052413
_pricingClient.GetPlanOrThrow(PlanType.FamiliesAnnually).Returns(familiesPlan);
2506-
_featureService.IsEnabled(FeatureFlagKeys.PM26462_Milestone_3).Returns(true);
25072414
_stripeEventUtilityService.IsSponsoredSubscription(subscription).Returns(false);
25082415

25092416
// Act
@@ -2534,83 +2441,6 @@ await _mailer.Received(1).SendEmail(
25342441
email.View.MonthlyRenewalPrice == (familiesPlan.PasswordManager.BasePrice / 12).ToString("C", new CultureInfo("en-US"))));
25352442
}
25362443

2537-
[Fact]
2538-
public async Task HandleAsync_WhenMilestone3Disabled_AndFamilies2025Plan_DoesNotUpdateSubscription()
2539-
{
2540-
// Arrange
2541-
var parsedEvent = new Event { Id = "evt_123", Type = "invoice.upcoming" };
2542-
var customerId = "cus_123";
2543-
var subscriptionId = "sub_123";
2544-
var passwordManagerItemId = "si_pm_123";
2545-
2546-
var invoice = new Invoice
2547-
{
2548-
CustomerId = customerId,
2549-
AmountDue = 40000,
2550-
NextPaymentAttempt = DateTime.UtcNow.AddDays(7),
2551-
Lines = new StripeList<InvoiceLineItem>
2552-
{
2553-
Data = [new() { Description = "Test Item" }]
2554-
}
2555-
};
2556-
2557-
var families2025Plan = new Families2025Plan();
2558-
2559-
var subscription = new Subscription
2560-
{
2561-
Id = subscriptionId,
2562-
CustomerId = customerId,
2563-
Items = new StripeList<SubscriptionItem>
2564-
{
2565-
Data =
2566-
[
2567-
new()
2568-
{
2569-
Id = passwordManagerItemId,
2570-
Price = new Price { Id = families2025Plan.PasswordManager.StripePlanId }
2571-
}
2572-
]
2573-
},
2574-
AutomaticTax = new SubscriptionAutomaticTax { Enabled = true },
2575-
Metadata = new Dictionary<string, string>()
2576-
};
2577-
2578-
var customer = new Customer
2579-
{
2580-
Id = customerId,
2581-
Subscriptions = new StripeList<Subscription> { Data = [subscription] },
2582-
Address = new Address { Country = "US" }
2583-
};
2584-
2585-
var organization = new Organization
2586-
{
2587-
Id = _organizationId,
2588-
BillingEmail = "org@example.com",
2589-
PlanType = PlanType.FamiliesAnnually2025
2590-
};
2591-
2592-
_stripeEventService.GetInvoice(parsedEvent).Returns(invoice);
2593-
_stripeAdapter.GetCustomerAsync(customerId, Arg.Any<CustomerGetOptions>()).Returns(customer);
2594-
_stripeEventUtilityService
2595-
.GetIdsFromMetadata(subscription.Metadata)
2596-
.Returns(new Tuple<Guid?, Guid?, Guid?>(_organizationId, null, null));
2597-
_organizationRepository.GetByIdAsync(_organizationId).Returns(organization);
2598-
_pricingClient.GetPlanOrThrow(PlanType.FamiliesAnnually2025).Returns(families2025Plan);
2599-
_featureService.IsEnabled(FeatureFlagKeys.PM26462_Milestone_3).Returns(false);
2600-
_stripeEventUtilityService.IsSponsoredSubscription(subscription).Returns(false);
2601-
2602-
// Act
2603-
await _sut.HandleAsync(parsedEvent);
2604-
2605-
// Assert - should not update subscription or organization when feature flag is disabled
2606-
await _stripeAdapter.DidNotReceive().UpdateSubscriptionAsync(
2607-
Arg.Any<string>(),
2608-
Arg.Any<SubscriptionUpdateOptions>());
2609-
2610-
await _organizationRepository.DidNotReceive().ReplaceAsync(
2611-
Arg.Is<Organization>(org => org.PlanType == PlanType.FamiliesAnnually));
2612-
}
2613-
26142444
#region Premium Renewal Email Tests
26152445

26162446
[Fact]
@@ -3084,7 +2914,6 @@ public async Task HandleAsync_Families_DeferEnabled_CallsScheduler()
30842914
_organizationRepository.GetByIdAsync(_organizationId).Returns(organization);
30852915
_pricingClient.GetPlanOrThrow(PlanType.FamiliesAnnually2019).Returns(families2019Plan);
30862916
_pricingClient.GetPlanOrThrow(PlanType.FamiliesAnnually).Returns(new FamiliesPlan());
3087-
_featureService.IsEnabled(FeatureFlagKeys.PM26462_Milestone_3).Returns(true);
30882917
_featureService.IsEnabled(FeatureFlagKeys.PM32645_DeferPriceMigrationToRenewal).Returns(true);
30892918
_stripeEventUtilityService.IsSponsoredSubscription(subscription).Returns(false);
30902919
_stripeAdapter.GetCouponAsync(CouponIDs.Milestone3SubscriptionDiscount)

0 commit comments

Comments
 (0)