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(); throw new NotFoundException();
} }
var plan = StaticStore.GetSecretsManagerPlan(org.PlanType); var plan = StaticStore.GetPlan(org.PlanType);
if (plan == null) if (plan?.SecretsManager == null)
{ {
throw new BadRequestException("Existing plan not found."); throw new BadRequestException("Existing plan not found.");
} }
@ -37,7 +37,7 @@ public class MaxProjectsQuery : IMaxProjectsQuery
if (plan.Type == PlanType.Free) if (plan.Type == PlanType.Free)
{ {
var projects = await _projectRepository.GetProjectCountByOrganizationIdAsync(organizationId); 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); return (null, null);

View File

@ -205,9 +205,8 @@ public class OrganizationsController : Controller
var organization = await GetOrganization(id, model); var organization = await GetOrganization(id, model);
if (organization.UseSecretsManager && if (organization.UseSecretsManager &&
!organization.SecretsManagerBeta !organization.SecretsManagerBeta &&
&& StaticStore.GetSecretsManagerPlan(organization.PlanType) == null !StaticStore.GetPlan(organization.PlanType).SupportsSecretsManager)
)
{ {
throw new BadRequestException("Plan does not support Secrets Manager"); 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 * Add mappings for individual properties as you need them
*/ */
public IEnumerable<Dictionary<string, object>> GetPlansHelper() => public IEnumerable<Dictionary<string, object>> GetPlansHelper() =>
StaticStore.SecretManagerPlans.Select(p => StaticStore.Plans
new Dictionary<string, object> .Where(p => p.SupportsSecretsManager)
.Select(p => new Dictionary<string, object>
{ {
{ "type", p.Type }, { "type", p.Type },
{ "baseServiceAccount", p.BaseServiceAccount } { "baseServiceAccount", p.SecretsManager.BaseServiceAccount }
}); });
public Organization CreateOrganization(Provider provider) public Organization CreateOrganization(Provider provider)

View File

@ -540,7 +540,7 @@ public class OrganizationsController : Controller
if (model.Type == OrganizationApiKeyType.BillingSync || model.Type == OrganizationApiKeyType.Scim) 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 // 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) if (plan.Product != ProductType.Enterprise)
{ {
throw new NotFoundException(); throw new NotFoundException();

View File

@ -19,30 +19,12 @@ public class PlansController : Controller
[HttpGet("")] [HttpGet("")]
[AllowAnonymous] [AllowAnonymous]
public ListResponseModel<PlanResponseModel> Get() 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 data = StaticStore.Plans;
var responses = data.Select(plan => new PlanResponseModel(plan)); var responses = data.Select(plan => new PlanResponseModel(plan));
return new ListResponseModel<PlanResponseModel>(responses); 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")] [HttpGet("sales-tax-rates")]
public async Task<ListResponseModel<TaxRateResponseModel>> GetTaxRates() public async Task<ListResponseModel<TaxRateResponseModel>> GetTaxRates()
{ {

View File

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

View File

@ -21,15 +21,6 @@ public class PlanResponseModel : ResponseModel
NameLocalizationKey = plan.NameLocalizationKey; NameLocalizationKey = plan.NameLocalizationKey;
DescriptionLocalizationKey = plan.DescriptionLocalizationKey; DescriptionLocalizationKey = plan.DescriptionLocalizationKey;
CanBeUsedByBusiness = plan.CanBeUsedByBusiness; 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; TrialPeriodDays = plan.TrialPeriodDays;
HasSelfHost = plan.HasSelfHost; HasSelfHost = plan.HasSelfHost;
HasPolicies = plan.HasPolicies; HasPolicies = plan.HasPolicies;
@ -45,22 +36,12 @@ public class PlanResponseModel : ResponseModel
DisplaySortOrder = plan.DisplaySortOrder; DisplaySortOrder = plan.DisplaySortOrder;
LegacyYear = plan.LegacyYear; LegacyYear = plan.LegacyYear;
Disabled = plan.Disabled; Disabled = plan.Disabled;
StripePlanId = plan.StripePlanId; if (plan.SecretsManager != null)
StripeSeatPlanId = plan.StripeSeatPlanId; {
StripeStoragePlanId = plan.StripeStoragePlanId; SecretsManager = new SecretsManagerPlanFeaturesResponseModel(plan.SecretsManager);
BasePrice = plan.BasePrice; }
SeatPrice = plan.SeatPrice;
AdditionalStoragePricePerGb = plan.AdditionalStoragePricePerGb;
PremiumAccessOptionPrice = plan.PremiumAccessOptionPrice;
AdditionalPricePerServiceAccount = plan.AdditionalPricePerServiceAccount; PasswordManager = new PasswordManagerPlanFeaturesResponseModel(plan.PasswordManager);
BaseServiceAccount = plan.BaseServiceAccount;
MaxServiceAccounts = plan.MaxServiceAccounts;
MaxAdditionalServiceAccounts = plan.MaxAdditionalServiceAccount;
HasAdditionalServiceAccountOption = plan.HasAdditionalServiceAccountOption;
MaxProjects = plan.MaxProjects;
BitwardenProduct = plan.BitwardenProduct;
StripeServiceAccountPlanId = plan.StripeServiceAccountPlanId;
} }
public PlanType Type { get; set; } public PlanType Type { get; set; }
@ -70,16 +51,6 @@ public class PlanResponseModel : ResponseModel
public string NameLocalizationKey { get; set; } public string NameLocalizationKey { get; set; }
public string DescriptionLocalizationKey { get; set; } public string DescriptionLocalizationKey { get; set; }
public bool CanBeUsedByBusiness { 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 int? TrialPeriodDays { get; set; }
public bool HasSelfHost { get; set; } public bool HasSelfHost { get; set; }
@ -98,21 +69,95 @@ public class PlanResponseModel : ResponseModel
public int DisplaySortOrder { get; set; } public int DisplaySortOrder { get; set; }
public int? LegacyYear { get; set; } public int? LegacyYear { get; set; }
public bool Disabled { 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 class SecretsManagerPlanFeaturesResponseModel
public string StripeSeatPlanId { get; set; } {
public string StripeStoragePlanId { get; set; } public SecretsManagerPlanFeaturesResponseModel(Plan.SecretsManagerPlanFeatures plan)
public string StripePremiumAccessPlanId { get; set; } {
public decimal BasePrice { get; set; } MaxServiceAccounts = plan.MaxServiceAccounts;
public decimal SeatPrice { get; set; } AllowServiceAccountsAutoscale = plan is { AllowServiceAccountsAutoscale: true };
public decimal AdditionalStoragePricePerGb { get; set; } StripeServiceAccountPlanId = plan.StripeServiceAccountPlanId;
public decimal PremiumAccessOptionPrice { get; set; } AdditionalPricePerServiceAccount = plan.AdditionalPricePerServiceAccount;
public string StripeServiceAccountPlanId { get; set; } BaseServiceAccount = plan.BaseServiceAccount;
public decimal? AdditionalPricePerServiceAccount { get; set; } MaxAdditionalServiceAccount = plan.MaxAdditionalServiceAccount;
public short? BaseServiceAccount { get; set; } HasAdditionalServiceAccountOption = plan is { HasAdditionalServiceAccountOption: true };
public short? MaxServiceAccounts { get; set; } StripeSeatPlanId = plan.StripeSeatPlanId;
public short? MaxAdditionalServiceAccounts { get; set; } HasAdditionalSeatsOption = plan is { HasAdditionalSeatsOption: true };
public bool HasAdditionalServiceAccountOption { get; set; } BasePrice = plan.BasePrice;
public short? MaxProjects { get; set; } SeatPrice = plan.SeatPrice;
public BitwardenProductType BitwardenProduct { get; set; } 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 && FamilySponsorshipAvailable = FamilySponsorshipFriendlyName == null &&
StaticStore.GetSponsoredPlan(PlanSponsorshipType.FamiliesForEnterprise) StaticStore.GetSponsoredPlan(PlanSponsorshipType.FamiliesForEnterprise)
.UsersCanSponsor(organization); .UsersCanSponsor(organization);
PlanProductType = StaticStore.GetPasswordManagerPlan(organization.PlanType).Product; PlanProductType = StaticStore.GetPlan(organization.PlanType).Product;
FamilySponsorshipLastSyncDate = organization.FamilySponsorshipLastSyncDate; FamilySponsorshipLastSyncDate = organization.FamilySponsorshipLastSyncDate;
FamilySponsorshipToDelete = organization.FamilySponsorshipToDelete; FamilySponsorshipToDelete = organization.FamilySponsorshipToDelete;
FamilySponsorshipValidUntil = organization.FamilySponsorshipValidUntil; FamilySponsorshipValidUntil = organization.FamilySponsorshipValidUntil;

View File

@ -42,6 +42,6 @@ public class ProfileProviderOrganizationResponseModel : ProfileOrganizationRespo
UserId = organization.UserId; UserId = organization.UserId;
ProviderId = organization.ProviderId; ProviderId = organization.ProviderId;
ProviderName = organization.ProviderName; 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.Entities;
using Bit.Core.Enums;
using Bit.Core.Models.Api; using Bit.Core.Models.Api;
using Bit.Core.Models.Business; using Bit.Core.Models.Business;
using Bit.Core.Utilities; using Bit.Core.Utilities;
@ -98,7 +97,6 @@ public class BillingSubscription
Quantity = item.Quantity; Quantity = item.Quantity;
SponsoredSubscriptionItem = item.SponsoredSubscriptionItem; SponsoredSubscriptionItem = item.SponsoredSubscriptionItem;
AddonSubscriptionItem = item.AddonSubscriptionItem; AddonSubscriptionItem = item.AddonSubscriptionItem;
BitwardenProduct = item.BitwardenProduct;
} }
public string Name { get; set; } public string Name { get; set; }
@ -107,7 +105,6 @@ public class BillingSubscription
public string Interval { get; set; } public string Interval { get; set; }
public bool SponsoredSubscriptionItem { get; set; } public bool SponsoredSubscriptionItem { get; set; }
public bool AddonSubscriptionItem { get; set; } public bool AddonSubscriptionItem { get; set; }
public BitwardenProductType BitwardenProduct { get; set; }
} }
} }

View File

@ -447,7 +447,7 @@ public class StripeController : Controller
// org // org
if (ids.Item1.HasValue) 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); 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 /// 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 /// Usually this is what the organization is billed for
/// </summary> /// </summary>
public int SmSeatsExcludingBase => SmSeats.HasValue ? SmSeats.Value - Plan.BaseSeats : 0; public int SmSeatsExcludingBase => SmSeats.HasValue ? SmSeats.Value - Plan.SecretsManager.BaseSeats : 0;
/// <summary> /// <summary>
/// The seats the organization will have after the update, excluding the base seats included in the plan /// 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 /// Usually this is what the organization is billed for
/// </summary> /// </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 SmSeatsChanged => SmSeats != Organization.SmSeats;
public bool SmServiceAccountsChanged => SmServiceAccounts != Organization.SmServiceAccounts; public bool SmServiceAccountsChanged => SmServiceAccounts != Organization.SmServiceAccounts;
public bool MaxAutoscaleSmSeatsChanged => MaxAutoscaleSmSeats != Organization.MaxAutoscaleSmSeats; public bool MaxAutoscaleSmSeatsChanged => MaxAutoscaleSmSeats != Organization.MaxAutoscaleSmSeats;
public bool MaxAutoscaleSmServiceAccountsChanged => public bool MaxAutoscaleSmServiceAccountsChanged =>
MaxAutoscaleSmServiceAccounts != Organization.MaxAutoscaleSmServiceAccounts; 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 SmSeatAutoscaleLimitReached => SmSeats.HasValue && MaxAutoscaleSmSeats.HasValue && SmSeats == MaxAutoscaleSmSeats;
public bool SmServiceAccountAutoscaleLimitReached => SmServiceAccounts.HasValue && public bool SmServiceAccountAutoscaleLimitReached => SmServiceAccounts.HasValue &&
@ -70,7 +70,7 @@ public class SecretsManagerSubscriptionUpdate
Organization = organization; Organization = organization;
if (Plan == null) if (!Plan.SupportsSecretsManager)
{ {
throw new NotFoundException("Invalid Secrets Manager plan."); throw new NotFoundException("Invalid Secrets Manager plan.");
} }

View File

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

View File

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

View File

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

View File

@ -2,64 +2,84 @@
namespace Bit.Core.Models.StaticStore; namespace Bit.Core.Models.StaticStore;
public class Plan public abstract record Plan
{ {
public PlanType Type { get; set; } public PlanType Type { get; protected init; }
public ProductType Product { get; set; } public ProductType Product { get; protected init; }
public string Name { get; set; } public string Name { get; protected init; }
public bool IsAnnual { get; set; } public bool IsAnnual { get; protected init; }
public string NameLocalizationKey { get; set; } public string NameLocalizationKey { get; protected init; }
public string DescriptionLocalizationKey { get; set; } public string DescriptionLocalizationKey { get; protected init; }
public bool CanBeUsedByBusiness { get; set; } public bool CanBeUsedByBusiness { get; protected init; }
public int BaseSeats { get; set; } public int? TrialPeriodDays { get; protected init; }
public short? BaseStorageGb { get; set; } public bool HasSelfHost { get; protected init; }
public short? MaxCollections { get; set; } public bool HasPolicies { get; protected init; }
public short? MaxUsers { get; set; } public bool HasGroups { get; protected init; }
public short? MaxServiceAccounts { get; set; } public bool HasDirectory { get; protected init; }
public bool AllowSeatAutoscale { get; set; } 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; } // Features
public int? MaxAdditionalSeats { get; set; } public int MaxProjects { get; init; }
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; } public record PasswordManagerPlanFeatures
public bool HasPolicies { get; set; } {
public bool HasGroups { get; set; } // Seats
public bool HasDirectory { get; set; } public string StripePlanId { get; init; }
public bool HasEvents { get; set; } public string StripeSeatPlanId { get; init; }
public bool HasTotp { get; set; } public decimal BasePrice { get; init; }
public bool Has2fa { get; set; } public decimal SeatPrice { get; init; }
public bool HasApi { get; set; } public bool AllowSeatAutoscale { get; init; }
public bool HasSso { get; set; } public bool HasAdditionalSeatsOption { get; init; }
public bool HasKeyConnector { get; set; } public int? MaxAdditionalSeats { get; init; }
public bool HasScim { get; set; } public int BaseSeats { get; init; }
public bool HasResetPassword { get; set; } public bool HasPremiumAccessOption { get; init; }
public bool UsersGetPremium { get; set; } public string StripePremiumAccessPlanId { get; init; }
public bool HasCustomPermissions { get; set; } public decimal PremiumAccessOptionPrice { get; init; }
public short? MaxSeats { get; init; }
public int UpgradeSortOrder { get; set; } // Storage
public int DisplaySortOrder { get; set; } public short? BaseStorageGb { get; init; }
public int? LegacyYear { get; set; } public bool HasAdditionalStorageOption { get; init; }
public bool Disabled { get; set; } public decimal AdditionalStoragePricePerGb { get; init; }
public string StripeStoragePlanId { get; init; }
public string StripePlanId { get; set; } public short? MaxAdditionalStorage { get; init; }
public string StripeSeatPlanId { get; set; } // Feature
public string StripeStoragePlanId { get; set; } public short? MaxCollections { get; init; }
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; }
} }

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; var requiredSponsoringProductType = StaticStore.GetSponsoredPlan(selfHostedSponsorship.PlanSponsorshipType)?.SponsoringProductType;
if (requiredSponsoringProductType == null if (requiredSponsoringProductType == null
|| StaticStore.GetPasswordManagerPlan(sponsoringOrg.PlanType).Product != requiredSponsoringProductType.Value) || StaticStore.GetPlan(sponsoringOrg.PlanType).Product != requiredSponsoringProductType.Value)
{ {
continue; // prevent unsupported sponsorships continue; // prevent unsupported sponsorships
} }

