mirror of
https://github.com/bitwarden/server.git
synced 2024-11-24 12:35:25 +01:00
[PM-14798] Update ProviderEventService for multi-organization enterprises (#5026)
This commit is contained in:
parent
702a81b161
commit
25afd50ab4
@ -1,7 +1,6 @@
|
|||||||
using Bit.Billing.Constants;
|
using Bit.Billing.Constants;
|
||||||
using Bit.Core.AdminConsole.Repositories;
|
using Bit.Core.AdminConsole.Repositories;
|
||||||
using Bit.Core.Billing.Entities;
|
using Bit.Core.Billing.Entities;
|
||||||
using Bit.Core.Billing.Enums;
|
|
||||||
using Bit.Core.Billing.Repositories;
|
using Bit.Core.Billing.Repositories;
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
@ -42,78 +41,52 @@ public class ProviderEventService(
|
|||||||
case HandledStripeWebhook.InvoiceCreated:
|
case HandledStripeWebhook.InvoiceCreated:
|
||||||
{
|
{
|
||||||
var clients =
|
var clients =
|
||||||
(await providerOrganizationRepository.GetManyDetailsByProviderAsync(parsedProviderId))
|
await providerOrganizationRepository.GetManyDetailsByProviderAsync(parsedProviderId);
|
||||||
.Where(providerOrganization => providerOrganization.Status == OrganizationStatusType.Managed);
|
|
||||||
|
|
||||||
var providerPlans = await providerPlanRepository.GetByProviderId(parsedProviderId);
|
var providerPlans = await providerPlanRepository.GetByProviderId(parsedProviderId);
|
||||||
|
|
||||||
var enterpriseProviderPlan =
|
var invoiceItems = new List<ProviderInvoiceItem>();
|
||||||
providerPlans.FirstOrDefault(providerPlan => providerPlan.PlanType == PlanType.EnterpriseMonthly);
|
|
||||||
|
|
||||||
var teamsProviderPlan =
|
foreach (var client in clients)
|
||||||
providerPlans.FirstOrDefault(providerPlan => providerPlan.PlanType == PlanType.TeamsMonthly);
|
|
||||||
|
|
||||||
if (enterpriseProviderPlan == null || !enterpriseProviderPlan.IsConfigured() ||
|
|
||||||
teamsProviderPlan == null || !teamsProviderPlan.IsConfigured())
|
|
||||||
{
|
{
|
||||||
logger.LogError("Provider {ProviderID} is missing or has misconfigured provider plans", parsedProviderId);
|
if (client.Status != OrganizationStatusType.Managed)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
throw new Exception("Cannot record invoice line items for Provider with missing or misconfigured provider plans");
|
var plan = StaticStore.Plans.Single(x => x.Name == client.Plan && providerPlans.Any(y => y.PlanType == x.Type));
|
||||||
}
|
|
||||||
|
|
||||||
var enterprisePlan = StaticStore.GetPlan(PlanType.EnterpriseMonthly);
|
var discountedPercentage = (100 - (invoice.Discount?.Coupon?.PercentOff ?? 0)) / 100;
|
||||||
|
|
||||||
var teamsPlan = StaticStore.GetPlan(PlanType.TeamsMonthly);
|
var discountedSeatPrice = plan.PasswordManager.ProviderPortalSeatPrice * discountedPercentage;
|
||||||
|
|
||||||
var discountedPercentage = (100 - (invoice.Discount?.Coupon?.PercentOff ?? 0)) / 100;
|
|
||||||
|
|
||||||
var discountedEnterpriseSeatPrice = enterprisePlan.PasswordManager.ProviderPortalSeatPrice * discountedPercentage;
|
|
||||||
|
|
||||||
var discountedTeamsSeatPrice = teamsPlan.PasswordManager.ProviderPortalSeatPrice * discountedPercentage;
|
|
||||||
|
|
||||||
var invoiceItems = clients.Select(client => new ProviderInvoiceItem
|
|
||||||
{
|
|
||||||
ProviderId = parsedProviderId,
|
|
||||||
InvoiceId = invoice.Id,
|
|
||||||
InvoiceNumber = invoice.Number,
|
|
||||||
ClientId = client.OrganizationId,
|
|
||||||
ClientName = client.OrganizationName,
|
|
||||||
PlanName = client.Plan,
|
|
||||||
AssignedSeats = client.Seats ?? 0,
|
|
||||||
UsedSeats = client.OccupiedSeats ?? 0,
|
|
||||||
Total = client.Plan == enterprisePlan.Name
|
|
||||||
? (client.Seats ?? 0) * discountedEnterpriseSeatPrice
|
|
||||||
: (client.Seats ?? 0) * discountedTeamsSeatPrice
|
|
||||||
}).ToList();
|
|
||||||
|
|
||||||
if (enterpriseProviderPlan.PurchasedSeats is null or 0)
|
|
||||||
{
|
|
||||||
var enterpriseClientSeats = invoiceItems
|
|
||||||
.Where(item => item.PlanName == enterprisePlan.Name)
|
|
||||||
.Sum(item => item.AssignedSeats);
|
|
||||||
|
|
||||||
var unassignedEnterpriseSeats = enterpriseProviderPlan.SeatMinimum - enterpriseClientSeats ?? 0;
|
|
||||||
|
|
||||||
invoiceItems.Add(new ProviderInvoiceItem
|
invoiceItems.Add(new ProviderInvoiceItem
|
||||||
{
|
{
|
||||||
ProviderId = parsedProviderId,
|
ProviderId = parsedProviderId,
|
||||||
InvoiceId = invoice.Id,
|
InvoiceId = invoice.Id,
|
||||||
InvoiceNumber = invoice.Number,
|
InvoiceNumber = invoice.Number,
|
||||||
ClientName = "Unassigned seats",
|
ClientId = client.OrganizationId,
|
||||||
PlanName = enterprisePlan.Name,
|
ClientName = client.OrganizationName,
|
||||||
AssignedSeats = unassignedEnterpriseSeats,
|
PlanName = client.Plan,
|
||||||
UsedSeats = 0,
|
AssignedSeats = client.Seats ?? 0,
|
||||||
Total = unassignedEnterpriseSeats * discountedEnterpriseSeatPrice
|
UsedSeats = client.OccupiedSeats ?? 0,
|
||||||
|
Total = (client.Seats ?? 0) * discountedSeatPrice
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (teamsProviderPlan.PurchasedSeats is null or 0)
|
foreach (var providerPlan in providerPlans.Where(x => x.PurchasedSeats is null or 0))
|
||||||
{
|
{
|
||||||
var teamsClientSeats = invoiceItems
|
var plan = StaticStore.GetPlan(providerPlan.PlanType);
|
||||||
.Where(item => item.PlanName == teamsPlan.Name)
|
|
||||||
|
var clientSeats = invoiceItems
|
||||||
|
.Where(item => item.PlanName == plan.Name)
|
||||||
.Sum(item => item.AssignedSeats);
|
.Sum(item => item.AssignedSeats);
|
||||||
|
|
||||||
var unassignedTeamsSeats = teamsProviderPlan.SeatMinimum - teamsClientSeats ?? 0;
|
var unassignedSeats = providerPlan.SeatMinimum - clientSeats ?? 0;
|
||||||
|
|
||||||
|
var discountedPercentage = (100 - (invoice.Discount?.Coupon?.PercentOff ?? 0)) / 100;
|
||||||
|
|
||||||
|
var discountedSeatPrice = plan.PasswordManager.ProviderPortalSeatPrice * discountedPercentage;
|
||||||
|
|
||||||
invoiceItems.Add(new ProviderInvoiceItem
|
invoiceItems.Add(new ProviderInvoiceItem
|
||||||
{
|
{
|
||||||
@ -121,10 +94,10 @@ public class ProviderEventService(
|
|||||||
InvoiceId = invoice.Id,
|
InvoiceId = invoice.Id,
|
||||||
InvoiceNumber = invoice.Number,
|
InvoiceNumber = invoice.Number,
|
||||||
ClientName = "Unassigned seats",
|
ClientName = "Unassigned seats",
|
||||||
PlanName = teamsPlan.Name,
|
PlanName = plan.Name,
|
||||||
AssignedSeats = unassignedTeamsSeats,
|
AssignedSeats = unassignedSeats,
|
||||||
UsedSeats = 0,
|
UsedSeats = 0,
|
||||||
Total = unassignedTeamsSeats * discountedTeamsSeatPrice
|
Total = unassignedSeats * discountedSeatPrice
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,7 +8,6 @@ using Bit.Core.Billing.Enums;
|
|||||||
using Bit.Core.Billing.Repositories;
|
using Bit.Core.Billing.Repositories;
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
using FluentAssertions;
|
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using NSubstitute;
|
using NSubstitute;
|
||||||
using Stripe;
|
using Stripe;
|
||||||
@ -89,54 +88,6 @@ public class ProviderEventServiceTests
|
|||||||
await _providerOrganizationRepository.DidNotReceiveWithAnyArgs().GetManyDetailsByProviderAsync(Arg.Any<Guid>());
|
await _providerOrganizationRepository.DidNotReceiveWithAnyArgs().GetManyDetailsByProviderAsync(Arg.Any<Guid>());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task TryRecordInvoiceLineItems_InvoiceCreated_MisconfiguredProviderPlans_ThrowsException()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var stripeEvent = await StripeTestEvents.GetAsync(StripeEventType.InvoiceCreated);
|
|
||||||
|
|
||||||
const string subscriptionId = "sub_1";
|
|
||||||
var providerId = Guid.NewGuid();
|
|
||||||
|
|
||||||
var invoice = new Invoice
|
|
||||||
{
|
|
||||||
SubscriptionId = subscriptionId
|
|
||||||
};
|
|
||||||
|
|
||||||
_stripeEventService.GetInvoice(stripeEvent).Returns(invoice);
|
|
||||||
|
|
||||||
var subscription = new Subscription
|
|
||||||
{
|
|
||||||
Metadata = new Dictionary<string, string> { { "providerId", providerId.ToString() } }
|
|
||||||
};
|
|
||||||
|
|
||||||
_stripeFacade.GetSubscription(subscriptionId).Returns(subscription);
|
|
||||||
|
|
||||||
var providerPlans = new List<ProviderPlan>
|
|
||||||
{
|
|
||||||
new ()
|
|
||||||
{
|
|
||||||
Id = Guid.NewGuid(),
|
|
||||||
ProviderId = providerId,
|
|
||||||
PlanType = PlanType.TeamsMonthly,
|
|
||||||
AllocatedSeats = 0,
|
|
||||||
PurchasedSeats = 0,
|
|
||||||
SeatMinimum = 100
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
_providerPlanRepository.GetByProviderId(providerId).Returns(providerPlans);
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var function = async () => await _providerEventService.TryRecordInvoiceLineItems(stripeEvent);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
await function
|
|
||||||
.Should()
|
|
||||||
.ThrowAsync<Exception>()
|
|
||||||
.WithMessage("Cannot record invoice line items for Provider with missing or misconfigured provider plans");
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task TryRecordInvoiceLineItems_InvoiceCreated_Succeeds()
|
public async Task TryRecordInvoiceLineItems_InvoiceCreated_Succeeds()
|
||||||
{
|
{
|
||||||
|
Loading…
Reference in New Issue
Block a user