Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[AC-1650] [AC-1578] #3320

Merged
merged 10 commits into from
Nov 1, 2023
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -30,29 +30,36 @@ await sutProvider.GetDependency<IProjectRepository>().DidNotReceiveWithAnyArgs()

[Theory]
[BitAutoData(PlanType.FamiliesAnnually2019)]
[BitAutoData(PlanType.TeamsMonthly2019)]
[BitAutoData(PlanType.TeamsAnnually2019)]
[BitAutoData(PlanType.EnterpriseMonthly2019)]
[BitAutoData(PlanType.EnterpriseAnnually2019)]
[BitAutoData(PlanType.Custom)]
[BitAutoData(PlanType.FamiliesAnnually)]
public async Task GetByOrgIdAsync_SmPlanIsNull_ThrowsBadRequest(PlanType planType,
SutProvider<MaxProjectsQuery> sutProvider, Organization organization)
{
organization.PlanType = planType;
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organization.Id).Returns(organization);
sutProvider.GetDependency<IOrganizationRepository>()
.GetByIdAsync(organization.Id)
.Returns(organization);

await Assert.ThrowsAsync<BadRequestException>(
async () => await sutProvider.Sut.GetByOrgIdAsync(organization.Id, 1));

await sutProvider.GetDependency<IProjectRepository>().DidNotReceiveWithAnyArgs()
await sutProvider.GetDependency<IProjectRepository>()
.DidNotReceiveWithAnyArgs()
.GetProjectCountByOrganizationIdAsync(organization.Id);
}

[Theory]
[BitAutoData(PlanType.TeamsMonthly2019)]
[BitAutoData(PlanType.TeamsMonthly2020)]
[BitAutoData(PlanType.TeamsMonthly)]
[BitAutoData(PlanType.TeamsAnnually2019)]
[BitAutoData(PlanType.TeamsAnnually2020)]
[BitAutoData(PlanType.TeamsAnnually)]
[BitAutoData(PlanType.EnterpriseMonthly2019)]
[BitAutoData(PlanType.EnterpriseMonthly2020)]
[BitAutoData(PlanType.EnterpriseMonthly)]
[BitAutoData(PlanType.EnterpriseAnnually2019)]
[BitAutoData(PlanType.EnterpriseAnnually2020)]
[BitAutoData(PlanType.EnterpriseAnnually)]
public async Task GetByOrgIdAsync_SmNoneFreePlans_ReturnsNull(PlanType planType,
SutProvider<MaxProjectsQuery> sutProvider, Organization organization)
Expand Down
8 changes: 8 additions & 0 deletions src/Admin/Views/Shared/_OrganizationFormScripts.cshtml
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@

