From 5ce7aaecc28f2f7f08660ee0ae91ad3dd353cbf8 Mon Sep 17 00:00:00 2001 From: Jonas Hendrickx Date: Thu, 10 Oct 2024 09:53:50 +0200 Subject: [PATCH] [PM-11525] Tax calculation shown to customers potentially incorrect --- .../Billing/Controllers/BillingController.cs | 52 +++++++++++++++++++ .../Requests/CalculateTaxRequestModel.cs | 10 ++++ .../Responses/CalculateTaxResponseModel.cs | 12 +++++ src/Core/Services/IStripeAdapter.cs | 1 + .../Services/Implementations/StripeAdapter.cs | 7 +++ 5 files changed, 82 insertions(+) create mode 100644 src/Api/Billing/Controllers/BillingController.cs create mode 100644 src/Api/Billing/Models/Requests/CalculateTaxRequestModel.cs create mode 100644 src/Api/Billing/Models/Responses/CalculateTaxResponseModel.cs diff --git a/src/Api/Billing/Controllers/BillingController.cs b/src/Api/Billing/Controllers/BillingController.cs new file mode 100644 index 000000000..a0f3d2c66 --- /dev/null +++ b/src/Api/Billing/Controllers/BillingController.cs @@ -0,0 +1,52 @@ +using Bit.Api.Billing.Models.Requests; +using Bit.Api.Billing.Models.Responses; +using Bit.Core.Services; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace Bit.Api.Billing.Controllers; + +[Route("billing")] +[Authorize("Application")] +public class BillingController( + IStripeAdapter stripeAdapter) : Controller +{ + [HttpPost] + [Route("calculate-tax")] + [AllowAnonymous] + public async Task CalculateTaxAsync([FromBody] CalculateTaxRequestModel requestBody) + { + var options = new Stripe.Tax.CalculationCreateOptions + { + Currency = "usd", + CustomerDetails = new() + { + Address = new() + { + PostalCode = requestBody.BillingAddressPostalCode, + Country = requestBody.BillingAddressCountry + }, + AddressSource = "billing" + }, + LineItems = new() + { + new() + { + Amount = Convert.ToInt64(requestBody.Amount * 100), + Reference = "Subscription", + }, + } + }; + var taxCalculation = await stripeAdapter.CalculateTaxAsync(options); + var response = new CalculateTaxResponseModel + { + SalesTaxRate = taxCalculation.TaxBreakdown.Any() + ? decimal.Parse(taxCalculation.TaxBreakdown.Single().TaxRateDetails.PercentageDecimal) / 100 + : 0, + SalesTaxAmount = Convert.ToDecimal(taxCalculation.TaxAmountExclusive) / 100, + TaxableAmount = Convert.ToDecimal(requestBody.Amount), + TotalAmount = Convert.ToDecimal(taxCalculation.AmountTotal) / 100, + }; + return TypedResults.Ok(response); + } +} diff --git a/src/Api/Billing/Models/Requests/CalculateTaxRequestModel.cs b/src/Api/Billing/Models/Requests/CalculateTaxRequestModel.cs new file mode 100644 index 000000000..bac2aaa00 --- /dev/null +++ b/src/Api/Billing/Models/Requests/CalculateTaxRequestModel.cs @@ -0,0 +1,10 @@ +namespace Bit.Api.Billing.Models.Requests; + +public class CalculateTaxRequestModel +{ + public decimal Amount { get; set; } + + public string BillingAddressPostalCode { get; set; } + + public string BillingAddressCountry { get; set; } +} diff --git a/src/Api/Billing/Models/Responses/CalculateTaxResponseModel.cs b/src/Api/Billing/Models/Responses/CalculateTaxResponseModel.cs new file mode 100644 index 000000000..e28876b53 --- /dev/null +++ b/src/Api/Billing/Models/Responses/CalculateTaxResponseModel.cs @@ -0,0 +1,12 @@ +namespace Bit.Api.Billing.Models.Responses; + +public class CalculateTaxResponseModel +{ + public decimal SalesTaxAmount { get; set; } + + public decimal SalesTaxRate { get; set; } + + public decimal TaxableAmount { get; set; } + + public decimal TotalAmount { get; set; } +} diff --git a/src/Core/Services/IStripeAdapter.cs b/src/Core/Services/IStripeAdapter.cs index bb57f1cd0..915ea2401 100644 --- a/src/Core/Services/IStripeAdapter.cs +++ b/src/Core/Services/IStripeAdapter.cs @@ -5,6 +5,7 @@ namespace Bit.Core.Services; public interface IStripeAdapter { + Task CalculateTaxAsync(Stripe.Tax.CalculationCreateOptions options); Task CustomerCreateAsync(Stripe.CustomerCreateOptions customerCreateOptions); Task CustomerGetAsync(string id, Stripe.CustomerGetOptions options = null); Task CustomerUpdateAsync(string id, Stripe.CustomerUpdateOptions options = null); diff --git a/src/Core/Services/Implementations/StripeAdapter.cs b/src/Core/Services/Implementations/StripeAdapter.cs index 100a47f75..56af3709c 100644 --- a/src/Core/Services/Implementations/StripeAdapter.cs +++ b/src/Core/Services/Implementations/StripeAdapter.cs @@ -5,6 +5,7 @@ namespace Bit.Core.Services; public class StripeAdapter : IStripeAdapter { + private readonly Stripe.Tax.CalculationService _taxCalculationService; private readonly Stripe.CustomerService _customerService; private readonly Stripe.SubscriptionService _subscriptionService; private readonly Stripe.InvoiceService _invoiceService; @@ -21,6 +22,7 @@ public class StripeAdapter : IStripeAdapter public StripeAdapter() { + _taxCalculationService = new Stripe.Tax.CalculationService(); _customerService = new Stripe.CustomerService(); _subscriptionService = new Stripe.SubscriptionService(); _invoiceService = new Stripe.InvoiceService(); @@ -36,6 +38,11 @@ public class StripeAdapter : IStripeAdapter _testClockService = new Stripe.TestHelpers.TestClockService(); } + public Task CalculateTaxAsync(Stripe.Tax.CalculationCreateOptions options) + { + return _taxCalculationService.CreateAsync(options); + } + public Task CustomerCreateAsync(Stripe.CustomerCreateOptions options) { return _customerService.CreateAsync(options);