1
0
mirror of https://github.com/bitwarden/server.git synced 2025-06-30 15:42:48 -05: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

@ -4,6 +4,7 @@ using Bit.Api.Billing.Models.Requests;
using Bit.Api.Billing.Models.Responses;
using Bit.Core.Billing.Models;
using Bit.Core.Billing.Models.Sales;
using Bit.Core.Billing.Pricing;
using Bit.Core.Billing.Services;
using Bit.Core.Context;
using Bit.Core.Repositories;
@ -21,6 +22,7 @@ public class OrganizationBillingController(
IOrganizationBillingService organizationBillingService,
IOrganizationRepository organizationRepository,
IPaymentService paymentService,
IPricingClient pricingClient,
ISubscriberService subscriberService,
IPaymentHistoryService paymentHistoryService,
IUserService userService) : BaseBillingController
@ -279,7 +281,7 @@ public class OrganizationBillingController(
}
var organizationSignup = model.ToOrganizationSignup(user);
var sale = OrganizationSale.From(organization, organizationSignup);
var plan = StaticStore.GetPlan(model.PlanType);
var plan = await pricingClient.GetPlanOrThrow(model.PlanType);
sale.Organization.PlanType = plan.Type;
sale.Organization.Plan = plan.Name;
sale.SubscriptionSetup.SkipTrial = true;

View File

