2022-12-12 07:15:46 +01:00
|
|
|
|
// ***********************************************************************
|
|
|
|
|
// Assembly : FCS.Lib.Utility
|
|
|
|
|
// Author : FH
|
|
|
|
|
// Created : 03-30-2022
|
|
|
|
|
//
|
|
|
|
|
// Last Modified By : FH
|
|
|
|
|
// Last Modified On : 04-19-2022
|
|
|
|
|
// ***********************************************************************
|
2022-12-14 11:31:05 +01:00
|
|
|
|
// <copyright file="VatFormatValidator.cs" company="FCS-TECH">
|
2022-12-12 07:15:46 +01:00
|
|
|
|
// Copyright (C) 2022 FCS Frede's Computer Services.
|
|
|
|
|
// This program is free software: you can redistribute it and/or modify
|
|
|
|
|
// it under the terms of the GNU Affero General Public License as
|
|
|
|
|
// published by the Free Software Foundation, either version 3 of the
|
|
|
|
|
// License, or (at your option) any later version.
|
|
|
|
|
//
|
|
|
|
|
// This program is distributed in the hope that it will be useful,
|
|
|
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
|
// GNU Affero General Public License for more details.
|
|
|
|
|
//
|
|
|
|
|
// You should have received a copy of the GNU Affero General Public License
|
|
|
|
|
// along with this program. If not, see [https://www.gnu.org/licenses]
|
|
|
|
|
// </copyright>
|
|
|
|
|
// <summary></summary>
|
|
|
|
|
// ***********************************************************************
|
2023-01-19 13:50:04 +01:00
|
|
|
|
|
|
|
|
|
using System.Collections.Generic;
|
2022-12-12 07:15:46 +01:00
|
|
|
|
using System.Linq;
|
2023-01-19 13:50:04 +01:00
|
|
|
|
using System.Text.RegularExpressions;
|
2022-12-12 07:15:46 +01:00
|
|
|
|
|
|
|
|
|
namespace FCS.Lib.Utility
|
|
|
|
|
{
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Vat format validator
|
|
|
|
|
/// </summary>
|
|
|
|
|
public static class VatFormatValidator
|
|
|
|
|
{
|
|
|
|
|
// https://ec.europa.eu/taxation_customs/vies/faqvies.do#item_11
|
|
|
|
|
// https://ec.europa.eu/taxation_customs/vies/
|
|
|
|
|
|
2023-01-19 13:50:04 +01:00
|
|
|
|
|
|
|
|
|
//https://www.bolagsverket.se/apierochoppnadata.2531.html
|
|
|
|
|
|
2022-12-12 07:15:46 +01:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Check vat number format
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="countryCode"></param>
|
|
|
|
|
/// <param name="vatNumber"></param>
|
|
|
|
|
/// <returns>bool indicating if the vat number conform to country specification</returns>
|
|
|
|
|
public static bool CheckVat(string countryCode, string vatNumber)
|
|
|
|
|
{
|
2023-02-01 13:11:35 +01:00
|
|
|
|
if (string.IsNullOrWhiteSpace(vatNumber))
|
|
|
|
|
return false;
|
|
|
|
|
|
2023-01-19 13:50:04 +01:00
|
|
|
|
var sanitizedVat = SanitizeVatNumber(vatNumber);
|
2023-02-01 13:11:35 +01:00
|
|
|
|
|
2022-12-12 07:15:46 +01:00
|
|
|
|
return countryCode.ToUpperInvariant() switch
|
|
|
|
|
{
|
2023-01-19 13:50:04 +01:00
|
|
|
|
"DK" => ValidateDkVat(sanitizedVat),
|
|
|
|
|
"NO" => ValidateNoOrg(sanitizedVat),
|
|
|
|
|
"SE" => ValidateSeOrg(sanitizedVat),
|
2022-12-12 07:15:46 +01:00
|
|
|
|
_ => false
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// sanitize vat number
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="vatNumber"></param>
|
|
|
|
|
/// <returns>sanitized string</returns>
|
|
|
|
|
public static string SanitizeVatNumber(string vatNumber)
|
|
|
|
|
{
|
2023-01-19 13:50:04 +01:00
|
|
|
|
if (string.IsNullOrWhiteSpace(vatNumber))
|
|
|
|
|
return "";
|
|
|
|
|
// remove anything but digits
|
|
|
|
|
var regexObj = new Regex(@"[^\d]");
|
|
|
|
|
return regexObj.Replace(vatNumber, "");
|
2022-12-12 07:15:46 +01:00
|
|
|
|
}
|
|
|
|
|
|
2023-01-19 13:50:04 +01:00
|
|
|
|
private static bool ValidateDkVat(string vatNumber)
|
2022-12-12 07:15:46 +01:00
|
|
|
|
{
|
|
|
|
|
// https://wiki.scn.sap.com/wiki/display/CRM/Denmark
|
|
|
|
|
// 8 digits 0 to 9
|
|
|
|
|
// C1..C7
|
|
|
|
|
// C8 check-digit MOD11
|
|
|
|
|
// C1 > 0
|
|
|
|
|
// R = (2*C1 + 7*C2 + 6*C3 + 5*C4 + 4*C5 + 3*C6 + 2*C7 + C8)
|
|
|
|
|
if(vatNumber.Length == 8 && long.TryParse(vatNumber, out _))
|
|
|
|
|
return ValidateMod11(vatNumber);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-19 13:50:04 +01:00
|
|
|
|
private static bool ValidateNoOrg(string vatNumber)
|
2022-12-12 07:15:46 +01:00
|
|
|
|
{
|
|
|
|
|
// https://wiki.scn.sap.com/wiki/display/CRM/Norway
|
|
|
|
|
// 12 digits
|
|
|
|
|
// C1..C8 random 0 to 9
|
|
|
|
|
// C9 check-digit MOD11
|
|
|
|
|
// C10 C11 C12 chars == MVA
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
if (vatNumber.Length == 9 && long.TryParse(vatNumber, out _))
|
|
|
|
|
return ValidateMod11(vatNumber);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
catch
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-19 13:50:04 +01:00
|
|
|
|
private static bool ValidateSeOrg(string orgNumber)
|
2022-12-12 07:15:46 +01:00
|
|
|
|
{
|
|
|
|
|
// https://wiki.scn.sap.com/wiki/display/CRM/Sweden
|
|
|
|
|
// 12 digits 0 to 9
|
|
|
|
|
// C10 = (10 – (18 + 5 + 1 + 8 + 4)MOD10 10) MOD10
|
|
|
|
|
// R = S1 + S3 + S5 + S7 + S9
|
|
|
|
|
// Si = int(Ci/5) + (Ci*2)MOD10)
|
|
|
|
|
// https://www.skatteverket.se/skatter/mervardesskattmoms/momsregistreringsnummer.4.18e1b10334ebe8bc80002649.html
|
2023-01-19 13:50:04 +01:00
|
|
|
|
// EU MOMS => C11 C12 == 01 (De två sista siffrorna är alltid 01)
|
|
|
|
|
// C11 C12 is not used inside Sweden
|
|
|
|
|
// C1 is type of org and C2 to C9 is org number
|
|
|
|
|
// C10 is check digit
|
|
|
|
|
|
|
|
|
|
var orgToCheck = orgNumber;
|
|
|
|
|
if (long.Parse(orgToCheck) == 0)
|
2022-12-12 07:15:46 +01:00
|
|
|
|
return false;
|
|
|
|
|
|
2023-01-19 13:50:04 +01:00
|
|
|
|
switch (orgToCheck.Length)
|
|
|
|
|
{
|
|
|
|
|
// personal vat se
|
|
|
|
|
case 6:
|
|
|
|
|
return ValidateFormatSeExt(orgToCheck);
|
|
|
|
|
|
|
|
|
|
case < 10:
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
// strip EU extension `01`
|
|
|
|
|
case 12:
|
|
|
|
|
orgNumber = orgNumber.Substring(0, 10);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var c10 = C10(orgToCheck);
|
|
|
|
|
|
|
|
|
|
// compare calculated org number with incoming org number
|
|
|
|
|
return $"{orgToCheck.Substring(0, 9)}{c10}" == orgNumber;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static int C10(string vatToCheck)
|
|
|
|
|
{
|
|
|
|
|
// check digit calculation
|
2022-12-12 07:15:46 +01:00
|
|
|
|
var r = new[] { 0, 2, 4, 6, 8 }
|
2023-01-19 13:50:04 +01:00
|
|
|
|
.Sum(m => (int)char.GetNumericValue(vatToCheck[m]) / 5 +
|
|
|
|
|
(int)char.GetNumericValue(vatToCheck[m]) * 2 % 10);
|
|
|
|
|
var c1 = new[] { 1, 3, 5, 7 }.Sum(m => (int)char.GetNumericValue(vatToCheck[m]));
|
2022-12-12 07:15:46 +01:00
|
|
|
|
var c10 = (10 - (r + c1) % 10) % 10;
|
2023-01-19 13:50:04 +01:00
|
|
|
|
return c10;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static bool ValidateFormatSeExt(string ssn)
|
|
|
|
|
{
|
|
|
|
|
// Swedish personally held companies uses SSN number
|
|
|
|
|
// a relaxed validation is required as only first 6 digits is supplied
|
|
|
|
|
// birthday format e.g. 991231
|
|
|
|
|
|
|
|
|
|
if (ssn.Length is not 6 or 10 || int.Parse(ssn) == 0)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
var y = int.Parse(ssn.Substring(0,2));
|
|
|
|
|
var m = int.Parse(ssn.Substring(2,2));
|
|
|
|
|
var d = int.Parse(ssn.Substring(4,2));
|
|
|
|
|
// this calculation is only valid within 21st century
|
|
|
|
|
var leap = y % 4 == 0; // 2000 was a leap year;
|
|
|
|
|
// day
|
|
|
|
|
if(d is < 1 or > 31)
|
|
|
|
|
return false;
|
|
|
|
|
// month
|
|
|
|
|
switch (m)
|
|
|
|
|
{
|
|
|
|
|
// feb
|
|
|
|
|
case 2:
|
|
|
|
|
{
|
|
|
|
|
if (leap)
|
|
|
|
|
return d <= 29;
|
|
|
|
|
return d <= 28;
|
|
|
|
|
}
|
|
|
|
|
// apr, jun, sep, nov
|
|
|
|
|
case 4 or 6 or 9 or 11:
|
|
|
|
|
return d <= 30;
|
|
|
|
|
// jan, mar, may, july, aug, oct, dec
|
|
|
|
|
case 1 or 3 or 5 or 7 or 8 or 10 or 12:
|
|
|
|
|
return true;
|
|
|
|
|
// does not exist
|
|
|
|
|
default:
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2022-12-12 07:15:46 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static bool ValidateMod11(string number)
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
if (long.Parse(number) == 0)
|
|
|
|
|
return false;
|
|
|
|
|
var sum = 0;
|
|
|
|
|
for (int i = number.Length - 1, multiplier = 1; i >= 0; i--)
|
|
|
|
|
{
|
|
|
|
|
// Console.WriteLine($"char: {number[i]} multiplier: {multiplier}");
|
|
|
|
|
sum += (int)char.GetNumericValue(number[i]) * multiplier;
|
|
|
|
|
if (++multiplier > 7) multiplier = 2;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return sum % 11 == 0;
|
|
|
|
|
}
|
|
|
|
|
catch
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-19 13:50:04 +01:00
|
|
|
|
|
|
|
|
|
///// <summary>
|
|
|
|
|
/////
|
|
|
|
|
///// </summary>
|
|
|
|
|
///// <param name="ssn"></param>
|
|
|
|
|
///// <returns></returns>
|
|
|
|
|
//public static string FakeVatSsnSe(string ssn)
|
|
|
|
|
//{
|
|
|
|
|
// var fake = ssn.PadRight(9, '8');
|
|
|
|
|
// var c10 = SeGenerateCheckDigit(fake);
|
|
|
|
|
// return $"{fake}{c10}";
|
|
|
|
|
//}
|
|
|
|
|
|
|
|
|
|
///// <summary>
|
|
|
|
|
/////
|
|
|
|
|
///// </summary>
|
|
|
|
|
///// <param name="org"></param>
|
|
|
|
|
///// <returns></returns>
|
|
|
|
|
//public static bool OrgIsPrivate(string org)
|
|
|
|
|
//{
|
|
|
|
|
// var orgType = new List<string>() { };
|
|
|
|
|
|
|
|
|
|
// return ValidateFormatSeExt(org.Substring(0, 5));
|
|
|
|
|
//}
|
|
|
|
|
|
|
|
|
|
///// <summary>
|
|
|
|
|
/////
|
|
|
|
|
///// </summary>
|
|
|
|
|
///// <param name="vatNumber"></param>
|
|
|
|
|
///// <returns></returns>
|
|
|
|
|
//public static bool CheckLuhn(string vatNumber)
|
|
|
|
|
//{
|
|
|
|
|
// // https://www.geeksforgeeks.org/luhn-algorithm/
|
|
|
|
|
|
|
|
|
|
// var nDigits = vatNumber.Length;
|
|
|
|
|
// var nSum = 0;
|
|
|
|
|
// var isSecond = false;
|
|
|
|
|
// for (var i = nDigits - 1; i >= 0; i--)
|
|
|
|
|
// {
|
|
|
|
|
// var d = (int)char.GetNumericValue(vatNumber[i]) - '0';
|
|
|
|
|
// if (isSecond)
|
|
|
|
|
// d *= 2;
|
|
|
|
|
// // We add two digits to handle
|
|
|
|
|
// // cases that make two digits
|
|
|
|
|
// // after doubling
|
|
|
|
|
// nSum += d / 10;
|
|
|
|
|
// nSum += d % 10;
|
|
|
|
|
|
|
|
|
|
// isSecond = !isSecond;
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
// return nSum % 10 == 0;
|
|
|
|
|
//}
|
|
|
|
|
|
|
|
|
|
|
2022-12-12 07:15:46 +01:00
|
|
|
|
//private static bool ValidateMod10(string number)
|
|
|
|
|
//{
|
|
|
|
|
// if (long.Parse(number) == 0)
|
|
|
|
|
// return false;
|
|
|
|
|
|
|
|
|
|
// var nDigits = number.Length;
|
|
|
|
|
// var nSum = 0;
|
|
|
|
|
// var isSecond = false;
|
|
|
|
|
// for (var i = nDigits - 1; i >= 0; i--)
|
|
|
|
|
// {
|
|
|
|
|
// var d = (int)char.GetNumericValue(number[i]) - '0';
|
|
|
|
|
// if (isSecond)
|
|
|
|
|
// d *= 2;
|
|
|
|
|
// nSum += d / 10;
|
|
|
|
|
// nSum += d % 10;
|
|
|
|
|
// isSecond = !isSecond;
|
|
|
|
|
// }
|
|
|
|
|
// return nSum % 10 == 0;
|
|
|
|
|
//}
|
|
|
|
|
|
|
|
|
|
//private static string GetMod10CheckDigit(string number)
|
|
|
|
|
//{
|
|
|
|
|
// var sum = 0;
|
|
|
|
|
// var alt = true;
|
|
|
|
|
// var digits = number.ToCharArray();
|
|
|
|
|
// for (var i = digits.Length - 1; i >= 0; i--)
|
|
|
|
|
// {
|
|
|
|
|
// var curDigit = digits[i] - 48;
|
|
|
|
|
// if (alt)
|
|
|
|
|
// {
|
|
|
|
|
// curDigit *= 2;
|
|
|
|
|
// if (curDigit > 9)
|
|
|
|
|
// curDigit -= 9;
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
// sum += curDigit;
|
|
|
|
|
// alt = !alt;
|
|
|
|
|
// }
|
|
|
|
|
// return sum % 10 == 0 ? "0" : (10 - sum % 10).ToString();
|
|
|
|
|
//}
|
|
|
|
|
|
|
|
|
|
//private string AddMod11CheckDigit(string number)
|
|
|
|
|
//{
|
|
|
|
|
// return number + GetMod11CheckDigit(number);
|
|
|
|
|
//}
|
|
|
|
|
|
|
|
|
|
//private static string GetMod11CheckDigit(string number)
|
|
|
|
|
//{
|
|
|
|
|
// var sum = 0;
|
|
|
|
|
// for (int i = number.Length - 1, multiplier = 2; i >= 0; i--)
|
|
|
|
|
// {
|
|
|
|
|
// sum += (int)char.GetNumericValue(number[i]) * multiplier;
|
|
|
|
|
// if (++multiplier > 7) multiplier = 2;
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
// var modulo = sum % 11;
|
|
|
|
|
// return modulo is 0 or 1 ? "0" : (11 - modulo).ToString();
|
|
|
|
|
//}
|
|
|
|
|
|
|
|
|
|
//private static bool CheckLuhn(string vatNumber)
|
|
|
|
|
//{
|
|
|
|
|
// // https://www.geeksforgeeks.org/luhn-algorithm/
|
|
|
|
|
|
|
|
|
|
// var nDigits = vatNumber.Length;
|
|
|
|
|
// var nSum = 0;
|
|
|
|
|
// var isSecond = false;
|
|
|
|
|
// for (var i = nDigits - 1; i >= 0; i--)
|
|
|
|
|
// {
|
|
|
|
|
// var d = (int)char.GetNumericValue(vatNumber[i]) - '0';
|
|
|
|
|
// if (isSecond)
|
|
|
|
|
// d *= 2;
|
|
|
|
|
// // We add two digits to handle
|
|
|
|
|
// // cases that make two digits
|
|
|
|
|
// // after doubling
|
|
|
|
|
// nSum += d / 10;
|
|
|
|
|
// nSum += d % 10;
|
|
|
|
|
|
|
|
|
|
// isSecond = !isSecond;
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
// return nSum % 10 == 0;
|
|
|
|
|
//}
|
|
|
|
|
|
|
|
|
|
}
|
2022-04-06 12:02:25 +02:00
|
|
|
|
}
|