function togglePlanFeatures(planType) {
switch(planType) {
case '@((byte)PlanType.TeamsMonthly2019)':
case '@((byte)PlanType.TeamsAnnually2019)':
case '@((byte)PlanType.TeamsMonthly2020)':
case '@((byte)PlanType.TeamsAnnually2020)':
case '@((byte)PlanType.TeamsMonthly)':
case '@((byte)PlanType.TeamsAnnually)':
document.getElementById('@(nameof(Model.UsePolicies))').checked = false;
Expand All @@ -81,6 +85,10 @@
document.getElementById('@(nameof(Model.UseScim))').checked = false;
break;

case '@((byte)PlanType.EnterpriseMonthly2019)':
case '@((byte)PlanType.EnterpriseAnnually2019)':
case '@((byte)PlanType.EnterpriseMonthly2020)':
case '@((byte)PlanType.EnterpriseAnnually2020)':
case '@((byte)PlanType.EnterpriseMonthly)':
case '@((byte)PlanType.EnterpriseAnnually)':
document.getElementById('@(nameof(Model.UsePolicies))').checked = true;
Expand Down
26 changes: 24 additions & 2 deletions src/Api/Controllers/PlansController.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
using Bit.Api.Models.Response;
using Bit.Core;
using Bit.Core.Context;
using Bit.Core.Enums;
using Bit.Core.Repositories;
using Bit.Core.Services;
using Bit.Core.Utilities;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
Expand All @@ -11,17 +15,35 @@ namespace Bit.Api.Controllers;
public class PlansController : Controller
{
private readonly ITaxRateRepository _taxRateRepository;
public PlansController(ITaxRateRepository taxRateRepository)
private readonly IFeatureService _featureService;
private readonly ICurrentContext _currentContext;

public PlansController(
ITaxRateRepository taxRateRepository,
IFeatureService featureService,
ICurrentContext currentContext)
{
_taxRateRepository = taxRateRepository;
_featureService = featureService;
_currentContext = currentContext;
}

[HttpGet("")]
[AllowAnonymous]
public ListResponseModel<PlanResponseModel> Get()
{
var plansUpgradeIsEnabled = _featureService.IsEnabled(FeatureFlagKeys.BillingPlansUpgrade, _currentContext);
var data = StaticStore.Plans;
var responses = data.Select(plan => new PlanResponseModel(plan));
var responses = data
.Where(plan => plansUpgradeIsEnabled || plan.Type <= PlanType.EnterpriseAnnually2020)
.Select(plan =>
{
if (plan.Type is <= PlanType.EnterpriseAnnually2020 and >= PlanType.TeamsMonthly2020)
{
plan.LegacyYear = null;
}
return new PlanResponseModel(plan);
});
return new ListResponseModel<PlanResponseModel>(responses);
}

Expand Down
3 changes: 1 addition & 2 deletions src/Api/Models/Response/ProfileOrganizationResponseModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,7 @@ public ProfileOrganizationResponseModel(OrganizationUserOrganizationDetails orga
UsePasswordManager = organization.UsePasswordManager;
UsersGetPremium = organization.UsersGetPremium;
UseCustomPermissions = organization.UseCustomPermissions;
UseActivateAutofillPolicy = organization.PlanType == PlanType.EnterpriseAnnually ||
organization.PlanType == PlanType.EnterpriseMonthly;
UseActivateAutofillPolicy = StaticStore.GetPlan(organization.PlanType).Product == ProductType.Enterprise;
SelfHost = organization.SelfHost;
Seats = organization.Seats;
MaxCollections = organization.MaxCollections;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,7 @@ public ProfileProviderOrganizationResponseModel(ProviderUserOrganizationDetails
UseResetPassword = organization.UseResetPassword;
UsersGetPremium = organization.UsersGetPremium;
UseCustomPermissions = organization.UseCustomPermissions;
UseActivateAutofillPolicy = organization.PlanType == PlanType.EnterpriseAnnually ||
organization.PlanType == PlanType.EnterpriseMonthly;
UseActivateAutofillPolicy = StaticStore.GetPlan(organization.PlanType).Product == ProductType.Enterprise;
SelfHost = organization.SelfHost;
Seats = organization.Seats;
MaxCollections = organization.MaxCollections;
Expand Down
4 changes: 4 additions & 0 deletions src/Billing/Controllers/FreshsalesController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -159,14 +159,18 @@ private static bool TryGetPlanName(PlanType planType, out string planName)
planName = "Families";
return true;
case PlanType.TeamsAnnually:
case PlanType.TeamsAnnually2020:
case PlanType.TeamsAnnually2019:
case PlanType.TeamsMonthly:
case PlanType.TeamsMonthly2020:
case PlanType.TeamsMonthly2019:
planName = "Teams";
return true;
case PlanType.EnterpriseAnnually:
case PlanType.EnterpriseAnnually2020:
case PlanType.EnterpriseAnnually2019:
case PlanType.EnterpriseMonthly:
case PlanType.EnterpriseMonthly2020:
case PlanType.EnterpriseMonthly2019:
planName = "Enterprise";
return true;
Expand Down
13 changes: 1 addition & 12 deletions src/Billing/Controllers/StripeController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -642,18 +642,7 @@ private async Task AttemptToPayOpenSubscriptionAsync(Subscription unpaidSubscrip
return new Tuple<Guid?, Guid?>(orgId, userId);
}

private bool OrgPlanForInvoiceNotifications(Organization org)
{
switch (org.PlanType)
{
case PlanType.FamiliesAnnually:
case PlanType.TeamsAnnually:
case PlanType.EnterpriseAnnually:
return true;
default:
return false;
}
}
private static bool OrgPlanForInvoiceNotifications(Organization org) => StaticStore.GetPlan(org.PlanType).IsAnnual;

private async Task<bool> AttemptToPayInvoiceAsync(Invoice invoice, bool attemptToPayWithStripe = false)
{
Expand Down
1 change: 1 addition & 0 deletions src/Core/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ public static class FeatureFlagKeys
public const string Fido2VaultCredentials = "fido2-vault-credentials";
public const string AutofillV2 = "autofill-v2";
public const string BrowserFilelessImport = "browser-fileless-import";
public const string BillingPlansUpgrade = "billing-plans-upgrade";

public static List<string> GetAllKeys()
{
Expand Down
16 changes: 12 additions & 4 deletions src/Core/Enums/PlanType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,20 @@ public enum PlanType : byte
Custom = 6,
[Display(Name = "Families")]
FamiliesAnnually = 7,
[Display(Name = "Teams (Monthly) 2020")]
TeamsMonthly2020 = 8,
[Display(Name = "Teams (Annually) 2020")]
TeamsAnnually2020 = 9,
[Display(Name = "Enterprise (Monthly) 2020")]
EnterpriseMonthly2020 = 10,
[Display(Name = "Enterprise (Annually) 2020")]
EnterpriseAnnually2020 = 11,
[Display(Name = "Teams (Monthly)")]
TeamsMonthly = 8,
TeamsMonthly = 12,
[Display(Name = "Teams (Annually)")]
TeamsAnnually = 9,
TeamsAnnually = 13,
[Display(Name = "Enterprise (Monthly)")]
EnterpriseMonthly = 10,
EnterpriseMonthly = 14,
[Display(Name = "Enterprise (Annually)")]
EnterpriseAnnually = 11,
EnterpriseAnnually = 15,
}
2 changes: 1 addition & 1 deletion src/Core/Models/StaticStore/Plan.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public abstract record Plan
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 int? LegacyYear { get; set; }
cyprain-okeke marked this conversation as resolved.
Show resolved Hide resolved
public bool Disabled { get; protected init; }
public PasswordManagerPlanFeatures PasswordManager { get; protected init; }
public SecretsManagerPlanFeatures SecretsManager { get; protected init; }
Expand Down
36 changes: 36 additions & 0 deletions src/Core/Models/StaticStore/Plans/Enterprise2019Plan.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,52 @@ public Enterprise2019Plan(bool isAnnual)
HasTotp = true;
Has2fa = true;
HasApi = true;
HasSso = true;
HasKeyConnector = true;
HasScim = true;
HasResetPassword = true;
UsersGetPremium = true;
HasCustomPermissions = true;

UpgradeSortOrder = 3;
DisplaySortOrder = 3;
LegacyYear = 2020;

SecretsManager = new Enterprise2019SecretsManagerFeatures(isAnnual);
PasswordManager = new Enterprise2019PasswordManagerFeatures(isAnnual);
}

private record Enterprise2019SecretsManagerFeatures : SecretsManagerPlanFeatures
{
public Enterprise2019SecretsManagerFeatures(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 Enterprise2019PasswordManagerFeatures : PasswordManagerPlanFeatures
{
public Enterprise2019PasswordManagerFeatures(bool isAnnual)
Expand Down
101 changes: 101 additions & 0 deletions src/Core/Models/StaticStore/Plans/Enterprise2020Plan.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
using Bit.Core.Enums;

namespace Bit.Core.Models.StaticStore.Plans;

public record Enterprise2020Plan : Models.StaticStore.Plan
{
public Enterprise2020Plan(bool isAnnual)
{
Type = isAnnual ? PlanType.EnterpriseAnnually2020 : PlanType.EnterpriseMonthly2020;
Product = ProductType.Enterprise;
Name = isAnnual ? "Enterprise (Annually) 2020" : "Enterprise (Monthly) 2020";
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;
LegacyYear = 2023;

PasswordManager = new Enterprise2020PasswordManagerFeatures(isAnnual);
SecretsManager = new Enterprise2020SecretsManagerFeatures(isAnnual);
}

private record Enterprise2020SecretsManagerFeatures : SecretsManagerPlanFeatures
{
public Enterprise2020SecretsManagerFeatures(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 Enterprise2020PasswordManagerFeatures : PasswordManagerPlanFeatures
{
public Enterprise2020PasswordManagerFeatures(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;
}
}
}
}
8 changes: 4 additions & 4 deletions src/Core/Models/StaticStore/Plans/EnterprisePlan.cs
Original file line number Diff line number Diff line change
Expand Up @@ -85,14 +85,14 @@ public EnterprisePasswordManagerFeatures(bool isAnnual)
{
AdditionalStoragePricePerGb = 4;
StripeStoragePlanId = "storage-gb-annually";
StripeSeatPlanId = "2020-enterprise-org-seat-annually";
SeatPrice = 60;
StripeSeatPlanId = "2023-enterprise-org-seat-annually";
SeatPrice = 72;
}
else
{
StripeSeatPlanId = "2020-enterprise-seat-monthly";
StripeSeatPlanId = "2023-enterprise-seat-monthly";
StripeStoragePlanId = "storage-gb-monthly";
SeatPrice = 6;
SeatPrice = 7;
AdditionalStoragePricePerGb = 0.5M;
}
}
Expand Down
1 change: 1 addition & 0 deletions src/Core/Models/StaticStore/Plans/Families2019Plan.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ public Families2019Plan()

HasSelfHost = true;
HasTotp = true;
UsersGetPremium = true;

UpgradeSortOrder = 1;
DisplaySortOrder = 1;
Expand Down
Loading
Loading