@ -8,6 +8,7 @@ using Bit.Core.AdminConsole.Entities;
using Bit.Core.Billing.Constants;
using Bit.Core.Billing.Entities;
using Bit.Core.Billing.Models;
using Bit.Core.Billing.Pricing;
using Bit.Core.Billing.Repositories;
using Bit.Core.Billing.Services;
using Bit.Core.Context;
@ -45,7 +46,8 @@ public class OrganizationsController(
IAddSecretsManagerSubscriptionCommand addSecretsManagerSubscriptionCommand,
IReferenceEventService referenceEventService,
ISubscriberService subscriberService,
IOrganizationInstallationRepository organizationInstallationRepository)
IOrganizationInstallationRepository organizationInstallationRepository,
IPricingClient pricingClient)
: Controller
{
[HttpGet("{id:guid}/subscription")]
@ -62,26 +64,28 @@ public class OrganizationsController(
throw new NotFoundException();
}
if (!globalSettings.SelfHosted && organization.Gateway != null)
{
var subscriptionInfo = await paymentService.GetSubscriptionAsync(organization);
if (subscriptionInfo == null)
{
throw new NotFoundException();
}
var hideSensitiveData = !await currentContext.EditSubscription(id);
return new OrganizationSubscriptionResponseModel(organization, subscriptionInfo, hideSensitiveData);
}
if (globalSettings.SelfHosted)
{
var orgLicense = await licensingService.ReadOrganizationLicenseAsync(organization);
return new OrganizationSubscriptionResponseModel(organization, orgLicense);
}
return new OrganizationSubscriptionResponseModel(organization);
var plan = await pricingClient.GetPlanOrThrow(organization.PlanType);
if (string.IsNullOrEmpty(organization.GatewaySubscriptionId))
{
return new OrganizationSubscriptionResponseModel(organization, plan);
}
var subscriptionInfo = await paymentService.GetSubscriptionAsync(organization);
if (subscriptionInfo == null)
{
throw new NotFoundException();
}
var hideSensitiveData = !await currentContext.EditSubscription(id);
return new OrganizationSubscriptionResponseModel(organization, subscriptionInfo, plan, hideSensitiveData);
}
[HttpGet("{id:guid}/license")]
@ -165,7 +169,8 @@ public class OrganizationsController(
organization = await AdjustOrganizationSeatsForSmTrialAsync(id, organization, model);
var organizationUpdate = model.ToSecretsManagerSubscriptionUpdate(organization);
var plan = await pricingClient.GetPlanOrThrow(organization.PlanType);
var organizationUpdate = model.ToSecretsManagerSubscriptionUpdate(organization, plan);
await updateSecretsManagerSubscriptionCommand.UpdateSubscriptionAsync(organizationUpdate);

View File

@ -2,6 +2,7 @@
using Bit.Api.Billing.Models.Responses;
using Bit.Core.AdminConsole.Repositories;
using Bit.Core.Billing.Models;
using Bit.Core.Billing.Pricing;
using Bit.Core.Billing.Repositories;
using Bit.Core.Billing.Services;
using Bit.Core.Context;
@ -20,6 +21,7 @@ namespace Bit.Api.Billing.Controllers;
public class ProviderBillingController(
ICurrentContext currentContext,
ILogger<BaseProviderController> logger,
IPricingClient pricingClient,
IProviderBillingService providerBillingService,
IProviderPlanRepository providerPlanRepository,
IProviderRepository providerRepository,
@ -84,13 +86,25 @@ public class ProviderBillingController(
var providerPlans = await providerPlanRepository.GetByProviderId(provider.Id);
var configuredProviderPlans = await Task.WhenAll(providerPlans.Select(async providerPlan =>
{
var plan = await pricingClient.GetPlanOrThrow(providerPlan.PlanType);
return new ConfiguredProviderPlan(
providerPlan.Id,
providerPlan.ProviderId,
plan,
providerPlan.SeatMinimum ?? 0,
providerPlan.PurchasedSeats ?? 0,
providerPlan.AllocatedSeats ?? 0);
}));
var taxInformation = GetTaxInformation(subscription.Customer);
var subscriptionSuspension = await GetSubscriptionSuspensionAsync(stripeAdapter, subscription);
var response = ProviderSubscriptionResponse.From(
subscription,
providerPlans,
configuredProviderPlans,
taxInformation,
subscriptionSuspension,
provider);

View File

@ -1,9 +1,7 @@
using Bit.Core.AdminConsole.Entities.Provider;
using Bit.Core.AdminConsole.Enums.Provider;
using Bit.Core.Billing.Entities;
using Bit.Core.Billing.Enums;
using Bit.Core.Billing.Models;
using Bit.Core.Utilities;
using Stripe;
namespace Bit.Api.Billing.Models.Responses;
@ -25,26 +23,24 @@ public record ProviderSubscriptionResponse(
public static ProviderSubscriptionResponse From(
Subscription subscription,
ICollection<ProviderPlan> providerPlans,
ICollection<ConfiguredProviderPlan> providerPlans,
TaxInformation taxInformation,
SubscriptionSuspension subscriptionSuspension,
Provider provider)
{
var providerPlanResponses = providerPlans
.Where(providerPlan => providerPlan.IsConfigured())
.Select(ConfiguredProviderPlan.From)
.Select(configuredProviderPlan =>
.Select(providerPlan =>
{
var plan = StaticStore.GetPlan(configuredProviderPlan.PlanType);
var cost = (configuredProviderPlan.SeatMinimum + configuredProviderPlan.PurchasedSeats) * plan.PasswordManager.ProviderPortalSeatPrice;
var plan = providerPlan.Plan;
var cost = (providerPlan.SeatMinimum + providerPlan.PurchasedSeats) * plan.PasswordManager.ProviderPortalSeatPrice;
var cadence = plan.IsAnnual ? _annualCadence : _monthlyCadence;
return new ProviderPlanResponse(
plan.Name,
plan.Type,
plan.ProductTier,
configuredProviderPlan.SeatMinimum,
configuredProviderPlan.PurchasedSeats,
configuredProviderPlan.AssignedSeats,
providerPlan.SeatMinimum,
providerPlan.PurchasedSeats,
providerPlan.AssignedSeats,
cost,
cadence);
});

View File

@ -1,6 +1,7 @@
using System.Net;
using Bit.Api.Billing.Public.Models;
using Bit.Api.Models.Public.Response;
using Bit.Core.Billing.Pricing;
using Bit.Core.Context;
using Bit.Core.OrganizationFeatures.OrganizationSubscriptions.Interface;
using Bit.Core.Repositories;
@ -21,19 +22,22 @@ public class OrganizationController : Controller
private readonly IOrganizationRepository _organizationRepository;
private readonly IUpdateSecretsManagerSubscriptionCommand _updateSecretsManagerSubscriptionCommand;
private readonly ILogger<OrganizationController> _logger;
private readonly IPricingClient _pricingClient;
public OrganizationController(
IOrganizationService organizationService,
ICurrentContext currentContext,
IOrganizationRepository organizationRepository,
IUpdateSecretsManagerSubscriptionCommand updateSecretsManagerSubscriptionCommand,
ILogger<OrganizationController> logger)
ILogger<OrganizationController> logger,
IPricingClient pricingClient)
{
_organizationService = organizationService;
_currentContext = currentContext;
_organizationRepository = organizationRepository;
_updateSecretsManagerSubscriptionCommand = updateSecretsManagerSubscriptionCommand;
_logger = logger;
_pricingClient = pricingClient;
}
/// <summary>
@ -140,7 +144,8 @@ public class OrganizationController : Controller
return "Organization has no access to Secrets Manager.";
}
var secretsManagerUpdate = model.SecretsManager.ToSecretsManagerSubscriptionUpdate(organization);
var plan = await _pricingClient.GetPlanOrThrow(organization.PlanType);
var secretsManagerUpdate = model.SecretsManager.ToSecretsManagerSubscriptionUpdate(organization, plan);
await _updateSecretsManagerSubscriptionCommand.UpdateSubscriptionAsync(secretsManagerUpdate);
return string.Empty;

View File

@ -1,6 +1,7 @@
using System.ComponentModel.DataAnnotations;
using Bit.Core.AdminConsole.Entities;
using Bit.Core.Models.Business;
using Bit.Core.Models.StaticStore;
namespace Bit.Api.Billing.Public.Models;
@ -93,17 +94,17 @@ public class SecretsManagerSubscriptionUpdateModel
set { _maxAutoScaleServiceAccounts = value < 0 ? null : value; }
}
public virtual SecretsManagerSubscriptionUpdate ToSecretsManagerSubscriptionUpdate(Organization organization)
public virtual SecretsManagerSubscriptionUpdate ToSecretsManagerSubscriptionUpdate(Organization organization, Plan plan)
{
var update = UpdateUpdateMaxAutoScale(organization);
var update = UpdateUpdateMaxAutoScale(organization, plan);
UpdateSeats(organization, update);
UpdateServiceAccounts(organization, update);
return update;
}
private SecretsManagerSubscriptionUpdate UpdateUpdateMaxAutoScale(Organization organization)
private SecretsManagerSubscriptionUpdate UpdateUpdateMaxAutoScale(Organization organization, Plan plan)
{
var update = new SecretsManagerSubscriptionUpdate(organization, false)
var update = new SecretsManagerSubscriptionUpdate(organization, plan, false)
{
MaxAutoscaleSmSeats = MaxAutoScaleSeats ?? organization.MaxAutoscaleSmSeats,
MaxAutoscaleSmServiceAccounts = MaxAutoScaleServiceAccounts ?? organization.MaxAutoscaleSmServiceAccounts