Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weโ€™ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[PM-13450] Admin: Display Multi-organization Enterprise attributes on provider details #4955

Open
wants to merge 22 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
201 changes: 100 additions & 101 deletions bitwarden_license/src/Commercial.Core/Billing/ProviderBillingService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
using Bit.Core.Billing.Extensions;
using Bit.Core.Billing.Repositories;
using Bit.Core.Billing.Services;
using Bit.Core.Billing.Services.Contracts;
using Bit.Core.Context;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
Expand Down Expand Up @@ -437,144 +438,142 @@
}
}

public async Task UpdateSeatMinimums(
Provider provider,
int enterpriseSeatMinimum,
int teamsSeatMinimum)
public async Task ChangePlan(ChangeProviderPlanCommand command)
Copy link
Contributor

Choose a reason for hiding this comment

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

โ“ Looks like this is called in one place where we already have access to both the Provider and the ProviderPlan. Could we just include those in the command rather than double fetch them from the DB?

Copy link
Member Author

Choose a reason for hiding this comment

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

I just have to check if I can refactor the duplicate call to retrieve provider plans further at this point

{
ArgumentNullException.ThrowIfNull(provider);
var plan = await providerPlanRepository.GetByIdAsync(command.ProviderPlanId);

if (enterpriseSeatMinimum < 0 || teamsSeatMinimum < 0)
if (plan == null)
{
throw new BadRequestException("Provider seat minimums must be at least 0.");
throw new BadRequestException("Provider plan not found.");
}

var subscription = await stripeAdapter.SubscriptionGetAsync(provider.GatewaySubscriptionId);

var subscriptionItemOptionsList = new List<SubscriptionItemOptions>();
if (plan.PlanType == command.NewPlan)
{
return;
}

var providerPlans = await providerPlanRepository.GetByProviderId(provider.Id);
var oldPlanConfiguration = StaticStore.GetPlan(plan.PlanType);

var enterpriseProviderPlan =
providerPlans.Single(providerPlan => providerPlan.PlanType == PlanType.EnterpriseMonthly);
plan.PlanType = command.NewPlan;
await providerPlanRepository.ReplaceAsync(plan);

if (enterpriseProviderPlan.SeatMinimum != enterpriseSeatMinimum)
Subscription subscription;
try
{
subscription = await stripeAdapter.ProviderSubscriptionGetAsync(command.GatewaySubscriptionId, plan.ProviderId);
}
catch (InvalidOperationException)

Check warning on line 465 in bitwarden_license/src/Commercial.Core/Billing/ProviderBillingService.cs

View check run for this annotation

Codecov / codecov/patch

bitwarden_license/src/Commercial.Core/Billing/ProviderBillingService.cs#L465

Added line #L465 was not covered by tests
{
var enterprisePriceId = StaticStore.GetPlan(PlanType.EnterpriseMonthly).PasswordManager
.StripeProviderPortalSeatPlanId;
throw new ConflictException("Subscription not found.");

Check warning on line 467 in bitwarden_license/src/Commercial.Core/Billing/ProviderBillingService.cs

View check run for this annotation

Codecov / codecov/patch

bitwarden_license/src/Commercial.Core/Billing/ProviderBillingService.cs#L467

Added line #L467 was not covered by tests
}

var enterpriseSubscriptionItem = subscription.Items.First(item => item.Price.Id == enterprisePriceId);
var oldSubscriptionItem = subscription.Items.SingleOrDefault(x =>
jonashendrickx marked this conversation as resolved.
Show resolved Hide resolved
x.Price.Id == oldPlanConfiguration.PasswordManager.StripeProviderPortalSeatPlanId);

if (enterpriseProviderPlan.PurchasedSeats == 0)
{
if (enterpriseProviderPlan.AllocatedSeats > enterpriseSeatMinimum)
var updateOptions = new SubscriptionUpdateOptions
{
Items =
[
new SubscriptionItemOptions
{
enterpriseProviderPlan.PurchasedSeats =
enterpriseProviderPlan.AllocatedSeats - enterpriseSeatMinimum;

subscriptionItemOptionsList.Add(new SubscriptionItemOptions
{
Id = enterpriseSubscriptionItem.Id,
Price = enterprisePriceId,
Quantity = enterpriseProviderPlan.AllocatedSeats
});
}
else
Price = StaticStore.GetPlan(command.NewPlan).PasswordManager.StripeProviderPortalSeatPlanId,
Quantity = oldSubscriptionItem!.Quantity
jonashendrickx marked this conversation as resolved.
Show resolved Hide resolved
},
new SubscriptionItemOptions
{
subscriptionItemOptionsList.Add(new SubscriptionItemOptions
{
Id = enterpriseSubscriptionItem.Id,
Price = enterprisePriceId,
Quantity = enterpriseSeatMinimum
});
Id = oldSubscriptionItem.Id,
Deleted = true
}
}
else
{
var totalEnterpriseSeats = enterpriseProviderPlan.SeatMinimum + enterpriseProviderPlan.PurchasedSeats;
]
};

if (enterpriseSeatMinimum <= totalEnterpriseSeats)
{
enterpriseProviderPlan.PurchasedSeats = totalEnterpriseSeats - enterpriseSeatMinimum;
}
else
{
enterpriseProviderPlan.PurchasedSeats = 0;
subscriptionItemOptionsList.Add(new SubscriptionItemOptions
{
Id = enterpriseSubscriptionItem.Id,
Price = enterprisePriceId,
Quantity = enterpriseSeatMinimum
});
}
}
await stripeAdapter.SubscriptionUpdateAsync(command.GatewaySubscriptionId, updateOptions);
}

