| 
 | 1 | +# excise.py - functions for handling EU Excise numbers  | 
 | 2 | +# coding: utf-8  | 
 | 3 | +#  | 
 | 4 | +# Copyright (C) 2023 Cédric Krier  | 
 | 5 | +#  | 
 | 6 | +# This library is free software; you can redistribute it and/or  | 
 | 7 | +# modify it under the terms of the GNU Lesser General Public  | 
 | 8 | +# License as published by the Free Software Foundation; either  | 
 | 9 | +# version 2.1 of the License, or (at your option) any later version.  | 
 | 10 | +#  | 
 | 11 | +# This library is distributed in the hope that it will be useful,  | 
 | 12 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of  | 
 | 13 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  | 
 | 14 | +# Lesser General Public License for more details.  | 
 | 15 | +#  | 
 | 16 | +# You should have received a copy of the GNU Lesser General Public  | 
 | 17 | +# License along with this library; if not, write to the Free Software  | 
 | 18 | +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  | 
 | 19 | +# 02110-1301 USA  | 
 | 20 | + | 
 | 21 | +"""Excise Number  | 
 | 22 | +
  | 
 | 23 | +The Excise Number is the identification number issued by the competent  | 
 | 24 | +authority in respect of the person or premises.  | 
 | 25 | +
  | 
 | 26 | +The first two letters are the ISO country code of the Member State where the  | 
 | 27 | +operator is located (e.g. LU);  | 
 | 28 | +The next 11 alphanumeric characters are the identifier of the operator.  | 
 | 29 | +The identifier must include 11 digits, shorter identifiers must be padded to  | 
 | 30 | +the left with zeroes (e.g. 00000987ABC)  | 
 | 31 | +
  | 
 | 32 | +More information:  | 
 | 33 | +
  | 
 | 34 | +* https://ec.europa.eu/taxation_customs/dds2/seed/help/seedhedn.jsp  | 
 | 35 | +
  | 
 | 36 | +>>> compact('LU 00000987ABC')  | 
 | 37 | +'LU00000987ABC'  | 
 | 38 | +>>> validate('LU00000987ABC')  | 
 | 39 | +'LU00000987ABC'  | 
 | 40 | +"""  | 
 | 41 | + | 
 | 42 | +from stdnum.eu.vat import MEMBER_STATES  | 
 | 43 | +from stdnum.exceptions import *  | 
 | 44 | +from stdnum.util import clean, get_soap_client  | 
 | 45 | + | 
 | 46 | + | 
 | 47 | +seed_wsdl = 'https://ec.europa.eu/taxation_customs/dds2/seed/services/excise/verification?wsdl'  | 
 | 48 | +"""The WSDL URL of the System for Exchange of Excise Data (SEED)."""  | 
 | 49 | + | 
 | 50 | + | 
 | 51 | +def compact(number):  | 
 | 52 | +    """Convert the number to the minimal representation. This strips the number  | 
 | 53 | +    of any valid separators and removes surrounding whitespace."""  | 
 | 54 | +    number = clean(number, ' ').upper().strip()  | 
 | 55 | +    return number  | 
 | 56 | + | 
 | 57 | + | 
 | 58 | +def validate(number):  | 
 | 59 | +    """Check if the number is a valid Excise number."""  | 
 | 60 | +    number = clean(number, ' ').upper().strip()  | 
 | 61 | +    cc = number[:2]  | 
 | 62 | +    if cc.lower() not in MEMBER_STATES:  | 
 | 63 | +        raise InvalidComponent()  | 
 | 64 | +    if len(number) != 13:  | 
 | 65 | +        raise InvalidLength()  | 
 | 66 | +    return number  | 
 | 67 | + | 
 | 68 | + | 
 | 69 | +def is_valid(number):  | 
 | 70 | +    """Check if the number is a valid Excise number."""  | 
 | 71 | +    try:  | 
 | 72 | +        return bool(validate(number))  | 
 | 73 | +    except ValidationError:  | 
 | 74 | +        return False  | 
 | 75 | + | 
 | 76 | + | 
 | 77 | +def check_seed(number, timeout=30):  # pragma: no cover (not part of normal test suite)  | 
 | 78 | +    """Query the online European Commission System for Exchange of Excise Data  | 
 | 79 | +    (SEED) for validity of the provided number. Note that the service has  | 
 | 80 | +    usage limitations (see the VIES website for details). The timeout is in  | 
 | 81 | +    seconds. This returns a dict-like object."""  | 
 | 82 | +    number = compact(number)  | 
 | 83 | +    client = get_soap_client(seed_wsdl, timeout)  | 
 | 84 | +    return client.verifyExcise(number)  | 
0 commit comments