8000 Added Czech bank account numbers · arthurdejong/python-stdnum@baed869 · GitHub
[go: up one dir, main page]

Skip to content

Commit baed869

Browse files
author
petr.prikryl
committed
Added Czech bank account numbers
#295
1 parent e831d07 commit baed869

File tree

6 files changed

+354
-0
lines changed

6 files changed

+354
-0
lines changed

docs/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@ Available formats
153153
cu.ni
154154
cusip
155155
cy.vat
156+
cz.bankaccount
156157
cz.dic
157158
cz.rc
158159
de.handelsregisternummer

docs/stdnum.cz.bankaccount.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
stdnum.cz.bankaccount
2+
=====================
3+
4+
.. automodule:: stdnum.cz.bankaccount
5+
:members:

stdnum/cz/bankaccount.py

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
# bankaccount.py - functions for handling Czech bank account numbers
2+
# coding: utf-8
3+
#
4+
# Copyright (C) 2022 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+
"""Czech bank account number
22+
23+
The Czech bank account numbers consist of up to 20 digits:
24+
UUUUUK-MMMMMMMMKM/XXXX
25+
26+
The first part is prefix that is up to 6 digits. The following part is from 2 to 10 digits.
27+
Both parts could be filled with zeros from left if missing.
28+
The final 4 digits represent the bank code.
29+
30+
More information:
31+
32+
* https://www.penize.cz/osobni-ucty/424173-tajemstvi-cisla-uctu-klicem-pro-banky-je-11
33+
* http://www.zlatakoruna.info/zpravy/ucty/cislo-uctu-v-cr
34+
35+
>>> validate('34278-0727558021/0100')
36+
'034278-0727558021/0100'
37+
>>> validate('4278-727558021/0100') # invalid check digits (prefix)
38+
Traceback (most recent call last):
39+
...
40+
InvalidChecksum: ...
41+
>>> validate('34278-727558021/0000') # invalid bank
42+
Traceback (most recent call last):
43+
...
44+
InvalidComponent: ...
45+
>>> format('34278-727558021/0100')
46+
'034278-0727558021/0100'
47+
"""
48+
49+
import re
50+
51+
from stdnum.exceptions import (
52+
InvalidChecksum, InvalidComponent, InvalidFormat, ValidationError)
53+
from stdnum.util import clean
54+
55+
56+
_prefix_regex = r'[0-9]{0,6}'
57+
_root_regex = r'[0-9]{2,10}'
58+
_bank_regex = r'[0-9]{4}'
59+
_regex = r'((%s)-)?(%s)\/(%s)' % (_prefix_regex, _root_regex, _bank_regex)
60+
61+
_root_weights = [6, 3, 7, 9, 10, 5, 8, 4, 2, 1]
62+
_prefix_weights = _root_weights[4:] # prefix weights are same as root, but we are interested in last 6 weights only
63+
64+
65+
def _parse(number):
66+
number = clean(number).strip()
67+
match = re.match(_regex, number)
68+
if match:
69+
prefix = match.group(2)
70+
root = match.group(3)
71+
bank = match.group(4)
72+
return prefix, root, bank
73+
74+
75+
def compact(number):
76+
"""Convert the number to the minimal representation. This strips the
77+
number of any valid separators and removes surrounding whitespace."""
78+
parsed = _parse(number)
79+
if parsed:
80+
prefix, root, bank = parsed
81+
return ''.join((
82+
prefix + '-' if prefix else '', root, '/', bank,
83+
))
84+
85+
86+
def _info(bank_code):
87+
from stdnum import numdb
88+
info = {}
89+
for nr, found in numdb.get('cz/banks').info(bank_code):
90+
info.update(found)
91+
return info
92+
93+
94+
def info(number):
95+
"""Return a dictionary of data about the supplied number. This typically
96+
returns the name of the bank and branch and a BIC if it is valid."""
97+
parsed = _parse(number)
98+
if parsed:
99+
return _info(parsed[2])
100+
101+
102+
def _get_checksum(part):
103+
if len(part) > 6:
104+
weights = _root_weights
105+
part.zfill(10)
106+
else:
107+
weights = _prefix_weights
108+
part.zfill(6)
109+
110+
_sum = 0
111+
for i, n in enumerate(part):
112+
_sum += weights[i] * int(n)
113+
114+
return _sum % 11
115+
116+
117+
def is_checksum_valid(part):
118+
"""Check if prefix or root of bank account number is valid."""
119+
return _get_checksum(part) == 0
120+
121+
122+
def is_bank_valid(bank_code):
123+
"""Check if bank code is valid."""
124+
return 'bank' in _info(bank_code)
125+
126+
127+
def validate(number):
128+
"""Check if the number provided is a valid bank account number."""
129+
number = format(number) # fill missing zeros
130+
if not number:
131+
raise InvalidFormat()
132+
133+
prefix, root, bank = _parse(number)
134+
135+
if not is_checksum_valid(prefix):
136+
raise InvalidChecksum()
137+
138+
if not is_checksum_valid(root):
139+
raise InvalidChecksum()
140+
141+
if not is_bank_valid(bank):
142+
raise InvalidComponent()
143+
144+
return number
145+
146+
147+
def is_valid(number):
148+
"""Check if the number provided is a valid bank account number."""
149+
try:
150+
return bool(validate(number))
151+
except ValidationError:
152+
return False
153+
154+
155+
def format(number):
156+
"""Reformat the number to the standard presentation format."""
157+
parsed = _parse(number)
158+
if parsed:
159+
return ''.join((
160+
(parsed[0] or '').zfill(6), '-', parsed[1].zfill(10), '/', parsed[2],
161+
))

