1
0
mirror of https://github.com/bitwarden/server.git synced 2024-11-21 12:05:42 +01:00

[AC 1451] Refactor staticstore plans and consuming logic (#3164)

* refactor the plan and create new objects

* initial commit

* Add new plan types

* continue the refactoring by adding new plantypes

* changes for plans

* Refactoring continues

* making changes for plan

* Fixing the failing test

* Fixing  whitespace

* Fix some in correct values

* Resolve the plan data

* rearranging the plan

* Make the plan more immutable

* Resolve the lint errors

* Fix the failing test

* Add custom plan

* Fix the failing test

* Fix the failing test

* resolve the failing addons after refactoring

* Refactoring

* Merge branch 'master' into ac-1451/refactor-staticstore-plans-and-consuming-logic

* merge from master

* Merge branch 'master' into ac-1451/refactor-staticstore-plans-and-consuming-logic

* format whitespace

* resolve the conflict

* Fix some pr comments

* Fixing some of the pr comments

* fixing some of the pr comments

* Resolve some pr comments

* Resolve pr comments

* Resolves some pr comments

* Resolving some or comments

* Resolve a failing test

* fix the failing test

* Resolving some pr comments

* Fix the failing test

* resolve pr comment

* add a using statement fir a failing test

---------

Co-authored-by: Thomas Rittson <trittson@bitwarden.com>
This commit is contained in:
cyprain-okeke 2023-10-17 15:56:35 +01:00 committed by GitHub
parent 1c3bd4d252
commit 8177821e8b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
48 changed files with 1041 additions and 1173 deletions

View File

@ -28,8 +28,8 @@ public class MaxProjectsQuery : IMaxProjectsQuery
throw new NotFoundException();
}
var plan = StaticStore.GetSecretsManagerPlan(org.PlanType);
if (plan == null)
var plan = StaticStore.GetPlan(org.PlanType);
if (plan?.SecretsManager == null)
{
throw new BadRequestException("Existing plan not found.");
}
@ -37,7 +37,7 @@ public class MaxProjectsQuery : IMaxProjectsQuery
if (plan.Type == PlanType.Free)
{
var projects = await _projectRepository.GetProjectCountByOrganizationIdAsync(organizationId);
return projects + projectsToAdd > plan.MaxProjects ? (plan.MaxProjects, true) : (plan.MaxProjects, false);
return ((short? max, bool? overMax))(projects + projectsToAdd > plan.SecretsManager.MaxProjects ? (plan.SecretsManager.MaxProjects, true) : (plan.SecretsManager.MaxProjects, false));
}
return (null, null);

View File

@ -205,9 +205,8 @@ public class OrganizationsController : Controller
var organization = await GetOrganization(id, model);
if (organization.UseSecretsManager &&
!organization.SecretsManagerBeta
&& StaticStore.GetSecretsManagerPlan(organization.PlanType) == null
)
!organization.SecretsManagerBeta &&
!StaticStore.GetPlan(organization.PlanType).SupportsSecretsManager)
{
throw new BadRequestException("Plan does not support Secrets Manager");
}

View File

@ -158,11 +158,12 @@ public class OrganizationEditModel : OrganizationViewModel
* Add mappings for individual properties as you need them
*/
public IEnumerable<Dictionary<string, object>> GetPlansHelper() =>
StaticStore.SecretManagerPlans.Select(p =>
new Dictionary<string, object>
StaticStore.Plans
.Where(p => p.SupportsSecretsManager)
.Select(p => new Dictionary<string, object>
{
{ "type", p.Type },
{ "baseServiceAccount", p.BaseServiceAccount }
{ "baseServiceAccount", p.SecretsManager.BaseServiceAccount }
});
public Organization CreateOrganization(Provider provider)

View File

@ -540,7 +540,7 @@ public class OrganizationsController : Controller
if (model.Type == OrganizationApiKeyType.BillingSync || model.Type == OrganizationApiKeyType.Scim)
{
// Non-enterprise orgs should not be able to create or view an apikey of billing sync/scim key types
var plan = StaticStore.GetPasswordManagerPlan(organization.PlanType);
var plan = StaticStore.GetPlan(organization.PlanType);
if (plan.Product != ProductType.Enterprise)
{
throw new NotFoundException();

View File

@ -19,30 +19,12 @@ public class PlansController : Controller
[HttpGet("")]
[AllowAnonymous]
public ListResponseModel<PlanResponseModel> Get()
{
var data = StaticStore.PasswordManagerPlans;
var responses = data.Select(plan => new PlanResponseModel(plan));
return new ListResponseModel<PlanResponseModel>(responses);
}
[HttpGet("all")]
[AllowAnonymous]
public ListResponseModel<PlanResponseModel> GetAllPlans()
{
var data = StaticStore.Plans;
var responses = data.Select(plan => new PlanResponseModel(plan));
return new ListResponseModel<PlanResponseModel>(responses);
}
[HttpGet("sm-plans")]
[AllowAnonymous]
public ListResponseModel<PlanResponseModel> GetSecretsManagerPlans()
{
var data = StaticStore.SecretManagerPlans;
var responses = data.Select(plan => new PlanResponseModel(plan));
return new ListResponseModel<PlanResponseModel>(responses);
}
[HttpGet("sales-tax-rates")]
public async Task<ListResponseModel<TaxRateResponseModel>> GetTaxRates()
{

View File

@ -26,12 +26,7 @@ public class OrganizationResponseModel : ResponseModel
BusinessCountry = organization.BusinessCountry;
BusinessTaxNumber = organization.BusinessTaxNumber;
BillingEmail = organization.BillingEmail;
Plan = new PlanResponseModel(StaticStore.PasswordManagerPlans.FirstOrDefault(plan => plan.Type == organization.PlanType));
var matchingPlan = StaticStore.GetSecretsManagerPlan(organization.PlanType);
if (matchingPlan != null)
{
SecretsManagerPlan = new PlanResponseModel(matchingPlan);
}
Plan = new PlanResponseModel(StaticStore.GetPlan(organization.PlanType));
PlanType = organization.PlanType;
Seats = organization.Seats;
MaxAutoscaleSeats = organization.MaxAutoscaleSeats;

View File

@ -21,15 +21,6 @@ public class PlanResponseModel : ResponseModel
NameLocalizationKey = plan.NameLocalizationKey;
DescriptionLocalizationKey = plan.DescriptionLocalizationKey;
CanBeUsedByBusiness = plan.CanBeUsedByBusiness;
BaseSeats = plan.BaseSeats;
BaseStorageGb = plan.BaseStorageGb;
MaxCollections = plan.MaxCollections;
MaxUsers = plan.MaxUsers;
HasAdditionalSeatsOption = plan.HasAdditionalSeatsOption;
HasAdditionalStorageOption = plan.HasAdditionalStorageOption;
MaxAdditionalSeats = plan.MaxAdditionalSeats;
MaxAdditionalStorage = plan.MaxAdditionalStorage;
HasPremiumAccessOption = plan.HasPremiumAccessOption;
TrialPeriodDays = plan.TrialPeriodDays;
HasSelfHost = plan.HasSelfHost;
HasPolicies = plan.HasPolicies;
@ -45,22 +36,12 @@ public class PlanResponseModel : ResponseModel
DisplaySortOrder = plan.DisplaySortOrder;
LegacyYear = plan.LegacyYear;
Disabled = plan.Disabled;
StripePlanId = plan.StripePlanId;
StripeSeatPlanId = plan.StripeSeatPlanId;
StripeStoragePlanId = plan.StripeStoragePlanId;
BasePrice = plan.BasePrice;
SeatPrice = plan.SeatPrice;
AdditionalStoragePricePerGb = plan.AdditionalStoragePricePerGb;
PremiumAccessOptionPrice = plan.PremiumAccessOptionPrice;
if (plan.SecretsManager != null)
{
SecretsManager = new SecretsManagerPlanFeaturesResponseModel(plan.SecretsManager);
}
AdditionalPricePerServiceAccount = plan.AdditionalPricePerServiceAccount;
BaseServiceAccount = plan.BaseServiceAccount;
MaxServiceAccounts = plan.MaxServiceAccounts;
MaxAdditionalServiceAccounts = plan.MaxAdditionalServiceAccount;
HasAdditionalServiceAccountOption = plan.HasAdditionalServiceAccountOption;
MaxProjects = plan.MaxProjects;
BitwardenProduct = plan.BitwardenProduct;
StripeServiceAccountPlanId = plan.StripeServiceAccountPlanId;
PasswordManager = new PasswordManagerPlanFeaturesResponseModel(plan.PasswordManager);
}
public PlanType Type { get; set; }
@ -70,16 +51,6 @@ public class PlanResponseModel : ResponseModel
public string NameLocalizationKey { get; set; }
public string DescriptionLocalizationKey { get; set; }
public bool CanBeUsedByBusiness { get; set; }
public int BaseSeats { get; set; }
public short? BaseStorageGb { get; set; }
public short? MaxCollections { get; set; }
public short? MaxUsers { get; set; }
public bool HasAdditionalSeatsOption { get; set; }
public int? MaxAdditionalSeats { get; set; }
public bool HasAdditionalStorageOption { get; set; }
public short? MaxAdditionalStorage { get; set; }
public bool HasPremiumAccessOption { get; set; }
public int? TrialPeriodDays { get; set; }
public bool HasSelfHost { get; set; }
@ -98,21 +69,95 @@ public class PlanResponseModel : ResponseModel
public int DisplaySortOrder { get; set; }
public int? LegacyYear { get; set; }
public bool Disabled { get; set; }
public SecretsManagerPlanFeaturesResponseModel SecretsManager { get; protected init; }
public PasswordManagerPlanFeaturesResponseModel PasswordManager { get; protected init; }
public string StripePlanId { get; set; }
public string StripeSeatPlanId { get; set; }
public string StripeStoragePlanId { get; set; }
public string StripePremiumAccessPlanId { get; set; }
public decimal BasePrice { get; set; }
public decimal SeatPrice { get; set; }
public decimal AdditionalStoragePricePerGb { get; set; }
public decimal PremiumAccessOptionPrice { get; set; }
public string StripeServiceAccountPlanId { get; set; }
public decimal? AdditionalPricePerServiceAccount { get; set; }
public short? BaseServiceAccount { get; set; }
public short? MaxServiceAccounts { get; set; }
public short? MaxAdditionalServiceAccounts { get; set; }
public bool HasAdditionalServiceAccountOption { get; set; }
public short? MaxProjects { get; set; }
public BitwardenProductType BitwardenProduct { get; set; }
public class SecretsManagerPlanFeaturesResponseModel
{
public SecretsManagerPlanFeaturesResponseModel(Plan.SecretsManagerPlanFeatures plan)
{
MaxServiceAccounts = plan.MaxServiceAccounts;
AllowServiceAccountsAutoscale = plan is { AllowServiceAccountsAutoscale: true };
StripeServiceAccountPlanId = plan.StripeServiceAccountPlanId;
AdditionalPricePerServiceAccount = plan.AdditionalPricePerServiceAccount;
BaseServiceAccount = plan.BaseServiceAccount;
MaxAdditionalServiceAccount = plan.MaxAdditionalServiceAccount;
HasAdditionalServiceAccountOption = plan is { HasAdditionalServiceAccountOption: true };
StripeSeatPlanId = plan.StripeSeatPlanId;
HasAdditionalSeatsOption = plan is { HasAdditionalSeatsOption: true };
BasePrice = plan.BasePrice;
SeatPrice = plan.SeatPrice;
BaseSeats = plan.BaseSeats;
MaxSeats = plan.MaxSeats;
MaxAdditionalSeats = plan.MaxAdditionalSeats;
AllowSeatAutoscale = plan.AllowSeatAutoscale;
MaxProjects = plan.MaxProjects;
}
// Service accounts
public short? MaxServiceAccounts { get; init; }
public bool AllowServiceAccountsAutoscale { get; init; }
public string StripeServiceAccountPlanId { get; init; }
public decimal? AdditionalPricePerServiceAccount { get; init; }
public short? BaseServiceAccount { get; init; }
public short? MaxAdditionalServiceAccount { get; init; }
public bool HasAdditionalServiceAccountOption { get; init; }
// Seats
public string StripeSeatPlanId { get; init; }
public bool HasAdditionalSeatsOption { get; init; }
public decimal BasePrice { get; init; }
public decimal SeatPrice { get; init; }
public int BaseSeats { get; init; }
public short? MaxSeats { get; init; }
public int? MaxAdditionalSeats { get; init; }
public bool AllowSeatAutoscale { get; init; }
// Features
public int MaxProjects { get; init; }
}
public record PasswordManagerPlanFeaturesResponseModel
{
public PasswordManagerPlanFeaturesResponseModel(Plan.PasswordManagerPlanFeatures plan)
{
StripePlanId = plan.StripePlanId;
StripeSeatPlanId = plan.StripeSeatPlanId;
BasePrice = plan.BasePrice;
SeatPrice = plan.SeatPrice;
AllowSeatAutoscale = plan.AllowSeatAutoscale;
HasAdditionalSeatsOption = plan.HasAdditionalSeatsOption;
MaxAdditionalSeats = plan.MaxAdditionalSeats;
BaseSeats = plan.BaseSeats;
HasPremiumAccessOption = plan.HasPremiumAccessOption;
StripePremiumAccessPlanId = plan.StripePremiumAccessPlanId;
PremiumAccessOptionPrice = plan.PremiumAccessOptionPrice;
MaxSeats = plan.MaxSeats;
BaseStorageGb = plan.BaseStorageGb;
HasAdditionalStorageOption = plan.HasAdditionalStorageOption;
AdditionalStoragePricePerGb = plan.AdditionalStoragePricePerGb;
StripeStoragePlanId = plan.StripeStoragePlanId;
MaxAdditionalStorage = plan.MaxAdditionalStorage;
MaxCollections = plan.MaxCollections;
}
// Seats
public string StripePlanId { get; init; }
public string StripeSeatPlanId { get; init; }
public decimal BasePrice { get; init; }
public decimal SeatPrice { get; init; }
public bool AllowSeatAutoscale { get; init; }
public bool HasAdditionalSeatsOption { get; init; }
public int? MaxAdditionalSeats { get; init; }
public int BaseSeats { get; init; }
public bool HasPremiumAccessOption { get; init; }
public string StripePremiumAccessPlanId { get; init; }
public decimal PremiumAccessOptionPrice { get; init; }
public short? MaxSeats { get; init; }
// Storage
public short? BaseStorageGb { get; init; }
public bool HasAdditionalStorageOption { get; init; }
public decimal AdditionalStoragePricePerGb { get; init; }
public string StripeStoragePlanId { get; init; }
public short? MaxAdditionalStorage { get; init; }
// Feature
public short? MaxCollections { get; init; }
}
}

View File

@ -55,7 +55,7 @@ public class ProfileOrganizationResponseModel : ResponseModel
FamilySponsorshipAvailable = FamilySponsorshipFriendlyName == null &&
StaticStore.GetSponsoredPlan(PlanSponsorshipType.FamiliesForEnterprise)
.UsersCanSponsor(organization);
PlanProductType = StaticStore.GetPasswordManagerPlan(organization.PlanType).Product;
PlanProductType = StaticStore.GetPlan(organization.PlanType).Product;
FamilySponsorshipLastSyncDate = organization.FamilySponsorshipLastSyncDate;
FamilySponsorshipToDelete = organization.FamilySponsorshipToDelete;
FamilySponsorshipValidUntil = organization.FamilySponsorshipValidUntil;

View File

@ -42,6 +42,6 @@ public class ProfileProviderOrganizationResponseModel : ProfileOrganizationRespo
UserId = organization.UserId;
ProviderId = organization.ProviderId;
ProviderName = organization.ProviderName;
PlanProductType = StaticStore.GetPasswordManagerPlan(organization.PlanType).Product;
PlanProductType = StaticStore.GetPlan(organization.PlanType).Product;
}
}

View File

@ -1,5 +1,4 @@
using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.Models.Api;
using Bit.Core.Models.Business;
using Bit.Core.Utilities;
@ -98,7 +97,6 @@ public class BillingSubscription
Quantity = item.Quantity;
SponsoredSubscriptionItem = item.SponsoredSubscriptionItem;
AddonSubscriptionItem = item.AddonSubscriptionItem;
BitwardenProduct = item.BitwardenProduct;
}
public string Name { get; set; }
@ -107,7 +105,6 @@ public class BillingSubscription
public string Interval { get; set; }
public bool SponsoredSubscriptionItem { get; set; }
public bool AddonSubscriptionItem { get; set; }
public BitwardenProductType BitwardenProduct { get; set; }
}
}

View File

