60

I need to do an IBAN validation check using JavaScript. The rules I need to follow are:

Validating the IBAN An IBAN is validated by converting it into an integer and performing a basic mod-97 operation (as described in ISO 7064) on it. If the IBAN is valid, the remainder equals 1.

  1. Check that the total IBAN length is correct as per the country. If not, the IBAN is invalid

  2. Move the four initial characters to the end of the string

  3. Replace each letter in the string with two digits, thereby expanding the string, where A = 10, B = 11, ..., Z = 35

  4. Interpret the string as a decimal integer and compute the remainder of that number on division by 97

I am doing this for a Belarusian IBAN so it has to follow the following format

2C 31N -

RU1230000000000000000000000000000

How do I modify the following to meet the above rules;

function validateIBAN(iban) {
  var newIban = iban.toUpperCase(),
    modulo = function(divident, divisor) {
      var cDivident = '';
      var cRest = '';

      for (var i in divident) {
        var cChar = divident[i];
        var cOperator = cRest + '' + cDivident + '' + cChar;

        if (cOperator < parseInt(divisor)) {
          cDivident += '' + cChar;
        } else {
          cRest = cOperator % divisor;
          if (cRest == 0) {
            cRest = '';
          }
          cDivident = '';
        }

      }
      cRest += '' + cDivident;
      if (cRest == '') {
        cRest = 0;
      }
      return cRest;
    };

  if (newIban.search(/^[A-Z]{2}/gi) < 0) {
    return false;
  }

  newIban = newIban.substring(4) + newIban.substring(0, 4);

  newIban = newIban.replace(/[A-Z]/g, function(match) {
    return match.charCodeAt(0) - 55;
  });

  return parseInt(modulo(newIban, 97), 10) === 1;
}


console.log(validateIBAN("RU1230000000000000000000000000000"));
VLAZ
  • 26,331
  • 9
  • 49
  • 67
pmillio
  • 1,089
  • 2
  • 13
  • 20

11 Answers11

59

Based on the work of http://toms-cafe.de/iban/iban.js I have developed my version of an IBAN validation check.

You can modify the country support by modifying the variable CODE_LENGTHS.

Here is my implementation:

function alertValidIBAN(iban) {
    alert(isValidIBANNumber(iban));
}

/*
 * Returns 1 if the IBAN is valid 
 * Returns FALSE if the IBAN's length is not as should be (for CY the IBAN Should be 28 chars long starting with CY )
 * Returns any other number (checksum) when the IBAN is invalid (check digits do not match)
 */
function isValidIBANNumber(input) {
    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,   
        AL: 28, BY: 28, CR: 22, EG: 29, GE: 22, IQ: 23, LC: 32, SC: 31, ST: 25,
        SV: 28, TL: 23, UA: 29, VA: 22, VG: 24, XK: 20
    };
    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;
}

function mod97(string) {
    var checksum = string.slice(0, 2), fragment;
    for (var offset = 2; offset < string.length; offset += 7) {
        fragment = String(checksum) + string.substring(offset, offset + 7);
        checksum = parseInt(fragment, 10) % 97;
    }
    return checksum;
}
input { width:300px; }
Enter IBAN 
<input type="test" id="iban"/>
<button onclick="alertValidIBAN(document.getElementById('iban').value);">check IBAN</button>
SuperStormer
  • 4,997
  • 5
  • 25
  • 35
MaVRoSCy
  • 17,747
  • 15
  • 82
  • 125
  • 2
    Seems to work great. Added some more country codes and since it's a validate method I guess it should return boolean not the checksum value. So I also changed the return to be `return mod97(digits) === 1;` – Artur K. Nov 11 '16 at 10:24
  • 1
    @Ismaestro no, it returns '55' - > which as per description 'Returns any other number (checksum) when the IBAN is invalid' – MaVRoSCy Mar 28 '17 at 08:35
  • Checked sniippet with account real, don't run correctly. Use https://github.com/arhs/iban.js solution – Mastercafe Nov 13 '20 at 17:27
  • 7
    Is it correct 'CR' is mentioned twice in the 'CODE_LENGTHS' list? See column 2 and 3. – Peps Feb 12 '21 at 11:47
  • CR is 22, not 21 (presumed costa rica) – Kenan Balija Feb 17 '21 at 23:44
  • 2
    The snippet returns for each IBAN [even correct] always `false`. DE12500105170648489890 – Peter VARGA Dec 18 '21 at 15:12
  • `alertValidIBAN(null)` from snippet always returns false as it does not read the input field. – coyer Dec 20 '22 at 05:34
  • CR key is present twice in the CODE_LENGTHS object – lezhumain Apr 30 '23 at 18:34
  • Could you also set the checksum initially to 0 (zero) and start from offset = 0? (In my opinion it would reduce the (read) complexity of the mod97 function) – Sergej Werfel Jun 23 '23 at 05:29
  • Checknumber values 00, 01 and 99 like in GB01BARC20714583608387 are invalid, but the algorithm doesn't check them – Sergej Werfel Jun 23 '23 at 08:18
