1
0
mirror of https://github.com/bitwarden/server.git synced 2025-07-04 01:22:50 -05:00
This commit is contained in:
Jonas Hendrickx
2025-03-18 11:35:47 +01:00
parent 4d9ee4ab62
commit 8e5bd1fa61
7 changed files with 99 additions and 52 deletions

View File

@ -5,6 +5,7 @@ using Bit.Core.Billing.Enums;
using Bit.Core.Billing.Migration.Models;
using Bit.Core.Billing.Pricing;
using Bit.Core.Billing.Repositories;
using Bit.Core.Billing.Services;
using Bit.Core.Enums;
using Bit.Core.Repositories;
using Bit.Core.Services;
@ -20,7 +21,8 @@ public class OrganizationMigrator(
IMigrationTrackerCache migrationTrackerCache,
IOrganizationRepository organizationRepository,
IPricingClient pricingClient,
IStripeAdapter stripeAdapter) : IOrganizationMigrator
IStripeAdapter stripeAdapter,
IOrganizationAutomaticTaxStrategy automaticTaxStrategy) : IOrganizationMigrator
{
private const string _cancellationComment = "Cancelled as part of provider migration to Consolidated Billing";
@ -231,10 +233,6 @@ public class OrganizationMigrator(
var subscriptionCreateOptions = new SubscriptionCreateOptions
{
AutomaticTax = new SubscriptionAutomaticTaxOptions
{
Enabled = true
},
Customer = customer.Id,
CollectionMethod = collectionMethod,
DaysUntilDue = collectionMethod == StripeConstants.CollectionMethod.SendInvoice ? 30 : null,
@ -248,6 +246,8 @@ public class OrganizationMigrator(
TrialPeriodDays = plan.TrialPeriodDays
};
await automaticTaxStrategy.SetCreateOptionsAsync(subscriptionCreateOptions, customer);
var subscription = await stripeAdapter.SubscriptionCreateAsync(subscriptionCreateOptions);
organization.GatewaySubscriptionId = subscription.Id;

View File

@ -74,42 +74,33 @@ public class OrganizationAutomaticTaxStrategy(
private async Task<bool?> IsEnabledAsync(Subscription subscription)
{
if (subscription.AutomaticTax.Enabled ||
!subscription.Customer.HasBillingLocation() ||
await IsNonTaxableNonUsBusinessUseSubscriptionAsync(subscription))
bool shouldBeEnabled;
if (subscription.Customer.HasBillingLocation() && subscription.Customer.Address.Country == "US")
{
return null;
shouldBeEnabled = true;
}
else
{
var familyPriceIds = await _familyPriceIdsTask.Value;
shouldBeEnabled = subscription.Items.Select(item => item.Price.Id).Intersect(familyPriceIds).Any();
}
return !await IsNonTaxableNonUsBusinessUseSubscriptionAsync(subscription);
}
if (subscription.AutomaticTax.Enabled != shouldBeEnabled)
{
return shouldBeEnabled;
}
private async Task<bool> IsNonTaxableNonUsBusinessUseSubscriptionAsync(Subscription subscription)
{
var familyPriceIds = await _familyPriceIdsTask.Value;
return subscription.Customer.Address.Country != "US" &&
!subscription.Items.Select(item => item.Price.Id).Intersect(familyPriceIds).Any() &&
!subscription.Customer.TaxIds.Any();
return null;
}
private async Task<bool?> IsEnabledAsync(SubscriptionCreateOptions options, Customer customer)
{
if (!customer.HasBillingLocation() ||
await IsNonTaxableNonUsBusinessUseSubscriptionAsync(options, customer))
if (customer.HasBillingLocation() && customer.Address.Country == "US")
{
return null;
return true;
}
return !await IsNonTaxableNonUsBusinessUseSubscriptionAsync(options, customer);
}
private async Task<bool> IsNonTaxableNonUsBusinessUseSubscriptionAsync(SubscriptionCreateOptions options, Customer customer)
{
var familyPriceIds = await _familyPriceIdsTask.Value;
return customer.Address.Country != "US" &&
!options.Items.Select(item => item.Price).Intersect(familyPriceIds).Any() &&
!customer.TaxIds.Any();
return options.Items.Select(item => item.Price).Intersect(familyPriceIds).Any();
}
}

View File

@ -30,7 +30,8 @@ public class OrganizationBillingService(
ISetupIntentCache setupIntentCache,
IStripeAdapter stripeAdapter,
ISubscriberService subscriberService,
ITaxService taxService) : IOrganizationBillingService
ITaxService taxService,
IOrganizationAutomaticTaxStrategy organizationAutomaticTaxStrategy) : IOrganizationBillingService
{
public async Task Finalize(OrganizationSale sale)
{
@ -380,10 +381,6 @@ public class OrganizationBillingService(
var subscriptionCreateOptions = new SubscriptionCreateOptions
{
AutomaticTax = new SubscriptionAutomaticTaxOptions
{
Enabled = customerHasTaxInfo
},
CollectionMethod = StripeConstants.CollectionMethod.ChargeAutomatically,
Customer = customer.Id,
Items = subscriptionItemOptionsList,
@ -395,6 +392,8 @@ public class OrganizationBillingService(
TrialPeriodDays = subscriptionSetup.SkipTrial ? 0 : plan.TrialPeriodDays
};
await organizationAutomaticTaxStrategy.SetCreateOptionsAsync(subscriptionCreateOptions, customer);
return await stripeAdapter.SubscriptionCreateAsync(subscriptionCreateOptions);
}

View File

@ -24,7 +24,9 @@ public class SubscriberService(
ILogger<SubscriberService> logger,
ISetupIntentCache setupIntentCache,
IStripeAdapter stripeAdapter,
ITaxService taxService) : ISubscriberService
ITaxService taxService,
IIndividualAutomaticTaxStrategy individualAutomaticTaxStrategy,
IOrganizationAutomaticTaxStrategy organizationAutomaticTaxStrategy) : ISubscriberService
{
public async Task CancelSubscription(
ISubscriber subscriber,
@ -597,7 +599,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
{
@ -661,21 +663,17 @@ public class SubscriberService(
}
}
if (SubscriberIsEligibleForAutomaticTax(subscriber, customer))
if (!string.IsNullOrEmpty(subscriber.GatewaySubscriptionId))
{
await stripeAdapter.SubscriptionUpdateAsync(subscriber.GatewaySubscriptionId,
new SubscriptionUpdateOptions
{
AutomaticTax = new SubscriptionAutomaticTaxOptions { Enabled = true }
});
var subscription = await stripeAdapter.SubscriptionGetAsync(subscriber.GatewaySubscriptionId);
var automaticTaxOptions = subscriber.IsUser()
? await individualAutomaticTaxStrategy.GetUpdateOptionsAsync(subscription)
: await organizationAutomaticTaxStrategy.GetUpdateOptionsAsync(subscription);
if (automaticTaxOptions?.AutomaticTax?.Enabled != null)
{
await stripeAdapter.SubscriptionUpdateAsync(subscriber.GatewaySubscriptionId, automaticTaxOptions);
}
}
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;
}
public async Task VerifyBankAccount(