Skip to content
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
19 changes: 0 additions & 19 deletions src/Api/Billing/Controllers/OrganizationBillingController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,25 +21,6 @@ public class OrganizationBillingController(
IStripePaymentService paymentService,
IPaymentHistoryService paymentHistoryService) : BaseBillingController
{
// TODO: Remove when pm-25379-use-new-organization-metadata-structure is removed.
[HttpGet("metadata")]
public async Task<IResult> GetMetadataAsync([FromRoute] Guid organizationId)
{
if (!await currentContext.OrganizationUser(organizationId))
{
return Error.Unauthorized();
}

var metadata = await organizationBillingService.GetMetadata(organizationId);

if (metadata == null)
{
return Error.NotFound();
}

return TypedResults.Ok(metadata);
}

// TODO: Migrate to Query / OrganizationBillingVNextController
[HttpGet("history")]
public async Task<IResult> GetHistoryAsync([FromRoute] Guid organizationId)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
using Bit.Api.Billing.Models.Requests.Payment;
using Bit.Api.Billing.Models.Requests.Subscriptions;
using Bit.Api.Billing.Models.Requirements;
using Bit.Core;
using Bit.Core.AdminConsole.Entities;
using Bit.Core.Billing.Commands;
using Bit.Core.Billing.Organizations.Queries;
Expand Down Expand Up @@ -117,7 +116,6 @@ public async Task<IResult> RestartSubscriptionAsync(

[Authorize<MemberOrProviderRequirement>]
[HttpGet("metadata")]
[RequireFeature(FeatureFlagKeys.PM25379_UseNewOrganizationMetadataStructure)]
[InjectOrganization]
public async Task<IResult> GetMetadataAsync(
[BindNever] Organization organization)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
ο»Ώusing Bit.Api.AdminConsole.Authorization;
using Bit.Api.AdminConsole.Authorization.Requirements;
using Bit.Api.Billing.Attributes;
using Bit.Core;
using Bit.Core.AdminConsole.Entities;
using Bit.Core.Billing.Organizations.Queries;
using Bit.Core.Utilities;
Expand All @@ -19,7 +18,6 @@ public class SelfHostedOrganizationBillingVNextController(
{
[Authorize<MemberOrProviderRequirement>]
[HttpGet("metadata")]
[RequireFeature(FeatureFlagKeys.PM25379_UseNewOrganizationMetadataStructure)]
[InjectOrganization]
public async Task<IResult> GetMetadataAsync([BindNever] Organization organization)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,6 @@ public interface IOrganizationBillingService
/// </example>
Task Finalize(OrganizationSale sale);

/// <summary>
/// Retrieve metadata about the organization represented bsy the provided <paramref name="organizationId"/>.
/// </summary>
/// <param name="organizationId">The ID of the organization to retrieve metadata for.</param>
/// <returns>An <see cref="OrganizationMetadata"/> record.</returns>
Task<OrganizationMetadata?> GetMetadata(Guid organizationId);

/// <summary>
/// Updates the provided <paramref name="organization"/>'s payment source and tax information.
/// If the <paramref name="organization"/> does not have a Stripe <see cref="Stripe.Customer"/>, this method will create one using the provided
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,52 +54,6 @@ public async Task Finalize(OrganizationSale sale)
}
}

public async Task<OrganizationMetadata?> GetMetadata(Guid organizationId)
{
var organization = await organizationRepository.GetByIdAsync(organizationId);

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);

var subscription = await subscriberService.GetSubscription(organization, new SubscriptionGetOptions
{
Expand = ["discounts.coupon.applies_to"]
});

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);
}

public async Task UpdatePaymentMethod(
Organization organization,
TokenizedPaymentSource tokenizedPaymentSource,
Expand Down Expand Up @@ -565,38 +519,6 @@ ProductTierType.TeamsStarter or
return customer;
}

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 coupon = subscription.Discounts?.FirstOrDefault(discount =>
discount.Coupon?.Id == StripeConstants.CouponIDs.SecretsManagerStandalone)?.Coupon;

if (coupon == null)
{
return false;
}

var subscriptionProductIds = subscription.Items.Data.Select(item => item.Plan.ProductId);

var couponAppliesTo = coupon.AppliesTo?.Products;

return subscriptionProductIds.Intersect(couponAppliesTo ?? []).Any();
}

