mirror of
https://github.com/bitwarden/server.git
synced 2025-02-23 03:01:23 +01:00
Plan And Price Updates (#859)
* Expanded the Plan model to make plan & product data a bit more dynamic * Created a Product enum to track versioned instances of the same plan * Created and API call and Response model for getting plan & product data from the server
This commit is contained in:
parent
61b11e398b
commit
c8220fdfa6
@ -471,7 +471,7 @@ namespace Bit.Api.Controllers
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[HttpGet("{id}/tax")]
|
||||
[SelfHosted(NotSelfHostedOnly = true)]
|
||||
public async Task<TaxInfoResponseModel> GetTaxInfo(string id)
|
||||
@ -491,7 +491,7 @@ namespace Bit.Api.Controllers
|
||||
var taxInfo = await _paymentService.GetTaxInfoAsync(organization);
|
||||
return new TaxInfoResponseModel(taxInfo);
|
||||
}
|
||||
|
||||
|
||||
[HttpPut("{id}/tax")]
|
||||
[SelfHosted(NotSelfHostedOnly = true)]
|
||||
public async Task PutTaxInfo(string id, [FromBody]OrganizationTaxInfoUpdateRequestModel model)
|
||||
|
21
src/Api/Controllers/PlansController.cs
Normal file
21
src/Api/Controllers/PlansController.cs
Normal file
@ -0,0 +1,21 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Bit.Core.Utilities;
|
||||
using Bit.Core.Models.Api;
|
||||
using System.Linq;
|
||||
|
||||
namespace Bit.Api.Controllers
|
||||
{
|
||||
[Route("plans")]
|
||||
[Authorize("Web")]
|
||||
public class PlansController : Controller
|
||||
{
|
||||
[HttpGet("")]
|
||||
public ListResponseModel<PlanResponseModel> Get()
|
||||
{
|
||||
var data = StaticStore.Plans;
|
||||
var responses = data.Select(plan => new PlanResponseModel(plan));
|
||||
return new ListResponseModel<PlanResponseModel>(responses);
|
||||
}
|
||||
}
|
||||
}
|
@ -6,21 +6,27 @@ namespace Bit.Core.Enums
|
||||
{
|
||||
[Display(Name = "Free")]
|
||||
Free = 0,
|
||||
[Display(Name = "Families")]
|
||||
FamiliesAnnually = 1,
|
||||
[Display(Name = "Teams (Monthly)")]
|
||||
TeamsMonthly = 2,
|
||||
[Display(Name = "Teams (Annually)")]
|
||||
TeamsAnnually = 3,
|
||||
[Display(Name = "Enterprise (Monthly)")]
|
||||
EnterpriseMonthly = 4,
|
||||
[Display(Name = "Enterprise (Annually)")]
|
||||
EnterpriseAnnually = 5,
|
||||
[Display(Name = "Families 2019")]
|
||||
FamiliesAnnually2019 = 1,
|
||||
[Display(Name = "Teams (Monthly) 2019")]
|
||||
TeamsMonthly2019 = 2,
|
||||
[Display(Name = "Teams (Annually) 2019")]
|
||||
TeamsAnnually2019 = 3,
|
||||
[Display(Name = "Enterprise (Monthly) 2019")]
|
||||
EnterpriseMonthly2019 = 4,
|
||||
[Display(Name = "Enterprise (Annually) 2019")]
|
||||
EnterpriseAnnually2019 = 5,
|
||||
[Display(Name = "Custom")]
|
||||
Custom = 6,
|
||||
[Display(Name = "PLACEHOLDER")]
|
||||
SsoPlaceholderMonthly = 10,
|
||||
[Display(Name = "PLACEHOLDER")]
|
||||
SsoPlaceholderAnnually = 11,
|
||||
[Display(Name = "Families")]
|
||||
FamiliesAnnually = 7,
|
||||
[Display(Name = "Teams (Monthly)")]
|
||||
TeamsMonthly = 8,
|
||||
[Display(Name = "Teams (Annually)")]
|
||||
TeamsAnnually = 9,
|
||||
[Display(Name = "Enterprise (Monthly)")]
|
||||
EnterpriseMonthly = 10,
|
||||
[Display(Name = "Enterprise (Annually)")]
|
||||
EnterpriseAnnually= 11,
|
||||
}
|
||||
}
|
||||
|
17
src/Core/Enums/ProductType.cs
Normal file
17
src/Core/Enums/ProductType.cs
Normal file
@ -0,0 +1,17 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Bit.Core.Enums
|
||||
{
|
||||
public enum ProductType : byte
|
||||
{
|
||||
[Display(Name = "Free")]
|
||||
Free = 0,
|
||||
[Display(Name = "Families")]
|
||||
Families = 1,
|
||||
[Display(Name = "Teams")]
|
||||
Teams = 2,
|
||||
[Display(Name = "Enterprise")]
|
||||
Enterprise = 3,
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,9 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Bit.Core.Models.Table;
|
||||
using System.Collections.Generic;
|
||||
using Bit.Core.Models.Business;
|
||||
using Bit.Core.Models.StaticStore;
|
||||
using System.Linq;
|
||||
using Bit.Core.Enums;
|
||||
|
||||
namespace Bit.Core.Models.Api
|
||||
{
|
||||
@ -25,7 +26,7 @@ namespace Bit.Core.Models.Api
|
||||
BusinessCountry = organization.BusinessCountry;
|
||||
BusinessTaxNumber = organization.BusinessTaxNumber;
|
||||
BillingEmail = organization.BillingEmail;
|
||||
Plan = organization.Plan;
|
||||
Plan = new PlanResponseModel(Utilities.StaticStore.Plans.FirstOrDefault(plan => plan.Type == organization.PlanType));
|
||||
PlanType = organization.PlanType;
|
||||
Seats = organization.Seats;
|
||||
MaxCollections = organization.MaxCollections;
|
||||
@ -51,8 +52,8 @@ namespace Bit.Core.Models.Api
|
||||
public string BusinessCountry { get; set; }
|
||||
public string BusinessTaxNumber { get; set; }
|
||||
public string BillingEmail { get; set; }
|
||||
public string Plan { get; set; }
|
||||
public Enums.PlanType PlanType { get; set; }
|
||||
public PlanResponseModel Plan { get; set; }
|
||||
public PlanType PlanType { get; set; }
|
||||
public short? Seats { get; set; }
|
||||
public short? MaxCollections { get; set; }
|
||||
public short? MaxStorageGb { get; set; }
|
||||
|
100
src/Core/Models/Api/Response/PlanResponseModel.cs
Normal file
100
src/Core/Models/Api/Response/PlanResponseModel.cs
Normal file
@ -0,0 +1,100 @@
|
||||
using System;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models.StaticStore;
|
||||
|
||||
namespace Bit.Core.Models.Api
|
||||
{
|
||||
public class PlanResponseModel : ResponseModel
|
||||
{
|
||||
public PlanResponseModel(Plan plan, string obj = "plan")
|
||||
: base(obj)
|
||||
{
|
||||
if (plan == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(plan));
|
||||
}
|
||||
|
||||
Type = plan.Type;
|
||||
Product = plan.Product;
|
||||
Name = plan.Name;
|
||||
IsAnnual = plan.IsAnnual;
|
||||
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;
|
||||
HasGroups = plan.HasGroups;
|
||||
HasDirectory = plan.HasDirectory;
|
||||
HasEvents = plan.HasEvents;
|
||||
HasTotp = plan.HasTotp;
|
||||
Has2fa = plan.Has2fa;
|
||||
HasSso = plan.HasSso;
|
||||
UsersGetPremium = plan.UsersGetPremium;
|
||||
UpgradeSortOrder = plan.UpgradeSortOrder;
|
||||
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;
|
||||
}
|
||||
|
||||
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 bool HasAdditionalSeatsOption { get; set; }
|
||||
public short? 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; }
|
||||
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 UsersGetPremium { 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 StripePremiumAccessPlanId { get; set; }
|
||||
public decimal BasePrice { get; set; }
|
||||
public decimal SeatPrice { get; set; }
|
||||
public decimal AdditionalStoragePricePerGb { get; set; }
|
||||
public decimal PremiumAccessOptionPrice { get; set; }
|
||||
}
|
||||
}
|
@ -235,7 +235,7 @@ namespace Bit.Core.Models.Business
|
||||
{
|
||||
valid = organization.UsePolicies == UsePolicies;
|
||||
}
|
||||
|
||||
|
||||
if (valid && Version >= 7)
|
||||
{
|
||||
valid = organization.UseSso == UseSso;
|
||||
|
@ -4,32 +4,48 @@ namespace Bit.Core.Models.StaticStore
|
||||
{
|
||||
public class 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 bool HasAdditionalSeatsOption { get; set; }
|
||||
public short? 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; }
|
||||
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 UsersGetPremium { 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 StripePremiumAccessPlanId { get; set; }
|
||||
public PlanType Type { get; set; }
|
||||
public short BaseSeats { get; set; }
|
||||
public bool CanBuyAdditionalSeats { get; set; }
|
||||
public short? MaxAdditionalSeats { get; set; }
|
||||
public bool CanBuyPremiumAccessAddon { get; set; }
|
||||
public bool UseGroups { get; set; }
|
||||
public bool UsePolicies { get; set; }
|
||||
public bool UseSso { get; set; }
|
||||
public bool UseDirectory { get; set; }
|
||||
public bool UseEvents { get; set; }
|
||||
public bool UseTotp { get; set; }
|
||||
public bool Use2fa { get; set; }
|
||||
public bool UseApi { get; set; }
|
||||
public short? MaxStorageGb { get; set; }
|
||||
public decimal BasePrice { get; set; }
|
||||
public decimal SeatPrice { get; set; }
|
||||
public short? MaxCollections { get; set; }
|
||||
public int UpgradeSortOrder { get; set; }
|
||||
public bool Disabled { get; set; }
|
||||
public int? TrialPeriodDays { get; set; }
|
||||
public bool SelfHost { get; set; }
|
||||
public bool UsersGetPremium { get; set; }
|
||||
public decimal AdditionalStoragePricePerGb { get; set; }
|
||||
public decimal PremiumAccessOptionPrice { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -177,7 +177,7 @@ namespace Bit.Core.Services
|
||||
ValidateOrganizationUpgradeParameters(newPlan, upgrade);
|
||||
|
||||
var newPlanSeats = (short)(newPlan.BaseSeats +
|
||||
(newPlan.CanBuyAdditionalSeats ? upgrade.AdditionalSeats : 0));
|
||||
(newPlan.HasAdditionalSeatsOption ? upgrade.AdditionalSeats : 0));
|
||||
if (!organization.Seats.HasValue || organization.Seats.Value > newPlanSeats)
|
||||
{
|
||||
var userCount = await _organizationUserRepository.GetCountByOrganizationIdAsync(organization.Id);
|
||||
@ -200,7 +200,7 @@ namespace Bit.Core.Services
|
||||
}
|
||||
}
|
||||
|
||||
if (!newPlan.UseGroups && organization.UseGroups)
|
||||
if (!newPlan.HasGroups && organization.UseGroups)
|
||||
{
|
||||
var groups = await _groupRepository.GetManyByOrganizationIdAsync(organization.Id);
|
||||
if (groups.Any())
|
||||
@ -210,7 +210,7 @@ namespace Bit.Core.Services
|
||||
}
|
||||
}
|
||||
|
||||
if (!newPlan.UsePolicies && organization.UsePolicies)
|
||||
if (!newPlan.HasPolicies && organization.UsePolicies)
|
||||
{
|
||||
var policies = await _policyRepository.GetManyByOrganizationIdAsync(organization.Id);
|
||||
if (policies.Any(p => p.Enabled))
|
||||
@ -219,8 +219,8 @@ namespace Bit.Core.Services
|
||||
$"Disable your policies.");
|
||||
}
|
||||
}
|
||||
|
||||
if (!newPlan.UseSso && organization.UseSso)
|
||||
|
||||
if (!newPlan.HasSso && organization.UseSso)
|
||||
{
|
||||
var ssoConfig = await _ssoConfigRepository.GetByOrganizationIdAsync(organization.Id);
|
||||
if (ssoConfig != null && ssoConfig.Enabled)
|
||||
@ -250,16 +250,25 @@ namespace Bit.Core.Services
|
||||
organization.PlanType = newPlan.Type;
|
||||
organization.Seats = (short)(newPlan.BaseSeats + upgrade.AdditionalSeats);
|
||||
organization.MaxCollections = newPlan.MaxCollections;
|
||||
organization.MaxStorageGb = !newPlan.MaxStorageGb.HasValue ?
|
||||
(short?)null : (short)(newPlan.MaxStorageGb.Value + upgrade.AdditionalStorageGb);
|
||||
organization.UseGroups = newPlan.UseGroups;
|
||||
organization.UseDirectory = newPlan.UseDirectory;
|
||||
organization.UseEvents = newPlan.UseEvents;
|
||||
organization.UseTotp = newPlan.UseTotp;
|
||||
organization.Use2fa = newPlan.Use2fa;
|
||||
organization.UseApi = newPlan.UseApi;
|
||||
organization.SelfHost = newPlan.SelfHost;
|
||||
organization.UsePolicies = newPlan.UsePolicies;
|
||||
organization.MaxStorageGb = !newPlan.BaseStorageGb.HasValue ?
|
||||
(short?)null : (short)(newPlan.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.SelfHost = newPlan.HasSelfHost;
|
||||
organization.UsePolicies = newPlan.HasPolicies;
|
||||
organization.MaxStorageGb = !newPlan.MaxAdditionalStorage.HasValue ?
|
||||
(short?)null : (short)(newPlan.MaxAdditionalStorage.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.SelfHost = newPlan.HasSelfHost;
|
||||
organization.UsersGetPremium = newPlan.UsersGetPremium || upgrade.PremiumAccessAddon;
|
||||
organization.Plan = newPlan.Name;
|
||||
organization.Enabled = success;
|
||||
@ -293,7 +302,7 @@ namespace Bit.Core.Services
|
||||
throw new BadRequestException("Existing plan not found.");
|
||||
}
|
||||
|
||||
if (!plan.MaxStorageGb.HasValue)
|
||||
if (!plan.HasAdditionalStorageOption)
|
||||
{
|
||||
throw new BadRequestException("Plan does not allow additional storage.");
|
||||
}
|
||||
@ -335,7 +344,7 @@ namespace Bit.Core.Services
|
||||
throw new BadRequestException("Existing plan not found.");
|
||||
}
|
||||
|
||||
if (!plan.CanBuyAdditionalSeats)
|
||||
if (!plan.HasAdditionalSeatsOption)
|
||||
{
|
||||
throw new BadRequestException("Plan does not allow additional seats.");
|
||||
}
|
||||
@ -515,17 +524,17 @@ namespace Bit.Core.Services
|
||||
PlanType = plan.Type,
|
||||
Seats = (short)(plan.BaseSeats + signup.AdditionalSeats),
|
||||
MaxCollections = plan.MaxCollections,
|
||||
MaxStorageGb = !plan.MaxStorageGb.HasValue ?
|
||||
(short?)null : (short)(plan.MaxStorageGb.Value + signup.AdditionalStorageGb),
|
||||
UsePolicies = plan.UsePolicies,
|
||||
UseSso = plan.UseSso,
|
||||
UseGroups = plan.UseGroups,
|
||||
UseEvents = plan.UseEvents,
|
||||
UseDirectory = plan.UseDirectory,
|
||||
UseTotp = plan.UseTotp,
|
||||
Use2fa = plan.Use2fa,
|
||||
UseApi = plan.UseApi,
|
||||
SelfHost = plan.SelfHost,
|
||||
MaxStorageGb = !plan.BaseStorageGb.HasValue ?
|
||||
(short?)null : (short)(plan.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,
|
||||
SelfHost = plan.HasSelfHost,
|
||||
UsersGetPremium = plan.UsersGetPremium || signup.PremiumAccessAddon,
|
||||
Plan = plan.Name,
|
||||
Gateway = null,
|
||||
@ -763,7 +772,7 @@ namespace Bit.Core.Services
|
||||
$"policies. Your new license does not allow for the use of policies. Disable all policies.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (!license.UseSso && organization.UseSso)
|
||||
{
|
||||
var ssoConfig = await _ssoConfigRepository.GetByOrganizationIdAsync(organization.Id);
|
||||
@ -1522,7 +1531,7 @@ namespace Bit.Core.Services
|
||||
|
||||
private void ValidateOrganizationUpgradeParameters(Models.StaticStore.Plan plan, OrganizationUpgrade upgrade)
|
||||
{
|
||||
if (!plan.MaxStorageGb.HasValue && upgrade.AdditionalStorageGb > 0)
|
||||
if (!plan.HasAdditionalStorageOption && upgrade.AdditionalStorageGb > 0)
|
||||
{
|
||||
throw new BadRequestException("Plan does not allow additional storage.");
|
||||
}
|
||||
@ -1532,7 +1541,7 @@ namespace Bit.Core.Services
|
||||
throw new BadRequestException("You can't subtract storage!");
|
||||
}
|
||||
|
||||
if (!plan.CanBuyPremiumAccessAddon && upgrade.PremiumAccessAddon)
|
||||
if (!plan.HasPremiumAccessOption && upgrade.PremiumAccessAddon)
|
||||
{
|
||||
throw new BadRequestException("This plan does not allow you to buy the premium access addon.");
|
||||
}
|
||||
@ -1547,12 +1556,12 @@ namespace Bit.Core.Services
|
||||
throw new BadRequestException("You can't subtract seats!");
|
||||
}
|
||||
|
||||
if (!plan.CanBuyAdditionalSeats && upgrade.AdditionalSeats > 0)
|
||||
if (!plan.HasAdditionalSeatsOption && upgrade.AdditionalSeats > 0)
|
||||
{
|
||||
throw new BadRequestException("Plan does not allow additional users.");
|
||||
}
|
||||
|
||||
if (plan.CanBuyAdditionalSeats && plan.MaxAdditionalSeats.HasValue &&
|
||||
if (plan.HasAdditionalSeatsOption && plan.MaxAdditionalSeats.HasValue &&
|
||||
upgrade.AdditionalSeats > plan.MaxAdditionalSeats.Value)
|
||||
{
|
||||
throw new BadRequestException($"Selected plan allows a maximum of " +
|
||||
|
@ -103,109 +103,327 @@ namespace Bit.Core.Utilities
|
||||
new Plan
|
||||
{
|
||||
Type = PlanType.Free,
|
||||
BaseSeats = 2,
|
||||
CanBuyAdditionalSeats = false,
|
||||
MaxCollections = 2,
|
||||
Product = ProductType.Free,
|
||||
Name = "Free",
|
||||
UpgradeSortOrder = -1 // Always the lowest plan, cannot be upgraded to
|
||||
NameLocalizationKey = "planNameFree",
|
||||
DescriptionLocalizationKey = "planDescFree",
|
||||
BaseSeats = 2,
|
||||
MaxCollections = 2,
|
||||
MaxUsers = 2,
|
||||
|
||||
UpgradeSortOrder = -1, // Always the lowest plan, cannot be upgraded to
|
||||
DisplaySortOrder = -1
|
||||
},
|
||||
new Plan
|
||||
{
|
||||
Type = PlanType.FamiliesAnnually2019,
|
||||
Product = ProductType.Families,
|
||||
Name = "Families 2019",
|
||||
IsAnnual = true,
|
||||
NameLocalizationKey = "planNameFamilies",
|
||||
DescriptionLocalizationKey = "planDescFamilies",
|
||||
BaseSeats = 6,
|
||||
BaseStorageGb = 1,
|
||||
MaxUsers = 6,
|
||||
|
||||
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
|
||||
},
|
||||
new Plan
|
||||
{
|
||||
Type = PlanType.TeamsAnnually2019,
|
||||
Product = ProductType.Teams,
|
||||
Name = "Teams (Annually) 2019",
|
||||
IsAnnual = true,
|
||||
NameLocalizationKey = "planNameTeams",
|
||||
DescriptionLocalizationKey = "planDescTeams",
|
||||
CanBeUsedByBusiness = true,
|
||||
BaseSeats = 6,
|
||||
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
|
||||
},
|
||||
new Plan
|
||||
{
|
||||
Type = PlanType.TeamsMonthly2019,
|
||||
Product = ProductType.Teams,
|
||||
Name = "Teams (Monthly) 2019",
|
||||
NameLocalizationKey = "planNameTeams",
|
||||
DescriptionLocalizationKey = "planDescTeams",
|
||||
CanBeUsedByBusiness = true,
|
||||
BaseSeats = 6,
|
||||
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
|
||||
},
|
||||
new Plan
|
||||
{
|
||||
Type = PlanType.EnterpriseAnnually2019,
|
||||
Name = "Enterprise (Annually) 2019",
|
||||
IsAnnual = true,
|
||||
Product = ProductType.Enterprise,
|
||||
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,
|
||||
|
||||
UpgradeSortOrder = 3,
|
||||
DisplaySortOrder = 3,
|
||||
LegacyYear = 2020,
|
||||
|
||||
StripePlanId = null,
|
||||
StripeSeatPlanId = "enterprise-org-seat-annually",
|
||||
StripeStoragePlanId = "storage-gb-annually",
|
||||
BasePrice = 0,
|
||||
SeatPrice = 36,
|
||||
AdditionalStoragePricePerGb = 4
|
||||
},
|
||||
new Plan
|
||||
{
|
||||
Type = PlanType.EnterpriseMonthly2019,
|
||||
Product = ProductType.Enterprise,
|
||||
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,
|
||||
|
||||
UpgradeSortOrder = 3,
|
||||
DisplaySortOrder = 3,
|
||||
LegacyYear = 2020,
|
||||
|
||||
StripePlanId = null,
|
||||
StripeSeatPlanId = "enterprise-org-seat-monthly",
|
||||
StripeStoragePlanId = "storage-gb-monthly",
|
||||
BasePrice = 0,
|
||||
SeatPrice = 4M,
|
||||
AdditionalStoragePricePerGb = 0.5M
|
||||
},
|
||||
new Plan
|
||||
{
|
||||
Type = PlanType.FamiliesAnnually,
|
||||
BaseSeats = 5,
|
||||
BasePrice = 12,
|
||||
CanBuyAdditionalSeats = false,
|
||||
CanBuyPremiumAccessAddon = true,
|
||||
Product = ProductType.Families,
|
||||
Name = "Families",
|
||||
IsAnnual = true,
|
||||
NameLocalizationKey = "planNameFamilies",
|
||||
DescriptionLocalizationKey = "planDescFamilies",
|
||||
BaseSeats = 6,
|
||||
BaseStorageGb = 1,
|
||||
MaxUsers = 6,
|
||||
|
||||
TrialPeriodDays = 7,
|
||||
|
||||
HasSelfHost = true,
|
||||
HasTotp = true,
|
||||
UsersGetPremium = true,
|
||||
|
||||
UpgradeSortOrder = 1,
|
||||
DisplaySortOrder = 1,
|
||||
|
||||
StripePlanId = "personal-org-annually",
|
||||
StripeStoragePlanId = "storage-gb-annually",
|
||||
StripePremiumAccessPlanId = "personal-org-premium-access-annually",
|
||||
UpgradeSortOrder = 1,
|
||||
TrialPeriodDays = 7,
|
||||
UseTotp = true,
|
||||
MaxStorageGb = 1,
|
||||
SelfHost = true
|
||||
},
|
||||
new Plan
|
||||
{
|
||||
Type = PlanType.TeamsMonthly,
|
||||
BaseSeats = 5,
|
||||
BasePrice = 8,
|
||||
SeatPrice = 2.5M,
|
||||
CanBuyAdditionalSeats = true,
|
||||
Name = "Teams (Monthly)",
|
||||
StripePlanId = "teams-org-monthly",
|
||||
StripeSeatPlanId = "teams-org-seat-monthly",
|
||||
StripeStoragePlanId = "storage-gb-monthly",
|
||||
UpgradeSortOrder = 2,
|
||||
TrialPeriodDays = 7,
|
||||
UseTotp = true,
|
||||
MaxStorageGb = 1
|
||||
BasePrice = 40,
|
||||
AdditionalStoragePricePerGb = 4
|
||||
},
|
||||
new Plan
|
||||
{
|
||||
Type = PlanType.TeamsAnnually,
|
||||
BaseSeats = 5,
|
||||
BasePrice = 60,
|
||||
SeatPrice = 24,
|
||||
CanBuyAdditionalSeats = true,
|
||||
Product = ProductType.Teams,
|
||||
Name = "Teams (Annually)",
|
||||
StripePlanId = "teams-org-annually",
|
||||
StripeSeatPlanId = "teams-org-seat-annually",
|
||||
StripeStoragePlanId = "storage-gb-annually",
|
||||
UpgradeSortOrder = 2,
|
||||
IsAnnual = true,
|
||||
NameLocalizationKey = "planNameTeams",
|
||||
DescriptionLocalizationKey = "planDescTeams",
|
||||
CanBeUsedByBusiness = true,
|
||||
BaseStorageGb = 1,
|
||||
BaseSeats = 0,
|
||||
|
||||
HasAdditionalSeatsOption = true,
|
||||
HasAdditionalStorageOption = true,
|
||||
TrialPeriodDays = 7,
|
||||
UseTotp = true,
|
||||
MaxStorageGb = 1
|
||||
|
||||
HasTotp = true,
|
||||
UsersGetPremium = true,
|
||||
|
||||
UpgradeSortOrder = 2,
|
||||
DisplaySortOrder = 2,
|
||||
|
||||
StripeSeatPlanId = "2020-teams-org-seat-annually",
|
||||
StripeStoragePlanId = "storage-gb-annually",
|
||||
SeatPrice = 36,
|
||||
AdditionalStoragePricePerGb = 4
|
||||
},
|
||||
new Plan
|
||||
{
|
||||
Type = PlanType.EnterpriseMonthly,
|
||||
Type = PlanType.TeamsMonthly,
|
||||
Product = ProductType.Teams,
|
||||
Name = "Teams (Monthly)",
|
||||
NameLocalizationKey = "planNameTeams",
|
||||
DescriptionLocalizationKey = "planDescTeams",
|
||||
CanBeUsedByBusiness = true,
|
||||
BaseStorageGb = 1,
|
||||
BaseSeats = 0,
|
||||
BasePrice = 0,
|
||||
SeatPrice = 4M,
|
||||
CanBuyAdditionalSeats = true,
|
||||
Name = "Enterprise (Monthly)",
|
||||
StripePlanId = null,
|
||||
StripeSeatPlanId = "enterprise-org-seat-monthly",
|
||||
StripeStoragePlanId = "storage-gb-monthly",
|
||||
UpgradeSortOrder = 3,
|
||||
|
||||
HasAdditionalSeatsOption = true,
|
||||
HasAdditionalStorageOption = true,
|
||||
TrialPeriodDays = 7,
|
||||
UsePolicies = true,
|
||||
UseGroups = true,
|
||||
UseDirectory = true,
|
||||
UseEvents = true,
|
||||
UseTotp = true,
|
||||
Use2fa = true,
|
||||
UseApi = true,
|
||||
MaxStorageGb = 1,
|
||||
SelfHost = true,
|
||||
UsersGetPremium = true
|
||||
|
||||
HasTotp = true,
|
||||
UsersGetPremium = true,
|
||||
|
||||
UpgradeSortOrder = 2,
|
||||
DisplaySortOrder = 2,
|
||||
|
||||
StripeSeatPlanId = "2020-teams-org-seat-monthly",
|
||||
StripeStoragePlanId = "storage-gb-monthly",
|
||||
SeatPrice = 4,
|
||||
AdditionalStoragePricePerGb = 0.5M
|
||||
},
|
||||
new Plan
|
||||
{
|
||||
Type = PlanType.EnterpriseAnnually,
|
||||
BaseSeats = 0,
|
||||
BasePrice = 0,
|
||||
SeatPrice = 36,
|
||||
CanBuyAdditionalSeats = true,
|
||||
Name = "Enterprise (Annually)",
|
||||
StripePlanId = null,
|
||||
StripeSeatPlanId = "enterprise-org-seat-annually",
|
||||
StripeStoragePlanId = "storage-gb-annually",
|
||||
UpgradeSortOrder = 3,
|
||||
Product = ProductType.Enterprise,
|
||||
IsAnnual = true,
|
||||
NameLocalizationKey = "planNameEnterprise",
|
||||
DescriptionLocalizationKey = "planDescEnterprise",
|
||||
CanBeUsedByBusiness = true,
|
||||
BaseSeats = 0,
|
||||
BaseStorageGb = 1,
|
||||
|
||||
HasAdditionalSeatsOption = true,
|
||||
HasAdditionalStorageOption = true,
|
||||
TrialPeriodDays = 7,
|
||||
UsePolicies = true,
|
||||
UseGroups = true,
|
||||
UseDirectory = true,
|
||||
UseEvents = true,
|
||||
UseTotp = true,
|
||||
Use2fa = true,
|
||||
UseApi = true,
|
||||
MaxStorageGb = 1,
|
||||
SelfHost = true,
|
||||
UsersGetPremium = true
|
||||
}
|
||||
|
||||
HasPolicies = true,
|
||||
HasSelfHost = true,
|
||||
HasGroups = true,
|
||||
HasDirectory = true,
|
||||
HasEvents = true,
|
||||
HasTotp = true,
|
||||
Has2fa = true,
|
||||
HasApi = true,
|
||||
HasSso = true,
|
||||
UsersGetPremium = true,
|
||||
|
||||
UpgradeSortOrder = 3,
|
||||
DisplaySortOrder = 3,
|
||||
|
||||
StripeSeatPlanId = "2020-enterprise-org-seat-annually",
|
||||
StripeStoragePlanId = "storage-gb-annually",
|
||||
BasePrice = 0,
|
||||
SeatPrice = 60,
|
||||
AdditionalStoragePricePerGb = 4
|
||||
},
|
||||
new Plan
|
||||
{
|
||||
Type = PlanType.EnterpriseMonthly,
|
||||
Product = ProductType.Enterprise,
|
||||
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,
|
||||
UsersGetPremium = true,
|
||||
|
||||
UpgradeSortOrder = 3,
|
||||
DisplaySortOrder = 3,
|
||||
|
||||
StripeSeatPlanId = "2020-enterprise-org-seat-monthly",
|
||||
StripeStoragePlanId = "storage-gb-monthly",
|
||||
BasePrice = 0,
|
||||
SeatPrice = 6,
|
||||
AdditionalStoragePricePerGb = 0.5M
|
||||
},
|
||||
};
|
||||
|
||||
#endregion
|
||||
|
Loading…
Reference in New Issue
Block a user