mirror of
https://github.com/bitwarden/server.git
synced 2025-03-12 13:29:14 +01:00
[PM-18028] Enabling automatic tax for customers without country or with manual tax rates set (#5376)
This commit is contained in:
parent
9f4aa1ab2b
commit
4bef2357d5
@ -1,6 +1,7 @@
|
||||
using Bit.Billing.Constants;
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.Repositories;
|
||||
using Bit.Core.Billing.Extensions;
|
||||
using Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.Interfaces;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
@ -160,16 +161,16 @@ public class UpcomingInvoiceHandler : IUpcomingInvoiceHandler
|
||||
|
||||
private async Task<Subscription> TryEnableAutomaticTaxAsync(Subscription subscription)
|
||||
{
|
||||
if (subscription.AutomaticTax.Enabled)
|
||||
var customerGetOptions = new CustomerGetOptions { Expand = ["tax"] };
|
||||
var customer = await _stripeFacade.GetCustomer(subscription.CustomerId, customerGetOptions);
|
||||
|
||||
var subscriptionUpdateOptions = new SubscriptionUpdateOptions();
|
||||
|
||||
if (!subscriptionUpdateOptions.EnableAutomaticTax(customer, subscription))
|
||||
{
|
||||
return subscription;
|
||||
}
|
||||
|
||||
var subscriptionUpdateOptions = new SubscriptionUpdateOptions
|
||||
{
|
||||
AutomaticTax = new SubscriptionAutomaticTaxOptions { Enabled = true }
|
||||
};
|
||||
|
||||
return await _stripeFacade.UpdateSubscription(subscription.Id, subscriptionUpdateOptions);
|
||||
}
|
||||
|
||||
|
16
src/Core/Billing/Extensions/CustomerExtensions.cs
Normal file
16
src/Core/Billing/Extensions/CustomerExtensions.cs
Normal file
@ -0,0 +1,16 @@
|
||||
using Bit.Core.Billing.Constants;
|
||||
using Stripe;
|
||||
|
||||
namespace Bit.Core.Billing.Extensions;
|
||||
|
||||
public static class CustomerExtensions
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Determines if a Stripe customer supports automatic tax
|
||||
/// </summary>
|
||||
/// <param name="customer"></param>
|
||||
/// <returns></returns>
|
||||
public static bool HasTaxLocationVerified(this Customer customer) =>
|
||||
customer?.Tax?.AutomaticTax == StripeConstants.AutomaticTaxStatus.Supported;
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
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,35 @@
|
||||
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;
|
||||
}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
using Stripe;
|
||||
|
||||
namespace Bit.Core.Billing.Extensions;
|
||||
|
||||
public static class UpcomingInvoiceOptionsExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Attempts to enable automatic tax for given upcoming invoice options.
|
||||
/// </summary>
|
||||
/// <param name="options"></param>
|
||||
/// <param name="customer">The existing customer to which the upcoming invoice belongs.</param>
|
||||
/// <param name="subscription">The existing subscription to which the upcoming invoice belongs.</param>
|
||||
/// <returns>Returns true when successful, false when conditions are not met.</returns>
|
||||
public static bool EnableAutomaticTax(
|
||||
this UpcomingInvoiceOptions options,
|
||||
Customer customer,
|
||||
Subscription subscription)
|
||||
{
|
||||
if (subscription != null && 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.AutomaticTax = new InvoiceAutomaticTaxOptions { Enabled = true };
|
||||
options.SubscriptionDefaultTaxRates = [];
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
@ -320,7 +320,7 @@ public class PremiumUserBillingService(
|
||||
{
|
||||
AutomaticTax = new SubscriptionAutomaticTaxOptions
|
||||
{
|
||||
Enabled = true
|
||||
Enabled = customer.Tax?.AutomaticTax == StripeConstants.AutomaticTaxStatus.Supported,
|
||||
},
|
||||
CollectionMethod = StripeConstants.CollectionMethod.ChargeAutomatically,
|
||||
Customer = customer.Id,
|
||||
|
@ -661,11 +661,21 @@ public class SubscriberService(
|
||||
}
|
||||
}
|
||||
|
||||
await stripeAdapter.SubscriptionUpdateAsync(subscriber.GatewaySubscriptionId,
|
||||
new SubscriptionUpdateOptions
|
||||
{
|
||||
AutomaticTax = new SubscriptionAutomaticTaxOptions { Enabled = true },
|
||||
});
|
||||
if (SubscriberIsEligibleForAutomaticTax(subscriber, customer))
|
||||
{
|
||||
await stripeAdapter.SubscriptionUpdateAsync(subscriber.GatewaySubscriptionId,
|
||||
new SubscriptionUpdateOptions
|
||||
{
|
||||
AutomaticTax = new SubscriptionAutomaticTaxOptions { Enabled = true }
|
||||
});
|
||||
}
|
||||
|
||||
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(
|
||||
|
@ -177,7 +177,7 @@ public class StripePaymentService : IPaymentService
|
||||
customer = await _stripeAdapter.CustomerCreateAsync(customerCreateOptions);
|
||||
subCreateOptions.AddExpand("latest_invoice.payment_intent");
|
||||
subCreateOptions.Customer = customer.Id;
|
||||
subCreateOptions.AutomaticTax = new SubscriptionAutomaticTaxOptions { Enabled = true };
|
||||
subCreateOptions.EnableAutomaticTax(customer);
|
||||
|
||||
subscription = await _stripeAdapter.SubscriptionCreateAsync(subCreateOptions);
|
||||
if (subscription.Status == "incomplete" && subscription.LatestInvoice?.PaymentIntent != null)
|
||||
@ -358,10 +358,9 @@ public class StripePaymentService : IPaymentService
|
||||
customer = await _stripeAdapter.CustomerUpdateAsync(org.GatewayCustomerId, customerUpdateOptions);
|
||||
}
|
||||
|
||||
var subCreateOptions = new OrganizationUpgradeSubscriptionOptions(customer.Id, org, plan, upgrade)
|
||||
{
|
||||
AutomaticTax = new SubscriptionAutomaticTaxOptions { Enabled = true }
|
||||
};
|
||||
var subCreateOptions = new OrganizationUpgradeSubscriptionOptions(customer.Id, org, plan, upgrade);
|
||||
|
||||
subCreateOptions.EnableAutomaticTax(customer);
|
||||
|
||||
var (stripePaymentMethod, paymentMethodType) = IdentifyPaymentMethod(customer, subCreateOptions);
|
||||
|
||||
@ -520,10 +519,6 @@ public class StripePaymentService : IPaymentService
|
||||
|
||||
var customerCreateOptions = new CustomerCreateOptions
|
||||
{
|
||||
Tax = new CustomerTaxOptions
|
||||
{
|
||||
ValidateLocation = StripeConstants.ValidateTaxLocationTiming.Immediately
|
||||
},
|
||||
Description = user.Name,
|
||||
Email = user.Email,
|
||||
Metadata = stripeCustomerMetadata,
|
||||
@ -561,7 +556,6 @@ public class StripePaymentService : IPaymentService
|
||||
|
||||
var subCreateOptions = new SubscriptionCreateOptions
|
||||
{
|
||||
AutomaticTax = new SubscriptionAutomaticTaxOptions { Enabled = true },
|
||||
Customer = customer.Id,
|
||||
Items = [],
|
||||
Metadata = new Dictionary<string, string>
|
||||
@ -581,10 +575,12 @@ public class StripePaymentService : IPaymentService
|
||||
subCreateOptions.Items.Add(new SubscriptionItemOptions
|
||||
{
|
||||
Plan = StoragePlanId,
|
||||
Quantity = additionalStorageGb,
|
||||
Quantity = additionalStorageGb
|
||||
});
|
||||
}
|
||||
|
||||
subCreateOptions.EnableAutomaticTax(customer);
|
||||
|
||||
var subscription = await ChargeForNewSubscriptionAsync(user, customer, createdStripeCustomer,
|
||||
stripePaymentMethod, paymentMethodType, subCreateOptions, braintreeCustomer);
|
||||
|
||||
@ -622,7 +618,10 @@ public class StripePaymentService : IPaymentService
|
||||
SubscriptionItems = ToInvoiceSubscriptionItemOptions(subCreateOptions.Items)
|
||||
});
|
||||
|
||||
previewInvoice.AutomaticTax = new InvoiceAutomaticTax { Enabled = true };
|
||||
if (customer.HasTaxLocationVerified())
|
||||
{
|
||||
previewInvoice.AutomaticTax = new InvoiceAutomaticTax { Enabled = true };
|
||||
}
|
||||
|
||||
if (previewInvoice.AmountDue > 0)
|
||||
{
|
||||
@ -680,12 +679,10 @@ public class StripePaymentService : IPaymentService
|
||||
Customer = customer.Id,
|
||||
SubscriptionItems = ToInvoiceSubscriptionItemOptions(subCreateOptions.Items),
|
||||
SubscriptionDefaultTaxRates = subCreateOptions.DefaultTaxRates,
|
||||
AutomaticTax = new InvoiceAutomaticTaxOptions
|
||||
{
|
||||
Enabled = true
|
||||
}
|
||||
};
|
||||
|
||||
upcomingInvoiceOptions.EnableAutomaticTax(customer, null);
|
||||
|
||||
var previewInvoice = await _stripeAdapter.InvoiceUpcomingAsync(upcomingInvoiceOptions);
|
||||
|
||||
if (previewInvoice.AmountDue > 0)
|
||||
@ -804,11 +801,7 @@ public class StripePaymentService : IPaymentService
|
||||
Items = updatedItemOptions,
|
||||
ProrationBehavior = invoiceNow ? Constants.AlwaysInvoice : Constants.CreateProrations,
|
||||
DaysUntilDue = daysUntilDue ?? 1,
|
||||
CollectionMethod = "send_invoice",
|
||||
AutomaticTax = new SubscriptionAutomaticTaxOptions
|
||||
{
|
||||
Enabled = true
|
||||
}
|
||||
CollectionMethod = "send_invoice"
|
||||
};
|
||||
if (!invoiceNow && isAnnualPlan && sub.Status.Trim() != "trialing")
|
||||
{
|
||||
@ -816,6 +809,8 @@ public class StripePaymentService : IPaymentService
|
||||
new SubscriptionPendingInvoiceItemIntervalOptions { Interval = "month" };
|
||||
}
|
||||
|
||||
subUpdateOptions.EnableAutomaticTax(sub.Customer, sub);
|
||||
|
||||
if (!subscriptionUpdate.UpdateNeeded(sub))
|
||||
{
|
||||
// No need to update subscription, quantity matches
|
||||
@ -1500,11 +1495,13 @@ public class StripePaymentService : IPaymentService
|
||||
if (!string.IsNullOrEmpty(subscriber.GatewaySubscriptionId) &&
|
||||
customer.Subscriptions.Any(sub =>
|
||||
sub.Id == subscriber.GatewaySubscriptionId &&
|
||||
!sub.AutomaticTax.Enabled))
|
||||
!sub.AutomaticTax.Enabled) &&
|
||||
customer.HasTaxLocationVerified())
|
||||
{
|
||||
var subscriptionUpdateOptions = new SubscriptionUpdateOptions
|
||||
{
|
||||
AutomaticTax = new SubscriptionAutomaticTaxOptions { Enabled = true }
|
||||
AutomaticTax = new SubscriptionAutomaticTaxOptions { Enabled = true },
|
||||
DefaultTaxRates = []
|
||||
};
|
||||
|
||||
_ = await _stripeAdapter.SubscriptionUpdateAsync(
|
||||
|
Loading…
Reference in New Issue
Block a user