@ -447,7 +447,7 @@ public class StripeController : Controller
// org
if (ids.Item1.HasValue)
{
if (subscription.Items.Any(i => StaticStore.PasswordManagerPlans.Any(p => p.StripePlanId == i.Plan.Id)))
if (subscription.Items.Any(i => StaticStore.Plans.Any(p => p.PasswordManager.StripePlanId == i.Plan.Id)))
{
await _organizationService.EnableAsync(ids.Item1.Value, subscription.CurrentPeriodEnd);

View File

@ -1,11 +0,0 @@
using System.ComponentModel.DataAnnotations;
namespace Bit.Core.Enums;
public enum BitwardenProductType : byte
{
[Display(Name = "Password Manager")]
PasswordManager = 0,
[Display(Name = "Secrets Manager")]
SecretsManager = 1,
}

View File

@ -43,18 +43,18 @@ public class SecretsManagerSubscriptionUpdate
/// The seats the organization will have after the update, excluding the base seats included in the plan
/// Usually this is what the organization is billed for
/// </summary>
public int SmSeatsExcludingBase => SmSeats.HasValue ? SmSeats.Value - Plan.BaseSeats : 0;
public int SmSeatsExcludingBase => SmSeats.HasValue ? SmSeats.Value - Plan.SecretsManager.BaseSeats : 0;
/// <summary>
/// The seats the organization will have after the update, excluding the base seats included in the plan
/// Usually this is what the organization is billed for
/// </summary>
public int SmServiceAccountsExcludingBase => SmServiceAccounts.HasValue ? SmServiceAccounts.Value - Plan.BaseServiceAccount.GetValueOrDefault() : 0;
public int SmServiceAccountsExcludingBase => SmServiceAccounts.HasValue ? SmServiceAccounts.Value - Plan.SecretsManager!.BaseServiceAccount : 0;
public bool SmSeatsChanged => SmSeats != Organization.SmSeats;
public bool SmServiceAccountsChanged => SmServiceAccounts != Organization.SmServiceAccounts;
public bool MaxAutoscaleSmSeatsChanged => MaxAutoscaleSmSeats != Organization.MaxAutoscaleSmSeats;
public bool MaxAutoscaleSmServiceAccountsChanged =>
MaxAutoscaleSmServiceAccounts != Organization.MaxAutoscaleSmServiceAccounts;
public Plan Plan => Utilities.StaticStore.GetSecretsManagerPlan(Organization.PlanType);
public Plan Plan => Utilities.StaticStore.GetPlan(Organization.PlanType);
public bool SmSeatAutoscaleLimitReached => SmSeats.HasValue && MaxAutoscaleSmSeats.HasValue && SmSeats == MaxAutoscaleSmSeats;
public bool SmServiceAccountAutoscaleLimitReached => SmServiceAccounts.HasValue &&
@ -70,7 +70,7 @@ public class SecretsManagerSubscriptionUpdate
Organization = organization;
if (Plan == null)
if (!Plan.SupportsSecretsManager)
{
throw new NotFoundException("Invalid Secrets Manager plan.");
}

View File

@ -1,12 +1,12 @@
using Bit.Core.Entities;
using Bit.Core.Enums;
using Stripe;
using Plan = Bit.Core.Models.StaticStore.Plan;
namespace Bit.Core.Models.Business;
public class OrganizationSubscriptionOptionsBase : Stripe.SubscriptionCreateOptions
{
public OrganizationSubscriptionOptionsBase(Organization org, List<StaticStore.Plan> plans, TaxInfo taxInfo, int additionalSeats,
public OrganizationSubscriptionOptionsBase(Organization org, StaticStore.Plan plan, TaxInfo taxInfo, int additionalSeats,
int additionalStorageGb, bool premiumAccessAddon, int additionalSmSeats, int additionalServiceAccounts)
{
Items = new List<SubscriptionItemOptions>();
@ -14,79 +14,80 @@ public class OrganizationSubscriptionOptionsBase : Stripe.SubscriptionCreateOpti
{
[org.GatewayIdField()] = org.Id.ToString()
};
foreach (var plan in plans)
{
AddPlanIdToSubscription(plan);
switch (plan.BitwardenProduct)
{
case BitwardenProductType.PasswordManager:
{
AddPremiumAccessAddon(premiumAccessAddon, plan);
AddAdditionalSeatToSubscription(additionalSeats, plan);
AddAdditionalStorage(additionalStorageGb, plan);
break;
}
case BitwardenProductType.SecretsManager:
{
AddAdditionalSeatToSubscription(additionalSmSeats, plan);
AddServiceAccount(additionalServiceAccounts, plan);
break;
}
}
AddPlanIdToSubscription(plan);
if (org.UseSecretsManager)
{
AddSecretsManagerSeat(plan, additionalSmSeats);
AddServiceAccount(plan, additionalServiceAccounts);
}
AddPremiumAccessAddon(plan, premiumAccessAddon);
AddPasswordManagerSeat(plan, additionalSeats);
AddAdditionalStorage(plan, additionalStorageGb);
if (!string.IsNullOrWhiteSpace(taxInfo?.StripeTaxRateId))
{
DefaultTaxRates = new List<string> { taxInfo.StripeTaxRateId };
}
}
private void AddServiceAccount(int additionalServiceAccounts, StaticStore.Plan plan)
private void AddSecretsManagerSeat(Plan plan, int additionalSmSeats)
{
if (additionalServiceAccounts > 0 && plan.StripeServiceAccountPlanId != null)
if (additionalSmSeats > 0 && plan.SecretsManager.StripeSeatPlanId != null)
{
Items.Add(new SubscriptionItemOptions
{ Plan = plan.SecretsManager.StripeSeatPlanId, Quantity = additionalSmSeats });
}
}
private void AddPasswordManagerSeat(Plan plan, int additionalSeats)
{
if (additionalSeats > 0 && plan.PasswordManager.StripeSeatPlanId != null)
{
Items.Add(new SubscriptionItemOptions
{ Plan = plan.PasswordManager.StripeSeatPlanId, Quantity = additionalSeats });
}
}
private void AddServiceAccount(StaticStore.Plan plan, int additionalServiceAccounts)
{
if (additionalServiceAccounts > 0 && plan.SecretsManager.StripeServiceAccountPlanId != null)
{
Items.Add(new SubscriptionItemOptions
{
Plan = plan.StripeServiceAccountPlanId,
Plan = plan.SecretsManager.StripeServiceAccountPlanId,
Quantity = additionalServiceAccounts
});
}
}
private void AddAdditionalStorage(int additionalStorageGb, StaticStore.Plan plan)
private void AddAdditionalStorage(StaticStore.Plan plan, int additionalStorageGb)
{
if (additionalStorageGb > 0)
{
Items.Add(new SubscriptionItemOptions
{
Plan = plan.StripeStoragePlanId,
Plan = plan.PasswordManager.StripeStoragePlanId,
Quantity = additionalStorageGb
});
}
}
private void AddPremiumAccessAddon(bool premiumAccessAddon, StaticStore.Plan plan)
private void AddPremiumAccessAddon(StaticStore.Plan plan, bool premiumAccessAddon)
{
if (premiumAccessAddon && plan.StripePremiumAccessPlanId != null)
if (premiumAccessAddon && plan.PasswordManager.StripePremiumAccessPlanId != null)
{
Items.Add(new SubscriptionItemOptions { Plan = plan.StripePremiumAccessPlanId, Quantity = 1 });
}
}
private void AddAdditionalSeatToSubscription(int additionalSeats, StaticStore.Plan plan)
{
if (additionalSeats > 0 && plan.StripeSeatPlanId != null)
{
Items.Add(new SubscriptionItemOptions { Plan = plan.StripeSeatPlanId, Quantity = additionalSeats });
Items.Add(new SubscriptionItemOptions { Plan = plan.PasswordManager.StripePremiumAccessPlanId, Quantity = 1 });
}
}
private void AddPlanIdToSubscription(StaticStore.Plan plan)
{
if (plan.StripePlanId != null)
if (plan.PasswordManager.StripePlanId != null)
{
Items.Add(new SubscriptionItemOptions { Plan = plan.StripePlanId, Quantity = 1 });
Items.Add(new SubscriptionItemOptions { Plan = plan.PasswordManager.StripePlanId, Quantity = 1 });
}
}
}
@ -94,14 +95,14 @@ public class OrganizationSubscriptionOptionsBase : Stripe.SubscriptionCreateOpti
public class OrganizationPurchaseSubscriptionOptions : OrganizationSubscriptionOptionsBase
{
public OrganizationPurchaseSubscriptionOptions(
Organization org, List<StaticStore.Plan> plans,
Organization org, StaticStore.Plan plan,
TaxInfo taxInfo, int additionalSeats,
int additionalStorageGb, bool premiumAccessAddon,
int additionalSmSeats, int additionalServiceAccounts) :
base(org, plans, taxInfo, additionalSeats, additionalStorageGb, premiumAccessAddon, additionalSmSeats, additionalServiceAccounts)
base(org, plan, taxInfo, additionalSeats, additionalStorageGb, premiumAccessAddon, additionalSmSeats, additionalServiceAccounts)
{
OffSession = true;
TrialPeriodDays = plans.FirstOrDefault(x => x.BitwardenProduct == BitwardenProductType.PasswordManager)!.TrialPeriodDays;
TrialPeriodDays = plan.TrialPeriodDays;
}
}
@ -109,8 +110,8 @@ public class OrganizationUpgradeSubscriptionOptions : OrganizationSubscriptionOp
{
public OrganizationUpgradeSubscriptionOptions(
string customerId, Organization org,
List<StaticStore.Plan> plans, OrganizationUpgrade upgrade) :
base(org, plans, upgrade.TaxInfo, upgrade.AdditionalSeats, upgrade.AdditionalStorageGb,
StaticStore.Plan plan, OrganizationUpgrade upgrade) :
base(org, plan, upgrade.TaxInfo, upgrade.AdditionalSeats, upgrade.AdditionalStorageGb,
upgrade.PremiumAccessAddon, upgrade.AdditionalSmSeats.GetValueOrDefault(),
upgrade.AdditionalServiceAccounts.GetValueOrDefault())
{

View File

@ -1,5 +1,4 @@
using Bit.Core.Enums;
using Stripe;
using Stripe;
namespace Bit.Core.Models.Business;
@ -64,16 +63,12 @@ public class SubscriptionInfo
Interval = item.Plan.Interval;
AddonSubscriptionItem =
Utilities.StaticStore.IsAddonSubscriptionItem(item.Plan.Id);
BitwardenProduct =
Utilities.StaticStore.GetPlanByStripeId(item.Plan.Id)?.BitwardenProduct ?? BitwardenProductType.PasswordManager;
}
Quantity = (int)item.Quantity;
SponsoredSubscriptionItem = Utilities.StaticStore.SponsoredPlans.Any(p => p.StripePlanId == item.Plan.Id);
}
public BitwardenProductType BitwardenProduct { get; set; }
public bool AddonSubscriptionItem { get; set; }
public string Name { get; set; }

View File

@ -1,5 +1,4 @@
using Bit.Core.Entities;
using Bit.Core.Enums;
using Stripe;
namespace Bit.Core.Models.Business;
@ -30,29 +29,23 @@ public abstract class SubscriptionUpdate
planId == null ? null : subscription.Items?.Data?.FirstOrDefault(i => i.Plan.Id == planId);
}
public class SeatSubscriptionUpdate : SubscriptionUpdate
public abstract class BaseSeatSubscriptionUpdate : SubscriptionUpdate
{
private readonly int _previousSeats;
private readonly StaticStore.Plan _plan;
protected readonly StaticStore.Plan Plan;
private readonly long? _additionalSeats;
protected override List<string> PlanIds => new() { _plan.StripeSeatPlanId };
public SeatSubscriptionUpdate(Organization organization, StaticStore.Plan plan, long? additionalSeats)
protected BaseSeatSubscriptionUpdate(Organization organization, StaticStore.Plan plan, long? additionalSeats, int previousSeats)
{
_plan = plan;
Plan = plan;
_additionalSeats = additionalSeats;
switch (plan.BitwardenProduct)
{
case BitwardenProductType.PasswordManager:
_previousSeats = organization.Seats.GetValueOrDefault();
break;
case BitwardenProductType.SecretsManager:
_previousSeats = organization.SmSeats.GetValueOrDefault();
break;
}
_previousSeats = previousSeats;
}
protected abstract string GetPlanId();
protected override List<string> PlanIds => new() { GetPlanId() };
public override List<SubscriptionItemOptions> UpgradeItemsOptions(Subscription subscription)
{
var item = SubscriptionItem(subscription, PlanIds.Single());
@ -85,12 +78,30 @@ public class SeatSubscriptionUpdate : SubscriptionUpdate
}
}
public class SeatSubscriptionUpdate : BaseSeatSubscriptionUpdate
{
public SeatSubscriptionUpdate(Organization organization, StaticStore.Plan plan, long? additionalSeats)
: base(organization, plan, additionalSeats, organization.Seats.GetValueOrDefault())
{ }
protected override string GetPlanId() => Plan.PasswordManager.StripeSeatPlanId;
}
public class SmSeatSubscriptionUpdate : BaseSeatSubscriptionUpdate
{
public SmSeatSubscriptionUpdate(Organization organization, StaticStore.Plan plan, long? additionalSeats)
: base(organization, plan, additionalSeats, organization.SmSeats.GetValueOrDefault())
{ }
protected override string GetPlanId() => Plan.SecretsManager.StripeSeatPlanId;
}
public class ServiceAccountSubscriptionUpdate : SubscriptionUpdate
{
private long? _prevServiceAccounts;
private readonly StaticStore.Plan _plan;
private readonly long? _additionalServiceAccounts;
protected override List<string> PlanIds => new() { _plan.StripeServiceAccountPlanId };
protected override List<string> PlanIds => new() { _plan.SecretsManager.StripeServiceAccountPlanId };
public ServiceAccountSubscriptionUpdate(Organization organization, StaticStore.Plan plan, long? additionalServiceAccounts)
{
@ -190,7 +201,7 @@ public class SponsorOrganizationSubscriptionUpdate : SubscriptionUpdate
public SponsorOrganizationSubscriptionUpdate(StaticStore.Plan existingPlan, StaticStore.SponsoredPlan sponsoredPlan, bool applySponsorship)
{
_existingPlanStripeId = existingPlan.StripePlanId;
_existingPlanStripeId = existingPlan.PasswordManager.StripePlanId;
_sponsoredPlanStripeId = sponsoredPlan?.StripePlanId;
_applySponsorship = applySponsorship;
}
@ -269,7 +280,7 @@ public class SecretsManagerSubscribeUpdate : SubscriptionUpdate
private readonly long? _additionalServiceAccounts;
private readonly int _previousSeats;
private readonly int _previousServiceAccounts;
protected override List<string> PlanIds => new() { _plan.StripeSeatPlanId, _plan.StripeServiceAccountPlanId };
protected override List<string> PlanIds => new() { _plan.SecretsManager.StripeSeatPlanId, _plan.SecretsManager.StripeServiceAccountPlanId };
public SecretsManagerSubscribeUpdate(Organization organization, StaticStore.Plan plan, long? additionalSeats, long? additionalServiceAccounts)
{
_plan = plan;
@ -303,7 +314,7 @@ public class SecretsManagerSubscribeUpdate : SubscriptionUpdate
{
updatedItems.Add(new SubscriptionItemOptions
{
Price = _plan.StripeSeatPlanId,
Price = _plan.SecretsManager.StripeSeatPlanId,
Quantity = _additionalSeats
});
}
@ -312,7 +323,7 @@ public class SecretsManagerSubscribeUpdate : SubscriptionUpdate
{
updatedItems.Add(new SubscriptionItemOptions
{
Price = _plan.StripeServiceAccountPlanId,
Price = _plan.SecretsManager.StripeServiceAccountPlanId,
Quantity = _additionalServiceAccounts
});
}
@ -322,14 +333,14 @@ public class SecretsManagerSubscribeUpdate : SubscriptionUpdate
{
updatedItems.Add(new SubscriptionItemOptions
{
Price = _plan.StripeSeatPlanId,
Price = _plan.SecretsManager.StripeSeatPlanId,
Quantity = _previousSeats,
Deleted = _previousSeats == 0 ? true : (bool?)null,
});
updatedItems.Add(new SubscriptionItemOptions
{
Price = _plan.StripeServiceAccountPlanId,
Price = _plan.SecretsManager.StripeServiceAccountPlanId,
Quantity = _previousServiceAccounts,
Deleted = _previousServiceAccounts == 0 ? true : (bool?)null,
});

View File

@ -2,64 +2,84 @@
namespace Bit.Core.Models.StaticStore;
public class Plan
public abstract record Plan
{
public PlanType Type { get; set; }
public ProductType Product { get; set; }
public string Name { get; set; }
public bool IsAnnual { get; set; }
public string NameLocalizationKey { get; set; }
public string DescriptionLocalizationKey { get; set; }
public bool CanBeUsedByBusiness { get; set; }
public int BaseSeats { get; set; }
public short? BaseStorageGb { get; set; }
public short? MaxCollections { get; set; }
public short? MaxUsers { get; set; }
public short? MaxServiceAccounts { get; set; }
public bool AllowSeatAutoscale { get; set; }
public PlanType Type { get; protected init; }
public ProductType Product { get; protected init; }
public string Name { get; protected init; }
public bool IsAnnual { get; protected init; }
public string NameLocalizationKey { get; protected init; }
public string DescriptionLocalizationKey { get; protected init; }
public bool CanBeUsedByBusiness { get; protected init; }
public int? TrialPeriodDays { get; protected init; }
public bool HasSelfHost { get; protected init; }
public bool HasPolicies { get; protected init; }
public bool HasGroups { get; protected init; }
public bool HasDirectory { get; protected init; }
public bool HasEvents { get; protected init; }
public bool HasTotp { get; protected init; }
public bool Has2fa { get; protected init; }
public bool HasApi { get; protected init; }
public bool HasSso { get; protected init; }
public bool HasKeyConnector { get; protected init; }
public bool HasScim { get; protected init; }
public bool HasResetPassword { get; protected init; }
public bool UsersGetPremium { get; protected init; }
public bool HasCustomPermissions { get; protected init; }
public int UpgradeSortOrder { get; protected init; }
public int DisplaySortOrder { get; protected init; }
public int? LegacyYear { get; protected init; }
public bool Disabled { get; protected init; }
public PasswordManagerPlanFeatures PasswordManager { get; protected init; }
public SecretsManagerPlanFeatures SecretsManager { get; protected init; }
public bool SupportsSecretsManager => SecretsManager != null;
public bool AllowServiceAccountsAutoscale { get; set; }
public record SecretsManagerPlanFeatures
{
// Service accounts
public short? MaxServiceAccounts { get; init; }
public bool AllowServiceAccountsAutoscale { get; init; }
public string StripeServiceAccountPlanId { get; init; }
public decimal? AdditionalPricePerServiceAccount { get; init; }
public short BaseServiceAccount { get; init; }
public short? MaxAdditionalServiceAccount { get; init; }
public bool HasAdditionalServiceAccountOption { get; init; }
// Seats
public string StripeSeatPlanId { get; init; }
public bool HasAdditionalSeatsOption { get; init; }
public decimal BasePrice { get; init; }
public decimal SeatPrice { get; init; }
public int BaseSeats { get; init; }
public short? MaxSeats { get; init; }
public int? MaxAdditionalSeats { get; init; }
public bool AllowSeatAutoscale { get; init; }
public bool HasAdditionalSeatsOption { get; set; }
public int? MaxAdditionalSeats { get; set; }
public bool HasAdditionalStorageOption { get; set; }
public short? MaxAdditionalStorage { get; set; }
public bool HasPremiumAccessOption { get; set; }
public int? TrialPeriodDays { get; set; }
// Features
public int MaxProjects { get; init; }
}
public bool HasSelfHost { get; set; }
public bool HasPolicies { get; set; }
public bool HasGroups { get; set; }
public bool HasDirectory { get; set; }
public bool HasEvents { get; set; }
public bool HasTotp { get; set; }
public bool Has2fa { get; set; }
public bool HasApi { get; set; }
public bool HasSso { get; set; }
public bool HasKeyConnector { get; set; }
public bool HasScim { get; set; }
public bool HasResetPassword { get; set; }
public bool UsersGetPremium { get; set; }
public bool HasCustomPermissions { get; set; }
public int UpgradeSortOrder { get; set; }
public int DisplaySortOrder { get; set; }
public int? LegacyYear { get; set; }
public bool Disabled { get; set; }
public string StripePlanId { get; set; }
public string StripeSeatPlanId { get; set; }
public string StripeStoragePlanId { get; set; }
public string StripeServiceAccountPlanId { get; set; }
public string StripePremiumAccessPlanId { get; set; }
public decimal BasePrice { get; set; }
public decimal SeatPrice { get; set; }
public decimal AdditionalStoragePricePerGb { get; set; }
public decimal PremiumAccessOptionPrice { get; set; }
public decimal? AdditionalPricePerServiceAccount { get; set; }
public short? BaseServiceAccount { get; set; }
public short? MaxAdditionalServiceAccount { get; set; }
public bool HasAdditionalServiceAccountOption { get; set; }
public short? MaxProjects { get; set; }
public BitwardenProductType BitwardenProduct { get; set; }
public record PasswordManagerPlanFeatures
{
// Seats
public string StripePlanId { get; init; }
public string StripeSeatPlanId { get; init; }
public decimal BasePrice { get; init; }
public decimal SeatPrice { get; init; }
public bool AllowSeatAutoscale { get; init; }
public bool HasAdditionalSeatsOption { get; init; }
public int? MaxAdditionalSeats { get; init; }
public int BaseSeats { get; init; }
public bool HasPremiumAccessOption { get; init; }
public string StripePremiumAccessPlanId { get; init; }
public decimal PremiumAccessOptionPrice { get; init; }
public short? MaxSeats { get; init; }
// Storage
public short? BaseStorageGb { get; init; }
public bool HasAdditionalStorageOption { get; init; }
public decimal AdditionalStoragePricePerGb { get; init; }
public string StripeStoragePlanId { get; init; }
public short? MaxAdditionalStorage { get; init; }
// Feature
public short? MaxCollections { get; init; }
}
}

View File

@ -0,0 +1,20 @@
using Bit.Core.Enums;
namespace Bit.Core.Models.StaticStore.Plans;
public record CustomPlan : Models.StaticStore.Plan
{
public CustomPlan()
{
Type = PlanType.Custom;
PasswordManager = new CustomPasswordManagerFeatures();
}
private record CustomPasswordManagerFeatures : PasswordManagerPlanFeatures
{
public CustomPasswordManagerFeatures()
{
AllowSeatAutoscale = true;
}
}
}

View File

@ -0,0 +1,65 @@
using Bit.Core.Enums;
namespace Bit.Core.Models.StaticStore.Plans;
public record Enterprise2019Plan : Models.StaticStore.Plan
{
public Enterprise2019Plan(bool isAnnual)
{
Type = isAnnual ? PlanType.EnterpriseAnnually2019 : PlanType.EnterpriseMonthly2019;
Product = ProductType.Enterprise;
Name = isAnnual ? "Enterprise (Annually) 2019" : "Enterprise (Monthly) 2019";
IsAnnual = isAnnual;
NameLocalizationKey = "planNameEnterprise";
DescriptionLocalizationKey = "planDescEnterprise";
CanBeUsedByBusiness = true;
TrialPeriodDays = 7;
HasPolicies = true;
HasSelfHost = true;
HasGroups = true;
HasDirectory = true;
HasEvents = true;
HasTotp = true;
Has2fa = true;
HasApi = true;
UsersGetPremium = true;
HasCustomPermissions = true;
UpgradeSortOrder = 3;
DisplaySortOrder = 3;
LegacyYear = 2020;
PasswordManager = new Enterprise2019PasswordManagerFeatures(isAnnual);
}
private record Enterprise2019PasswordManagerFeatures : PasswordManagerPlanFeatures
{
public Enterprise2019PasswordManagerFeatures(bool isAnnual)
{
BaseSeats = 0;
BaseStorageGb = 1;
HasAdditionalStorageOption = true;
HasAdditionalSeatsOption = true;
AllowSeatAutoscale = true;
if (isAnnual)
{
StripeStoragePlanId = "storage-gb-annually";
StripeSeatPlanId = "enterprise-org-seat-annually";
SeatPrice = 36;
AdditionalStoragePricePerGb = 4;
}
else
{
StripeSeatPlanId = "enterprise-org-seat-monthly";
StripeStoragePlanId = "storage-gb-monthly";
SeatPrice = 4M;
AdditionalStoragePricePerGb = 0.5M;
}
}
}
}

View File

@ -0,0 +1,100 @@
using Bit.Core.Enums;
namespace Bit.Core.Models.StaticStore.Plans;
public record EnterprisePlan : Models.StaticStore.Plan
{
public EnterprisePlan(bool isAnnual)
{
Type = isAnnual ? PlanType.EnterpriseAnnually : PlanType.EnterpriseMonthly;
Product = ProductType.Enterprise;
Name = isAnnual ? "Enterprise (Annually)" : "Enterprise (Monthly)";
IsAnnual = isAnnual;
NameLocalizationKey = "planNameEnterprise";
DescriptionLocalizationKey = "planDescEnterprise";
CanBeUsedByBusiness = true;
TrialPeriodDays = 7;
HasPolicies = true;
HasSelfHost = true;
HasGroups = true;
HasDirectory = true;
HasEvents = true;
HasTotp = true;
Has2fa = true;
HasApi = true;
HasSso = true;
HasKeyConnector = true;
HasScim = true;
HasResetPassword = true;
UsersGetPremium = true;
HasCustomPermissions = true;
UpgradeSortOrder = 3;
DisplaySortOrder = 3;
PasswordManager = new EnterprisePasswordManagerFeatures(isAnnual);
SecretsManager = new EnterpriseSecretsManagerFeatures(isAnnual);
}
private record EnterpriseSecretsManagerFeatures : SecretsManagerPlanFeatures
{
public EnterpriseSecretsManagerFeatures(bool isAnnual)
{
BaseSeats = 0;
BasePrice = 0;
BaseServiceAccount = 200;
HasAdditionalSeatsOption = true;
HasAdditionalServiceAccountOption = true;
AllowSeatAutoscale = true;
AllowServiceAccountsAutoscale = true;
if (isAnnual)
{
StripeSeatPlanId = "secrets-manager-enterprise-seat-annually";
StripeServiceAccountPlanId = "secrets-manager-service-account-annually";
SeatPrice = 144;
AdditionalPricePerServiceAccount = 6;
}
else
{
StripeSeatPlanId = "secrets-manager-enterprise-seat-monthly";
StripeServiceAccountPlanId = "secrets-manager-service-account-monthly";
SeatPrice = 13;
AdditionalPricePerServiceAccount = 0.5M;
}
}
}
private record EnterprisePasswordManagerFeatures : PasswordManagerPlanFeatures
{
public EnterprisePasswordManagerFeatures(bool isAnnual)
{
BaseSeats = 0;
BaseStorageGb = 1;
HasAdditionalStorageOption = true;
HasAdditionalSeatsOption = true;
AllowSeatAutoscale = true;
if (isAnnual)
{
AdditionalStoragePricePerGb = 4;
StripeStoragePlanId = "storage-gb-annually";
StripeSeatPlanId = "2020-enterprise-org-seat-annually";
SeatPrice = 60;
}
else
{
StripeSeatPlanId = "2020-enterprise-seat-monthly";
StripeStoragePlanId = "storage-gb-monthly";
SeatPrice = 6;
AdditionalStoragePricePerGb = 0.5M;
}
}
}
}

View File

@ -0,0 +1,49 @@
using Bit.Core.Enums;
namespace Bit.Core.Models.StaticStore.Plans;
public record Families2019Plan : Models.StaticStore.Plan
{
public Families2019Plan()
{
Type = PlanType.FamiliesAnnually2019;
Product = ProductType.Families;
Name = "Families 2019";
IsAnnual = true;
NameLocalizationKey = "planNameFamilies";
DescriptionLocalizationKey = "planDescFamilies";
TrialPeriodDays = 7;
HasSelfHost = true;
HasTotp = true;
UpgradeSortOrder = 1;
DisplaySortOrder = 1;
LegacyYear = 2020;
PasswordManager = new Families2019PasswordManagerFeatures();
}
private record Families2019PasswordManagerFeatures : PasswordManagerPlanFeatures
{
public Families2019PasswordManagerFeatures()
{
BaseSeats = 5;
BaseStorageGb = 1;
MaxSeats = 5;
HasAdditionalStorageOption = true;
HasPremiumAccessOption = true;
StripePlanId = "personal-org-annually";
StripeStoragePlanId = "storage-gb-annually";
StripePremiumAccessPlanId = "personal-org-premium-access-annually";
BasePrice = 12;
AdditionalStoragePricePerGb = 4;
PremiumAccessOptionPrice = 40;
AllowSeatAutoscale = false;
}
}
}

View File

@ -0,0 +1,46 @@
using Bit.Core.Enums;
namespace Bit.Core.Models.StaticStore.Plans;
public record FamiliesPlan : Models.StaticStore.Plan
{
public FamiliesPlan()
{
Type = PlanType.FamiliesAnnually;
Product = ProductType.Families;
Name = "Families";
IsAnnual = true;
NameLocalizationKey = "planNameFamilies";
DescriptionLocalizationKey = "planDescFamilies";
TrialPeriodDays = 7;
HasSelfHost = true;
HasTotp = true;
UsersGetPremium = true;
UpgradeSortOrder = 1;
DisplaySortOrder = 1;
PasswordManager = new TeamsPasswordManagerFeatures();
}
private record TeamsPasswordManagerFeatures : PasswordManagerPlanFeatures
{
public TeamsPasswordManagerFeatures()
{
BaseSeats = 6;
BaseStorageGb = 1;
MaxSeats = 6;
HasAdditionalStorageOption = true;
StripePlanId = "2020-families-org-annually";
StripeStoragePlanId = "storage-gb-annually";
BasePrice = 40;
AdditionalStoragePricePerGb = 4;
AllowSeatAutoscale = false;
}
}
}

View File

@ -0,0 +1,47 @@
using Bit.Core.Enums;
namespace Bit.Core.Models.StaticStore.Plans;
public record FreePlan : Models.StaticStore.Plan
{
public FreePlan()
{
Type = PlanType.Free;
Product = ProductType.Free;
Name = "Free";
NameLocalizationKey = "planNameFree";
DescriptionLocalizationKey = "planDescFree";
UpgradeSortOrder = -1; // Always the lowest plan, cannot be upgraded to
DisplaySortOrder = -1;
PasswordManager = new FreePasswordManagerFeatures();
SecretsManager = new FreeSecretsManagerFeatures();
}
private record FreeSecretsManagerFeatures : SecretsManagerPlanFeatures
{
public FreeSecretsManagerFeatures()
{
BaseSeats = 2;
BaseServiceAccount = 3;
MaxProjects = 3;
MaxSeats = 2;
MaxServiceAccounts = 3;
AllowSeatAutoscale = false;
}
}
private record FreePasswordManagerFeatures : PasswordManagerPlanFeatures
{
public FreePasswordManagerFeatures()
{
BaseSeats = 2;
MaxCollections = 2;
MaxSeats = 2;
AllowSeatAutoscale = false;
}
}
}

View File

@ -0,0 +1,60 @@
using Bit.Core.Enums;
namespace Bit.Core.Models.StaticStore.Plans;
public record Teams2019Plan : Models.StaticStore.Plan
{
public Teams2019Plan(bool isAnnual)
{
Type = isAnnual ? PlanType.TeamsAnnually2019 : PlanType.TeamsMonthly2019;
Product = ProductType.Teams;
Name = isAnnual ? "Teams (Annually) 2019" : "Teams (Monthly) 2019";
IsAnnual = isAnnual;
NameLocalizationKey = "planNameTeams";
DescriptionLocalizationKey = "planDescTeams";
CanBeUsedByBusiness = true;
TrialPeriodDays = 7;
HasTotp = true;
UpgradeSortOrder = 2;
DisplaySortOrder = 2;
LegacyYear = 2020;
PasswordManager = new Teams2019PasswordManagerFeatures(isAnnual);
}
private record Teams2019PasswordManagerFeatures : PasswordManagerPlanFeatures
{
public Teams2019PasswordManagerFeatures(bool isAnnual)
{
BaseSeats = 5;
BaseStorageGb = 1;
HasAdditionalStorageOption = true;
HasAdditionalSeatsOption = true;
AllowSeatAutoscale = true;
if (isAnnual)
{
StripePlanId = "teams-org-annually";
StripeStoragePlanId = "storage-gb-annually";
StripeSeatPlanId = "teams-org-seat-annually";
SeatPrice = 24;
BasePrice = 60;
AdditionalStoragePricePerGb = 4;
}
else
{
StripePlanId = "teams-org-monthly";
StripeSeatPlanId = "teams-org-seat-monthly";
StripeStoragePlanId = "storage-gb-monthly";
BasePrice = 8;
SeatPrice = 2.5M;
AdditionalStoragePricePerGb = 0.5M;
}
}
}
}

View File

@ -0,0 +1,94 @@
using Bit.Core.Enums;
namespace Bit.Core.Models.StaticStore.Plans;
public record TeamsPlan : Models.StaticStore.Plan
{
public TeamsPlan(bool isAnnual)
{
Type = isAnnual ? PlanType.TeamsAnnually : PlanType.TeamsMonthly;
Product = ProductType.Teams;
Name = isAnnual ? "Teams (Annually)" : "Teams (Monthly)";
IsAnnual = isAnnual;
NameLocalizationKey = "planNameTeams";
DescriptionLocalizationKey = "planDescTeams";
CanBeUsedByBusiness = true;
TrialPeriodDays = 7;
HasGroups = true;
HasDirectory = true;
HasEvents = true;
HasTotp = true;
Has2fa = true;
HasApi = true;
UsersGetPremium = true;
UpgradeSortOrder = 2;
DisplaySortOrder = 2;
PasswordManager = new TeamsPasswordManagerFeatures(isAnnual);
SecretsManager = new TeamsSecretsManagerFeatures(isAnnual);
}
private record TeamsSecretsManagerFeatures : SecretsManagerPlanFeatures
{
public TeamsSecretsManagerFeatures(bool isAnnual)
{
BaseSeats = 0;
BasePrice = 0;
BaseServiceAccount = 50;
HasAdditionalSeatsOption = true;
HasAdditionalServiceAccountOption = true;
AllowSeatAutoscale = true;
AllowServiceAccountsAutoscale = true;
if (isAnnual)
{
StripeSeatPlanId = "secrets-manager-teams-seat-annually";
StripeServiceAccountPlanId = "secrets-manager-service-account-annually";
SeatPrice = 72;
AdditionalPricePerServiceAccount = 6;
}
else
{
StripeSeatPlanId = "secrets-manager-teams-seat-monthly";
StripeServiceAccountPlanId = "secrets-manager-service-account-monthly";
SeatPrice = 7;
AdditionalPricePerServiceAccount = 0.5M;
}
}
}
private record TeamsPasswordManagerFeatures : PasswordManagerPlanFeatures
{
public TeamsPasswordManagerFeatures(bool isAnnual)
{
BaseSeats = 0;
BaseStorageGb = 1;
BasePrice = 0;
HasAdditionalStorageOption = true;
HasAdditionalSeatsOption = true;
AllowSeatAutoscale = true;
if (isAnnual)
{
StripeStoragePlanId = "storage-gb-annually";
StripeSeatPlanId = "2020-teams-org-seat-annually";
SeatPrice = 36;
AdditionalStoragePricePerGb = 4;
}
else
{
StripeSeatPlanId = "2020-teams-org-seat-monthly";
StripeStoragePlanId = "storage-gb-monthly";
SeatPrice = 4;
AdditionalStoragePricePerGb = 0.5M;
}
}
}
}

View File

@ -54,7 +54,7 @@ public class CloudSyncSponsorshipsCommand : ICloudSyncSponsorshipsCommand
{
var requiredSponsoringProductType = StaticStore.GetSponsoredPlan(selfHostedSponsorship.PlanSponsorshipType)?.SponsoringProductType;
if (requiredSponsoringProductType == null
|| StaticStore.GetPasswordManagerPlan(sponsoringOrg.PlanType).Product != requiredSponsoringProductType.Value)
|| StaticStore.GetPlan(sponsoringOrg.PlanType).Product != requiredSponsoringProductType.Value)
{
continue; // prevent unsupported sponsorships
}

View File

@ -51,7 +51,7 @@ public class SetUpSponsorshipCommand : ISetUpSponsorshipCommand
var requiredSponsoredProductType = StaticStore.GetSponsoredPlan(sponsorship.PlanSponsorshipType.Value)?.SponsoredProductType;
if (requiredSponsoredProductType == null ||
sponsoredOrganization == null ||
StaticStore.GetPasswordManagerPlan(sponsoredOrganization.PlanType).Product != requiredSponsoredProductType.Value)
StaticStore.GetPlan(sponsoredOrganization.PlanType).Product != requiredSponsoredProductType.Value)
{
throw new BadRequestException("Can only redeem sponsorship offer on families organizations.");
}

View File

@ -56,7 +56,7 @@ public class ValidateSponsorshipCommand : CancelSponsorshipCommand, IValidateSpo
return false;
}
var sponsoringOrgPlan = Utilities.StaticStore.GetPasswordManagerPlan(sponsoringOrganization.PlanType);
var sponsoringOrgPlan = Utilities.StaticStore.GetPlan(sponsoringOrganization.PlanType);
if (OrgDisabledForMoreThanGracePeriod(sponsoringOrganization) ||
sponsoredPlan.SponsoringProductType != sponsoringOrgPlan.Product ||
existingSponsorship.ToDelete ||

View File

@ -32,7 +32,7 @@ public class CreateSponsorshipCommand : ICreateSponsorshipCommand
var requiredSponsoringProductType = StaticStore.GetSponsoredPlan(sponsorshipType)?.SponsoringProductType;
if (requiredSponsoringProductType == null ||
sponsoringOrg == null ||
StaticStore.GetPasswordManagerPlan(sponsoringOrg.PlanType).Product != requiredSponsoringProductType.Value)
StaticStore.GetPlan(sponsoringOrg.PlanType).Product != requiredSponsoringProductType.Value)
{
throw new BadRequestException("Specified Organization cannot sponsor other organizations.");
}

View File

@ -30,7 +30,7 @@ public class AddSecretsManagerSubscriptionCommand : IAddSecretsManagerSubscripti
{
await ValidateOrganization(organization);
var plan = StaticStore.GetSecretsManagerPlan(organization.PlanType);
var plan = StaticStore.GetPlan(organization.PlanType);
var signup = SetOrganizationUpgrade(organization, additionalSmSeats, additionalServiceAccounts);
_organizationService.ValidateSecretsManagerPlan(plan, signup);
@ -39,8 +39,8 @@ public class AddSecretsManagerSubscriptionCommand : IAddSecretsManagerSubscripti
await _paymentService.AddSecretsManagerToSubscription(organization, plan, additionalSmSeats, additionalServiceAccounts);
}
organization.SmSeats = plan.BaseSeats + additionalSmSeats;
organization.SmServiceAccounts = plan.BaseServiceAccount.GetValueOrDefault() + additionalServiceAccounts;
organization.SmSeats = plan.SecretsManager.BaseSeats + additionalSmSeats;
organization.SmServiceAccounts = plan.SecretsManager.BaseServiceAccount + additionalServiceAccounts;
organization.UseSecretsManager = true;
await _organizationService.ReplaceAndUpdateCacheAsync(organization);
@ -79,7 +79,7 @@ public class AddSecretsManagerSubscriptionCommand : IAddSecretsManagerSubscripti
throw new BadRequestException("Organization already uses Secrets Manager.");
}
var plan = StaticStore.GetSecretsManagerPlan(organization.PlanType);
var plan = StaticStore.Plans.FirstOrDefault(p => p.Type == organization.PlanType && p.SupportsSecretsManager);
if (string.IsNullOrWhiteSpace(organization.GatewayCustomerId) && plan.Product != ProductType.Free)
{
throw new BadRequestException("No payment method found.");

View File

@ -205,10 +205,10 @@ public class UpdateSecretsManagerSubscriptionCommand : IUpdateSecretsManagerSubs
}
// Check plan maximum seats
if (!plan.HasAdditionalSeatsOption ||
(plan.MaxAdditionalSeats.HasValue && update.SmSeatsExcludingBase > plan.MaxAdditionalSeats.Value))
if (!plan.SecretsManager.HasAdditionalSeatsOption ||
(plan.SecretsManager.MaxAdditionalSeats.HasValue && update.SmSeatsExcludingBase > plan.SecretsManager.MaxAdditionalSeats.Value))
{
var planMaxSeats = plan.BaseSeats + plan.MaxAdditionalSeats.GetValueOrDefault();
var planMaxSeats = plan.SecretsManager.BaseSeats + plan.SecretsManager.MaxAdditionalSeats.GetValueOrDefault();
throw new BadRequestException($"You have reached the maximum number of Secrets Manager seats ({planMaxSeats}) for this plan.");
}
@ -222,9 +222,9 @@ public class UpdateSecretsManagerSubscriptionCommand : IUpdateSecretsManagerSubs
}
// Check minimum seats included with plan
if (plan.BaseSeats > update.SmSeats.Value)
if (plan.SecretsManager.BaseSeats > update.SmSeats.Value)
{
throw new BadRequestException($"Plan has a minimum of {plan.BaseSeats} Secrets Manager seats.");
throw new BadRequestException($"Plan has a minimum of {plan.SecretsManager.BaseSeats} Secrets Manager seats.");
}
// Check minimum seats required by business logic
@ -262,11 +262,11 @@ public class UpdateSecretsManagerSubscriptionCommand : IUpdateSecretsManagerSubs
}
// Check plan maximum service accounts
if (!plan.HasAdditionalServiceAccountOption ||
(plan.MaxAdditionalServiceAccount.HasValue && update.SmServiceAccountsExcludingBase > plan.MaxAdditionalServiceAccount.Value))
if (!plan.SecretsManager.HasAdditionalServiceAccountOption ||
(plan.SecretsManager.MaxAdditionalServiceAccount.HasValue && update.SmServiceAccountsExcludingBase > plan.SecretsManager.MaxAdditionalServiceAccount.Value))
{
var planMaxServiceAccounts = plan.BaseServiceAccount.GetValueOrDefault() +
plan.MaxAdditionalServiceAccount.GetValueOrDefault();
var planMaxServiceAccounts = plan.SecretsManager.BaseServiceAccount +
plan.SecretsManager.MaxAdditionalServiceAccount.GetValueOrDefault();
throw new BadRequestException($"You have reached the maximum number of service accounts ({planMaxServiceAccounts}) for this plan.");
}
@ -281,9 +281,9 @@ public class UpdateSecretsManagerSubscriptionCommand : IUpdateSecretsManagerSubs
}
// Check minimum service accounts included with plan
if (plan.BaseServiceAccount.HasValue && plan.BaseServiceAccount.Value > update.SmServiceAccounts.Value)
if (plan.SecretsManager.BaseServiceAccount > update.SmServiceAccounts.Value)
{
throw new BadRequestException($"Plan has a minimum of {plan.BaseServiceAccount} service accounts.");
throw new BadRequestException($"Plan has a minimum of {plan.SecretsManager.BaseServiceAccount} service accounts.");
}
// Check minimum service accounts required by business logic
@ -319,15 +319,15 @@ public class UpdateSecretsManagerSubscriptionCommand : IUpdateSecretsManagerSubs
throw new BadRequestException($"Cannot set max Secrets Manager seat autoscaling below current Secrets Manager seat count.");
}
if (plan.MaxUsers.HasValue && update.MaxAutoscaleSmSeats.Value > plan.MaxUsers)
if (plan.SecretsManager.MaxSeats.HasValue && update.MaxAutoscaleSmSeats.Value > plan.SecretsManager.MaxSeats)
{
throw new BadRequestException(string.Concat(
$"Your plan has a Secrets Manager seat limit of {plan.MaxUsers}, ",
$"Your plan has a Secrets Manager seat limit of {plan.SecretsManager.MaxSeats}, ",
$"but you have specified a max autoscale count of {update.MaxAutoscaleSmSeats}.",
"Reduce your max autoscale count."));
}
if (!plan.AllowSeatAutoscale)
if (!plan.SecretsManager.AllowSeatAutoscale)
{
throw new BadRequestException("Your plan does not allow Secrets Manager seat autoscaling.");
}
@ -349,15 +349,15 @@ public class UpdateSecretsManagerSubscriptionCommand : IUpdateSecretsManagerSubs
$"Cannot set max service accounts autoscaling below current service accounts count.");
}
if (!plan.AllowServiceAccountsAutoscale)
if (!plan.SecretsManager.AllowServiceAccountsAutoscale)
{
throw new BadRequestException("Your plan does not allow service accounts autoscaling.");
}
if (plan.MaxServiceAccounts.HasValue && update.MaxAutoscaleSmServiceAccounts.Value > plan.MaxServiceAccounts)
if (plan.SecretsManager.MaxServiceAccounts.HasValue && update.MaxAutoscaleSmServiceAccounts.Value > plan.SecretsManager.MaxServiceAccounts)
{
throw new BadRequestException(string.Concat(
$"Your plan has a service account limit of {plan.MaxServiceAccounts}, ",
$"Your plan has a service account limit of {plan.SecretsManager.MaxServiceAccounts}, ",
$"but you have specified a max autoscale count of {update.MaxAutoscaleSmServiceAccounts}.",
"Reduce your max autoscale count."));
}

