Bank account number validation in IBAN format I wrote a JavaScript function for validating bank account numbers in IBAN format for my own joy. Could you have a look if you don't see any obvious mistakes or places for improvement?

function isIbanValid(value) {

    var lengthMap = getLengthMap();

    //cleanup
    value = value.toString().toUpperCase().replace(/\s/g, '').replace(/[-]/g, '');
    //check if alphanumeric
    if (!/^[a-zA-Z0-9]+$/.test(value)) return false;
    //extract countrycode
    var countryCode = value.substring(0, 2);
    //check if letter
    if (!/([a-z]+[\s\-]?)*/i.test(countryCode)) return false;
    //check string length
    if (value.length != lengthMap[countryCode]) return false;

    value = value.concat(value.substring(0, 4)).substring(4);
    value = value.replace(countryCode, countryCodeToStringValue(countryCode));

    return modulo(value, 97) == 1;

    function countryCodeToStringValue(countryCode) {
        return "".concat(((countryCode.charCodeAt(0)) - 55).toString() + (countryCode.charCodeAt(1) - 55).toString());
    }

    function modulo(divident, divisor) {
        var quantization = 12;
        while (quantization &lt; divident.length) {
            var part = divident.substring(0, quantization);
            divident = (part % divisor) + divident.substring(quantization);
        }
        return divident % divisor;
    }

    function getLengthMap() {
        var lengthMap = {};
        lengthMap["AD"] = 24;
        lengthMap["AT"] = 20;
        lengthMap["AZ"] = 28;
        lengthMap["BH"] = 22;
        lengthMap["BE"] = 16;
        lengthMap["BA"] = 20;
        lengthMap["BR"] = 29;
        lengthMap["BG"] = 22;
        lengthMap["CR"] = 21;
        lengthMap["HR"] = 21;
        lengthMap["CY"] = 28;
        lengthMap["CZ"] = 24;
        lengthMap["DK"] = 18;
        lengthMap["DO"] = 28;
        lengthMap["EE"] = 20;
        lengthMap["FO"] = 18;
        lengthMap["FI"] = 18;
        lengthMap["FR"] = 27;
        lengthMap["DE"] = 22;
        lengthMap["GR"] = 27;
        lengthMap["GI"] = 23;
        lengthMap["GL"] = 18;
        lengthMap["GT"] = 28;
        lengthMap["HU"] = 28;
        lengthMap["IS"] = 26;
        lengthMap["IE"] = 22;
        lengthMap["IL"] = 23;
        lengthMap["IT"] = 27;
        lengthMap["JO"] = 30;
        lengthMap["KZ"] = 20;
        lengthMap["KW"] = 30;
        lengthMap["LV"] = 21;
        lengthMap["LB"] = 28;
        lengthMap["LI"] = 21;
        lengthMap["LT"] = 20;
        lengthMap["LU"] = 20;
        lengthMap["MK"] = 19;
        lengthMap["MT"] = 31;
        lengthMap["MR"] = 27;
        lengthMap["MU"] = 30;
        lengthMap["MC"] = 27;
        lengthMap["MD"] = 24;
        lengthMap["ME"] = 22;
        lengthMap["NL"] = 18;
        lengthMap["NO"] = 15;
        lengthMap["PK"] = 24;
        lengthMap["PS"] = 29;
        lengthMap["PL"] = 28;
        lengthMap["PT"] = 25;
        lengthMap["QA"] = 29;
        lengthMap["RO"] = 24;
        lengthMap["SM"] = 27;
        lengthMap["SA"] = 24;
        lengthMap["RS"] = 22;
        lengthMap["SK"] = 24;
        lengthMap["SI"] = 19;
        lengthMap["ES"] = 24;
        lengthMap["SE"] = 24;
        lengthMap["CH"] = 21;
        lengthMap["TN"] = 24;
        lengthMap["TR"] = 26;
        lengthMap["AE"] = 23;
        lengthMap["GB"] = 22;

        return lengthMap;
    }
}
I'm pretty sure this will fail for some IBANs. You convert the country code to digits, but I don't see you doing the same for any other letters in the IBAN - and there might well be others. The example Wikipedia gives is GB82WEST12345698765432. The "WEST" should be converted to numbers as well (same procedure as the country code; 'A' = 10, B = 11, etc.).
Not sure why getLengthMap is a function - it's basically a constant value, and might as well be declared as a simple variable.
The regexes have some redundancy.This one, /^[a-zA-Z0-9]+$/, checks for lowercase letters, though the line before has already called toUpperCase(). And the next regex accepts trailing whitespace and dashes, though those have been removed.
And while you're using regexes, you might as well use that to pick the string apart, rather than substring plus magic numbers.
Your modulo function seems fine, though there's little reason to have it accept a divisor argument. Since it's an internal helper function specifically for IBAN, it'll always be mod 97. So in this case, I might actually prefer that that was hard-coded (and the function was named mod97 or something equally explanatory).


