mirror of
https://github.com/bitwarden/server.git
synced 2025-06-30 15:42:48 -05:00
[AC-1938] Update provider payment method (#4140)
* Refactored GET provider subscription Refactoring this endpoint and its associated tests in preparation for the addition of more endpoints that share similar patterns * Replaced StripePaymentService call in AccountsController, OrganizationsController This was made in error during a previous PR. Since this is not related to Consolidated Billing, we want to try not to include it in these changes. * Removing GetPaymentInformation call from ProviderBillingService This method is a good call for the SubscriberService as we'll want to extend the functionality to all subscriber types * Refactored GetTaxInformation to use Billing owned DTO * Add UpdateTaxInformation to SubscriberService * Added GetTaxInformation and UpdateTaxInformation endpoints to ProviderBillingController * Added controller to manage creation of Stripe SetupIntents With the deprecation of the Sources API, we need to move the bank account creation process to using SetupIntents. This controller brings both the creation of "card" and "us_bank_account" SetupIntents under billing management. * Added UpdatePaymentMethod method to SubscriberService This method utilizes the SetupIntents created by the StripeController from the previous commit when a customer adds a card or us_bank_account payment method (Stripe). We need to cache the most recent SetupIntent for the subscriber so that we know which PaymentMethod is their most recent even when it hasn't been confirmed yet. * Refactored GetPaymentMethod to use billing owned DTO and check setup intents * Added GetPaymentMethod and UpdatePaymentMethod endpoints to ProviderBillingController * Re-added GetPaymentInformation endpoint to consolidate API calls on the payment method page * Added VerifyBankAccount endpoint to ProviderBillingController in order to finalize bank account payment methods * Updated BitPayInvoiceRequestModel to support providers * run dotnet format * Conner's feedback * Run dotnet format'
This commit is contained in:
@ -21,7 +21,6 @@ using Bit.Core.Utilities;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using NSubstitute;
|
||||
using NSubstitute.ReturnsExtensions;
|
||||
using Stripe;
|
||||
using Xunit;
|
||||
using static Bit.Core.Test.Billing.Utilities;
|
||||
@ -701,60 +700,33 @@ public class ProviderBillingServiceTests
|
||||
|
||||
#endregion
|
||||
|
||||
#region GetSubscriptionData
|
||||
#region GetConsolidatedBillingSubscription
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task GetSubscriptionData_NullProvider_ReturnsNull(
|
||||
SutProvider<ProviderBillingService> sutProvider,
|
||||
Guid providerId)
|
||||
{
|
||||
var providerRepository = sutProvider.GetDependency<IProviderRepository>();
|
||||
|
||||
providerRepository.GetByIdAsync(providerId).ReturnsNull();
|
||||
|
||||
var subscriptionData = await sutProvider.Sut.GetSubscriptionDTO(providerId);
|
||||
|
||||
Assert.Null(subscriptionData);
|
||||
|
||||
await providerRepository.Received(1).GetByIdAsync(providerId);
|
||||
}
|
||||
public async Task GetConsolidatedBillingSubscription_NullProvider_ThrowsArgumentNullException(
|
||||
SutProvider<ProviderBillingService> sutProvider) =>
|
||||
await Assert.ThrowsAsync<ArgumentNullException>(() => sutProvider.Sut.GetConsolidatedBillingSubscription(null));
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task GetSubscriptionData_NullSubscription_ReturnsNull(
|
||||
public async Task GetConsolidatedBillingSubscription_NullSubscription_ReturnsNull(
|
||||
SutProvider<ProviderBillingService> sutProvider,
|
||||
Guid providerId,
|
||||
Provider provider)
|
||||
{
|
||||
var providerRepository = sutProvider.GetDependency<IProviderRepository>();
|
||||
var consolidatedBillingSubscription = await sutProvider.Sut.GetConsolidatedBillingSubscription(provider);
|
||||
|
||||
providerRepository.GetByIdAsync(providerId).Returns(provider);
|
||||
Assert.Null(consolidatedBillingSubscription);
|
||||
|
||||
var subscriberService = sutProvider.GetDependency<ISubscriberService>();
|
||||
|
||||
subscriberService.GetSubscription(provider).ReturnsNull();
|
||||
|
||||
var subscriptionData = await sutProvider.Sut.GetSubscriptionDTO(providerId);
|
||||
|
||||
Assert.Null(subscriptionData);
|
||||
|
||||
await providerRepository.Received(1).GetByIdAsync(providerId);
|
||||
|
||||
await subscriberService.Received(1).GetSubscription(
|
||||
await sutProvider.GetDependency<ISubscriberService>().Received(1).GetSubscription(
|
||||
provider,
|
||||
Arg.Is<SubscriptionGetOptions>(
|
||||
options => options.Expand.Count == 1 && options.Expand.First() == "customer"));
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task GetSubscriptionData_Success(
|
||||
public async Task GetConsolidatedBillingSubscription_Success(
|
||||
SutProvider<ProviderBillingService> sutProvider,
|
||||
Guid providerId,
|
||||
Provider provider)
|
||||
{
|
||||
var providerRepository = sutProvider.GetDependency<IProviderRepository>();
|
||||
|
||||
providerRepository.GetByIdAsync(providerId).Returns(provider);
|
||||
|
||||
var subscriberService = sutProvider.GetDependency<ISubscriberService>();
|
||||
|
||||
var subscription = new Subscription();
|
||||
@ -767,7 +739,7 @@ public class ProviderBillingServiceTests
|
||||
var enterprisePlan = new ProviderPlan
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
ProviderId = providerId,
|
||||
ProviderId = provider.Id,
|
||||
PlanType = PlanType.EnterpriseMonthly,
|
||||
SeatMinimum = 100,
|
||||
PurchasedSeats = 0,
|
||||
@ -777,7 +749,7 @@ public class ProviderBillingServiceTests
|
||||
var teamsPlan = new ProviderPlan
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
ProviderId = providerId,
|
||||
ProviderId = provider.Id,
|
||||
PlanType = PlanType.TeamsMonthly,
|
||||
SeatMinimum = 50,
|
||||
PurchasedSeats = 10,
|
||||
@ -786,37 +758,28 @@ public class ProviderBillingServiceTests
|
||||
|
||||
var providerPlans = new List<ProviderPlan> { enterprisePlan, teamsPlan, };
|
||||
|
||||
providerPlanRepository.GetByProviderId(providerId).Returns(providerPlans);
|
||||
providerPlanRepository.GetByProviderId(provider.Id).Returns(providerPlans);
|
||||
|
||||
var subscriptionData = await sutProvider.Sut.GetSubscriptionDTO(providerId);
|
||||
var consolidatedBillingSubscription = await sutProvider.Sut.GetConsolidatedBillingSubscription(provider);
|
||||
|
||||
Assert.NotNull(subscriptionData);
|
||||
Assert.NotNull(consolidatedBillingSubscription);
|
||||
|
||||
Assert.Equivalent(subscriptionData.Subscription, subscription);
|
||||
Assert.Equivalent(consolidatedBillingSubscription.Subscription, subscription);
|
||||
|
||||
Assert.Equal(2, subscriptionData.ProviderPlans.Count);
|
||||
Assert.Equal(2, consolidatedBillingSubscription.ProviderPlans.Count);
|
||||
|
||||
var configuredEnterprisePlan =
|
||||
subscriptionData.ProviderPlans.FirstOrDefault(configuredPlan =>
|
||||
consolidatedBillingSubscription.ProviderPlans.FirstOrDefault(configuredPlan =>
|
||||
configuredPlan.PlanType == PlanType.EnterpriseMonthly);
|
||||
|
||||
var configuredTeamsPlan =
|
||||
subscriptionData.ProviderPlans.FirstOrDefault(configuredPlan =>
|
||||
consolidatedBillingSubscription.ProviderPlans.FirstOrDefault(configuredPlan =>
|
||||
configuredPlan.PlanType == PlanType.TeamsMonthly);
|
||||
|
||||
Compare(enterprisePlan, configuredEnterprisePlan);
|
||||
|
||||
Compare(teamsPlan, configuredTeamsPlan);
|
||||
|
||||
await providerRepository.Received(1).GetByIdAsync(providerId);
|
||||
|
||||
await subscriberService.Received(1).GetSubscription(
|
||||
provider,
|
||||
Arg.Is<SubscriptionGetOptions>(
|
||||
options => options.Expand.Count == 1 && options.Expand.First() == "customer"));
|
||||
|
||||
await providerPlanRepository.Received(1).GetByProviderId(providerId);
|
||||
|
||||
return;
|
||||
|
||||
void Compare(ProviderPlan providerPlan, ConfiguredProviderPlanDTO configuredProviderPlan)
|
||||
@ -1005,106 +968,4 @@ public class ProviderBillingServiceTests
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region GetPaymentInformationAsync
|
||||
[Theory, BitAutoData]
|
||||
public async Task GetPaymentInformationAsync_NullProvider_ReturnsNull(
|
||||
SutProvider<ProviderBillingService> sutProvider,
|
||||
Guid providerId)
|
||||
{
|
||||
var providerRepository = sutProvider.GetDependency<IProviderRepository>();
|
||||
providerRepository.GetByIdAsync(providerId).ReturnsNull();
|
||||
|
||||
var paymentService = sutProvider.GetDependency<ISubscriberService>();
|
||||
paymentService.GetTaxInformationAsync(Arg.Any<Provider>()).ReturnsNull();
|
||||
paymentService.GetPaymentMethodAsync(Arg.Any<Provider>()).ReturnsNull();
|
||||
|
||||
var sut = sutProvider.Sut;
|
||||
|
||||
var paymentInfo = await sut.GetPaymentInformationAsync(providerId);
|
||||
|
||||
Assert.Null(paymentInfo);
|
||||
await providerRepository.Received(1).GetByIdAsync(providerId);
|
||||
await paymentService.DidNotReceive().GetTaxInformationAsync(Arg.Any<Provider>());
|
||||
await paymentService.DidNotReceive().GetPaymentMethodAsync(Arg.Any<Provider>());
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task GetPaymentInformationAsync_NullSubscription_ReturnsNull(
|
||||
SutProvider<ProviderBillingService> sutProvider,
|
||||
Guid providerId,
|
||||
Provider provider)
|
||||
{
|
||||
var providerRepository = sutProvider.GetDependency<IProviderRepository>();
|
||||
|
||||
providerRepository.GetByIdAsync(providerId).Returns(provider);
|
||||
|
||||
var subscriberService = sutProvider.GetDependency<ISubscriberService>();
|
||||
|
||||
subscriberService.GetTaxInformationAsync(provider).ReturnsNull();
|
||||
subscriberService.GetPaymentMethodAsync(provider).ReturnsNull();
|
||||
|
||||
var paymentInformation = await sutProvider.Sut.GetPaymentInformationAsync(providerId);
|
||||
|
||||
Assert.Null(paymentInformation);
|
||||
await providerRepository.Received(1).GetByIdAsync(providerId);
|
||||
await subscriberService.Received(1).GetTaxInformationAsync(provider);
|
||||
await subscriberService.Received(1).GetPaymentMethodAsync(provider);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task GetPaymentInformationAsync_ResellerProvider_ThrowContactSupport(
|
||||
SutProvider<ProviderBillingService> sutProvider,
|
||||
Guid providerId,
|
||||
Provider provider)
|
||||
{
|
||||
provider.Id = providerId;
|
||||
provider.Type = ProviderType.Reseller;
|
||||
var providerRepository = sutProvider.GetDependency<IProviderRepository>();
|
||||
providerRepository.GetByIdAsync(providerId).Returns(provider);
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BillingException>(
|
||||
() => sutProvider.Sut.GetPaymentInformationAsync(providerId));
|
||||
|
||||
Assert.Equal("Consolidated billing does not support reseller-type providers", exception.Message);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task GetPaymentInformationAsync_Success_ReturnsProviderPaymentInfoDTO(
|
||||
SutProvider<ProviderBillingService> sutProvider,
|
||||
Guid providerId,
|
||||
Provider provider)
|
||||
{
|
||||
provider.Id = providerId;
|
||||
provider.Type = ProviderType.Msp;
|
||||
var taxInformation = new TaxInfo { TaxIdNumber = "12345" };
|
||||
var paymentMethod = new PaymentMethod
|
||||
{
|
||||
Id = "pm_test123",
|
||||
Type = "card",
|
||||
Card = new PaymentMethodCard
|
||||
{
|
||||
Brand = "visa",
|
||||
Last4 = "4242",
|
||||
ExpMonth = 12,
|
||||
ExpYear = 2024
|
||||
}
|
||||
};
|
||||
var billingInformation = new BillingInfo { PaymentSource = new BillingInfo.BillingSource(paymentMethod) };
|
||||
|
||||
var providerRepository = sutProvider.GetDependency<IProviderRepository>();
|
||||
providerRepository.GetByIdAsync(providerId).Returns(provider);
|
||||
|
||||
var subscriberService = sutProvider.GetDependency<ISubscriberService>();
|
||||
subscriberService.GetTaxInformationAsync(provider).Returns(taxInformation);
|
||||
subscriberService.GetPaymentMethodAsync(provider).Returns(billingInformation.PaymentSource);
|
||||
|
||||
var result = await sutProvider.Sut.GetPaymentInformationAsync(providerId);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal(billingInformation.PaymentSource, result.billingSource);
|
||||
Assert.Equal(taxInformation, result.taxInfo);
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
|
Reference in New Issue
Block a user