mirror of
https://github.com/bitwarden/server
synced 2025-12-06 00:03:34 +00:00
[PM-23287] Enable Provider When Subscription Is Paid (#6113)
* test : add tests for provider update * feat: add provider update logic and dependencies * fix: remove duplicate dependencies * refactor: updated switch logic for helper method * test: add feature flag to tests * feat: add feature flag for changes
This commit is contained in:
@@ -56,11 +56,13 @@ public class SubscriptionUpdatedHandler : ISubscriptionUpdatedHandler
|
||||
_stripeEventService = stripeEventService;
|
||||
_stripeEventUtilityService = stripeEventUtilityService;
|
||||
_organizationService = organizationService;
|
||||
_providerService = providerService;
|
||||
_stripeFacade = stripeFacade;
|
||||
_organizationSponsorshipRenewCommand = organizationSponsorshipRenewCommand;
|
||||
_userService = userService;
|
||||
_pushNotificationService = pushNotificationService;
|
||||
_organizationRepository = organizationRepository;
|
||||
_providerRepository = providerRepository;
|
||||
_schedulerFactory = schedulerFactory;
|
||||
_organizationEnableCommand = organizationEnableCommand;
|
||||
_organizationDisableCommand = organizationDisableCommand;
|
||||
@@ -126,13 +128,34 @@ public class SubscriptionUpdatedHandler : ISubscriptionUpdatedHandler
|
||||
}
|
||||
break;
|
||||
}
|
||||
case StripeSubscriptionStatus.Active when providerId.HasValue:
|
||||
{
|
||||
var providerPortalTakeover = _featureService.IsEnabled(FeatureFlagKeys.PM21821_ProviderPortalTakeover);
|
||||
if (!providerPortalTakeover)
|
||||
{
|
||||
break;
|
||||
}
|
||||
var provider = await _providerRepository.GetByIdAsync(providerId.Value);
|
||||
if (provider != null)
|
||||
{
|
||||
provider.Enabled = true;
|
||||
await _providerService.UpdateAsync(provider);
|
||||
|
||||
if (IsProviderSubscriptionNowActive(parsedEvent, subscription))
|
||||
{
|
||||
// Update the CancelAtPeriodEnd subscription option to prevent the now active provider subscription from being cancelled
|
||||
var subscriptionUpdateOptions = new SubscriptionUpdateOptions { CancelAtPeriodEnd = false };
|
||||
await _stripeFacade.UpdateSubscription(subscription.Id, subscriptionUpdateOptions);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case StripeSubscriptionStatus.Active:
|
||||
{
|
||||
if (userId.HasValue)
|
||||
{
|
||||
await _userService.EnablePremiumAsync(userId.Value, subscription.CurrentPeriodEnd);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -170,6 +193,36 @@ public class SubscriptionUpdatedHandler : ISubscriptionUpdatedHandler
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the provider subscription status has changed from a non-active to an active status type
|
||||
/// If the previous status is already active(active,past-due,trialing),canceled,or null, then this will return false.
|
||||
/// </summary>
|
||||
/// <param name="parsedEvent">The event containing the previous subscription status</param>
|
||||
/// <param name="subscription">The current subscription status</param>
|
||||
/// <returns>A boolean that represents whether the event status has changed from a non-active status to an active status</returns>
|
||||
private static bool IsProviderSubscriptionNowActive(Event parsedEvent, Subscription subscription)
|
||||
{
|
||||
if (parsedEvent.Data.PreviousAttributes == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var previousSubscription = parsedEvent
|
||||
.Data
|
||||
.PreviousAttributes
|
||||
.ToObject<Subscription>() as Subscription;
|
||||
|
||||
return previousSubscription?.Status switch
|
||||
{
|
||||
StripeSubscriptionStatus.IncompleteExpired
|
||||
or StripeSubscriptionStatus.Paused
|
||||
or StripeSubscriptionStatus.Incomplete
|
||||
or StripeSubscriptionStatus.Unpaid
|
||||
when subscription.Status == StripeSubscriptionStatus.Active => true,
|
||||
_ => false
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the Password Manager coupon if the organization is removing the Secrets Manager trial.
|
||||
/// Only applies to organizations that have a subscription from the Secrets Manager trial.
|
||||
|
||||
@@ -17,6 +17,7 @@ using Bit.Core.Services;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using NSubstitute;
|
||||
using NSubstitute.ReturnsExtensions;
|
||||
using Quartz;
|
||||
using Stripe;
|
||||
using Stripe.TestHelpers;
|
||||
@@ -54,8 +55,10 @@ public class SubscriptionUpdatedHandlerTests
|
||||
_stripeFacade = Substitute.For<IStripeFacade>();
|
||||
_organizationSponsorshipRenewCommand = Substitute.For<IOrganizationSponsorshipRenewCommand>();
|
||||
_userService = Substitute.For<IUserService>();
|
||||
_providerService = Substitute.For<IProviderService>();
|
||||
_pushNotificationService = Substitute.For<IPushNotificationService>();
|
||||
_organizationRepository = Substitute.For<IOrganizationRepository>();
|
||||
_providerRepository = Substitute.For<IProviderRepository>();
|
||||
_schedulerFactory = Substitute.For<ISchedulerFactory>();
|
||||
_organizationEnableCommand = Substitute.For<IOrganizationEnableCommand>();
|
||||
_organizationDisableCommand = Substitute.For<IOrganizationDisableCommand>();
|
||||
@@ -663,4 +666,354 @@ public class SubscriptionUpdatedHandlerTests
|
||||
await _stripeFacade.Received(1).DeleteCustomerDiscount(subscription.CustomerId);
|
||||
await _stripeFacade.Received(1).DeleteSubscriptionDiscount(subscription.Id);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(GetNonActiveSubscriptions))]
|
||||
public async Task
|
||||
HandleAsync_ActiveProviderSubscriptionEvent_AndPreviousSubscriptionStatusWasNonActive_EnableProviderAndUpdateSubscription(
|
||||
Subscription previousSubscription)
|
||||
{
|
||||
// Arrange
|
||||
var (providerId, newSubscription, provider, parsedEvent) =
|
||||
CreateProviderTestInputsForUpdatedActiveSubscriptionStatus(previousSubscription);
|
||||
|
||||
_stripeEventService
|
||||
.GetSubscription(Arg.Any<Event>(), Arg.Any<bool>(), Arg.Any<List<string>>())
|
||||
.Returns(newSubscription);
|
||||
|
||||
_stripeEventUtilityService
|
||||
.GetIdsFromMetadata(Arg.Any<Dictionary<string, string>>())
|
||||
.Returns(Tuple.Create<Guid?, Guid?, Guid?>(null, null, providerId));
|
||||
|
||||
_providerRepository
|
||||
.GetByIdAsync(Arg.Any<Guid>())
|
||||
.Returns(provider);
|
||||
_stripeFacade
|
||||
.UpdateSubscription(Arg.Any<string>(), Arg.Any<SubscriptionUpdateOptions>())
|
||||
.Returns(newSubscription);
|
||||
_featureService.IsEnabled(FeatureFlagKeys.PM21821_ProviderPortalTakeover)
|
||||
.Returns(true);
|
||||
|
||||
// Act
|
||||
await _sut.HandleAsync(parsedEvent);
|
||||
|
||||
// Assert
|
||||
await _stripeEventService
|
||||
.Received(1)
|
||||
.GetSubscription(parsedEvent, true, Arg.Any<List<string>>());
|
||||
_stripeEventUtilityService
|
||||
.Received(1)
|
||||
.GetIdsFromMetadata(newSubscription.Metadata);
|
||||
await _providerRepository
|
||||
.Received(1)
|
||||
.GetByIdAsync(providerId);
|
||||
await _providerService
|
||||
.Received(1)
|
||||
.UpdateAsync(Arg.Is<Provider>(p => p.Id == providerId && p.Enabled == true));
|
||||
await _stripeFacade
|
||||
.Received(1)
|
||||
.UpdateSubscription(newSubscription.Id,
|
||||
Arg.Is<SubscriptionUpdateOptions>(options => options.CancelAtPeriodEnd == false));
|
||||
_featureService
|
||||
.Received(1)
|
||||
.IsEnabled(FeatureFlagKeys.PM21821_ProviderPortalTakeover);
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public async Task
|
||||
HandleAsync_ActiveProviderSubscriptionEvent_AndPreviousSubscriptionStatusWasCanceled_EnableProvider()
|
||||
{
|
||||
// Arrange
|
||||
var previousSubscription = new Subscription { Id = "sub_123", Status = StripeSubscriptionStatus.Canceled };
|
||||
var (providerId, newSubscription, provider, parsedEvent) =
|
||||
CreateProviderTestInputsForUpdatedActiveSubscriptionStatus(previousSubscription);
|
||||
|
||||
_stripeEventService
|
||||
.GetSubscription(Arg.Any<Event>(), Arg.Any<bool>(), Arg.Any<List<string>>())
|
||||
.Returns(newSubscription);
|
||||
_stripeEventUtilityService
|
||||
.GetIdsFromMetadata(Arg.Any<Dictionary<string, string>>())
|
||||
.Returns(Tuple.Create<Guid?, Guid?, Guid?>(null, null, providerId));
|
||||
_providerRepository
|
||||
.GetByIdAsync(Arg.Any<Guid>())
|
||||
.Returns(provider);
|
||||
_featureService.IsEnabled(FeatureFlagKeys.PM21821_ProviderPortalTakeover)
|
||||
.Returns(true);
|
||||
|
||||
// Act
|
||||
await _sut.HandleAsync(parsedEvent);
|
||||
|
||||
// Assert
|
||||
await _stripeEventService
|
||||
.Received(1)
|
||||
.GetSubscription(parsedEvent, true, Arg.Any<List<string>>());
|
||||
_stripeEventUtilityService
|
||||
.Received(1)
|
||||
.GetIdsFromMetadata(newSubscription.Metadata);
|
||||
await _providerRepository.Received(1).GetByIdAsync(providerId);
|
||||
await _providerService
|
||||
.Received(1)
|
||||
.UpdateAsync(Arg.Is<Provider>(p => p.Id == providerId && p.Enabled == true));
|
||||
await _stripeFacade
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.UpdateSubscription(Arg.Any<string>());
|
||||
_featureService
|
||||
.Received(1)
|
||||
.IsEnabled(FeatureFlagKeys.PM21821_ProviderPortalTakeover);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task
|
||||
HandleAsync_ActiveProviderSubscriptionEvent_AndPreviousSubscriptionStatusWasAlreadyActive_EnableProvider()
|
||||
{
|
||||
// Arrange
|
||||
var previousSubscription = new Subscription { Id = "sub_123", Status = StripeSubscriptionStatus.Active };
|
||||
var (providerId, newSubscription, provider, parsedEvent) =
|
||||
CreateProviderTestInputsForUpdatedActiveSubscriptionStatus(previousSubscription);
|
||||
|
||||
_stripeEventService
|
||||
.GetSubscription(Arg.Any<Event>(), Arg.Any<bool>(), Arg.Any<List<string>>())
|
||||
.Returns(newSubscription);
|
||||
_stripeEventUtilityService
|
||||
.GetIdsFromMetadata(Arg.Any<Dictionary<string, string>>())
|
||||
.Returns(Tuple.Create<Guid?, Guid?, Guid?>(null, null, providerId));
|
||||
_providerRepository
|
||||
.GetByIdAsync(Arg.Any<Guid>())
|
||||
.Returns(provider);
|
||||
_featureService.IsEnabled(FeatureFlagKeys.PM21821_ProviderPortalTakeover)
|
||||
.Returns(true);
|
||||
|
||||
// Act
|
||||
await _sut.HandleAsync(parsedEvent);
|
||||
|
||||
// Assert
|
||||
await _stripeEventService
|
||||
.Received(1)
|
||||
.GetSubscription(parsedEvent, true, Arg.Any<List<string>>());
|
||||
_stripeEventUtilityService
|
||||
.Received(1)
|
||||
.GetIdsFromMetadata(newSubscription.Metadata);
|
||||
await _providerRepository.Received(1).GetByIdAsync(providerId);
|
||||
await _providerService
|
||||
.Received(1)
|
||||
.UpdateAsync(Arg.Is<Provider>(p => p.Id == providerId && p.Enabled == true));
|
||||
await _stripeFacade
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.UpdateSubscription(Arg.Any<string>());
|
||||
_featureService
|
||||
.Received(1)
|
||||
.IsEnabled(FeatureFlagKeys.PM21821_ProviderPortalTakeover);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task
|
||||
HandleAsync_ActiveProviderSubscriptionEvent_AndPreviousSubscriptionStatusWasTrailing_EnableProvider()
|
||||
{
|
||||
// Arrange
|
||||
var previousSubscription = new Subscription { Id = "sub_123", Status = StripeSubscriptionStatus.Trialing };
|
||||
var (providerId, newSubscription, provider, parsedEvent) =
|
||||
CreateProviderTestInputsForUpdatedActiveSubscriptionStatus(previousSubscription);
|
||||
|
||||
_stripeEventService
|
||||
.GetSubscription(Arg.Any<Event>(), Arg.Any<bool>(), Arg.Any<List<string>>())
|
||||
.Returns(newSubscription);
|
||||
_stripeEventUtilityService
|
||||
.GetIdsFromMetadata(Arg.Any<Dictionary<string, string>>())
|
||||
.Returns(Tuple.Create<Guid?, Guid?, Guid?>(null, null, providerId));
|
||||
_providerRepository
|
||||
.GetByIdAsync(Arg.Any<Guid>())
|
||||
.Returns(provider);
|
||||
_featureService.IsEnabled(FeatureFlagKeys.PM21821_ProviderPortalTakeover)
|
||||
.Returns(true);
|
||||
|
||||
// Act
|
||||
await _sut.HandleAsync(parsedEvent);
|
||||
|
||||
// Assert
|
||||
await _stripeEventService
|
||||
.Received(1)
|
||||
.GetSubscription(parsedEvent, true, Arg.Any<List<string>>());
|
||||
_stripeEventUtilityService
|
||||
.Received(1)
|
||||
.GetIdsFromMetadata(newSubscription.Metadata);
|
||||
await _providerRepository.Received(1).GetByIdAsync(providerId);
|
||||
await _providerService
|
||||
.Received(1)
|
||||
.UpdateAsync(Arg.Is<Provider>(p => p.Id == providerId && p.Enabled == true));
|
||||
await _stripeFacade
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.UpdateSubscription(Arg.Any<string>());
|
||||
_featureService
|
||||
.Received(1)
|
||||
.IsEnabled(FeatureFlagKeys.PM21821_ProviderPortalTakeover);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task
|
||||
HandleAsync_ActiveProviderSubscriptionEvent_AndPreviousSubscriptionStatusWasPastDue_EnableProvider()
|
||||
{
|
||||
// Arrange
|
||||
var previousSubscription = new Subscription { Id = "sub_123", Status = StripeSubscriptionStatus.PastDue };
|
||||
var (providerId, newSubscription, provider, parsedEvent) =
|
||||
CreateProviderTestInputsForUpdatedActiveSubscriptionStatus(previousSubscription);
|
||||
|
||||
|
||||
_stripeEventService
|
||||
.GetSubscription(Arg.Any<Event>(), Arg.Any<bool>(), Arg.Any<List<string>>())
|
||||
.Returns(newSubscription);
|
||||
_stripeEventUtilityService
|
||||
.GetIdsFromMetadata(Arg.Any<Dictionary<string, string>>())
|
||||
.Returns(Tuple.Create<Guid?, Guid?, Guid?>(null, null, providerId));
|
||||
_providerRepository
|
||||
.GetByIdAsync(Arg.Any<Guid>())
|
||||
.Returns(provider);
|
||||
_featureService.IsEnabled(FeatureFlagKeys.PM21821_ProviderPortalTakeover)
|
||||
.Returns(true);
|
||||
|
||||
// Act
|
||||
await _sut.HandleAsync(parsedEvent);
|
||||
|
||||
// Assert
|
||||
await _stripeEventService
|
||||
.Received(1)
|
||||
.GetSubscription(parsedEvent, true, Arg.Any<List<string>>());
|
||||
_stripeEventUtilityService
|
||||
.Received(1)
|
||||
.GetIdsFromMetadata(newSubscription.Metadata);
|
||||
await _providerRepository
|
||||
.Received(1)
|
||||
.GetByIdAsync(Arg.Any<Guid>());
|
||||
await _providerService
|
||||
.Received(1)
|
||||
.UpdateAsync(Arg.Is<Provider>(p => p.Id == providerId && p.Enabled == true));
|
||||
await _stripeFacade
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.UpdateSubscription(Arg.Any<string>());
|
||||
_featureService
|
||||
.Received(1)
|
||||
.IsEnabled(FeatureFlagKeys.PM21821_ProviderPortalTakeover);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task HandleAsync_ActiveProviderSubscriptionEvent_AndProviderDoesNotExist_NoChanges()
|
||||
{
|
||||
// Arrange
|
||||
var previousSubscription = new Subscription { Id = "sub_123", Status = StripeSubscriptionStatus.Unpaid };
|
||||
var (providerId, newSubscription, _, parsedEvent) =
|
||||
CreateProviderTestInputsForUpdatedActiveSubscriptionStatus(previousSubscription);
|
||||
|
||||
_stripeEventService
|
||||
.GetSubscription(Arg.Any<Event>(), Arg.Any<bool>(), Arg.Any<List<string>>())
|
||||
.Returns(newSubscription);
|
||||
_stripeEventUtilityService
|
||||
.GetIdsFromMetadata(Arg.Any<Dictionary<string, string>>())
|
||||
.Returns(Tuple.Create<Guid?, Guid?, Guid?>(null, null, providerId));
|
||||
_providerRepository
|
||||
.GetByIdAsync(Arg.Any<Guid>())
|
||||
.ReturnsNull();
|
||||
_featureService.IsEnabled(FeatureFlagKeys.PM21821_ProviderPortalTakeover)
|
||||
.Returns(true);
|
||||
|
||||
// Act
|
||||
await _sut.HandleAsync(parsedEvent);
|
||||
|
||||
// Assert
|
||||
await _stripeEventService
|
||||
.Received(1)
|
||||
.GetSubscription(parsedEvent, true, Arg.Any<List<string>>());
|
||||
_stripeEventUtilityService
|
||||
.Received(1)
|
||||
.GetIdsFromMetadata(newSubscription.Metadata);
|
||||
await _providerRepository
|
||||
.Received(1)
|
||||
.GetByIdAsync(providerId);
|
||||
await _providerService
|
||||
.DidNotReceive()
|
||||
.UpdateAsync(Arg.Any<Provider>());
|
||||
await _stripeFacade
|
||||
.DidNotReceive()
|
||||
.UpdateSubscription(Arg.Any<string>());
|
||||
_featureService
|
||||
.Received(1)
|
||||
.IsEnabled(FeatureFlagKeys.PM21821_ProviderPortalTakeover);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task HandleAsync_ActiveProviderSubscriptionEvent_WithNoPreviousAttributes_EnableProvider()
|
||||
{
|
||||
// Arrange
|
||||
var (providerId, newSubscription, provider, parsedEvent) =
|
||||
CreateProviderTestInputsForUpdatedActiveSubscriptionStatus(null);
|
||||
|
||||
_stripeEventService
|
||||
.GetSubscription(Arg.Any<Event>(), Arg.Any<bool>(), Arg.Any<List<string>>())
|
||||
.Returns(newSubscription);
|
||||
_stripeEventUtilityService
|
||||
.GetIdsFromMetadata(Arg.Any<Dictionary<string, string>>())
|
||||
.Returns(Tuple.Create<Guid?, Guid?, Guid?>(null, null, providerId));
|
||||
_providerRepository
|
||||
.GetByIdAsync(Arg.Any<Guid>())
|
||||
.Returns(provider);
|
||||
_featureService.IsEnabled(FeatureFlagKeys.PM21821_ProviderPortalTakeover)
|
||||
.Returns(true);
|
||||
|
||||
// Act
|
||||
await _sut.HandleAsync(parsedEvent);
|
||||
|
||||
// Assert
|
||||
await _stripeEventService
|
||||
.Received(1)
|
||||
.GetSubscription(parsedEvent, true, Arg.Any<List<string>>());
|
||||
_stripeEventUtilityService
|
||||
.Received(1)
|
||||
.GetIdsFromMetadata(newSubscription.Metadata);
|
||||
await _providerRepository
|
||||
.Received(1)
|
||||
.GetByIdAsync(Arg.Any<Guid>());
|
||||
await _providerService
|
||||
.Received(1)
|
||||
.UpdateAsync(Arg.Is<Provider>(p => p.Id == providerId && p.Enabled == true));
|
||||
await _stripeFacade
|
||||
.DidNotReceive()
|
||||
.UpdateSubscription(Arg.Any<string>());
|
||||
_featureService
|
||||
.Received(1)
|
||||
.IsEnabled(FeatureFlagKeys.PM21821_ProviderPortalTakeover);
|
||||
}
|
||||
|
||||
private static (Guid providerId, Subscription newSubscription, Provider provider, Event parsedEvent)
|
||||
CreateProviderTestInputsForUpdatedActiveSubscriptionStatus(Subscription? previousSubscription)
|
||||
{
|
||||
var providerId = Guid.NewGuid();
|
||||
var newSubscription = new Subscription
|
||||
{
|
||||
Id = previousSubscription?.Id ?? "sub_123",
|
||||
Status = StripeSubscriptionStatus.Active,
|
||||
Metadata = new Dictionary<string, string> { { "providerId", providerId.ToString() } },
|
||||
};
|
||||
|
||||
var provider = new Provider { Id = providerId, Enabled = false };
|
||||
var parsedEvent = new Event
|
||||
{
|
||||
Data = new EventData
|
||||
{
|
||||
Object = newSubscription,
|
||||
PreviousAttributes =
|
||||
previousSubscription == null ? null : JObject.FromObject(previousSubscription)
|
||||
}
|
||||
};
|
||||
return (providerId, newSubscription, provider, parsedEvent);
|
||||
}
|
||||
|
||||
public static IEnumerable<object[]> GetNonActiveSubscriptions()
|
||||
{
|
||||
return new List<object[]>
|
||||
{
|
||||
new object[] { new Subscription { Id = "sub_123", Status = StripeSubscriptionStatus.Unpaid }, },
|
||||
new object[] { new Subscription { Id = "sub_123", Status = StripeSubscriptionStatus.Incomplete }, },
|
||||
new object[] { new Subscription { Id = "sub_123", Status = StripeSubscriptionStatus.IncompleteExpired }, },
|
||||
new object[] { new Subscription { Id = "sub_123", Status = StripeSubscriptionStatus.Paused }, },
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user