mirror of
https://github.com/bitwarden/server.git
synced 2024-11-21 12:05:42 +01:00
Sales tax id type detection
This commit is contained in:
parent
e2a5b93bed
commit
e77430823c
@ -0,0 +1,151 @@
|
||||
using Bit.Core.Billing.Services;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Commercial.Core.Test.Billing;
|
||||
|
||||
[SutProviderCustomize]
|
||||
public class TaxServiceTests
|
||||
{
|
||||
[Theory]
|
||||
[BitAutoData("AD", "A-123456-Z", "ad_nrt")]
|
||||
[BitAutoData("AD", "A123456Z", "ad_nrt")]
|
||||
[BitAutoData("AR", "20-12345678-9", "ar_cuit")]
|
||||
[BitAutoData("AR", "20123456789", "ar_cuit")]
|
||||
[BitAutoData("AU", "01259983598", "au_abn")]
|
||||
[BitAutoData("AU", "123456789123", "au_arn")]
|
||||
[BitAutoData("AT", "ATU12345678", "eu_vat")]
|
||||
[BitAutoData("BH", "123456789012345", "bh_vat")]
|
||||
[BitAutoData("BY", "123456789", "by_tin")]
|
||||
[BitAutoData("BE", "BE0123456789", "eu_vat")]
|
||||
[BitAutoData("BO", "123456789", "bo_tin")]
|
||||
[BitAutoData("BR", "01.234.456/5432-10", "br_cnpj")]
|
||||
[BitAutoData("BR", "01234456543210", "br_cnpj")]
|
||||
[BitAutoData("BR", "123.456.789-87", "br_cpf")]
|
||||
[BitAutoData("BR", "12345678987", "br_cpf")]
|
||||
[BitAutoData("BG", "123456789", "bg_uic")]
|
||||
[BitAutoData("BG", "BG012100705", "eu_vat")]
|
||||
[BitAutoData("CA", "100728494", "ca_bn")]
|
||||
[BitAutoData("CA", "123456789RT0001", "ca_gst_hst")]
|
||||
[BitAutoData("CA", "PST-1234-1234", "ca_pst_bc")]
|
||||
[BitAutoData("CA", "123456-7", "ca_pst_mb")]
|
||||
[BitAutoData("CA", "1234567", "ca_pst_sk")]
|
||||
[BitAutoData("CA", "1234567890TQ1234", "ca_qst")]
|
||||
[BitAutoData("CL", "11.121.326-1", "cl_tin")]
|
||||
[BitAutoData("CL", "11121326-1", "cl_tin")]
|
||||
[BitAutoData("CL", "23.121.326-K", "cl_tin")]
|
||||
[BitAutoData("CL", "43651326-K", "cl_tin")]
|
||||
[BitAutoData("CN", "123456789012345678", "cn_tin")]
|
||||
[BitAutoData("CN", "123456789012345", "cn_tin")]
|
||||
[BitAutoData("CO", "123.456.789-0", "co_nit")]
|
||||
[BitAutoData("CO", "1234567890", "co_nit")]
|
||||
[BitAutoData("CR", "1-234-567890", "cr_tin")]
|
||||
[BitAutoData("CR", "1234567890", "cr_tin")]
|
||||
[BitAutoData("HR", "HR12345678912", "eu_vat")]
|
||||
[BitAutoData("HR", "12345678901", "hr_oib")]
|
||||
[BitAutoData("CY", "CY12345678X", "eu_vat")]
|
||||
[BitAutoData("CZ", "CZ12345678", "eu_vat")]
|
||||
[BitAutoData("DK", "DK12345678", "eu_vat")]
|
||||
[BitAutoData("DO", "123-4567890-1", "do_rcn")]
|
||||
[BitAutoData("DO", "12345678901", "do_rcn")]
|
||||
[BitAutoData("EC", "1234567890001", "ec_ruc")]
|
||||
[BitAutoData("EG", "123456789", "eg_tin")]
|
||||
[BitAutoData("SV", "1234-567890-123-4", "sv_nit")]
|
||||
[BitAutoData("SV", "12345678901234", "sv_nit")]
|
||||
[BitAutoData("EE", "EE123456789", "eu_vat")]
|
||||
[BitAutoData("EU", "EU123456789", "eu_oss_vat")]
|
||||
[BitAutoData("FI", "FI12345678", "eu_vat")]
|
||||
[BitAutoData("FR", "FR12345678901", "eu_vat")]
|
||||
[BitAutoData("GE", "123456789", "ge_vat")]
|
||||
[BitAutoData("DE", "1234567890", "de_stn")]
|
||||
[BitAutoData("DE", "DE123456789", "eu_vat")]
|
||||
[BitAutoData("GR", "EL123456789", "eu_vat")]
|
||||
[BitAutoData("HK", "12345678", "hk_br")]
|
||||
[BitAutoData("HU", "HU12345678", "eu_vat")]
|
||||
[BitAutoData("HU", "12345678-1-23", "hu_tin")]
|
||||
[BitAutoData("HU", "12345678123", "hu_tin")]
|
||||
[BitAutoData("IS", "123456", "is_vat")]
|
||||
[BitAutoData("IN", "12ABCDE1234F1Z5", "in_gst")]
|
||||
[BitAutoData("IN", "12ABCDE3456FGZH", "in_gst")]
|
||||
[BitAutoData("ID", "012.345.678.9-012.345", "id_npwp")]
|
||||
[BitAutoData("ID", "0123456789012345", "id_npwp")]
|
||||
[BitAutoData("IE", "IE1234567A", "eu_vat")]
|
||||
[BitAutoData("IE", "IE1234567AB", "eu_vat")]
|
||||
[BitAutoData("IL", "000012345", "il_vat")]
|
||||
[BitAutoData("IL", "123456789", "il_vat")]
|
||||
[BitAutoData("IT", "IT12345678901", "eu_vat")]
|
||||
[BitAutoData("JP", "1234567890123", "jp_cn")]
|
||||
[BitAutoData("JP", "12345", "jp_rn")]
|
||||
[BitAutoData("KZ", "123456789012", "kz_bin")]
|
||||
[BitAutoData("KE", "P000111111A", "ke_pin")]
|
||||
[BitAutoData("LV", "LV12345678912", "eu_vat")]
|
||||
[BitAutoData("LI", "CHE123456789", "li_uid")]
|
||||
[BitAutoData("LI", "12345", "li_vat")]
|
||||
[BitAutoData("LT", "LT123456789123", "eu_vat")]
|
||||
[BitAutoData("LU", "LU12345678", "eu_vat")]
|
||||
[BitAutoData("MY", "12345678", "my_frp")]
|
||||
[BitAutoData("MY", "C 1234567890", "my_itn")]
|
||||
[BitAutoData("MY", "C1234567890", "my_itn")]
|
||||
[BitAutoData("MY", "A12-3456-78912345", "my_sst")]
|
||||
[BitAutoData("MY", "A12345678912345", "my_sst")]
|
||||
[BitAutoData("MT", "MT12345678", "eu_vat")]
|
||||
[BitAutoData("MX", "ABC010203AB9", "mx_rfc")]
|
||||
[BitAutoData("MD", "1003600", "md_vat")]
|
||||
[BitAutoData("MA", "12345678", "ma_vat")]
|
||||
[BitAutoData("NL", "NL123456789B12", "eu_vat")]
|
||||
[BitAutoData("NZ", "123456789", "nz_gst")]
|
||||
[BitAutoData("NG", "12345678-0001", "ng_tin")]
|
||||
[BitAutoData("NO", "123456789MVA", "no_vat")]
|
||||
[BitAutoData("NO", "1234567", "no_voec")]
|
||||
[BitAutoData("OM", "OM1234567890", "om_vat")]
|
||||
[BitAutoData("PE", "12345678901", "pe_ruc")]
|
||||
[BitAutoData("PH", "123456789012", "ph_tin")]
|
||||
[BitAutoData("PL", "PL1234567890", "eu_vat")]
|
||||
[BitAutoData("PT", "PT123456789", "eu_vat")]
|
||||
[BitAutoData("RO", "RO1234567891", "eu_vat")]
|
||||
[BitAutoData("RO", "1234567890123", "ro_tin")]
|
||||
[BitAutoData("RU", "1234567891", "ru_inn")]
|
||||
[BitAutoData("RU", "123456789", "ru_kpp")]
|
||||
[BitAutoData("SA", "123456789012345", "sa_vat")]
|
||||
[BitAutoData("RS", "123456789", "rs_pib")]
|
||||
[BitAutoData("SG", "M12345678X", "sg_gst")]
|
||||
[BitAutoData("SG", "123456789F", "sg_uen")]
|
||||
[BitAutoData("SK", "SK1234567891", "eu_vat")]
|
||||
[BitAutoData("SI", "SI12345678", "eu_vat")]
|
||||
[BitAutoData("SI", "12345678", "si_tin")]
|
||||
[BitAutoData("ZA", "4123456789", "za_vat")]
|
||||
[BitAutoData("KR", "123-45-67890", "kr_brn")]
|
||||
[BitAutoData("KR", "1234567890", "kr_brn")]
|
||||
[BitAutoData("ES", "A12345678", "es_cif")]
|
||||
[BitAutoData("ES", "ESX1234567X", "eu_vat")]
|
||||
[BitAutoData("SE", "SE123456789012", "eu_vat")]
|
||||
[BitAutoData("CH", "CHE-123.456.789 HR", "ch_uid")]
|
||||
[BitAutoData("CH", "CHE123456789HR", "ch_uid")]
|
||||
[BitAutoData("CH", "CHE-123.456.789 MWST", "ch_vat")]
|
||||
[BitAutoData("CH", "CHE123456789MWST", "ch_vat")]
|
||||
[BitAutoData("TW", "12345678", "tw_vat")]
|
||||
[BitAutoData("TH", "1234567890123", "th_vat")]
|
||||
[BitAutoData("TR", "0123456789", "tr_tin")]
|
||||
[BitAutoData("UA", "123456789", "ua_vat")]
|
||||
[BitAutoData("AE", "123456789012345", "ae_trn")]
|
||||
[BitAutoData("GB", "XI123456789", "eu_vat")]
|
||||
[BitAutoData("GB", "GB123456789", "gb_vat")]
|
||||
[BitAutoData("US", "12-3456789", "us_ein")]
|
||||
[BitAutoData("UY", "123456789012", "uy_ruc")]
|
||||
[BitAutoData("UZ", "123456789", "uz_tin")]
|
||||
[BitAutoData("UZ", "123456789012", "uz_vat")]
|
||||
[BitAutoData("VE", "A-12345678-9", "ve_rif")]
|
||||
[BitAutoData("VE", "A123456789", "ve_rif")]
|
||||
[BitAutoData("VN", "1234567890", "vn_tin")]
|
||||
public void GetStripeTaxCode_WithValidCountryAndTaxId_ReturnsExpectedTaxIdType(
|
||||
string country,
|
||||
string taxId,
|
||||
string expected,
|
||||
SutProvider<TaxService> sutProvider)
|
||||
{
|
||||
var result = sutProvider.Sut.GetStripeTaxCode(country, taxId);
|
||||
|
||||
Assert.Equal(expected, result);
|
||||
}
|
||||
}
|
@ -21,7 +21,8 @@ public class OrganizationBillingController(
|
||||
IOrganizationRepository organizationRepository,
|
||||
IPaymentService paymentService,
|
||||
ISubscriberService subscriberService,
|
||||
IPaymentHistoryService paymentHistoryService) : BaseBillingController
|
||||
IPaymentHistoryService paymentHistoryService,
|
||||
ITaxService taxService) : BaseBillingController
|
||||
{
|
||||
[HttpGet("metadata")]
|
||||
public async Task<IResult> GetMetadataAsync([FromRoute] Guid organizationId)
|
||||
@ -267,6 +268,8 @@ public class OrganizationBillingController(
|
||||
return Error.NotFound();
|
||||
}
|
||||
|
||||
requestBody.TaxIdType = taxService.GetStripeTaxCode(requestBody.Country, requestBody.TaxId);
|
||||
|
||||
var taxInformation = requestBody.ToDomain();
|
||||
|
||||
await subscriberService.UpdateTaxInformation(organization, taxInformation);
|
||||
|
@ -311,17 +311,16 @@ public class OrganizationsController(
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
var taxInfo = new TaxInfo
|
||||
{
|
||||
TaxIdNumber = model.TaxId,
|
||||
BillingAddressLine1 = model.Line1,
|
||||
BillingAddressLine2 = model.Line2,
|
||||
BillingAddressCity = model.City,
|
||||
BillingAddressState = model.State,
|
||||
BillingAddressPostalCode = model.PostalCode,
|
||||
BillingAddressCountry = model.Country,
|
||||
};
|
||||
await paymentService.SaveTaxInfoAsync(organization, taxInfo);
|
||||
var taxInfo = new TaxInformation(
|
||||
model.Country,
|
||||
model.PostalCode,
|
||||
model.TaxId,
|
||||
model.TaxIdType,
|
||||
model.Line1,
|
||||
model.Line2,
|
||||
model.City,
|
||||
model.State);
|
||||
await subscriberService.UpdateTaxInformation(organization, taxInfo);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -120,6 +120,7 @@ public class ProviderBillingController(
|
||||
requestBody.Country,
|
||||
requestBody.PostalCode,
|
||||
requestBody.TaxId,
|
||||
requestBody.TaxIdType,
|
||||
requestBody.Line1,
|
||||
requestBody.Line2,
|
||||
requestBody.City,
|
||||
|
@ -1,6 +1,6 @@
|
||||
using Bit.Api.Billing.Models.Responses;
|
||||
using Bit.Core.Billing.Services;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Utilities;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http.HttpResults;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
@ -10,7 +10,8 @@ namespace Bit.Api.Billing.Controllers;
|
||||
|
||||
[Authorize("Application")]
|
||||
public class StripeController(
|
||||
IStripeAdapter stripeAdapter) : Controller
|
||||
IStripeAdapter stripeAdapter,
|
||||
ITaxService taxService) : Controller
|
||||
{
|
||||
[HttpPost]
|
||||
[Route("~/setup-intent/bank-account")]
|
||||
@ -54,7 +55,7 @@ public class StripeController(
|
||||
[Route("~/tax/id-types")]
|
||||
public IResult GetTaxIdTypes()
|
||||
{
|
||||
var response = TaxIdTypesResponse.From(StaticStore.SupportedTaxIdTypes);
|
||||
var response = TaxIdTypesResponse.From(taxService.GetTaxIdTypes());
|
||||
return TypedResults.Ok(response);
|
||||
}
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ public class TaxInformationRequestBody
|
||||
[Required]
|
||||
public string PostalCode { get; set; }
|
||||
public string TaxId { get; set; }
|
||||
public string? TaxIdType { get; set; }
|
||||
public string Line1 { get; set; }
|
||||
public string Line2 { get; set; }
|
||||
public string City { get; set; }
|
||||
@ -19,6 +20,7 @@ public class TaxInformationRequestBody
|
||||
Country,
|
||||
PostalCode,
|
||||
TaxId,
|
||||
TaxIdType,
|
||||
Line1,
|
||||
Line2,
|
||||
City,
|
||||
|
@ -4,7 +4,8 @@ namespace Bit.Api.Models.Request;
|
||||
|
||||
public class ExpandedTaxInfoUpdateRequestModel : TaxInfoUpdateRequestModel
|
||||
{
|
||||
public string TaxId { get; set; }
|
||||
public string? TaxId { get; set; }
|
||||
public string? TaxIdType { get; set; }
|
||||
public string Line1 { get; set; }
|
||||
public string Line2 { get; set; }
|
||||
public string City { get; set; }
|
||||
|
@ -11,6 +11,7 @@ public static class ServiceCollectionExtensions
|
||||
{
|
||||
public static void AddBillingOperations(this IServiceCollection services)
|
||||
{
|
||||
services.AddSingleton<ITaxService, TaxService>();
|
||||
services.AddTransient<IOrganizationBillingService, OrganizationBillingService>();
|
||||
services.AddTransient<IPremiumUserBillingService, PremiumUserBillingService>();
|
||||
services.AddTransient<ISetupIntentCache, SetupIntentDistributedCache>();
|
||||
|
@ -65,6 +65,7 @@ public class OrganizationSale
|
||||
signup.TaxInfo.BillingAddressCountry,
|
||||
signup.TaxInfo.BillingAddressPostalCode,
|
||||
signup.TaxInfo.TaxIdNumber,
|
||||
signup.TaxInfo.TaxIdType,
|
||||
signup.TaxInfo.BillingAddressLine1,
|
||||
signup.TaxInfo.BillingAddressLine2,
|
||||
signup.TaxInfo.BillingAddressCity,
|
||||
|
@ -1,4 +1,6 @@
|
||||
namespace Bit.Core.Billing.Models;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Bit.Core.Billing.Models;
|
||||
|
||||
public class TaxIdType
|
||||
{
|
||||
@ -12,6 +14,8 @@ public class TaxIdType
|
||||
/// </summary>
|
||||
public string Code { get; set; }
|
||||
|
||||
public Regex ValidationExpression { get; set; }
|
||||
|
||||
public string Description { get; set; }
|
||||
|
||||
public string Example { get; set; }
|
||||
|
@ -1,5 +1,4 @@
|
||||
using Bit.Core.Models.Business;
|
||||
using Stripe;
|
||||
|
||||
namespace Bit.Core.Billing.Models;
|
||||
|
||||
@ -7,6 +6,7 @@ public record TaxInformation(
|
||||
string Country,
|
||||
string PostalCode,
|
||||
string TaxId,
|
||||
string TaxIdType,
|
||||
string Line1,
|
||||
string Line2,
|
||||
string City,
|
||||
@ -16,165 +16,9 @@ public record TaxInformation(
|
||||
taxInfo.BillingAddressCountry,
|
||||
taxInfo.BillingAddressPostalCode,
|
||||
taxInfo.TaxIdNumber,
|
||||
taxInfo.TaxIdType,
|
||||
taxInfo.BillingAddressLine1,
|
||||
taxInfo.BillingAddressLine2,
|
||||
taxInfo.BillingAddressCity,
|
||||
taxInfo.BillingAddressState);
|
||||
|
||||
public (AddressOptions, List<CustomerTaxIdDataOptions>) GetStripeOptions()
|
||||
{
|
||||
var address = new AddressOptions
|
||||
{
|
||||
Country = Country,
|
||||
PostalCode = PostalCode,
|
||||
Line1 = Line1,
|
||||
Line2 = Line2,
|
||||
City = City,
|
||||
State = State
|
||||
};
|
||||
|
||||
var customerTaxIdDataOptionsList = !string.IsNullOrEmpty(TaxId)
|
||||
? new List<CustomerTaxIdDataOptions> { new() { Type = GetTaxIdType(), Value = TaxId } }
|
||||
: null;
|
||||
|
||||
return (address, customerTaxIdDataOptionsList);
|
||||
}
|
||||
|
||||
public string GetTaxIdType()
|
||||
{
|
||||
if (string.IsNullOrEmpty(Country) || string.IsNullOrEmpty(TaxId))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
switch (Country.ToUpper())
|
||||
{
|
||||
case "AD":
|
||||
return "ad_nrt";
|
||||
case "AE":
|
||||
return "ae_trn";
|
||||
case "AR":
|
||||
return "ar_cuit";
|
||||
case "AU":
|
||||
return "au_abn";
|
||||
case "BO":
|
||||
return "bo_tin";
|
||||
case "BR":
|
||||
return "br_cnpj";
|
||||
case "CA":
|
||||
// May break for those in Québec given the assumption of QST
|
||||
if (State?.Contains("bec") ?? false)
|
||||
{
|
||||
return "ca_qst";
|
||||
}
|
||||
return "ca_bn";
|
||||
case "CH":
|
||||
return "ch_vat";
|
||||
case "CL":
|
||||
return "cl_tin";
|
||||
case "CN":
|
||||
return "cn_tin";
|
||||
case "CO":
|
||||
return "co_nit";
|
||||
case "CR":
|
||||
return "cr_tin";
|
||||
case "DO":
|
||||
return "do_rcn";
|
||||
case "EC":
|
||||
return "ec_ruc";
|
||||
case "EG":
|
||||
return "eg_tin";
|
||||
case "GE":
|
||||
return "ge_vat";
|
||||
case "ID":
|
||||
return "id_npwp";
|
||||
case "IL":
|
||||
return "il_vat";
|
||||
case "IS":
|
||||
return "is_vat";
|
||||
case "KE":
|
||||
return "ke_pin";
|
||||
case "AT":
|
||||
case "BE":
|
||||
case "BG":
|
||||
case "CY":
|
||||
case "CZ":
|
||||
case "DE":
|
||||
case "DK":
|
||||
case "EE":
|
||||
case "ES":
|
||||
case "FI":
|
||||
case "FR":
|
||||
case "GB":
|
||||
case "GR":
|
||||
case "HR":
|
||||
case "HU":
|
||||
case "IE":
|
||||
case "IT":
|
||||
case "LT":
|
||||
case "LU":
|
||||
case "LV":
|
||||
case "MT":
|
||||
case "NL":
|
||||
case "PL":
|
||||
case "PT":
|
||||
case "RO":
|
||||
case "SE":
|
||||
case "SI":
|
||||
case "SK":
|
||||
return "eu_vat";
|
||||
case "HK":
|
||||
return "hk_br";
|
||||
case "IN":
|
||||
return "in_gst";
|
||||
case "JP":
|
||||
return "jp_cn";
|
||||
case "KR":
|
||||
return "kr_brn";
|
||||
case "LI":
|
||||
return "li_uid";
|
||||
case "MX":
|
||||
return "mx_rfc";
|
||||
case "MY":
|
||||
return "my_sst";
|
||||
case "NO":
|
||||
return "no_vat";
|
||||
case "NZ":
|
||||
return "nz_gst";
|
||||
case "PE":
|
||||
return "pe_ruc";
|
||||
case "PH":
|
||||
return "ph_tin";
|
||||
case "RS":
|
||||
return "rs_pib";
|
||||
case "RU":
|
||||
return "ru_inn";
|
||||
case "SA":
|
||||
return "sa_vat";
|
||||
case "SG":
|
||||
return "sg_gst";
|
||||
case "SV":
|
||||
return "sv_nit";
|
||||
case "TH":
|
||||
return "th_vat";
|
||||
case "TR":
|
||||
return "tr_tin";
|
||||
case "TW":
|
||||
return "tw_vat";
|
||||
case "UA":
|
||||
return "ua_vat";
|
||||
case "US":
|
||||
return "us_ein";
|
||||
case "UY":
|
||||
return "uy_ruc";
|
||||
case "VE":
|
||||
return "ve_rif";
|
||||
case "VN":
|
||||
return "vn_tin";
|
||||
case "ZA":
|
||||
return "za_vat";
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
10
src/Core/Billing/Services/ITaxService.cs
Normal file
10
src/Core/Billing/Services/ITaxService.cs
Normal file
@ -0,0 +1,10 @@
|
||||
using Bit.Core.Billing.Models;
|
||||
|
||||
namespace Bit.Core.Billing.Services;
|
||||
|
||||
public interface ITaxService
|
||||
{
|
||||
string? GetStripeTaxCode(string country, string taxId);
|
||||
|
||||
IEnumerable<TaxIdType> GetTaxIdTypes();
|
||||
}
|
@ -28,7 +28,8 @@ public class OrganizationBillingService(
|
||||
IOrganizationRepository organizationRepository,
|
||||
ISetupIntentCache setupIntentCache,
|
||||
IStripeAdapter stripeAdapter,
|
||||
ISubscriberService subscriberService) : IOrganizationBillingService
|
||||
ISubscriberService subscriberService,
|
||||
ITaxService taxService) : IOrganizationBillingService
|
||||
{
|
||||
public async Task Finalize(OrganizationSale sale)
|
||||
{
|
||||
@ -167,14 +168,38 @@ public class OrganizationBillingService(
|
||||
throw new BillingException();
|
||||
}
|
||||
|
||||
var (address, taxIdData) = customerSetup.TaxInformation.GetStripeOptions();
|
||||
|
||||
customerCreateOptions.Address = address;
|
||||
customerCreateOptions.Address = new AddressOptions
|
||||
{
|
||||
Line1 = customerSetup.TaxInformation.Line1,
|
||||
Line2 = customerSetup.TaxInformation.Line2,
|
||||
City = customerSetup.TaxInformation.City,
|
||||
PostalCode = customerSetup.TaxInformation.PostalCode,
|
||||
State = customerSetup.TaxInformation.State,
|
||||
Country = customerSetup.TaxInformation.Country,
|
||||
};
|
||||
customerCreateOptions.Tax = new CustomerTaxOptions
|
||||
{
|
||||
ValidateLocation = StripeConstants.ValidateTaxLocationTiming.Immediately
|
||||
};
|
||||
customerCreateOptions.TaxIdData = taxIdData;
|
||||
|
||||
if (customerSetup.TaxInformation.TaxId != null)
|
||||
{
|
||||
var taxIdType = taxService.GetStripeTaxCode(customerSetup.TaxInformation.Country,
|
||||
customerSetup.TaxInformation.TaxId);
|
||||
|
||||
if (taxIdType == null)
|
||||
{
|
||||
logger.LogWarning("Could not determine tax ID type for organization '{OrganizationID}' in country '{Country}' with tax ID '{TaxID}'.",
|
||||
organization.Id,
|
||||
customerSetup.TaxInformation.Country,
|
||||
customerSetup.TaxInformation.TaxId);
|
||||
}
|
||||
|
||||
customerCreateOptions.TaxIdData =
|
||||
[
|
||||
new() { Type = taxIdType, Value = customerSetup.TaxInformation.TaxId }
|
||||
];
|
||||
}
|
||||
|
||||
var (paymentMethodType, paymentMethodToken) = customerSetup.TokenizedPaymentSource;
|
||||
|
||||
|
@ -24,7 +24,8 @@ public class PremiumUserBillingService(
|
||||
ISetupIntentCache setupIntentCache,
|
||||
IStripeAdapter stripeAdapter,
|
||||
ISubscriberService subscriberService,
|
||||
IUserRepository userRepository) : IPremiumUserBillingService
|
||||
IUserRepository userRepository,
|
||||
ITaxService taxService) : IPremiumUserBillingService
|
||||
{
|
||||
public async Task Finalize(PremiumUserSale sale)
|
||||
{
|
||||
@ -82,13 +83,19 @@ public class PremiumUserBillingService(
|
||||
throw new BillingException();
|
||||
}
|
||||
|
||||
var (address, taxIdData) = customerSetup.TaxInformation.GetStripeOptions();
|
||||
|
||||
var subscriberName = user.SubscriberName();
|
||||
|
||||
var customerCreateOptions = new CustomerCreateOptions
|
||||
{
|
||||
Address = address,
|
||||
Address = new AddressOptions
|
||||
{
|
||||
Line1 = customerSetup.TaxInformation.Line1,
|
||||
Line2 = customerSetup.TaxInformation.Line2,
|
||||
City = customerSetup.TaxInformation.City,
|
||||
PostalCode = customerSetup.TaxInformation.PostalCode,
|
||||
State = customerSetup.TaxInformation.State,
|
||||
Country = customerSetup.TaxInformation.Country,
|
||||
},
|
||||
Description = user.Name,
|
||||
Email = user.Email,
|
||||
Expand = ["tax"],
|
||||
@ -113,10 +120,28 @@ public class PremiumUserBillingService(
|
||||
Tax = new CustomerTaxOptions
|
||||
{
|
||||
ValidateLocation = StripeConstants.ValidateTaxLocationTiming.Immediately
|
||||
},
|
||||
TaxIdData = taxIdData
|
||||
}
|
||||
};
|
||||
|
||||
if (customerSetup.TaxInformation.TaxId != null)
|
||||
{
|
||||
var taxIdType = taxService.GetStripeTaxCode(customerSetup.TaxInformation.Country,
|
||||
customerSetup.TaxInformation.TaxId);
|
||||
|
||||
if (taxIdType == null)
|
||||
{
|
||||
logger.LogWarning("Could not infer tax ID type in country '{Country}' with tax ID '{TaxID}'.",
|
||||
customerSetup.TaxInformation.Country,
|
||||
customerSetup.TaxInformation.TaxId);
|
||||
throw new Exceptions.BadRequestException("billingTaxIdTypeInferenceError");
|
||||
}
|
||||
|
||||
customerCreateOptions.TaxIdData =
|
||||
[
|
||||
new() { Type = taxIdType, Value = customerSetup.TaxInformation.TaxId }
|
||||
];
|
||||
}
|
||||
|
||||
var (paymentMethodType, paymentMethodToken) = customerSetup.TokenizedPaymentSource;
|
||||
|
||||
var braintreeCustomerId = "";
|
||||
|
@ -22,7 +22,8 @@ public class SubscriberService(
|
||||
IGlobalSettings globalSettings,
|
||||
ILogger<SubscriberService> logger,
|
||||
ISetupIntentCache setupIntentCache,
|
||||
IStripeAdapter stripeAdapter) : ISubscriberService
|
||||
IStripeAdapter stripeAdapter,
|
||||
ITaxService taxService) : ISubscriberService
|
||||
{
|
||||
public async Task CancelSubscription(
|
||||
ISubscriber subscriber,
|
||||
@ -616,16 +617,51 @@ public class SubscriberService(
|
||||
await stripeAdapter.TaxIdDeleteAsync(customer.Id, taxId.Id);
|
||||
}
|
||||
|
||||
var taxIdType = taxInformation.GetTaxIdType();
|
||||
if (string.IsNullOrWhiteSpace(taxInformation.TaxId))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(taxInformation.TaxId) &&
|
||||
!string.IsNullOrWhiteSpace(taxIdType))
|
||||
string taxIdType;
|
||||
if (string.IsNullOrWhiteSpace(taxInformation.TaxIdType))
|
||||
{
|
||||
await stripeAdapter.TaxIdCreateAsync(customer.Id, new TaxIdCreateOptions
|
||||
taxIdType = taxService.GetStripeTaxCode(taxInformation.Country,
|
||||
taxInformation.TaxId);
|
||||
|
||||
if (taxIdType == null)
|
||||
{
|
||||
Type = taxIdType,
|
||||
Value = taxInformation.TaxId,
|
||||
});
|
||||
logger.LogWarning("Could not infer tax ID type in country '{Country}' with tax ID '{TaxID}'.",
|
||||
taxInformation.Country,
|
||||
taxInformation.TaxId);
|
||||
throw new Exceptions.BadRequestException("billingTaxIdTypeInferenceError");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
taxIdType = taxInformation.TaxIdType;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await stripeAdapter.TaxIdCreateAsync(customer.Id,
|
||||
new TaxIdCreateOptions { Type = taxIdType, Value = taxInformation.TaxId });
|
||||
}
|
||||
catch (StripeException e)
|
||||
{
|
||||
switch (e.StripeError.Code)
|
||||
{
|
||||
case "tax_id_invalid":
|
||||
logger.LogWarning("Invalid tax ID '{TaxID}' for country '{Country}'.",
|
||||
taxInformation.TaxId,
|
||||
taxInformation.Country);
|
||||
throw new Exceptions.BadRequestException("billingInvalidTaxIdError");
|
||||
default:
|
||||
logger.LogError(e, "Error creating tax ID '{TaxId}' in country '{Country}' for customer '{CustomerID}'.",
|
||||
taxInformation.TaxId,
|
||||
taxInformation.Country,
|
||||
customer.Id);
|
||||
throw new Exceptions.BadRequestException("billingTaxIdCreationError");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -756,6 +792,7 @@ public class SubscriberService(
|
||||
customer.Address.Country,
|
||||
customer.Address.PostalCode,
|
||||
customer.TaxIds?.FirstOrDefault()?.Value,
|
||||
customer.TaxIds?.FirstOrDefault()?.Type,
|
||||
customer.Address.Line1,
|
||||
customer.Address.Line2,
|
||||
customer.Address.City,
|
||||
|
907
src/Core/Billing/Services/TaxService.cs
Normal file
907
src/Core/Billing/Services/TaxService.cs
Normal file
@ -0,0 +1,907 @@
|
||||
using System.Text.RegularExpressions;
|
||||
using Bit.Core.Billing.Models;
|
||||
|
||||
namespace Bit.Core.Billing.Services;
|
||||
|
||||
public class TaxService : ITaxService
|
||||
{
|
||||
/// <summary>
|
||||
/// Retrieves a list of supported tax ID types for customers.
|
||||
/// </summary>
|
||||
/// <remarks>Compiled list from <see href="https://docs.stripe.com/billing/customer/tax-ids">Stripe</see></remarks>
|
||||
private static readonly IEnumerable<TaxIdType> _taxIdTypes =
|
||||
[
|
||||
new()
|
||||
{
|
||||
Country = "AD",
|
||||
Code = "ad_nrt",
|
||||
Description = "Andorran NRT number",
|
||||
Example = "A-123456-Z",
|
||||
ValidationExpression = new Regex("^([A-Z]{1})-?([0-9]{6})-?([A-Z]{1})$")
|
||||
},
|
||||
new()
|
||||
{
|
||||
Country = "AR",
|
||||
Code = "ar_cuit",
|
||||
Description = "Argentinian tax ID number",
|
||||
Example = "12-34567890-1",
|
||||
ValidationExpression = new Regex("^([0-9]{2})-?([0-9]{8})-?([0-9]{1})$")
|
||||
},
|
||||
new()
|
||||
{
|
||||
Country = "AU",
|
||||
Code = "au_abn",
|
||||
Description = "Australian Business Number (AU ABN)",
|
||||
Example = "123456789012",
|
||||
ValidationExpression = new Regex("^[0-9]{11}$")
|
||||
},
|
||||
new()
|
||||
{
|
||||
Country = "AU",
|
||||
Code = "au_arn",
|
||||
Description = "Australian Taxation Office Reference Number",
|
||||
Example = "123456789123",
|
||||
ValidationExpression = new Regex("^[0-9]{12}$")
|
||||
},
|
||||
new()
|
||||
{
|
||||
Country = "AT",
|
||||
Code = "eu_vat",
|
||||
Description = "European VAT number (Austria)",
|
||||
Example = "ATU12345678",
|
||||
ValidationExpression = new Regex("^ATU[0-9]{8}$")
|
||||
},
|
||||
new()
|
||||
{
|
||||
Country = "BH",
|
||||
Code = "bh_vat",
|
||||
Description = "Bahraini VAT Number",
|
||||
Example = "123456789012345",
|
||||
ValidationExpression = new Regex("^[0-9]{15}$")
|
||||
},
|
||||
new()
|
||||
{
|
||||
Country = "BY",
|
||||
Code = "by_tin",
|
||||
Description = "Belarus TIN Number",
|
||||
Example = "123456789",
|
||||
ValidationExpression = new Regex("^[0-9]{9}$")
|
||||
},
|
||||
new()
|
||||
{
|
||||
Country = "BE",
|
||||
Code = "eu_vat",
|
||||
Description = "European VAT number (Belgium)",
|
||||
Example = "BE0123456789",
|
||||
ValidationExpression = new Regex("^BE[0-9]{10}$")
|
||||
},
|
||||
new()
|
||||
{
|
||||
Country = "BO",
|
||||
Code = "bo_tin",
|
||||
Description = "Bolivian tax ID",
|
||||
Example = "123456789",
|
||||
ValidationExpression = new Regex("^[0-9]{9}$")
|
||||
},
|
||||
new()
|
||||
{
|
||||
Country = "BR",
|
||||
Code = "br_cnpj",
|
||||
Description = "Brazilian CNPJ number",
|
||||
Example = "01.234.456/5432-10",
|
||||
ValidationExpression = new Regex("^[0-9]{2}.?[0-9]{3}.?[0-9]{3}/?[0-9]{4}-?[0-9]{2}$")
|
||||
},
|
||||
new()
|
||||
{
|
||||
Country = "BR",
|
||||
Code = "br_cpf",
|
||||
Description = "Brazilian CPF number",
|
||||
Example = "123.456.789-87",
|
||||
ValidationExpression = new Regex("^[0-9]{3}.?[0-9]{3}.?[0-9]{3}-?[0-9]{2}$")
|
||||
},
|
||||
new()
|
||||
{
|
||||
Country = "BG",
|
||||
Code = "bg_uic",
|
||||
Description = "Bulgaria Unified Identification Code",
|
||||
Example = "123456789",
|
||||
ValidationExpression = new Regex("^[0-9]{9}$")
|
||||
},
|
||||
new()
|
||||
{
|
||||
Country = "BG",
|
||||
Code = "eu_vat",
|
||||
Description = "European VAT number (Bulgaria)",
|
||||
Example = "BG0123456789",
|
||||
ValidationExpression = new Regex("^BG[0-9]{9,10}$")
|
||||
},
|
||||
new()
|
||||
{
|
||||
Country = "CA",
|
||||
Code = "ca_bn",
|
||||
Description = "Canadian BN",
|
||||
Example = "123456789",
|
||||
ValidationExpression = new Regex("^[0-9]{9}$")
|
||||
},
|
||||
new()
|
||||
{
|
||||
Country = "CA",
|
||||
Code = "ca_gst_hst",
|
||||
Description = "Canadian GST/HST number",
|
||||
Example = "123456789RT0002",
|
||||
ValidationExpression = new Regex("^[0-9]{9}RT[0-9]{4}$")
|
||||
},
|
||||
new()
|
||||
{
|
||||
Country = "CA",
|
||||
Code = "ca_pst_bc",
|
||||
Description = "Canadian PST number (British Columbia)",
|
||||
Example = "PST-1234-5678",
|
||||
ValidationExpression = new Regex("^PST-[0-9]{4}-[0-9]{4}$")
|
||||
},
|
||||
new()
|
||||
{
|
||||
Country = "CA",
|
||||
Code = "ca_pst_mb",
|
||||
Description = "Canadian PST number (Manitoba)",
|
||||
Example = "123456-7",
|
||||
ValidationExpression = new Regex("^[0-9]{6}-[0-9]{1}$")
|
||||
},
|
||||
new()
|
||||
{
|
||||
Country = "CA",
|
||||
Code = "ca_pst_sk",
|
||||
Description = "Canadian PST number (Saskatchewan)",
|
||||
Example = "1234567",
|
||||
ValidationExpression = new Regex("^[0-9]{7}$")
|
||||
},
|
||||
new()
|
||||
{
|
||||
Country = "CA",
|
||||
Code = "ca_qst",
|
||||
Description = "Canadian QST number (Québec)",
|
||||
Example = "1234567890TQ1234",
|
||||
ValidationExpression = new Regex("^[0-9]{10}TQ[0-9]{4}$")
|
||||
},
|
||||
new()
|
||||
{
|
||||
Country = "CL",
|
||||
Code = "cl_tin",
|
||||
Description = "Chilean TIN",
|
||||
Example = "12.345.678-K",
|
||||
ValidationExpression = new Regex("^[0-9]{2}.?[0-9]{3}.?[0-9]{3}-?[0-9A-Z]{1}$")
|
||||
},
|
||||
new()
|
||||
{
|
||||
Country = "CN",
|
||||
Code = "cn_tin",
|
||||
Description = "Chinese tax ID",
|
||||
Example = "123456789012345678",
|
||||
ValidationExpression = new Regex("^[0-9]{15,18}$")
|
||||
},
|
||||
new()
|
||||
{
|
||||
Country = "CO",
|
||||
Code = "co_nit",
|
||||
Description = "Colombian NIT number",
|
||||
Example = "123.456.789-0",
|
||||
ValidationExpression = new Regex("^[0-9]{3}.?[0-9]{3}.?[0-9]{3}-?[0-9]{1}$")
|
||||
},
|
||||
new()
|
||||
{
|
||||
Country = "CR",
|
||||
Code = "cr_tin",
|
||||
Description = "Costa Rican tax ID",
|
||||
Example = "1-234-567890",
|
||||
ValidationExpression = new Regex("^[0-9]{1}-?[0-9]{3}-?[0-9]{6}$")
|
||||
},
|
||||
new()
|
||||
{
|
||||
Country = "HR",
|
||||
Code = "eu_vat",
|
||||
Description = "European VAT number (Croatia)",
|
||||
Example = "HR12345678912",
|
||||
ValidationExpression = new Regex("^HR[0-9]{11}$")
|
||||
},
|
||||
new()
|
||||
{
|
||||
Country = "HR",
|
||||
Code = "hr_oib",
|
||||
Description = "Croatian Personal Identification Number",
|
||||
Example = "12345678901",
|
||||
ValidationExpression = new Regex("^[0-9]{11}$")
|
||||
},
|
||||
new()
|
||||
{
|
||||
Country = "CY",
|
||||
Code = "eu_vat",
|
||||
Description = "European VAT number (Cyprus)",
|
||||
Example = "CY12345678X",
|
||||
ValidationExpression = new Regex("^CY[0-9]{8}[A-Z]{1}$")
|
||||
},
|
||||
new()
|
||||
{
|
||||
Country = "CZ",
|
||||
Code = "eu_vat",
|
||||
Description = "European VAT number (Czech Republic)",
|
||||
Example = "CZ12345678",
|
||||
ValidationExpression = new Regex("^CZ[0-9]{8,10}$")
|
||||
},
|
||||
new()
|
||||
{
|
||||
Country = "DK",
|
||||
Code = "eu_vat",
|
||||
Description = "European VAT number (Denmark)",
|
||||
Example = "DK12345678",
|
||||
ValidationExpression = new Regex("^DK[0-9]{8}$")
|
||||
},
|
||||
new()
|
||||
{
|
||||
Country = "DO",
|
||||
Code = "do_rcn",
|
||||
Description = "Dominican RCN number",
|
||||
Example = "123-4567890-1",
|
||||
ValidationExpression = new Regex("^[0-9]{3}-?[0-9]{7}-?[0-9]{1}$")
|
||||
},
|
||||
new()
|
||||
{
|
||||
Country = "EC",
|
||||
Code = "ec_ruc",
|
||||
Description = "Ecuadorian RUC number",
|
||||
Example = "1234567890001",
|
||||
ValidationExpression = new Regex("^[0-9]{13}$")
|
||||
},
|
||||
new()
|
||||
{
|
||||
Country = "EG",
|
||||
Code = "eg_tin",
|
||||
Description = "Egyptian Tax Identification Number",
|
||||
Example = "123456789",
|
||||
ValidationExpression = new Regex("^[0-9]{9}$")
|
||||
},
|
||||
|
||||
new()
|
||||
{
|
||||
Country = "SV",
|
||||
Code = "sv_nit",
|
||||
Description = "El Salvadorian NIT number",
|
||||
Example = "1234-567890-123-4",
|
||||
ValidationExpression = new Regex("^[0-9]{4}-?[0-9]{6}-?[0-9]{3}-?[0-9]{1}$")
|
||||
},
|
||||
|
||||
new()
|
||||
{
|
||||
Country = "EE",
|
||||
Code = "eu_vat",
|
||||
Description = "European VAT number (Estonia)",
|
||||
Example = "EE123456789",
|
||||
ValidationExpression = new Regex("^EE[0-9]{9}$")
|
||||
},
|
||||
|
||||
new()
|
||||
{
|
||||
Country = "EU",
|
||||
Code = "eu_oss_vat",
|
||||
Description = "European One Stop Shop VAT number for non-Union scheme",
|
||||
Example = "EU123456789",
|
||||
ValidationExpression = new Regex("^EU[0-9]{9}$")
|
||||
},
|
||||
new()
|
||||
{
|
||||
Country = "FI",
|
||||
Code = "eu_vat",
|
||||
Description = "European VAT number (Finland)",
|
||||
Example = "FI12345678",
|
||||
ValidationExpression = new Regex("^FI[0-9]{8}$")
|
||||
},
|
||||
new()
|
||||
{
|
||||
Country = "FR",
|
||||
Code = "eu_vat",
|
||||
Description = "European VAT number (France)",
|
||||
Example = "FR12345678901",
|
||||
ValidationExpression = new Regex("^FR[0-9A-Z]{2}[0-9]{9}$")
|
||||
},
|
||||
new()
|
||||
{
|
||||
Country = "GE",
|
||||
Code = "ge_vat",
|
||||
Description = "Georgian VAT",
|
||||
Example = "123456789",
|
||||
ValidationExpression = new Regex("^[0-9]{9}$")
|
||||
},
|
||||
new()
|
||||
{
|
||||
Country = "DE",
|
||||
Code = "de_stn",
|
||||
Description = "German Tax Number (Steuernummer)",
|
||||
Example = "1234567890",
|
||||
ValidationExpression = new Regex("^[0-9]{10}$")
|
||||
},
|
||||
new()
|
||||
{
|
||||
Country = "DE",
|
||||
Code = "eu_vat",
|
||||
Description = "European VAT number (Germany)",
|
||||
Example = "DE123456789",
|
||||
ValidationExpression = new Regex("^DE[0-9]{9}$")
|
||||
},
|
||||
new()
|
||||
{
|
||||
Country = "GR",
|
||||
Code = "eu_vat",
|
||||
Description = "European VAT number (Greece)",
|
||||
Example = "EL123456789",
|
||||
ValidationExpression = new Regex("^EL[0-9]{9}$")
|
||||
},
|
||||
new()
|
||||
{
|
||||
Country = "HK",
|
||||
Code = "hk_br",
|
||||
Description = "Hong Kong BR number",
|
||||
Example = "12345678",
|
||||
ValidationExpression = new Regex("^[0-9]{8}$")
|
||||
},
|
||||
new()
|
||||
{
|
||||
Country = "HU",
|
||||
Code = "eu_vat",
|
||||
Description = "European VAT number (Hungaria)",
|
||||
Example = "HU12345678",
|
||||
ValidationExpression = new Regex("^HU[0-9]{8}$")
|
||||
},
|
||||
new()
|
||||
{
|
||||
Country = "HU",
|
||||
Code = "hu_tin",
|
||||
Description = "Hungary tax number (adószám)",
|
||||
Example = "12345678-1-23",
|
||||
ValidationExpression = new Regex("^[0-9]{8}-?[0-9]-?[0-9]{2}$")
|
||||
},
|
||||
new()
|
||||
{
|
||||
Country = "IS",
|
||||
Code = "is_vat",
|
||||
Description = "Icelandic VAT",
|
||||
Example = "123456",
|
||||
ValidationExpression = new Regex("^[0-9]{6}$")
|
||||
},
|
||||
new()
|
||||
{
|
||||
Country = "IN",
|
||||
Code = "in_gst",
|
||||
Description = "Indian GST number",
|
||||
Example = "12ABCDE3456FGZH",
|
||||
ValidationExpression = new Regex("^[0-9]{2}[A-Z]{5}[0-9]{4}[A-Z]{1}[1-9A-Z]{1}Z[0-9A-Z]{1}$")
|
||||
},
|
||||
new()
|
||||
{
|
||||
Country = "ID",
|
||||
Code = "id_npwp",
|
||||
Description = "Indonesian NPWP number",
|
||||
Example = "012.345.678.9-012.345",
|
||||
ValidationExpression = new Regex("^[0-9]{3}.?[0-9]{3}.?[0-9]{3}.?[0-9]{1}-?[0-9]{3}.?[0-9]{3}$")
|
||||
},
|
||||
new()
|
||||
{
|
||||
Country = "IE",
|
||||
Code = "eu_vat",
|
||||
Description = "European VAT number (Ireland)",
|
||||
Example = "IE1234567AB",
|
||||
ValidationExpression = new Regex("^IE[0-9]{7}[A-Z]{1,2}$")
|
||||
},
|
||||
new()
|
||||
{
|
||||
Country = "IL",
|
||||
Code = "il_vat",
|
||||
Description = "Israel VAT",
|
||||
Example = "000012345",
|
||||
ValidationExpression = new Regex("^[0-9]{9}$")
|
||||
},
|
||||
new()
|
||||
{
|
||||
Country = "IT",
|
||||
Code = "eu_vat",
|
||||
Description = "European VAT number (Italy)",
|
||||
Example = "IT12345678912",
|
||||
ValidationExpression = new Regex("^IT[0-9]{11}$")
|
||||
},
|
||||
new()
|
||||
{
|
||||
Country = "JP",
|
||||
Code = "jp_cn",
|
||||
Description = "Japanese Corporate Number (*Hōjin Bangō*)",
|
||||
Example = "1234567891234",
|
||||
ValidationExpression = new Regex("^[0-9]{13}$")
|
||||
},
|
||||
new()
|
||||
{
|
||||
Country = "JP",
|
||||
Code = "jp_rn",
|
||||
Description =
|
||||
"Japanese Registered Foreign Businesses' Registration Number (*Tōroku Kokugai Jigyōsha no Tōroku Bangō*)",
|
||||
Example = "12345",
|
||||
ValidationExpression = new Regex("^[0-9]{5}$")
|
||||
},
|
||||
new()
|
||||
{
|
||||
Country = "JP",
|
||||
Code = "jp_trn",
|
||||
Description = "Japanese Tax Registration Number (*Tōroku Bangō*)",
|
||||
Example = "T1234567891234",
|
||||
ValidationExpression = new Regex("^T[0-9]{13}$")
|
||||
},
|
||||
new()
|
||||
{
|
||||
Country = "KZ",
|
||||
Code = "kz_bin",
|
||||
Description = "Kazakhstani Business Identification Number",
|
||||
Example = "123456789012",
|
||||
ValidationExpression = new Regex("^[0-9]{12}$")
|
||||
},
|
||||
new()
|
||||
{
|
||||
Country = "KE",
|
||||
Code = "ke_pin",
|
||||
Description = "Kenya Revenue Authority Personal Identification Number",
|
||||
Example = "P000111111A",
|
||||
ValidationExpression = new Regex("^[A-Z]{1}[0-9]{9}[A-Z]{1}$")
|
||||
},
|
||||
new()
|
||||
{
|
||||
Country = "LV",
|
||||
Code = "eu_vat",
|
||||
Description = "European VAT number",
|
||||
Example = "LV12345678912",
|
||||
ValidationExpression = new Regex("^LV[0-9]{11}$")
|
||||
},
|
||||
new()
|
||||
{
|
||||
Country = "LI",
|
||||
Code = "li_uid",
|
||||
Description = "Liechtensteinian UID number",
|
||||
Example = "CHE123456789",
|
||||
ValidationExpression = new Regex("^CHE[0-9]{9}$")
|
||||
},
|
||||
new()
|
||||
{
|
||||
Country = "LI",
|
||||
Code = "li_vat",
|
||||
Description = "Liechtensteinian VAT number",
|
||||
Example = "12345",
|
||||
ValidationExpression = new Regex("^[0-9]{5}$")
|
||||
},
|
||||
new()
|
||||
{
|
||||
Country = "LT",
|
||||
Code = "eu_vat",
|
||||
Description = "European VAT number (Lithuania)",
|
||||
Example = "LT123456789123",
|
||||
ValidationExpression = new Regex("^LT[0-9]{9,12}$")
|
||||
},
|
||||
new()
|
||||
{
|
||||
Country = "LU",
|
||||
Code = "eu_vat",
|
||||
Description = "European VAT number (Luxembourg)",
|
||||
Example = "LU12345678",
|
||||
ValidationExpression = new Regex("^LU[0-9]{8}$")
|
||||
},
|
||||
new()
|
||||
{
|
||||
Country = "MY",
|
||||
Code = "my_frp",
|
||||
Description = "Malaysian FRP number",
|
||||
Example = "12345678",
|
||||
ValidationExpression = new Regex("^[0-9]{8}$")
|
||||
},
|
||||
new()
|
||||
{
|
||||
Country = "MY",
|
||||
Code = "my_itn",
|
||||
Description = "Malaysian ITN",
|
||||
Example = "C 1234567890",
|
||||
ValidationExpression = new Regex("^[A-Z]{1} ?[0-9]{10}$")
|
||||
},
|
||||
new()
|
||||
{
|
||||
Country = "MY",
|
||||
Code = "my_sst",
|
||||
Description = "Malaysian SST number",
|
||||
Example = "A12-3456-78912345",
|
||||
ValidationExpression = new Regex("^[A-Z]{1}[0-9]{2}-?[0-9]{4}-?[0-9]{8}$")
|
||||
},
|
||||
new()
|
||||
{
|
||||
Country = "MT",
|
||||
Code = "eu_vat",
|
||||
Description = "European VAT number (Malta)",
|
||||
Example = "MT12345678",
|
||||
ValidationExpression = new Regex("^MT[0-9]{8}$")
|
||||
},
|
||||
new()
|
||||
{
|
||||
Country = "MX",
|
||||
Code = "mx_rfc",
|
||||
Description = "Mexican RFC number",
|
||||
Example = "ABC010203AB9",
|
||||
ValidationExpression = new Regex("^[A-Z]{3}[0-9]{6}[A-Z0-9]{3}$")
|
||||
},
|
||||
new()
|
||||
{
|
||||
Country = "MD",
|
||||
Code = "md_vat",
|
||||
Description = "Moldova VAT Number",
|
||||
Example = "1234567",
|
||||
ValidationExpression = new Regex("^[0-9]{7}$")
|
||||
},
|
||||
new()
|
||||
{
|
||||
Country = "MA",
|
||||
Code = "ma_vat",
|
||||
Description = "Morocco VAT Number",
|
||||
Example = "12345678",
|
||||
ValidationExpression = new Regex("^[0-9]{8}$")
|
||||
},
|
||||
new()
|
||||
{
|
||||
Country = "NL",
|
||||
Code = "eu_vat",
|
||||
Description = "European VAT number (Netherlands)",
|
||||
Example = "NL123456789B12",
|
||||
ValidationExpression = new Regex("^NL[0-9]{9}B[0-9]{2}$")
|
||||
},
|
||||
new()
|
||||
{
|
||||
Country = "NZ",
|
||||
Code = "nz_gst",
|
||||
Description = "New Zealand GST number",
|
||||
Example = "123456789",
|
||||
ValidationExpression = new Regex("^[0-9]{9}$")
|
||||
},
|
||||
new()
|
||||
{
|
||||
Country = "NG",
|
||||
Code = "ng_tin",
|
||||
Description = "Nigerian TIN Number",
|
||||
Example = "12345678-0001",
|
||||
ValidationExpression = new Regex("^[0-9]{8}-[0-9]{4}$")
|
||||
},
|
||||
new()
|
||||
{
|
||||
Country = "NO",
|
||||
Code = "no_vat",
|
||||
Description = "Norwegian VAT number",
|
||||
Example = "123456789MVA",
|
||||
ValidationExpression = new Regex("^[0-9]{9}MVA$")
|
||||
},
|
||||
new()
|
||||
{
|
||||
Country = "NO",
|
||||
Code = "no_voec",
|
||||
Description = "Norwegian VAT on e-commerce number",
|
||||
Example = "1234567",
|
||||
ValidationExpression = new Regex("^[0-9]{7}$")
|
||||
},
|
||||
new()
|
||||
{
|
||||
Country = "OM",
|
||||
Code = "om_vat",
|
||||
Description = "Omani VAT Number",
|
||||
Example = "OM1234567890",
|
||||
ValidationExpression = new Regex("^OM[0-9]{10}$")
|
||||
},
|
||||
new()
|
||||
{
|
||||
Country = "PE",
|
||||
Code = "pe_ruc",
|
||||
Description = "Peruvian RUC number",
|
||||
Example = "12345678901",
|
||||
ValidationExpression = new Regex("^[0-9]{11}$")
|
||||
},
|
||||
new()
|
||||
{
|
||||
Country = "PH",
|
||||
Code = "ph_tin",
|
||||
Description = "Philippines Tax Identification Number",
|
||||
Example = "123456789012",
|
||||
ValidationExpression = new Regex("^[0-9]{12}$")
|
||||
},
|
||||
new()
|
||||
{
|
||||
Country = "PL",
|
||||
Code = "eu_vat",
|
||||
Description = "European VAT number (Poland)",
|
||||
Example = "PL1234567890",
|
||||
ValidationExpression = new Regex("^PL[0-9]{10}$")
|
||||
},
|
||||
new()
|
||||
{
|
||||
Country = "PT",
|
||||
Code = "eu_vat",
|
||||
Description = "European VAT number (Portugal)",
|
||||
Example = "PT123456789",
|
||||
ValidationExpression = new Regex("^PT[0-9]{9}$")
|
||||
},
|
||||
new()
|
||||
{
|
||||
Country = "RO",
|
||||
Code = "eu_vat",
|
||||
Description = "European VAT number (Romania)",
|
||||
Example = "RO1234567891",
|
||||
ValidationExpression = new Regex("^RO[0-9]{2,10}$")
|
||||
},
|
||||
new()
|
||||
{
|
||||
Country = "RO",
|
||||
Code = "ro_tin",
|
||||
Description = "Romanian tax ID number",
|
||||
Example = "1234567890123",
|
||||
ValidationExpression = new Regex("^[0-9]{13}$")
|
||||
},
|
||||
new()
|
||||
{
|
||||
Country = "RU",
|
||||
Code = "ru_inn",
|
||||
Description = "Russian INN",
|
||||
Example = "1234567891",
|
||||
ValidationExpression = new Regex("^[0-9]{10,12}$")
|
||||
},
|
||||
new()
|
||||
{
|
||||
Country = "RU",
|
||||
Code = "ru_kpp",
|
||||
Description = "Russian KPP",
|
||||
Example = "123456789",
|
||||
ValidationExpression = new Regex("^[0-9]{9}$")
|
||||
},
|
||||
new()
|
||||
{
|
||||
Country = "SA",
|
||||
Code = "sa_vat",
|
||||
Description = "Saudi Arabia VAT",
|
||||
Example = "123456789012345",
|
||||
ValidationExpression = new Regex("^[0-9]{15}$")
|
||||
},
|
||||
new()
|
||||
{
|
||||
Country = "RS",
|
||||
Code = "rs_pib",
|
||||
Description = "Serbian PIB number",
|
||||
Example = "123456789",
|
||||
ValidationExpression = new Regex("^[0-9]{9}$")
|
||||
},
|
||||
new()
|
||||
{
|
||||
Country = "SG",
|
||||
Code = "sg_gst",
|
||||
Description = "Singaporean GST",
|
||||
Example = "M12345678X",
|
||||
ValidationExpression = new Regex("^[A-Z]{1}[0-9]{8}[A-Z]{1}$")
|
||||
},
|
||||
new()
|
||||
{
|
||||
Country = "SG",
|
||||
Code = "sg_uen",
|
||||
Description = "Singaporean UEN",
|
||||
Example = "123456789F",
|
||||
ValidationExpression = new Regex("^[0-9]{9}[A-Z]{1}$")
|
||||
},
|
||||
new()
|
||||
{
|
||||
Country = "SK",
|
||||
Code = "eu_vat",
|
||||
Description = "European VAT number (Slovakia)",
|
||||
Example = "SK1234567891",
|
||||
ValidationExpression = new Regex("^SK[0-9]{10}$")
|
||||
},
|
||||
new()
|
||||
{
|
||||
Country = "SI",
|
||||
Code = "eu_vat",
|
||||
Description = "European VAT number (Slovenia)",
|
||||
Example = "SI12345678",
|
||||
ValidationExpression = new Regex("^SI[0-9]{8}$")
|
||||
},
|
||||
new()
|
||||
{
|
||||
Country = "SI",
|
||||
Code = "si_tin",
|
||||
Description = "Slovenia tax number (davčna številka)",
|
||||
Example = "12345678",
|
||||
ValidationExpression = new Regex("^[0-9]{8}$")
|
||||
},
|
||||
new()
|
||||
{
|
||||
Country = "ZA",
|
||||
Code = "za_vat",
|
||||
Description = "South African VAT number",
|
||||
Example = "4123456789",
|
||||
ValidationExpression = new Regex("^[0-9]{10}$")
|
||||
},
|
||||
new()
|
||||
{
|
||||
Country = "KR",
|
||||
Code = "kr_brn",
|
||||
Description = "Korean BRN",
|
||||
Example = "123-45-67890",
|
||||
ValidationExpression = new Regex("^[0-9]{3}-?[0-9]{2}-?[0-9]{5}$")
|
||||
},
|
||||
new()
|
||||
{
|
||||
Country = "ES",
|
||||
Code = "es_cif",
|
||||
Description = "Spanish NIF/CIF number",
|
||||
Example = "A12345678",
|
||||
ValidationExpression = new Regex("^[A-Z]{1}[0-9]{8}$")
|
||||
},
|
||||
new()
|
||||
{
|
||||
Country = "ES",
|
||||
Code = "eu_vat",
|
||||
Description = "European VAT number (Spain)",
|
||||
Example = "ESA1234567Z",
|
||||
ValidationExpression = new Regex("^ES[A-Z]{1}[0-9]{7}[A-Z]{1}$")
|
||||
},
|
||||
new()
|
||||
{
|
||||
Country = "SE",
|
||||
Code = "eu_vat",
|
||||
Description = "European VAT number (Sweden)",
|
||||
Example = "SE123456789123",
|
||||
ValidationExpression = new Regex("^SE[0-9]{12}$")
|
||||
},
|
||||
new()
|
||||
{
|
||||
Country = "CH",
|
||||
Code = "ch_uid",
|
||||
Description = "Switzerland UID number",
|
||||
Example = "CHE-123.456.789 HR",
|
||||
ValidationExpression = new Regex("^CHE-?[0-9]{3}.?[0-9]{3}.?[0-9]{3} ?HR$")
|
||||
},
|
||||
new()
|
||||
{
|
||||
Country = "CH",
|
||||
Code = "ch_vat",
|
||||
Description = "Switzerland VAT number",
|
||||
Example = "CHE-123.456.789 MWST",
|
||||
ValidationExpression = new Regex("^CHE-?[0-9]{3}.?[0-9]{3}.?[0-9]{3} ?MWST$")
|
||||
},
|
||||
new()
|
||||
{
|
||||
Country = "TW",
|
||||
Code = "tw_vat",
|
||||
Description = "Taiwanese VAT",
|
||||
Example = "12345678",
|
||||
ValidationExpression = new Regex("^[0-9]{8}$")
|
||||
},
|
||||
new()
|
||||
{
|
||||
Country = "TZ",
|
||||
Code = "tz_vat",
|
||||
Description = "Tanzania VAT Number",
|
||||
Example = "12345678A",
|
||||
ValidationExpression = new Regex("^[0-9]{8}[A-Z]{1}$")
|
||||
},
|
||||
new()
|
||||
{
|
||||
Country = "TH",
|
||||
Code = "th_vat",
|
||||
Description = "Thai VAT",
|
||||
Example = "1234567891234",
|
||||
ValidationExpression = new Regex("^[0-9]{13}$")
|
||||
},
|
||||
new()
|
||||
{
|
||||
Country = "TR",
|
||||
Code = "tr_tin",
|
||||
Description = "Turkish TIN Number",
|
||||
Example = "0123456789",
|
||||
ValidationExpression = new Regex("^[0-9]{10}$")
|
||||
},
|
||||
new()
|
||||
{
|
||||
Country = "UA",
|
||||
Code = "ua_vat",
|
||||
Description = "Ukrainian VAT",
|
||||
Example = "123456789",
|
||||
ValidationExpression = new Regex("^[0-9]{9}$")
|
||||
},
|
||||
new()
|
||||
{
|
||||
Country = "AE",
|
||||
Code = "ae_trn",
|
||||
Description = "United Arab Emirates TRN",
|
||||
Example = "123456789012345",
|
||||
ValidationExpression = new Regex("^[0-9]{15}$")
|
||||
},
|
||||
new()
|
||||
{
|
||||
Country = "GB",
|
||||
Code = "eu_vat",
|
||||
Description = "Northern Ireland VAT number",
|
||||
Example = "XI123456789",
|
||||
ValidationExpression = new Regex("^XI[0-9]{9}$")
|
||||
},
|
||||
new()
|
||||
{
|
||||
Country = "GB",
|
||||
Code = "gb_vat",
|
||||
Description = "United Kingdom VAT number",
|
||||
Example = "GB123456789",
|
||||
ValidationExpression = new Regex("^GB[0-9]{9}$")
|
||||
},
|
||||
new()
|
||||
{
|
||||
Country = "US",
|
||||
Code = "us_ein",
|
||||
Description = "United States EIN",
|
||||
Example = "12-3456789",
|
||||
ValidationExpression = new Regex("^[0-9]{2}-?[0-9]{7}$")
|
||||
},
|
||||
new()
|
||||
{
|
||||
Country = "UY",
|
||||
Code = "uy_ruc",
|
||||
Description = "Uruguayan RUC number",
|
||||
Example = "123456789012",
|
||||
ValidationExpression = new Regex("^[0-9]{12}$")
|
||||
},
|
||||
new()
|
||||
{
|
||||
Country = "UZ",
|
||||
Code = "uz_tin",
|
||||
Description = "Uzbekistan TIN Number",
|
||||
Example = "123456789",
|
||||
ValidationExpression = new Regex("^[0-9]{9}$")
|
||||
},
|
||||
new()
|
||||
{
|
||||
Country = "UZ",
|
||||
Code = "uz_vat",
|
||||
Description = "Uzbekistan VAT Number",
|
||||
Example = "123456789012",
|
||||
ValidationExpression = new Regex("^[0-9]{12}$")
|
||||
},
|
||||
new()
|
||||
{
|
||||
Country = "VE",
|
||||
Code = "ve_rif",
|
||||
Description = "Venezuelan RIF number",
|
||||
Example = "A-12345678-9",
|
||||
ValidationExpression = new Regex("^[A-Z]{1}-?[0-9]{8}-?[0-9]{1}$")
|
||||
},
|
||||
new()
|
||||
{
|
||||
Country = "VN",
|
||||
Code = "vn_tin",
|
||||
Description = "Vietnamese tax ID number",
|
||||
Example = "1234567890",
|
||||
ValidationExpression = new Regex("^[0-9]{10}$")
|
||||
}
|
||||
];
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the Stripe tax code for a given country and tax ID.
|
||||
/// </summary>
|
||||
/// <param name="country"></param>
|
||||
/// <param name="taxId"></param>
|
||||
/// <returns>
|
||||
/// Returns the Stripe tax code if the tax ID is valid for the country.
|
||||
/// Returns null if the tax ID is invalid or the country is not supported.
|
||||
/// </returns>
|
||||
public string? GetStripeTaxCode(string country, string taxId)
|
||||
{
|
||||
foreach (var taxIdType in _taxIdTypes.Where(x => x.Country == country))
|
||||
{
|
||||
if (taxIdType.ValidationExpression.IsMatch(taxId))
|
||||
{
|
||||
return taxIdType.Code;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public IEnumerable<TaxIdType> GetTaxIdTypes() => _taxIdTypes;
|
||||
}
|
@ -82,6 +82,7 @@ public static class Utilities
|
||||
customer.Address.Country,
|
||||
customer.Address.PostalCode,
|
||||
customer.TaxIds?.FirstOrDefault()?.Value,
|
||||
customer.TaxIds?.FirstOrDefault()?.Type,
|
||||
customer.Address.Line1,
|
||||
customer.Address.Line2,
|
||||
customer.Address.City,
|
||||
|
@ -2,18 +2,10 @@
|
||||
|
||||
public class TaxInfo
|
||||
{
|
||||
private string _taxIdNumber = null;
|
||||
private string _taxIdType = null;
|
||||
public string? TaxIdNumber { get; set; }
|
||||
public string? TaxIdType { get; set; }
|
||||
|
||||
|
||||
public string TaxIdNumber
|
||||
{
|
||||
get => _taxIdNumber;
|
||||
set
|
||||
{
|
||||
_taxIdNumber = value;
|
||||
_taxIdType = null;
|
||||
}
|
||||
}
|
||||
public string StripeTaxRateId { get; set; }
|
||||
public string BillingAddressLine1 { get; set; }
|
||||
public string BillingAddressLine2 { get; set; }
|
||||
@ -21,201 +13,6 @@ public class TaxInfo
|
||||
public string BillingAddressState { get; set; }
|
||||
public string BillingAddressPostalCode { get; set; }
|
||||
public string BillingAddressCountry { get; set; } = "US";
|
||||
public string TaxIdType
|
||||
{
|
||||
get
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(BillingAddressCountry) ||
|
||||
string.IsNullOrWhiteSpace(TaxIdNumber))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
if (!string.IsNullOrWhiteSpace(_taxIdType))
|
||||
{
|
||||
return _taxIdType;
|
||||
}
|
||||
|
||||
switch (BillingAddressCountry.ToUpper())
|
||||
{
|
||||
case "AD":
|
||||
_taxIdType = "ad_nrt";
|
||||
break;
|
||||
case "AE":
|
||||
_taxIdType = "ae_trn";
|
||||
break;
|
||||
case "AR":
|
||||
_taxIdType = "ar_cuit";
|
||||
break;
|
||||
case "AU":
|
||||
_taxIdType = "au_abn";
|
||||
break;
|
||||
case "BO":
|
||||
_taxIdType = "bo_tin";
|
||||
break;
|
||||
case "BR":
|
||||
_taxIdType = "br_cnpj";
|
||||
break;
|
||||
case "CA":
|
||||
// May break for those in Québec given the assumption of QST
|
||||
if (BillingAddressState?.Contains("bec") ?? false)
|
||||
{
|
||||
_taxIdType = "ca_qst";
|
||||
break;
|
||||
}
|
||||
_taxIdType = "ca_bn";
|
||||
break;
|
||||
case "CH":
|
||||
_taxIdType = "ch_vat";
|
||||
break;
|
||||
case "CL":
|
||||
_taxIdType = "cl_tin";
|
||||
break;
|
||||
case "CN":
|
||||
_taxIdType = "cn_tin";
|
||||
break;
|
||||
case "CO":
|
||||
_taxIdType = "co_nit";
|
||||
break;
|
||||
case "CR":
|
||||
_taxIdType = "cr_tin";
|
||||
break;
|
||||
case "DO":
|
||||
_taxIdType = "do_rcn";
|
||||
break;
|
||||
case "EC":
|
||||
_taxIdType = "ec_ruc";
|
||||
break;
|
||||
case "EG":
|
||||
_taxIdType = "eg_tin";
|
||||
break;
|
||||
case "GE":
|
||||
_taxIdType = "ge_vat";
|
||||
break;
|
||||
case "ID":
|
||||
_taxIdType = "id_npwp";
|
||||
break;
|
||||
case "IL":
|
||||
_taxIdType = "il_vat";
|
||||
break;
|
||||
case "IS":
|
||||
_taxIdType = "is_vat";
|
||||
break;
|
||||
case "KE":
|
||||
_taxIdType = "ke_pin";
|
||||
break;
|
||||
case "AT":
|
||||
case "BE":
|
||||
case "BG":
|
||||
case "CY":
|
||||
case "CZ":
|
||||
case "DE":
|
||||
case "DK":
|
||||
case "EE":
|
||||
case "ES":
|
||||
case "FI":
|
||||
case "FR":
|
||||
case "GB":
|
||||
case "GR":
|
||||
case "HR":
|
||||
case "HU":
|
||||
case "IE":
|
||||
case "IT":
|
||||
case "LT":
|
||||
case "LU":
|
||||
case "LV":
|
||||
case "MT":
|
||||
case "NL":
|
||||
case "PL":
|
||||
case "PT":
|
||||
case "RO":
|
||||
case "SE":
|
||||
case "SI":
|
||||
case "SK":
|
||||
_taxIdType = "eu_vat";
|
||||
break;
|
||||
case "HK":
|
||||
_taxIdType = "hk_br";
|
||||
break;
|
||||
case "IN":
|
||||
_taxIdType = "in_gst";
|
||||
break;
|
||||
case "JP":
|
||||
_taxIdType = "jp_cn";
|
||||
break;
|
||||
case "KR":
|
||||
_taxIdType = "kr_brn";
|
||||
break;
|
||||
case "LI":
|
||||
_taxIdType = "li_uid";
|
||||
break;
|
||||
case "MX":
|
||||
_taxIdType = "mx_rfc";
|
||||
break;
|
||||
case "MY":
|
||||
_taxIdType = "my_sst";
|
||||
break;
|
||||
case "NO":
|
||||
_taxIdType = "no_vat";
|
||||
break;
|
||||
case "NZ":
|
||||
_taxIdType = "nz_gst";
|
||||
break;
|
||||
case "PE":
|
||||
_taxIdType = "pe_ruc";
|
||||
break;
|
||||
case "PH":
|
||||
_taxIdType = "ph_tin";
|
||||
break;
|
||||
case "RS":
|
||||
_taxIdType = "rs_pib";
|
||||
break;
|
||||
case "RU":
|
||||
_taxIdType = "ru_inn";
|
||||
break;
|
||||
case "SA":
|
||||
_taxIdType = "sa_vat";
|
||||
break;
|
||||
case "SG":
|
||||
_taxIdType = "sg_gst";
|
||||
break;
|
||||
case "SV":
|
||||
_taxIdType = "sv_nit";
|
||||
break;
|
||||
case "TH":
|
||||
_taxIdType = "th_vat";
|
||||
break;
|
||||
case "TR":
|
||||
_taxIdType = "tr_tin";
|
||||
break;
|
||||
case "TW":
|
||||
_taxIdType = "tw_vat";
|
||||
break;
|
||||
case "UA":
|
||||
_taxIdType = "ua_vat";
|
||||
break;
|
||||
case "US":
|
||||
_taxIdType = "us_ein";
|
||||
break;
|
||||
case "UY":
|
||||
_taxIdType = "uy_ruc";
|
||||
break;
|
||||
case "VE":
|
||||
_taxIdType = "ve_rif";
|
||||
break;
|
||||
case "VN":
|
||||
_taxIdType = "vn_tin";
|
||||
break;
|
||||
case "ZA":
|
||||
_taxIdType = "za_vat";
|
||||
break;
|
||||
default:
|
||||
_taxIdType = null;
|
||||
break;
|
||||
}
|
||||
|
||||
return _taxIdType;
|
||||
}
|
||||
}
|
||||
|
||||
public bool HasTaxId
|
||||
{
|
||||
|
@ -1694,8 +1694,18 @@ public class StripePaymentService : IPaymentService
|
||||
{
|
||||
await _stripeAdapter.TaxIdDeleteAsync(customer.Id, taxId.Id);
|
||||
}
|
||||
if (!string.IsNullOrWhiteSpace(taxInfo.TaxIdNumber) &&
|
||||
!string.IsNullOrWhiteSpace(taxInfo.TaxIdType))
|
||||
|
||||
if (taxInfo.TaxIdType == null)
|
||||
{
|
||||
_logger.LogWarning("Could not infer tax ID type in country '{Country}' with tax ID '{TaxID}'.",
|
||||
taxInfo.BillingAddressCountry,
|
||||
taxInfo.TaxIdNumber);
|
||||
throw new BadRequestException("billingTaxIdTypeInferenceError");
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(taxInfo.TaxIdNumber))
|
||||
{
|
||||
try
|
||||
{
|
||||
await _stripeAdapter.TaxIdCreateAsync(customer.Id, new TaxIdCreateOptions
|
||||
{
|
||||
@ -1703,6 +1713,24 @@ public class StripePaymentService : IPaymentService
|
||||
Value = taxInfo.TaxIdNumber,
|
||||
});
|
||||
}
|
||||
catch (StripeException e)
|
||||
{
|
||||
switch (e.StripeError.Code)
|
||||
{
|
||||
case "tax_id_invalid":
|
||||
_logger.LogWarning("Invalid tax ID '{TaxID}' for country '{Country}'.",
|
||||
taxInfo.TaxIdNumber,
|
||||
taxInfo.BillingAddressCountry);
|
||||
throw new BadRequestException("billingInvalidTaxIdError");
|
||||
default:
|
||||
_logger.LogError(e, "Error creating tax ID '{TaxId}' in country '{Country}' for customer '{CustomerID}'.",
|
||||
taxInfo.TaxIdNumber,
|
||||
taxInfo.BillingAddressCountry,
|
||||
customer.Id);
|
||||
throw new BadRequestException("billingTaxIdCreationError");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
using System.Collections.Immutable;
|
||||
using Bit.Core.Billing.Enums;
|
||||
using Bit.Core.Billing.Models;
|
||||
using Bit.Core.Billing.Models.StaticStore.Plans;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models.Data.Organizations.OrganizationUsers;
|
||||
@ -172,294 +171,4 @@ public static class StaticStore
|
||||
p.PasswordManager.StripeStoragePlanId == stripePlanId ||
|
||||
(p.SecretsManager?.StripeServiceAccountPlanId == stripePlanId));
|
||||
}
|
||||
|
||||
public static readonly IEnumerable<TaxIdType> SupportedTaxIdTypes =
|
||||
[
|
||||
new() { Country = "AD", Code = "ad_nrt", Description = "Andorran NRT number", Example = "A-123456-Z" },
|
||||
new()
|
||||
{
|
||||
Country = "AR",
|
||||
Code = "ar_cuit",
|
||||
Description = "Argentinian tax ID number",
|
||||
Example = "12-3456789-01"
|
||||
},
|
||||
new()
|
||||
{
|
||||
Country = "AU",
|
||||
Code = "au_abn",
|
||||
Description = "Australian Business Number (AU ABN)",
|
||||
Example = "12345678912"
|
||||
},
|
||||
new()
|
||||
{
|
||||
Country = "AU",
|
||||
Code = "au_arn",
|
||||
Description = "Australian Taxation Office Reference Number",
|
||||
Example = "123456789123"
|
||||
},
|
||||
new() { Country = "AT", Code = "eu_vat", Description = "European VAT number", Example = "ATU12345678" },
|
||||
new() { Country = "BH", Code = "bh_vat", Description = "Bahraini VAT Number", Example = "123456789012345" },
|
||||
new() { Country = "BY", Code = "by_tin", Description = "Belarus TIN Number", Example = "123456789" },
|
||||
new() { Country = "BE", Code = "eu_vat", Description = "European VAT number", Example = "BE0123456789" },
|
||||
new() { Country = "BO", Code = "bo_tin", Description = "Bolivian tax ID", Example = "123456789" },
|
||||
new()
|
||||
{
|
||||
Country = "BR",
|
||||
Code = "br_cnpj",
|
||||
Description = "Brazilian CNPJ number",
|
||||
Example = "01.234.456/5432-10"
|
||||
},
|
||||
new() { Country = "BR", Code = "br_cpf", Description = "Brazilian CPF number", Example = "123.456.789-87" },
|
||||
new()
|
||||
{
|
||||
Country = "BG",
|
||||
Code = "bg_uic",
|
||||
Description = "Bulgaria Unified Identification Code",
|
||||
Example = "123456789"
|
||||
},
|
||||
new() { Country = "BG", Code = "eu_vat", Description = "European VAT number", Example = "BG0123456789" },
|
||||
new() { Country = "CA", Code = "ca_bn", Description = "Canadian BN", Example = "123456789" },
|
||||
new()
|
||||
{
|
||||
Country = "CA",
|
||||
Code = "ca_gst_hst",
|
||||
Description = "Canadian GST/HST number",
|
||||
Example = "123456789RT0002"
|
||||
},
|
||||
new()
|
||||
{
|
||||
Country = "CA",
|
||||
Code = "ca_pst_bc",
|
||||
Description = "Canadian PST number (British Columbia)",
|
||||
Example = "PST-1234-5678"
|
||||
},
|
||||
new()
|
||||
{
|
||||
Country = "CA",
|
||||
Code = "ca_pst_mb",
|
||||
Description = "Canadian PST number (Manitoba)",
|
||||
Example = "123456-7"
|
||||
},
|
||||
new()
|
||||
{
|
||||
Country = "CA",
|
||||
Code = "ca_pst_sk",
|
||||
Description = "Canadian PST number (Saskatchewan)",
|
||||
Example = "1234567"
|
||||
},
|
||||
new()
|
||||
{
|
||||
Country = "CA",
|
||||
Code = "ca_qst",
|
||||
Description = "Canadian QST number (Québec)",
|
||||
Example = "1234567890TQ1234"
|
||||
},
|
||||
new() { Country = "CL", Code = "cl_tin", Description = "Chilean TIN", Example = "12.345.678-K" },
|
||||
new() { Country = "CN", Code = "cn_tin", Description = "Chinese tax ID", Example = "123456789012345678" },
|
||||
new() { Country = "CO", Code = "co_nit", Description = "Colombian NIT number", Example = "123.456.789-0" },
|
||||
new() { Country = "CR", Code = "cr_tin", Description = "Costa Rican tax ID", Example = "1-234-567890" },
|
||||
new() { Country = "HR", Code = "eu_vat", Description = "European VAT number", Example = "HR12345678912" },
|
||||
new()
|
||||
{
|
||||
Country = "HR",
|
||||
Code = "hr_oib",
|
||||
Description = "Croatian Personal Identification Number",
|
||||
Example = "12345678901"
|
||||
},
|
||||
new() { Country = "CY", Code = "eu_vat", Description = "European VAT number", Example = "CY12345678Z" },
|
||||
new() { Country = "CZ", Code = "eu_vat", Description = "European VAT number", Example = "CZ1234567890" },
|
||||
new() { Country = "DK", Code = "eu_vat", Description = "European VAT number", Example = "DK12345678" },
|
||||
new() { Country = "DO", Code = "do_rcn", Description = "Dominican RCN number", Example = "123-4567890-1" },
|
||||
new() { Country = "EC", Code = "ec_ruc", Description = "Ecuadorian RUC number", Example = "1234567890001" },
|
||||
new()
|
||||
{
|
||||
Country = "EG",
|
||||
Code = "eg_tin",
|
||||
Description = "Egyptian Tax Identification Number",
|
||||
Example = "123456789"
|
||||
},
|
||||
|
||||
new()
|
||||
{
|
||||
Country = "SV",
|
||||
Code = "sv_nit",
|
||||
Description = "El Salvadorian NIT number",
|
||||
Example = "1234-567890-123-4"
|
||||
},
|
||||
|
||||
new() { Country = "EE", Code = "eu_vat", Description = "European VAT number", Example = "EE123456789" },
|
||||
|
||||
new()
|
||||
{
|
||||
Country = "EU",
|
||||
Code = "eu_oss_vat",
|
||||
Description = "European One Stop Shop VAT number for non-Union scheme",
|
||||
Example = "EU123456789"
|
||||
},
|
||||
new() { Country = "FI", Code = "eu_vat", Description = "European VAT number", Example = "FI12345678" },
|
||||
new() { Country = "FR", Code = "eu_vat", Description = "European VAT number", Example = "FRAB123456789" },
|
||||
new() { Country = "GE", Code = "ge_vat", Description = "Georgian VAT", Example = "123456789" },
|
||||
new()
|
||||
{
|
||||
Country = "DE",
|
||||
Code = "de_stn",
|
||||
Description = "German Tax Number (Steuernummer)",
|
||||
Example = "1234567890"
|
||||
},
|
||||
new() { Country = "DE", Code = "eu_vat", Description = "European VAT number", Example = "DE123456789" },
|
||||
new() { Country = "GR", Code = "eu_vat", Description = "European VAT number", Example = "EL123456789" },
|
||||
new() { Country = "HK", Code = "hk_br", Description = "Hong Kong BR number", Example = "12345678" },
|
||||
new() { Country = "HU", Code = "eu_vat", Description = "European VAT number", Example = "HU12345678" },
|
||||
new()
|
||||
{
|
||||
Country = "HU",
|
||||
Code = "hu_tin",
|
||||
Description = "Hungary tax number (adószám)",
|
||||
Example = "12345678-1-23"
|
||||
},
|
||||
new() { Country = "IS", Code = "is_vat", Description = "Icelandic VAT", Example = "123456" },
|
||||
new() { Country = "IN", Code = "in_gst", Description = "Indian GST number", Example = "12ABCDE3456FGZH" },
|
||||
new()
|
||||
{
|
||||
Country = "ID",
|
||||
Code = "id_npwp",
|
||||
Description = "Indonesian NPWP number",
|
||||
Example = "012.345.678.9-012.345"
|
||||
},
|
||||
new() { Country = "IE", Code = "eu_vat", Description = "European VAT number", Example = "IE1234567AB" },
|
||||
new() { Country = "IL", Code = "il_vat", Description = "Israel VAT", Example = "000012345" },
|
||||
new() { Country = "IT", Code = "eu_vat", Description = "European VAT number", Example = "IT12345678912" },
|
||||
new()
|
||||
{
|
||||
Country = "JP",
|
||||
Code = "jp_cn",
|
||||
Description = "Japanese Corporate Number (*Hōjin Bangō*)",
|
||||
Example = "1234567891234"
|
||||
},
|
||||
new()
|
||||
{
|
||||
Country = "JP",
|
||||
Code = "jp_rn",
|
||||
Description =
|
||||
"Japanese Registered Foreign Businesses' Registration Number (*Tōroku Kokugai Jigyōsha no Tōroku Bangō*)",
|
||||
Example = "12345"
|
||||
},
|
||||
new()
|
||||
{
|
||||
Country = "JP",
|
||||
Code = "jp_trn",
|
||||
Description = "Japanese Tax Registration Number (*Tōroku Bangō*)",
|
||||
Example = "T1234567891234"
|
||||
},
|
||||
new()
|
||||
{
|
||||
Country = "KZ",
|
||||
Code = "kz_bin",
|
||||
Description = "Kazakhstani Business Identification Number",
|
||||
Example = "123456789012"
|
||||
},
|
||||
new()
|
||||
{
|
||||
Country = "KE",
|
||||
Code = "ke_pin",
|
||||
Description = "Kenya Revenue Authority Personal Identification Number",
|
||||
Example = "P000111111A"
|
||||
},
|
||||
new() { Country = "LV", Code = "eu_vat", Description = "European VAT number", Example = "LV12345678912" },
|
||||
new()
|
||||
{
|
||||
Country = "LI",
|
||||
Code = "li_uid",
|
||||
Description = "Liechtensteinian UID number",
|
||||
Example = "CHE123456789"
|
||||
},
|
||||
new() { Country = "LI", Code = "li_vat", Description = "Liechtensteinian VAT number", Example = "12345" },
|
||||
new() { Country = "LT", Code = "eu_vat", Description = "European VAT number", Example = "LT123456789123" },
|
||||
new() { Country = "LU", Code = "eu_vat", Description = "European VAT number", Example = "LU12345678" },
|
||||
new() { Country = "MY", Code = "my_frp", Description = "Malaysian FRP number", Example = "12345678" },
|
||||
new() { Country = "MY", Code = "my_itn", Description = "Malaysian ITN", Example = "C 1234567890" },
|
||||
new() { Country = "MY", Code = "my_sst", Description = "Malaysian SST number", Example = "A12-3456-78912345" },
|
||||
new() { Country = "MT", Code = "eu_vat", Description = "European VAT number", Example = "MT12345678" },
|
||||
new() { Country = "MX", Code = "mx_rfc", Description = "Mexican RFC number", Example = "ABC010203AB9" },
|
||||
new() { Country = "MD", Code = "md_vat", Description = "Moldova VAT Number", Example = "1234567" },
|
||||
new() { Country = "MA", Code = "ma_vat", Description = "Morocco VAT Number", Example = "12345678" },
|
||||
new() { Country = "NL", Code = "eu_vat", Description = "European VAT number", Example = "NL123456789B12" },
|
||||
new() { Country = "NZ", Code = "nz_gst", Description = "New Zealand GST number", Example = "123456789" },
|
||||
new() { Country = "NG", Code = "ng_tin", Description = "Nigerian TIN Number", Example = "12345678-0001" },
|
||||
new() { Country = "NO", Code = "no_vat", Description = "Norwegian VAT number", Example = "123456789MVA" },
|
||||
new()
|
||||
{
|
||||
Country = "NO",
|
||||
Code = "no_voec",
|
||||
Description = "Norwegian VAT on e-commerce number",
|
||||
Example = "1234567"
|
||||
},
|
||||
new() { Country = "OM", Code = "om_vat", Description = "Omani VAT Number", Example = "OM1234567890" },
|
||||
new() { Country = "PE", Code = "pe_ruc", Description = "Peruvian RUC number", Example = "12345678901" },
|
||||
new()
|
||||
{
|
||||
Country = "PH",
|
||||
Code = "ph_tin",
|
||||
Description = "Philippines Tax Identification Number",
|
||||
Example = "123456789012"
|
||||
},
|
||||
new() { Country = "PL", Code = "eu_vat", Description = "European VAT number", Example = "PL1234567890" },
|
||||
new() { Country = "PT", Code = "eu_vat", Description = "European VAT number", Example = "PT123456789" },
|
||||
new() { Country = "RO", Code = "eu_vat", Description = "European VAT number", Example = "RO1234567891" },
|
||||
new() { Country = "RO", Code = "ro_tin", Description = "Romanian tax ID number", Example = "1234567890123" },
|
||||
new() { Country = "RU", Code = "ru_inn", Description = "Russian INN", Example = "1234567891" },
|
||||
new() { Country = "RU", Code = "ru_kpp", Description = "Russian KPP", Example = "123456789" },
|
||||
new() { Country = "SA", Code = "sa_vat", Description = "Saudi Arabia VAT", Example = "123456789012345" },
|
||||
new() { Country = "RS", Code = "rs_pib", Description = "Serbian PIB number", Example = "123456789" },
|
||||
new() { Country = "SG", Code = "sg_gst", Description = "Singaporean GST", Example = "M12345678X" },
|
||||
new() { Country = "SG", Code = "sg_uen", Description = "Singaporean UEN", Example = "123456789F" },
|
||||
new() { Country = "SK", Code = "eu_vat", Description = "European VAT number", Example = "SK1234567891" },
|
||||
new() { Country = "SI", Code = "eu_vat", Description = "European VAT number", Example = "SI12345678" },
|
||||
new()
|
||||
{
|
||||
Country = "SI",
|
||||
Code = "si_tin",
|
||||
Description = "Slovenia tax number (davčna številka)",
|
||||
Example = "12345678"
|
||||
},
|
||||
new() { Country = "ZA", Code = "za_vat", Description = "South African VAT number", Example = "4123456789" },
|
||||
new() { Country = "KR", Code = "kr_brn", Description = "Korean BRN", Example = "123-45-67890" },
|
||||
new() { Country = "ES", Code = "es_cif", Description = "Spanish NIF/CIF number", Example = "A12345678" },
|
||||
new() { Country = "ES", Code = "eu_vat", Description = "European VAT number", Example = "ESA1234567Z" },
|
||||
new() { Country = "SE", Code = "eu_vat", Description = "European VAT number", Example = "SE123456789123" },
|
||||
new()
|
||||
{
|
||||
Country = "CH",
|
||||
Code = "ch_uid",
|
||||
Description = "Switzerland UID number",
|
||||
Example = "CHE-123.456.789 HR"
|
||||
},
|
||||
new()
|
||||
{
|
||||
Country = "CH",
|
||||
Code = "ch_vat",
|
||||
Description = "Switzerland VAT number",
|
||||
Example = "CHE-123.456.789 MWST"
|
||||
},
|
||||
new() { Country = "TW", Code = "tw_vat", Description = "Taiwanese VAT", Example = "12345678" },
|
||||
new() { Country = "TZ", Code = "tz_vat", Description = "Tanzania VAT Number", Example = "12345678A" },
|
||||
new() { Country = "TH", Code = "th_vat", Description = "Thai VAT", Example = "1234567891234" },
|
||||
new() { Country = "TR", Code = "tr_tin", Description = "Turkish TIN Number", Example = "0123456789" },
|
||||
new() { Country = "UA", Code = "ua_vat", Description = "Ukrainian VAT", Example = "123456789" },
|
||||
new()
|
||||
{
|
||||
Country = "AE",
|
||||
Code = "ae_trn",
|
||||
Description = "United Arab Emirates TRN",
|
||||
Example = "123456789012345"
|
||||
},
|
||||
new() { Country = "GB", Code = "eu_vat", Description = "Northern Ireland VAT number", Example = "XI123456789" },
|
||||
new() { Country = "GB", Code = "gb_vat", Description = "United Kingdom VAT number", Example = "GB123456789" },
|
||||
new() { Country = "US", Code = "us_ein", Description = "United States EIN", Example = "12-3456789" },
|
||||
new() { Country = "UY", Code = "uy_ruc", Description = "Uruguayan RUC number", Example = "123456789012" },
|
||||
new() { Country = "UZ", Code = "uz_tin", Description = "Uzbekistan TIN Number", Example = "123456789" },
|
||||
new() { Country = "UZ", Code = "uz_vat", Description = "Uzbekistan VAT Number", Example = "123456789012" },
|
||||
new() { Country = "VE", Code = "ve_rif", Description = "Venezuelan RIF number", Example = "A-12345678-9" },
|
||||
new() { Country = "VN", Code = "vn_tin", Description = "Vietnamese tax ID number", Example = "1234567890" }
|
||||
];
|
||||
}
|
||||
|
@ -1545,7 +1545,7 @@ public class SubscriberServiceTests
|
||||
{
|
||||
var stripeAdapter = sutProvider.GetDependency<IStripeAdapter>();
|
||||
|
||||
var customer = new Customer { Id = provider.GatewayCustomerId, TaxIds = new StripeList<TaxId> { Data = [new TaxId { Id = "tax_id_1" }] } };
|
||||
var customer = new Customer { Id = provider.GatewayCustomerId, TaxIds = new StripeList<TaxId> { Data = [new TaxId { Id = "tax_id_1", Type = "us_ein" }] } };
|
||||
|
||||
stripeAdapter.CustomerGetAsync(provider.GatewayCustomerId, Arg.Is<CustomerGetOptions>(
|
||||
options => options.Expand.Contains("tax_ids"))).Returns(customer);
|
||||
@ -1554,6 +1554,7 @@ public class SubscriberServiceTests
|
||||
"US",
|
||||
"12345",
|
||||
"123456789",
|
||||
"us_ein",
|
||||
"123 Example St.",
|
||||
null,
|
||||
"Example Town",
|
||||
|
Loading…
Reference in New Issue
Block a user