Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weโ€™ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[PM-15807]Move subscription to 'canceled' 7 days after unpaid #5221

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/Billing/Jobs/JobsHostedService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,6 @@
public static void AddJobsServices(IServiceCollection services)
{
services.AddTransient<AliveJob>();
services.AddTransient<SubscriptionCancellationJob>();

Check warning on line 35 in src/Billing/Jobs/JobsHostedService.cs

View check run for this annotation

Codecov / codecov/patch

src/Billing/Jobs/JobsHostedService.cs#L35

Added line #L35 was not covered by tests
}
}
58 changes: 58 additions & 0 deletions src/Billing/Jobs/SubscriptionCancellationJob.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
๏ปฟusing Bit.Billing.Services;
using Bit.Core.Repositories;
using Quartz;
using Stripe;

namespace Bit.Billing.Jobs;

public class SubscriptionCancellationJob(
IStripeFacade stripeFacade,
IOrganizationRepository organizationRepository)

Check warning on line 10 in src/Billing/Jobs/SubscriptionCancellationJob.cs

View check run for this annotation

Codecov / codecov/patch

src/Billing/Jobs/SubscriptionCancellationJob.cs#L8-L10

Added lines #L8 - L10 were not covered by tests
: IJob
{
public async Task Execute(IJobExecutionContext context)
{
var subscriptionId = context.MergedJobDataMap.GetString("subscriptionId");

Check warning on line 15 in src/Billing/Jobs/SubscriptionCancellationJob.cs

View check run for this annotation

Codecov / codecov/patch

src/Billing/Jobs/SubscriptionCancellationJob.cs#L14-L15

Added lines #L14 - L15 were not covered by tests
var organizationId = new Guid(context.MergedJobDataMap.GetString("organizationId") ?? string.Empty);

var organization = await organizationRepository.GetByIdAsync(organizationId);

Check warning on line 18 in src/Billing/Jobs/SubscriptionCancellationJob.cs

View check run for this annotation

Codecov / codecov/patch

src/Billing/Jobs/SubscriptionCancellationJob.cs#L18

Added line #L18 was not covered by tests
if (organization == null || organization.Enabled)
{

Check warning on line 20 in src/Billing/Jobs/SubscriptionCancellationJob.cs

View check run for this annotation

Codecov / codecov/patch

src/Billing/Jobs/SubscriptionCancellationJob.cs#L20

Added line #L20 was not covered by tests
// Organization was deleted or re-enabled by CS, skip cancellation
return;

Check warning on line 22 in src/Billing/Jobs/SubscriptionCancellationJob.cs

View check run for this annotation

Codecov / codecov/patch

src/Billing/Jobs/SubscriptionCancellationJob.cs#L22

Added line #L22 was not covered by tests
}

var subscription = await stripeFacade.GetSubscription(subscriptionId);

Check warning on line 25 in src/Billing/Jobs/SubscriptionCancellationJob.cs

View check run for this annotation

Codecov / codecov/patch

src/Billing/Jobs/SubscriptionCancellationJob.cs#L25

Added line #L25 was not covered by tests
if (subscription?.Status != "unpaid")
{

Check warning on line 27 in src/Billing/Jobs/SubscriptionCancellationJob.cs

View check run for this annotation

Codecov / codecov/patch

src/Billing/Jobs/SubscriptionCancellationJob.cs#L27

Added line #L27 was not covered by tests
// Subscription is no longer unpaid, skip cancellation
return;

Check warning on line 29 in src/Billing/Jobs/SubscriptionCancellationJob.cs

View check run for this annotation

Codecov / codecov/patch

src/Billing/Jobs/SubscriptionCancellationJob.cs#L29

Added line #L29 was not covered by tests
}

// Cancel the subscription
await stripeFacade.CancelSubscription(subscriptionId, new SubscriptionCancelOptions());

Check warning on line 33 in src/Billing/Jobs/SubscriptionCancellationJob.cs

View check run for this annotation

Codecov / codecov/patch

src/Billing/Jobs/SubscriptionCancellationJob.cs#L33

Added line #L33 was not covered by tests

// Void any open invoices
var options = new InvoiceListOptions
{
Status = "open",
Subscription = subscriptionId,
Limit = 100
};
var invoices = await stripeFacade.ListInvoices(options);

Check warning on line 42 in src/Billing/Jobs/SubscriptionCancellationJob.cs

View check run for this annotation

Codecov / codecov/patch

src/Billing/Jobs/SubscriptionCancellationJob.cs#L36-L42

Added lines #L36 - L42 were not covered by tests
foreach (var invoice in invoices)
{
await stripeFacade.VoidInvoice(invoice.Id);
}

Check warning on line 46 in src/Billing/Jobs/SubscriptionCancellationJob.cs

View check run for this annotation

Codecov / codecov/patch

src/Billing/Jobs/SubscriptionCancellationJob.cs#L44-L46

Added lines #L44 - L46 were not covered by tests

while (invoices.HasMore)
{
options.StartingAfter = invoices.Data.Last().Id;
invoices = await stripeFacade.ListInvoices(options);

Check warning on line 51 in src/Billing/Jobs/SubscriptionCancellationJob.cs

View check run for this annotation

Codecov / codecov/patch

src/Billing/Jobs/SubscriptionCancellationJob.cs#L49-L51

Added lines #L49 - L51 were not covered by tests
foreach (var invoice in invoices)
{
await stripeFacade.VoidInvoice(invoice.Id);
}
}
}

Check warning on line 57 in src/Billing/Jobs/SubscriptionCancellationJob.cs

View check run for this annotation

Codecov / codecov/patch

src/Billing/Jobs/SubscriptionCancellationJob.cs#L53-L57

Added lines #L53 - L57 were not covered by tests
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
๏ปฟusing Bit.Billing.Constants;
using Bit.Billing.Jobs;
using Bit.Core;
using Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.Interfaces;
using Bit.Core.Repositories;
using Bit.Core.Services;
using Bit.Core.Utilities;
using Quartz;
using Stripe;
using Event = Stripe.Event;

Expand All @@ -18,6 +21,8 @@
private readonly IUserService _userService;
private readonly IPushNotificationService _pushNotificationService;
private readonly IOrganizationRepository _organizationRepository;
private readonly ISchedulerFactory _schedulerFactory;
private readonly IFeatureService _featureService;

public SubscriptionUpdatedHandler(
IStripeEventService stripeEventService,
Expand All @@ -27,7 +32,9 @@
IOrganizationSponsorshipRenewCommand organizationSponsorshipRenewCommand,
IUserService userService,
IPushNotificationService pushNotificationService,
IOrganizationRepository organizationRepository)
IOrganizationRepository organizationRepository,
ISchedulerFactory schedulerFactory,
IFeatureService featureService)

