using Bit.Core.AdminConsole.Entities; using Bit.Core.Billing.Enums; using Bit.Core.Enums; using Bit.Core.Exceptions; using Bit.Core.Models.Business; using Bit.Core.Services; using Bit.Core.Settings; using Bit.Core.Utilities; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; using Braintree; using NSubstitute; using Xunit; using Customer = Braintree.Customer; using PaymentMethod = Braintree.PaymentMethod; using PaymentMethodType = Bit.Core.Enums.PaymentMethodType; namespace Bit.Core.Test.Services; [SutProviderCustomize] public class StripePaymentServiceTests { [Theory] [BitAutoData(PaymentMethodType.BitPay)] [BitAutoData(PaymentMethodType.BitPay)] [BitAutoData(PaymentMethodType.Credit)] [BitAutoData(PaymentMethodType.WireTransfer)] [BitAutoData(PaymentMethodType.Check)] public async Task PurchaseOrganizationAsync_Invalid(PaymentMethodType paymentMethodType, SutProvider sutProvider) { var exception = await Assert.ThrowsAsync( () => sutProvider.Sut.PurchaseOrganizationAsync(null, paymentMethodType, null, null, 0, 0, false, null, false, -1, -1)); Assert.Equal("Payment method is not supported at this time.", exception.Message); } [Theory, BitAutoData] public async Task PurchaseOrganizationAsync_Stripe_ProviderOrg_Coupon_Add(SutProvider sutProvider, Organization organization, string paymentToken, TaxInfo taxInfo, bool provider = true) { var plan = StaticStore.GetPlan(PlanType.EnterpriseAnnually); var stripeAdapter = sutProvider.GetDependency(); stripeAdapter.CustomerCreateAsync(default).ReturnsForAnyArgs(new Stripe.Customer { Id = "C-1", }); stripeAdapter.SubscriptionCreateAsync(default).ReturnsForAnyArgs(new Stripe.Subscription { Id = "S-1", CurrentPeriodEnd = DateTime.Today.AddDays(10), }); sutProvider.GetDependency() .BaseServiceUri.CloudRegion .Returns("US"); var result = await sutProvider.Sut.PurchaseOrganizationAsync(organization, PaymentMethodType.Card, paymentToken, plan, 0, 0, false, taxInfo, provider); Assert.Null(result); Assert.Equal(GatewayType.Stripe, organization.Gateway); Assert.Equal("C-1", organization.GatewayCustomerId); Assert.Equal("S-1", organization.GatewaySubscriptionId); Assert.True(organization.Enabled); Assert.Equal(DateTime.Today.AddDays(10), organization.ExpirationDate); await stripeAdapter.Received().CustomerCreateAsync(Arg.Is(c => c.Description == organization.BusinessName && c.Email == organization.BillingEmail && c.Source == paymentToken && c.PaymentMethod == null && c.Coupon == "msp-discount-35" && c.Metadata.Count == 1 && c.Metadata["region"] == "US" && c.InvoiceSettings.DefaultPaymentMethod == null && c.Address.Country == taxInfo.BillingAddressCountry && c.Address.PostalCode == taxInfo.BillingAddressPostalCode && c.Address.Line1 == taxInfo.BillingAddressLine1 && c.Address.Line2 == taxInfo.BillingAddressLine2 && c.Address.City == taxInfo.BillingAddressCity && c.Address.State == taxInfo.BillingAddressState && c.TaxIdData == null )); await stripeAdapter.Received().SubscriptionCreateAsync(Arg.Is(s => s.Customer == "C-1" && s.Expand[0] == "latest_invoice.payment_intent" && s.Metadata[organization.GatewayIdField()] == organization.Id.ToString() && s.Items.Count == 0 )); } [Theory, BitAutoData] public async Task PurchaseOrganizationAsync_SM_Stripe_ProviderOrg_Coupon_Add(SutProvider sutProvider, Organization organization, string paymentToken, TaxInfo taxInfo, bool provider = true) { var plan = StaticStore.GetPlan(PlanType.EnterpriseAnnually); organization.UseSecretsManager = true; var stripeAdapter = sutProvider.GetDependency(); stripeAdapter.CustomerCreateAsync(default).ReturnsForAnyArgs(new Stripe.Customer { Id = "C-1", }); stripeAdapter.SubscriptionCreateAsync(default).ReturnsForAnyArgs(new Stripe.Subscription { Id = "S-1", CurrentPeriodEnd = DateTime.Today.AddDays(10), }); sutProvider.GetDependency() .BaseServiceUri.CloudRegion .Returns("US"); var result = await sutProvider.Sut.PurchaseOrganizationAsync(organization, PaymentMethodType.Card, paymentToken, plan, 1, 1, false, taxInfo, provider, 1, 1); Assert.Null(result); Assert.Equal(GatewayType.Stripe, organization.Gateway); Assert.Equal("C-1", organization.GatewayCustomerId); Assert.Equal("S-1", organization.GatewaySubscriptionId); Assert.True(organization.Enabled); Assert.Equal(DateTime.Today.AddDays(10), organization.ExpirationDate); await stripeAdapter.Received().CustomerCreateAsync(Arg.Is(c => c.Description == organization.BusinessName && c.Email == organization.BillingEmail && c.Source == paymentToken && c.PaymentMethod == null && c.Coupon == "msp-discount-35" && c.Metadata.Count == 1 && c.Metadata["region"] == "US" && c.InvoiceSettings.DefaultPaymentMethod == null && c.Address.Country == taxInfo.BillingAddressCountry && c.Address.PostalCode == taxInfo.BillingAddressPostalCode && c.Address.Line1 == taxInfo.BillingAddressLine1 && c.Address.Line2 == taxInfo.BillingAddressLine2 && c.Address.City == taxInfo.BillingAddressCity && c.Address.State == taxInfo.BillingAddressState && c.TaxIdData == null )); await stripeAdapter.Received().SubscriptionCreateAsync(Arg.Is(s => s.Customer == "C-1" && s.Expand[0] == "latest_invoice.payment_intent" && s.Metadata[organization.GatewayIdField()] == organization.Id.ToString() && s.Items.Count == 4 )); } [Theory, BitAutoData] public async Task PurchaseOrganizationAsync_Stripe(SutProvider sutProvider, Organization organization, string paymentToken, TaxInfo taxInfo) { var plan = StaticStore.GetPlan(PlanType.EnterpriseAnnually); organization.UseSecretsManager = true; var stripeAdapter = sutProvider.GetDependency(); stripeAdapter.CustomerCreateAsync(default).ReturnsForAnyArgs(new Stripe.Customer { Id = "C-1", }); stripeAdapter.SubscriptionCreateAsync(default).ReturnsForAnyArgs(new Stripe.Subscription { Id = "S-1", CurrentPeriodEnd = DateTime.Today.AddDays(10), }); sutProvider.GetDependency() .BaseServiceUri.CloudRegion .Returns("US"); var result = await sutProvider.Sut.PurchaseOrganizationAsync(organization, PaymentMethodType.Card, paymentToken, plan, 0, 0 , false, taxInfo, false, 8, 10); Assert.Null(result); Assert.Equal(GatewayType.Stripe, organization.Gateway); Assert.Equal("C-1", organization.GatewayCustomerId); Assert.Equal("S-1", organization.GatewaySubscriptionId); Assert.True(organization.Enabled); Assert.Equal(DateTime.Today.AddDays(10), organization.ExpirationDate); await stripeAdapter.Received().CustomerCreateAsync(Arg.Is(c => c.Description == organization.BusinessName && c.Email == organization.BillingEmail && c.Source == paymentToken && c.PaymentMethod == null && c.Metadata.Count == 1 && c.Metadata["region"] == "US" && c.InvoiceSettings.DefaultPaymentMethod == null && c.InvoiceSettings.CustomFields != null && c.InvoiceSettings.CustomFields[0].Name == "Organization" && c.InvoiceSettings.CustomFields[0].Value == organization.SubscriberName().Substring(0, 30) && c.Address.Country == taxInfo.BillingAddressCountry && c.Address.PostalCode == taxInfo.BillingAddressPostalCode && c.Address.Line1 == taxInfo.BillingAddressLine1 && c.Address.Line2 == taxInfo.BillingAddressLine2 && c.Address.City == taxInfo.BillingAddressCity && c.Address.State == taxInfo.BillingAddressState && c.TaxIdData == null )); await stripeAdapter.Received().SubscriptionCreateAsync(Arg.Is(s => s.Customer == "C-1" && s.Expand[0] == "latest_invoice.payment_intent" && s.Metadata[organization.GatewayIdField()] == organization.Id.ToString() && s.Items.Count == 2 )); } [Theory, BitAutoData] public async Task PurchaseOrganizationAsync_Stripe_PM(SutProvider sutProvider, Organization organization, string paymentToken, TaxInfo taxInfo) { var plan = StaticStore.GetPlan(PlanType.EnterpriseAnnually); paymentToken = "pm_" + paymentToken; var stripeAdapter = sutProvider.GetDependency(); stripeAdapter.CustomerCreateAsync(default).ReturnsForAnyArgs(new Stripe.Customer { Id = "C-1", }); stripeAdapter.SubscriptionCreateAsync(default).ReturnsForAnyArgs(new Stripe.Subscription { Id = "S-1", CurrentPeriodEnd = DateTime.Today.AddDays(10), }); sutProvider.GetDependency() .BaseServiceUri.CloudRegion .Returns("US"); var result = await sutProvider.Sut.PurchaseOrganizationAsync(organization, PaymentMethodType.Card, paymentToken, plan, 0, 0, false, taxInfo); Assert.Null(result); Assert.Equal(GatewayType.Stripe, organization.Gateway); Assert.Equal("C-1", organization.GatewayCustomerId); Assert.Equal("S-1", organization.GatewaySubscriptionId); Assert.True(organization.Enabled); Assert.Equal(DateTime.Today.AddDays(10), organization.ExpirationDate); await stripeAdapter.Received().CustomerCreateAsync(Arg.Is(c => c.Description == organization.BusinessName && c.Email == organization.BillingEmail && c.Source == null && c.PaymentMethod == paymentToken && c.Metadata.Count == 1 && c.Metadata["region"] == "US" && c.InvoiceSettings.DefaultPaymentMethod == paymentToken && c.InvoiceSettings.CustomFields != null && c.InvoiceSettings.CustomFields[0].Name == "Organization" && c.InvoiceSettings.CustomFields[0].Value == organization.SubscriberName().Substring(0, 30) && c.Address.Country == taxInfo.BillingAddressCountry && c.Address.PostalCode == taxInfo.BillingAddressPostalCode && c.Address.Line1 == taxInfo.BillingAddressLine1 && c.Address.Line2 == taxInfo.BillingAddressLine2 && c.Address.City == taxInfo.BillingAddressCity && c.Address.State == taxInfo.BillingAddressState && c.TaxIdData == null )); await stripeAdapter.Received().SubscriptionCreateAsync(Arg.Is(s => s.Customer == "C-1" && s.Expand[0] == "latest_invoice.payment_intent" && s.Metadata[organization.GatewayIdField()] == organization.Id.ToString() && s.Items.Count == 0 )); } [Theory, BitAutoData] public async Task PurchaseOrganizationAsync_Stripe_Declined(SutProvider sutProvider, Organization organization, string paymentToken, TaxInfo taxInfo) { var plan = StaticStore.GetPlan(PlanType.EnterpriseAnnually); paymentToken = "pm_" + paymentToken; var stripeAdapter = sutProvider.GetDependency(); stripeAdapter.CustomerCreateAsync(default).ReturnsForAnyArgs(new Stripe.Customer { Id = "C-1", }); stripeAdapter.SubscriptionCreateAsync(default).ReturnsForAnyArgs(new Stripe.Subscription { Id = "S-1", CurrentPeriodEnd = DateTime.Today.AddDays(10), Status = "incomplete", LatestInvoice = new Stripe.Invoice { PaymentIntent = new Stripe.PaymentIntent { Status = "requires_payment_method", }, }, }); var exception = await Assert.ThrowsAsync( () => sutProvider.Sut.PurchaseOrganizationAsync(organization, PaymentMethodType.Card, paymentToken, plan, 0, 0, false, taxInfo)); Assert.Equal("Payment method was declined.", exception.Message); await stripeAdapter.Received(1).CustomerDeleteAsync("C-1"); } [Theory, BitAutoData] public async Task PurchaseOrganizationAsync_SM_Stripe_Declined(SutProvider sutProvider, Organization organization, string paymentToken, TaxInfo taxInfo) { var plan = StaticStore.GetPlan(PlanType.EnterpriseAnnually); paymentToken = "pm_" + paymentToken; var stripeAdapter = sutProvider.GetDependency(); stripeAdapter.CustomerCreateAsync(default).ReturnsForAnyArgs(new Stripe.Customer { Id = "C-1", }); stripeAdapter.SubscriptionCreateAsync(default).ReturnsForAnyArgs(new Stripe.Subscription { Id = "S-1", CurrentPeriodEnd = DateTime.Today.AddDays(10), Status = "incomplete", LatestInvoice = new Stripe.Invoice { PaymentIntent = new Stripe.PaymentIntent { Status = "requires_payment_method", }, }, }); var exception = await Assert.ThrowsAsync( () => sutProvider.Sut.PurchaseOrganizationAsync(organization, PaymentMethodType.Card, paymentToken, plan, 1, 12, false, taxInfo, false, 10, 10)); Assert.Equal("Payment method was declined.", exception.Message); await stripeAdapter.Received(1).CustomerDeleteAsync("C-1"); } [Theory, BitAutoData] public async Task PurchaseOrganizationAsync_Stripe_RequiresAction(SutProvider sutProvider, Organization organization, string paymentToken, TaxInfo taxInfo) { var plan = StaticStore.GetPlan(PlanType.EnterpriseAnnually); var stripeAdapter = sutProvider.GetDependency(); stripeAdapter.CustomerCreateAsync(default).ReturnsForAnyArgs(new Stripe.Customer { Id = "C-1", }); stripeAdapter.SubscriptionCreateAsync(default).ReturnsForAnyArgs(new Stripe.Subscription { Id = "S-1", CurrentPeriodEnd = DateTime.Today.AddDays(10), Status = "incomplete", LatestInvoice = new Stripe.Invoice { PaymentIntent = new Stripe.PaymentIntent { Status = "requires_action", ClientSecret = "clientSecret", }, }, }); var result = await sutProvider.Sut.PurchaseOrganizationAsync(organization, PaymentMethodType.Card, paymentToken, plan, 0, 0, false, taxInfo); Assert.Equal("clientSecret", result); Assert.False(organization.Enabled); } [Theory, BitAutoData] public async Task PurchaseOrganizationAsync_SM_Stripe_RequiresAction(SutProvider sutProvider, Organization organization, string paymentToken, TaxInfo taxInfo) { var plan = StaticStore.GetPlan(PlanType.EnterpriseAnnually); var stripeAdapter = sutProvider.GetDependency(); stripeAdapter.CustomerCreateAsync(default).ReturnsForAnyArgs(new Stripe.Customer { Id = "C-1", }); stripeAdapter.SubscriptionCreateAsync(default).ReturnsForAnyArgs(new Stripe.Subscription { Id = "S-1", CurrentPeriodEnd = DateTime.Today.AddDays(10), Status = "incomplete", LatestInvoice = new Stripe.Invoice { PaymentIntent = new Stripe.PaymentIntent { Status = "requires_action", ClientSecret = "clientSecret", }, }, }); var result = await sutProvider.Sut.PurchaseOrganizationAsync(organization, PaymentMethodType.Card, paymentToken, plan, 10, 10, false, taxInfo, false, 10, 10); Assert.Equal("clientSecret", result); Assert.False(organization.Enabled); } [Theory, BitAutoData] public async Task PurchaseOrganizationAsync_Paypal(SutProvider sutProvider, Organization organization, string paymentToken, TaxInfo taxInfo) { var plan = StaticStore.GetPlan(PlanType.EnterpriseAnnually); var stripeAdapter = sutProvider.GetDependency(); stripeAdapter.CustomerCreateAsync(default).ReturnsForAnyArgs(new Stripe.Customer { Id = "C-1", }); stripeAdapter.SubscriptionCreateAsync(default).ReturnsForAnyArgs(new Stripe.Subscription { Id = "S-1", CurrentPeriodEnd = DateTime.Today.AddDays(10), }); sutProvider.GetDependency() .BaseServiceUri.CloudRegion .Returns("US"); var customer = Substitute.For(); customer.Id.ReturnsForAnyArgs("Braintree-Id"); customer.PaymentMethods.ReturnsForAnyArgs(new[] { Substitute.For() }); var customerResult = Substitute.For>(); customerResult.IsSuccess().Returns(true); customerResult.Target.ReturnsForAnyArgs(customer); var braintreeGateway = sutProvider.GetDependency(); braintreeGateway.Customer.CreateAsync(default).ReturnsForAnyArgs(customerResult); var result = await sutProvider.Sut.PurchaseOrganizationAsync(organization, PaymentMethodType.PayPal, paymentToken, plan, 0, 0, false, taxInfo); Assert.Null(result); Assert.Equal(GatewayType.Stripe, organization.Gateway); Assert.Equal("C-1", organization.GatewayCustomerId); Assert.Equal("S-1", organization.GatewaySubscriptionId); Assert.True(organization.Enabled); Assert.Equal(DateTime.Today.AddDays(10), organization.ExpirationDate); await stripeAdapter.Received().CustomerCreateAsync(Arg.Is(c => c.Description == organization.BusinessName && c.Email == organization.BillingEmail && c.PaymentMethod == null && c.Metadata.Count == 2 && c.Metadata["btCustomerId"] == "Braintree-Id" && c.Metadata["region"] == "US" && c.InvoiceSettings.DefaultPaymentMethod == null && c.Address.Country == taxInfo.BillingAddressCountry && c.Address.PostalCode == taxInfo.BillingAddressPostalCode && c.Address.Line1 == taxInfo.BillingAddressLine1 && c.Address.Line2 == taxInfo.BillingAddressLine2 && c.Address.City == taxInfo.BillingAddressCity && c.Address.State == taxInfo.BillingAddressState && c.TaxIdData == null )); await stripeAdapter.Received().SubscriptionCreateAsync(Arg.Is(s => s.Customer == "C-1" && s.Expand[0] == "latest_invoice.payment_intent" && s.Metadata[organization.GatewayIdField()] == organization.Id.ToString() && s.Items.Count == 0 )); } [Theory, BitAutoData] public async Task PurchaseOrganizationAsync_SM_Paypal(SutProvider sutProvider, Organization organization, string paymentToken, TaxInfo taxInfo) { var plan = StaticStore.GetPlan(PlanType.EnterpriseAnnually); organization.UseSecretsManager = true; var stripeAdapter = sutProvider.GetDependency(); stripeAdapter.CustomerCreateAsync(default).ReturnsForAnyArgs(new Stripe.Customer { Id = "C-1", }); stripeAdapter.SubscriptionCreateAsync(default).ReturnsForAnyArgs(new Stripe.Subscription { Id = "S-1", CurrentPeriodEnd = DateTime.Today.AddDays(10), }); var customer = Substitute.For(); customer.Id.ReturnsForAnyArgs("Braintree-Id"); customer.PaymentMethods.ReturnsForAnyArgs(new[] { Substitute.For() }); var customerResult = Substitute.For>(); customerResult.IsSuccess().Returns(true); customerResult.Target.ReturnsForAnyArgs(customer); var braintreeGateway = sutProvider.GetDependency(); braintreeGateway.Customer.CreateAsync(default).ReturnsForAnyArgs(customerResult); sutProvider.GetDependency() .BaseServiceUri.CloudRegion .Returns("US"); var additionalStorage = (short)2; var additionalSeats = 10; var additionalSmSeats = 5; var additionalServiceAccounts = 20; var result = await sutProvider.Sut.PurchaseOrganizationAsync(organization, PaymentMethodType.PayPal, paymentToken, plan, additionalStorage, additionalSeats, false, taxInfo, false, additionalSmSeats, additionalServiceAccounts); Assert.Null(result); Assert.Equal(GatewayType.Stripe, organization.Gateway); Assert.Equal("C-1", organization.GatewayCustomerId); Assert.Equal("S-1", organization.GatewaySubscriptionId); Assert.True(organization.Enabled); Assert.Equal(DateTime.Today.AddDays(10), organization.ExpirationDate); await stripeAdapter.Received().CustomerCreateAsync(Arg.Is(c => c.Description == organization.BusinessName && c.Email == organization.BillingEmail && c.PaymentMethod == null && c.Metadata.Count == 2 && c.Metadata["region"] == "US" && c.Metadata["btCustomerId"] == "Braintree-Id" && c.InvoiceSettings.DefaultPaymentMethod == null && c.Address.Country == taxInfo.BillingAddressCountry && c.Address.PostalCode == taxInfo.BillingAddressPostalCode && c.Address.Line1 == taxInfo.BillingAddressLine1 && c.Address.Line2 == taxInfo.BillingAddressLine2 && c.Address.City == taxInfo.BillingAddressCity && c.Address.State == taxInfo.BillingAddressState && c.TaxIdData == null )); await stripeAdapter.Received().SubscriptionCreateAsync(Arg.Is(s => s.Customer == "C-1" && s.Expand[0] == "latest_invoice.payment_intent" && s.Metadata[organization.GatewayIdField()] == organization.Id.ToString() && s.Items.Count == 4 && s.Items.Count(i => i.Plan == plan.PasswordManager.StripeSeatPlanId && i.Quantity == additionalSeats) == 1 && s.Items.Count(i => i.Plan == plan.PasswordManager.StripeStoragePlanId && i.Quantity == additionalStorage) == 1 && s.Items.Count(i => i.Plan == plan.SecretsManager.StripeSeatPlanId && i.Quantity == additionalSmSeats) == 1 && s.Items.Count(i => i.Plan == plan.SecretsManager.StripeServiceAccountPlanId && i.Quantity == additionalServiceAccounts) == 1 )); } [Theory, BitAutoData] public async Task PurchaseOrganizationAsync_Paypal_FailedCreate(SutProvider sutProvider, Organization organization, string paymentToken, TaxInfo taxInfo) { var plan = StaticStore.GetPlan(PlanType.EnterpriseAnnually); var customerResult = Substitute.For>(); customerResult.IsSuccess().Returns(false); var braintreeGateway = sutProvider.GetDependency(); braintreeGateway.Customer.CreateAsync(default).ReturnsForAnyArgs(customerResult); var exception = await Assert.ThrowsAsync( () => sutProvider.Sut.PurchaseOrganizationAsync(organization, PaymentMethodType.PayPal, paymentToken, plan, 0, 0, false, taxInfo)); Assert.Equal("Failed to create PayPal customer record.", exception.Message); } [Theory, BitAutoData] public async Task PurchaseOrganizationAsync_SM_Paypal_FailedCreate(SutProvider sutProvider, Organization organization, string paymentToken, TaxInfo taxInfo) { var plan = StaticStore.GetPlan(PlanType.EnterpriseAnnually); var customerResult = Substitute.For>(); customerResult.IsSuccess().Returns(false); var braintreeGateway = sutProvider.GetDependency(); braintreeGateway.Customer.CreateAsync(default).ReturnsForAnyArgs(customerResult); var exception = await Assert.ThrowsAsync( () => sutProvider.Sut.PurchaseOrganizationAsync(organization, PaymentMethodType.PayPal, paymentToken, plan, 1, 1, false, taxInfo, false, 8, 8)); Assert.Equal("Failed to create PayPal customer record.", exception.Message); } [Theory, BitAutoData] public async Task PurchaseOrganizationAsync_PayPal_Declined(SutProvider sutProvider, Organization organization, string paymentToken, TaxInfo taxInfo) { var plans = StaticStore.GetPlan(PlanType.EnterpriseAnnually); paymentToken = "pm_" + paymentToken; var stripeAdapter = sutProvider.GetDependency(); stripeAdapter.CustomerCreateAsync(default).ReturnsForAnyArgs(new Stripe.Customer { Id = "C-1", }); stripeAdapter.SubscriptionCreateAsync(default).ReturnsForAnyArgs(new Stripe.Subscription { Id = "S-1", CurrentPeriodEnd = DateTime.Today.AddDays(10), Status = "incomplete", LatestInvoice = new Stripe.Invoice { PaymentIntent = new Stripe.PaymentIntent { Status = "requires_payment_method", }, }, }); var customer = Substitute.For(); customer.Id.ReturnsForAnyArgs("Braintree-Id"); customer.PaymentMethods.ReturnsForAnyArgs(new[] { Substitute.For() }); var customerResult = Substitute.For>(); customerResult.IsSuccess().Returns(true); customerResult.Target.ReturnsForAnyArgs(customer); var braintreeGateway = sutProvider.GetDependency(); braintreeGateway.Customer.CreateAsync(default).ReturnsForAnyArgs(customerResult); var exception = await Assert.ThrowsAsync( () => sutProvider.Sut.PurchaseOrganizationAsync(organization, PaymentMethodType.PayPal, paymentToken, plans, 0, 0, false, taxInfo)); Assert.Equal("Payment method was declined.", exception.Message); await stripeAdapter.Received(1).CustomerDeleteAsync("C-1"); await braintreeGateway.Customer.Received(1).DeleteAsync("Braintree-Id"); } [Theory, BitAutoData] public async Task UpgradeFreeOrganizationAsync_Success(SutProvider sutProvider, Organization organization, TaxInfo taxInfo) { organization.GatewaySubscriptionId = null; var stripeAdapter = sutProvider.GetDependency(); stripeAdapter.CustomerGetAsync(default).ReturnsForAnyArgs(new Stripe.Customer { Id = "C-1", Metadata = new Dictionary { { "btCustomerId", "B-123" }, } }); stripeAdapter.CustomerUpdateAsync(default).ReturnsForAnyArgs(new Stripe.Customer { Id = "C-1", Metadata = new Dictionary { { "btCustomerId", "B-123" }, } }); stripeAdapter.InvoiceUpcomingAsync(default).ReturnsForAnyArgs(new Stripe.Invoice { PaymentIntent = new Stripe.PaymentIntent { Status = "requires_payment_method", }, AmountDue = 0 }); stripeAdapter.SubscriptionCreateAsync(default).ReturnsForAnyArgs(new Stripe.Subscription { }); var plan = StaticStore.GetPlan(PlanType.EnterpriseAnnually); var upgrade = new OrganizationUpgrade() { AdditionalStorageGb = 0, AdditionalSeats = 0, PremiumAccessAddon = false, TaxInfo = taxInfo, AdditionalSmSeats = 0, AdditionalServiceAccounts = 0 }; var result = await sutProvider.Sut.UpgradeFreeOrganizationAsync(organization, plan, upgrade); Assert.Null(result); } [Theory, BitAutoData] public async Task UpgradeFreeOrganizationAsync_SM_Success(SutProvider sutProvider, Organization organization, TaxInfo taxInfo) { organization.GatewaySubscriptionId = null; var stripeAdapter = sutProvider.GetDependency(); stripeAdapter.CustomerGetAsync(default).ReturnsForAnyArgs(new Stripe.Customer { Id = "C-1", Metadata = new Dictionary { { "btCustomerId", "B-123" }, } }); stripeAdapter.CustomerUpdateAsync(default).ReturnsForAnyArgs(new Stripe.Customer { Id = "C-1", Metadata = new Dictionary { { "btCustomerId", "B-123" }, } }); stripeAdapter.InvoiceUpcomingAsync(default).ReturnsForAnyArgs(new Stripe.Invoice { PaymentIntent = new Stripe.PaymentIntent { Status = "requires_payment_method", }, AmountDue = 0 }); stripeAdapter.SubscriptionCreateAsync(default).ReturnsForAnyArgs(new Stripe.Subscription { }); var upgrade = new OrganizationUpgrade() { AdditionalStorageGb = 1, AdditionalSeats = 10, PremiumAccessAddon = false, TaxInfo = taxInfo, AdditionalSmSeats = 5, AdditionalServiceAccounts = 50 }; var plan = StaticStore.GetPlan(PlanType.EnterpriseAnnually); var result = await sutProvider.Sut.UpgradeFreeOrganizationAsync(organization, plan, upgrade); Assert.Null(result); } [Theory, BitAutoData] public async Task UpgradeFreeOrganizationAsync_WhenCustomerHasNoAddress_UpdatesCustomerAddressWithTaxInfo( SutProvider sutProvider, Organization organization, TaxInfo taxInfo) { organization.GatewaySubscriptionId = null; var stripeAdapter = sutProvider.GetDependency(); var featureService = sutProvider.GetDependency(); stripeAdapter.CustomerGetAsync(default).ReturnsForAnyArgs(new Stripe.Customer { Id = "C-1", Metadata = new Dictionary { { "btCustomerId", "B-123" }, } }); stripeAdapter.CustomerUpdateAsync(default).ReturnsForAnyArgs(new Stripe.Customer { Id = "C-1", Metadata = new Dictionary { { "btCustomerId", "B-123" }, } }); stripeAdapter.InvoiceUpcomingAsync(default).ReturnsForAnyArgs(new Stripe.Invoice { PaymentIntent = new Stripe.PaymentIntent { Status = "requires_payment_method", }, AmountDue = 0 }); stripeAdapter.SubscriptionCreateAsync(default).ReturnsForAnyArgs(new Stripe.Subscription { }); var upgrade = new OrganizationUpgrade() { AdditionalStorageGb = 1, AdditionalSeats = 10, PremiumAccessAddon = false, TaxInfo = taxInfo, AdditionalSmSeats = 5, AdditionalServiceAccounts = 50 }; var plan = StaticStore.GetPlan(PlanType.EnterpriseAnnually); _ = await sutProvider.Sut.UpgradeFreeOrganizationAsync(organization, plan, upgrade); await stripeAdapter.Received() .CustomerUpdateAsync(organization.GatewayCustomerId, Arg.Is(c => c.Address.Country == taxInfo.BillingAddressCountry && c.Address.PostalCode == taxInfo.BillingAddressPostalCode && c.Address.Line1 == taxInfo.BillingAddressLine1 && c.Address.Line2 == taxInfo.BillingAddressLine2 && c.Address.City == taxInfo.BillingAddressCity && c.Address.State == taxInfo.BillingAddressState)); } }