"""
This module contains a set of functions to validate a VAT number according to
a country VAT format rules. Besides the general regex match, the validation
functions perform some country specific calculations on the digits using
algorithms such as MOD 11 or Lunh's algorithm.
All of these functions are in the format ``validate_vat_XX`` where XX is the
IS0 3166 country code. They receive a string representing a VAT number in that
country format and return ``True`` or ``False`` whether that code is valid or
not.
**Usage**:
>>> validate_vat_pt('PT980405319')
True
>>> validate_vat_pt('PT-980 405 319')
True
>>> validate_vat_pt('80405319')
False
Each function is responsible to sanitize the input (remove preciding country
code, spaces, punctuation, *etc...*)
.. seealso::
The list of VAT validation algorithms are published here:
https://ec.europa.eu/taxation_customs/tin/
This wikipedia page contains a great overview of the different formats:
https://en.wikipedia.org/wiki/VAT_identification_number
"""
from functools import reduce
from math import floor, ceil
from typing import Callable, Dict, List, Optional
import re
[docs]def validate_vat_at(vat: str) -> bool:
"""Validates a VAT number against austrian VAT format specification.
In Austria is also named "Umsatzsteuer-Identifikationsnummer" (UID).
The number must contain the letter 'U' followed by 8 digits and the last
digit is the check digit.
:param vat: VAT number to validate.
:return: ``True`` if the given VAT is valid, ``False`` otherwise.
"""
def calc_check_digit(code: str) -> int:
c2, c3, c4, c5, c6, c7, c8 = map(int, code)
s3 = floor(c3 / 5) + (c3 * 2) % 10
s5 = floor(c5 / 5) + (c5 * 2) % 10
s7 = floor(c7 / 5) + (c7 * 2) % 10
return (10 - (s3 + s5 + s7 + c2 + c4 + c6 + c8 + 4) % 10) % 10
sanitized_vat = re.sub(r"(^AT)|\s*|\W*", "", vat, flags=re.I)
if len(sanitized_vat) != 9:
return False
if sanitized_vat[0] != "U" and sanitized_vat[0] != "u":
return False
if not sanitized_vat[1:].isdigit():
return False
return calc_check_digit(sanitized_vat[1:-1]) == int(sanitized_vat[-1])
[docs]def validate_vat_be(vat: str) -> bool:
"""Validates a VAT number against belgian VAT format specification.
In Belgium is also named "BTW identificatienummer" (BTW-nr).
The number must contain 10 digits starting with 0 or 1. The old numbering
schema had 9 digits, and is also accepted by this function.
:param vat: VAT number to validate.
:return: ``True`` if the given VAT is valid, ``False`` otherwise.
"""
sanitized_vat = re.sub(r"(^BE)|\s*|\W*", "", vat, flags=re.I)
# If the VAT is in the old format, convert to the new one
if len(sanitized_vat) == 9:
sanitized_vat = "0" + sanitized_vat
if len(sanitized_vat) != 10:
return False
if not sanitized_vat.isdigit():
return False
if sanitized_vat[0] != "0" or sanitized_vat[1] == "0":
return False
return 97 - int(sanitized_vat[:-2]) % 97 == int(sanitized_vat[-2:])
[docs]def validate_vat_bg(vat: str) -> bool:
"""Validates a VAT number against bulgarian VAT format specification.
In Bulgary is also named "Identifikacionen nomer po DDS" (ДДС номер ).
The number must contain 9 or 10 digits. It assumes one of four formats:
"legal entities", "physical persons", "foreigners" and "miscellaneous".
:param vat: VAT number to validate.
:return: ``True`` if the given VAT is valid, ``False`` otherwise.
"""
def calc_check_digit_legal_entity(code: str) -> int:
check_digit = sum(i * int(ch) for i, ch in enumerate(code, 1)) % 11
if check_digit == 10:
check_digit = sum(i * int(ch) for i, ch in enumerate(code, 3)) % 11
return check_digit % 10
def calc_check_digit_person(code: str) -> int:
weights = (2, 4, 8, 5, 10, 9, 7, 3, 6)
return sum(w * int(ch) for w, ch in zip(weights, code)) % 11 % 10
def calc_check_digit_foreigner(code: str) -> int:
weights = (21, 19, 17, 13, 11, 9, 7, 3, 1)
return sum(w * int(ch) for w, ch in zip(weights, code)) % 10
def calc_check_digit_misc(code: str) -> Optional[int]:
weights = (4, 3, 2, 7, 6, 5, 4, 3, 2)
check_digit = (
11 - sum(w * int(ch) for w, ch in zip(weights, code)) % 11
)
if check_digit == 11:
return 0
elif check_digit == 10:
return None
else:
return check_digit
sanitized_vat = re.sub(r"(^BG)|\s*|\W*", "", vat, flags=re.I)
if not sanitized_vat.isdigit():
return False
if len(sanitized_vat) == 9:
check_digit = calc_check_digit_legal_entity(sanitized_vat[:-1])
return check_digit == int(sanitized_vat[-1])
elif len(sanitized_vat) == 10:
# Check if it corresponds to a physical person VAT
check_digit_person = calc_check_digit_person(sanitized_vat[:-1])
year_is_valid = int(sanitized_vat[:2]) in range(0, 100)
month_is_valid = int(sanitized_vat[2:4]) in range(1, 41)
day_is_valid = int(sanitized_vat[4:6]) in range(1, 31)
date_is_valid = year_is_valid and month_is_valid and day_is_valid
is_person = (
check_digit_person == int(sanitized_vat[-1]) and date_is_valid
)
# Check if it corresponds to a foreigner VAT
check_digit_foreigner = calc_check_digit_foreigner(sanitized_vat[:-1])
is_foreigner = check_digit_foreigner == int(sanitized_vat[-1])
# Check if ti corresponds to a miscellaneous VAT
check_digit_misc = calc_check_digit_misc(sanitized_vat[:-1])
is_miscellaneous = check_digit_misc == int(sanitized_vat[-1])
return is_person or is_foreigner or is_miscellaneous
else:
return False
[docs]def validate_vat_cy(vat: str) -> bool:
"""Validates a VAT number against cyprus VAT format specification.
In Cyprus is also named "Arithmós Engraphḗs phi. pi. a." (ΦΠΑ).
The number must contain 8 digits followed by a letter that is used to check
the number.
:param vat: VAT number to validate.
:return: ``True`` if the given VAT is valid, ``False`` otherwise.
"""
def calc_check_char(code: str) -> str:
key = [1, 0, 5, 7, 9, 13, 15, 17, 19, 21]
check_char = (
sum(int(ch) for ch in code[1::2])
+ sum(key[int(ch)] for ch in code[::2])
) % 26
return chr(65 + check_char)
sanitized_vat = re.sub(r"(^CY)|\s*|\W*", "", vat, flags=re.I)
if not sanitized_vat[:-1].isdigit():
return False
if not sanitized_vat[-1].isalpha():
return False
if len(sanitized_vat) != 9:
return False
if sanitized_vat[0:2] == "12":
return False
return calc_check_char(sanitized_vat[:-1]) == sanitized_vat[-1]
[docs]def validate_vat_cz(vat: str) -> bool:
"""Validates a VAT number against czech republic VAT format specification.
In Czech Republic is also named "Daňové identifikační číslo" (DIČ).
The number must contain 8 to 10 digits depending if it is a legar entity or
individual.
:param vat: VAT number to validate.
:return: ``True`` if the given VAT is valid, ``False`` otherwise.
"""
match = re.match(r"^(CZ)?(\d{8})(\d?)(\d?)$", vat)
if not match:
return False
c1, c2, c3, c4, c5, c6, c7, c8 = map(int, match.group(2))
c9 = int(match.group(3)) if match.group(3) else None
c10 = int(match.group(4)) if match.group(4) else None
def legal_entities_style():
if c9 is not None or c10 is not None:
return False
a1 = 8 * c1 + 7 * c2 + 6 * c3 + 5 * c4 + 4 * c5 + 3 * c6 + 2 * c7
a2 = a1 + 11 if a1 % 11 == 0 else ceil(a1 / 11) * 11
return c8 == (a2 - a1) % 10
def individuals_style_1():
if c9 is None or c10 is not None:
return False
year = int("".join(map(str, [c1, c2])))
month = int("".join(map(str, [c3, c4])))
day = int("".join(map(str, [c5, c6])))
return (
year in range(0, 54)
and (month in range(1, 13) or month in range(51, 63))
and day in range(1, 32)
)
def individuals_style_2():
if c9 is None or c10 is not None:
return False
a1 = 8 * c2 + 7 * c3 + 6 * c4 + 5 * c5 + 4 * c6 + 3 * c7 + 2 * c8
a2 = a1 + 11 if a1 % 11 == 0 else ceil(a1 / 11) * 11
return c9 == [0, 8, 7, 6, 5, 4, 3, 2, 1, 0, 9, 8][a2 - a1]
def individuals_style_3():
if c9 is None or c10 is None:
return False
r1 = int("".join(map(str, [c1, c2, c3, c4, c5, c6, c7, c8, c9, c10])))
r2 = (
int("".join(map(str, [c1, c2])))
+ int("".join(map(str, [c3, c4])))
+ int("".join(map(str, [c5, c6])))
+ int("".join(map(str, [c7, c8])))
+ int("".join(map(str, [c9, c10])))
)
return r1 % 11 == 0 and r2 % 11 == 0
return (
legal_entities_style()
or individuals_style_1()
or individuals_style_2()
or individuals_style_3()
)
[docs]def validate_vat_de(vat: str) -> bool:
"""Validates a VAT number against german VAT format specification.
In Germany is also named "Umsatzsteuer-Identifikationsnummer" (USt-IdNr).
The number must contain 9 digits and the first one cannot be 0.
:param vat: VAT number to validate.
:return: ``True`` if the given VAT is valid, ``False`` otherwise.
"""
match = re.match(r"^(DE)?(\d{9})$", vat)
if not match:
return False
*c1_c8, c9 = map(int, match.group(2))
p = reduce(
lambda x, c: ((2 * (10 if (c + x) % 10 == 0 else (c + x) % 10)) % 11),
c1_c8,
10,
)
r = 11 - p
return (c1_c8[0] > 0) and ((r == 10 and c9 == 0) or (c9 == r))
[docs]def validate_vat_dk(vat: str) -> bool:
"""Validates a VAT number against dannish VAT format specification.
In Denmark is also named "Momsregistreringsnummer" (CVR).
The number must contain 8 digits and the last digit is the check digit.
It uses MOD 11 algorithm to calculate the check digit.
:param vat: VAT number to validate.
:return: ``True`` if the given VAT is valid, ``False`` otherwise.
.. seealso:: https://erhvervsstyrelsen.dk/modulus_11
"""
def checksum(code: str) -> int:
weights = (2, 7, 6, 5, 4, 3, 2, 1)
return sum(weight * int(ch) for weight, ch in zip(weights, code)) % 11
sanitized_vat = re.sub(r"(^DK)|\s*|\W*", "", vat, flags=re.I)
if not sanitized_vat.isdigit():
return False
if len(sanitized_vat) != 8:
return False
if sanitized_vat[0] == "0":
return False
return checksum(sanitized_vat) == 0
[docs]def validate_vat_ee(vat: str) -> bool:
"""Validates a VAT number against estoniaon VAT format specification.
In Estonia is also named "Käibemaksukohustuslase number" (KMKR).
The number must contain 9 digits.
:param vat: VAT number to validate.
:return: ``True`` if the given VAT is valid, ``False`` otherwise.
"""
match = re.match(r"^(EE)?(\d{9})$", vat)
if not match:
return False
c1, c2, c3, c4, c5, c6, c7, c8, c9 = map(int, match.group(2))
r = 3 * c1 + 7 * c2 + 1 * c3 + 3 * c4 + 7 * c5 + 1 * c6 + 3 * c7 + 7 * c8
return c9 == 10 * ceil(r / 10) - r
[docs]def validate_vat_el(vat: str) -> bool:
"""Validates a VAT number against greece VAT format specification.
In Greece is also named "Arithmós Forologikou Mētrṓou" (ΑΦΜ).
The number must contain 9 digits and the last digit is the check digit.
It uses MOD 11 algorithm to calculate the check digit.
:param vat: VAT number to validate.
:return: ``True`` if the given VAT is valid, ``False`` otherwise.
"""
match = re.match(r"^(EL|GR)?(\d{9})$", vat)
if not match:
return False
c1, c2, c3, c4, c5, c6, c7, c8, c9 = map(int, match.group(2))
r = (
256 * c1
+ 128 * c2
+ 64 * c3
+ 32 * c4
+ 16 * c5
+ 8 * c6
+ 4 * c7
+ 2 * c8
) % 11
return c9 == ((r % 11) % 10)
def validate_vat_es(vat: str) -> bool:
match = re.match(r"^(ES)?(\w)(\d{7})(\w)$", vat)
if not match:
return False
return bool(match)
[docs]def validate_vat_fi(vat: str) -> bool:
"""Validates a VAT number against finnish VAT format specification.
In Finland is also named "Arvonlisäveronumero" (AVL nro).
The number must contain 8 digits and the last digit is the check digit.
It uses MOD 11-2 algorithm to calculate the check digit.
:param vat: VAT number to validate.
:return: ``True`` if the given VAT is valid, ``False`` otherwise.
.. seealso:: http://tarkistusmerkit.teppovuori.fi/tarkmerk.htm#y-tunnus2
"""
def checksum(code: str) -> int:
weights = (7, 9, 10, 5, 8, 4, 2, 1)
return sum(weight * int(ch) for weight, ch in zip(weights, code)) % 11
sanitized_vat = re.sub(r"(^FI)|\s*|\W*", "", vat, flags=re.I)
if not sanitized_vat.isdigit():
return False
if len(sanitized_vat) != 8:
return False
return checksum(sanitized_vat) == 0
[docs]def validate_vat_fr(vat: str) -> bool:
"""Validates a VAT number against french VAT format specification.
In France is also named "Numéro de TVA intracommunautaire" (TVA).
The number must contain 2 control characters followed by 9 digits.
:param vat: VAT number to validate.
:return: ``True`` if the given VAT is valid, ``False`` otherwise.
"""
match = re.match(r"^(FR?)?(\d{2})(\d{9})", vat)
if not match:
return False
return int(match.group(2)) == (int(match.group(3)) * 100 + 12) % 97
def validate_vat_gb(vat: str) -> bool:
# TODO
match = re.match(r"^(GB)?(\d{9}(\d{3})?|[A-Z]{2}\d{3})$", vat)
return bool(match)
[docs]def validate_vat_hr(vat: str) -> bool:
"""Validates a VAT number against croatian VAT format specification.
In Croatia is also named "PDV Id. Broj OIB" (PDV-ID; OIB).
The number must contain 11 digits and the last digit is the check digit.
It uses MOD 11-10 algorithm to calculate the check digit.
:param vat: VAT number to validate.
:return: ``True`` if the given VAT is valid, ``False`` otherwise.
"""
match = re.match(r"^(HR)?(\d{11})$", vat)
if not match:
return False
*digits, last_digit = map(int, match.group(2))
product = 10
for digit in digits:
soma = (digit + product) % 10
if soma == 0:
soma = 10
product = (2 * soma) % 11
return (product + last_digit) % 10 == 1
[docs]def validate_vat_hu(vat: str) -> bool:
"""Validates a VAT number against hungarian VAT format specification.
In Hungary is also named "Közösségi adószám" (ΑNUM).
The number must contain 8 digits and the last digit is the check digit.
:param vat: VAT number to validate.
:return: ``True`` if the given VAT is valid, ``False`` otherwise.
"""
match = re.match(r"^(HU)?(\d{8})$", vat)
if not match:
return False
c1, c2, c3, c4, c5, c6, c7, c8 = map(int, match.group(2))
r = 9 * c1 + 7 * c2 + 3 * c3 + 1 * c4 + 9 * c5 + 7 * c6 + 3 * c7
return ((r % 10 == 0) and (c8 == 0)) or (10 - (r % 10) == c8)
[docs]def validate_vat_ie(vat: str) -> bool:
"""Validates a VAT number against irish VAT format specification.
In Ireland is also named "Value added tax identification no." (VAT/CBL).
The number must be in one of the following formats:
- 7 digits and 1 letter, optionally followed by another letter
- 1 digit, 1 letter or "+", "*" and 5 digits and 1 letter
The number must contain 8 digits and the last digit is the check digit.
:param vat: VAT number to validate.
:return: ``True`` if the given VAT is valid, ``False`` otherwise.
"""
def old_style() -> bool:
match = re.match(r"^(IE)?(\d)([A-Z+*])(\d{5})([A-W])", vat)
if not match:
return False
c1 = int(match.group(2))
c3, c4, c5, c6, c7 = map(int, match.group(4))
_, c8 = match.group(3), match.group(5)
r = (8 * 0 + 7 * c3 + 6 * c4 + 5 * c5 + 4 * c6 + 3 * c7 + 2 * c1) % 23
check_chars = "WABCDEFGHIJKLMNOPQRSTUV"
return c8 == check_chars[r]
def new_style() -> bool:
match = re.match(r"^(IE)?(\d{7})([A-W])([A-IW])?", vat)
if not match:
return False
c1, c2, c3, c4, c5, c6, c7 = map(int, match.group(2))
c8 = match.group(3)
c9 = "WABCDEFGHI".index(match.group(4)) if match.group(4) else 0
r = (
9 * c9
+ 8 * c1
+ 7 * c2
+ 6 * c3
+ 5 * c4
+ 4 * c5
+ 3 * c6
+ 2 * c7
) % 23
check_chars = "WABCDEFGHIJKLMNOPQRSTUV"
return (r < len(check_chars)) and (c8 == check_chars[r])
return old_style() or new_style()
[docs]def validate_vat_it(vat: str) -> bool:
"""Validates a VAT number against italien VAT format specification.
In Italy is also named "Partita IVA" (P.IVA).
The number must contain 11 digits and the last digit is the check digit.
It uses Luhn Algorithm to calculate the check digit.
:param vat: VAT number to validate.
:return: ``True`` if the given VAT is valid, ``False`` otherwise.
.. seealso:: https://en.wikipedia.org/wiki/Luhn_algorithm
"""
match = re.match(r"^(IT)?(\d{11})$", vat)
if not match:
return False
c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11 = map(int, match.group(2))
d2 = floor(c2 / 5) + ((2 * c2) % 10)
d4 = floor(c4 / 5) + ((2 * c4) % 10)
d6 = floor(c6 / 5) + ((2 * c6) % 10)
d8 = floor(c8 / 5) + ((2 * c8) % 10)
d10 = floor(c10 / 5) + ((2 * c10) % 10)
s1 = c1 + c3 + c5 + c7 + c9
s2 = d2 + d4 + d6 + d8 + d10
return c11 == (10 - (s1 + s2) % 10) % 10
[docs]def validate_vat_lt(vat: str) -> bool:
"""Validates a VAT number against lithuanian VAT format specification.
In Lithuania is also named "Pridėtinės vertės mokestis" (PVM KODAS).
The number must contain 9 or 12 digits.
:param vat: VAT number to validate.
:return: ``True`` if the given VAT is valid, ``False`` otherwise.
"""
def legal_persons_style() -> bool:
match = re.match(r"^(LT)?(\d{9})$", vat)
if not match:
return False
c1, c2, c3, c4, c5, c6, c7, c8, c9 = map(int, match.group(2))
if c8 != 1:
return False
r1 = (
1 * c1
+ 2 * c2
+ 3 * c3
+ 4 * c4
+ 5 * c5
+ 6 * c6
+ 7 * c7
+ 8 * c8
) % 11
r2 = (
3 * c1
+ 4 * c2
+ 5 * c3
+ 6 * c4
+ 7 * c5
+ 8 * c6
+ 9 * c7
+ 1 * c8
) % 11
if r1 % 10 != 0:
return c9 == r1
else:
return (r2 == 10 and c9 == 0) or (c9 == r2)
def temporary_registered_taxpayers_style() -> bool:
match = re.match(r"^(LT)?(\d{12})$", vat)
if not match:
return False
c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11, c12 = map(
int, match.group(2)
)
if c11 != 1:
return False
r1 = (
1 * c1
+ 2 * c2
+ 3 * c3
+ 4 * c4
+ 5 * c5
+ 6 * c6
+ 7 * c7
+ 8 * c8
+ 9 * c9
+ 1 * c10
+ 2 * c11
) % 11
r2 = (
3 * c1
+ 4 * c2
+ 6 * c4
+ 7 * c5
+ 8 * c6
+ 9 * c7
+ 1 * c8
+ 2 * c9
+ 3 * c10
+ 4 * c11
) % 11
if r1 % 10 != 0:
return c12 == r1
else:
return (r2 == 10 and c12 == 0) or (c12 == r2)
return legal_persons_style() or temporary_registered_taxpayers_style()
[docs]def validate_vat_lu(vat: str) -> bool:
"""Validates a VAT number against luxembourg VAT format specification.
In Luxembourg is also named "Numéro d'identification à la taxe sur la
valeur ajoutée" (No. TVA).
The number must contain 8 digits and the last two digits are the check
digits.
:param vat: VAT number to validate.
:return: ``True`` if the given VAT is valid, ``False`` otherwise.
"""
match = re.match(r"^(LU)?(\d{8})$", vat)
if not match:
return False
c1_c6 = int(match.group(2)[:6])
c7_c8 = int(match.group(2)[6:])
return c7_c8 == c1_c6 % 89
[docs]def validate_vat_lv(vat: str) -> bool:
"""Validates a VAT number against latvian VAT format specification.
In Latvia is also named "Pievienotās vērtības nodokļa" (PVN).
The number must contain 11 digits and can be in one of the following
formats:
- 1st digit is bigger than 3, and so use a MOD 11 algorithm
- 1st digit is 3, 2nd digit is 2, followed by 9 digits
- 2 digits represent a day of month, followed by 2 digits that represent
the month, followed by 2 digits that represent the year, followed by 1
digit equals to 0, 1 or 2, followed by 4 digits
:param vat: VAT number to validate.
:return: ``True`` if the given VAT is valid, ``False`` otherwise.
"""
def style_1() -> bool:
match = re.match(r"^(LV)?([4-9]\d{10})$", vat)
if not match:
return False
c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11 = map(int, match.group(2))
r = 3 - (
(
9 * c1
+ 1 * c2
+ 4 * c3
+ 8 * c4
+ 3 * c5
+ 10 * c6
+ 2 * c7
+ 5 * c8
+ 7 * c9
+ 6 * c10
)
% 11
)
return (r != -1) and (
(r < -1 and c11 == r + 11) or (r > -1 and c11 == r)
)
def style_2() -> bool:
match = re.match(r"^(LV)?32\d{9}", vat)
return bool(match)
def style_3() -> bool:
match = re.match(r"^(LV)?(\d{2})(\d{2})(\d{2})[012](\d{4})$", vat)
if not match:
return False
day, month, year = map(int, match.groups()[1:4])
return day in range(0, 31) and month in range(0, 12)
return style_1() or style_2() or style_3()
[docs]def validate_vat_mt(vat: str) -> bool:
"""Validates a VAT number against maltese VAT format specification.
In Malta is also named "Vat reg. no." (VAT No.).
The number must contain 8 digits and the last two digits are the check
digits.
It uses MOD 37 algorithm to calculate the check digits.
:param vat: VAT number to validate.
:return: ``True`` if the given VAT is valid, ``False`` otherwise.
"""
match = re.match(r"^(MT)?(\d{8})$", vat)
if not match:
return False
c1, c2, c3, c4, c5, c6, c7, c8 = map(int, match.group(2))
c7_c8 = int("".join(map(str, [c7, c8])))
r = 37 - (3 * c1 + 4 * c2 + 6 * c3 + 7 * c4 + 8 * c5 + 9 * c6) % 37
return (r == 0 and c7_c8 == 37) or (c7_c8 == r)
[docs]def validate_vat_nl(vat: str) -> bool:
"""Validates a VAT number against netherlands VAT format specification.
In Netherlands is also named "Btw-nummer" Btw-nr.).
The number must contain 9 digits followed by the letter 'B' followed by 2
digits.
It uses MOD 11 algorithm to calculate the check digit.
:param vat: VAT number to validate.
:return: ``True`` if the given VAT is valid, ``False`` otherwise.
"""
match = re.match(r"^(NL)?(\d{9})B(\d{2})$", vat)
if not match:
return False
c1, c2, c3, c4, c5, c6, c7, c8, c9 = map(int, match.group(2))
c11, c12 = map(int, match.group(3))
r = (
9 * c1 + 8 * c2 + 7 * c3 + 6 * c4 + 5 * c5 + 4 * c6 + 3 * c7 + 2 * c8
) % 11
return r != 10 and r == c9
[docs]def validate_vat_pl(vat: str) -> bool:
"""Validates a VAT number against polish VAT format specification.
In Poland is also named "numer identyfikacji podatkowej" (NIP).
The number must contain 10 digits and the last digit is the check digit.
It uses MOD 11 algorithm to calculate the check digit.
:param vat: VAT number to validate.
:return: ``True`` if the given VAT is valid, ``False`` otherwise.
"""
match = re.match(r"^(PL)?(\d{10})$", vat)
if not match:
return False
c1, c2, c3, c4, c5, c6, c7, c8, c9, c10 = map(int, match.group(2))
r = (
6 * c1
+ 5 * c2
+ 7 * c3
+ 2 * c4
+ 3 * c5
+ 4 * c6
+ 5 * c7
+ 6 * c8
+ 7 * c9
) % 11
return r != 10 and r == c10
[docs]def validate_vat_pt(vat: str) -> bool:
"""Validates a VAT number against portuguese VAT format specification.
In Portugal is also named "Número de Identificação Fiscal" (NIF).
The number must contain 9 digits and the last digit is the check digit.
It uses MOD 11 algorithm to calculate the check digit.
:param vat: VAT number to validate.
:return: ``True`` if the given VAT is valid, ``False`` otherwise.
.. seealso:: https://pt.wikipedia.org/wiki/Número_de_identificação_fiscal
"""
def calc_check_digit(code: str) -> int:
check_digit = sum((9 - i) * int(ch) for i, ch in enumerate(code))
return (11 - check_digit) % 11 % 10
sanitized_vat = re.sub(r"(^PT)|\s*|\W*", "", vat, flags=re.I)
if not sanitized_vat.isdigit():
return False
if len(sanitized_vat) != 9:
return False
if sanitized_vat[0] == "0":
return False
return calc_check_digit(sanitized_vat[:-1]) == int(sanitized_vat[-1])
[docs]def validate_vat_ro(vat: str) -> bool:
"""Validates a VAT number against romanian VAT format specification.
In Romania is also named "Codul de identificare fiscală" (CIF).
The number must contain 2 to 10 digits and the last digit is the check
digit. It uses MOD 11 algorithm to calculate the check digit.
:param vat: VAT number to validate.
:return: ``True`` if the given VAT is valid, ``False`` otherwise.
.. seealso:: https://vatdesk.eu/en/romania/
"""
def calc_check_digit(code: str) -> int:
index = 10 - len(code)
weights = [7, 5, 3, 2, 1, 7, 5, 3, 2][index:]
check_digit = (
10
* sum(weight * int(ch) for weight, ch in zip(weights, code))
% 11
)
return 0 if check_digit == 10 else check_digit
sanitized_vat = re.sub(r"(^RO)|\s*|\W*", "", vat, flags=re.I)
if len(sanitized_vat) not in range(2, 11):
return False
if not sanitized_vat.isdigit():
return False
return calc_check_digit(sanitized_vat) == int(sanitized_vat[-1])
[docs]def validate_vat_se(vat: str) -> bool:
"""Validates a VAT number against swedish VAT format specification.
In Sweden is also named "momsregistreringsnummer" (Momsnr).
The number must contain 12 digits.
:param vat: VAT number to validate.
:return: ``True`` if the given VAT is valid, ``False`` otherwise.
"""
match = re.match(r"^(SE)?(\d{12})$", vat)
if not match:
return False
c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11, c12 = map(
int, match.group(2)
)
s1 = int(c1 / 5) + (c1 * 2) % 10
s3 = int(c3 / 5) + (c3 * 2) % 10
s5 = int(c5 / 5) + (c5 * 2) % 10
s7 = int(c7 / 5) + (c7 * 2) % 10
s9 = int(c9 / 5) + (c9 * 2) % 10
r = s1 + s3 + s5 + s7 + s9
return c10 == (10 - (r + c2 + c4 + c6 + c8) % 10) % 10
[docs]def validate_vat_si(vat: str) -> bool:
"""Validates a VAT number against slovenian VAT format specification.
In Slovenia is also named "Davčna številka" (ID za DDV).
The number must contain 8 digits and the last digit is the check digit.
It uses MOD 11 algorithm to calculate the check digit.
:param vat: VAT number to validate.
:return: ``True`` if the given VAT is valid, ``False`` otherwise.
"""
match = re.match(r"^(SI)?(\d{8})$", vat)
if not match:
return False
c1, c2, c3, c4, c5, c6, c7, c8 = map(int, match.group(2))
r = 11 - (
(c1 * 8 + c2 * 7 + c3 * 6 + c4 * 5 + c5 * 4 + c6 * 3 + c7 * 2) % 11
)
return (r != 11) and ((r == 10 and c8 == 0) or (c8 == r))
[docs]def validate_vat_sk(vat: str) -> bool:
"""Validates a VAT number against slovakian VAT format specification.
In Slovakia is also named "Identifikačné číslo pre daň z pridanej hodnoty"
(IČ DPH).
The number must contain 10 digits and be divisible by 11.
:param vat: VAT number to validate.
:return: ``True`` if the given VAT is valid, ``False`` otherwise.
"""
match = re.match(r"^(SK)?(\d{10})$", vat)
if not match:
return False
return int(match.group(2)) % 11 == 0
#: Maps a country code to the respective country VAT rule
EU_RULES: Dict[str, Callable[[str], bool]] = {
"AT": validate_vat_at,
"BE": validate_vat_be,
"BG": validate_vat_bg,
"HR": validate_vat_hr,
"CY": validate_vat_cy,
"CZ": validate_vat_cz,
"DE": validate_vat_de,
"DK": validate_vat_dk,
"EE": validate_vat_ee,
"EL": validate_vat_el,
"ES": validate_vat_es,
"FI": validate_vat_fi,
"FR": validate_vat_fr,
"GB": validate_vat_gb,
"HU": validate_vat_hu,
"IE": validate_vat_ie,
"IT": validate_vat_it,
"LT": validate_vat_lt,
"LU": validate_vat_lu,
"LV": validate_vat_lv,
"MT": validate_vat_mt,
"NL": validate_vat_nl,
"PL": validate_vat_pl,
"PT": validate_vat_pt,
"RO": validate_vat_ro,
"SE": validate_vat_se,
"SI": validate_vat_si,
"SK": validate_vat_sk,
}
#: List of european union country codes
EU_COUNTRY_CODES: List[str] = list(EU_RULES.keys())