1
0
mirror of https://github.com/bitwarden/server.git synced 2024-11-25 12:45:18 +01:00

[AC-2312] Server: Update ProviderOrganizationsController.Delete to update provider plan (#4008)

* initial commit

Signed-off-by: Cy Okeke <cokeke@bitwarden.com>

* fix the failing unit test

Signed-off-by: Cy Okeke <cokeke@bitwarden.com>

* Resolve some pr comments

Signed-off-by: Cy Okeke <cokeke@bitwarden.com>

* resolves some pr comments

Signed-off-by: Cy Okeke <cokeke@bitwarden.com>

* resolve the collection expression suggestion

Signed-off-by: Cy Okeke <cokeke@bitwarden.com>

* resolve pr comments

Signed-off-by: Cy Okeke <cokeke@bitwarden.com>

* test for when the flag is on

Signed-off-by: Cy Okeke <cokeke@bitwarden.com>

* rename the test

Signed-off-by: Cy Okeke <cokeke@bitwarden.com>

---------

Signed-off-by: Cy Okeke <cokeke@bitwarden.com>
This commit is contained in:
cyprain-okeke 2024-04-25 14:24:14 +01:00 committed by GitHub
parent eac2b9f0b8
commit b220de0126
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 121 additions and 21 deletions

View File

@ -1,11 +1,16 @@
using Bit.Core.AdminConsole.Entities;
using Bit.Core;
using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.Entities.Provider;
using Bit.Core.AdminConsole.Enums.Provider;
using Bit.Core.AdminConsole.Providers.Interfaces;
using Bit.Core.AdminConsole.Repositories;
using Bit.Core.Billing.Commands;
using Bit.Core.Billing.Constants;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.Repositories;
using Bit.Core.Services;
using Bit.Core.Utilities;
using Microsoft.Extensions.Logging;
using Stripe;
@ -20,6 +25,8 @@ public class RemoveOrganizationFromProviderCommand : IRemoveOrganizationFromProv
private readonly IOrganizationService _organizationService;
private readonly IProviderOrganizationRepository _providerOrganizationRepository;
private readonly IStripeAdapter _stripeAdapter;
private readonly IScaleSeatsCommand _scaleSeatsCommand;
private readonly IFeatureService _featureService;
public RemoveOrganizationFromProviderCommand(
IEventService eventService,
@ -28,7 +35,9 @@ public class RemoveOrganizationFromProviderCommand : IRemoveOrganizationFromProv
IOrganizationRepository organizationRepository,
IOrganizationService organizationService,
IProviderOrganizationRepository providerOrganizationRepository,
IStripeAdapter stripeAdapter)
IStripeAdapter stripeAdapter,
IScaleSeatsCommand scaleSeatsCommand,
IFeatureService featureService)
{
_eventService = eventService;
_logger = logger;
@ -37,6 +46,8 @@ public class RemoveOrganizationFromProviderCommand : IRemoveOrganizationFromProv
_organizationService = organizationService;
_providerOrganizationRepository = providerOrganizationRepository;
_stripeAdapter = stripeAdapter;
_scaleSeatsCommand = scaleSeatsCommand;
_featureService = featureService;
}
public async Task RemoveOrganizationFromProvider(
@ -65,8 +76,6 @@ public class RemoveOrganizationFromProviderCommand : IRemoveOrganizationFromProv
organization.BillingEmail = organizationOwnerEmails.MinBy(email => email);
await _organizationRepository.ReplaceAsync(organization);
var customerUpdateOptions = new CustomerUpdateOptions
{
Coupon = string.Empty,
@ -75,13 +84,41 @@ public class RemoveOrganizationFromProviderCommand : IRemoveOrganizationFromProv
await _stripeAdapter.CustomerUpdateAsync(organization.GatewayCustomerId, customerUpdateOptions);
var isConsolidatedBillingEnabled = _featureService.IsEnabled(FeatureFlagKeys.EnableConsolidatedBilling);
if (isConsolidatedBillingEnabled && provider.Status == ProviderStatusType.Billable)
{
var plan = StaticStore.GetPlan(organization.PlanType).PasswordManager;
var subscriptionCreateOptions = new SubscriptionCreateOptions
{
Customer = organization.GatewayCustomerId,
CollectionMethod = StripeConstants.CollectionMethod.SendInvoice,
DaysUntilDue = 30,
AutomaticTax = new SubscriptionAutomaticTaxOptions { Enabled = true },
Metadata = new Dictionary<string, string>
{
{ "organizationId", organization.Id.ToString() }
},
OffSession = true,
ProrationBehavior = StripeConstants.ProrationBehavior.CreateProrations,
Items = [new SubscriptionItemOptions { Price = plan.StripeSeatPlanId, Quantity = organization.Seats }]
};
var subscription = await _stripeAdapter.SubscriptionCreateAsync(subscriptionCreateOptions);
organization.GatewaySubscriptionId = subscription.Id;
await _scaleSeatsCommand.ScalePasswordManagerSeats(provider, organization.PlanType,
-(organization.Seats ?? 0));
}
else
{
var subscriptionUpdateOptions = new SubscriptionUpdateOptions
{
CollectionMethod = "send_invoice",
DaysUntilDue = 30
};
await _stripeAdapter.SubscriptionUpdateAsync(organization.GatewaySubscriptionId, subscriptionUpdateOptions);
}
await _organizationRepository.ReplaceAsync(organization);
await _mailService.SendProviderUpdatePaymentMethod(
organization.Id,

View File

@ -1,7 +1,10 @@
using Bit.Commercial.Core.AdminConsole.Providers;
using Bit.Core;
using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.Entities.Provider;
using Bit.Core.AdminConsole.Enums.Provider;
using Bit.Core.AdminConsole.Repositories;
using Bit.Core.Billing.Commands;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.Repositories;
@ -81,7 +84,7 @@ public class RemoveOrganizationFromProviderCommandTests
}
[Theory, BitAutoData]
public async Task RemoveOrganizationFromProvider_MakesCorrectInvocations(
public async Task RemoveOrganizationFromProvider_MakesCorrectInvocations__FeatureFlagOff(
Provider provider,
ProviderOrganization providerOrganization,
Organization organization,
@ -97,30 +100,90 @@ public class RemoveOrganizationFromProviderCommandTests
includeProvider: false)
.Returns(true);
var organizationOwnerEmails = new List<string> { "a@gmail.com", "b@gmail.com" };
var organizationOwnerEmails = new List<string> { "a@example.com", "b@example.com" };
organizationRepository.GetOwnerEmailAddressesById(organization.Id).Returns(organizationOwnerEmails);
var stripeAdapter = sutProvider.GetDependency<IStripeAdapter>();
stripeAdapter.SubscriptionCreateAsync(default).ReturnsForAnyArgs(new Stripe.Subscription
{
Id = "S-1",
CurrentPeriodEnd = DateTime.Today.AddDays(10),
});
await sutProvider.Sut.RemoveOrganizationFromProvider(provider, providerOrganization, organization);
await organizationRepository.Received(1).ReplaceAsync(Arg.Is<Organization>(
org => org.Id == organization.Id && org.BillingEmail == "a@gmail.com"));
var stripeAdapter = sutProvider.GetDependency<IStripeAdapter>();
org => org.Id == organization.Id && org.BillingEmail == "a@example.com"));
await stripeAdapter.Received(1).CustomerUpdateAsync(
organization.GatewayCustomerId, Arg.Is<CustomerUpdateOptions>(
options => options.Coupon == string.Empty && options.Email == "a@gmail.com"));
await stripeAdapter.Received(1).SubscriptionUpdateAsync(
organization.GatewaySubscriptionId, Arg.Is<SubscriptionUpdateOptions>(
options => options.CollectionMethod == "send_invoice" && options.DaysUntilDue == 30));
options => options.Coupon == string.Empty && options.Email == "a@example.com"));
await sutProvider.GetDependency<IMailService>().Received(1).SendProviderUpdatePaymentMethod(
organization.Id,
organization.Name,
provider.Name,
Arg.Is<IEnumerable<string>>(emails => emails.Contains("a@gmail.com") && emails.Contains("b@gmail.com")));
Arg.Is<IEnumerable<string>>(emails => emails.Contains("a@example.com") && emails.Contains("b@example.com")));
await sutProvider.GetDependency<IProviderOrganizationRepository>().Received(1)
.DeleteAsync(providerOrganization);
await sutProvider.GetDependency<IEventService>().Received(1).LogProviderOrganizationEventAsync(
providerOrganization,
EventType.ProviderOrganization_Removed);
}
[Theory, BitAutoData]
public async Task RemoveOrganizationFromProvider_CreatesSubscriptionAndScalesSeats_FeatureFlagON(Provider provider,
ProviderOrganization providerOrganization,
Organization organization,
SutProvider<RemoveOrganizationFromProviderCommand> sutProvider)
{
providerOrganization.ProviderId = provider.Id;
provider.Status = ProviderStatusType.Billable;
var organizationRepository = sutProvider.GetDependency<IOrganizationRepository>();
sutProvider.GetDependency<IOrganizationService>().HasConfirmedOwnersExceptAsync(
providerOrganization.OrganizationId,
Array.Empty<Guid>(),
includeProvider: false)
.Returns(true);
var organizationOwnerEmails = new List<string> { "a@example.com", "b@example.com" };
organizationRepository.GetOwnerEmailAddressesById(organization.Id).Returns(organizationOwnerEmails);
var stripeAdapter = sutProvider.GetDependency<IStripeAdapter>();
stripeAdapter.SubscriptionCreateAsync(default).ReturnsForAnyArgs(new Stripe.Subscription
{
Id = "S-1",
CurrentPeriodEnd = DateTime.Today.AddDays(10),
});
sutProvider.GetDependency<IFeatureService>().IsEnabled(FeatureFlagKeys.EnableConsolidatedBilling).Returns(true);
await sutProvider.Sut.RemoveOrganizationFromProvider(provider, providerOrganization, organization);
await stripeAdapter.Received(1).CustomerUpdateAsync(
organization.GatewayCustomerId, Arg.Is<CustomerUpdateOptions>(
options => options.Coupon == string.Empty && options.Email == "a@example.com"));
await stripeAdapter.Received(1).SubscriptionCreateAsync(Arg.Is<SubscriptionCreateOptions>(c =>
c.Customer == organization.GatewayCustomerId &&
c.CollectionMethod == "send_invoice" &&
c.DaysUntilDue == 30 &&
c.Items.Count == 1
));
await sutProvider.GetDependency<IScaleSeatsCommand>().Received(1)
.ScalePasswordManagerSeats(provider, organization.PlanType, -(int)organization.Seats);
await organizationRepository.Received(1).ReplaceAsync(Arg.Is<Organization>(
org => org.Id == organization.Id && org.BillingEmail == "a@example.com" &&
org.GatewaySubscriptionId == "S-1"));
await sutProvider.GetDependency<IMailService>().Received(1).SendProviderUpdatePaymentMethod(
organization.Id,
organization.Name,
provider.Name,
Arg.Is<IEnumerable<string>>(emails =>
emails.Contains("a@example.com") && emails.Contains("b@example.com")));
await sutProvider.GetDependency<IProviderOrganizationRepository>().Received(1)
.DeleteAsync(providerOrganization);