Skip to content

Commit 794240f

Browse files
authored
[PM-29732] (fix) storage job no longer ignores trialing and past_due statuses (#6737)
1 parent 39a6719 commit 794240f

2 files changed

Lines changed: 163 additions & 29 deletions

File tree

src/Billing/Jobs/ReconcileAdditionalStorageJob.cs

Lines changed: 13 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -39,15 +39,11 @@ protected override async Task ExecuteJobAsync(IJobExecutionContext context)
3939
logger.LogInformation("Starting ReconcileAdditionalStorageJob (live mode: {LiveMode})", liveMode);
4040

4141
var priceIds = new[] { _storageGbMonthlyPriceId, _storageGbAnnuallyPriceId, _personalStorageGbAnnuallyPriceId };
42+
var stripeStatusesToProcess = new[] { StripeConstants.SubscriptionStatus.Active, StripeConstants.SubscriptionStatus.Trialing, StripeConstants.SubscriptionStatus.PastDue };
4243

4344
foreach (var priceId in priceIds)
4445
{
45-
var options = new SubscriptionListOptions
46-
{
47-
Limit = 100,
48-
Status = StripeConstants.SubscriptionStatus.Active,
49-
Price = priceId
50-
};
46+
var options = new SubscriptionListOptions { Limit = 100, Price = priceId };
5147

5248
await foreach (var subscription in stripeFacade.ListSubscriptionsAutoPagingAsync(options))
5349
{
@@ -64,7 +60,7 @@ protected override async Task ExecuteJobAsync(IJobExecutionContext context)
6460
failures.Count > 0
6561
? $", Failures: {Environment.NewLine}{string.Join(Environment.NewLine, failures)}"
6662
: string.Empty
67-
);
63+
);
6864
return;
6965
}
7066

@@ -73,6 +69,12 @@ protected override async Task ExecuteJobAsync(IJobExecutionContext context)
7369
continue;
7470
}
7571

72+
if (!stripeStatusesToProcess.Contains(subscription.Status))
73+
{
74+
logger.LogInformation("Skipping subscription with unsupported status: {SubscriptionId} - {Status}", subscription.Id, subscription.Status);
75+
continue;
76+
}
77+
7678
logger.LogInformation("Processing subscription: {SubscriptionId}", subscription.Id);
7779
subscriptionsFound++;
7880

@@ -133,7 +135,7 @@ protected override async Task ExecuteJobAsync(IJobExecutionContext context)
133135
failures.Count > 0
134136
? $", Failures: {Environment.NewLine}{string.Join(Environment.NewLine, failures)}"
135137
: string.Empty
136-
);
138+
);
137139
}
138140

139141
private SubscriptionUpdateOptions? BuildSubscriptionUpdateOptions(
@@ -145,15 +147,7 @@ protected override async Task ExecuteJobAsync(IJobExecutionContext context)
145147
return null;
146148
}
147149

148-
var updateOptions = new SubscriptionUpdateOptions
149-
{
150-
ProrationBehavior = StripeConstants.ProrationBehavior.CreateProrations,
151-
Metadata = new Dictionary<string, string>
152-
{
153-
[StripeConstants.MetadataKeys.StorageReconciled2025] = DateTime.UtcNow.ToString("o")
154-
},
155-
Items = []
156-
};
150+
var updateOptions = new SubscriptionUpdateOptions { ProrationBehavior = StripeConstants.ProrationBehavior.CreateProrations, Metadata = new Dictionary<string, string> { [StripeConstants.MetadataKeys.StorageReconciled2025] = DateTime.UtcNow.ToString("o") }, Items = [] };
157151

158152
var hasUpdates = false;
159153

@@ -172,11 +166,7 @@ protected override async Task ExecuteJobAsync(IJobExecutionContext context)
172166
newQuantity,
173167
item.Price.Id);
174168

175-
updateOptions.Items.Add(new SubscriptionItemOptions
176-
{
177-
Id = item.Id,
178-
Quantity = newQuantity
179-
});
169+
updateOptions.Items.Add(new SubscriptionItemOptions { Id = item.Id, Quantity = newQuantity });
180170
}
181171
else
182172
{
@@ -185,11 +175,7 @@ protected override async Task ExecuteJobAsync(IJobExecutionContext context)
185175
currentQuantity,
186176
item.Price.Id);
187177

188-
updateOptions.Items.Add(new SubscriptionItemOptions
189-
{
190-
Id = item.Id,
191-
Deleted = true
192-
});
178+
updateOptions.Items.Add(new SubscriptionItemOptions { Id = item.Id, Deleted = true });
193179
}
194180
}
195181

test/Billing.Test/Jobs/ReconcileAdditionalStorageJobTests.cs

Lines changed: 150 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ public async Task Execute_FeatureFlagEnabled_ProcessesSubscriptions()
6262

6363
// Assert
6464
_stripeFacade.Received(3).ListSubscriptionsAutoPagingAsync(
65-
Arg.Is<SubscriptionListOptions>(o => o.Status == "active"));
65+
Arg.Is<SubscriptionListOptions>(o => o.Limit == 100));
6666
}
6767

