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

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

Open
wants to merge 19 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
aa3e9fe
Remove gRPC and convert PricingClient to HttpClient wrapper
amorask-bitwarden Jan 14, 2025
c752582
Add PlanType.GetProductTier extension
amorask-bitwarden Jan 14, 2025
3435e75
Remove invocations of the StaticStore in non-Test code
amorask-bitwarden Jan 15, 2025
8538693
Deprecate StaticStore entry points
amorask-bitwarden Jan 15, 2025
2dddebf
Merge branch 'main' into billing/PM-16684/integrate-pricing-service
amorask-bitwarden Jan 15, 2025
b2593b6
Run dotnet format
amorask-bitwarden Jan 15, 2025
39eb028
Merge branch 'main' into billing/PM-16684/integrate-pricing-service
amorask-bitwarden Jan 16, 2025
679c158
Merge branch 'main' into billing/PM-16684/integrate-pricing-service
amorask-bitwarden Jan 16, 2025
250ac13
Merge branch 'main' into billing/PM-16684/integrate-pricing-service
amorask-bitwarden Jan 17, 2025
90ad0bb
Matt's feedback
amorask-bitwarden Jan 17, 2025
c5ce133
Merge branch 'main' into billing/PM-16684/integrate-pricing-service
amorask-bitwarden Jan 21, 2025
cfce0cf
Merge branch 'main' into billing/PM-16684/integrate-pricing-service
amorask-bitwarden Jan 23, 2025
f9ab39f
Run dotnet format
amorask-bitwarden Jan 23, 2025
8d60635
Rui's feedback
amorask-bitwarden Jan 23, 2025
88c0b8e
Merge branch 'main' into billing/PM-16684/integrate-pricing-service
amorask-bitwarden Jan 27, 2025
a8df63c
Merge branch 'main' into billing/PM-16684/integrate-pricing-service
amorask-bitwarden Jan 27, 2025
6dd97f0
Run dotnet format
amorask-bitwarden Jan 27, 2025
e85b501
Merge branch 'main' into billing/PM-16684/integrate-pricing-service
amorask-bitwarden Jan 28, 2025
ceadae5
Merge branch 'main' into billing/PM-16684/integrate-pricing-service
amorask-bitwarden Jan 28, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@
using Bit.Core.AdminConsole.Repositories;
using Bit.Core.Billing.Constants;
using Bit.Core.Billing.Extensions;
using Bit.Core.Billing.Pricing;
using Bit.Core.Billing.Services;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.Repositories;
using Bit.Core.Services;
using Bit.Core.Utilities;
using Stripe;

namespace Bit.Commercial.Core.AdminConsole.Providers;
Expand All @@ -27,6 +27,7 @@ public class RemoveOrganizationFromProviderCommand : IRemoveOrganizationFromProv
private readonly IProviderBillingService _providerBillingService;
private readonly ISubscriberService _subscriberService;
private readonly IHasConfirmedOwnersExceptQuery _hasConfirmedOwnersExceptQuery;
private readonly IPricingClient _pricingClient;

