1# iban.py - functions for handling International Bank Account Numbers (IBANs) 2# 3# Copyright (C) 2011-2018 Arthur de Jong 4# 5# This library is free software; you can redistribute it and/or 6# modify it under the terms of the GNU Lesser General Public 7# License as published by the Free Software Foundation; either 8# version 2.1 of the License, or (at your option) any later version. 9# 10# This library is distributed in the hope that it will be useful, 11# but WITHOUT ANY WARRANTY; without even the implied warranty of 12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13# Lesser General Public License for more details. 14# 15# You should have received a copy of the GNU Lesser General Public 16# License along with this library; if not, write to the Free Software 17# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 18# 02110-1301 USA 19 20"""IBAN (International Bank Account Number). 21 22The IBAN is used to identify bank accounts across national borders. The 23first two letters are a country code. The next two digits are check digits 24for the ISO 7064 Mod 97, 10 checksum. Each country uses its own format 25for the remainder of the number. 26 27Some countries may also use checksum algorithms within their number but 28this is only checked for a few countries. 29 30More information: 31 32* https://en.wikipedia.org/wiki/International_Bank_Account_Number 33* https://www.swift.com/products_services/bic_and_iban_format_registration_iban_format_r 34 35>>> validate('GR16 0110 1050 0000 1054 7023 795') 36'GR1601101050000010547023795' 37>>> validate('BE31435411161155') 38'BE31435411161155' 39>>> compact('GR16 0110 1050 0000 1054 7023 795') 40'GR1601101050000010547023795' 41>>> format('GR1601101050000010547023795') 42'GR16 0110 1050 0000 1054 7023 795' 43>>> calc_check_digits('BExx435411161155') 44'31' 45""" 46 47import re 48 49from stdnum import numdb 50from stdnum.exceptions import * 51from stdnum.iso7064 import mod_97_10 52from stdnum.util import clean, get_cc_module 53 54 55# our open copy of the IBAN database 56_ibandb = numdb.get('iban') 57 58# regular expression to check IBAN structure 59_struct_re = re.compile(r'([1-9][0-9]*)!([nac])') 60 61# cache of country codes to modules 62_country_modules = {} 63 64 65def compact(number): 66 """Convert the iban number to the minimal representation. This strips the 67 number of any valid separators and removes surrounding whitespace.""" 68 return clean(number, ' -.').strip().upper() 69 70 71def calc_check_digits(number): 72 """Calculate the check digits that should be put in the number to make 73 it valid. Check digits in the supplied number are ignored.""" 74 number = compact(number) 75 return mod_97_10.calc_check_digits(number[4:] + number[:2]) 76 77 78def _struct_to_re(structure): 79 """Convert an IBAN structure to a regular expression that can be used 80 to validate the number.""" 81 def conv(match): 82 chars = { 83 'n': '[0-9]', 84 'a': '[A-Z]', 85 'c': '[A-Za-z0-9]', 86 }[match.group(2)] 87 return '%s{%s}' % (chars, match.group(1)) 88 return re.compile('^%s$' % _struct_re.sub(conv, structure)) 89 90 91def _get_cc_module(cc): 92 """Get the IBAN module based on the country code.""" 93 cc = cc.lower() 94 if cc not in _country_modules: 95 _country_modules[cc] = get_cc_module(cc, 'iban') 96 return _country_modules[cc] 97 98 99def validate(number, check_country=True): 100 """Check if the number provided is a valid IBAN. The country-specific 101 check can be disabled with the check_country argument.""" 102 number = compact(number) 103 # ensure that checksum is valid 104 mod_97_10.validate(number[4:] + number[:4]) 105 # look up the number 106 info = _ibandb.info(number) 107 if not info[0][1]: 108 raise InvalidComponent() 109 # check if the bban part of number has the correct structure 110 bban = number[4:] 111 if not _struct_to_re(info[0][1].get('bban', '')).match(bban): 112 raise InvalidFormat() 113 # check the country-specific module if it exists 114 if check_country: 115 module = _get_cc_module(number[:2]) 116 if module: 117 module.validate(number) 118 # return the compact representation 119 return number 120 121 122def is_valid(number, check_country=True): 123 """Check if the number provided is a valid IBAN.""" 124 try: 125 return bool(validate(number, check_country=check_country)) 126 except ValidationError: 127 return False 128 129 130def format(number, separator=' '): 131 """Reformat the passed number to the space-separated format.""" 132 number = compact(number) 133 return separator.join(number[i:i + 4] for i in range(0, len(number), 4)) 134