36

You can use this library to validate and format an IBAN: https://github.com/arhs/iban.js (disclaimer: I wrote the library)

However, neither Russia nor Belarus are supported, as I can't find any mention of those countries on the IBAN page of wikipedia nor in the official IBAN registry so I'm afraid you'll have to modify the library code yourself to add it.

Laurent VB
  • 1,187
  • 9
  • 18
  • 2
    this code cannot validate Moldovan IBAN. for example `MD75EX0900002374642125EU`. according to www.iban.com it is valid – armen Nov 10 '15 at 07:04
  • 1
    Thanks @armen. Issue open (https://github.com/arhs/iban.js/issues/23) will fix it soon. – Laurent VB Nov 11 '15 at 12:34
  • very nice @Laurent VB, is there any mailing list to inform me after this fix ? – armen Nov 11 '15 at 13:39
  • @armen you can subscribe to the github issue notifications. I'll also come and keep this SO thread updated. – Laurent VB Nov 12 '15 at 09:29
13

I answered a similar question. Use this list of Regular expressions to validate your IBANS. There are 70 countries, so in general you should be good to go.

AL[a-zA-Z0-9]{2}\s?([0-9]{4}\s?){2}([a-zA-Z0-9]{4}\s?){4}\s?
AD[a-zA-Z0-9]{2}\s?([0-9]{4}\s?){2}([a-zA-Z0-9]{4}\s?){3}\s?
AT[a-zA-Z0-9]{2}\s?([0-9]{4}\s?){4}\s?
AZ[a-zA-Z0-9]{2}\s?([a-zA-Z0-9]{4}\s?){1}([0-9]{4}\s?){5}\s?
BH[a-zA-Z0-9]{2}\s?([a-zA-Z]{4}\s?){1}([a-zA-Z0-9]{4}\s?){3}([a-zA-Z0-9]{2})\s?
BY[a-zA-Z0-9]{2}\s?([a-zA-Z0-9]{4}\s?){1}([0-9]{4}\s?){5}\s?
BE[a-zA-Z0-9]{2}\s?([0-9]{4}\s?){3}\s?
BA[a-zA-Z0-9]{2}\s?([0-9]{4}\s?){4}\s?
BR[a-zA-Z0-9]{2}\s?([0-9]{4}\s?){5}([0-9]{3})([a-zA-Z]{1}\s?)([a-zA-Z0-9]{1})\s?
BG[a-zA-Z0-9]{2}\s?([a-zA-Z]{4}\s?){1}([0-9]{4}\s?){1}([0-9]{2})([a-zA-Z0-9]{2}\s?)([a-zA-Z0-9]{4}\s?){1}([a-zA-Z0-9]{2})\s?
CR[a-zA-Z0-9]{2}\s?([0-9]{4}\s?){4}([0-9]{2})\s?
HR[a-zA-Z0-9]{2}\s?([0-9]{4}\s?){4}([0-9]{1})\s?
CY[a-zA-Z0-9]{2}\s?([0-9]{4}\s?){2}([a-zA-Z0-9]{4}\s?){4}\s?
CZ[a-zA-Z0-9]{2}\s?([0-9]{4}\s?){5}\s?
DK[a-zA-Z0-9]{2}\s?([0-9]{4}\s?){3}([0-9]{2})\s?
DO[a-zA-Z0-9]{2}\s?([a-zA-Z]{4}\s?){1}([0-9]{4}\s?){5}\s?
TL[a-zA-Z0-9]{2}\s?([0-9]{4}\s?){4}([0-9]{3})\s?
EE[a-zA-Z0-9]{2}\s?([0-9]{4}\s?){4}\s?
FO[a-zA-Z0-9]{2}\s?([0-9]{4}\s?){3}([0-9]{2})\s?
FI[a-zA-Z0-9]{2}\s?([0-9]{4}\s?){3}([0-9]{2})\s?
FR[a-zA-Z0-9]{2}\s?([0-9]{4}\s?){2}([0-9]{2})([a-zA-Z0-9]{2}\s?)([a-zA-Z0-9]{4}\s?){2}([a-zA-Z0-9]{1})([0-9]{2})\s?
GE[a-zA-Z0-9]{2}\s?([a-zA-Z0-9]{2})([0-9]{2}\s?)([0-9]{4}\s?){3}([0-9]{2})\s?
DE[a-zA-Z0-9]{2}\s?([0-9]{4}\s?){4}([0-9]{2})\s?
GI[a-zA-Z0-9]{2}\s?([a-zA-Z]{4}\s?){1}([a-zA-Z0-9]{4}\s?){3}([a-zA-Z0-9]{3})\s?
GR[a-zA-Z0-9]{2}\s?([0-9]{4}\s?){1}([0-9]{3})([a-zA-Z0-9]{1}\s?)([a-zA-Z0-9]{4}\s?){3}([a-zA-Z0-9]{3})\s?
GL[a-zA-Z0-9]{2}\s?([0-9]{4}\s?){3}([0-9]{2})\s?
GT[a-zA-Z0-9]{2}\s?([a-zA-Z0-9]{4}\s?){1}([a-zA-Z0-9]{4}\s?){5}\s?
HU[a-zA-Z0-9]{2}\s?([0-9]{4}\s?){6}\s?
IS[a-zA-Z0-9]{2}\s?([0-9]{4}\s?){5}([0-9]{2})\s?
IE[a-zA-Z0-9]{2}\s?([a-zA-Z0-9]{4}\s?){1}([0-9]{4}\s?){3}([0-9]{2})\s?
IL[a-zA-Z0-9]{2}\s?([0-9]{4}\s?){4}([0-9]{3})\s?
IT[a-zA-Z0-9]{2}\s?([a-zA-Z]{1})([0-9]{3}\s?)([0-9]{4}\s?){1}([0-9]{3})([a-zA-Z0-9]{1}\s?)([a-zA-Z0-9]{4}\s?){2}([a-zA-Z0-9]{3})\s?
JO[a-zA-Z0-9]{2}\s?([a-zA-Z]{4}\s?){1}([0-9]{4}\s?){5}([0-9]{2})\s?
KZ[a-zA-Z0-9]{2}\s?([0-9]{4}\s?){3}([0-9]{1})([a-zA-Z0-9]{3}\s?)([a-zA-Z0-9]{4}\s?){2}([a-zA-Z0-9]{2})\s?
XK[a-zA-Z0-9]{2}\s?([0-9]{4}\s?){1}([0-9]{4}\s?){2}([0-9]{2})([0-9]{2}\s?)\s?
KW[a-zA-Z0-9]{2}\s?([a-zA-Z]{4}\s?){1}([a-zA-Z0-9]{4}\s?){5}([a-zA-Z0-9]{2})\s?
LV[a-zA-Z0-9]{2}\s?([a-zA-Z]{4}\s?){1}([a-zA-Z0-9]{4}\s?){3}([a-zA-Z0-9]{1})\s?
LB[a-zA-Z0-9]{2}\s?([0-9]{4}\s?){1}([a-zA-Z0-9]{4}\s?){5}\s?
LI[a-zA-Z0-9]{2}\s?([0-9]{4}\s?){1}([0-9]{1})([a-zA-Z0-9]{3}\s?)([a-zA-Z0-9]{4}\s?){2}([a-zA-Z0-9]{1})\s?
LT[a-zA-Z0-9]{2}\s?([0-9]{4}\s?){4}\s?
LU[a-zA-Z0-9]{2}\s?([0-9]{3})([a-zA-Z0-9]{1}\s?)([a-zA-Z0-9]{4}\s?){3}\s?
MK[a-zA-Z0-9]{2}\s?([0-9]{3})([a-zA-Z0-9]{1}\s?)([a-zA-Z0-9]{4}\s?){2}([a-zA-Z0-9]{1})([0-9]{2})\s?
MT[a-zA-Z0-9]{2}\s?([a-zA-Z]{4}\s?){1}([0-9]{4}\s?){1}([0-9]{1})([a-zA-Z0-9]{3}\s?)([a-zA-Z0-9]{4}\s?){3}([a-zA-Z0-9]{3})\s?
MR[a-zA-Z0-9]{2}\s?([0-9]{4}\s?){5}([0-9]{3})\s?
MU[a-zA-Z0-9]{2}\s?([a-zA-Z]{4}\s?){1}([0-9]{4}\s?){4}([0-9]{3})([a-zA-Z]{1}\s?)([a-zA-Z]{2})\s?
MC[a-zA-Z0-9]{2}\s?([0-9]{4}\s?){2}([0-9]{2})([a-zA-Z0-9]{2}\s?)([a-zA-Z0-9]{4}\s?){2}([a-zA-Z0-9]{1})([0-9]{2})\s?
MD[a-zA-Z0-9]{2}\s?([a-zA-Z0-9]{2})([a-zA-Z0-9]{2}\s?)([a-zA-Z0-9]{4}\s?){4}\s?
ME[a-zA-Z0-9]{2}\s?([0-9]{4}\s?){4}([0-9]{2})\s?
NL[a-zA-Z0-9]{2}\s?([a-zA-Z]{4}\s?){1}([0-9]{4}\s?){2}([0-9]{2})\s?
NO[a-zA-Z0-9]{2}\s?([0-9]{4}\s?){2}([0-9]{3})\s?
PK[a-zA-Z0-9]{2}\s?([a-zA-Z0-9]{4}\s?){1}([0-9]{4}\s?){4}\s?
PS[a-zA-Z0-9]{2}\s?([a-zA-Z0-9]{4}\s?){1}([0-9]{4}\s?){5}([0-9]{1})\s?
PL[a-zA-Z0-9]{2}\s?([0-9]{4}\s?){6}\s?
PT[a-zA-Z0-9]{2}\s?([0-9]{4}\s?){5}([0-9]{1})\s?
QA[a-zA-Z0-9]{2}\s?([a-zA-Z]{4}\s?){1}([a-zA-Z0-9]{4}\s?){5}([a-zA-Z0-9]{1})\s?
RO[a-zA-Z0-9]{2}\s?([a-zA-Z]{4}\s?){1}([a-zA-Z0-9]{4}\s?){4}\s?
SM[a-zA-Z0-9]{2}\s?([a-zA-Z]{1})([0-9]{3}\s?)([0-9]{4}\s?){1}([0-9]{3})([a-zA-Z0-9]{1}\s?)([a-zA-Z0-9]{4}\s?){2}([a-zA-Z0-9]{3})\s?
SA[a-zA-Z0-9]{2}\s?([0-9]{2})([a-zA-Z0-9]{2}\s?)([a-zA-Z0-9]{4}\s?){4}\s?
RS[a-zA-Z0-9]{2}\s?([0-9]{4}\s?){4}([0-9]{2})\s?
SK[a-zA-Z0-9]{2}\s?([0-9]{4}\s?){5}\s?
SI[a-zA-Z0-9]{2}\s?([0-9]{4}\s?){3}([0-9]{3})\s?
ES[a-zA-Z0-9]{2}\s?([0-9]{4}\s?){5}\s?
SE[a-zA-Z0-9]{2}\s?([0-9]{4}\s?){5}\s?
CH[a-zA-Z0-9]{2}\s?([0-9]{4}\s?){1}([0-9]{1})([a-zA-Z0-9]{3}\s?)([a-zA-Z0-9]{4}\s?){2}([a-zA-Z0-9]{1})\s?
TN[a-zA-Z0-9]{2}\s?([0-9]{4}\s?){5}\s?
TR[a-zA-Z0-9]{2}\s?([0-9]{4}\s?){1}([0-9]{1})([a-zA-Z0-9]{3}\s?)([a-zA-Z0-9]{4}\s?){3}([a-zA-Z0-9]{2})\s?
AE[a-zA-Z0-9]{2}\s?([0-9]{3})([0-9]{1}\s?)([0-9]{4}\s?){3}([0-9]{3})\s?
GB[a-zA-Z0-9]{2}\s?([a-zA-Z]{4}\s?){1}([0-9]{4}\s?){3}([0-9]{2})\s?
VA[a-zA-Z0-9]{2}\s?([0-9]{3})([0-9]{1}\s?)([0-9]{4}\s?){3}([0-9]{2})\s?
VG[a-zA-Z0-9]{2}\s?([a-zA-Z0-9]{4}\s?){1}([0-9]{4}\s?){4}\s? 
Pascal Zoleko
  • 691
  • 6
  • 8
  • The list contains IBAN regex for 70 countries. The IBANs are readily tested. Does the 3 downvotes mean, these IBANs are not useful ? – Pascal Zoleko Nov 30 '19 at 23:39
  • 2
    Upvoted because I found it useful but it seems to be not correct at all: I think after first two characters always 2 digits should follow. so instead of e.g. `AL[a-zA-Z0-9]{2}` it should be `AL[0-9]{2}` right!? – coyer Feb 04 '20 at 15:24
  • Oh thanks a lot. you are right, My B. I had added "c" instead of "n" in my tests for AL IBANs so according to Wikipedia: https://en.wikipedia.org/wiki/International_Bank_Account_Number it should be 8**n**,16c. – Pascal Zoleko Feb 06 '20 at 23:25
  • Just to be clear: it's not only for AL it affects all IBANs. Your quoted wikipedia says that IBAN consists ob 2 letter country code, followed by 2 (check)digits (always numeric) and then up to 30 alphanumeric characters that are country-specific. So the first block ´[a-zA-Z0-9]´should always just be ´[0-9]´ – coyer Feb 08 '20 at 05:11
  • Thank you. You are right the first block is always [0-9] but the rest should remain the same. – Pascal Zoleko Feb 09 '20 at 11:33
  • 1
    Upvoted, because I needed regex for one country, not a general one. – Paweł Własiuk Jul 29 '20 at 11:56
7

"Jquery validation plugin" also supports iban check:

https://github.com/jzaefferer/jquery-validation/blob/master/src/additional/iban.js

Haluk
  • 2,091
  • 2
  • 27
  • 35
7

Based on previous answers I created a Directive that implements Angular Validator method.
I also added the types.

iban-validator.directive.ts

import { Validator, AbstractControl, NG_VALIDATORS } from '@angular/forms';
import { Directive } from '@angular/core';

@Directive({
  selector: '[ibanValidators]',
  providers: [{
    provide: NG_VALIDATORS,
    useExisting: IbanValidatorDirective,
    multi: true
  }]
})

export class IbanValidatorDirective implements Validator {

  validate(control: AbstractControl): object {
    const codeLengths = {
      AD: 24, AE: 23, AL: 28, 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, LC: 30, 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
    };
    if (control.value) {
      const iban = control.value.toUpperCase().replace(/[^A-Z0-9]/g, '');
      const code = iban.match(/^([A-Z]{2})(\d{2})([A-Z\d]+)$/);
      let digits: number;
      if (!code || iban.length !== codeLengths[code[1]]) {
        return { ibanValid: false };
      }
      digits = (code[3] + code[1] + code[2]).replace(/[A-Z]/g, (letter: string) => {
        return letter.charCodeAt(0) - 55;
      });
      return this.mod97(digits) === 1 ? null : { ibanValid: false };
    }
  }

  private mod97(digital: number | string) {
    digital = digital.toString();
    let checksum: number | string = digital.slice(0, 2);
    let fragment = '';
    for (let offset = 2; offset < digital.length; offset += 7) {
      fragment = checksum + digital.substring(offset, offset + 7);
      checksum = parseInt(fragment, 10) % 97;
    }
    return checksum;
  }
}

Then just use the directive on an input.

Sandwell
  • 1,451
  • 2
  • 16
  • 31
5

I know this is an old topic, but since it showed up on #1 place on Google, and since the solutions here aren't really refined I would like to show you my take on it:

export const validIban = (value) => {
    let rearrange =
        value.substring(4, value.length)
        + value.substring(0, 4);
    let alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".split('');
    let alphaMap = {};
    let number = [];

    alphabet.forEach((value, index) => {
        alphaMap[value] = index + 10;
    });

    rearrange.split('').forEach((value, index) => {
        number[index] = alphaMap[value] || value;
    });

    return modulo(number.join('').toString(), 97) === 1;
}

const modulo = (aNumStr, aDiv) => {
    var tmp = "";
    var i, r;
    for (i = 0; i < aNumStr.length; i++) {
        tmp += aNumStr.charAt(i);
        r = tmp % aDiv;
        tmp = r.toString();
    }
    return tmp / 1;
}

It works pretty well for me you can test it with validIban(testThis) / http://randomiban.com

knif3r
  • 439
  • 4
  • 13
3

Copy/Paste ready Typescript code for all countries mentioned here:

function validateIban(input: string) {
  const codeLengths = {
    AD: 24,
    AE: 23,
    AL: 28,
    AT: 20,
    AZ: 28,
    BA: 20,
    BE: 16,
    BG: 22,
    BH: 22,
    BI: 28,
    BR: 29,
    BY: 28,
    CH: 21,
    CR: 22,
    CY: 28,
    CZ: 24,
    DE: 22,
    DK: 18,
    DO: 28,
    EE: 20,
    EG: 29,
    ES: 24,
    LC: 32,
    FI: 18,
    FO: 18,
    FR: 27,
    GB: 22,
    GE: 22,
    GI: 23,
    GL: 18,
    GR: 27,
    GT: 28,
    HR: 21,
    HU: 28,
    IE: 22,
    IL: 23,
    IQ: 23,
    IS: 26,
    IT: 27,
    JO: 30,
    KW: 30,
    KZ: 20,
    LB: 28,
    LI: 21,
    LT: 20,
    LU: 20,
    LV: 21,
    LY: 25,
    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,
    SC: 31,
    SD: 18,
    SE: 24,
    SI: 19,
    SK: 24,
    SM: 27,
    ST: 25,
    SV: 28,
    TL: 23,
    TN: 24,
    TR: 26,
    UA: 29,
    VA: 22,
    VG: 24,
    XK: 20,
  };
  const iban = input.toUpperCase().replace(/[^A-Z0-9]/g, "");
  const code = iban.match(/^([A-Z]{2})(\d{2})([A-Z\d]+)$/);
  if (!code || iban.length !== (codeLengths as any)[code[1]]) {
    return false;
  }
  const digits = (code[3] + code[1] + code[2]).replace(/[A-Z]/g, (letter: string) => {
    return (letter.charCodeAt(0) - 55).toString();
  });
  return mod97(digits) === 1;
}

and the checksum:

function mod97(digital: number | string) {
  digital = digital.toString();
  let checksum: number | string = digital.slice(0, 2);
  let fragment = "";
  for (let offset = 2; offset < digital.length; offset += 7) {
    fragment = checksum + digital.substring(offset, offset + 7);
    checksum = parseInt(fragment, 10) % 97;
  }
  return checksum;
}
2

I wrote 2 regexes, the first one is for SEPA-only countries, the other is for all countries that support IBAN, as of 2021-10.

IBAN regex for within-SEPA area countries

The first one checks if an IBAN is valid within the SEPA area, the IBAN must belong to a country that is within the SEPA area. (it considers IBAN outside the SEPA area as invalid)

^(?:((?:IT|SM)\d{2}[A-Z]{1}\d{22})|(NL\d{2}[A-Z]{4}\d{10})|(LV\d{2}[A-Z]{4}\d{13})|((?:BG|GB|IE)\d{2}[A-Z]{4}\d{14})|(GI\d{2}[A-Z]{4}\d{15})|(RO\d{2}[A-Z]{4}\d{16})|(MT\d{2}[A-Z]{4}\d{23})|(NO\d{13})|((?:DK|FI)\d{16})|((?:SI)\d{17})|((?:AT|EE|LU|LT)\d{18})|((?:HR|LI|CH)\d{19})|((?:DE|VA)\d{20})|((?:AD|CZ|ES|MD|SK|SE)\d{22})|(PT\d{23})|((?:IS)\d{24})|((?:BE)\d{14})|((?:FR|MC|GR)\d{25})|((?:PL|HU|CY)\d{26}))$

Specifications:

Testing: https://regex101.com/r/oLRltj/1/

IBAN regex for any country

^(?:(?:CR|DE|ME|RS|VA)\d{20}|(?:CZ|ES|SE|SK|TN)\d{22}|(?:DK|FI|FO|GL|SD)\d{16}|(?:AT|BA|EE|LT|XK)\d{18}|(?:AE|IL|TL)\d{21}|(?:LY|PT|ST)\d{23}|(?:IT|SM)\d{2}[A-Z]\d{10}[A-Za-z0-9]{12}|(?:HU|PL)\d{26}|(?:AL|CY)\d{10}[A-Za-z0-9]{16}|(?:CH|LI)\d{7}[A-Za-z0-9]{12}|(?:FR|MC)\d{12}[A-Za-z0-9]{11}\d{2}|(?:GB|IE)\d{2}[A-Z]{4}\d{14}|(?:KZ|LU)\d{5}[A-Za-z0-9]{13}|(?:GI|IQ)\d{2}[A-Z]{4}[A-Za-z0-9]{15}|(?:PK|RO)\d{2}[A-Z]{4}[A-Za-z0-9]{16}|(?:PS|QA)\d{2}[A-Z]{4}[A-Za-z0-9]{21}|AD\d{10}[A-Za-z0-9]{12}|AZ\d{2}[A-Z]{4}[A-Za-z0-9]{20}|BE\d{14}|BG\d{2}[A-Z]{4}\d{6}[A-Za-z0-9]{8}|BH\d{2}[A-Z]{4}[A-Za-z0-9]{14}|BR\d{25}[A-Z][A-Za-z0-9]|BY\d{2}[A-Za-z0-9]{4}\d{4}[A-Za-z0-9]{16}|DO\d{2}[A-Za-z0-9]{4}\d{20}|EG\d{27}|GE\d{2}[A-Z]\d{16}|GT\d{2}[A-Za-z0-9]{24}|GR\d{9}[A-Za-z0-9]{16}|HR\d{19}|IS\d{24}|JO\d{2}[A-Z]{4}\d{4}[A-Za-z0-9]{18}|KW\d{2}[A-Z]{4}[A-Za-z0-9]{22}|LC\d{2}[A-Z]{4}[A-Za-z0-9]{24}|LB\d{6}[A-Za-z0-9]{20}|LV\d{2}[A-Z]{4}\d{13}|MD\d{2}[A-Za-z0-9]{20}|MK\d{5}[A-Za-z0-9]{10}\d{2}|MR\d{25}|MT\d{2}[A-Z]{4}\d{5}[A-Za-z0-9]{18}|MU\d{2}[A-Z]{4}\d{19}[A-Z]{3}|NL\d{2}[A-Z]{4}\d{10}|NO\d{13}|SA\d{4}[A-Za-z0-9]{18}|SC\d{2}[A-Z]{4}\d{20}[A-Z]{3}|SI\d{17}|SV\d{2}[A-Z]{4}\d{20}|TR\d{8}[A-Za-z0-9]{16}|UA\d{8}[A-Za-z0-9]{19}|VG\d{2}[A-Z]{4}\d{16}|GE\d{2}[A-Z]{2}\d{16})$

This second regex was taken from https://stackoverflow.com/a/69068004/2391795, and slightly modified to add proper support for GE (Georgia) country.

Testing: https://regex101.com/r/I3oiuo/1/

Vadorequest
  • 16,593
  • 24
  • 118
  • 215
  • Georgia's IBAN like `GE82517219517812137884` doesn't work. – Fred Hors Sep 14 '22 at 10:36
  • Egypt's ones like `EG7194356565899151369854425` doesn't either. You can generate random ones with: http://randomiban.com/?country=Egypt. – Fred Hors Sep 14 '22 at 10:40
  • I suggest if you find errors in those regexes, to use the https://regex101.com/r/I3oiuo/1/, duplicate it, and provide a fix (and tests!) for your country. This way it will keep becoming better and more accurate. If you do so, I'll use your newer regex101 link in the main answer, while keeping a history of changes for transparency. Thanks in advance! – Vadorequest Sep 18 '22 at 13:18
2

Just using this website api : go to https://api.ibanapi.com and get apk_key

iban    = SA4705000068203056854000
api_key = ea649b36c1d1648bf0f12df82a8da475e2ee3447
url     = 'https://api.ibanapi.com/v1/validate/'iban+'?api_key='+api_key+''

The IBAN Checker

  • Check the structure of the IBAN and prepare it for the validation engine
  • Make sure the country supports the IBAN as per the Swift public registery
  • Validate the IBAN using the check digits.
  • If the country level validation is supported, it will run through it
  • Get the bank information for this IBAN from our registery
KING-CODE
  • 39
  • 5
1

Based on the last SWIFT IBAN Registrar publication (June 2021), available here, I updated Rony Nasr's REGEX proposal. It includes 79 countries and checks country-specific IBAN structure and length. (It does NOT, however, do a checksum MOD97 verification).

/^(?:(?:CR|DE|ME|RS|VA)\d{20}|(?:CZ|ES|SE|SK|TN)\d{22}|(?:DK|FI|FO|GL|SD)\d{16}|(?:AT|BA|EE|LT|XK)\d{18}|(?:AE|IL|TL)\d{21}|(?:LY|PT|ST)\d{23}|(?:IT|SM)\d{2}[A-Z]\d{10}[A-Za-z0-9]{12}|(?:HU|PL)\d{26}|(?:AL|CY)\d{10}[A-Za-z0-9]{16}|(?:CH|LI)\d{7}[A-Za-z0-9]{12}|(?:FR|MC)\d{12}[A-Za-z0-9]{11}\d{2}|(?:GB|IE)\d{2}[A-Z]{4}\d{14}|(?:KZ|LU)\d{5}[A-Za-z0-9]{13}|(?:GI|IQ)\d{2}[A-Z]{4}[A-Za-z0-9]{15}|(?:PK|RO)\d{2}[A-Z]{4}[A-Za-z0-9]{16}|(?:PS|QA)\d{2}[A-Z]{4}[A-Za-z0-9]{21}|AD\d{10}[A-Za-z0-9]{12}|AZ\d{2}[A-Z]{4}[A-Za-z0-9]{20}|BE\d{14}|BG\d{2}[A-Z]{4}\d{6}[A-Za-z0-9]{8}|BH\d{2}[A-Z]{4}[A-Za-z0-9]{14}|BR\d{25}[A-Z][A-Za-z0-9]|BY\d{2}[A-Za-z0-9]{4}\d{4}[A-Za-z0-9]{16}|DO\d{2}[A-Za-z0-9]{4}\d{20}|EG\d{27}|GE\d{2}[A-Z]\d{16}|GT\d{2}[A-Za-z0-9]{24}|GR\d{9}[A-Za-z0-9]{16}|HR\d{19}|IS\d{24}|JO\d{2}[A-Z]{4}\d{4}[A-Za-z0-9]{18}|KW\d{2}[A-Z]{4}[A-Za-z0-9]{22}|LC\d{2}[A-Z]{4}[A-Za-z0-9]{24}|LB\d{6}[A-Za-z0-9]{20}|LV\d{2}[A-Z]{4}\d{13}|MD\d{2}[A-Za-z0-9]{20}|MK\d{5}[A-Za-z0-9]{10}\d{2}|MR\d{25}|MT\d{2}[A-Z]{4}\d{5}[A-Za-z0-9]{18}|MU\d{2}[A-Z]{4}\d{19}[A-Z]{3}|NL\d{2}[A-Z]{4}\d{10}|NO\d{13}|SA\d{4}[A-Za-z0-9]{18}|SC\d{2}[A-Z]{4}\d{20}[A-Z]{3}|SI\d{17}|SV\d{2}[A-Z]{4}\d{20}|TR\d{8}[A-Za-z0-9]{16}|UA\d{8}[A-Za-z0-9]{19}|VG\d{2}[A-Z]{4}\d{16}|GE\d{2}[A-Z]{2}\d{16})$/gm
Vadorequest
  • 16,593
  • 24
  • 118
  • 215
Numa Roch
  • 11
  • 1
-2

I have managed to create the below RegEx for quick validation based on the current format.

/^(?:(?:IT|SM)\d{2}[A-Z]\d{22}|NL\d{2}[A-Z]{4}\d{10}|RO\d{2}[A-Z]{4}\d[A-Z]\d{14}|LV\d{2}[A-Z]{4}\d{13}|FR\d{19}[A-Z]\d{5}|LI\d{17}[A-Z]{2}|MD\d{2}[A-Z]{2}\d{18}|(?:BG|GB|IE)\d{2}[A-Z]{4}\d{14}|BH\d{2}[A-Z]{4}\d{10}[A-Z]{2}\d{2}|GE\d{2}[A-Z]{2}\d{16}|GI\d{2}[A-Z]{4}\d{15}|BR\d{25}[A-Z]\d|MU\d{2}[A-Z]{4}\d{19}[A-Z]{3}|PS\d{2}[A-Z]{4}\d{21}|QA\d{2}[A-Z]{4}\d{14}[A-Z]{7}|(?:AZ|DO|GT)\d{2}[A-Z]{4}\d{20}|(?:BJ|ML|SN|CI)\d{2}[A-Z]\d{23}|(?:PK|VG)\d{2}[A-Z]{4}\d{16}|(?:KW|JO)\d{2}[A-Z]{4}\d{22}|MT\d{2}[A-Z]{4}\d{12}[A-Z]{7}\d{3}[A-Z]|NO\d{13}|(?:BE|BI)\d{14}|(?:DK|FI|GL|FO)\d{16}|(?:MK|SI)\d{17}|(?:BA|LT|AT|EE|KZ|LU|XK)\d{18}|(?:HR|CH|CR)\d{19}|(?:DE|ME|RS)\d{20}|(?:AE|IL|TL)\d{21}|(?:AD|CZ|ES|SA|DZ|SK|SE|TN)\d{22}|(?:PT|AO|CV|MZ)\d{23}|(?:IS|IR|TR)\d{24}|(?:MR|MC|BF|CM|GR|MG)\d{25}|(?:AL|DO|LB|PL|CY|HU)\d{26})$/i
Dharman
  • 30,962
  • 25
  • 85
  • 135
Rony Nasr
  • 1
  • 1