View File

@ -51,7 +51,7 @@ public class SetUpSponsorshipCommand : ISetUpSponsorshipCommand
var requiredSponsoredProductType = StaticStore.GetSponsoredPlan(sponsorship.PlanSponsorshipType.Value)?.SponsoredProductType; var requiredSponsoredProductType = StaticStore.GetSponsoredPlan(sponsorship.PlanSponsorshipType.Value)?.SponsoredProductType;
if (requiredSponsoredProductType == null || if (requiredSponsoredProductType == null ||
sponsoredOrganization == 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."); throw new BadRequestException("Can only redeem sponsorship offer on families organizations.");
} }

View File

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

View File

@ -32,7 +32,7 @@ public class CreateSponsorshipCommand : ICreateSponsorshipCommand
var requiredSponsoringProductType = StaticStore.GetSponsoredPlan(sponsorshipType)?.SponsoringProductType; var requiredSponsoringProductType = StaticStore.GetSponsoredPlan(sponsorshipType)?.SponsoringProductType;
if (requiredSponsoringProductType == null || if (requiredSponsoringProductType == null ||
sponsoringOrg == 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."); throw new BadRequestException("Specified Organization cannot sponsor other organizations.");
} }

View File

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

View File

@ -205,10 +205,10 @@ public class UpdateSecretsManagerSubscriptionCommand : IUpdateSecretsManagerSubs
} }
// Check plan maximum seats // Check plan maximum seats
if (!plan.HasAdditionalSeatsOption || if (!plan.SecretsManager.HasAdditionalSeatsOption ||
(plan.MaxAdditionalSeats.HasValue && update.SmSeatsExcludingBase > plan.MaxAdditionalSeats.Value)) (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."); 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 // 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 // Check minimum seats required by business logic
@ -262,11 +262,11 @@ public class UpdateSecretsManagerSubscriptionCommand : IUpdateSecretsManagerSubs
} }
// Check plan maximum service accounts // Check plan maximum service accounts
if (!plan.HasAdditionalServiceAccountOption || if (!plan.SecretsManager.HasAdditionalServiceAccountOption ||
(plan.MaxAdditionalServiceAccount.HasValue && update.SmServiceAccountsExcludingBase > plan.MaxAdditionalServiceAccount.Value)) (plan.SecretsManager.MaxAdditionalServiceAccount.HasValue && update.SmServiceAccountsExcludingBase > plan.SecretsManager.MaxAdditionalServiceAccount.Value))
{ {
var planMaxServiceAccounts = plan.BaseServiceAccount.GetValueOrDefault() + var planMaxServiceAccounts = plan.SecretsManager.BaseServiceAccount +
plan.MaxAdditionalServiceAccount.GetValueOrDefault(); plan.SecretsManager.MaxAdditionalServiceAccount.GetValueOrDefault();
throw new BadRequestException($"You have reached the maximum number of service accounts ({planMaxServiceAccounts}) for this plan."); 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 // 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 // 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."); 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( 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}.", $"but you have specified a max autoscale count of {update.MaxAutoscaleSmSeats}.",
"Reduce your max autoscale count.")); "Reduce your max autoscale count."));
} }
if (!plan.AllowSeatAutoscale) if (!plan.SecretsManager.AllowSeatAutoscale)
{ {
throw new BadRequestException("Your plan does not allow Secrets Manager seat autoscaling."); 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."); $"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."); 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( 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}.", $"but you have specified a max autoscale count of {update.MaxAutoscaleSmServiceAccounts}.",
"Reduce your max autoscale count.")); "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."); throw new BadRequestException("Your account has no payment method available.");
} }
var existingPasswordManagerPlan = StaticStore.PasswordManagerPlans.FirstOrDefault(p => p.Type == organization.PlanType); var existingPlan = StaticStore.GetPlan(organization.PlanType);
if (existingPasswordManagerPlan == null) if (existingPlan == null)
{ {
throw new BadRequestException("Existing plan not found."); throw new BadRequestException("Existing plan not found.");
} }
var newPasswordManagerPlan = var newPlan = StaticStore.Plans.FirstOrDefault(p => p.Type == upgrade.Plan && !p.Disabled);
StaticStore.PasswordManagerPlans.FirstOrDefault(p => p.Type == upgrade.Plan && !p.Disabled); if (newPlan == null)
if (newPasswordManagerPlan == null)
{ {
throw new BadRequestException("Plan not found."); 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."); 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."); 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."); throw new BadRequestException("You can only upgrade from the free plan. Contact support.");
} }
_organizationService.ValidatePasswordManagerPlan(newPasswordManagerPlan, upgrade); _organizationService.ValidatePasswordManagerPlan(newPlan, upgrade);
var newSecretsManagerPlan =
StaticStore.SecretManagerPlans.FirstOrDefault(p => p.Type == upgrade.Plan && !p.Disabled);
if (upgrade.UseSecretsManager) if (upgrade.UseSecretsManager)
{ {
_organizationService.ValidateSecretsManagerPlan(newSecretsManagerPlan, upgrade); _organizationService.ValidateSecretsManagerPlan(newPlan, upgrade);
} }
var newPasswordManagerPlanSeats = (short)(newPasswordManagerPlan.BaseSeats + var updatedPasswordManagerSeats = (short)(newPlan.PasswordManager.BaseSeats +
(newPasswordManagerPlan.HasAdditionalSeatsOption ? upgrade.AdditionalSeats : 0)); (newPlan.PasswordManager.HasAdditionalSeatsOption ? upgrade.AdditionalSeats : 0));
if (!organization.Seats.HasValue || organization.Seats.Value > newPasswordManagerPlanSeats) if (!organization.Seats.HasValue || organization.Seats.Value > updatedPasswordManagerSeats)
{ {
var occupiedSeats = var occupiedSeats =
await _organizationUserRepository.GetOccupiedSeatCountByOrganizationIdAsync(organization.Id); await _organizationUserRepository.GetOccupiedSeatCountByOrganizationIdAsync(organization.Id);
if (occupiedSeats > newPasswordManagerPlanSeats) if (occupiedSeats > updatedPasswordManagerSeats)
{ {
throw new BadRequestException($"Your organization currently has {occupiedSeats} seats filled. " + 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 > organization.MaxCollections.Value >
newPasswordManagerPlan.MaxCollections.Value)) newPlan.PasswordManager.MaxCollections.Value))
{ {
var collectionCount = await _collectionRepository.GetCountByOrganizationIdAsync(organization.Id); 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. " + 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."); "Remove some collections.");
} }
} }
if (!newPasswordManagerPlan.HasGroups && organization.UseGroups) if (!newPlan.HasGroups && organization.UseGroups)
{ {
var groups = await _groupRepository.GetManyByOrganizationIdAsync(organization.Id); var groups = await _groupRepository.GetManyByOrganizationIdAsync(organization.Id);
if (groups.Any()) 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); var policies = await _policyRepository.GetManyByOrganizationIdAsync(organization.Id);
if (policies.Any(p => p.Enabled)) 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); var ssoConfig = await _ssoConfigRepository.GetByOrganizationIdAsync(organization.Id);
if (ssoConfig != null && ssoConfig.Enabled) 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); var ssoConfig = await _ssoConfigRepository.GetByOrganizationIdAsync(organization.Id);
if (ssoConfig != null && ssoConfig.GetData().MemberDecryptionType == MemberDecryptionType.KeyConnector) 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 = var resetPasswordPolicy =
await _policyRepository.GetByOrganizationIdTypeAsync(organization.Id, PolicyType.ResetPassword); 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, var scimConnections = await _organizationConnectionRepository.GetByOrganizationIdTypeAsync(organization.Id,
OrganizationConnectionType.Scim); OrganizationConnectionType.Scim);
@ -197,7 +195,7 @@ public class UpgradeOrganizationPlanCommand : IUpgradeOrganizationPlanCommand
} }
} }
if (!newPasswordManagerPlan.HasCustomPermissions && organization.UseCustomPermissions) if (!newPlan.HasCustomPermissions && organization.UseCustomPermissions)
{ {
var organizationCustomUsers = var organizationCustomUsers =
await _organizationUserRepository.GetManyByOrganizationAsync(organization.Id, 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? // TODO: Check storage?
@ -220,12 +218,8 @@ public class UpgradeOrganizationPlanCommand : IUpgradeOrganizationPlanCommand
if (string.IsNullOrWhiteSpace(organization.GatewaySubscriptionId)) 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, paymentIntentClientSecret = await _paymentService.UpgradeFreeOrganizationAsync(organization,
organizationUpgradePlan, upgrade); newPlan, upgrade);
success = string.IsNullOrWhiteSpace(paymentIntentClientSecret); success = string.IsNullOrWhiteSpace(paymentIntentClientSecret);
} }
else else
@ -235,34 +229,34 @@ public class UpgradeOrganizationPlanCommand : IUpgradeOrganizationPlanCommand
} }
organization.BusinessName = upgrade.BusinessName; organization.BusinessName = upgrade.BusinessName;
organization.PlanType = newPasswordManagerPlan.Type; organization.PlanType = newPlan.Type;
organization.Seats = (short)(newPasswordManagerPlan.BaseSeats + upgrade.AdditionalSeats); organization.Seats = (short)(newPlan.PasswordManager.BaseSeats + upgrade.AdditionalSeats);
organization.MaxCollections = newPasswordManagerPlan.MaxCollections; organization.MaxCollections = newPlan.PasswordManager.MaxCollections;
organization.UseGroups = newPasswordManagerPlan.HasGroups; organization.UseGroups = newPlan.HasGroups;
organization.UseDirectory = newPasswordManagerPlan.HasDirectory; organization.UseDirectory = newPlan.HasDirectory;
organization.UseEvents = newPasswordManagerPlan.HasEvents; organization.UseEvents = newPlan.HasEvents;
organization.UseTotp = newPasswordManagerPlan.HasTotp; organization.UseTotp = newPlan.HasTotp;
organization.Use2fa = newPasswordManagerPlan.Has2fa; organization.Use2fa = newPlan.Has2fa;
organization.UseApi = newPasswordManagerPlan.HasApi; organization.UseApi = newPlan.HasApi;
organization.SelfHost = newPasswordManagerPlan.HasSelfHost; organization.SelfHost = newPlan.HasSelfHost;
organization.UsePolicies = newPasswordManagerPlan.HasPolicies; organization.UsePolicies = newPlan.HasPolicies;
organization.MaxStorageGb = !newPasswordManagerPlan.BaseStorageGb.HasValue organization.MaxStorageGb = !newPlan.PasswordManager.BaseStorageGb.HasValue
? (short?)null ? (short?)null
: (short)(newPasswordManagerPlan.BaseStorageGb.Value + upgrade.AdditionalStorageGb); : (short)(newPlan.PasswordManager.BaseStorageGb.Value + upgrade.AdditionalStorageGb);
organization.UseGroups = newPasswordManagerPlan.HasGroups; organization.UseGroups = newPlan.HasGroups;
organization.UseDirectory = newPasswordManagerPlan.HasDirectory; organization.UseDirectory = newPlan.HasDirectory;
organization.UseEvents = newPasswordManagerPlan.HasEvents; organization.UseEvents = newPlan.HasEvents;
organization.UseTotp = newPasswordManagerPlan.HasTotp; organization.UseTotp = newPlan.HasTotp;
organization.Use2fa = newPasswordManagerPlan.Has2fa; organization.Use2fa = newPlan.Has2fa;
organization.UseApi = newPasswordManagerPlan.HasApi; organization.UseApi = newPlan.HasApi;
organization.UseSso = newPasswordManagerPlan.HasSso; organization.UseSso = newPlan.HasSso;
organization.UseKeyConnector = newPasswordManagerPlan.HasKeyConnector; organization.UseKeyConnector = newPlan.HasKeyConnector;
organization.UseScim = newPasswordManagerPlan.HasScim; organization.UseScim = newPlan.HasScim;
organization.UseResetPassword = newPasswordManagerPlan.HasResetPassword; organization.UseResetPassword = newPlan.HasResetPassword;
organization.SelfHost = newPasswordManagerPlan.HasSelfHost; organization.SelfHost = newPlan.HasSelfHost;
organization.UsersGetPremium = newPasswordManagerPlan.UsersGetPremium || upgrade.PremiumAccessAddon; organization.UsersGetPremium = newPlan.UsersGetPremium || upgrade.PremiumAccessAddon;
organization.UseCustomPermissions = newPasswordManagerPlan.HasCustomPermissions; organization.UseCustomPermissions = newPlan.HasCustomPermissions;
organization.Plan = newPasswordManagerPlan.Name; organization.Plan = newPlan.Name;
organization.Enabled = success; organization.Enabled = success;
organization.PublicKey = upgrade.PublicKey; organization.PublicKey = upgrade.PublicKey;
organization.PrivateKey = upgrade.PrivateKey; organization.PrivateKey = upgrade.PrivateKey;
@ -271,8 +265,8 @@ public class UpgradeOrganizationPlanCommand : IUpgradeOrganizationPlanCommand
if (upgrade.UseSecretsManager) if (upgrade.UseSecretsManager)
{ {
organization.SmSeats = newSecretsManagerPlan.BaseSeats + upgrade.AdditionalSmSeats.GetValueOrDefault(); organization.SmSeats = newPlan.SecretsManager.BaseSeats + upgrade.AdditionalSmSeats.GetValueOrDefault();
organization.SmServiceAccounts = newSecretsManagerPlan.BaseServiceAccount.GetValueOrDefault() + organization.SmServiceAccounts = newPlan.SecretsManager.BaseServiceAccount +
upgrade.AdditionalServiceAccounts.GetValueOrDefault(); upgrade.AdditionalServiceAccounts.GetValueOrDefault();
} }
@ -283,10 +277,10 @@ public class UpgradeOrganizationPlanCommand : IUpgradeOrganizationPlanCommand
await _referenceEventService.RaiseEventAsync( await _referenceEventService.RaiseEventAsync(
new ReferenceEvent(ReferenceEventType.UpgradePlan, organization, _currentContext) new ReferenceEvent(ReferenceEventType.UpgradePlan, organization, _currentContext)
{ {
PlanName = newPasswordManagerPlan.Name, PlanName = newPlan.Name,
PlanType = newPasswordManagerPlan.Type, PlanType = newPlan.Type,
OldPlanName = existingPasswordManagerPlan.Name, OldPlanName = existingPlan.Name,
OldPlanType = existingPasswordManagerPlan.Type, OldPlanType = existingPlan.Type,
Seats = organization.Seats, Seats = organization.Seats,
Storage = organization.MaxStorageGb, Storage = organization.MaxStorageGb,
// TODO: add reference events for SmSeats and Service Accounts - see AC-1481 // 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, private async Task ValidateSecretsManagerSeatsAndServiceAccountAsync(OrganizationUpgrade upgrade, Organization organization,
Models.StaticStore.Plan newSecretsManagerPlan) Models.StaticStore.Plan newSecretsManagerPlan)
{ {
var newPlanSmSeats = (short)(newSecretsManagerPlan.BaseSeats + var newPlanSmSeats = (short)(newSecretsManagerPlan.SecretsManager.BaseSeats +
(newSecretsManagerPlan.HasAdditionalSeatsOption (newSecretsManagerPlan.SecretsManager.HasAdditionalSeatsOption
? upgrade.AdditionalSmSeats ? upgrade.AdditionalSmSeats
: 0)); : 0));
var occupiedSmSeats = var occupiedSmSeats =
@ -316,10 +310,10 @@ public class UpgradeOrganizationPlanCommand : IUpgradeOrganizationPlanCommand
} }
} }
var additionalServiceAccounts = newSecretsManagerPlan.HasAdditionalServiceAccountOption var additionalServiceAccounts = newSecretsManagerPlan.SecretsManager.HasAdditionalServiceAccountOption
? upgrade.AdditionalServiceAccounts ? upgrade.AdditionalServiceAccounts
: 0; : 0;
var newPlanServiceAccounts = newSecretsManagerPlan.BaseServiceAccount + additionalServiceAccounts; var newPlanServiceAccounts = newSecretsManagerPlan.SecretsManager.BaseServiceAccount + additionalServiceAccounts;
if (!organization.SmServiceAccounts.HasValue || organization.SmServiceAccounts.Value > newPlanServiceAccounts) if (!organization.SmServiceAccounts.HasValue || organization.SmServiceAccounts.Value > newPlanServiceAccounts)
{ {
@ -329,7 +323,7 @@ public class UpgradeOrganizationPlanCommand : IUpgradeOrganizationPlanCommand
{ {
throw new BadRequestException( throw new BadRequestException(
$"Your organization currently has {currentServiceAccounts} service accounts. " + $"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."); "Remove some service accounts or increase your subscription.");
} }
} }

