arthurdejong / python-stdnum

A Python library to provide functions to handle, parse and validate standard numbers.
https://arthurdejong.org/python-stdnum/
GNU Lesser General Public License v2.1
484 stars 203 forks source link

Validate European VAT numbers with EU prefix #417

Closed pokoli closed 10 months ago

pokoli commented 1 year ago

I've found several companies that have an European VAT number prefixes with EU.

I was unable to search for so much references about the format, only explanation is on the wikipedia page.

I implemented a simple validator that accepts the EU format by just validating the lenght of the identifier and that everythins is digits (all examples I found use the following format).

Some other example using such vat identifier:

https://help.market.envato.com/hc/en-us/articles/203727600-VAT-FAQ

arthurdejong commented 1 year ago

Thanks for pointing this out.

I've been digging a bit and found https://eur-lex.europa.eu/legal-content/EN/TXT/?uri=uriserv:OJ.L_.2020.040.01.0114.01.ENG&toc=OJ:L:2020:040:FULL#ntr1-L_2020040EN.01011801-E0001 which points to the "EU" (Voes number), "IM" (Import number) and "IN" (Intermediary number) prefixes that could be valid.

I've sadly not found a real source if what the check digit algorithm is. There is supposed to be a FITSDEV2-SC12-TS-Mini1SS (Mini-1SS – Technical Specifications) document that describes the algorithm but it appears not to be publicly available. There is a FITSDEV2-SC12-FS-Mini1SS (Mini-1SS - Functional Specifications) document available that contains a footnote with "For security reasons, the present document dos not disclose the algorithm. It is available in a separately distributed technical annex.".

The first three digits can however be validated with something like:

ISO_3166_1_MEMBER_STATES = (
    '040',  # Austria
    '056',  # Belgium
    '100',  # Bulgaria
    '191',  # Croatia
    '196',  # Cyprus
    '203',  # Czechia
    '208',  # Denmark
    '233',  # Estonia
    '246',  # Finland
    '250',  # France
    '276',  # Germany
    '300',  # Greece
    '348',  # Hungary
    '372',  # Ireland
    '380',  # Italy
    '428',  # Latvia
    '440',  # Lithuania
    '442',  # Luxembourg
    '470',  # Malta
    '528',  # Netherland
    '616',  # Poland
    '620',  # Portugal
    '642',  # Romania
    '703',  # Slovakia
    '705',  # Slovenia
    '724',  # Spain
    '752',  # Sweden
    '900',  # N. Ireland
)

and in the check:

if [number[2:5] not in ISO_3166_1_MEMBER_STATES:
    raise InvalidComponent()

To got more insight into the check digit algorithm it would be really useful to have more samples to test with.

pokoli commented 11 months ago

Hi Arthur,

I can confirm that Goddady is ussing now EU372022452 as identifier when issuing invoices to European companies. Unfortunatly I do not have access to more examples. But at least this validate the 372 prefix,

I also just checked that the EU prefix is not a valid option ins the check VIES website, which sounds stranges as they are using the identifier for operations in europoe.

arthurdejong commented 10 months ago

Hi @pokoli,

I've merged your code as f58e08d, moving it to a separate module, hopefully adding useful documentation, also supporting numbers with the "IM" prefix and validating the member state part.

I would have really liked to have a few more numbers but I did manage to also find an IM number. It feels a bit weird to have a module for a scheme where only two valid numbers are known to exist.