Question

As designing a new platform we tried to integrate the IBAN numbers. We have to make sure that the IBAN is validated and the IBAN stored to the database is always correct. So what would be a proper way to validate the number?

Was it helpful?

Solution

As the logic was explained in my other question, I've created a function myself. Based on the logic explained in the Wikipedia article find a proper function below. Country specific validation.

Algorithm and character lengths per country at https://en.wikipedia.org/wiki/International_Bank_Account_Number#Validating_the_IBAN.

function checkIBAN($iban)
{
    $iban = strtolower(str_replace(' ','',$iban));
    $Countries = array('al'=>28,'ad'=>24,'at'=>20,'az'=>28,'bh'=>22,'be'=>16,'ba'=>20,'br'=>29,'bg'=>22,'cr'=>21,'hr'=>21,'cy'=>28,'cz'=>24,'dk'=>18,'do'=>28,'ee'=>20,'fo'=>18,'fi'=>18,'fr'=>27,'ge'=>22,'de'=>22,'gi'=>23,'gr'=>27,'gl'=>18,'gt'=>28,'hu'=>28,'is'=>26,'ie'=>22,'il'=>23,'it'=>27,'jo'=>30,'kz'=>20,'kw'=>30,'lv'=>21,'lb'=>28,'li'=>21,'lt'=>20,'lu'=>20,'mk'=>19,'mt'=>31,'mr'=>27,'mu'=>30,'mc'=>27,'md'=>24,'me'=>22,'nl'=>18,'no'=>15,'pk'=>24,'ps'=>29,'pl'=>28,'pt'=>25,'qa'=>29,'ro'=>24,'sm'=>27,'sa'=>24,'rs'=>22,'sk'=>24,'si'=>19,'es'=>24,'se'=>24,'ch'=>21,'tn'=>24,'tr'=>26,'ae'=>23,'gb'=>22,'vg'=>24);
    $Chars = array('a'=>10,'b'=>11,'c'=>12,'d'=>13,'e'=>14,'f'=>15,'g'=>16,'h'=>17,'i'=>18,'j'=>19,'k'=>20,'l'=>21,'m'=>22,'n'=>23,'o'=>24,'p'=>25,'q'=>26,'r'=>27,'s'=>28,'t'=>29,'u'=>30,'v'=>31,'w'=>32,'x'=>33,'y'=>34,'z'=>35);

    if(strlen($iban) == $Countries[substr($iban,0,2)]){
                
        $MovedChar = substr($iban, 4).substr($iban,0,4);
        $MovedCharArray = str_split($MovedChar);
        $NewString = "";

        foreach($MovedCharArray AS $key => $value){
            if(!is_numeric($MovedCharArray[$key])){
                $MovedCharArray[$key] = $Chars[$MovedCharArray[$key]];
            }
            $NewString .= $MovedCharArray[$key];
        }
        
        if(bcmod($NewString, '97') == 1)
        {
            return true;
        }
    }
    return false;
}

OTHER TIPS

Slight modification of @PeterFox answer including support for bcmod() when bcmath is not available,

<?php

function isValidIBAN ($iban) {

  $iban = strtolower($iban);
  $Countries = array(
    'al'=>28,'ad'=>24,'at'=>20,'az'=>28,'bh'=>22,'be'=>16,'ba'=>20,'br'=>29,'bg'=>22,'cr'=>21,'hr'=>21,'cy'=>28,'cz'=>24,
    'dk'=>18,'do'=>28,'ee'=>20,'fo'=>18,'fi'=>18,'fr'=>27,'ge'=>22,'de'=>22,'gi'=>23,'gr'=>27,'gl'=>18,'gt'=>28,'hu'=>28,
    'is'=>26,'ie'=>22,'il'=>23,'it'=>27,'jo'=>30,'kz'=>20,'kw'=>30,'lv'=>21,'lb'=>28,'li'=>21,'lt'=>20,'lu'=>20,'mk'=>19,
    'mt'=>31,'mr'=>27,'mu'=>30,'mc'=>27,'md'=>24,'me'=>22,'nl'=>18,'no'=>15,'pk'=>24,'ps'=>29,'pl'=>28,'pt'=>25,'qa'=>29,
    'ro'=>24,'sm'=>27,'sa'=>24,'rs'=>22,'sk'=>24,'si'=>19,'es'=>24,'se'=>24,'ch'=>21,'tn'=>24,'tr'=>26,'ae'=>23,'gb'=>22,'vg'=>24
  );
  $Chars = array(
    'a'=>10,'b'=>11,'c'=>12,'d'=>13,'e'=>14,'f'=>15,'g'=>16,'h'=>17,'i'=>18,'j'=>19,'k'=>20,'l'=>21,'m'=>22,
    'n'=>23,'o'=>24,'p'=>25,'q'=>26,'r'=>27,'s'=>28,'t'=>29,'u'=>30,'v'=>31,'w'=>32,'x'=>33,'y'=>34,'z'=>35
  );

  if (strlen($iban) != $Countries[ substr($iban,0,2) ]) { return false; }

  $MovedChar = substr($iban, 4) . substr($iban,0,4);
  $MovedCharArray = str_split($MovedChar);
  $NewString = "";

  foreach ($MovedCharArray as $k => $v) {

    if ( !is_numeric($MovedCharArray[$k]) ) {
      $MovedCharArray[$k] = $Chars[$MovedCharArray[$k]];
    }
    $NewString .= $MovedCharArray[$k];
  }
  if (function_exists("bcmod")) { return bcmod($NewString, '97') == 1; }

  // http://au2.php.net/manual/en/function.bcmod.php#38474
  $x = $NewString; $y = "97";
  $take = 5; $mod = "";

  do {
    $a = (int)$mod . substr($x, 0, $take);
    $x = substr($x, $take);
    $mod = $a % $y;
  }
  while (strlen($x));

  return (int)$mod == 1;
}

