1
0
mirror of https://github.com/bitwarden/server.git synced 2025-07-03 09:02:48 -05:00

[PM-19147] Automatic Tax Improvements (#5545)

* Pm 19147 2 (#5544)

* Pm 19147 2 (#5544)

* Unit tests for tax strategies `GetUpdateOptions`

* Only allow automatic tax flag to be updated for complete subscription updates such as plan changes, not when upgrading additional storage, seats, etc

* unit tests for factory

* Fix build

* Automatic tax for tax estimation

* Fix stub

* Fix stub

* "customer.tax_ids" isn't expanded in some flows.

* Fix SubscriberServiceTests.cs

* BusinessUseAutomaticTaxStrategy > SetUpdateOptions tests

* Fix ProviderBillingServiceTests.cs
This commit is contained in:
Jonas Hendrickx
2025-04-02 19:47:48 +02:00
committed by GitHub
parent 10ea2cb3eb
commit b309de141d
25 changed files with 1448 additions and 108 deletions

View File

@ -1,6 +1,7 @@
using Bit.Core.Billing.Caches;
using Bit.Core.Billing.Constants;
using Bit.Core.Billing.Models;
using Bit.Core.Billing.Services.Contracts;
using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
@ -20,11 +21,13 @@ namespace Bit.Core.Billing.Services.Implementations;
public class SubscriberService(
IBraintreeGateway braintreeGateway,
IFeatureService featureService,
IGlobalSettings globalSettings,
ILogger<SubscriberService> logger,
ISetupIntentCache setupIntentCache,
IStripeAdapter stripeAdapter,
ITaxService taxService) : ISubscriberService
ITaxService taxService,
IAutomaticTaxFactory automaticTaxFactory) : ISubscriberService
{
public async Task CancelSubscription(
ISubscriber subscriber,
@ -438,7 +441,8 @@ public class SubscriberService(
ArgumentNullException.ThrowIfNull(subscriber);
ArgumentNullException.ThrowIfNull(tokenizedPaymentSource);
var customer = await GetCustomerOrThrow(subscriber);
var customerGetOptions = new CustomerGetOptions { Expand = ["tax", "tax_ids"] };
var customer = await GetCustomerOrThrow(subscriber, customerGetOptions);
var (type, token) = tokenizedPaymentSource;
@ -597,7 +601,7 @@ public class SubscriberService(
Expand = ["subscriptions", "tax", "tax_ids"]
});
await stripeAdapter.CustomerUpdateAsync(customer.Id, new CustomerUpdateOptions
customer = await stripeAdapter.CustomerUpdateAsync(customer.Id, new CustomerUpdateOptions
{
Address = new AddressOptions
{
@ -607,7 +611,8 @@ public class SubscriberService(
Line2 = taxInformation.Line2,
City = taxInformation.City,
State = taxInformation.State
}
},
Expand = ["subscriptions", "tax", "tax_ids"]
});
var taxId = customer.TaxIds?.FirstOrDefault();
@ -661,21 +666,42 @@ public class SubscriberService(
}
}
if (SubscriberIsEligibleForAutomaticTax(subscriber, customer))
if (featureService.IsEnabled(FeatureFlagKeys.PM19147_AutomaticTaxImprovements))
{
await stripeAdapter.SubscriptionUpdateAsync(subscriber.GatewaySubscriptionId,
new SubscriptionUpdateOptions
if (!string.IsNullOrEmpty(subscriber.GatewaySubscriptionId))
{
var subscriptionGetOptions = new SubscriptionGetOptions
{
AutomaticTax = new SubscriptionAutomaticTaxOptions { Enabled = true }
});
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)
{
await stripeAdapter.SubscriptionUpdateAsync(subscriber.GatewaySubscriptionId, automaticTaxOptions);
}
}
}
else
{
if (SubscriberIsEligibleForAutomaticTax(subscriber, customer))
{
await stripeAdapter.SubscriptionUpdateAsync(subscriber.GatewaySubscriptionId,
new SubscriptionUpdateOptions
{
AutomaticTax = new SubscriptionAutomaticTaxOptions { Enabled = true }
});
}
return;
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;
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;
}
}
public async Task VerifyBankAccount(