

 This page uses content from Wikipedia. The original article was at IBAN. The list of authors can be seen in the page history. As with Rosetta Code, the text of Wikipedia is available under the GNU FDL. (See links for details on variance)



The   International Bank Account Number (IBAN)   is an internationally agreed means of identifying bank accounts across national borders with a reduced risk of propagating transcription errors.

The IBAN consists of up to 34 alphanumeric characters:

   first the two-letter ISO 3166-1 alpha-2 country code, 
   then two check digits, and 
   finally a country-specific Basic Bank Account Number (BBAN). 


The check digits enable a sanity check of the bank account number to confirm its integrity even before submitting a transaction.



Task

Validate the following fictitious IBAN:    GB82 WEST 1234 5698 7654 32 



Details of the algorithm can be found on the Wikipedia page.




 
#load "str.cma"
#load "nums.cma"  (* for module Big_int *)
 
 
(* Countries and length of their IBAN. *)
(* Taken from https://en.wikipedia.org/wiki/International_Bank_Account_Number#IBAN_formats_by_country *)
let countries = [
  ("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); ("TL", 23); ("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); ("XK", 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); ("DZ", 24); ("AO", 25); ("BJ", 28); ("BF", 27);
  ("BI", 16); ("CM", 27); ("CV", 25); ("IR", 26); ("CI", 28); ("MG", 27);
  ("ML", 28); ("MZ", 25); ("SN", 28); ("UA", 29)
]
(* Put the countries in a Hashtbl for faster search... *)
let tbl_countries =
  let htbl = Hashtbl.create (List.length countries) in
  let _ = List.iter (fun (k, v) -> Hashtbl.add htbl k v) countries in
  htbl
 
 
(* Delete spaces and put all letters in upper case. *)
let clean_iban iban =
  Str.global_replace (Str.regexp " +") "" iban
  |> String.uppercase_ascii
 
 
(* Each country has an IBAN with a specific length. *)
let check_length ib =
  let iso_length = List.hd countries |> fst |> String.length in
  let country_code = String.sub ib 0 iso_length in
  try
    Hashtbl.find tbl_countries country_code = String.length ib
  with
    Not_found -> false
 
 
(* Convert a string into a list of chars. *)
let charlist_of_string s = 
  let l = String.length s in
  let rec doloop i =
    if i >= l then []
    else s.[i] :: doloop (i + 1)
  in
  doloop 0
 
 
(* Letters are associated to values: A=10, B=11, ..., Z=35. *)
let val_of_char c =
  match c with
  | '0' .. '9' -> int_of_char c - int_of_char '0'
  | 'A' .. 'Z' -> int_of_char c - int_of_char 'A' + 10
  | _ -> failwith (Printf.sprintf "Character not allowed: %c" c)
 
 
(* Compute the mod-97 value and check it is equal to 1. *)
let check_mod97 ib =
  let l = String.length ib
  and taken = 4 in
  let prefix = String.sub ib 0 taken
  and rest = String.sub ib taken (l - taken) in
  let newval = rest ^ prefix (* move the 4 initial characters to the end of the string *)
            |> charlist_of_string  (* convert the string into a list of chars *)
            |> List.map val_of_char  (* convert each char into its integer value *)
            |> List.map string_of_int  (* convert the integers into strings... *)
            |> List.fold_left (^) "" in  (* ...and concatenate said strings *)
  (* Now compute the mod-97 using the Big Integers provided by OCaml, and
   * compare the result to 1. *)
  Big_int.eq_big_int
    (Big_int.mod_big_int (Big_int.big_int_of_string newval)
                         (Big_int.big_int_of_int 97))
    (Big_int.big_int_of_int 1)
 
 
(* Do the validation as described in the Wikipedia article at
 * https://en.wikipedia.org/wiki/International_Bank_Account_Number#Validating_the_IBAN *)
let validate iban =
  let ib = clean_iban iban in
  check_length ib && check_mod97 ib
 
 
let () =
  let ibans = [
    ("GB82 WEST 1234 5698 7654 32", true);
    ("GB82 TEST 1234 5698 7654 32", false);
    ("GB81 WEST 1234 5698 7654 32", false);
    ("GB82 WEST 1234 5698 7654 3", false);
    ("SA03 8000 0000 6080 1016 7519", true);
    ("CH93 0076 2011 6238 5295 7", true);
    ("\"Completely incorrect iban\"", false)
  ] in
  let testit (ib, exp) =
    let res = validate ib in
    Printf.printf "%s is %svalid. Expected %b [%s]\n"
                  ib (if res then "" else "not ")
                  exp (if res = exp then "PASS" else "FAIL")
  in 
  List.iter (fun pair -> testit pair) ibans
 