mirror of
https://github.com/bitwarden/server
synced 2025-12-10 13:23:27 +00:00
[PM-21827] Implement mechanism to suspend currently unpaid providers (#6119)
* Manually suspend provider and set cancel_at when we receive 'suspend_provider' metadata update * Run dotnet format'
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
using Bit.Billing.Constants;
|
using System.Globalization;
|
||||||
|
using Bit.Billing.Constants;
|
||||||
using Bit.Billing.Jobs;
|
using Bit.Billing.Jobs;
|
||||||
using Bit.Core;
|
using Bit.Core;
|
||||||
using Bit.Core.AdminConsole.OrganizationFeatures.Organizations.Interfaces;
|
using Bit.Core.AdminConsole.OrganizationFeatures.Organizations.Interfaces;
|
||||||
@@ -316,7 +317,7 @@ public class SubscriptionUpdatedHandler : ISubscriptionUpdatedHandler
|
|||||||
private async Task HandleUnpaidProviderSubscriptionAsync(
|
private async Task HandleUnpaidProviderSubscriptionAsync(
|
||||||
Guid providerId,
|
Guid providerId,
|
||||||
Event parsedEvent,
|
Event parsedEvent,
|
||||||
Subscription subscription)
|
Subscription currentSubscription)
|
||||||
{
|
{
|
||||||
var providerPortalTakeover = _featureService.IsEnabled(FeatureFlagKeys.PM21821_ProviderPortalTakeover);
|
var providerPortalTakeover = _featureService.IsEnabled(FeatureFlagKeys.PM21821_ProviderPortalTakeover);
|
||||||
|
|
||||||
@@ -338,26 +339,43 @@ public class SubscriptionUpdatedHandler : ISubscriptionUpdatedHandler
|
|||||||
|
|
||||||
if (parsedEvent.Data.PreviousAttributes != null)
|
if (parsedEvent.Data.PreviousAttributes != null)
|
||||||
{
|
{
|
||||||
if (parsedEvent.Data.PreviousAttributes.ToObject<Subscription>() as Subscription is
|
var previousSubscription = parsedEvent.Data.PreviousAttributes.ToObject<Subscription>() as Subscription;
|
||||||
{
|
|
||||||
Status:
|
var updateIsSubscriptionGoingUnpaid = previousSubscription is
|
||||||
StripeSubscriptionStatus.Trialing or
|
|
||||||
StripeSubscriptionStatus.Active or
|
|
||||||
StripeSubscriptionStatus.PastDue
|
|
||||||
} && subscription is
|
|
||||||
{
|
|
||||||
Status: StripeSubscriptionStatus.Unpaid,
|
|
||||||
LatestInvoice.BillingReason: "subscription_cycle" or "subscription_create"
|
|
||||||
})
|
|
||||||
{
|
{
|
||||||
if (subscription.TestClock != null)
|
Status:
|
||||||
|
StripeSubscriptionStatus.Trialing or
|
||||||
|
StripeSubscriptionStatus.Active or
|
||||||
|
StripeSubscriptionStatus.PastDue
|
||||||
|
} && currentSubscription is
|
||||||
|
{
|
||||||
|
Status: StripeSubscriptionStatus.Unpaid,
|
||||||
|
LatestInvoice.BillingReason: "subscription_cycle" or "subscription_create"
|
||||||
|
};
|
||||||
|
|
||||||
|
var updateIsManualSuspensionViaMetadata = CheckForManualSuspensionViaMetadata(
|
||||||
|
previousSubscription, currentSubscription);
|
||||||
|
|
||||||
|
if (updateIsSubscriptionGoingUnpaid || updateIsManualSuspensionViaMetadata)
|
||||||
|
{
|
||||||
|
if (currentSubscription.TestClock != null)
|
||||||
{
|
{
|
||||||
await WaitForTestClockToAdvanceAsync(subscription.TestClock);
|
await WaitForTestClockToAdvanceAsync(currentSubscription.TestClock);
|
||||||
}
|
}
|
||||||
|
|
||||||
var now = subscription.TestClock?.FrozenTime ?? DateTime.UtcNow;
|
var now = currentSubscription.TestClock?.FrozenTime ?? DateTime.UtcNow;
|
||||||
await _stripeFacade.UpdateSubscription(subscription.Id,
|
|
||||||
new SubscriptionUpdateOptions { CancelAt = now.AddDays(7) });
|
var subscriptionUpdateOptions = new SubscriptionUpdateOptions { CancelAt = now.AddDays(7) };
|
||||||
|
|
||||||
|
if (updateIsManualSuspensionViaMetadata)
|
||||||
|
{
|
||||||
|
subscriptionUpdateOptions.Metadata = new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
["suspended_provider_via_webhook_at"] = DateTime.UtcNow.ToString(CultureInfo.InvariantCulture)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
await _stripeFacade.UpdateSubscription(currentSubscription.Id, subscriptionUpdateOptions);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -379,4 +397,37 @@ public class SubscriptionUpdatedHandler : ISubscriptionUpdatedHandler
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static bool CheckForManualSuspensionViaMetadata(
|
||||||
|
Subscription? previousSubscription,
|
||||||
|
Subscription currentSubscription)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* When metadata on a subscription is updated, we'll receive an event that has:
|
||||||
|
* Previous Metadata: { newlyAddedKey: null }
|
||||||
|
* Current Metadata: { newlyAddedKey: newlyAddedValue }
|
||||||
|
*
|
||||||
|
* As such, our check for a manual suspension must ensure that the 'previous_attributes' does contain the
|
||||||
|
* 'metadata' property, but also that the "suspend_provider" key in that metadata is set to null.
|
||||||
|
*
|
||||||
|
* If we don't do this and instead do a null coalescing check on 'previous_attributes?.metadata?.TryGetValue',
|
||||||
|
* we'll end up marking an event where 'previous_attributes.metadata' = null (which could be any subscription update
|
||||||
|
* that does not update the metadata) the same as a manual suspension.
|
||||||
|
*/
|
||||||
|
const string key = "suspend_provider";
|
||||||
|
|
||||||
|
if (previousSubscription is not { Metadata: not null } ||
|
||||||
|
!previousSubscription.Metadata.TryGetValue(key, out var previousValue))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (previousValue == null)
|
||||||
|
{
|
||||||
|
return !string.IsNullOrEmpty(
|
||||||
|
currentSubscription.Metadata.TryGetValue(key, out var currentValue) ? currentValue : null);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ using NSubstitute;
|
|||||||
using NSubstitute.ReturnsExtensions;
|
using NSubstitute.ReturnsExtensions;
|
||||||
using Quartz;
|
using Quartz;
|
||||||
using Stripe;
|
using Stripe;
|
||||||
using Stripe.TestHelpers;
|
|
||||||
using Xunit;
|
using Xunit;
|
||||||
using Event = Stripe.Event;
|
using Event = Stripe.Event;
|
||||||
|
|
||||||
@@ -36,14 +35,12 @@ public class SubscriptionUpdatedHandlerTests
|
|||||||
private readonly IUserService _userService;
|
private readonly IUserService _userService;
|
||||||
private readonly IPushNotificationService _pushNotificationService;
|
private readonly IPushNotificationService _pushNotificationService;
|
||||||
private readonly IOrganizationRepository _organizationRepository;
|
private readonly IOrganizationRepository _organizationRepository;
|
||||||
private readonly ISchedulerFactory _schedulerFactory;
|
|
||||||
private readonly IOrganizationEnableCommand _organizationEnableCommand;
|
private readonly IOrganizationEnableCommand _organizationEnableCommand;
|
||||||
private readonly IOrganizationDisableCommand _organizationDisableCommand;
|
private readonly IOrganizationDisableCommand _organizationDisableCommand;
|
||||||
private readonly IPricingClient _pricingClient;
|
private readonly IPricingClient _pricingClient;
|
||||||
private readonly IFeatureService _featureService;
|
private readonly IFeatureService _featureService;
|
||||||
private readonly IProviderRepository _providerRepository;
|
private readonly IProviderRepository _providerRepository;
|
||||||
private readonly IProviderService _providerService;
|
private readonly IProviderService _providerService;
|
||||||
private readonly ILogger<SubscriptionUpdatedHandler> _logger;
|
|
||||||
private readonly IScheduler _scheduler;
|
private readonly IScheduler _scheduler;
|
||||||
private readonly SubscriptionUpdatedHandler _sut;
|
private readonly SubscriptionUpdatedHandler _sut;
|
||||||
|
|
||||||
@@ -58,18 +55,17 @@ public class SubscriptionUpdatedHandlerTests
|
|||||||
_providerService = Substitute.For<IProviderService>();
|
_providerService = Substitute.For<IProviderService>();
|
||||||
_pushNotificationService = Substitute.For<IPushNotificationService>();
|
_pushNotificationService = Substitute.For<IPushNotificationService>();
|
||||||
_organizationRepository = Substitute.For<IOrganizationRepository>();
|
_organizationRepository = Substitute.For<IOrganizationRepository>();
|
||||||
_providerRepository = Substitute.For<IProviderRepository>();
|
var schedulerFactory = Substitute.For<ISchedulerFactory>();
|
||||||
_schedulerFactory = Substitute.For<ISchedulerFactory>();
|
|
||||||
_organizationEnableCommand = Substitute.For<IOrganizationEnableCommand>();
|
_organizationEnableCommand = Substitute.For<IOrganizationEnableCommand>();
|
||||||
_organizationDisableCommand = Substitute.For<IOrganizationDisableCommand>();
|
_organizationDisableCommand = Substitute.For<IOrganizationDisableCommand>();
|
||||||
_pricingClient = Substitute.For<IPricingClient>();
|
_pricingClient = Substitute.For<IPricingClient>();
|
||||||
_featureService = Substitute.For<IFeatureService>();
|
_featureService = Substitute.For<IFeatureService>();
|
||||||
_providerRepository = Substitute.For<IProviderRepository>();
|
_providerRepository = Substitute.For<IProviderRepository>();
|
||||||
_providerService = Substitute.For<IProviderService>();
|
_providerService = Substitute.For<IProviderService>();
|
||||||
_logger = Substitute.For<ILogger<SubscriptionUpdatedHandler>>();
|
var logger = Substitute.For<ILogger<SubscriptionUpdatedHandler>>();
|
||||||
_scheduler = Substitute.For<IScheduler>();
|
_scheduler = Substitute.For<IScheduler>();
|
||||||
|
|
||||||
_schedulerFactory.GetScheduler().Returns(_scheduler);
|
schedulerFactory.GetScheduler().Returns(_scheduler);
|
||||||
|
|
||||||
_sut = new SubscriptionUpdatedHandler(
|
_sut = new SubscriptionUpdatedHandler(
|
||||||
_stripeEventService,
|
_stripeEventService,
|
||||||
@@ -80,14 +76,14 @@ public class SubscriptionUpdatedHandlerTests
|
|||||||
_userService,
|
_userService,
|
||||||
_pushNotificationService,
|
_pushNotificationService,
|
||||||
_organizationRepository,
|
_organizationRepository,
|
||||||
_schedulerFactory,
|
schedulerFactory,
|
||||||
_organizationEnableCommand,
|
_organizationEnableCommand,
|
||||||
_organizationDisableCommand,
|
_organizationDisableCommand,
|
||||||
_pricingClient,
|
_pricingClient,
|
||||||
_featureService,
|
_featureService,
|
||||||
_providerRepository,
|
_providerRepository,
|
||||||
_providerService,
|
_providerService,
|
||||||
_logger);
|
logger);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@@ -126,61 +122,54 @@ public class SubscriptionUpdatedHandlerTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task HandleAsync_UnpaidProviderSubscription_WithValidTransition_DisablesProviderAndSchedulesCancellation()
|
public async Task
|
||||||
|
HandleAsync_UnpaidProviderSubscription_WithManualSuspensionViaMetadata_DisablesProviderAndSchedulesCancellation()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var providerId = Guid.NewGuid();
|
var providerId = Guid.NewGuid();
|
||||||
const string subscriptionId = "sub_123";
|
var subscriptionId = "sub_test123";
|
||||||
var frozenTime = DateTime.UtcNow;
|
|
||||||
|
|
||||||
var testClock = new TestClock
|
var previousSubscription = new Subscription
|
||||||
{
|
{
|
||||||
Id = "clock_123",
|
Id = subscriptionId,
|
||||||
Status = "ready",
|
Status = StripeSubscriptionStatus.Active,
|
||||||
FrozenTime = frozenTime
|
Metadata = new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
["suspend_provider"] = null // This is the key part - metadata exists, but value is null
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var subscription = new Subscription
|
var currentSubscription = new Subscription
|
||||||
{
|
{
|
||||||
Id = subscriptionId,
|
Id = subscriptionId,
|
||||||
Status = StripeSubscriptionStatus.Unpaid,
|
Status = StripeSubscriptionStatus.Unpaid,
|
||||||
Metadata = new Dictionary<string, string> { { "providerId", providerId.ToString() } },
|
CurrentPeriodEnd = DateTime.UtcNow.AddDays(30),
|
||||||
LatestInvoice = new Invoice { BillingReason = "subscription_cycle" },
|
Metadata = new Dictionary<string, string>
|
||||||
TestClock = testClock
|
{
|
||||||
};
|
["providerId"] = providerId.ToString(),
|
||||||
|
["suspend_provider"] = "true" // Now has a value, indicating manual suspension
|
||||||
var provider = new Provider
|
},
|
||||||
{
|
TestClock = null
|
||||||
Id = providerId,
|
|
||||||
Name = "Test Provider",
|
|
||||||
Enabled = true
|
|
||||||
};
|
};
|
||||||
|
|
||||||
var parsedEvent = new Event
|
var parsedEvent = new Event
|
||||||
{
|
{
|
||||||
|
Id = "evt_test123",
|
||||||
|
Type = HandledStripeWebhook.SubscriptionUpdated,
|
||||||
Data = new EventData
|
Data = new EventData
|
||||||
{
|
{
|
||||||
PreviousAttributes = JObject.FromObject(new
|
Object = currentSubscription,
|
||||||
{
|
PreviousAttributes = JObject.FromObject(previousSubscription)
|
||||||
status = "active"
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
_stripeEventService.GetSubscription(Arg.Any<Event>(), Arg.Any<bool>(), Arg.Any<List<string>>())
|
var provider = new Provider { Id = providerId, Enabled = true };
|
||||||
.Returns(subscription);
|
|
||||||
|
|
||||||
_stripeEventUtilityService.GetIdsFromMetadata(Arg.Any<Dictionary<string, string>>())
|
_featureService.IsEnabled(FeatureFlagKeys.PM21821_ProviderPortalTakeover).Returns(true);
|
||||||
|
_stripeEventService.GetSubscription(parsedEvent, true, Arg.Any<List<string>>()).Returns(currentSubscription);
|
||||||
|
_stripeEventUtilityService.GetIdsFromMetadata(currentSubscription.Metadata)
|
||||||
.Returns(Tuple.Create<Guid?, Guid?, Guid?>(null, null, providerId));
|
.Returns(Tuple.Create<Guid?, Guid?, Guid?>(null, null, providerId));
|
||||||
|
_providerRepository.GetByIdAsync(providerId).Returns(provider);
|
||||||
_featureService.IsEnabled(FeatureFlagKeys.PM21821_ProviderPortalTakeover)
|
|
||||||
.Returns(true);
|
|
||||||
|
|
||||||
_providerRepository.GetByIdAsync(providerId)
|
|
||||||
.Returns(provider);
|
|
||||||
|
|
||||||
_stripeFacade.GetTestClock(testClock.Id)
|
|
||||||
.Returns(testClock);
|
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
await _sut.HandleAsync(parsedEvent);
|
await _sut.HandleAsync(parsedEvent);
|
||||||
@@ -188,8 +177,75 @@ public class SubscriptionUpdatedHandlerTests
|
|||||||
// Assert
|
// Assert
|
||||||
Assert.False(provider.Enabled);
|
Assert.False(provider.Enabled);
|
||||||
await _providerService.Received(1).UpdateAsync(provider);
|
await _providerService.Received(1).UpdateAsync(provider);
|
||||||
await _stripeFacade.Received(1).UpdateSubscription(subscriptionId,
|
|
||||||
Arg.Is<SubscriptionUpdateOptions>(o => o.CancelAt == frozenTime.AddDays(7)));
|
// Verify that UpdateSubscription was called with both CancelAt and the new metadata
|
||||||
|
await _stripeFacade.Received(1).UpdateSubscription(
|
||||||
|
subscriptionId,
|
||||||
|
Arg.Is<SubscriptionUpdateOptions>(options =>
|
||||||
|
options.CancelAt.HasValue &&
|
||||||
|
options.CancelAt.Value <= DateTime.UtcNow.AddDays(7).AddMinutes(1) &&
|
||||||
|
options.Metadata != null &&
|
||||||
|
options.Metadata.ContainsKey("suspended_provider_via_webhook_at")));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task
|
||||||
|
HandleAsync_UnpaidProviderSubscription_WithValidTransition_DisablesProviderAndSchedulesCancellation()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var providerId = Guid.NewGuid();
|
||||||
|
var subscriptionId = "sub_test123";
|
||||||
|
|
||||||
|
var previousSubscription = new Subscription
|
||||||
|
{
|
||||||
|
Id = subscriptionId,
|
||||||
|
Status = StripeSubscriptionStatus.Active,
|
||||||
|
Metadata = new Dictionary<string, string> { ["providerId"] = providerId.ToString() }
|
||||||
|
};
|
||||||
|
|
||||||
|
var currentSubscription = new Subscription
|
||||||
|
{
|
||||||
|
Id = subscriptionId,
|
||||||
|
Status = StripeSubscriptionStatus.Unpaid,
|
||||||
|
CurrentPeriodEnd = DateTime.UtcNow.AddDays(30),
|
||||||
|
Metadata = new Dictionary<string, string> { ["providerId"] = providerId.ToString() },
|
||||||
|
LatestInvoice = new Invoice { BillingReason = "subscription_cycle" },
|
||||||
|
TestClock = null
|
||||||
|
};
|
||||||
|
|
||||||
|
var parsedEvent = new Event
|
||||||
|
{
|
||||||
|
Id = "evt_test123",
|
||||||
|
Type = HandledStripeWebhook.SubscriptionUpdated,
|
||||||
|
Data = new EventData
|
||||||
|
{
|
||||||
|
Object = currentSubscription,
|
||||||
|
PreviousAttributes = JObject.FromObject(previousSubscription)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var provider = new Provider { Id = providerId, Enabled = true };
|
||||||
|
|
||||||
|
_featureService.IsEnabled(FeatureFlagKeys.PM21821_ProviderPortalTakeover).Returns(true);
|
||||||
|
_stripeEventService.GetSubscription(parsedEvent, true, Arg.Any<List<string>>()).Returns(currentSubscription);
|
||||||
|
_stripeEventUtilityService.GetIdsFromMetadata(currentSubscription.Metadata)
|
||||||
|
.Returns(Tuple.Create<Guid?, Guid?, Guid?>(null, null, providerId));
|
||||||
|
_providerRepository.GetByIdAsync(providerId).Returns(provider);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await _sut.HandleAsync(parsedEvent);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.False(provider.Enabled);
|
||||||
|
await _providerService.Received(1).UpdateAsync(provider);
|
||||||
|
|
||||||
|
// Verify that UpdateSubscription was called with CancelAt but WITHOUT suspension metadata
|
||||||
|
await _stripeFacade.Received(1).UpdateSubscription(
|
||||||
|
subscriptionId,
|
||||||
|
Arg.Is<SubscriptionUpdateOptions>(options =>
|
||||||
|
options.CancelAt.HasValue &&
|
||||||
|
options.CancelAt.Value <= DateTime.UtcNow.AddDays(7).AddMinutes(1) &&
|
||||||
|
(options.Metadata == null || !options.Metadata.ContainsKey("suspended_provider_via_webhook_at"))));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@@ -207,12 +263,7 @@ public class SubscriptionUpdatedHandlerTests
|
|||||||
LatestInvoice = new Invoice { BillingReason = "subscription_cycle" }
|
LatestInvoice = new Invoice { BillingReason = "subscription_cycle" }
|
||||||
};
|
};
|
||||||
|
|
||||||
var provider = new Provider
|
var provider = new Provider { Id = providerId, Name = "Test Provider", Enabled = true };
|
||||||
{
|
|
||||||
Id = providerId,
|
|
||||||
Name = "Test Provider",
|
|
||||||
Enabled = true
|
|
||||||
};
|
|
||||||
|
|
||||||
var parsedEvent = new Event
|
var parsedEvent = new Event
|
||||||
{
|
{
|
||||||
@@ -220,7 +271,7 @@ public class SubscriptionUpdatedHandlerTests
|
|||||||
{
|
{
|
||||||
PreviousAttributes = JObject.FromObject(new
|
PreviousAttributes = JObject.FromObject(new
|
||||||
{
|
{
|
||||||
status = "unpaid" // No valid transition
|
status = "unpaid" // No valid transition
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -261,20 +312,9 @@ public class SubscriptionUpdatedHandlerTests
|
|||||||
LatestInvoice = new Invoice { BillingReason = "subscription_cycle" }
|
LatestInvoice = new Invoice { BillingReason = "subscription_cycle" }
|
||||||
};
|
};
|
||||||
|
|
||||||
var provider = new Provider
|
var provider = new Provider { Id = providerId, Name = "Test Provider", Enabled = true };
|
||||||
{
|
|
||||||
Id = providerId,
|
|
||||||
Name = "Test Provider",
|
|
||||||
Enabled = true
|
|
||||||
};
|
|
||||||
|
|
||||||
var parsedEvent = new Event
|
var parsedEvent = new Event { Data = new EventData { PreviousAttributes = null } };
|
||||||
{
|
|
||||||
Data = new EventData
|
|
||||||
{
|
|
||||||
PreviousAttributes = null
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
_stripeEventService.GetSubscription(Arg.Any<Event>(), Arg.Any<bool>(), Arg.Any<List<string>>())
|
_stripeEventService.GetSubscription(Arg.Any<Event>(), Arg.Any<bool>(), Arg.Any<List<string>>())
|
||||||
.Returns(subscription);
|
.Returns(subscription);
|
||||||
@@ -314,12 +354,7 @@ public class SubscriptionUpdatedHandlerTests
|
|||||||
LatestInvoice = new Invoice { BillingReason = "renewal" }
|
LatestInvoice = new Invoice { BillingReason = "renewal" }
|
||||||
};
|
};
|
||||||
|
|
||||||
var provider = new Provider
|
var provider = new Provider { Id = providerId, Name = "Test Provider", Enabled = true };
|
||||||
{
|
|
||||||
Id = providerId,
|
|
||||||
Name = "Test Provider",
|
|
||||||
Enabled = true
|
|
||||||
};
|
|
||||||
|
|
||||||
var parsedEvent = new Event { Data = new EventData() };
|
var parsedEvent = new Event { Data = new EventData() };
|
||||||
|
|
||||||
@@ -434,10 +469,10 @@ public class SubscriptionUpdatedHandlerTests
|
|||||||
Metadata = new Dictionary<string, string> { { "userId", userId.ToString() } },
|
Metadata = new Dictionary<string, string> { { "userId", userId.ToString() } },
|
||||||
Items = new StripeList<SubscriptionItem>
|
Items = new StripeList<SubscriptionItem>
|
||||||
{
|
{
|
||||||
Data = new List<SubscriptionItem>
|
Data =
|
||||||
{
|
[
|
||||||
new() { Price = new Price { Id = IStripeEventUtilityService.PremiumPlanId } }
|
new SubscriptionItem { Price = new Price { Id = IStripeEventUtilityService.PremiumPlanId } }
|
||||||
}
|
]
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -478,11 +513,7 @@ public class SubscriptionUpdatedHandlerTests
|
|||||||
Metadata = new Dictionary<string, string> { { "organizationId", organizationId.ToString() } }
|
Metadata = new Dictionary<string, string> { { "organizationId", organizationId.ToString() } }
|
||||||
};
|
};
|
||||||
|
|
||||||
var organization = new Organization
|
var organization = new Organization { Id = organizationId, PlanType = PlanType.EnterpriseAnnually2023 };
|
||||||
{
|
|
||||||
Id = organizationId,
|
|
||||||
PlanType = PlanType.EnterpriseAnnually2023
|
|
||||||
};
|
|
||||||
var parsedEvent = new Event { Data = new EventData() };
|
var parsedEvent = new Event { Data = new EventData() };
|
||||||
|
|
||||||
_stripeEventService.GetSubscription(Arg.Any<Event>(), Arg.Any<bool>(), Arg.Any<List<string>>())
|
_stripeEventService.GetSubscription(Arg.Any<Event>(), Arg.Any<bool>(), Arg.Any<List<string>>())
|
||||||
@@ -495,7 +526,7 @@ public class SubscriptionUpdatedHandlerTests
|
|||||||
.Returns(organization);
|
.Returns(organization);
|
||||||
|
|
||||||
_stripeFacade.ListInvoices(Arg.Any<InvoiceListOptions>())
|
_stripeFacade.ListInvoices(Arg.Any<InvoiceListOptions>())
|
||||||
.Returns(new StripeList<Invoice> { Data = new List<Invoice> { new Invoice { Id = "inv_123" } } });
|
.Returns(new StripeList<Invoice> { Data = [new Invoice { Id = "inv_123" }] });
|
||||||
|
|
||||||
var plan = new Enterprise2023Plan(true);
|
var plan = new Enterprise2023Plan(true);
|
||||||
_pricingClient.GetPlanOrThrow(organization.PlanType)
|
_pricingClient.GetPlanOrThrow(organization.PlanType)
|
||||||
@@ -577,7 +608,8 @@ public class SubscriptionUpdatedHandlerTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task HandleAsync_WhenSubscriptionIsActive_AndOrganizationHasSecretsManagerTrial_AndRemovingSecretsManagerTrial_RemovesPasswordManagerCoupon()
|
public async Task
|
||||||
|
HandleAsync_WhenSubscriptionIsActive_AndOrganizationHasSecretsManagerTrial_AndRemovingSecretsManagerTrial_RemovesPasswordManagerCoupon()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var organizationId = Guid.NewGuid();
|
var organizationId = Guid.NewGuid();
|
||||||
@@ -589,34 +621,18 @@ public class SubscriptionUpdatedHandlerTests
|
|||||||
CustomerId = "cus_123",
|
CustomerId = "cus_123",
|
||||||
Items = new StripeList<SubscriptionItem>
|
Items = new StripeList<SubscriptionItem>
|
||||||
{
|
{
|
||||||
Data = new List<SubscriptionItem>
|
Data = [new SubscriptionItem { Plan = new Plan { Id = "2023-enterprise-org-seat-annually" } }]
|
||||||
{
|
|
||||||
new() { Plan = new Stripe.Plan { Id = "2023-enterprise-org-seat-annually" } }
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
Customer = new Customer
|
Customer = new Customer
|
||||||
{
|
{
|
||||||
Balance = 0,
|
Balance = 0,
|
||||||
Discount = new Discount
|
Discount = new Discount { Coupon = new Coupon { Id = "sm-standalone" } }
|
||||||
{
|
|
||||||
Coupon = new Coupon { Id = "sm-standalone" }
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
Discount = new Discount
|
Discount = new Discount { Coupon = new Coupon { Id = "sm-standalone" } },
|
||||||
{
|
Metadata = new Dictionary<string, string> { { "organizationId", organizationId.ToString() } }
|
||||||
Coupon = new Coupon { Id = "sm-standalone" }
|
|
||||||
},
|
|
||||||
Metadata = new Dictionary<string, string>
|
|
||||||
{
|
|
||||||
{ "organizationId", organizationId.ToString() }
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
var organization = new Organization
|
var organization = new Organization { Id = organizationId, PlanType = PlanType.EnterpriseAnnually2023 };
|
||||||
{
|
|
||||||
Id = organizationId,
|
|
||||||
PlanType = PlanType.EnterpriseAnnually2023
|
|
||||||
};
|
|
||||||
|
|
||||||
var plan = new Enterprise2023Plan(true);
|
var plan = new Enterprise2023Plan(true);
|
||||||
_pricingClient.GetPlanOrThrow(organization.PlanType)
|
_pricingClient.GetPlanOrThrow(organization.PlanType)
|
||||||
@@ -631,20 +647,14 @@ public class SubscriptionUpdatedHandlerTests
|
|||||||
{
|
{
|
||||||
items = new
|
items = new
|
||||||
{
|
{
|
||||||
data = new[]
|
data = new[] { new { plan = new { id = "secrets-manager-enterprise-seat-annually" } } }
|
||||||
{
|
|
||||||
new { plan = new { id = "secrets-manager-enterprise-seat-annually" } }
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
Items = new StripeList<SubscriptionItem>
|
Items = new StripeList<SubscriptionItem>
|
||||||
{
|
{
|
||||||
Data = new List<SubscriptionItem>
|
Data =
|
||||||
{
|
[
|
||||||
new SubscriptionItem
|
new SubscriptionItem { Plan = new Plan { Id = "secrets-manager-enterprise-seat-annually" } }
|
||||||
{
|
]
|
||||||
Plan = new Stripe.Plan { Id = "secrets-manager-enterprise-seat-annually" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -990,7 +1000,7 @@ public class SubscriptionUpdatedHandlerTests
|
|||||||
{
|
{
|
||||||
Id = previousSubscription?.Id ?? "sub_123",
|
Id = previousSubscription?.Id ?? "sub_123",
|
||||||
Status = StripeSubscriptionStatus.Active,
|
Status = StripeSubscriptionStatus.Active,
|
||||||
Metadata = new Dictionary<string, string> { { "providerId", providerId.ToString() } },
|
Metadata = new Dictionary<string, string> { { "providerId", providerId.ToString() } }
|
||||||
};
|
};
|
||||||
|
|
||||||
var provider = new Provider { Id = providerId, Enabled = false };
|
var provider = new Provider { Id = providerId, Enabled = false };
|
||||||
@@ -1010,10 +1020,10 @@ public class SubscriptionUpdatedHandlerTests
|
|||||||
{
|
{
|
||||||
return new List<object[]>
|
return new List<object[]>
|
||||||
{
|
{
|
||||||
new object[] { new Subscription { Id = "sub_123", Status = StripeSubscriptionStatus.Unpaid }, },
|
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.Incomplete } },
|
||||||
new object[] { new Subscription { Id = "sub_123", Status = StripeSubscriptionStatus.IncompleteExpired }, },
|
new object[] { new Subscription { Id = "sub_123", Status = StripeSubscriptionStatus.IncompleteExpired } },
|
||||||
new object[] { new Subscription { Id = "sub_123", Status = StripeSubscriptionStatus.Paused }, },
|
new object[] { new Subscription { Id = "sub_123", Status = StripeSubscriptionStatus.Paused } }
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user