Check warning on line 37 in src/Billing/Services/Implementations/SubscriptionUpdatedHandler.cs

View check run for this annotation

Codecov / codecov/patch

src/Billing/Services/Implementations/SubscriptionUpdatedHandler.cs#L35-L37

Added lines #L35 - L37 were not covered by tests
{
_stripeEventService = stripeEventService;
_stripeEventUtilityService = stripeEventUtilityService;
Expand All @@ -37,6 +44,8 @@
_userService = userService;
_pushNotificationService = pushNotificationService;
_organizationRepository = organizationRepository;
_schedulerFactory = schedulerFactory;
_featureService = featureService;

Check warning on line 48 in src/Billing/Services/Implementations/SubscriptionUpdatedHandler.cs

View check run for this annotation

Codecov / codecov/patch

src/Billing/Services/Implementations/SubscriptionUpdatedHandler.cs#L47-L48

Added lines #L47 - L48 were not covered by tests
}

/// <summary>
Expand All @@ -54,6 +63,10 @@
when organizationId.HasValue:
{
await _organizationService.DisableAsync(organizationId.Value, subscription.CurrentPeriodEnd);
if (subscription.Status == StripeSubscriptionStatus.Unpaid)
{
await ScheduleCancellationJobAsync(subscription.Id, organizationId.Value);
}

Check warning on line 69 in src/Billing/Services/Implementations/SubscriptionUpdatedHandler.cs

View check run for this annotation

Codecov / codecov/patch

src/Billing/Services/Implementations/SubscriptionUpdatedHandler.cs#L67-L69

Added lines #L67 - L69 were not covered by tests
break;
}
case StripeSubscriptionStatus.Unpaid or StripeSubscriptionStatus.IncompleteExpired:
Expand Down Expand Up @@ -182,4 +195,27 @@
await _stripeFacade.DeleteSubscriptionDiscount(subscription.Id);
}
}