private async Task UpdateMissingPaymentMethodBehaviourAsync(Organization organization)
{
var subscription = await subscriberService.GetSubscriptionOrThrow(organization);
Expand Down
1 change: 0 additions & 1 deletion src/Core/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,6 @@ public static class FeatureFlagKeys

/* Billing Team */
public const string TrialPayment = "PM-8163-trial-payment";
public const string PM25379_UseNewOrganizationMetadataStructure = "pm-25379-use-new-organization-metadata-structure";
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 PM26793_FetchPremiumPriceFromPricingService = "pm-26793-fetch-premium-price-from-pricing-service";
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
ο»Ώusing Bit.Api.Billing.Controllers;
using Bit.Core.AdminConsole.Entities;
using Bit.Core.Billing.Models;
using Bit.Core.Billing.Organizations.Models;
using Bit.Core.Billing.Organizations.Services;
using Bit.Core.Billing.Services;
using Bit.Core.Context;
using Bit.Core.Repositories;
Expand All @@ -20,50 +18,6 @@ namespace Bit.Api.Test.Billing.Controllers;
[SutProviderCustomize]
public class OrganizationBillingControllerTests
{
[Theory, BitAutoData]
public async Task GetMetadataAsync_Unauthorized_ReturnsUnauthorized(
Guid organizationId,
SutProvider<OrganizationBillingController> sutProvider)
{
sutProvider.GetDependency<ICurrentContext>().AccessMembersTab(organizationId).Returns(false);

var result = await sutProvider.Sut.GetMetadataAsync(organizationId);

AssertUnauthorized(result);
}

[Theory, BitAutoData]
public async Task GetMetadataAsync_MetadataNull_NotFound(
Guid organizationId,
SutProvider<OrganizationBillingController> sutProvider)
{
sutProvider.GetDependency<ICurrentContext>().OrganizationUser(organizationId).Returns(true);
sutProvider.GetDependency<IOrganizationBillingService>().GetMetadata(organizationId).Returns((OrganizationMetadata)null);

var result = await sutProvider.Sut.GetMetadataAsync(organizationId);

AssertNotFound(result);
}

[Theory, BitAutoData]
public async Task GetMetadataAsync_OK(
Guid organizationId,
SutProvider<OrganizationBillingController> sutProvider)
{
sutProvider.GetDependency<ICurrentContext>().OrganizationUser(organizationId).Returns(true);
sutProvider.GetDependency<IOrganizationBillingService>().GetMetadata(organizationId)
.Returns(new OrganizationMetadata(true, 10));

var result = await sutProvider.Sut.GetMetadataAsync(organizationId);

Assert.IsType<Ok<OrganizationMetadata>>(result);

var response = ((Ok<OrganizationMetadata>)result).Value;

Assert.True(response.IsOnSecretsManagerStandalone);
Assert.Equal(10, response.OrganizationOccupiedSeats);
}

[Theory, BitAutoData]
public async Task GetHistoryAsync_Unauthorized_ReturnsUnauthorized(
Guid organizationId,
Expand Down
105 changes: 0 additions & 105 deletions test/Core.Test/Billing/Services/OrganizationBillingServiceTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
using Bit.Core.Billing.Payment.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.Test.Billing.Mocks;
using Bit.Test.Common.AutoFixture;
Expand All @@ -21,110 +20,6 @@ namespace Bit.Core.Test.Billing.Services;
[SutProviderCustomize]
public class OrganizationBillingServiceTests
{
#region GetMetadata

[Theory, BitAutoData]
public async Task GetMetadata_Succeeds(
Guid organizationId,
Organization organization,
SutProvider<OrganizationBillingService> sutProvider)
{
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organizationId).Returns(organization);
sutProvider.GetDependency<IPricingClient>().ListPlans().Returns(MockPlans.Plans.ToList());

sutProvider.GetDependency<IPricingClient>().GetPlanOrThrow(organization.PlanType)
.Returns(MockPlans.Get(organization.PlanType));

var subscriberService = sutProvider.GetDependency<ISubscriberService>();
var organizationSeatCount = new OrganizationSeatCounts { Users = 1, Sponsored = 0 };
var customer = new Customer();

subscriberService
.GetCustomer(organization)
.Returns(customer);

subscriberService.GetSubscription(organization, Arg.Is<SubscriptionGetOptions>(options =>
options.Expand.Contains("discounts.coupon.applies_to"))).Returns(new Subscription
{
Discounts =
[
new Discount
{
Coupon = new Coupon
{
Id = StripeConstants.CouponIDs.SecretsManagerStandalone,
AppliesTo = new CouponAppliesTo
{
Products = ["product_id"]
}
}
}
],
Items = new StripeList<SubscriptionItem>
{
Data =
[
new SubscriptionItem
{
Plan = new Plan
{
ProductId = "product_id"
}
}
]
}
});

sutProvider.GetDependency<IOrganizationRepository>()
.GetOccupiedSeatCountByOrganizationIdAsync(organization.Id)
.Returns(new OrganizationSeatCounts { Users = 1, Sponsored = 0 });

var metadata = await sutProvider.Sut.GetMetadata(organizationId);

Assert.True(metadata!.IsOnSecretsManagerStandalone);
}

#endregion

#region GetMetadata - Null Customer or Subscription

[Theory, BitAutoData]
public async Task GetMetadata_WhenCustomerOrSubscriptionIsNull_ReturnsDefaultMetadata(
Guid organizationId,
Organization organization,
SutProvider<OrganizationBillingService> sutProvider)
{
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organizationId).Returns(organization);

sutProvider.GetDependency<IPricingClient>().ListPlans().Returns(MockPlans.Plans.ToList());

sutProvider.GetDependency<IPricingClient>().GetPlanOrThrow(organization.PlanType)
.Returns(MockPlans.Get(organization.PlanType));

sutProvider.GetDependency<IOrganizationRepository>()
.GetOccupiedSeatCountByOrganizationIdAsync(organization.Id)
.Returns(new OrganizationSeatCounts { Users = 1, Sponsored = 0 });

var subscriberService = sutProvider.GetDependency<ISubscriberService>();

// Set up subscriber service to return null for customer
subscriberService
.GetCustomer(organization)
.Returns((Customer)null);

// Set up subscriber service to return null for subscription
subscriberService.GetSubscription(organization, Arg.Is<SubscriptionGetOptions>(options =>
options.Expand.Contains("discounts.coupon.applies_to"))).Returns((Subscription)null);

var metadata = await sutProvider.Sut.GetMetadata(organizationId);

Assert.NotNull(metadata);
Assert.False(metadata!.IsOnSecretsManagerStandalone);
Assert.Equal(1, metadata.OrganizationOccupiedSeats);
}

#endregion

#region Finalize - Trial Settings

[Theory, BitAutoData]
Expand Down
Loading