api.py 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136
  1. # Copyright 2018 Agile Geeks
  2. # Permission is hereby granted, free of charge, to any person obtaining a copy of this software
  3. # and associated documentation files (the "Software"), to deal in the Software without restriction,
  4. # including without limitation the rights to use, copy, modify, merge, publish, distribute,
  5. # sublicense, and/or sell copies of the Software, and to permit persons to whom the Software
  6. # is furnished to do so, subject to the following conditions:
  7. # The above copyright notice and this permission notice shall be included in all copies or substantial
  8. # portions of the Software.
  9. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
  10. # LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
  11. # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
  12. # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
  13. # OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  14. from __future__ import unicode_literals, print_function
  15. import re
  16. import sys
  17. VAT_NUMBER_REGEXPS = {
  18. 'AT': re.compile(r'^U\d{8}$', re.IGNORECASE),
  19. 'BE': re.compile(r'^\d{9,10}$'),
  20. 'BG': re.compile(r'^\d{9,10}$'),
  21. 'CY': re.compile(r'^\d{8}[a-z]$', re.IGNORECASE),
  22. 'CZ': re.compile(r'^\d{8,10}$'),
  23. 'DE': re.compile(r'^\d{9}$'),
  24. 'DK': re.compile(r'^\d{8}$'),
  25. 'EE': re.compile(r'^\d{9}$'),
  26. 'ES': re.compile(r'^[\da-z]\d{7}[\da-z]$', re.IGNORECASE),
  27. 'FI': re.compile(r'^\d{8}$'),
  28. 'FR': re.compile(r'^[\da-z]{2}\d{9}$', re.IGNORECASE),
  29. 'GB': re.compile(r'^((\d{9})|(\d{12})|(GD\d{3})|(HA\d{3}))$',
  30. re.IGNORECASE),
  31. 'GR': re.compile(r'^\d{9}$'),
  32. 'HR': re.compile(r'^\d{11}$'),
  33. 'HU': re.compile(r'^\d{8}$'),
  34. 'IE': re.compile(r'^((\d{7}[a-z])|(\d[a-z]\d{5}[a-z])|(\d{6,7}[a-z]{2}))$',
  35. re.IGNORECASE),
  36. 'IT': re.compile(r'^\d{11}$'),
  37. 'LT': re.compile(r'^((\d{9})|(\d{12}))$'),
  38. 'LU': re.compile(r'^\d{8}$'),
  39. 'LV': re.compile(r'^\d{11}$'),
  40. 'MT': re.compile(r'^\d{8}$'),
  41. 'NL': re.compile(r'^\d{9}B\d{2,3}$', re.IGNORECASE),
  42. 'PL': re.compile(r'^\d{10}$'),
  43. 'PT': re.compile(r'^\d{9}$'),
  44. 'RO': re.compile(r'^\d{2,10}$'),
  45. 'SE': re.compile(r'^\d{12}$'),
  46. 'SI': re.compile(r'^\d{8}$'),
  47. 'SK': re.compile(r'^\d{10}$'),
  48. }
  49. def load_class(module_name, class_name):
  50. mod = __import__(module_name, fromlist=[class_name])
  51. klass = getattr(mod, class_name)
  52. return klass
  53. def load_cc_validator(cc):
  54. module_name = 'pyVat.validators.%s' % cc
  55. klass_name = 'Validator'
  56. klass = load_class(module_name, klass_name)
  57. return klass
  58. class VatValidationError(Exception):
  59. """
  60. Exception thrown by the Validator object when the VAT number is not valid
  61. """
  62. pass
  63. class Validator(object):
  64. def __init__(self, vat_number, vat_country_code=None):
  65. self.error_message = None
  66. self.vat_number = vat_number
  67. self.vat_country_code = vat_country_code
  68. def clean(self):
  69. vat_number = self.vat_number
  70. vat_country_code = self.vat_country_code
  71. try:
  72. vat_number = str(vat_number)
  73. except:
  74. VatValidationError('Invalid VAT number provided')
  75. vat_number = vat_number.replace(' ', '')
  76. if vat_country_code is not None:
  77. try:
  78. vat_country_code = str(vat_country_code)
  79. except:
  80. VatValidationError('Invalid VAT country provided')
  81. vat_country_code = vat_country_code.replace(' ', '')
  82. vat_country_code = vat_country_code.upper()
  83. # if no vat_country_code provided we try to extract it from vat_number
  84. else:
  85. try:
  86. vat_country_code = vat_number[:2]
  87. except:
  88. raise VatValidationError('Invalid VAT number provided')
  89. vat_country_code = vat_country_code.upper()
  90. vat_number = vat_number[2:]
  91. if vat_country_code == 'EL':
  92. vat_country_code = 'GR'
  93. if vat_country_code not in VAT_NUMBER_REGEXPS.keys():
  94. raise VatValidationError('Invalid VAT country')
  95. if len(vat_number)>2:
  96. if vat_number[:2].upper() == vat_country_code:
  97. vat_number = vat_number[2:]
  98. return vat_number.upper(),vat_country_code.upper()
  99. def validate(self):
  100. try:
  101. self.vat_number, self.country_code = self.clean()
  102. except VatValidationError as e:
  103. self.error_message = str(e)
  104. return False
  105. validator_klass = load_cc_validator(self.country_code.lower())
  106. validator = validator_klass()
  107. return validator.validate(str(self.vat_number))