private async Task ScheduleCancellationJobAsync(string subscriptionId, Guid organizationId)
{
var isResellerManagedOrgAlertEnabled = _featureService.IsEnabled(FeatureFlagKeys.ResellerManagedOrgAlert);

Check warning on line 201 in src/Billing/Services/Implementations/SubscriptionUpdatedHandler.cs

View check run for this annotation

Codecov / codecov/patch

src/Billing/Services/Implementations/SubscriptionUpdatedHandler.cs#L200-L201

Added lines #L200 - L201 were not covered by tests

if (isResellerManagedOrgAlertEnabled)
{
var scheduler = await _schedulerFactory.GetScheduler();

Check warning on line 205 in src/Billing/Services/Implementations/SubscriptionUpdatedHandler.cs

View check run for this annotation

Codecov / codecov/patch

src/Billing/Services/Implementations/SubscriptionUpdatedHandler.cs#L204-L205

Added lines #L204 - L205 were not covered by tests

var job = JobBuilder.Create<SubscriptionCancellationJob>()
.WithIdentity($"cancel-sub-{subscriptionId}", "subscription-cancellations")
.UsingJobData("subscriptionId", subscriptionId)
.UsingJobData("organizationId", organizationId.ToString())
.Build();

Check warning on line 211 in src/Billing/Services/Implementations/SubscriptionUpdatedHandler.cs

View check run for this annotation

Codecov / codecov/patch

src/Billing/Services/Implementations/SubscriptionUpdatedHandler.cs#L207-L211

Added lines #L207 - L211 were not covered by tests

var trigger = TriggerBuilder.Create()
.WithIdentity($"cancel-trigger-{subscriptionId}", "subscription-cancellations")
.StartAt(DateTimeOffset.UtcNow.AddDays(7))
.Build();

Check warning on line 216 in src/Billing/Services/Implementations/SubscriptionUpdatedHandler.cs

View check run for this annotation

Codecov / codecov/patch

src/Billing/Services/Implementations/SubscriptionUpdatedHandler.cs#L213-L216

Added lines #L213 - L216 were not covered by tests

await scheduler.ScheduleJob(job, trigger);
}
}

Check warning on line 220 in src/Billing/Services/Implementations/SubscriptionUpdatedHandler.cs

View check run for this annotation

Codecov / codecov/patch

src/Billing/Services/Implementations/SubscriptionUpdatedHandler.cs#L218-L220

Added lines #L218 - L220 were not covered by tests
}
8 changes: 8 additions & 0 deletions src/Billing/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using Bit.Core.Utilities;
using Bit.SharedWeb.Utilities;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Quartz;
using Stripe;

namespace Bit.Billing;
Expand Down Expand Up @@ -101,6 +102,13 @@
services.AddScoped<IStripeEventService, StripeEventService>();
services.AddScoped<IProviderEventService, ProviderEventService>();

// Add Quartz services first
services.AddQuartz(q =>
{
q.UseMicrosoftDependencyInjectionJobFactory();
});
services.AddQuartzHostedService();

Check warning on line 110 in src/Billing/Startup.cs

View check run for this annotation

Codecov / codecov/patch

src/Billing/Startup.cs#L106-L110

Added lines #L106 - L110 were not covered by tests

// Jobs service
Jobs.JobsHostedService.AddJobsServices(services);
services.AddHostedService<Jobs.JobsHostedService>();
Expand Down
3 changes: 3 additions & 0 deletions src/Core/Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@
<PackageReference Include="YubicoDotNetClient" Version="1.2.0" />
<PackageReference Include="Microsoft.Extensions.Caching.StackExchangeRedis" Version="8.0.10" />
<PackageReference Include="LaunchDarkly.ServerSdk" Version="8.6.0" />
<PackageReference Include="Quartz" Version="3.13.1" />
<PackageReference Include="Quartz.Extensions.Hosting" Version="3.13.1" />
<PackageReference Include="Quartz.Extensions.DependencyInjection" Version="3.13.1" />
</ItemGroup>

<ItemGroup>
Expand Down
Loading