Skip to content
9 changes: 8 additions & 1 deletion src/Core/Billing/Licenses/Queries/GetUserLicenseQuery.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
ο»Ώusing Bit.Core.Billing.Models.Business;
using Bit.Core.Billing.Services;
using Bit.Core.Entities;
using Bit.Core.Services;

Expand All @@ -10,10 +11,16 @@ public interface IGetUserLicenseQuery
}

public class GetUserLicenseQuery(
IUserService userService) : IGetUserLicenseQuery
IUserService userService,
IStripeAdapter stripeAdapter) : IGetUserLicenseQuery
{
public async Task<UserLicense> Run(User user)
{
var subscription = string.IsNullOrEmpty(user.GatewaySubscriptionId)
? null
: await stripeAdapter.GetSubscriptionAsync(user.GatewaySubscriptionId);
SubscriptionLicenseValidator.ValidateSubscriptionForLicenseGeneration(subscription);

return await userService.GenerateLicenseAsync(user);
}
}
25 changes: 25 additions & 0 deletions src/Core/Billing/Licenses/SubscriptionLicenseValidator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
ο»Ώusing Bit.Core.Billing.Constants;
using Bit.Core.Exceptions;
using Stripe;

namespace Bit.Core.Billing.Licenses;

public static class SubscriptionLicenseValidator
{
public static void ValidateSubscriptionForLicenseGeneration(Subscription? subscription)
{
if (subscription == null)
{
throw new BadRequestException("No active subscription found.");
}

var status = subscription.Status;

if (status is StripeConstants.SubscriptionStatus.Canceled or StripeConstants.SubscriptionStatus.Incomplete
or StripeConstants.SubscriptionStatus.IncompleteExpired)
{
throw new BadRequestException(
"Unable to generate license due to a payment issue. Please update your billing information or contact support for assistance.");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@

using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.Repositories;
using Bit.Core.Billing.Licenses;
using Bit.Core.Billing.Organizations.Models;
using Bit.Core.Billing.Services;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.Models.Business;
using Bit.Core.Platform.Installations;
using Bit.Core.Services;

namespace Bit.Core.Billing.Organizations.Queries;

Expand All @@ -23,22 +23,22 @@ public class GetCloudOrganizationLicenseQuery : IGetCloudOrganizationLicenseQuer
{
private readonly IInstallationRepository _installationRepository;
private readonly IStripePaymentService _paymentService;
private readonly IStripeAdapter _stripeAdapter;
private readonly ILicensingService _licensingService;
private readonly IProviderRepository _providerRepository;
private readonly IFeatureService _featureService;

public GetCloudOrganizationLicenseQuery(
IInstallationRepository installationRepository,
IStripePaymentService paymentService,
IStripeAdapter stripeAdapter,
ILicensingService licensingService,
IProviderRepository providerRepository,
IFeatureService featureService)
IProviderRepository providerRepository)
{
_installationRepository = installationRepository;
_paymentService = paymentService;
_stripeAdapter = stripeAdapter;
_licensingService = licensingService;
_providerRepository = providerRepository;
_featureService = featureService;
}

public async Task<OrganizationLicense> GetLicenseAsync(Organization organization, Guid installationId,
Expand All @@ -51,6 +51,10 @@ public async Task<OrganizationLicense> GetLicenseAsync(Organization organization
}

var subscriptionInfo = await GetSubscriptionAsync(organization);
var subscription = string.IsNullOrEmpty(organization.GatewaySubscriptionId)
? null
: await _stripeAdapter.GetSubscriptionAsync(organization.GatewaySubscriptionId);
SubscriptionLicenseValidator.ValidateSubscriptionForLicenseGeneration(subscription);
var license = new OrganizationLicense(organization, subscriptionInfo, installationId, _licensingService, version);
license.Token = await _licensingService.CreateOrganizationTokenAsync(organization, installationId, subscriptionInfo);

Expand Down
145 changes: 145 additions & 0 deletions test/Core.Test/Billing/Licenses/Queries/GetUserLicenseQueryTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
ο»Ώusing Bit.Core.Billing.Licenses.Queries;
using Bit.Core.Billing.Models.Business;
using Bit.Core.Billing.Services;
using Bit.Core.Entities;
using Bit.Core.Exceptions;
using Bit.Core.Services;
using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes;
using NSubstitute;
using Stripe;
using Xunit;

namespace Bit.Core.Test.Billing.Licenses.Queries;

[SutProviderCustomize]
public class GetUserLicenseQueryTests
{
[Theory]
[BitAutoData]
public async Task RunAsync_CanceledSubscription_Throws(
SutProvider<GetUserLicenseQuery> sutProvider,
User user)
{
user.GatewaySubscriptionId = "sub_123";
sutProvider.GetDependency<IStripeAdapter>()
.GetSubscriptionAsync(user.GatewaySubscriptionId, Arg.Any<SubscriptionGetOptions>())
.Returns(new Subscription { Status = "canceled" });

var exception = await Assert.ThrowsAsync<BadRequestException>(async () =>
await sutProvider.Sut.Run(user));
Assert.Contains("Unable to generate license due to a payment issue", exception.Message);
}

[Theory]
[BitAutoData]
public async Task RunAsync_IncompleteSubscription_Throws(
SutProvider<GetUserLicenseQuery> sutProvider,
User user)
{
user.GatewaySubscriptionId = "sub_123";
sutProvider.GetDependency<IStripeAdapter>()
.GetSubscriptionAsync(user.GatewaySubscriptionId, Arg.Any<SubscriptionGetOptions>())
.Returns(new Subscription { Status = "incomplete" });

var exception = await Assert.ThrowsAsync<BadRequestException>(async () =>
await sutProvider.Sut.Run(user));
Assert.Contains("Unable to generate license due to a payment issue", exception.Message);
}

[Theory]
[BitAutoData]
public async Task RunAsync_IncompleteExpiredSubscription_Throws(
SutProvider<GetUserLicenseQuery> sutProvider,
User user)
{
user.GatewaySubscriptionId = "sub_123";
sutProvider.GetDependency<IStripeAdapter>()
.GetSubscriptionAsync(user.GatewaySubscriptionId, Arg.Any<SubscriptionGetOptions>())
.Returns(new Subscription { Status = "incomplete_expired" });

var exception = await Assert.ThrowsAsync<BadRequestException>(async () =>
await sutProvider.Sut.Run(user));
Assert.Contains("Unable to generate license due to a payment issue", exception.Message);
}

[Theory]
[BitAutoData]
public async Task RunAsync_NullSubscription_Throws(
SutProvider<GetUserLicenseQuery> sutProvider,
User user)
{
user.GatewaySubscriptionId = null;

var exception = await Assert.ThrowsAsync<BadRequestException>(async () =>
await sutProvider.Sut.Run(user));
Assert.Contains("No active subscription found", exception.Message);
}

[Theory]
[BitAutoData]
public async Task RunAsync_ActiveSubscription_Succeeds(
SutProvider<GetUserLicenseQuery> sutProvider,
User user, UserLicense userLicense)
{
user.GatewaySubscriptionId = "sub_123";
sutProvider.GetDependency<IStripeAdapter>()
.GetSubscriptionAsync(user.GatewaySubscriptionId, Arg.Any<SubscriptionGetOptions>())
.Returns(new Subscription { Status = "active" });
sutProvider.GetDependency<IUserService>().GenerateLicenseAsync(user).Returns(userLicense);

var result = await sutProvider.Sut.Run(user);

Assert.NotNull(result);
Assert.Equal(userLicense, result);
}

[Theory]
[BitAutoData]
public async Task RunAsync_TrialingSubscription_Succeeds(
SutProvider<GetUserLicenseQuery> sutProvider,
User user, UserLicense userLicense)
{
user.GatewaySubscriptionId = "sub_123";
sutProvider.GetDependency<IStripeAdapter>()
.GetSubscriptionAsync(user.GatewaySubscriptionId, Arg.Any<SubscriptionGetOptions>())
.Returns(new Subscription { Status = "trialing" });
sutProvider.GetDependency<IUserService>().GenerateLicenseAsync(user).Returns(userLicense);

var result = await sutProvider.Sut.Run(user);

Assert.NotNull(result);
Assert.Equal(userLicense, result);
}

[Theory]
[BitAutoData]
public async Task RunAsync_PastDueSubscription_Succeeds(
SutProvider<GetUserLicenseQuery> sutProvider,
User user, UserLicense userLicense)
{
user.GatewaySubscriptionId = "sub_123";
sutProvider.GetDependency<IStripeAdapter>()
.GetSubscriptionAsync(user.GatewaySubscriptionId, Arg.Any<SubscriptionGetOptions>())
.Returns(new Subscription { Status = "past_due" });
sutProvider.GetDependency<IUserService>().GenerateLicenseAsync(user).Returns(userLicense);

var result = await sutProvider.Sut.Run(user);

Assert.NotNull(result);
Assert.Equal(userLicense, result);
}

[Theory]
[BitAutoData]
public async Task RunAsync_EmptySubscriptionId_Throws(
SutProvider<GetUserLicenseQuery> sutProvider,
User user)
{
user.GatewaySubscriptionId = "";

var exception = await Assert.ThrowsAsync<BadRequestException>(async () =>
await sutProvider.Sut.Run(user));
Assert.Contains("No active subscription found", exception.Message);
}
}
Loading
Loading