From 59d90667aec818417a6112553c7842b796bac7c3 Mon Sep 17 00:00:00 2001 From: Sergey Melesh Date: Mon, 16 Jan 2023 18:41:52 +0300 Subject: [PATCH] [Validator] Sync IBAN formats with Swift IBAN registry --- .gitattributes | 1 + Constraints/IbanValidator.php | 116 ++++++++------ Resources/bin/sync-iban-formats.php | 204 ++++++++++++++++++++++++ Tests/Constraints/IbanValidatorTest.php | 27 +++- 4 files changed, 295 insertions(+), 53 deletions(-) create mode 100755 Resources/bin/sync-iban-formats.php diff --git a/.gitattributes b/.gitattributes index 84c7add05..c34694db5 100644 --- a/.gitattributes +++ b/.gitattributes @@ -2,3 +2,4 @@ /phpunit.xml.dist export-ignore /.gitattributes export-ignore /.gitignore export-ignore +/Resources/bin/sync-iban-formats.php export-ignore diff --git a/Constraints/IbanValidator.php b/Constraints/IbanValidator.php index 215eb16f1..173cb6678 100644 --- a/Constraints/IbanValidator.php +++ b/Constraints/IbanValidator.php @@ -20,8 +20,6 @@ * @author Manuel Reinhard * @author Michael Schummel * @author Bernhard Schussek - * - * @see http://www.michael-schummel.de/2007/10/05/iban-prufung-mit-php/ */ class IbanValidator extends ConstraintValidator { @@ -34,107 +32,135 @@ class IbanValidator extends ConstraintValidator * a BBAN (Basic Bank Account Number) which has a fixed length per country and, * included within it, a bank identifier with a fixed position and a fixed length per country * - * @see https://www.swift.com/sites/default/files/resources/iban_registry.pdf + * @see Resources/bin/sync-iban-formats.php + * @see https://www.swift.com/swift-resource/11971/download?language=en + * @see https://en.wikipedia.org/wiki/International_Bank_Account_Number */ private const FORMATS = [ + // auto-generated 'AD' => 'AD\d{2}\d{4}\d{4}[\dA-Z]{12}', // Andorra - 'AE' => 'AE\d{2}\d{3}\d{16}', // United Arab Emirates + 'AE' => 'AE\d{2}\d{3}\d{16}', // United Arab Emirates (The) 'AL' => 'AL\d{2}\d{8}[\dA-Z]{16}', // Albania 'AO' => 'AO\d{2}\d{21}', // Angola 'AT' => 'AT\d{2}\d{5}\d{11}', // Austria - 'AX' => 'FI\d{2}\d{6}\d{7}\d{1}', // Aland Islands + 'AX' => 'FI\d{2}\d{3}\d{11}', // Finland 'AZ' => 'AZ\d{2}[A-Z]{4}[\dA-Z]{20}', // Azerbaijan 'BA' => 'BA\d{2}\d{3}\d{3}\d{8}\d{2}', // Bosnia and Herzegovina 'BE' => 'BE\d{2}\d{3}\d{7}\d{2}', // Belgium - 'BF' => 'BF\d{2}\d{23}', // Burkina Faso + 'BF' => 'BF\d{2}[\dA-Z]{2}\d{22}', // Burkina Faso 'BG' => 'BG\d{2}[A-Z]{4}\d{4}\d{2}[\dA-Z]{8}', // Bulgaria 'BH' => 'BH\d{2}[A-Z]{4}[\dA-Z]{14}', // Bahrain - 'BI' => 'BI\d{2}\d{12}', // Burundi - 'BJ' => 'BJ\d{2}[A-Z]{1}\d{23}', // Benin - 'BY' => 'BY\d{2}[\dA-Z]{4}\d{4}[\dA-Z]{16}', // Belarus - https://bank.codes/iban/structure/belarus/ - 'BL' => 'FR\d{2}\d{5}\d{5}[\dA-Z]{11}\d{2}', // Saint Barthelemy - 'BR' => 'BR\d{2}\d{8}\d{5}\d{10}[A-Z][\dA-Z]', // Brazil - 'CG' => 'CG\d{2}\d{23}', // Congo + 'BI' => 'BI\d{2}\d{5}\d{5}\d{11}\d{2}', // Burundi + 'BJ' => 'BJ\d{2}[\dA-Z]{2}\d{22}', // Benin + 'BL' => 'FR\d{2}\d{5}\d{5}[\dA-Z]{11}\d{2}', // France + 'BR' => 'BR\d{2}\d{8}\d{5}\d{10}[A-Z]{1}[\dA-Z]{1}', // Brazil + 'BY' => 'BY\d{2}[\dA-Z]{4}\d{4}[\dA-Z]{16}', // Republic of Belarus + 'CF' => 'CF\d{2}\d{23}', // Central African Republic + 'CG' => 'CG\d{2}\d{23}', // Congo, Republic of the 'CH' => 'CH\d{2}\d{5}[\dA-Z]{12}', // Switzerland - 'CI' => 'CI\d{2}[A-Z]{1}\d{23}', // Ivory Coast - 'CM' => 'CM\d{2}\d{23}', // Cameron - 'CR' => 'CR\d{2}0\d{3}\d{14}', // Costa Rica - 'CV' => 'CV\d{2}\d{21}', // Cape Verde + 'CI' => 'CI\d{2}[A-Z]{1}\d{23}', // Côte d'Ivoire + 'CM' => 'CM\d{2}\d{23}', // Cameroon + 'CR' => 'CR\d{2}\d{4}\d{14}', // Costa Rica + 'CV' => 'CV\d{2}\d{21}', // Cabo Verde 'CY' => 'CY\d{2}\d{3}\d{5}[\dA-Z]{16}', // Cyprus - 'CZ' => 'CZ\d{2}\d{20}', // Czech Republic + 'CZ' => 'CZ\d{2}\d{4}\d{6}\d{10}', // Czechia 'DE' => 'DE\d{2}\d{8}\d{10}', // Germany + 'DJ' => 'DJ\d{2}\d{5}\d{5}\d{11}\d{2}', // Djibouti + 'DK' => 'DK\d{2}\d{4}\d{9}\d{1}', // Denmark 'DO' => 'DO\d{2}[\dA-Z]{4}\d{20}', // Dominican Republic - 'DK' => 'DK\d{2}\d{4}\d{10}', // Denmark - 'DZ' => 'DZ\d{2}\d{20}', // Algeria + 'DZ' => 'DZ\d{2}\d{22}', // Algeria 'EE' => 'EE\d{2}\d{2}\d{2}\d{11}\d{1}', // Estonia - 'ES' => 'ES\d{2}\d{4}\d{4}\d{1}\d{1}\d{10}', // Spain (also includes Canary Islands, Ceuta and Melilla) - 'FI' => 'FI\d{2}\d{6}\d{7}\d{1}', // Finland + 'EG' => 'EG\d{2}\d{4}\d{4}\d{17}', // Egypt + 'ES' => 'ES\d{2}\d{4}\d{4}\d{1}\d{1}\d{10}', // Spain + 'FI' => 'FI\d{2}\d{3}\d{11}', // Finland 'FO' => 'FO\d{2}\d{4}\d{9}\d{1}', // Faroe Islands 'FR' => 'FR\d{2}\d{5}\d{5}[\dA-Z]{11}\d{2}', // France - 'GF' => 'FR\d{2}\d{5}\d{5}[\dA-Z]{11}\d{2}', // French Guyana - 'GB' => 'GB\d{2}[A-Z]{4}\d{6}\d{8}', // United Kingdom of Great Britain and Northern Ireland + 'GA' => 'GA\d{2}\d{23}', // Gabon + 'GB' => 'GB\d{2}[A-Z]{4}\d{6}\d{8}', // United Kingdom 'GE' => 'GE\d{2}[A-Z]{2}\d{16}', // Georgia + 'GF' => 'FR\d{2}\d{5}\d{5}[\dA-Z]{11}\d{2}', // France + 'GG' => 'GB\d{2}[A-Z]{4}\d{6}\d{8}', // United Kingdom 'GI' => 'GI\d{2}[A-Z]{4}[\dA-Z]{15}', // Gibraltar 'GL' => 'GL\d{2}\d{4}\d{9}\d{1}', // Greenland - 'GP' => 'FR\d{2}\d{5}\d{5}[\dA-Z]{11}\d{2}', // Guadeloupe + 'GP' => 'FR\d{2}\d{5}\d{5}[\dA-Z]{11}\d{2}', // France + 'GQ' => 'GQ\d{2}\d{23}', // Equatorial Guinea 'GR' => 'GR\d{2}\d{3}\d{4}[\dA-Z]{16}', // Greece 'GT' => 'GT\d{2}[\dA-Z]{4}[\dA-Z]{20}', // Guatemala + 'GW' => 'GW\d{2}[\dA-Z]{2}\d{19}', // Guinea-Bissau + 'HN' => 'HN\d{2}[A-Z]{4}\d{20}', // Honduras 'HR' => 'HR\d{2}\d{7}\d{10}', // Croatia 'HU' => 'HU\d{2}\d{3}\d{4}\d{1}\d{15}\d{1}', // Hungary 'IE' => 'IE\d{2}[A-Z]{4}\d{6}\d{8}', // Ireland 'IL' => 'IL\d{2}\d{3}\d{3}\d{13}', // Israel + 'IM' => 'GB\d{2}[A-Z]{4}\d{6}\d{8}', // United Kingdom + 'IQ' => 'IQ\d{2}[A-Z]{4}\d{3}\d{12}', // Iraq 'IR' => 'IR\d{2}\d{22}', // Iran 'IS' => 'IS\d{2}\d{4}\d{2}\d{6}\d{10}', // Iceland 'IT' => 'IT\d{2}[A-Z]{1}\d{5}\d{5}[\dA-Z]{12}', // Italy + 'JE' => 'GB\d{2}[A-Z]{4}\d{6}\d{8}', // United Kingdom 'JO' => 'JO\d{2}[A-Z]{4}\d{4}[\dA-Z]{18}', // Jordan - 'KW' => 'KW\d{2}[A-Z]{4}\d{22}', // KUWAIT + 'KM' => 'KM\d{2}\d{23}', // Comoros + 'KW' => 'KW\d{2}[A-Z]{4}[\dA-Z]{22}', // Kuwait 'KZ' => 'KZ\d{2}\d{3}[\dA-Z]{13}', // Kazakhstan - 'LB' => 'LB\d{2}\d{4}[\dA-Z]{20}', // LEBANON - 'LI' => 'LI\d{2}\d{5}[\dA-Z]{12}', // Liechtenstein (Principality of) + 'LB' => 'LB\d{2}\d{4}[\dA-Z]{20}', // Lebanon + 'LC' => 'LC\d{2}[A-Z]{4}[\dA-Z]{24}', // Saint Lucia + 'LI' => 'LI\d{2}\d{5}[\dA-Z]{12}', // Liechtenstein 'LT' => 'LT\d{2}\d{5}\d{11}', // Lithuania 'LU' => 'LU\d{2}\d{3}[\dA-Z]{13}', // Luxembourg 'LV' => 'LV\d{2}[A-Z]{4}[\dA-Z]{13}', // Latvia + 'LY' => 'LY\d{2}\d{3}\d{3}\d{15}', // Libya + 'MA' => 'MA\d{2}\d{24}', // Morocco 'MC' => 'MC\d{2}\d{5}\d{5}[\dA-Z]{11}\d{2}', // Monaco 'MD' => 'MD\d{2}[\dA-Z]{2}[\dA-Z]{18}', // Moldova 'ME' => 'ME\d{2}\d{3}\d{13}\d{2}', // Montenegro - 'MF' => 'FR\d{2}\d{5}\d{5}[\dA-Z]{11}\d{2}', // Saint Martin (French part) + 'MF' => 'FR\d{2}\d{5}\d{5}[\dA-Z]{11}\d{2}', // France 'MG' => 'MG\d{2}\d{23}', // Madagascar - 'MK' => 'MK\d{2}\d{3}[\dA-Z]{10}\d{2}', // Macedonia, Former Yugoslav Republic of - 'ML' => 'ML\d{2}[A-Z]{1}\d{23}', // Mali - 'MQ' => 'FR\d{2}\d{5}\d{5}[\dA-Z]{11}\d{2}', // Martinique + 'MK' => 'MK\d{2}\d{3}[\dA-Z]{10}\d{2}', // Macedonia + 'ML' => 'ML\d{2}[\dA-Z]{2}\d{22}', // Mali + 'MQ' => 'FR\d{2}\d{5}\d{5}[\dA-Z]{11}\d{2}', // France 'MR' => 'MR\d{2}\d{5}\d{5}\d{11}\d{2}', // Mauritania 'MT' => 'MT\d{2}[A-Z]{4}\d{5}[\dA-Z]{18}', // Malta 'MU' => 'MU\d{2}[A-Z]{4}\d{2}\d{2}\d{12}\d{3}[A-Z]{3}', // Mauritius 'MZ' => 'MZ\d{2}\d{21}', // Mozambique - 'NC' => 'FR\d{2}\d{5}\d{5}[\dA-Z]{11}\d{2}', // New Caledonia - 'NL' => 'NL\d{2}[A-Z]{4}\d{10}', // The Netherlands + 'NC' => 'FR\d{2}\d{5}\d{5}[\dA-Z]{11}\d{2}', // France + 'NE' => 'NE\d{2}[A-Z]{2}\d{22}', // Niger + 'NI' => 'NI\d{2}[A-Z]{4}\d{24}', // Nicaragua + 'NL' => 'NL\d{2}[A-Z]{4}\d{10}', // Netherlands (The) 'NO' => 'NO\d{2}\d{4}\d{6}\d{1}', // Norway - 'PF' => 'FR\d{2}\d{5}\d{5}[\dA-Z]{11}\d{2}', // French Polynesia + 'PF' => 'FR\d{2}\d{5}\d{5}[\dA-Z]{11}\d{2}', // France 'PK' => 'PK\d{2}[A-Z]{4}[\dA-Z]{16}', // Pakistan 'PL' => 'PL\d{2}\d{8}\d{16}', // Poland - 'PM' => 'FR\d{2}\d{5}\d{5}[\dA-Z]{11}\d{2}', // Saint Pierre et Miquelon + 'PM' => 'FR\d{2}\d{5}\d{5}[\dA-Z]{11}\d{2}', // France 'PS' => 'PS\d{2}[A-Z]{4}[\dA-Z]{21}', // Palestine, State of - 'PT' => 'PT\d{2}\d{4}\d{4}\d{11}\d{2}', // Portugal (plus Azores and Madeira) + 'PT' => 'PT\d{2}\d{4}\d{4}\d{11}\d{2}', // Portugal 'QA' => 'QA\d{2}[A-Z]{4}[\dA-Z]{21}', // Qatar - 'RE' => 'FR\d{2}\d{5}\d{5}[\dA-Z]{11}\d{2}', // Reunion + 'RE' => 'FR\d{2}\d{5}\d{5}[\dA-Z]{11}\d{2}', // France 'RO' => 'RO\d{2}[A-Z]{4}[\dA-Z]{16}', // Romania 'RS' => 'RS\d{2}\d{3}\d{13}\d{2}', // Serbia + 'RU' => 'RU\d{2}\d{9}\d{5}[\dA-Z]{15}', // Russia 'SA' => 'SA\d{2}\d{2}[\dA-Z]{18}', // Saudi Arabia + 'SC' => 'SC\d{2}[A-Z]{4}\d{2}\d{2}\d{16}[A-Z]{3}', // Seychelles + 'SD' => 'SD\d{2}\d{2}\d{12}', // Sudan 'SE' => 'SE\d{2}\d{3}\d{16}\d{1}', // Sweden 'SI' => 'SI\d{2}\d{5}\d{8}\d{2}', // Slovenia - 'SK' => 'SK\d{2}\d{4}\d{6}\d{10}', // Slovak Republic + 'SK' => 'SK\d{2}\d{4}\d{6}\d{10}', // Slovakia 'SM' => 'SM\d{2}[A-Z]{1}\d{5}\d{5}[\dA-Z]{12}', // San Marino - 'SN' => 'SN\d{2}[A-Z]{1}\d{23}', // Senegal - 'TF' => 'FR\d{2}\d{5}\d{5}[\dA-Z]{11}\d{2}', // French Southern Territories + 'SN' => 'SN\d{2}[A-Z]{2}\d{22}', // Senegal + 'SO' => 'SO\d{2}\d{4}\d{3}\d{12}', // Somalia + 'ST' => 'ST\d{2}\d{4}\d{4}\d{11}\d{2}', // Sao Tome and Principe + 'SV' => 'SV\d{2}[A-Z]{4}\d{20}', // El Salvador + 'TD' => 'TD\d{2}\d{23}', // Chad + 'TF' => 'FR\d{2}\d{5}\d{5}[\dA-Z]{11}\d{2}', // France + 'TG' => 'TG\d{2}[A-Z]{2}\d{22}', // Togo 'TL' => 'TL\d{2}\d{3}\d{14}\d{2}', // Timor-Leste 'TN' => 'TN\d{2}\d{2}\d{3}\d{13}\d{2}', // Tunisia - 'TR' => 'TR\d{2}\d{5}[\dA-Z]{1}[\dA-Z]{16}', // Turkey + 'TR' => 'TR\d{2}\d{5}\d{1}[\dA-Z]{16}', // Turkey 'UA' => 'UA\d{2}\d{6}[\dA-Z]{19}', // Ukraine 'VA' => 'VA\d{2}\d{3}\d{15}', // Vatican City State - 'VG' => 'VG\d{2}[A-Z]{4}\d{16}', // Virgin Islands, British - 'WF' => 'FR\d{2}\d{5}\d{5}[\dA-Z]{11}\d{2}', // Wallis and Futuna Islands - 'XK' => 'XK\d{2}\d{4}\d{10}\d{2}', // Republic of Kosovo - 'YT' => 'FR\d{2}\d{5}\d{5}[\dA-Z]{11}\d{2}', // Mayotte + 'VG' => 'VG\d{2}[A-Z]{4}\d{16}', // Virgin Islands + 'WF' => 'FR\d{2}\d{5}\d{5}[\dA-Z]{11}\d{2}', // France + 'XK' => 'XK\d{2}\d{4}\d{10}\d{2}', // Kosovo + 'YT' => 'FR\d{2}\d{5}\d{5}[\dA-Z]{11}\d{2}', // France ]; /** diff --git a/Resources/bin/sync-iban-formats.php b/Resources/bin/sync-iban-formats.php new file mode 100755 index 000000000..fa7ba520c --- /dev/null +++ b/Resources/bin/sync-iban-formats.php @@ -0,0 +1,204 @@ +#!/usr/bin/env php + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +if ('cli' !== \PHP_SAPI) { + throw new \Exception('This script must be run from the command line.'); +} + +/* + * This script syncs IBAN formats from the upstream and updates them into IbanValidator. + * + * Usage: + * php Resources/bin/sync-iban-formats.php + */ + +error_reporting(\E_ALL); + +set_error_handler(static function (int $type, string $msg, string $file, int $line): void { + throw new \ErrorException($msg, 0, $type, $file, $line); +}); + +echo "Collecting IBAN formats...\n"; + +$formats = array_merge( + (new WikipediaIbanProvider())->getIbanFormats(), + (new SwiftRegistryIbanProvider())->getIbanFormats() +); + +printf("Collected %d IBAN formats\n", count($formats)); + +echo "Updating validator...\n"; + +updateValidatorFormats(__DIR__.'/../../Constraints/IbanValidator.php', $formats); + +echo "Done.\n"; + +exit(0); + +function updateValidatorFormats(string $validatorPath, array $formats): void +{ + ksort($formats); + + $formatsContent = "[\n"; + $formatsContent .= " // auto-generated\n"; + + foreach ($formats as $countryCode => [$format, $country]) { + $formatsContent .= " '{$countryCode}' => '{$format}', // {$country}\n"; + } + + $formatsContent .= ' ]'; + + $validatorContent = file_get_contents($validatorPath); + + $validatorContent = preg_replace( + '/FORMATS = \[.*?\];/s', + "FORMATS = {$formatsContent};", + $validatorContent + ); + + file_put_contents($validatorPath, $validatorContent); +} + +final class SwiftRegistryIbanProvider +{ + /** + * @return array + */ + public function getIbanFormats(): array + { + $items = $this->readPropertiesFromRegistry([ + 'Name of country' => 'country', + 'IBAN prefix country code (ISO 3166)' => 'country_code', + 'IBAN structure' => 'iban_structure', + 'Country code includes other countries/territories' => 'included_country_codes', + ]); + + $formats = []; + + foreach ($items as $item) { + $formats[$item['country_code']] = [$this->buildIbanRegexp($item['iban_structure']), $item['country']]; + + foreach ($this->parseCountryCodesList($item['included_country_codes']) as $includedCountryCode) { + $formats[$includedCountryCode] = $formats[$item['country_code']]; + } + } + + return $formats; + } + + /** + * @return list + */ + private function parseCountryCodesList(string $countryCodesList): array + { + if ('N/A' === $countryCodesList) { + return []; + } + + $countryCodes = []; + + foreach (explode(',', $countryCodesList) as $countryCode) { + $countryCodes[] = preg_replace('/^([A-Z]{2})(\s+\(.+?\))?$/', '$1', trim($countryCode)); + } + + return $countryCodes; + } + + /** + * @param array $properties + * + * @return list> + */ + private function readPropertiesFromRegistry(array $properties): array + { + $items = []; + + $registryContent = file_get_contents('https://www.swift.com/swift-resource/11971/download'); + $lines = explode("\n", $registryContent); + + // skip header line + array_shift($lines); + + foreach ($lines as $line) { + $columns = str_getcsv($line, "\t"); + $propertyLabel = array_shift($columns); + + if (!isset($properties[$propertyLabel])) { + continue; + } + + $propertyField = $properties[$propertyLabel]; + + foreach ($columns as $index => $value) { + $items[$index][$propertyField] = $value; + } + } + + return array_values($items); + } + + private function buildIbanRegexp(string $ibanStructure): string + { + $pattern = $ibanStructure; + + $pattern = preg_replace('/(\d+)!n/', '\\d{$1}', $pattern); + $pattern = preg_replace('/(\d+)!a/', '[A-Z]{$1}', $pattern); + $pattern = preg_replace('/(\d+)!c/', '[\\dA-Z]{$1}', $pattern); + + return $pattern; + } +} + +final class WikipediaIbanProvider +{ + /** + * @return array + */ + public function getIbanFormats(): array + { + $formats = []; + + foreach ($this->readIbanFormatsTable() as $item) { + if (!preg_match('/^([A-Z]{2})/', $item['Example'], $matches)) { + continue; + } + + $countryCode = $matches[1]; + + $formats[$countryCode] = [$this->buildIbanRegexp($countryCode, $item['BBAN Format']), $item['Country']]; + } + + return $formats; + } + + /** + * @return list> + */ + private function readIbanFormatsTable(): array + { + $tablesResponse = file_get_contents('https://www.wikitable2json.com/api/International_Bank_Account_Number?table=3&keyRows=1&clearRef=true'); + + return json_decode($tablesResponse, true, 512, JSON_THROW_ON_ERROR)[0]; + } + + private function buildIbanRegexp(string $countryCode, string $bbanFormat): string + { + $pattern = $bbanFormat; + + $pattern = preg_replace('/\s*,\s*/', '', $pattern); + $pattern = preg_replace('/(\d+)n/', '\\d{$1}', $pattern); + $pattern = preg_replace('/(\d+)a/', '[A-Z]{$1}', $pattern); + $pattern = preg_replace('/(\d+)c/', '[\\dA-Z]{$1}', $pattern); + + return $countryCode.'\\d{2}'.$pattern; + } +} diff --git a/Tests/Constraints/IbanValidatorTest.php b/Tests/Constraints/IbanValidatorTest.php index 257f47b03..70994f509 100644 --- a/Tests/Constraints/IbanValidatorTest.php +++ b/Tests/Constraints/IbanValidatorTest.php @@ -116,6 +116,17 @@ public static function getValidIbans() ['AE07 0331 2345 6789 0123 456'], // UAE ['GB12 CPBK 0892 9965 0449 91'], // United Kingdom + ['DJ21 0001 0000 0001 5400 0100 186'], // Djibouti + ['EG38 0019 0005 0000 0000 2631 8000 2'], // Egypt + ['IQ98 NBIQ 8501 2345 6789 012'], // Iraq + ['LC55 HEMM 0001 0001 0012 0012 0002 3015'], // Saint Lucia + ['LY83 0020 4800 0020 1001 2036 1'], // Libya + ['RU02 0445 2560 0407 0281 0412 3456 7890 1'], // Russia + ['SC18 SSCB 1101 0000 0000 0000 1497 USD'], // Seychelles + ['SD21 2901 0501 2340 01'], // Sudan + ['ST23 0002 0000 0289 3557 1014 8'], // Sao Tome and Principe + ['SV62 CENR 0000 0000 0000 0070 0025'], // El Salvador + // Extended country list // http://www.nordea.com/Our+services/International+products+and+services/Cash+Management/IBAN+countries/908462.html // https://www.swift.com/sites/default/files/resources/iban_registry.pdf @@ -126,8 +137,8 @@ public static function getValidIbans() ['BR9700360305000010009795493P1'], // Brazil ['BR1800000000141455123924100C2'], // Brazil ['VG96VPVG0000012345678901'], // British Virgin Islands - ['BF1030134020015400945000643'], // Burkina Faso - ['BI43201011067444'], // Burundi + ['BF42BF0840101300463574000390'], // Burkina Faso + ['BI4210000100010000332045181'], // Burundi ['CM2110003001000500000605306'], // Cameroon ['CV64000300004547069110176'], // Cape Verde ['FR7630007000110009970004942'], // Central African Republic @@ -152,7 +163,7 @@ public static function getValidIbans() ['XK051212012345678906'], // Republic of Kosovo ['PT50000200000163099310355'], // Sao Tome and Principe ['SA0380000000608010167519'], // Saudi Arabia - ['SN12K00100152000025690007542'], // Senegal + ['SN08SN0100152000048500003035'], // Senegal ['TL380080012345678910157'], // Timor-Leste ['TN5914207207100707129648'], // Tunisia ['TR330006100519786457841326'], // Turkey @@ -244,13 +255,13 @@ public static function getIbansWithInvalidFormat() ['BR9700360305000010009795493P11'], // Brazil ['BR1800000000141455123924100C21'], // Brazil ['VG96VPVG00000123456789011'], // British Virgin Islands - ['BF10301340200154009450006431'], // Burkina Faso + ['BF1030134020015400945000643'], // Burkina Faso ['BI432010110674441'], // Burundi ['CM21100030010005000006053061'], // Cameroon ['CV640003000045470691101761'], // Cape Verde ['FR76300070001100099700049421'], // Central African Republic ['CG52300110002021512345678901'], // Congo - ['CR05152020010262840661'], // Costa Rica + ['CR05A52020010262840661'], // Costa Rica ['CR0515202001026284066'], // Costa Rica ['DO28BAGR000000012124536113241'], // Dominican Republic ['GT82TRAJ010200000012100296901'], // Guatemala @@ -357,8 +368,8 @@ public static function getIbansWithValidFormatButIncorrectChecksum() ['BR9700360305000010009795493P2'], // Brazil ['BR1800000000141455123924100C3'], // Brazil ['VG96VPVG0000012345678902'], // British Virgin Islands - ['BF1030134020015400945000644'], // Burkina Faso - ['BI43201011067445'], // Burundi + ['BF41BF0840101300463574000390'], // Burkina Faso + ['BI3210000100010000332045181'], // Burundi ['CM2110003001000500000605307'], // Cameroon ['CV64000300004547069110177'], // Cape Verde ['FR7630007000110009970004943'], // Central African Republic @@ -383,7 +394,7 @@ public static function getIbansWithValidFormatButIncorrectChecksum() ['XK051212012345678907'], // Republic of Kosovo ['PT50000200000163099310356'], // Sao Tome and Principe ['SA0380000000608010167518'], // Saudi Arabia - ['SN12K00100152000025690007543'], // Senegal + ['SN07SN0100152000048500003035'], // Senegal ['TL380080012345678910158'], // Timor-Leste ['TN5914207207100707129649'], // Tunisia ['TR330006100519786457841327'], // Turkey