diff --git a/stdnum/in_/ifsc.py b/stdnum/in_/ifsc.py new file mode 100644 index 00000000..1254909e --- /dev/null +++ b/stdnum/in_/ifsc.py @@ -0,0 +1,102 @@ +# ifsc.py - functions for handling Indian IFSC bank branch codes +# +# Copyright (C) 2026 Yash Budhia +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, see . + +"""IFSC (Indian Financial System Code). + +The Indian Financial System Code (IFSC) is an 11 character alphanumeric code +that the Reserve Bank of India (RBI) assigns to every bank branch that takes +part in the NEFT, RTGS and IMPS electronic funds transfer systems. It is +required for every domestic electronic transfer in India and is also used to +route UPI payments. + +The code consists of a 4 letter bank code, a fifth character that is reserved +and always 0, and a 6 character branch code. The branch code is often numeric +but may contain letters, which is common for cooperative and other sub-member +banks that transact through a sponsor bank. There is no check digit. + +More information: + +* https://en.wikipedia.org/wiki/Indian_Financial_System_Code +* https://www.rbi.org.in/Scripts/IFSCMICRDetails.aspx +* https://github.com/razorpay/ifsc + +>>> validate('SBIN0000001') +'SBIN0000001' +>>> validate('hdfc0cabblm') +'HDFC0CABBLM' +>>> validate('SBIN000001') +Traceback (most recent call last): + ... +InvalidLength: ... +>>> validate('SB1N0000001') # bank code must be letters +Traceback (most recent call last): + ... +InvalidFormat: ... +>>> validate('SBIN1000001') # fifth character is reserved and must be 0 +Traceback (most recent call last): + ... +InvalidComponent: ... +>>> info('ICIC0000001')['bank_code'] +'ICIC' +""" + +from __future__ import annotations + +import re + +from stdnum.exceptions import * +from stdnum.util import clean + + +_ifsc_re = re.compile(r'^[A-Z]{4}[A-Z0-9]{7}$') + + +def compact(number: str) -> str: + """Convert the number to the minimal representation. This strips the + number of any valid separators and removes surrounding whitespace.""" + return clean(number, ' -').upper().strip() + + +def validate(number: str) -> str: + """Check if the number provided is a valid IFSC. This checks the length + and formatting.""" + number = compact(number) + if len(number) != 11: + raise InvalidLength() + if not _ifsc_re.match(number): + raise InvalidFormat() + if number[4] != '0': + raise InvalidComponent() + return number + + +def is_valid(number: str) -> bool: + """Check if the number provided is a valid IFSC. This checks the length + and formatting.""" + try: + return bool(validate(number)) + except ValidationError: + return False + + +def info(number: str) -> dict[str, str]: + """Provide information that can be decoded from the IFSC.""" + number = compact(number) + return { + 'bank_code': number[:4], + 'branch_code': number[5:], + } diff --git a/tests/test_in_ifsc.doctest b/tests/test_in_ifsc.doctest new file mode 100644 index 00000000..36d9a41f --- /dev/null +++ b/tests/test_in_ifsc.doctest @@ -0,0 +1,148 @@ +test_in_ifsc.doctest - more detailed doctests for stdnum.in_.ifsc module + +Copyright (C) 2026 Yash Budhia + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, see . + + +This file contains more detailed doctests for the stdnum.in_.ifsc module. It +tries to cover more corner cases and detailed functionality that is not +really useful as module documentation. + +>>> from stdnum.in_ import ifsc +>>> from stdnum.exceptions import * + +These codes come from the openly maintained Razorpay IFSC dataset, which is +derived from the list the RBI publishes, and should all be valid. They were +picked to cover many different banks and both numeric and alphanumeric branch +codes. + +>>> numbers = ''' +... +... SBIN0000001 +... HDFC0000001 +... ICIC0000001 +... UTIB0000001 +... PUNB0000100 +... ABHY0065001 +... ADCC0000001 +... AIRP00000SC +... AKJB0000001 +... ALLA0888888 +... AMCB0RTGS4S +... ANDB0TRESUY +... APBL0000001 +... APGB0000001 +... APGV0000001 +... APMC0000001 +... ASBL0000001 +... AUBL0000001 +... AUCB0000001 +... BACB0000001 +... BARB0BGGBXX +... BARC0INBB01 +... BBKM0000001 +... BCBM0000001 +... BCEY0CHEN01 +... BDBL0000001 +... BKDN0RTGSSC +... BKID000DCBL +... BNPA0RTGSMI +... BOFA0MM6205 +... BOTM0003611 +... CBIN0281102 +... CCBL0209003 +... CHAS0INBX01 +... CITI0RTGSMI +... CIUB0000082 +... CLBL0000001 +... CNRB0RTGS01 +... COAS0000001 +... CORP0000920 +... COSB0000025 +... CRLY0000001 +... CRUB0000001 +... CSBK0000001 +... CSBX0CSB001 +... DBSS0IN0811 +... DCBL0000037 +... DEOB0000001 +... DEUT0000PBC +... DLSC0000001 +... DLXB0000146 +... DMKJ0000001 +... DNSB0000001 +... DOHB0000001 +... EBIL0000001 +... ESFB0000001 +... ESMF0000001 +... FDRL0000379 +... FINO0000001 +... FSFB0000001 +... +... ''' +>>> [x for x in numbers.splitlines() if x and not ifsc.is_valid(x)] +[] + + +The fifth character is reserved and must always be 0. + +>>> ifsc.validate('SBIN1000001') +Traceback (most recent call last): + ... +InvalidComponent: ... +>>> ifsc.is_valid('SBINA000001') +False + + +The bank code is four letters and the branch code is six letters or digits. + +>>> ifsc.validate('SB1N0000001') +Traceback (most recent call last): + ... +InvalidFormat: ... +>>> ifsc.validate('SBIN00000#1') +Traceback (most recent call last): + ... +InvalidFormat: ... + + +The code is always exactly 11 characters long. + +>>> ifsc.validate('SBIN000001') +Traceback (most recent call last): + ... +InvalidLength: ... +>>> ifsc.validate('SBIN00000012') +Traceback (most recent call last): + ... +InvalidLength: ... + + +Lower case, surrounding whitespace and separators are normalised away. + +>>> ifsc.compact(' hdfc0cabblm ') +'HDFC0CABBLM' +>>> ifsc.validate('icic0000001') +'ICIC0000001' +>>> ifsc.validate('UTIB 0000001') +'UTIB0000001' + + +The code splits into a bank code and a branch code. + +>>> ifsc.info('UTIB0000001') == {'bank_code': 'UTIB', 'branch_code': '000001'} +True +>>> ifsc.info('HDFC0CABBLM')['branch_code'] +'CABBLM'