1
0
mirror of https://github.com/bitwarden/server.git synced 2025-07-10 04:14:41 -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

@ -9,6 +9,8 @@ using Bit.Core.Billing.Models.Api.Responses;
using Bit.Core.Billing.Models.Business;
using Bit.Core.Billing.Pricing;
using Bit.Core.Billing.Services;
using Bit.Core.Billing.Services.Contracts;
using Bit.Core.Billing.Services.Implementations.AutomaticTax;
using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
@ -16,6 +18,7 @@ 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;
@ -36,6 +39,8 @@ public class StripePaymentService : IPaymentService
private readonly ITaxService _taxService;
private readonly ISubscriberService _subscriberService;
private readonly IPricingClient _pricingClient;
private readonly IAutomaticTaxFactory _automaticTaxFactory;
private readonly IAutomaticTaxStrategy _personalUseTaxStrategy;
public StripePaymentService(
ITransactionRepository transactionRepository,
@ -46,7 +51,9 @@ public class StripePaymentService : IPaymentService
IFeatureService featureService,
ITaxService taxService,
ISubscriberService subscriberService,
IPricingClient pricingClient)
IPricingClient pricingClient,
IAutomaticTaxFactory automaticTaxFactory,
[FromKeyedServices(AutomaticTaxFactory.PersonalUse)] IAutomaticTaxStrategy personalUseTaxStrategy)
{
_transactionRepository = transactionRepository;
_logger = logger;
@ -57,6 +64,8 @@ public class StripePaymentService : IPaymentService
_taxService = taxService;
_subscriberService = subscriberService;
_pricingClient = pricingClient;
_automaticTaxFactory = automaticTaxFactory;
_personalUseTaxStrategy = personalUseTaxStrategy;
}
private async Task ChangeOrganizationSponsorship(
@ -91,9 +100,7 @@ public class StripePaymentService : IPaymentService
SubscriptionUpdate subscriptionUpdate, bool invoiceNow = false)
{
// remember, when in doubt, throw
var subGetOptions = new SubscriptionGetOptions();
// subGetOptions.AddExpand("customer");
subGetOptions.AddExpand("customer.tax");
var subGetOptions = new SubscriptionGetOptions { Expand = ["customer.tax", "customer.tax_ids"] };
var sub = await _stripeAdapter.SubscriptionGetAsync(subscriber.GatewaySubscriptionId, subGetOptions);
if (sub == null)
{
@ -124,7 +131,19 @@ public class StripePaymentService : IPaymentService
new SubscriptionPendingInvoiceItemIntervalOptions { Interval = "month" };
}
subUpdateOptions.EnableAutomaticTax(sub.Customer, sub);
if (subscriptionUpdate is CompleteSubscriptionUpdate)
{
if (_featureService.IsEnabled(FeatureFlagKeys.PM19147_AutomaticTaxImprovements))
{
var automaticTaxParameters = new AutomaticTaxFactoryParameters(subscriber, updatedItemOptions.Select(x => x.Plan ?? x.Price));
var automaticTaxStrategy = await _automaticTaxFactory.CreateAsync(automaticTaxParameters);
automaticTaxStrategy.SetUpdateOptions(subUpdateOptions, sub);
}
else
{
subUpdateOptions.EnableAutomaticTax(sub.Customer, sub);
}
}
if (!subscriptionUpdate.UpdateNeeded(sub))
{
@ -811,21 +830,46 @@ public class StripePaymentService : IPaymentService
});
}
if (!string.IsNullOrEmpty(subscriber.GatewaySubscriptionId) &&
customer.Subscriptions.Any(sub =>
sub.Id == subscriber.GatewaySubscriptionId &&
!sub.AutomaticTax.Enabled) &&
customer.HasTaxLocationVerified())
if (_featureService.IsEnabled(FeatureFlagKeys.PM19147_AutomaticTaxImprovements))
{
var subscriptionUpdateOptions = new SubscriptionUpdateOptions
if (!string.IsNullOrEmpty(subscriber.GatewaySubscriptionId))
{
AutomaticTax = new SubscriptionAutomaticTaxOptions { Enabled = true },
DefaultTaxRates = []
};
var subscriptionGetOptions = new SubscriptionGetOptions
{
Expand = ["customer.tax", "customer.tax_ids"]
};
var subscription = await _stripeAdapter.SubscriptionGetAsync(subscriber.GatewaySubscriptionId, subscriptionGetOptions);
_ = await _stripeAdapter.SubscriptionUpdateAsync(
subscriber.GatewaySubscriptionId,
subscriptionUpdateOptions);
var automaticTaxParameters = new AutomaticTaxFactoryParameters(subscriber, subscription.Items.Select(x => x.Price.Id));
var automaticTaxStrategy = await _automaticTaxFactory.CreateAsync(automaticTaxParameters);
var subscriptionUpdateOptions = automaticTaxStrategy.GetUpdateOptions(subscription);
if (subscriptionUpdateOptions != null)
{
_ = await _stripeAdapter.SubscriptionUpdateAsync(
subscriber.GatewaySubscriptionId,
subscriptionUpdateOptions);
}
}
}
else
{
if (!string.IsNullOrEmpty(subscriber.GatewaySubscriptionId) &&
customer.Subscriptions.Any(sub =>
sub.Id == subscriber.GatewaySubscriptionId &&
!sub.AutomaticTax.Enabled) &&
customer.HasTaxLocationVerified())
{
var subscriptionUpdateOptions = new SubscriptionUpdateOptions
{
AutomaticTax = new SubscriptionAutomaticTaxOptions { Enabled = true },
DefaultTaxRates = []
};
_ = await _stripeAdapter.SubscriptionUpdateAsync(
subscriber.GatewaySubscriptionId,
subscriptionUpdateOptions);
}
}
}
catch
@ -1214,6 +1258,8 @@ public class StripePaymentService : IPaymentService
}
}
_personalUseTaxStrategy.SetInvoiceCreatePreviewOptions(options);
try
{
var invoice = await _stripeAdapter.InvoiceCreatePreviewAsync(options);
@ -1256,10 +1302,6 @@ public class StripePaymentService : IPaymentService
var options = new InvoiceCreatePreviewOptions
{
AutomaticTax = new InvoiceAutomaticTaxOptions
{
Enabled = true,
},
Currency = "usd",
SubscriptionDetails = new InvoiceSubscriptionDetailsOptions
{
@ -1347,9 +1389,11 @@ public class StripePaymentService : IPaymentService
];
}
Customer gatewayCustomer = null;
if (!string.IsNullOrWhiteSpace(gatewayCustomerId))
{
var gatewayCustomer = await _stripeAdapter.CustomerGetAsync(gatewayCustomerId);
gatewayCustomer = await _stripeAdapter.CustomerGetAsync(gatewayCustomerId);
if (gatewayCustomer.Discount != null)
{
@ -1367,6 +1411,10 @@ public class StripePaymentService : IPaymentService
}
}
var automaticTaxFactoryParameters = new AutomaticTaxFactoryParameters(parameters.PasswordManager.Plan);
var automaticTaxStrategy = await _automaticTaxFactory.CreateAsync(automaticTaxFactoryParameters);
automaticTaxStrategy.SetInvoiceCreatePreviewOptions(options);
try
{
var invoice = await _stripeAdapter.InvoiceCreatePreviewAsync(options);