View File

@ -73,69 +73,67 @@ public class UpgradeOrganizationPlanCommand : IUpgradeOrganizationPlanCommand
throw new BadRequestException("Your account has no payment method available.");
}
var existingPasswordManagerPlan = StaticStore.PasswordManagerPlans.FirstOrDefault(p => p.Type == organization.PlanType);
if (existingPasswordManagerPlan == null)
var existingPlan = StaticStore.GetPlan(organization.PlanType);
if (existingPlan == null)
{
throw new BadRequestException("Existing plan not found.");
}
var newPasswordManagerPlan =
StaticStore.PasswordManagerPlans.FirstOrDefault(p => p.Type == upgrade.Plan && !p.Disabled);
if (newPasswordManagerPlan == null)
var newPlan = StaticStore.Plans.FirstOrDefault(p => p.Type == upgrade.Plan && !p.Disabled);
if (newPlan == null)
{
throw new BadRequestException("Plan not found.");
}
if (existingPasswordManagerPlan.Type == newPasswordManagerPlan.Type)
if (existingPlan.Type == newPlan.Type)
{
throw new BadRequestException("Organization is already on this plan.");
}
if (existingPasswordManagerPlan.UpgradeSortOrder >= newPasswordManagerPlan.UpgradeSortOrder)
if (existingPlan.UpgradeSortOrder >= newPlan.UpgradeSortOrder)
{
throw new BadRequestException("You cannot upgrade to this plan.");
}
if (existingPasswordManagerPlan.Type != PlanType.Free)
if (existingPlan.Type != PlanType.Free)
{
throw new BadRequestException("You can only upgrade from the free plan. Contact support.");
}
_organizationService.ValidatePasswordManagerPlan(newPasswordManagerPlan, upgrade);
var newSecretsManagerPlan =
StaticStore.SecretManagerPlans.FirstOrDefault(p => p.Type == upgrade.Plan && !p.Disabled);
_organizationService.ValidatePasswordManagerPlan(newPlan, upgrade);
if (upgrade.UseSecretsManager)
{
_organizationService.ValidateSecretsManagerPlan(newSecretsManagerPlan, upgrade);
_organizationService.ValidateSecretsManagerPlan(newPlan, upgrade);
}
var newPasswordManagerPlanSeats = (short)(newPasswordManagerPlan.BaseSeats +
(newPasswordManagerPlan.HasAdditionalSeatsOption ? upgrade.AdditionalSeats : 0));
if (!organization.Seats.HasValue || organization.Seats.Value > newPasswordManagerPlanSeats)
var updatedPasswordManagerSeats = (short)(newPlan.PasswordManager.BaseSeats +
(newPlan.PasswordManager.HasAdditionalSeatsOption ? upgrade.AdditionalSeats : 0));
if (!organization.Seats.HasValue || organization.Seats.Value > updatedPasswordManagerSeats)
{
var occupiedSeats =
await _organizationUserRepository.GetOccupiedSeatCountByOrganizationIdAsync(organization.Id);
if (occupiedSeats > newPasswordManagerPlanSeats)
if (occupiedSeats > updatedPasswordManagerSeats)
{
throw new BadRequestException($"Your organization currently has {occupiedSeats} seats filled. " +
$"Your new plan only has ({newPasswordManagerPlanSeats}) seats. Remove some users.");
$"Your new plan only has ({updatedPasswordManagerSeats}) seats. Remove some users.");
}
}
if (newPasswordManagerPlan.MaxCollections.HasValue && (!organization.MaxCollections.HasValue ||
if (newPlan.PasswordManager.MaxCollections.HasValue && (!organization.MaxCollections.HasValue ||
organization.MaxCollections.Value >
newPasswordManagerPlan.MaxCollections.Value))
newPlan.PasswordManager.MaxCollections.Value))
{
var collectionCount = await _collectionRepository.GetCountByOrganizationIdAsync(organization.Id);
if (collectionCount > newPasswordManagerPlan.MaxCollections.Value)
if (collectionCount > newPlan.PasswordManager.MaxCollections.Value)
{
throw new BadRequestException($"Your organization currently has {collectionCount} collections. " +
$"Your new plan allows for a maximum of ({newPasswordManagerPlan.MaxCollections.Value}) collections. " +
$"Your new plan allows for a maximum of ({newPlan.PasswordManager.MaxCollections.Value}) collections. " +
"Remove some collections.");
}
}
if (!newPasswordManagerPlan.HasGroups && organization.UseGroups)
if (!newPlan.HasGroups && organization.UseGroups)
{
var groups = await _groupRepository.GetManyByOrganizationIdAsync(organization.Id);
if (groups.Any())
@ -145,7 +143,7 @@ public class UpgradeOrganizationPlanCommand : IUpgradeOrganizationPlanCommand
}
}
if (!newPasswordManagerPlan.HasPolicies && organization.UsePolicies)
if (!newPlan.HasPolicies && organization.UsePolicies)
{
var policies = await _policyRepository.GetManyByOrganizationIdAsync(organization.Id);
if (policies.Any(p => p.Enabled))
@ -155,7 +153,7 @@ public class UpgradeOrganizationPlanCommand : IUpgradeOrganizationPlanCommand
}
}
if (!newPasswordManagerPlan.HasSso && organization.UseSso)
if (!newPlan.HasSso && organization.UseSso)
{
var ssoConfig = await _ssoConfigRepository.GetByOrganizationIdAsync(organization.Id);
if (ssoConfig != null && ssoConfig.Enabled)
@ -165,7 +163,7 @@ public class UpgradeOrganizationPlanCommand : IUpgradeOrganizationPlanCommand
}
}
if (!newPasswordManagerPlan.HasKeyConnector && organization.UseKeyConnector)
if (!newPlan.HasKeyConnector && organization.UseKeyConnector)
{
var ssoConfig = await _ssoConfigRepository.GetByOrganizationIdAsync(organization.Id);
if (ssoConfig != null && ssoConfig.GetData().MemberDecryptionType == MemberDecryptionType.KeyConnector)
@ -175,7 +173,7 @@ public class UpgradeOrganizationPlanCommand : IUpgradeOrganizationPlanCommand
}
}
if (!newPasswordManagerPlan.HasResetPassword && organization.UseResetPassword)
if (!newPlan.HasResetPassword && organization.UseResetPassword)
{
var resetPasswordPolicy =
await _policyRepository.GetByOrganizationIdTypeAsync(organization.Id, PolicyType.ResetPassword);
@ -186,7 +184,7 @@ public class UpgradeOrganizationPlanCommand : IUpgradeOrganizationPlanCommand
}
}
if (!newPasswordManagerPlan.HasScim && organization.UseScim)
if (!newPlan.HasScim && organization.UseScim)
{
var scimConnections = await _organizationConnectionRepository.GetByOrganizationIdTypeAsync(organization.Id,
OrganizationConnectionType.Scim);
@ -197,7 +195,7 @@ public class UpgradeOrganizationPlanCommand : IUpgradeOrganizationPlanCommand
}
}
if (!newPasswordManagerPlan.HasCustomPermissions && organization.UseCustomPermissions)
if (!newPlan.HasCustomPermissions && organization.UseCustomPermissions)
{
var organizationCustomUsers =
await _organizationUserRepository.GetManyByOrganizationAsync(organization.Id,
@ -209,9 +207,9 @@ public class UpgradeOrganizationPlanCommand : IUpgradeOrganizationPlanCommand
}
}
if (upgrade.UseSecretsManager && newSecretsManagerPlan != null)
if (upgrade.UseSecretsManager)
{
await ValidateSecretsManagerSeatsAndServiceAccountAsync(upgrade, organization, newSecretsManagerPlan);
await ValidateSecretsManagerSeatsAndServiceAccountAsync(upgrade, organization, newPlan);
}
// TODO: Check storage?
@ -220,12 +218,8 @@ public class UpgradeOrganizationPlanCommand : IUpgradeOrganizationPlanCommand
if (string.IsNullOrWhiteSpace(organization.GatewaySubscriptionId))
{
var organizationUpgradePlan = upgrade.UseSecretsManager
? StaticStore.Plans.Where(p => p.Type == upgrade.Plan).ToList()
: StaticStore.Plans.Where(p => p.Type == upgrade.Plan && p.BitwardenProduct == BitwardenProductType.PasswordManager).ToList();
paymentIntentClientSecret = await _paymentService.UpgradeFreeOrganizationAsync(organization,
organizationUpgradePlan, upgrade);
newPlan, upgrade);
success = string.IsNullOrWhiteSpace(paymentIntentClientSecret);
}
else
@ -235,34 +229,34 @@ public class UpgradeOrganizationPlanCommand : IUpgradeOrganizationPlanCommand
}
organization.BusinessName = upgrade.BusinessName;
organization.PlanType = newPasswordManagerPlan.Type;
organization.Seats = (short)(newPasswordManagerPlan.BaseSeats + upgrade.AdditionalSeats);
organization.MaxCollections = newPasswordManagerPlan.MaxCollections;
organization.UseGroups = newPasswordManagerPlan.HasGroups;
organization.UseDirectory = newPasswordManagerPlan.HasDirectory;
organization.UseEvents = newPasswordManagerPlan.HasEvents;
organization.UseTotp = newPasswordManagerPlan.HasTotp;
organization.Use2fa = newPasswordManagerPlan.Has2fa;
organization.UseApi = newPasswordManagerPlan.HasApi;
organization.SelfHost = newPasswordManagerPlan.HasSelfHost;
organization.UsePolicies = newPasswordManagerPlan.HasPolicies;
organization.MaxStorageGb = !newPasswordManagerPlan.BaseStorageGb.HasValue
organization.PlanType = newPlan.Type;
organization.Seats = (short)(newPlan.PasswordManager.BaseSeats + upgrade.AdditionalSeats);
organization.MaxCollections = newPlan.PasswordManager.MaxCollections;
organization.UseGroups = newPlan.HasGroups;
organization.UseDirectory = newPlan.HasDirectory;
organization.UseEvents = newPlan.HasEvents;
organization.UseTotp = newPlan.HasTotp;
organization.Use2fa = newPlan.Has2fa;
organization.UseApi = newPlan.HasApi;
organization.SelfHost = newPlan.HasSelfHost;
organization.UsePolicies = newPlan.HasPolicies;
organization.MaxStorageGb = !newPlan.PasswordManager.BaseStorageGb.HasValue
? (short?)null
: (short)(newPasswordManagerPlan.BaseStorageGb.Value + upgrade.AdditionalStorageGb);
organization.UseGroups = newPasswordManagerPlan.HasGroups;
organization.UseDirectory = newPasswordManagerPlan.HasDirectory;
organization.UseEvents = newPasswordManagerPlan.HasEvents;
organization.UseTotp = newPasswordManagerPlan.HasTotp;
organization.Use2fa = newPasswordManagerPlan.Has2fa;
organization.UseApi = newPasswordManagerPlan.HasApi;
organization.UseSso = newPasswordManagerPlan.HasSso;
organization.UseKeyConnector = newPasswordManagerPlan.HasKeyConnector;
organization.UseScim = newPasswordManagerPlan.HasScim;
organization.UseResetPassword = newPasswordManagerPlan.HasResetPassword;
organization.SelfHost = newPasswordManagerPlan.HasSelfHost;
organization.UsersGetPremium = newPasswordManagerPlan.UsersGetPremium || upgrade.PremiumAccessAddon;
organization.UseCustomPermissions = newPasswordManagerPlan.HasCustomPermissions;
organization.Plan = newPasswordManagerPlan.Name;
: (short)(newPlan.PasswordManager.BaseStorageGb.Value + upgrade.AdditionalStorageGb);
organization.UseGroups = newPlan.HasGroups;
organization.UseDirectory = newPlan.HasDirectory;
organization.UseEvents = newPlan.HasEvents;
organization.UseTotp = newPlan.HasTotp;
organization.Use2fa = newPlan.Has2fa;
organization.UseApi = newPlan.HasApi;
organization.UseSso = newPlan.HasSso;
organization.UseKeyConnector = newPlan.HasKeyConnector;
organization.UseScim = newPlan.HasScim;
organization.UseResetPassword = newPlan.HasResetPassword;
organization.SelfHost = newPlan.HasSelfHost;
organization.UsersGetPremium = newPlan.UsersGetPremium || upgrade.PremiumAccessAddon;
organization.UseCustomPermissions = newPlan.HasCustomPermissions;
organization.Plan = newPlan.Name;
organization.Enabled = success;
organization.PublicKey = upgrade.PublicKey;
organization.PrivateKey = upgrade.PrivateKey;
@ -271,8 +265,8 @@ public class UpgradeOrganizationPlanCommand : IUpgradeOrganizationPlanCommand
if (upgrade.UseSecretsManager)
{
organization.SmSeats = newSecretsManagerPlan.BaseSeats + upgrade.AdditionalSmSeats.GetValueOrDefault();
organization.SmServiceAccounts = newSecretsManagerPlan.BaseServiceAccount.GetValueOrDefault() +
organization.SmSeats = newPlan.SecretsManager.BaseSeats + upgrade.AdditionalSmSeats.GetValueOrDefault();
organization.SmServiceAccounts = newPlan.SecretsManager.BaseServiceAccount +
upgrade.AdditionalServiceAccounts.GetValueOrDefault();
}
@ -283,10 +277,10 @@ public class UpgradeOrganizationPlanCommand : IUpgradeOrganizationPlanCommand
await _referenceEventService.RaiseEventAsync(
new ReferenceEvent(ReferenceEventType.UpgradePlan, organization, _currentContext)
{
PlanName = newPasswordManagerPlan.Name,
PlanType = newPasswordManagerPlan.Type,
OldPlanName = existingPasswordManagerPlan.Name,
OldPlanType = existingPasswordManagerPlan.Type,
PlanName = newPlan.Name,
PlanType = newPlan.Type,
OldPlanName = existingPlan.Name,
OldPlanType = existingPlan.Type,
Seats = organization.Seats,
Storage = organization.MaxStorageGb,
// TODO: add reference events for SmSeats and Service Accounts - see AC-1481
@ -299,8 +293,8 @@ public class UpgradeOrganizationPlanCommand : IUpgradeOrganizationPlanCommand
private async Task ValidateSecretsManagerSeatsAndServiceAccountAsync(OrganizationUpgrade upgrade, Organization organization,
Models.StaticStore.Plan newSecretsManagerPlan)
{
var newPlanSmSeats = (short)(newSecretsManagerPlan.BaseSeats +
(newSecretsManagerPlan.HasAdditionalSeatsOption
var newPlanSmSeats = (short)(newSecretsManagerPlan.SecretsManager.BaseSeats +
(newSecretsManagerPlan.SecretsManager.HasAdditionalSeatsOption
? upgrade.AdditionalSmSeats
: 0));
var occupiedSmSeats =
@ -316,10 +310,10 @@ public class UpgradeOrganizationPlanCommand : IUpgradeOrganizationPlanCommand
}
}
var additionalServiceAccounts = newSecretsManagerPlan.HasAdditionalServiceAccountOption
var additionalServiceAccounts = newSecretsManagerPlan.SecretsManager.HasAdditionalServiceAccountOption
? upgrade.AdditionalServiceAccounts
: 0;
var newPlanServiceAccounts = newSecretsManagerPlan.BaseServiceAccount + additionalServiceAccounts;
var newPlanServiceAccounts = newSecretsManagerPlan.SecretsManager.BaseServiceAccount + additionalServiceAccounts;
if (!organization.SmServiceAccounts.HasValue || organization.SmServiceAccounts.Value > newPlanServiceAccounts)
{
@ -329,7 +323,7 @@ public class UpgradeOrganizationPlanCommand : IUpgradeOrganizationPlanCommand
{
throw new BadRequestException(
$"Your organization currently has {currentServiceAccounts} service accounts. " +
$"Your new plan only allows {newSecretsManagerPlan.MaxServiceAccounts} service accounts. " +
$"Your new plan only allows {newSecretsManagerPlan.SecretsManager.MaxServiceAccounts} service accounts. " +
"Remove some service accounts or increase your subscription.");
}
}

View File

@ -9,12 +9,12 @@ public interface IPaymentService
{
Task CancelAndRecoverChargesAsync(ISubscriber subscriber);
Task<string> PurchaseOrganizationAsync(Organization org, PaymentMethodType paymentMethodType,
string paymentToken, List<Plan> plans, short additionalStorageGb, int additionalSeats,
string paymentToken, Plan plan, short additionalStorageGb, int additionalSeats,
bool premiumAccessAddon, TaxInfo taxInfo, bool provider = false, int additionalSmSeats = 0,
int additionalServiceAccount = 0);
Task SponsorOrganizationAsync(Organization org, OrganizationSponsorship sponsorship);
Task RemoveOrganizationSponsorshipAsync(Organization org, OrganizationSponsorship sponsorship);
Task<string> UpgradeFreeOrganizationAsync(Organization org, List<Plan> plans, OrganizationUpgrade upgrade);
Task<string> UpgradeFreeOrganizationAsync(Organization org, Plan plan, OrganizationUpgrade upgrade);
Task<string> PurchasePremiumAsync(User user, PaymentMethodType paymentMethodType, string paymentToken,
short additionalStorageGb, TaxInfo taxInfo);
Task<string> AdjustSeatsAsync(Organization organization, Plan plan, int additionalSeats, DateTime? prorationDate = null);

View File

@ -175,19 +175,19 @@ public class OrganizationService : IOrganizationService
throw new NotFoundException();
}
var plan = StaticStore.PasswordManagerPlans.FirstOrDefault(p => p.Type == organization.PlanType);
var plan = StaticStore.Plans.FirstOrDefault(p => p.Type == organization.PlanType);
if (plan == null)
{
throw new BadRequestException("Existing plan not found.");
}
if (!plan.HasAdditionalStorageOption)
if (!plan.PasswordManager.HasAdditionalStorageOption)
{
throw new BadRequestException("Plan does not allow additional storage.");
}
var secret = await BillingHelpers.AdjustStorageAsync(_paymentService, organization, storageAdjustmentGb,
plan.StripeStoragePlanId);
plan.PasswordManager.StripeStoragePlanId);
await _referenceEventService.RaiseEventAsync(
new ReferenceEvent(ReferenceEventType.AdjustStorage, organization, _currentContext)
{
@ -233,21 +233,21 @@ public class OrganizationService : IOrganizationService
throw new BadRequestException($"Cannot set max seat autoscaling below current seat count.");
}
var plan = StaticStore.PasswordManagerPlans.FirstOrDefault(p => p.Type == organization.PlanType);
var plan = StaticStore.GetPlan(organization.PlanType);
if (plan == null)
{
throw new BadRequestException("Existing plan not found.");
}
if (!plan.AllowSeatAutoscale)
if (!plan.PasswordManager.AllowSeatAutoscale)
{
throw new BadRequestException("Your plan does not allow seat autoscaling.");
}
if (plan.MaxUsers.HasValue && maxAutoscaleSeats.HasValue &&
maxAutoscaleSeats > plan.MaxUsers)
if (plan.PasswordManager.MaxSeats.HasValue && maxAutoscaleSeats.HasValue &&
maxAutoscaleSeats > plan.PasswordManager.MaxSeats)
{
throw new BadRequestException(string.Concat($"Your plan has a seat limit of {plan.MaxUsers}, ",
throw new BadRequestException(string.Concat($"Your plan has a seat limit of {plan.PasswordManager.MaxSeats}, ",
$"but you have specified a max autoscale count of {maxAutoscaleSeats}.",
"Reduce your max autoscale seat count."));
}
@ -285,21 +285,21 @@ public class OrganizationService : IOrganizationService
throw new BadRequestException("No subscription found.");
}
var plan = StaticStore.PasswordManagerPlans.FirstOrDefault(p => p.Type == organization.PlanType);
var plan = StaticStore.Plans.FirstOrDefault(p => p.Type == organization.PlanType);
if (plan == null)
{
throw new BadRequestException("Existing plan not found.");
}
if (!plan.HasAdditionalSeatsOption)
if (!plan.PasswordManager.HasAdditionalSeatsOption)
{
throw new BadRequestException("Plan does not allow additional seats.");
}
var newSeatTotal = organization.Seats.Value + seatAdjustment;
if (plan.BaseSeats > newSeatTotal)
if (plan.PasswordManager.BaseSeats > newSeatTotal)
{
throw new BadRequestException($"Plan has a minimum of {plan.BaseSeats} seats.");
throw new BadRequestException($"Plan has a minimum of {plan.PasswordManager.BaseSeats} seats.");
}
if (newSeatTotal <= 0)
@ -307,11 +307,11 @@ public class OrganizationService : IOrganizationService
throw new BadRequestException("You must have at least 1 seat.");
}
var additionalSeats = newSeatTotal - plan.BaseSeats;
if (plan.MaxAdditionalSeats.HasValue && additionalSeats > plan.MaxAdditionalSeats.Value)
var additionalSeats = newSeatTotal - plan.PasswordManager.BaseSeats;
if (plan.PasswordManager.MaxAdditionalSeats.HasValue && additionalSeats > plan.PasswordManager.MaxAdditionalSeats.Value)
{
throw new BadRequestException($"Organization plan allows a maximum of " +
$"{plan.MaxAdditionalSeats.Value} additional seats.");
$"{plan.PasswordManager.MaxAdditionalSeats.Value} additional seats.");
}
if (!organization.Seats.HasValue || organization.Seats.Value > newSeatTotal)
@ -403,11 +403,10 @@ public class OrganizationService : IOrganizationService
public async Task<Tuple<Organization, OrganizationUser>> SignUpAsync(OrganizationSignup signup,
bool provider = false)
{
var passwordManagerPlan = StaticStore.PasswordManagerPlans.FirstOrDefault(p => p.Type == signup.Plan);
var plan = StaticStore.GetPlan(signup.Plan);
ValidatePasswordManagerPlan(passwordManagerPlan, signup);
ValidatePasswordManagerPlan(plan, signup);
var secretsManagerPlan = StaticStore.SecretManagerPlans.FirstOrDefault(p => p.Type == signup.Plan);
if (signup.UseSecretsManager)
{
if (provider)
@ -415,7 +414,7 @@ public class OrganizationService : IOrganizationService
throw new BadRequestException(
"Organizations with a Managed Service Provider do not support Secrets Manager.");
}
ValidateSecretsManagerPlan(secretsManagerPlan, signup);
ValidateSecretsManagerPlan(plan, signup);
}
if (!provider)
@ -430,25 +429,25 @@ public class OrganizationService : IOrganizationService
Name = signup.Name,
BillingEmail = signup.BillingEmail,
BusinessName = signup.BusinessName,
PlanType = passwordManagerPlan.Type,
Seats = (short)(passwordManagerPlan.BaseSeats + signup.AdditionalSeats),
MaxCollections = passwordManagerPlan.MaxCollections,
MaxStorageGb = !passwordManagerPlan.BaseStorageGb.HasValue ?
(short?)null : (short)(passwordManagerPlan.BaseStorageGb.Value + signup.AdditionalStorageGb),
UsePolicies = passwordManagerPlan.HasPolicies,
UseSso = passwordManagerPlan.HasSso,
UseGroups = passwordManagerPlan.HasGroups,
UseEvents = passwordManagerPlan.HasEvents,
UseDirectory = passwordManagerPlan.HasDirectory,
UseTotp = passwordManagerPlan.HasTotp,
Use2fa = passwordManagerPlan.Has2fa,
UseApi = passwordManagerPlan.HasApi,
UseResetPassword = passwordManagerPlan.HasResetPassword,
SelfHost = passwordManagerPlan.HasSelfHost,
UsersGetPremium = passwordManagerPlan.UsersGetPremium || signup.PremiumAccessAddon,
UseCustomPermissions = passwordManagerPlan.HasCustomPermissions,
UseScim = passwordManagerPlan.HasScim,
Plan = passwordManagerPlan.Name,
PlanType = plan!.Type,
Seats = (short)(plan.PasswordManager.BaseSeats + signup.AdditionalSeats),
MaxCollections = plan.PasswordManager.MaxCollections,
MaxStorageGb = !plan.PasswordManager.BaseStorageGb.HasValue ?
(short?)null : (short)(plan.PasswordManager.BaseStorageGb.Value + signup.AdditionalStorageGb),
UsePolicies = plan.HasPolicies,
UseSso = plan.HasSso,
UseGroups = plan.HasGroups,
UseEvents = plan.HasEvents,
UseDirectory = plan.HasDirectory,
UseTotp = plan.HasTotp,
Use2fa = plan.Has2fa,
UseApi = plan.HasApi,
UseResetPassword = plan.HasResetPassword,
SelfHost = plan.HasSelfHost,
UsersGetPremium = plan.UsersGetPremium || signup.PremiumAccessAddon,
UseCustomPermissions = plan.HasCustomPermissions,
UseScim = plan.HasScim,
Plan = plan.Name,
Gateway = null,
ReferenceData = signup.Owner.ReferenceData,
Enabled = true,
@ -464,12 +463,12 @@ public class OrganizationService : IOrganizationService
if (signup.UseSecretsManager)
{
organization.SmSeats = secretsManagerPlan.BaseSeats + signup.AdditionalSmSeats.GetValueOrDefault();
organization.SmServiceAccounts = secretsManagerPlan.BaseServiceAccount.GetValueOrDefault() +
organization.SmSeats = plan.SecretsManager.BaseSeats + signup.AdditionalSmSeats.GetValueOrDefault();
organization.SmServiceAccounts = plan.SecretsManager.BaseServiceAccount +
signup.AdditionalServiceAccounts.GetValueOrDefault();
}
if (passwordManagerPlan.Type == PlanType.Free && !provider)
if (plan.Type == PlanType.Free && !provider)
{
var adminCount =
await _organizationUserRepository.GetCountByFreeOrganizationAdminUserAsync(signup.Owner.Id);
@ -478,14 +477,10 @@ public class OrganizationService : IOrganizationService
throw new BadRequestException("You can only be an admin of one free organization.");
}
}
else if (passwordManagerPlan.Type != PlanType.Free)
else if (plan.Type != PlanType.Free)
{
var purchaseOrganizationPlan = signup.UseSecretsManager
? StaticStore.Plans.Where(p => p.Type == signup.Plan).ToList()
: StaticStore.PasswordManagerPlans.Where(p => p.Type == signup.Plan).Take(1).ToList();
await _paymentService.PurchaseOrganizationAsync(organization, signup.PaymentMethodType.Value,
signup.PaymentToken, purchaseOrganizationPlan, signup.AdditionalStorageGb, signup.AdditionalSeats,
signup.PaymentToken, plan, signup.AdditionalStorageGb, signup.AdditionalSeats,
signup.PremiumAccessAddon, signup.TaxInfo, provider, signup.AdditionalSmSeats.GetValueOrDefault(),
signup.AdditionalServiceAccounts.GetValueOrDefault());
}
@ -495,8 +490,8 @@ public class OrganizationService : IOrganizationService
await _referenceEventService.RaiseEventAsync(
new ReferenceEvent(ReferenceEventType.Signup, organization, _currentContext)
{
PlanName = passwordManagerPlan.Name,
PlanType = passwordManagerPlan.Type,
PlanName = plan.Name,
PlanType = plan.Type,
Seats = returnValue.Item1.Seats,
Storage = returnValue.Item1.MaxStorageGb,
// TODO: add reference events for SmSeats and Service Accounts - see AC-1481
@ -525,7 +520,7 @@ public class OrganizationService : IOrganizationService
}
if (license.PlanType != PlanType.Custom &&
StaticStore.PasswordManagerPlans.FirstOrDefault(p => p.Type == license.PlanType && !p.Disabled) == null)
StaticStore.Plans.FirstOrDefault(p => p.Type == license.PlanType && !p.Disabled) == null)
{
throw new BadRequestException("Plan not found.");
}
@ -1955,11 +1950,6 @@ public class OrganizationService : IOrganizationService
throw new BadRequestException($"{productType} Plan not found.");
}
if (plan.BaseSeats + additionalSeats <= 0)
{
throw new BadRequestException($"You do not have any {productType} seats!");
}
if (additionalSeats < 0)
{
throw new BadRequestException($"You can't subtract {productType} seats!");
@ -1970,7 +1960,7 @@ public class OrganizationService : IOrganizationService
{
ValidatePlan(plan, upgrade.AdditionalSeats, "Password Manager");
if (plan.BaseSeats + upgrade.AdditionalSeats <= 0)
if (plan.PasswordManager.BaseSeats + upgrade.AdditionalSeats <= 0)
{
throw new BadRequestException($"You do not have any Password Manager seats!");
}
@ -1980,7 +1970,7 @@ public class OrganizationService : IOrganizationService
throw new BadRequestException($"You can't subtract Password Manager seats!");
}
if (!plan.HasAdditionalStorageOption && upgrade.AdditionalStorageGb > 0)
if (!plan.PasswordManager.HasAdditionalStorageOption && upgrade.AdditionalStorageGb > 0)
{
throw new BadRequestException("Plan does not allow additional storage.");
}
@ -1990,29 +1980,39 @@ public class OrganizationService : IOrganizationService
throw new BadRequestException("You can't subtract storage!");
}
if (!plan.HasPremiumAccessOption && upgrade.PremiumAccessAddon)
if (!plan.PasswordManager.HasPremiumAccessOption && upgrade.PremiumAccessAddon)
{
throw new BadRequestException("This plan does not allow you to buy the premium access addon.");
}
if (!plan.HasAdditionalSeatsOption && upgrade.AdditionalSeats > 0)
if (!plan.PasswordManager.HasAdditionalSeatsOption && upgrade.AdditionalSeats > 0)
{
throw new BadRequestException("Plan does not allow additional users.");
}
if (plan.HasAdditionalSeatsOption && plan.MaxAdditionalSeats.HasValue &&
upgrade.AdditionalSeats > plan.MaxAdditionalSeats.Value)
if (plan.PasswordManager.HasAdditionalSeatsOption && plan.PasswordManager.MaxAdditionalSeats.HasValue &&
upgrade.AdditionalSeats > plan.PasswordManager.MaxAdditionalSeats.Value)
{
throw new BadRequestException($"Selected plan allows a maximum of " +
$"{plan.MaxAdditionalSeats.GetValueOrDefault(0)} additional users.");
$"{plan.PasswordManager.MaxAdditionalSeats.GetValueOrDefault(0)} additional users.");
}
}
public void ValidateSecretsManagerPlan(Models.StaticStore.Plan plan, OrganizationUpgrade upgrade)
{
if (plan.SupportsSecretsManager == false)
{
throw new BadRequestException("Invalid Secrets Manager plan selected.");
}
ValidatePlan(plan, upgrade.AdditionalSmSeats.GetValueOrDefault(), "Secrets Manager");
if (!plan.HasAdditionalServiceAccountOption && upgrade.AdditionalServiceAccounts > 0)
if (plan.SecretsManager.BaseSeats + upgrade.AdditionalSmSeats <= 0)
{
throw new BadRequestException($"You do not have any Secrets Manager seats!");
}
if (!plan.SecretsManager.HasAdditionalServiceAccountOption && upgrade.AdditionalServiceAccounts > 0)
{
throw new BadRequestException("Plan does not allow additional Service Accounts.");
}
@ -2027,14 +2027,14 @@ public class OrganizationService : IOrganizationService
throw new BadRequestException("You can't subtract Service Accounts!");
}
switch (plan.HasAdditionalSeatsOption)
switch (plan.SecretsManager.HasAdditionalSeatsOption)
{
case false when upgrade.AdditionalSmSeats > 0:
throw new BadRequestException("Plan does not allow additional users.");
case true when plan.MaxAdditionalSeats.HasValue &&
upgrade.AdditionalSmSeats > plan.MaxAdditionalSeats.Value:
case true when plan.SecretsManager.MaxAdditionalSeats.HasValue &&
upgrade.AdditionalSmSeats > plan.SecretsManager.MaxAdditionalSeats.Value:
throw new BadRequestException($"Selected plan allows a maximum of " +
$"{plan.MaxAdditionalSeats.GetValueOrDefault(0)} additional users.");
$"{plan.SecretsManager.MaxAdditionalSeats.GetValueOrDefault(0)} additional users.");
}
}
@ -2457,7 +2457,7 @@ public class OrganizationService : IOrganizationService
public async Task CreatePendingOrganization(Organization organization, string ownerEmail, ClaimsPrincipal user, IUserService userService, bool salesAssistedTrialStarted)
{
var plan = StaticStore.PasswordManagerPlans.FirstOrDefault(p => p.Type == organization.PlanType);
var plan = StaticStore.Plans.FirstOrDefault(p => p.Type == organization.PlanType);
if (plan is not { LegacyYear: null })
{
throw new BadRequestException("Invalid plan selected.");

View File

@ -49,7 +49,7 @@ public class StripePaymentService : IPaymentService
}
public async Task<string> PurchaseOrganizationAsync(Organization org, PaymentMethodType paymentMethodType,
string paymentToken, List<StaticStore.Plan> plans, short additionalStorageGb,
string paymentToken, StaticStore.Plan plan, short additionalStorageGb,
int additionalSeats, bool premiumAccessAddon, TaxInfo taxInfo, bool provider = false,
int additionalSmSeats = 0, int additionalServiceAccount = 0)
{
@ -119,7 +119,7 @@ public class StripePaymentService : IPaymentService
}
}
var subCreateOptions = new OrganizationPurchaseSubscriptionOptions(org, plans, taxInfo, additionalSeats, additionalStorageGb, premiumAccessAddon
var subCreateOptions = new OrganizationPurchaseSubscriptionOptions(org, plan, taxInfo, additionalSeats, additionalStorageGb, premiumAccessAddon
, additionalSmSeats, additionalServiceAccount);
Stripe.Customer customer = null;
@ -211,7 +211,7 @@ public class StripePaymentService : IPaymentService
private async Task ChangeOrganizationSponsorship(Organization org, OrganizationSponsorship sponsorship, bool applySponsorship)
{
var existingPlan = Utilities.StaticStore.GetPasswordManagerPlan(org.PlanType);
var existingPlan = Utilities.StaticStore.GetPlan(org.PlanType);
var sponsoredPlan = sponsorship != null ?
Utilities.StaticStore.GetSponsoredPlan(sponsorship.PlanSponsorshipType.Value) :
null;
@ -231,7 +231,7 @@ public class StripePaymentService : IPaymentService
public Task RemoveOrganizationSponsorshipAsync(Organization org, OrganizationSponsorship sponsorship) =>
ChangeOrganizationSponsorship(org, sponsorship, false);
public async Task<string> UpgradeFreeOrganizationAsync(Organization org, List<StaticStore.Plan> plans,
public async Task<string> UpgradeFreeOrganizationAsync(Organization org, StaticStore.Plan plan,
OrganizationUpgrade upgrade)
{
if (!string.IsNullOrWhiteSpace(org.GatewaySubscriptionId))
@ -266,7 +266,7 @@ public class StripePaymentService : IPaymentService
}
}
var subCreateOptions = new OrganizationUpgradeSubscriptionOptions(customer.Id, org, plans, upgrade);
var subCreateOptions = new OrganizationUpgradeSubscriptionOptions(customer.Id, org, plan, upgrade);
var (stripePaymentMethod, paymentMethodType) = IdentifyPaymentMethod(customer, subCreateOptions);
var subscription = await ChargeForNewSubscriptionAsync(org, customer, false,

View File

@ -1,398 +0,0 @@
using Bit.Core.Enums;
using Bit.Core.Models.StaticStore;
namespace Bit.Core.Utilities;
public static class PasswordManagerPlanStore
{
public static IEnumerable<Plan> CreatePlan()
{
return new List<Plan>
{
new Plan
{
Type = PlanType.Free,
Product = ProductType.Free,
BitwardenProduct = BitwardenProductType.PasswordManager,
Name = "Free",
NameLocalizationKey = "planNameFree",
DescriptionLocalizationKey = "planDescFree",
BaseSeats = 2,
MaxCollections = 2,
MaxUsers = 2,
UpgradeSortOrder = -1, // Always the lowest plan, cannot be upgraded to
DisplaySortOrder = -1,
AllowSeatAutoscale = false,
},
new Plan
{
Type = PlanType.FamiliesAnnually2019,
Product = ProductType.Families,
BitwardenProduct = BitwardenProductType.PasswordManager,
Name = "Families 2019",
IsAnnual = true,
NameLocalizationKey = "planNameFamilies",
DescriptionLocalizationKey = "planDescFamilies",
BaseSeats = 5,
BaseStorageGb = 1,
MaxUsers = 5,
HasAdditionalStorageOption = true,
HasPremiumAccessOption = true,
TrialPeriodDays = 7,
HasSelfHost = true,
HasTotp = true,
UpgradeSortOrder = 1,
DisplaySortOrder = 1,
LegacyYear = 2020,
StripePlanId = "personal-org-annually",
StripeStoragePlanId = "storage-gb-annually",
StripePremiumAccessPlanId = "personal-org-premium-access-annually",
BasePrice = 12,
AdditionalStoragePricePerGb = 4,
PremiumAccessOptionPrice = 40,
AllowSeatAutoscale = false,
},
new Plan
{
Type = PlanType.TeamsAnnually2019,
Product = ProductType.Teams,
BitwardenProduct = BitwardenProductType.PasswordManager,
Name = "Teams (Annually) 2019",
IsAnnual = true,
NameLocalizationKey = "planNameTeams",
DescriptionLocalizationKey = "planDescTeams",
CanBeUsedByBusiness = true,
BaseSeats = 5,
BaseStorageGb = 1,
HasAdditionalSeatsOption = true,
HasAdditionalStorageOption = true,
TrialPeriodDays = 7,
HasTotp = true,
UpgradeSortOrder = 2,
DisplaySortOrder = 2,
LegacyYear = 2020,
StripePlanId = "teams-org-annually",
StripeSeatPlanId = "teams-org-seat-annually",
StripeStoragePlanId = "storage-gb-annually",
BasePrice = 60,
SeatPrice = 24,
AdditionalStoragePricePerGb = 4,
AllowSeatAutoscale = true,
},
new Plan
{
Type = PlanType.TeamsMonthly2019,
Product = ProductType.Teams,
BitwardenProduct = BitwardenProductType.PasswordManager,
Name = "Teams (Monthly) 2019",
NameLocalizationKey = "planNameTeams",
DescriptionLocalizationKey = "planDescTeams",
CanBeUsedByBusiness = true,
BaseSeats = 5,
BaseStorageGb = 1,
HasAdditionalSeatsOption = true,
HasAdditionalStorageOption = true,
TrialPeriodDays = 7,
HasTotp = true,
UpgradeSortOrder = 2,
DisplaySortOrder = 2,
LegacyYear = 2020,
StripePlanId = "teams-org-monthly",
StripeSeatPlanId = "teams-org-seat-monthly",
StripeStoragePlanId = "storage-gb-monthly",
BasePrice = 8,
SeatPrice = 2.5M,
AdditionalStoragePricePerGb = 0.5M,
AllowSeatAutoscale = true,
},
new Plan
{
Type = PlanType.EnterpriseAnnually2019,
Name = "Enterprise (Annually) 2019",
IsAnnual = true,
Product = ProductType.Enterprise,
BitwardenProduct = BitwardenProductType.PasswordManager,
NameLocalizationKey = "planNameEnterprise",
DescriptionLocalizationKey = "planDescEnterprise",
CanBeUsedByBusiness = true,
BaseSeats = 0,
BaseStorageGb = 1,
HasAdditionalSeatsOption = true,
HasAdditionalStorageOption = true,
TrialPeriodDays = 7,
HasPolicies = true,
HasSelfHost = true,
HasGroups = true,
HasDirectory = true,
HasEvents = true,
HasTotp = true,
Has2fa = true,
HasApi = true,
UsersGetPremium = true,
HasCustomPermissions = true,
UpgradeSortOrder = 3,
DisplaySortOrder = 3,
LegacyYear = 2020,
StripePlanId = null,
StripeSeatPlanId = "enterprise-org-seat-annually",
StripeStoragePlanId = "storage-gb-annually",
BasePrice = 0,
SeatPrice = 36,
AdditionalStoragePricePerGb = 4,
AllowSeatAutoscale = true,
},
new Plan
{
Type = PlanType.EnterpriseMonthly2019,
Product = ProductType.Enterprise,
BitwardenProduct = BitwardenProductType.PasswordManager,
Name = "Enterprise (Monthly) 2019",
NameLocalizationKey = "planNameEnterprise",
DescriptionLocalizationKey = "planDescEnterprise",
CanBeUsedByBusiness = true,
BaseSeats = 0,
BaseStorageGb = 1,
HasAdditionalSeatsOption = true,
HasAdditionalStorageOption = true,
TrialPeriodDays = 7,
HasPolicies = true,
HasGroups = true,
HasDirectory = true,
HasEvents = true,
HasTotp = true,
Has2fa = true,
HasApi = true,
HasSelfHost = true,
UsersGetPremium = true,
HasCustomPermissions = true,
UpgradeSortOrder = 3,
DisplaySortOrder = 3,
LegacyYear = 2020,
StripePlanId = null,
StripeSeatPlanId = "enterprise-org-seat-monthly",
StripeStoragePlanId = "storage-gb-monthly",
BasePrice = 0,
SeatPrice = 4M,
AdditionalStoragePricePerGb = 0.5M,
AllowSeatAutoscale = true,
},
new Plan
{
Type = PlanType.FamiliesAnnually,
Product = ProductType.Families,
BitwardenProduct = BitwardenProductType.PasswordManager,
Name = "Families",
IsAnnual = true,
NameLocalizationKey = "planNameFamilies",
DescriptionLocalizationKey = "planDescFamilies",
BaseSeats = 6,
BaseStorageGb = 1,
MaxUsers = 6,
HasAdditionalStorageOption = true,
TrialPeriodDays = 7,
HasSelfHost = true,
HasTotp = true,
UsersGetPremium = true,
UpgradeSortOrder = 1,
DisplaySortOrder = 1,
StripePlanId = "2020-families-org-annually",
StripeStoragePlanId = "storage-gb-annually",
BasePrice = 40,
AdditionalStoragePricePerGb = 4,
AllowSeatAutoscale = false,
},
new Plan
{
Type = PlanType.TeamsAnnually,
Product = ProductType.Teams,
BitwardenProduct = BitwardenProductType.PasswordManager,
Name = "Teams (Annually)",
IsAnnual = true,
NameLocalizationKey = "planNameTeams",
DescriptionLocalizationKey = "planDescTeams",
CanBeUsedByBusiness = true,
BaseStorageGb = 1,
BaseSeats = 0,
HasAdditionalSeatsOption = true,
HasAdditionalStorageOption = true,
TrialPeriodDays = 7,
Has2fa = true,
HasApi = true,
HasDirectory = true,
HasEvents = true,
HasGroups = true,
HasTotp = true,
UsersGetPremium = true,
UpgradeSortOrder = 2,
DisplaySortOrder = 2,
StripeSeatPlanId = "2020-teams-org-seat-annually",
StripeStoragePlanId = "storage-gb-annually",
SeatPrice = 36,
AdditionalStoragePricePerGb = 4,
AllowSeatAutoscale = true,
},
new Plan
{
Type = PlanType.TeamsMonthly,
Product = ProductType.Teams,
BitwardenProduct = BitwardenProductType.PasswordManager,
Name = "Teams (Monthly)",
NameLocalizationKey = "planNameTeams",
DescriptionLocalizationKey = "planDescTeams",
CanBeUsedByBusiness = true,
BaseStorageGb = 1,
BaseSeats = 0,
HasAdditionalSeatsOption = true,
HasAdditionalStorageOption = true,
TrialPeriodDays = 7,
Has2fa = true,
HasApi = true,
HasDirectory = true,
HasEvents = true,
HasGroups = true,
HasTotp = true,
UsersGetPremium = true,
UpgradeSortOrder = 2,
DisplaySortOrder = 2,
StripeSeatPlanId = "2020-teams-org-seat-monthly",
StripeStoragePlanId = "storage-gb-monthly",
SeatPrice = 4,
AdditionalStoragePricePerGb = 0.5M,
AllowSeatAutoscale = true,
},
new Plan
{
Type = PlanType.EnterpriseAnnually,
Name = "Enterprise (Annually)",
Product = ProductType.Enterprise,
BitwardenProduct = BitwardenProductType.PasswordManager,
IsAnnual = true,
NameLocalizationKey = "planNameEnterprise",
DescriptionLocalizationKey = "planDescEnterprise",
CanBeUsedByBusiness = true,
BaseSeats = 0,
BaseStorageGb = 1,
HasAdditionalSeatsOption = true,
HasAdditionalStorageOption = true,
TrialPeriodDays = 7,
HasPolicies = true,
HasSelfHost = true,
HasGroups = true,
HasDirectory = true,
HasEvents = true,
HasTotp = true,
Has2fa = true,
HasApi = true,
HasSso = true,
HasKeyConnector = true,
HasScim = true,
HasResetPassword = true,
UsersGetPremium = true,
HasCustomPermissions = true,
UpgradeSortOrder = 3,
DisplaySortOrder = 3,
StripeSeatPlanId = "2020-enterprise-org-seat-annually",
StripeStoragePlanId = "storage-gb-annually",
BasePrice = 0,
SeatPrice = 60,
AdditionalStoragePricePerGb = 4,
AllowSeatAutoscale = true,
},
new Plan
{
Type = PlanType.EnterpriseMonthly,
Product = ProductType.Enterprise,
BitwardenProduct = BitwardenProductType.PasswordManager,
Name = "Enterprise (Monthly)",
NameLocalizationKey = "planNameEnterprise",
DescriptionLocalizationKey = "planDescEnterprise",
CanBeUsedByBusiness = true,
BaseSeats = 0,
BaseStorageGb = 1,
HasAdditionalSeatsOption = true,
HasAdditionalStorageOption = true,
TrialPeriodDays = 7,
HasPolicies = true,
HasGroups = true,
HasDirectory = true,
HasEvents = true,
HasTotp = true,
Has2fa = true,
HasApi = true,
HasSelfHost = true,
HasSso = true,
HasKeyConnector = true,
HasScim = true,
HasResetPassword = true,
UsersGetPremium = true,
HasCustomPermissions = true,
UpgradeSortOrder = 3,
DisplaySortOrder = 3,
StripeSeatPlanId = "2020-enterprise-seat-monthly",
StripeStoragePlanId = "storage-gb-monthly",
BasePrice = 0,
SeatPrice = 6,
AdditionalStoragePricePerGb = 0.5M,
AllowSeatAutoscale = true,
},
new Plan
{
Type = PlanType.Custom,
AllowSeatAutoscale = true,
},
};
}
}

View File

@ -1,172 +0,0 @@
using Bit.Core.Enums;
using Bit.Core.Models.StaticStore;
namespace Bit.Core.Utilities;
public static class SecretsManagerPlanStore
{
public static IEnumerable<Plan> CreatePlan()
{
return new List<Plan>
{
new Plan
{
Type = PlanType.EnterpriseMonthly,
Product = ProductType.Enterprise,
BitwardenProduct = BitwardenProductType.SecretsManager,
Name = "Enterprise (Monthly)",
NameLocalizationKey = "planNameEnterprise",
DescriptionLocalizationKey = "planDescEnterprise",
CanBeUsedByBusiness = true,
BaseSeats = 0,
BaseServiceAccount = 200,
HasAdditionalSeatsOption = true,
HasAdditionalServiceAccountOption = true,
TrialPeriodDays = 7,
HasPolicies = true,
HasGroups = true,
HasDirectory = true,
HasEvents = true,
HasTotp = true,
Has2fa = true,
HasApi = true,
HasSelfHost = true,
HasSso = true,
HasKeyConnector = true,
HasScim = true,
HasResetPassword = true,
UsersGetPremium = true,
HasCustomPermissions = true,
UpgradeSortOrder = 3,
DisplaySortOrder = 3,
StripeSeatPlanId = "secrets-manager-enterprise-seat-monthly",
StripeServiceAccountPlanId = "secrets-manager-service-account-monthly",
BasePrice = 0,
SeatPrice = 13,
AdditionalPricePerServiceAccount = 0.5M,
AllowSeatAutoscale = true,
AllowServiceAccountsAutoscale = true
},
new Plan
{
Type = PlanType.EnterpriseAnnually,
Name = "Enterprise (Annually)",
Product = ProductType.Enterprise,
BitwardenProduct = BitwardenProductType.SecretsManager,
IsAnnual = true,
NameLocalizationKey = "planNameEnterprise",
DescriptionLocalizationKey = "planDescEnterprise",
CanBeUsedByBusiness = true,
BaseSeats = 0,
BaseServiceAccount = 200,
HasAdditionalSeatsOption = true,
HasAdditionalServiceAccountOption = true,
TrialPeriodDays = 7,
HasPolicies = true,
HasSelfHost = true,
HasGroups = true,
HasDirectory = true,
HasEvents = true,
HasTotp = true,
Has2fa = true,
HasApi = true,
HasSso = true,
HasKeyConnector = true,
HasScim = true,
HasResetPassword = true,
UsersGetPremium = true,
HasCustomPermissions = true,
UpgradeSortOrder = 3,
DisplaySortOrder = 3,
StripeSeatPlanId = "secrets-manager-enterprise-seat-annually",
StripeServiceAccountPlanId = "secrets-manager-service-account-annually",
BasePrice = 0,
SeatPrice = 144,
AdditionalPricePerServiceAccount = 6,
AllowSeatAutoscale = true,
AllowServiceAccountsAutoscale = true
},
new Plan
{
Type = PlanType.TeamsMonthly,
Name = "Teams (Monthly)",
Product = ProductType.Teams,
BitwardenProduct = BitwardenProductType.SecretsManager,
NameLocalizationKey = "planNameTeams",
DescriptionLocalizationKey = "planDescTeams",
CanBeUsedByBusiness = true,
BaseSeats = 0,
BaseServiceAccount = 50,
HasAdditionalSeatsOption = true,
HasAdditionalServiceAccountOption = true,
TrialPeriodDays = 7,
Has2fa = true,
HasApi = true,
HasDirectory = true,
HasEvents = true,
HasGroups = true,
HasTotp = true,
UsersGetPremium = true,
UpgradeSortOrder = 2,
DisplaySortOrder = 2,
StripeSeatPlanId = "secrets-manager-teams-seat-monthly",
StripeServiceAccountPlanId = "secrets-manager-service-account-monthly",
BasePrice = 0,
SeatPrice = 7,
AdditionalPricePerServiceAccount = 0.5M,
AllowSeatAutoscale = true,
AllowServiceAccountsAutoscale = true
},
new Plan
{
Type = PlanType.TeamsAnnually,
Name = "Teams (Annually)",
Product = ProductType.Teams,
BitwardenProduct = BitwardenProductType.SecretsManager,
IsAnnual = true,
NameLocalizationKey = "planNameTeams",
DescriptionLocalizationKey = "planDescTeams",
CanBeUsedByBusiness = true,
BaseSeats = 0,
BaseServiceAccount = 50,
HasAdditionalSeatsOption = true,
HasAdditionalServiceAccountOption = true,
TrialPeriodDays = 7,
Has2fa = true,
HasApi = true,
HasDirectory = true,
HasEvents = true,
HasGroups = true,
HasTotp = true,
UsersGetPremium = true,
UpgradeSortOrder = 2,
DisplaySortOrder = 2,
StripeSeatPlanId = "secrets-manager-teams-seat-annually",
StripeServiceAccountPlanId = "secrets-manager-service-account-annually",
BasePrice = 0,
SeatPrice = 72,
AdditionalPricePerServiceAccount = 6,
AllowSeatAutoscale = true,
AllowServiceAccountsAutoscale = true
},
new Plan
{
Type = PlanType.Free,
Product = ProductType.Free,
BitwardenProduct = BitwardenProductType.SecretsManager,
Name = "Free",
NameLocalizationKey = "planNameFree",
DescriptionLocalizationKey = "planDescFree",
BaseSeats = 2,
BaseServiceAccount = 3,
MaxProjects = 3,
MaxUsers = 2,
MaxServiceAccounts = 3,
UpgradeSortOrder = -1, // Always the lowest plan, cannot be upgraded to
DisplaySortOrder = -1,
AllowSeatAutoscale = false,
}
};
}
}

View File

@ -1,6 +1,8 @@
using Bit.Core.Enums;
using System.Collections.Immutable;
using Bit.Core.Enums;
using Bit.Core.Models.Data.Organizations.OrganizationUsers;
using Bit.Core.Models.StaticStore;
using Bit.Core.Models.StaticStore.Plans;
namespace Bit.Core.Utilities;
@ -104,21 +106,26 @@ public class StaticStore
GlobalDomains.Add(GlobalEquivalentDomainsType.Pinterest, new List<string> { "pinterest.com", "pinterest.com.au", "pinterest.cl", "pinterest.de", "pinterest.dk", "pinterest.es", "pinterest.fr", "pinterest.co.uk", "pinterest.jp", "pinterest.co.kr", "pinterest.nz", "pinterest.pt", "pinterest.se" });
#endregion
#region Plans
Plans = new List<Models.StaticStore.Plan>
{
new EnterprisePlan(true),
new EnterprisePlan(false),
new TeamsPlan(true),
new TeamsPlan(false),
new FamiliesPlan(),
new FreePlan(),
new CustomPlan(),
PasswordManagerPlans = PasswordManagerPlanStore.CreatePlan();
SecretManagerPlans = SecretsManagerPlanStore.CreatePlan();
Plans = PasswordManagerPlans.Concat(SecretManagerPlans);
#endregion
new Enterprise2019Plan(true),
new Enterprise2019Plan(false),
new Teams2019Plan(true),
new Teams2019Plan(false),
new Families2019Plan(),
}.ToImmutableList();
}
public static IDictionary<GlobalEquivalentDomainsType, IEnumerable<string>> GlobalDomains { get; set; }
public static IEnumerable<Plan> Plans { get; set; }
public static IEnumerable<Plan> SecretManagerPlans { get; set; }
public static IEnumerable<Plan> PasswordManagerPlans { get; set; }
public static IEnumerable<Models.StaticStore.Plan> Plans { get; }
public static IEnumerable<SponsoredPlan> SponsoredPlans { get; set; } = new[]
{
new SponsoredPlan
@ -128,21 +135,20 @@ public class StaticStore
SponsoringProductType = ProductType.Enterprise,
StripePlanId = "2021-family-for-enterprise-annually",
UsersCanSponsor = (OrganizationUserOrganizationDetails org) =>
GetPasswordManagerPlan(org.PlanType).Product == ProductType.Enterprise,
GetPlan(org.PlanType).Product == ProductType.Enterprise,
}
};
public static Plan GetPasswordManagerPlan(PlanType planType) =>
PasswordManagerPlans.SingleOrDefault(p => p.Type == planType);
public static Plan GetSecretsManagerPlan(PlanType planType) =>
SecretManagerPlans.SingleOrDefault(p => p.Type == planType);
public static Models.StaticStore.Plan GetPlan(PlanType planType) =>
Plans.SingleOrDefault(p => p.Type == planType);
public static SponsoredPlan GetSponsoredPlan(PlanSponsorshipType planSponsorshipType) =>
SponsoredPlans.FirstOrDefault(p => p.PlanSponsorshipType == planSponsorshipType);
/// <summary>
/// Determines if the stripe plan id is an addon item by checking if the provided stripe plan id
/// matches either the <see cref="Plan.StripeStoragePlanId"/> or <see cref="Plan.StripeServiceAccountPlanId"/>
/// matches either the <see cref="Plan.PasswordManagerPlanFeatures.StripeStoragePlanId"/> or <see cref="Plan.SecretsManagerPlanFeatures.StripeServiceAccountPlanId"/>
/// in any <see cref="Plans"/>.
/// </summary>
/// <param name="stripePlanId"></param>
@ -151,41 +157,8 @@ public class StaticStore
/// </returns>
public static bool IsAddonSubscriptionItem(string stripePlanId)
{
if (PasswordManagerPlans.Select(p => p.StripeStoragePlanId).Contains(stripePlanId))
{
return true;
}
if (SecretManagerPlans.Select(p => p.StripeServiceAccountPlanId).Contains(stripePlanId))
{
return true;
}
return false;
}
/// <summary>
/// Get a <see cref="Plan"/> by comparing the provided stripeId to the various
/// Stripe plan ids within a <see cref="Plan"/>.
/// The following <see cref="Plan"/> properties are checked:
/// <list type="bullet">
/// <item><see cref="Plan.StripePlanId"/></item>
/// <item><see cref="Plan.StripeSeatPlanId"/></item>
/// <item><see cref="Plan.StripeStoragePlanId"/></item>
/// <item><see cref="Plan.StripeServiceAccountPlanId"/></item>
/// <item><see cref="Plan.StripePremiumAccessPlanId"/></item>
/// </list>
/// </summary>
/// <param name="stripeId"></param>
/// <returns>The plan if a matching stripeId was found, null otherwise</returns>
public static Plan GetPlanByStripeId(string stripeId)
{
return Plans.FirstOrDefault(p =>
p.StripePlanId == stripeId ||
p.StripeSeatPlanId == stripeId ||
p.StripeStoragePlanId == stripeId ||
p.StripeServiceAccountPlanId == stripeId ||
p.StripePremiumAccessPlanId == stripeId
);
return Plans.Any(p =>
p.PasswordManager.StripeStoragePlanId == stripePlanId ||
(p.SecretsManager?.StripeServiceAccountPlanId == stripePlanId));
}
}