In all, I think you can do a lot more with fewer regexes, which would help streamline the code.

To sanitize the input:

var iban = value.toUpperCase().replace(/[^A-Z0-9]/g, '');


To "parse" the IBAN:

var match = iban.match(/^([A-Z]{2})(\d{2})(.+)$/);


if match is null the code's automatically invalid. Otherwise, match[1] will be the 2-letter country code, match[2] will be the check digits, and match[3] will be the rest of the IBAN. Provided there's a match it should then rearranged as match[3] + match[1] + match[2]

Alternatively, you can check and rearrange in one go:

var code = iban.replace(/^([A-Z]{2})(\d{2})(.+)$/, "$3$1$2");


if code === iban, nothing's been rearranged, which means there wasn't a match, and the code's invalid. You'd still have to extract the country code, though, so not much is won by doing this.

Finally, to convert letters to numbers:

 var digits = iban.replace(/[A-Z]/g, function (letter) {
   return String(letter.charCodeAt(0) - 55);
 });




Here's my take

var validIBAN = (function () { // use an IIFE
  // A "constant" lookup table of IBAN lengths per country
  // (the funky formatting is just to make it fit better in the answer here on CR)
  var CODE_LENGTHS = {
    AD: 24, AE: 23, AT: 20, AZ: 28, BA: 20, BE: 16, BG: 22, BH: 22, BR: 29,
    CH: 21, CR: 21, CY: 28, CZ: 24, DE: 22, DK: 18, DO: 28, EE: 20, ES: 24,
    FI: 18, FO: 18, FR: 27, GB: 22, GI: 23, GL: 18, GR: 27, GT: 28, HR: 21,
    HU: 28, IE: 22, IL: 23, IS: 26, IT: 27, JO: 30, KW: 30, KZ: 20, LB: 28,
    LI: 21, LT: 20, LU: 20, LV: 21, MC: 27, MD: 24, ME: 22, MK: 19, MR: 27,
    MT: 31, MU: 30, NL: 18, NO: 15, PK: 24, PL: 28, PS: 29, PT: 25, QA: 29,
    RO: 24, RS: 22, SA: 24, SE: 24, SI: 19, SK: 24, SM: 27, TN: 24, TR: 26
  };

  // piece-wise mod97 using 9 digit "chunks", as per Wikipedia's example:
  // http://en.wikipedia.org/wiki/International_Bank_Account_Number#Modulo_operation_on_IBAN
  function mod97(string) {
    var checksum = string.slice(0, 2),
        fragment;

    for(var offset = 2 ; offset &lt; string.length ; offset += 7) {
      fragment = String(checksum) + string.substring(offset, offset + 7);
      checksum = parseInt(fragment, 10) % 97;
    }

    return checksum;
  }

  // return a function that does the actual work
  return function (input) {
    var iban = String(input).toUpperCase().replace(/[^A-Z0-9]/g, ''), // keep only alphanumeric characters
        code = iban.match(/^([A-Z]{2})(\d{2})([A-Z\d]+)$/),           // match and capture (1) the country code, (2) the check digits, and (3) the rest
        digits;

    // check syntax and length
    if(!code || iban.length !== CODE_LENGTHS[code[1]]) {
      return false;
    }

    // rearrange country code and check digits, and convert chars to ints
    digits = (code[3] + code[1] + code[2]).replace(/[A-Z]/g, function (letter) {
      return letter.charCodeAt(0) - 55;
    });

    // final check
    return mod97(digits) === 1;
  };
}());