mirror of
https://github.com/bitwarden/server.git
synced 2025-07-01 08:02:49 -05:00
[AC-1910] Allocate seats to a provider organization (#3936)
* Add endpoint to update a provider organization's seats for consolidated billing. * Fixed failing tests
This commit is contained in:
@ -1,5 +1,4 @@
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
using Stripe;
|
||||
|
||||
@ -279,25 +278,6 @@ public class CompleteSubscriptionUpdate : SubscriptionUpdate
|
||||
};
|
||||
}
|
||||
|
||||
private static SubscriptionItem FindSubscriptionItem(Subscription subscription, string planId)
|
||||
{
|
||||
if (string.IsNullOrEmpty(planId))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var data = subscription.Items.Data;
|
||||
|
||||
var subscriptionItem = data.FirstOrDefault(item => item.Plan?.Id == planId) ?? data.FirstOrDefault(item => item.Price?.Id == planId);
|
||||
|
||||
return subscriptionItem;
|
||||
}
|
||||
|
||||
private static string GetPasswordManagerPlanId(StaticStore.Plan plan)
|
||||
=> IsNonSeatBasedPlan(plan)
|
||||
? plan.PasswordManager.StripePlanId
|
||||
: plan.PasswordManager.StripeSeatPlanId;
|
||||
|
||||
private static SubscriptionData GetSubscriptionDataFor(Organization organization)
|
||||
{
|
||||
var plan = Utilities.StaticStore.GetPlan(organization.PlanType);
|
||||
@ -320,10 +300,4 @@ public class CompleteSubscriptionUpdate : SubscriptionUpdate
|
||||
0
|
||||
};
|
||||
}
|
||||
|
||||
private static bool IsNonSeatBasedPlan(StaticStore.Plan plan)
|
||||
=> plan.Type is
|
||||
>= PlanType.FamiliesAnnually2019 and <= PlanType.EnterpriseAnnually2019
|
||||
or PlanType.FamiliesAnnually
|
||||
or PlanType.TeamsStarter;
|
||||
}
|
||||
|
61
src/Core/Models/Business/ProviderSubscriptionUpdate.cs
Normal file
61
src/Core/Models/Business/ProviderSubscriptionUpdate.cs
Normal file
@ -0,0 +1,61 @@
|
||||
using Bit.Core.Billing.Extensions;
|
||||
using Bit.Core.Enums;
|
||||
using Stripe;
|
||||
|
||||
using static Bit.Core.Billing.Utilities;
|
||||
|
||||
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(
|
||||
PlanType planType,
|
||||
int previouslyPurchasedSeats,
|
||||
int newlyPurchasedSeats)
|
||||
{
|
||||
if (!planType.SupportsConsolidatedBilling())
|
||||
{
|
||||
throw ContactSupport($"Cannot create a {nameof(ProviderSubscriptionUpdate)} for {nameof(PlanType)} that doesn't support consolidated billing");
|
||||
}
|
||||
|
||||
_planId = GetPasswordManagerPlanId(Utilities.StaticStore.GetPlan(planType));
|
||||
_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
|
||||
}
|
||||
];
|
||||
}
|
||||
}
|
@ -18,7 +18,7 @@ public class SeatSubscriptionUpdate : SubscriptionUpdate
|
||||
|
||||
public override List<SubscriptionItemOptions> UpgradeItemsOptions(Subscription subscription)
|
||||
{
|
||||
var item = SubscriptionItem(subscription, PlanIds.Single());
|
||||
var item = FindSubscriptionItem(subscription, PlanIds.Single());
|
||||
return new()
|
||||
{
|
||||
new SubscriptionItemOptions
|
||||
@ -34,7 +34,7 @@ public class SeatSubscriptionUpdate : SubscriptionUpdate
|
||||
public override List<SubscriptionItemOptions> RevertItemsOptions(Subscription subscription)
|
||||
{
|
||||
|
||||
var item = SubscriptionItem(subscription, PlanIds.Single());
|
||||
var item = FindSubscriptionItem(subscription, PlanIds.Single());
|
||||
return new()
|
||||
{
|
||||
new SubscriptionItemOptions
|
||||
|
@ -19,7 +19,7 @@ public class ServiceAccountSubscriptionUpdate : SubscriptionUpdate
|
||||
|
||||
public override List<SubscriptionItemOptions> UpgradeItemsOptions(Subscription subscription)
|
||||
{
|
||||
var item = SubscriptionItem(subscription, PlanIds.Single());
|
||||
var item = FindSubscriptionItem(subscription, PlanIds.Single());
|
||||
_prevServiceAccounts = item?.Quantity ?? 0;
|
||||
return new()
|
||||
{
|
||||
@ -35,7 +35,7 @@ public class ServiceAccountSubscriptionUpdate : SubscriptionUpdate
|
||||
|
||||
public override List<SubscriptionItemOptions> RevertItemsOptions(Subscription subscription)
|
||||
{
|
||||
var item = SubscriptionItem(subscription, PlanIds.Single());
|
||||
var item = FindSubscriptionItem(subscription, PlanIds.Single());
|
||||
return new()
|
||||
{
|
||||
new SubscriptionItemOptions
|
||||
|
@ -19,7 +19,7 @@ public class SmSeatSubscriptionUpdate : SubscriptionUpdate
|
||||
|
||||
public override List<SubscriptionItemOptions> UpgradeItemsOptions(Subscription subscription)
|
||||
{
|
||||
var item = SubscriptionItem(subscription, PlanIds.Single());
|
||||
var item = FindSubscriptionItem(subscription, PlanIds.Single());
|
||||
return new()
|
||||
{
|
||||
new SubscriptionItemOptions
|
||||
@ -35,7 +35,7 @@ public class SmSeatSubscriptionUpdate : SubscriptionUpdate
|
||||
public override List<SubscriptionItemOptions> RevertItemsOptions(Subscription subscription)
|
||||
{
|
||||
|
||||
var item = SubscriptionItem(subscription, PlanIds.Single());
|
||||
var item = FindSubscriptionItem(subscription, PlanIds.Single());
|
||||
return new()
|
||||
{
|
||||
new SubscriptionItemOptions
|
||||
|
@ -74,10 +74,10 @@ public class SponsorOrganizationSubscriptionUpdate : SubscriptionUpdate
|
||||
private string AddStripePlanId => _applySponsorship ? _sponsoredPlanStripeId : _existingPlanStripeId;
|
||||
private Stripe.SubscriptionItem RemoveStripeItem(Subscription subscription) =>
|
||||
_applySponsorship ?
|
||||
SubscriptionItem(subscription, _existingPlanStripeId) :
|
||||
SubscriptionItem(subscription, _sponsoredPlanStripeId);
|
||||
FindSubscriptionItem(subscription, _existingPlanStripeId) :
|
||||
FindSubscriptionItem(subscription, _sponsoredPlanStripeId);
|
||||
private Stripe.SubscriptionItem AddStripeItem(Subscription subscription) =>
|
||||
_applySponsorship ?
|
||||
SubscriptionItem(subscription, _sponsoredPlanStripeId) :
|
||||
SubscriptionItem(subscription, _existingPlanStripeId);
|
||||
FindSubscriptionItem(subscription, _sponsoredPlanStripeId) :
|
||||
FindSubscriptionItem(subscription, _existingPlanStripeId);
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ public class StorageSubscriptionUpdate : SubscriptionUpdate
|
||||
|
||||
public override List<SubscriptionItemOptions> UpgradeItemsOptions(Subscription subscription)
|
||||
{
|
||||
var item = SubscriptionItem(subscription, PlanIds.Single());
|
||||
var item = FindSubscriptionItem(subscription, PlanIds.Single());
|
||||
_prevStorage = item?.Quantity ?? 0;
|
||||
return new()
|
||||
{
|
||||
@ -38,7 +38,7 @@ public class StorageSubscriptionUpdate : SubscriptionUpdate
|
||||
throw new Exception("Unknown previous value, must first call UpgradeItemsOptions");
|
||||
}
|
||||
|
||||
var item = SubscriptionItem(subscription, PlanIds.Single());
|
||||
var item = FindSubscriptionItem(subscription, PlanIds.Single());
|
||||
return new()
|
||||
{
|
||||
new SubscriptionItemOptions
|
||||
|
@ -1,4 +1,5 @@
|
||||
using Stripe;
|
||||
using Bit.Core.Enums;
|
||||
using Stripe;
|
||||
|
||||
namespace Bit.Core.Models.Business;
|
||||
|
||||
@ -15,7 +16,7 @@ public abstract class SubscriptionUpdate
|
||||
foreach (var upgradeItemOptions in upgradeItemsOptions)
|
||||
{
|
||||
var upgradeQuantity = upgradeItemOptions.Quantity ?? 0;
|
||||
var existingQuantity = SubscriptionItem(subscription, upgradeItemOptions.Plan)?.Quantity ?? 0;
|
||||
var existingQuantity = FindSubscriptionItem(subscription, upgradeItemOptions.Plan)?.Quantity ?? 0;
|
||||
if (upgradeQuantity != existingQuantity)
|
||||
{
|
||||
return true;
|
||||
@ -24,6 +25,28 @@ public abstract class SubscriptionUpdate
|
||||
return false;
|
||||
}
|
||||
|
||||
protected static SubscriptionItem SubscriptionItem(Subscription subscription, string planId) =>
|
||||
planId == null ? null : subscription.Items?.Data?.FirstOrDefault(i => i.Plan.Id == planId);
|
||||
protected static SubscriptionItem FindSubscriptionItem(Subscription subscription, string planId)
|
||||
{
|
||||
if (string.IsNullOrEmpty(planId))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var data = subscription.Items.Data;
|
||||
|
||||
var subscriptionItem = data.FirstOrDefault(item => item.Plan?.Id == planId) ?? data.FirstOrDefault(item => item.Price?.Id == planId);
|
||||
|
||||
return subscriptionItem;
|
||||
}
|
||||
|
||||
protected static string GetPasswordManagerPlanId(StaticStore.Plan plan)
|
||||
=> IsNonSeatBasedPlan(plan)
|
||||
? plan.PasswordManager.StripePlanId
|
||||
: plan.PasswordManager.StripeSeatPlanId;
|
||||
|
||||
protected static bool IsNonSeatBasedPlan(StaticStore.Plan plan)
|
||||
=> plan.Type is
|
||||
>= PlanType.FamiliesAnnually2019 and <= PlanType.EnterpriseAnnually2019
|
||||
or PlanType.FamiliesAnnually
|
||||
or PlanType.TeamsStarter;
|
||||
}
|
||||
|
Reference in New Issue
Block a user