diff --git a/bitwarden_license/src/Commercial.Core/Billing/ProviderBillingService.cs b/bitwarden_license/src/Commercial.Core/Billing/ProviderBillingService.cs index 6f52f59de..2ee7f606d 100644 --- a/bitwarden_license/src/Commercial.Core/Billing/ProviderBillingService.cs +++ b/bitwarden_license/src/Commercial.Core/Billing/ProviderBillingService.cs @@ -79,7 +79,7 @@ public class ProviderBillingService( if (string.IsNullOrEmpty(taxInfo.BillingAddressCountry) || string.IsNullOrEmpty(taxInfo.BillingAddressPostalCode)) { - logger.LogError("Cannot create Stripe customer for provider ({ID}) - Both the provider's country and postal code are required", provider.Id); + logger.LogError("Cannot create customer for provider ({ProviderID}) without both a country and postal code", provider.Id); throw ContactSupport(); } @@ -97,7 +97,6 @@ public class ProviderBillingService( City = taxInfo.BillingAddressCity, State = taxInfo.BillingAddressState }, - Coupon = "msp-discount-35", Description = provider.DisplayBusinessName(), Email = provider.BillingEmail, InvoiceSettings = new CustomerInvoiceSettingsOptions @@ -399,20 +398,13 @@ public class ProviderBillingService( { ArgumentNullException.ThrowIfNull(provider); - if (!string.IsNullOrEmpty(provider.GatewaySubscriptionId)) - { - logger.LogWarning("Cannot start Provider subscription - Provider ({ID}) already has a {FieldName}", provider.Id, nameof(provider.GatewaySubscriptionId)); - - throw ContactSupport(); - } - var customer = await subscriberService.GetCustomerOrThrow(provider); var providerPlans = await providerPlanRepository.GetByProviderId(provider.Id); if (providerPlans == null || providerPlans.Count == 0) { - logger.LogError("Cannot start Provider subscription - Provider ({ID}) has no configured plans", provider.Id); + logger.LogError("Cannot start subscription for provider ({ProviderID}) that has no configured plans", provider.Id); throw ContactSupport(); } @@ -422,9 +414,9 @@ public class ProviderBillingService( var teamsProviderPlan = providerPlans.SingleOrDefault(providerPlan => providerPlan.PlanType == PlanType.TeamsMonthly); - if (teamsProviderPlan == null) + if (teamsProviderPlan == null || !teamsProviderPlan.IsConfigured()) { - logger.LogError("Cannot start Provider subscription - Provider ({ID}) has no configured Teams Monthly plan", provider.Id); + logger.LogError("Cannot start subscription for provider ({ProviderID}) that has no configured Teams plan", provider.Id); throw ContactSupport(); } @@ -440,9 +432,9 @@ public class ProviderBillingService( var enterpriseProviderPlan = providerPlans.SingleOrDefault(providerPlan => providerPlan.PlanType == PlanType.EnterpriseMonthly); - if (enterpriseProviderPlan == null) + if (enterpriseProviderPlan == null || !enterpriseProviderPlan.IsConfigured()) { - logger.LogError("Cannot start Provider subscription - Provider ({ID}) has no configured Enterprise Monthly plan", provider.Id); + logger.LogError("Cannot start subscription for provider ({ProviderID}) that has no configured Enterprise plan", provider.Id); throw ContactSupport(); } @@ -481,7 +473,7 @@ public class ProviderBillingService( { await providerRepository.ReplaceAsync(provider); - logger.LogError("Started incomplete Provider ({ProviderID}) subscription ({SubscriptionID})", provider.Id, subscription.Id); + logger.LogError("Started incomplete provider ({ProviderID}) subscription ({SubscriptionID})", provider.Id, subscription.Id); throw ContactSupport(); } diff --git a/bitwarden_license/test/Commercial.Core.Test/Billing/ProviderBillingServiceTests.cs b/bitwarden_license/test/Commercial.Core.Test/Billing/ProviderBillingServiceTests.cs index caebde363..a176187f0 100644 --- a/bitwarden_license/test/Commercial.Core.Test/Billing/ProviderBillingServiceTests.cs +++ b/bitwarden_license/test/Commercial.Core.Test/Billing/ProviderBillingServiceTests.cs @@ -556,7 +556,6 @@ public class ProviderBillingServiceTests o.Address.Line2 == taxInfo.BillingAddressLine2 && o.Address.City == taxInfo.BillingAddressCity && o.Address.State == taxInfo.BillingAddressState && - o.Coupon == "msp-discount-35" && o.Description == WebUtility.HtmlDecode(provider.BusinessName) && o.Email == provider.BillingEmail && o.InvoiceSettings.CustomFields.FirstOrDefault().Name == "Provider" && @@ -579,7 +578,6 @@ public class ProviderBillingServiceTests o.Address.Line2 == taxInfo.BillingAddressLine2 && o.Address.City == taxInfo.BillingAddressCity && o.Address.State == taxInfo.BillingAddressState && - o.Coupon == "msp-discount-35" && o.Description == WebUtility.HtmlDecode(provider.BusinessName) && o.Email == provider.BillingEmail && o.InvoiceSettings.CustomFields.FirstOrDefault().Name == "Provider" && @@ -1023,16 +1021,6 @@ public class ProviderBillingServiceTests SutProvider sutProvider) => await Assert.ThrowsAsync(() => sutProvider.Sut.StartSubscription(null)); - [Theory, BitAutoData] - public async Task StartSubscription_AlreadyHasGatewaySubscriptionId_ContactSupport( - SutProvider sutProvider, - Provider provider) - { - provider.GatewaySubscriptionId = "subscription_id"; - - await ThrowsContactSupportAsync(() => sutProvider.Sut.StartSubscription(provider)); - } - [Theory, BitAutoData] public async Task StartSubscription_NoProviderPlans_ContactSupport( SutProvider sutProvider, @@ -1121,8 +1109,24 @@ public class ProviderBillingServiceTests var providerPlans = new List { - new() { PlanType = PlanType.TeamsMonthly, SeatMinimum = 100 }, - new() { PlanType = PlanType.EnterpriseMonthly, SeatMinimum = 100 } + new() + { + Id = Guid.NewGuid(), + ProviderId = provider.Id, + PlanType = PlanType.TeamsMonthly, + SeatMinimum = 100, + PurchasedSeats = 0, + AllocatedSeats = 0 + }, + new() + { + Id = Guid.NewGuid(), + ProviderId = provider.Id, + PlanType = PlanType.EnterpriseMonthly, + SeatMinimum = 100, + PurchasedSeats = 0, + AllocatedSeats = 0 + } }; sutProvider.GetDependency().GetByProviderId(provider.Id) @@ -1153,8 +1157,24 @@ public class ProviderBillingServiceTests var providerPlans = new List { - new() { PlanType = PlanType.TeamsMonthly, SeatMinimum = 100 }, - new() { PlanType = PlanType.EnterpriseMonthly, SeatMinimum = 100 } + new() + { + Id = Guid.NewGuid(), + ProviderId = provider.Id, + PlanType = PlanType.TeamsMonthly, + SeatMinimum = 100, + PurchasedSeats = 0, + AllocatedSeats = 0 + }, + new() + { + Id = Guid.NewGuid(), + ProviderId = provider.Id, + PlanType = PlanType.EnterpriseMonthly, + SeatMinimum = 100, + PurchasedSeats = 0, + AllocatedSeats = 0 + } }; sutProvider.GetDependency().GetByProviderId(provider.Id) diff --git a/src/Core/Billing/Services/IProviderBillingService.cs b/src/Core/Billing/Services/IProviderBillingService.cs index fbed616a6..5c215bd71 100644 --- a/src/Core/Billing/Services/IProviderBillingService.cs +++ b/src/Core/Billing/Services/IProviderBillingService.cs @@ -1,6 +1,7 @@ using Bit.Core.AdminConsole.Entities; using Bit.Core.AdminConsole.Entities.Provider; using Bit.Core.AdminConsole.Enums.Provider; +using Bit.Core.Billing.Entities; using Bit.Core.Billing.Enums; using Bit.Core.Billing.Models; using Bit.Core.Models.Business; @@ -43,6 +44,12 @@ public interface IProviderBillingService Provider provider, Organization organization); + /// + /// Generate a provider's client invoice report in CSV format for the specified . Utilizes the + /// records saved for the as part of our webhook processing for the "invoice.created" and "invoice.finalized" Stripe events. + /// + /// The ID of the Stripe to generate the report for. + /// The provider's client invoice report as a byte array. Task GenerateClientInvoiceReport( string invoiceId);