// *********************************************************************** // Assembly : FCS.Lib.Utility // Author : fhdk // Created : 2023 03 09 17:42 // // Last Modified By: fhdk // Last Modified On : 2023 03 14 09:16 // *********************************************************************** // // Copyright (C) 2023-2023 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] // // // *********************************************************************** using System.Linq; using System.Text.RegularExpressions; namespace FCS.Lib.Utility; /// /// Vat format validator /// public static class VatFormatValidator { // https://ec.europa.eu/taxation_customs/vies/faqvies.do#item_11 // https://ec.europa.eu/taxation_customs/vies/ //https://www.bolagsverket.se/apierochoppnadata.2531.html /// /// Check vat number format /// /// /// /// bool indicating if the vat number conform to country specification public static bool CheckVat(string countryCode, string vatNumber) { if (string.IsNullOrWhiteSpace(vatNumber)) return false; var sanitizedVat = SanitizeVatNumber(vatNumber); return countryCode.ToUpperInvariant() switch { "DK" => ValidateDkVat(sanitizedVat), "NO" => ValidateNoOrg(sanitizedVat), "SE" => ValidateSeOrg(sanitizedVat), _ => false }; } /// /// sanitize vat number /// /// /// sanitized string public static string SanitizeVatNumber(string vatNumber) { if (string.IsNullOrWhiteSpace(vatNumber)) return ""; // remove anything but digits var regexObj = new Regex(@"[^\d]"); return regexObj.Replace(vatNumber, ""); } private static bool ValidateDkVat(string vatNumber) { // 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; } private static bool ValidateNoOrg(string vatNumber) { // 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; } } private static bool ValidateSeOrg(string orgNumber) { // 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 // 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.TryParse(orgToCheck, out _)) return false; 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 var r = new[] { 0, 2, 4, 6, 8 } .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])); var c10 = (10 - (r + c1) % 10) % 10; 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; } } 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; } } ///// ///// ///// ///// ///// //public static string FakeVatSsnSe(string ssn) //{ // var fake = ssn.PadRight(9, '8'); // var c10 = SeGenerateCheckDigit(fake); // return $"{fake}{c10}"; //} ///// ///// ///// ///// ///// //public static bool OrgIsPrivate(string org) //{ // var orgType = new List() { }; // return ValidateFormatSeExt(org.Substring(0, 5)); //} ///// ///// ///// ///// ///// //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; //} //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; //} }