View File

@ -20,11 +20,11 @@ namespace Bit.Api.Test.Controllers;
public class OrganizationSponsorshipsControllerTests
{
public static IEnumerable<object[]> EnterprisePlanTypes =>
Enum.GetValues<PlanType>().Where(p => StaticStore.GetPasswordManagerPlan(p).Product == ProductType.Enterprise).Select(p => new object[] { p });
Enum.GetValues<PlanType>().Where(p => StaticStore.GetPlan(p).Product == ProductType.Enterprise).Select(p => new object[] { p });
public static IEnumerable<object[]> NonEnterprisePlanTypes =>
Enum.GetValues<PlanType>().Where(p => StaticStore.GetPasswordManagerPlan(p).Product != ProductType.Enterprise).Select(p => new object[] { p });
Enum.GetValues<PlanType>().Where(p => StaticStore.GetPlan(p).Product != ProductType.Enterprise).Select(p => new object[] { p });
public static IEnumerable<object[]> NonFamiliesPlanTypes =>
Enum.GetValues<PlanType>().Where(p => StaticStore.GetPasswordManagerPlan(p).Product != ProductType.Families).Select(p => new object[] { p });
Enum.GetValues<PlanType>().Where(p => StaticStore.GetPlan(p).Product != ProductType.Families).Select(p => new object[] { p });
public static IEnumerable<object[]> NonConfirmedOrganizationUsersStatuses =>
Enum.GetValues<OrganizationUserStatusType>()

View File

@ -73,7 +73,7 @@ public class SyncControllerTests
user.EquivalentDomains = JsonSerializer.Serialize(userEquivalentDomains);
user.ExcludedGlobalEquivalentDomains = JsonSerializer.Serialize(userExcludedGlobalEquivalentDomains);
// At least 1 org needs to be enabled to fully test
// At least 1 org needs to be enabled to fully test
if (!organizationUserDetails.Any(o => o.Enabled))
{
// We need at least 1 enabled org
@ -165,7 +165,7 @@ public class SyncControllerTests
user.EquivalentDomains = JsonSerializer.Serialize(userEquivalentDomains);
user.ExcludedGlobalEquivalentDomains = JsonSerializer.Serialize(userExcludedGlobalEquivalentDomains);
// All orgs disabled
// All orgs disabled
if (organizationUserDetails.Count > 0)
{
foreach (var orgUserDetails in organizationUserDetails)
@ -218,7 +218,7 @@ public class SyncControllerTests
Assert.IsType<SyncResponseModel>(result);
// Collections should be empty when all standard orgs are disabled.
// Collections should be empty when all standard orgs are disabled.
Assert.Empty(result.Collections);
}
@ -297,7 +297,7 @@ public class SyncControllerTests
Assert.IsType<SyncResponseModel>(result);
// Look up ProviderOrg output and compare to ProviderOrg method inputs to ensure
// product type is set correctly.
// product type is set correctly.
foreach (var profProviderOrg in result.Profile.ProviderOrganizations)
{
var matchedProviderUserOrgDetails =
@ -305,7 +305,7 @@ public class SyncControllerTests
if (matchedProviderUserOrgDetails != null)
{
var providerOrgProductType = StaticStore.GetPasswordManagerPlan(matchedProviderUserOrgDetails.PlanType).Product;
var providerOrgProductType = StaticStore.GetPlan(matchedProviderUserOrgDetails.PlanType).Product;
Assert.Equal(providerOrgProductType, profProviderOrg.PlanProductType);
}
}
@ -337,7 +337,7 @@ public class SyncControllerTests
await sendRepository.ReceivedWithAnyArgs(1)
.GetManyByUserIdAsync(default);
// These two are only called when at least 1 enabled org.
// These two are only called when at least 1 enabled org.
if (hasEnabledOrgs)
{
await collectionRepository.ReceivedWithAnyArgs(1)
@ -347,7 +347,7 @@ public class SyncControllerTests
}
else
{
// all disabled orgs
// all disabled orgs
await collectionRepository.ReceivedWithAnyArgs(0)
.GetManyByUserIdAsync(default);
await collectionCipherRepository.ReceivedWithAnyArgs(0)

View File

@ -66,7 +66,7 @@ internal class PaidOrganization : ICustomization
public PlanType CheckedPlanType { get; set; }
public void Customize(IFixture fixture)
{
var validUpgradePlans = StaticStore.PasswordManagerPlans.Where(p => p.Type != PlanType.Free && p.LegacyYear == null).OrderBy(p => p.UpgradeSortOrder).Select(p => p.Type).ToList();
var validUpgradePlans = StaticStore.Plans.Where(p => p.Type != PlanType.Free && p.LegacyYear == null).OrderBy(p => p.UpgradeSortOrder).Select(p => p.Type).ToList();
var lowestActivePaidPlan = validUpgradePlans.First();
CheckedPlanType = CheckedPlanType.Equals(PlanType.Free) ? lowestActivePaidPlan : CheckedPlanType;
validUpgradePlans.Remove(lowestActivePaidPlan);
@ -94,11 +94,11 @@ internal class FreeOrganizationUpgrade : ICustomization
.With(o => o.PlanType, PlanType.Free));
var plansToIgnore = new List<PlanType> { PlanType.Free, PlanType.Custom };
var selectedPlan = StaticStore.PasswordManagerPlans.Last(p => !plansToIgnore.Contains(p.Type) && !p.Disabled);
var selectedPlan = StaticStore.Plans.Last(p => !plansToIgnore.Contains(p.Type) && !p.Disabled);
fixture.Customize<OrganizationUpgrade>(composer => composer
.With(ou => ou.Plan, selectedPlan.Type)
.With(ou => ou.PremiumAccessAddon, selectedPlan.HasPremiumAccessOption));
.With(ou => ou.PremiumAccessAddon, selectedPlan.PasswordManager.HasPremiumAccessOption));
fixture.Customize<Organization>(composer => composer
.Without(o => o.GatewaySubscriptionId));
}
@ -140,7 +140,7 @@ public class SecretsManagerOrganizationCustomization : ICustomization
.With(o => o.UseSecretsManager, true)
.With(o => o.SecretsManagerBeta, false)
.With(o => o.PlanType, planType)
.With(o => o.Plan, StaticStore.GetPasswordManagerPlan(planType).Name)
.With(o => o.Plan, StaticStore.GetPlan(planType).Name)
.With(o => o.MaxAutoscaleSmSeats, (int?)null)
.With(o => o.MaxAutoscaleSmServiceAccounts, (int?)null)
);

