mirror of
https://github.com/bitwarden/server.git
synced 2025-07-02 16:42:50 -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:
@ -47,6 +47,8 @@ public static class StripeConstants
|
||||
public static class MetadataKeys
|
||||
{
|
||||
public const string OrganizationId = "organizationId";
|
||||
public const string ProviderId = "providerId";
|
||||
public const string UserId = "userId";
|
||||
}
|
||||
|
||||
public static class PaymentBehavior
|
||||
|
@ -21,7 +21,7 @@ public static class CustomerExtensions
|
||||
/// <param name="customer"></param>
|
||||
/// <returns></returns>
|
||||
public static bool HasTaxLocationVerified(this Customer customer) =>
|
||||
customer?.Tax?.AutomaticTax == StripeConstants.AutomaticTaxStatus.Supported;
|
||||
customer?.Tax?.AutomaticTax != StripeConstants.AutomaticTaxStatus.UnrecognizedLocation;
|
||||
|
||||
public static decimal GetBillingBalance(this Customer customer)
|
||||
{
|
||||
|
@ -4,6 +4,7 @@ using Bit.Core.Billing.Licenses.Extensions;
|
||||
using Bit.Core.Billing.Pricing;
|
||||
using Bit.Core.Billing.Services;
|
||||
using Bit.Core.Billing.Services.Implementations;
|
||||
using Bit.Core.Billing.Services.Implementations.AutomaticTax;
|
||||
|
||||
namespace Bit.Core.Billing.Extensions;
|
||||
|
||||
@ -18,6 +19,9 @@ 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();
|
||||
}
|
||||
|
@ -1,26 +0,0 @@
|
||||
using Stripe;
|
||||
|
||||
namespace Bit.Core.Billing.Extensions;
|
||||
|
||||
public static class SubscriptionCreateOptionsExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Attempts to enable automatic tax for given new subscription options.
|
||||
/// </summary>
|
||||
/// <param name="options"></param>
|
||||
/// <param name="customer">The existing customer.</param>
|
||||
/// <returns>Returns true when successful, false when conditions are not met.</returns>
|
||||
public static bool EnableAutomaticTax(this SubscriptionCreateOptions options, Customer customer)
|
||||
{
|
||||
// We might only need to check the automatic tax status.
|
||||
if (!customer.HasTaxLocationVerified() && string.IsNullOrWhiteSpace(customer.Address?.Country))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
options.DefaultTaxRates = [];
|
||||
options.AutomaticTax = new SubscriptionAutomaticTaxOptions { Enabled = true };
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
#nullable enable
|
||||
using Bit.Core.Billing.Enums;
|
||||
using Bit.Core.Entities;
|
||||
|
||||
namespace Bit.Core.Billing.Services.Contracts;
|
||||
|
||||
public class AutomaticTaxFactoryParameters
|
||||
{
|
||||
public AutomaticTaxFactoryParameters(PlanType planType)
|
||||
{
|
||||
PlanType = planType;
|
||||
}
|
||||
|
||||
public AutomaticTaxFactoryParameters(ISubscriber subscriber, IEnumerable<string> prices)
|
||||
{
|
||||
Subscriber = subscriber;
|
||||
Prices = prices;
|
||||
}
|
||||
|
||||
public AutomaticTaxFactoryParameters(IEnumerable<string> prices)
|
||||
{
|
||||
Prices = prices;
|
||||
}
|
||||
|
||||
public ISubscriber? Subscriber { get; init; }
|
||||
|
||||
public PlanType? PlanType { get; init; }
|
||||
|
||||
public IEnumerable<string>? Prices { get; init; }
|
||||
}
|
11
src/Core/Billing/Services/IAutomaticTaxFactory.cs
Normal file
11
src/Core/Billing/Services/IAutomaticTaxFactory.cs
Normal file
@ -0,0 +1,11 @@
|
||||
using Bit.Core.Billing.Services.Contracts;
|
||||
|
||||
namespace Bit.Core.Billing.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);
|
||||
}
|
33
src/Core/Billing/Services/IAutomaticTaxStrategy.cs
Normal file
33
src/Core/Billing/Services/IAutomaticTaxStrategy.cs
Normal file
@ -0,0 +1,33 @@
|
||||
#nullable enable
|
||||
using Stripe;
|
||||
|
||||
namespace Bit.Core.Billing.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);
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
#nullable enable
|
||||
using Bit.Core.Billing.Enums;
|
||||
using Bit.Core.Billing.Pricing;
|
||||
using Bit.Core.Billing.Services.Contracts;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Services;
|
||||
|
||||
namespace Bit.Core.Billing.Services.Implementations.AutomaticTax;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
@ -0,0 +1,96 @@
|
||||
#nullable enable
|
||||
using Bit.Core.Billing.Extensions;
|
||||
using Bit.Core.Services;
|
||||
using Stripe;
|
||||
|
||||
namespace Bit.Core.Billing.Services.Implementations.AutomaticTax;
|
||||
|
||||
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.HasTaxLocationVerified())
|
||||
{
|
||||
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();
|
||||
}
|
||||
}
|
@ -0,0 +1,64 @@
|
||||
#nullable enable
|
||||
using Bit.Core.Billing.Extensions;
|
||||
using Bit.Core.Services;
|
||||
using Stripe;
|
||||
|
||||
namespace Bit.Core.Billing.Services.Implementations.AutomaticTax;
|
||||
|
||||
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.HasTaxLocationVerified();
|
||||
}
|
||||
}
|
@ -1,9 +1,11 @@
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.Billing.Caches;
|
||||
using Bit.Core.Billing.Constants;
|
||||
using Bit.Core.Billing.Extensions;
|
||||
using Bit.Core.Billing.Models;
|
||||
using Bit.Core.Billing.Models.Sales;
|
||||
using Bit.Core.Billing.Pricing;
|
||||
using Bit.Core.Billing.Services.Contracts;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Repositories;
|
||||
@ -23,6 +25,7 @@ namespace Bit.Core.Billing.Services.Implementations;
|
||||
|
||||
public class OrganizationBillingService(
|
||||
IBraintreeGateway braintreeGateway,
|
||||
IFeatureService featureService,
|
||||
IGlobalSettings globalSettings,
|
||||
ILogger<OrganizationBillingService> logger,
|
||||
IOrganizationRepository organizationRepository,
|
||||
@ -30,7 +33,8 @@ public class OrganizationBillingService(
|
||||
ISetupIntentCache setupIntentCache,
|
||||
IStripeAdapter stripeAdapter,
|
||||
ISubscriberService subscriberService,
|
||||
ITaxService taxService) : IOrganizationBillingService
|
||||
ITaxService taxService,
|
||||
IAutomaticTaxFactory automaticTaxFactory) : IOrganizationBillingService
|
||||
{
|
||||
public async Task Finalize(OrganizationSale sale)
|
||||
{
|
||||
@ -143,7 +147,7 @@ public class OrganizationBillingService(
|
||||
Coupon = customerSetup.Coupon,
|
||||
Description = organization.DisplayBusinessName(),
|
||||
Email = organization.BillingEmail,
|
||||
Expand = ["tax"],
|
||||
Expand = ["tax", "tax_ids"],
|
||||
InvoiceSettings = new CustomerInvoiceSettingsOptions
|
||||
{
|
||||
CustomFields = [
|
||||
@ -369,21 +373,8 @@ public class OrganizationBillingService(
|
||||
}
|
||||
}
|
||||
|
||||
var customerHasTaxInfo = customer is
|
||||
{
|
||||
Address:
|
||||
{
|
||||
Country: not null and not "",
|
||||
PostalCode: not null and not ""
|
||||
}
|
||||
};
|
||||
|
||||
var subscriptionCreateOptions = new SubscriptionCreateOptions
|
||||
{
|
||||
AutomaticTax = new SubscriptionAutomaticTaxOptions
|
||||
{
|
||||
Enabled = customerHasTaxInfo
|
||||
},
|
||||
CollectionMethod = StripeConstants.CollectionMethod.ChargeAutomatically,
|
||||
Customer = customer.Id,
|
||||
Items = subscriptionItemOptionsList,
|
||||
@ -395,6 +386,18 @@ public class OrganizationBillingService(
|
||||
TrialPeriodDays = subscriptionSetup.SkipTrial ? 0 : plan.TrialPeriodDays
|
||||
};
|
||||
|
||||
if (featureService.IsEnabled(FeatureFlagKeys.PM19147_AutomaticTaxImprovements))
|
||||
{
|
||||
var automaticTaxParameters = new AutomaticTaxFactoryParameters(subscriptionSetup.PlanType);
|
||||
var automaticTaxStrategy = await automaticTaxFactory.CreateAsync(automaticTaxParameters);
|
||||
automaticTaxStrategy.SetCreateOptions(subscriptionCreateOptions, customer);
|
||||
}
|
||||
else
|
||||
{
|
||||
subscriptionCreateOptions.AutomaticTax ??= new SubscriptionAutomaticTaxOptions();
|
||||
subscriptionCreateOptions.AutomaticTax.Enabled = customer.HasBillingLocation();
|
||||
}
|
||||
|
||||
return await stripeAdapter.SubscriptionCreateAsync(subscriptionCreateOptions);
|
||||
}
|
||||
|
||||
|
@ -2,6 +2,7 @@
|
||||
using Bit.Core.Billing.Constants;
|
||||
using Bit.Core.Billing.Models;
|
||||
using Bit.Core.Billing.Models.Sales;
|
||||
using Bit.Core.Billing.Services.Implementations.AutomaticTax;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
@ -9,6 +10,7 @@ using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Settings;
|
||||
using Braintree;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Stripe;
|
||||
using Customer = Stripe.Customer;
|
||||
@ -20,12 +22,14 @@ using static Utilities;
|
||||
|
||||
public class PremiumUserBillingService(
|
||||
IBraintreeGateway braintreeGateway,
|
||||
IFeatureService featureService,
|
||||
IGlobalSettings globalSettings,
|
||||
ILogger<PremiumUserBillingService> logger,
|
||||
ISetupIntentCache setupIntentCache,
|
||||
IStripeAdapter stripeAdapter,
|
||||
ISubscriberService subscriberService,
|
||||
IUserRepository userRepository) : IPremiumUserBillingService
|
||||
IUserRepository userRepository,
|
||||
[FromKeyedServices(AutomaticTaxFactory.PersonalUse)] IAutomaticTaxStrategy automaticTaxStrategy) : IPremiumUserBillingService
|
||||
{
|
||||
public async Task Credit(User user, decimal amount)
|
||||
{
|
||||
@ -318,10 +322,6 @@ public class PremiumUserBillingService(
|
||||
|
||||
var subscriptionCreateOptions = new SubscriptionCreateOptions
|
||||
{
|
||||
AutomaticTax = new SubscriptionAutomaticTaxOptions
|
||||
{
|
||||
Enabled = customer.Tax?.AutomaticTax == StripeConstants.AutomaticTaxStatus.Supported,
|
||||
},
|
||||
CollectionMethod = StripeConstants.CollectionMethod.ChargeAutomatically,
|
||||
Customer = customer.Id,
|
||||
Items = subscriptionItemOptionsList,
|
||||
@ -335,6 +335,18 @@ public class PremiumUserBillingService(
|
||||
OffSession = true
|
||||
};
|
||||
|
||||
if (featureService.IsEnabled(FeatureFlagKeys.PM19147_AutomaticTaxImprovements))
|
||||
{
|
||||
automaticTaxStrategy.SetCreateOptions(subscriptionCreateOptions, customer);
|
||||
}
|
||||
else
|
||||
{
|
||||
subscriptionCreateOptions.AutomaticTax = new SubscriptionAutomaticTaxOptions
|
||||
{
|
||||
Enabled = customer.Tax?.AutomaticTax == StripeConstants.AutomaticTaxStatus.Supported,
|
||||
};
|
||||
}
|
||||
|
||||
var subscription = await stripeAdapter.SubscriptionCreateAsync(subscriptionCreateOptions);
|
||||
|
||||
if (usingPayPal)
|
||||
|
@ -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(
|
||||
|
Reference in New Issue
Block a user