enterpriseProviderPlan.SeatMinimum = enterpriseSeatMinimum;
public async Task UpdateSeatMinimums(UpdateProviderSeatMinimumsCommand command)
Copy link
Contributor

Choose a reason for hiding this comment

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

โ“ Same question as above with regard to this command.

{
if (command.Configuration.Any(x => x.SeatsMinimum < 0))
{
throw new BadRequestException("Provider seat minimums must be at least 0.");
}

await providerPlanRepository.ReplaceAsync(enterpriseProviderPlan);
Subscription subscription;
try
{
subscription = await stripeAdapter.ProviderSubscriptionGetAsync(command.GatewaySubscriptionId, command.Id);
}
catch (InvalidOperationException)
{
throw new ConflictException("Subscription not found.");

Check warning on line 507 in bitwarden_license/src/Commercial.Core/Billing/ProviderBillingService.cs

View check run for this annotation

Codecov / codecov/patch

bitwarden_license/src/Commercial.Core/Billing/ProviderBillingService.cs#L505-L507

Added lines #L505 - L507 were not covered by tests
}

var teamsProviderPlan =
providerPlans.Single(providerPlan => providerPlan.PlanType == PlanType.TeamsMonthly);
var subscriptionItemOptionsList = new List<SubscriptionItemOptions>();

if (teamsProviderPlan.SeatMinimum != teamsSeatMinimum)
{
var teamsPriceId = StaticStore.GetPlan(PlanType.TeamsMonthly).PasswordManager
.StripeProviderPortalSeatPlanId;
var providerPlans = await providerPlanRepository.GetByProviderId(command.Id);

var teamsSubscriptionItem = subscription.Items.First(item => item.Price.Id == teamsPriceId);
foreach (var newPlanConfiguration in command.Configuration)
{
jonashendrickx marked this conversation as resolved.
Show resolved Hide resolved
var providerPlan =
providerPlans.Single(providerPlan => providerPlan.PlanType == newPlanConfiguration.Plan);

if (teamsProviderPlan.PurchasedSeats == 0)
if (providerPlan.SeatMinimum != newPlanConfiguration.SeatsMinimum)
{
if (teamsProviderPlan.AllocatedSeats > teamsSeatMinimum)
{
teamsProviderPlan.PurchasedSeats = teamsProviderPlan.AllocatedSeats - teamsSeatMinimum;
var priceId = StaticStore.GetPlan(newPlanConfiguration.Plan).PasswordManager
.StripeProviderPortalSeatPlanId;
var subscriptionItem = subscription.Items.First(item => item.Price.Id == priceId);

subscriptionItemOptionsList.Add(new SubscriptionItemOptions
{
Id = teamsSubscriptionItem.Id,
Price = teamsPriceId,
Quantity = teamsProviderPlan.AllocatedSeats
});
}
else
if (providerPlan.PurchasedSeats == 0)
{
subscriptionItemOptionsList.Add(new SubscriptionItemOptions
if (providerPlan.AllocatedSeats > newPlanConfiguration.SeatsMinimum)
{
Id = teamsSubscriptionItem.Id,
Price = teamsPriceId,
Quantity = teamsSeatMinimum
});
}
}
else
{
var totalTeamsSeats = teamsProviderPlan.SeatMinimum + teamsProviderPlan.PurchasedSeats;

if (teamsSeatMinimum <= totalTeamsSeats)
{
teamsProviderPlan.PurchasedSeats = totalTeamsSeats - teamsSeatMinimum;
providerPlan.PurchasedSeats = providerPlan.AllocatedSeats - newPlanConfiguration.SeatsMinimum;

subscriptionItemOptionsList.Add(new SubscriptionItemOptions
{
Id = subscriptionItem.Id,
Price = priceId,
Quantity = providerPlan.AllocatedSeats
});
}
else
{
subscriptionItemOptionsList.Add(new SubscriptionItemOptions
{
Id = subscriptionItem.Id,
Price = priceId,
Quantity = newPlanConfiguration.SeatsMinimum
});
}
}
else
{
teamsProviderPlan.PurchasedSeats = 0;
subscriptionItemOptionsList.Add(new SubscriptionItemOptions
var totalSeats = providerPlan.SeatMinimum + providerPlan.PurchasedSeats;

if (newPlanConfiguration.SeatsMinimum <= totalSeats)
{
providerPlan.PurchasedSeats = totalSeats - newPlanConfiguration.SeatsMinimum;
}
else
{
Id = teamsSubscriptionItem.Id,
Price = teamsPriceId,
Quantity = teamsSeatMinimum
});
providerPlan.PurchasedSeats = 0;
subscriptionItemOptionsList.Add(new SubscriptionItemOptions
{
Id = subscriptionItem.Id,
Price = priceId,
Quantity = newPlanConfiguration.SeatsMinimum
});
}
}
}

teamsProviderPlan.SeatMinimum = teamsSeatMinimum;
providerPlan.SeatMinimum = newPlanConfiguration.SeatsMinimum;

await providerPlanRepository.ReplaceAsync(teamsProviderPlan);
await providerPlanRepository.ReplaceAsync(providerPlan);
}
}

if (subscriptionItemOptionsList.Count > 0)
{
await stripeAdapter.SubscriptionUpdateAsync(provider.GatewaySubscriptionId,
await stripeAdapter.SubscriptionUpdateAsync(command.GatewaySubscriptionId,
new SubscriptionUpdateOptions { Items = subscriptionItemOptionsList });
}
}
Expand Down
Loading
Loading