mirror of
https://github.com/bitwarden/server.git
synced 2025-04-16 02:28:13 -05:00
WIP
This commit is contained in:
parent
8768e69f76
commit
24826ec3bd
@ -28,6 +28,7 @@ public class RemoveOrganizationFromProviderCommand : IRemoveOrganizationFromProv
|
||||
private readonly ISubscriberService _subscriberService;
|
||||
private readonly IHasConfirmedOwnersExceptQuery _hasConfirmedOwnersExceptQuery;
|
||||
private readonly IPricingClient _pricingClient;
|
||||
private readonly IOrganizationAutomaticTaxStrategy _organizationAutomaticTaxStrategy;
|
||||
|
||||
public RemoveOrganizationFromProviderCommand(
|
||||
IEventService eventService,
|
||||
@ -40,7 +41,8 @@ public class RemoveOrganizationFromProviderCommand : IRemoveOrganizationFromProv
|
||||
IProviderBillingService providerBillingService,
|
||||
ISubscriberService subscriberService,
|
||||
IHasConfirmedOwnersExceptQuery hasConfirmedOwnersExceptQuery,
|
||||
IPricingClient pricingClient)
|
||||
IPricingClient pricingClient,
|
||||
IOrganizationAutomaticTaxStrategy organizationAutomaticTaxStrategy)
|
||||
{
|
||||
_eventService = eventService;
|
||||
_mailService = mailService;
|
||||
@ -53,6 +55,7 @@ public class RemoveOrganizationFromProviderCommand : IRemoveOrganizationFromProv
|
||||
_subscriberService = subscriberService;
|
||||
_hasConfirmedOwnersExceptQuery = hasConfirmedOwnersExceptQuery;
|
||||
_pricingClient = pricingClient;
|
||||
_organizationAutomaticTaxStrategy = organizationAutomaticTaxStrategy;
|
||||
}
|
||||
|
||||
public async Task RemoveOrganizationFromProvider(
|
||||
@ -107,7 +110,7 @@ public class RemoveOrganizationFromProviderCommand : IRemoveOrganizationFromProv
|
||||
organization.IsValidClient() &&
|
||||
!string.IsNullOrEmpty(organization.GatewayCustomerId))
|
||||
{
|
||||
await _stripeAdapter.CustomerUpdateAsync(organization.GatewayCustomerId, new CustomerUpdateOptions
|
||||
var customer = await _stripeAdapter.CustomerUpdateAsync(organization.GatewayCustomerId, new CustomerUpdateOptions
|
||||
{
|
||||
Description = string.Empty,
|
||||
Email = organization.BillingEmail
|
||||
@ -120,7 +123,6 @@ public class RemoveOrganizationFromProviderCommand : IRemoveOrganizationFromProv
|
||||
Customer = organization.GatewayCustomerId,
|
||||
CollectionMethod = StripeConstants.CollectionMethod.SendInvoice,
|
||||
DaysUntilDue = 30,
|
||||
AutomaticTax = new SubscriptionAutomaticTaxOptions { Enabled = true },
|
||||
Metadata = new Dictionary<string, string>
|
||||
{
|
||||
{ "organizationId", organization.Id.ToString() }
|
||||
@ -130,6 +132,8 @@ public class RemoveOrganizationFromProviderCommand : IRemoveOrganizationFromProv
|
||||
Items = [new SubscriptionItemOptions { Price = plan.PasswordManager.StripeSeatPlanId, Quantity = organization.Seats }]
|
||||
};
|
||||
|
||||
await _organizationAutomaticTaxStrategy.SetCreateOptionsAsync(subscriptionCreateOptions, customer);
|
||||
|
||||
var subscription = await _stripeAdapter.SubscriptionCreateAsync(subscriptionCreateOptions);
|
||||
|
||||
organization.GatewaySubscriptionId = subscription.Id;
|
||||
|
@ -40,7 +40,8 @@ public class ProviderBillingService(
|
||||
IProviderUserRepository providerUserRepository,
|
||||
IStripeAdapter stripeAdapter,
|
||||
ISubscriberService subscriberService,
|
||||
ITaxService taxService) : IProviderBillingService
|
||||
ITaxService taxService,
|
||||
IOrganizationAutomaticTaxStrategy organizationAutomaticTaxStrategy) : IProviderBillingService
|
||||
{
|
||||
[RequireFeature(FeatureFlagKeys.P15179_AddExistingOrgsFromProviderPortal)]
|
||||
public async Task AddExistingOrganization(
|
||||
@ -589,10 +590,6 @@ public class ProviderBillingService(
|
||||
|
||||
var subscriptionCreateOptions = new SubscriptionCreateOptions
|
||||
{
|
||||
AutomaticTax = new SubscriptionAutomaticTaxOptions
|
||||
{
|
||||
Enabled = true
|
||||
},
|
||||
CollectionMethod = StripeConstants.CollectionMethod.SendInvoice,
|
||||
Customer = customer.Id,
|
||||
DaysUntilDue = 30,
|
||||
@ -605,6 +602,8 @@ public class ProviderBillingService(
|
||||
ProrationBehavior = StripeConstants.ProrationBehavior.CreateProrations
|
||||
};
|
||||
|
||||
await organizationAutomaticTaxStrategy.SetCreateOptionsAsync(subscriptionCreateOptions, customer);
|
||||
|
||||
try
|
||||
{
|
||||
var subscription = await stripeAdapter.SubscriptionCreateAsync(subscriptionCreateOptions);
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -1,35 +0,0 @@
|
||||
using Stripe;
|
||||
|
||||
namespace Bit.Core.Billing.Extensions;
|
||||
|
||||
public static class SubscriptionUpdateOptionsExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Attempts to enable automatic tax for given subscription options.
|
||||
/// </summary>
|
||||
/// <param name="options"></param>
|
||||
/// <param name="customer">The existing customer to which the subscription belongs.</param>
|
||||
/// <param name="subscription">The existing subscription.</param>
|
||||
/// <returns>Returns true when successful, false when conditions are not met.</returns>
|
||||
public static bool EnableAutomaticTax(
|
||||
this SubscriptionUpdateOptions options,
|
||||
Customer customer,
|
||||
Subscription subscription)
|
||||
{
|
||||
if (subscription.AutomaticTax.Enabled)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
@ -5,6 +5,6 @@ namespace Bit.Core.Billing.Services;
|
||||
public interface IAutomaticTaxStrategy
|
||||
{
|
||||
Task<SubscriptionUpdateOptions> GetUpdateOptionsAsync(Subscription subscription);
|
||||
Task SetCreateOptionsAsync(SubscriptionCreateOptions options, Customer customer = null);
|
||||
Task SetCreateOptionsAsync(SubscriptionCreateOptions options, Customer customer);
|
||||
Task SetUpdateOptionsAsync(SubscriptionUpdateOptions options, Subscription subscription);
|
||||
}
|
||||
|
@ -1,13 +1,20 @@
|
||||
using Stripe;
|
||||
using Bit.Core.Billing.Constants;
|
||||
using Stripe;
|
||||
|
||||
namespace Bit.Core.Billing.Services.Implementations.AutomaticTax;
|
||||
|
||||
public class IndividualAutomaticTaxStrategy : IIndividualAutomaticTaxStrategy
|
||||
{
|
||||
public Task SetCreateOptionsAsync(SubscriptionCreateOptions options, Customer customer = null)
|
||||
public Task SetCreateOptionsAsync(SubscriptionCreateOptions options, Customer customer)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(options);
|
||||
options.AutomaticTax = new SubscriptionAutomaticTaxOptions { Enabled = true };
|
||||
ArgumentNullException.ThrowIfNull(customer);
|
||||
|
||||
options.AutomaticTax = new SubscriptionAutomaticTaxOptions
|
||||
{
|
||||
Enabled = ShouldEnable(customer)
|
||||
};
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
@ -16,18 +23,23 @@ public class IndividualAutomaticTaxStrategy : IIndividualAutomaticTaxStrategy
|
||||
|
||||
ArgumentNullException.ThrowIfNull(options);
|
||||
|
||||
if (subscription.AutomaticTax.Enabled)
|
||||
if (subscription.AutomaticTax.Enabled == ShouldEnable(subscription.Customer))
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
options.AutomaticTax = new SubscriptionAutomaticTaxOptions { Enabled = true };
|
||||
options.AutomaticTax = new SubscriptionAutomaticTaxOptions
|
||||
{
|
||||
Enabled = ShouldEnable(subscription.Customer)
|
||||
};
|
||||
options.DefaultTaxRates = [];
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task<SubscriptionUpdateOptions> GetUpdateOptionsAsync(Subscription subscription)
|
||||
{
|
||||
if (subscription.AutomaticTax.Enabled)
|
||||
if (subscription.AutomaticTax.Enabled == ShouldEnable(subscription.Customer))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
@ -36,10 +48,16 @@ public class IndividualAutomaticTaxStrategy : IIndividualAutomaticTaxStrategy
|
||||
{
|
||||
AutomaticTax = new SubscriptionAutomaticTaxOptions
|
||||
{
|
||||
Enabled = true
|
||||
}
|
||||
Enabled = ShouldEnable(subscription.Customer),
|
||||
},
|
||||
DefaultTaxRates = []
|
||||
};
|
||||
|
||||
return Task.FromResult(options);
|
||||
}
|
||||
|
||||
private static bool ShouldEnable(Customer customer)
|
||||
{
|
||||
return customer.Tax?.AutomaticTax == StripeConstants.AutomaticTaxStatus.Supported;
|
||||
}
|
||||
}
|
||||
|
@ -8,6 +8,15 @@ namespace Bit.Core.Billing.Services.Implementations.AutomaticTax;
|
||||
public class OrganizationAutomaticTaxStrategy(
|
||||
IPricingClient pricingClient) : IOrganizationAutomaticTaxStrategy
|
||||
{
|
||||
private readonly Lazy<Task<IEnumerable<string>>> _familyPriceIdsTask = 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<SubscriptionUpdateOptions> GetUpdateOptionsAsync(Subscription subscription)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(subscription);
|
||||
@ -23,13 +32,14 @@ public class OrganizationAutomaticTaxStrategy(
|
||||
AutomaticTax = new SubscriptionAutomaticTaxOptions
|
||||
{
|
||||
Enabled = isEnabled.Value
|
||||
}
|
||||
},
|
||||
DefaultTaxRates = []
|
||||
};
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
public async Task SetCreateOptionsAsync(SubscriptionCreateOptions options, Customer customer = null)
|
||||
public async Task SetCreateOptionsAsync(SubscriptionCreateOptions options, Customer customer)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(options);
|
||||
ArgumentNullException.ThrowIfNull(customer);
|
||||
@ -59,6 +69,7 @@ public class OrganizationAutomaticTaxStrategy(
|
||||
{
|
||||
Enabled = isEnabled.Value
|
||||
};
|
||||
options.DefaultTaxRates = [];
|
||||
}
|
||||
|
||||
private async Task<bool?> IsEnabledAsync(Subscription subscription)
|
||||
@ -75,13 +86,9 @@ public class OrganizationAutomaticTaxStrategy(
|
||||
|
||||
private async Task<bool> IsNonTaxableNonUsBusinessUseSubscriptionAsync(Subscription subscription)
|
||||
{
|
||||
var familyPriceIds = (await Task.WhenAll(
|
||||
pricingClient.GetPlanOrThrow(PlanType.FamiliesAnnually2019),
|
||||
pricingClient.GetPlanOrThrow(PlanType.FamiliesAnnually)))
|
||||
.Select(plan => plan.PasswordManager.StripePlanId);
|
||||
var familyPriceIds = await _familyPriceIdsTask.Value;
|
||||
|
||||
return subscription.Customer.Address.Country != "US" &&
|
||||
subscription.IsOrganization() &&
|
||||
!subscription.Items.Select(item => item.Price.Id).Intersect(familyPriceIds).Any() &&
|
||||
!subscription.Customer.TaxIds.Any();
|
||||
}
|
||||
@ -99,10 +106,7 @@ public class OrganizationAutomaticTaxStrategy(
|
||||
|
||||
private async Task<bool> IsNonTaxableNonUsBusinessUseSubscriptionAsync(SubscriptionCreateOptions options, Customer customer)
|
||||
{
|
||||
var familyPriceIds = (await Task.WhenAll(
|
||||
pricingClient.GetPlanOrThrow(PlanType.FamiliesAnnually2019),
|
||||
pricingClient.GetPlanOrThrow(PlanType.FamiliesAnnually)))
|
||||
.Select(plan => plan.PasswordManager.StripePlanId);
|
||||
var familyPriceIds = await _familyPriceIdsTask.Value;
|
||||
|
||||
return customer.Address.Country != "US" &&
|
||||
!options.Items.Select(item => item.Price).Intersect(familyPriceIds).Any() &&
|
||||
|
@ -25,7 +25,8 @@ public class PremiumUserBillingService(
|
||||
ISetupIntentCache setupIntentCache,
|
||||
IStripeAdapter stripeAdapter,
|
||||
ISubscriberService subscriberService,
|
||||
IUserRepository userRepository) : IPremiumUserBillingService
|
||||
IUserRepository userRepository,
|
||||
IIndividualAutomaticTaxStrategy individualAutomaticTaxStrategy) : IPremiumUserBillingService
|
||||
{
|
||||
public async Task Credit(User user, decimal amount)
|
||||
{
|
||||
@ -318,10 +319,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 +332,8 @@ public class PremiumUserBillingService(
|
||||
OffSession = true
|
||||
};
|
||||
|
||||
await individualAutomaticTaxStrategy.SetCreateOptionsAsync(subscriptionCreateOptions, customer);
|
||||
|
||||
var subscription = await stripeAdapter.SubscriptionCreateAsync(subscriptionCreateOptions);
|
||||
|
||||
if (usingPayPal)
|
||||
|
@ -36,6 +36,8 @@ public class StripePaymentService : IPaymentService
|
||||
private readonly ITaxService _taxService;
|
||||
private readonly ISubscriberService _subscriberService;
|
||||
private readonly IPricingClient _pricingClient;
|
||||
private readonly IIndividualAutomaticTaxStrategy _individualAutomaticTaxStrategy;
|
||||
private readonly IOrganizationAutomaticTaxStrategy _organizationAutomaticTaxStrategy;
|
||||
|
||||
public StripePaymentService(
|
||||
ITransactionRepository transactionRepository,
|
||||
@ -46,7 +48,9 @@ public class StripePaymentService : IPaymentService
|
||||
IFeatureService featureService,
|
||||
ITaxService taxService,
|
||||
ISubscriberService subscriberService,
|
||||
IPricingClient pricingClient)
|
||||
IPricingClient pricingClient,
|
||||
IIndividualAutomaticTaxStrategy individualAutomaticTaxStrategy,
|
||||
IOrganizationAutomaticTaxStrategy organizationAutomaticTaxStrategy)
|
||||
{
|
||||
_transactionRepository = transactionRepository;
|
||||
_logger = logger;
|
||||
@ -57,6 +61,8 @@ public class StripePaymentService : IPaymentService
|
||||
_taxService = taxService;
|
||||
_subscriberService = subscriberService;
|
||||
_pricingClient = pricingClient;
|
||||
_individualAutomaticTaxStrategy = individualAutomaticTaxStrategy;
|
||||
_organizationAutomaticTaxStrategy = organizationAutomaticTaxStrategy;
|
||||
}
|
||||
|
||||
private async Task ChangeOrganizationSponsorship(
|
||||
@ -124,7 +130,7 @@ public class StripePaymentService : IPaymentService
|
||||
new SubscriptionPendingInvoiceItemIntervalOptions { Interval = "month" };
|
||||
}
|
||||
|
||||
subUpdateOptions.EnableAutomaticTax(sub.Customer, sub);
|
||||
await _organizationAutomaticTaxStrategy.SetUpdateOptionsAsync(subUpdateOptions, sub);
|
||||
|
||||
if (!subscriptionUpdate.UpdateNeeded(sub))
|
||||
{
|
||||
@ -812,20 +818,20 @@ public class StripePaymentService : IPaymentService
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(subscriber.GatewaySubscriptionId) &&
|
||||
customer.Subscriptions.Any(sub =>
|
||||
sub.Id == subscriber.GatewaySubscriptionId &&
|
||||
!sub.AutomaticTax.Enabled) &&
|
||||
customer.HasTaxLocationVerified())
|
||||
customer.Subscriptions.Any(sub => sub.Id == subscriber.GatewaySubscriptionId))
|
||||
{
|
||||
var subscriptionUpdateOptions = new SubscriptionUpdateOptions
|
||||
{
|
||||
AutomaticTax = new SubscriptionAutomaticTaxOptions { Enabled = true },
|
||||
DefaultTaxRates = []
|
||||
};
|
||||
var subscription = await _stripeAdapter.SubscriptionGetAsync(subscriber.GatewaySubscriptionId);
|
||||
|
||||
_ = await _stripeAdapter.SubscriptionUpdateAsync(
|
||||
subscriber.GatewaySubscriptionId,
|
||||
subscriptionUpdateOptions);
|
||||
var subscriptionUpdateOptions = subscriber is User
|
||||
? await _individualAutomaticTaxStrategy.GetUpdateOptionsAsync(subscription)
|
||||
: await _organizationAutomaticTaxStrategy.GetUpdateOptionsAsync(subscription);
|
||||
|
||||
if (subscriptionUpdateOptions != null)
|
||||
{
|
||||
_ = await _stripeAdapter.SubscriptionUpdateAsync(
|
||||
subscriber.GatewaySubscriptionId,
|
||||
subscriptionUpdateOptions);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
|
Loading…
x
Reference in New Issue
Block a user