stdnum/cz/banks.dat

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
# generated from kody_bank_CR.csv downloaded from
2+
# https://www.cnb.cz/cs/platebni-styk/.galleries/ucty_kody_bank/download/kody_bank_CR.csv
3+
0100 bic="KOMBCZPP" bank="Komerční banka, a.s." certis="True"
4+
0300 bic="CEKOCZPP" bank="Československá obchodní banka, a. s." certis="True"
5+
0600 bic="AGBACZPP" bank="MONETA Money Bank, a.s." certis="True"
6+
0710 bic="CNBACZPP" bank="ČESKÁ NÁRODNÍ BANKA" certis="True"
7+
0800 bic="GIBACZPX" bank="Česká spořitelna, a.s." certis="True"
8+
2010 bic="FIOBCZPP" bank="Fio banka, a.s." certis="True"
9+
2020 bic="BOTKCZPP" bank="MUFG Bank (Europe) N.V. Prague Branch" certis="True"
10+
2060 bic="CITFCZPP" bank="Citfin, spořitelní družstvo" certis="True"
11+
2070 bic="MPUBCZPP" bank="TRINITY BANK a.s." certis="True"
12+
2100 bank="Hypoteční banka, a.s." certis="True"
13+
2200 bank="Peněžní dům, spořitelní družstvo" certis="True"
14+
2220 bic="ARTTCZPP" bank="Artesa, spořitelní družstvo" certis="True"
15+
2250 bic="CTASCZ22" bank="Banka CREDITAS a.s." certis="True"
16+
2260 bank="NEY spořitelní družstvo" certis="True"
17+
2275 bank="Podnikatelská družstevní záložna"
18+
2600 bic="CITICZPX" bank="Citibank Europe plc, organizační složka"
19+
2700 bic="BACXCZPP" bank="UniCredit Bank Czech Republic and Slovakia, a.s." certis="True"
20+
3030 bic="AIRACZPP" bank="Air Bank a.s." certis="True"
21+
3050 bic="BPPFCZP1" bank="BNP Paribas Personal Finance SA, odštěpný závod" certis="True"
22+
3060 bic="BPKOCZPP" bank="PKO BP S.A., Czech Branch" certis="True"
23+
3500 bic="INGBCZPP" bank="ING Bank N.V." certis="True"
24+
4000 bic="EXPNCZPP" bank="Expobank CZ a.s." certis="True"
25+
4300 bic="NROZCZPP" bank="Národní rozvojová banka, a.s." certis="True"
26+
5500 bic="RZBCCZPP" bank="Raiffeisenbank a.s." certis="True"
27+
5800 bic="JTBPCZPP" bank="J&T BANKA, a.s."
28+
6000 bic="PMBPCZPP" bank="PPF banka a.s." certis="True"
29+
6100 bic="EQBKCZPP" bank="Raiffeisenbank a.s. (do 31. 12. 2021 Equa bank a.s.)" certis="True"
30+
6200 bic="COBACZPX" bank="COMMERZBANK Aktiengesellschaft, pobočka Praha" certis="True"
31+
6210 bic="BREXCZPP" bank="mBank S.A., organizační složka" certis="True"
32+
6300 bic="GEBACZPP" bank="BNP Paribas S.A., pobočka Česká republika" certis="True"
33+
6700 bic="SUBACZPP" bank="Všeobecná úverová banka a.s., pobočka Praha" certis="True"
34+
6800 bic="VBOECZ2X" bank="Sberbank CZ, a.s. v likvidaci" certis="True"
35+
7910 bic="DEUTCZPX" bank="Deutsche Bank Aktiengesellschaft Filiale Prag, organizační složka" certis="True"
36+
7950 bank="Raiffeisen stavební spořitelna a.s." certis="True"
37+
7960 bank="ČSOB Stavební spořitelna, a.s." certis="True"
38+
7970 bank="MONETA Stavební Spořitelna, a.s." certis="True"
39+
7990 bank="Modrá pyramida stavební spořitelna, a.s." certis="True"
40+
8030 bic="GENOCZ21" bank="Volksbank Raiffeisenbank Nordoberpfalz eG pobočka Cheb" certis="True"
41+
8040 bic="OBKLCZ2X" bank="Oberbank AG pobočka Česká republika" certis="True"
42+
8060 bank="Stavební spořitelna České spořitelny, a.s." certis="True"
43+
8090 bic="CZEECZPP" bank="Česká exportní banka, a.s." certis="True"
44+
8150 bic="MIDLCZPP" bank="HSBC Continental Europe, Czech Republic" certis="True"
45+
8190 bank="Sparkasse Oberlausitz-Niederschlesien" certis="True"
46+
8198 bic="FFCSCZP1" bank="FAS finance company s.r.o."
47+
8199 bic="MOUSCZP2" bank="MoneyPolo Europe s.r.o."
48+
8200 bank="PRIVAT BANK der Raiffeisenlandesbank Oberösterreich Aktiengesellschaft, pobočka Česká republika"
49+
8220 bic="PAERCZP1" bank="Payment execution s.r.o."
50+
8230 bank="ABAPAY s.r.o."
51+
8240 bank="Družstevní záložna Kredit, v likvidaci"
52+
8250 bic="BKCHCZPP" bank="Bank of China (CEE) Ltd. Prague Branch" certis="True"
53+
8255 bic="COMMCZPP" bank="Bank of Communications Co., Ltd., Prague Branch odštěpný závod" certis="True"
54+
8265 bic="ICBKCZPP" bank="Industrial and Commercial Bank of China Limited, Prague Branch, odštěpný závod" certis="True"
55+
8270 bic="FAPOCZP1" bank="Fairplay Pay s.r.o."
56+
8280 bic="BEFKCZP1" bank="B-Efekt a.s."
57+
8293 bic="MRPSCZPP" bank="Mercurius partners s.r.o."
58+
8299 bic="BEORCZP2" bank="BESTPAY s.r.o."
59+
8500 bank="Ferratum Bank plc"

