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