The accepted answer is not the preferred way of validation. The specification dictates the following:

  1. Check that the total IBAN length is correct as per the country. If not, the IBAN is invalid
  2. Replace the two check digits by 00 (e.g. GB00 for the UK)
  3. Move the four initial characters to the end of the string
  4. Replace the letters in the string with digits, expanding the string as necessary, such that A or a = 10, B or b = 11, and Z or z = 35. Each alphabetic character is therefore replaced by 2 digits
  5. Convert the string to an integer (i.e. ignore leading zeroes)
  6. Calculate mod-97 of the new number, which results in the remainder
  7. Subtract the remainder from 98, and use the result for the two check digits. If the result is a single digit number, pad it with a leading 0 to make a two-digit number

I've written a class that validates, formats and parses strings according to the spec. Hope this helps some save the time required to roll their own.

The code can be found on GitHub here.

top rated function does NOT work.

Just try a string with '%' in it...

I'm using this one :

function checkIBAN($iban) {

// Normalize input (remove spaces and make upcase)
$iban = strtoupper(str_replace(' ', '', $iban));

if (preg_match('/^[A-Z]{2}[0-9]{2}[A-Z0-9]{1,30}$/', $iban)) {
    $country = substr($iban, 0, 2);
    $check = intval(substr($iban, 2, 2));
    $account = substr($iban, 4);

    // To numeric representation
    $search = range('A','Z');
    foreach (range(10,35) as $tmp)
        $replace[]=strval($tmp);
    $numstr=str_replace($search, $replace, $account.$country.'00');

    // Calculate checksum
    $checksum = intval(substr($numstr, 0, 1));
    for ($pos = 1; $pos < strlen($numstr); $pos++) {
        $checksum *= 10;
        $checksum += intval(substr($numstr, $pos,1));
        $checksum %= 97;
    }

    return ((98-$checksum) == $check);
} else
    return false;
}

I found this solution in cakephp 3.7 validation class. Plain beautiful php realization.

/**
 * Check that the input value has a valid International Bank Account Number IBAN syntax
 * Requirements are uppercase, no whitespaces, max length 34, country code and checksum exist at right spots,
 * body matches against checksum via Mod97-10 algorithm
 *
 * @param string $check The value to check
 *
 * @return bool Success
 */
public static function iban($check)
{
    if (!preg_match('/^[A-Z]{2}[0-9]{2}[A-Z0-9]{1,30}$/', $check)) {
        return false;
    }

    $country = substr($check, 0, 2);
    $checkInt = intval(substr($check, 2, 2));
    $account = substr($check, 4);
    $search = range('A', 'Z');
    $replace = [];
    foreach (range(10, 35) as $tmp) {
        $replace[] = strval($tmp);
    }
    $numStr = str_replace($search, $replace, $account . $country . '00');
    $checksum = intval(substr($numStr, 0, 1));
    $numStrLength = strlen($numStr);
    for ($pos = 1; $pos < $numStrLength; $pos++) {
        $checksum *= 10;
        $checksum += intval(substr($numStr, $pos, 1));
        $checksum %= 97;
    }

    return ((98 - $checksum) === $checkInt);
}

This function check the IBAN and need GMP activate http://php.net/manual/en/book.gmp.php.

function checkIban($string){
    $to_check = substr($string, 4).substr($string, 0,4);
    $converted = '';
    for ($i = 0; $i < strlen($to_check); $i++){
        $char = strtoupper($to_check[$i]);
        if(preg_match('/[0-9A-Z]/',$char)){
            if(!preg_match('/\d/',$char)){
                $char = ord($char)-55;
            }
            $converted .= $char;
        }
    }

    // prevent: "gmp_mod() $num1 is not an integer string" error
    $converted = ltrim($converted, '0');

    return strlen($converted) && gmp_strval(gmp_mod($converted, '97')) == 1;
}

enjoy !

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top