tests/test_cz_bankaccount.doctest

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
test_cz_bankaccount.doctest - more detailed doctests for stdnum.cz.bankaccount
2+
3+
Copyright (C) 2022 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+
21+
This file contains more detailed doctests for the stdnum.cz.bankaccount
22+
module.
23+
24+
>>> from stdnum.cz import bankaccount
25+
26+
>>> bankaccount.validate('34278-0727558021/0100')
27+
'034278-0727558021/0100'
28+
29+
>>> bankaccount.is_valid('4278-0727558021/0100')
30+
False
31+
32+
>>> bankaccount.compact('34278-0727558021/0100')
33+
'34278-0727558021/0100'
34+
35+
>>> bankaccount.compact('1/0100')
36+
37+
>>> str(bankaccount.info('34278-0727558021/0100')['bic'])
38+
'KOMBCZPP'
39+
40+
>>> bankaccount.info('1/0000')
41+
42+
>>> bankaccount.validate('1/0100')
43+
Traceback (most recent call last):
44+
...
45+
bankaccount.InvalidFormat: ...
46+
47+
>>> bankaccount.validate('8021/0100')
48+
Traceback (most recent call last):
49+
...
50+
bankaccount.InvalidChecksum: ...
51+
52+
53+
These have been found online and should all be valid numbers.
54+
55+
>>> numbers = '''
56+
...
57+
... 19-2000145399/0800
58+
... 178124-4159/0710
59+
... 19-34222621/0710
60+
... 280154417/0300
61+
... 0500021502/0800
62+
...
63+
... '''
64+
>>> [x for x in numbers.splitlines() if x and not bankaccount.is_valid(x)]
65+
[]

