From e1a52f7c6e2b093cce910ca7621407705b9cc204 Mon Sep 17 00:00:00 2001 From: Alex Morask <144709477+amorask-bitwarden@users.noreply.github.com> Date: Mon, 26 Aug 2024 12:35:23 -0400 Subject: [PATCH] Set Stripe to assigned seats when assigned seats are greater than incoming seat min (#4679) --- .../Billing/ProviderBillingService.cs | 49 ++++++++++--- .../Billing/ProviderBillingServiceTests.cs | 68 ++++++++++++++++++- 2 files changed, 104 insertions(+), 13 deletions(-) diff --git a/bitwarden_license/src/Commercial.Core/Billing/ProviderBillingService.cs b/bitwarden_license/src/Commercial.Core/Billing/ProviderBillingService.cs index c38c8fbb4..787a11d1e 100644 --- a/bitwarden_license/src/Commercial.Core/Billing/ProviderBillingService.cs +++ b/bitwarden_license/src/Commercial.Core/Billing/ProviderBillingService.cs @@ -486,12 +486,27 @@ public class ProviderBillingService( if (enterpriseProviderPlan.PurchasedSeats == 0) { - subscriptionItemOptionsList.Add(new SubscriptionItemOptions + if (enterpriseProviderPlan.AllocatedSeats > enterpriseSeatMinimum) { - Id = enterpriseSubscriptionItem.Id, - Price = enterprisePriceId, - Quantity = enterpriseSeatMinimum - }); + enterpriseProviderPlan.PurchasedSeats = + enterpriseProviderPlan.AllocatedSeats - enterpriseSeatMinimum; + + subscriptionItemOptionsList.Add(new SubscriptionItemOptions + { + Id = enterpriseSubscriptionItem.Id, + Price = enterprisePriceId, + Quantity = enterpriseProviderPlan.AllocatedSeats + }); + } + else + { + subscriptionItemOptionsList.Add(new SubscriptionItemOptions + { + Id = enterpriseSubscriptionItem.Id, + Price = enterprisePriceId, + Quantity = enterpriseSeatMinimum + }); + } } else { @@ -530,12 +545,26 @@ public class ProviderBillingService( if (teamsProviderPlan.PurchasedSeats == 0) { - subscriptionItemOptionsList.Add(new SubscriptionItemOptions + if (teamsProviderPlan.AllocatedSeats > teamsSeatMinimum) { - Id = teamsSubscriptionItem.Id, - Price = teamsPriceId, - Quantity = teamsSeatMinimum - }); + teamsProviderPlan.PurchasedSeats = teamsProviderPlan.AllocatedSeats - teamsSeatMinimum; + + subscriptionItemOptionsList.Add(new SubscriptionItemOptions + { + Id = teamsSubscriptionItem.Id, + Price = teamsPriceId, + Quantity = teamsProviderPlan.AllocatedSeats + }); + } + else + { + subscriptionItemOptionsList.Add(new SubscriptionItemOptions + { + Id = teamsSubscriptionItem.Id, + Price = teamsPriceId, + Quantity = teamsSeatMinimum + }); + } } else { diff --git a/bitwarden_license/test/Commercial.Core.Test/Billing/ProviderBillingServiceTests.cs b/bitwarden_license/test/Commercial.Core.Test/Billing/ProviderBillingServiceTests.cs index 45a6fb983..d9ae9a559 100644 --- a/bitwarden_license/test/Commercial.Core.Test/Billing/ProviderBillingServiceTests.cs +++ b/bitwarden_license/test/Commercial.Core.Test/Billing/ProviderBillingServiceTests.cs @@ -1025,7 +1025,7 @@ public class ProviderBillingServiceTests await Assert.ThrowsAsync(() => sutProvider.Sut.UpdateSeatMinimums(provider, -10, 100)); [Theory, BitAutoData] - public async Task UpdateSeatMinimums_NoPurchasedSeats_SyncsStripeWithNewSeatMinimum( + public async Task UpdateSeatMinimums_NoPurchasedSeats_AllocatedHigherThanIncomingMinimum_UpdatesPurchasedSeats_SyncsStripeWithNewSeatMinimum( Provider provider, SutProvider sutProvider) { @@ -1062,8 +1062,70 @@ public class ProviderBillingServiceTests var providerPlans = new List { - new() { PlanType = PlanType.EnterpriseMonthly, SeatMinimum = 50, PurchasedSeats = 0 }, - new() { PlanType = PlanType.TeamsMonthly, SeatMinimum = 30, PurchasedSeats = 0 } + new() { PlanType = PlanType.EnterpriseMonthly, SeatMinimum = 50, PurchasedSeats = 0, AllocatedSeats = 20 }, + new() { PlanType = PlanType.TeamsMonthly, SeatMinimum = 30, PurchasedSeats = 0, AllocatedSeats = 25 } + }; + + providerPlanRepository.GetByProviderId(provider.Id).Returns(providerPlans); + + await sutProvider.Sut.UpdateSeatMinimums(provider, 30, 20); + + await providerPlanRepository.Received(1).ReplaceAsync(Arg.Is( + providerPlan => providerPlan.PlanType == PlanType.EnterpriseMonthly && providerPlan.SeatMinimum == 30)); + + await providerPlanRepository.Received(1).ReplaceAsync(Arg.Is( + providerPlan => providerPlan.PlanType == PlanType.TeamsMonthly && providerPlan.SeatMinimum == 20 && providerPlan.PurchasedSeats == 5)); + + await stripeAdapter.Received(1).SubscriptionUpdateAsync(provider.GatewaySubscriptionId, + Arg.Is( + options => + options.Items.Count == 2 && + options.Items.ElementAt(0).Id == enterpriseLineItemId && + options.Items.ElementAt(0).Quantity == 30 && + options.Items.ElementAt(1).Id == teamsLineItemId && + options.Items.ElementAt(1).Quantity == 25)); + } + + [Theory, BitAutoData] + public async Task UpdateSeatMinimums_NoPurchasedSeats_AllocatedLowerThanIncomingMinimum_SyncsStripeWithNewSeatMinimum( + Provider provider, + SutProvider sutProvider) + { + var stripeAdapter = sutProvider.GetDependency(); + var providerPlanRepository = sutProvider.GetDependency(); + + const string enterpriseLineItemId = "enterprise_line_item_id"; + const string teamsLineItemId = "teams_line_item_id"; + + var enterprisePriceId = StaticStore.GetPlan(PlanType.EnterpriseMonthly).PasswordManager.StripeProviderPortalSeatPlanId; + var teamsPriceId = StaticStore.GetPlan(PlanType.TeamsMonthly).PasswordManager.StripeProviderPortalSeatPlanId; + + var subscription = new Subscription + { + Items = new StripeList + { + Data = + [ + new SubscriptionItem + { + Id = enterpriseLineItemId, + Price = new Price { Id = enterprisePriceId } + }, + new SubscriptionItem + { + Id = teamsLineItemId, + Price = new Price { Id = teamsPriceId } + } + ] + } + }; + + stripeAdapter.SubscriptionGetAsync(provider.GatewaySubscriptionId).Returns(subscription); + + var providerPlans = new List + { + new() { PlanType = PlanType.EnterpriseMonthly, SeatMinimum = 50, PurchasedSeats = 0, AllocatedSeats = 40 }, + new() { PlanType = PlanType.TeamsMonthly, SeatMinimum = 30, PurchasedSeats = 0, AllocatedSeats = 15 } }; providerPlanRepository.GetByProviderId(provider.Id).Returns(providerPlans);