View File

@ -6,16 +6,16 @@ namespace Bit.Core.Test.OrganizationFeatures.OrganizationSponsorships.FamiliesFo
public abstract class FamiliesForEnterpriseTestsBase
{
public static IEnumerable<object[]> EnterprisePlanTypes =>
Enum.GetValues<PlanType>().Where(p => StaticStore.GetPasswordManagerPlan(p).Product == ProductType.Enterprise).Select(p => new object[] { p });
Enum.GetValues<PlanType>().Where(p => StaticStore.GetPlan(p).Product == ProductType.Enterprise).Select(p => new object[] { p });
public static IEnumerable<object[]> NonEnterprisePlanTypes =>
Enum.GetValues<PlanType>().Where(p => StaticStore.GetPasswordManagerPlan(p).Product != ProductType.Enterprise).Select(p => new object[] { p });
Enum.GetValues<PlanType>().Where(p => StaticStore.GetPlan(p).Product != ProductType.Enterprise).Select(p => new object[] { p });
public static IEnumerable<object[]> FamiliesPlanTypes =>
Enum.GetValues<PlanType>().Where(p => StaticStore.GetPasswordManagerPlan(p).Product == ProductType.Families).Select(p => new object[] { p });
Enum.GetValues<PlanType>().Where(p => StaticStore.GetPlan(p).Product == ProductType.Families).Select(p => new object[] { p });
public static IEnumerable<object[]> NonFamiliesPlanTypes =>
Enum.GetValues<PlanType>().Where(p => StaticStore.GetPasswordManagerPlan(p).Product != ProductType.Families).Select(p => new object[] { p });
Enum.GetValues<PlanType>().Where(p => StaticStore.GetPlan(p).Product != ProductType.Families).Select(p => new object[] { p });
public static IEnumerable<object[]> NonConfirmedOrganizationUsersStatuses =>
Enum.GetValues<OrganizationUserStatusType>()

View File

@ -32,7 +32,7 @@ public class AddSecretsManagerSubscriptionCommandTests
{
organization.PlanType = planType;
var plan = StaticStore.SecretManagerPlans.FirstOrDefault(p => p.Type == organization.PlanType);
var plan = StaticStore.Plans.FirstOrDefault(p => p.Type == organization.PlanType);
await sutProvider.Sut.SignUpAsync(organization, additionalSmSeats, additionalServiceAccounts);
@ -49,8 +49,8 @@ public class AddSecretsManagerSubscriptionCommandTests
// TODO: call ReferenceEventService - see AC-1481
sutProvider.GetDependency<IOrganizationService>().Received(1).ReplaceAndUpdateCacheAsync(Arg.Is<Organization>(c =>
c.SmSeats == plan.BaseSeats + additionalSmSeats &&
c.SmServiceAccounts == plan.BaseServiceAccount.GetValueOrDefault() + additionalServiceAccounts &&
c.SmSeats == plan.SecretsManager.BaseSeats + additionalSmSeats &&
c.SmServiceAccounts == plan.SecretsManager.BaseServiceAccount + additionalServiceAccounts &&
c.UseSecretsManager == true));
}

View File

@ -52,7 +52,7 @@ public class UpdateSecretsManagerSubscriptionCommandTests
await sutProvider.Sut.UpdateSubscriptionAsync(update);
var plan = StaticStore.SecretManagerPlans.FirstOrDefault(x => x.Type == organization.PlanType);
var plan = StaticStore.GetPlan(organization.PlanType);
await sutProvider.GetDependency<IPaymentService>().Received(1)
.AdjustSeatsAsync(organization, plan, update.SmSeatsExcludingBase);
await sutProvider.GetDependency<IPaymentService>().Received(1)
@ -96,7 +96,7 @@ public class UpdateSecretsManagerSubscriptionCommandTests
await sutProvider.Sut.UpdateSubscriptionAsync(update);
var plan = StaticStore.SecretManagerPlans.FirstOrDefault(x => x.Type == organization.PlanType);
var plan = StaticStore.GetPlan(organization.PlanType);
await sutProvider.GetDependency<IPaymentService>().Received(1)
.AdjustSeatsAsync(organization, plan, update.SmSeatsExcludingBase);
await sutProvider.GetDependency<IPaymentService>().Received(1)
@ -213,11 +213,11 @@ public class UpdateSecretsManagerSubscriptionCommandTests
public async Task AdjustServiceAccountsAsync_WithEnterpriseOrTeamsPlans_Success(PlanType planType, Guid organizationId,
SutProvider<UpdateSecretsManagerSubscriptionCommand> sutProvider)
{
var plan = StaticStore.SecretManagerPlans.FirstOrDefault(p => p.Type == planType);
var plan = StaticStore.GetPlan(planType);
var organizationSeats = plan.BaseSeats + 10;
var organizationSeats = plan.SecretsManager.BaseSeats + 10;
var organizationMaxAutoscaleSeats = 20;
var organizationServiceAccounts = plan.BaseServiceAccount.GetValueOrDefault() + 10;
var organizationServiceAccounts = plan.SecretsManager.BaseServiceAccount + 10;
var organizationMaxAutoscaleServiceAccounts = 300;
var organization = new Organization
@ -235,7 +235,7 @@ public class UpdateSecretsManagerSubscriptionCommandTests
var smServiceAccountsAdjustment = 10;
var expectedSmServiceAccounts = organizationServiceAccounts + smServiceAccountsAdjustment;
var expectedSmServiceAccountsExcludingBase = expectedSmServiceAccounts - plan.BaseServiceAccount.GetValueOrDefault();
var expectedSmServiceAccountsExcludingBase = expectedSmServiceAccounts - plan.SecretsManager.BaseServiceAccount;
var update = new SecretsManagerSubscriptionUpdate(organization, false).AdjustServiceAccounts(10);

View File

@ -94,6 +94,7 @@ public class UpgradeOrganizationPlanCommandTests
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organization.Id).Returns(organization);
upgrade.AdditionalSmSeats = 10;
upgrade.AdditionalSeats = 10;
upgrade.Plan = PlanType.TeamsAnnually;
await sutProvider.Sut.UpgradePlanAsync(organization.Id, upgrade);
await sutProvider.GetDependency<IOrganizationService>().Received(1).ReplaceAndUpdateCacheAsync(organization);
}
@ -108,8 +109,7 @@ public class UpgradeOrganizationPlanCommandTests
{
upgrade.Plan = planType;
var passwordManagerPlan = StaticStore.GetPasswordManagerPlan(upgrade.Plan);
var secretsManagerPlan = StaticStore.GetSecretsManagerPlan(upgrade.Plan);
var plan = StaticStore.GetPlan(upgrade.Plan);
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organization.Id).Returns(organization);
@ -121,9 +121,9 @@ public class UpgradeOrganizationPlanCommandTests
await sutProvider.GetDependency<IOrganizationService>().Received(1).ReplaceAndUpdateCacheAsync(
Arg.Is<Organization>(o =>
o.Seats == passwordManagerPlan.BaseSeats + upgrade.AdditionalSeats
&& o.SmSeats == secretsManagerPlan.BaseSeats + upgrade.AdditionalSmSeats
&& o.SmServiceAccounts == secretsManagerPlan.BaseServiceAccount + upgrade.AdditionalServiceAccounts));
o.Seats == plan.PasswordManager.BaseSeats + upgrade.AdditionalSeats
&& o.SmSeats == plan.SecretsManager.BaseSeats + upgrade.AdditionalSmSeats
&& o.SmServiceAccounts == plan.SecretsManager.BaseServiceAccount + upgrade.AdditionalServiceAccounts));
Assert.True(result.Item1);
Assert.NotNull(result.Item2);