update/cz_banks.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
#!/usr/bin/env python3
2+
# coding: utf-8
3+
4+
# update/cz_banks.py - script to download Bank list from Czech National Bank
5+
#
6+
# Copyright (C) 2022 Arthur de Jong
7+
#
8+
# This library is free software; you can redistribute it and/or
9+
# modify it under the terms of the GNU Lesser General Public
10+
# License as published by the Free Software Foundation; either
11+
# version 2.1 of the License, or (at your option) any later version.
12+
#
13+
# This library is distributed in the hope that it will be useful,
14+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
15+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16+
# Lesser General Public License for more details.
17+
#
18+
# You should have received a copy of the GNU Lesser General Public
19+
# License along with this library; if not, write to the Free Software
20+
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
21+
# 02110-1301 USA
22+
23+
"""This script downloads the list of banks with bank codes as used in the
24+
IBAN and BIC codes as published by the Czech National Bank."""
25+
import csv
26+
import os.path
27+
from io import StringIO
28+
29+
import requests
30+
31+
32+
# The location of the CSV version of the bank identification codes. Also see
33+
# https://www.cnb.cz/cs/platebni-styk/ucty-kody-bank/
34+
download_url = 'https://www.cnb.cz/cs/platebni-styk/.galleries/ucty_kody_bank/download/kody_bank_CR.csv'
35+
36+
37+
def get_values(csv_reader):
38+
"""Return values (bank_number, bic, bank_name, certis) from the CSV."""
39+
# skip first row (header)
40+
try:
41+
next(csv_reader)
42+
except StopIteration:
43+
pass # ignore empty CSV
44+
45+
for row in csv_reader:
46+
yield row[0], row[2], row[1], True if row[3] == 'A' else False
47+
48+
49+
if __name__ == '__main__':
50+
response = requests.get(download_url)
51+
response.raise_for_status()
52+
csv_reader = csv.reader(StringIO(response.content.decode('utf-8')), delimiter=';')
53+
print('# generated from %s downloaded from' % os.path.basename(download_url))
54+
print('# %s' % download_url)
55+
for bank_number, bic, bank, certis in get_values(csv_reader):
56+
info = '%s' % bank_number
57+
if bic:
58+
info += ' bic="%s"' % bic
59+
if bank:
60+
info += ' bank="%s"' % bank
61+
if certis:
62+
info += ' certis="%s"' % certis
63+
print(info)

0 commit comments

Comments
 (0)
0