diff --git a/src/validators/finance.py b/src/validators/finance.py index 9df5a970..2605dbfd 100644 --- a/src/validators/finance.py +++ b/src/validators/finance.py @@ -32,21 +32,32 @@ def _cusip_checksum(cusip: str): def _isin_checksum(value: str): - check, val = 0, None + # The check digit (last character) is always numeric. + if not value[-1].isdecimal(): + return False + # Expand the code into a string of digits, mapping each letter to its + # two-digit value (A=10, ..., Z=35) and leaving digits unchanged. + digits = "" for idx in range(12): c = value[idx] if c >= "0" and c <= "9" and idx > 1: - val = ord(c) - ord("0") + digits += c elif c >= "A" and c <= "Z": - val = 10 + ord(c) - ord("A") + digits += str(10 + ord(c) - ord("A")) elif c >= "a" and c <= "z": - val = 10 + ord(c) - ord("a") + digits += str(10 + ord(c) - ord("a")) else: return False + # Luhn checksum over the expanded digit string: starting from the + # rightmost digit, double every second digit and sum the resulting digits. + check = 0 + for idx, c in enumerate(reversed(digits)): + val = ord(c) - ord("0") if idx & 1: val += val + check += (val // 10) + (val % 10) return (check % 10) == 0 diff --git a/tests/test_finance.py b/tests/test_finance.py index a40fd333..8425bcca 100644 --- a/tests/test_finance.py +++ b/tests/test_finance.py @@ -24,13 +24,25 @@ def test_returns_failed_validation_on_invalid_cusip(value: str): # ==> ISIN <== # -@pytest.mark.parametrize("value", ["US0004026250", "JP000K0VF054", "US0378331005"]) +@pytest.mark.parametrize("value", ["US0004026250", "US0378331005", "GB0002634946", "DE000BAY0017"]) def test_returns_true_on_valid_isin(value: str): """Test returns true on valid isin.""" assert isin(value) -@pytest.mark.parametrize("value", ["010378331005", "XCVF", "00^^^1234", "A000009"]) +@pytest.mark.parametrize( + "value", + [ + "010378331005", + "XCVF", + "00^^^1234", + "A000009", + # valid length and characters but wrong check digit + "US0378331006", + "US0004026251", + "GB0002634947", + ], +) def test_returns_failed_validation_on_invalid_isin(value: str): """Test returns failed validation on invalid isin.""" assert isinstance(isin(value), ValidationError)