diff --git a/bitwarden_license/src/Commercial.Core/SecretsManager/Queries/Projects/MaxProjectsQuery.cs b/bitwarden_license/src/Commercial.Core/SecretsManager/Queries/Projects/MaxProjectsQuery.cs index fc30c2c6e..7afad6e82 100644 --- a/bitwarden_license/src/Commercial.Core/SecretsManager/Queries/Projects/MaxProjectsQuery.cs +++ b/bitwarden_license/src/Commercial.Core/SecretsManager/Queries/Projects/MaxProjectsQuery.cs @@ -28,8 +28,8 @@ public class MaxProjectsQuery : IMaxProjectsQuery throw new NotFoundException(); } - var plan = StaticStore.GetSecretsManagerPlan(org.PlanType); - if (plan == null) + var plan = StaticStore.GetPlan(org.PlanType); + if (plan?.SecretsManager == null) { throw new BadRequestException("Existing plan not found."); } @@ -37,7 +37,7 @@ public class MaxProjectsQuery : IMaxProjectsQuery if (plan.Type == PlanType.Free) { var projects = await _projectRepository.GetProjectCountByOrganizationIdAsync(organizationId); - return projects + projectsToAdd > plan.MaxProjects ? (plan.MaxProjects, true) : (plan.MaxProjects, false); + return ((short? max, bool? overMax))(projects + projectsToAdd > plan.SecretsManager.MaxProjects ? (plan.SecretsManager.MaxProjects, true) : (plan.SecretsManager.MaxProjects, false)); } return (null, null); diff --git a/src/Admin/Controllers/OrganizationsController.cs b/src/Admin/Controllers/OrganizationsController.cs index ceaee641e..7c296dd3c 100644 --- a/src/Admin/Controllers/OrganizationsController.cs +++ b/src/Admin/Controllers/OrganizationsController.cs @@ -205,9 +205,8 @@ public class OrganizationsController : Controller var organization = await GetOrganization(id, model); if (organization.UseSecretsManager && - !organization.SecretsManagerBeta - && StaticStore.GetSecretsManagerPlan(organization.PlanType) == null - ) + !organization.SecretsManagerBeta && + !StaticStore.GetPlan(organization.PlanType).SupportsSecretsManager) { throw new BadRequestException("Plan does not support Secrets Manager"); } diff --git a/src/Admin/Models/OrganizationEditModel.cs b/src/Admin/Models/OrganizationEditModel.cs index 76222fbb1..4e90f5bde 100644 --- a/src/Admin/Models/OrganizationEditModel.cs +++ b/src/Admin/Models/OrganizationEditModel.cs @@ -158,11 +158,12 @@ public class OrganizationEditModel : OrganizationViewModel * Add mappings for individual properties as you need them */ public IEnumerable> GetPlansHelper() => - StaticStore.SecretManagerPlans.Select(p => - new Dictionary + StaticStore.Plans + .Where(p => p.SupportsSecretsManager) + .Select(p => new Dictionary { { "type", p.Type }, - { "baseServiceAccount", p.BaseServiceAccount } + { "baseServiceAccount", p.SecretsManager.BaseServiceAccount } }); public Organization CreateOrganization(Provider provider) diff --git a/src/Api/Controllers/OrganizationsController.cs b/src/Api/Controllers/OrganizationsController.cs index 3c8e938eb..2fea9ec52 100644 --- a/src/Api/Controllers/OrganizationsController.cs +++ b/src/Api/Controllers/OrganizationsController.cs @@ -540,7 +540,7 @@ public class OrganizationsController : Controller if (model.Type == OrganizationApiKeyType.BillingSync || model.Type == OrganizationApiKeyType.Scim) { // Non-enterprise orgs should not be able to create or view an apikey of billing sync/scim key types - var plan = StaticStore.GetPasswordManagerPlan(organization.PlanType); + var plan = StaticStore.GetPlan(organization.PlanType); if (plan.Product != ProductType.Enterprise) { throw new NotFoundException(); diff --git a/src/Api/Controllers/PlansController.cs b/src/Api/Controllers/PlansController.cs index a7ae7c0b3..d738e60cf 100644 --- a/src/Api/Controllers/PlansController.cs +++ b/src/Api/Controllers/PlansController.cs @@ -19,30 +19,12 @@ public class PlansController : Controller [HttpGet("")] [AllowAnonymous] public ListResponseModel Get() - { - var data = StaticStore.PasswordManagerPlans; - var responses = data.Select(plan => new PlanResponseModel(plan)); - return new ListResponseModel(responses); - } - - [HttpGet("all")] - [AllowAnonymous] - public ListResponseModel GetAllPlans() { var data = StaticStore.Plans; var responses = data.Select(plan => new PlanResponseModel(plan)); return new ListResponseModel(responses); } - [HttpGet("sm-plans")] - [AllowAnonymous] - public ListResponseModel GetSecretsManagerPlans() - { - var data = StaticStore.SecretManagerPlans; - var responses = data.Select(plan => new PlanResponseModel(plan)); - return new ListResponseModel(responses); - } - [HttpGet("sales-tax-rates")] public async Task> GetTaxRates() { diff --git a/src/Api/Models/Response/Organizations/OrganizationResponseModel.cs b/src/Api/Models/Response/Organizations/OrganizationResponseModel.cs index f04c28c5e..b56a06777 100644 --- a/src/Api/Models/Response/Organizations/OrganizationResponseModel.cs +++ b/src/Api/Models/Response/Organizations/OrganizationResponseModel.cs @@ -26,12 +26,7 @@ public class OrganizationResponseModel : ResponseModel BusinessCountry = organization.BusinessCountry; BusinessTaxNumber = organization.BusinessTaxNumber; BillingEmail = organization.BillingEmail; - Plan = new PlanResponseModel(StaticStore.PasswordManagerPlans.FirstOrDefault(plan => plan.Type == organization.PlanType)); - var matchingPlan = StaticStore.GetSecretsManagerPlan(organization.PlanType); - if (matchingPlan != null) - { - SecretsManagerPlan = new PlanResponseModel(matchingPlan); - } + Plan = new PlanResponseModel(StaticStore.GetPlan(organization.PlanType)); PlanType = organization.PlanType; Seats = organization.Seats; MaxAutoscaleSeats = organization.MaxAutoscaleSeats; diff --git a/src/Api/Models/Response/PlanResponseModel.cs b/src/Api/Models/Response/PlanResponseModel.cs index f3b39f113..7d007421e 100644 --- a/src/Api/Models/Response/PlanResponseModel.cs +++ b/src/Api/Models/Response/PlanResponseModel.cs @@ -21,15 +21,6 @@ public class PlanResponseModel : ResponseModel NameLocalizationKey = plan.NameLocalizationKey; DescriptionLocalizationKey = plan.DescriptionLocalizationKey; CanBeUsedByBusiness = plan.CanBeUsedByBusiness; - BaseSeats = plan.BaseSeats; - BaseStorageGb = plan.BaseStorageGb; - MaxCollections = plan.MaxCollections; - MaxUsers = plan.MaxUsers; - HasAdditionalSeatsOption = plan.HasAdditionalSeatsOption; - HasAdditionalStorageOption = plan.HasAdditionalStorageOption; - MaxAdditionalSeats = plan.MaxAdditionalSeats; - MaxAdditionalStorage = plan.MaxAdditionalStorage; - HasPremiumAccessOption = plan.HasPremiumAccessOption; TrialPeriodDays = plan.TrialPeriodDays; HasSelfHost = plan.HasSelfHost; HasPolicies = plan.HasPolicies; @@ -45,22 +36,12 @@ public class PlanResponseModel : ResponseModel DisplaySortOrder = plan.DisplaySortOrder; LegacyYear = plan.LegacyYear; Disabled = plan.Disabled; - StripePlanId = plan.StripePlanId; - StripeSeatPlanId = plan.StripeSeatPlanId; - StripeStoragePlanId = plan.StripeStoragePlanId; - BasePrice = plan.BasePrice; - SeatPrice = plan.SeatPrice; - AdditionalStoragePricePerGb = plan.AdditionalStoragePricePerGb; - PremiumAccessOptionPrice = plan.PremiumAccessOptionPrice; + if (plan.SecretsManager != null) + { + SecretsManager = new SecretsManagerPlanFeaturesResponseModel(plan.SecretsManager); + } - AdditionalPricePerServiceAccount = plan.AdditionalPricePerServiceAccount; - BaseServiceAccount = plan.BaseServiceAccount; - MaxServiceAccounts = plan.MaxServiceAccounts; - MaxAdditionalServiceAccounts = plan.MaxAdditionalServiceAccount; - HasAdditionalServiceAccountOption = plan.HasAdditionalServiceAccountOption; - MaxProjects = plan.MaxProjects; - BitwardenProduct = plan.BitwardenProduct; - StripeServiceAccountPlanId = plan.StripeServiceAccountPlanId; + PasswordManager = new PasswordManagerPlanFeaturesResponseModel(plan.PasswordManager); } public PlanType Type { get; set; } @@ -70,16 +51,6 @@ public class PlanResponseModel : ResponseModel public string NameLocalizationKey { get; set; } public string DescriptionLocalizationKey { get; set; } public bool CanBeUsedByBusiness { get; set; } - public int BaseSeats { get; set; } - public short? BaseStorageGb { get; set; } - public short? MaxCollections { get; set; } - public short? MaxUsers { get; set; } - - public bool HasAdditionalSeatsOption { get; set; } - public int? MaxAdditionalSeats { get; set; } - public bool HasAdditionalStorageOption { get; set; } - public short? MaxAdditionalStorage { get; set; } - public bool HasPremiumAccessOption { get; set; } public int? TrialPeriodDays { get; set; } public bool HasSelfHost { get; set; } @@ -98,21 +69,95 @@ public class PlanResponseModel : ResponseModel public int DisplaySortOrder { get; set; } public int? LegacyYear { get; set; } public bool Disabled { get; set; } + public SecretsManagerPlanFeaturesResponseModel SecretsManager { get; protected init; } + public PasswordManagerPlanFeaturesResponseModel PasswordManager { get; protected init; } - public string StripePlanId { get; set; } - public string StripeSeatPlanId { get; set; } - public string StripeStoragePlanId { get; set; } - public string StripePremiumAccessPlanId { get; set; } - public decimal BasePrice { get; set; } - public decimal SeatPrice { get; set; } - public decimal AdditionalStoragePricePerGb { get; set; } - public decimal PremiumAccessOptionPrice { get; set; } - public string StripeServiceAccountPlanId { get; set; } - public decimal? AdditionalPricePerServiceAccount { get; set; } - public short? BaseServiceAccount { get; set; } - public short? MaxServiceAccounts { get; set; } - public short? MaxAdditionalServiceAccounts { get; set; } - public bool HasAdditionalServiceAccountOption { get; set; } - public short? MaxProjects { get; set; } - public BitwardenProductType BitwardenProduct { get; set; } + public class SecretsManagerPlanFeaturesResponseModel + { + public SecretsManagerPlanFeaturesResponseModel(Plan.SecretsManagerPlanFeatures plan) + { + MaxServiceAccounts = plan.MaxServiceAccounts; + AllowServiceAccountsAutoscale = plan is { AllowServiceAccountsAutoscale: true }; + StripeServiceAccountPlanId = plan.StripeServiceAccountPlanId; + AdditionalPricePerServiceAccount = plan.AdditionalPricePerServiceAccount; + BaseServiceAccount = plan.BaseServiceAccount; + MaxAdditionalServiceAccount = plan.MaxAdditionalServiceAccount; + HasAdditionalServiceAccountOption = plan is { HasAdditionalServiceAccountOption: true }; + StripeSeatPlanId = plan.StripeSeatPlanId; + HasAdditionalSeatsOption = plan is { HasAdditionalSeatsOption: true }; + BasePrice = plan.BasePrice; + SeatPrice = plan.SeatPrice; + BaseSeats = plan.BaseSeats; + MaxSeats = plan.MaxSeats; + MaxAdditionalSeats = plan.MaxAdditionalSeats; + AllowSeatAutoscale = plan.AllowSeatAutoscale; + MaxProjects = plan.MaxProjects; + } + // Service accounts + public short? MaxServiceAccounts { get; init; } + public bool AllowServiceAccountsAutoscale { get; init; } + public string StripeServiceAccountPlanId { get; init; } + public decimal? AdditionalPricePerServiceAccount { get; init; } + public short? BaseServiceAccount { get; init; } + public short? MaxAdditionalServiceAccount { get; init; } + public bool HasAdditionalServiceAccountOption { get; init; } + // Seats + public string StripeSeatPlanId { get; init; } + public bool HasAdditionalSeatsOption { get; init; } + public decimal BasePrice { get; init; } + public decimal SeatPrice { get; init; } + public int BaseSeats { get; init; } + public short? MaxSeats { get; init; } + public int? MaxAdditionalSeats { get; init; } + public bool AllowSeatAutoscale { get; init; } + + // Features + public int MaxProjects { get; init; } + } + + public record PasswordManagerPlanFeaturesResponseModel + { + public PasswordManagerPlanFeaturesResponseModel(Plan.PasswordManagerPlanFeatures plan) + { + StripePlanId = plan.StripePlanId; + StripeSeatPlanId = plan.StripeSeatPlanId; + BasePrice = plan.BasePrice; + SeatPrice = plan.SeatPrice; + AllowSeatAutoscale = plan.AllowSeatAutoscale; + HasAdditionalSeatsOption = plan.HasAdditionalSeatsOption; + MaxAdditionalSeats = plan.MaxAdditionalSeats; + BaseSeats = plan.BaseSeats; + HasPremiumAccessOption = plan.HasPremiumAccessOption; + StripePremiumAccessPlanId = plan.StripePremiumAccessPlanId; + PremiumAccessOptionPrice = plan.PremiumAccessOptionPrice; + MaxSeats = plan.MaxSeats; + BaseStorageGb = plan.BaseStorageGb; + HasAdditionalStorageOption = plan.HasAdditionalStorageOption; + AdditionalStoragePricePerGb = plan.AdditionalStoragePricePerGb; + StripeStoragePlanId = plan.StripeStoragePlanId; + MaxAdditionalStorage = plan.MaxAdditionalStorage; + MaxCollections = plan.MaxCollections; + } + // Seats + public string StripePlanId { get; init; } + public string StripeSeatPlanId { get; init; } + public decimal BasePrice { get; init; } + public decimal SeatPrice { get; init; } + public bool AllowSeatAutoscale { get; init; } + public bool HasAdditionalSeatsOption { get; init; } + public int? MaxAdditionalSeats { get; init; } + public int BaseSeats { get; init; } + public bool HasPremiumAccessOption { get; init; } + public string StripePremiumAccessPlanId { get; init; } + public decimal PremiumAccessOptionPrice { get; init; } + public short? MaxSeats { get; init; } + // Storage + public short? BaseStorageGb { get; init; } + public bool HasAdditionalStorageOption { get; init; } + public decimal AdditionalStoragePricePerGb { get; init; } + public string StripeStoragePlanId { get; init; } + public short? MaxAdditionalStorage { get; init; } + // Feature + public short? MaxCollections { get; init; } + } } diff --git a/src/Api/Models/Response/ProfileOrganizationResponseModel.cs b/src/Api/Models/Response/ProfileOrganizationResponseModel.cs index a0ededa0b..10b5ea161 100644 --- a/src/Api/Models/Response/ProfileOrganizationResponseModel.cs +++ b/src/Api/Models/Response/ProfileOrganizationResponseModel.cs @@ -55,7 +55,7 @@ public class ProfileOrganizationResponseModel : ResponseModel FamilySponsorshipAvailable = FamilySponsorshipFriendlyName == null && StaticStore.GetSponsoredPlan(PlanSponsorshipType.FamiliesForEnterprise) .UsersCanSponsor(organization); - PlanProductType = StaticStore.GetPasswordManagerPlan(organization.PlanType).Product; + PlanProductType = StaticStore.GetPlan(organization.PlanType).Product; FamilySponsorshipLastSyncDate = organization.FamilySponsorshipLastSyncDate; FamilySponsorshipToDelete = organization.FamilySponsorshipToDelete; FamilySponsorshipValidUntil = organization.FamilySponsorshipValidUntil; diff --git a/src/Api/Models/Response/ProfileProviderOrganizationResponseModel.cs b/src/Api/Models/Response/ProfileProviderOrganizationResponseModel.cs index 32bfd81db..9a66de2e1 100644 --- a/src/Api/Models/Response/ProfileProviderOrganizationResponseModel.cs +++ b/src/Api/Models/Response/ProfileProviderOrganizationResponseModel.cs @@ -42,6 +42,6 @@ public class ProfileProviderOrganizationResponseModel : ProfileOrganizationRespo UserId = organization.UserId; ProviderId = organization.ProviderId; ProviderName = organization.ProviderName; - PlanProductType = StaticStore.GetPasswordManagerPlan(organization.PlanType).Product; + PlanProductType = StaticStore.GetPlan(organization.PlanType).Product; } } diff --git a/src/Api/Models/Response/SubscriptionResponseModel.cs b/src/Api/Models/Response/SubscriptionResponseModel.cs index 042da2e9e..553b7dd99 100644 --- a/src/Api/Models/Response/SubscriptionResponseModel.cs +++ b/src/Api/Models/Response/SubscriptionResponseModel.cs @@ -1,5 +1,4 @@ using Bit.Core.Entities; -using Bit.Core.Enums; using Bit.Core.Models.Api; using Bit.Core.Models.Business; using Bit.Core.Utilities; @@ -98,7 +97,6 @@ public class BillingSubscription Quantity = item.Quantity; SponsoredSubscriptionItem = item.SponsoredSubscriptionItem; AddonSubscriptionItem = item.AddonSubscriptionItem; - BitwardenProduct = item.BitwardenProduct; } public string Name { get; set; } @@ -107,7 +105,6 @@ public class BillingSubscription public string Interval { get; set; } public bool SponsoredSubscriptionItem { get; set; } public bool AddonSubscriptionItem { get; set; } - public BitwardenProductType BitwardenProduct { get; set; } } } diff --git a/src/Billing/Controllers/StripeController.cs b/src/Billing/Controllers/StripeController.cs index 19366b53a..fb63e1993 100644 --- a/src/Billing/Controllers/StripeController.cs +++ b/src/Billing/Controllers/StripeController.cs @@ -447,7 +447,7 @@ public class StripeController : Controller // org if (ids.Item1.HasValue) { - if (subscription.Items.Any(i => StaticStore.PasswordManagerPlans.Any(p => p.StripePlanId == i.Plan.Id))) + if (subscription.Items.Any(i => StaticStore.Plans.Any(p => p.PasswordManager.StripePlanId == i.Plan.Id))) { await _organizationService.EnableAsync(ids.Item1.Value, subscription.CurrentPeriodEnd); diff --git a/src/Core/Enums/BitwardenProductType.cs b/src/Core/Enums/BitwardenProductType.cs deleted file mode 100644 index d0c358671..000000000 --- a/src/Core/Enums/BitwardenProductType.cs +++ /dev/null @@ -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, -} diff --git a/src/Core/Models/Business/SecretsManagerSubscriptionUpdate.cs b/src/Core/Models/Business/SecretsManagerSubscriptionUpdate.cs index 1cb7a41df..378747058 100644 --- a/src/Core/Models/Business/SecretsManagerSubscriptionUpdate.cs +++ b/src/Core/Models/Business/SecretsManagerSubscriptionUpdate.cs @@ -43,18 +43,18 @@ public class SecretsManagerSubscriptionUpdate /// The seats the organization will have after the update, excluding the base seats included in the plan /// Usually this is what the organization is billed for /// - public int SmSeatsExcludingBase => SmSeats.HasValue ? SmSeats.Value - Plan.BaseSeats : 0; + public int SmSeatsExcludingBase => SmSeats.HasValue ? SmSeats.Value - Plan.SecretsManager.BaseSeats : 0; /// /// 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 /// - public int SmServiceAccountsExcludingBase => SmServiceAccounts.HasValue ? SmServiceAccounts.Value - Plan.BaseServiceAccount.GetValueOrDefault() : 0; + public int SmServiceAccountsExcludingBase => SmServiceAccounts.HasValue ? SmServiceAccounts.Value - Plan.SecretsManager!.BaseServiceAccount : 0; public bool SmSeatsChanged => SmSeats != Organization.SmSeats; public bool SmServiceAccountsChanged => SmServiceAccounts != Organization.SmServiceAccounts; public bool MaxAutoscaleSmSeatsChanged => MaxAutoscaleSmSeats != Organization.MaxAutoscaleSmSeats; public bool MaxAutoscaleSmServiceAccountsChanged => MaxAutoscaleSmServiceAccounts != Organization.MaxAutoscaleSmServiceAccounts; - public Plan Plan => Utilities.StaticStore.GetSecretsManagerPlan(Organization.PlanType); + public Plan Plan => Utilities.StaticStore.GetPlan(Organization.PlanType); public bool SmSeatAutoscaleLimitReached => SmSeats.HasValue && MaxAutoscaleSmSeats.HasValue && SmSeats == MaxAutoscaleSmSeats; public bool SmServiceAccountAutoscaleLimitReached => SmServiceAccounts.HasValue && @@ -70,7 +70,7 @@ public class SecretsManagerSubscriptionUpdate Organization = organization; - if (Plan == null) + if (!Plan.SupportsSecretsManager) { throw new NotFoundException("Invalid Secrets Manager plan."); } diff --git a/src/Core/Models/Business/SubscriptionCreateOptions.cs b/src/Core/Models/Business/SubscriptionCreateOptions.cs index 86524a55b..0ea4c4c0b 100644 --- a/src/Core/Models/Business/SubscriptionCreateOptions.cs +++ b/src/Core/Models/Business/SubscriptionCreateOptions.cs @@ -1,12 +1,12 @@ using Bit.Core.Entities; -using Bit.Core.Enums; using Stripe; +using Plan = Bit.Core.Models.StaticStore.Plan; namespace Bit.Core.Models.Business; public class OrganizationSubscriptionOptionsBase : Stripe.SubscriptionCreateOptions { - public OrganizationSubscriptionOptionsBase(Organization org, List plans, TaxInfo taxInfo, int additionalSeats, + public OrganizationSubscriptionOptionsBase(Organization org, StaticStore.Plan plan, TaxInfo taxInfo, int additionalSeats, int additionalStorageGb, bool premiumAccessAddon, int additionalSmSeats, int additionalServiceAccounts) { Items = new List(); @@ -14,79 +14,80 @@ public class OrganizationSubscriptionOptionsBase : Stripe.SubscriptionCreateOpti { [org.GatewayIdField()] = org.Id.ToString() }; - foreach (var plan in plans) - { - AddPlanIdToSubscription(plan); - switch (plan.BitwardenProduct) - { - case BitwardenProductType.PasswordManager: - { - AddPremiumAccessAddon(premiumAccessAddon, plan); - AddAdditionalSeatToSubscription(additionalSeats, plan); - AddAdditionalStorage(additionalStorageGb, plan); - break; - } - case BitwardenProductType.SecretsManager: - { - AddAdditionalSeatToSubscription(additionalSmSeats, plan); - AddServiceAccount(additionalServiceAccounts, plan); - break; - } - } + AddPlanIdToSubscription(plan); + + if (org.UseSecretsManager) + { + AddSecretsManagerSeat(plan, additionalSmSeats); + AddServiceAccount(plan, additionalServiceAccounts); } + AddPremiumAccessAddon(plan, premiumAccessAddon); + AddPasswordManagerSeat(plan, additionalSeats); + AddAdditionalStorage(plan, additionalStorageGb); + if (!string.IsNullOrWhiteSpace(taxInfo?.StripeTaxRateId)) { DefaultTaxRates = new List { taxInfo.StripeTaxRateId }; } } - private void AddServiceAccount(int additionalServiceAccounts, StaticStore.Plan plan) + private void AddSecretsManagerSeat(Plan plan, int additionalSmSeats) { - if (additionalServiceAccounts > 0 && plan.StripeServiceAccountPlanId != null) + if (additionalSmSeats > 0 && plan.SecretsManager.StripeSeatPlanId != null) + { + Items.Add(new SubscriptionItemOptions + { Plan = plan.SecretsManager.StripeSeatPlanId, Quantity = additionalSmSeats }); + } + } + + private void AddPasswordManagerSeat(Plan plan, int additionalSeats) + { + if (additionalSeats > 0 && plan.PasswordManager.StripeSeatPlanId != null) + { + Items.Add(new SubscriptionItemOptions + { Plan = plan.PasswordManager.StripeSeatPlanId, Quantity = additionalSeats }); + } + } + + private void AddServiceAccount(StaticStore.Plan plan, int additionalServiceAccounts) + { + if (additionalServiceAccounts > 0 && plan.SecretsManager.StripeServiceAccountPlanId != null) { Items.Add(new SubscriptionItemOptions { - Plan = plan.StripeServiceAccountPlanId, + Plan = plan.SecretsManager.StripeServiceAccountPlanId, Quantity = additionalServiceAccounts }); } } - private void AddAdditionalStorage(int additionalStorageGb, StaticStore.Plan plan) + private void AddAdditionalStorage(StaticStore.Plan plan, int additionalStorageGb) { if (additionalStorageGb > 0) { Items.Add(new SubscriptionItemOptions { - Plan = plan.StripeStoragePlanId, + Plan = plan.PasswordManager.StripeStoragePlanId, Quantity = additionalStorageGb }); } } - private void AddPremiumAccessAddon(bool premiumAccessAddon, StaticStore.Plan plan) + private void AddPremiumAccessAddon(StaticStore.Plan plan, bool premiumAccessAddon) { - if (premiumAccessAddon && plan.StripePremiumAccessPlanId != null) + if (premiumAccessAddon && plan.PasswordManager.StripePremiumAccessPlanId != null) { - Items.Add(new SubscriptionItemOptions { Plan = plan.StripePremiumAccessPlanId, Quantity = 1 }); - } - } - - private void AddAdditionalSeatToSubscription(int additionalSeats, StaticStore.Plan plan) - { - if (additionalSeats > 0 && plan.StripeSeatPlanId != null) - { - Items.Add(new SubscriptionItemOptions { Plan = plan.StripeSeatPlanId, Quantity = additionalSeats }); + Items.Add(new SubscriptionItemOptions { Plan = plan.PasswordManager.StripePremiumAccessPlanId, Quantity = 1 }); } } private void AddPlanIdToSubscription(StaticStore.Plan plan) { - if (plan.StripePlanId != null) + if (plan.PasswordManager.StripePlanId != null) { - Items.Add(new SubscriptionItemOptions { Plan = plan.StripePlanId, Quantity = 1 }); + Items.Add(new SubscriptionItemOptions { Plan = plan.PasswordManager.StripePlanId, Quantity = 1 }); } } } @@ -94,14 +95,14 @@ public class OrganizationSubscriptionOptionsBase : Stripe.SubscriptionCreateOpti public class OrganizationPurchaseSubscriptionOptions : OrganizationSubscriptionOptionsBase { public OrganizationPurchaseSubscriptionOptions( - Organization org, List plans, + Organization org, StaticStore.Plan plan, TaxInfo taxInfo, int additionalSeats, int additionalStorageGb, bool premiumAccessAddon, int additionalSmSeats, int additionalServiceAccounts) : - base(org, plans, taxInfo, additionalSeats, additionalStorageGb, premiumAccessAddon, additionalSmSeats, additionalServiceAccounts) + base(org, plan, taxInfo, additionalSeats, additionalStorageGb, premiumAccessAddon, additionalSmSeats, additionalServiceAccounts) { OffSession = true; - TrialPeriodDays = plans.FirstOrDefault(x => x.BitwardenProduct == BitwardenProductType.PasswordManager)!.TrialPeriodDays; + TrialPeriodDays = plan.TrialPeriodDays; } } @@ -109,8 +110,8 @@ public class OrganizationUpgradeSubscriptionOptions : OrganizationSubscriptionOp { public OrganizationUpgradeSubscriptionOptions( string customerId, Organization org, - List plans, OrganizationUpgrade upgrade) : - base(org, plans, upgrade.TaxInfo, upgrade.AdditionalSeats, upgrade.AdditionalStorageGb, + StaticStore.Plan plan, OrganizationUpgrade upgrade) : + base(org, plan, upgrade.TaxInfo, upgrade.AdditionalSeats, upgrade.AdditionalStorageGb, upgrade.PremiumAccessAddon, upgrade.AdditionalSmSeats.GetValueOrDefault(), upgrade.AdditionalServiceAccounts.GetValueOrDefault()) { diff --git a/src/Core/Models/Business/SubscriptionInfo.cs b/src/Core/Models/Business/SubscriptionInfo.cs index 87fe3157c..f8284e0e3 100644 --- a/src/Core/Models/Business/SubscriptionInfo.cs +++ b/src/Core/Models/Business/SubscriptionInfo.cs @@ -1,5 +1,4 @@ -using Bit.Core.Enums; -using Stripe; +using Stripe; namespace Bit.Core.Models.Business; @@ -64,16 +63,12 @@ public class SubscriptionInfo Interval = item.Plan.Interval; AddonSubscriptionItem = Utilities.StaticStore.IsAddonSubscriptionItem(item.Plan.Id); - BitwardenProduct = - Utilities.StaticStore.GetPlanByStripeId(item.Plan.Id)?.BitwardenProduct ?? BitwardenProductType.PasswordManager; } Quantity = (int)item.Quantity; SponsoredSubscriptionItem = Utilities.StaticStore.SponsoredPlans.Any(p => p.StripePlanId == item.Plan.Id); } - public BitwardenProductType BitwardenProduct { get; set; } - public bool AddonSubscriptionItem { get; set; } public string Name { get; set; } diff --git a/src/Core/Models/Business/SubscriptionUpdate.cs b/src/Core/Models/Business/SubscriptionUpdate.cs index 93122b3cf..0dcf696db 100644 --- a/src/Core/Models/Business/SubscriptionUpdate.cs +++ b/src/Core/Models/Business/SubscriptionUpdate.cs @@ -1,5 +1,4 @@ using Bit.Core.Entities; -using Bit.Core.Enums; using Stripe; namespace Bit.Core.Models.Business; @@ -30,29 +29,23 @@ public abstract class SubscriptionUpdate planId == null ? null : subscription.Items?.Data?.FirstOrDefault(i => i.Plan.Id == planId); } -public class SeatSubscriptionUpdate : SubscriptionUpdate +public abstract class BaseSeatSubscriptionUpdate : SubscriptionUpdate { private readonly int _previousSeats; - private readonly StaticStore.Plan _plan; + protected readonly StaticStore.Plan Plan; private readonly long? _additionalSeats; - protected override List PlanIds => new() { _plan.StripeSeatPlanId }; - - public SeatSubscriptionUpdate(Organization organization, StaticStore.Plan plan, long? additionalSeats) + protected BaseSeatSubscriptionUpdate(Organization organization, StaticStore.Plan plan, long? additionalSeats, int previousSeats) { - _plan = plan; + Plan = plan; _additionalSeats = additionalSeats; - switch (plan.BitwardenProduct) - { - case BitwardenProductType.PasswordManager: - _previousSeats = organization.Seats.GetValueOrDefault(); - break; - case BitwardenProductType.SecretsManager: - _previousSeats = organization.SmSeats.GetValueOrDefault(); - break; - } + _previousSeats = previousSeats; } + protected abstract string GetPlanId(); + + protected override List PlanIds => new() { GetPlanId() }; + public override List UpgradeItemsOptions(Subscription subscription) { var item = SubscriptionItem(subscription, PlanIds.Single()); @@ -85,12 +78,30 @@ public class SeatSubscriptionUpdate : SubscriptionUpdate } } +public class SeatSubscriptionUpdate : BaseSeatSubscriptionUpdate +{ + public SeatSubscriptionUpdate(Organization organization, StaticStore.Plan plan, long? additionalSeats) + : base(organization, plan, additionalSeats, organization.Seats.GetValueOrDefault()) + { } + + protected override string GetPlanId() => Plan.PasswordManager.StripeSeatPlanId; +} + +public class SmSeatSubscriptionUpdate : BaseSeatSubscriptionUpdate +{ + public SmSeatSubscriptionUpdate(Organization organization, StaticStore.Plan plan, long? additionalSeats) + : base(organization, plan, additionalSeats, organization.SmSeats.GetValueOrDefault()) + { } + + protected override string GetPlanId() => Plan.SecretsManager.StripeSeatPlanId; +} + public class ServiceAccountSubscriptionUpdate : SubscriptionUpdate { private long? _prevServiceAccounts; private readonly StaticStore.Plan _plan; private readonly long? _additionalServiceAccounts; - protected override List PlanIds => new() { _plan.StripeServiceAccountPlanId }; + protected override List PlanIds => new() { _plan.SecretsManager.StripeServiceAccountPlanId }; public ServiceAccountSubscriptionUpdate(Organization organization, StaticStore.Plan plan, long? additionalServiceAccounts) { @@ -190,7 +201,7 @@ public class SponsorOrganizationSubscriptionUpdate : SubscriptionUpdate public SponsorOrganizationSubscriptionUpdate(StaticStore.Plan existingPlan, StaticStore.SponsoredPlan sponsoredPlan, bool applySponsorship) { - _existingPlanStripeId = existingPlan.StripePlanId; + _existingPlanStripeId = existingPlan.PasswordManager.StripePlanId; _sponsoredPlanStripeId = sponsoredPlan?.StripePlanId; _applySponsorship = applySponsorship; } @@ -269,7 +280,7 @@ public class SecretsManagerSubscribeUpdate : SubscriptionUpdate private readonly long? _additionalServiceAccounts; private readonly int _previousSeats; private readonly int _previousServiceAccounts; - protected override List PlanIds => new() { _plan.StripeSeatPlanId, _plan.StripeServiceAccountPlanId }; + protected override List PlanIds => new() { _plan.SecretsManager.StripeSeatPlanId, _plan.SecretsManager.StripeServiceAccountPlanId }; public SecretsManagerSubscribeUpdate(Organization organization, StaticStore.Plan plan, long? additionalSeats, long? additionalServiceAccounts) { _plan = plan; @@ -303,7 +314,7 @@ public class SecretsManagerSubscribeUpdate : SubscriptionUpdate { updatedItems.Add(new SubscriptionItemOptions { - Price = _plan.StripeSeatPlanId, + Price = _plan.SecretsManager.StripeSeatPlanId, Quantity = _additionalSeats }); } @@ -312,7 +323,7 @@ public class SecretsManagerSubscribeUpdate : SubscriptionUpdate { updatedItems.Add(new SubscriptionItemOptions { - Price = _plan.StripeServiceAccountPlanId, + Price = _plan.SecretsManager.StripeServiceAccountPlanId, Quantity = _additionalServiceAccounts }); } @@ -322,14 +333,14 @@ public class SecretsManagerSubscribeUpdate : SubscriptionUpdate { updatedItems.Add(new SubscriptionItemOptions { - Price = _plan.StripeSeatPlanId, + Price = _plan.SecretsManager.StripeSeatPlanId, Quantity = _previousSeats, Deleted = _previousSeats == 0 ? true : (bool?)null, }); updatedItems.Add(new SubscriptionItemOptions { - Price = _plan.StripeServiceAccountPlanId, + Price = _plan.SecretsManager.StripeServiceAccountPlanId, Quantity = _previousServiceAccounts, Deleted = _previousServiceAccounts == 0 ? true : (bool?)null, }); diff --git a/src/Core/Models/StaticStore/Plan.cs b/src/Core/Models/StaticStore/Plan.cs index f542e5e21..4f8b0435f 100644 --- a/src/Core/Models/StaticStore/Plan.cs +++ b/src/Core/Models/StaticStore/Plan.cs @@ -2,64 +2,84 @@ namespace Bit.Core.Models.StaticStore; -public class Plan +public abstract record Plan { - public PlanType Type { get; set; } - public ProductType Product { get; set; } - public string Name { get; set; } - public bool IsAnnual { get; set; } - public string NameLocalizationKey { get; set; } - public string DescriptionLocalizationKey { get; set; } - public bool CanBeUsedByBusiness { get; set; } - public int BaseSeats { get; set; } - public short? BaseStorageGb { get; set; } - public short? MaxCollections { get; set; } - public short? MaxUsers { get; set; } - public short? MaxServiceAccounts { get; set; } - public bool AllowSeatAutoscale { get; set; } + public PlanType Type { get; protected init; } + public ProductType Product { get; protected init; } + public string Name { get; protected init; } + public bool IsAnnual { get; protected init; } + public string NameLocalizationKey { get; protected init; } + public string DescriptionLocalizationKey { get; protected init; } + public bool CanBeUsedByBusiness { get; protected init; } + public int? TrialPeriodDays { get; protected init; } + public bool HasSelfHost { get; protected init; } + public bool HasPolicies { get; protected init; } + public bool HasGroups { get; protected init; } + public bool HasDirectory { get; protected init; } + public bool HasEvents { get; protected init; } + public bool HasTotp { get; protected init; } + public bool Has2fa { get; protected init; } + public bool HasApi { get; protected init; } + public bool HasSso { get; protected init; } + public bool HasKeyConnector { get; protected init; } + public bool HasScim { get; protected init; } + public bool HasResetPassword { get; protected init; } + public bool UsersGetPremium { get; protected init; } + public bool HasCustomPermissions { get; protected init; } + public int UpgradeSortOrder { get; protected init; } + public int DisplaySortOrder { get; protected init; } + public int? LegacyYear { get; protected init; } + public bool Disabled { get; protected init; } + public PasswordManagerPlanFeatures PasswordManager { get; protected init; } + public SecretsManagerPlanFeatures SecretsManager { get; protected init; } + public bool SupportsSecretsManager => SecretsManager != null; - public bool AllowServiceAccountsAutoscale { get; set; } + public record SecretsManagerPlanFeatures + { + // Service accounts + public short? MaxServiceAccounts { get; init; } + public bool AllowServiceAccountsAutoscale { get; init; } + public string StripeServiceAccountPlanId { get; init; } + public decimal? AdditionalPricePerServiceAccount { get; init; } + public short BaseServiceAccount { get; init; } + public short? MaxAdditionalServiceAccount { get; init; } + public bool HasAdditionalServiceAccountOption { get; init; } + // Seats + public string StripeSeatPlanId { get; init; } + public bool HasAdditionalSeatsOption { get; init; } + public decimal BasePrice { get; init; } + public decimal SeatPrice { get; init; } + public int BaseSeats { get; init; } + public short? MaxSeats { get; init; } + public int? MaxAdditionalSeats { get; init; } + public bool AllowSeatAutoscale { get; init; } - public bool HasAdditionalSeatsOption { get; set; } - public int? MaxAdditionalSeats { get; set; } - public bool HasAdditionalStorageOption { get; set; } - public short? MaxAdditionalStorage { get; set; } - public bool HasPremiumAccessOption { get; set; } - public int? TrialPeriodDays { get; set; } + // Features + public int MaxProjects { get; init; } + } - public bool HasSelfHost { get; set; } - public bool HasPolicies { get; set; } - public bool HasGroups { get; set; } - public bool HasDirectory { get; set; } - public bool HasEvents { get; set; } - public bool HasTotp { get; set; } - public bool Has2fa { get; set; } - public bool HasApi { get; set; } - public bool HasSso { get; set; } - public bool HasKeyConnector { get; set; } - public bool HasScim { get; set; } - public bool HasResetPassword { get; set; } - public bool UsersGetPremium { get; set; } - public bool HasCustomPermissions { get; set; } - - public int UpgradeSortOrder { get; set; } - public int DisplaySortOrder { get; set; } - public int? LegacyYear { get; set; } - public bool Disabled { get; set; } - - public string StripePlanId { get; set; } - public string StripeSeatPlanId { get; set; } - public string StripeStoragePlanId { get; set; } - public string StripeServiceAccountPlanId { get; set; } - public string StripePremiumAccessPlanId { get; set; } - public decimal BasePrice { get; set; } - public decimal SeatPrice { get; set; } - public decimal AdditionalStoragePricePerGb { get; set; } - public decimal PremiumAccessOptionPrice { get; set; } - public decimal? AdditionalPricePerServiceAccount { get; set; } - public short? BaseServiceAccount { get; set; } - public short? MaxAdditionalServiceAccount { get; set; } - public bool HasAdditionalServiceAccountOption { get; set; } - public short? MaxProjects { get; set; } - public BitwardenProductType BitwardenProduct { get; set; } + public record PasswordManagerPlanFeatures + { + // Seats + public string StripePlanId { get; init; } + public string StripeSeatPlanId { get; init; } + public decimal BasePrice { get; init; } + public decimal SeatPrice { get; init; } + public bool AllowSeatAutoscale { get; init; } + public bool HasAdditionalSeatsOption { get; init; } + public int? MaxAdditionalSeats { get; init; } + public int BaseSeats { get; init; } + public bool HasPremiumAccessOption { get; init; } + public string StripePremiumAccessPlanId { get; init; } + public decimal PremiumAccessOptionPrice { get; init; } + public short? MaxSeats { get; init; } + // Storage + public short? BaseStorageGb { get; init; } + public bool HasAdditionalStorageOption { get; init; } + public decimal AdditionalStoragePricePerGb { get; init; } + public string StripeStoragePlanId { get; init; } + public short? MaxAdditionalStorage { get; init; } + // Feature + public short? MaxCollections { get; init; } + } } diff --git a/src/Core/Models/StaticStore/Plans/CustomPlan.cs b/src/Core/Models/StaticStore/Plans/CustomPlan.cs new file mode 100644 index 000000000..77eee781e --- /dev/null +++ b/src/Core/Models/StaticStore/Plans/CustomPlan.cs @@ -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; + } + } +} diff --git a/src/Core/Models/StaticStore/Plans/Enterprise2019Plan.cs b/src/Core/Models/StaticStore/Plans/Enterprise2019Plan.cs new file mode 100644 index 000000000..8cbb579ea --- /dev/null +++ b/src/Core/Models/StaticStore/Plans/Enterprise2019Plan.cs @@ -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; + } + } + } +} diff --git a/src/Core/Models/StaticStore/Plans/EnterprisePlan.cs b/src/Core/Models/StaticStore/Plans/EnterprisePlan.cs new file mode 100644 index 000000000..fbb319883 --- /dev/null +++ b/src/Core/Models/StaticStore/Plans/EnterprisePlan.cs @@ -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; + } + } + } +} diff --git a/src/Core/Models/StaticStore/Plans/Families2019Plan.cs b/src/Core/Models/StaticStore/Plans/Families2019Plan.cs new file mode 100644 index 000000000..14ddb3405 --- /dev/null +++ b/src/Core/Models/StaticStore/Plans/Families2019Plan.cs @@ -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; + } + } +} diff --git a/src/Core/Models/StaticStore/Plans/FamiliesPlan.cs b/src/Core/Models/StaticStore/Plans/FamiliesPlan.cs new file mode 100644 index 000000000..9a6e90cf0 --- /dev/null +++ b/src/Core/Models/StaticStore/Plans/FamiliesPlan.cs @@ -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; + } + } +} diff --git a/src/Core/Models/StaticStore/Plans/FreePlan.cs b/src/Core/Models/StaticStore/Plans/FreePlan.cs new file mode 100644 index 000000000..2b647f91e --- /dev/null +++ b/src/Core/Models/StaticStore/Plans/FreePlan.cs @@ -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; + } + } +} diff --git a/src/Core/Models/StaticStore/Plans/Teams2019Plan.cs b/src/Core/Models/StaticStore/Plans/Teams2019Plan.cs new file mode 100644 index 000000000..f806f3773 --- /dev/null +++ b/src/Core/Models/StaticStore/Plans/Teams2019Plan.cs @@ -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; + } + } + } +} diff --git a/src/Core/Models/StaticStore/Plans/TeamsPlan.cs b/src/Core/Models/StaticStore/Plans/TeamsPlan.cs new file mode 100644 index 000000000..0e325242e --- /dev/null +++ b/src/Core/Models/StaticStore/Plans/TeamsPlan.cs @@ -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; + } + } + } +} diff --git a/src/Core/OrganizationFeatures/OrganizationSponsorships/FamiliesForEnterprise/Cloud/CloudSyncSponsorshipsCommand.cs b/src/Core/OrganizationFeatures/OrganizationSponsorships/FamiliesForEnterprise/Cloud/CloudSyncSponsorshipsCommand.cs index 5c0f1474a..d0569278b 100644 --- a/src/Core/OrganizationFeatures/OrganizationSponsorships/FamiliesForEnterprise/Cloud/CloudSyncSponsorshipsCommand.cs +++ b/src/Core/OrganizationFeatures/OrganizationSponsorships/FamiliesForEnterprise/Cloud/CloudSyncSponsorshipsCommand.cs @@ -54,7 +54,7 @@ public class CloudSyncSponsorshipsCommand : ICloudSyncSponsorshipsCommand { var requiredSponsoringProductType = StaticStore.GetSponsoredPlan(selfHostedSponsorship.PlanSponsorshipType)?.SponsoringProductType; if (requiredSponsoringProductType == null - || StaticStore.GetPasswordManagerPlan(sponsoringOrg.PlanType).Product != requiredSponsoringProductType.Value) + || StaticStore.GetPlan(sponsoringOrg.PlanType).Product != requiredSponsoringProductType.Value) { continue; // prevent unsupported sponsorships } diff --git a/src/Core/OrganizationFeatures/OrganizationSponsorships/FamiliesForEnterprise/Cloud/SetUpSponsorshipCommand.cs b/src/Core/OrganizationFeatures/OrganizationSponsorships/FamiliesForEnterprise/Cloud/SetUpSponsorshipCommand.cs index 81a8bac96..9230e7d13 100644 --- a/src/Core/OrganizationFeatures/OrganizationSponsorships/FamiliesForEnterprise/Cloud/SetUpSponsorshipCommand.cs +++ b/src/Core/OrganizationFeatures/OrganizationSponsorships/FamiliesForEnterprise/Cloud/SetUpSponsorshipCommand.cs @@ -51,7 +51,7 @@ public class SetUpSponsorshipCommand : ISetUpSponsorshipCommand var requiredSponsoredProductType = StaticStore.GetSponsoredPlan(sponsorship.PlanSponsorshipType.Value)?.SponsoredProductType; if (requiredSponsoredProductType == null || sponsoredOrganization == null || - StaticStore.GetPasswordManagerPlan(sponsoredOrganization.PlanType).Product != requiredSponsoredProductType.Value) + StaticStore.GetPlan(sponsoredOrganization.PlanType).Product != requiredSponsoredProductType.Value) { throw new BadRequestException("Can only redeem sponsorship offer on families organizations."); } diff --git a/src/Core/OrganizationFeatures/OrganizationSponsorships/FamiliesForEnterprise/Cloud/ValidateSponsorshipCommand.cs b/src/Core/OrganizationFeatures/OrganizationSponsorships/FamiliesForEnterprise/Cloud/ValidateSponsorshipCommand.cs index af2f0af65..3f2d7af5e 100644 --- a/src/Core/OrganizationFeatures/OrganizationSponsorships/FamiliesForEnterprise/Cloud/ValidateSponsorshipCommand.cs +++ b/src/Core/OrganizationFeatures/OrganizationSponsorships/FamiliesForEnterprise/Cloud/ValidateSponsorshipCommand.cs @@ -56,7 +56,7 @@ public class ValidateSponsorshipCommand : CancelSponsorshipCommand, IValidateSpo return false; } - var sponsoringOrgPlan = Utilities.StaticStore.GetPasswordManagerPlan(sponsoringOrganization.PlanType); + var sponsoringOrgPlan = Utilities.StaticStore.GetPlan(sponsoringOrganization.PlanType); if (OrgDisabledForMoreThanGracePeriod(sponsoringOrganization) || sponsoredPlan.SponsoringProductType != sponsoringOrgPlan.Product || existingSponsorship.ToDelete || diff --git a/src/Core/OrganizationFeatures/OrganizationSponsorships/FamiliesForEnterprise/CreateSponsorshipCommand.cs b/src/Core/OrganizationFeatures/OrganizationSponsorships/FamiliesForEnterprise/CreateSponsorshipCommand.cs index bfc33ebe8..69e6c3232 100644 --- a/src/Core/OrganizationFeatures/OrganizationSponsorships/FamiliesForEnterprise/CreateSponsorshipCommand.cs +++ b/src/Core/OrganizationFeatures/OrganizationSponsorships/FamiliesForEnterprise/CreateSponsorshipCommand.cs @@ -32,7 +32,7 @@ public class CreateSponsorshipCommand : ICreateSponsorshipCommand var requiredSponsoringProductType = StaticStore.GetSponsoredPlan(sponsorshipType)?.SponsoringProductType; if (requiredSponsoringProductType == null || sponsoringOrg == null || - StaticStore.GetPasswordManagerPlan(sponsoringOrg.PlanType).Product != requiredSponsoringProductType.Value) + StaticStore.GetPlan(sponsoringOrg.PlanType).Product != requiredSponsoringProductType.Value) { throw new BadRequestException("Specified Organization cannot sponsor other organizations."); } diff --git a/src/Core/OrganizationFeatures/OrganizationSubscriptions/AddSecretsManagerSubscriptionCommand.cs b/src/Core/OrganizationFeatures/OrganizationSubscriptions/AddSecretsManagerSubscriptionCommand.cs index 52136bd1b..bd33f336e 100644 --- a/src/Core/OrganizationFeatures/OrganizationSubscriptions/AddSecretsManagerSubscriptionCommand.cs +++ b/src/Core/OrganizationFeatures/OrganizationSubscriptions/AddSecretsManagerSubscriptionCommand.cs @@ -30,7 +30,7 @@ public class AddSecretsManagerSubscriptionCommand : IAddSecretsManagerSubscripti { await ValidateOrganization(organization); - var plan = StaticStore.GetSecretsManagerPlan(organization.PlanType); + var plan = StaticStore.GetPlan(organization.PlanType); var signup = SetOrganizationUpgrade(organization, additionalSmSeats, additionalServiceAccounts); _organizationService.ValidateSecretsManagerPlan(plan, signup); @@ -39,8 +39,8 @@ public class AddSecretsManagerSubscriptionCommand : IAddSecretsManagerSubscripti await _paymentService.AddSecretsManagerToSubscription(organization, plan, additionalSmSeats, additionalServiceAccounts); } - organization.SmSeats = plan.BaseSeats + additionalSmSeats; - organization.SmServiceAccounts = plan.BaseServiceAccount.GetValueOrDefault() + additionalServiceAccounts; + organization.SmSeats = plan.SecretsManager.BaseSeats + additionalSmSeats; + organization.SmServiceAccounts = plan.SecretsManager.BaseServiceAccount + additionalServiceAccounts; organization.UseSecretsManager = true; await _organizationService.ReplaceAndUpdateCacheAsync(organization); @@ -79,7 +79,7 @@ public class AddSecretsManagerSubscriptionCommand : IAddSecretsManagerSubscripti throw new BadRequestException("Organization already uses Secrets Manager."); } - var plan = StaticStore.GetSecretsManagerPlan(organization.PlanType); + var plan = StaticStore.Plans.FirstOrDefault(p => p.Type == organization.PlanType && p.SupportsSecretsManager); if (string.IsNullOrWhiteSpace(organization.GatewayCustomerId) && plan.Product != ProductType.Free) { throw new BadRequestException("No payment method found."); diff --git a/src/Core/OrganizationFeatures/OrganizationSubscriptions/UpdateSecretsManagerSubscriptionCommand.cs b/src/Core/OrganizationFeatures/OrganizationSubscriptions/UpdateSecretsManagerSubscriptionCommand.cs index ab96e55f7..aeaac974d 100644 --- a/src/Core/OrganizationFeatures/OrganizationSubscriptions/UpdateSecretsManagerSubscriptionCommand.cs +++ b/src/Core/OrganizationFeatures/OrganizationSubscriptions/UpdateSecretsManagerSubscriptionCommand.cs @@ -205,10 +205,10 @@ public class UpdateSecretsManagerSubscriptionCommand : IUpdateSecretsManagerSubs } // Check plan maximum seats - if (!plan.HasAdditionalSeatsOption || - (plan.MaxAdditionalSeats.HasValue && update.SmSeatsExcludingBase > plan.MaxAdditionalSeats.Value)) + if (!plan.SecretsManager.HasAdditionalSeatsOption || + (plan.SecretsManager.MaxAdditionalSeats.HasValue && update.SmSeatsExcludingBase > plan.SecretsManager.MaxAdditionalSeats.Value)) { - var planMaxSeats = plan.BaseSeats + plan.MaxAdditionalSeats.GetValueOrDefault(); + var planMaxSeats = plan.SecretsManager.BaseSeats + plan.SecretsManager.MaxAdditionalSeats.GetValueOrDefault(); throw new BadRequestException($"You have reached the maximum number of Secrets Manager seats ({planMaxSeats}) for this plan."); } @@ -222,9 +222,9 @@ public class UpdateSecretsManagerSubscriptionCommand : IUpdateSecretsManagerSubs } // Check minimum seats included with plan - if (plan.BaseSeats > update.SmSeats.Value) + if (plan.SecretsManager.BaseSeats > update.SmSeats.Value) { - throw new BadRequestException($"Plan has a minimum of {plan.BaseSeats} Secrets Manager seats."); + throw new BadRequestException($"Plan has a minimum of {plan.SecretsManager.BaseSeats} Secrets Manager seats."); } // Check minimum seats required by business logic @@ -262,11 +262,11 @@ public class UpdateSecretsManagerSubscriptionCommand : IUpdateSecretsManagerSubs } // Check plan maximum service accounts - if (!plan.HasAdditionalServiceAccountOption || - (plan.MaxAdditionalServiceAccount.HasValue && update.SmServiceAccountsExcludingBase > plan.MaxAdditionalServiceAccount.Value)) + if (!plan.SecretsManager.HasAdditionalServiceAccountOption || + (plan.SecretsManager.MaxAdditionalServiceAccount.HasValue && update.SmServiceAccountsExcludingBase > plan.SecretsManager.MaxAdditionalServiceAccount.Value)) { - var planMaxServiceAccounts = plan.BaseServiceAccount.GetValueOrDefault() + - plan.MaxAdditionalServiceAccount.GetValueOrDefault(); + var planMaxServiceAccounts = plan.SecretsManager.BaseServiceAccount + + plan.SecretsManager.MaxAdditionalServiceAccount.GetValueOrDefault(); throw new BadRequestException($"You have reached the maximum number of service accounts ({planMaxServiceAccounts}) for this plan."); } @@ -281,9 +281,9 @@ public class UpdateSecretsManagerSubscriptionCommand : IUpdateSecretsManagerSubs } // Check minimum service accounts included with plan - if (plan.BaseServiceAccount.HasValue && plan.BaseServiceAccount.Value > update.SmServiceAccounts.Value) + if (plan.SecretsManager.BaseServiceAccount > update.SmServiceAccounts.Value) { - throw new BadRequestException($"Plan has a minimum of {plan.BaseServiceAccount} service accounts."); + throw new BadRequestException($"Plan has a minimum of {plan.SecretsManager.BaseServiceAccount} service accounts."); } // Check minimum service accounts required by business logic @@ -319,15 +319,15 @@ public class UpdateSecretsManagerSubscriptionCommand : IUpdateSecretsManagerSubs throw new BadRequestException($"Cannot set max Secrets Manager seat autoscaling below current Secrets Manager seat count."); } - if (plan.MaxUsers.HasValue && update.MaxAutoscaleSmSeats.Value > plan.MaxUsers) + if (plan.SecretsManager.MaxSeats.HasValue && update.MaxAutoscaleSmSeats.Value > plan.SecretsManager.MaxSeats) { throw new BadRequestException(string.Concat( - $"Your plan has a Secrets Manager seat limit of {plan.MaxUsers}, ", + $"Your plan has a Secrets Manager seat limit of {plan.SecretsManager.MaxSeats}, ", $"but you have specified a max autoscale count of {update.MaxAutoscaleSmSeats}.", "Reduce your max autoscale count.")); } - if (!plan.AllowSeatAutoscale) + if (!plan.SecretsManager.AllowSeatAutoscale) { throw new BadRequestException("Your plan does not allow Secrets Manager seat autoscaling."); } @@ -349,15 +349,15 @@ public class UpdateSecretsManagerSubscriptionCommand : IUpdateSecretsManagerSubs $"Cannot set max service accounts autoscaling below current service accounts count."); } - if (!plan.AllowServiceAccountsAutoscale) + if (!plan.SecretsManager.AllowServiceAccountsAutoscale) { throw new BadRequestException("Your plan does not allow service accounts autoscaling."); } - if (plan.MaxServiceAccounts.HasValue && update.MaxAutoscaleSmServiceAccounts.Value > plan.MaxServiceAccounts) + if (plan.SecretsManager.MaxServiceAccounts.HasValue && update.MaxAutoscaleSmServiceAccounts.Value > plan.SecretsManager.MaxServiceAccounts) { throw new BadRequestException(string.Concat( - $"Your plan has a service account limit of {plan.MaxServiceAccounts}, ", + $"Your plan has a service account limit of {plan.SecretsManager.MaxServiceAccounts}, ", $"but you have specified a max autoscale count of {update.MaxAutoscaleSmServiceAccounts}.", "Reduce your max autoscale count.")); } diff --git a/src/Core/OrganizationFeatures/OrganizationSubscriptions/UpgradeOrganizationPlanCommand.cs b/src/Core/OrganizationFeatures/OrganizationSubscriptions/UpgradeOrganizationPlanCommand.cs index dec05ac9e..02b3818f1 100644 --- a/src/Core/OrganizationFeatures/OrganizationSubscriptions/UpgradeOrganizationPlanCommand.cs +++ b/src/Core/OrganizationFeatures/OrganizationSubscriptions/UpgradeOrganizationPlanCommand.cs @@ -73,69 +73,67 @@ public class UpgradeOrganizationPlanCommand : IUpgradeOrganizationPlanCommand throw new BadRequestException("Your account has no payment method available."); } - var existingPasswordManagerPlan = StaticStore.PasswordManagerPlans.FirstOrDefault(p => p.Type == organization.PlanType); - if (existingPasswordManagerPlan == null) + var existingPlan = StaticStore.GetPlan(organization.PlanType); + if (existingPlan == null) { throw new BadRequestException("Existing plan not found."); } - var newPasswordManagerPlan = - StaticStore.PasswordManagerPlans.FirstOrDefault(p => p.Type == upgrade.Plan && !p.Disabled); - if (newPasswordManagerPlan == null) + var newPlan = StaticStore.Plans.FirstOrDefault(p => p.Type == upgrade.Plan && !p.Disabled); + if (newPlan == null) { throw new BadRequestException("Plan not found."); } - if (existingPasswordManagerPlan.Type == newPasswordManagerPlan.Type) + if (existingPlan.Type == newPlan.Type) { throw new BadRequestException("Organization is already on this plan."); } - if (existingPasswordManagerPlan.UpgradeSortOrder >= newPasswordManagerPlan.UpgradeSortOrder) + if (existingPlan.UpgradeSortOrder >= newPlan.UpgradeSortOrder) { throw new BadRequestException("You cannot upgrade to this plan."); } - if (existingPasswordManagerPlan.Type != PlanType.Free) + if (existingPlan.Type != PlanType.Free) { throw new BadRequestException("You can only upgrade from the free plan. Contact support."); } - _organizationService.ValidatePasswordManagerPlan(newPasswordManagerPlan, upgrade); - var newSecretsManagerPlan = - StaticStore.SecretManagerPlans.FirstOrDefault(p => p.Type == upgrade.Plan && !p.Disabled); + _organizationService.ValidatePasswordManagerPlan(newPlan, upgrade); + if (upgrade.UseSecretsManager) { - _organizationService.ValidateSecretsManagerPlan(newSecretsManagerPlan, upgrade); + _organizationService.ValidateSecretsManagerPlan(newPlan, upgrade); } - var newPasswordManagerPlanSeats = (short)(newPasswordManagerPlan.BaseSeats + - (newPasswordManagerPlan.HasAdditionalSeatsOption ? upgrade.AdditionalSeats : 0)); - if (!organization.Seats.HasValue || organization.Seats.Value > newPasswordManagerPlanSeats) + var updatedPasswordManagerSeats = (short)(newPlan.PasswordManager.BaseSeats + + (newPlan.PasswordManager.HasAdditionalSeatsOption ? upgrade.AdditionalSeats : 0)); + if (!organization.Seats.HasValue || organization.Seats.Value > updatedPasswordManagerSeats) { var occupiedSeats = await _organizationUserRepository.GetOccupiedSeatCountByOrganizationIdAsync(organization.Id); - if (occupiedSeats > newPasswordManagerPlanSeats) + if (occupiedSeats > updatedPasswordManagerSeats) { throw new BadRequestException($"Your organization currently has {occupiedSeats} seats filled. " + - $"Your new plan only has ({newPasswordManagerPlanSeats}) seats. Remove some users."); + $"Your new plan only has ({updatedPasswordManagerSeats}) seats. Remove some users."); } } - if (newPasswordManagerPlan.MaxCollections.HasValue && (!organization.MaxCollections.HasValue || + if (newPlan.PasswordManager.MaxCollections.HasValue && (!organization.MaxCollections.HasValue || organization.MaxCollections.Value > - newPasswordManagerPlan.MaxCollections.Value)) + newPlan.PasswordManager.MaxCollections.Value)) { var collectionCount = await _collectionRepository.GetCountByOrganizationIdAsync(organization.Id); - if (collectionCount > newPasswordManagerPlan.MaxCollections.Value) + if (collectionCount > newPlan.PasswordManager.MaxCollections.Value) { throw new BadRequestException($"Your organization currently has {collectionCount} collections. " + - $"Your new plan allows for a maximum of ({newPasswordManagerPlan.MaxCollections.Value}) collections. " + + $"Your new plan allows for a maximum of ({newPlan.PasswordManager.MaxCollections.Value}) collections. " + "Remove some collections."); } } - if (!newPasswordManagerPlan.HasGroups && organization.UseGroups) + if (!newPlan.HasGroups && organization.UseGroups) { var groups = await _groupRepository.GetManyByOrganizationIdAsync(organization.Id); if (groups.Any()) @@ -145,7 +143,7 @@ public class UpgradeOrganizationPlanCommand : IUpgradeOrganizationPlanCommand } } - if (!newPasswordManagerPlan.HasPolicies && organization.UsePolicies) + if (!newPlan.HasPolicies && organization.UsePolicies) { var policies = await _policyRepository.GetManyByOrganizationIdAsync(organization.Id); if (policies.Any(p => p.Enabled)) @@ -155,7 +153,7 @@ public class UpgradeOrganizationPlanCommand : IUpgradeOrganizationPlanCommand } } - if (!newPasswordManagerPlan.HasSso && organization.UseSso) + if (!newPlan.HasSso && organization.UseSso) { var ssoConfig = await _ssoConfigRepository.GetByOrganizationIdAsync(organization.Id); if (ssoConfig != null && ssoConfig.Enabled) @@ -165,7 +163,7 @@ public class UpgradeOrganizationPlanCommand : IUpgradeOrganizationPlanCommand } } - if (!newPasswordManagerPlan.HasKeyConnector && organization.UseKeyConnector) + if (!newPlan.HasKeyConnector && organization.UseKeyConnector) { var ssoConfig = await _ssoConfigRepository.GetByOrganizationIdAsync(organization.Id); if (ssoConfig != null && ssoConfig.GetData().MemberDecryptionType == MemberDecryptionType.KeyConnector) @@ -175,7 +173,7 @@ public class UpgradeOrganizationPlanCommand : IUpgradeOrganizationPlanCommand } } - if (!newPasswordManagerPlan.HasResetPassword && organization.UseResetPassword) + if (!newPlan.HasResetPassword && organization.UseResetPassword) { var resetPasswordPolicy = await _policyRepository.GetByOrganizationIdTypeAsync(organization.Id, PolicyType.ResetPassword); @@ -186,7 +184,7 @@ public class UpgradeOrganizationPlanCommand : IUpgradeOrganizationPlanCommand } } - if (!newPasswordManagerPlan.HasScim && organization.UseScim) + if (!newPlan.HasScim && organization.UseScim) { var scimConnections = await _organizationConnectionRepository.GetByOrganizationIdTypeAsync(organization.Id, OrganizationConnectionType.Scim); @@ -197,7 +195,7 @@ public class UpgradeOrganizationPlanCommand : IUpgradeOrganizationPlanCommand } } - if (!newPasswordManagerPlan.HasCustomPermissions && organization.UseCustomPermissions) + if (!newPlan.HasCustomPermissions && organization.UseCustomPermissions) { var organizationCustomUsers = await _organizationUserRepository.GetManyByOrganizationAsync(organization.Id, @@ -209,9 +207,9 @@ public class UpgradeOrganizationPlanCommand : IUpgradeOrganizationPlanCommand } } - if (upgrade.UseSecretsManager && newSecretsManagerPlan != null) + if (upgrade.UseSecretsManager) { - await ValidateSecretsManagerSeatsAndServiceAccountAsync(upgrade, organization, newSecretsManagerPlan); + await ValidateSecretsManagerSeatsAndServiceAccountAsync(upgrade, organization, newPlan); } // TODO: Check storage? @@ -220,12 +218,8 @@ public class UpgradeOrganizationPlanCommand : IUpgradeOrganizationPlanCommand if (string.IsNullOrWhiteSpace(organization.GatewaySubscriptionId)) { - var organizationUpgradePlan = upgrade.UseSecretsManager - ? StaticStore.Plans.Where(p => p.Type == upgrade.Plan).ToList() - : StaticStore.Plans.Where(p => p.Type == upgrade.Plan && p.BitwardenProduct == BitwardenProductType.PasswordManager).ToList(); - paymentIntentClientSecret = await _paymentService.UpgradeFreeOrganizationAsync(organization, - organizationUpgradePlan, upgrade); + newPlan, upgrade); success = string.IsNullOrWhiteSpace(paymentIntentClientSecret); } else @@ -235,34 +229,34 @@ public class UpgradeOrganizationPlanCommand : IUpgradeOrganizationPlanCommand } organization.BusinessName = upgrade.BusinessName; - organization.PlanType = newPasswordManagerPlan.Type; - organization.Seats = (short)(newPasswordManagerPlan.BaseSeats + upgrade.AdditionalSeats); - organization.MaxCollections = newPasswordManagerPlan.MaxCollections; - organization.UseGroups = newPasswordManagerPlan.HasGroups; - organization.UseDirectory = newPasswordManagerPlan.HasDirectory; - organization.UseEvents = newPasswordManagerPlan.HasEvents; - organization.UseTotp = newPasswordManagerPlan.HasTotp; - organization.Use2fa = newPasswordManagerPlan.Has2fa; - organization.UseApi = newPasswordManagerPlan.HasApi; - organization.SelfHost = newPasswordManagerPlan.HasSelfHost; - organization.UsePolicies = newPasswordManagerPlan.HasPolicies; - organization.MaxStorageGb = !newPasswordManagerPlan.BaseStorageGb.HasValue + organization.PlanType = newPlan.Type; + organization.Seats = (short)(newPlan.PasswordManager.BaseSeats + upgrade.AdditionalSeats); + organization.MaxCollections = newPlan.PasswordManager.MaxCollections; + organization.UseGroups = newPlan.HasGroups; + organization.UseDirectory = newPlan.HasDirectory; + organization.UseEvents = newPlan.HasEvents; + organization.UseTotp = newPlan.HasTotp; + organization.Use2fa = newPlan.Has2fa; + organization.UseApi = newPlan.HasApi; + organization.SelfHost = newPlan.HasSelfHost; + organization.UsePolicies = newPlan.HasPolicies; + organization.MaxStorageGb = !newPlan.PasswordManager.BaseStorageGb.HasValue ? (short?)null - : (short)(newPasswordManagerPlan.BaseStorageGb.Value + upgrade.AdditionalStorageGb); - organization.UseGroups = newPasswordManagerPlan.HasGroups; - organization.UseDirectory = newPasswordManagerPlan.HasDirectory; - organization.UseEvents = newPasswordManagerPlan.HasEvents; - organization.UseTotp = newPasswordManagerPlan.HasTotp; - organization.Use2fa = newPasswordManagerPlan.Has2fa; - organization.UseApi = newPasswordManagerPlan.HasApi; - organization.UseSso = newPasswordManagerPlan.HasSso; - organization.UseKeyConnector = newPasswordManagerPlan.HasKeyConnector; - organization.UseScim = newPasswordManagerPlan.HasScim; - organization.UseResetPassword = newPasswordManagerPlan.HasResetPassword; - organization.SelfHost = newPasswordManagerPlan.HasSelfHost; - organization.UsersGetPremium = newPasswordManagerPlan.UsersGetPremium || upgrade.PremiumAccessAddon; - organization.UseCustomPermissions = newPasswordManagerPlan.HasCustomPermissions; - organization.Plan = newPasswordManagerPlan.Name; + : (short)(newPlan.PasswordManager.BaseStorageGb.Value + upgrade.AdditionalStorageGb); + organization.UseGroups = newPlan.HasGroups; + organization.UseDirectory = newPlan.HasDirectory; + organization.UseEvents = newPlan.HasEvents; + organization.UseTotp = newPlan.HasTotp; + organization.Use2fa = newPlan.Has2fa; + organization.UseApi = newPlan.HasApi; + organization.UseSso = newPlan.HasSso; + organization.UseKeyConnector = newPlan.HasKeyConnector; + organization.UseScim = newPlan.HasScim; + organization.UseResetPassword = newPlan.HasResetPassword; + organization.SelfHost = newPlan.HasSelfHost; + organization.UsersGetPremium = newPlan.UsersGetPremium || upgrade.PremiumAccessAddon; + organization.UseCustomPermissions = newPlan.HasCustomPermissions; + organization.Plan = newPlan.Name; organization.Enabled = success; organization.PublicKey = upgrade.PublicKey; organization.PrivateKey = upgrade.PrivateKey; @@ -271,8 +265,8 @@ public class UpgradeOrganizationPlanCommand : IUpgradeOrganizationPlanCommand if (upgrade.UseSecretsManager) { - organization.SmSeats = newSecretsManagerPlan.BaseSeats + upgrade.AdditionalSmSeats.GetValueOrDefault(); - organization.SmServiceAccounts = newSecretsManagerPlan.BaseServiceAccount.GetValueOrDefault() + + organization.SmSeats = newPlan.SecretsManager.BaseSeats + upgrade.AdditionalSmSeats.GetValueOrDefault(); + organization.SmServiceAccounts = newPlan.SecretsManager.BaseServiceAccount + upgrade.AdditionalServiceAccounts.GetValueOrDefault(); } @@ -283,10 +277,10 @@ public class UpgradeOrganizationPlanCommand : IUpgradeOrganizationPlanCommand await _referenceEventService.RaiseEventAsync( new ReferenceEvent(ReferenceEventType.UpgradePlan, organization, _currentContext) { - PlanName = newPasswordManagerPlan.Name, - PlanType = newPasswordManagerPlan.Type, - OldPlanName = existingPasswordManagerPlan.Name, - OldPlanType = existingPasswordManagerPlan.Type, + PlanName = newPlan.Name, + PlanType = newPlan.Type, + OldPlanName = existingPlan.Name, + OldPlanType = existingPlan.Type, Seats = organization.Seats, Storage = organization.MaxStorageGb, // TODO: add reference events for SmSeats and Service Accounts - see AC-1481 @@ -299,8 +293,8 @@ public class UpgradeOrganizationPlanCommand : IUpgradeOrganizationPlanCommand private async Task ValidateSecretsManagerSeatsAndServiceAccountAsync(OrganizationUpgrade upgrade, Organization organization, Models.StaticStore.Plan newSecretsManagerPlan) { - var newPlanSmSeats = (short)(newSecretsManagerPlan.BaseSeats + - (newSecretsManagerPlan.HasAdditionalSeatsOption + var newPlanSmSeats = (short)(newSecretsManagerPlan.SecretsManager.BaseSeats + + (newSecretsManagerPlan.SecretsManager.HasAdditionalSeatsOption ? upgrade.AdditionalSmSeats : 0)); var occupiedSmSeats = @@ -316,10 +310,10 @@ public class UpgradeOrganizationPlanCommand : IUpgradeOrganizationPlanCommand } } - var additionalServiceAccounts = newSecretsManagerPlan.HasAdditionalServiceAccountOption + var additionalServiceAccounts = newSecretsManagerPlan.SecretsManager.HasAdditionalServiceAccountOption ? upgrade.AdditionalServiceAccounts : 0; - var newPlanServiceAccounts = newSecretsManagerPlan.BaseServiceAccount + additionalServiceAccounts; + var newPlanServiceAccounts = newSecretsManagerPlan.SecretsManager.BaseServiceAccount + additionalServiceAccounts; if (!organization.SmServiceAccounts.HasValue || organization.SmServiceAccounts.Value > newPlanServiceAccounts) { @@ -329,7 +323,7 @@ public class UpgradeOrganizationPlanCommand : IUpgradeOrganizationPlanCommand { throw new BadRequestException( $"Your organization currently has {currentServiceAccounts} service accounts. " + - $"Your new plan only allows {newSecretsManagerPlan.MaxServiceAccounts} service accounts. " + + $"Your new plan only allows {newSecretsManagerPlan.SecretsManager.MaxServiceAccounts} service accounts. " + "Remove some service accounts or increase your subscription."); } } diff --git a/src/Core/Services/IPaymentService.cs b/src/Core/Services/IPaymentService.cs index 642a423dc..03c2e93dd 100644 --- a/src/Core/Services/IPaymentService.cs +++ b/src/Core/Services/IPaymentService.cs @@ -9,12 +9,12 @@ public interface IPaymentService { Task CancelAndRecoverChargesAsync(ISubscriber subscriber); Task PurchaseOrganizationAsync(Organization org, PaymentMethodType paymentMethodType, - string paymentToken, List plans, short additionalStorageGb, int additionalSeats, + string paymentToken, Plan plan, short additionalStorageGb, int additionalSeats, bool premiumAccessAddon, TaxInfo taxInfo, bool provider = false, int additionalSmSeats = 0, int additionalServiceAccount = 0); Task SponsorOrganizationAsync(Organization org, OrganizationSponsorship sponsorship); Task RemoveOrganizationSponsorshipAsync(Organization org, OrganizationSponsorship sponsorship); - Task UpgradeFreeOrganizationAsync(Organization org, List plans, OrganizationUpgrade upgrade); + Task UpgradeFreeOrganizationAsync(Organization org, Plan plan, OrganizationUpgrade upgrade); Task PurchasePremiumAsync(User user, PaymentMethodType paymentMethodType, string paymentToken, short additionalStorageGb, TaxInfo taxInfo); Task AdjustSeatsAsync(Organization organization, Plan plan, int additionalSeats, DateTime? prorationDate = null); diff --git a/src/Core/Services/Implementations/OrganizationService.cs b/src/Core/Services/Implementations/OrganizationService.cs index e8be0ff03..d1e1085ac 100644 --- a/src/Core/Services/Implementations/OrganizationService.cs +++ b/src/Core/Services/Implementations/OrganizationService.cs @@ -175,19 +175,19 @@ public class OrganizationService : IOrganizationService throw new NotFoundException(); } - var plan = StaticStore.PasswordManagerPlans.FirstOrDefault(p => p.Type == organization.PlanType); + var plan = StaticStore.Plans.FirstOrDefault(p => p.Type == organization.PlanType); if (plan == null) { throw new BadRequestException("Existing plan not found."); } - if (!plan.HasAdditionalStorageOption) + if (!plan.PasswordManager.HasAdditionalStorageOption) { throw new BadRequestException("Plan does not allow additional storage."); } var secret = await BillingHelpers.AdjustStorageAsync(_paymentService, organization, storageAdjustmentGb, - plan.StripeStoragePlanId); + plan.PasswordManager.StripeStoragePlanId); await _referenceEventService.RaiseEventAsync( new ReferenceEvent(ReferenceEventType.AdjustStorage, organization, _currentContext) { @@ -233,21 +233,21 @@ public class OrganizationService : IOrganizationService throw new BadRequestException($"Cannot set max seat autoscaling below current seat count."); } - var plan = StaticStore.PasswordManagerPlans.FirstOrDefault(p => p.Type == organization.PlanType); + var plan = StaticStore.GetPlan(organization.PlanType); if (plan == null) { throw new BadRequestException("Existing plan not found."); } - if (!plan.AllowSeatAutoscale) + if (!plan.PasswordManager.AllowSeatAutoscale) { throw new BadRequestException("Your plan does not allow seat autoscaling."); } - if (plan.MaxUsers.HasValue && maxAutoscaleSeats.HasValue && - maxAutoscaleSeats > plan.MaxUsers) + if (plan.PasswordManager.MaxSeats.HasValue && maxAutoscaleSeats.HasValue && + maxAutoscaleSeats > plan.PasswordManager.MaxSeats) { - throw new BadRequestException(string.Concat($"Your plan has a seat limit of {plan.MaxUsers}, ", + throw new BadRequestException(string.Concat($"Your plan has a seat limit of {plan.PasswordManager.MaxSeats}, ", $"but you have specified a max autoscale count of {maxAutoscaleSeats}.", "Reduce your max autoscale seat count.")); } @@ -285,21 +285,21 @@ public class OrganizationService : IOrganizationService throw new BadRequestException("No subscription found."); } - var plan = StaticStore.PasswordManagerPlans.FirstOrDefault(p => p.Type == organization.PlanType); + var plan = StaticStore.Plans.FirstOrDefault(p => p.Type == organization.PlanType); if (plan == null) { throw new BadRequestException("Existing plan not found."); } - if (!plan.HasAdditionalSeatsOption) + if (!plan.PasswordManager.HasAdditionalSeatsOption) { throw new BadRequestException("Plan does not allow additional seats."); } var newSeatTotal = organization.Seats.Value + seatAdjustment; - if (plan.BaseSeats > newSeatTotal) + if (plan.PasswordManager.BaseSeats > newSeatTotal) { - throw new BadRequestException($"Plan has a minimum of {plan.BaseSeats} seats."); + throw new BadRequestException($"Plan has a minimum of {plan.PasswordManager.BaseSeats} seats."); } if (newSeatTotal <= 0) @@ -307,11 +307,11 @@ public class OrganizationService : IOrganizationService throw new BadRequestException("You must have at least 1 seat."); } - var additionalSeats = newSeatTotal - plan.BaseSeats; - if (plan.MaxAdditionalSeats.HasValue && additionalSeats > plan.MaxAdditionalSeats.Value) + var additionalSeats = newSeatTotal - plan.PasswordManager.BaseSeats; + if (plan.PasswordManager.MaxAdditionalSeats.HasValue && additionalSeats > plan.PasswordManager.MaxAdditionalSeats.Value) { throw new BadRequestException($"Organization plan allows a maximum of " + - $"{plan.MaxAdditionalSeats.Value} additional seats."); + $"{plan.PasswordManager.MaxAdditionalSeats.Value} additional seats."); } if (!organization.Seats.HasValue || organization.Seats.Value > newSeatTotal) @@ -403,11 +403,10 @@ public class OrganizationService : IOrganizationService public async Task> SignUpAsync(OrganizationSignup signup, bool provider = false) { - var passwordManagerPlan = StaticStore.PasswordManagerPlans.FirstOrDefault(p => p.Type == signup.Plan); + var plan = StaticStore.GetPlan(signup.Plan); - ValidatePasswordManagerPlan(passwordManagerPlan, signup); + ValidatePasswordManagerPlan(plan, signup); - var secretsManagerPlan = StaticStore.SecretManagerPlans.FirstOrDefault(p => p.Type == signup.Plan); if (signup.UseSecretsManager) { if (provider) @@ -415,7 +414,7 @@ public class OrganizationService : IOrganizationService throw new BadRequestException( "Organizations with a Managed Service Provider do not support Secrets Manager."); } - ValidateSecretsManagerPlan(secretsManagerPlan, signup); + ValidateSecretsManagerPlan(plan, signup); } if (!provider) @@ -430,25 +429,25 @@ public class OrganizationService : IOrganizationService Name = signup.Name, BillingEmail = signup.BillingEmail, BusinessName = signup.BusinessName, - PlanType = passwordManagerPlan.Type, - Seats = (short)(passwordManagerPlan.BaseSeats + signup.AdditionalSeats), - MaxCollections = passwordManagerPlan.MaxCollections, - MaxStorageGb = !passwordManagerPlan.BaseStorageGb.HasValue ? - (short?)null : (short)(passwordManagerPlan.BaseStorageGb.Value + signup.AdditionalStorageGb), - UsePolicies = passwordManagerPlan.HasPolicies, - UseSso = passwordManagerPlan.HasSso, - UseGroups = passwordManagerPlan.HasGroups, - UseEvents = passwordManagerPlan.HasEvents, - UseDirectory = passwordManagerPlan.HasDirectory, - UseTotp = passwordManagerPlan.HasTotp, - Use2fa = passwordManagerPlan.Has2fa, - UseApi = passwordManagerPlan.HasApi, - UseResetPassword = passwordManagerPlan.HasResetPassword, - SelfHost = passwordManagerPlan.HasSelfHost, - UsersGetPremium = passwordManagerPlan.UsersGetPremium || signup.PremiumAccessAddon, - UseCustomPermissions = passwordManagerPlan.HasCustomPermissions, - UseScim = passwordManagerPlan.HasScim, - Plan = passwordManagerPlan.Name, + PlanType = plan!.Type, + Seats = (short)(plan.PasswordManager.BaseSeats + signup.AdditionalSeats), + MaxCollections = plan.PasswordManager.MaxCollections, + MaxStorageGb = !plan.PasswordManager.BaseStorageGb.HasValue ? + (short?)null : (short)(plan.PasswordManager.BaseStorageGb.Value + signup.AdditionalStorageGb), + UsePolicies = plan.HasPolicies, + UseSso = plan.HasSso, + UseGroups = plan.HasGroups, + UseEvents = plan.HasEvents, + UseDirectory = plan.HasDirectory, + UseTotp = plan.HasTotp, + Use2fa = plan.Has2fa, + UseApi = plan.HasApi, + UseResetPassword = plan.HasResetPassword, + SelfHost = plan.HasSelfHost, + UsersGetPremium = plan.UsersGetPremium || signup.PremiumAccessAddon, + UseCustomPermissions = plan.HasCustomPermissions, + UseScim = plan.HasScim, + Plan = plan.Name, Gateway = null, ReferenceData = signup.Owner.ReferenceData, Enabled = true, @@ -464,12 +463,12 @@ public class OrganizationService : IOrganizationService if (signup.UseSecretsManager) { - organization.SmSeats = secretsManagerPlan.BaseSeats + signup.AdditionalSmSeats.GetValueOrDefault(); - organization.SmServiceAccounts = secretsManagerPlan.BaseServiceAccount.GetValueOrDefault() + + organization.SmSeats = plan.SecretsManager.BaseSeats + signup.AdditionalSmSeats.GetValueOrDefault(); + organization.SmServiceAccounts = plan.SecretsManager.BaseServiceAccount + signup.AdditionalServiceAccounts.GetValueOrDefault(); } - if (passwordManagerPlan.Type == PlanType.Free && !provider) + if (plan.Type == PlanType.Free && !provider) { var adminCount = await _organizationUserRepository.GetCountByFreeOrganizationAdminUserAsync(signup.Owner.Id); @@ -478,14 +477,10 @@ public class OrganizationService : IOrganizationService throw new BadRequestException("You can only be an admin of one free organization."); } } - else if (passwordManagerPlan.Type != PlanType.Free) + else if (plan.Type != PlanType.Free) { - var purchaseOrganizationPlan = signup.UseSecretsManager - ? StaticStore.Plans.Where(p => p.Type == signup.Plan).ToList() - : StaticStore.PasswordManagerPlans.Where(p => p.Type == signup.Plan).Take(1).ToList(); - await _paymentService.PurchaseOrganizationAsync(organization, signup.PaymentMethodType.Value, - signup.PaymentToken, purchaseOrganizationPlan, signup.AdditionalStorageGb, signup.AdditionalSeats, + signup.PaymentToken, plan, signup.AdditionalStorageGb, signup.AdditionalSeats, signup.PremiumAccessAddon, signup.TaxInfo, provider, signup.AdditionalSmSeats.GetValueOrDefault(), signup.AdditionalServiceAccounts.GetValueOrDefault()); } @@ -495,8 +490,8 @@ public class OrganizationService : IOrganizationService await _referenceEventService.RaiseEventAsync( new ReferenceEvent(ReferenceEventType.Signup, organization, _currentContext) { - PlanName = passwordManagerPlan.Name, - PlanType = passwordManagerPlan.Type, + PlanName = plan.Name, + PlanType = plan.Type, Seats = returnValue.Item1.Seats, Storage = returnValue.Item1.MaxStorageGb, // TODO: add reference events for SmSeats and Service Accounts - see AC-1481 @@ -525,7 +520,7 @@ public class OrganizationService : IOrganizationService } if (license.PlanType != PlanType.Custom && - StaticStore.PasswordManagerPlans.FirstOrDefault(p => p.Type == license.PlanType && !p.Disabled) == null) + StaticStore.Plans.FirstOrDefault(p => p.Type == license.PlanType && !p.Disabled) == null) { throw new BadRequestException("Plan not found."); } @@ -1955,11 +1950,6 @@ public class OrganizationService : IOrganizationService throw new BadRequestException($"{productType} Plan not found."); } - if (plan.BaseSeats + additionalSeats <= 0) - { - throw new BadRequestException($"You do not have any {productType} seats!"); - } - if (additionalSeats < 0) { throw new BadRequestException($"You can't subtract {productType} seats!"); @@ -1970,7 +1960,7 @@ public class OrganizationService : IOrganizationService { ValidatePlan(plan, upgrade.AdditionalSeats, "Password Manager"); - if (plan.BaseSeats + upgrade.AdditionalSeats <= 0) + if (plan.PasswordManager.BaseSeats + upgrade.AdditionalSeats <= 0) { throw new BadRequestException($"You do not have any Password Manager seats!"); } @@ -1980,7 +1970,7 @@ public class OrganizationService : IOrganizationService throw new BadRequestException($"You can't subtract Password Manager seats!"); } - if (!plan.HasAdditionalStorageOption && upgrade.AdditionalStorageGb > 0) + if (!plan.PasswordManager.HasAdditionalStorageOption && upgrade.AdditionalStorageGb > 0) { throw new BadRequestException("Plan does not allow additional storage."); } @@ -1990,29 +1980,39 @@ public class OrganizationService : IOrganizationService throw new BadRequestException("You can't subtract storage!"); } - if (!plan.HasPremiumAccessOption && upgrade.PremiumAccessAddon) + if (!plan.PasswordManager.HasPremiumAccessOption && upgrade.PremiumAccessAddon) { throw new BadRequestException("This plan does not allow you to buy the premium access addon."); } - if (!plan.HasAdditionalSeatsOption && upgrade.AdditionalSeats > 0) + if (!plan.PasswordManager.HasAdditionalSeatsOption && upgrade.AdditionalSeats > 0) { throw new BadRequestException("Plan does not allow additional users."); } - if (plan.HasAdditionalSeatsOption && plan.MaxAdditionalSeats.HasValue && - upgrade.AdditionalSeats > plan.MaxAdditionalSeats.Value) + if (plan.PasswordManager.HasAdditionalSeatsOption && plan.PasswordManager.MaxAdditionalSeats.HasValue && + upgrade.AdditionalSeats > plan.PasswordManager.MaxAdditionalSeats.Value) { throw new BadRequestException($"Selected plan allows a maximum of " + - $"{plan.MaxAdditionalSeats.GetValueOrDefault(0)} additional users."); + $"{plan.PasswordManager.MaxAdditionalSeats.GetValueOrDefault(0)} additional users."); } } public void ValidateSecretsManagerPlan(Models.StaticStore.Plan plan, OrganizationUpgrade upgrade) { + if (plan.SupportsSecretsManager == false) + { + throw new BadRequestException("Invalid Secrets Manager plan selected."); + } + ValidatePlan(plan, upgrade.AdditionalSmSeats.GetValueOrDefault(), "Secrets Manager"); - if (!plan.HasAdditionalServiceAccountOption && upgrade.AdditionalServiceAccounts > 0) + if (plan.SecretsManager.BaseSeats + upgrade.AdditionalSmSeats <= 0) + { + throw new BadRequestException($"You do not have any Secrets Manager seats!"); + } + + if (!plan.SecretsManager.HasAdditionalServiceAccountOption && upgrade.AdditionalServiceAccounts > 0) { throw new BadRequestException("Plan does not allow additional Service Accounts."); } @@ -2027,14 +2027,14 @@ public class OrganizationService : IOrganizationService throw new BadRequestException("You can't subtract Service Accounts!"); } - switch (plan.HasAdditionalSeatsOption) + switch (plan.SecretsManager.HasAdditionalSeatsOption) { case false when upgrade.AdditionalSmSeats > 0: throw new BadRequestException("Plan does not allow additional users."); - case true when plan.MaxAdditionalSeats.HasValue && - upgrade.AdditionalSmSeats > plan.MaxAdditionalSeats.Value: + case true when plan.SecretsManager.MaxAdditionalSeats.HasValue && + upgrade.AdditionalSmSeats > plan.SecretsManager.MaxAdditionalSeats.Value: throw new BadRequestException($"Selected plan allows a maximum of " + - $"{plan.MaxAdditionalSeats.GetValueOrDefault(0)} additional users."); + $"{plan.SecretsManager.MaxAdditionalSeats.GetValueOrDefault(0)} additional users."); } } @@ -2457,7 +2457,7 @@ public class OrganizationService : IOrganizationService public async Task CreatePendingOrganization(Organization organization, string ownerEmail, ClaimsPrincipal user, IUserService userService, bool salesAssistedTrialStarted) { - var plan = StaticStore.PasswordManagerPlans.FirstOrDefault(p => p.Type == organization.PlanType); + var plan = StaticStore.Plans.FirstOrDefault(p => p.Type == organization.PlanType); if (plan is not { LegacyYear: null }) { throw new BadRequestException("Invalid plan selected."); diff --git a/src/Core/Services/Implementations/StripePaymentService.cs b/src/Core/Services/Implementations/StripePaymentService.cs index 33610bee7..e1ac1ef77 100644 --- a/src/Core/Services/Implementations/StripePaymentService.cs +++ b/src/Core/Services/Implementations/StripePaymentService.cs @@ -49,7 +49,7 @@ public class StripePaymentService : IPaymentService } public async Task PurchaseOrganizationAsync(Organization org, PaymentMethodType paymentMethodType, - string paymentToken, List plans, short additionalStorageGb, + string paymentToken, StaticStore.Plan plan, short additionalStorageGb, int additionalSeats, bool premiumAccessAddon, TaxInfo taxInfo, bool provider = false, int additionalSmSeats = 0, int additionalServiceAccount = 0) { @@ -119,7 +119,7 @@ public class StripePaymentService : IPaymentService } } - var subCreateOptions = new OrganizationPurchaseSubscriptionOptions(org, plans, taxInfo, additionalSeats, additionalStorageGb, premiumAccessAddon + var subCreateOptions = new OrganizationPurchaseSubscriptionOptions(org, plan, taxInfo, additionalSeats, additionalStorageGb, premiumAccessAddon , additionalSmSeats, additionalServiceAccount); Stripe.Customer customer = null; @@ -211,7 +211,7 @@ public class StripePaymentService : IPaymentService private async Task ChangeOrganizationSponsorship(Organization org, OrganizationSponsorship sponsorship, bool applySponsorship) { - var existingPlan = Utilities.StaticStore.GetPasswordManagerPlan(org.PlanType); + var existingPlan = Utilities.StaticStore.GetPlan(org.PlanType); var sponsoredPlan = sponsorship != null ? Utilities.StaticStore.GetSponsoredPlan(sponsorship.PlanSponsorshipType.Value) : null; @@ -231,7 +231,7 @@ public class StripePaymentService : IPaymentService public Task RemoveOrganizationSponsorshipAsync(Organization org, OrganizationSponsorship sponsorship) => ChangeOrganizationSponsorship(org, sponsorship, false); - public async Task UpgradeFreeOrganizationAsync(Organization org, List plans, + public async Task UpgradeFreeOrganizationAsync(Organization org, StaticStore.Plan plan, OrganizationUpgrade upgrade) { if (!string.IsNullOrWhiteSpace(org.GatewaySubscriptionId)) @@ -266,7 +266,7 @@ public class StripePaymentService : IPaymentService } } - var subCreateOptions = new OrganizationUpgradeSubscriptionOptions(customer.Id, org, plans, upgrade); + var subCreateOptions = new OrganizationUpgradeSubscriptionOptions(customer.Id, org, plan, upgrade); var (stripePaymentMethod, paymentMethodType) = IdentifyPaymentMethod(customer, subCreateOptions); var subscription = await ChargeForNewSubscriptionAsync(org, customer, false, diff --git a/src/Core/Utilities/PasswordManagerPlanStore.cs b/src/Core/Utilities/PasswordManagerPlanStore.cs deleted file mode 100644 index 1f9400eba..000000000 --- a/src/Core/Utilities/PasswordManagerPlanStore.cs +++ /dev/null @@ -1,398 +0,0 @@ -using Bit.Core.Enums; -using Bit.Core.Models.StaticStore; - -namespace Bit.Core.Utilities; - -public static class PasswordManagerPlanStore -{ - public static IEnumerable CreatePlan() - { - return new List - { - 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, - }, - }; - } -} diff --git a/src/Core/Utilities/SecretsManagerPlanStore.cs b/src/Core/Utilities/SecretsManagerPlanStore.cs deleted file mode 100644 index c37c42446..000000000 --- a/src/Core/Utilities/SecretsManagerPlanStore.cs +++ /dev/null @@ -1,172 +0,0 @@ -using Bit.Core.Enums; -using Bit.Core.Models.StaticStore; - -namespace Bit.Core.Utilities; - -public static class SecretsManagerPlanStore -{ - public static IEnumerable CreatePlan() - { - return new List - { - 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, - } - }; - } -} diff --git a/src/Core/Utilities/StaticStore.cs b/src/Core/Utilities/StaticStore.cs index ad11b2cfd..6726d9cd3 100644 --- a/src/Core/Utilities/StaticStore.cs +++ b/src/Core/Utilities/StaticStore.cs @@ -1,6 +1,8 @@ -using Bit.Core.Enums; +using System.Collections.Immutable; +using Bit.Core.Enums; using Bit.Core.Models.Data.Organizations.OrganizationUsers; using Bit.Core.Models.StaticStore; +using Bit.Core.Models.StaticStore.Plans; namespace Bit.Core.Utilities; @@ -104,21 +106,26 @@ public class StaticStore GlobalDomains.Add(GlobalEquivalentDomainsType.Pinterest, new List { "pinterest.com", "pinterest.com.au", "pinterest.cl", "pinterest.de", "pinterest.dk", "pinterest.es", "pinterest.fr", "pinterest.co.uk", "pinterest.jp", "pinterest.co.kr", "pinterest.nz", "pinterest.pt", "pinterest.se" }); #endregion - #region Plans + Plans = new List + { + new EnterprisePlan(true), + new EnterprisePlan(false), + new TeamsPlan(true), + new TeamsPlan(false), + new FamiliesPlan(), + new FreePlan(), + new CustomPlan(), - PasswordManagerPlans = PasswordManagerPlanStore.CreatePlan(); - SecretManagerPlans = SecretsManagerPlanStore.CreatePlan(); - - Plans = PasswordManagerPlans.Concat(SecretManagerPlans); - - - #endregion + new Enterprise2019Plan(true), + new Enterprise2019Plan(false), + new Teams2019Plan(true), + new Teams2019Plan(false), + new Families2019Plan(), + }.ToImmutableList(); } public static IDictionary> GlobalDomains { get; set; } - public static IEnumerable Plans { get; set; } - public static IEnumerable SecretManagerPlans { get; set; } - public static IEnumerable PasswordManagerPlans { get; set; } + public static IEnumerable Plans { get; } public static IEnumerable SponsoredPlans { get; set; } = new[] { new SponsoredPlan @@ -128,21 +135,20 @@ public class StaticStore SponsoringProductType = ProductType.Enterprise, StripePlanId = "2021-family-for-enterprise-annually", UsersCanSponsor = (OrganizationUserOrganizationDetails org) => - GetPasswordManagerPlan(org.PlanType).Product == ProductType.Enterprise, + GetPlan(org.PlanType).Product == ProductType.Enterprise, } }; - public static Plan GetPasswordManagerPlan(PlanType planType) => - PasswordManagerPlans.SingleOrDefault(p => p.Type == planType); - public static Plan GetSecretsManagerPlan(PlanType planType) => - SecretManagerPlans.SingleOrDefault(p => p.Type == planType); + public static Models.StaticStore.Plan GetPlan(PlanType planType) => + Plans.SingleOrDefault(p => p.Type == planType); + public static SponsoredPlan GetSponsoredPlan(PlanSponsorshipType planSponsorshipType) => SponsoredPlans.FirstOrDefault(p => p.PlanSponsorshipType == planSponsorshipType); /// /// Determines if the stripe plan id is an addon item by checking if the provided stripe plan id - /// matches either the or + /// matches either the or /// in any . /// /// @@ -151,41 +157,8 @@ public class StaticStore /// public static bool IsAddonSubscriptionItem(string stripePlanId) { - if (PasswordManagerPlans.Select(p => p.StripeStoragePlanId).Contains(stripePlanId)) - { - return true; - } - - if (SecretManagerPlans.Select(p => p.StripeServiceAccountPlanId).Contains(stripePlanId)) - { - return true; - } - - return false; - } - - /// - /// Get a by comparing the provided stripeId to the various - /// Stripe plan ids within a . - /// The following properties are checked: - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// The plan if a matching stripeId was found, null otherwise - public static Plan GetPlanByStripeId(string stripeId) - { - return Plans.FirstOrDefault(p => - p.StripePlanId == stripeId || - p.StripeSeatPlanId == stripeId || - p.StripeStoragePlanId == stripeId || - p.StripeServiceAccountPlanId == stripeId || - p.StripePremiumAccessPlanId == stripeId - ); + return Plans.Any(p => + p.PasswordManager.StripeStoragePlanId == stripePlanId || + (p.SecretsManager?.StripeServiceAccountPlanId == stripePlanId)); } } diff --git a/test/Api.Test/Controllers/OrganizationSponsorshipsControllerTests.cs b/test/Api.Test/Controllers/OrganizationSponsorshipsControllerTests.cs index 99bc1df63..e58add5ef 100644 --- a/test/Api.Test/Controllers/OrganizationSponsorshipsControllerTests.cs +++ b/test/Api.Test/Controllers/OrganizationSponsorshipsControllerTests.cs @@ -20,11 +20,11 @@ namespace Bit.Api.Test.Controllers; public class OrganizationSponsorshipsControllerTests { public static IEnumerable EnterprisePlanTypes => - Enum.GetValues().Where(p => StaticStore.GetPasswordManagerPlan(p).Product == ProductType.Enterprise).Select(p => new object[] { p }); + Enum.GetValues().Where(p => StaticStore.GetPlan(p).Product == ProductType.Enterprise).Select(p => new object[] { p }); public static IEnumerable NonEnterprisePlanTypes => - Enum.GetValues().Where(p => StaticStore.GetPasswordManagerPlan(p).Product != ProductType.Enterprise).Select(p => new object[] { p }); + Enum.GetValues().Where(p => StaticStore.GetPlan(p).Product != ProductType.Enterprise).Select(p => new object[] { p }); public static IEnumerable NonFamiliesPlanTypes => - Enum.GetValues().Where(p => StaticStore.GetPasswordManagerPlan(p).Product != ProductType.Families).Select(p => new object[] { p }); + Enum.GetValues().Where(p => StaticStore.GetPlan(p).Product != ProductType.Families).Select(p => new object[] { p }); public static IEnumerable NonConfirmedOrganizationUsersStatuses => Enum.GetValues() diff --git a/test/Api.Test/Vault/Controllers/SyncControllerTests.cs b/test/Api.Test/Vault/Controllers/SyncControllerTests.cs index b6e12eabc..0034120b1 100644 --- a/test/Api.Test/Vault/Controllers/SyncControllerTests.cs +++ b/test/Api.Test/Vault/Controllers/SyncControllerTests.cs @@ -73,7 +73,7 @@ public class SyncControllerTests user.EquivalentDomains = JsonSerializer.Serialize(userEquivalentDomains); user.ExcludedGlobalEquivalentDomains = JsonSerializer.Serialize(userExcludedGlobalEquivalentDomains); - // At least 1 org needs to be enabled to fully test + // At least 1 org needs to be enabled to fully test if (!organizationUserDetails.Any(o => o.Enabled)) { // We need at least 1 enabled org @@ -165,7 +165,7 @@ public class SyncControllerTests user.EquivalentDomains = JsonSerializer.Serialize(userEquivalentDomains); user.ExcludedGlobalEquivalentDomains = JsonSerializer.Serialize(userExcludedGlobalEquivalentDomains); - // All orgs disabled + // All orgs disabled if (organizationUserDetails.Count > 0) { foreach (var orgUserDetails in organizationUserDetails) @@ -218,7 +218,7 @@ public class SyncControllerTests Assert.IsType(result); - // Collections should be empty when all standard orgs are disabled. + // Collections should be empty when all standard orgs are disabled. Assert.Empty(result.Collections); } @@ -297,7 +297,7 @@ public class SyncControllerTests Assert.IsType(result); // Look up ProviderOrg output and compare to ProviderOrg method inputs to ensure - // product type is set correctly. + // product type is set correctly. foreach (var profProviderOrg in result.Profile.ProviderOrganizations) { var matchedProviderUserOrgDetails = @@ -305,7 +305,7 @@ public class SyncControllerTests if (matchedProviderUserOrgDetails != null) { - var providerOrgProductType = StaticStore.GetPasswordManagerPlan(matchedProviderUserOrgDetails.PlanType).Product; + var providerOrgProductType = StaticStore.GetPlan(matchedProviderUserOrgDetails.PlanType).Product; Assert.Equal(providerOrgProductType, profProviderOrg.PlanProductType); } } @@ -337,7 +337,7 @@ public class SyncControllerTests await sendRepository.ReceivedWithAnyArgs(1) .GetManyByUserIdAsync(default); - // These two are only called when at least 1 enabled org. + // These two are only called when at least 1 enabled org. if (hasEnabledOrgs) { await collectionRepository.ReceivedWithAnyArgs(1) @@ -347,7 +347,7 @@ public class SyncControllerTests } else { - // all disabled orgs + // all disabled orgs await collectionRepository.ReceivedWithAnyArgs(0) .GetManyByUserIdAsync(default); await collectionCipherRepository.ReceivedWithAnyArgs(0) diff --git a/test/Core.Test/AutoFixture/OrganizationFixtures.cs b/test/Core.Test/AutoFixture/OrganizationFixtures.cs index 4ef0e43fa..e4e75ed0e 100644 --- a/test/Core.Test/AutoFixture/OrganizationFixtures.cs +++ b/test/Core.Test/AutoFixture/OrganizationFixtures.cs @@ -66,7 +66,7 @@ internal class PaidOrganization : ICustomization public PlanType CheckedPlanType { get; set; } public void Customize(IFixture fixture) { - var validUpgradePlans = StaticStore.PasswordManagerPlans.Where(p => p.Type != PlanType.Free && p.LegacyYear == null).OrderBy(p => p.UpgradeSortOrder).Select(p => p.Type).ToList(); + var validUpgradePlans = StaticStore.Plans.Where(p => p.Type != PlanType.Free && p.LegacyYear == null).OrderBy(p => p.UpgradeSortOrder).Select(p => p.Type).ToList(); var lowestActivePaidPlan = validUpgradePlans.First(); CheckedPlanType = CheckedPlanType.Equals(PlanType.Free) ? lowestActivePaidPlan : CheckedPlanType; validUpgradePlans.Remove(lowestActivePaidPlan); @@ -94,11 +94,11 @@ internal class FreeOrganizationUpgrade : ICustomization .With(o => o.PlanType, PlanType.Free)); var plansToIgnore = new List { PlanType.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(composer => composer .With(ou => ou.Plan, selectedPlan.Type) - .With(ou => ou.PremiumAccessAddon, selectedPlan.HasPremiumAccessOption)); + .With(ou => ou.PremiumAccessAddon, selectedPlan.PasswordManager.HasPremiumAccessOption)); fixture.Customize(composer => composer .Without(o => o.GatewaySubscriptionId)); } @@ -140,7 +140,7 @@ public class SecretsManagerOrganizationCustomization : ICustomization .With(o => o.UseSecretsManager, true) .With(o => o.SecretsManagerBeta, false) .With(o => o.PlanType, planType) - .With(o => o.Plan, StaticStore.GetPasswordManagerPlan(planType).Name) + .With(o => o.Plan, StaticStore.GetPlan(planType).Name) .With(o => o.MaxAutoscaleSmSeats, (int?)null) .With(o => o.MaxAutoscaleSmServiceAccounts, (int?)null) ); diff --git a/test/Core.Test/OrganizationFeatures/OrganizationSponsorships/FamiliesForEnterprise/FamiliesForEnterpriseTestsBase.cs b/test/Core.Test/OrganizationFeatures/OrganizationSponsorships/FamiliesForEnterprise/FamiliesForEnterpriseTestsBase.cs index a3fce8b3d..e49b095d7 100644 --- a/test/Core.Test/OrganizationFeatures/OrganizationSponsorships/FamiliesForEnterprise/FamiliesForEnterpriseTestsBase.cs +++ b/test/Core.Test/OrganizationFeatures/OrganizationSponsorships/FamiliesForEnterprise/FamiliesForEnterpriseTestsBase.cs @@ -6,16 +6,16 @@ namespace Bit.Core.Test.OrganizationFeatures.OrganizationSponsorships.FamiliesFo public abstract class FamiliesForEnterpriseTestsBase { public static IEnumerable EnterprisePlanTypes => - Enum.GetValues().Where(p => StaticStore.GetPasswordManagerPlan(p).Product == ProductType.Enterprise).Select(p => new object[] { p }); + Enum.GetValues().Where(p => StaticStore.GetPlan(p).Product == ProductType.Enterprise).Select(p => new object[] { p }); public static IEnumerable NonEnterprisePlanTypes => - Enum.GetValues().Where(p => StaticStore.GetPasswordManagerPlan(p).Product != ProductType.Enterprise).Select(p => new object[] { p }); + Enum.GetValues().Where(p => StaticStore.GetPlan(p).Product != ProductType.Enterprise).Select(p => new object[] { p }); public static IEnumerable FamiliesPlanTypes => - Enum.GetValues().Where(p => StaticStore.GetPasswordManagerPlan(p).Product == ProductType.Families).Select(p => new object[] { p }); + Enum.GetValues().Where(p => StaticStore.GetPlan(p).Product == ProductType.Families).Select(p => new object[] { p }); public static IEnumerable NonFamiliesPlanTypes => - Enum.GetValues().Where(p => StaticStore.GetPasswordManagerPlan(p).Product != ProductType.Families).Select(p => new object[] { p }); + Enum.GetValues().Where(p => StaticStore.GetPlan(p).Product != ProductType.Families).Select(p => new object[] { p }); public static IEnumerable NonConfirmedOrganizationUsersStatuses => Enum.GetValues() diff --git a/test/Core.Test/OrganizationFeatures/OrganizationSubscriptionUpdate/AddSecretsManagerSubscriptionCommandTests.cs b/test/Core.Test/OrganizationFeatures/OrganizationSubscriptionUpdate/AddSecretsManagerSubscriptionCommandTests.cs index ec83fa102..810561c4b 100644 --- a/test/Core.Test/OrganizationFeatures/OrganizationSubscriptionUpdate/AddSecretsManagerSubscriptionCommandTests.cs +++ b/test/Core.Test/OrganizationFeatures/OrganizationSubscriptionUpdate/AddSecretsManagerSubscriptionCommandTests.cs @@ -32,7 +32,7 @@ public class AddSecretsManagerSubscriptionCommandTests { organization.PlanType = planType; - var plan = StaticStore.SecretManagerPlans.FirstOrDefault(p => p.Type == organization.PlanType); + var plan = StaticStore.Plans.FirstOrDefault(p => p.Type == organization.PlanType); await sutProvider.Sut.SignUpAsync(organization, additionalSmSeats, additionalServiceAccounts); @@ -49,8 +49,8 @@ public class AddSecretsManagerSubscriptionCommandTests // TODO: call ReferenceEventService - see AC-1481 sutProvider.GetDependency().Received(1).ReplaceAndUpdateCacheAsync(Arg.Is(c => - c.SmSeats == plan.BaseSeats + additionalSmSeats && - c.SmServiceAccounts == plan.BaseServiceAccount.GetValueOrDefault() + additionalServiceAccounts && + c.SmSeats == plan.SecretsManager.BaseSeats + additionalSmSeats && + c.SmServiceAccounts == plan.SecretsManager.BaseServiceAccount + additionalServiceAccounts && c.UseSecretsManager == true)); } diff --git a/test/Core.Test/OrganizationFeatures/OrganizationSubscriptionUpdate/UpdateSecretsManagerSubscriptionCommandTests.cs b/test/Core.Test/OrganizationFeatures/OrganizationSubscriptionUpdate/UpdateSecretsManagerSubscriptionCommandTests.cs index 2834db335..e64cbe304 100644 --- a/test/Core.Test/OrganizationFeatures/OrganizationSubscriptionUpdate/UpdateSecretsManagerSubscriptionCommandTests.cs +++ b/test/Core.Test/OrganizationFeatures/OrganizationSubscriptionUpdate/UpdateSecretsManagerSubscriptionCommandTests.cs @@ -52,7 +52,7 @@ public class UpdateSecretsManagerSubscriptionCommandTests await sutProvider.Sut.UpdateSubscriptionAsync(update); - var plan = StaticStore.SecretManagerPlans.FirstOrDefault(x => x.Type == organization.PlanType); + var plan = StaticStore.GetPlan(organization.PlanType); await sutProvider.GetDependency().Received(1) .AdjustSeatsAsync(organization, plan, update.SmSeatsExcludingBase); await sutProvider.GetDependency().Received(1) @@ -96,7 +96,7 @@ public class UpdateSecretsManagerSubscriptionCommandTests await sutProvider.Sut.UpdateSubscriptionAsync(update); - var plan = StaticStore.SecretManagerPlans.FirstOrDefault(x => x.Type == organization.PlanType); + var plan = StaticStore.GetPlan(organization.PlanType); await sutProvider.GetDependency().Received(1) .AdjustSeatsAsync(organization, plan, update.SmSeatsExcludingBase); await sutProvider.GetDependency().Received(1) @@ -213,11 +213,11 @@ public class UpdateSecretsManagerSubscriptionCommandTests public async Task AdjustServiceAccountsAsync_WithEnterpriseOrTeamsPlans_Success(PlanType planType, Guid organizationId, SutProvider sutProvider) { - var plan = StaticStore.SecretManagerPlans.FirstOrDefault(p => p.Type == planType); + var plan = StaticStore.GetPlan(planType); - var organizationSeats = plan.BaseSeats + 10; + var organizationSeats = plan.SecretsManager.BaseSeats + 10; var organizationMaxAutoscaleSeats = 20; - var organizationServiceAccounts = plan.BaseServiceAccount.GetValueOrDefault() + 10; + var organizationServiceAccounts = plan.SecretsManager.BaseServiceAccount + 10; var organizationMaxAutoscaleServiceAccounts = 300; var organization = new Organization @@ -235,7 +235,7 @@ public class UpdateSecretsManagerSubscriptionCommandTests var smServiceAccountsAdjustment = 10; var expectedSmServiceAccounts = organizationServiceAccounts + smServiceAccountsAdjustment; - var expectedSmServiceAccountsExcludingBase = expectedSmServiceAccounts - plan.BaseServiceAccount.GetValueOrDefault(); + var expectedSmServiceAccountsExcludingBase = expectedSmServiceAccounts - plan.SecretsManager.BaseServiceAccount; var update = new SecretsManagerSubscriptionUpdate(organization, false).AdjustServiceAccounts(10); diff --git a/test/Core.Test/OrganizationFeatures/OrganizationSubscriptionUpdate/UpgradeOrganizationPlanCommandTests.cs b/test/Core.Test/OrganizationFeatures/OrganizationSubscriptionUpdate/UpgradeOrganizationPlanCommandTests.cs index f18a8b5de..f79001639 100644 --- a/test/Core.Test/OrganizationFeatures/OrganizationSubscriptionUpdate/UpgradeOrganizationPlanCommandTests.cs +++ b/test/Core.Test/OrganizationFeatures/OrganizationSubscriptionUpdate/UpgradeOrganizationPlanCommandTests.cs @@ -94,6 +94,7 @@ public class UpgradeOrganizationPlanCommandTests sutProvider.GetDependency().GetByIdAsync(organization.Id).Returns(organization); upgrade.AdditionalSmSeats = 10; upgrade.AdditionalSeats = 10; + upgrade.Plan = PlanType.TeamsAnnually; await sutProvider.Sut.UpgradePlanAsync(organization.Id, upgrade); await sutProvider.GetDependency().Received(1).ReplaceAndUpdateCacheAsync(organization); } @@ -108,8 +109,7 @@ public class UpgradeOrganizationPlanCommandTests { upgrade.Plan = planType; - var passwordManagerPlan = StaticStore.GetPasswordManagerPlan(upgrade.Plan); - var secretsManagerPlan = StaticStore.GetSecretsManagerPlan(upgrade.Plan); + var plan = StaticStore.GetPlan(upgrade.Plan); sutProvider.GetDependency().GetByIdAsync(organization.Id).Returns(organization); @@ -121,9 +121,9 @@ public class UpgradeOrganizationPlanCommandTests await sutProvider.GetDependency().Received(1).ReplaceAndUpdateCacheAsync( Arg.Is(o => - o.Seats == passwordManagerPlan.BaseSeats + upgrade.AdditionalSeats - && o.SmSeats == secretsManagerPlan.BaseSeats + upgrade.AdditionalSmSeats - && o.SmServiceAccounts == secretsManagerPlan.BaseServiceAccount + upgrade.AdditionalServiceAccounts)); + o.Seats == plan.PasswordManager.BaseSeats + upgrade.AdditionalSeats + && o.SmSeats == plan.SecretsManager.BaseSeats + upgrade.AdditionalSmSeats + && o.SmServiceAccounts == plan.SecretsManager.BaseServiceAccount + upgrade.AdditionalServiceAccounts)); Assert.True(result.Item1); Assert.NotNull(result.Item2); diff --git a/test/Core.Test/Services/OrganizationServiceTests.cs b/test/Core.Test/Services/OrganizationServiceTests.cs index a4974d39a..2354d16bf 100644 --- a/test/Core.Test/Services/OrganizationServiceTests.cs +++ b/test/Core.Test/Services/OrganizationServiceTests.cs @@ -155,20 +155,20 @@ public class OrganizationServiceTests { signup.Plan = planType; - var passwordManagerPlan = StaticStore.GetPasswordManagerPlan(signup.Plan); + var plan = StaticStore.GetPlan(signup.Plan); signup.AdditionalSeats = 0; signup.PaymentMethodType = PaymentMethodType.Card; signup.PremiumAccessAddon = false; signup.UseSecretsManager = false; - var purchaseOrganizationPlan = StaticStore.Plans.Where(x => x.Type == signup.Plan).ToList(); + var purchaseOrganizationPlan = StaticStore.GetPlan(signup.Plan); var result = await sutProvider.Sut.SignUpAsync(signup); await sutProvider.GetDependency().Received(1).CreateAsync( Arg.Is(o => - o.Seats == passwordManagerPlan.BaseSeats + signup.AdditionalSeats + o.Seats == plan.PasswordManager.BaseSeats + signup.AdditionalSeats && o.SmSeats == null && o.SmServiceAccounts == null)); await sutProvider.GetDependency().Received(1).CreateAsync( @@ -177,8 +177,8 @@ public class OrganizationServiceTests await sutProvider.GetDependency().Received(1) .RaiseEventAsync(Arg.Is(referenceEvent => referenceEvent.Type == ReferenceEventType.Signup && - referenceEvent.PlanName == passwordManagerPlan.Name && - referenceEvent.PlanType == passwordManagerPlan.Type && + referenceEvent.PlanName == plan.Name && + referenceEvent.PlanType == plan.Type && referenceEvent.Seats == result.Item1.Seats && referenceEvent.Storage == result.Item1.MaxStorageGb)); // TODO: add reference events for SmSeats and Service Accounts - see AC-1481 @@ -192,7 +192,7 @@ public class OrganizationServiceTests Arg.Any(), signup.PaymentMethodType.Value, signup.PaymentToken, - Arg.Is>(plan => plan.Single() == passwordManagerPlan), + plan, signup.AdditionalStorageGb, signup.AdditionalSeats, signup.PremiumAccessAddon, @@ -212,8 +212,7 @@ public class OrganizationServiceTests { signup.Plan = planType; - var passwordManagerPlan = StaticStore.GetPasswordManagerPlan(signup.Plan); - var secretsManagerPlan = StaticStore.GetSecretsManagerPlan(signup.Plan); + var plan = StaticStore.GetPlan(signup.Plan); signup.UseSecretsManager = true; signup.AdditionalSeats = 15; @@ -222,23 +221,21 @@ public class OrganizationServiceTests signup.PaymentMethodType = PaymentMethodType.Card; signup.PremiumAccessAddon = false; - var purchaseOrganizationPlan = StaticStore.Plans.Where(x => x.Type == signup.Plan).ToList(); - var result = await sutProvider.Sut.SignUpAsync(signup); await sutProvider.GetDependency().Received(1).CreateAsync( Arg.Is(o => - o.Seats == passwordManagerPlan.BaseSeats + signup.AdditionalSeats - && o.SmSeats == secretsManagerPlan.BaseSeats + signup.AdditionalSmSeats - && o.SmServiceAccounts == secretsManagerPlan.BaseServiceAccount + signup.AdditionalServiceAccounts)); + o.Seats == plan.PasswordManager.BaseSeats + signup.AdditionalSeats + && o.SmSeats == plan.SecretsManager.BaseSeats + signup.AdditionalSmSeats + && o.SmServiceAccounts == plan.SecretsManager.BaseServiceAccount + signup.AdditionalServiceAccounts)); await sutProvider.GetDependency().Received(1).CreateAsync( Arg.Is(o => o.AccessSecretsManager == signup.UseSecretsManager)); await sutProvider.GetDependency().Received(1) .RaiseEventAsync(Arg.Is(referenceEvent => referenceEvent.Type == ReferenceEventType.Signup && - referenceEvent.PlanName == purchaseOrganizationPlan[0].Name && - referenceEvent.PlanType == purchaseOrganizationPlan[0].Type && + referenceEvent.PlanName == plan.Name && + referenceEvent.PlanType == plan.Type && referenceEvent.Seats == result.Item1.Seats && referenceEvent.Storage == result.Item1.MaxStorageGb)); // TODO: add reference events for SmSeats and Service Accounts - see AC-1481 @@ -252,7 +249,7 @@ public class OrganizationServiceTests Arg.Any(), signup.PaymentMethodType.Value, signup.PaymentToken, - Arg.Is>(plan => plan.All(p => purchaseOrganizationPlan.Contains(p))), + Arg.Is(plan), signup.AdditionalStorageGb, signup.AdditionalSeats, signup.PremiumAccessAddon, @@ -1706,7 +1703,7 @@ public class OrganizationServiceTests public void ValidateSecretsManagerPlan_ThrowsException_WhenInvalidPlanSelected( PlanType planType, SutProvider sutProvider) { - var plan = StaticStore.Plans.FirstOrDefault(x => x.Type == planType); + var plan = StaticStore.GetPlan(planType); var signup = new OrganizationUpgrade { @@ -1727,7 +1724,7 @@ public class OrganizationServiceTests [BitAutoData(PlanType.EnterpriseMonthly)] public void ValidateSecretsManagerPlan_ThrowsException_WhenNoSecretsManagerSeats(PlanType planType, SutProvider sutProvider) { - var plan = StaticStore.SecretManagerPlans.FirstOrDefault(x => x.Type == planType); + var plan = StaticStore.GetPlan(planType); var signup = new OrganizationUpgrade { UseSecretsManager = true, @@ -1744,7 +1741,7 @@ public class OrganizationServiceTests [BitAutoData(PlanType.Free)] public void ValidateSecretsManagerPlan_ThrowsException_WhenSubtractingSeats(PlanType planType, SutProvider sutProvider) { - var plan = StaticStore.SecretManagerPlans.FirstOrDefault(x => x.Type == planType); + var plan = StaticStore.GetPlan(planType); var signup = new OrganizationUpgrade { UseSecretsManager = true, @@ -1761,7 +1758,7 @@ public class OrganizationServiceTests PlanType planType, SutProvider sutProvider) { - var plan = StaticStore.SecretManagerPlans.FirstOrDefault(x => x.Type == planType); + var plan = StaticStore.GetPlan(planType); var signup = new OrganizationUpgrade { UseSecretsManager = true, @@ -1780,7 +1777,7 @@ public class OrganizationServiceTests [BitAutoData(PlanType.EnterpriseMonthly)] public void ValidateSecretsManagerPlan_ThrowsException_WhenMoreSeatsThanPasswordManagerSeats(PlanType planType, SutProvider sutProvider) { - var plan = StaticStore.SecretManagerPlans.FirstOrDefault(x => x.Type == planType); + var plan = StaticStore.GetPlan(planType); var signup = new OrganizationUpgrade { UseSecretsManager = true, @@ -1801,7 +1798,7 @@ public class OrganizationServiceTests PlanType planType, SutProvider sutProvider) { - var plan = StaticStore.SecretManagerPlans.FirstOrDefault(x => x.Type == planType); + var plan = StaticStore.GetPlan(planType); var signup = new OrganizationUpgrade { UseSecretsManager = true, @@ -1819,7 +1816,7 @@ public class OrganizationServiceTests PlanType planType, SutProvider sutProvider) { - var plan = StaticStore.SecretManagerPlans.FirstOrDefault(x => x.Type == planType); + var plan = StaticStore.GetPlan(planType); var signup = new OrganizationUpgrade { UseSecretsManager = true, @@ -1840,7 +1837,7 @@ public class OrganizationServiceTests PlanType planType, SutProvider sutProvider) { - var plan = StaticStore.SecretManagerPlans.FirstOrDefault(x => x.Type == planType); + var plan = StaticStore.GetPlan(planType); var signup = new OrganizationUpgrade { UseSecretsManager = true, diff --git a/test/Core.Test/Services/StripePaymentServiceTests.cs b/test/Core.Test/Services/StripePaymentServiceTests.cs index cc32a93b8..2133a14a9 100644 --- a/test/Core.Test/Services/StripePaymentServiceTests.cs +++ b/test/Core.Test/Services/StripePaymentServiceTests.cs @@ -40,7 +40,7 @@ public class StripePaymentServiceTests [Theory, BitAutoData] public async void PurchaseOrganizationAsync_Stripe_ProviderOrg_Coupon_Add(SutProvider 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(); stripeAdapter.CustomerCreateAsync(default).ReturnsForAnyArgs(new Stripe.Customer @@ -56,7 +56,7 @@ public class StripePaymentServiceTests .BaseServiceUri.CloudRegion .Returns("US"); - var result = await sutProvider.Sut.PurchaseOrganizationAsync(organization, PaymentMethodType.Card, paymentToken, plans, 0, 0, false, taxInfo, provider); + var result = await sutProvider.Sut.PurchaseOrganizationAsync(organization, PaymentMethodType.Card, paymentToken, plan, 0, 0, false, taxInfo, provider); Assert.Null(result); Assert.Equal(GatewayType.Stripe, organization.Gateway); @@ -95,8 +95,8 @@ public class StripePaymentServiceTests public async void PurchaseOrganizationAsync_SM_Stripe_ProviderOrg_Coupon_Add(SutProvider sutProvider, Organization organization, string paymentToken, TaxInfo taxInfo, bool provider = true) { - var plans = StaticStore.Plans.Where(p => p.Type == PlanType.EnterpriseAnnually).ToList(); - + var plan = StaticStore.GetPlan(PlanType.EnterpriseAnnually); + organization.UseSecretsManager = true; var stripeAdapter = sutProvider.GetDependency(); stripeAdapter.CustomerCreateAsync(default).ReturnsForAnyArgs(new Stripe.Customer { @@ -112,7 +112,7 @@ public class StripePaymentServiceTests .BaseServiceUri.CloudRegion .Returns("US"); - var result = await sutProvider.Sut.PurchaseOrganizationAsync(organization, PaymentMethodType.Card, paymentToken, plans, 1, 1, + var result = await sutProvider.Sut.PurchaseOrganizationAsync(organization, PaymentMethodType.Card, paymentToken, plan, 1, 1, false, taxInfo, provider, 1, 1); Assert.Null(result); @@ -151,8 +151,8 @@ public class StripePaymentServiceTests [Theory, BitAutoData] public async void PurchaseOrganizationAsync_Stripe(SutProvider 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(); stripeAdapter.CustomerCreateAsync(default).ReturnsForAnyArgs(new Stripe.Customer { @@ -167,7 +167,7 @@ public class StripePaymentServiceTests .BaseServiceUri.CloudRegion .Returns("US"); - var result = await sutProvider.Sut.PurchaseOrganizationAsync(organization, PaymentMethodType.Card, paymentToken, plans, 0, 0 + var result = await sutProvider.Sut.PurchaseOrganizationAsync(organization, PaymentMethodType.Card, paymentToken, plan, 0, 0 , false, taxInfo, false, 8, 10); Assert.Null(result); @@ -207,7 +207,7 @@ public class StripePaymentServiceTests [Theory, BitAutoData] public async void PurchaseOrganizationAsync_Stripe_PM(SutProvider sutProvider, Organization organization, string paymentToken, TaxInfo taxInfo) { - var plans = StaticStore.PasswordManagerPlans.Where(p => p.Type == PlanType.EnterpriseAnnually).ToList(); + var plan = StaticStore.GetPlan(PlanType.EnterpriseAnnually); paymentToken = "pm_" + paymentToken; var stripeAdapter = sutProvider.GetDependency(); @@ -224,7 +224,7 @@ public class StripePaymentServiceTests .BaseServiceUri.CloudRegion .Returns("US"); - var result = await sutProvider.Sut.PurchaseOrganizationAsync(organization, PaymentMethodType.Card, paymentToken, plans, 0, 0, false, taxInfo); + var result = await sutProvider.Sut.PurchaseOrganizationAsync(organization, PaymentMethodType.Card, paymentToken, plan, 0, 0, false, taxInfo); Assert.Null(result); Assert.Equal(GatewayType.Stripe, organization.Gateway); @@ -264,7 +264,7 @@ public class StripePaymentServiceTests [Theory, BitAutoData] public async void PurchaseOrganizationAsync_Stripe_TaxRate(SutProvider 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(); stripeAdapter.CustomerCreateAsync(default).ReturnsForAnyArgs(new Stripe.Customer @@ -280,7 +280,7 @@ public class StripePaymentServiceTests t.Country == taxInfo.BillingAddressCountry && t.PostalCode == taxInfo.BillingAddressPostalCode)) .Returns(new List { new() { Id = "T-1" } }); - var result = await sutProvider.Sut.PurchaseOrganizationAsync(organization, PaymentMethodType.Card, paymentToken, plans, 0, 0, false, taxInfo); + var result = await sutProvider.Sut.PurchaseOrganizationAsync(organization, PaymentMethodType.Card, paymentToken, plan, 0, 0, false, taxInfo); Assert.Null(result); @@ -293,7 +293,7 @@ public class StripePaymentServiceTests [Theory, BitAutoData] public async void PurchaseOrganizationAsync_Stripe_TaxRate_SM(SutProvider 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(); stripeAdapter.CustomerCreateAsync(default).ReturnsForAnyArgs(new Stripe.Customer @@ -309,7 +309,7 @@ public class StripePaymentServiceTests t.Country == taxInfo.BillingAddressCountry && t.PostalCode == taxInfo.BillingAddressPostalCode)) .Returns(new List { new() { Id = "T-1" } }); - var result = await sutProvider.Sut.PurchaseOrganizationAsync(organization, PaymentMethodType.Card, paymentToken, plans, 2, 2, + var result = await sutProvider.Sut.PurchaseOrganizationAsync(organization, PaymentMethodType.Card, paymentToken, plan, 2, 2, false, taxInfo, false, 2, 2); Assert.Null(result); @@ -323,7 +323,7 @@ public class StripePaymentServiceTests [Theory, BitAutoData] public async void PurchaseOrganizationAsync_Stripe_Declined(SutProvider sutProvider, Organization organization, string paymentToken, TaxInfo taxInfo) { - var plan = StaticStore.Plans.Where(p => p.Type == PlanType.EnterpriseAnnually).ToList(); + var plan = StaticStore.GetPlan(PlanType.EnterpriseAnnually); paymentToken = "pm_" + paymentToken; var stripeAdapter = sutProvider.GetDependency(); @@ -356,7 +356,7 @@ public class StripePaymentServiceTests [Theory, BitAutoData] public async void PurchaseOrganizationAsync_SM_Stripe_Declined(SutProvider sutProvider, Organization organization, string paymentToken, TaxInfo taxInfo) { - var plan = StaticStore.Plans.Where(p => p.Type == PlanType.EnterpriseAnnually).ToList(); + var plan = StaticStore.GetPlan(PlanType.EnterpriseAnnually); paymentToken = "pm_" + paymentToken; var stripeAdapter = sutProvider.GetDependency(); @@ -390,7 +390,7 @@ public class StripePaymentServiceTests [Theory, BitAutoData] public async void PurchaseOrganizationAsync_Stripe_RequiresAction(SutProvider 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(); stripeAdapter.CustomerCreateAsync(default).ReturnsForAnyArgs(new Stripe.Customer @@ -412,7 +412,7 @@ public class StripePaymentServiceTests }, }); - var result = await sutProvider.Sut.PurchaseOrganizationAsync(organization, PaymentMethodType.Card, paymentToken, plans, 0, 0, false, taxInfo); + var result = await sutProvider.Sut.PurchaseOrganizationAsync(organization, PaymentMethodType.Card, paymentToken, plan, 0, 0, false, taxInfo); Assert.Equal("clientSecret", result); Assert.False(organization.Enabled); @@ -421,7 +421,7 @@ public class StripePaymentServiceTests [Theory, BitAutoData] public async void PurchaseOrganizationAsync_SM_Stripe_RequiresAction(SutProvider 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(); stripeAdapter.CustomerCreateAsync(default).ReturnsForAnyArgs(new Stripe.Customer @@ -443,7 +443,7 @@ public class StripePaymentServiceTests }, }); - var result = await sutProvider.Sut.PurchaseOrganizationAsync(organization, PaymentMethodType.Card, paymentToken, plans, + var result = await sutProvider.Sut.PurchaseOrganizationAsync(organization, PaymentMethodType.Card, paymentToken, plan, 10, 10, false, taxInfo, false, 10, 10); Assert.Equal("clientSecret", result); @@ -453,7 +453,7 @@ public class StripePaymentServiceTests [Theory, BitAutoData] public async void PurchaseOrganizationAsync_Paypal(SutProvider 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(); stripeAdapter.CustomerCreateAsync(default).ReturnsForAnyArgs(new Stripe.Customer @@ -480,7 +480,7 @@ public class StripePaymentServiceTests var braintreeGateway = sutProvider.GetDependency(); braintreeGateway.Customer.CreateAsync(default).ReturnsForAnyArgs(customerResult); - var result = await sutProvider.Sut.PurchaseOrganizationAsync(organization, PaymentMethodType.PayPal, paymentToken, plans, 0, 0, false, taxInfo); + var result = await sutProvider.Sut.PurchaseOrganizationAsync(organization, PaymentMethodType.PayPal, paymentToken, plan, 0, 0, false, taxInfo); Assert.Null(result); Assert.Equal(GatewayType.Stripe, organization.Gateway); @@ -517,10 +517,8 @@ public class StripePaymentServiceTests [Theory, BitAutoData] public async void PurchaseOrganizationAsync_SM_Paypal(SutProvider sutProvider, Organization organization, string paymentToken, TaxInfo taxInfo) { - var plans = StaticStore.Plans.Where(p => p.Type == PlanType.EnterpriseAnnually).ToList(); - var passwordManagerPlan = plans.Single(p => p.BitwardenProduct == BitwardenProductType.PasswordManager); - var secretsManagerPlan = plans.Single(p => p.BitwardenProduct == BitwardenProductType.SecretsManager); - + var plan = StaticStore.GetPlan(PlanType.EnterpriseAnnually); + organization.UseSecretsManager = true; var stripeAdapter = sutProvider.GetDependency(); stripeAdapter.CustomerCreateAsync(default).ReturnsForAnyArgs(new Stripe.Customer { @@ -550,7 +548,7 @@ public class StripePaymentServiceTests var additionalSeats = 10; var additionalSmSeats = 5; var additionalServiceAccounts = 20; - var result = await sutProvider.Sut.PurchaseOrganizationAsync(organization, PaymentMethodType.PayPal, paymentToken, plans, + var result = await sutProvider.Sut.PurchaseOrganizationAsync(organization, PaymentMethodType.PayPal, paymentToken, plan, additionalStorage, additionalSeats, false, taxInfo, false, additionalSmSeats, additionalServiceAccounts); Assert.Null(result); @@ -582,17 +580,17 @@ public class StripePaymentServiceTests s.Expand[0] == "latest_invoice.payment_intent" && s.Metadata[organization.GatewayIdField()] == organization.Id.ToString() && s.Items.Count == 4 && - s.Items.Count(i => i.Plan == passwordManagerPlan.StripeSeatPlanId && i.Quantity == additionalSeats) == 1 && - s.Items.Count(i => i.Plan == passwordManagerPlan.StripeStoragePlanId && i.Quantity == additionalStorage) == 1 && - s.Items.Count(i => i.Plan == secretsManagerPlan.StripeSeatPlanId && i.Quantity == additionalSmSeats) == 1 && - s.Items.Count(i => i.Plan == secretsManagerPlan.StripeServiceAccountPlanId && i.Quantity == additionalServiceAccounts) == 1 + s.Items.Count(i => i.Plan == plan.PasswordManager.StripeSeatPlanId && i.Quantity == additionalSeats) == 1 && + s.Items.Count(i => i.Plan == plan.PasswordManager.StripeStoragePlanId && i.Quantity == additionalStorage) == 1 && + s.Items.Count(i => i.Plan == plan.SecretsManager.StripeSeatPlanId && i.Quantity == additionalSmSeats) == 1 && + s.Items.Count(i => i.Plan == plan.SecretsManager.StripeServiceAccountPlanId && i.Quantity == additionalServiceAccounts) == 1 )); } [Theory, BitAutoData] public async void PurchaseOrganizationAsync_Paypal_FailedCreate(SutProvider 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>(); customerResult.IsSuccess().Returns(false); @@ -601,7 +599,7 @@ public class StripePaymentServiceTests braintreeGateway.Customer.CreateAsync(default).ReturnsForAnyArgs(customerResult); var exception = await Assert.ThrowsAsync( - () => sutProvider.Sut.PurchaseOrganizationAsync(organization, PaymentMethodType.PayPal, paymentToken, plans, 0, 0, false, taxInfo)); + () => sutProvider.Sut.PurchaseOrganizationAsync(organization, PaymentMethodType.PayPal, paymentToken, plan, 0, 0, false, taxInfo)); Assert.Equal("Failed to create PayPal customer record.", exception.Message); } @@ -609,7 +607,7 @@ public class StripePaymentServiceTests [Theory, BitAutoData] public async void PurchaseOrganizationAsync_SM_Paypal_FailedCreate(SutProvider 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>(); customerResult.IsSuccess().Returns(false); @@ -618,7 +616,7 @@ public class StripePaymentServiceTests braintreeGateway.Customer.CreateAsync(default).ReturnsForAnyArgs(customerResult); var exception = await Assert.ThrowsAsync( - () => sutProvider.Sut.PurchaseOrganizationAsync(organization, PaymentMethodType.PayPal, paymentToken, plans, + () => sutProvider.Sut.PurchaseOrganizationAsync(organization, PaymentMethodType.PayPal, paymentToken, plan, 1, 1, false, taxInfo, false, 8, 8)); Assert.Equal("Failed to create PayPal customer record.", exception.Message); @@ -627,7 +625,7 @@ public class StripePaymentServiceTests [Theory, BitAutoData] public async void PurchaseOrganizationAsync_PayPal_Declined(SutProvider sutProvider, Organization organization, string paymentToken, TaxInfo taxInfo) { - var plans = StaticStore.Plans.Where(p => p.Type == PlanType.EnterpriseAnnually).ToList(); + var plans = StaticStore.GetPlan(PlanType.EnterpriseAnnually); paymentToken = "pm_" + paymentToken; var stripeAdapter = sutProvider.GetDependency(); @@ -689,7 +687,7 @@ public class StripePaymentServiceTests }); stripeAdapter.SubscriptionCreateAsync(default).ReturnsForAnyArgs(new Stripe.Subscription { }); - var plans = StaticStore.Plans.Where(p => p.Type == PlanType.EnterpriseAnnually).ToList(); + var plan = StaticStore.GetPlan(PlanType.EnterpriseAnnually); var upgrade = new OrganizationUpgrade() { @@ -700,7 +698,7 @@ public class StripePaymentServiceTests AdditionalSmSeats = 0, AdditionalServiceAccounts = 0 }; - var result = await sutProvider.Sut.UpgradeFreeOrganizationAsync(organization, plans, upgrade); + var result = await sutProvider.Sut.UpgradeFreeOrganizationAsync(organization, plan, upgrade); Assert.Null(result); } @@ -736,8 +734,8 @@ public class StripePaymentServiceTests AdditionalServiceAccounts = 50 }; - var plans = StaticStore.Plans.Where(p => p.Type == PlanType.EnterpriseAnnually).ToList(); - var result = await sutProvider.Sut.UpgradeFreeOrganizationAsync(organization, plans, upgrade); + var plan = StaticStore.GetPlan(PlanType.EnterpriseAnnually); + var result = await sutProvider.Sut.UpgradeFreeOrganizationAsync(organization, plan, upgrade); Assert.Null(result); } diff --git a/test/Core.Test/Utilities/StaticStoreTests.cs b/test/Core.Test/Utilities/StaticStoreTests.cs index d30a0e6c7..4e85928cc 100644 --- a/test/Core.Test/Utilities/StaticStoreTests.cs +++ b/test/Core.Test/Utilities/StaticStoreTests.cs @@ -1,5 +1,4 @@ using Bit.Core.Enums; -using Bit.Core.Models.StaticStore; using Bit.Core.Utilities; using Xunit; @@ -14,57 +13,18 @@ public class StaticStoreTests var plans = StaticStore.Plans; Assert.NotNull(plans); Assert.NotEmpty(plans); - Assert.Equal(17, plans.Count()); + Assert.Equal(12, plans.Count()); } [Theory] [InlineData(PlanType.EnterpriseAnnually)] - public void StaticStore_GetPasswordManagerPlanByPlanType_Success(PlanType planType) + [InlineData(PlanType.EnterpriseMonthly)] + [InlineData(PlanType.TeamsMonthly)] + [InlineData(PlanType.TeamsAnnually)] + public void StaticStore_GetPlan_Success(PlanType planType) { - var plan = StaticStore.GetPasswordManagerPlan(planType); - + var plan = StaticStore.GetPlan(planType); Assert.NotNull(plan); Assert.Equal(planType, plan.Type); } - - [Theory] - [InlineData(PlanType.EnterpriseAnnually)] - public void StaticStore_GetSecretsManagerPlanByPlanType_Success(PlanType planType) - { - var plan = StaticStore.GetSecretsManagerPlan(planType); - - Assert.NotNull(plan); - Assert.Equal(planType, plan.Type); - } - - [Theory] - [InlineData(PlanType.EnterpriseAnnually)] - public void StaticStore_GetPasswordManagerPlan_ReturnsPasswordManagerPlans(PlanType planType) - { - var plan = StaticStore.GetPasswordManagerPlan(planType); - Assert.NotNull(plan); - Assert.Equal(BitwardenProductType.PasswordManager, plan.BitwardenProduct); - } - - [Theory] - [InlineData(PlanType.EnterpriseAnnually)] - public void StaticStore_GetSecretsManagerPlan_ReturnsSecretManagerPlans(PlanType planType) - { - var plan = StaticStore.GetSecretsManagerPlan(planType); - Assert.NotNull(plan); - Assert.Equal(BitwardenProductType.SecretsManager, plan.BitwardenProduct); - } - - [Theory] - [InlineData(PlanType.EnterpriseAnnually, BitwardenProductType.PasswordManager)] - public void StaticStore_AddDuplicatePlans_SingleOrDefaultThrowsException(PlanType planType, BitwardenProductType bitwardenProductType) - { - var plansStore = new List - { - new Plan { Type = PlanType.EnterpriseAnnually, BitwardenProduct = BitwardenProductType.PasswordManager }, - new Plan { Type = PlanType.EnterpriseAnnually, BitwardenProduct = BitwardenProductType.PasswordManager } - }; - - Assert.Throws(() => plansStore.SingleOrDefault(p => p.Type == planType && p.BitwardenProduct == bitwardenProductType)); - } }