View File

@ -9,12 +9,12 @@ public interface IPaymentService
{ {
Task CancelAndRecoverChargesAsync(ISubscriber subscriber); Task CancelAndRecoverChargesAsync(ISubscriber subscriber);
Task<string> PurchaseOrganizationAsync(Organization org, PaymentMethodType paymentMethodType, 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, bool premiumAccessAddon, TaxInfo taxInfo, bool provider = false, int additionalSmSeats = 0,
int additionalServiceAccount = 0); int additionalServiceAccount = 0);
Task SponsorOrganizationAsync(Organization org, OrganizationSponsorship sponsorship); Task SponsorOrganizationAsync(Organization org, OrganizationSponsorship sponsorship);
Task RemoveOrganizationSponsorshipAsync(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, Task<string> PurchasePremiumAsync(User user, PaymentMethodType paymentMethodType, string paymentToken,
short additionalStorageGb, TaxInfo taxInfo); short additionalStorageGb, TaxInfo taxInfo);
Task<string> AdjustSeatsAsync(Organization organization, Plan plan, int additionalSeats, DateTime? prorationDate = null); 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(); 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) if (plan == null)
{ {
throw new BadRequestException("Existing plan not found."); throw new BadRequestException("Existing plan not found.");
} }
if (!plan.HasAdditionalStorageOption) if (!plan.PasswordManager.HasAdditionalStorageOption)
{ {
throw new BadRequestException("Plan does not allow additional storage."); throw new BadRequestException("Plan does not allow additional storage.");
} }
var secret = await BillingHelpers.AdjustStorageAsync(_paymentService, organization, storageAdjustmentGb, var secret = await BillingHelpers.AdjustStorageAsync(_paymentService, organization, storageAdjustmentGb,
plan.StripeStoragePlanId); plan.PasswordManager.StripeStoragePlanId);
await _referenceEventService.RaiseEventAsync( await _referenceEventService.RaiseEventAsync(
new ReferenceEvent(ReferenceEventType.AdjustStorage, organization, _currentContext) 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."); 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) if (plan == null)
{ {
throw new BadRequestException("Existing plan not found."); throw new BadRequestException("Existing plan not found.");
} }
if (!plan.AllowSeatAutoscale) if (!plan.PasswordManager.AllowSeatAutoscale)
{ {
throw new BadRequestException("Your plan does not allow seat autoscaling."); throw new BadRequestException("Your plan does not allow seat autoscaling.");
} }
if (plan.MaxUsers.HasValue && maxAutoscaleSeats.HasValue && if (plan.PasswordManager.MaxSeats.HasValue && maxAutoscaleSeats.HasValue &&
maxAutoscaleSeats > plan.MaxUsers) 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}.", $"but you have specified a max autoscale count of {maxAutoscaleSeats}.",
"Reduce your max autoscale seat count.")); "Reduce your max autoscale seat count."));
} }
@ -285,21 +285,21 @@ public class OrganizationService : IOrganizationService
throw new BadRequestException("No subscription found."); 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) if (plan == null)
{ {
throw new BadRequestException("Existing plan not found."); throw new BadRequestException("Existing plan not found.");
} }
if (!plan.HasAdditionalSeatsOption) if (!plan.PasswordManager.HasAdditionalSeatsOption)
{ {
throw new BadRequestException("Plan does not allow additional seats."); throw new BadRequestException("Plan does not allow additional seats.");
} }
var newSeatTotal = organization.Seats.Value + seatAdjustment; 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) if (newSeatTotal <= 0)
@ -307,11 +307,11 @@ public class OrganizationService : IOrganizationService
throw new BadRequestException("You must have at least 1 seat."); throw new BadRequestException("You must have at least 1 seat.");
} }
var additionalSeats = newSeatTotal - plan.BaseSeats; var additionalSeats = newSeatTotal - plan.PasswordManager.BaseSeats;
if (plan.MaxAdditionalSeats.HasValue && additionalSeats > plan.MaxAdditionalSeats.Value) if (plan.PasswordManager.MaxAdditionalSeats.HasValue && additionalSeats > plan.PasswordManager.MaxAdditionalSeats.Value)
{ {
throw new BadRequestException($"Organization plan allows a maximum of " + 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) 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, public async Task<Tuple<Organization, OrganizationUser>> SignUpAsync(OrganizationSignup signup,
bool provider = false) 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 (signup.UseSecretsManager)
{ {
if (provider) if (provider)
@ -415,7 +414,7 @@ public class OrganizationService : IOrganizationService
throw new BadRequestException( throw new BadRequestException(
"Organizations with a Managed Service Provider do not support Secrets Manager."); "Organizations with a Managed Service Provider do not support Secrets Manager.");
} }
ValidateSecretsManagerPlan(secretsManagerPlan, signup); ValidateSecretsManagerPlan(plan, signup);
} }
if (!provider) if (!provider)
@ -430,25 +429,25 @@ public class OrganizationService : IOrganizationService
Name = signup.Name, Name = signup.Name,
BillingEmail = signup.BillingEmail, BillingEmail = signup.BillingEmail,
BusinessName = signup.BusinessName, BusinessName = signup.BusinessName,
PlanType = passwordManagerPlan.Type, PlanType = plan!.Type,
Seats = (short)(passwordManagerPlan.BaseSeats + signup.AdditionalSeats), Seats = (short)(plan.PasswordManager.BaseSeats + signup.AdditionalSeats),
MaxCollections = passwordManagerPlan.MaxCollections, MaxCollections = plan.PasswordManager.MaxCollections,
MaxStorageGb = !passwordManagerPlan.BaseStorageGb.HasValue ? MaxStorageGb = !plan.PasswordManager.BaseStorageGb.HasValue ?
(short?)null : (short)(passwordManagerPlan.BaseStorageGb.Value + signup.AdditionalStorageGb), (short?)null : (short)(plan.PasswordManager.BaseStorageGb.Value + signup.AdditionalStorageGb),
UsePolicies = passwordManagerPlan.HasPolicies, UsePolicies = plan.HasPolicies,
UseSso = passwordManagerPlan.HasSso, UseSso = plan.HasSso,
UseGroups = passwordManagerPlan.HasGroups, UseGroups = plan.HasGroups,
UseEvents = passwordManagerPlan.HasEvents, UseEvents = plan.HasEvents,
UseDirectory = passwordManagerPlan.HasDirectory, UseDirectory = plan.HasDirectory,
UseTotp = passwordManagerPlan.HasTotp, UseTotp = plan.HasTotp,
Use2fa = passwordManagerPlan.Has2fa, Use2fa = plan.Has2fa,
UseApi = passwordManagerPlan.HasApi, UseApi = plan.HasApi,
UseResetPassword = passwordManagerPlan.HasResetPassword, UseResetPassword = plan.HasResetPassword,
SelfHost = passwordManagerPlan.HasSelfHost, SelfHost = plan.HasSelfHost,
UsersGetPremium = passwordManagerPlan.UsersGetPremium || signup.PremiumAccessAddon, UsersGetPremium = plan.UsersGetPremium || signup.PremiumAccessAddon,
UseCustomPermissions = passwordManagerPlan.HasCustomPermissions, UseCustomPermissions = plan.HasCustomPermissions,
UseScim = passwordManagerPlan.HasScim, UseScim = plan.HasScim,
Plan = passwordManagerPlan.Name, Plan = plan.Name,
Gateway = null, Gateway = null,
ReferenceData = signup.Owner.ReferenceData, ReferenceData = signup.Owner.ReferenceData,
Enabled = true, Enabled = true,
@ -464,12 +463,12 @@ public class OrganizationService : IOrganizationService
if (signup.UseSecretsManager) if (signup.UseSecretsManager)
{ {
organization.SmSeats = secretsManagerPlan.BaseSeats + signup.AdditionalSmSeats.GetValueOrDefault(); organization.SmSeats = plan.SecretsManager.BaseSeats + signup.AdditionalSmSeats.GetValueOrDefault();
organization.SmServiceAccounts = secretsManagerPlan.BaseServiceAccount.GetValueOrDefault() + organization.SmServiceAccounts = plan.SecretsManager.BaseServiceAccount +
signup.AdditionalServiceAccounts.GetValueOrDefault(); signup.AdditionalServiceAccounts.GetValueOrDefault();
} }
if (passwordManagerPlan.Type == PlanType.Free && !provider) if (plan.Type == PlanType.Free && !provider)
{ {
var adminCount = var adminCount =
await _organizationUserRepository.GetCountByFreeOrganizationAdminUserAsync(signup.Owner.Id); 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."); 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, 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.PremiumAccessAddon, signup.TaxInfo, provider, signup.AdditionalSmSeats.GetValueOrDefault(),
signup.AdditionalServiceAccounts.GetValueOrDefault()); signup.AdditionalServiceAccounts.GetValueOrDefault());
} }
@ -495,8 +490,8 @@ public class OrganizationService : IOrganizationService
await _referenceEventService.RaiseEventAsync( await _referenceEventService.RaiseEventAsync(
new ReferenceEvent(ReferenceEventType.Signup, organization, _currentContext) new ReferenceEvent(ReferenceEventType.Signup, organization, _currentContext)
{ {
PlanName = passwordManagerPlan.Name, PlanName = plan.Name,
PlanType = passwordManagerPlan.Type, PlanType = plan.Type,
Seats = returnValue.Item1.Seats, Seats = returnValue.Item1.Seats,
Storage = returnValue.Item1.MaxStorageGb, Storage = returnValue.Item1.MaxStorageGb,
// TODO: add reference events for SmSeats and Service Accounts - see AC-1481 // 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 && 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."); throw new BadRequestException("Plan not found.");
} }
@ -1955,11 +1950,6 @@ public class OrganizationService : IOrganizationService
throw new BadRequestException($"{productType} Plan not found."); 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) if (additionalSeats < 0)
{ {
throw new BadRequestException($"You can't subtract {productType} seats!"); throw new BadRequestException($"You can't subtract {productType} seats!");
@ -1970,7 +1960,7 @@ public class OrganizationService : IOrganizationService
{ {
ValidatePlan(plan, upgrade.AdditionalSeats, "Password Manager"); 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!"); 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!"); 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."); 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!"); 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."); 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."); throw new BadRequestException("Plan does not allow additional users.");
} }
if (plan.HasAdditionalSeatsOption && plan.MaxAdditionalSeats.HasValue && if (plan.PasswordManager.HasAdditionalSeatsOption && plan.PasswordManager.MaxAdditionalSeats.HasValue &&
upgrade.AdditionalSeats > plan.MaxAdditionalSeats.Value) upgrade.AdditionalSeats > plan.PasswordManager.MaxAdditionalSeats.Value)
{ {
throw new BadRequestException($"Selected plan allows a maximum of " + 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) 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"); 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."); 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!"); throw new BadRequestException("You can't subtract Service Accounts!");
} }
switch (plan.HasAdditionalSeatsOption) switch (plan.SecretsManager.HasAdditionalSeatsOption)
{ {
case false when upgrade.AdditionalSmSeats > 0: case false when upgrade.AdditionalSmSeats > 0:
throw new BadRequestException("Plan does not allow additional users."); throw new BadRequestException("Plan does not allow additional users.");
case true when plan.MaxAdditionalSeats.HasValue && case true when plan.SecretsManager.MaxAdditionalSeats.HasValue &&
upgrade.AdditionalSmSeats > plan.MaxAdditionalSeats.Value: upgrade.AdditionalSmSeats > plan.SecretsManager.MaxAdditionalSeats.Value:
throw new BadRequestException($"Selected plan allows a maximum of " + 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) 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 }) if (plan is not { LegacyYear: null })
{ {
throw new BadRequestException("Invalid plan selected."); 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, 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 additionalSeats, bool premiumAccessAddon, TaxInfo taxInfo, bool provider = false,
int additionalSmSeats = 0, int additionalServiceAccount = 0) 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); , additionalSmSeats, additionalServiceAccount);
Stripe.Customer customer = null; Stripe.Customer customer = null;
@ -211,7 +211,7 @@ public class StripePaymentService : IPaymentService
private async Task ChangeOrganizationSponsorship(Organization org, OrganizationSponsorship sponsorship, bool applySponsorship) 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 ? var sponsoredPlan = sponsorship != null ?
Utilities.StaticStore.GetSponsoredPlan(sponsorship.PlanSponsorshipType.Value) : Utilities.StaticStore.GetSponsoredPlan(sponsorship.PlanSponsorshipType.Value) :
null; null;
@ -231,7 +231,7 @@ public class StripePaymentService : IPaymentService
public Task RemoveOrganizationSponsorshipAsync(Organization org, OrganizationSponsorship sponsorship) => public Task RemoveOrganizationSponsorshipAsync(Organization org, OrganizationSponsorship sponsorship) =>
ChangeOrganizationSponsorship(org, sponsorship, false); 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) OrganizationUpgrade upgrade)
{ {
if (!string.IsNullOrWhiteSpace(org.GatewaySubscriptionId)) 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 (stripePaymentMethod, paymentMethodType) = IdentifyPaymentMethod(customer, subCreateOptions);
var subscription = await ChargeForNewSubscriptionAsync(org, customer, false, 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.Data.Organizations.OrganizationUsers;
using Bit.Core.Models.StaticStore; using Bit.Core.Models.StaticStore;
using Bit.Core.Models.StaticStore.Plans;
namespace Bit.Core.Utilities; 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" }); 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 #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(); new Enterprise2019Plan(true),
SecretManagerPlans = SecretsManagerPlanStore.CreatePlan(); new Enterprise2019Plan(false),
new Teams2019Plan(true),
Plans = PasswordManagerPlans.Concat(SecretManagerPlans); new Teams2019Plan(false),
new Families2019Plan(),
}.ToImmutableList();
#endregion
} }
public static IDictionary<GlobalEquivalentDomainsType, IEnumerable<string>> GlobalDomains { get; set; } public static IDictionary<GlobalEquivalentDomainsType, IEnumerable<string>> GlobalDomains { get; set; }
public static IEnumerable<Plan> Plans { get; set; } public static IEnumerable<Models.StaticStore.Plan> Plans { get; }
public static IEnumerable<Plan> SecretManagerPlans { get; set; }
public static IEnumerable<Plan> PasswordManagerPlans { get; set; }
public static IEnumerable<SponsoredPlan> SponsoredPlans { get; set; } = new[] public static IEnumerable<SponsoredPlan> SponsoredPlans { get; set; } = new[]
{ {
new SponsoredPlan new SponsoredPlan
@ -128,21 +135,20 @@ public class StaticStore
SponsoringProductType = ProductType.Enterprise, SponsoringProductType = ProductType.Enterprise,
StripePlanId = "2021-family-for-enterprise-annually", StripePlanId = "2021-family-for-enterprise-annually",
UsersCanSponsor = (OrganizationUserOrganizationDetails org) => 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) => public static Models.StaticStore.Plan GetPlan(PlanType planType) =>
SecretManagerPlans.SingleOrDefault(p => p.Type == planType); Plans.SingleOrDefault(p => p.Type == planType);
public static SponsoredPlan GetSponsoredPlan(PlanSponsorshipType planSponsorshipType) => public static SponsoredPlan GetSponsoredPlan(PlanSponsorshipType planSponsorshipType) =>
SponsoredPlans.FirstOrDefault(p => p.PlanSponsorshipType == planSponsorshipType); SponsoredPlans.FirstOrDefault(p => p.PlanSponsorshipType == planSponsorshipType);
/// <summary> /// <summary>
/// Determines if the stripe plan id is an addon item by checking if the provided stripe plan id /// 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"/>. /// in any <see cref="Plans"/>.
/// </summary> /// </summary>
/// <param name="stripePlanId"></param> /// <param name="stripePlanId"></param>
@ -151,41 +157,8 @@ public class StaticStore
/// </returns> /// </returns>
public static bool IsAddonSubscriptionItem(string stripePlanId) public static bool IsAddonSubscriptionItem(string stripePlanId)
{ {
if (PasswordManagerPlans.Select(p => p.StripeStoragePlanId).Contains(stripePlanId)) return Plans.Any(p =>
{ p.PasswordManager.StripeStoragePlanId == stripePlanId ||
return true; (p.SecretsManager?.StripeServiceAccountPlanId == stripePlanId));
}
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
);
} }
} }

