Yet another IBAN Checker

Started by Roland Stowasser, February 29, 2024, 01:29:42 AM

Previous topic - Next topic

0 Members and 1 Guest are viewing this topic.

Roland Stowasser

At Theo's subforum I found the Powerbasic source code for an IBAN checker. I also found some example code with Rosetta.org and adopted the task a little bit for Oxygenbasic. The challenge was to move the first 4 digits to the end of the code, change existing letters A - Z to numbers 10 - 35 and apply a mod 97 calculation with the resulting number. If the result is 1, then the code may be a valid IBAN.

I used a more current list for the country codes. The program can be executed just in time  or compiled to  a 32-bit or to a 64-bit exe file.

IBANcheck.o2bas:
'Identify an IBAN
'see: https://rosettacode.org/wiki/IBAN
'see: https://en.wikipedia.org/wiki/International_Bank_Account_Number

$ filename "IBANcheck.exe"

'uses rtl32
'uses rtl64

uses console

function replace(string t,w,r) as string  'ParseUtil.inc
========================================
/**
  substitutes each w with r in t

  @param t is the main string where substitutions are made
  @param w is the key string to be replaced
  @param r is the replacement string
  @return the main string with replacements
**/
int a,b,lw,lr
string s=t
'
lw=len(w)
lr=len(r)
a=1
do
  a=instr(a,s,w)
  if a=0 then exit do
  s=left(s,a-1)+r+mid(s,a+lw)
  a+=lr
loop
return s
end function

function mod97(string s) as uint
--------------------------------
'Special division of a big number, a bit like in school
'with paper and pencil, determine the remainder
  uint start = 10
  uint r = mod(val(left(s, 9)), 97)   
  while start <= len(s)
    r = mod(val(str(r) & mid(s, start, 7)), 97)
    start += 7
  wend
  if r != 1 then print "Remainder = " r ": "
  return r
end function


' List updated to release 96, February 2024, of IBAN Registry for ISO 13616 (87 countries)
string countryCodes =
  "AD24001 AE23002 AL28003 AT20004 AZ28005 BA20006 BE16007 BG22008 BH22009 BI27010 " +
  "BR29011 BY28012 CH21013 CR22014 CY28015 CZ24016 DE22017 DJ27018 DK18019 DO28020 " +
  "EE20021 EG29022 ES24023 FI18024 FK18025 FO18026 FR27027 GB22028 GE22029 GI23030 " +
  "GL18031 GR27032 GT28033 HR21034 HU28035 IE22036 IL23037 IQ23038 IS26039 IT27040 " +
  "JO30041 KW30042 KZ20043 LB28044 LC32045 LI21046 LT20047 LU20048 LV21049 LY25050 " +
  "MC27051 MD24052 ME22053 MK19054 MN20055 MR27056 MT31057 MU30058 NI28059 NL18060 " +
  "NO15061 OM23062 PK24063 PL28064 PS29065 PT25066 QA29067 RO24068 RS22069 RU33070 " +
  "SA24071 SC31072 SD18073 SE24074 SI19075 SK24076 SM27077 SO23078 ST25079 SV28080 " +
  "TL23081 TN24082 TR26083 UA29084 VA22085 VG24086 XK20087"

dim string countries(87)
countries = {
  "Andorra","United Arab Emirates","Albania","Austria","Azerbaijan","Bosnia and Herzegovina","Belgium","Bulgaria","Bahrein","Burundi",
  "Brazil","Republic of Belarus","Switzerland","Costa Rica","Cyprus","Czech Republic","Germany","Djibouti","Denmark","Dominican Republic",
  "Estonia","Egypt","Spain","Finnland","Falkland Islands","Faroe Islands","France","United Kingdom","Georgia","Gibraltar",
  "Greenland","Greece","Guatemala","Croatia","Hungary","Ireland","Israel","Iraq","Iceland","Italy",
  "Jordan","Kuwait","Kazakhstan","Lebanon","Saint Lucia","Liechtenstein","Lithuania","Luxembourg","Latvia","Libya",
  "Monaco","Moldova","Montenegro","Macedonia","Mongolia","Mauritania","Malta","Mauritius","Nicaragua","Netherlands",
  "Norway","Oman","Pakistan","Poland","Palestine, State of","Portugal","Qatar","Romania","Serbia","Russia",
  "Saudi Arabia","Seychelles","Sudan","Sweden","Slovenia","Slovakia","San Marino","Somalia","Sao Tome and Principe","El Salvador",
  "Timor-Leste","Tunisia","Turkey","Ukraine","Vatican City State","Virgin Islands","Kosovo"
}

function findCountryCode(string cc) as int
  return instr(countryCodes, ucase(cc))
end function

string country

