mirror of
https://github.com/bitwarden/server.git
synced 2025-04-07 22:18:17 -05:00
[PM-13837] Switch provider price IDs (#5518)
* Add ProviderPriceAdapter This is a temporary utility that will be used to manage retrieval of provider price IDs until all providers can be migrated to the new price structure. * Updated ProviderBillingService.ChangePlan * Update ProviderBillingService.SetupSubscription * Update ProviderBillingService.UpdateSeatMinimums * Update ProviderBillingService.CurrySeatScalingUpdate * Mark StripeProviderPortalSeatPlanId obsolete * Run dotnet format
This commit is contained in:
parent
1cc854ddb9
commit
282e80ca02
@ -35,7 +35,6 @@ public class ProviderBillingService(
|
|||||||
IGlobalSettings globalSettings,
|
IGlobalSettings globalSettings,
|
||||||
ILogger<ProviderBillingService> logger,
|
ILogger<ProviderBillingService> logger,
|
||||||
IOrganizationRepository organizationRepository,
|
IOrganizationRepository organizationRepository,
|
||||||
IPaymentService paymentService,
|
|
||||||
IPricingClient pricingClient,
|
IPricingClient pricingClient,
|
||||||
IProviderInvoiceItemRepository providerInvoiceItemRepository,
|
IProviderInvoiceItemRepository providerInvoiceItemRepository,
|
||||||
IProviderOrganizationRepository providerOrganizationRepository,
|
IProviderOrganizationRepository providerOrganizationRepository,
|
||||||
@ -148,36 +147,29 @@ public class ProviderBillingService(
|
|||||||
|
|
||||||
public async Task ChangePlan(ChangeProviderPlanCommand command)
|
public async Task ChangePlan(ChangeProviderPlanCommand command)
|
||||||
{
|
{
|
||||||
var plan = await providerPlanRepository.GetByIdAsync(command.ProviderPlanId);
|
var (provider, providerPlanId, newPlanType) = command;
|
||||||
|
|
||||||
if (plan == null)
|
var providerPlan = await providerPlanRepository.GetByIdAsync(providerPlanId);
|
||||||
|
|
||||||
|
if (providerPlan == null)
|
||||||
{
|
{
|
||||||
throw new BadRequestException("Provider plan not found.");
|
throw new BadRequestException("Provider plan not found.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (plan.PlanType == command.NewPlan)
|
if (providerPlan.PlanType == newPlanType)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var oldPlanConfiguration = await pricingClient.GetPlanOrThrow(plan.PlanType);
|
var subscription = await subscriberService.GetSubscriptionOrThrow(provider);
|
||||||
var newPlanConfiguration = await pricingClient.GetPlanOrThrow(command.NewPlan);
|
|
||||||
|
|
||||||
plan.PlanType = command.NewPlan;
|
var oldPriceId = ProviderPriceAdapter.GetPriceId(provider, subscription, providerPlan.PlanType);
|
||||||
await providerPlanRepository.ReplaceAsync(plan);
|
var newPriceId = ProviderPriceAdapter.GetPriceId(provider, subscription, newPlanType);
|
||||||
|
|
||||||
Subscription subscription;
|
providerPlan.PlanType = newPlanType;
|
||||||
try
|
await providerPlanRepository.ReplaceAsync(providerPlan);
|
||||||
{
|
|
||||||
subscription = await stripeAdapter.ProviderSubscriptionGetAsync(command.GatewaySubscriptionId, plan.ProviderId);
|
|
||||||
}
|
|
||||||
catch (InvalidOperationException)
|
|
||||||
{
|
|
||||||
throw new ConflictException("Subscription not found.");
|
|
||||||
}
|
|
||||||
|
|
||||||
var oldSubscriptionItem = subscription.Items.SingleOrDefault(x =>
|
var oldSubscriptionItem = subscription.Items.SingleOrDefault(x => x.Price.Id == oldPriceId);
|
||||||
x.Price.Id == oldPlanConfiguration.PasswordManager.StripeProviderPortalSeatPlanId);
|
|
||||||
|
|
||||||
var updateOptions = new SubscriptionUpdateOptions
|
var updateOptions = new SubscriptionUpdateOptions
|
||||||
{
|
{
|
||||||
@ -185,7 +177,7 @@ public class ProviderBillingService(
|
|||||||
[
|
[
|
||||||
new SubscriptionItemOptions
|
new SubscriptionItemOptions
|
||||||
{
|
{
|
||||||
Price = newPlanConfiguration.PasswordManager.StripeProviderPortalSeatPlanId,
|
Price = newPriceId,
|
||||||
Quantity = oldSubscriptionItem!.Quantity
|
Quantity = oldSubscriptionItem!.Quantity
|
||||||
},
|
},
|
||||||
new SubscriptionItemOptions
|
new SubscriptionItemOptions
|
||||||
@ -196,12 +188,14 @@ public class ProviderBillingService(
|
|||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
await stripeAdapter.SubscriptionUpdateAsync(command.GatewaySubscriptionId, updateOptions);
|
await stripeAdapter.SubscriptionUpdateAsync(provider.GatewaySubscriptionId, updateOptions);
|
||||||
|
|
||||||
// Refactor later to ?ChangeClientPlanCommand? (ProviderPlanId, ProviderId, OrganizationId)
|
// Refactor later to ?ChangeClientPlanCommand? (ProviderPlanId, ProviderId, OrganizationId)
|
||||||
// 1. Retrieve PlanType and PlanName for ProviderPlan
|
// 1. Retrieve PlanType and PlanName for ProviderPlan
|
||||||
// 2. Assign PlanType & PlanName to Organization
|
// 2. Assign PlanType & PlanName to Organization
|
||||||
var providerOrganizations = await providerOrganizationRepository.GetManyDetailsByProviderAsync(plan.ProviderId);
|
var providerOrganizations = await providerOrganizationRepository.GetManyDetailsByProviderAsync(providerPlan.ProviderId);
|
||||||
|
|
||||||
|
var newPlan = await pricingClient.GetPlanOrThrow(newPlanType);
|
||||||
|
|
||||||
foreach (var providerOrganization in providerOrganizations)
|
foreach (var providerOrganization in providerOrganizations)
|
||||||
{
|
{
|
||||||
@ -210,8 +204,8 @@ public class ProviderBillingService(
|
|||||||
{
|
{
|
||||||
throw new ConflictException($"Organization '{providerOrganization.Id}' not found.");
|
throw new ConflictException($"Organization '{providerOrganization.Id}' not found.");
|
||||||
}
|
}
|
||||||
organization.PlanType = command.NewPlan;
|
organization.PlanType = newPlanType;
|
||||||
organization.Plan = newPlanConfiguration.Name;
|
organization.Plan = newPlan.Name;
|
||||||
await organizationRepository.ReplaceAsync(organization);
|
await organizationRepository.ReplaceAsync(organization);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -405,7 +399,7 @@ public class ProviderBillingService(
|
|||||||
|
|
||||||
var newlyAssignedSeatTotal = currentlyAssignedSeatTotal + seatAdjustment;
|
var newlyAssignedSeatTotal = currentlyAssignedSeatTotal + seatAdjustment;
|
||||||
|
|
||||||
var update = CurrySeatScalingUpdate(
|
var scaleQuantityTo = CurrySeatScalingUpdate(
|
||||||
provider,
|
provider,
|
||||||
providerPlan,
|
providerPlan,
|
||||||
newlyAssignedSeatTotal);
|
newlyAssignedSeatTotal);
|
||||||
@ -428,9 +422,7 @@ public class ProviderBillingService(
|
|||||||
else if (currentlyAssignedSeatTotal <= seatMinimum &&
|
else if (currentlyAssignedSeatTotal <= seatMinimum &&
|
||||||
newlyAssignedSeatTotal > seatMinimum)
|
newlyAssignedSeatTotal > seatMinimum)
|
||||||
{
|
{
|
||||||
await update(
|
await scaleQuantityTo(newlyAssignedSeatTotal);
|
||||||
seatMinimum,
|
|
||||||
newlyAssignedSeatTotal);
|
|
||||||
}
|
}
|
||||||
/*
|
/*
|
||||||
* Above the limit => Above the limit:
|
* Above the limit => Above the limit:
|
||||||
@ -439,9 +431,7 @@ public class ProviderBillingService(
|
|||||||
else if (currentlyAssignedSeatTotal > seatMinimum &&
|
else if (currentlyAssignedSeatTotal > seatMinimum &&
|
||||||
newlyAssignedSeatTotal > seatMinimum)
|
newlyAssignedSeatTotal > seatMinimum)
|
||||||
{
|
{
|
||||||
await update(
|
await scaleQuantityTo(newlyAssignedSeatTotal);
|
||||||
currentlyAssignedSeatTotal,
|
|
||||||
newlyAssignedSeatTotal);
|
|
||||||
}
|
}
|
||||||
/*
|
/*
|
||||||
* Above the limit => Below the limit:
|
* Above the limit => Below the limit:
|
||||||
@ -450,9 +440,7 @@ public class ProviderBillingService(
|
|||||||
else if (currentlyAssignedSeatTotal > seatMinimum &&
|
else if (currentlyAssignedSeatTotal > seatMinimum &&
|
||||||
newlyAssignedSeatTotal <= seatMinimum)
|
newlyAssignedSeatTotal <= seatMinimum)
|
||||||
{
|
{
|
||||||
await update(
|
await scaleQuantityTo(seatMinimum);
|
||||||
currentlyAssignedSeatTotal,
|
|
||||||
seatMinimum);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -586,9 +574,11 @@ public class ProviderBillingService(
|
|||||||
throw new BillingException();
|
throw new BillingException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var priceId = ProviderPriceAdapter.GetActivePriceId(provider, providerPlan.PlanType);
|
||||||
|
|
||||||
subscriptionItemOptionsList.Add(new SubscriptionItemOptions
|
subscriptionItemOptionsList.Add(new SubscriptionItemOptions
|
||||||
{
|
{
|
||||||
Price = plan.PasswordManager.StripeProviderPortalSeatPlanId,
|
Price = priceId,
|
||||||
Quantity = providerPlan.SeatMinimum
|
Quantity = providerPlan.SeatMinimum
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -654,43 +644,37 @@ public class ProviderBillingService(
|
|||||||
|
|
||||||
public async Task UpdateSeatMinimums(UpdateProviderSeatMinimumsCommand command)
|
public async Task UpdateSeatMinimums(UpdateProviderSeatMinimumsCommand command)
|
||||||
{
|
{
|
||||||
if (command.Configuration.Any(x => x.SeatsMinimum < 0))
|
var (provider, updatedPlanConfigurations) = command;
|
||||||
|
|
||||||
|
if (updatedPlanConfigurations.Any(x => x.SeatsMinimum < 0))
|
||||||
{
|
{
|
||||||
throw new BadRequestException("Provider seat minimums must be at least 0.");
|
throw new BadRequestException("Provider seat minimums must be at least 0.");
|
||||||
}
|
}
|
||||||
|
|
||||||
Subscription subscription;
|
var subscription = await subscriberService.GetSubscriptionOrThrow(provider);
|
||||||
try
|
|
||||||
{
|
|
||||||
subscription = await stripeAdapter.ProviderSubscriptionGetAsync(command.GatewaySubscriptionId, command.Id);
|
|
||||||
}
|
|
||||||
catch (InvalidOperationException)
|
|
||||||
{
|
|
||||||
throw new ConflictException("Subscription not found.");
|
|
||||||
}
|
|
||||||
|
|
||||||
var subscriptionItemOptionsList = new List<SubscriptionItemOptions>();
|
var subscriptionItemOptionsList = new List<SubscriptionItemOptions>();
|
||||||
|
|
||||||
var providerPlans = await providerPlanRepository.GetByProviderId(command.Id);
|
var providerPlans = await providerPlanRepository.GetByProviderId(provider.Id);
|
||||||
|
|
||||||
foreach (var newPlanConfiguration in command.Configuration)
|
foreach (var updatedPlanConfiguration in updatedPlanConfigurations)
|
||||||
{
|
{
|
||||||
|
var (updatedPlanType, updatedSeatMinimum) = updatedPlanConfiguration;
|
||||||
|
|
||||||
var providerPlan =
|
var providerPlan =
|
||||||
providerPlans.Single(providerPlan => providerPlan.PlanType == newPlanConfiguration.Plan);
|
providerPlans.Single(providerPlan => providerPlan.PlanType == updatedPlanType);
|
||||||
|
|
||||||
if (providerPlan.SeatMinimum != newPlanConfiguration.SeatsMinimum)
|
if (providerPlan.SeatMinimum != updatedSeatMinimum)
|
||||||
{
|
{
|
||||||
var newPlan = await pricingClient.GetPlanOrThrow(newPlanConfiguration.Plan);
|
var priceId = ProviderPriceAdapter.GetPriceId(provider, subscription, updatedPlanType);
|
||||||
|
|
||||||
var priceId = newPlan.PasswordManager.StripeProviderPortalSeatPlanId;
|
|
||||||
|
|
||||||
var subscriptionItem = subscription.Items.First(item => item.Price.Id == priceId);
|
var subscriptionItem = subscription.Items.First(item => item.Price.Id == priceId);
|
||||||
|
|
||||||
if (providerPlan.PurchasedSeats == 0)
|
if (providerPlan.PurchasedSeats == 0)
|
||||||
{
|
{
|
||||||
if (providerPlan.AllocatedSeats > newPlanConfiguration.SeatsMinimum)
|
if (providerPlan.AllocatedSeats > updatedSeatMinimum)
|
||||||
{
|
{
|
||||||
providerPlan.PurchasedSeats = providerPlan.AllocatedSeats - newPlanConfiguration.SeatsMinimum;
|
providerPlan.PurchasedSeats = providerPlan.AllocatedSeats - updatedSeatMinimum;
|
||||||
|
|
||||||
subscriptionItemOptionsList.Add(new SubscriptionItemOptions
|
subscriptionItemOptionsList.Add(new SubscriptionItemOptions
|
||||||
{
|
{
|
||||||
@ -705,7 +689,7 @@ public class ProviderBillingService(
|
|||||||
{
|
{
|
||||||
Id = subscriptionItem.Id,
|
Id = subscriptionItem.Id,
|
||||||
Price = priceId,
|
Price = priceId,
|
||||||
Quantity = newPlanConfiguration.SeatsMinimum
|
Quantity = updatedSeatMinimum
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -713,9 +697,9 @@ public class ProviderBillingService(
|
|||||||
{
|
{
|
||||||
var totalSeats = providerPlan.SeatMinimum + providerPlan.PurchasedSeats;
|
var totalSeats = providerPlan.SeatMinimum + providerPlan.PurchasedSeats;
|
||||||
|
|
||||||
if (newPlanConfiguration.SeatsMinimum <= totalSeats)
|
if (updatedSeatMinimum <= totalSeats)
|
||||||
{
|
{
|
||||||
providerPlan.PurchasedSeats = totalSeats - newPlanConfiguration.SeatsMinimum;
|
providerPlan.PurchasedSeats = totalSeats - updatedSeatMinimum;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -724,12 +708,12 @@ public class ProviderBillingService(
|
|||||||
{
|
{
|
||||||
Id = subscriptionItem.Id,
|
Id = subscriptionItem.Id,
|
||||||
Price = priceId,
|
Price = priceId,
|
||||||
Quantity = newPlanConfiguration.SeatsMinimum
|
Quantity = updatedSeatMinimum
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
providerPlan.SeatMinimum = newPlanConfiguration.SeatsMinimum;
|
providerPlan.SeatMinimum = updatedSeatMinimum;
|
||||||
|
|
||||||
await providerPlanRepository.ReplaceAsync(providerPlan);
|
await providerPlanRepository.ReplaceAsync(providerPlan);
|
||||||
}
|
}
|
||||||
@ -737,23 +721,33 @@ public class ProviderBillingService(
|
|||||||
|
|
||||||
if (subscriptionItemOptionsList.Count > 0)
|
if (subscriptionItemOptionsList.Count > 0)
|
||||||
{
|
{
|
||||||
await stripeAdapter.SubscriptionUpdateAsync(command.GatewaySubscriptionId,
|
await stripeAdapter.SubscriptionUpdateAsync(provider.GatewaySubscriptionId,
|
||||||
new SubscriptionUpdateOptions { Items = subscriptionItemOptionsList });
|
new SubscriptionUpdateOptions { Items = subscriptionItemOptionsList });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Func<int, int, Task> CurrySeatScalingUpdate(
|
private Func<int, Task> CurrySeatScalingUpdate(
|
||||||
Provider provider,
|
Provider provider,
|
||||||
ProviderPlan providerPlan,
|
ProviderPlan providerPlan,
|
||||||
int newlyAssignedSeats) => async (currentlySubscribedSeats, newlySubscribedSeats) =>
|
int newlyAssignedSeats) => async newlySubscribedSeats =>
|
||||||
{
|
{
|
||||||
var plan = await pricingClient.GetPlanOrThrow(providerPlan.PlanType);
|
var subscription = await subscriberService.GetSubscriptionOrThrow(provider);
|
||||||
|
|
||||||
await paymentService.AdjustSeats(
|
var priceId = ProviderPriceAdapter.GetPriceId(provider, subscription, providerPlan.PlanType);
|
||||||
provider,
|
|
||||||
plan,
|
var item = subscription.Items.First(item => item.Price.Id == priceId);
|
||||||
currentlySubscribedSeats,
|
|
||||||
newlySubscribedSeats);
|
await stripeAdapter.SubscriptionUpdateAsync(provider.GatewaySubscriptionId, new SubscriptionUpdateOptions
|
||||||
|
{
|
||||||
|
Items = [
|
||||||
|
new SubscriptionItemOptions
|
||||||
|
{
|
||||||
|
Id = item.Id,
|
||||||
|
Price = priceId,
|
||||||
|
Quantity = newlySubscribedSeats
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
var newlyPurchasedSeats = newlySubscribedSeats > providerPlan.SeatMinimum
|
var newlyPurchasedSeats = newlySubscribedSeats > providerPlan.SeatMinimum
|
||||||
? newlySubscribedSeats - providerPlan.SeatMinimum
|
? newlySubscribedSeats - providerPlan.SeatMinimum
|
||||||
|
@ -0,0 +1,133 @@
|
|||||||
|
// ReSharper disable SwitchExpressionHandlesSomeKnownEnumValuesWithExceptionInDefault
|
||||||
|
#nullable enable
|
||||||
|
using Bit.Core.AdminConsole.Entities.Provider;
|
||||||
|
using Bit.Core.AdminConsole.Enums.Provider;
|
||||||
|
using Bit.Core.Billing;
|
||||||
|
using Bit.Core.Billing.Enums;
|
||||||
|
using Stripe;
|
||||||
|
|
||||||
|
namespace Bit.Commercial.Core.Billing;
|
||||||
|
|
||||||
|
public static class ProviderPriceAdapter
|
||||||
|
{
|
||||||
|
public static class MSP
|
||||||
|
{
|
||||||
|
public static class Active
|
||||||
|
{
|
||||||
|
public const string Enterprise = "provider-portal-enterprise-monthly-2025";
|
||||||
|
public const string Teams = "provider-portal-teams-monthly-2025";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Legacy
|
||||||
|
{
|
||||||
|
public const string Enterprise = "password-manager-provider-portal-enterprise-monthly-2024";
|
||||||
|
public const string Teams = "password-manager-provider-portal-teams-monthly-2024";
|
||||||
|
public static readonly List<string> List = [Enterprise, Teams];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class BusinessUnit
|
||||||
|
{
|
||||||
|
public static class Active
|
||||||
|
{
|
||||||
|
public const string Annually = "business-unit-portal-enterprise-annually-2025";
|
||||||
|
public const string Monthly = "business-unit-portal-enterprise-monthly-2025";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Legacy
|
||||||
|
{
|
||||||
|
public const string Annually = "password-manager-provider-portal-enterprise-annually-2024";
|
||||||
|
public const string Monthly = "password-manager-provider-portal-enterprise-monthly-2024";
|
||||||
|
public static readonly List<string> List = [Annually, Monthly];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Uses the <paramref name="provider"/>'s <see cref="Provider.Type"/> and <paramref name="subscription"/> to determine
|
||||||
|
/// whether the <paramref name="provider"/> is on active or legacy pricing and then returns a Stripe price ID for the provided
|
||||||
|
/// <paramref name="planType"/> based on that determination.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="provider">The provider to get the Stripe price ID for.</param>
|
||||||
|
/// <param name="subscription">The provider's subscription.</param>
|
||||||
|
/// <param name="planType">The plan type correlating to the desired Stripe price ID.</param>
|
||||||
|
/// <returns>A Stripe <see cref="Stripe.Price"/> ID.</returns>
|
||||||
|
/// <exception cref="BillingException">Thrown when the provider's type is not <see cref="ProviderType.Msp"/> or <see cref="ProviderType.MultiOrganizationEnterprise"/>.</exception>
|
||||||
|
/// <exception cref="BillingException">Thrown when the provided <see cref="planType"/> does not relate to a Stripe price ID.</exception>
|
||||||
|
public static string GetPriceId(
|
||||||
|
Provider provider,
|
||||||
|
Subscription subscription,
|
||||||
|
PlanType planType)
|
||||||
|
{
|
||||||
|
var priceIds = subscription.Items.Select(item => item.Price.Id);
|
||||||
|
|
||||||
|
var invalidPlanType =
|
||||||
|
new BillingException(message: $"PlanType {planType} does not have an associated provider price in Stripe");
|
||||||
|
|
||||||
|
return provider.Type switch
|
||||||
|
{
|
||||||
|
ProviderType.Msp => MSP.Legacy.List.Intersect(priceIds).Any()
|
||||||
|
? planType switch
|
||||||
|
{
|
||||||
|
PlanType.TeamsMonthly => MSP.Legacy.Teams,
|
||||||
|
PlanType.EnterpriseMonthly => MSP.Legacy.Enterprise,
|
||||||
|
_ => throw invalidPlanType
|
||||||
|
}
|
||||||
|
: planType switch
|
||||||
|
{
|
||||||
|
PlanType.TeamsMonthly => MSP.Active.Teams,
|
||||||
|
PlanType.EnterpriseMonthly => MSP.Active.Enterprise,
|
||||||
|
_ => throw invalidPlanType
|
||||||
|
},
|
||||||
|
ProviderType.MultiOrganizationEnterprise => BusinessUnit.Legacy.List.Intersect(priceIds).Any()
|
||||||
|
? planType switch
|
||||||
|
{
|
||||||
|
PlanType.EnterpriseAnnually => BusinessUnit.Legacy.Annually,
|
||||||
|
PlanType.EnterpriseMonthly => BusinessUnit.Legacy.Monthly,
|
||||||
|
_ => throw invalidPlanType
|
||||||
|
}
|
||||||
|
: planType switch
|
||||||
|
{
|
||||||
|
PlanType.EnterpriseAnnually => BusinessUnit.Active.Annually,
|
||||||
|
PlanType.EnterpriseMonthly => BusinessUnit.Active.Monthly,
|
||||||
|
_ => throw invalidPlanType
|
||||||
|
},
|
||||||
|
_ => throw new BillingException(
|
||||||
|
$"ProviderType {provider.Type} does not have any associated provider price IDs")
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Uses the <paramref name="provider"/>'s <see cref="Provider.Type"/> to return the active Stripe price ID for the provided
|
||||||
|
/// <paramref name="planType"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="provider">The provider to get the Stripe price ID for.</param>
|
||||||
|
/// <param name="planType">The plan type correlating to the desired Stripe price ID.</param>
|
||||||
|
/// <returns>A Stripe <see cref="Stripe.Price"/> ID.</returns>
|
||||||
|
/// <exception cref="BillingException">Thrown when the provider's type is not <see cref="ProviderType.Msp"/> or <see cref="ProviderType.MultiOrganizationEnterprise"/>.</exception>
|
||||||
|
/// <exception cref="BillingException">Thrown when the provided <see cref="planType"/> does not relate to a Stripe price ID.</exception>
|
||||||
|
public static string GetActivePriceId(
|
||||||
|
Provider provider,
|
||||||
|
PlanType planType)
|
||||||
|
{
|
||||||
|
var invalidPlanType =
|
||||||
|
new BillingException(message: $"PlanType {planType} does not have an associated provider price in Stripe");
|
||||||
|
|
||||||
|
return provider.Type switch
|
||||||
|
{
|
||||||
|
ProviderType.Msp => planType switch
|
||||||
|
{
|
||||||
|
PlanType.TeamsMonthly => MSP.Active.Teams,
|
||||||
|
PlanType.EnterpriseMonthly => MSP.Active.Enterprise,
|
||||||
|
_ => throw invalidPlanType
|
||||||
|
},
|
||||||
|
ProviderType.MultiOrganizationEnterprise => planType switch
|
||||||
|
{
|
||||||
|
PlanType.EnterpriseAnnually => BusinessUnit.Active.Annually,
|
||||||
|
PlanType.EnterpriseMonthly => BusinessUnit.Active.Monthly,
|
||||||
|
_ => throw invalidPlanType
|
||||||
|
},
|
||||||
|
_ => throw new BillingException(
|
||||||
|
$"ProviderType {provider.Type} does not have any associated provider price IDs")
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@ -4,6 +4,7 @@ using Bit.Commercial.Core.Billing;
|
|||||||
using Bit.Commercial.Core.Billing.Models;
|
using Bit.Commercial.Core.Billing.Models;
|
||||||
using Bit.Core.AdminConsole.Entities;
|
using Bit.Core.AdminConsole.Entities;
|
||||||
using Bit.Core.AdminConsole.Entities.Provider;
|
using Bit.Core.AdminConsole.Entities.Provider;
|
||||||
|
using Bit.Core.AdminConsole.Enums.Provider;
|
||||||
using Bit.Core.AdminConsole.Models.Data.Provider;
|
using Bit.Core.AdminConsole.Models.Data.Provider;
|
||||||
using Bit.Core.AdminConsole.Repositories;
|
using Bit.Core.AdminConsole.Repositories;
|
||||||
using Bit.Core.Billing.Constants;
|
using Bit.Core.Billing.Constants;
|
||||||
@ -115,6 +116,8 @@ public class ProviderBillingServiceTests
|
|||||||
SutProvider<ProviderBillingService> sutProvider)
|
SutProvider<ProviderBillingService> sutProvider)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
|
provider.Type = ProviderType.MultiOrganizationEnterprise;
|
||||||
|
|
||||||
var providerPlanRepository = sutProvider.GetDependency<IProviderPlanRepository>();
|
var providerPlanRepository = sutProvider.GetDependency<IProviderPlanRepository>();
|
||||||
var existingPlan = new ProviderPlan
|
var existingPlan = new ProviderPlan
|
||||||
{
|
{
|
||||||
@ -132,10 +135,7 @@ public class ProviderBillingServiceTests
|
|||||||
sutProvider.GetDependency<IPricingClient>().GetPlanOrThrow(existingPlan.PlanType)
|
sutProvider.GetDependency<IPricingClient>().GetPlanOrThrow(existingPlan.PlanType)
|
||||||
.Returns(StaticStore.GetPlan(existingPlan.PlanType));
|
.Returns(StaticStore.GetPlan(existingPlan.PlanType));
|
||||||
|
|
||||||
var stripeAdapter = sutProvider.GetDependency<IStripeAdapter>();
|
sutProvider.GetDependency<ISubscriberService>().GetSubscriptionOrThrow(provider)
|
||||||
stripeAdapter.ProviderSubscriptionGetAsync(
|
|
||||||
Arg.Is(provider.GatewaySubscriptionId),
|
|
||||||
Arg.Is(provider.Id))
|
|
||||||
.Returns(new Subscription
|
.Returns(new Subscription
|
||||||
{
|
{
|
||||||
Id = provider.GatewaySubscriptionId,
|
Id = provider.GatewaySubscriptionId,
|
||||||
@ -158,7 +158,7 @@ public class ProviderBillingServiceTests
|
|||||||
});
|
});
|
||||||
|
|
||||||
var command =
|
var command =
|
||||||
new ChangeProviderPlanCommand(providerPlanId, PlanType.EnterpriseMonthly, provider.GatewaySubscriptionId);
|
new ChangeProviderPlanCommand(provider, providerPlanId, PlanType.EnterpriseMonthly);
|
||||||
|
|
||||||
sutProvider.GetDependency<IPricingClient>().GetPlanOrThrow(command.NewPlan)
|
sutProvider.GetDependency<IPricingClient>().GetPlanOrThrow(command.NewPlan)
|
||||||
.Returns(StaticStore.GetPlan(command.NewPlan));
|
.Returns(StaticStore.GetPlan(command.NewPlan));
|
||||||
@ -170,6 +170,8 @@ public class ProviderBillingServiceTests
|
|||||||
await providerPlanRepository.Received(1)
|
await providerPlanRepository.Received(1)
|
||||||
.ReplaceAsync(Arg.Is<ProviderPlan>(p => p.PlanType == PlanType.EnterpriseMonthly));
|
.ReplaceAsync(Arg.Is<ProviderPlan>(p => p.PlanType == PlanType.EnterpriseMonthly));
|
||||||
|
|
||||||
|
var stripeAdapter = sutProvider.GetDependency<IStripeAdapter>();
|
||||||
|
|
||||||
await stripeAdapter.Received(1)
|
await stripeAdapter.Received(1)
|
||||||
.SubscriptionUpdateAsync(
|
.SubscriptionUpdateAsync(
|
||||||
Arg.Is(provider.GatewaySubscriptionId),
|
Arg.Is(provider.GatewaySubscriptionId),
|
||||||
@ -405,6 +407,23 @@ public class ProviderBillingServiceTests
|
|||||||
|
|
||||||
sutProvider.GetDependency<IProviderPlanRepository>().GetByProviderId(provider.Id).Returns(providerPlans);
|
sutProvider.GetDependency<IProviderPlanRepository>().GetByProviderId(provider.Id).Returns(providerPlans);
|
||||||
|
|
||||||
|
var subscription = new Subscription
|
||||||
|
{
|
||||||
|
Items = new StripeList<SubscriptionItem>
|
||||||
|
{
|
||||||
|
Data =
|
||||||
|
[
|
||||||
|
new SubscriptionItem { Price = new Price { Id = ProviderPriceAdapter.MSP.Active.Teams } },
|
||||||
|
new SubscriptionItem
|
||||||
|
{
|
||||||
|
Price = new Price { Id = ProviderPriceAdapter.MSP.Active.Enterprise }
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
sutProvider.GetDependency<ISubscriberService>().GetSubscriptionOrThrow(provider).Returns(subscription);
|
||||||
|
|
||||||
// 50 seats currently assigned with a seat minimum of 100
|
// 50 seats currently assigned with a seat minimum of 100
|
||||||
var teamsMonthlyPlan = StaticStore.GetPlan(PlanType.TeamsMonthly);
|
var teamsMonthlyPlan = StaticStore.GetPlan(PlanType.TeamsMonthly);
|
||||||
|
|
||||||
@ -427,11 +446,9 @@ public class ProviderBillingServiceTests
|
|||||||
await sutProvider.Sut.ScaleSeats(provider, PlanType.TeamsMonthly, 10);
|
await sutProvider.Sut.ScaleSeats(provider, PlanType.TeamsMonthly, 10);
|
||||||
|
|
||||||
// 50 assigned seats + 10 seat scale up = 60 seats, well below the 100 minimum
|
// 50 assigned seats + 10 seat scale up = 60 seats, well below the 100 minimum
|
||||||
await sutProvider.GetDependency<IPaymentService>().DidNotReceiveWithAnyArgs().AdjustSeats(
|
await sutProvider.GetDependency<IStripeAdapter>().DidNotReceiveWithAnyArgs().SubscriptionUpdateAsync(
|
||||||
Arg.Any<Provider>(),
|
Arg.Any<string>(),
|
||||||
Arg.Any<Bit.Core.Models.StaticStore.Plan>(),
|
Arg.Any<SubscriptionUpdateOptions>());
|
||||||
Arg.Any<int>(),
|
|
||||||
Arg.Any<int>());
|
|
||||||
|
|
||||||
await sutProvider.GetDependency<IProviderPlanRepository>().Received(1).ReplaceAsync(Arg.Is<ProviderPlan>(
|
await sutProvider.GetDependency<IProviderPlanRepository>().Received(1).ReplaceAsync(Arg.Is<ProviderPlan>(
|
||||||
pPlan => pPlan.AllocatedSeats == 60));
|
pPlan => pPlan.AllocatedSeats == 60));
|
||||||
@ -474,6 +491,23 @@ public class ProviderBillingServiceTests
|
|||||||
|
|
||||||
sutProvider.GetDependency<IProviderPlanRepository>().GetByProviderId(provider.Id).Returns(providerPlans);
|
sutProvider.GetDependency<IProviderPlanRepository>().GetByProviderId(provider.Id).Returns(providerPlans);
|
||||||
|
|
||||||
|
var subscription = new Subscription
|
||||||
|
{
|
||||||
|
Items = new StripeList<SubscriptionItem>
|
||||||
|
{
|
||||||
|
Data =
|
||||||
|
[
|
||||||
|
new SubscriptionItem { Price = new Price { Id = ProviderPriceAdapter.MSP.Active.Teams } },
|
||||||
|
new SubscriptionItem
|
||||||
|
{
|
||||||
|
Price = new Price { Id = ProviderPriceAdapter.MSP.Active.Enterprise }
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
sutProvider.GetDependency<ISubscriberService>().GetSubscriptionOrThrow(provider).Returns(subscription);
|
||||||
|
|
||||||
// 95 seats currently assigned with a seat minimum of 100
|
// 95 seats currently assigned with a seat minimum of 100
|
||||||
var teamsMonthlyPlan = StaticStore.GetPlan(PlanType.TeamsMonthly);
|
var teamsMonthlyPlan = StaticStore.GetPlan(PlanType.TeamsMonthly);
|
||||||
|
|
||||||
@ -496,11 +530,12 @@ public class ProviderBillingServiceTests
|
|||||||
await sutProvider.Sut.ScaleSeats(provider, PlanType.TeamsMonthly, 10);
|
await sutProvider.Sut.ScaleSeats(provider, PlanType.TeamsMonthly, 10);
|
||||||
|
|
||||||
// 95 current + 10 seat scale = 105 seats, 5 above the minimum
|
// 95 current + 10 seat scale = 105 seats, 5 above the minimum
|
||||||
await sutProvider.GetDependency<IPaymentService>().Received(1).AdjustSeats(
|
await sutProvider.GetDependency<IStripeAdapter>().Received(1).SubscriptionUpdateAsync(
|
||||||
provider,
|
provider.GatewaySubscriptionId,
|
||||||
StaticStore.GetPlan(providerPlan.PlanType),
|
Arg.Is<SubscriptionUpdateOptions>(
|
||||||
providerPlan.SeatMinimum!.Value,
|
options =>
|
||||||
105);
|
options.Items.First().Price == ProviderPriceAdapter.MSP.Active.Teams &&
|
||||||
|
options.Items.First().Quantity == 105));
|
||||||
|
|
||||||
// 105 total seats - 100 minimum = 5 purchased seats
|
// 105 total seats - 100 minimum = 5 purchased seats
|
||||||
await sutProvider.GetDependency<IProviderPlanRepository>().Received(1).ReplaceAsync(Arg.Is<ProviderPlan>(
|
await sutProvider.GetDependency<IProviderPlanRepository>().Received(1).ReplaceAsync(Arg.Is<ProviderPlan>(
|
||||||
@ -544,6 +579,23 @@ public class ProviderBillingServiceTests
|
|||||||
|
|
||||||
sutProvider.GetDependency<IProviderPlanRepository>().GetByProviderId(provider.Id).Returns(providerPlans);
|
sutProvider.GetDependency<IProviderPlanRepository>().GetByProviderId(provider.Id).Returns(providerPlans);
|
||||||
|
|
||||||
|
var subscription = new Subscription
|
||||||
|
{
|
||||||
|
Items = new StripeList<SubscriptionItem>
|
||||||
|
{
|
||||||
|
Data =
|
||||||
|
[
|
||||||
|
new SubscriptionItem { Price = new Price { Id = ProviderPriceAdapter.MSP.Active.Teams } },
|
||||||
|
new SubscriptionItem
|
||||||
|
{
|
||||||
|
Price = new Price { Id = ProviderPriceAdapter.MSP.Active.Enterprise }
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
sutProvider.GetDependency<ISubscriberService>().GetSubscriptionOrThrow(provider).Returns(subscription);
|
||||||
|
|
||||||
// 110 seats currently assigned with a seat minimum of 100
|
// 110 seats currently assigned with a seat minimum of 100
|
||||||
var teamsMonthlyPlan = StaticStore.GetPlan(PlanType.TeamsMonthly);
|
var teamsMonthlyPlan = StaticStore.GetPlan(PlanType.TeamsMonthly);
|
||||||
|
|
||||||
@ -566,11 +618,12 @@ public class ProviderBillingServiceTests
|
|||||||
await sutProvider.Sut.ScaleSeats(provider, PlanType.TeamsMonthly, 10);
|
await sutProvider.Sut.ScaleSeats(provider, PlanType.TeamsMonthly, 10);
|
||||||
|
|
||||||
// 110 current + 10 seat scale up = 120 seats
|
// 110 current + 10 seat scale up = 120 seats
|
||||||
await sutProvider.GetDependency<IPaymentService>().Received(1).AdjustSeats(
|
await sutProvider.GetDependency<IStripeAdapter>().Received(1).SubscriptionUpdateAsync(
|
||||||
provider,
|
provider.GatewaySubscriptionId,
|
||||||
StaticStore.GetPlan(providerPlan.PlanType),
|
Arg.Is<SubscriptionUpdateOptions>(
|
||||||
110,
|
options =>
|
||||||
120);
|
options.Items.First().Price == ProviderPriceAdapter.MSP.Active.Teams &&
|
||||||
|
options.Items.First().Quantity == 120));
|
||||||
|
|
||||||
// 120 total seats - 100 seat minimum = 20 purchased seats
|
// 120 total seats - 100 seat minimum = 20 purchased seats
|
||||||
await sutProvider.GetDependency<IProviderPlanRepository>().Received(1).ReplaceAsync(Arg.Is<ProviderPlan>(
|
await sutProvider.GetDependency<IProviderPlanRepository>().Received(1).ReplaceAsync(Arg.Is<ProviderPlan>(
|
||||||
@ -614,6 +667,23 @@ public class ProviderBillingServiceTests
|
|||||||
|
|
||||||
sutProvider.GetDependency<IProviderPlanRepository>().GetByProviderId(provider.Id).Returns(providerPlans);
|
sutProvider.GetDependency<IProviderPlanRepository>().GetByProviderId(provider.Id).Returns(providerPlans);
|
||||||
|
|
||||||
|
var subscription = new Subscription
|
||||||
|
{
|
||||||
|
Items = new StripeList<SubscriptionItem>
|
||||||
|
{
|
||||||
|
Data =
|
||||||
|
[
|
||||||
|
new SubscriptionItem { Price = new Price { Id = ProviderPriceAdapter.MSP.Active.Teams } },
|
||||||
|
new SubscriptionItem
|
||||||
|
{
|
||||||
|
Price = new Price { Id = ProviderPriceAdapter.MSP.Active.Enterprise }
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
sutProvider.GetDependency<ISubscriberService>().GetSubscriptionOrThrow(provider).Returns(subscription);
|
||||||
|
|
||||||
// 110 seats currently assigned with a seat minimum of 100
|
// 110 seats currently assigned with a seat minimum of 100
|
||||||
var teamsMonthlyPlan = StaticStore.GetPlan(PlanType.TeamsMonthly);
|
var teamsMonthlyPlan = StaticStore.GetPlan(PlanType.TeamsMonthly);
|
||||||
|
|
||||||
@ -636,11 +706,12 @@ public class ProviderBillingServiceTests
|
|||||||
await sutProvider.Sut.ScaleSeats(provider, PlanType.TeamsMonthly, -30);
|
await sutProvider.Sut.ScaleSeats(provider, PlanType.TeamsMonthly, -30);
|
||||||
|
|
||||||
// 110 seats - 30 scale down seats = 80 seats, below the 100 seat minimum.
|
// 110 seats - 30 scale down seats = 80 seats, below the 100 seat minimum.
|
||||||
await sutProvider.GetDependency<IPaymentService>().Received(1).AdjustSeats(
|
await sutProvider.GetDependency<IStripeAdapter>().Received(1).SubscriptionUpdateAsync(
|
||||||
provider,
|
provider.GatewaySubscriptionId,
|
||||||
StaticStore.GetPlan(providerPlan.PlanType),
|
Arg.Is<SubscriptionUpdateOptions>(
|
||||||
110,
|
options =>
|
||||||
providerPlan.SeatMinimum!.Value);
|
options.Items.First().Price == ProviderPriceAdapter.MSP.Active.Teams &&
|
||||||
|
options.Items.First().Quantity == providerPlan.SeatMinimum!.Value));
|
||||||
|
|
||||||
// Being below the seat minimum means no purchased seats.
|
// Being below the seat minimum means no purchased seats.
|
||||||
await sutProvider.GetDependency<IProviderPlanRepository>().Received(1).ReplaceAsync(Arg.Is<ProviderPlan>(
|
await sutProvider.GetDependency<IProviderPlanRepository>().Received(1).ReplaceAsync(Arg.Is<ProviderPlan>(
|
||||||
@ -977,6 +1048,7 @@ public class ProviderBillingServiceTests
|
|||||||
SutProvider<ProviderBillingService> sutProvider,
|
SutProvider<ProviderBillingService> sutProvider,
|
||||||
Provider provider)
|
Provider provider)
|
||||||
{
|
{
|
||||||
|
provider.Type = ProviderType.Msp;
|
||||||
provider.GatewaySubscriptionId = null;
|
provider.GatewaySubscriptionId = null;
|
||||||
|
|
||||||
var customer = new Customer
|
var customer = new Customer
|
||||||
@ -1020,9 +1092,6 @@ public class ProviderBillingServiceTests
|
|||||||
sutProvider.GetDependency<IProviderPlanRepository>().GetByProviderId(provider.Id)
|
sutProvider.GetDependency<IProviderPlanRepository>().GetByProviderId(provider.Id)
|
||||||
.Returns(providerPlans);
|
.Returns(providerPlans);
|
||||||
|
|
||||||
var teamsPlan = StaticStore.GetPlan(PlanType.TeamsMonthly);
|
|
||||||
var enterprisePlan = StaticStore.GetPlan(PlanType.EnterpriseMonthly);
|
|
||||||
|
|
||||||
var expected = new Subscription { Id = "subscription_id", Status = StripeConstants.SubscriptionStatus.Active };
|
var expected = new Subscription { Id = "subscription_id", Status = StripeConstants.SubscriptionStatus.Active };
|
||||||
|
|
||||||
sutProvider.GetDependency<IAutomaticTaxStrategy>()
|
sutProvider.GetDependency<IAutomaticTaxStrategy>()
|
||||||
@ -1045,9 +1114,9 @@ public class ProviderBillingServiceTests
|
|||||||
sub.Customer == "customer_id" &&
|
sub.Customer == "customer_id" &&
|
||||||
sub.DaysUntilDue == 30 &&
|
sub.DaysUntilDue == 30 &&
|
||||||
sub.Items.Count == 2 &&
|
sub.Items.Count == 2 &&
|
||||||
sub.Items.ElementAt(0).Price == teamsPlan.PasswordManager.StripeProviderPortalSeatPlanId &&
|
sub.Items.ElementAt(0).Price == ProviderPriceAdapter.MSP.Active.Teams &&
|
||||||
sub.Items.ElementAt(0).Quantity == 100 &&
|
sub.Items.ElementAt(0).Quantity == 100 &&
|
||||||
sub.Items.ElementAt(1).Price == enterprisePlan.PasswordManager.StripeProviderPortalSeatPlanId &&
|
sub.Items.ElementAt(1).Price == ProviderPriceAdapter.MSP.Active.Enterprise &&
|
||||||
sub.Items.ElementAt(1).Quantity == 100 &&
|
sub.Items.ElementAt(1).Quantity == 100 &&
|
||||||
sub.Metadata["providerId"] == provider.Id.ToString() &&
|
sub.Metadata["providerId"] == provider.Id.ToString() &&
|
||||||
sub.OffSession == true &&
|
sub.OffSession == true &&
|
||||||
@ -1069,8 +1138,7 @@ public class ProviderBillingServiceTests
|
|||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var command = new UpdateProviderSeatMinimumsCommand(
|
var command = new UpdateProviderSeatMinimumsCommand(
|
||||||
provider.Id,
|
provider,
|
||||||
provider.GatewaySubscriptionId,
|
|
||||||
[
|
[
|
||||||
(PlanType.TeamsMonthly, -10),
|
(PlanType.TeamsMonthly, -10),
|
||||||
(PlanType.EnterpriseMonthly, 50)
|
(PlanType.EnterpriseMonthly, 50)
|
||||||
@ -1089,6 +1157,8 @@ public class ProviderBillingServiceTests
|
|||||||
SutProvider<ProviderBillingService> sutProvider)
|
SutProvider<ProviderBillingService> sutProvider)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
|
provider.Type = ProviderType.Msp;
|
||||||
|
|
||||||
var stripeAdapter = sutProvider.GetDependency<IStripeAdapter>();
|
var stripeAdapter = sutProvider.GetDependency<IStripeAdapter>();
|
||||||
var providerPlanRepository = sutProvider.GetDependency<IProviderPlanRepository>();
|
var providerPlanRepository = sutProvider.GetDependency<IProviderPlanRepository>();
|
||||||
|
|
||||||
@ -1118,9 +1188,7 @@ public class ProviderBillingServiceTests
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
stripeAdapter.ProviderSubscriptionGetAsync(
|
sutProvider.GetDependency<ISubscriberService>().GetSubscriptionOrThrow(provider).Returns(subscription);
|
||||||
provider.GatewaySubscriptionId,
|
|
||||||
provider.Id).Returns(subscription);
|
|
||||||
|
|
||||||
var providerPlans = new List<ProviderPlan>
|
var providerPlans = new List<ProviderPlan>
|
||||||
{
|
{
|
||||||
@ -1137,8 +1205,7 @@ public class ProviderBillingServiceTests
|
|||||||
providerPlanRepository.GetByProviderId(provider.Id).Returns(providerPlans);
|
providerPlanRepository.GetByProviderId(provider.Id).Returns(providerPlans);
|
||||||
|
|
||||||
var command = new UpdateProviderSeatMinimumsCommand(
|
var command = new UpdateProviderSeatMinimumsCommand(
|
||||||
provider.Id,
|
provider,
|
||||||
provider.GatewaySubscriptionId,
|
|
||||||
[
|
[
|
||||||
(PlanType.EnterpriseMonthly, 30),
|
(PlanType.EnterpriseMonthly, 30),
|
||||||
(PlanType.TeamsMonthly, 20)
|
(PlanType.TeamsMonthly, 20)
|
||||||
@ -1170,6 +1237,8 @@ public class ProviderBillingServiceTests
|
|||||||
SutProvider<ProviderBillingService> sutProvider)
|
SutProvider<ProviderBillingService> sutProvider)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
|
provider.Type = ProviderType.Msp;
|
||||||
|
|
||||||
var stripeAdapter = sutProvider.GetDependency<IStripeAdapter>();
|
var stripeAdapter = sutProvider.GetDependency<IStripeAdapter>();
|
||||||
var providerPlanRepository = sutProvider.GetDependency<IProviderPlanRepository>();
|
var providerPlanRepository = sutProvider.GetDependency<IProviderPlanRepository>();
|
||||||
|
|
||||||
@ -1199,7 +1268,7 @@ public class ProviderBillingServiceTests
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
stripeAdapter.ProviderSubscriptionGetAsync(provider.GatewaySubscriptionId, provider.Id).Returns(subscription);
|
sutProvider.GetDependency<ISubscriberService>().GetSubscriptionOrThrow(provider).Returns(subscription);
|
||||||
|
|
||||||
var providerPlans = new List<ProviderPlan>
|
var providerPlans = new List<ProviderPlan>
|
||||||
{
|
{
|
||||||
@ -1216,8 +1285,7 @@ public class ProviderBillingServiceTests
|
|||||||
providerPlanRepository.GetByProviderId(provider.Id).Returns(providerPlans);
|
providerPlanRepository.GetByProviderId(provider.Id).Returns(providerPlans);
|
||||||
|
|
||||||
var command = new UpdateProviderSeatMinimumsCommand(
|
var command = new UpdateProviderSeatMinimumsCommand(
|
||||||
provider.Id,
|
provider,
|
||||||
provider.GatewaySubscriptionId,
|
|
||||||
[
|
[
|
||||||
(PlanType.EnterpriseMonthly, 70),
|
(PlanType.EnterpriseMonthly, 70),
|
||||||
(PlanType.TeamsMonthly, 50)
|
(PlanType.TeamsMonthly, 50)
|
||||||
@ -1249,6 +1317,8 @@ public class ProviderBillingServiceTests
|
|||||||
SutProvider<ProviderBillingService> sutProvider)
|
SutProvider<ProviderBillingService> sutProvider)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
|
provider.Type = ProviderType.Msp;
|
||||||
|
|
||||||
var stripeAdapter = sutProvider.GetDependency<IStripeAdapter>();
|
var stripeAdapter = sutProvider.GetDependency<IStripeAdapter>();
|
||||||
var providerPlanRepository = sutProvider.GetDependency<IProviderPlanRepository>();
|
var providerPlanRepository = sutProvider.GetDependency<IProviderPlanRepository>();
|
||||||
|
|
||||||
@ -1278,7 +1348,7 @@ public class ProviderBillingServiceTests
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
stripeAdapter.ProviderSubscriptionGetAsync(provider.GatewaySubscriptionId, provider.Id).Returns(subscription);
|
sutProvider.GetDependency<ISubscriberService>().GetSubscriptionOrThrow(provider).Returns(subscription);
|
||||||
|
|
||||||
var providerPlans = new List<ProviderPlan>
|
var providerPlans = new List<ProviderPlan>
|
||||||
{
|
{
|
||||||
@ -1295,8 +1365,7 @@ public class ProviderBillingServiceTests
|
|||||||
providerPlanRepository.GetByProviderId(provider.Id).Returns(providerPlans);
|
providerPlanRepository.GetByProviderId(provider.Id).Returns(providerPlans);
|
||||||
|
|
||||||
var command = new UpdateProviderSeatMinimumsCommand(
|
var command = new UpdateProviderSeatMinimumsCommand(
|
||||||
provider.Id,
|
provider,
|
||||||
provider.GatewaySubscriptionId,
|
|
||||||
[
|
[
|
||||||
(PlanType.EnterpriseMonthly, 60),
|
(PlanType.EnterpriseMonthly, 60),
|
||||||
(PlanType.TeamsMonthly, 60)
|
(PlanType.TeamsMonthly, 60)
|
||||||
@ -1322,6 +1391,8 @@ public class ProviderBillingServiceTests
|
|||||||
SutProvider<ProviderBillingService> sutProvider)
|
SutProvider<ProviderBillingService> sutProvider)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
|
provider.Type = ProviderType.Msp;
|
||||||
|
|
||||||
var stripeAdapter = sutProvider.GetDependency<IStripeAdapter>();
|
var stripeAdapter = sutProvider.GetDependency<IStripeAdapter>();
|
||||||
var providerPlanRepository = sutProvider.GetDependency<IProviderPlanRepository>();
|
var providerPlanRepository = sutProvider.GetDependency<IProviderPlanRepository>();
|
||||||
|
|
||||||
@ -1351,7 +1422,7 @@ public class ProviderBillingServiceTests
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
stripeAdapter.ProviderSubscriptionGetAsync(provider.GatewaySubscriptionId, provider.Id).Returns(subscription);
|
sutProvider.GetDependency<ISubscriberService>().GetSubscriptionOrThrow(provider).Returns(subscription);
|
||||||
|
|
||||||
var providerPlans = new List<ProviderPlan>
|
var providerPlans = new List<ProviderPlan>
|
||||||
{
|
{
|
||||||
@ -1368,8 +1439,7 @@ public class ProviderBillingServiceTests
|
|||||||
providerPlanRepository.GetByProviderId(provider.Id).Returns(providerPlans);
|
providerPlanRepository.GetByProviderId(provider.Id).Returns(providerPlans);
|
||||||
|
|
||||||
var command = new UpdateProviderSeatMinimumsCommand(
|
var command = new UpdateProviderSeatMinimumsCommand(
|
||||||
provider.Id,
|
provider,
|
||||||
provider.GatewaySubscriptionId,
|
|
||||||
[
|
[
|
||||||
(PlanType.EnterpriseMonthly, 80),
|
(PlanType.EnterpriseMonthly, 80),
|
||||||
(PlanType.TeamsMonthly, 80)
|
(PlanType.TeamsMonthly, 80)
|
||||||
@ -1401,6 +1471,8 @@ public class ProviderBillingServiceTests
|
|||||||
SutProvider<ProviderBillingService> sutProvider)
|
SutProvider<ProviderBillingService> sutProvider)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
|
provider.Type = ProviderType.Msp;
|
||||||
|
|
||||||
var stripeAdapter = sutProvider.GetDependency<IStripeAdapter>();
|
var stripeAdapter = sutProvider.GetDependency<IStripeAdapter>();
|
||||||
var providerPlanRepository = sutProvider.GetDependency<IProviderPlanRepository>();
|
var providerPlanRepository = sutProvider.GetDependency<IProviderPlanRepository>();
|
||||||
|
|
||||||
@ -1430,7 +1502,7 @@ public class ProviderBillingServiceTests
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
stripeAdapter.ProviderSubscriptionGetAsync(provider.GatewaySubscriptionId, provider.Id).Returns(subscription);
|
sutProvider.GetDependency<ISubscriberService>().GetSubscriptionOrThrow(provider).Returns(subscription);
|
||||||
|
|
||||||
var providerPlans = new List<ProviderPlan>
|
var providerPlans = new List<ProviderPlan>
|
||||||
{
|
{
|
||||||
@ -1447,8 +1519,7 @@ public class ProviderBillingServiceTests
|
|||||||
providerPlanRepository.GetByProviderId(provider.Id).Returns(providerPlans);
|
providerPlanRepository.GetByProviderId(provider.Id).Returns(providerPlans);
|
||||||
|
|
||||||
var command = new UpdateProviderSeatMinimumsCommand(
|
var command = new UpdateProviderSeatMinimumsCommand(
|
||||||
provider.Id,
|
provider,
|
||||||
provider.GatewaySubscriptionId,
|
|
||||||
[
|
[
|
||||||
(PlanType.EnterpriseMonthly, 70),
|
(PlanType.EnterpriseMonthly, 70),
|
||||||
(PlanType.TeamsMonthly, 30)
|
(PlanType.TeamsMonthly, 30)
|
||||||
|
@ -0,0 +1,151 @@
|
|||||||
|
using Bit.Commercial.Core.Billing;
|
||||||
|
using Bit.Core.AdminConsole.Entities.Provider;
|
||||||
|
using Bit.Core.AdminConsole.Enums.Provider;
|
||||||
|
using Bit.Core.Billing.Enums;
|
||||||
|
using Stripe;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Bit.Commercial.Core.Test.Billing;
|
||||||
|
|
||||||
|
public class ProviderPriceAdapterTests
|
||||||
|
{
|
||||||
|
[Theory]
|
||||||
|
[InlineData("password-manager-provider-portal-enterprise-monthly-2024", PlanType.EnterpriseMonthly)]
|
||||||
|
[InlineData("password-manager-provider-portal-teams-monthly-2024", PlanType.TeamsMonthly)]
|
||||||
|
public void GetPriceId_MSP_Legacy_Succeeds(string priceId, PlanType planType)
|
||||||
|
{
|
||||||
|
var provider = new Provider
|
||||||
|
{
|
||||||
|
Id = Guid.NewGuid(),
|
||||||
|
Type = ProviderType.Msp
|
||||||
|
};
|
||||||
|
|
||||||
|
var subscription = new Subscription
|
||||||
|
{
|
||||||
|
Items = new StripeList<SubscriptionItem>
|
||||||
|
{
|
||||||
|
Data =
|
||||||
|
[
|
||||||
|
new SubscriptionItem { Price = new Price { Id = priceId } }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = ProviderPriceAdapter.GetPriceId(provider, subscription, planType);
|
||||||
|
|
||||||
|
Assert.Equal(result, priceId);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("provider-portal-enterprise-monthly-2025", PlanType.EnterpriseMonthly)]
|
||||||
|
[InlineData("provider-portal-teams-monthly-2025", PlanType.TeamsMonthly)]
|
||||||
|
public void GetPriceId_MSP_Active_Succeeds(string priceId, PlanType planType)
|
||||||
|
{
|
||||||
|
var provider = new Provider
|
||||||
|
{
|
||||||
|
Id = Guid.NewGuid(),
|
||||||
|
Type = ProviderType.Msp
|
||||||
|
};
|
||||||
|
|
||||||
|
var subscription = new Subscription
|
||||||
|
{
|
||||||
|
Items = new StripeList<SubscriptionItem>
|
||||||
|
{
|
||||||
|
Data =
|
||||||
|
[
|
||||||
|
new SubscriptionItem { Price = new Price { Id = priceId } }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = ProviderPriceAdapter.GetPriceId(provider, subscription, planType);
|
||||||
|
|
||||||
|
Assert.Equal(result, priceId);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("password-manager-provider-portal-enterprise-annually-2024", PlanType.EnterpriseAnnually)]
|
||||||
|
[InlineData("password-manager-provider-portal-enterprise-monthly-2024", PlanType.EnterpriseMonthly)]
|
||||||
|
public void GetPriceId_BusinessUnit_Legacy_Succeeds(string priceId, PlanType planType)
|
||||||
|
{
|
||||||
|
var provider = new Provider
|
||||||
|
{
|
||||||
|
Id = Guid.NewGuid(),
|
||||||
|
Type = ProviderType.MultiOrganizationEnterprise
|
||||||
|
};
|
||||||
|
|
||||||
|
var subscription = new Subscription
|
||||||
|
{
|
||||||
|
Items = new StripeList<SubscriptionItem>
|
||||||
|
{
|
||||||
|
Data =
|
||||||
|
[
|
||||||
|
new SubscriptionItem { Price = new Price { Id = priceId } }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = ProviderPriceAdapter.GetPriceId(provider, subscription, planType);
|
||||||
|
|
||||||
|
Assert.Equal(result, priceId);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("business-unit-portal-enterprise-annually-2025", PlanType.EnterpriseAnnually)]
|
||||||
|
[InlineData("business-unit-portal-enterprise-monthly-2025", PlanType.EnterpriseMonthly)]
|
||||||
|
public void GetPriceId_BusinessUnit_Active_Succeeds(string priceId, PlanType planType)
|
||||||
|
{
|
||||||
|
var provider = new Provider
|
||||||
|
{
|
||||||
|
Id = Guid.NewGuid(),
|
||||||
|
Type = ProviderType.MultiOrganizationEnterprise
|
||||||
|
};
|
||||||
|
|
||||||
|
var subscription = new Subscription
|
||||||
|
{
|
||||||
|
Items = new StripeList<SubscriptionItem>
|
||||||
|
{
|
||||||
|
Data =
|
||||||
|
[
|
||||||
|
new SubscriptionItem { Price = new Price { Id = priceId } }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = ProviderPriceAdapter.GetPriceId(provider, subscription, planType);
|
||||||
|
|
||||||
|
Assert.Equal(result, priceId);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("provider-portal-enterprise-monthly-2025", PlanType.EnterpriseMonthly)]
|
||||||
|
[InlineData("provider-portal-teams-monthly-2025", PlanType.TeamsMonthly)]
|
||||||
|
public void GetActivePriceId_MSP_Succeeds(string priceId, PlanType planType)
|
||||||
|
{
|
||||||
|
var provider = new Provider
|
||||||
|
{
|
||||||
|
Id = Guid.NewGuid(),
|
||||||
|
Type = ProviderType.Msp
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = ProviderPriceAdapter.GetActivePriceId(provider, planType);
|
||||||
|
|
||||||
|
Assert.Equal(result, priceId);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("business-unit-portal-enterprise-annually-2025", PlanType.EnterpriseAnnually)]
|
||||||
|
[InlineData("business-unit-portal-enterprise-monthly-2025", PlanType.EnterpriseMonthly)]
|
||||||
|
public void GetActivePriceId_BusinessUnit_Succeeds(string priceId, PlanType planType)
|
||||||
|
{
|
||||||
|
var provider = new Provider
|
||||||
|
{
|
||||||
|
Id = Guid.NewGuid(),
|
||||||
|
Type = ProviderType.MultiOrganizationEnterprise
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = ProviderPriceAdapter.GetActivePriceId(provider, planType);
|
||||||
|
|
||||||
|
Assert.Equal(result, priceId);
|
||||||
|
}
|
||||||
|
}
|
@ -300,8 +300,7 @@ public class ProvidersController : Controller
|
|||||||
{
|
{
|
||||||
case ProviderType.Msp:
|
case ProviderType.Msp:
|
||||||
var updateMspSeatMinimumsCommand = new UpdateProviderSeatMinimumsCommand(
|
var updateMspSeatMinimumsCommand = new UpdateProviderSeatMinimumsCommand(
|
||||||
provider.Id,
|
provider,
|
||||||
provider.GatewaySubscriptionId,
|
|
||||||
[
|
[
|
||||||
(Plan: PlanType.TeamsMonthly, SeatsMinimum: model.TeamsMonthlySeatMinimum),
|
(Plan: PlanType.TeamsMonthly, SeatsMinimum: model.TeamsMonthlySeatMinimum),
|
||||||
(Plan: PlanType.EnterpriseMonthly, SeatsMinimum: model.EnterpriseMonthlySeatMinimum)
|
(Plan: PlanType.EnterpriseMonthly, SeatsMinimum: model.EnterpriseMonthlySeatMinimum)
|
||||||
@ -314,15 +313,14 @@ public class ProvidersController : Controller
|
|||||||
|
|
||||||
// 1. Change the plan and take over any old values.
|
// 1. Change the plan and take over any old values.
|
||||||
var changeMoePlanCommand = new ChangeProviderPlanCommand(
|
var changeMoePlanCommand = new ChangeProviderPlanCommand(
|
||||||
|
provider,
|
||||||
existingMoePlan.Id,
|
existingMoePlan.Id,
|
||||||
model.Plan!.Value,
|
model.Plan!.Value);
|
||||||
provider.GatewaySubscriptionId);
|
|
||||||
await _providerBillingService.ChangePlan(changeMoePlanCommand);
|
await _providerBillingService.ChangePlan(changeMoePlanCommand);
|
||||||
|
|
||||||
// 2. Update the seat minimums.
|
// 2. Update the seat minimums.
|
||||||
var updateMoeSeatMinimumsCommand = new UpdateProviderSeatMinimumsCommand(
|
var updateMoeSeatMinimumsCommand = new UpdateProviderSeatMinimumsCommand(
|
||||||
provider.Id,
|
provider,
|
||||||
provider.GatewaySubscriptionId,
|
|
||||||
[
|
[
|
||||||
(Plan: model.Plan!.Value, SeatsMinimum: model.EnterpriseMinimumSeats!.Value)
|
(Plan: model.Plan!.Value, SeatsMinimum: model.EnterpriseMinimumSeats!.Value)
|
||||||
]);
|
]);
|
||||||
|
@ -309,8 +309,7 @@ public class ProviderMigrator(
|
|||||||
.SeatMinimum ?? 0;
|
.SeatMinimum ?? 0;
|
||||||
|
|
||||||
var updateSeatMinimumsCommand = new UpdateProviderSeatMinimumsCommand(
|
var updateSeatMinimumsCommand = new UpdateProviderSeatMinimumsCommand(
|
||||||
provider.Id,
|
provider,
|
||||||
provider.GatewaySubscriptionId,
|
|
||||||
[
|
[
|
||||||
(Plan: PlanType.EnterpriseMonthly, SeatsMinimum: enterpriseSeatMinimum),
|
(Plan: PlanType.EnterpriseMonthly, SeatsMinimum: enterpriseSeatMinimum),
|
||||||
(Plan: PlanType.TeamsMonthly, SeatsMinimum: teamsSeatMinimum)
|
(Plan: PlanType.TeamsMonthly, SeatsMinimum: teamsSeatMinimum)
|
||||||
|
@ -75,6 +75,7 @@ public abstract record Plan
|
|||||||
// Seats
|
// Seats
|
||||||
public string StripePlanId { get; init; }
|
public string StripePlanId { get; init; }
|
||||||
public string StripeSeatPlanId { get; init; }
|
public string StripeSeatPlanId { get; init; }
|
||||||
|
[Obsolete("No longer used to retrieve a provider's price ID. Use ProviderPriceAdapter instead.")]
|
||||||
public string StripeProviderPortalSeatPlanId { get; init; }
|
public string StripeProviderPortalSeatPlanId { get; init; }
|
||||||
public decimal BasePrice { get; init; }
|
public decimal BasePrice { get; init; }
|
||||||
public decimal SeatPrice { get; init; }
|
public decimal SeatPrice { get; init; }
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
using Bit.Core.Billing.Enums;
|
using Bit.Core.AdminConsole.Entities.Provider;
|
||||||
|
using Bit.Core.Billing.Enums;
|
||||||
|
|
||||||
namespace Bit.Core.Billing.Services.Contracts;
|
namespace Bit.Core.Billing.Services.Contracts;
|
||||||
|
|
||||||
public record ChangeProviderPlanCommand(
|
public record ChangeProviderPlanCommand(
|
||||||
|
Provider Provider,
|
||||||
Guid ProviderPlanId,
|
Guid ProviderPlanId,
|
||||||
PlanType NewPlan,
|
PlanType NewPlan);
|
||||||
string GatewaySubscriptionId);
|
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
using Bit.Core.Billing.Enums;
|
using Bit.Core.AdminConsole.Entities.Provider;
|
||||||
|
using Bit.Core.Billing.Enums;
|
||||||
|
|
||||||
namespace Bit.Core.Billing.Services.Contracts;
|
namespace Bit.Core.Billing.Services.Contracts;
|
||||||
|
|
||||||
/// <param name="Id">The ID of the provider to update the seat minimums for.</param>
|
/// <param name="Provider">The provider to update the seat minimums for.</param>
|
||||||
/// <param name="Configuration">The new seat minimums for the provider.</param>
|
/// <param name="Configuration">The new seat minimums for the provider.</param>
|
||||||
public record UpdateProviderSeatMinimumsCommand(
|
public record UpdateProviderSeatMinimumsCommand(
|
||||||
Guid Id,
|
Provider Provider,
|
||||||
string GatewaySubscriptionId,
|
|
||||||
IReadOnlyCollection<(PlanType Plan, int SeatsMinimum)> Configuration);
|
IReadOnlyCollection<(PlanType Plan, int SeatsMinimum)> Configuration);
|
||||||
|
@ -1,62 +0,0 @@
|
|||||||
using Bit.Core.Billing;
|
|
||||||
using Bit.Core.Billing.Enums;
|
|
||||||
using Bit.Core.Billing.Extensions;
|
|
||||||
using Stripe;
|
|
||||||
using Plan = Bit.Core.Models.StaticStore.Plan;
|
|
||||||
|
|
||||||
namespace Bit.Core.Models.Business;
|
|
||||||
|
|
||||||
public class ProviderSubscriptionUpdate : SubscriptionUpdate
|
|
||||||
{
|
|
||||||
private readonly string _planId;
|
|
||||||
private readonly int _previouslyPurchasedSeats;
|
|
||||||
private readonly int _newlyPurchasedSeats;
|
|
||||||
|
|
||||||
protected override List<string> PlanIds => [_planId];
|
|
||||||
|
|
||||||
public ProviderSubscriptionUpdate(
|
|
||||||
Plan plan,
|
|
||||||
int previouslyPurchasedSeats,
|
|
||||||
int newlyPurchasedSeats)
|
|
||||||
{
|
|
||||||
if (!plan.Type.SupportsConsolidatedBilling())
|
|
||||||
{
|
|
||||||
throw new BillingException(
|
|
||||||
message: $"Cannot create a {nameof(ProviderSubscriptionUpdate)} for {nameof(PlanType)} that doesn't support consolidated billing");
|
|
||||||
}
|
|
||||||
|
|
||||||
_planId = plan.PasswordManager.StripeProviderPortalSeatPlanId;
|
|
||||||
_previouslyPurchasedSeats = previouslyPurchasedSeats;
|
|
||||||
_newlyPurchasedSeats = newlyPurchasedSeats;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override List<SubscriptionItemOptions> RevertItemsOptions(Subscription subscription)
|
|
||||||
{
|
|
||||||
var subscriptionItem = FindSubscriptionItem(subscription, _planId);
|
|
||||||
|
|
||||||
return
|
|
||||||
[
|
|
||||||
new SubscriptionItemOptions
|
|
||||||
{
|
|
||||||
Id = subscriptionItem.Id,
|
|
||||||
Price = _planId,
|
|
||||||
Quantity = _previouslyPurchasedSeats
|
|
||||||
}
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
public override List<SubscriptionItemOptions> UpgradeItemsOptions(Subscription subscription)
|
|
||||||
{
|
|
||||||
var subscriptionItem = FindSubscriptionItem(subscription, _planId);
|
|
||||||
|
|
||||||
return
|
|
||||||
[
|
|
||||||
new SubscriptionItemOptions
|
|
||||||
{
|
|
||||||
Id = subscriptionItem.Id,
|
|
||||||
Price = _planId,
|
|
||||||
Quantity = _newlyPurchasedSeats
|
|
||||||
}
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,5 +1,4 @@
|
|||||||
using Bit.Core.AdminConsole.Entities;
|
using Bit.Core.AdminConsole.Entities;
|
||||||
using Bit.Core.AdminConsole.Entities.Provider;
|
|
||||||
using Bit.Core.Billing.Models;
|
using Bit.Core.Billing.Models;
|
||||||
using Bit.Core.Billing.Models.Api.Requests.Accounts;
|
using Bit.Core.Billing.Models.Api.Requests.Accounts;
|
||||||
using Bit.Core.Billing.Models.Api.Requests.Organizations;
|
using Bit.Core.Billing.Models.Api.Requests.Organizations;
|
||||||
@ -25,11 +24,6 @@ public interface IPaymentService
|
|||||||
int? newlyPurchasedAdditionalSecretsManagerServiceAccounts,
|
int? newlyPurchasedAdditionalSecretsManagerServiceAccounts,
|
||||||
int newlyPurchasedAdditionalStorage);
|
int newlyPurchasedAdditionalStorage);
|
||||||
Task<string> AdjustSeatsAsync(Organization organization, Plan plan, int additionalSeats);
|
Task<string> AdjustSeatsAsync(Organization organization, Plan plan, int additionalSeats);
|
||||||
Task<string> AdjustSeats(
|
|
||||||
Provider provider,
|
|
||||||
Plan plan,
|
|
||||||
int currentlySubscribedSeats,
|
|
||||||
int newlySubscribedSeats);
|
|
||||||
Task<string> AdjustSmSeatsAsync(Organization organization, Plan plan, int additionalSeats);
|
Task<string> AdjustSmSeatsAsync(Organization organization, Plan plan, int additionalSeats);
|
||||||
Task<string> AdjustStorageAsync(IStorableSubscriber storableSubscriber, int additionalStorage, string storagePlanId);
|
Task<string> AdjustStorageAsync(IStorableSubscriber storableSubscriber, int additionalStorage, string storagePlanId);
|
||||||
|
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
using Bit.Core.AdminConsole.Entities;
|
using Bit.Core.AdminConsole.Entities;
|
||||||
using Bit.Core.AdminConsole.Entities.Provider;
|
|
||||||
using Bit.Core.Billing.Constants;
|
using Bit.Core.Billing.Constants;
|
||||||
using Bit.Core.Billing.Extensions;
|
using Bit.Core.Billing.Extensions;
|
||||||
using Bit.Core.Billing.Models;
|
using Bit.Core.Billing.Models;
|
||||||
@ -251,18 +250,6 @@ public class StripePaymentService : IPaymentService
|
|||||||
public Task<string> AdjustSeatsAsync(Organization organization, StaticStore.Plan plan, int additionalSeats) =>
|
public Task<string> AdjustSeatsAsync(Organization organization, StaticStore.Plan plan, int additionalSeats) =>
|
||||||
FinalizeSubscriptionChangeAsync(organization, new SeatSubscriptionUpdate(organization, plan, additionalSeats));
|
FinalizeSubscriptionChangeAsync(organization, new SeatSubscriptionUpdate(organization, plan, additionalSeats));
|
||||||
|
|
||||||
public Task<string> AdjustSeats(
|
|
||||||
Provider provider,
|
|
||||||
StaticStore.Plan plan,
|
|
||||||
int currentlySubscribedSeats,
|
|
||||||
int newlySubscribedSeats)
|
|
||||||
=> FinalizeSubscriptionChangeAsync(
|
|
||||||
provider,
|
|
||||||
new ProviderSubscriptionUpdate(
|
|
||||||
plan,
|
|
||||||
currentlySubscribedSeats,
|
|
||||||
newlySubscribedSeats));
|
|
||||||
|
|
||||||
public Task<string> AdjustSmSeatsAsync(Organization organization, StaticStore.Plan plan, int additionalSeats) =>
|
public Task<string> AdjustSmSeatsAsync(Organization organization, StaticStore.Plan plan, int additionalSeats) =>
|
||||||
FinalizeSubscriptionChangeAsync(
|
FinalizeSubscriptionChangeAsync(
|
||||||
organization,
|
organization,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user