View File

@ -20,11 +20,11 @@ namespace Bit.Api.Test.Controllers;
public class OrganizationSponsorshipsControllerTests public class OrganizationSponsorshipsControllerTests
{ {
public static IEnumerable<object[]> EnterprisePlanTypes => 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 => 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 => 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 => public static IEnumerable<object[]> NonConfirmedOrganizationUsersStatuses =>
Enum.GetValues<OrganizationUserStatusType>() Enum.GetValues<OrganizationUserStatusType>()

View File

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

View File

@ -66,7 +66,7 @@ internal class PaidOrganization : ICustomization
public PlanType CheckedPlanType { get; set; } public PlanType CheckedPlanType { get; set; }
public void Customize(IFixture fixture) 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(); var lowestActivePaidPlan = validUpgradePlans.First();
CheckedPlanType = CheckedPlanType.Equals(PlanType.Free) ? lowestActivePaidPlan : CheckedPlanType; CheckedPlanType = CheckedPlanType.Equals(PlanType.Free) ? lowestActivePaidPlan : CheckedPlanType;
validUpgradePlans.Remove(lowestActivePaidPlan); validUpgradePlans.Remove(lowestActivePaidPlan);
@ -94,11 +94,11 @@ internal class FreeOrganizationUpgrade : ICustomization
.With(o => o.PlanType, PlanType.Free)); .With(o => o.PlanType, PlanType.Free));
var plansToIgnore = new List<PlanType> { PlanType.Free, PlanType.Custom }; 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 fixture.Customize<OrganizationUpgrade>(composer => composer
.With(ou => ou.Plan, selectedPlan.Type) .With(ou => ou.Plan, selectedPlan.Type)
.With(ou => ou.PremiumAccessAddon, selectedPlan.HasPremiumAccessOption)); .With(ou => ou.PremiumAccessAddon, selectedPlan.PasswordManager.HasPremiumAccessOption));
fixture.Customize<Organization>(composer => composer fixture.Customize<Organization>(composer => composer
.Without(o => o.GatewaySubscriptionId)); .Without(o => o.GatewaySubscriptionId));
} }
@ -140,7 +140,7 @@ public class SecretsManagerOrganizationCustomization : ICustomization
.With(o => o.UseSecretsManager, true) .With(o => o.UseSecretsManager, true)
.With(o => o.SecretsManagerBeta, false) .With(o => o.SecretsManagerBeta, false)
.With(o => o.PlanType, planType) .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.MaxAutoscaleSmSeats, (int?)null)
.With(o => o.MaxAutoscaleSmServiceAccounts, (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 abstract class FamiliesForEnterpriseTestsBase
{ {
public static IEnumerable<object[]> EnterprisePlanTypes => 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 => 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 => 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 => 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 => public static IEnumerable<object[]> NonConfirmedOrganizationUsersStatuses =>
Enum.GetValues<OrganizationUserStatusType>() Enum.GetValues<OrganizationUserStatusType>()

View File

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

View File

@ -52,7 +52,7 @@ public class UpdateSecretsManagerSubscriptionCommandTests
await sutProvider.Sut.UpdateSubscriptionAsync(update); 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) await sutProvider.GetDependency<IPaymentService>().Received(1)
.AdjustSeatsAsync(organization, plan, update.SmSeatsExcludingBase); .AdjustSeatsAsync(organization, plan, update.SmSeatsExcludingBase);
await sutProvider.GetDependency<IPaymentService>().Received(1) await sutProvider.GetDependency<IPaymentService>().Received(1)
@ -96,7 +96,7 @@ public class UpdateSecretsManagerSubscriptionCommandTests
await sutProvider.Sut.UpdateSubscriptionAsync(update); 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) await sutProvider.GetDependency<IPaymentService>().Received(1)
.AdjustSeatsAsync(organization, plan, update.SmSeatsExcludingBase); .AdjustSeatsAsync(organization, plan, update.SmSeatsExcludingBase);
await sutProvider.GetDependency<IPaymentService>().Received(1) await sutProvider.GetDependency<IPaymentService>().Received(1)
@ -213,11 +213,11 @@ public class UpdateSecretsManagerSubscriptionCommandTests
public async Task AdjustServiceAccountsAsync_WithEnterpriseOrTeamsPlans_Success(PlanType planType, Guid organizationId, public async Task AdjustServiceAccountsAsync_WithEnterpriseOrTeamsPlans_Success(PlanType planType, Guid organizationId,
SutProvider<UpdateSecretsManagerSubscriptionCommand> sutProvider) 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 organizationMaxAutoscaleSeats = 20;
var organizationServiceAccounts = plan.BaseServiceAccount.GetValueOrDefault() + 10; var organizationServiceAccounts = plan.SecretsManager.BaseServiceAccount + 10;
var organizationMaxAutoscaleServiceAccounts = 300; var organizationMaxAutoscaleServiceAccounts = 300;
var organization = new Organization var organization = new Organization
@ -235,7 +235,7 @@ public class UpdateSecretsManagerSubscriptionCommandTests
var smServiceAccountsAdjustment = 10; var smServiceAccountsAdjustment = 10;
var expectedSmServiceAccounts = organizationServiceAccounts + smServiceAccountsAdjustment; var expectedSmServiceAccounts = organizationServiceAccounts + smServiceAccountsAdjustment;
var expectedSmServiceAccountsExcludingBase = expectedSmServiceAccounts - plan.BaseServiceAccount.GetValueOrDefault(); var expectedSmServiceAccountsExcludingBase = expectedSmServiceAccounts - plan.SecretsManager.BaseServiceAccount;
var update = new SecretsManagerSubscriptionUpdate(organization, false).AdjustServiceAccounts(10); 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); sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organization.Id).Returns(organization);
upgrade.AdditionalSmSeats = 10; upgrade.AdditionalSmSeats = 10;
upgrade.AdditionalSeats = 10; upgrade.AdditionalSeats = 10;
upgrade.Plan = PlanType.TeamsAnnually;
await sutProvider.Sut.UpgradePlanAsync(organization.Id, upgrade); await sutProvider.Sut.UpgradePlanAsync(organization.Id, upgrade);
await sutProvider.GetDependency<IOrganizationService>().Received(1).ReplaceAndUpdateCacheAsync(organization); await sutProvider.GetDependency<IOrganizationService>().Received(1).ReplaceAndUpdateCacheAsync(organization);
} }
@ -108,8 +109,7 @@ public class UpgradeOrganizationPlanCommandTests
{ {
upgrade.Plan = planType; upgrade.Plan = planType;
var passwordManagerPlan = StaticStore.GetPasswordManagerPlan(upgrade.Plan); var plan = StaticStore.GetPlan(upgrade.Plan);
var secretsManagerPlan = StaticStore.GetSecretsManagerPlan(upgrade.Plan);
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organization.Id).Returns(organization); sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organization.Id).Returns(organization);
@ -121,9 +121,9 @@ public class UpgradeOrganizationPlanCommandTests
await sutProvider.GetDependency<IOrganizationService>().Received(1).ReplaceAndUpdateCacheAsync( await sutProvider.GetDependency<IOrganizationService>().Received(1).ReplaceAndUpdateCacheAsync(
Arg.Is<Organization>(o => Arg.Is<Organization>(o =>
o.Seats == passwordManagerPlan.BaseSeats + upgrade.AdditionalSeats o.Seats == plan.PasswordManager.BaseSeats + upgrade.AdditionalSeats
&& o.SmSeats == secretsManagerPlan.BaseSeats + upgrade.AdditionalSmSeats && o.SmSeats == plan.SecretsManager.BaseSeats + upgrade.AdditionalSmSeats
&& o.SmServiceAccounts == secretsManagerPlan.BaseServiceAccount + upgrade.AdditionalServiceAccounts)); && o.SmServiceAccounts == plan.SecretsManager.BaseServiceAccount + upgrade.AdditionalServiceAccounts));
Assert.True(result.Item1); Assert.True(result.Item1);
Assert.NotNull(result.Item2); Assert.NotNull(result.Item2);

View File

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

View File

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

View File

@ -1,5 +1,4 @@
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Models.StaticStore;
using Bit.Core.Utilities; using Bit.Core.Utilities;
using Xunit; using Xunit;
@ -14,57 +13,18 @@ public class StaticStoreTests
var plans = StaticStore.Plans; var plans = StaticStore.Plans;
Assert.NotNull(plans); Assert.NotNull(plans);
Assert.NotEmpty(plans); Assert.NotEmpty(plans);
Assert.Equal(17, plans.Count()); Assert.Equal(12, plans.Count());
} }
[Theory] [Theory]
[InlineData(PlanType.EnterpriseAnnually)] [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.NotNull(plan);
Assert.Equal(planType, plan.Type); 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));
}
} }