mirror of
https://github.com/bitwarden/server
synced 2025-12-06 00:03:34 +00:00
[PM-24350] fix tax calculation (#6251)
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
|
||||
using System.Globalization;
|
||||
using Bit.Commercial.Core.Billing.Providers.Models;
|
||||
using Bit.Core;
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.Entities.Provider;
|
||||
using Bit.Core.AdminConsole.Enums.Provider;
|
||||
@@ -282,7 +283,7 @@ public class ProviderBillingService(
|
||||
]
|
||||
};
|
||||
|
||||
if (providerCustomer.Address is not { Country: "US" })
|
||||
if (providerCustomer.Address is not { Country: Constants.CountryAbbreviations.UnitedStates })
|
||||
{
|
||||
customerCreateOptions.TaxExempt = StripeConstants.TaxExempt.Reverse;
|
||||
}
|
||||
@@ -525,7 +526,7 @@ public class ProviderBillingService(
|
||||
}
|
||||
};
|
||||
|
||||
if (taxInfo.BillingAddressCountry is not "US")
|
||||
if (taxInfo.BillingAddressCountry is not Constants.CountryAbbreviations.UnitedStates)
|
||||
{
|
||||
options.TaxExempt = StripeConstants.TaxExempt.Reverse;
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Text.Json.Serialization;
|
||||
using Bit.Core;
|
||||
using Bit.Core.Billing.Enums;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
@@ -139,7 +140,7 @@ public class OrganizationCreateRequestModel : IValidatableObject
|
||||
new string[] { nameof(BillingAddressCountry) });
|
||||
}
|
||||
|
||||
if (PlanType != PlanType.Free && BillingAddressCountry == "US" &&
|
||||
if (PlanType != PlanType.Free && BillingAddressCountry == Constants.CountryAbbreviations.UnitedStates &&
|
||||
string.IsNullOrWhiteSpace(BillingAddressPostalCode))
|
||||
{
|
||||
yield return new ValidationResult("Zip / postal code is required.",
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#nullable disable
|
||||
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Bit.Core;
|
||||
using Bit.Core.Settings;
|
||||
using Enums = Bit.Core.Enums;
|
||||
|
||||
@@ -35,7 +36,7 @@ public class PremiumRequestModel : IValidatableObject
|
||||
{
|
||||
yield return new ValidationResult("Payment token or license is required.");
|
||||
}
|
||||
if (Country == "US" && string.IsNullOrWhiteSpace(PostalCode))
|
||||
if (Country == Constants.CountryAbbreviations.UnitedStates && string.IsNullOrWhiteSpace(PostalCode))
|
||||
{
|
||||
yield return new ValidationResult("Zip / postal code is required.",
|
||||
new string[] { nameof(PostalCode) });
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#nullable disable
|
||||
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Bit.Core;
|
||||
|
||||
namespace Bit.Api.Models.Request.Accounts;
|
||||
|
||||
@@ -13,7 +14,7 @@ public class TaxInfoUpdateRequestModel : IValidatableObject
|
||||
|
||||
public virtual IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
|
||||
{
|
||||
if (Country == "US" && string.IsNullOrWhiteSpace(PostalCode))
|
||||
if (Country == Constants.CountryAbbreviations.UnitedStates && string.IsNullOrWhiteSpace(PostalCode))
|
||||
{
|
||||
yield return new ValidationResult("Zip / postal code is required.",
|
||||
new string[] { nameof(PostalCode) });
|
||||
|
||||
@@ -218,7 +218,7 @@ public class StripeEventService : IStripeEventService
|
||||
|
||||
private static string GetCustomerRegion(IDictionary<string, string> customerMetadata)
|
||||
{
|
||||
const string defaultRegion = "US";
|
||||
const string defaultRegion = Core.Constants.CountryAbbreviations.UnitedStates;
|
||||
|
||||
if (customerMetadata is null)
|
||||
{
|
||||
|
||||
@@ -203,7 +203,7 @@ public class UpcomingInvoiceHandler(
|
||||
{
|
||||
var nonUSBusinessUse =
|
||||
organization.PlanType.GetProductTier() != ProductTierType.Families &&
|
||||
subscription.Customer.Address.Country != "US";
|
||||
subscription.Customer.Address.Country != Core.Constants.CountryAbbreviations.UnitedStates;
|
||||
|
||||
if (nonUSBusinessUse && subscription.Customer.TaxExempt != StripeConstants.TaxExempt.Reverse)
|
||||
{
|
||||
@@ -248,7 +248,7 @@ public class UpcomingInvoiceHandler(
|
||||
Subscription subscription,
|
||||
string eventId)
|
||||
{
|
||||
if (subscription.Customer.Address.Country != "US" &&
|
||||
if (subscription.Customer.Address.Country != Core.Constants.CountryAbbreviations.UnitedStates &&
|
||||
subscription.Customer.TaxExempt != StripeConstants.TaxExempt.Reverse)
|
||||
{
|
||||
try
|
||||
|
||||
@@ -22,6 +22,19 @@ public static class BillingExtensions
|
||||
_ => throw new BillingException($"PlanType {planType} could not be matched to a ProductTierType")
|
||||
};
|
||||
|
||||
public static bool IsBusinessProductTierType(this PlanType planType)
|
||||
=> IsBusinessProductTierType(planType.GetProductTier());
|
||||
|
||||
public static bool IsBusinessProductTierType(this ProductTierType productTierType)
|
||||
=> productTierType switch
|
||||
{
|
||||
ProductTierType.Free => false,
|
||||
ProductTierType.Families => false,
|
||||
ProductTierType.Enterprise => true,
|
||||
ProductTierType.Teams => true,
|
||||
ProductTierType.TeamsStarter => true
|
||||
};
|
||||
|
||||
public static bool IsBillable(this Provider provider) =>
|
||||
provider is
|
||||
{
|
||||
|
||||
@@ -25,9 +25,6 @@ public static class ServiceCollectionExtensions
|
||||
services.AddTransient<IPremiumUserBillingService, PremiumUserBillingService>();
|
||||
services.AddTransient<ISetupIntentCache, SetupIntentDistributedCache>();
|
||||
services.AddTransient<ISubscriberService, SubscriberService>();
|
||||
services.AddKeyedTransient<IAutomaticTaxStrategy, PersonalUseAutomaticTaxStrategy>(AutomaticTaxFactory.PersonalUse);
|
||||
services.AddKeyedTransient<IAutomaticTaxStrategy, BusinessUseAutomaticTaxStrategy>(AutomaticTaxFactory.BusinessUse);
|
||||
services.AddTransient<IAutomaticTaxFactory, AutomaticTaxFactory>();
|
||||
services.AddLicenseServices();
|
||||
services.AddPricingClient();
|
||||
services.AddTransient<IPreviewTaxAmountCommand, PreviewTaxAmountCommand>();
|
||||
|
||||
@@ -275,7 +275,7 @@ public class OrganizationBillingService(
|
||||
|
||||
|
||||
if (planType.GetProductTier() is not ProductTierType.Free and not ProductTierType.Families &&
|
||||
customerSetup.TaxInformation.Country != "US")
|
||||
customerSetup.TaxInformation.Country != Core.Constants.CountryAbbreviations.UnitedStates)
|
||||
{
|
||||
customerCreateOptions.TaxExempt = StripeConstants.TaxExempt.Reverse;
|
||||
}
|
||||
@@ -514,14 +514,14 @@ public class OrganizationBillingService(
|
||||
|
||||
customer = customer switch
|
||||
{
|
||||
{ Address.Country: not "US", TaxExempt: not StripeConstants.TaxExempt.Reverse } => await
|
||||
{ Address.Country: not Core.Constants.CountryAbbreviations.UnitedStates, TaxExempt: not StripeConstants.TaxExempt.Reverse } => await
|
||||
stripeAdapter.CustomerUpdateAsync(customer.Id,
|
||||
new CustomerUpdateOptions
|
||||
{
|
||||
Expand = expansions,
|
||||
TaxExempt = StripeConstants.TaxExempt.Reverse
|
||||
}),
|
||||
{ Address.Country: "US", TaxExempt: StripeConstants.TaxExempt.Reverse } => await
|
||||
{ Address.Country: Core.Constants.CountryAbbreviations.UnitedStates, TaxExempt: StripeConstants.TaxExempt.Reverse } => await
|
||||
stripeAdapter.CustomerUpdateAsync(customer.Id,
|
||||
new CustomerUpdateOptions
|
||||
{
|
||||
|
||||
@@ -84,7 +84,7 @@ public class UpdateBillingAddressCommand(
|
||||
State = billingAddress.State
|
||||
},
|
||||
Expand = ["subscriptions", "tax_ids"],
|
||||
TaxExempt = billingAddress.Country != "US"
|
||||
TaxExempt = billingAddress.Country != Core.Constants.CountryAbbreviations.UnitedStates
|
||||
? StripeConstants.TaxExempt.Reverse
|
||||
: StripeConstants.TaxExempt.None
|
||||
});
|
||||
|
||||
@@ -801,15 +801,13 @@ public class SubscriberService(
|
||||
_ => false
|
||||
};
|
||||
|
||||
|
||||
|
||||
if (isBusinessUseSubscriber)
|
||||
{
|
||||
switch (customer)
|
||||
{
|
||||
case
|
||||
{
|
||||
Address.Country: not "US",
|
||||
Address.Country: not Core.Constants.CountryAbbreviations.UnitedStates,
|
||||
TaxExempt: not TaxExempt.Reverse
|
||||
}:
|
||||
await stripeAdapter.CustomerUpdateAsync(customer.Id,
|
||||
@@ -817,7 +815,7 @@ public class SubscriberService(
|
||||
break;
|
||||
case
|
||||
{
|
||||
Address.Country: "US",
|
||||
Address.Country: Core.Constants.CountryAbbreviations.UnitedStates,
|
||||
TaxExempt: TaxExempt.Reverse
|
||||
}:
|
||||
await stripeAdapter.CustomerUpdateAsync(customer.Id,
|
||||
@@ -840,8 +838,8 @@ public class SubscriberService(
|
||||
{
|
||||
User => true,
|
||||
Organization organization => organization.PlanType.GetProductTier() == ProductTierType.Families ||
|
||||
customer.Address.Country == "US" || (customer.TaxIds?.Any() ?? false),
|
||||
Provider => customer.Address.Country == "US" || (customer.TaxIds?.Any() ?? false),
|
||||
customer.Address.Country == Core.Constants.CountryAbbreviations.UnitedStates || (customer.TaxIds?.Any() ?? false),
|
||||
Provider => customer.Address.Country == Core.Constants.CountryAbbreviations.UnitedStates || (customer.TaxIds?.Any() ?? false),
|
||||
_ => false
|
||||
};
|
||||
|
||||
|
||||
@@ -95,17 +95,11 @@ public class PreviewTaxAmountCommand(
|
||||
}
|
||||
}
|
||||
|
||||
if (planType.GetProductTier() == ProductTierType.Families)
|
||||
options.AutomaticTax = new InvoiceAutomaticTaxOptions { Enabled = true };
|
||||
if (parameters.PlanType.IsBusinessProductTierType() &&
|
||||
parameters.TaxInformation.Country != Core.Constants.CountryAbbreviations.UnitedStates)
|
||||
{
|
||||
options.AutomaticTax = new InvoiceAutomaticTaxOptions { Enabled = true };
|
||||
}
|
||||
else
|
||||
{
|
||||
options.AutomaticTax = new InvoiceAutomaticTaxOptions
|
||||
{
|
||||
Enabled = options.CustomerDetails.Address.Country == "US" ||
|
||||
options.CustomerDetails.TaxIds is [_, ..]
|
||||
};
|
||||
options.CustomerDetails.TaxExempt = StripeConstants.TaxExempt.Reverse;
|
||||
}
|
||||
|
||||
var invoice = await stripeAdapter.InvoiceCreatePreviewAsync(options);
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
using Bit.Core.Billing.Tax.Models;
|
||||
|
||||
namespace Bit.Core.Billing.Tax.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Responsible for defining the correct automatic tax strategy for either personal use of business use.
|
||||
/// </summary>
|
||||
public interface IAutomaticTaxFactory
|
||||
{
|
||||
Task<IAutomaticTaxStrategy> CreateAsync(AutomaticTaxFactoryParameters parameters);
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
#nullable enable
|
||||
using Stripe;
|
||||
|
||||
namespace Bit.Core.Billing.Tax.Services;
|
||||
|
||||
public interface IAutomaticTaxStrategy
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="subscription"></param>
|
||||
/// <returns>
|
||||
/// Returns <see cref="SubscriptionUpdateOptions" /> if changes are to be applied to the subscription, returns null
|
||||
/// otherwise.
|
||||
/// </returns>
|
||||
SubscriptionUpdateOptions? GetUpdateOptions(Subscription subscription);
|
||||
|
||||
/// <summary>
|
||||
/// Modifies an existing <see cref="SubscriptionCreateOptions" /> object with the automatic tax flag set correctly.
|
||||
/// </summary>
|
||||
/// <param name="options"></param>
|
||||
/// <param name="customer"></param>
|
||||
void SetCreateOptions(SubscriptionCreateOptions options, Customer customer);
|
||||
|
||||
/// <summary>
|
||||
/// Modifies an existing <see cref="SubscriptionUpdateOptions" /> object with the automatic tax flag set correctly.
|
||||
/// </summary>
|
||||
/// <param name="options"></param>
|
||||
/// <param name="subscription"></param>
|
||||
void SetUpdateOptions(SubscriptionUpdateOptions options, Subscription subscription);
|
||||
|
||||
void SetInvoiceCreatePreviewOptions(InvoiceCreatePreviewOptions options);
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
#nullable enable
|
||||
using Bit.Core.Billing.Enums;
|
||||
using Bit.Core.Billing.Pricing;
|
||||
using Bit.Core.Billing.Tax.Models;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Services;
|
||||
|
||||
namespace Bit.Core.Billing.Tax.Services.Implementations;
|
||||
|
||||
public class AutomaticTaxFactory(
|
||||
IFeatureService featureService,
|
||||
IPricingClient pricingClient) : IAutomaticTaxFactory
|
||||
{
|
||||
public const string BusinessUse = "business-use";
|
||||
public const string PersonalUse = "personal-use";
|
||||
|
||||
private readonly Lazy<Task<IEnumerable<string>>> _personalUsePlansTask = new(async () =>
|
||||
{
|
||||
var plans = await Task.WhenAll(
|
||||
pricingClient.GetPlanOrThrow(PlanType.FamiliesAnnually2019),
|
||||
pricingClient.GetPlanOrThrow(PlanType.FamiliesAnnually));
|
||||
|
||||
return plans.Select(plan => plan.PasswordManager.StripePlanId);
|
||||
});
|
||||
|
||||
public async Task<IAutomaticTaxStrategy> CreateAsync(AutomaticTaxFactoryParameters parameters)
|
||||
{
|
||||
if (parameters.Subscriber is User)
|
||||
{
|
||||
return new PersonalUseAutomaticTaxStrategy(featureService);
|
||||
}
|
||||
|
||||
if (parameters.PlanType.HasValue)
|
||||
{
|
||||
var plan = await pricingClient.GetPlanOrThrow(parameters.PlanType.Value);
|
||||
return plan.CanBeUsedByBusiness
|
||||
? new BusinessUseAutomaticTaxStrategy(featureService)
|
||||
: new PersonalUseAutomaticTaxStrategy(featureService);
|
||||
}
|
||||
|
||||
var personalUsePlans = await _personalUsePlansTask.Value;
|
||||
|
||||
if (parameters.Prices != null && parameters.Prices.Any(x => personalUsePlans.Any(y => y == x)))
|
||||
{
|
||||
return new PersonalUseAutomaticTaxStrategy(featureService);
|
||||
}
|
||||
|
||||
return new BusinessUseAutomaticTaxStrategy(featureService);
|
||||
}
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
#nullable enable
|
||||
using Bit.Core.Billing.Extensions;
|
||||
using Bit.Core.Services;
|
||||
using Stripe;
|
||||
|
||||
namespace Bit.Core.Billing.Tax.Services.Implementations;
|
||||
|
||||
public class BusinessUseAutomaticTaxStrategy(IFeatureService featureService) : IAutomaticTaxStrategy
|
||||
{
|
||||
public SubscriptionUpdateOptions? GetUpdateOptions(Subscription subscription)
|
||||
{
|
||||
if (!featureService.IsEnabled(FeatureFlagKeys.PM19422_AllowAutomaticTaxUpdates))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var shouldBeEnabled = ShouldBeEnabled(subscription.Customer);
|
||||
if (subscription.AutomaticTax.Enabled == shouldBeEnabled)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var options = new SubscriptionUpdateOptions
|
||||
{
|
||||
AutomaticTax = new SubscriptionAutomaticTaxOptions
|
||||
{
|
||||
Enabled = shouldBeEnabled
|
||||
},
|
||||
DefaultTaxRates = []
|
||||
};
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
public void SetCreateOptions(SubscriptionCreateOptions options, Customer customer)
|
||||
{
|
||||
options.AutomaticTax = new SubscriptionAutomaticTaxOptions
|
||||
{
|
||||
Enabled = ShouldBeEnabled(customer)
|
||||
};
|
||||
}
|
||||
|
||||
public void SetUpdateOptions(SubscriptionUpdateOptions options, Subscription subscription)
|
||||
{
|
||||
if (!featureService.IsEnabled(FeatureFlagKeys.PM19422_AllowAutomaticTaxUpdates))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var shouldBeEnabled = ShouldBeEnabled(subscription.Customer);
|
||||
|
||||
if (subscription.AutomaticTax.Enabled == shouldBeEnabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
options.AutomaticTax = new SubscriptionAutomaticTaxOptions
|
||||
{
|
||||
Enabled = shouldBeEnabled
|
||||
};
|
||||
options.DefaultTaxRates = [];
|
||||
}
|
||||
|
||||
public void SetInvoiceCreatePreviewOptions(InvoiceCreatePreviewOptions options)
|
||||
{
|
||||
options.AutomaticTax ??= new InvoiceAutomaticTaxOptions();
|
||||
|
||||
if (options.CustomerDetails.Address.Country == "US")
|
||||
{
|
||||
options.AutomaticTax.Enabled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
options.AutomaticTax.Enabled = options.CustomerDetails.TaxIds != null && options.CustomerDetails.TaxIds.Any();
|
||||
}
|
||||
|
||||
private bool ShouldBeEnabled(Customer customer)
|
||||
{
|
||||
if (!customer.HasRecognizedTaxLocation())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (customer.Address.Country == "US")
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (customer.TaxIds == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(customer.TaxIds), "`customer.tax_ids` must be expanded.");
|
||||
}
|
||||
|
||||
return customer.TaxIds.Any();
|
||||
}
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
#nullable enable
|
||||
using Bit.Core.Billing.Extensions;
|
||||
using Bit.Core.Services;
|
||||
using Stripe;
|
||||
|
||||
namespace Bit.Core.Billing.Tax.Services.Implementations;
|
||||
|
||||
public class PersonalUseAutomaticTaxStrategy(IFeatureService featureService) : IAutomaticTaxStrategy
|
||||
{
|
||||
public void SetCreateOptions(SubscriptionCreateOptions options, Customer customer)
|
||||
{
|
||||
options.AutomaticTax = new SubscriptionAutomaticTaxOptions
|
||||
{
|
||||
Enabled = ShouldBeEnabled(customer)
|
||||
};
|
||||
}
|
||||
|
||||
public void SetUpdateOptions(SubscriptionUpdateOptions options, Subscription subscription)
|
||||
{
|
||||
if (!featureService.IsEnabled(FeatureFlagKeys.PM19422_AllowAutomaticTaxUpdates))
|
||||
{
|
||||
return;
|
||||
}
|
||||
options.AutomaticTax = new SubscriptionAutomaticTaxOptions
|
||||
{
|
||||
Enabled = ShouldBeEnabled(subscription.Customer)
|
||||
};
|
||||
options.DefaultTaxRates = [];
|
||||
}
|
||||
|
||||
public SubscriptionUpdateOptions? GetUpdateOptions(Subscription subscription)
|
||||
{
|
||||
if (!featureService.IsEnabled(FeatureFlagKeys.PM19422_AllowAutomaticTaxUpdates))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (subscription.AutomaticTax.Enabled == ShouldBeEnabled(subscription.Customer))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var options = new SubscriptionUpdateOptions
|
||||
{
|
||||
AutomaticTax = new SubscriptionAutomaticTaxOptions
|
||||
{
|
||||
Enabled = ShouldBeEnabled(subscription.Customer),
|
||||
},
|
||||
DefaultTaxRates = []
|
||||
};
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
public void SetInvoiceCreatePreviewOptions(InvoiceCreatePreviewOptions options)
|
||||
{
|
||||
options.AutomaticTax = new InvoiceAutomaticTaxOptions { Enabled = true };
|
||||
}
|
||||
|
||||
private static bool ShouldBeEnabled(Customer customer)
|
||||
{
|
||||
return customer.HasRecognizedTaxLocation();
|
||||
}
|
||||
}
|
||||
@@ -52,6 +52,19 @@ public static class Constants
|
||||
/// regardless of whether there is a proration or not.
|
||||
/// </summary>
|
||||
public const string AlwaysInvoice = "always_invoice";
|
||||
|
||||
/// <summary>
|
||||
/// Used primarily to determine whether a customer's business is inside or outside the United States
|
||||
/// for billing purposes.
|
||||
/// </summary>
|
||||
public static class CountryAbbreviations
|
||||
{
|
||||
/// <summary>
|
||||
/// Abbreviation for The United States.
|
||||
/// This value must match what Stripe uses for the `Country` field value for the United States.
|
||||
/// </summary>
|
||||
public const string UnitedStates = "US";
|
||||
}
|
||||
}
|
||||
|
||||
public static class AuthConstants
|
||||
|
||||
@@ -13,5 +13,5 @@ public class TaxInfo
|
||||
public string BillingAddressCity { get; set; }
|
||||
public string BillingAddressState { get; set; }
|
||||
public string BillingAddressPostalCode { get; set; }
|
||||
public string BillingAddressCountry { get; set; } = "US";
|
||||
public string BillingAddressCountry { get; set; } = Constants.CountryAbbreviations.UnitedStates;
|
||||
}
|
||||
|
||||
@@ -9,11 +9,9 @@ using Bit.Core.Billing.Extensions;
|
||||
using Bit.Core.Billing.Models;
|
||||
using Bit.Core.Billing.Organizations.Models;
|
||||
using Bit.Core.Billing.Pricing;
|
||||
using Bit.Core.Billing.Tax.Models;
|
||||
using Bit.Core.Billing.Tax.Requests;
|
||||
using Bit.Core.Billing.Tax.Responses;
|
||||
using Bit.Core.Billing.Tax.Services;
|
||||
using Bit.Core.Billing.Tax.Services.Implementations;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
@@ -21,7 +19,6 @@ using Bit.Core.Models.BitStripe;
|
||||
using Bit.Core.Models.Business;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Settings;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Stripe;
|
||||
using PaymentMethod = Stripe.PaymentMethod;
|
||||
@@ -41,8 +38,6 @@ public class StripePaymentService : IPaymentService
|
||||
private readonly IFeatureService _featureService;
|
||||
private readonly ITaxService _taxService;
|
||||
private readonly IPricingClient _pricingClient;
|
||||
private readonly IAutomaticTaxFactory _automaticTaxFactory;
|
||||
private readonly IAutomaticTaxStrategy _personalUseTaxStrategy;
|
||||
|
||||
public StripePaymentService(
|
||||
ITransactionRepository transactionRepository,
|
||||
@@ -52,9 +47,7 @@ public class StripePaymentService : IPaymentService
|
||||
IGlobalSettings globalSettings,
|
||||
IFeatureService featureService,
|
||||
ITaxService taxService,
|
||||
IPricingClient pricingClient,
|
||||
IAutomaticTaxFactory automaticTaxFactory,
|
||||
[FromKeyedServices(AutomaticTaxFactory.PersonalUse)] IAutomaticTaxStrategy personalUseTaxStrategy)
|
||||
IPricingClient pricingClient)
|
||||
{
|
||||
_transactionRepository = transactionRepository;
|
||||
_logger = logger;
|
||||
@@ -64,8 +57,6 @@ public class StripePaymentService : IPaymentService
|
||||
_featureService = featureService;
|
||||
_taxService = taxService;
|
||||
_pricingClient = pricingClient;
|
||||
_automaticTaxFactory = automaticTaxFactory;
|
||||
_personalUseTaxStrategy = personalUseTaxStrategy;
|
||||
}
|
||||
|
||||
private async Task ChangeOrganizationSponsorship(
|
||||
@@ -137,7 +128,7 @@ public class StripePaymentService : IPaymentService
|
||||
{
|
||||
if (sub.Customer is
|
||||
{
|
||||
Address.Country: not "US",
|
||||
Address.Country: not Constants.CountryAbbreviations.UnitedStates,
|
||||
TaxExempt: not StripeConstants.TaxExempt.Reverse
|
||||
})
|
||||
{
|
||||
@@ -987,8 +978,6 @@ public class StripePaymentService : IPaymentService
|
||||
}
|
||||
}
|
||||
|
||||
_personalUseTaxStrategy.SetInvoiceCreatePreviewOptions(options);
|
||||
|
||||
try
|
||||
{
|
||||
var invoice = await _stripeAdapter.InvoiceCreatePreviewAsync(options);
|
||||
@@ -1152,9 +1141,12 @@ public class StripePaymentService : IPaymentService
|
||||
}
|
||||
}
|
||||
|
||||
var automaticTaxFactoryParameters = new AutomaticTaxFactoryParameters(parameters.PasswordManager.Plan);
|
||||
var automaticTaxStrategy = await _automaticTaxFactory.CreateAsync(automaticTaxFactoryParameters);
|
||||
automaticTaxStrategy.SetInvoiceCreatePreviewOptions(options);
|
||||
options.AutomaticTax = new InvoiceAutomaticTaxOptions { Enabled = true };
|
||||
if (parameters.PasswordManager.Plan.IsBusinessProductTierType() &&
|
||||
parameters.TaxInformation.Country != Constants.CountryAbbreviations.UnitedStates)
|
||||
{
|
||||
options.CustomerDetails.TaxExempt = StripeConstants.TaxExempt.Reverse;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
|
||||
@@ -181,7 +181,7 @@ public class PreviewTaxAmountCommandTests
|
||||
options.SubscriptionDetails.Items.Count == 1 &&
|
||||
options.SubscriptionDetails.Items[0].Price == plan.PasswordManager.StripeSeatPlanId &&
|
||||
options.SubscriptionDetails.Items[0].Quantity == 1 &&
|
||||
options.AutomaticTax.Enabled == false
|
||||
options.AutomaticTax.Enabled == true
|
||||
))
|
||||
.Returns(expectedInvoice);
|
||||
|
||||
@@ -273,4 +273,269 @@ public class PreviewTaxAmountCommandTests
|
||||
var badRequest = result.AsT1;
|
||||
Assert.Equal("We couldn't find a corresponding tax ID type for the tax ID you provided. Please try again or contact support for assistance.", badRequest.Response);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Run_USBased_PersonalUse_SetsAutomaticTaxEnabled()
|
||||
{
|
||||
// Arrange
|
||||
var parameters = new OrganizationTrialParameters
|
||||
{
|
||||
PlanType = PlanType.FamiliesAnnually,
|
||||
ProductType = ProductType.PasswordManager,
|
||||
TaxInformation = new TaxInformationDTO
|
||||
{
|
||||
Country = "US",
|
||||
PostalCode = "12345"
|
||||
}
|
||||
};
|
||||
|
||||
var plan = StaticStore.GetPlan(parameters.PlanType);
|
||||
|
||||
_pricingClient.GetPlanOrThrow(parameters.PlanType).Returns(plan);
|
||||
|
||||
var expectedInvoice = new Invoice { Tax = 1000 }; // $10.00 in cents
|
||||
_stripeAdapter.InvoiceCreatePreviewAsync(Arg.Any<InvoiceCreatePreviewOptions>())
|
||||
.Returns(expectedInvoice);
|
||||
|
||||
// Act
|
||||
var result = await _command.Run(parameters);
|
||||
|
||||
// Assert
|
||||
await _stripeAdapter.Received(1).InvoiceCreatePreviewAsync(Arg.Is<InvoiceCreatePreviewOptions>(options =>
|
||||
options.AutomaticTax.Enabled == true
|
||||
));
|
||||
Assert.True(result.IsT0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Run_USBased_BusinessUse_SetsAutomaticTaxEnabled()
|
||||
{
|
||||
// Arrange
|
||||
var parameters = new OrganizationTrialParameters
|
||||
{
|
||||
PlanType = PlanType.EnterpriseAnnually,
|
||||
ProductType = ProductType.PasswordManager,
|
||||
TaxInformation = new TaxInformationDTO
|
||||
{
|
||||
Country = "US",
|
||||
PostalCode = "12345"
|
||||
}
|
||||
};
|
||||
|
||||
var plan = StaticStore.GetPlan(parameters.PlanType);
|
||||
|
||||
_pricingClient.GetPlanOrThrow(parameters.PlanType).Returns(plan);
|
||||
|
||||
var expectedInvoice = new Invoice { Tax = 1000 }; // $10.00 in cents
|
||||
_stripeAdapter.InvoiceCreatePreviewAsync(Arg.Any<InvoiceCreatePreviewOptions>())
|
||||
.Returns(expectedInvoice);
|
||||
|
||||
// Act
|
||||
var result = await _command.Run(parameters);
|
||||
|
||||
// Assert
|
||||
await _stripeAdapter.Received(1).InvoiceCreatePreviewAsync(Arg.Is<InvoiceCreatePreviewOptions>(options =>
|
||||
options.AutomaticTax.Enabled == true
|
||||
));
|
||||
Assert.True(result.IsT0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Run_NonUSBased_PersonalUse_SetsAutomaticTaxEnabled()
|
||||
{
|
||||
// Arrange
|
||||
var parameters = new OrganizationTrialParameters
|
||||
{
|
||||
PlanType = PlanType.FamiliesAnnually,
|
||||
ProductType = ProductType.PasswordManager,
|
||||
TaxInformation = new TaxInformationDTO
|
||||
{
|
||||
Country = "CA",
|
||||
PostalCode = "12345"
|
||||
}
|
||||
};
|
||||
|
||||
var plan = StaticStore.GetPlan(parameters.PlanType);
|
||||
|
||||
_pricingClient.GetPlanOrThrow(parameters.PlanType).Returns(plan);
|
||||
|
||||
var expectedInvoice = new Invoice { Tax = 1000 }; // $10.00 in cents
|
||||
_stripeAdapter.InvoiceCreatePreviewAsync(Arg.Any<InvoiceCreatePreviewOptions>())
|
||||
.Returns(expectedInvoice);
|
||||
|
||||
// Act
|
||||
var result = await _command.Run(parameters);
|
||||
|
||||
// Assert
|
||||
await _stripeAdapter.Received(1).InvoiceCreatePreviewAsync(Arg.Is<InvoiceCreatePreviewOptions>(options =>
|
||||
options.AutomaticTax.Enabled == true
|
||||
));
|
||||
Assert.True(result.IsT0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Run_NonUSBased_BusinessUse_SetsAutomaticTaxEnabled()
|
||||
{
|
||||
// Arrange
|
||||
var parameters = new OrganizationTrialParameters
|
||||
{
|
||||
PlanType = PlanType.EnterpriseAnnually,
|
||||
ProductType = ProductType.PasswordManager,
|
||||
TaxInformation = new TaxInformationDTO
|
||||
{
|
||||
Country = "CA",
|
||||
PostalCode = "12345"
|
||||
}
|
||||
};
|
||||
|
||||
var plan = StaticStore.GetPlan(parameters.PlanType);
|
||||
|
||||
_pricingClient.GetPlanOrThrow(parameters.PlanType).Returns(plan);
|
||||
|
||||
var expectedInvoice = new Invoice { Tax = 1000 }; // $10.00 in cents
|
||||
_stripeAdapter.InvoiceCreatePreviewAsync(Arg.Any<InvoiceCreatePreviewOptions>())
|
||||
.Returns(expectedInvoice);
|
||||
|
||||
// Act
|
||||
var result = await _command.Run(parameters);
|
||||
|
||||
// Assert
|
||||
await _stripeAdapter.Received(1).InvoiceCreatePreviewAsync(Arg.Is<InvoiceCreatePreviewOptions>(options =>
|
||||
options.AutomaticTax.Enabled == true
|
||||
));
|
||||
Assert.True(result.IsT0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Run_USBased_PersonalUse_DoesNotSetTaxExempt()
|
||||
{
|
||||
// Arrange
|
||||
var parameters = new OrganizationTrialParameters
|
||||
{
|
||||
PlanType = PlanType.FamiliesAnnually,
|
||||
ProductType = ProductType.PasswordManager,
|
||||
TaxInformation = new TaxInformationDTO
|
||||
{
|
||||
Country = "US",
|
||||
PostalCode = "12345"
|
||||
}
|
||||
};
|
||||
|
||||
var plan = StaticStore.GetPlan(parameters.PlanType);
|
||||
|
||||
_pricingClient.GetPlanOrThrow(parameters.PlanType).Returns(plan);
|
||||
|
||||
var expectedInvoice = new Invoice { Tax = 1000 }; // $10.00 in cents
|
||||
_stripeAdapter.InvoiceCreatePreviewAsync(Arg.Any<InvoiceCreatePreviewOptions>())
|
||||
.Returns(expectedInvoice);
|
||||
|
||||
// Act
|
||||
var result = await _command.Run(parameters);
|
||||
|
||||
// Assert
|
||||
await _stripeAdapter.Received(1).InvoiceCreatePreviewAsync(Arg.Is<InvoiceCreatePreviewOptions>(options =>
|
||||
options.CustomerDetails.TaxExempt == null
|
||||
));
|
||||
Assert.True(result.IsT0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Run_USBased_BusinessUse_DoesNotSetTaxExempt()
|
||||
{
|
||||
// Arrange
|
||||
var parameters = new OrganizationTrialParameters
|
||||
{
|
||||
PlanType = PlanType.EnterpriseAnnually,
|
||||
ProductType = ProductType.PasswordManager,
|
||||
TaxInformation = new TaxInformationDTO
|
||||
{
|
||||
Country = "US",
|
||||
PostalCode = "12345"
|
||||
}
|
||||
};
|
||||
|
||||
var plan = StaticStore.GetPlan(parameters.PlanType);
|
||||
|
||||
_pricingClient.GetPlanOrThrow(parameters.PlanType).Returns(plan);
|
||||
|
||||
var expectedInvoice = new Invoice { Tax = 1000 }; // $10.00 in cents
|
||||
_stripeAdapter.InvoiceCreatePreviewAsync(Arg.Any<InvoiceCreatePreviewOptions>())
|
||||
.Returns(expectedInvoice);
|
||||
|
||||
// Act
|
||||
var result = await _command.Run(parameters);
|
||||
|
||||
// Assert
|
||||
await _stripeAdapter.Received(1).InvoiceCreatePreviewAsync(Arg.Is<InvoiceCreatePreviewOptions>(options =>
|
||||
options.CustomerDetails.TaxExempt == null
|
||||
));
|
||||
Assert.True(result.IsT0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Run_NonUSBased_PersonalUse_DoesNotSetTaxExempt()
|
||||
{
|
||||
// Arrange
|
||||
var parameters = new OrganizationTrialParameters
|
||||
{
|
||||
PlanType = PlanType.FamiliesAnnually,
|
||||
ProductType = ProductType.PasswordManager,
|
||||
TaxInformation = new TaxInformationDTO
|
||||
{
|
||||
Country = "CA",
|
||||
PostalCode = "12345"
|
||||
}
|
||||
};
|
||||
|
||||
var plan = StaticStore.GetPlan(parameters.PlanType);
|
||||
|
||||
_pricingClient.GetPlanOrThrow(parameters.PlanType).Returns(plan);
|
||||
|
||||
var expectedInvoice = new Invoice { Tax = 1000 }; // $10.00 in cents
|
||||
_stripeAdapter.InvoiceCreatePreviewAsync(Arg.Any<InvoiceCreatePreviewOptions>())
|
||||
.Returns(expectedInvoice);
|
||||
|
||||
// Act
|
||||
var result = await _command.Run(parameters);
|
||||
|
||||
// Assert
|
||||
await _stripeAdapter.Received(1).InvoiceCreatePreviewAsync(Arg.Is<InvoiceCreatePreviewOptions>(options =>
|
||||
options.CustomerDetails.TaxExempt == null
|
||||
));
|
||||
Assert.True(result.IsT0);
|
||||
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Run_NonUSBased_BusinessUse_SetsTaxExemptReverse()
|
||||
{
|
||||
// Arrange
|
||||
var parameters = new OrganizationTrialParameters
|
||||
{
|
||||
PlanType = PlanType.EnterpriseAnnually,
|
||||
ProductType = ProductType.PasswordManager,
|
||||
TaxInformation = new TaxInformationDTO
|
||||
{
|
||||
Country = "CA",
|
||||
PostalCode = "12345"
|
||||
}
|
||||
};
|
||||
|
||||
var plan = StaticStore.GetPlan(parameters.PlanType);
|
||||
|
||||
_pricingClient.GetPlanOrThrow(parameters.PlanType).Returns(plan);
|
||||
|
||||
var expectedInvoice = new Invoice { Tax = 1000 }; // $10.00 in cents
|
||||
_stripeAdapter.InvoiceCreatePreviewAsync(Arg.Any<InvoiceCreatePreviewOptions>())
|
||||
.Returns(expectedInvoice);
|
||||
|
||||
// Act
|
||||
var result = await _command.Run(parameters);
|
||||
|
||||
// Assert
|
||||
await _stripeAdapter.Received(1).InvoiceCreatePreviewAsync(Arg.Is<InvoiceCreatePreviewOptions>(options =>
|
||||
options.CustomerDetails.TaxExempt == StripeConstants.TaxExempt.Reverse
|
||||
));
|
||||
Assert.True(result.IsT0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,105 +0,0 @@
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.Billing.Enums;
|
||||
using Bit.Core.Billing.Models.StaticStore.Plans;
|
||||
using Bit.Core.Billing.Pricing;
|
||||
using Bit.Core.Billing.Tax.Models;
|
||||
using Bit.Core.Billing.Tax.Services.Implementations;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.Billing.Tax.Services;
|
||||
|
||||
[SutProviderCustomize]
|
||||
public class AutomaticTaxFactoryTests
|
||||
{
|
||||
[BitAutoData]
|
||||
[Theory]
|
||||
public async Task CreateAsync_ReturnsPersonalUseStrategy_WhenSubscriberIsUser(SutProvider<AutomaticTaxFactory> sut)
|
||||
{
|
||||
var parameters = new AutomaticTaxFactoryParameters(new User(), []);
|
||||
|
||||
var actual = await sut.Sut.CreateAsync(parameters);
|
||||
|
||||
Assert.IsType<PersonalUseAutomaticTaxStrategy>(actual);
|
||||
}
|
||||
|
||||
[BitAutoData]
|
||||
[Theory]
|
||||
public async Task CreateAsync_ReturnsPersonalUseStrategy_WhenSubscriberIsOrganizationWithFamiliesAnnuallyPrice(
|
||||
SutProvider<AutomaticTaxFactory> sut)
|
||||
{
|
||||
var familiesPlan = new FamiliesPlan();
|
||||
var parameters = new AutomaticTaxFactoryParameters(new Organization(), [familiesPlan.PasswordManager.StripePlanId]);
|
||||
|
||||
sut.GetDependency<IPricingClient>()
|
||||
.GetPlanOrThrow(Arg.Is<PlanType>(p => p == PlanType.FamiliesAnnually))
|
||||
.Returns(new FamiliesPlan());
|
||||
|
||||
sut.GetDependency<IPricingClient>()
|
||||
.GetPlanOrThrow(Arg.Is<PlanType>(p => p == PlanType.FamiliesAnnually2019))
|
||||
.Returns(new Families2019Plan());
|
||||
|
||||
var actual = await sut.Sut.CreateAsync(parameters);
|
||||
|
||||
Assert.IsType<PersonalUseAutomaticTaxStrategy>(actual);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task CreateAsync_ReturnsBusinessUseStrategy_WhenSubscriberIsOrganizationWithBusinessUsePrice(
|
||||
EnterpriseAnnually plan,
|
||||
SutProvider<AutomaticTaxFactory> sut)
|
||||
{
|
||||
var parameters = new AutomaticTaxFactoryParameters(new Organization(), [plan.PasswordManager.StripePlanId]);
|
||||
|
||||
sut.GetDependency<IPricingClient>()
|
||||
.GetPlanOrThrow(Arg.Is<PlanType>(p => p == PlanType.FamiliesAnnually))
|
||||
.Returns(new FamiliesPlan());
|
||||
|
||||
sut.GetDependency<IPricingClient>()
|
||||
.GetPlanOrThrow(Arg.Is<PlanType>(p => p == PlanType.FamiliesAnnually2019))
|
||||
.Returns(new Families2019Plan());
|
||||
|
||||
var actual = await sut.Sut.CreateAsync(parameters);
|
||||
|
||||
Assert.IsType<BusinessUseAutomaticTaxStrategy>(actual);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task CreateAsync_ReturnsPersonalUseStrategy_WhenPlanIsMeantForPersonalUse(SutProvider<AutomaticTaxFactory> sut)
|
||||
{
|
||||
var parameters = new AutomaticTaxFactoryParameters(PlanType.FamiliesAnnually);
|
||||
sut.GetDependency<IPricingClient>()
|
||||
.GetPlanOrThrow(Arg.Is<PlanType>(p => p == parameters.PlanType.Value))
|
||||
.Returns(new FamiliesPlan());
|
||||
|
||||
var actual = await sut.Sut.CreateAsync(parameters);
|
||||
|
||||
Assert.IsType<PersonalUseAutomaticTaxStrategy>(actual);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task CreateAsync_ReturnsBusinessUseStrategy_WhenPlanIsMeantForBusinessUse(SutProvider<AutomaticTaxFactory> sut)
|
||||
{
|
||||
var parameters = new AutomaticTaxFactoryParameters(PlanType.EnterpriseAnnually);
|
||||
sut.GetDependency<IPricingClient>()
|
||||
.GetPlanOrThrow(Arg.Is<PlanType>(p => p == parameters.PlanType.Value))
|
||||
.Returns(new EnterprisePlan(true));
|
||||
|
||||
var actual = await sut.Sut.CreateAsync(parameters);
|
||||
|
||||
Assert.IsType<BusinessUseAutomaticTaxStrategy>(actual);
|
||||
}
|
||||
|
||||
public record EnterpriseAnnually : EnterprisePlan
|
||||
{
|
||||
public EnterpriseAnnually() : base(true)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,492 +0,0 @@
|
||||
using Bit.Core.Billing.Constants;
|
||||
using Bit.Core.Billing.Tax.Services.Implementations;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using NSubstitute;
|
||||
using Stripe;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.Billing.Tax.Services;
|
||||
|
||||
[SutProviderCustomize]
|
||||
public class BusinessUseAutomaticTaxStrategyTests
|
||||
{
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public void GetUpdateOptions_ReturnsNull_WhenFeatureFlagAllowingToUpdateSubscriptionsIsDisabled(
|
||||
SutProvider<BusinessUseAutomaticTaxStrategy> sutProvider)
|
||||
{
|
||||
var subscription = new Subscription();
|
||||
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(Arg.Is<string>(p => p == FeatureFlagKeys.PM19422_AllowAutomaticTaxUpdates))
|
||||
.Returns(false);
|
||||
|
||||
var actual = sutProvider.Sut.GetUpdateOptions(subscription);
|
||||
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public void GetUpdateOptions_ReturnsNull_WhenSubscriptionDoesNotNeedUpdating(
|
||||
SutProvider<BusinessUseAutomaticTaxStrategy> sutProvider)
|
||||
{
|
||||
var subscription = new Subscription
|
||||
{
|
||||
AutomaticTax = new SubscriptionAutomaticTax
|
||||
{
|
||||
Enabled = true
|
||||
},
|
||||
Customer = new Customer
|
||||
{
|
||||
Address = new Address
|
||||
{
|
||||
Country = "US",
|
||||
},
|
||||
Tax = new CustomerTax
|
||||
{
|
||||
AutomaticTax = StripeConstants.AutomaticTaxStatus.Supported
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(Arg.Is<string>(p => p == FeatureFlagKeys.PM19422_AllowAutomaticTaxUpdates))
|
||||
.Returns(true);
|
||||
|
||||
var actual = sutProvider.Sut.GetUpdateOptions(subscription);
|
||||
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public void GetUpdateOptions_SetsAutomaticTaxToFalse_WhenTaxLocationIsUnrecognizedOrInvalid(
|
||||
SutProvider<BusinessUseAutomaticTaxStrategy> sutProvider)
|
||||
{
|
||||
var subscription = new Subscription
|
||||
{
|
||||
AutomaticTax = new SubscriptionAutomaticTax
|
||||
{
|
||||
Enabled = true
|
||||
},
|
||||
Customer = new Customer
|
||||
{
|
||||
Tax = new CustomerTax
|
||||
{
|
||||
AutomaticTax = StripeConstants.AutomaticTaxStatus.UnrecognizedLocation
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(Arg.Is<string>(p => p == FeatureFlagKeys.PM19422_AllowAutomaticTaxUpdates))
|
||||
.Returns(true);
|
||||
|
||||
var actual = sutProvider.Sut.GetUpdateOptions(subscription);
|
||||
|
||||
Assert.NotNull(actual);
|
||||
Assert.False(actual.AutomaticTax.Enabled);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public void GetUpdateOptions_SetsAutomaticTaxToTrue_ForAmericanCustomers(
|
||||
SutProvider<BusinessUseAutomaticTaxStrategy> sutProvider)
|
||||
{
|
||||
var subscription = new Subscription
|
||||
{
|
||||
AutomaticTax = new SubscriptionAutomaticTax
|
||||
{
|
||||
Enabled = false
|
||||
},
|
||||
Customer = new Customer
|
||||
{
|
||||
Address = new Address
|
||||
{
|
||||
Country = "US",
|
||||
},
|
||||
Tax = new CustomerTax
|
||||
{
|
||||
AutomaticTax = StripeConstants.AutomaticTaxStatus.Supported
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(Arg.Is<string>(p => p == FeatureFlagKeys.PM19422_AllowAutomaticTaxUpdates))
|
||||
.Returns(true);
|
||||
|
||||
var actual = sutProvider.Sut.GetUpdateOptions(subscription);
|
||||
|
||||
Assert.NotNull(actual);
|
||||
Assert.True(actual.AutomaticTax.Enabled);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public void GetUpdateOptions_SetsAutomaticTaxToTrue_ForGlobalCustomersWithTaxIds(
|
||||
SutProvider<BusinessUseAutomaticTaxStrategy> sutProvider)
|
||||
{
|
||||
var subscription = new Subscription
|
||||
{
|
||||
AutomaticTax = new SubscriptionAutomaticTax
|
||||
{
|
||||
Enabled = false
|
||||
},
|
||||
Customer = new Customer
|
||||
{
|
||||
Address = new Address
|
||||
{
|
||||
Country = "ES",
|
||||
},
|
||||
Tax = new CustomerTax
|
||||
{
|
||||
AutomaticTax = StripeConstants.AutomaticTaxStatus.Supported
|
||||
},
|
||||
TaxIds = new StripeList<TaxId>
|
||||
{
|
||||
Data = new List<TaxId>
|
||||
{
|
||||
new()
|
||||
{
|
||||
Country = "ES",
|
||||
Type = "eu_vat",
|
||||
Value = "ESZ8880999Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(Arg.Is<string>(p => p == FeatureFlagKeys.PM19422_AllowAutomaticTaxUpdates))
|
||||
.Returns(true);
|
||||
|
||||
var actual = sutProvider.Sut.GetUpdateOptions(subscription);
|
||||
|
||||
Assert.NotNull(actual);
|
||||
Assert.True(actual.AutomaticTax.Enabled);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public void GetUpdateOptions_ThrowsArgumentNullException_WhenTaxIdsIsNull(
|
||||
SutProvider<BusinessUseAutomaticTaxStrategy> sutProvider)
|
||||
{
|
||||
var subscription = new Subscription
|
||||
{
|
||||
AutomaticTax = new SubscriptionAutomaticTax
|
||||
{
|
||||
Enabled = true
|
||||
},
|
||||
Customer = new Customer
|
||||
{
|
||||
Address = new Address
|
||||
{
|
||||
Country = "ES",
|
||||
},
|
||||
Tax = new CustomerTax
|
||||
{
|
||||
AutomaticTax = StripeConstants.AutomaticTaxStatus.Supported
|
||||
},
|
||||
TaxIds = null
|
||||
}
|
||||
};
|
||||
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(Arg.Is<string>(p => p == FeatureFlagKeys.PM19422_AllowAutomaticTaxUpdates))
|
||||
.Returns(true);
|
||||
|
||||
Assert.Throws<ArgumentNullException>(() => sutProvider.Sut.GetUpdateOptions(subscription));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public void GetUpdateOptions_SetsAutomaticTaxToTrue_ForGlobalCustomersWithoutTaxIds(
|
||||
SutProvider<BusinessUseAutomaticTaxStrategy> sutProvider)
|
||||
{
|
||||
|
||||
var subscription = new Subscription
|
||||
{
|
||||
AutomaticTax = new SubscriptionAutomaticTax
|
||||
{
|
||||
Enabled = true
|
||||
},
|
||||
Customer = new Customer
|
||||
{
|
||||
Address = new Address
|
||||
{
|
||||
Country = "ES",
|
||||
},
|
||||
Tax = new CustomerTax
|
||||
{
|
||||
AutomaticTax = StripeConstants.AutomaticTaxStatus.Supported
|
||||
},
|
||||
TaxIds = new StripeList<TaxId>
|
||||
{
|
||||
Data = new List<TaxId>()
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(Arg.Is<string>(p => p == FeatureFlagKeys.PM19422_AllowAutomaticTaxUpdates))
|
||||
.Returns(true);
|
||||
|
||||
var actual = sutProvider.Sut.GetUpdateOptions(subscription);
|
||||
|
||||
Assert.NotNull(actual);
|
||||
Assert.False(actual.AutomaticTax.Enabled);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public void SetUpdateOptions_SetsNothing_WhenFeatureFlagAllowingToUpdateSubscriptionsIsDisabled(
|
||||
SutProvider<BusinessUseAutomaticTaxStrategy> sutProvider)
|
||||
{
|
||||
var options = new SubscriptionUpdateOptions();
|
||||
|
||||
var subscription = new Subscription
|
||||
{
|
||||
Customer = new Customer
|
||||
{
|
||||
Address = new()
|
||||
{
|
||||
Country = "US"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(Arg.Is<string>(p => p == FeatureFlagKeys.PM19422_AllowAutomaticTaxUpdates))
|
||||
.Returns(false);
|
||||
|
||||
sutProvider.Sut.SetUpdateOptions(options, subscription);
|
||||
|
||||
Assert.Null(options.AutomaticTax);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public void SetUpdateOptions_SetsNothing_WhenSubscriptionDoesNotNeedUpdating(
|
||||
SutProvider<BusinessUseAutomaticTaxStrategy> sutProvider)
|
||||
{
|
||||
var options = new SubscriptionUpdateOptions();
|
||||
|
||||
var subscription = new Subscription
|
||||
{
|
||||
AutomaticTax = new SubscriptionAutomaticTax
|
||||
{
|
||||
Enabled = true
|
||||
},
|
||||
Customer = new Customer
|
||||
{
|
||||
Address = new Address
|
||||
{
|
||||
Country = "US",
|
||||
},
|
||||
Tax = new CustomerTax
|
||||
{
|
||||
AutomaticTax = StripeConstants.AutomaticTaxStatus.Supported
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(Arg.Is<string>(p => p == FeatureFlagKeys.PM19422_AllowAutomaticTaxUpdates))
|
||||
.Returns(true);
|
||||
|
||||
sutProvider.Sut.SetUpdateOptions(options, subscription);
|
||||
|
||||
Assert.Null(options.AutomaticTax);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public void SetUpdateOptions_SetsAutomaticTaxToFalse_WhenTaxLocationIsUnrecognizedOrInvalid(
|
||||
SutProvider<BusinessUseAutomaticTaxStrategy> sutProvider)
|
||||
{
|
||||
var options = new SubscriptionUpdateOptions();
|
||||
|
||||
var subscription = new Subscription
|
||||
{
|
||||
AutomaticTax = new SubscriptionAutomaticTax
|
||||
{
|
||||
Enabled = true
|
||||
},
|
||||
Customer = new Customer
|
||||
{
|
||||
Tax = new CustomerTax
|
||||
{
|
||||
AutomaticTax = StripeConstants.AutomaticTaxStatus.UnrecognizedLocation
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(Arg.Is<string>(p => p == FeatureFlagKeys.PM19422_AllowAutomaticTaxUpdates))
|
||||
.Returns(true);
|
||||
|
||||
sutProvider.Sut.SetUpdateOptions(options, subscription);
|
||||
|
||||
Assert.False(options.AutomaticTax.Enabled);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public void SetUpdateOptions_SetsAutomaticTaxToTrue_ForAmericanCustomers(
|
||||
SutProvider<BusinessUseAutomaticTaxStrategy> sutProvider)
|
||||
{
|
||||
var options = new SubscriptionUpdateOptions();
|
||||
|
||||
var subscription = new Subscription
|
||||
{
|
||||
AutomaticTax = new SubscriptionAutomaticTax
|
||||
{
|
||||
Enabled = false
|
||||
},
|
||||
Customer = new Customer
|
||||
{
|
||||
Address = new Address
|
||||
{
|
||||
Country = "US",
|
||||
},
|
||||
Tax = new CustomerTax
|
||||
{
|
||||
AutomaticTax = StripeConstants.AutomaticTaxStatus.Supported
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(Arg.Is<string>(p => p == FeatureFlagKeys.PM19422_AllowAutomaticTaxUpdates))
|
||||
.Returns(true);
|
||||
|
||||
sutProvider.Sut.SetUpdateOptions(options, subscription);
|
||||
|
||||
Assert.True(options.AutomaticTax!.Enabled);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public void SetUpdateOptions_SetsAutomaticTaxToTrue_ForGlobalCustomersWithTaxIds(
|
||||
SutProvider<BusinessUseAutomaticTaxStrategy> sutProvider)
|
||||
{
|
||||
var options = new SubscriptionUpdateOptions();
|
||||
|
||||
var subscription = new Subscription
|
||||
{
|
||||
AutomaticTax = new SubscriptionAutomaticTax
|
||||
{
|
||||
Enabled = false
|
||||
},
|
||||
Customer = new Customer
|
||||
{
|
||||
Address = new Address
|
||||
{
|
||||
Country = "ES",
|
||||
},
|
||||
Tax = new CustomerTax
|
||||
{
|
||||
AutomaticTax = StripeConstants.AutomaticTaxStatus.Supported
|
||||
},
|
||||
TaxIds = new StripeList<TaxId>
|
||||
{
|
||||
Data = new List<TaxId>
|
||||
{
|
||||
new()
|
||||
{
|
||||
Country = "ES",
|
||||
Type = "eu_vat",
|
||||
Value = "ESZ8880999Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(Arg.Is<string>(p => p == FeatureFlagKeys.PM19422_AllowAutomaticTaxUpdates))
|
||||
.Returns(true);
|
||||
|
||||
sutProvider.Sut.SetUpdateOptions(options, subscription);
|
||||
|
||||
Assert.True(options.AutomaticTax!.Enabled);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public void SetUpdateOptions_ThrowsArgumentNullException_WhenTaxIdsIsNull(
|
||||
SutProvider<BusinessUseAutomaticTaxStrategy> sutProvider)
|
||||
{
|
||||
var options = new SubscriptionUpdateOptions();
|
||||
|
||||
var subscription = new Subscription
|
||||
{
|
||||
AutomaticTax = new SubscriptionAutomaticTax
|
||||
{
|
||||
Enabled = true
|
||||
},
|
||||
Customer = new Customer
|
||||
{
|
||||
Address = new Address
|
||||
{
|
||||
Country = "ES",
|
||||
},
|
||||
Tax = new CustomerTax
|
||||
{
|
||||
AutomaticTax = StripeConstants.AutomaticTaxStatus.Supported
|
||||
},
|
||||
TaxIds = null
|
||||
}
|
||||
};
|
||||
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(Arg.Is<string>(p => p == FeatureFlagKeys.PM19422_AllowAutomaticTaxUpdates))
|
||||
.Returns(true);
|
||||
|
||||
Assert.Throws<ArgumentNullException>(() => sutProvider.Sut.SetUpdateOptions(options, subscription));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public void SetUpdateOptions_SetsAutomaticTaxToTrue_ForGlobalCustomersWithoutTaxIds(
|
||||
SutProvider<BusinessUseAutomaticTaxStrategy> sutProvider)
|
||||
{
|
||||
var options = new SubscriptionUpdateOptions();
|
||||
|
||||
var subscription = new Subscription
|
||||
{
|
||||
AutomaticTax = new SubscriptionAutomaticTax
|
||||
{
|
||||
Enabled = true
|
||||
},
|
||||
Customer = new Customer
|
||||
{
|
||||
Address = new Address
|
||||
{
|
||||
Country = "ES",
|
||||
},
|
||||
Tax = new CustomerTax
|
||||
{
|
||||
AutomaticTax = StripeConstants.AutomaticTaxStatus.Supported
|
||||
},
|
||||
TaxIds = new StripeList<TaxId>
|
||||
{
|
||||
Data = new List<TaxId>()
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(Arg.Is<string>(p => p == FeatureFlagKeys.PM19422_AllowAutomaticTaxUpdates))
|
||||
.Returns(true);
|
||||
|
||||
sutProvider.Sut.SetUpdateOptions(options, subscription);
|
||||
|
||||
Assert.False(options.AutomaticTax!.Enabled);
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
using Bit.Core.Billing.Tax.Services;
|
||||
using Stripe;
|
||||
|
||||
namespace Bit.Core.Test.Billing.Tax.Services;
|
||||
|
||||
/// <param name="isAutomaticTaxEnabled">
|
||||
/// Whether the subscription options will have automatic tax enabled or not.
|
||||
/// </param>
|
||||
public class FakeAutomaticTaxStrategy(
|
||||
bool isAutomaticTaxEnabled) : IAutomaticTaxStrategy
|
||||
{
|
||||
public SubscriptionUpdateOptions? GetUpdateOptions(Subscription subscription)
|
||||
{
|
||||
return new SubscriptionUpdateOptions
|
||||
{
|
||||
AutomaticTax = new SubscriptionAutomaticTaxOptions { Enabled = isAutomaticTaxEnabled }
|
||||
};
|
||||
}
|
||||
|
||||
public void SetCreateOptions(SubscriptionCreateOptions options, Customer customer)
|
||||
{
|
||||
options.AutomaticTax = new SubscriptionAutomaticTaxOptions { Enabled = isAutomaticTaxEnabled };
|
||||
}
|
||||
|
||||
public void SetUpdateOptions(SubscriptionUpdateOptions options, Subscription subscription)
|
||||
{
|
||||
options.AutomaticTax = new SubscriptionAutomaticTaxOptions { Enabled = isAutomaticTaxEnabled };
|
||||
}
|
||||
|
||||
public void SetInvoiceCreatePreviewOptions(InvoiceCreatePreviewOptions options)
|
||||
{
|
||||
options.AutomaticTax = new InvoiceAutomaticTaxOptions { Enabled = isAutomaticTaxEnabled };
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,217 +0,0 @@
|
||||
using Bit.Core.Billing.Constants;
|
||||
using Bit.Core.Billing.Tax.Services.Implementations;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using NSubstitute;
|
||||
using Stripe;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.Billing.Tax.Services;
|
||||
|
||||
[SutProviderCustomize]
|
||||
public class PersonalUseAutomaticTaxStrategyTests
|
||||
{
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public void GetUpdateOptions_ReturnsNull_WhenFeatureFlagAllowingToUpdateSubscriptionsIsDisabled(
|
||||
SutProvider<PersonalUseAutomaticTaxStrategy> sutProvider)
|
||||
{
|
||||
var subscription = new Subscription();
|
||||
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(Arg.Is<string>(p => p == FeatureFlagKeys.PM19422_AllowAutomaticTaxUpdates))
|
||||
.Returns(false);
|
||||
|
||||
var actual = sutProvider.Sut.GetUpdateOptions(subscription);
|
||||
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public void GetUpdateOptions_ReturnsNull_WhenSubscriptionDoesNotNeedUpdating(
|
||||
SutProvider<PersonalUseAutomaticTaxStrategy> sutProvider)
|
||||
{
|
||||
var subscription = new Subscription
|
||||
{
|
||||
AutomaticTax = new SubscriptionAutomaticTax
|
||||
{
|
||||
Enabled = true
|
||||
},
|
||||
Customer = new Customer
|
||||
{
|
||||
Address = new Address
|
||||
{
|
||||
Country = "US",
|
||||
},
|
||||
Tax = new CustomerTax
|
||||
{
|
||||
AutomaticTax = StripeConstants.AutomaticTaxStatus.Supported
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(Arg.Is<string>(p => p == FeatureFlagKeys.PM19422_AllowAutomaticTaxUpdates))
|
||||
.Returns(true);
|
||||
|
||||
var actual = sutProvider.Sut.GetUpdateOptions(subscription);
|
||||
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public void GetUpdateOptions_SetsAutomaticTaxToFalse_WhenTaxLocationIsUnrecognizedOrInvalid(
|
||||
SutProvider<PersonalUseAutomaticTaxStrategy> sutProvider)
|
||||
{
|
||||
var subscription = new Subscription
|
||||
{
|
||||
AutomaticTax = new SubscriptionAutomaticTax
|
||||
{
|
||||
Enabled = true
|
||||
},
|
||||
Customer = new Customer
|
||||
{
|
||||
Tax = new CustomerTax
|
||||
{
|
||||
AutomaticTax = StripeConstants.AutomaticTaxStatus.UnrecognizedLocation
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(Arg.Is<string>(p => p == FeatureFlagKeys.PM19422_AllowAutomaticTaxUpdates))
|
||||
.Returns(true);
|
||||
|
||||
var actual = sutProvider.Sut.GetUpdateOptions(subscription);
|
||||
|
||||
Assert.NotNull(actual);
|
||||
Assert.False(actual.AutomaticTax.Enabled);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData("CA")]
|
||||
[BitAutoData("ES")]
|
||||
[BitAutoData("US")]
|
||||
public void GetUpdateOptions_SetsAutomaticTaxToTrue_ForAllCountries(
|
||||
string country, SutProvider<PersonalUseAutomaticTaxStrategy> sutProvider)
|
||||
{
|
||||
var subscription = new Subscription
|
||||
{
|
||||
AutomaticTax = new SubscriptionAutomaticTax
|
||||
{
|
||||
Enabled = false
|
||||
},
|
||||
Customer = new Customer
|
||||
{
|
||||
Address = new Address
|
||||
{
|
||||
Country = country
|
||||
},
|
||||
Tax = new CustomerTax
|
||||
{
|
||||
AutomaticTax = StripeConstants.AutomaticTaxStatus.Supported
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(Arg.Is<string>(p => p == FeatureFlagKeys.PM19422_AllowAutomaticTaxUpdates))
|
||||
.Returns(true);
|
||||
|
||||
var actual = sutProvider.Sut.GetUpdateOptions(subscription);
|
||||
|
||||
Assert.NotNull(actual);
|
||||
Assert.True(actual.AutomaticTax.Enabled);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData("CA")]
|
||||
[BitAutoData("ES")]
|
||||
[BitAutoData("US")]
|
||||
public void GetUpdateOptions_SetsAutomaticTaxToTrue_ForGlobalCustomersWithTaxIds(
|
||||
string country, SutProvider<PersonalUseAutomaticTaxStrategy> sutProvider)
|
||||
{
|
||||
var subscription = new Subscription
|
||||
{
|
||||
AutomaticTax = new SubscriptionAutomaticTax
|
||||
{
|
||||
Enabled = false
|
||||
},
|
||||
Customer = new Customer
|
||||
{
|
||||
Address = new Address
|
||||
{
|
||||
Country = country,
|
||||
},
|
||||
Tax = new CustomerTax
|
||||
{
|
||||
AutomaticTax = StripeConstants.AutomaticTaxStatus.Supported
|
||||
},
|
||||
TaxIds = new StripeList<TaxId>
|
||||
{
|
||||
Data = new List<TaxId>
|
||||
{
|
||||
new()
|
||||
{
|
||||
Country = "ES",
|
||||
Type = "eu_vat",
|
||||
Value = "ESZ8880999Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(Arg.Is<string>(p => p == FeatureFlagKeys.PM19422_AllowAutomaticTaxUpdates))
|
||||
.Returns(true);
|
||||
|
||||
var actual = sutProvider.Sut.GetUpdateOptions(subscription);
|
||||
|
||||
Assert.NotNull(actual);
|
||||
Assert.True(actual.AutomaticTax.Enabled);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData("CA")]
|
||||
[BitAutoData("ES")]
|
||||
[BitAutoData("US")]
|
||||
public void GetUpdateOptions_SetsAutomaticTaxToTrue_ForGlobalCustomersWithoutTaxIds(
|
||||
string country, SutProvider<PersonalUseAutomaticTaxStrategy> sutProvider)
|
||||
{
|
||||
var subscription = new Subscription
|
||||
{
|
||||
AutomaticTax = new SubscriptionAutomaticTax
|
||||
{
|
||||
Enabled = false
|
||||
},
|
||||
Customer = new Customer
|
||||
{
|
||||
Address = new Address
|
||||
{
|
||||
Country = country
|
||||
},
|
||||
Tax = new CustomerTax
|
||||
{
|
||||
AutomaticTax = StripeConstants.AutomaticTaxStatus.Supported
|
||||
},
|
||||
TaxIds = new StripeList<TaxId>
|
||||
{
|
||||
Data = new List<TaxId>()
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(Arg.Is<string>(p => p == FeatureFlagKeys.PM19422_AllowAutomaticTaxUpdates))
|
||||
.Returns(true);
|
||||
|
||||
var actual = sutProvider.Sut.GetUpdateOptions(subscription);
|
||||
|
||||
Assert.NotNull(actual);
|
||||
Assert.True(actual.AutomaticTax.Enabled);
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,10 @@
|
||||
using Bit.Core.Billing.Enums;
|
||||
using Bit.Core.Billing.Constants;
|
||||
using Bit.Core.Billing.Enums;
|
||||
using Bit.Core.Billing.Models.StaticStore.Plans;
|
||||
using Bit.Core.Billing.Pricing;
|
||||
using Bit.Core.Billing.Tax.Models;
|
||||
using Bit.Core.Billing.Tax.Requests;
|
||||
using Bit.Core.Billing.Tax.Services;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Test.Billing.Tax.Services;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using NSubstitute;
|
||||
@@ -23,10 +21,6 @@ public class StripePaymentServiceTests
|
||||
public async Task PreviewInvoiceAsync_ForOrganization_CalculatesSalesTaxCorrectlyForFamiliesWithoutAdditionalStorage(
|
||||
SutProvider<StripePaymentService> sutProvider)
|
||||
{
|
||||
sutProvider.GetDependency<IAutomaticTaxFactory>()
|
||||
.CreateAsync(Arg.Is<AutomaticTaxFactoryParameters>(p => p.PlanType == PlanType.FamiliesAnnually))
|
||||
.Returns(new FakeAutomaticTaxStrategy(true));
|
||||
|
||||
var familiesPlan = new FamiliesPlan();
|
||||
sutProvider.GetDependency<IPricingClient>()
|
||||
.GetPlanOrThrow(Arg.Is<PlanType>(p => p == PlanType.FamiliesAnnually))
|
||||
@@ -74,10 +68,6 @@ public class StripePaymentServiceTests
|
||||
public async Task PreviewInvoiceAsync_ForOrganization_CalculatesSalesTaxCorrectlyForFamiliesWithAdditionalStorage(
|
||||
SutProvider<StripePaymentService> sutProvider)
|
||||
{
|
||||
sutProvider.GetDependency<IAutomaticTaxFactory>()
|
||||
.CreateAsync(Arg.Is<AutomaticTaxFactoryParameters>(p => p.PlanType == PlanType.FamiliesAnnually))
|
||||
.Returns(new FakeAutomaticTaxStrategy(true));
|
||||
|
||||
var familiesPlan = new FamiliesPlan();
|
||||
sutProvider.GetDependency<IPricingClient>()
|
||||
.GetPlanOrThrow(Arg.Is<PlanType>(p => p == PlanType.FamiliesAnnually))
|
||||
@@ -125,10 +115,6 @@ public class StripePaymentServiceTests
|
||||
public async Task PreviewInvoiceAsync_ForOrganization_CalculatesSalesTaxCorrectlyForFamiliesForEnterpriseWithoutAdditionalStorage(
|
||||
SutProvider<StripePaymentService> sutProvider)
|
||||
{
|
||||
sutProvider.GetDependency<IAutomaticTaxFactory>()
|
||||
.CreateAsync(Arg.Is<AutomaticTaxFactoryParameters>(p => p.PlanType == PlanType.FamiliesAnnually))
|
||||
.Returns(new FakeAutomaticTaxStrategy(true));
|
||||
|
||||
var familiesPlan = new FamiliesPlan();
|
||||
sutProvider.GetDependency<IPricingClient>()
|
||||
.GetPlanOrThrow(Arg.Is<PlanType>(p => p == PlanType.FamiliesAnnually))
|
||||
@@ -177,10 +163,6 @@ public class StripePaymentServiceTests
|
||||
public async Task PreviewInvoiceAsync_ForOrganization_CalculatesSalesTaxCorrectlyForFamiliesForEnterpriseWithAdditionalStorage(
|
||||
SutProvider<StripePaymentService> sutProvider)
|
||||
{
|
||||
sutProvider.GetDependency<IAutomaticTaxFactory>()
|
||||
.CreateAsync(Arg.Is<AutomaticTaxFactoryParameters>(p => p.PlanType == PlanType.FamiliesAnnually))
|
||||
.Returns(new FakeAutomaticTaxStrategy(true));
|
||||
|
||||
var familiesPlan = new FamiliesPlan();
|
||||
sutProvider.GetDependency<IPricingClient>()
|
||||
.GetPlanOrThrow(Arg.Is<PlanType>(p => p == PlanType.FamiliesAnnually))
|
||||
@@ -223,4 +205,340 @@ public class StripePaymentServiceTests
|
||||
Assert.Equal(4.08M, actual.TotalAmount);
|
||||
Assert.Equal(4M, actual.TaxableBaseAmount);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task PreviewInvoiceAsync_USBased_PersonalUse_SetsAutomaticTaxEnabled(SutProvider<StripePaymentService> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
var familiesPlan = new FamiliesPlan();
|
||||
sutProvider.GetDependency<IPricingClient>()
|
||||
.GetPlanOrThrow(Arg.Is<PlanType>(p => p == PlanType.FamiliesAnnually))
|
||||
.Returns(familiesPlan);
|
||||
|
||||
var parameters = new PreviewOrganizationInvoiceRequestBody
|
||||
{
|
||||
PasswordManager = new OrganizationPasswordManagerRequestModel
|
||||
{
|
||||
Plan = PlanType.FamiliesAnnually
|
||||
},
|
||||
TaxInformation = new TaxInformationRequestModel
|
||||
{
|
||||
Country = "US",
|
||||
PostalCode = "12345"
|
||||
}
|
||||
};
|
||||
|
||||
var stripeAdapter = sutProvider.GetDependency<IStripeAdapter>();
|
||||
stripeAdapter
|
||||
.InvoiceCreatePreviewAsync(Arg.Any<InvoiceCreatePreviewOptions>())
|
||||
.Returns(new Invoice
|
||||
{
|
||||
TotalExcludingTax = 400,
|
||||
Tax = 8,
|
||||
Total = 408
|
||||
});
|
||||
|
||||
// Act
|
||||
await sutProvider.Sut.PreviewInvoiceAsync(parameters, null, null);
|
||||
|
||||
// Assert
|
||||
await stripeAdapter.Received(1).InvoiceCreatePreviewAsync(Arg.Is<InvoiceCreatePreviewOptions>(options =>
|
||||
options.AutomaticTax.Enabled == true
|
||||
));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task PreviewInvoiceAsync_USBased_BusinessUse_SetsAutomaticTaxEnabled(SutProvider<StripePaymentService> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
var plan = new EnterprisePlan(true);
|
||||
sutProvider.GetDependency<IPricingClient>()
|
||||
.GetPlanOrThrow(Arg.Is<PlanType>(p => p == PlanType.EnterpriseAnnually))
|
||||
.Returns(plan);
|
||||
|
||||
var parameters = new PreviewOrganizationInvoiceRequestBody
|
||||
{
|
||||
PasswordManager = new OrganizationPasswordManagerRequestModel
|
||||
{
|
||||
Plan = PlanType.EnterpriseAnnually
|
||||
},
|
||||
TaxInformation = new TaxInformationRequestModel
|
||||
{
|
||||
Country = "US",
|
||||
PostalCode = "12345"
|
||||
}
|
||||
};
|
||||
|
||||
var stripeAdapter = sutProvider.GetDependency<IStripeAdapter>();
|
||||
stripeAdapter
|
||||
.InvoiceCreatePreviewAsync(Arg.Any<InvoiceCreatePreviewOptions>())
|
||||
.Returns(new Invoice
|
||||
{
|
||||
TotalExcludingTax = 400,
|
||||
Tax = 8,
|
||||
Total = 408
|
||||
});
|
||||
|
||||
// Act
|
||||
await sutProvider.Sut.PreviewInvoiceAsync(parameters, null, null);
|
||||
|
||||
// Assert
|
||||
await stripeAdapter.Received(1).InvoiceCreatePreviewAsync(Arg.Is<InvoiceCreatePreviewOptions>(options =>
|
||||
options.AutomaticTax.Enabled == true
|
||||
));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task PreviewInvoiceAsync_NonUSBased_PersonalUse_SetsAutomaticTaxEnabled(SutProvider<StripePaymentService> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
var familiesPlan = new FamiliesPlan();
|
||||
sutProvider.GetDependency<IPricingClient>()
|
||||
.GetPlanOrThrow(Arg.Is<PlanType>(p => p == PlanType.FamiliesAnnually))
|
||||
.Returns(familiesPlan);
|
||||
|
||||
var parameters = new PreviewOrganizationInvoiceRequestBody
|
||||
{
|
||||
PasswordManager = new OrganizationPasswordManagerRequestModel
|
||||
{
|
||||
Plan = PlanType.FamiliesAnnually
|
||||
},
|
||||
TaxInformation = new TaxInformationRequestModel
|
||||
{
|
||||
Country = "FR",
|
||||
PostalCode = "12345"
|
||||
}
|
||||
};
|
||||
|
||||
var stripeAdapter = sutProvider.GetDependency<IStripeAdapter>();
|
||||
stripeAdapter
|
||||
.InvoiceCreatePreviewAsync(Arg.Any<InvoiceCreatePreviewOptions>())
|
||||
.Returns(new Invoice
|
||||
{
|
||||
TotalExcludingTax = 400,
|
||||
Tax = 8,
|
||||
Total = 408
|
||||
});
|
||||
|
||||
// Act
|
||||
await sutProvider.Sut.PreviewInvoiceAsync(parameters, null, null);
|
||||
|
||||
// Assert
|
||||
await stripeAdapter.Received(1).InvoiceCreatePreviewAsync(Arg.Is<InvoiceCreatePreviewOptions>(options =>
|
||||
options.AutomaticTax.Enabled == true
|
||||
));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task PreviewInvoiceAsync_NonUSBased_BusinessUse_SetsAutomaticTaxEnabled(SutProvider<StripePaymentService> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
var plan = new EnterprisePlan(true);
|
||||
sutProvider.GetDependency<IPricingClient>()
|
||||
.GetPlanOrThrow(Arg.Is<PlanType>(p => p == PlanType.EnterpriseAnnually))
|
||||
.Returns(plan);
|
||||
|
||||
var parameters = new PreviewOrganizationInvoiceRequestBody
|
||||
{
|
||||
PasswordManager = new OrganizationPasswordManagerRequestModel
|
||||
{
|
||||
Plan = PlanType.EnterpriseAnnually
|
||||
},
|
||||
TaxInformation = new TaxInformationRequestModel
|
||||
{
|
||||
Country = "FR",
|
||||
PostalCode = "12345"
|
||||
}
|
||||
};
|
||||
|
||||
var stripeAdapter = sutProvider.GetDependency<IStripeAdapter>();
|
||||
stripeAdapter
|
||||
.InvoiceCreatePreviewAsync(Arg.Any<InvoiceCreatePreviewOptions>())
|
||||
.Returns(new Invoice
|
||||
{
|
||||
TotalExcludingTax = 400,
|
||||
Tax = 8,
|
||||
Total = 408
|
||||
});
|
||||
|
||||
// Act
|
||||
await sutProvider.Sut.PreviewInvoiceAsync(parameters, null, null);
|
||||
|
||||
// Assert
|
||||
await stripeAdapter.Received(1).InvoiceCreatePreviewAsync(Arg.Is<InvoiceCreatePreviewOptions>(options =>
|
||||
options.AutomaticTax.Enabled == true
|
||||
));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task PreviewInvoiceAsync_USBased_PersonalUse_DoesNotSetTaxExempt(SutProvider<StripePaymentService> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
var familiesPlan = new FamiliesPlan();
|
||||
sutProvider.GetDependency<IPricingClient>()
|
||||
.GetPlanOrThrow(Arg.Is<PlanType>(p => p == PlanType.FamiliesAnnually))
|
||||
.Returns(familiesPlan);
|
||||
|
||||
var parameters = new PreviewOrganizationInvoiceRequestBody
|
||||
{
|
||||
PasswordManager = new OrganizationPasswordManagerRequestModel
|
||||
{
|
||||
Plan = PlanType.FamiliesAnnually
|
||||
},
|
||||
TaxInformation = new TaxInformationRequestModel
|
||||
{
|
||||
Country = "US",
|
||||
PostalCode = "12345"
|
||||
}
|
||||
};
|
||||
|
||||
var stripeAdapter = sutProvider.GetDependency<IStripeAdapter>();
|
||||
stripeAdapter
|
||||
.InvoiceCreatePreviewAsync(Arg.Any<InvoiceCreatePreviewOptions>())
|
||||
.Returns(new Invoice
|
||||
{
|
||||
TotalExcludingTax = 400,
|
||||
Tax = 8,
|
||||
Total = 408
|
||||
});
|
||||
|
||||
// Act
|
||||
await sutProvider.Sut.PreviewInvoiceAsync(parameters, null, null);
|
||||
|
||||
// Assert
|
||||
await stripeAdapter.Received(1).InvoiceCreatePreviewAsync(Arg.Is<InvoiceCreatePreviewOptions>(options =>
|
||||
options.CustomerDetails.TaxExempt == null
|
||||
));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task PreviewInvoiceAsync_USBased_BusinessUse_DoesNotSetTaxExempt(SutProvider<StripePaymentService> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
var plan = new EnterprisePlan(true);
|
||||
sutProvider.GetDependency<IPricingClient>()
|
||||
.GetPlanOrThrow(Arg.Is<PlanType>(p => p == PlanType.EnterpriseAnnually))
|
||||
.Returns(plan);
|
||||
|
||||
var parameters = new PreviewOrganizationInvoiceRequestBody
|
||||
{
|
||||
PasswordManager = new OrganizationPasswordManagerRequestModel
|
||||
{
|
||||
Plan = PlanType.EnterpriseAnnually
|
||||
},
|
||||
TaxInformation = new TaxInformationRequestModel
|
||||
{
|
||||
Country = "US",
|
||||
PostalCode = "12345"
|
||||
}
|
||||
};
|
||||
|
||||
var stripeAdapter = sutProvider.GetDependency<IStripeAdapter>();
|
||||
stripeAdapter
|
||||
.InvoiceCreatePreviewAsync(Arg.Any<InvoiceCreatePreviewOptions>())
|
||||
.Returns(new Invoice
|
||||
{
|
||||
TotalExcludingTax = 400,
|
||||
Tax = 8,
|
||||
Total = 408
|
||||
});
|
||||
|
||||
// Act
|
||||
await sutProvider.Sut.PreviewInvoiceAsync(parameters, null, null);
|
||||
|
||||
// Assert
|
||||
await stripeAdapter.Received(1).InvoiceCreatePreviewAsync(Arg.Is<InvoiceCreatePreviewOptions>(options =>
|
||||
options.CustomerDetails.TaxExempt == null
|
||||
));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task PreviewInvoiceAsync_NonUSBased_PersonalUse_DoesNotSetTaxExempt(SutProvider<StripePaymentService> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
var familiesPlan = new FamiliesPlan();
|
||||
sutProvider.GetDependency<IPricingClient>()
|
||||
.GetPlanOrThrow(Arg.Is<PlanType>(p => p == PlanType.FamiliesAnnually))
|
||||
.Returns(familiesPlan);
|
||||
|
||||
var parameters = new PreviewOrganizationInvoiceRequestBody
|
||||
{
|
||||
PasswordManager = new OrganizationPasswordManagerRequestModel
|
||||
{
|
||||
Plan = PlanType.FamiliesAnnually
|
||||
},
|
||||
TaxInformation = new TaxInformationRequestModel
|
||||
{
|
||||
Country = "FR",
|
||||
PostalCode = "12345"
|
||||
}
|
||||
};
|
||||
|
||||
var stripeAdapter = sutProvider.GetDependency<IStripeAdapter>();
|
||||
stripeAdapter
|
||||
.InvoiceCreatePreviewAsync(Arg.Any<InvoiceCreatePreviewOptions>())
|
||||
.Returns(new Invoice
|
||||
{
|
||||
TotalExcludingTax = 400,
|
||||
Tax = 8,
|
||||
Total = 408
|
||||
});
|
||||
|
||||
// Act
|
||||
await sutProvider.Sut.PreviewInvoiceAsync(parameters, null, null);
|
||||
|
||||
// Assert
|
||||
await stripeAdapter.Received(1).InvoiceCreatePreviewAsync(Arg.Is<InvoiceCreatePreviewOptions>(options =>
|
||||
options.CustomerDetails.TaxExempt == null
|
||||
));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task PreviewInvoiceAsync_NonUSBased_BusinessUse_SetsTaxExemptReverse(SutProvider<StripePaymentService> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
var plan = new EnterprisePlan(true);
|
||||
sutProvider.GetDependency<IPricingClient>()
|
||||
.GetPlanOrThrow(Arg.Is<PlanType>(p => p == PlanType.EnterpriseAnnually))
|
||||
.Returns(plan);
|
||||
|
||||
var parameters = new PreviewOrganizationInvoiceRequestBody
|
||||
{
|
||||
PasswordManager = new OrganizationPasswordManagerRequestModel
|
||||
{
|
||||
Plan = PlanType.EnterpriseAnnually
|
||||
},
|
||||
TaxInformation = new TaxInformationRequestModel
|
||||
{
|
||||
Country = "FR",
|
||||
PostalCode = "12345"
|
||||
}
|
||||
};
|
||||
|
||||
var stripeAdapter = sutProvider.GetDependency<IStripeAdapter>();
|
||||
stripeAdapter
|
||||
.InvoiceCreatePreviewAsync(Arg.Any<InvoiceCreatePreviewOptions>())
|
||||
.Returns(new Invoice
|
||||
{
|
||||
TotalExcludingTax = 400,
|
||||
Tax = 8,
|
||||
Total = 408
|
||||
});
|
||||
|
||||
// Act
|
||||
await sutProvider.Sut.PreviewInvoiceAsync(parameters, null, null);
|
||||
|
||||
// Assert
|
||||
await stripeAdapter.Received(1).InvoiceCreatePreviewAsync(Arg.Is<InvoiceCreatePreviewOptions>(options =>
|
||||
options.CustomerDetails.TaxExempt == StripeConstants.TaxExempt.Reverse
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user