1# bic.py - functions for handling ISO 9362 Business identifier codes
2#
3# Copyright (C) 2015 Lifealike Ltd
4# Copyright (C) 2017-2018 Arthur de Jong
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"""BIC (ISO 9362 Business identifier codes).
22
23An ISO 9362 identifier (also: BIC, BEI, or SWIFT code) uniquely
24identifies an institution. They are commonly used to route financial
25transactions.
26
27The code consists of a 4 letter institution code, a 2 letter country code,
28and a 2 character location code, optionally followed by a three character
29branch code.
30
31>>> validate('AGRIFRPP882')
32'AGRIFRPP882'
33>>> validate('ABNA BE 2A')
34'ABNABE2A'
35>>> validate('AGRIFRPP')
36'AGRIFRPP'
37>>> validate('AGRIFRPP8')
38Traceback (most recent call last):
39    ...
40InvalidLength: ..
41>>> validate('AGRIF2PP')  # country code can't contain digits
42Traceback (most recent call last):
43    ...
44InvalidFormat: ..
45>>> format('agriFRPP')  # conventionally caps
46'AGRIFRPP'
47
48"""
49
50import re
51
52from stdnum.exceptions import *
53from stdnum.util import clean
54
55
56_bic_re = re.compile(r'^[A-Z]{6}[0-9A-Z]{2}([0-9A-Z]{3})?$')
57
58
59def compact(number):
60    """Convert the number to the minimal representation. This strips the
61    number of any surrounding whitespace."""
62    return clean(number, ' -').strip().upper()
63
64
65def validate(number):
66    """Check if the number is a valid routing number. This checks the length
67    and characters in each position."""
68    number = compact(number)
69    if len(number) not in (8, 11):
70        raise InvalidLength()
71    match = _bic_re.search(number)
72    if not match:
73        raise InvalidFormat()
74    return number
75
76
77def is_valid(number):
78    """Check if the number provided is a valid BIC."""
79    try:
80        return bool(validate(number))
81    except ValidationError:
82        return False
83
84
85def format(number):
86    """Reformat the number to the standard presentation format."""
87    return compact(number)
88