mirror of
https://github.com/bitwarden/server
synced 2025-12-28 14:13:48 +00:00
[AC 1409] Secrets Manager Subscription Stripe Integration (#3019)
* Adding the Secret manager to the Plan List * Adding the unit test for the StaticStoreTests class * Fix whitespace formatting * Fix whitespace formatting * Price update * Resolving the PR comments * Resolving PR comments * Fixing the whitespace * only password manager plans are return for now * format whitespace * Resolve the test issue * Fixing the failing test * Refactoring the Plan separation * add a unit test for SingleOrDefault * Fix the whitespace format * Separate the PM and SM plans * Fixing the whitespace * Remove unnecessary directive * Fix imports ordering * Fix imports ordering * Resolve imports ordering * Fixing imports ordering * Fix response model, add MaxProjects * Fix filename * Fix format * Fix: seat price should match annual/monthly * Fix service account annual pricing * Changes for secret manager signup and upgradeplan * Changes for secrets manager signup and upgrade * refactoring the code * Format whitespace * remove unnecessary using directive * Resolve the PR comment on Subscription creation * Resolve PR comment * Add password manager to the error message * Add UseSecretsManager to the event log * Resolve PR comment on plan validation * Resolving pr comments for service account count * Resolving pr comments for service account count * Resolve the pr comments * Remove the store procedure that is no-longer needed * Rename a property properly * Resolving the PR comment * Resolve PR comments * Resolving PR comments * Resolving the Pr comments * Resolving some PR comments * Resolving the PR comments * Resolving the build identity build * Add additional Validation * Resolve the Lint issues * remove unnecessary using directive * Remove the white spaces * Adding unit test for the stripe payment * Remove the incomplete test * Fixing the failing test * Fix the failing test * Fix the fail test on organization service * Fix the failing unit test * Fix the whitespace format * Fix the failing test * Fix the whitespace format * resolve pr comments * Fix the lint message * Resolve the PR comments * resolve pr comments * Resolve pr comments * Resolve the pr comments * remove unused code * Added for sm validation test * Fix the whitespace format issues --------- Co-authored-by: Thomas Rittson <trittson@bitwarden.com> Co-authored-by: Thomas Rittson <31796059+eliykat@users.noreply.github.com>
This commit is contained in:
@@ -13,6 +13,7 @@ using Bit.Core.Exceptions;
|
||||
using Bit.Core.Models.Business;
|
||||
using Bit.Core.Models.Data;
|
||||
using Bit.Core.Models.Data.Organizations.OrganizationUsers;
|
||||
using Bit.Core.Models.StaticStore;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Settings;
|
||||
@@ -22,6 +23,7 @@ using Bit.Core.Test.AutoFixture.PolicyFixtures;
|
||||
using Bit.Core.Tools.Enums;
|
||||
using Bit.Core.Tools.Models.Business;
|
||||
using Bit.Core.Tools.Services;
|
||||
using Bit.Core.Utilities;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using NSubstitute;
|
||||
@@ -174,6 +176,20 @@ public class OrganizationServiceTests
|
||||
Assert.Contains("already on this plan", exception.Message);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task UpgradePlan_SM_AlreadyInPlan_Throws(Organization organization, OrganizationUpgrade upgrade,
|
||||
SutProvider<OrganizationService> sutProvider)
|
||||
{
|
||||
upgrade.Plan = organization.PlanType;
|
||||
upgrade.UseSecretsManager = true;
|
||||
upgrade.AdditionalSmSeats = 10;
|
||||
upgrade.AdditionalServiceAccounts = 10;
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organization.Id).Returns(organization);
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.UpgradePlanAsync(organization.Id, upgrade));
|
||||
Assert.Contains("already on this plan", exception.Message);
|
||||
}
|
||||
|
||||
[Theory, PaidOrganizationCustomize(CheckedPlanType = PlanType.Free), BitAutoData]
|
||||
public async Task UpgradePlan_UpgradeFromPaidPlan_Throws(Organization organization, OrganizationUpgrade upgrade,
|
||||
SutProvider<OrganizationService> sutProvider)
|
||||
@@ -184,16 +200,147 @@ public class OrganizationServiceTests
|
||||
Assert.Contains("can only upgrade", exception.Message);
|
||||
}
|
||||
|
||||
[Theory, PaidOrganizationCustomize(CheckedPlanType = PlanType.Free), BitAutoData]
|
||||
public async Task UpgradePlan_SM_UpgradeFromPaidPlan_Throws(Organization organization, OrganizationUpgrade upgrade,
|
||||
SutProvider<OrganizationService> sutProvider)
|
||||
{
|
||||
upgrade.UseSecretsManager = true;
|
||||
upgrade.AdditionalSmSeats = 10;
|
||||
upgrade.AdditionalServiceAccounts = 10;
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organization.Id).Returns(organization);
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.UpgradePlanAsync(organization.Id, upgrade));
|
||||
Assert.Contains("can only upgrade", exception.Message);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[FreeOrganizationUpgradeCustomize, BitAutoData]
|
||||
public async Task UpgradePlan_Passes(Organization organization, OrganizationUpgrade upgrade,
|
||||
SutProvider<OrganizationService> sutProvider)
|
||||
{
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organization.Id).Returns(organization);
|
||||
upgrade.AdditionalSmSeats = 10;
|
||||
upgrade.AdditionalSeats = 10;
|
||||
await sutProvider.Sut.UpgradePlanAsync(organization.Id, upgrade);
|
||||
await sutProvider.GetDependency<IOrganizationRepository>().Received(1).ReplaceAsync(organization);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task SignUp_SM_Passes(OrganizationSignup signup, SutProvider<OrganizationService> sutProvider)
|
||||
{
|
||||
signup.AdditionalSmSeats = 10;
|
||||
signup.AdditionalSeats = 10;
|
||||
signup.Plan = PlanType.EnterpriseAnnually;
|
||||
signup.UseSecretsManager = true;
|
||||
signup.PaymentMethodType = PaymentMethodType.Card;
|
||||
signup.PremiumAccessAddon = false;
|
||||
|
||||
var purchaseOrganizationPlan = StaticStore.Plans.Where(x => x.Type == signup.Plan).ToList();
|
||||
|
||||
var result = await sutProvider.Sut.SignUpAsync(signup);
|
||||
|
||||
await sutProvider.GetDependency<IReferenceEventService>().Received(1)
|
||||
.RaiseEventAsync(Arg.Is<ReferenceEvent>(referenceEvent =>
|
||||
referenceEvent.Type == ReferenceEventType.Signup &&
|
||||
referenceEvent.PlanName == purchaseOrganizationPlan[0].Name &&
|
||||
referenceEvent.PlanType == purchaseOrganizationPlan[0].Type &&
|
||||
referenceEvent.Seats == result.Item1.Seats &&
|
||||
referenceEvent.SmSeats == result.Item1.SmSeats &&
|
||||
referenceEvent.ServiceAccounts == result.Item1.SmServiceAccounts &&
|
||||
referenceEvent.UseSecretsManager == result.Item1.UseSecretsManager &&
|
||||
referenceEvent.Storage == result.Item1.MaxStorageGb));
|
||||
|
||||
Assert.NotNull(result);
|
||||
Assert.NotNull(result.Item1);
|
||||
Assert.NotNull(result.Item2);
|
||||
Assert.IsType<Tuple<Organization, OrganizationUser>>(result);
|
||||
|
||||
await sutProvider.GetDependency<IPaymentService>().Received(1).PurchaseOrganizationAsync(
|
||||
Arg.Any<Organization>(),
|
||||
signup.PaymentMethodType.Value,
|
||||
signup.PaymentToken,
|
||||
Arg.Is<List<Plan>>(plan => plan.All(p => purchaseOrganizationPlan.Contains(p))),
|
||||
signup.AdditionalStorageGb,
|
||||
signup.AdditionalSeats,
|
||||
signup.PremiumAccessAddon,
|
||||
signup.TaxInfo,
|
||||
false,
|
||||
signup.AdditionalSmSeats.GetValueOrDefault(),
|
||||
signup.AdditionalServiceAccounts.GetValueOrDefault()
|
||||
);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task SignUpAsync_SecretManagerValidation_ShouldThrowException(OrganizationSignup signup, SutProvider<OrganizationService> sutProvider)
|
||||
{
|
||||
signup.AdditionalSmSeats = 10;
|
||||
signup.AdditionalSeats = 10;
|
||||
signup.Plan = PlanType.EnterpriseAnnually;
|
||||
signup.UseSecretsManager = true;
|
||||
signup.PaymentMethodType = PaymentMethodType.Card;
|
||||
signup.PremiumAccessAddon = false;
|
||||
signup.AdditionalServiceAccounts = 10;
|
||||
|
||||
var purchaseOrganizationPlan = StaticStore.Plans.Where(x => x.Type == signup.Plan && x.BitwardenProduct == BitwardenProductType.PasswordManager).ToList();
|
||||
var secretsManagerPlan = StaticStore.GetSecretsManagerPlan(PlanType.EnterpriseAnnually);
|
||||
secretsManagerPlan.HasAdditionalServiceAccountOption = false;
|
||||
purchaseOrganizationPlan.Add(secretsManagerPlan);
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.SignUpAsync(signup));
|
||||
Assert.Contains("Plan does not allow additional Service Accounts.", exception.Message);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task SignUpAsync_SMSeatsGreatThanPMSeat_ShouldThrowException(OrganizationSignup signup, SutProvider<OrganizationService> sutProvider)
|
||||
{
|
||||
signup.AdditionalSmSeats = 100;
|
||||
signup.AdditionalSeats = 10;
|
||||
signup.Plan = PlanType.EnterpriseAnnually;
|
||||
signup.UseSecretsManager = true;
|
||||
signup.PaymentMethodType = PaymentMethodType.Card;
|
||||
signup.PremiumAccessAddon = false;
|
||||
signup.AdditionalServiceAccounts = 10;
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.SignUpAsync(signup));
|
||||
Assert.Contains("You cannot have more Secrets Manager seats than Password Manager seats", exception.Message);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task SignUpAsync_InvalidateServiceAccount_ShouldThrowException(OrganizationSignup signup, SutProvider<OrganizationService> sutProvider)
|
||||
{
|
||||
signup.AdditionalSmSeats = 10;
|
||||
signup.AdditionalSeats = 10;
|
||||
signup.Plan = PlanType.EnterpriseAnnually;
|
||||
signup.UseSecretsManager = true;
|
||||
signup.PaymentMethodType = PaymentMethodType.Card;
|
||||
signup.PremiumAccessAddon = false;
|
||||
signup.AdditionalServiceAccounts = -10;
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.SignUpAsync(signup));
|
||||
Assert.Contains("You can't subtract Service Accounts!", exception.Message);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[FreeOrganizationUpgradeCustomize, BitAutoData]
|
||||
public async Task UpgradePlan_SM_Passes(Organization organization, OrganizationUpgrade upgrade,
|
||||
SutProvider<OrganizationService> sutProvider)
|
||||
{
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organization.Id).Returns(organization);
|
||||
upgrade.AdditionalSmSeats = 10;
|
||||
upgrade.AdditionalSeats = 10;
|
||||
var result = await sutProvider.Sut.UpgradePlanAsync(organization.Id, upgrade);
|
||||
await sutProvider.GetDependency<IOrganizationRepository>().Received(1).ReplaceAsync(organization);
|
||||
Assert.True(result.Item1);
|
||||
Assert.NotNull(result.Item2);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[OrganizationInviteCustomize(InviteeUserType = OrganizationUserType.User,
|
||||
InvitorUserType = OrganizationUserType.Owner), BitAutoData]
|
||||
@@ -1469,4 +1616,5 @@ public class OrganizationServiceTests
|
||||
|
||||
Assert.Equal(includeProvider, result);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ public class StripePaymentServiceTests
|
||||
public async void PurchaseOrganizationAsync_Invalid(PaymentMethodType paymentMethodType, SutProvider<StripePaymentService> sutProvider)
|
||||
{
|
||||
var exception = await Assert.ThrowsAsync<GatewayException>(
|
||||
() => sutProvider.Sut.PurchaseOrganizationAsync(null, paymentMethodType, null, null, 0, 0, false, null));
|
||||
() => 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);
|
||||
}
|
||||
@@ -39,7 +39,7 @@ public class StripePaymentServiceTests
|
||||
[Theory, BitAutoData]
|
||||
public async void PurchaseOrganizationAsync_Stripe_ProviderOrg_Coupon_Add(SutProvider<StripePaymentService> sutProvider, Organization organization, string paymentToken, TaxInfo taxInfo, bool provider = true)
|
||||
{
|
||||
var plan = StaticStore.PasswordManagerPlans.First(p => p.Type == PlanType.EnterpriseAnnually);
|
||||
var plans = StaticStore.Plans.Where(p => p.Type == PlanType.EnterpriseAnnually).ToList();
|
||||
|
||||
var stripeAdapter = sutProvider.GetDependency<IStripeAdapter>();
|
||||
stripeAdapter.CustomerCreateAsync(default).ReturnsForAnyArgs(new Stripe.Customer
|
||||
@@ -52,7 +52,7 @@ public class StripePaymentServiceTests
|
||||
CurrentPeriodEnd = DateTime.Today.AddDays(10),
|
||||
});
|
||||
|
||||
var result = await sutProvider.Sut.PurchaseOrganizationAsync(organization, PaymentMethodType.Card, paymentToken, plan, 0, 0, false, taxInfo, provider);
|
||||
var result = await sutProvider.Sut.PurchaseOrganizationAsync(organization, PaymentMethodType.Card, paymentToken, plans, 0, 0, false, taxInfo, provider);
|
||||
|
||||
Assert.Null(result);
|
||||
Assert.Equal(GatewayType.Stripe, organization.Gateway);
|
||||
@@ -86,10 +86,63 @@ public class StripePaymentServiceTests
|
||||
));
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async void PurchaseOrganizationAsync_SM_Stripe_ProviderOrg_Coupon_Add(SutProvider<StripePaymentService> sutProvider, Organization organization,
|
||||
string paymentToken, TaxInfo taxInfo, bool provider = true)
|
||||
{
|
||||
var plans = StaticStore.Plans.Where(p => p.Type == PlanType.EnterpriseAnnually).ToList();
|
||||
|
||||
var stripeAdapter = sutProvider.GetDependency<IStripeAdapter>();
|
||||
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 result = await sutProvider.Sut.PurchaseOrganizationAsync(organization, PaymentMethodType.Card, paymentToken, plans, 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<Stripe.CustomerCreateOptions>(c =>
|
||||
c.Description == organization.BusinessName &&
|
||||
c.Email == organization.BillingEmail &&
|
||||
c.Source == paymentToken &&
|
||||
c.PaymentMethod == null &&
|
||||
c.Coupon == "msp-discount-35" &&
|
||||
!c.Metadata.Any() &&
|
||||
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<Stripe.SubscriptionCreateOptions>(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 void PurchaseOrganizationAsync_Stripe(SutProvider<StripePaymentService> sutProvider, Organization organization, string paymentToken, TaxInfo taxInfo)
|
||||
{
|
||||
var plan = StaticStore.PasswordManagerPlans.First(p => p.Type == PlanType.EnterpriseAnnually);
|
||||
var plans = StaticStore.Plans.Where(p => p.Type == PlanType.EnterpriseAnnually).ToList();
|
||||
|
||||
var stripeAdapter = sutProvider.GetDependency<IStripeAdapter>();
|
||||
stripeAdapter.CustomerCreateAsync(default).ReturnsForAnyArgs(new Stripe.Customer
|
||||
@@ -102,7 +155,8 @@ public class StripePaymentServiceTests
|
||||
CurrentPeriodEnd = DateTime.Today.AddDays(10),
|
||||
});
|
||||
|
||||
var result = await sutProvider.Sut.PurchaseOrganizationAsync(organization, PaymentMethodType.Card, paymentToken, plan, 0, 0, false, taxInfo);
|
||||
var result = await sutProvider.Sut.PurchaseOrganizationAsync(organization, PaymentMethodType.Card, paymentToken, plans, 0, 0
|
||||
, false, taxInfo, false, 8, 10);
|
||||
|
||||
Assert.Null(result);
|
||||
Assert.Equal(GatewayType.Stripe, organization.Gateway);
|
||||
@@ -110,7 +164,6 @@ public class StripePaymentServiceTests
|
||||
Assert.Equal("S-1", organization.GatewaySubscriptionId);
|
||||
Assert.True(organization.Enabled);
|
||||
Assert.Equal(DateTime.Today.AddDays(10), organization.ExpirationDate);
|
||||
var res = organization.SubscriberName();
|
||||
await stripeAdapter.Received().CustomerCreateAsync(Arg.Is<Stripe.CustomerCreateOptions>(c =>
|
||||
c.Description == organization.BusinessName &&
|
||||
c.Email == organization.BillingEmail &&
|
||||
@@ -134,67 +187,14 @@ public class StripePaymentServiceTests
|
||||
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 void PurchaseOrganizationAsync_Stripe_PM(SutProvider<StripePaymentService> sutProvider, Organization organization, string paymentToken, TaxInfo taxInfo)
|
||||
{
|
||||
var plan = StaticStore.PasswordManagerPlans.First(p => p.Type == PlanType.EnterpriseAnnually);
|
||||
paymentToken = "pm_" + paymentToken;
|
||||
|
||||
var stripeAdapter = sutProvider.GetDependency<IStripeAdapter>();
|
||||
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 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<Stripe.CustomerCreateOptions>(c =>
|
||||
c.Description == organization.BusinessName &&
|
||||
c.Email == organization.BillingEmail &&
|
||||
c.Source == null &&
|
||||
c.PaymentMethod == paymentToken &&
|
||||
!c.Metadata.Any() &&
|
||||
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<Stripe.SubscriptionCreateOptions>(s =>
|
||||
s.Customer == "C-1" &&
|
||||
s.Expand[0] == "latest_invoice.payment_intent" &&
|
||||
s.Metadata[organization.GatewayIdField()] == organization.Id.ToString() &&
|
||||
s.Items.Count == 0
|
||||
s.Items.Count == 2
|
||||
));
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async void PurchaseOrganizationAsync_Stripe_TaxRate(SutProvider<StripePaymentService> sutProvider, Organization organization, string paymentToken, TaxInfo taxInfo)
|
||||
{
|
||||
var plan = StaticStore.PasswordManagerPlans.First(p => p.Type == PlanType.EnterpriseAnnually);
|
||||
var plans = StaticStore.Plans.Where(p => p.Type == PlanType.EnterpriseAnnually).ToList();
|
||||
|
||||
var stripeAdapter = sutProvider.GetDependency<IStripeAdapter>();
|
||||
stripeAdapter.CustomerCreateAsync(default).ReturnsForAnyArgs(new Stripe.Customer
|
||||
@@ -210,7 +210,37 @@ public class StripePaymentServiceTests
|
||||
t.Country == taxInfo.BillingAddressCountry && t.PostalCode == taxInfo.BillingAddressPostalCode))
|
||||
.Returns(new List<TaxRate> { new() { Id = "T-1" } });
|
||||
|
||||
var result = await sutProvider.Sut.PurchaseOrganizationAsync(organization, PaymentMethodType.Card, paymentToken, plan, 0, 0, false, taxInfo);
|
||||
var result = await sutProvider.Sut.PurchaseOrganizationAsync(organization, PaymentMethodType.Card, paymentToken, plans, 0, 0, false, taxInfo);
|
||||
|
||||
Assert.Null(result);
|
||||
|
||||
await stripeAdapter.Received().SubscriptionCreateAsync(Arg.Is<Stripe.SubscriptionCreateOptions>(s =>
|
||||
s.DefaultTaxRates.Count == 1 &&
|
||||
s.DefaultTaxRates[0] == "T-1"
|
||||
));
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async void PurchaseOrganizationAsync_Stripe_TaxRate_SM(SutProvider<StripePaymentService> sutProvider, Organization organization, string paymentToken, TaxInfo taxInfo)
|
||||
{
|
||||
var plans = StaticStore.Plans.Where(p => p.Type == PlanType.EnterpriseAnnually).ToList();
|
||||
|
||||
var stripeAdapter = sutProvider.GetDependency<IStripeAdapter>();
|
||||
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<ITaxRateRepository>().GetByLocationAsync(Arg.Is<TaxRate>(t =>
|
||||
t.Country == taxInfo.BillingAddressCountry && t.PostalCode == taxInfo.BillingAddressPostalCode))
|
||||
.Returns(new List<TaxRate> { new() { Id = "T-1" } });
|
||||
|
||||
var result = await sutProvider.Sut.PurchaseOrganizationAsync(organization, PaymentMethodType.Card, paymentToken, plans, 2, 2,
|
||||
false, taxInfo, false, 2, 2);
|
||||
|
||||
Assert.Null(result);
|
||||
|
||||
@@ -223,7 +253,7 @@ public class StripePaymentServiceTests
|
||||
[Theory, BitAutoData]
|
||||
public async void PurchaseOrganizationAsync_Stripe_Declined(SutProvider<StripePaymentService> sutProvider, Organization organization, string paymentToken, TaxInfo taxInfo)
|
||||
{
|
||||
var plan = StaticStore.PasswordManagerPlans.First(p => p.Type == PlanType.EnterpriseAnnually);
|
||||
var plan = StaticStore.Plans.Where(p => p.Type == PlanType.EnterpriseAnnually).ToList();
|
||||
paymentToken = "pm_" + paymentToken;
|
||||
|
||||
var stripeAdapter = sutProvider.GetDependency<IStripeAdapter>();
|
||||
@@ -253,10 +283,44 @@ public class StripePaymentServiceTests
|
||||
await stripeAdapter.Received(1).CustomerDeleteAsync("C-1");
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async void PurchaseOrganizationAsync_SM_Stripe_Declined(SutProvider<StripePaymentService> sutProvider, Organization organization, string paymentToken, TaxInfo taxInfo)
|
||||
{
|
||||
var plan = StaticStore.Plans.Where(p => p.Type == PlanType.EnterpriseAnnually).ToList();
|
||||
paymentToken = "pm_" + paymentToken;
|
||||
|
||||
var stripeAdapter = sutProvider.GetDependency<IStripeAdapter>();
|
||||
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<GatewayException>(
|
||||
() => 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 void PurchaseOrganizationAsync_Stripe_RequiresAction(SutProvider<StripePaymentService> sutProvider, Organization organization, string paymentToken, TaxInfo taxInfo)
|
||||
{
|
||||
var plan = StaticStore.PasswordManagerPlans.First(p => p.Type == PlanType.EnterpriseAnnually);
|
||||
var plans = StaticStore.Plans.Where(p => p.Type == PlanType.EnterpriseAnnually).ToList();
|
||||
|
||||
var stripeAdapter = sutProvider.GetDependency<IStripeAdapter>();
|
||||
stripeAdapter.CustomerCreateAsync(default).ReturnsForAnyArgs(new Stripe.Customer
|
||||
@@ -278,7 +342,39 @@ public class StripePaymentServiceTests
|
||||
},
|
||||
});
|
||||
|
||||
var result = await sutProvider.Sut.PurchaseOrganizationAsync(organization, PaymentMethodType.Card, paymentToken, plan, 0, 0, false, taxInfo);
|
||||
var result = await sutProvider.Sut.PurchaseOrganizationAsync(organization, PaymentMethodType.Card, paymentToken, plans, 0, 0, false, taxInfo);
|
||||
|
||||
Assert.Equal("clientSecret", result);
|
||||
Assert.False(organization.Enabled);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async void PurchaseOrganizationAsync_SM_Stripe_RequiresAction(SutProvider<StripePaymentService> sutProvider, Organization organization, string paymentToken, TaxInfo taxInfo)
|
||||
{
|
||||
var plans = StaticStore.Plans.Where(p => p.Type == PlanType.EnterpriseAnnually).ToList();
|
||||
|
||||
var stripeAdapter = sutProvider.GetDependency<IStripeAdapter>();
|
||||
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, plans,
|
||||
10, 10, false, taxInfo, false, 10, 10);
|
||||
|
||||
Assert.Equal("clientSecret", result);
|
||||
Assert.False(organization.Enabled);
|
||||
@@ -287,7 +383,7 @@ public class StripePaymentServiceTests
|
||||
[Theory, BitAutoData]
|
||||
public async void PurchaseOrganizationAsync_Paypal(SutProvider<StripePaymentService> sutProvider, Organization organization, string paymentToken, TaxInfo taxInfo)
|
||||
{
|
||||
var plan = StaticStore.PasswordManagerPlans.First(p => p.Type == PlanType.EnterpriseAnnually);
|
||||
var plans = StaticStore.Plans.Where(p => p.Type == PlanType.EnterpriseAnnually).ToList();
|
||||
|
||||
var stripeAdapter = sutProvider.GetDependency<IStripeAdapter>();
|
||||
stripeAdapter.CustomerCreateAsync(default).ReturnsForAnyArgs(new Stripe.Customer
|
||||
@@ -310,7 +406,7 @@ public class StripePaymentServiceTests
|
||||
var braintreeGateway = sutProvider.GetDependency<IBraintreeGateway>();
|
||||
braintreeGateway.Customer.CreateAsync(default).ReturnsForAnyArgs(customerResult);
|
||||
|
||||
var result = await sutProvider.Sut.PurchaseOrganizationAsync(organization, PaymentMethodType.PayPal, paymentToken, plan, 0, 0, false, taxInfo);
|
||||
var result = await sutProvider.Sut.PurchaseOrganizationAsync(organization, PaymentMethodType.PayPal, paymentToken, plans, 0, 0, false, taxInfo);
|
||||
|
||||
Assert.Null(result);
|
||||
Assert.Equal(GatewayType.Stripe, organization.Gateway);
|
||||
@@ -343,10 +439,70 @@ public class StripePaymentServiceTests
|
||||
));
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async void PurchaseOrganizationAsync_SM_Paypal(SutProvider<StripePaymentService> sutProvider, Organization organization, string paymentToken, TaxInfo taxInfo)
|
||||
{
|
||||
var plans = StaticStore.Plans.Where(p => p.Type == PlanType.EnterpriseAnnually).ToList();
|
||||
|
||||
var stripeAdapter = sutProvider.GetDependency<IStripeAdapter>();
|
||||
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>();
|
||||
customer.Id.ReturnsForAnyArgs("Braintree-Id");
|
||||
customer.PaymentMethods.ReturnsForAnyArgs(new[] { Substitute.For<PaymentMethod>() });
|
||||
var customerResult = Substitute.For<Result<Customer>>();
|
||||
customerResult.IsSuccess().Returns(true);
|
||||
customerResult.Target.ReturnsForAnyArgs(customer);
|
||||
|
||||
var braintreeGateway = sutProvider.GetDependency<IBraintreeGateway>();
|
||||
braintreeGateway.Customer.CreateAsync(default).ReturnsForAnyArgs(customerResult);
|
||||
|
||||
var result = await sutProvider.Sut.PurchaseOrganizationAsync(organization, PaymentMethodType.PayPal, paymentToken, plans,
|
||||
2, 10, false, taxInfo, false, 10, 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<Stripe.CustomerCreateOptions>(c =>
|
||||
c.Description == organization.BusinessName &&
|
||||
c.Email == organization.BillingEmail &&
|
||||
c.PaymentMethod == null &&
|
||||
c.Metadata.Count == 1 &&
|
||||
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<Stripe.SubscriptionCreateOptions>(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 void PurchaseOrganizationAsync_Paypal_FailedCreate(SutProvider<StripePaymentService> sutProvider, Organization organization, string paymentToken, TaxInfo taxInfo)
|
||||
{
|
||||
var plan = StaticStore.PasswordManagerPlans.First(p => p.Type == PlanType.EnterpriseAnnually);
|
||||
var plans = StaticStore.Plans.Where(p => p.Type == PlanType.EnterpriseAnnually).ToList();
|
||||
|
||||
var customerResult = Substitute.For<Result<Customer>>();
|
||||
customerResult.IsSuccess().Returns(false);
|
||||
@@ -355,7 +511,25 @@ public class StripePaymentServiceTests
|
||||
braintreeGateway.Customer.CreateAsync(default).ReturnsForAnyArgs(customerResult);
|
||||
|
||||
var exception = await Assert.ThrowsAsync<GatewayException>(
|
||||
() => sutProvider.Sut.PurchaseOrganizationAsync(organization, PaymentMethodType.PayPal, paymentToken, plan, 0, 0, false, taxInfo));
|
||||
() => sutProvider.Sut.PurchaseOrganizationAsync(organization, PaymentMethodType.PayPal, paymentToken, plans, 0, 0, false, taxInfo));
|
||||
|
||||
Assert.Equal("Failed to create PayPal customer record.", exception.Message);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async void PurchaseOrganizationAsync_SM_Paypal_FailedCreate(SutProvider<StripePaymentService> sutProvider, Organization organization, string paymentToken, TaxInfo taxInfo)
|
||||
{
|
||||
var plans = StaticStore.Plans.Where(p => p.Type == PlanType.EnterpriseAnnually).ToList();
|
||||
|
||||
var customerResult = Substitute.For<Result<Customer>>();
|
||||
customerResult.IsSuccess().Returns(false);
|
||||
|
||||
var braintreeGateway = sutProvider.GetDependency<IBraintreeGateway>();
|
||||
braintreeGateway.Customer.CreateAsync(default).ReturnsForAnyArgs(customerResult);
|
||||
|
||||
var exception = await Assert.ThrowsAsync<GatewayException>(
|
||||
() => sutProvider.Sut.PurchaseOrganizationAsync(organization, PaymentMethodType.PayPal, paymentToken, plans,
|
||||
1, 1, false, taxInfo, false, 8, 8));
|
||||
|
||||
Assert.Equal("Failed to create PayPal customer record.", exception.Message);
|
||||
}
|
||||
@@ -363,7 +537,7 @@ public class StripePaymentServiceTests
|
||||
[Theory, BitAutoData]
|
||||
public async void PurchaseOrganizationAsync_PayPal_Declined(SutProvider<StripePaymentService> sutProvider, Organization organization, string paymentToken, TaxInfo taxInfo)
|
||||
{
|
||||
var plan = StaticStore.PasswordManagerPlans.First(p => p.Type == PlanType.EnterpriseAnnually);
|
||||
var plans = StaticStore.Plans.Where(p => p.Type == PlanType.EnterpriseAnnually).ToList();
|
||||
paymentToken = "pm_" + paymentToken;
|
||||
|
||||
var stripeAdapter = sutProvider.GetDependency<IStripeAdapter>();
|
||||
@@ -396,7 +570,7 @@ public class StripePaymentServiceTests
|
||||
braintreeGateway.Customer.CreateAsync(default).ReturnsForAnyArgs(customerResult);
|
||||
|
||||
var exception = await Assert.ThrowsAsync<GatewayException>(
|
||||
() => sutProvider.Sut.PurchaseOrganizationAsync(organization, PaymentMethodType.PayPal, paymentToken, plan, 0, 0, false, taxInfo));
|
||||
() => sutProvider.Sut.PurchaseOrganizationAsync(organization, PaymentMethodType.PayPal, paymentToken, plans, 0, 0, false, taxInfo));
|
||||
|
||||
Assert.Equal("Payment method was declined.", exception.Message);
|
||||
|
||||
@@ -425,8 +599,36 @@ public class StripePaymentServiceTests
|
||||
});
|
||||
stripeAdapter.SubscriptionCreateAsync(default).ReturnsForAnyArgs(new Stripe.Subscription { });
|
||||
|
||||
var plan = StaticStore.PasswordManagerPlans.First(p => p.Type == PlanType.EnterpriseAnnually);
|
||||
var result = await sutProvider.Sut.UpgradeFreeOrganizationAsync(organization, plan, 0, 0, false, taxInfo);
|
||||
var plans = StaticStore.Plans.Where(p => p.Type == PlanType.EnterpriseAnnually).ToList();
|
||||
var result = await sutProvider.Sut.UpgradeFreeOrganizationAsync(organization, plans, 0, 0, false, taxInfo);
|
||||
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async void UpgradeFreeOrganizationAsync_SM_Success(SutProvider<StripePaymentService> sutProvider,
|
||||
Organization organization, TaxInfo taxInfo)
|
||||
{
|
||||
organization.GatewaySubscriptionId = null;
|
||||
var stripeAdapter = sutProvider.GetDependency<IStripeAdapter>();
|
||||
stripeAdapter.CustomerGetAsync(default).ReturnsForAnyArgs(new Stripe.Customer
|
||||
{
|
||||
Id = "C-1",
|
||||
Metadata = new Dictionary<string, string>
|
||||
{
|
||||
{ "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 plans = StaticStore.Plans.Where(p => p.Type == PlanType.EnterpriseAnnually).ToList();
|
||||
var result = await sutProvider.Sut.UpgradeFreeOrganizationAsync(organization, plans, 0, 0,
|
||||
false, taxInfo);
|
||||
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user