mirror of
https://github.com/bitwarden/server
synced 2025-12-22 03:03:33 +00:00
[PM-21092] Set tax exemption to reverse charge for non-US business-use customers (#5812)
* Set automatic tax to enabled and tax exempt to reverse where applicable when ff is on * Fix and add tests * Run dotnet format * Run dotnet format * PM-21745: Resolve defect * PM-21770: Resolve defect * Run dotnet format'
This commit is contained in:
@@ -1,7 +1,10 @@
|
||||
using Bit.Core.Billing.Caches;
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.Entities.Provider;
|
||||
using Bit.Core.Billing.Caches;
|
||||
using Bit.Core.Billing.Constants;
|
||||
using Bit.Core.Billing.Enums;
|
||||
using Bit.Core.Billing.Extensions;
|
||||
using Bit.Core.Billing.Models;
|
||||
using Bit.Core.Billing.Services.Contracts;
|
||||
using Bit.Core.Billing.Tax.Models;
|
||||
using Bit.Core.Billing.Tax.Services;
|
||||
using Bit.Core.Entities;
|
||||
@@ -28,8 +31,7 @@ public class SubscriberService(
|
||||
ILogger<SubscriberService> logger,
|
||||
ISetupIntentCache setupIntentCache,
|
||||
IStripeAdapter stripeAdapter,
|
||||
ITaxService taxService,
|
||||
IAutomaticTaxFactory automaticTaxFactory) : ISubscriberService
|
||||
ITaxService taxService) : ISubscriberService
|
||||
{
|
||||
public async Task CancelSubscription(
|
||||
ISubscriber subscriber,
|
||||
@@ -128,7 +130,7 @@ public class SubscriberService(
|
||||
[subscriber.BraintreeCloudRegionField()] = globalSettings.BaseServiceUri.CloudRegion
|
||||
},
|
||||
Email = subscriber.BillingEmailAddress(),
|
||||
PaymentMethodNonce = paymentMethodNonce,
|
||||
PaymentMethodNonce = paymentMethodNonce
|
||||
});
|
||||
|
||||
if (customerResult.IsSuccess())
|
||||
@@ -482,7 +484,7 @@ public class SubscriberService(
|
||||
|
||||
var matchingSetupIntent = setupIntentsForUpdatedPaymentMethod.First();
|
||||
|
||||
// Find the customer's existing setup intents that should be cancelled.
|
||||
// Find the customer's existing setup intents that should be canceled.
|
||||
var existingSetupIntentsForCustomer = (await getExistingSetupIntentsForCustomer)
|
||||
.Where(si =>
|
||||
si.Status is "requires_payment_method" or "requires_confirmation" or "requires_action");
|
||||
@@ -519,7 +521,7 @@ public class SubscriberService(
|
||||
await stripeAdapter.PaymentMethodAttachAsync(token,
|
||||
new PaymentMethodAttachOptions { Customer = subscriber.GatewayCustomerId });
|
||||
|
||||
// Find the customer's existing setup intents that should be cancelled.
|
||||
// Find the customer's existing setup intents that should be canceled.
|
||||
var existingSetupIntentsForCustomer = (await getExistingSetupIntentsForCustomer)
|
||||
.Where(si =>
|
||||
si.Status is "requires_payment_method" or "requires_confirmation" or "requires_action");
|
||||
@@ -637,7 +639,8 @@ public class SubscriberService(
|
||||
logger.LogWarning("Could not infer tax ID type in country '{Country}' with tax ID '{TaxID}'.",
|
||||
taxInformation.Country,
|
||||
taxInformation.TaxId);
|
||||
throw new Exceptions.BadRequestException("billingTaxIdTypeInferenceError");
|
||||
|
||||
throw new BadRequestException("billingTaxIdTypeInferenceError");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -654,53 +657,84 @@ public class SubscriberService(
|
||||
logger.LogWarning("Invalid tax ID '{TaxID}' for country '{Country}'.",
|
||||
taxInformation.TaxId,
|
||||
taxInformation.Country);
|
||||
throw new Exceptions.BadRequestException("billingInvalidTaxIdError");
|
||||
|
||||
throw new BadRequestException("billingInvalidTaxIdError");
|
||||
|
||||
default:
|
||||
logger.LogError(e,
|
||||
"Error creating tax ID '{TaxId}' in country '{Country}' for customer '{CustomerID}'.",
|
||||
taxInformation.TaxId,
|
||||
taxInformation.Country,
|
||||
customer.Id);
|
||||
throw new Exceptions.BadRequestException("billingTaxIdCreationError");
|
||||
|
||||
throw new BadRequestException("billingTaxIdCreationError");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (featureService.IsEnabled(FeatureFlagKeys.PM19147_AutomaticTaxImprovements))
|
||||
var subscription =
|
||||
customer.Subscriptions.First(subscription => subscription.Id == subscriber.GatewaySubscriptionId);
|
||||
|
||||
var isBusinessUseSubscriber = subscriber switch
|
||||
{
|
||||
if (!string.IsNullOrEmpty(subscriber.GatewaySubscriptionId))
|
||||
Organization organization => organization.PlanType.GetProductTier() is not ProductTierType.Free and not ProductTierType.Families,
|
||||
Provider => true,
|
||||
_ => false
|
||||
};
|
||||
|
||||
var setNonUSBusinessUseToReverseCharge =
|
||||
featureService.IsEnabled(FeatureFlagKeys.PM21092_SetNonUSBusinessUseToReverseCharge);
|
||||
|
||||
if (setNonUSBusinessUseToReverseCharge && isBusinessUseSubscriber)
|
||||
{
|
||||
switch (customer)
|
||||
{
|
||||
var subscriptionGetOptions = new SubscriptionGetOptions
|
||||
case
|
||||
{
|
||||
Expand = ["customer.tax", "customer.tax_ids"]
|
||||
};
|
||||
var subscription = await stripeAdapter.SubscriptionGetAsync(subscriber.GatewaySubscriptionId, subscriptionGetOptions);
|
||||
var automaticTaxParameters = new AutomaticTaxFactoryParameters(subscriber, subscription.Items.Select(x => x.Price.Id));
|
||||
var automaticTaxStrategy = await automaticTaxFactory.CreateAsync(automaticTaxParameters);
|
||||
var automaticTaxOptions = automaticTaxStrategy.GetUpdateOptions(subscription);
|
||||
if (automaticTaxOptions?.AutomaticTax?.Enabled != null)
|
||||
Address.Country: not "US",
|
||||
TaxExempt: not StripeConstants.TaxExempt.Reverse
|
||||
}:
|
||||
await stripeAdapter.CustomerUpdateAsync(customer.Id,
|
||||
new CustomerUpdateOptions { TaxExempt = StripeConstants.TaxExempt.Reverse });
|
||||
break;
|
||||
case
|
||||
{
|
||||
await stripeAdapter.SubscriptionUpdateAsync(subscriber.GatewaySubscriptionId, automaticTaxOptions);
|
||||
}
|
||||
Address.Country: "US",
|
||||
TaxExempt: StripeConstants.TaxExempt.Reverse
|
||||
}:
|
||||
await stripeAdapter.CustomerUpdateAsync(customer.Id,
|
||||
new CustomerUpdateOptions { TaxExempt = StripeConstants.TaxExempt.None });
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (SubscriberIsEligibleForAutomaticTax(subscriber, customer))
|
||||
|
||||
if (!subscription.AutomaticTax.Enabled)
|
||||
{
|
||||
await stripeAdapter.SubscriptionUpdateAsync(subscriber.GatewaySubscriptionId,
|
||||
await stripeAdapter.SubscriptionUpdateAsync(subscription.Id,
|
||||
new SubscriptionUpdateOptions
|
||||
{
|
||||
AutomaticTax = new SubscriptionAutomaticTaxOptions { Enabled = true }
|
||||
});
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var automaticTaxShouldBeEnabled = subscriber switch
|
||||
{
|
||||
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),
|
||||
_ => false
|
||||
};
|
||||
|
||||
return;
|
||||
|
||||
bool SubscriberIsEligibleForAutomaticTax(ISubscriber localSubscriber, Customer localCustomer)
|
||||
=> !string.IsNullOrEmpty(localSubscriber.GatewaySubscriptionId) &&
|
||||
(localCustomer.Subscriptions?.Any(sub => sub.Id == localSubscriber.GatewaySubscriptionId && !sub.AutomaticTax.Enabled) ?? false) &&
|
||||
localCustomer.Tax?.AutomaticTax == StripeConstants.AutomaticTaxStatus.Supported;
|
||||
if (automaticTaxShouldBeEnabled && !subscription.AutomaticTax.Enabled)
|
||||
{
|
||||
await stripeAdapter.SubscriptionUpdateAsync(subscription.Id,
|
||||
new SubscriptionUpdateOptions
|
||||
{
|
||||
AutomaticTax = new SubscriptionAutomaticTaxOptions { Enabled = true }
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user