php-iban.php 50 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336
  1. <?php
  2. # PHP IBAN - http://github.com/globalcitizen/php-iban - LGPLv3
  3. # Global flag by request
  4. $__disable_iiban_gmp_extension=false;
  5. # Verify an IBAN number.
  6. # If $machine_format_only, do not tolerate unclean (eg. spaces, dashes, leading 'IBAN ' or 'IIBAN ', lower case) input.
  7. # (Otherwise, input can be printed 'IIBAN xx xx xx...' or 'IBAN xx xx xx...' or machine 'xxxxx' format.)
  8. # Returns true or false.
  9. function verify_iban($iban,$machine_format_only=false) {
  10. # First convert to machine format.
  11. if(!$machine_format_only) { $iban = iban_to_machine_format($iban); }
  12. # Get country of IBAN
  13. $country = iban_get_country_part($iban);
  14. # Test length of IBAN
  15. if(strlen($iban)!=iban_country_get_iban_length($country)) { return false; }
  16. # Get checksum of IBAN
  17. $checksum = iban_get_checksum_part($iban);
  18. # Get country-specific IBAN format regex
  19. $regex = '/'.iban_country_get_iban_format_regex($country).'/';
  20. # Check regex
  21. if(preg_match($regex,$iban)) {
  22. # Regex passed, check checksum
  23. if(!iban_verify_checksum($iban)) {
  24. return false;
  25. }
  26. }
  27. else {
  28. return false;
  29. }
  30. # Otherwise it 'could' exist
  31. return true;
  32. }
  33. # Convert an IBAN to machine format. To do this, we
  34. # remove IBAN from the start, if present, and remove
  35. # non basic roman letter / digit characters
  36. function iban_to_machine_format($iban) {
  37. # Uppercase and trim spaces from left
  38. $iban = ltrim(strtoupper($iban));
  39. # Remove IIBAN or IBAN from start of string, if present
  40. $iban = preg_replace('/^I?IBAN/','',$iban);
  41. # Remove all non basic roman letter / digit characters
  42. $iban = preg_replace('/[^a-zA-Z0-9]/','',$iban);
  43. return $iban;
  44. }
  45. # Convert an IBAN to human format. To do this, we
  46. # simply insert spaces right now, as per the ECBS
  47. # (European Committee for Banking Standards)
  48. # recommendations available at:
  49. # http://www.europeanpaymentscouncil.eu/knowledge_bank_download.cfm?file=ECBS%20standard%20implementation%20guidelines%20SIG203V3.2.pdf
  50. function iban_to_human_format($iban) {
  51. # Remove all spaces
  52. $iban = str_replace(' ','',$iban);
  53. # Add spaces every four characters
  54. return wordwrap($iban,4,' ',true);
  55. }
  56. # Convert an IBAN to obfuscated presentation. To do this, we
  57. # replace the checksum and all subsequent characters with an
  58. # asterisk, except for the final four characters, and then
  59. # return in human format, ie.
  60. # HU69107000246667654851100005 -> HU** **** **** **** **** **** 0005
  61. #
  62. # We avoid the checksum as it may be used to infer the rest
  63. # of the IBAN in cases where the country has few valid banks
  64. # and branches, or other information about the account such
  65. # as bank, branch, or date issued is known (where a sequential
  66. # issuance scheme is in use).
  67. #
  68. # Note that output of this function should be presented with
  69. # other information to a user, such as the date last used or
  70. # the date added to their account, in order to better facilitate
  71. # unambiguous relative identification.
  72. function iban_to_obfuscated_format($iban) {
  73. $iban = iban_to_machine_format($iban);
  74. $tr = substr($iban,0,2);
  75. for($i=2;$i<strlen($iban)-4;$i++) {
  76. $tr .= '*';
  77. }
  78. $tr .= substr($iban,strlen($iban)-4);
  79. return iban_to_human_format($tr);
  80. }
  81. # Get the country part from an IBAN
  82. function iban_get_country_part($iban) {
  83. $iban = iban_to_machine_format($iban);
  84. return substr($iban,0,2);
  85. }
  86. # Get the checksum part from an IBAN
  87. function iban_get_checksum_part($iban) {
  88. $iban = iban_to_machine_format($iban);
  89. return substr($iban,2,2);
  90. }
  91. # Get the BBAN part from an IBAN
  92. function iban_get_bban_part($iban) {
  93. $iban = iban_to_machine_format($iban);
  94. return substr($iban,4);
  95. }
  96. # Check the checksum of an IBAN - code modified from Validate_Finance PEAR class
  97. function iban_verify_checksum($iban) {
  98. # convert to machine format
  99. $iban = iban_to_machine_format($iban);
  100. # move first 4 chars (countrycode and checksum) to the end of the string
  101. $tempiban = substr($iban, 4).substr($iban, 0, 4);
  102. # subsitutute chars
  103. $tempiban = iban_checksum_string_replace($tempiban);
  104. # mod97-10
  105. $result = iban_mod97_10($tempiban);
  106. # checkvalue of 1 indicates correct IBAN checksum
  107. if ($result != 1) {
  108. return false;
  109. }
  110. return true;
  111. }
  112. # Find the correct checksum for an IBAN
  113. # $iban The IBAN whose checksum should be calculated
  114. function iban_find_checksum($iban) {
  115. $iban = iban_to_machine_format($iban);
  116. # move first 4 chars to right
  117. $left = substr($iban,0,2) . '00'; # but set right-most 2 (checksum) to '00'
  118. $right = substr($iban,4);
  119. # glue back together
  120. $tmp = $right . $left;
  121. # convert letters using conversion table
  122. $tmp = iban_checksum_string_replace($tmp);
  123. # get mod97-10 output
  124. $checksum = iban_mod97_10_checksum($tmp);
  125. # return 98 minus the mod97-10 output, left zero padded to two digits
  126. return str_pad((98-$checksum),2,'0',STR_PAD_LEFT);
  127. }
  128. # Set the correct checksum for an IBAN
  129. # $iban IBAN whose checksum should be set
  130. function iban_set_checksum($iban) {
  131. $iban = iban_to_machine_format($iban);
  132. return substr($iban,0,2) . iban_find_checksum($iban) . substr($iban,4);
  133. }
  134. # Character substitution required for IBAN MOD97-10 checksum validation/generation
  135. # $s Input string (IBAN)
  136. function iban_checksum_string_replace($s) {
  137. $iban_replace_chars = range('A','Z');
  138. foreach (range(10,35) as $tempvalue) { $iban_replace_values[]=strval($tempvalue); }
  139. return str_replace($iban_replace_chars,$iban_replace_values,$s);
  140. }
  141. # Same as below but actually returns resulting checksum
  142. function iban_mod97_10_checksum($numeric_representation) {
  143. $checksum = intval(substr($numeric_representation, 0, 1));
  144. for ($position = 1; $position < strlen($numeric_representation); $position++) {
  145. $checksum *= 10;
  146. $checksum += intval(substr($numeric_representation,$position,1));
  147. $checksum %= 97;
  148. }
  149. return $checksum;
  150. }
  151. # Perform MOD97-10 checksum calculation ('Germanic-level efficiency' version - thanks Chris!)
  152. function iban_mod97_10($numeric_representation) {
  153. global $__disable_iiban_gmp_extension;
  154. # prefer php5 gmp extension if available
  155. if(!($__disable_iiban_gmp_extension) && function_exists('gmp_intval') && $numeric_representation!='') { return gmp_intval(gmp_mod(gmp_init($numeric_representation, 10),'97')) === 1; }
  156. /*
  157. # old manual processing (~16x slower)
  158. $checksum = intval(substr($numeric_representation, 0, 1));
  159. for ($position = 1; $position < strlen($numeric_representation); $position++) {
  160. $checksum *= 10;
  161. $checksum += intval(substr($numeric_representation,$position,1));
  162. $checksum %= 97;
  163. }
  164. return $checksum;
  165. */
  166. # new manual processing (~3x slower)
  167. $length = strlen($numeric_representation);
  168. $rest = "";
  169. $position = 0;
  170. while ($position < $length) {
  171. $value = 9-strlen($rest);
  172. $n = $rest . substr($numeric_representation,$position,$value);
  173. $rest = $n % 97;
  174. $position = $position + $value;
  175. }
  176. return ($rest === 1);
  177. }
  178. # Get an array of all the parts from an IBAN
  179. function iban_get_parts($iban) {
  180. return array(
  181. 'checksum' => iban_get_checksum_part($iban),
  182. 'bban' => iban_get_bban_part($iban),
  183. 'bank' => iban_get_bank_part($iban),
  184. 'country' => iban_get_country_part($iban),
  185. 'branch' => iban_get_branch_part($iban),
  186. 'account' => iban_get_account_part($iban),
  187. 'nationalchecksum' => iban_get_nationalchecksum_part($iban)
  188. );
  189. }
  190. # Get the Bank ID (institution code) from an IBAN
  191. function iban_get_bank_part($iban) {
  192. $iban = iban_to_machine_format($iban);
  193. $country = iban_get_country_part($iban);
  194. $start = iban_country_get_bankid_start_offset($country);
  195. $stop = iban_country_get_bankid_stop_offset($country);
  196. if($start!=''&&$stop!='') {
  197. $bban = iban_get_bban_part($iban);
  198. return substr($bban,$start,($stop-$start+1));
  199. }
  200. return '';
  201. }
  202. # Get the Branch ID (sort code) from an IBAN
  203. function iban_get_branch_part($iban) {
  204. $iban = iban_to_machine_format($iban);
  205. $country = iban_get_country_part($iban);
  206. $start = iban_country_get_branchid_start_offset($country);
  207. $stop = iban_country_get_branchid_stop_offset($country);
  208. if($start!=''&&$stop!='') {
  209. $bban = iban_get_bban_part($iban);
  210. return substr($bban,$start,($stop-$start+1));
  211. }
  212. return '';
  213. }
  214. # Get the (branch-local) account ID from an IBAN
  215. function iban_get_account_part($iban) {
  216. $iban = iban_to_machine_format($iban);
  217. $country = iban_get_country_part($iban);
  218. $start = iban_country_get_branchid_stop_offset($country);
  219. if($start=='') {
  220. $start = iban_country_get_bankid_stop_offset($country);
  221. }
  222. if($start!='') {
  223. $bban = iban_get_bban_part($iban);
  224. return substr($bban,$start+1);
  225. }
  226. return '';
  227. }
  228. # Get the national checksum part from an IBAN
  229. function iban_get_nationalchecksum_part($iban) {
  230. $iban = iban_to_machine_format($iban);
  231. $country = iban_get_country_part($iban);
  232. $start = iban_country_get_nationalchecksum_start_offset($country);
  233. if($start == '') { return ''; }
  234. $stop = iban_country_get_nationalchecksum_stop_offset($country);
  235. if($stop == '') { return ''; }
  236. $bban = iban_get_bban_part($iban);
  237. return substr($bban,$start,($stop-$start+1));
  238. }
  239. # Get the name of an IBAN country
  240. function iban_country_get_country_name($iban_country) {
  241. return _iban_country_get_info($iban_country,'country_name');
  242. }
  243. # Get the domestic example for an IBAN country
  244. function iban_country_get_domestic_example($iban_country) {
  245. return _iban_country_get_info($iban_country,'domestic_example');
  246. }
  247. # Get the BBAN example for an IBAN country
  248. function iban_country_get_bban_example($iban_country) {
  249. return _iban_country_get_info($iban_country,'bban_example');
  250. }
  251. # Get the BBAN format (in SWIFT format) for an IBAN country
  252. function iban_country_get_bban_format_swift($iban_country) {
  253. return _iban_country_get_info($iban_country,'bban_format_swift');
  254. }
  255. # Get the BBAN format (as a regular expression) for an IBAN country
  256. function iban_country_get_bban_format_regex($iban_country) {
  257. return _iban_country_get_info($iban_country,'bban_format_regex');
  258. }
  259. # Get the BBAN length for an IBAN country
  260. function iban_country_get_bban_length($iban_country) {
  261. return _iban_country_get_info($iban_country,'bban_length');
  262. }
  263. # Get the IBAN example for an IBAN country
  264. function iban_country_get_iban_example($iban_country) {
  265. return _iban_country_get_info($iban_country,'iban_example');
  266. }
  267. # Get the IBAN format (in SWIFT format) for an IBAN country
  268. function iban_country_get_iban_format_swift($iban_country) {
  269. return _iban_country_get_info($iban_country,'iban_format_swift');
  270. }
  271. # Get the IBAN format (as a regular expression) for an IBAN country
  272. function iban_country_get_iban_format_regex($iban_country) {
  273. return _iban_country_get_info($iban_country,'iban_format_regex');
  274. }
  275. # Get the IBAN length for an IBAN country
  276. function iban_country_get_iban_length($iban_country) {
  277. return _iban_country_get_info($iban_country,'iban_length');
  278. }
  279. # Get the BBAN Bank ID start offset for an IBAN country
  280. function iban_country_get_bankid_start_offset($iban_country) {
  281. return _iban_country_get_info($iban_country,'bban_bankid_start_offset');
  282. }
  283. # Get the BBAN Bank ID stop offset for an IBAN country
  284. function iban_country_get_bankid_stop_offset($iban_country) {
  285. return _iban_country_get_info($iban_country,'bban_bankid_stop_offset');
  286. }
  287. # Get the BBAN Branch ID start offset for an IBAN country
  288. function iban_country_get_branchid_start_offset($iban_country) {
  289. return _iban_country_get_info($iban_country,'bban_branchid_start_offset');
  290. }
  291. # Get the BBAN Branch ID stop offset for an IBAN country
  292. function iban_country_get_branchid_stop_offset($iban_country) {
  293. return _iban_country_get_info($iban_country,'bban_branchid_stop_offset');
  294. }
  295. # Get the BBAN (national) checksum start offset for an IBAN country
  296. # Returns '' when (often) not present)
  297. function iban_country_get_nationalchecksum_start_offset($iban_country) {
  298. return _iban_country_get_info($iban_country,'bban_checksum_start_offset');
  299. }
  300. # Get the BBAN (national) checksum stop offset for an IBAN country
  301. # Returns '' when (often) not present)
  302. function iban_country_get_nationalchecksum_stop_offset($iban_country) {
  303. return _iban_country_get_info($iban_country,'bban_checksum_stop_offset');
  304. }
  305. # Get the registry edition for an IBAN country
  306. function iban_country_get_registry_edition($iban_country) {
  307. return _iban_country_get_info($iban_country,'registry_edition');
  308. }
  309. # Is the IBAN country one official issued by SWIFT?
  310. function iban_country_get_country_swift_official($iban_country) {
  311. return _iban_country_get_info($iban_country,'country_swift_official');
  312. }
  313. # Is the IBAN country a SEPA member?
  314. function iban_country_is_sepa($iban_country) {
  315. return _iban_country_get_info($iban_country,'country_sepa');
  316. }
  317. # Get the IANA code of an IBAN country
  318. function iban_country_get_iana($iban_country) {
  319. return _iban_country_get_info($iban_country,'country_iana');
  320. }
  321. # Get the ISO3166-1 alpha-2 code of an IBAN country
  322. function iban_country_get_iso3166($iban_country) {
  323. return _iban_country_get_info($iban_country,'country_iso3166');
  324. }
  325. # Get the parent registrar IBAN country of an IBAN country
  326. function iban_country_get_parent_registrar($iban_country) {
  327. return _iban_country_get_info($iban_country,'parent_registrar');
  328. }
  329. # Get the official currency of an IBAN country as an ISO4217 alpha code
  330. # (Note: Returns '' if there is no official currency, eg. for AA (IIBAN))
  331. function iban_country_get_currency_iso4217($iban_country) {
  332. return _iban_country_get_info($iban_country,'currency_iso4217');
  333. }
  334. # Get the URL of an IBAN country's central bank
  335. # (Note: Returns '' if there is no central bank. Also, note that
  336. # sometimes multiple countries share one central bank)
  337. function iban_country_get_central_bank_url($iban_country) {
  338. $result = _iban_country_get_info($iban_country,'central_bank_url');
  339. if($result!='') { $result = 'http://' . $result . '/'; }
  340. return $result;
  341. }
  342. # Get the name of an IBAN country's central bank
  343. # (Note: Returns '' if there is no central bank. Also, note that
  344. # sometimes multiple countries share one central bank)
  345. function iban_country_get_central_bank_name($iban_country) {
  346. return _iban_country_get_info($iban_country,'central_bank_name');
  347. }
  348. # Get the list of all IBAN countries
  349. function iban_countries() {
  350. _iban_load_registry();
  351. global $_iban_registry;
  352. return array_keys($_iban_registry);
  353. }
  354. # Get the membership of an IBAN country
  355. # (Note: Possible Values eu_member, efta_member, other_member, non_member)
  356. function iban_country_get_membership($iban_country) {
  357. return _iban_country_get_info($iban_country,'membership');
  358. }
  359. # Get the membership of an IBAN country
  360. # (Note: Possible Values eu_member, efta_member, other_member, non_member)
  361. function iban_country_get_is_eu_member($iban_country) {
  362. $membership = _iban_country_get_info($iban_country,'membership');
  363. if ($membership === 'eu_member') {
  364. $result = true;
  365. } else {
  366. $result = false;
  367. }
  368. return $result;
  369. }
  370. # Given an incorrect IBAN, return an array of zero or more checksum-valid
  371. # suggestions for what the user might have meant, based upon common
  372. # mistranscriptions.
  373. # IDEAS:
  374. # - length correction via adding/removing leading zeros from any single component
  375. # - overlength correction via dropping final digit(s)
  376. # - national checksum algorithm checks (apply same testing methodology, abstract to separate function)
  377. # - length correction by removing double digits (xxyzabxybaaz = change aa to a, or xx to x)
  378. # - validate bank codes
  379. # - utilize format knowledge with regards to alphanumeric applicability in that offset in that national BBAN format
  380. # - turkish TL/TK thing
  381. # - norway NO gets dropped due to mis-identification with "No." for number (ie. if no country code try prepending NO)
  382. function iban_mistranscription_suggestions($incorrect_iban) {
  383. # remove funky characters
  384. $incorrect_iban = iban_to_machine_format($incorrect_iban);
  385. # abort on ridiculous length input (but be liberal)
  386. $length = strlen($incorrect_iban);
  387. if($length<5 || $length>34) { return array('(supplied iban length insane)'); }
  388. # abort if mistranscriptions data is unable to load
  389. if(!_iban_load_mistranscriptions()) { return array('(failed to load mistranscriptions)'); }
  390. # init
  391. global $_iban_mistranscriptions;
  392. $suggestions = array();
  393. # we have a string of approximately IBAN-like length.
  394. # ... now let's make suggestions.
  395. $numbers = array('0','1','2','3','4','5','6','7','8','9');
  396. for($i=0;$i<$length;$i++) {
  397. # get the character at this position
  398. $character = substr($incorrect_iban,$i,1);
  399. # for each known transcription error resulting in this character
  400. foreach($_iban_mistranscriptions[$character] as $possible_origin) {
  401. # if we're:
  402. # - in the first 2 characters (country) and the possible replacement
  403. # is a letter
  404. # - in the 3rd or 4th characters (checksum) and the possible
  405. # replacement is a number
  406. # - later in the string
  407. if(($i<2 && !in_array($possible_origin,$numbers)) ||
  408. ($i>=2 && $i<=3 && in_array($possible_origin,$numbers)) ||
  409. $i>3) {
  410. # construct a possible IBAN using this possible origin for the
  411. # mistranscribed character, replaced at this position only
  412. $possible_iban = substr($incorrect_iban,0,$i) . $possible_origin . substr($incorrect_iban,$i+1);
  413. # if the checksum passes, return it as a possibility
  414. if(verify_iban($possible_iban)) {
  415. array_push($suggestions,$possible_iban);
  416. }
  417. }
  418. }
  419. }
  420. # now we check for the type of mistransposition case where all of
  421. # the characters of a certain type within a string were mistransposed.
  422. # - first generate a character frequency table
  423. $char_freqs = array();
  424. for($i=0;$i<strlen($incorrect_iban);$i++) {
  425. if(!isset($char_freqs[substr($incorrect_iban,$i,1)])) {
  426. $char_freqs[substr($incorrect_iban,$i,1)] = 1;
  427. }
  428. else {
  429. $char_freqs[substr($incorrect_iban,$i,1)]++;
  430. }
  431. }
  432. # - now, for each of the characters in the string...
  433. foreach($char_freqs as $char=>$freq) {
  434. # if the character occurs more than once
  435. if($freq>1) {
  436. # check the 'all occurrences of <char> were mistranscribed' case
  437. foreach($_iban_mistranscriptions[$char] as $possible_origin) {
  438. $possible_iban = str_replace($char,$possible_origin,$incorrect_iban);
  439. if(verify_iban($possible_iban)) {
  440. array_push($suggestions,$possible_iban);
  441. }
  442. }
  443. }
  444. }
  445. return $suggestions;
  446. }
  447. ##### internal use functions - safe to ignore ######
  448. # Load the IBAN registry from disk.
  449. global $_iban_registry;
  450. $_iban_registry = array();
  451. function _iban_load_registry() {
  452. global $_iban_registry;
  453. # if the registry is not yet loaded, or has been corrupted, reload
  454. if(!is_array($_iban_registry) || count($_iban_registry)<1) {
  455. $data = file_get_contents(dirname(__FILE__) . '/registry.txt');
  456. $lines = explode("\n",$data);
  457. array_shift($lines); # drop leading description line
  458. # loop through lines
  459. foreach($lines as $line) {
  460. if($line!='') {
  461. # avoid spewing tonnes of PHP warnings under bad PHP configs - see issue #69
  462. if(function_exists('ini_set')) {
  463. # split to fields
  464. $old_display_errors_value = ini_get('display_errors');
  465. ini_set('display_errors',false);
  466. $old_error_reporting_value = ini_get('error_reporting');
  467. ini_set('error_reporting',false);
  468. }
  469. list($country,$country_name,$domestic_example,$bban_example,$bban_format_swift,$bban_format_regex,$bban_length,$iban_example,$iban_format_swift,$iban_format_regex,$iban_length,$bban_bankid_start_offset,$bban_bankid_stop_offset,$bban_branchid_start_offset,$bban_branchid_stop_offset,$registry_edition,$country_sepa,$country_swift_official,$bban_checksum_start_offset,$bban_checksum_stop_offset,$country_iana,$country_iso3166,$parent_registrar,$currency_iso4217,$central_bank_url,$central_bank_name,$membership) = explode('|',$line);
  470. # avoid spewing tonnes of PHP warnings under bad PHP configs - see issue #69
  471. if(function_exists('ini_set')) {
  472. ini_set('display_errors',$old_display_errors_value);
  473. ini_set('error_reporting',$old_error_reporting_value);
  474. }
  475. # assign to registry
  476. $_iban_registry[$country] = array(
  477. 'country' => $country,
  478. 'country_name' => $country_name,
  479. 'country_sepa' => $country_sepa,
  480. 'domestic_example' => $domestic_example,
  481. 'bban_example' => $bban_example,
  482. 'bban_format_swift' => $bban_format_swift,
  483. 'bban_format_regex' => $bban_format_regex,
  484. 'bban_length' => $bban_length,
  485. 'iban_example' => $iban_example,
  486. 'iban_format_swift' => $iban_format_swift,
  487. 'iban_format_regex' => $iban_format_regex,
  488. 'iban_length' => $iban_length,
  489. 'bban_bankid_start_offset' => $bban_bankid_start_offset,
  490. 'bban_bankid_stop_offset' => $bban_bankid_stop_offset,
  491. 'bban_branchid_start_offset' => $bban_branchid_start_offset,
  492. 'bban_branchid_stop_offset' => $bban_branchid_stop_offset,
  493. 'registry_edition' => $registry_edition,
  494. 'country_swift_official' => $country_swift_official,
  495. 'bban_checksum_start_offset' => $bban_checksum_start_offset,
  496. 'bban_checksum_stop_offset' => $bban_checksum_stop_offset,
  497. 'country_iana' => $country_iana,
  498. 'country_iso3166' => $country_iso3166,
  499. 'parent_registrar' => $parent_registrar,
  500. 'currency_iso4217' => $currency_iso4217,
  501. 'central_bank_url' => $central_bank_url,
  502. 'central_bank_name' => $central_bank_name,
  503. 'membership' => $membership
  504. );
  505. }
  506. }
  507. }
  508. }
  509. # Get information from the IBAN registry by example IBAN / code combination
  510. function _iban_get_info($iban,$code) {
  511. $country = iban_get_country_part($iban);
  512. return _iban_country_get_info($country,$code);
  513. }
  514. # Get information from the IBAN registry by country / code combination
  515. function _iban_country_get_info($country,$code) {
  516. _iban_load_registry();
  517. global $_iban_registry;
  518. $country = strtoupper($country);
  519. $code = strtolower($code);
  520. if(array_key_exists($country,$_iban_registry)) {
  521. if(array_key_exists($code,$_iban_registry[$country])) {
  522. return $_iban_registry[$country][$code];
  523. }
  524. }
  525. return false;
  526. }
  527. # Load common mistranscriptions from disk.
  528. function _iban_load_mistranscriptions() {
  529. global $_iban_mistranscriptions;
  530. # do not reload if already present
  531. if(is_array($_iban_mistranscriptions) && count($_iban_mistranscriptions) == 36) { return true; }
  532. $_iban_mistranscriptions = array();
  533. $file = dirname(__FILE__) . '/mistranscriptions.txt';
  534. if(!file_exists($file) || !is_readable($file)) { return false; }
  535. $data = file_get_contents($file);
  536. $lines = explode("\n",$data);
  537. foreach($lines as $line) {
  538. # match lines with ' c-<x> = <something>' where x is a word-like character
  539. if(preg_match('/^ *c-(\w) = (.*?)$/',$line,$matches)) {
  540. # normalize the character to upper case
  541. $character = strtoupper($matches[1]);
  542. # break the possible origins list at '/', strip quotes & spaces
  543. $chars = explode(' ',str_replace('"','',preg_replace('/ *?\/ *?/','',$matches[2])));
  544. # assign as possible mistranscriptions for that character
  545. $_iban_mistranscriptions[$character] = $chars;
  546. }
  547. }
  548. return true;
  549. }
  550. # Find the correct national checksum for an IBAN
  551. # (Returns the correct national checksum as a string, or '' if unimplemented for this IBAN's country)
  552. # (NOTE: only works for some countries)
  553. function iban_find_nationalchecksum($iban) {
  554. return _iban_nationalchecksum_implementation($iban,'find');
  555. }
  556. # Verify the correct national checksum for an IBAN
  557. # (Returns true or false, or '' if unimplemented for this IBAN's country)
  558. # (NOTE: only works for some countries)
  559. function iban_verify_nationalchecksum($iban) {
  560. return _iban_nationalchecksum_implementation($iban,'verify');
  561. }
  562. # Verify the correct national checksum for an IBAN
  563. # (Returns the (possibly) corrected IBAN, or '' if unimplemented for this IBAN's country)
  564. # (NOTE: only works for some countries)
  565. function iban_set_nationalchecksum($iban) {
  566. $result = _iban_nationalchecksum_implementation($iban,'set');
  567. if($result != '' ) {
  568. $result = iban_set_checksum($result); # recalculate IBAN-level checksum
  569. }
  570. return $result;
  571. }
  572. # Internal function to overwrite the national checksum portion of an IBAN
  573. function _iban_nationalchecksum_set($iban,$nationalchecksum) {
  574. $country = iban_get_country_part($iban);
  575. $start = iban_country_get_nationalchecksum_start_offset($country);
  576. if($start == '') { return ''; }
  577. $stop = iban_country_get_nationalchecksum_stop_offset($country);
  578. if($stop == '') { return ''; }
  579. # determine the BBAN
  580. $bban = iban_get_bban_part($iban);
  581. # alter the BBAN
  582. $firstbit = substr($bban,0,$start); # 'string before the checksum'
  583. $lastbit = substr($bban,$stop+1); # 'string after the checksum'
  584. $fixed_bban = $firstbit . $nationalchecksum . $lastbit;
  585. # reconstruct the fixed IBAN
  586. $fixed_iban = $country . iban_get_checksum_part($iban) . $fixed_bban;
  587. return $fixed_iban;
  588. }
  589. # Currently unused but may be useful for Norway.
  590. # ISO7064 MOD11-2
  591. # Adapted from https://gist.github.com/andreCatita/5714353 by Andrew Catita
  592. function _iso7064_mod112_catita($input) {
  593. $p = 0;
  594. for ($i = 0; $i < strlen($input); $i++) {
  595. $c = $input[$i];
  596. $p = 2 * ($p + $c);
  597. }
  598. $p %= 11;
  599. $result = (12 - $p) % 11;
  600. if($result == 10) { $result = 'X'; }
  601. return $result;
  602. }
  603. # Currently unused but may be useful for Norway.
  604. # ISO 7064:1983.MOD 11-2
  605. # by goseaside@sina.com
  606. function _iso7064_mod112_goseaside($vString) {
  607. $sigma = '';
  608. $wi = array(1, 2, 4, 8, 5, 10, 9, 7, 3, 6);
  609. $hash_map = array('1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2');
  610. $i_size = strlen($vString);
  611. $bModify = '?' == substr($vString, -1);
  612. $i_size1 = $bModify ? $i_size : $i_size + 1;
  613. for ($i = 1; $i <= $i_size; $i++) {
  614. $i1 = $vString[$i - 1] * 1;
  615. $w1 = $wi[($i_size1 - $i) % 10];
  616. $sigma += ($i1 * $w1) % 11;
  617. }
  618. if($bModify) return str_replace('?', $hash_map[($sigma % 11)], $vString);
  619. else return $hash_map[($sigma % 11)];
  620. }
  621. # ISO7064 MOD97-10 (Bosnia, etc.)
  622. # (Credit: Adapted from https://github.com/stvkoch/ISO7064-Mod-97-10/blob/master/ISO7064Mod97_10.php)
  623. function _iso7064_mod97_10($str) {
  624. $ai=1;
  625. $ch = ord($str[strlen($str)-1]) - 48;
  626. if($ch < 0 || $ch > 9) return false;
  627. $check=$ch;
  628. for($i=strlen($str)-2;$i>=0;$i--) {
  629. $ch = ord($str[$i]) - 48;
  630. if ($ch < 0 || $ch > 9) return false;
  631. $ai=($ai*10)%97;
  632. $check+= ($ai * ((int)$ch));
  633. }
  634. return (98-($check%97));
  635. }
  636. # Implement the national checksum for a Belgium (BE) IBAN
  637. # (Credit: @gaetan-be, fixed by @Olympic1)
  638. function _iban_nationalchecksum_implementation_be($iban,$mode) {
  639. if($mode != 'set' && $mode != 'find' && $mode != 'verify') { return ''; } # blank value on return to distinguish from correct execution
  640. $nationalchecksum = iban_get_nationalchecksum_part($iban);
  641. $bban = iban_get_bban_part($iban);
  642. $bban_less_checksum = substr($bban, 0, -strlen($nationalchecksum));
  643. $expected_nationalchecksum = $bban_less_checksum % 97;
  644. if($mode=='find') {
  645. return $expected_nationalchecksum;
  646. }
  647. elseif($mode=='set') {
  648. return _iban_nationalchecksum_set($iban,$expected_nationalchecksum);
  649. }
  650. elseif($mode=='verify') {
  651. return ($nationalchecksum == $expected_nationalchecksum);
  652. }
  653. }
  654. # MOD11 helper function for the Spanish (ES) IBAN national checksum implementation
  655. # (Credit: @dem3trio, code lifted from Spanish Wikipedia at https://es.wikipedia.org/wiki/C%C3%B3digo_cuenta_cliente)
  656. function _iban_nationalchecksum_implementation_es_mod11_helper($numero) {
  657. if(strlen($numero)!=10) return "?";
  658. $cifras = Array(1,2,4,8,5,10,9,7,3,6);
  659. $chequeo=0;
  660. for($i=0; $i<10; $i++) {
  661. $chequeo += substr($numero,$i,1) * $cifras[$i];
  662. }
  663. $chequeo = 11 - ($chequeo % 11);
  664. if ($chequeo == 11) $chequeo = 0;
  665. if ($chequeo == 10) $chequeo = 1;
  666. return $chequeo;
  667. }
  668. # Implement the national checksum for a Spanish (ES) IBAN
  669. # (Credit: @dem3trio, adapted from code on Spanish Wikipedia at https://es.wikipedia.org/wiki/C%C3%B3digo_cuenta_cliente)
  670. function _iban_nationalchecksum_implementation_es($iban,$mode) {
  671. if($mode != 'set' && $mode != 'find' && $mode != 'verify') { return ''; } # blank value on return to distinguish from correct execution
  672. # extract appropriate substrings
  673. $bankprefix = iban_get_bank_part($iban) . iban_get_branch_part($iban);
  674. $nationalchecksum = iban_get_nationalchecksum_part($iban);
  675. $account = iban_get_account_part($iban);
  676. $account_less_checksum = substr($account,2);
  677. # first we calculate the initial checksum digit, which is MOD11 of the bank prefix with '00' prepended
  678. $expected_nationalchecksum = _iban_nationalchecksum_implementation_es_mod11_helper("00".$bankprefix);
  679. # then we append the second digit, which is MOD11 of the account
  680. $expected_nationalchecksum .= _iban_nationalchecksum_implementation_es_mod11_helper($account_less_checksum);
  681. if($mode=='find') {
  682. return $expected_nationalchecksum;
  683. }
  684. elseif($mode=='set') {
  685. return _iban_nationalchecksum_set($iban,$expected_nationalchecksum);
  686. }
  687. elseif($mode=='verify') {
  688. return ($nationalchecksum == $expected_nationalchecksum);
  689. }
  690. }
  691. # Helper function for the France (FR) BBAN national checksum implementation
  692. # (Credit: @gaetan-be)
  693. function _iban_nationalchecksum_implementation_fr_letters2numbers_helper($bban) {
  694. $allNumbers = "";
  695. $conversion = array(
  696. "A" => 1, "B" => 2, "C" => 3, "D" => 4, "E" => 5, "F" => 6, "G" => 7, "H" => 8, "I" => 9,
  697. "J" => 1, "K" => 2, "L" => 3, "M" => 4, "N" => 5, "O" => 6, "P" => 7, "Q" => 8, "R" => 9,
  698. "S" => 2, "T" => 3, "U" => 4, "V" => 5, "W" => 6, "X" => 7, "Y" => 8, "Z" => 9
  699. );
  700. for ($i=0; $i < strlen($bban); $i++) {
  701. if(is_numeric($bban[$i])) {
  702. $allNumbers .= $bban[$i];
  703. }
  704. else {
  705. $letter = strtoupper($bban[$i]);
  706. if(array_key_exists($letter, $conversion)) {
  707. $allNumbers .= $conversion[$letter];
  708. }
  709. else {
  710. return null;
  711. }
  712. }
  713. }
  714. return $allNumbers;
  715. }
  716. # NOTE: Worryingly at least one domestic number found within CF online is
  717. # not passing national checksum support. Perhaps banks do not issue
  718. # with correct RIB (French-style national checksum) despite using
  719. # the legacy format? Perhaps this is a mistranscribed number?
  720. # http://www.radiomariacentrafrique.org/virement-bancaire.aspx
  721. # ie. CF19 20001 00001 01401832401 40
  722. # The following two numbers work:
  723. # http://fondationvoixducoeur.net/fr/pour-contribuer.html
  724. # ie. CF4220002002003712551080145 and CF4220001004113717538890110
  725. # Since in the latter case the bank is the same as the former and
  726. # the French structure, terminology and 2/3 correct is a fairly high
  727. # correlation, we are going to assume that the first error is theirs.
  728. #
  729. # Implement the national checksum for a Central African Republic (CF) IBAN
  730. function _iban_nationalchecksum_implementation_cf($iban,$mode) {
  731. return _iban_nationalchecksum_implementation_fr($iban,$mode);
  732. }
  733. # Implement the national checksum for a Chad (TD) IBAN
  734. function _iban_nationalchecksum_implementation_td($iban,$mode) {
  735. return _iban_nationalchecksum_implementation_fr($iban,$mode);
  736. }
  737. # Implement the national checksum for a Comoros (KM) IBAN
  738. function _iban_nationalchecksum_implementation_km($iban,$mode) {
  739. return _iban_nationalchecksum_implementation_fr($iban,$mode);
  740. }
  741. # Implement the national checksum for a Congo (CG) IBAN
  742. function _iban_nationalchecksum_implementation_cg($iban,$mode) {
  743. return _iban_nationalchecksum_implementation_fr($iban,$mode);
  744. }
  745. # Implement the national checksum for a Djibouti (DJ) IBAN
  746. function _iban_nationalchecksum_implementation_dj($iban,$mode) {
  747. return _iban_nationalchecksum_implementation_fr($iban,$mode);
  748. }
  749. # Implement the national checksum for an Equitorial Guinea (GQ) IBAN
  750. function _iban_nationalchecksum_implementation_gq($iban,$mode) {
  751. return _iban_nationalchecksum_implementation_fr($iban,$mode);
  752. }
  753. # Implement the national checksum for a Gabon (GA) IBAN
  754. function _iban_nationalchecksum_implementation_ga($iban,$mode) {
  755. return _iban_nationalchecksum_implementation_fr($iban,$mode);
  756. }
  757. # Implement the national checksum for a Monaco (MC) IBAN
  758. # (Credit: @gaetan-be)
  759. function _iban_nationalchecksum_implementation_mc($iban,$mode) {
  760. return _iban_nationalchecksum_implementation_fr($iban,$mode);
  761. }
  762. # Implement the national checksum for a France (FR) IBAN
  763. # (Credit: @gaetan-be, http://www.credit-card.be/BankAccount/ValidationRules.htm#FR_Validation and
  764. # https://docs.oracle.com/cd/E18727_01/doc.121/e13483/T359831T498954.htm)
  765. function _iban_nationalchecksum_implementation_fr($iban,$mode) {
  766. if($mode != 'set' && $mode != 'find' && $mode != 'verify') { return ''; } # blank value on return to distinguish from correct execution
  767. # first, extract the BBAN
  768. $bban = iban_get_bban_part($iban);
  769. # convert to numeric form
  770. $bban_numeric_form = _iban_nationalchecksum_implementation_fr_letters2numbers_helper($bban);
  771. # if the result was null, something is horribly wrong
  772. if(is_null($bban_numeric_form)) { return ''; }
  773. # extract other parts
  774. $bank = substr($bban_numeric_form,0,5);
  775. $branch = substr($bban_numeric_form,5,5);
  776. $account = substr($bban_numeric_form,10,11);
  777. # actual implementation: mod97( (89 x bank number "Code banque") + (15 x branch code "Code guichet") + (3 x account number "Numéro de compte") )
  778. $sum = (89*($bank+0)) + ((15*($branch+0)));
  779. $sum += (3*($account+0));
  780. $expected_nationalchecksum = 97 - ($sum % 97);
  781. if(strlen($expected_nationalchecksum) == 1) { $expected_nationalchecksum = '0' . $expected_nationalchecksum; }
  782. # return
  783. if($mode=='find') {
  784. return $expected_nationalchecksum;
  785. }
  786. elseif($mode=='set') {
  787. return _iban_nationalchecksum_set($iban,$expected_nationalchecksum);
  788. }
  789. elseif($mode=='verify') {
  790. return (iban_get_nationalchecksum_part($iban) == $expected_nationalchecksum);
  791. }
  792. }
  793. # Implement the national checksum for a Norway (NO) IBAN
  794. # (NOTE: Built from description at https://docs.oracle.com/cd/E18727_01/doc.121/e13483/T359831T498954.htm, not well tested)
  795. function _iban_nationalchecksum_implementation_no($iban,$mode) {
  796. if($mode != 'set' && $mode != 'find' && $mode != 'verify') { return ''; } # blank value on return to distinguish from correct execution
  797. # first, extract the BBAN
  798. $bban = iban_get_bban_part($iban);
  799. # then, the account
  800. $account = iban_get_account_part($iban);
  801. # existing checksum
  802. $nationalchecksum = iban_get_nationalchecksum_part($iban);
  803. # bban less checksum
  804. $bban_less_checksum = substr($bban,0,strlen($bban)-strlen($nationalchecksum));
  805. # factor table
  806. $factors = array(5,4,3,2,7,6,5,4,3,2);
  807. # calculate checksum
  808. $total = 0;
  809. for($i=0;$i<10;$i++) {
  810. $total += $bban_less_checksum[$i] * $factors[$i];
  811. }
  812. $total += $nationalchecksum;
  813. # mod11
  814. $remainder = $total % 11;
  815. # to find the correct check digit, we add the remainder to the current check digit,
  816. # mod10 (ie. rounding at 10, such that 10 = 0, 11 = 1, etc.)
  817. $calculated_checksum = ($nationalchecksum + $remainder)%10;
  818. if($mode == 'find') {
  819. if($remainder == 0) { return $nationalchecksum; }
  820. else {
  821. return $calculated_checksum;
  822. }
  823. }
  824. elseif($mode == 'set') {
  825. return _iban_nationalchecksum_set($iban,$calculated_checksum);
  826. }
  827. elseif($mode == 'verify') {
  828. if($remainder == 0) { return true; }
  829. return false;
  830. }
  831. }
  832. # ISO/IEC 7064, MOD 11-2
  833. # @param $input string Must contain only characters ('0123456789').
  834. # @output A 1 character string containing '0123456789X',
  835. # or '' (empty string) on failure due to bad input.
  836. # (Credit: php-iso7064 @ https://github.com/globalcitizen/php-iso7064)
  837. function _iso7064_mod11_2($input) {
  838. $input = strtoupper($input); # normalize
  839. if(!preg_match('/^[0123456789]+$/',$input)) { return ''; } # bad input
  840. $modulus = 11;
  841. $radix = 2;
  842. $output_values = '0123456789X';
  843. $p = 0;
  844. for($i=0; $i<strlen($input); $i++) {
  845. $val = strpos($output_values,substr($input,$i,1));
  846. if($val < 0) { return ''; } # illegal character encountered
  847. $p = (($p + $val) * $radix) % $modulus;
  848. }
  849. $checksum = ($modulus - $p + 1) % $modulus;
  850. return substr($output_values,$checksum,1);
  851. }
  852. # Implement the national checksum systems based on ISO7064 MOD11-2 Algorithm
  853. function _iban_nationalchecksum_implementation_iso7064_mod11_2($iban,$mode,$drop_at_front=0,$drop_at_end=1) {
  854. if($mode != 'set' && $mode != 'find' && $mode != 'verify') { return ''; } # blank value on return to distinguish from correct execution
  855. # first, extract the BBAN
  856. $bban = iban_get_bban_part($iban);
  857. # get the current and computed checksum
  858. $nationalchecksum = iban_get_nationalchecksum_part($iban);
  859. # drop characters from the front and end of the BBAN as requested
  860. $bban_less_checksum = substr($bban,$drop_at_front,strlen($bban)-$drop_at_end);
  861. # calculate expected checksum
  862. $expected_nationalchecksum = _iso7064_mod11_2($bban_less_checksum);
  863. # return
  864. if($mode=='find') {
  865. return $expected_nationalchecksum;
  866. }
  867. elseif($mode=='set') {
  868. return _iban_nationalchecksum_set($iban,$expected_nationalchecksum);
  869. }
  870. elseif($mode=='verify') {
  871. return (iban_get_nationalchecksum_part($iban) == $expected_nationalchecksum);
  872. }
  873. }
  874. # Implement the national checksum systems based on Damm Algorithm
  875. function _iban_nationalchecksum_implementation_damm($iban,$mode) {
  876. if($mode != 'set' && $mode != 'find' && $mode != 'verify') { return ''; } # blank value on return to distinguish from correct execution
  877. # first, extract the BBAN
  878. $bban = iban_get_bban_part($iban);
  879. # get the current and computed checksum
  880. $nationalchecksum = iban_get_nationalchecksum_part($iban);
  881. # drop trailing checksum characters
  882. $bban_less_checksum = substr($bban,0,strlen($bban)-strlen($nationalchecksum));
  883. # calculate expected checksum
  884. $expected_nationalchecksum = _damm($bban_less_checksum);
  885. # return
  886. if($mode=='find') {
  887. return $expected_nationalchecksum;
  888. }
  889. elseif($mode=='set') {
  890. return _iban_nationalchecksum_set($iban,$expected_nationalchecksum);
  891. }
  892. elseif($mode=='verify') {
  893. return (iban_get_nationalchecksum_part($iban) == $expected_nationalchecksum);
  894. }
  895. }
  896. # Implement the national checksum systems based on Verhoeff Algorithm
  897. function _iban_nationalchecksum_implementation_verhoeff($iban,$mode,$strip_length_end,$strip_length_front=0) {
  898. if($mode != 'set' && $mode != 'find' && $mode != 'verify') { return ''; } # blank value on return to distinguish from correct execution
  899. # first, extract the BBAN
  900. $bban = iban_get_bban_part($iban);
  901. # if necessary, drop this many leading characters
  902. $bban = substr($bban,$strip_length_front);
  903. # drop the trailing checksum digit
  904. $bban_less_checksum = substr($bban,0,strlen($bban)-$strip_length_end);
  905. # get the current and computed checksum
  906. $nationalchecksum = iban_get_nationalchecksum_part($iban);
  907. $expected_nationalchecksum = _verhoeff($bban_less_checksum);
  908. # return
  909. if($mode=='find') {
  910. return $expected_nationalchecksum;
  911. }
  912. elseif($mode=='set') {
  913. return _iban_nationalchecksum_set($iban,$expected_nationalchecksum);
  914. }
  915. elseif($mode=='verify') {
  916. return (iban_get_nationalchecksum_part($iban) == $expected_nationalchecksum);
  917. }
  918. }
  919. # ISO/IEC 7064, MOD 97-10
  920. # @param $input string Must contain only characters ('0123456789').
  921. # @output A 2 character string containing '0123456789',
  922. # or '' (empty string) on failure due to bad input.
  923. # (Credit: php-iso7064 @ https://github.com/globalcitizen/php-iso7064)
  924. function _iso7064_mod97_10_generated($input) {
  925. $input = strtoupper($input); # normalize
  926. if(!preg_match('/^[0123456789]+$/',$input)) { return ''; } # bad input
  927. $modulus = 97;
  928. $radix = 10;
  929. $output_values = '0123456789';
  930. $p = 0;
  931. for($i=0; $i<strlen($input); $i++) {
  932. $val = strpos($output_values,substr($input,$i,1));
  933. if($val < 0) { return ''; } # illegal character encountered
  934. $p = (($p + $val) * $radix) % $modulus;
  935. }
  936. $p = ($p*$radix) % $modulus;
  937. $checksum = ($modulus - $p + 1) % $modulus;
  938. $second = $checksum % $radix;
  939. $first = ($checksum - $second) / $radix;
  940. return substr($output_values,$first,1) . substr($output_values,$second,1);
  941. }
  942. # Implement the national checksum for an Montenegro (ME) IBAN
  943. # (NOTE: Reverse engineered)
  944. function _iban_nationalchecksum_implementation_me($iban,$mode) {
  945. return _iban_nationalchecksum_implementation_mod97_10($iban,$mode);
  946. }
  947. # Implement the national checksum for an Macedonia (MK) IBAN
  948. # (NOTE: Reverse engineered)
  949. function _iban_nationalchecksum_implementation_mk($iban,$mode) {
  950. return _iban_nationalchecksum_implementation_mod97_10($iban,$mode);
  951. }
  952. # Implement the national checksum for an Netherlands (NL) IBAN
  953. # This applies to most banks, but not to 'INGB', therefore we
  954. # treat it specially here.
  955. # (Original code: Validate_NL PEAR class, since extended)
  956. function _iban_nationalchecksum_implementation_nl($iban,$mode) {
  957. if($mode != 'set' && $mode != 'find' && $mode != 'verify') { return ''; } # blank value on return to distinguish from correct execution
  958. $bank = iban_get_bank_part($iban);
  959. if(strtoupper($bank) == 'INGB') {
  960. return '';
  961. }
  962. $account = iban_get_account_part($iban);
  963. $checksum = 0;
  964. for ($i = 0; $i < 10; $i++) {
  965. $checksum += ((int)$account[$i] * (10 - $i));
  966. }
  967. $remainder = $checksum % 11;
  968. if($mode=='verify') {
  969. return ($remainder==0); # we return the result of mod11, if 0 it's good
  970. }
  971. elseif($mode=='set') {
  972. if($remainder==0) {
  973. return $iban; # we return as expected if the checksum is ok
  974. }
  975. return ''; # we return unimplemented if the checksum is bad
  976. }
  977. elseif($mode=='find') {
  978. return ''; # does not make sense for this 0-digit checksum
  979. }
  980. }
  981. # Implement the national checksum for an Portugal (PT) IBAN
  982. # (NOTE: Reverse engineered)
  983. function _iban_nationalchecksum_implementation_pt($iban,$mode) {
  984. if($mode != 'set' && $mode != 'find' && $mode != 'verify') { return ''; } # blank value on return to distinguish from correct execution
  985. $nationalchecksum = iban_get_nationalchecksum_part($iban);
  986. $bban = iban_get_bban_part($iban);
  987. $bban_less_checksum = substr($bban,0,strlen($bban)-2);
  988. $expected_nationalchecksum = _iso7064_mod97_10_generated($bban_less_checksum);
  989. if($mode=='find') {
  990. return $expected_nationalchecksum;
  991. }
  992. elseif($mode=='set') {
  993. return _iban_nationalchecksum_set($iban,$expected_nationalchecksum);
  994. }
  995. elseif($mode=='verify') {
  996. return ($nationalchecksum == $expected_nationalchecksum);
  997. }
  998. }
  999. # Implement the national checksum for an Serbia (RS) IBAN
  1000. # (NOTE: Reverse engineered, including bank 'Narodna banka Srbije' (908) exception. For two
  1001. # separately published and legitimate looking IBANs from that bank, there appears to
  1002. # be a +97 offset on the checksum, so we simply ignore all checksums for this bank.)
  1003. function _iban_nationalchecksum_implementation_rs($iban,$mode) {
  1004. $bank = iban_get_bank_part($iban);
  1005. if($bank == '908') {
  1006. return '';
  1007. }
  1008. return _iban_nationalchecksum_implementation_mod97_10($iban,$mode);
  1009. }
  1010. # Implement the national checksum for an Slovenia (SI) IBAN
  1011. # Note: It appears that the central bank does not use these
  1012. # checksums, thus an exception has been added.
  1013. # (NOTE: Reverse engineered)
  1014. function _iban_nationalchecksum_implementation_si($iban,$mode) {
  1015. $bank = iban_get_bank_part($iban);
  1016. # Bank of Slovenia does not use the legacy checksum scheme.
  1017. # Accounts in this namespace appear to be the central bank
  1018. # accounts for licensed local banks.
  1019. if($bank == '01') {
  1020. return '';
  1021. }
  1022. return _iban_nationalchecksum_implementation_mod97_10($iban,$mode);
  1023. }
  1024. # Implement the national checksum for Slovak Republic (SK) IBAN
  1025. # Source of algorithm: https://www.nbs.sk/_img/Documents/_Legislativa/_Vestnik/OPAT8-09.pdf
  1026. # Account number is currently verified only, it's possible to also add validation for bank code and account number prefix
  1027. function _iban_nationalchecksum_implementation_sk($iban,$mode) {
  1028. if ($mode !== 'verify') {
  1029. return '';
  1030. }
  1031. $account = iban_get_account_part($iban);
  1032. $weights = array(6, 3, 7, 9, 10, 5, 8, 4, 2, 1);
  1033. $sum = 0;
  1034. for ($i=0; $i < 10; $i++) {
  1035. $sum += $account[$i] * $weights[$i];
  1036. }
  1037. return $sum % 11 === 0;
  1038. }
  1039. # Implement the national checksum for MOD97-10 countries
  1040. function _iban_nationalchecksum_implementation_mod97_10($iban,$mode) {
  1041. if($mode != 'set' && $mode != 'find' && $mode != 'verify') { return ''; } # blank value on return to distinguish from correct execution
  1042. $nationalchecksum = iban_get_nationalchecksum_part($iban);
  1043. $bban = iban_get_bban_part($iban);
  1044. $bban_less_checksum = substr($bban,0,strlen($bban)-2);
  1045. $expected_nationalchecksum = _iso7064_mod97_10_generated($bban_less_checksum);
  1046. if($mode=='find') {
  1047. return $expected_nationalchecksum;
  1048. }
  1049. elseif($mode=='set') {
  1050. return _iban_nationalchecksum_set($iban,$expected_nationalchecksum);
  1051. }
  1052. elseif($mode=='verify') {
  1053. return ($nationalchecksum == $expected_nationalchecksum);
  1054. }
  1055. }
  1056. # Implement the national checksum for an Timor-Lest (TL) IBAN
  1057. # (NOTE: Reverse engineered, but works on 2 different IBAN from official sources)
  1058. function _iban_nationalchecksum_implementation_tl($iban,$mode) {
  1059. if($mode != 'set' && $mode != 'find' && $mode != 'verify') { return ''; } # blank value on return to distinguish from correct execution
  1060. $nationalchecksum = iban_get_nationalchecksum_part($iban);
  1061. $bban = iban_get_bban_part($iban);
  1062. $bban_less_checksum = substr($bban,0,strlen($bban)-2);
  1063. $expected_nationalchecksum = _iso7064_mod97_10_generated($bban_less_checksum);
  1064. if($mode=='find') {
  1065. return $expected_nationalchecksum;
  1066. }
  1067. elseif($mode=='set') {
  1068. return _iban_nationalchecksum_set($iban,$expected_nationalchecksum);
  1069. }
  1070. elseif($mode=='verify') {
  1071. return ($nationalchecksum == $expected_nationalchecksum);
  1072. }
  1073. }
  1074. # Luhn Check
  1075. # (Credit: Adapted from @gajus' https://gist.github.com/troelskn/1287893#gistcomment-857491)
  1076. function _luhn($string) {
  1077. $checksum='';
  1078. foreach (str_split(strrev((string) $string)) as $i => $d) {
  1079. $checksum .= $i %2 !== 0 ? $d * 2 : $d;
  1080. }
  1081. return array_sum(str_split($checksum)) % 10;
  1082. }
  1083. # Verhoeff checksum
  1084. # (Credit: Adapted from Semyon Velichko's code at https://en.wikibooks.org/wiki/Algorithm_Implementation/Checksums/Verhoeff_Algorithm#PHP)
  1085. function _verhoeff($input) {
  1086. if($input == '' || preg_match('/[^0-9]/',$input)) { return ''; } # reject non-numeric input
  1087. $d = array(
  1088. array(0,1,2,3,4,5,6,7,8,9),
  1089. array(1,2,3,4,0,6,7,8,9,5),
  1090. array(2,3,4,0,1,7,8,9,5,6),
  1091. array(3,4,0,1,2,8,9,5,6,7),
  1092. array(4,0,1,2,3,9,5,6,7,8),
  1093. array(5,9,8,7,6,0,4,3,2,1),
  1094. array(6,5,9,8,7,1,0,4,3,2),
  1095. array(7,6,5,9,8,2,1,0,4,3),
  1096. array(8,7,6,5,9,3,2,1,0,4),
  1097. array(9,8,7,6,5,4,3,2,1,0)
  1098. );
  1099. $p = array(
  1100. array(0,1,2,3,4,5,6,7,8,9),
  1101. array(1,5,7,6,2,8,3,0,9,4),
  1102. array(5,8,0,3,7,9,6,1,4,2),
  1103. array(8,9,1,6,0,4,3,5,2,7),
  1104. array(9,4,5,3,1,2,6,8,7,0),
  1105. array(4,2,8,6,5,7,3,9,0,1),
  1106. array(2,7,9,3,8,0,6,4,1,5),
  1107. array(7,0,4,6,9,1,3,2,5,8)
  1108. );
  1109. $inv = array(0,4,3,2,1,5,6,7,8,9);
  1110. $r = 0;
  1111. foreach(array_reverse(str_split($input)) as $n => $N) {
  1112. $r = $d[$r][$p[($n+1)%8][$N]];
  1113. }
  1114. return $inv[$r];
  1115. }
  1116. # Damm checksum
  1117. # (Credit: https://en.wikibooks.org/wiki/Algorithm_Implementation/Checksums/Damm_Algorithm#PHP)
  1118. function _damm($input) {
  1119. if($input=='' || preg_match('/[^0-9]/',$input)) { return ''; } # non-numeric input
  1120. // from http://www.md-software.de/math/DAMM_Quasigruppen.txt
  1121. $matrix = array(
  1122. array(0, 3, 1, 7, 5, 9, 8, 6, 4, 2),
  1123. array(7, 0, 9, 2, 1, 5, 4, 8, 6, 3),
  1124. array(4, 2, 0, 6, 8, 7, 1, 3, 5, 9),
  1125. array(1, 7, 5, 0, 9, 8, 3, 4, 2, 6),
  1126. array(6, 1, 2, 3, 0, 4, 5, 9, 7, 8),
  1127. array(3, 6, 7, 4, 2, 0, 9, 5, 8, 1),
  1128. array(5, 8, 6, 9, 7, 2, 0, 1, 3, 4),
  1129. array(8, 9, 4, 5, 3, 6, 2, 0, 1, 7),
  1130. array(9, 4, 3, 8, 6, 1, 7, 2, 0, 5),
  1131. array(2, 5, 8, 1, 4, 3, 6, 7, 9, 0),
  1132. );
  1133. $checksum = 0;
  1134. for ($i=0; $i<strlen($input); $i++) {
  1135. $character = substr($input,$i,1);
  1136. $checksum = $matrix[$checksum][$character];
  1137. }
  1138. return $checksum;
  1139. }
  1140. # Implement the national checksum for an Italian (IT) IBAN
  1141. function _iban_nationalchecksum_implementation_it($iban,$mode) {
  1142. if($mode != 'set' && $mode != 'find' && $mode != 'verify') { return ''; } # blank value on return to distinguish from correct execution
  1143. $nationalchecksum = iban_get_nationalchecksum_part($iban);
  1144. $bban = iban_get_bban_part($iban);
  1145. $bban_less_checksum = substr($bban,1);
  1146. $expected_nationalchecksum = _italian($bban_less_checksum);
  1147. if($mode=='find') {
  1148. return $expected_nationalchecksum;
  1149. }
  1150. elseif($mode=='set') {
  1151. return _iban_nationalchecksum_set($iban,$expected_nationalchecksum);
  1152. }
  1153. elseif($mode=='verify') {
  1154. return (iban_get_nationalchecksum_part($iban) == $expected_nationalchecksum);
  1155. }
  1156. }
  1157. # Implement the national checksum for a San Marino (SM) IBAN
  1158. function _iban_nationalchecksum_implementation_sm($iban,$mode) {
  1159. // San Marino adheres to Italian rules.
  1160. return _iban_nationalchecksum_implementation_it($iban,$mode);
  1161. }
  1162. # Italian (and San Marino's) checksum
  1163. # (Credit: Translated by Francesco Zanoni from http://community.visual-basic.it/lucianob/archive/2004/12/26/2464.aspx)
  1164. # (Source: European Commettee of Banking Standards' Register of European Account Numbers (TR201 V3.23 — FEBRUARY 2007),
  1165. # available at URL http://www.cnb.cz/cs/platebni_styk/iban/download/TR201.pdf)
  1166. function _italian($input)
  1167. {
  1168. $digits = str_split('0123456789');
  1169. $letters = str_split('ABCDEFGHIJKLMNOPQRSTUVWXYZ-. ');
  1170. $lengthOfBbanWithoutChecksum = 22;
  1171. $divisor = 26;
  1172. $evenList = array(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28);
  1173. $oddList = array(1, 0, 5, 7, 9, 13, 15, 17, 19, 21, 2, 4, 18, 20, 11, 3, 6, 8, 12, 14, 16, 10, 22, 25, 24, 23, 27, 28, 26);
  1174. // Character value computation
  1175. $sum = 0;
  1176. for ($k = 0; $k < $lengthOfBbanWithoutChecksum; $k++) {
  1177. $i = array_search($input[$k], $digits);
  1178. if ($i === false) {
  1179. $i = array_search($input[$k], $letters);
  1180. }
  1181. // In case of wrong characters,
  1182. // an unallowed checksum value is returned.
  1183. if ($i === false) {
  1184. return '';
  1185. }
  1186. $sum += (($k % 2) == 0 ? $oddList[$i] : $evenList[$i]);
  1187. }
  1188. return $letters[$sum % $divisor];
  1189. }
  1190. # Internal proxy function to access national checksum implementations
  1191. # $iban = IBAN to work with (length and country must be valid, IBAN checksum and national checksum may be incorrect)
  1192. # $mode = 'find', 'set', or 'verify'
  1193. # - In 'find' mode, the correct national checksum for $iban is returned.
  1194. # - In 'set' mode, a (possibly) modified version of $iban with the national checksum corrected is returned.
  1195. # - In 'verify' mode, the checksum within $iban is compared to correctly calculated value, and true or false is returned.
  1196. # If a national checksum algorithm does not exist or remains unimplemented for this country, or the supplied $iban or $mode is invalid, '' is returned.
  1197. # (NOTE: We cannot collapse 'verify' mode and implement here via simple string comparison between 'find' mode output and the nationalchecksum part,
  1198. # because some countries have systems which do not map to this approach, for example the Netherlands has no checksum part yet an algorithm exists)
  1199. function _iban_nationalchecksum_implementation($iban,$mode) {
  1200. if($mode != 'set' && $mode != 'find' && $mode != 'verify') { return ''; } # blank value on return to distinguish from correct execution
  1201. $iban = iban_to_machine_format($iban);
  1202. $country = iban_get_country_part($iban);
  1203. if(strlen($iban)!=iban_country_get_iban_length($country)) { return ''; }
  1204. $function_name = '_iban_nationalchecksum_implementation_' . strtolower($country);
  1205. if(function_exists($function_name)) {
  1206. return $function_name($iban,$mode);
  1207. }
  1208. return '';
  1209. }
  1210. ?>