1
0
mirror of https://github.com/bitwarden/server synced 2026-01-05 01:53:17 +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

@@ -2,6 +2,7 @@
using Bit.Core.AdminConsole.OrganizationFeatures.Organizations.Interfaces;
using Bit.Core.AdminConsole.Repositories;
using Bit.Core.Billing.Enums;
using Bit.Core.Billing.Pricing;
using Bit.Core.Context;
using Bit.Core.Platform.Push;
using Bit.Core.Repositories;
@@ -9,7 +10,6 @@ using Bit.Core.Services;
using Bit.Core.Tools.Enums;
using Bit.Core.Tools.Models.Business;
using Bit.Core.Tools.Services;
using Bit.Core.Utilities;
using Event = Stripe.Event;
namespace Bit.Billing.Services.Implementations;
@@ -28,6 +28,7 @@ public class PaymentSucceededHandler : IPaymentSucceededHandler
private readonly IStripeEventUtilityService _stripeEventUtilityService;
private readonly IPushNotificationService _pushNotificationService;
private readonly IOrganizationEnableCommand _organizationEnableCommand;
private readonly IPricingClient _pricingClient;
public PaymentSucceededHandler(
ILogger<PaymentSucceededHandler> logger,
@@ -41,7 +42,8 @@ public class PaymentSucceededHandler : IPaymentSucceededHandler
IStripeEventUtilityService stripeEventUtilityService,
IUserService userService,
IPushNotificationService pushNotificationService,
IOrganizationEnableCommand organizationEnableCommand)
IOrganizationEnableCommand organizationEnableCommand,
IPricingClient pricingClient)
{
_logger = logger;
_stripeEventService = stripeEventService;
@@ -55,6 +57,7 @@ public class PaymentSucceededHandler : IPaymentSucceededHandler
_userService = userService;
_pushNotificationService = pushNotificationService;
_organizationEnableCommand = organizationEnableCommand;
_pricingClient = pricingClient;
}
/// <summary>
@@ -96,9 +99,9 @@ public class PaymentSucceededHandler : IPaymentSucceededHandler
return;
}
var teamsMonthly = StaticStore.GetPlan(PlanType.TeamsMonthly);
var teamsMonthly = await _pricingClient.GetPlanOrThrow(PlanType.TeamsMonthly);
var enterpriseMonthly = StaticStore.GetPlan(PlanType.EnterpriseMonthly);
var enterpriseMonthly = await _pricingClient.GetPlanOrThrow(PlanType.EnterpriseMonthly);
var teamsMonthlyLineItem =
subscription.Items.Data.FirstOrDefault(item =>
@@ -137,14 +140,21 @@ public class PaymentSucceededHandler : IPaymentSucceededHandler
}
else if (organizationId.HasValue)
{
if (!subscription.Items.Any(i =>
StaticStore.Plans.Any(p => p.PasswordManager.StripePlanId == i.Plan.Id)))
var organization = await _organizationRepository.GetByIdAsync(organizationId.Value);
if (organization == null)
{
return;
}
var plan = await _pricingClient.GetPlanOrThrow(organization.PlanType);
if (subscription.Items.All(item => plan.PasswordManager.StripePlanId != item.Plan.Id))
{
return;
}
await _organizationEnableCommand.EnableAsync(organizationId.Value, subscription.CurrentPeriodEnd);
var organization = await _organizationRepository.GetByIdAsync(organizationId.Value);
await _pushNotificationService.PushSyncOrganizationStatusAsync(organization);
await _referenceEventService.RaiseEventAsync(

View File

@@ -1,15 +1,17 @@
using Bit.Billing.Constants;
using Bit.Core.AdminConsole.Repositories;
using Bit.Core.Billing.Entities;
using Bit.Core.Billing.Pricing;
using Bit.Core.Billing.Repositories;
using Bit.Core.Enums;
using Bit.Core.Utilities;
using Bit.Core.Repositories;
using Stripe;
namespace Bit.Billing.Services.Implementations;
public class ProviderEventService(
ILogger<ProviderEventService> logger,
IOrganizationRepository organizationRepository,
IPricingClient pricingClient,
IProviderInvoiceItemRepository providerInvoiceItemRepository,
IProviderOrganizationRepository providerOrganizationRepository,
IProviderPlanRepository providerPlanRepository,
@@ -54,7 +56,14 @@ public class ProviderEventService(
continue;
}
var plan = StaticStore.Plans.Single(x => x.Name == client.Plan && providerPlans.Any(y => y.PlanType == x.Type));
var organization = await organizationRepository.GetByIdAsync(client.OrganizationId);
if (organization == null)
{
return;
}
var plan = await pricingClient.GetPlanOrThrow(organization.PlanType);
var discountedPercentage = (100 - (invoice.Discount?.Coupon?.PercentOff ?? 0)) / 100;
@@ -76,7 +85,7 @@ public class ProviderEventService(
foreach (var providerPlan in providerPlans.Where(x => x.PurchasedSeats is null or 0))
{
var plan = StaticStore.GetPlan(providerPlan.PlanType);
var plan = await pricingClient.GetPlanOrThrow(providerPlan.PlanType);
var clientSeats = invoiceItems
.Where(item => item.PlanName == plan.Name)

View File

@@ -2,11 +2,11 @@
using Bit.Billing.Jobs;
using Bit.Core;
using Bit.Core.AdminConsole.OrganizationFeatures.Organizations.Interfaces;
using Bit.Core.Billing.Pricing;
using Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.Interfaces;
using Bit.Core.Platform.Push;
using Bit.Core.Repositories;
using Bit.Core.Services;
using Bit.Core.Utilities;
using Quartz;
using Stripe;
using Event = Stripe.Event;
@@ -27,6 +27,7 @@ public class SubscriptionUpdatedHandler : ISubscriptionUpdatedHandler
private readonly IFeatureService _featureService;
private readonly IOrganizationEnableCommand _organizationEnableCommand;
private readonly IOrganizationDisableCommand _organizationDisableCommand;
private readonly IPricingClient _pricingClient;
public SubscriptionUpdatedHandler(
IStripeEventService stripeEventService,
@@ -40,7 +41,8 @@ public class SubscriptionUpdatedHandler : ISubscriptionUpdatedHandler
ISchedulerFactory schedulerFactory,
IFeatureService featureService,
IOrganizationEnableCommand organizationEnableCommand,
IOrganizationDisableCommand organizationDisableCommand)
IOrganizationDisableCommand organizationDisableCommand,
IPricingClient pricingClient)
{
_stripeEventService = stripeEventService;
_stripeEventUtilityService = stripeEventUtilityService;
@@ -54,6 +56,7 @@ public class SubscriptionUpdatedHandler : ISubscriptionUpdatedHandler
_featureService = featureService;
_organizationEnableCommand = organizationEnableCommand;
_organizationDisableCommand = organizationDisableCommand;
_pricingClient = pricingClient;
}
/// <summary>
@@ -156,7 +159,8 @@ public class SubscriptionUpdatedHandler : ISubscriptionUpdatedHandler
/// </summary>
/// <param name="parsedEvent"></param>
/// <param name="subscription"></param>
private async Task RemovePasswordManagerCouponIfRemovingSecretsManagerTrialAsync(Event parsedEvent,
private async Task RemovePasswordManagerCouponIfRemovingSecretsManagerTrialAsync(
Event parsedEvent,
Subscription subscription)
{
if (parsedEvent.Data.PreviousAttributes?.items is null)
@@ -164,6 +168,22 @@ public class SubscriptionUpdatedHandler : ISubscriptionUpdatedHandler
return;
}
var organization = subscription.Metadata.TryGetValue("organizationId", out var organizationId)
? await _organizationRepository.GetByIdAsync(Guid.Parse(organizationId))
: null;
if (organization == null)
{
return;
}
var plan = await _pricingClient.GetPlanOrThrow(organization.PlanType);
if (!plan.SupportsSecretsManager)
{
return;
}
var previousSubscription = parsedEvent.Data
.PreviousAttributes
.ToObject<Subscription>() as Subscription;
@@ -171,17 +191,14 @@ public class SubscriptionUpdatedHandler : ISubscriptionUpdatedHandler
// This being false doesn't necessarily mean that the organization doesn't subscribe to Secrets Manager.
// If there are changes to any subscription item, Stripe sends every item in the subscription, both
// changed and unchanged.
var previousSubscriptionHasSecretsManager = previousSubscription?.Items is not null &&
previousSubscription.Items.Any(previousItem =>
StaticStore.Plans.Any(p =>
p.SecretsManager is not null &&
p.SecretsManager.StripeSeatPlanId ==
previousItem.Plan.Id));
var previousSubscriptionHasSecretsManager =
previousSubscription?.Items is not null &&
previousSubscription.Items.Any(
previousSubscriptionItem => previousSubscriptionItem.Plan.Id == plan.SecretsManager.StripeSeatPlanId);
var currentSubscriptionHasSecretsManager = subscription.Items.Any(i =>
StaticStore.Plans.Any(p =>
p.SecretsManager is not null &&
p.SecretsManager.StripeSeatPlanId == i.Plan.Id));
var currentSubscriptionHasSecretsManager =
subscription.Items.Any(
currentSubscriptionItem => currentSubscriptionItem.Plan.Id == plan.SecretsManager.StripeSeatPlanId);
if (!previousSubscriptionHasSecretsManager || currentSubscriptionHasSecretsManager)
{

View File

@@ -1,12 +1,11 @@
using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.Repositories;
using Bit.Core.AdminConsole.Repositories;
using Bit.Core.Billing.Constants;
using Bit.Core.Billing.Enums;
using Bit.Core.Billing.Extensions;
using Bit.Core.Billing.Pricing;
using Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.Interfaces;
using Bit.Core.Repositories;
using Bit.Core.Services;
using Bit.Core.Utilities;
using Stripe;
using Event = Stripe.Event;
@@ -16,6 +15,7 @@ public class UpcomingInvoiceHandler(
ILogger<StripeEventProcessor> logger,
IMailService mailService,
IOrganizationRepository organizationRepository,
IPricingClient pricingClient,
IProviderRepository providerRepository,
IStripeFacade stripeFacade,
IStripeEventService stripeEventService,
@@ -52,7 +52,9 @@ public class UpcomingInvoiceHandler(
await TryEnableAutomaticTaxAsync(subscription);
if (!HasAnnualPlan(organization))
var plan = await pricingClient.GetPlanOrThrow(organization.PlanType);
if (!plan.IsAnnual)
{
return;
}
@@ -136,7 +138,7 @@ public class UpcomingInvoiceHandler(
{
if (subscription.AutomaticTax.Enabled ||
!subscription.Customer.HasBillingLocation() ||
IsNonTaxableNonUSBusinessUseSubscription(subscription))
await IsNonTaxableNonUSBusinessUseSubscription(subscription))
{
return;
}
@@ -150,14 +152,12 @@ public class UpcomingInvoiceHandler(
return;
bool IsNonTaxableNonUSBusinessUseSubscription(Subscription localSubscription)
async Task<bool> IsNonTaxableNonUSBusinessUseSubscription(Subscription localSubscription)
{
var familyPriceIds = new List<string>
{
// TODO: Replace with the PricingClient
StaticStore.GetPlan(PlanType.FamiliesAnnually2019).PasswordManager.StripePlanId,
StaticStore.GetPlan(PlanType.FamiliesAnnually).PasswordManager.StripePlanId
};
var familyPriceIds = (await Task.WhenAll(
pricingClient.GetPlanOrThrow(PlanType.FamiliesAnnually2019),
pricingClient.GetPlanOrThrow(PlanType.FamiliesAnnually)))
.Select(plan => plan.PasswordManager.StripePlanId);
return localSubscription.Customer.Address.Country != "US" &&
localSubscription.Metadata.ContainsKey(StripeConstants.MetadataKeys.OrganizationId) &&
@@ -165,6 +165,4 @@ public class UpcomingInvoiceHandler(
!localSubscription.Customer.TaxIds.Any();
}
}
private static bool HasAnnualPlan(Organization org) => StaticStore.GetPlan(org.PlanType).IsAnnual;
}