function validateIBAN(string IBAN) as bool
  int pos, idx
  string s_IBAN = ucase(IBAN)
  string n

  'remove spaces and hyphen
  s_IBAN = replace (s_IBAN, " ","")
  s_IBAN = replace (s_IBAN, "-","")

  pos = findCountryCode(left(s_IBAN,2) + str(len(s_IBAN)))
  if pos = 0 then
    printl "Either Country code does not exist or length of code not correct!"
    printl "IBAN is not valid!" : printl
    return false
  else
    idx=val(mid(countryCodes,pos+4,3))
    country = countries(idx)     
  end if
   
  ' move first 4 characters to end
  s_IBAN = mid(s_IBAN, 5) + left(s_IBAN, 4)
   
  ' replace A to Z with numbers 10 To 35
  n = ""
  int i
  for i = 1 to len(s_IBAN) 
    if mid(s_IBAN,i,1) >= "0" and mid(s_IBAN,i,1) <= "9" then
      'is number
      n &= mid(s_IBAN,i,1)
    elseif mid(s_IBAN,i,1) >= "A" and mid(s_IBAN,i,1) <= "Z" then     
      'convert to number 10..35
      n &= str(asc(mid(s_IBAN,i,1))-55)
    else
      printl "something wrong with IBAN number"
      printl "IBAN is not valid!" : printl
      return false
    end If
  next 
  'printl "IBAN converted to number :"
  'printl n

  if mod97(n) != 1 then
    print "Remainder not 1 - "
    printl "IBAN is not valid!" : printl
    return false
  end if

  print "Country: " Country " - "
  printl "IBAN may be valid." : printl
  return true   
end function

--------------------------------------------------------------
SetConsoleTitle "IBAN Checker"
bool iv
string IBAN

printl "This little program can be helpful for checking an IBAN "
printl "(International Bank Account Number) for errors e.g.:"
printl
IBAN = "GB82 WEST 1234 5698 7654 32" : printl IBAN
iv = validateIBAN(IBAN)
IBAN = "GB82 TEST 1234 5698 7654 32" : printl IBAN
iv = validateIBAN(IBAN)
IBAN = "GB81 WEST 1234 5698 7654 3" : printl IBAN
iv = validateIBAN(IBAN)
IBAN = "SA03 8000 0000 6080 1016 7519" : printl IBAN
iv = validateIBAN(IBAN)
IBAN ="CH93 0076 2011 6238 5295 7" : printl IBAN
iv = validateIBAN(IBAN)
--------------------------------------------------------------

string a
start:

printl : print "Would you like to check another IBAN? (Y/N) "
a = ucase(input())
if left(a,1) != "Y" then goto fin

cls
printl "Please enter an IBAN:"
IBAN = rtrim ltrim input()
if len(IBAN) > 3 then
  iv = validateIBAN(IBAN)
end if 

goto start

fin:

  •  

Charles Pegge

Thanks Roland,

It works with my current account. I'll include it as demos\DataProcessing\IBANchecker.o2bas.

Theo Gottwald

#2
The provided O2-Basic code seems to be well-structured and should work as intended for validating IBAN numbers. However, there are a few improvements that can be suggested:

In the replace function, you can use the replace$ intrinsic function available in O2-Basic, which simplifies the implementation:

function replace(string t, w, r) as string
  return replace$(t, w, r)
end function
In the mod97 function, you can optimize the loop by processing the string in chunks of 9 characters instead of 7:

function mod97(string s) as uint
  uint start = 10
  uint r = mod(val(left(s, 9)), 97)
  while start <= len(s)
    r = mod(val(str(r) & mid(s, start, 9)), 97)
    start += 9
  wend
  if r != 1 then print "Remainder = " r ": "
  return r
end function
Instead of using a single string variable countryCodes to store country codes and their corresponding check digits, you can use a dictionary (if available in O2-Basic) or an array to make the code cleaner and easier to maintain:

dim uint checkDigits(87)
checkDigits = [24, 23, 28, ..., 20, 23, 26] ' Add the rest of the check digits here

function findCountryCode(string cc) as int
  int idx
  for idx = 0 to 86
    if ucase(left(cc, 2)) = ucase(left(countries(idx), 2)) then
      return checkDigits(idx)
    end if
  next idx
  return 0
end function
In the validateIBAN function, you can remove the country variable since it is not used anywhere else in the code.

Consider adding error handling for unexpected input formats or invalid country codes.

Finally, it's a good practice to add comments to your code to make it easier for others (and yourself) to understand what each part of the code does.

PS: Says "MISTRAL LARGE" I can not comment on that.

Roland Stowasser

Hi Theo,

thank you for your comments. This was my first little project in a while, I am glad I did not forget everything about O2. I still have to mention the following:
replace$ does not exist as a built-in function in Oxygenbasic (I believe). I derived the mod97 function from Wikipedia, I found similar constructions in Rosetta Code. But piece-wise calculation D mod 97 can be done in many ways. Although not required for the task I added the names of the countries, because I wanted to show the country of the code in the result.

The execution speed could be increased of course (just by using strptr and byte array), but for this small app with a specific purpose, that does not make sense to me.
  •  

Theo Gottwald

You code is fine, i was just testing MISTRAL on it.