Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 102 additions & 0 deletions stdnum/in_/ifsc.py
Original file line number Diff line number Diff line change
@@ -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 <https://www.gnu.org/licenses/>.

"""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:],
}
148 changes: 148 additions & 0 deletions tests/test_in_ifsc.doctest
Original file line number Diff line number Diff line change
@@ -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 <https://www.gnu.org/licenses/>.


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'
Loading