From 5bbd9054017850b86c1620e29a0e53e331b00aed Mon Sep 17 00:00:00 2001
From: Conner Turnbull <133619638+cturnbull-bitwarden@users.noreply.github.com>
Date: Thu, 20 Feb 2025 13:03:29 -0500
Subject: [PATCH] [PM-18436] Only cancel subscriptions when creating or
renewing (#5423)
* Only cancel subscriptions during creation or cycle renewal
* Resolved possible null reference warning
* Inverted conitional to reduce nesting
---
.../Jobs/SubscriptionCancellationJob.cs | 4 +-
.../SubscriptionUpdatedHandler.cs | 43 +++++++++++--------
2 files changed, 26 insertions(+), 21 deletions(-)
diff --git a/src/Billing/Jobs/SubscriptionCancellationJob.cs b/src/Billing/Jobs/SubscriptionCancellationJob.cs
index c46581272e..b59bb10eaf 100644
--- a/src/Billing/Jobs/SubscriptionCancellationJob.cs
+++ b/src/Billing/Jobs/SubscriptionCancellationJob.cs
@@ -23,9 +23,9 @@ public class SubscriptionCancellationJob(
}
var subscription = await stripeFacade.GetSubscription(subscriptionId);
- if (subscription?.Status != "unpaid")
+ if (subscription?.Status != "unpaid" ||
+ subscription.LatestInvoice?.BillingReason is not ("subscription_cycle" or "subscription_create"))
{
- // Subscription is no longer unpaid, skip cancellation
return;
}
diff --git a/src/Billing/Services/Implementations/SubscriptionUpdatedHandler.cs b/src/Billing/Services/Implementations/SubscriptionUpdatedHandler.cs
index 10a1d1a186..4e142f8cae 100644
--- a/src/Billing/Services/Implementations/SubscriptionUpdatedHandler.cs
+++ b/src/Billing/Services/Implementations/SubscriptionUpdatedHandler.cs
@@ -59,7 +59,7 @@ public class SubscriptionUpdatedHandler : ISubscriptionUpdatedHandler
///
public async Task HandleAsync(Event parsedEvent)
{
- var subscription = await _stripeEventService.GetSubscription(parsedEvent, true, ["customer", "discounts"]);
+ var subscription = await _stripeEventService.GetSubscription(parsedEvent, true, ["customer", "discounts", "latest_invoice"]);
var (organizationId, userId, providerId) = _stripeEventUtilityService.GetIdsFromMetadata(subscription.Metadata);
switch (subscription.Status)
@@ -68,7 +68,8 @@ public class SubscriptionUpdatedHandler : ISubscriptionUpdatedHandler
when organizationId.HasValue:
{
await _organizationService.DisableAsync(organizationId.Value, subscription.CurrentPeriodEnd);
- if (subscription.Status == StripeSubscriptionStatus.Unpaid)
+ if (subscription.Status == StripeSubscriptionStatus.Unpaid &&
+ subscription.LatestInvoice is { BillingReason: "subscription_cycle" or "subscription_create" })
{
await ScheduleCancellationJobAsync(subscription.Id, organizationId.Value);
}
@@ -96,7 +97,10 @@ public class SubscriptionUpdatedHandler : ISubscriptionUpdatedHandler
{
await _organizationEnableCommand.EnableAsync(organizationId.Value);
var organization = await _organizationRepository.GetByIdAsync(organizationId.Value);
- await _pushNotificationService.PushSyncOrganizationStatusAsync(organization);
+ if (organization != null)
+ {
+ await _pushNotificationService.PushSyncOrganizationStatusAsync(organization);
+ }
break;
}
case StripeSubscriptionStatus.Active:
@@ -204,23 +208,24 @@ public class SubscriptionUpdatedHandler : ISubscriptionUpdatedHandler
private async Task ScheduleCancellationJobAsync(string subscriptionId, Guid organizationId)
{
var isResellerManagedOrgAlertEnabled = _featureService.IsEnabled(FeatureFlagKeys.ResellerManagedOrgAlert);
-
- if (isResellerManagedOrgAlertEnabled)
+ if (!isResellerManagedOrgAlertEnabled)
{
- var scheduler = await _schedulerFactory.GetScheduler();
-
- var job = JobBuilder.Create()
- .WithIdentity($"cancel-sub-{subscriptionId}", "subscription-cancellations")
- .UsingJobData("subscriptionId", subscriptionId)
- .UsingJobData("organizationId", organizationId.ToString())
- .Build();
-
- var trigger = TriggerBuilder.Create()
- .WithIdentity($"cancel-trigger-{subscriptionId}", "subscription-cancellations")
- .StartAt(DateTimeOffset.UtcNow.AddDays(7))
- .Build();
-
- await scheduler.ScheduleJob(job, trigger);
+ return;
}
+
+ var scheduler = await _schedulerFactory.GetScheduler();
+
+ var job = JobBuilder.Create()
+ .WithIdentity($"cancel-sub-{subscriptionId}", "subscription-cancellations")
+ .UsingJobData("subscriptionId", subscriptionId)
+ .UsingJobData("organizationId", organizationId.ToString())
+ .Build();
+
+ var trigger = TriggerBuilder.Create()
+ .WithIdentity($"cancel-trigger-{subscriptionId}", "subscription-cancellations")
+ .StartAt(DateTimeOffset.UtcNow.AddDays(7))
+ .Build();
+
+ await scheduler.ScheduleJob(job, trigger);
}
}