View File

@ -155,20 +155,20 @@ public class OrganizationServiceTests
{
signup.Plan = planType;
var passwordManagerPlan = StaticStore.GetPasswordManagerPlan(signup.Plan);
var plan = StaticStore.GetPlan(signup.Plan);
signup.AdditionalSeats = 0;
signup.PaymentMethodType = PaymentMethodType.Card;
signup.PremiumAccessAddon = false;
signup.UseSecretsManager = false;
var purchaseOrganizationPlan = StaticStore.Plans.Where(x => x.Type == signup.Plan).ToList();
var purchaseOrganizationPlan = StaticStore.GetPlan(signup.Plan);
var result = await sutProvider.Sut.SignUpAsync(signup);
await sutProvider.GetDependency<IOrganizationRepository>().Received(1).CreateAsync(
Arg.Is<Organization>(o =>
o.Seats == passwordManagerPlan.BaseSeats + signup.AdditionalSeats
o.Seats == plan.PasswordManager.BaseSeats + signup.AdditionalSeats
&& o.SmSeats == null
&& o.SmServiceAccounts == null));
await sutProvider.GetDependency<IOrganizationUserRepository>().Received(1).CreateAsync(
@ -177,8 +177,8 @@ public class OrganizationServiceTests
await sutProvider.GetDependency<IReferenceEventService>().Received(1)
.RaiseEventAsync(Arg.Is<ReferenceEvent>(referenceEvent =>
referenceEvent.Type == ReferenceEventType.Signup &&
referenceEvent.PlanName == passwordManagerPlan.Name &&
referenceEvent.PlanType == passwordManagerPlan.Type &&
referenceEvent.PlanName == plan.Name &&
referenceEvent.PlanType == plan.Type &&
referenceEvent.Seats == result.Item1.Seats &&
referenceEvent.Storage == result.Item1.MaxStorageGb));
// TODO: add reference events for SmSeats and Service Accounts - see AC-1481
@ -192,7 +192,7 @@ public class OrganizationServiceTests
Arg.Any<Organization>(),
signup.PaymentMethodType.Value,
signup.PaymentToken,
Arg.Is<List<Plan>>(plan => plan.Single() == passwordManagerPlan),
plan,
signup.AdditionalStorageGb,
signup.AdditionalSeats,
signup.PremiumAccessAddon,
@ -212,8 +212,7 @@ public class OrganizationServiceTests
{
signup.Plan = planType;
var passwordManagerPlan = StaticStore.GetPasswordManagerPlan(signup.Plan);
var secretsManagerPlan = StaticStore.GetSecretsManagerPlan(signup.Plan);
var plan = StaticStore.GetPlan(signup.Plan);
signup.UseSecretsManager = true;
signup.AdditionalSeats = 15;
@ -222,23 +221,21 @@ public class OrganizationServiceTests
signup.PaymentMethodType = PaymentMethodType.Card;
signup.PremiumAccessAddon = false;
var purchaseOrganizationPlan = StaticStore.Plans.Where(x => x.Type == signup.Plan).ToList();
var result = await sutProvider.Sut.SignUpAsync(signup);
await sutProvider.GetDependency<IOrganizationRepository>().Received(1).CreateAsync(
Arg.Is<Organization>(o =>
o.Seats == passwordManagerPlan.BaseSeats + signup.AdditionalSeats
&& o.SmSeats == secretsManagerPlan.BaseSeats + signup.AdditionalSmSeats
&& o.SmServiceAccounts == secretsManagerPlan.BaseServiceAccount + signup.AdditionalServiceAccounts));
o.Seats == plan.PasswordManager.BaseSeats + signup.AdditionalSeats
&& o.SmSeats == plan.SecretsManager.BaseSeats + signup.AdditionalSmSeats
&& o.SmServiceAccounts == plan.SecretsManager.BaseServiceAccount + signup.AdditionalServiceAccounts));
await sutProvider.GetDependency<IOrganizationUserRepository>().Received(1).CreateAsync(
Arg.Is<OrganizationUser>(o => o.AccessSecretsManager == signup.UseSecretsManager));
await sutProvider.GetDependency<IReferenceEventService>().Received(1)
.RaiseEventAsync(Arg.Is<ReferenceEvent>(referenceEvent =>
referenceEvent.Type == ReferenceEventType.Signup &&
referenceEvent.PlanName == purchaseOrganizationPlan[0].Name &&
referenceEvent.PlanType == purchaseOrganizationPlan[0].Type &&
referenceEvent.PlanName == plan.Name &&
referenceEvent.PlanType == plan.Type &&
referenceEvent.Seats == result.Item1.Seats &&
referenceEvent.Storage == result.Item1.MaxStorageGb));
// TODO: add reference events for SmSeats and Service Accounts - see AC-1481
@ -252,7 +249,7 @@ public class OrganizationServiceTests
Arg.Any<Organization>(),
signup.PaymentMethodType.Value,
signup.PaymentToken,
Arg.Is<List<Plan>>(plan => plan.All(p => purchaseOrganizationPlan.Contains(p))),
Arg.Is<Plan>(plan),
signup.AdditionalStorageGb,
signup.AdditionalSeats,
signup.PremiumAccessAddon,
@ -1706,7 +1703,7 @@ public class OrganizationServiceTests
public void ValidateSecretsManagerPlan_ThrowsException_WhenInvalidPlanSelected(
PlanType planType, SutProvider<OrganizationService> sutProvider)
{
var plan = StaticStore.Plans.FirstOrDefault(x => x.Type == planType);
var plan = StaticStore.GetPlan(planType);
var signup = new OrganizationUpgrade
{
@ -1727,7 +1724,7 @@ public class OrganizationServiceTests
[BitAutoData(PlanType.EnterpriseMonthly)]
public void ValidateSecretsManagerPlan_ThrowsException_WhenNoSecretsManagerSeats(PlanType planType, SutProvider<OrganizationService> sutProvider)
{
var plan = StaticStore.SecretManagerPlans.FirstOrDefault(x => x.Type == planType);
var plan = StaticStore.GetPlan(planType);
var signup = new OrganizationUpgrade
{
UseSecretsManager = true,
@ -1744,7 +1741,7 @@ public class OrganizationServiceTests
[BitAutoData(PlanType.Free)]
public void ValidateSecretsManagerPlan_ThrowsException_WhenSubtractingSeats(PlanType planType, SutProvider<OrganizationService> sutProvider)
{
var plan = StaticStore.SecretManagerPlans.FirstOrDefault(x => x.Type == planType);
var plan = StaticStore.GetPlan(planType);
var signup = new OrganizationUpgrade
{
UseSecretsManager = true,
@ -1761,7 +1758,7 @@ public class OrganizationServiceTests
PlanType planType,
SutProvider<OrganizationService> sutProvider)
{
var plan = StaticStore.SecretManagerPlans.FirstOrDefault(x => x.Type == planType);
var plan = StaticStore.GetPlan(planType);
var signup = new OrganizationUpgrade
{
UseSecretsManager = true,
@ -1780,7 +1777,7 @@ public class OrganizationServiceTests
[BitAutoData(PlanType.EnterpriseMonthly)]
public void ValidateSecretsManagerPlan_ThrowsException_WhenMoreSeatsThanPasswordManagerSeats(PlanType planType, SutProvider<OrganizationService> sutProvider)
{
var plan = StaticStore.SecretManagerPlans.FirstOrDefault(x => x.Type == planType);
var plan = StaticStore.GetPlan(planType);
var signup = new OrganizationUpgrade
{
UseSecretsManager = true,
@ -1801,7 +1798,7 @@ public class OrganizationServiceTests
PlanType planType,
SutProvider<OrganizationService> sutProvider)
{
var plan = StaticStore.SecretManagerPlans.FirstOrDefault(x => x.Type == planType);
var plan = StaticStore.GetPlan(planType);
var signup = new OrganizationUpgrade
{
UseSecretsManager = true,
@ -1819,7 +1816,7 @@ public class OrganizationServiceTests
PlanType planType,
SutProvider<OrganizationService> sutProvider)
{
var plan = StaticStore.SecretManagerPlans.FirstOrDefault(x => x.Type == planType);
var plan = StaticStore.GetPlan(planType);
var signup = new OrganizationUpgrade
{
UseSecretsManager = true,
@ -1840,7 +1837,7 @@ public class OrganizationServiceTests
PlanType planType,
SutProvider<OrganizationService> sutProvider)
{
var plan = StaticStore.SecretManagerPlans.FirstOrDefault(x => x.Type == planType);
var plan = StaticStore.GetPlan(planType);
var signup = new OrganizationUpgrade
{
UseSecretsManager = true,

View File

@ -40,7 +40,7 @@ public class StripePaymentServiceTests
[Theory, BitAutoData]
public async void PurchaseOrganizationAsync_Stripe_ProviderOrg_Coupon_Add(SutProvider<StripePaymentService> sutProvider, Organization organization, string paymentToken, TaxInfo taxInfo, bool provider = true)
{
var plans = StaticStore.Plans.Where(p => p.Type == PlanType.EnterpriseAnnually).ToList();
var plan = StaticStore.GetPlan(PlanType.EnterpriseAnnually);
var stripeAdapter = sutProvider.GetDependency<IStripeAdapter>();
stripeAdapter.CustomerCreateAsync(default).ReturnsForAnyArgs(new Stripe.Customer
@ -56,7 +56,7 @@ public class StripePaymentServiceTests
.BaseServiceUri.CloudRegion
.Returns("US");
var result = await sutProvider.Sut.PurchaseOrganizationAsync(organization, PaymentMethodType.Card, paymentToken, plans, 0, 0, false, taxInfo, provider);
var result = await sutProvider.Sut.PurchaseOrganizationAsync(organization, PaymentMethodType.Card, paymentToken, plan, 0, 0, false, taxInfo, provider);
Assert.Null(result);
Assert.Equal(GatewayType.Stripe, organization.Gateway);
@ -95,8 +95,8 @@ public class StripePaymentServiceTests
public async void PurchaseOrganizationAsync_SM_Stripe_ProviderOrg_Coupon_Add(SutProvider<StripePaymentService> sutProvider, Organization organization,
string paymentToken, TaxInfo taxInfo, bool provider = true)
{
var plans = StaticStore.Plans.Where(p => p.Type == PlanType.EnterpriseAnnually).ToList();
var plan = StaticStore.GetPlan(PlanType.EnterpriseAnnually);
organization.UseSecretsManager = true;
var stripeAdapter = sutProvider.GetDependency<IStripeAdapter>();
stripeAdapter.CustomerCreateAsync(default).ReturnsForAnyArgs(new Stripe.Customer
{
@ -112,7 +112,7 @@ public class StripePaymentServiceTests
.BaseServiceUri.CloudRegion
.Returns("US");
var result = await sutProvider.Sut.PurchaseOrganizationAsync(organization, PaymentMethodType.Card, paymentToken, plans, 1, 1,
var result = await sutProvider.Sut.PurchaseOrganizationAsync(organization, PaymentMethodType.Card, paymentToken, plan, 1, 1,
false, taxInfo, provider, 1, 1);
Assert.Null(result);
@ -151,8 +151,8 @@ public class StripePaymentServiceTests
[Theory, BitAutoData]
public async void PurchaseOrganizationAsync_Stripe(SutProvider<StripePaymentService> sutProvider, Organization organization, string paymentToken, TaxInfo taxInfo)
{
var plans = StaticStore.Plans.Where(p => p.Type == PlanType.EnterpriseAnnually).ToList();
var plan = StaticStore.GetPlan(PlanType.EnterpriseAnnually);
organization.UseSecretsManager = true;
var stripeAdapter = sutProvider.GetDependency<IStripeAdapter>();
stripeAdapter.CustomerCreateAsync(default).ReturnsForAnyArgs(new Stripe.Customer
{
@ -167,7 +167,7 @@ public class StripePaymentServiceTests
.BaseServiceUri.CloudRegion
.Returns("US");
var result = await sutProvider.Sut.PurchaseOrganizationAsync(organization, PaymentMethodType.Card, paymentToken, plans, 0, 0
var result = await sutProvider.Sut.PurchaseOrganizationAsync(organization, PaymentMethodType.Card, paymentToken, plan, 0, 0
, false, taxInfo, false, 8, 10);
Assert.Null(result);
@ -207,7 +207,7 @@ public class StripePaymentServiceTests
[Theory, BitAutoData]
public async void PurchaseOrganizationAsync_Stripe_PM(SutProvider<StripePaymentService> sutProvider, Organization organization, string paymentToken, TaxInfo taxInfo)
{
var plans = StaticStore.PasswordManagerPlans.Where(p => p.Type == PlanType.EnterpriseAnnually).ToList();
var plan = StaticStore.GetPlan(PlanType.EnterpriseAnnually);
paymentToken = "pm_" + paymentToken;
var stripeAdapter = sutProvider.GetDependency<IStripeAdapter>();
@ -224,7 +224,7 @@ public class StripePaymentServiceTests
.BaseServiceUri.CloudRegion
.Returns("US");
var result = await sutProvider.Sut.PurchaseOrganizationAsync(organization, PaymentMethodType.Card, paymentToken, plans, 0, 0, false, taxInfo);
var result = await sutProvider.Sut.PurchaseOrganizationAsync(organization, PaymentMethodType.Card, paymentToken, plan, 0, 0, false, taxInfo);
Assert.Null(result);
Assert.Equal(GatewayType.Stripe, organization.Gateway);
@ -264,7 +264,7 @@ public class StripePaymentServiceTests
[Theory, BitAutoData]
public async void PurchaseOrganizationAsync_Stripe_TaxRate(SutProvider<StripePaymentService> sutProvider, Organization organization, string paymentToken, TaxInfo taxInfo)
{
var plans = StaticStore.Plans.Where(p => p.Type == PlanType.EnterpriseAnnually).ToList();
var plan = StaticStore.GetPlan(PlanType.EnterpriseAnnually);
var stripeAdapter = sutProvider.GetDependency<IStripeAdapter>();
stripeAdapter.CustomerCreateAsync(default).ReturnsForAnyArgs(new Stripe.Customer
@ -280,7 +280,7 @@ public class StripePaymentServiceTests
t.Country == taxInfo.BillingAddressCountry && t.PostalCode == taxInfo.BillingAddressPostalCode))
.Returns(new List<TaxRate> { new() { Id = "T-1" } });
var result = await sutProvider.Sut.PurchaseOrganizationAsync(organization, PaymentMethodType.Card, paymentToken, plans, 0, 0, false, taxInfo);
var result = await sutProvider.Sut.PurchaseOrganizationAsync(organization, PaymentMethodType.Card, paymentToken, plan, 0, 0, false, taxInfo);
Assert.Null(result);
@ -293,7 +293,7 @@ public class StripePaymentServiceTests
[Theory, BitAutoData]
public async void PurchaseOrganizationAsync_Stripe_TaxRate_SM(SutProvider<StripePaymentService> sutProvider, Organization organization, string paymentToken, TaxInfo taxInfo)
{
var plans = StaticStore.Plans.Where(p => p.Type == PlanType.EnterpriseAnnually).ToList();
var plan = StaticStore.GetPlan(PlanType.EnterpriseAnnually);
var stripeAdapter = sutProvider.GetDependency<IStripeAdapter>();
stripeAdapter.CustomerCreateAsync(default).ReturnsForAnyArgs(new Stripe.Customer
@ -309,7 +309,7 @@ public class StripePaymentServiceTests
t.Country == taxInfo.BillingAddressCountry && t.PostalCode == taxInfo.BillingAddressPostalCode))
.Returns(new List<TaxRate> { new() { Id = "T-1" } });
var result = await sutProvider.Sut.PurchaseOrganizationAsync(organization, PaymentMethodType.Card, paymentToken, plans, 2, 2,
var result = await sutProvider.Sut.PurchaseOrganizationAsync(organization, PaymentMethodType.Card, paymentToken, plan, 2, 2,
false, taxInfo, false, 2, 2);
Assert.Null(result);
@ -323,7 +323,7 @@ public class StripePaymentServiceTests
[Theory, BitAutoData]
public async void PurchaseOrganizationAsync_Stripe_Declined(SutProvider<StripePaymentService> sutProvider, Organization organization, string paymentToken, TaxInfo taxInfo)
{
var plan = StaticStore.Plans.Where(p => p.Type == PlanType.EnterpriseAnnually).ToList();
var plan = StaticStore.GetPlan(PlanType.EnterpriseAnnually);
paymentToken = "pm_" + paymentToken;
var stripeAdapter = sutProvider.GetDependency<IStripeAdapter>();
@ -356,7 +356,7 @@ public class StripePaymentServiceTests
[Theory, BitAutoData]
public async void PurchaseOrganizationAsync_SM_Stripe_Declined(SutProvider<StripePaymentService> sutProvider, Organization organization, string paymentToken, TaxInfo taxInfo)
{
var plan = StaticStore.Plans.Where(p => p.Type == PlanType.EnterpriseAnnually).ToList();
var plan = StaticStore.GetPlan(PlanType.EnterpriseAnnually);
paymentToken = "pm_" + paymentToken;
var stripeAdapter = sutProvider.GetDependency<IStripeAdapter>();
@ -390,7 +390,7 @@ public class StripePaymentServiceTests
[Theory, BitAutoData]
public async void PurchaseOrganizationAsync_Stripe_RequiresAction(SutProvider<StripePaymentService> sutProvider, Organization organization, string paymentToken, TaxInfo taxInfo)
{
var plans = StaticStore.Plans.Where(p => p.Type == PlanType.EnterpriseAnnually).ToList();
var plan = StaticStore.GetPlan(PlanType.EnterpriseAnnually);
var stripeAdapter = sutProvider.GetDependency<IStripeAdapter>();
stripeAdapter.CustomerCreateAsync(default).ReturnsForAnyArgs(new Stripe.Customer
@ -412,7 +412,7 @@ public class StripePaymentServiceTests
},
});
var result = await sutProvider.Sut.PurchaseOrganizationAsync(organization, PaymentMethodType.Card, paymentToken, plans, 0, 0, false, taxInfo);
var result = await sutProvider.Sut.PurchaseOrganizationAsync(organization, PaymentMethodType.Card, paymentToken, plan, 0, 0, false, taxInfo);
Assert.Equal("clientSecret", result);
Assert.False(organization.Enabled);
@ -421,7 +421,7 @@ public class StripePaymentServiceTests
[Theory, BitAutoData]
public async void PurchaseOrganizationAsync_SM_Stripe_RequiresAction(SutProvider<StripePaymentService> sutProvider, Organization organization, string paymentToken, TaxInfo taxInfo)
{
var plans = StaticStore.Plans.Where(p => p.Type == PlanType.EnterpriseAnnually).ToList();
var plan = StaticStore.GetPlan(PlanType.EnterpriseAnnually);
var stripeAdapter = sutProvider.GetDependency<IStripeAdapter>();
stripeAdapter.CustomerCreateAsync(default).ReturnsForAnyArgs(new Stripe.Customer
@ -443,7 +443,7 @@ public class StripePaymentServiceTests
},
});
var result = await sutProvider.Sut.PurchaseOrganizationAsync(organization, PaymentMethodType.Card, paymentToken, plans,
var result = await sutProvider.Sut.PurchaseOrganizationAsync(organization, PaymentMethodType.Card, paymentToken, plan,
10, 10, false, taxInfo, false, 10, 10);
Assert.Equal("clientSecret", result);
@ -453,7 +453,7 @@ public class StripePaymentServiceTests
[Theory, BitAutoData]
public async void PurchaseOrganizationAsync_Paypal(SutProvider<StripePaymentService> sutProvider, Organization organization, string paymentToken, TaxInfo taxInfo)
{
var plans = StaticStore.Plans.Where(p => p.Type == PlanType.EnterpriseAnnually).ToList();
var plan = StaticStore.GetPlan(PlanType.EnterpriseAnnually);
var stripeAdapter = sutProvider.GetDependency<IStripeAdapter>();
stripeAdapter.CustomerCreateAsync(default).ReturnsForAnyArgs(new Stripe.Customer
@ -480,7 +480,7 @@ public class StripePaymentServiceTests
var braintreeGateway = sutProvider.GetDependency<IBraintreeGateway>();
braintreeGateway.Customer.CreateAsync(default).ReturnsForAnyArgs(customerResult);
var result = await sutProvider.Sut.PurchaseOrganizationAsync(organization, PaymentMethodType.PayPal, paymentToken, plans, 0, 0, false, taxInfo);
var result = await sutProvider.Sut.PurchaseOrganizationAsync(organization, PaymentMethodType.PayPal, paymentToken, plan, 0, 0, false, taxInfo);
Assert.Null(result);
Assert.Equal(GatewayType.Stripe, organization.Gateway);
@ -517,10 +517,8 @@ public class StripePaymentServiceTests
[Theory, BitAutoData]
public async void PurchaseOrganizationAsync_SM_Paypal(SutProvider<StripePaymentService> sutProvider, Organization organization, string paymentToken, TaxInfo taxInfo)
{
var plans = StaticStore.Plans.Where(p => p.Type == PlanType.EnterpriseAnnually).ToList();
var passwordManagerPlan = plans.Single(p => p.BitwardenProduct == BitwardenProductType.PasswordManager);
var secretsManagerPlan = plans.Single(p => p.BitwardenProduct == BitwardenProductType.SecretsManager);
var plan = StaticStore.GetPlan(PlanType.EnterpriseAnnually);
organization.UseSecretsManager = true;
var stripeAdapter = sutProvider.GetDependency<IStripeAdapter>();
stripeAdapter.CustomerCreateAsync(default).ReturnsForAnyArgs(new Stripe.Customer
{
@ -550,7 +548,7 @@ public class StripePaymentServiceTests
var additionalSeats = 10;
var additionalSmSeats = 5;
var additionalServiceAccounts = 20;
var result = await sutProvider.Sut.PurchaseOrganizationAsync(organization, PaymentMethodType.PayPal, paymentToken, plans,
var result = await sutProvider.Sut.PurchaseOrganizationAsync(organization, PaymentMethodType.PayPal, paymentToken, plan,
additionalStorage, additionalSeats, false, taxInfo, false, additionalSmSeats, additionalServiceAccounts);
Assert.Null(result);
@ -582,17 +580,17 @@ public class StripePaymentServiceTests
s.Expand[0] == "latest_invoice.payment_intent" &&
s.Metadata[organization.GatewayIdField()] == organization.Id.ToString() &&
s.Items.Count == 4 &&
s.Items.Count(i => i.Plan == passwordManagerPlan.StripeSeatPlanId && i.Quantity == additionalSeats) == 1 &&
s.Items.Count(i => i.Plan == passwordManagerPlan.StripeStoragePlanId && i.Quantity == additionalStorage) == 1 &&
s.Items.Count(i => i.Plan == secretsManagerPlan.StripeSeatPlanId && i.Quantity == additionalSmSeats) == 1 &&
s.Items.Count(i => i.Plan == secretsManagerPlan.StripeServiceAccountPlanId && i.Quantity == additionalServiceAccounts) == 1
s.Items.Count(i => i.Plan == plan.PasswordManager.StripeSeatPlanId && i.Quantity == additionalSeats) == 1 &&
s.Items.Count(i => i.Plan == plan.PasswordManager.StripeStoragePlanId && i.Quantity == additionalStorage) == 1 &&
s.Items.Count(i => i.Plan == plan.SecretsManager.StripeSeatPlanId && i.Quantity == additionalSmSeats) == 1 &&
s.Items.Count(i => i.Plan == plan.SecretsManager.StripeServiceAccountPlanId && i.Quantity == additionalServiceAccounts) == 1
));
}
[Theory, BitAutoData]
public async void PurchaseOrganizationAsync_Paypal_FailedCreate(SutProvider<StripePaymentService> sutProvider, Organization organization, string paymentToken, TaxInfo taxInfo)
{
var plans = StaticStore.Plans.Where(p => p.Type == PlanType.EnterpriseAnnually).ToList();
var plan = StaticStore.GetPlan(PlanType.EnterpriseAnnually);
var customerResult = Substitute.For<Result<Customer>>();
customerResult.IsSuccess().Returns(false);
@ -601,7 +599,7 @@ public class StripePaymentServiceTests
braintreeGateway.Customer.CreateAsync(default).ReturnsForAnyArgs(customerResult);
var exception = await Assert.ThrowsAsync<GatewayException>(
() => sutProvider.Sut.PurchaseOrganizationAsync(organization, PaymentMethodType.PayPal, paymentToken, plans, 0, 0, false, taxInfo));
() => sutProvider.Sut.PurchaseOrganizationAsync(organization, PaymentMethodType.PayPal, paymentToken, plan, 0, 0, false, taxInfo));
Assert.Equal("Failed to create PayPal customer record.", exception.Message);
}
@ -609,7 +607,7 @@ public class StripePaymentServiceTests
[Theory, BitAutoData]
public async void PurchaseOrganizationAsync_SM_Paypal_FailedCreate(SutProvider<StripePaymentService> sutProvider, Organization organization, string paymentToken, TaxInfo taxInfo)
{
var plans = StaticStore.Plans.Where(p => p.Type == PlanType.EnterpriseAnnually).ToList();
var plan = StaticStore.GetPlan(PlanType.EnterpriseAnnually);
var customerResult = Substitute.For<Result<Customer>>();
customerResult.IsSuccess().Returns(false);
@ -618,7 +616,7 @@ public class StripePaymentServiceTests
braintreeGateway.Customer.CreateAsync(default).ReturnsForAnyArgs(customerResult);
var exception = await Assert.ThrowsAsync<GatewayException>(
() => sutProvider.Sut.PurchaseOrganizationAsync(organization, PaymentMethodType.PayPal, paymentToken, plans,
() => sutProvider.Sut.PurchaseOrganizationAsync(organization, PaymentMethodType.PayPal, paymentToken, plan,
1, 1, false, taxInfo, false, 8, 8));
Assert.Equal("Failed to create PayPal customer record.", exception.Message);
@ -627,7 +625,7 @@ public class StripePaymentServiceTests
[Theory, BitAutoData]
public async void PurchaseOrganizationAsync_PayPal_Declined(SutProvider<StripePaymentService> sutProvider, Organization organization, string paymentToken, TaxInfo taxInfo)
{
var plans = StaticStore.Plans.Where(p => p.Type == PlanType.EnterpriseAnnually).ToList();
var plans = StaticStore.GetPlan(PlanType.EnterpriseAnnually);
paymentToken = "pm_" + paymentToken;
var stripeAdapter = sutProvider.GetDependency<IStripeAdapter>();
@ -689,7 +687,7 @@ public class StripePaymentServiceTests
});
stripeAdapter.SubscriptionCreateAsync(default).ReturnsForAnyArgs(new Stripe.Subscription { });
var plans = StaticStore.Plans.Where(p => p.Type == PlanType.EnterpriseAnnually).ToList();
var plan = StaticStore.GetPlan(PlanType.EnterpriseAnnually);
var upgrade = new OrganizationUpgrade()
{
@ -700,7 +698,7 @@ public class StripePaymentServiceTests
AdditionalSmSeats = 0,
AdditionalServiceAccounts = 0
};
var result = await sutProvider.Sut.UpgradeFreeOrganizationAsync(organization, plans, upgrade);
var result = await sutProvider.Sut.UpgradeFreeOrganizationAsync(organization, plan, upgrade);
Assert.Null(result);
}
@ -736,8 +734,8 @@ public class StripePaymentServiceTests
AdditionalServiceAccounts = 50
};
var plans = StaticStore.Plans.Where(p => p.Type == PlanType.EnterpriseAnnually).ToList();
var result = await sutProvider.Sut.UpgradeFreeOrganizationAsync(organization, plans, upgrade);
var plan = StaticStore.GetPlan(PlanType.EnterpriseAnnually);
var result = await sutProvider.Sut.UpgradeFreeOrganizationAsync(organization, plan, upgrade);
Assert.Null(result);
}

View File

@ -1,5 +1,4 @@
using Bit.Core.Enums;
using Bit.Core.Models.StaticStore;
using Bit.Core.Utilities;
using Xunit;
@ -14,57 +13,18 @@ public class StaticStoreTests
var plans = StaticStore.Plans;
Assert.NotNull(plans);
Assert.NotEmpty(plans);
Assert.Equal(17, plans.Count());
Assert.Equal(12, plans.Count());
}
[Theory]
[InlineData(PlanType.EnterpriseAnnually)]
public void StaticStore_GetPasswordManagerPlanByPlanType_Success(PlanType planType)
[InlineData(PlanType.EnterpriseMonthly)]
[InlineData(PlanType.TeamsMonthly)]
[InlineData(PlanType.TeamsAnnually)]
public void StaticStore_GetPlan_Success(PlanType planType)
{
var plan = StaticStore.GetPasswordManagerPlan(planType);
var plan = StaticStore.GetPlan(planType);
Assert.NotNull(plan);
Assert.Equal(planType, plan.Type);
}
[Theory]
[InlineData(PlanType.EnterpriseAnnually)]
public void StaticStore_GetSecretsManagerPlanByPlanType_Success(PlanType planType)
{
var plan = StaticStore.GetSecretsManagerPlan(planType);
Assert.NotNull(plan);
Assert.Equal(planType, plan.Type);
}
[Theory]
[InlineData(PlanType.EnterpriseAnnually)]
public void StaticStore_GetPasswordManagerPlan_ReturnsPasswordManagerPlans(PlanType planType)
{
var plan = StaticStore.GetPasswordManagerPlan(planType);
Assert.NotNull(plan);
Assert.Equal(BitwardenProductType.PasswordManager, plan.BitwardenProduct);
}
[Theory]
[InlineData(PlanType.EnterpriseAnnually)]
public void StaticStore_GetSecretsManagerPlan_ReturnsSecretManagerPlans(PlanType planType)
{
var plan = StaticStore.GetSecretsManagerPlan(planType);
Assert.NotNull(plan);
Assert.Equal(BitwardenProductType.SecretsManager, plan.BitwardenProduct);
}
[Theory]
[InlineData(PlanType.EnterpriseAnnually, BitwardenProductType.PasswordManager)]
public void StaticStore_AddDuplicatePlans_SingleOrDefaultThrowsException(PlanType planType, BitwardenProductType bitwardenProductType)
{
var plansStore = new List<Plan>
{
new Plan { Type = PlanType.EnterpriseAnnually, BitwardenProduct = BitwardenProductType.PasswordManager },
new Plan { Type = PlanType.EnterpriseAnnually, BitwardenProduct = BitwardenProductType.PasswordManager }
};
Assert.Throws<InvalidOperationException>(() => plansStore.SingleOrDefault(p => p.Type == planType && p.BitwardenProduct == bitwardenProductType));
}
}