6868
#endregion
@@ -553,6 +553,152 @@ public async Task Execute_UpdateFails_LogsError()
553553

554554
#endregion
555555

556+
#region Subscription Status Filtering Tests
557+
558+
[Fact]
559+
public async Task Execute_ActiveStatusSubscription_ProcessesSubscription()
560+
{
561+
// Arrange
562+
var context = CreateJobExecutionContext();
563+
_featureService.IsEnabled(FeatureFlagKeys.PM28265_EnableReconcileAdditionalStorageJob).Returns(true);
564+
_featureService.IsEnabled(FeatureFlagKeys.PM28265_ReconcileAdditionalStorageJobEnableLiveMode).Returns(true);
565+
566+
var subscription = CreateSubscription("sub_123", "storage-gb-monthly", quantity: 10, status: StripeConstants.SubscriptionStatus.Active);
567+
568+
_stripeFacade.ListSubscriptionsAutoPagingAsync(Arg.Any<SubscriptionListOptions>())
569+
.Returns(AsyncEnumerable.Create(subscription));
570+
_stripeFacade.UpdateSubscription(Arg.Any<string>(), Arg.Any<SubscriptionUpdateOptions>())
571+
.Returns(subscription);
572+
573+
// Act
574+
await _sut.Execute(context);
575+
576+
// Assert
577+
await _stripeFacade.Received(1).UpdateSubscription("sub_123", Arg.Any<SubscriptionUpdateOptions>());
578+
}
579+
580+
[Fact]
581+
public async Task Execute_TrialingStatusSubscription_ProcessesSubscription()
582+
{
583+
// Arrange
584+
var context = CreateJobExecutionContext();
585+
_featureService.IsEnabled(FeatureFlagKeys.PM28265_EnableReconcileAdditionalStorageJob).Returns(true);
586+
_featureService.IsEnabled(FeatureFlagKeys.PM28265_ReconcileAdditionalStorageJobEnableLiveMode).Returns(true);
587+
588+
var subscription = CreateSubscription("sub_123", "storage-gb-monthly", quantity: 10, status: StripeConstants.SubscriptionStatus.Trialing);
589+
590+
_stripeFacade.ListSubscriptionsAutoPagingAsync(Arg.Any<SubscriptionListOptions>())
591+
.Returns(AsyncEnumerable.Create(subscription));
592+
_stripeFacade.UpdateSubscription(Arg.Any<string>(), Arg.Any<SubscriptionUpdateOptions>())
593+
.Returns(subscription);
594+
595+
// Act
596+
await _sut.Execute(context);
597+
598+
// Assert
599+
await _stripeFacade.Received(1).UpdateSubscription("sub_123", Arg.Any<SubscriptionUpdateOptions>());
600+
}
601+
602+
[Fact]
603+
public async Task Execute_PastDueStatusSubscription_ProcessesSubscription()
604+
{
605+
// Arrange
606+
var context = CreateJobExecutionContext();
607+
_featureService.IsEnabled(FeatureFlagKeys.PM28265_EnableReconcileAdditionalStorageJob).Returns(true);
608+
_featureService.IsEnabled(FeatureFlagKeys.PM28265_ReconcileAdditionalStorageJobEnableLiveMode).Returns(true);
609+
610+
var subscription = CreateSubscription("sub_123", "storage-gb-monthly", quantity: 10, status: StripeConstants.SubscriptionStatus.PastDue);
611+
612+
_stripeFacade.ListSubscriptionsAutoPagingAsync(Arg.Any<SubscriptionListOptions>())
613+
.Returns(AsyncEnumerable.Create(subscription));
614+
_stripeFacade.UpdateSubscription(Arg.Any<string>(), Arg.Any<SubscriptionUpdateOptions>())
615+
.Returns(subscription);
616+
617+
// Act
618+
await _sut.Execute(context);
619+
620+
// Assert
621+
await _stripeFacade.Received(1).UpdateSubscription("sub_123", Arg.Any<SubscriptionUpdateOptions>());
622+
}
623+
624+
[Fact]
625+
public async Task Execute_CanceledStatusSubscription_SkipsSubscription()
626+
{
627+
// Arrange
628+
var context = CreateJobExecutionContext();
629+
_featureService.IsEnabled(FeatureFlagKeys.PM28265_EnableReconcileAdditionalStorageJob).Returns(true);
630+
_featureService.IsEnabled(FeatureFlagKeys.PM28265_ReconcileAdditionalStorageJobEnableLiveMode).Returns(true);
631+
632+
var subscription = CreateSubscription("sub_123", "storage-gb-monthly", quantity: 10, status: StripeConstants.SubscriptionStatus.Canceled);
633+
634+
_stripeFacade.ListSubscriptionsAutoPagingAsync(Arg.Any<SubscriptionListOptions>())
635+
.Returns(AsyncEnumerable.Create(subscription));
636+
637+
// Act
638+
await _sut.Execute(context);
639+
640+
// Assert
641+
await _stripeFacade.DidNotReceiveWithAnyArgs().UpdateSubscription(null!);
642+
}
643+
644+
[Fact]
645+
public async Task Execute_IncompleteStatusSubscription_SkipsSubscription()
646+
{
647+
// Arrange
648+
var context = CreateJobExecutionContext();
649+
_featureService.IsEnabled(FeatureFlagKeys.PM28265_EnableReconcileAdditionalStorageJob).Returns(true);
650+
_featureService.IsEnabled(FeatureFlagKeys.PM28265_ReconcileAdditionalStorageJobEnableLiveMode).Returns(true);
651+
652+
var subscription = CreateSubscription("sub_123", "storage-gb-monthly", quantity: 10, status: StripeConstants.SubscriptionStatus.Incomplete);
653+
654+
_stripeFacade.ListSubscriptionsAutoPagingAsync(Arg.Any<SubscriptionListOptions>())
655+
.Returns(AsyncEnumerable.Create(subscription));
656+
657+
// Act
658+
await _sut.Execute(context);
659+
660+
// Assert
661+
await _stripeFacade.DidNotReceiveWithAnyArgs().UpdateSubscription(null!);
662+
}
663+
664+
[Fact]
665+
public async Task Execute_MixedSubscriptionStatuses_OnlyProcessesValidStatuses()
666+
{
667+
// Arrange
668+
var context = CreateJobExecutionContext();
669+
_featureService.IsEnabled(FeatureFlagKeys.PM28265_EnableReconcileAdditionalStorageJob).Returns(true);
670+
_featureService.IsEnabled(FeatureFlagKeys.PM28265_ReconcileAdditionalStorageJobEnableLiveMode).Returns(true);
671+
672+
var activeSubscription = CreateSubscription("sub_active", "storage-gb-monthly", quantity: 10, status: StripeConstants.SubscriptionStatus.Active);
673+
var trialingSubscription = CreateSubscription("sub_trialing", "storage-gb-monthly", quantity: 8, status: StripeConstants.SubscriptionStatus.Trialing);
674+
var pastDueSubscription = CreateSubscription("sub_pastdue", "storage-gb-monthly", quantity: 6, status: StripeConstants.SubscriptionStatus.PastDue);
675+
var canceledSubscription = CreateSubscription("sub_canceled", "storage-gb-monthly", quantity: 5, status: StripeConstants.SubscriptionStatus.Canceled);
676+
var incompleteSubscription = CreateSubscription("sub_incomplete", "storage-gb-monthly", quantity: 4, status: StripeConstants.SubscriptionStatus.Incomplete);
677+
678+
_stripeFacade.ListSubscriptionsAutoPagingAsync(Arg.Any<SubscriptionListOptions>())
679+
.Returns(AsyncEnumerable.Create(activeSubscription, trialingSubscription, pastDueSubscription, canceledSubscription, incompleteSubscription));
680+
_stripeFacade.UpdateSubscription(Arg.Any<string>(), Arg.Any<SubscriptionUpdateOptions>())
681+
.Returns(callInfo => callInfo.Arg<string>() switch
682+
{
683+
"sub_active" => activeSubscription,
684+
"sub_trialing" => trialingSubscription,
685+
"sub_pastdue" => pastDueSubscription,
686+
_ => null
687+
});
688+
689+
// Act
690+
await _sut.Execute(context);
691+
692+
// Assert
693+
await _stripeFacade.Received(1).UpdateSubscription("sub_active", Arg.Any<SubscriptionUpdateOptions>());
694+
await _stripeFacade.Received(1).UpdateSubscription("sub_trialing", Arg.Any<SubscriptionUpdateOptions>());
695+
await _stripeFacade.Received(1).UpdateSubscription("sub_pastdue", Arg.Any<SubscriptionUpdateOptions>());
696+
await _stripeFacade.DidNotReceive().UpdateSubscription("sub_canceled", Arg.Any<SubscriptionUpdateOptions>());
697+
await _stripeFacade.DidNotReceive().UpdateSubscription("sub_incomplete", Arg.Any<SubscriptionUpdateOptions>());
698+
}
699+
700+
#endregion
701+
556702
#region Cancellation Tests
557703

558704
[Fact]
@@ -598,7 +744,8 @@ private static Subscription CreateSubscription(
598744
string id,
599745
string priceId,
600746
long? quantity = null,
601-
Dictionary<string, string>? metadata = null)
747+
Dictionary<string, string>? metadata = null,
748+
string status = StripeConstants.SubscriptionStatus.Active)
602749
{
603750
var price = new Price { Id = priceId };
604751
var item = new SubscriptionItem
@@ -611,6 +758,7 @@ private static Subscription CreateSubscription(
611758
return new Subscription
612759
{
613760
Id = id,
761+
Status = status,
614762
Metadata = metadata,
615763
Items = new StripeList<SubscriptionItem>
616764
{

0 commit comments

Comments
 (0)