mirror of
https://github.com/bitwarden/server.git
synced 2025-01-07 19:37:51 +01:00
Merge branch 'main' into pm-15807-Move-subscription-to-canceled-7-days-after-unpai'
This commit is contained in:
commit
8392ddc4c2
@ -34,6 +34,9 @@ public static class ServiceCollectionExtensions
|
||||
Url = new Uri("https://github.com/bitwarden/server/blob/master/LICENSE.txt")
|
||||
}
|
||||
});
|
||||
|
||||
config.CustomSchemaIds(type => type.FullName);
|
||||
|
||||
config.SwaggerDoc("internal", new OpenApiInfo { Title = "Bitwarden Internal API", Version = "latest" });
|
||||
|
||||
config.AddSecurityDefinition("oauth2-client-credentials", new OpenApiSecurityScheme
|
||||
|
@ -17,6 +17,7 @@ public static class ServiceCollectionExtensions
|
||||
services.AddTransient<IPremiumUserBillingService, PremiumUserBillingService>();
|
||||
services.AddTransient<ISetupIntentCache, SetupIntentDistributedCache>();
|
||||
services.AddTransient<ISubscriberService, SubscriberService>();
|
||||
// services.AddSingleton<IPricingClient, PricingClient>();
|
||||
services.AddLicenseServices();
|
||||
}
|
||||
}
|
||||
|
@ -8,8 +8,11 @@ public abstract record Plan
|
||||
public ProductTierType ProductTier { get; protected init; }
|
||||
public string Name { get; protected init; }
|
||||
public bool IsAnnual { get; protected init; }
|
||||
// TODO: Move to the client
|
||||
public string NameLocalizationKey { get; protected init; }
|
||||
// TODO: Move to the client
|
||||
public string DescriptionLocalizationKey { get; protected init; }
|
||||
// TODO: Remove
|
||||
public bool CanBeUsedByBusiness { get; protected init; }
|
||||
public int? TrialPeriodDays { get; protected init; }
|
||||
public bool HasSelfHost { get; protected init; }
|
||||
@ -27,7 +30,9 @@ public abstract record Plan
|
||||
public bool UsersGetPremium { get; protected init; }
|
||||
public bool HasCustomPermissions { get; protected init; }
|
||||
public int UpgradeSortOrder { get; protected init; }
|
||||
// TODO: Move to the client
|
||||
public int DisplaySortOrder { get; protected init; }
|
||||
// TODO: Remove
|
||||
public int? LegacyYear { get; protected init; }
|
||||
public bool Disabled { get; protected init; }
|
||||
public PasswordManagerPlanFeatures PasswordManager { get; protected init; }
|
||||
@ -45,15 +50,19 @@ public abstract record Plan
|
||||
public string StripeServiceAccountPlanId { get; init; }
|
||||
public decimal? AdditionalPricePerServiceAccount { get; init; }
|
||||
public short BaseServiceAccount { get; init; }
|
||||
// TODO: Unused, remove
|
||||
public short? MaxAdditionalServiceAccount { get; init; }
|
||||
public bool HasAdditionalServiceAccountOption { get; init; }
|
||||
// Seats
|
||||
public string StripeSeatPlanId { get; init; }
|
||||
public bool HasAdditionalSeatsOption { get; init; }
|
||||
// TODO: Remove, SM is never packaged
|
||||
public decimal BasePrice { get; init; }
|
||||
public decimal SeatPrice { get; init; }
|
||||
// TODO: Remove, SM is never packaged
|
||||
public int BaseSeats { get; init; }
|
||||
public short? MaxSeats { get; init; }
|
||||
// TODO: Unused, remove
|
||||
public int? MaxAdditionalSeats { get; init; }
|
||||
public bool AllowSeatAutoscale { get; init; }
|
||||
|
||||
@ -72,8 +81,10 @@ public abstract record Plan
|
||||
public decimal ProviderPortalSeatPrice { get; init; }
|
||||
public bool AllowSeatAutoscale { get; init; }
|
||||
public bool HasAdditionalSeatsOption { get; init; }
|
||||
// TODO: Remove, never set.
|
||||
public int? MaxAdditionalSeats { get; init; }
|
||||
public int BaseSeats { get; init; }
|
||||
// TODO: Remove premium access as it's deprecated
|
||||
public bool HasPremiumAccessOption { get; init; }
|
||||
public string StripePremiumAccessPlanId { get; init; }
|
||||
public decimal PremiumAccessOptionPrice { get; init; }
|
||||
@ -83,6 +94,7 @@ public abstract record Plan
|
||||
public bool HasAdditionalStorageOption { get; init; }
|
||||
public decimal AdditionalStoragePricePerGb { get; init; }
|
||||
public string StripeStoragePlanId { get; init; }
|
||||
// TODO: Remove
|
||||
public short? MaxAdditionalStorage { get; init; }
|
||||
// Feature
|
||||
public short? MaxCollections { get; init; }
|
||||
|
12
src/Core/Billing/Pricing/IPricingClient.cs
Normal file
12
src/Core/Billing/Pricing/IPricingClient.cs
Normal file
@ -0,0 +1,12 @@
|
||||
using Bit.Core.Billing.Enums;
|
||||
using Bit.Core.Models.StaticStore;
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace Bit.Core.Billing.Pricing;
|
||||
|
||||
public interface IPricingClient
|
||||
{
|
||||
Task<Plan?> GetPlan(PlanType planType);
|
||||
Task<List<Plan>> ListPlans();
|
||||
}
|
232
src/Core/Billing/Pricing/PlanAdapter.cs
Normal file
232
src/Core/Billing/Pricing/PlanAdapter.cs
Normal file
@ -0,0 +1,232 @@
|
||||
using Bit.Core.Billing.Enums;
|
||||
using Bit.Core.Models.StaticStore;
|
||||
using Proto.Billing.Pricing;
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace Bit.Core.Billing.Pricing;
|
||||
|
||||
public record PlanAdapter : Plan
|
||||
{
|
||||
public PlanAdapter(PlanResponse planResponse)
|
||||
{
|
||||
Type = ToPlanType(planResponse.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;
|
||||
HasSelfHost = HasFeature("selfHost");
|
||||
HasPolicies = HasFeature("policies");
|
||||
HasGroups = HasFeature("groups");
|
||||
HasDirectory = HasFeature("directory");
|
||||
HasEvents = HasFeature("events");
|
||||
HasTotp = HasFeature("totp");
|
||||
Has2fa = HasFeature("2fa");
|
||||
HasApi = HasFeature("api");
|
||||
HasSso = HasFeature("sso");
|
||||
HasKeyConnector = HasFeature("keyConnector");
|
||||
HasScim = HasFeature("scim");
|
||||
HasResetPassword = HasFeature("resetPassword");
|
||||
UsersGetPremium = HasFeature("usersGetPremium");
|
||||
UpgradeSortOrder = planResponse.AdditionalData != null
|
||||
? int.Parse(planResponse.AdditionalData["upgradeSortOrder"])
|
||||
: 0;
|
||||
DisplaySortOrder = planResponse.AdditionalData != null
|
||||
? int.Parse(planResponse.AdditionalData["displaySortOrder"])
|
||||
: 0;
|
||||
HasCustomPermissions = HasFeature("customPermissions");
|
||||
Disabled = !planResponse.Available;
|
||||
PasswordManager = ToPasswordManagerPlanFeatures(planResponse);
|
||||
SecretsManager = planResponse.SecretsManager != null ? ToSecretsManagerPlanFeatures(planResponse) : null;
|
||||
|
||||
return;
|
||||
|
||||
bool HasFeature(string lookupKey) => planResponse.Features.Any(feature => feature.LookupKey == lookupKey);
|
||||
}
|
||||
|
||||
#region Mappings
|
||||
|
||||
private static PlanType ToPlanType(string lookupKey)
|
||||
=> lookupKey switch
|
||||
{
|
||||
"enterprise-annually" => PlanType.EnterpriseAnnually,
|
||||
"enterprise-annually-2019" => PlanType.EnterpriseAnnually2019,
|
||||
"enterprise-annually-2020" => PlanType.EnterpriseAnnually2020,
|
||||
"enterprise-annually-2023" => PlanType.EnterpriseAnnually2023,
|
||||
"enterprise-monthly" => PlanType.EnterpriseMonthly,
|
||||
"enterprise-monthly-2019" => PlanType.EnterpriseMonthly2019,
|
||||
"enterprise-monthly-2020" => PlanType.EnterpriseMonthly2020,
|
||||
"enterprise-monthly-2023" => PlanType.EnterpriseMonthly2023,
|
||||
"families" => PlanType.FamiliesAnnually,
|
||||
"families-2019" => PlanType.FamiliesAnnually2019,
|
||||
"free" => PlanType.Free,
|
||||
"teams-annually" => PlanType.TeamsAnnually,
|
||||
"teams-annually-2019" => PlanType.TeamsAnnually2019,
|
||||
"teams-annually-2020" => PlanType.TeamsAnnually2020,
|
||||
"teams-annually-2023" => PlanType.TeamsAnnually2023,
|
||||
"teams-monthly" => PlanType.TeamsMonthly,
|
||||
"teams-monthly-2019" => PlanType.TeamsMonthly2019,
|
||||
"teams-monthly-2020" => PlanType.TeamsMonthly2020,
|
||||
"teams-monthly-2023" => PlanType.TeamsMonthly2023,
|
||||
"teams-starter" => PlanType.TeamsStarter,
|
||||
"teams-starter-2023" => PlanType.TeamsStarter2023,
|
||||
_ => throw new BillingException() // TODO: Flesh out
|
||||
};
|
||||
|
||||
private static ProductTierType ToProductTierType(PlanType planType)
|
||||
=> planType switch
|
||||
{
|
||||
PlanType.Free => ProductTierType.Free,
|
||||
PlanType.FamiliesAnnually or PlanType.FamiliesAnnually2019 => ProductTierType.Families,
|
||||
PlanType.TeamsStarter or PlanType.TeamsStarter2023 => ProductTierType.TeamsStarter,
|
||||
_ when planType.ToString().Contains("Teams") => ProductTierType.Teams,
|
||||
_ when planType.ToString().Contains("Enterprise") => ProductTierType.Enterprise,
|
||||
_ => throw new BillingException() // TODO: Flesh out
|
||||
};
|
||||
|
||||
private static PasswordManagerPlanFeatures ToPasswordManagerPlanFeatures(PlanResponse planResponse)
|
||||
{
|
||||
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;
|
||||
|
||||
return new PasswordManagerPlanFeatures
|
||||
{
|
||||
StripePlanId = stripePlanId,
|
||||
StripeSeatPlanId = stripeSeatPlanId,
|
||||
StripeProviderPortalSeatPlanId = stripeProviderPortalSeatPlanId,
|
||||
BasePrice = basePrice,
|
||||
SeatPrice = seatPrice,
|
||||
ProviderPortalSeatPrice = providerPortalSeatPrice,
|
||||
AllowSeatAutoscale = scales,
|
||||
HasAdditionalSeatsOption = scales,
|
||||
BaseSeats = baseSeats,
|
||||
MaxSeats = maxSeats,
|
||||
BaseStorageGb = baseStorageGb,
|
||||
HasAdditionalStorageOption = hasAdditionalStorageOption,
|
||||
StripeStoragePlanId = stripeStoragePlanId,
|
||||
MaxCollections = maxCollections
|
||||
};
|
||||
}
|
||||
|
||||
private static SecretsManagerPlanFeatures ToSecretsManagerPlanFeatures(PlanResponse planResponse)
|
||||
{
|
||||
var seats = planResponse.SecretsManager.Seats;
|
||||
var serviceAccounts = planResponse.SecretsManager.ServiceAccounts;
|
||||
|
||||
var maxServiceAccounts = GetMaxServiceAccounts(serviceAccounts);
|
||||
var allowServiceAccountsAutoscale = serviceAccounts.KindCase == FreeOrScalableDTO.KindOneofCase.Scalable;
|
||||
var stripeServiceAccountPlanId = GetStripeServiceAccountPlanId(serviceAccounts);
|
||||
var additionalPricePerServiceAccount = GetAdditionalPricePerServiceAccount(serviceAccounts);
|
||||
var baseServiceAccount = GetBaseServiceAccount(serviceAccounts);
|
||||
var hasAdditionalServiceAccountOption = serviceAccounts.KindCase == FreeOrScalableDTO.KindOneofCase.Scalable;
|
||||
var stripeSeatPlanId = GetStripeSeatPlanId(seats);
|
||||
var hasAdditionalSeatsOption = seats.KindCase == FreeOrScalableDTO.KindOneofCase.Scalable;
|
||||
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;
|
||||
|
||||
return new SecretsManagerPlanFeatures
|
||||
{
|
||||
MaxServiceAccounts = maxServiceAccounts,
|
||||
AllowServiceAccountsAutoscale = allowServiceAccountsAutoscale,
|
||||
StripeServiceAccountPlanId = stripeServiceAccountPlanId,
|
||||
AdditionalPricePerServiceAccount = additionalPricePerServiceAccount,
|
||||
BaseServiceAccount = baseServiceAccount,
|
||||
HasAdditionalServiceAccountOption = hasAdditionalServiceAccountOption,
|
||||
StripeSeatPlanId = stripeSeatPlanId,
|
||||
HasAdditionalSeatsOption = hasAdditionalSeatsOption,
|
||||
SeatPrice = seatPrice,
|
||||
MaxSeats = maxSeats,
|
||||
AllowSeatAutoscale = allowSeatAutoscale,
|
||||
MaxProjects = maxProjects
|
||||
};
|
||||
}
|
||||
|
||||
private static decimal? GetAdditionalPricePerServiceAccount(FreeOrScalableDTO freeOrScalable)
|
||||
=> freeOrScalable.KindCase != FreeOrScalableDTO.KindOneofCase.Scalable
|
||||
? null
|
||||
: decimal.Parse(freeOrScalable.Scalable.Price);
|
||||
|
||||
private static decimal GetBasePrice(PurchasableDTO purchasable)
|
||||
=> purchasable.KindCase != PurchasableDTO.KindOneofCase.Packaged ? 0 : decimal.Parse(purchasable.Packaged.Price);
|
||||
|
||||
private static int GetBaseSeats(PurchasableDTO purchasable)
|
||||
=> purchasable.KindCase != PurchasableDTO.KindOneofCase.Packaged ? 0 : purchasable.Packaged.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
|
||||
};
|
||||
|
||||
private static short? GetMaxSeats(PurchasableDTO purchasable)
|
||||
=> purchasable.KindCase != PurchasableDTO.KindOneofCase.Free ? null : (short)purchasable.Free.Quantity;
|
||||
|
||||
private static short? GetMaxSeats(FreeOrScalableDTO freeOrScalable)
|
||||
=> freeOrScalable.KindCase != FreeOrScalableDTO.KindOneofCase.Free ? null : (short)freeOrScalable.Free.Quantity;
|
||||
|
||||
private static short? GetMaxServiceAccounts(FreeOrScalableDTO freeOrScalable)
|
||||
=> freeOrScalable.KindCase != FreeOrScalableDTO.KindOneofCase.Free ? null : (short)freeOrScalable.Free.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
|
||||
};
|
||||
|
||||
private static decimal GetSeatPrice(FreeOrScalableDTO freeOrScalable)
|
||||
=> freeOrScalable.KindCase != FreeOrScalableDTO.KindOneofCase.Scalable
|
||||
? 0
|
||||
: decimal.Parse(freeOrScalable.Scalable.Price);
|
||||
|
||||
private static string? GetStripePlanId(PurchasableDTO purchasable)
|
||||
=> purchasable.KindCase != PurchasableDTO.KindOneofCase.Packaged ? null : purchasable.Packaged.StripePriceId;
|
||||
|
||||
private static string? GetStripeSeatPlanId(PurchasableDTO purchasable)
|
||||
=> purchasable.KindCase switch
|
||||
{
|
||||
PurchasableDTO.KindOneofCase.Packaged => purchasable.Packaged.Additional?.StripePriceId,
|
||||
PurchasableDTO.KindOneofCase.Scalable => purchasable.Scalable.StripePriceId,
|
||||
_ => null
|
||||
};
|
||||
|
||||
private static string? GetStripeSeatPlanId(FreeOrScalableDTO freeOrScalable)
|
||||
=> freeOrScalable.KindCase != FreeOrScalableDTO.KindOneofCase.Scalable
|
||||
? null
|
||||
: freeOrScalable.Scalable.StripePriceId;
|
||||
|
||||
private static string? GetStripeServiceAccountPlanId(FreeOrScalableDTO freeOrScalable)
|
||||
=> freeOrScalable.KindCase != FreeOrScalableDTO.KindOneofCase.Scalable
|
||||
? null
|
||||
: freeOrScalable.Scalable.StripePriceId;
|
||||
|
||||
#endregion
|
||||
}
|
92
src/Core/Billing/Pricing/PricingClient.cs
Normal file
92
src/Core/Billing/Pricing/PricingClient.cs
Normal file
@ -0,0 +1,92 @@
|
||||
using Bit.Core.Billing.Enums;
|
||||
using Bit.Core.Models.StaticStore;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Settings;
|
||||
using Bit.Core.Utilities;
|
||||
using Google.Protobuf.WellKnownTypes;
|
||||
using Grpc.Core;
|
||||
using Grpc.Net.Client;
|
||||
using Proto.Billing.Pricing;
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace Bit.Core.Billing.Pricing;
|
||||
|
||||
public class PricingClient(
|
||||
IFeatureService featureService,
|
||||
GlobalSettings globalSettings) : IPricingClient
|
||||
{
|
||||
public async Task<Plan?> GetPlan(PlanType planType)
|
||||
{
|
||||
var usePricingService = featureService.IsEnabled(FeatureFlagKeys.UsePricingService);
|
||||
|
||||
if (!usePricingService)
|
||||
{
|
||||
return StaticStore.GetPlan(planType);
|
||||
}
|
||||
|
||||
using var channel = GrpcChannel.ForAddress(globalSettings.PricingUri);
|
||||
var client = new PasswordManager.PasswordManagerClient(channel);
|
||||
|
||||
var lookupKey = ToLookupKey(planType);
|
||||
if (string.IsNullOrEmpty(lookupKey))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var response =
|
||||
await client.GetPlanByLookupKeyAsync(new GetPlanByLookupKeyRequest { LookupKey = lookupKey });
|
||||
|
||||
return new PlanAdapter(response);
|
||||
}
|
||||
catch (RpcException rpcException) when (rpcException.StatusCode == StatusCode.NotFound)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<List<Plan>> ListPlans()
|
||||
{
|
||||
var usePricingService = featureService.IsEnabled(FeatureFlagKeys.UsePricingService);
|
||||
|
||||
if (!usePricingService)
|
||||
{
|
||||
return StaticStore.Plans.ToList();
|
||||
}
|
||||
|
||||
using var channel = GrpcChannel.ForAddress(globalSettings.PricingUri);
|
||||
var client = new PasswordManager.PasswordManagerClient(channel);
|
||||
|
||||
var response = await client.ListPlansAsync(new Empty());
|
||||
return response.Plans.Select(Plan (plan) => new PlanAdapter(plan)).ToList();
|
||||
}
|
||||
|
||||
private static string? ToLookupKey(PlanType planType)
|
||||
=> planType switch
|
||||
{
|
||||
PlanType.EnterpriseAnnually => "enterprise-annually",
|
||||
PlanType.EnterpriseAnnually2019 => "enterprise-annually-2019",
|
||||
PlanType.EnterpriseAnnually2020 => "enterprise-annually-2020",
|
||||
PlanType.EnterpriseAnnually2023 => "enterprise-annually-2023",
|
||||
PlanType.EnterpriseMonthly => "enterprise-monthly",
|
||||
PlanType.EnterpriseMonthly2019 => "enterprise-monthly-2019",
|
||||
PlanType.EnterpriseMonthly2020 => "enterprise-monthly-2020",
|
||||
PlanType.EnterpriseMonthly2023 => "enterprise-monthly-2023",
|
||||
PlanType.FamiliesAnnually => "families",
|
||||
PlanType.FamiliesAnnually2019 => "families-2019",
|
||||
PlanType.Free => "free",
|
||||
PlanType.TeamsAnnually => "teams-annually",
|
||||
PlanType.TeamsAnnually2019 => "teams-annually-2019",
|
||||
PlanType.TeamsAnnually2020 => "teams-annually-2020",
|
||||
PlanType.TeamsAnnually2023 => "teams-annually-2023",
|
||||
PlanType.TeamsMonthly => "teams-monthly",
|
||||
PlanType.TeamsMonthly2019 => "teams-monthly-2019",
|
||||
PlanType.TeamsMonthly2020 => "teams-monthly-2020",
|
||||
PlanType.TeamsMonthly2023 => "teams-monthly-2023",
|
||||
PlanType.TeamsStarter => "teams-starter",
|
||||
PlanType.TeamsStarter2023 => "teams-starter-2023",
|
||||
_ => null
|
||||
};
|
||||
}
|
92
src/Core/Billing/Pricing/Protos/password-manager.proto
Normal file
92
src/Core/Billing/Pricing/Protos/password-manager.proto
Normal file
@ -0,0 +1,92 @@
|
||||
syntax = "proto3";
|
||||
|
||||
option csharp_namespace = "Proto.Billing.Pricing";
|
||||
|
||||
package plans;
|
||||
|
||||
import "google/protobuf/empty.proto";
|
||||
import "google/protobuf/struct.proto";
|
||||
import "google/protobuf/wrappers.proto";
|
||||
|
||||
service PasswordManager {
|
||||
rpc GetPlanByLookupKey (GetPlanByLookupKeyRequest) returns (PlanResponse);
|
||||
rpc ListPlans (google.protobuf.Empty) returns (ListPlansResponse);
|
||||
}
|
||||
|
||||
// Requests
|
||||
message GetPlanByLookupKeyRequest {
|
||||
string lookupKey = 1;
|
||||
}
|
||||
|
||||
// Responses
|
||||
message PlanResponse {
|
||||
string name = 1;
|
||||
string lookupKey = 2;
|
||||
string tier = 4;
|
||||
optional string cadence = 6;
|
||||
optional google.protobuf.Int32Value legacyYear = 8;
|
||||
bool available = 9;
|
||||
repeated FeatureDTO features = 10;
|
||||
PurchasableDTO seats = 11;
|
||||
optional ScalableDTO managedSeats = 12;
|
||||
optional ScalableDTO storage = 13;
|
||||
optional SecretsManagerPurchasablesDTO secretsManager = 14;
|
||||
optional google.protobuf.Int32Value trialPeriodDays = 15;
|
||||
repeated string canUpgradeTo = 16;
|
||||
map<string, string> additionalData = 17;
|
||||
}
|
||||
|
||||
message ListPlansResponse {
|
||||
repeated PlanResponse plans = 1;
|
||||
}
|
||||
|
||||
// DTOs
|
||||
message FeatureDTO {
|
||||
string name = 1;
|
||||
string lookupKey = 2;
|
||||
}
|
||||
|
||||
message FreeDTO {
|
||||
int32 quantity = 2;
|
||||
string type = 4;
|
||||
}
|
||||
|
||||
message PackagedDTO {
|
||||
message AdditionalSeats {
|
||||
string stripePriceId = 1;
|
||||
string price = 2;
|
||||
}
|
||||
|
||||
int32 quantity = 2;
|
||||
string stripePriceId = 3;
|
||||
string price = 4;
|
||||
optional AdditionalSeats additional = 5;
|
||||
string type = 6;
|
||||
}
|
||||
|
||||
message ScalableDTO {
|
||||
int32 provided = 2;
|
||||
string stripePriceId = 6;
|
||||
string price = 7;
|
||||
string type = 9;
|
||||
}
|
||||
|
||||
message PurchasableDTO {
|
||||
oneof kind {
|
||||
FreeDTO free = 1;
|
||||
PackagedDTO packaged = 2;
|
||||
ScalableDTO scalable = 3;
|
||||
}
|
||||
}
|
||||
|
||||
message FreeOrScalableDTO {
|
||||
oneof kind {
|
||||
FreeDTO free = 1;
|
||||
ScalableDTO scalable = 2;
|
||||
}
|
||||
}
|
||||
|
||||
message SecretsManagerPurchasablesDTO {
|
||||
FreeOrScalableDTO seats = 1;
|
||||
FreeOrScalableDTO serviceAccounts = 2;
|
||||
}
|
@ -164,6 +164,7 @@ public static class FeatureFlagKeys
|
||||
public const string AuthenticatorSyncAndroid = "enable-authenticator-sync-android";
|
||||
public const string AppReviewPrompt = "app-review-prompt";
|
||||
public const string ResellerManagedOrgAlert = "PM-15814-alert-owners-of-reseller-managed-orgs";
|
||||
public const string UsePricingService = "use-pricing-service";
|
||||
|
||||
public static List<string> GetAllKeys()
|
||||
{
|
||||
|
@ -25,6 +25,12 @@
|
||||
<PackageReference Include="AWSSDK.SQS" Version="3.7.400.64" />
|
||||
<PackageReference Include="Azure.Data.Tables" Version="12.9.0" />
|
||||
<PackageReference Include="Azure.Extensions.AspNetCore.DataProtection.Blobs" Version="1.3.4" />
|
||||
<PackageReference Include="Google.Protobuf" Version="3.29.2" />
|
||||
<PackageReference Include="Grpc.Net.Client" Version="2.67.0" />
|
||||
<PackageReference Include="Grpc.Tools" Version="2.68.1">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.AspNetCore.DataProtection" Version="8.0.10" />
|
||||
<PackageReference Include="Azure.Messaging.ServiceBus" Version="7.18.1" />
|
||||
<PackageReference Include="Azure.Storage.Blobs" Version="12.21.2" />
|
||||
@ -44,6 +50,7 @@
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Identity.Stores" Version="8.0.10" />
|
||||
<PackageReference Include="OneOf" Version="3.0.271" />
|
||||
<PackageReference Include="Quartz" Version="3.13.1" />
|
||||
<PackageReference Include="SendGrid" Version="9.29.3" />
|
||||
<PackageReference Include="Serilog.AspNetCore" Version="8.0.3" />
|
||||
@ -62,6 +69,10 @@
|
||||
<PackageReference Include="LaunchDarkly.ServerSdk" Version="8.6.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Protobuf Include="Billing\Pricing\Protos\password-manager.proto" GrpcServices="Client" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Resources\" />
|
||||
<Folder Include="Properties\" />
|
||||
|
@ -81,8 +81,8 @@ public class GlobalSettings : IGlobalSettings
|
||||
public virtual IDomainVerificationSettings DomainVerification { get; set; } = new DomainVerificationSettings();
|
||||
public virtual ILaunchDarklySettings LaunchDarkly { get; set; } = new LaunchDarklySettings();
|
||||
public virtual string DevelopmentDirectory { get; set; }
|
||||
|
||||
public virtual bool EnableEmailVerification { get; set; }
|
||||
public virtual string PricingUri { get; set; }
|
||||
|
||||
public string BuildExternalUri(string explicitValue, string name)
|
||||
{
|
||||
|
Loading…
Reference in New Issue
Block a user