1
0
mirror of https://github.com/bitwarden/server synced 2025-12-23 11:43:23 +00:00

[PM-16684] Integrate Pricing Service behind FF (#5276)

* Remove gRPC and convert PricingClient to HttpClient wrapper

* Add PlanType.GetProductTier extension

Many instances of StaticStore use are just to get the ProductTierType of a PlanType, but this can be derived from the PlanType itself without having to fetch the entire plan.

* Remove invocations of the StaticStore in non-Test code

* Deprecate StaticStore entry points

* Run dotnet format

* Matt's feedback

* Run dotnet format

* Rui's feedback

* Run dotnet format

* Replacements since approval

* Run dotnet format
This commit is contained in:
Alex Morask
2025-02-27 07:55:46 -05:00
committed by GitHub
parent bd66f06bd9
commit a2e665cb96
78 changed files with 1178 additions and 712 deletions

View File

@@ -1,6 +1,6 @@
using Bit.Core.Billing.Enums;
using Bit.Core.Billing.Pricing.Models;
using Bit.Core.Models.StaticStore;
using Proto.Billing.Pricing;
#nullable enable
@@ -8,15 +8,15 @@ namespace Bit.Core.Billing.Pricing;
public record PlanAdapter : Plan
{
public PlanAdapter(PlanResponse planResponse)
public PlanAdapter(PlanDTO plan)
{
Type = ToPlanType(planResponse.LookupKey);
Type = ToPlanType(plan.LookupKey);
ProductTier = ToProductTierType(Type);
Name = planResponse.Name;
IsAnnual = !string.IsNullOrEmpty(planResponse.Cadence) && planResponse.Cadence == "annually";
NameLocalizationKey = planResponse.AdditionalData?["nameLocalizationKey"];
DescriptionLocalizationKey = planResponse.AdditionalData?["descriptionLocalizationKey"];
TrialPeriodDays = planResponse.TrialPeriodDays;
Name = plan.Name;
IsAnnual = plan.Cadence is "annually";
NameLocalizationKey = plan.AdditionalData["nameLocalizationKey"];
DescriptionLocalizationKey = plan.AdditionalData["descriptionLocalizationKey"];
TrialPeriodDays = plan.TrialPeriodDays;
HasSelfHost = HasFeature("selfHost");
HasPolicies = HasFeature("policies");
HasGroups = HasFeature("groups");
@@ -30,20 +30,20 @@ public record PlanAdapter : Plan
HasScim = HasFeature("scim");
HasResetPassword = HasFeature("resetPassword");
UsersGetPremium = HasFeature("usersGetPremium");
UpgradeSortOrder = planResponse.AdditionalData != null
? int.Parse(planResponse.AdditionalData["upgradeSortOrder"])
UpgradeSortOrder = plan.AdditionalData.TryGetValue("upgradeSortOrder", out var upgradeSortOrder)
? int.Parse(upgradeSortOrder)
: 0;
DisplaySortOrder = planResponse.AdditionalData != null
? int.Parse(planResponse.AdditionalData["displaySortOrder"])
DisplaySortOrder = plan.AdditionalData.TryGetValue("displaySortOrder", out var displaySortOrder)
? int.Parse(displaySortOrder)
: 0;
HasCustomPermissions = HasFeature("customPermissions");
Disabled = !planResponse.Available;
PasswordManager = ToPasswordManagerPlanFeatures(planResponse);
SecretsManager = planResponse.SecretsManager != null ? ToSecretsManagerPlanFeatures(planResponse) : null;
Disabled = !plan.Available;
LegacyYear = plan.LegacyYear;
PasswordManager = ToPasswordManagerPlanFeatures(plan);
SecretsManager = plan.SecretsManager != null ? ToSecretsManagerPlanFeatures(plan) : null;
return;
bool HasFeature(string lookupKey) => planResponse.Features.Any(feature => feature.LookupKey == lookupKey);
bool HasFeature(string lookupKey) => plan.Features.Any(feature => feature.LookupKey == lookupKey);
}
#region Mappings
@@ -86,29 +86,25 @@ public record PlanAdapter : Plan
_ => throw new BillingException() // TODO: Flesh out
};
private static PasswordManagerPlanFeatures ToPasswordManagerPlanFeatures(PlanResponse planResponse)
private static PasswordManagerPlanFeatures ToPasswordManagerPlanFeatures(PlanDTO plan)
{
var stripePlanId = GetStripePlanId(planResponse.Seats);
var stripeSeatPlanId = GetStripeSeatPlanId(planResponse.Seats);
var stripeProviderPortalSeatPlanId = planResponse.ManagedSeats?.StripePriceId;
var basePrice = GetBasePrice(planResponse.Seats);
var seatPrice = GetSeatPrice(planResponse.Seats);
var providerPortalSeatPrice =
planResponse.ManagedSeats != null ? decimal.Parse(planResponse.ManagedSeats.Price) : 0;
var scales = planResponse.Seats.KindCase switch
{
PurchasableDTO.KindOneofCase.Scalable => true,
PurchasableDTO.KindOneofCase.Packaged => planResponse.Seats.Packaged.Additional != null,
_ => false
};
var baseSeats = GetBaseSeats(planResponse.Seats);
var maxSeats = GetMaxSeats(planResponse.Seats);
var baseStorageGb = (short?)planResponse.Storage?.Provided;
var hasAdditionalStorageOption = planResponse.Storage != null;
var stripeStoragePlanId = planResponse.Storage?.StripePriceId;
short? maxCollections =
planResponse.AdditionalData != null &&
planResponse.AdditionalData.TryGetValue("passwordManager.maxCollections", out var value) ? short.Parse(value) : null;
var stripePlanId = GetStripePlanId(plan.Seats);
var stripeSeatPlanId = GetStripeSeatPlanId(plan.Seats);
var stripeProviderPortalSeatPlanId = plan.ManagedSeats?.StripePriceId;
var basePrice = GetBasePrice(plan.Seats);
var seatPrice = GetSeatPrice(plan.Seats);
var providerPortalSeatPrice = plan.ManagedSeats?.Price ?? 0;
var scales = plan.Seats.Match(
_ => false,
packaged => packaged.Additional != null,
_ => true);
var baseSeats = GetBaseSeats(plan.Seats);
var maxSeats = GetMaxSeats(plan.Seats);
var baseStorageGb = (short?)plan.Storage?.Provided;
var hasAdditionalStorageOption = plan.Storage != null;
var additionalStoragePricePerGb = plan.Storage?.Price ?? 0;
var stripeStoragePlanId = plan.Storage?.StripePriceId;
short? maxCollections = plan.AdditionalData.TryGetValue("passwordManager.maxCollections", out var value) ? short.Parse(value) : null;
return new PasswordManagerPlanFeatures
{
@@ -124,30 +120,29 @@ public record PlanAdapter : Plan
MaxSeats = maxSeats,
BaseStorageGb = baseStorageGb,
HasAdditionalStorageOption = hasAdditionalStorageOption,
AdditionalStoragePricePerGb = additionalStoragePricePerGb,
StripeStoragePlanId = stripeStoragePlanId,
MaxCollections = maxCollections
};
}
private static SecretsManagerPlanFeatures ToSecretsManagerPlanFeatures(PlanResponse planResponse)
private static SecretsManagerPlanFeatures ToSecretsManagerPlanFeatures(PlanDTO plan)
{
var seats = planResponse.SecretsManager.Seats;
var serviceAccounts = planResponse.SecretsManager.ServiceAccounts;
var seats = plan.SecretsManager!.Seats;
var serviceAccounts = plan.SecretsManager.ServiceAccounts;
var maxServiceAccounts = GetMaxServiceAccounts(serviceAccounts);
var allowServiceAccountsAutoscale = serviceAccounts.KindCase == FreeOrScalableDTO.KindOneofCase.Scalable;
var allowServiceAccountsAutoscale = serviceAccounts.IsScalable;
var stripeServiceAccountPlanId = GetStripeServiceAccountPlanId(serviceAccounts);
var additionalPricePerServiceAccount = GetAdditionalPricePerServiceAccount(serviceAccounts);
var baseServiceAccount = GetBaseServiceAccount(serviceAccounts);
var hasAdditionalServiceAccountOption = serviceAccounts.KindCase == FreeOrScalableDTO.KindOneofCase.Scalable;
var hasAdditionalServiceAccountOption = serviceAccounts.IsScalable;
var stripeSeatPlanId = GetStripeSeatPlanId(seats);
var hasAdditionalSeatsOption = seats.KindCase == FreeOrScalableDTO.KindOneofCase.Scalable;
var hasAdditionalSeatsOption = seats.IsScalable;
var seatPrice = GetSeatPrice(seats);
var maxSeats = GetMaxSeats(seats);
var allowSeatAutoscale = seats.KindCase == FreeOrScalableDTO.KindOneofCase.Scalable;
var maxProjects =
planResponse.AdditionalData != null &&
planResponse.AdditionalData.TryGetValue("secretsManager.maxProjects", out var value) ? short.Parse(value) : 0;
var allowSeatAutoscale = seats.IsScalable;
var maxProjects = plan.AdditionalData.TryGetValue("secretsManager.maxProjects", out var value) ? short.Parse(value) : 0;
return new SecretsManagerPlanFeatures
{
@@ -167,66 +162,54 @@ public record PlanAdapter : Plan
}
private static decimal? GetAdditionalPricePerServiceAccount(FreeOrScalableDTO freeOrScalable)
=> freeOrScalable.KindCase != FreeOrScalableDTO.KindOneofCase.Scalable
? null
: decimal.Parse(freeOrScalable.Scalable.Price);
=> freeOrScalable.FromScalable(x => x.Price);
private static decimal GetBasePrice(PurchasableDTO purchasable)
=> purchasable.KindCase != PurchasableDTO.KindOneofCase.Packaged ? 0 : decimal.Parse(purchasable.Packaged.Price);
=> purchasable.FromPackaged(x => x.Price);
private static int GetBaseSeats(PurchasableDTO purchasable)
=> purchasable.KindCase != PurchasableDTO.KindOneofCase.Packaged ? 0 : purchasable.Packaged.Quantity;
=> purchasable.FromPackaged(x => x.Quantity);
private static short GetBaseServiceAccount(FreeOrScalableDTO freeOrScalable)
=> freeOrScalable.KindCase switch
{
FreeOrScalableDTO.KindOneofCase.Free => (short)freeOrScalable.Free.Quantity,
FreeOrScalableDTO.KindOneofCase.Scalable => (short)freeOrScalable.Scalable.Provided,
_ => 0
};
=> freeOrScalable.Match(
free => (short)free.Quantity,
scalable => (short)scalable.Provided);
private static short? GetMaxSeats(PurchasableDTO purchasable)
=> purchasable.KindCase != PurchasableDTO.KindOneofCase.Free ? null : (short)purchasable.Free.Quantity;
=> purchasable.Match<short?>(
free => (short)free.Quantity,
packaged => (short)packaged.Quantity,
_ => null);
private static short? GetMaxSeats(FreeOrScalableDTO freeOrScalable)
=> freeOrScalable.KindCase != FreeOrScalableDTO.KindOneofCase.Free ? null : (short)freeOrScalable.Free.Quantity;
=> freeOrScalable.FromFree(x => (short)x.Quantity);
private static short? GetMaxServiceAccounts(FreeOrScalableDTO freeOrScalable)
=> freeOrScalable.KindCase != FreeOrScalableDTO.KindOneofCase.Free ? null : (short)freeOrScalable.Free.Quantity;
=> freeOrScalable.FromFree(x => (short)x.Quantity);
private static decimal GetSeatPrice(PurchasableDTO purchasable)
=> purchasable.KindCase switch
{
PurchasableDTO.KindOneofCase.Packaged => purchasable.Packaged.Additional != null ? decimal.Parse(purchasable.Packaged.Additional.Price) : 0,
PurchasableDTO.KindOneofCase.Scalable => decimal.Parse(purchasable.Scalable.Price),
_ => 0
};
=> purchasable.Match(
_ => 0,
packaged => packaged.Additional?.Price ?? 0,
scalable => scalable.Price);
private static decimal GetSeatPrice(FreeOrScalableDTO freeOrScalable)
=> freeOrScalable.KindCase != FreeOrScalableDTO.KindOneofCase.Scalable
? 0
: decimal.Parse(freeOrScalable.Scalable.Price);
=> freeOrScalable.FromScalable(x => x.Price);
private static string? GetStripePlanId(PurchasableDTO purchasable)
=> purchasable.KindCase != PurchasableDTO.KindOneofCase.Packaged ? null : purchasable.Packaged.StripePriceId;
=> purchasable.FromPackaged(x => x.StripePriceId);
private static string? GetStripeSeatPlanId(PurchasableDTO purchasable)
=> purchasable.KindCase switch
{
PurchasableDTO.KindOneofCase.Packaged => purchasable.Packaged.Additional?.StripePriceId,
PurchasableDTO.KindOneofCase.Scalable => purchasable.Scalable.StripePriceId,
_ => null
};
=> purchasable.Match(
_ => null,
packaged => packaged.Additional?.StripePriceId,
scalable => scalable.StripePriceId);
private static string? GetStripeSeatPlanId(FreeOrScalableDTO freeOrScalable)
=> freeOrScalable.KindCase != FreeOrScalableDTO.KindOneofCase.Scalable
? null
: freeOrScalable.Scalable.StripePriceId;
=> freeOrScalable.FromScalable(x => x.StripePriceId);
private static string? GetStripeServiceAccountPlanId(FreeOrScalableDTO freeOrScalable)
=> freeOrScalable.KindCase != FreeOrScalableDTO.KindOneofCase.Scalable
? null
: freeOrScalable.Scalable.StripePriceId;
=> freeOrScalable.FromScalable(x => x.StripePriceId);
#endregion
}