mirror of
https://github.com/bitwarden/server
synced 2025-12-06 00:03:34 +00:00
This reverts commit 3bef57259d.
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -231,4 +231,3 @@ bitwarden_license/src/Sso/Sso.zip
|
|||||||
/identity.json
|
/identity.json
|
||||||
/api.json
|
/api.json
|
||||||
/api.public.json
|
/api.public.json
|
||||||
.serena/
|
|
||||||
|
|||||||
@@ -38,7 +38,9 @@ public class OrganizationBillingController(
|
|||||||
return Error.NotFound();
|
return Error.NotFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
return TypedResults.Ok(metadata);
|
var response = OrganizationMetadataResponse.From(metadata);
|
||||||
|
|
||||||
|
return TypedResults.Ok(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("history")]
|
[HttpGet("history")]
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ using Bit.Api.Billing.Attributes;
|
|||||||
using Bit.Api.Billing.Models.Requests.Payment;
|
using Bit.Api.Billing.Models.Requests.Payment;
|
||||||
using Bit.Api.Billing.Models.Requests.Subscriptions;
|
using Bit.Api.Billing.Models.Requests.Subscriptions;
|
||||||
using Bit.Api.Billing.Models.Requirements;
|
using Bit.Api.Billing.Models.Requirements;
|
||||||
using Bit.Core;
|
|
||||||
using Bit.Core.AdminConsole.Entities;
|
using Bit.Core.AdminConsole.Entities;
|
||||||
using Bit.Core.Billing.Commands;
|
using Bit.Core.Billing.Commands;
|
||||||
using Bit.Core.Billing.Organizations.Queries;
|
using Bit.Core.Billing.Organizations.Queries;
|
||||||
@@ -26,7 +25,6 @@ public class OrganizationBillingVNextController(
|
|||||||
ICreateBitPayInvoiceForCreditCommand createBitPayInvoiceForCreditCommand,
|
ICreateBitPayInvoiceForCreditCommand createBitPayInvoiceForCreditCommand,
|
||||||
IGetBillingAddressQuery getBillingAddressQuery,
|
IGetBillingAddressQuery getBillingAddressQuery,
|
||||||
IGetCreditQuery getCreditQuery,
|
IGetCreditQuery getCreditQuery,
|
||||||
IGetOrganizationMetadataQuery getOrganizationMetadataQuery,
|
|
||||||
IGetOrganizationWarningsQuery getOrganizationWarningsQuery,
|
IGetOrganizationWarningsQuery getOrganizationWarningsQuery,
|
||||||
IGetPaymentMethodQuery getPaymentMethodQuery,
|
IGetPaymentMethodQuery getPaymentMethodQuery,
|
||||||
IRestartSubscriptionCommand restartSubscriptionCommand,
|
IRestartSubscriptionCommand restartSubscriptionCommand,
|
||||||
@@ -115,23 +113,6 @@ public class OrganizationBillingVNextController(
|
|||||||
return Handle(result);
|
return Handle(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Authorize<MemberOrProviderRequirement>]
|
|
||||||
[HttpGet("metadata")]
|
|
||||||
[RequireFeature(FeatureFlagKeys.PM25379_UseNewOrganizationMetadataStructure)]
|
|
||||||
[InjectOrganization]
|
|
||||||
public async Task<IResult> GetMetadataAsync(
|
|
||||||
[BindNever] Organization organization)
|
|
||||||
{
|
|
||||||
var metadata = await getOrganizationMetadataQuery.Run(organization);
|
|
||||||
|
|
||||||
if (metadata == null)
|
|
||||||
{
|
|
||||||
return TypedResults.NotFound();
|
|
||||||
}
|
|
||||||
|
|
||||||
return TypedResults.Ok(metadata);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Authorize<MemberOrProviderRequirement>]
|
[Authorize<MemberOrProviderRequirement>]
|
||||||
[HttpGet("warnings")]
|
[HttpGet("warnings")]
|
||||||
[InjectOrganization]
|
[InjectOrganization]
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
using Bit.Core.Billing.Organizations.Models;
|
||||||
|
|
||||||
|
namespace Bit.Api.Billing.Models.Responses;
|
||||||
|
|
||||||
|
public record OrganizationMetadataResponse(
|
||||||
|
bool IsEligibleForSelfHost,
|
||||||
|
bool IsManaged,
|
||||||
|
bool IsOnSecretsManagerStandalone,
|
||||||
|
bool IsSubscriptionUnpaid,
|
||||||
|
bool HasSubscription,
|
||||||
|
bool HasOpenInvoice,
|
||||||
|
bool IsSubscriptionCanceled,
|
||||||
|
DateTime? InvoiceDueDate,
|
||||||
|
DateTime? InvoiceCreatedDate,
|
||||||
|
DateTime? SubPeriodEndDate,
|
||||||
|
int OrganizationOccupiedSeats)
|
||||||
|
{
|
||||||
|
public static OrganizationMetadataResponse From(OrganizationMetadata metadata)
|
||||||
|
=> new(
|
||||||
|
metadata.IsEligibleForSelfHost,
|
||||||
|
metadata.IsManaged,
|
||||||
|
metadata.IsOnSecretsManagerStandalone,
|
||||||
|
metadata.IsSubscriptionUnpaid,
|
||||||
|
metadata.HasSubscription,
|
||||||
|
metadata.HasOpenInvoice,
|
||||||
|
metadata.IsSubscriptionCanceled,
|
||||||
|
metadata.InvoiceDueDate,
|
||||||
|
metadata.InvoiceCreatedDate,
|
||||||
|
metadata.SubPeriodEndDate,
|
||||||
|
metadata.OrganizationOccupiedSeats);
|
||||||
|
}
|
||||||
@@ -31,7 +31,6 @@ public static class ServiceCollectionExtensions
|
|||||||
services.AddPaymentOperations();
|
services.AddPaymentOperations();
|
||||||
services.AddOrganizationLicenseCommandsQueries();
|
services.AddOrganizationLicenseCommandsQueries();
|
||||||
services.AddPremiumCommands();
|
services.AddPremiumCommands();
|
||||||
services.AddTransient<IGetOrganizationMetadataQuery, GetOrganizationMetadataQuery>();
|
|
||||||
services.AddTransient<IGetOrganizationWarningsQuery, GetOrganizationWarningsQuery>();
|
services.AddTransient<IGetOrganizationWarningsQuery, GetOrganizationWarningsQuery>();
|
||||||
services.AddTransient<IRestartSubscriptionCommand, RestartSubscriptionCommand>();
|
services.AddTransient<IRestartSubscriptionCommand, RestartSubscriptionCommand>();
|
||||||
services.AddTransient<IPreviewOrganizationTaxCommand, PreviewOrganizationTaxCommand>();
|
services.AddTransient<IPreviewOrganizationTaxCommand, PreviewOrganizationTaxCommand>();
|
||||||
|
|||||||
@@ -1,10 +1,28 @@
|
|||||||
namespace Bit.Core.Billing.Organizations.Models;
|
namespace Bit.Core.Billing.Organizations.Models;
|
||||||
|
|
||||||
public record OrganizationMetadata(
|
public record OrganizationMetadata(
|
||||||
|
bool IsEligibleForSelfHost,
|
||||||
|
bool IsManaged,
|
||||||
bool IsOnSecretsManagerStandalone,
|
bool IsOnSecretsManagerStandalone,
|
||||||
|
bool IsSubscriptionUnpaid,
|
||||||
|
bool HasSubscription,
|
||||||
|
bool HasOpenInvoice,
|
||||||
|
bool IsSubscriptionCanceled,
|
||||||
|
DateTime? InvoiceDueDate,
|
||||||
|
DateTime? InvoiceCreatedDate,
|
||||||
|
DateTime? SubPeriodEndDate,
|
||||||
int OrganizationOccupiedSeats)
|
int OrganizationOccupiedSeats)
|
||||||
{
|
{
|
||||||
public static OrganizationMetadata Default => new OrganizationMetadata(
|
public static OrganizationMetadata Default => new OrganizationMetadata(
|
||||||
false,
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
0);
|
0);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,95 +0,0 @@
|
|||||||
using Bit.Core.AdminConsole.Entities;
|
|
||||||
using Bit.Core.Billing.Constants;
|
|
||||||
using Bit.Core.Billing.Organizations.Models;
|
|
||||||
using Bit.Core.Billing.Pricing;
|
|
||||||
using Bit.Core.Billing.Services;
|
|
||||||
using Bit.Core.Repositories;
|
|
||||||
using Bit.Core.Settings;
|
|
||||||
using Stripe;
|
|
||||||
|
|
||||||
namespace Bit.Core.Billing.Organizations.Queries;
|
|
||||||
|
|
||||||
public interface IGetOrganizationMetadataQuery
|
|
||||||
{
|
|
||||||
Task<OrganizationMetadata?> Run(Organization organization);
|
|
||||||
}
|
|
||||||
|
|
||||||
public class GetOrganizationMetadataQuery(
|
|
||||||
IGlobalSettings globalSettings,
|
|
||||||
IOrganizationRepository organizationRepository,
|
|
||||||
IPricingClient pricingClient,
|
|
||||||
ISubscriberService subscriberService) : IGetOrganizationMetadataQuery
|
|
||||||
{
|
|
||||||
public async Task<OrganizationMetadata?> Run(Organization organization)
|
|
||||||
{
|
|
||||||
if (organization == null)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (globalSettings.SelfHosted)
|
|
||||||
{
|
|
||||||
return OrganizationMetadata.Default;
|
|
||||||
}
|
|
||||||
|
|
||||||
var orgOccupiedSeats = await organizationRepository.GetOccupiedSeatCountByOrganizationIdAsync(organization.Id);
|
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(organization.GatewaySubscriptionId))
|
|
||||||
{
|
|
||||||
return OrganizationMetadata.Default with
|
|
||||||
{
|
|
||||||
OrganizationOccupiedSeats = orgOccupiedSeats.Total
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
var customer = await subscriberService.GetCustomer(organization,
|
|
||||||
new CustomerGetOptions { Expand = ["discount.coupon.applies_to"] });
|
|
||||||
|
|
||||||
var subscription = await subscriberService.GetSubscription(organization);
|
|
||||||
|
|
||||||
if (customer == null || subscription == null)
|
|
||||||
{
|
|
||||||
return OrganizationMetadata.Default with
|
|
||||||
{
|
|
||||||
OrganizationOccupiedSeats = orgOccupiedSeats.Total
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
var isOnSecretsManagerStandalone = await IsOnSecretsManagerStandalone(organization, customer, subscription);
|
|
||||||
|
|
||||||
return new OrganizationMetadata(
|
|
||||||
isOnSecretsManagerStandalone,
|
|
||||||
orgOccupiedSeats.Total);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<bool> IsOnSecretsManagerStandalone(
|
|
||||||
Organization organization,
|
|
||||||
Customer? customer,
|
|
||||||
Subscription? subscription)
|
|
||||||
{
|
|
||||||
if (customer == null || subscription == null)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var plan = await pricingClient.GetPlanOrThrow(organization.PlanType);
|
|
||||||
|
|
||||||
if (!plan.SupportsSecretsManager)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var hasCoupon = customer.Discount?.Coupon?.Id == StripeConstants.CouponIDs.SecretsManagerStandalone;
|
|
||||||
|
|
||||||
if (!hasCoupon)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var subscriptionProductIds = subscription.Items.Data.Select(item => item.Plan.ProductId);
|
|
||||||
|
|
||||||
var couponAppliesTo = customer.Discount?.Coupon?.AppliesTo?.Products;
|
|
||||||
|
|
||||||
return subscriptionProductIds.Intersect(couponAppliesTo ?? []).Any();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -74,12 +74,16 @@ public class OrganizationBillingService(
|
|||||||
return OrganizationMetadata.Default;
|
return OrganizationMetadata.Default;
|
||||||
}
|
}
|
||||||
|
|
||||||
var orgOccupiedSeats = await organizationRepository.GetOccupiedSeatCountByOrganizationIdAsync(organization.Id);
|
var isEligibleForSelfHost = await IsEligibleForSelfHostAsync(organization);
|
||||||
|
|
||||||
|
var isManaged = organization.Status == OrganizationStatusType.Managed;
|
||||||
|
var orgOccupiedSeats = await organizationRepository.GetOccupiedSeatCountByOrganizationIdAsync(organization.Id);
|
||||||
if (string.IsNullOrWhiteSpace(organization.GatewaySubscriptionId))
|
if (string.IsNullOrWhiteSpace(organization.GatewaySubscriptionId))
|
||||||
{
|
{
|
||||||
return OrganizationMetadata.Default with
|
return OrganizationMetadata.Default with
|
||||||
{
|
{
|
||||||
|
IsEligibleForSelfHost = isEligibleForSelfHost,
|
||||||
|
IsManaged = isManaged,
|
||||||
OrganizationOccupiedSeats = orgOccupiedSeats.Total
|
OrganizationOccupiedSeats = orgOccupiedSeats.Total
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -93,14 +97,28 @@ public class OrganizationBillingService(
|
|||||||
{
|
{
|
||||||
return OrganizationMetadata.Default with
|
return OrganizationMetadata.Default with
|
||||||
{
|
{
|
||||||
OrganizationOccupiedSeats = orgOccupiedSeats.Total
|
IsEligibleForSelfHost = isEligibleForSelfHost,
|
||||||
|
IsManaged = isManaged
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
var isOnSecretsManagerStandalone = await IsOnSecretsManagerStandalone(organization, customer, subscription);
|
var isOnSecretsManagerStandalone = await IsOnSecretsManagerStandalone(organization, customer, subscription);
|
||||||
|
|
||||||
|
var invoice = !string.IsNullOrEmpty(subscription.LatestInvoiceId)
|
||||||
|
? await stripeAdapter.InvoiceGetAsync(subscription.LatestInvoiceId, new InvoiceGetOptions())
|
||||||
|
: null;
|
||||||
|
|
||||||
return new OrganizationMetadata(
|
return new OrganizationMetadata(
|
||||||
|
isEligibleForSelfHost,
|
||||||
|
isManaged,
|
||||||
isOnSecretsManagerStandalone,
|
isOnSecretsManagerStandalone,
|
||||||
|
subscription.Status == StripeConstants.SubscriptionStatus.Unpaid,
|
||||||
|
true,
|
||||||
|
invoice?.Status == StripeConstants.InvoiceStatus.Open,
|
||||||
|
subscription.Status == StripeConstants.SubscriptionStatus.Canceled,
|
||||||
|
invoice?.DueDate,
|
||||||
|
invoice?.Created,
|
||||||
|
subscription.CurrentPeriodEnd,
|
||||||
orgOccupiedSeats.Total);
|
orgOccupiedSeats.Total);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -518,6 +536,16 @@ public class OrganizationBillingService(
|
|||||||
return customer;
|
return customer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task<bool> IsEligibleForSelfHostAsync(
|
||||||
|
Organization organization)
|
||||||
|
{
|
||||||
|
var plans = await pricingClient.ListPlans();
|
||||||
|
|
||||||
|
var eligibleSelfHostPlans = plans.Where(plan => plan.HasSelfHost).Select(plan => plan.Type);
|
||||||
|
|
||||||
|
return eligibleSelfHostPlans.Contains(organization.PlanType);
|
||||||
|
}
|
||||||
|
|
||||||
private async Task<bool> IsOnSecretsManagerStandalone(
|
private async Task<bool> IsOnSecretsManagerStandalone(
|
||||||
Organization organization,
|
Organization organization,
|
||||||
Customer? customer,
|
Customer? customer,
|
||||||
|
|||||||
@@ -179,7 +179,6 @@ public static class FeatureFlagKeys
|
|||||||
public const string PM19422_AllowAutomaticTaxUpdates = "pm-19422-allow-automatic-tax-updates";
|
public const string PM19422_AllowAutomaticTaxUpdates = "pm-19422-allow-automatic-tax-updates";
|
||||||
public const string PM21821_ProviderPortalTakeover = "pm-21821-provider-portal-takeover";
|
public const string PM21821_ProviderPortalTakeover = "pm-21821-provider-portal-takeover";
|
||||||
public const string PM22415_TaxIDWarnings = "pm-22415-tax-id-warnings";
|
public const string PM22415_TaxIDWarnings = "pm-22415-tax-id-warnings";
|
||||||
public const string PM25379_UseNewOrganizationMetadataStructure = "pm-25379-use-new-organization-metadata-structure";
|
|
||||||
public const string PM24996ImplementUpgradeFromFreeDialog = "pm-24996-implement-upgrade-from-free-dialog";
|
public const string PM24996ImplementUpgradeFromFreeDialog = "pm-24996-implement-upgrade-from-free-dialog";
|
||||||
public const string PM24032_NewNavigationPremiumUpgradeButton = "pm-24032-new-navigation-premium-upgrade-button";
|
public const string PM24032_NewNavigationPremiumUpgradeButton = "pm-24032-new-navigation-premium-upgrade-button";
|
||||||
public const string PM23713_PremiumBadgeOpensNewPremiumUpgradeDialog = "pm-23713-premium-badge-opens-new-premium-upgrade-dialog";
|
public const string PM23713_PremiumBadgeOpensNewPremiumUpgradeDialog = "pm-23713-premium-badge-opens-new-premium-upgrade-dialog";
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using Bit.Api.Billing.Controllers;
|
using Bit.Api.Billing.Controllers;
|
||||||
|
using Bit.Api.Billing.Models.Responses;
|
||||||
using Bit.Core.AdminConsole.Entities;
|
using Bit.Core.AdminConsole.Entities;
|
||||||
using Bit.Core.Billing.Models;
|
using Bit.Core.Billing.Models;
|
||||||
using Bit.Core.Billing.Organizations.Models;
|
using Bit.Core.Billing.Organizations.Models;
|
||||||
@@ -52,16 +53,19 @@ public class OrganizationBillingControllerTests
|
|||||||
{
|
{
|
||||||
sutProvider.GetDependency<ICurrentContext>().OrganizationUser(organizationId).Returns(true);
|
sutProvider.GetDependency<ICurrentContext>().OrganizationUser(organizationId).Returns(true);
|
||||||
sutProvider.GetDependency<IOrganizationBillingService>().GetMetadata(organizationId)
|
sutProvider.GetDependency<IOrganizationBillingService>().GetMetadata(organizationId)
|
||||||
.Returns(new OrganizationMetadata(true, 10));
|
.Returns(new OrganizationMetadata(true, true, true, true, true, true, true, null, null, null, 0));
|
||||||
|
|
||||||
var result = await sutProvider.Sut.GetMetadataAsync(organizationId);
|
var result = await sutProvider.Sut.GetMetadataAsync(organizationId);
|
||||||
|
|
||||||
Assert.IsType<Ok<OrganizationMetadata>>(result);
|
Assert.IsType<Ok<OrganizationMetadataResponse>>(result);
|
||||||
|
|
||||||
var response = ((Ok<OrganizationMetadata>)result).Value;
|
var response = ((Ok<OrganizationMetadataResponse>)result).Value;
|
||||||
|
|
||||||
|
Assert.True(response.IsEligibleForSelfHost);
|
||||||
|
Assert.True(response.IsManaged);
|
||||||
Assert.True(response.IsOnSecretsManagerStandalone);
|
Assert.True(response.IsOnSecretsManagerStandalone);
|
||||||
Assert.Equal(10, response.OrganizationOccupiedSeats);
|
Assert.True(response.IsSubscriptionUnpaid);
|
||||||
|
Assert.True(response.HasSubscription);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory, BitAutoData]
|
[Theory, BitAutoData]
|
||||||
|
|||||||
@@ -1,369 +0,0 @@
|
|||||||
using Bit.Core.AdminConsole.Entities;
|
|
||||||
using Bit.Core.Billing.Constants;
|
|
||||||
using Bit.Core.Billing.Enums;
|
|
||||||
using Bit.Core.Billing.Organizations.Models;
|
|
||||||
using Bit.Core.Billing.Organizations.Queries;
|
|
||||||
using Bit.Core.Billing.Pricing;
|
|
||||||
using Bit.Core.Billing.Services;
|
|
||||||
using Bit.Core.Models.Data.Organizations.OrganizationUsers;
|
|
||||||
using Bit.Core.Repositories;
|
|
||||||
using Bit.Core.Settings;
|
|
||||||
using Bit.Core.Utilities;
|
|
||||||
using Bit.Test.Common.AutoFixture;
|
|
||||||
using Bit.Test.Common.AutoFixture.Attributes;
|
|
||||||
using NSubstitute;
|
|
||||||
using NSubstitute.ReturnsExtensions;
|
|
||||||
using Stripe;
|
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace Bit.Core.Test.Billing.Organizations.Queries;
|
|
||||||
|
|
||||||
[SutProviderCustomize]
|
|
||||||
public class GetOrganizationMetadataQueryTests
|
|
||||||
{
|
|
||||||
[Theory, BitAutoData]
|
|
||||||
public async Task Run_NullOrganization_ReturnsNull(
|
|
||||||
SutProvider<GetOrganizationMetadataQuery> sutProvider)
|
|
||||||
{
|
|
||||||
var result = await sutProvider.Sut.Run(null);
|
|
||||||
|
|
||||||
Assert.Null(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory, BitAutoData]
|
|
||||||
public async Task Run_SelfHosted_ReturnsDefault(
|
|
||||||
Organization organization,
|
|
||||||
SutProvider<GetOrganizationMetadataQuery> sutProvider)
|
|
||||||
{
|
|
||||||
sutProvider.GetDependency<IGlobalSettings>().SelfHosted.Returns(true);
|
|
||||||
|
|
||||||
var result = await sutProvider.Sut.Run(organization);
|
|
||||||
|
|
||||||
Assert.Equal(OrganizationMetadata.Default, result);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory, BitAutoData]
|
|
||||||
public async Task Run_NoGatewaySubscriptionId_ReturnsDefaultWithOccupiedSeats(
|
|
||||||
Organization organization,
|
|
||||||
SutProvider<GetOrganizationMetadataQuery> sutProvider)
|
|
||||||
{
|
|
||||||
organization.GatewaySubscriptionId = null;
|
|
||||||
|
|
||||||
sutProvider.GetDependency<IGlobalSettings>().SelfHosted.Returns(false);
|
|
||||||
sutProvider.GetDependency<IOrganizationRepository>()
|
|
||||||
.GetOccupiedSeatCountByOrganizationIdAsync(organization.Id)
|
|
||||||
.Returns(new OrganizationSeatCounts { Users = 10, Sponsored = 0 });
|
|
||||||
|
|
||||||
var result = await sutProvider.Sut.Run(organization);
|
|
||||||
|
|
||||||
Assert.NotNull(result);
|
|
||||||
Assert.False(result.IsOnSecretsManagerStandalone);
|
|
||||||
Assert.Equal(10, result.OrganizationOccupiedSeats);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory, BitAutoData]
|
|
||||||
public async Task Run_NullCustomer_ReturnsDefaultWithOccupiedSeats(
|
|
||||||
Organization organization,
|
|
||||||
SutProvider<GetOrganizationMetadataQuery> sutProvider)
|
|
||||||
{
|
|
||||||
organization.GatewaySubscriptionId = "sub_123";
|
|
||||||
|
|
||||||
sutProvider.GetDependency<IGlobalSettings>().SelfHosted.Returns(false);
|
|
||||||
sutProvider.GetDependency<IOrganizationRepository>()
|
|
||||||
.GetOccupiedSeatCountByOrganizationIdAsync(organization.Id)
|
|
||||||
.Returns(new OrganizationSeatCounts { Users = 5, Sponsored = 0 });
|
|
||||||
|
|
||||||
sutProvider.GetDependency<ISubscriberService>()
|
|
||||||
.GetCustomer(organization, Arg.Is<CustomerGetOptions>(options =>
|
|
||||||
options.Expand.Contains("discount.coupon.applies_to")))
|
|
||||||
.ReturnsNull();
|
|
||||||
|
|
||||||
var result = await sutProvider.Sut.Run(organization);
|
|
||||||
|
|
||||||
Assert.NotNull(result);
|
|
||||||
Assert.False(result.IsOnSecretsManagerStandalone);
|
|
||||||
Assert.Equal(5, result.OrganizationOccupiedSeats);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory, BitAutoData]
|
|
||||||
public async Task Run_NullSubscription_ReturnsDefaultWithOccupiedSeats(
|
|
||||||
Organization organization,
|
|
||||||
SutProvider<GetOrganizationMetadataQuery> sutProvider)
|
|
||||||
{
|
|
||||||
organization.GatewaySubscriptionId = "sub_123";
|
|
||||||
|
|
||||||
var customer = new Customer();
|
|
||||||
|
|
||||||
sutProvider.GetDependency<IGlobalSettings>().SelfHosted.Returns(false);
|
|
||||||
sutProvider.GetDependency<IOrganizationRepository>()
|
|
||||||
.GetOccupiedSeatCountByOrganizationIdAsync(organization.Id)
|
|
||||||
.Returns(new OrganizationSeatCounts { Users = 7, Sponsored = 0 });
|
|
||||||
|
|
||||||
sutProvider.GetDependency<ISubscriberService>()
|
|
||||||
.GetCustomer(organization, Arg.Is<CustomerGetOptions>(options =>
|
|
||||||
options.Expand.Contains("discount.coupon.applies_to")))
|
|
||||||
.Returns(customer);
|
|
||||||
|
|
||||||
sutProvider.GetDependency<ISubscriberService>()
|
|
||||||
.GetSubscription(organization)
|
|
||||||
.ReturnsNull();
|
|
||||||
|
|
||||||
var result = await sutProvider.Sut.Run(organization);
|
|
||||||
|
|
||||||
Assert.NotNull(result);
|
|
||||||
Assert.False(result.IsOnSecretsManagerStandalone);
|
|
||||||
Assert.Equal(7, result.OrganizationOccupiedSeats);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory, BitAutoData]
|
|
||||||
public async Task Run_WithSecretsManagerStandaloneCoupon_ReturnsMetadataWithFlag(
|
|
||||||
Organization organization,
|
|
||||||
SutProvider<GetOrganizationMetadataQuery> sutProvider)
|
|
||||||
{
|
|
||||||
organization.GatewaySubscriptionId = "sub_123";
|
|
||||||
organization.PlanType = PlanType.EnterpriseAnnually;
|
|
||||||
|
|
||||||
var productId = "product_123";
|
|
||||||
var customer = new Customer
|
|
||||||
{
|
|
||||||
Discount = new Discount
|
|
||||||
{
|
|
||||||
Coupon = new Coupon
|
|
||||||
{
|
|
||||||
Id = StripeConstants.CouponIDs.SecretsManagerStandalone,
|
|
||||||
AppliesTo = new CouponAppliesTo
|
|
||||||
{
|
|
||||||
Products = [productId]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var subscription = new Subscription
|
|
||||||
{
|
|
||||||
Items = new StripeList<SubscriptionItem>
|
|
||||||
{
|
|
||||||
Data =
|
|
||||||
[
|
|
||||||
new SubscriptionItem
|
|
||||||
{
|
|
||||||
Plan = new Plan
|
|
||||||
{
|
|
||||||
ProductId = productId
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
sutProvider.GetDependency<IGlobalSettings>().SelfHosted.Returns(false);
|
|
||||||
sutProvider.GetDependency<IOrganizationRepository>()
|
|
||||||
.GetOccupiedSeatCountByOrganizationIdAsync(organization.Id)
|
|
||||||
.Returns(new OrganizationSeatCounts { Users = 15, Sponsored = 0 });
|
|
||||||
|
|
||||||
sutProvider.GetDependency<ISubscriberService>()
|
|
||||||
.GetCustomer(organization, Arg.Is<CustomerGetOptions>(options =>
|
|
||||||
options.Expand.Contains("discount.coupon.applies_to")))
|
|
||||||
.Returns(customer);
|
|
||||||
|
|
||||||
sutProvider.GetDependency<ISubscriberService>()
|
|
||||||
.GetSubscription(organization)
|
|
||||||
.Returns(subscription);
|
|
||||||
|
|
||||||
sutProvider.GetDependency<IPricingClient>()
|
|
||||||
.GetPlanOrThrow(organization.PlanType)
|
|
||||||
.Returns(StaticStore.GetPlan(organization.PlanType));
|
|
||||||
|
|
||||||
var result = await sutProvider.Sut.Run(organization);
|
|
||||||
|
|
||||||
Assert.NotNull(result);
|
|
||||||
Assert.True(result.IsOnSecretsManagerStandalone);
|
|
||||||
Assert.Equal(15, result.OrganizationOccupiedSeats);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory, BitAutoData]
|
|
||||||
public async Task Run_WithoutSecretsManagerStandaloneCoupon_ReturnsMetadataWithoutFlag(
|
|
||||||
Organization organization,
|
|
||||||
SutProvider<GetOrganizationMetadataQuery> sutProvider)
|
|
||||||
{
|
|
||||||
organization.GatewaySubscriptionId = "sub_123";
|
|
||||||
organization.PlanType = PlanType.TeamsAnnually;
|
|
||||||
|
|
||||||
var customer = new Customer
|
|
||||||
{
|
|
||||||
Discount = null
|
|
||||||
};
|
|
||||||
|
|
||||||
var subscription = new Subscription
|
|
||||||
{
|
|
||||||
Items = new StripeList<SubscriptionItem>
|
|
||||||
{
|
|
||||||
Data =
|
|
||||||
[
|
|
||||||
new SubscriptionItem
|
|
||||||
{
|
|
||||||
Plan = new Plan
|
|
||||||
{
|
|
||||||
ProductId = "product_123"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
sutProvider.GetDependency<IGlobalSettings>().SelfHosted.Returns(false);
|
|
||||||
sutProvider.GetDependency<IOrganizationRepository>()
|
|
||||||
.GetOccupiedSeatCountByOrganizationIdAsync(organization.Id)
|
|
||||||
.Returns(new OrganizationSeatCounts { Users = 20, Sponsored = 0 });
|
|
||||||
|
|
||||||
sutProvider.GetDependency<ISubscriberService>()
|
|
||||||
.GetCustomer(organization, Arg.Is<CustomerGetOptions>(options =>
|
|
||||||
options.Expand.Contains("discount.coupon.applies_to")))
|
|
||||||
.Returns(customer);
|
|
||||||
|
|
||||||
sutProvider.GetDependency<ISubscriberService>()
|
|
||||||
.GetSubscription(organization)
|
|
||||||
.Returns(subscription);
|
|
||||||
|
|
||||||
sutProvider.GetDependency<IPricingClient>()
|
|
||||||
.GetPlanOrThrow(organization.PlanType)
|
|
||||||
.Returns(StaticStore.GetPlan(organization.PlanType));
|
|
||||||
|
|
||||||
var result = await sutProvider.Sut.Run(organization);
|
|
||||||
|
|
||||||
Assert.NotNull(result);
|
|
||||||
Assert.False(result.IsOnSecretsManagerStandalone);
|
|
||||||
Assert.Equal(20, result.OrganizationOccupiedSeats);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory, BitAutoData]
|
|
||||||
public async Task Run_CouponDoesNotApplyToSubscriptionProducts_ReturnsFalseForStandaloneFlag(
|
|
||||||
Organization organization,
|
|
||||||
SutProvider<GetOrganizationMetadataQuery> sutProvider)
|
|
||||||
{
|
|
||||||
organization.GatewaySubscriptionId = "sub_123";
|
|
||||||
organization.PlanType = PlanType.EnterpriseAnnually;
|
|
||||||
|
|
||||||
var customer = new Customer
|
|
||||||
{
|
|
||||||
Discount = new Discount
|
|
||||||
{
|
|
||||||
Coupon = new Coupon
|
|
||||||
{
|
|
||||||
Id = StripeConstants.CouponIDs.SecretsManagerStandalone,
|
|
||||||
AppliesTo = new CouponAppliesTo
|
|
||||||
{
|
|
||||||
Products = ["different_product_id"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var subscription = new Subscription
|
|
||||||
{
|
|
||||||
Items = new StripeList<SubscriptionItem>
|
|
||||||
{
|
|
||||||
Data =
|
|
||||||
[
|
|
||||||
new SubscriptionItem
|
|
||||||
{
|
|
||||||
Plan = new Plan
|
|
||||||
{
|
|
||||||
ProductId = "product_123"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
sutProvider.GetDependency<IGlobalSettings>().SelfHosted.Returns(false);
|
|
||||||
sutProvider.GetDependency<IOrganizationRepository>()
|
|
||||||
.GetOccupiedSeatCountByOrganizationIdAsync(organization.Id)
|
|
||||||
.Returns(new OrganizationSeatCounts { Users = 12, Sponsored = 0 });
|
|
||||||
|
|
||||||
sutProvider.GetDependency<ISubscriberService>()
|
|
||||||
.GetCustomer(organization, Arg.Is<CustomerGetOptions>(options =>
|
|
||||||
options.Expand.Contains("discount.coupon.applies_to")))
|
|
||||||
.Returns(customer);
|
|
||||||
|
|
||||||
sutProvider.GetDependency<ISubscriberService>()
|
|
||||||
.GetSubscription(organization)
|
|
||||||
.Returns(subscription);
|
|
||||||
|
|
||||||
sutProvider.GetDependency<IPricingClient>()
|
|
||||||
.GetPlanOrThrow(organization.PlanType)
|
|
||||||
.Returns(StaticStore.GetPlan(organization.PlanType));
|
|
||||||
|
|
||||||
var result = await sutProvider.Sut.Run(organization);
|
|
||||||
|
|
||||||
Assert.NotNull(result);
|
|
||||||
Assert.False(result.IsOnSecretsManagerStandalone);
|
|
||||||
Assert.Equal(12, result.OrganizationOccupiedSeats);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory, BitAutoData]
|
|
||||||
public async Task Run_PlanDoesNotSupportSecretsManager_ReturnsFalseForStandaloneFlag(
|
|
||||||
Organization organization,
|
|
||||||
SutProvider<GetOrganizationMetadataQuery> sutProvider)
|
|
||||||
{
|
|
||||||
organization.GatewaySubscriptionId = "sub_123";
|
|
||||||
organization.PlanType = PlanType.FamiliesAnnually;
|
|
||||||
|
|
||||||
var productId = "product_123";
|
|
||||||
var customer = new Customer
|
|
||||||
{
|
|
||||||
Discount = new Discount
|
|
||||||
{
|
|
||||||
Coupon = new Coupon
|
|
||||||
{
|
|
||||||
Id = StripeConstants.CouponIDs.SecretsManagerStandalone,
|
|
||||||
AppliesTo = new CouponAppliesTo
|
|
||||||
{
|
|
||||||
Products = [productId]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var subscription = new Subscription
|
|
||||||
{
|
|
||||||
Items = new StripeList<SubscriptionItem>
|
|
||||||
{
|
|
||||||
Data =
|
|
||||||
[
|
|
||||||
new SubscriptionItem
|
|
||||||
{
|
|
||||||
Plan = new Plan
|
|
||||||
{
|
|
||||||
ProductId = productId
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
sutProvider.GetDependency<IGlobalSettings>().SelfHosted.Returns(false);
|
|
||||||
sutProvider.GetDependency<IOrganizationRepository>()
|
|
||||||
.GetOccupiedSeatCountByOrganizationIdAsync(organization.Id)
|
|
||||||
.Returns(new OrganizationSeatCounts { Users = 8, Sponsored = 0 });
|
|
||||||
|
|
||||||
sutProvider.GetDependency<ISubscriberService>()
|
|
||||||
.GetCustomer(organization, Arg.Is<CustomerGetOptions>(options =>
|
|
||||||
options.Expand.Contains("discount.coupon.applies_to")))
|
|
||||||
.Returns(customer);
|
|
||||||
|
|
||||||
sutProvider.GetDependency<ISubscriberService>()
|
|
||||||
.GetSubscription(organization)
|
|
||||||
.Returns(subscription);
|
|
||||||
|
|
||||||
sutProvider.GetDependency<IPricingClient>()
|
|
||||||
.GetPlanOrThrow(organization.PlanType)
|
|
||||||
.Returns(StaticStore.GetPlan(organization.PlanType));
|
|
||||||
|
|
||||||
var result = await sutProvider.Sut.Run(organization);
|
|
||||||
|
|
||||||
Assert.NotNull(result);
|
|
||||||
Assert.False(result.IsOnSecretsManagerStandalone);
|
|
||||||
Assert.Equal(8, result.OrganizationOccupiedSeats);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -96,10 +96,6 @@ public class OrganizationBillingServiceTests
|
|||||||
sutProvider.GetDependency<IPricingClient>().GetPlanOrThrow(organization.PlanType)
|
sutProvider.GetDependency<IPricingClient>().GetPlanOrThrow(organization.PlanType)
|
||||||
.Returns(StaticStore.GetPlan(organization.PlanType));
|
.Returns(StaticStore.GetPlan(organization.PlanType));
|
||||||
|
|
||||||
sutProvider.GetDependency<IOrganizationRepository>()
|
|
||||||
.GetOccupiedSeatCountByOrganizationIdAsync(organization.Id)
|
|
||||||
.Returns(new OrganizationSeatCounts { Users = 1, Sponsored = 0 });
|
|
||||||
|
|
||||||
var subscriberService = sutProvider.GetDependency<ISubscriberService>();
|
var subscriberService = sutProvider.GetDependency<ISubscriberService>();
|
||||||
|
|
||||||
// Set up subscriber service to return null for customer
|
// Set up subscriber service to return null for customer
|
||||||
@@ -114,7 +110,13 @@ public class OrganizationBillingServiceTests
|
|||||||
|
|
||||||
Assert.NotNull(metadata);
|
Assert.NotNull(metadata);
|
||||||
Assert.False(metadata!.IsOnSecretsManagerStandalone);
|
Assert.False(metadata!.IsOnSecretsManagerStandalone);
|
||||||
Assert.Equal(1, metadata.OrganizationOccupiedSeats);
|
Assert.False(metadata.HasSubscription);
|
||||||
|
Assert.False(metadata.IsSubscriptionUnpaid);
|
||||||
|
Assert.False(metadata.HasOpenInvoice);
|
||||||
|
Assert.False(metadata.IsSubscriptionCanceled);
|
||||||
|
Assert.Null(metadata.InvoiceDueDate);
|
||||||
|
Assert.Null(metadata.InvoiceCreatedDate);
|
||||||
|
Assert.Null(metadata.SubPeriodEndDate);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|||||||
Reference in New Issue
Block a user