public RemoveOrganizationFromProviderCommand(
IEventService eventService,
Expand All @@ -38,7 +39,8 @@ public RemoveOrganizationFromProviderCommand(
IFeatureService featureService,
IProviderBillingService providerBillingService,
ISubscriberService subscriberService,
IHasConfirmedOwnersExceptQuery hasConfirmedOwnersExceptQuery)
IHasConfirmedOwnersExceptQuery hasConfirmedOwnersExceptQuery,
IPricingClient pricingClient)
{
_eventService = eventService;
_mailService = mailService;
Expand All @@ -50,6 +52,7 @@ public RemoveOrganizationFromProviderCommand(
_providerBillingService = providerBillingService;
_subscriberService = subscriberService;
_hasConfirmedOwnersExceptQuery = hasConfirmedOwnersExceptQuery;
_pricingClient = pricingClient;
}

public async Task RemoveOrganizationFromProvider(
Expand Down Expand Up @@ -110,7 +113,7 @@ private async Task ResetOrganizationBillingAsync(
Email = organization.BillingEmail
});

var plan = StaticStore.GetPlan(organization.PlanType).PasswordManager;
var plan = await _pricingClient.GetPlanOrThrow(organization.PlanType);

var subscriptionCreateOptions = new SubscriptionCreateOptions
{
Expand All @@ -124,7 +127,7 @@ private async Task ResetOrganizationBillingAsync(
},
OffSession = true,
ProrationBehavior = StripeConstants.ProrationBehavior.CreateProrations,
Items = [new SubscriptionItemOptions { Price = plan.StripeSeatPlanId, Quantity = organization.Seats }]
Items = [new SubscriptionItemOptions { Price = plan.PasswordManager.StripeSeatPlanId, Quantity = organization.Seats }]
};

var subscription = await _stripeAdapter.SubscriptionCreateAsync(subscriptionCreateOptions);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using Bit.Core.AdminConsole.Repositories;
using Bit.Core.AdminConsole.Services;
using Bit.Core.Billing.Enums;
using Bit.Core.Billing.Pricing;
using Bit.Core.Billing.Services;
using Bit.Core.Context;
using Bit.Core.Entities;
Expand Down Expand Up @@ -50,6 +51,7 @@ public class ProviderService : IProviderService
private readonly IDataProtectorTokenFactory<ProviderDeleteTokenable> _providerDeleteTokenDataFactory;
private readonly IApplicationCacheService _applicationCacheService;
private readonly IProviderBillingService _providerBillingService;
private readonly IPricingClient _pricingClient;

public ProviderService(IProviderRepository providerRepository, IProviderUserRepository providerUserRepository,
IProviderOrganizationRepository providerOrganizationRepository, IUserRepository userRepository,
Expand All @@ -58,7 +60,7 @@ public ProviderService(IProviderRepository providerRepository, IProviderUserRepo
IOrganizationRepository organizationRepository, GlobalSettings globalSettings,
ICurrentContext currentContext, IStripeAdapter stripeAdapter, IFeatureService featureService,
IDataProtectorTokenFactory<ProviderDeleteTokenable> providerDeleteTokenDataFactory,
IApplicationCacheService applicationCacheService, IProviderBillingService providerBillingService)
IApplicationCacheService applicationCacheService, IProviderBillingService providerBillingService, IPricingClient pricingClient)
{
_providerRepository = providerRepository;
_providerUserRepository = providerUserRepository;
Expand All @@ -77,6 +79,7 @@ public ProviderService(IProviderRepository providerRepository, IProviderUserRepo
_providerDeleteTokenDataFactory = providerDeleteTokenDataFactory;
_applicationCacheService = applicationCacheService;
_providerBillingService = providerBillingService;
_pricingClient = pricingClient;
}

public async Task<Provider> CompleteSetupAsync(Provider provider, Guid ownerUserId, string token, string key, TaxInfo taxInfo = null)
Expand Down Expand Up @@ -452,30 +455,31 @@ private async Task ApplyProviderPriceRateAsync(Organization organization, Provid

if (!string.IsNullOrWhiteSpace(organization.GatewaySubscriptionId))
{
var subscriptionItem = await GetSubscriptionItemAsync(organization.GatewaySubscriptionId,
GetStripeSeatPlanId(organization.PlanType));
var plan = await _pricingClient.GetPlanOrThrow(organization.PlanType);

var subscriptionItem = await GetSubscriptionItemAsync(
organization.GatewaySubscriptionId,
plan.PasswordManager.StripeSeatPlanId);

var extractedPlanType = PlanTypeMappings(organization);
var extractedPlan = await _pricingClient.GetPlanOrThrow(extractedPlanType);

if (subscriptionItem != null)
{
await UpdateSubscriptionAsync(subscriptionItem, GetStripeSeatPlanId(extractedPlanType), organization);
await UpdateSubscriptionAsync(subscriptionItem, extractedPlan.PasswordManager.StripeSeatPlanId, organization);
}
}

await _organizationRepository.UpsertAsync(organization);
}

private async Task<Stripe.SubscriptionItem> GetSubscriptionItemAsync(string subscriptionId, string oldPlanId)
private async Task<SubscriptionItem> GetSubscriptionItemAsync(string subscriptionId, string oldPlanId)
{
var subscriptionDetails = await _stripeAdapter.SubscriptionGetAsync(subscriptionId);
return subscriptionDetails.Items.Data.FirstOrDefault(item => item.Price.Id == oldPlanId);
}

private static string GetStripeSeatPlanId(PlanType planType)
{
return StaticStore.GetPlan(planType).PasswordManager.StripeSeatPlanId;
}

private async Task UpdateSubscriptionAsync(Stripe.SubscriptionItem subscriptionItem, string extractedPlanType, Organization organization)
private async Task UpdateSubscriptionAsync(SubscriptionItem subscriptionItem, string extractedPlanType, Organization organization)
{
try
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using Bit.Core.Billing.Constants;
using Bit.Core.Billing.Entities;
using Bit.Core.Billing.Enums;
using Bit.Core.Billing.Pricing;
using Bit.Core.Billing.Repositories;
using Bit.Core.Billing.Services;
using Bit.Core.Billing.Services.Contracts;
Expand All @@ -16,7 +17,6 @@
using Bit.Core.Repositories;
using Bit.Core.Services;
using Bit.Core.Settings;
using Bit.Core.Utilities;
using CsvHelper;
using Microsoft.Extensions.Logging;
using Stripe;
Expand All @@ -28,6 +28,7 @@ public class ProviderBillingService(
ILogger<ProviderBillingService> logger,
IOrganizationRepository organizationRepository,
IPaymentService paymentService,
IPricingClient pricingClient,
IProviderInvoiceItemRepository providerInvoiceItemRepository,
IProviderOrganizationRepository providerOrganizationRepository,
IProviderPlanRepository providerPlanRepository,
Expand All @@ -49,7 +50,8 @@ public async Task ChangePlan(ChangeProviderPlanCommand command)
return;
}

var oldPlanConfiguration = StaticStore.GetPlan(plan.PlanType);
var oldPlanConfiguration = await pricingClient.GetPlanOrThrow(plan.PlanType);
var newPlanConfiguration = await pricingClient.GetPlanOrThrow(command.NewPlan);

plan.PlanType = command.NewPlan;
await providerPlanRepository.ReplaceAsync(plan);
Expand All @@ -73,7 +75,7 @@ public async Task ChangePlan(ChangeProviderPlanCommand command)
[
new SubscriptionItemOptions
{
Price = StaticStore.GetPlan(command.NewPlan).PasswordManager.StripeProviderPortalSeatPlanId,
Price = newPlanConfiguration.PasswordManager.StripeProviderPortalSeatPlanId,
Quantity = oldSubscriptionItem!.Quantity
},
new SubscriptionItemOptions
Expand All @@ -99,7 +101,7 @@ public async Task ChangePlan(ChangeProviderPlanCommand command)
throw new ConflictException($"Organization '{providerOrganization.Id}' not found.");
}
organization.PlanType = command.NewPlan;
organization.Plan = StaticStore.GetPlan(command.NewPlan).Name;
organization.Plan = newPlanConfiguration.Name;
await organizationRepository.ReplaceAsync(organization);
}
}
Expand Down Expand Up @@ -388,7 +390,7 @@ public async Task<Subscription> SetupSubscription(

foreach (var providerPlan in providerPlans)
{
var plan = StaticStore.GetPlan(providerPlan.PlanType);
var plan = await pricingClient.GetPlanOrThrow(providerPlan.PlanType);

if (!providerPlan.IsConfigured())
{
Expand Down Expand Up @@ -472,8 +474,10 @@ public async Task UpdateSeatMinimums(UpdateProviderSeatMinimumsCommand command)

if (providerPlan.SeatMinimum != newPlanConfiguration.SeatsMinimum)
{
var priceId = StaticStore.GetPlan(newPlanConfiguration.Plan).PasswordManager
.StripeProviderPortalSeatPlanId;
var newPlan = await pricingClient.GetPlanOrThrow(newPlanConfiguration.Plan);

var priceId = newPlan.PasswordManager.StripeProviderPortalSeatPlanId;

var subscriptionItem = subscription.Items.First(item => item.Price.Id == priceId);

if (providerPlan.PurchasedSeats == 0)
Expand Down Expand Up @@ -537,7 +541,7 @@ private Func<int, int, Task> CurrySeatScalingUpdate(
ProviderPlan providerPlan,
int newlyAssignedSeats) => async (currentlySubscribedSeats, newlySubscribedSeats) =>
{
var plan = StaticStore.GetPlan(providerPlan.PlanType);
var plan = await pricingClient.GetPlanOrThrow(providerPlan.PlanType);

await paymentService.AdjustSeats(
provider,
Expand All @@ -561,7 +565,7 @@ private async Task<int> GetAssignedSeatTotalAsync(Provider provider, PlanType pl
var providerOrganizations =
await providerOrganizationRepository.GetManyDetailsByProviderAsync(provider.Id);

var plan = StaticStore.GetPlan(planType);
var plan = await pricingClient.GetPlanOrThrow(planType);

return providerOrganizations
.Where(providerOrganization => providerOrganization.Plan == plan.Name && providerOrganization.Status == OrganizationStatusType.Managed)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ public MaxProjectsQuery(
throw new NotFoundException();
}

// TODO: PRICING -> https://bitwarden.atlassian.net/browse/PM-17122
var plan = StaticStore.GetPlan(org.PlanType);
if (plan?.SecretsManager == null)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using Bit.Core.AdminConsole.Repositories;
using Bit.Core.Billing.Constants;
using Bit.Core.Billing.Enums;
using Bit.Core.Billing.Pricing;
using Bit.Core.Billing.Services;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
Expand Down Expand Up @@ -205,6 +206,8 @@ public async Task RemoveOrganizationFromProvider_OrganizationStripeEnabled_Conso

var teamsMonthlyPlan = StaticStore.GetPlan(PlanType.TeamsMonthly);

sutProvider.GetDependency<IPricingClient>().GetPlanOrThrow(PlanType.TeamsMonthly).Returns(teamsMonthlyPlan);

sutProvider.GetDependency<IHasConfirmedOwnersExceptQuery>().HasConfirmedOwnersExceptAsync(
providerOrganization.OrganizationId,
[],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using Bit.Core.AdminConsole.Models.Business.Tokenables;
using Bit.Core.AdminConsole.Repositories;
using Bit.Core.Billing.Enums;
using Bit.Core.Billing.Pricing;
using Bit.Core.Billing.Services;
using Bit.Core.Context;
using Bit.Core.Entities;
Expand Down Expand Up @@ -550,8 +551,14 @@ public async Task AddOrganization_CreateBeforeNov62023_PlanTypeUpdated(Provider
organization.PlanType = PlanType.EnterpriseMonthly;
organization.Plan = "Enterprise (Monthly)";

sutProvider.GetDependency<IPricingClient>().GetPlanOrThrow(organization.PlanType)
.Returns(StaticStore.GetPlan(organization.PlanType));

var expectedPlanType = PlanType.EnterpriseMonthly2020;

sutProvider.GetDependency<IPricingClient>().GetPlanOrThrow(expectedPlanType)
.Returns(StaticStore.GetPlan(expectedPlanType));

var expectedPlanId = "2020-enterprise-org-seat-monthly";

sutProvider.GetDependency<IProviderRepository>().GetByIdAsync(provider.Id).Returns(provider);
Expand Down
Loading
Loading