From ca2596f67961d8d67688d5c59309e1860a8db96a Mon Sep 17 00:00:00 2001 From: Owen Kieffer-Jones Date: Sun, 24 May 2015 19:08:14 +0200 Subject: [PATCH 1/5] Fix IBAN generator --- src/Faker/Provider/Payment.php | 39 +++++++++++++++++----------------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/src/Faker/Provider/Payment.php b/src/Faker/Provider/Payment.php index cadbc48d33..28d29cb06e 100644 --- a/src/Faker/Provider/Payment.php +++ b/src/Faker/Provider/Payment.php @@ -87,7 +87,7 @@ class Payment extends Base 'IL' => array(array('n', 3), array('n', 3), array('n', 13)), 'IS' => array(array('n', 4), array('n', 2), array('n', 6), array('n', 10)), 'IT' => array(array('a', 1), array('n', 5), array('n', 5), array('c', 12)), - 'KW' => array(array('a', 4), array('c', 22)), + 'KW' => array(array('a', 4), array('n', 22)), 'KZ' => array(array('n', 3), array('c', 13)), 'LB' => array(array('n', 4), array('c', 20)), 'LI' => array(array('n', 5), array('c', 12)), @@ -115,7 +115,7 @@ class Payment extends Base 'SK' => array(array('n', 4), array('n', 6), array('n', 10)), 'SM' => array(array('a', 1), array('n', 5), array('n', 5), array('c', 12)), 'TN' => array(array('n', 2), array('n', 3), array('n', 13), array('n', 2)), - 'TR' => array(array('n', 5), array('c', 1), array('c', 16)), + 'TR' => array(array('n', 5), array('n', 1), array('c', 16)), 'VG' => array(array('a', 4), array('n', 16)), ); @@ -213,7 +213,7 @@ public function creditCardDetails($valid = true) protected static function iban($countryCode, $prefix = '', $length = null) { $countryCode = strtoupper($countryCode); - $format = !isset(static::$ibanFormats[$countryCode]) ? array() : static::$ibanFormats[$countryCode]; + $format = !isset(static::$ibanFormats[$countryCode]) ? null : static::$ibanFormats[$countryCode]; if ($length === null) { if ($format === null) { $length = 24; @@ -225,22 +225,19 @@ protected static function iban($countryCode, $prefix = '', $length = null) } } } + if ($format === null) { + return false; + $format = array(array('n', $length)); + } - $result = $prefix; - $length -= strlen($prefix); - $nextPart = array_shift($format); - if ($nextPart !== false) { - list($class, $groupCount) = $nextPart; - } else { - $class = 'n'; - $groupCount = 0; + $expandedFormat = ''; + foreach($format as list($class, $length)) { + $expandedFormat .= str_repeat($class, $length); } - $groupCount = $nextPart === false ? 0 : $nextPart[1]; - for ($i = 0; $i < $length; $i++) { - if ($nextPart !== false && $groupCount-- < 1) { - $nextPart = array_shift($format); - list($class, $groupCount) = $nextPart; - } + + $result = $prefix; + $expandedFormat = substr($expandedFormat, strlen($result)); + foreach (str_split($expandedFormat) as $class) { switch ($class) { default: case 'c': @@ -257,8 +254,12 @@ protected static function iban($countryCode, $prefix = '', $length = null) $result = static::addBankCodeChecksum($result, $countryCode); - $countryNumber = 100 * (ord($countryCode[0])-55) + (ord($countryCode[1])-55); - $tempResult = $result . $countryNumber . '00'; + $tempResult = $result . $countryCode . '00'; + + $tempResult = preg_replace_callback('/[A-Z]/', function($matches) { + return str_pad(ord($matches[0]) - 55, 2, '0', STR_PAD_LEFT); + }, $tempResult); + // perform MOD97-10 checksum calculation $checksum = (int) $tempResult[0]; for ($i = 1, $size = strlen($tempResult); $i < $size; $i++) { From ef11e942d6a163534c62def7fe744e50fde1f066 Mon Sep 17 00:00:00 2001 From: Owen Kieffer-Jones Date: Sun, 24 May 2015 20:21:07 +0200 Subject: [PATCH 2/5] Code style --- src/Faker/Provider/Payment.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Faker/Provider/Payment.php b/src/Faker/Provider/Payment.php index 28d29cb06e..ad764961f6 100644 --- a/src/Faker/Provider/Payment.php +++ b/src/Faker/Provider/Payment.php @@ -231,7 +231,7 @@ protected static function iban($countryCode, $prefix = '', $length = null) } $expandedFormat = ''; - foreach($format as list($class, $length)) { + foreach ($format as list($class, $length)) { $expandedFormat .= str_repeat($class, $length); } @@ -256,7 +256,7 @@ protected static function iban($countryCode, $prefix = '', $length = null) $tempResult = $result . $countryCode . '00'; - $tempResult = preg_replace_callback('/[A-Z]/', function($matches) { + $tempResult = preg_replace_callback('/[A-Z]/', function ($matches) { return str_pad(ord($matches[0]) - 55, 2, '0', STR_PAD_LEFT); }, $tempResult); From b91943660abf482eb60ee4395b0ee4763f08e639 Mon Sep 17 00:00:00 2001 From: Owen Kieffer-Jones Date: Sun, 24 May 2015 20:57:31 +0200 Subject: [PATCH 3/5] Rewrite for PHP 5.3 support --- src/Faker/Provider/Payment.php | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/Faker/Provider/Payment.php b/src/Faker/Provider/Payment.php index ad764961f6..d284fe64d5 100644 --- a/src/Faker/Provider/Payment.php +++ b/src/Faker/Provider/Payment.php @@ -231,7 +231,8 @@ protected static function iban($countryCode, $prefix = '', $length = null) } $expandedFormat = ''; - foreach ($format as list($class, $length)) { + foreach ($format as $item) { + list($class, $length) = $item; $expandedFormat .= str_repeat($class, $length); } @@ -254,11 +255,17 @@ protected static function iban($countryCode, $prefix = '', $length = null) $result = static::addBankCodeChecksum($result, $countryCode); - $tempResult = $result . $countryCode . '00'; + $tempResult = str_split($result . $countryCode . '00'); - $tempResult = preg_replace_callback('/[A-Z]/', function ($matches) { - return str_pad(ord($matches[0]) - 55, 2, '0', STR_PAD_LEFT); - }, $tempResult); + // Convert letters to numbers + foreach ($tempResult as &$letter) { + $num = ord($letter) - 55; + if ($num >= 10 && $num <= 35) { + $letter = str_pad($num, 2, '0', STR_PAD_LEFT); + } + } + unset($letter); + $tempResult = join('', $tempResult); // perform MOD97-10 checksum calculation $checksum = (int) $tempResult[0]; From d0f985fd93eacbce510c4a6baaa73242b89f0f94 Mon Sep 17 00:00:00 2001 From: Owen Kieffer-Jones Date: Sun, 7 Jun 2015 00:37:41 +0200 Subject: [PATCH 4/5] Removed debug code --- src/Faker/Provider/Payment.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Faker/Provider/Payment.php b/src/Faker/Provider/Payment.php index d284fe64d5..9193cf5991 100644 --- a/src/Faker/Provider/Payment.php +++ b/src/Faker/Provider/Payment.php @@ -226,7 +226,6 @@ protected static function iban($countryCode, $prefix = '', $length = null) } } if ($format === null) { - return false; $format = array(array('n', $length)); } From 9a2721ee214dac5dadc72a214521b87866af571b Mon Sep 17 00:00:00 2001 From: Owen Kieffer-Jones Date: Sat, 13 Jun 2015 12:08:23 +0200 Subject: [PATCH 5/5] Refactor IBAN checksum to calculator class, Add unit tests --- src/Faker/Calculator/Iban.php | 67 ++++++ src/Faker/Provider/Payment.php | 23 +-- test/Faker/Calculator/IbanTest.php | 305 ++++++++++++++++++++++++++++ test/Faker/Provider/PaymentTest.php | 110 ++++++++++ 4 files changed, 484 insertions(+), 21 deletions(-) create mode 100644 src/Faker/Calculator/Iban.php create mode 100644 test/Faker/Calculator/IbanTest.php diff --git a/src/Faker/Calculator/Iban.php b/src/Faker/Calculator/Iban.php new file mode 100644 index 0000000000..1c11afa54c --- /dev/null +++ b/src/Faker/Calculator/Iban.php @@ -0,0 +1,67 @@ += 10 && $num <= 35) { - $letter = str_pad($num, 2, '0', STR_PAD_LEFT); - } - } - unset($letter); - $tempResult = join('', $tempResult); - - // perform MOD97-10 checksum calculation - $checksum = (int) $tempResult[0]; - for ($i = 1, $size = strlen($tempResult); $i < $size; $i++) { - $checksum = (10 * $checksum + (int) $tempResult[$i]) % 97; - } - $checksum = 98 - $checksum; - if ($checksum < 10) { - $checksum = '0'.$checksum; - } + $checksum = Iban::checksum($countryCode . '00' . $result); return $countryCode . $checksum . $result; } diff --git a/test/Faker/Calculator/IbanTest.php b/test/Faker/Calculator/IbanTest.php new file mode 100644 index 0000000000..12fe8e6d2d --- /dev/null +++ b/test/Faker/Calculator/IbanTest.php @@ -0,0 +1,305 @@ +assertEquals($checksum, Iban::checksum($iban), $iban); + } + + public function validatorProvider() + { + return array( + array('AL47212110090000000235698741', true), + array('AD1200012030200359100100', true), + array('AT611904300234573201', true), + array('AZ21NABZ00000000137010001944', true), + array('BH67BMAG00001299123456', true), + array('BE68539007547034', true), + array('BA391290079401028494', true), + array('BR7724891749412660603618210F3', true), + array('BG80BNBG96611020345678', true), + array('CR0515202001026284066', true), + array('HR1210010051863000160', true), + array('CY17002001280000001200527600', true), + array('CZ6508000000192000145399', true), + array('DK5000400440116243', true), + array('DO28BAGR00000001212453611324', true), + array('EE382200221020145685', true), + array('FO6264600001631634', true), + array('FI2112345600000785', true), + array('FR1420041010050500013M02606', true), + array('GE29NB0000000101904917', true), + array('DE89370400440532013000', true), + array('GI75NWBK000000007099453', true), + array('GR1601101250000000012300695', true), + array('GL8964710001000206', true), + array('GT82TRAJ01020000001210029690', true), + array('HU42117730161111101800000000', true), + array('IS140159260076545510730339', true), + array('IE29AIBK93115212345678', true), + array('IL620108000000099999999', true), + array('IT60X0542811101000000123456', true), + array('KZ86125KZT5004100100', true), + array('KW81CBKU0000000000001234560101', true), + array('LV80BANK0000435195001', true), + array('LB62099900000001001901229114', true), + array('LI21088100002324013AA', true), + array('LT121000011101001000', true), + array('LU280019400644750000', true), + array('MK07250120000058984', true), + array('MT84MALT011000012345MTLCAST001S', true), + array('MR1300020001010000123456753', true), + array('MU17BOMM0101101030300200000MUR', true), + array('MD24AG000225100013104168', true), + array('MC5811222000010123456789030', true), + array('ME25505000012345678951', true), + array('NL91ABNA0417164300', true), + array('NO9386011117947', true), + array('PK36SCBL0000001123456702', true), + array('PL61109010140000071219812874', true), + array('PS92PALS000000000400123456702', true), + array('PT50000201231234567890154', true), + array('QA58DOHB00001234567890ABCDEFG', true), + array('RO49AAAA1B31007593840000', true), + array('SM86U0322509800000000270100', true), + array('SA0380000000608010167519', true), + array('RS35260005601001611379', true), + array('SK3112000000198742637541', true), + array('SI56263300012039086', true), + array('ES9121000418450200051332', true), + array('SE4550000000058398257466', true), + array('CH9300762011623852957', true), + array('TN5910006035183598478831', true), + array('TR330006100519786457841326', true), + array('AE070331234567890123456', true), + array('GB29NWBK60161331926819', true), + array('VG96VPVG0000012345678901', true), + array('YY24KIHB12476423125915947930915268', true), + array('ZZ25VLQT382332233206588011313776421', true), + + + array('AL4721211009000000023569874', false), + array('AD120001203020035910010', false), + array('AT61190430023457320', false), + array('AZ21NABZ0000000013701000194', false), + array('BH67BMAG0000129912345', false), + array('BE6853900754703', false), + array('BA39129007940102849', false), + array('BR7724891749412660603618210F', false), + array('BG80BNBG9661102034567', false), + array('CR051520200102628406', false), + array('HR121001005186300016', false), + array('CY1700200128000000120052760', false), + array('CZ650800000019200014539', false), + array('DK500040044011624', false), + array('DO28BAGR0000000121245361132', false), + array('EE38220022102014568', false), + array('FO626460000163163', false), + array('FI2112345600000780', false), + array('FR1420041010050500013M0260', false), + array('GE29NB000000010190491', false), + array('DE8937040044053201300', false), + array('GI75NWBK00000000709945', false), + array('GR160110125000000001230069', false), + array('GL896471000100020', false), + array('GT82TRAJ0102000000121002969', false), + array('HU4211773016111110180000000', false), + array('IS14015926007654551073033', false), + array('IE29AIBK9311521234567', false), + array('IL62010800000009999999', false), + array('IT60X054281110100000012345', false), + array('KZ86125KZT500410010', false), + array('KW81CBKU000000000000123456010', false), + array('LV80BANK000043519500', false), + array('LB6209990000000100190122911', false), + array('LI21088100002324013A', false), + array('LT12100001110100100', false), + array('LU28001940064475000', false), + array('MK0725012000005898', false), + array('MT84MALT011000012345MTLCAST001', false), + array('MR130002000101000012345675', false), + array('MU17BOMM0101101030300200000MU', false), + array('MD24AG00022510001310416', false), + array('MC58112220000101234567890', false), + array('ME2550500001234567895', false), + array('NL91ABNA041716430', false), + array('NO938601111794', false), + array('PK36SCBL000000112345670', false), + array('PL6110901014000007121981287', false), + array('PS92PALS00000000040012345670', false), + array('PT5000020123123456789015', false), + array('QA58DOHB00001234567890ABCDEF', false), + array('RO49AAAA1B3100759384000', false), + array('SM86U032250980000000027010', false), + array('SA038000000060801016751', false), + array('RS3526000560100161137', false), + array('SK311200000019874263754', false), + array('SI5626330001203908', false), + array('ES912100041845020005133', false), + array('SE455000000005839825746', false), + array('CH930076201162385295', false), + array('TN591000603518359847883', false), + array('TR33000610051978645784132', false), + array('AE07033123456789012345', false), + array('GB29NWBK6016133192681', false), + array('VG96VPVG000001234567890', false), + array('YY24KIHB1247642312591594793091526', false), + array('ZZ25VLQT38233223320658801131377642', false), + ); + } + + /** + * @dataProvider validatorProvider + */ + public function testIsValid($iban, $isValid) + { + $this->assertEquals($isValid, Iban::isValid($iban), $iban); + } + + public function alphaToNumberProvider() + { + return array( + array('A', 10), + array('B', 11), + array('C', 12), + array('D', 13), + array('E', 14), + array('F', 15), + array('G', 16), + array('H', 17), + array('I', 18), + array('J', 19), + array('K', 20), + array('L', 21), + array('M', 22), + array('N', 23), + array('O', 24), + array('P', 25), + array('Q', 26), + array('R', 27), + array('S', 28), + array('T', 29), + array('U', 30), + array('V', 31), + array('W', 32), + array('X', 33), + array('Y', 34), + array('Z', 35), + ); + } + + /** + * @dataProvider alphaToNumberProvider + */ + public function testAlphaToNumber($letter, $number) + { + $this->assertEquals($number, Iban::alphaToNumber($letter), $letter); + } + + public function mod97Provider() + { + // Large numbers + $return = array( + array('123456789123456789', 7), + array('111222333444555666', 73), + array('4242424242424242424242', 19), + array('271828182845904523536028', 68), + ); + + // 0-200 + for ($i = 0; $i < 200; $i++) { + $return[] = array((string)$i, $i % 97); + } + + return $return; + } + /** + * @dataProvider mod97Provider + */ + public function testMod97($number, $result) + { + $this->assertEquals($result, Iban::mod97($number), $number); + } +} diff --git a/test/Faker/Provider/PaymentTest.php b/test/Faker/Provider/PaymentTest.php index 0f20d91f36..f7c63b8695 100644 --- a/test/Faker/Provider/PaymentTest.php +++ b/test/Faker/Provider/PaymentTest.php @@ -2,6 +2,7 @@ namespace Faker\Test\Provider; +use Faker\Calculator\Iban; use Faker\Calculator\Luhn; use Faker\Generator; use Faker\Provider\Base as BaseProvider; @@ -23,6 +24,27 @@ public function setUp() $this->faker = $faker; } + public function localeDataProvider() + { + $providerPath = realpath(__DIR__ . '/../../../src/Faker/Provider'); + $localePaths = array_filter(glob($providerPath . '/*', GLOB_ONLYDIR)); + foreach ($localePaths as $path) { + $parts = explode('/', $path); + $locales[] = array($parts[count($parts) - 1]); + } + + return $locales; + } + + public function loadLocalProviders($locale) + { + $providerPath = realpath(__DIR__ . '/../../../src/Faker/Provider'); + if (file_exists($providerPath.'/'.$locale.'/Payment.php')) { + $payment = "\\Faker\\Provider\\$locale\\Payment"; + $this->faker->addProvider(new $payment($this->faker)); + } + } + public function testCreditCardTypeReturnsValidVendorName() { $this->assertTrue(in_array($this->faker->creditCardType, array('Visa', 'MasterCard', 'American Express', 'Discover Card'))); @@ -65,4 +87,92 @@ public function testRandomCard() $this->assertEquals(count($cardDetails), 4); $this->assertEquals(array('type', 'number', 'name', 'expirationDate'), array_keys($cardDetails)); } + + protected $ibanFormats = array( + 'AD' => '/^AD\d{2}\d{4}\d{4}[A-Z0-9]{12}$/', + 'AE' => '/^AE\d{2}\d{3}\d{16}$/', + 'AL' => '/^AL\d{2}\d{8}[A-Z0-9]{16}$/', + 'AT' => '/^AT\d{2}\d{5}\d{11}$/', + 'AZ' => '/^AZ\d{2}[A-Z]{4}[A-Z0-9]{20}$/', + 'BA' => '/^BA\d{2}\d{3}\d{3}\d{8}\d{2}$/', + 'BE' => '/^BE\d{2}\d{3}\d{7}\d{2}$/', + 'BG' => '/^BG\d{2}[A-Z]{4}\d{4}\d{2}[A-Z0-9]{8}$/', + 'BH' => '/^BH\d{2}[A-Z]{4}[A-Z0-9]{14}$/', + 'BR' => '/^BR\d{2}\d{8}\d{5}\d{10}[A-Z]{1}[A-Z0-9]{1}$/', + 'CH' => '/^CH\d{2}\d{5}[A-Z0-9]{12}$/', + 'CR' => '/^CR\d{2}\d{3}\d{14}$/', + 'CY' => '/^CY\d{2}\d{3}\d{5}[A-Z0-9]{16}$/', + 'CZ' => '/^CZ\d{2}\d{4}\d{6}\d{10}$/', + 'DE' => '/^DE\d{2}\d{8}\d{10}$/', + 'DK' => '/^DK\d{2}\d{4}\d{9}\d{1}$/', + 'DO' => '/^DO\d{2}[A-Z0-9]{4}\d{20}$/', + 'EE' => '/^EE\d{2}\d{2}\d{2}\d{11}\d{1}$/', + 'ES' => '/^ES\d{2}\d{4}\d{4}\d{1}\d{1}\d{10}$/', + 'FR' => '/^FR\d{2}\d{5}\d{5}[A-Z0-9]{11}\d{2}$/', + 'GB' => '/^GB\d{2}[A-Z]{4}\d{6}\d{8}$/', + 'GE' => '/^GE\d{2}[A-Z]{2}\d{16}$/', + 'GI' => '/^GI\d{2}[A-Z]{4}[A-Z0-9]{15}$/', + 'GR' => '/^GR\d{2}\d{3}\d{4}[A-Z0-9]{16}$/', + 'GT' => '/^GT\d{2}[A-Z0-9]{4}[A-Z0-9]{20}$/', + 'HR' => '/^HR\d{2}\d{7}\d{10}$/', + 'HU' => '/^HU\d{2}\d{3}\d{4}\d{1}\d{15}\d{1}$/', + 'IE' => '/^IE\d{2}[A-Z]{4}\d{6}\d{8}$/', + 'IL' => '/^IL\d{2}\d{3}\d{3}\d{13}$/', + 'IS' => '/^IS\d{2}\d{4}\d{2}\d{6}\d{10}$/', + 'IT' => '/^IT\d{2}[A-Z]{1}\d{5}\d{5}[A-Z0-9]{12}$/', + 'KW' => '/^KW\d{2}[A-Z]{4}\d{22}$/', + 'KZ' => '/^KZ\d{2}\d{3}[A-Z0-9]{13}$/', + 'LB' => '/^LB\d{2}\d{4}[A-Z0-9]{20}$/', + 'LI' => '/^LI\d{2}\d{5}[A-Z0-9]{12}$/', + 'LT' => '/^LT\d{2}\d{5}\d{11}$/', + 'LU' => '/^LU\d{2}\d{3}[A-Z0-9]{13}$/', + 'LV' => '/^LV\d{2}[A-Z]{4}[A-Z0-9]{13}$/', + 'MC' => '/^MC\d{2}\d{5}\d{5}[A-Z0-9]{11}\d{2}$/', + 'MD' => '/^MD\d{2}[A-Z0-9]{2}[A-Z0-9]{18}$/', + 'ME' => '/^ME\d{2}\d{3}\d{13}\d{2}$/', + 'MK' => '/^MK\d{2}\d{3}[A-Z0-9]{10}\d{2}$/', + 'MR' => '/^MR\d{2}\d{5}\d{5}\d{11}\d{2}$/', + 'MT' => '/^MT\d{2}[A-Z]{4}\d{5}[A-Z0-9]{18}$/', + 'MU' => '/^MU\d{2}[A-Z]{4}\d{2}\d{2}\d{12}\d{3}[A-Z]{3}$/', + 'NL' => '/^NL\d{2}[A-Z]{4}\d{10}$/', + 'NO' => '/^NO\d{2}\d{4}\d{6}\d{1}$/', + 'PK' => '/^PK\d{2}[A-Z]{4}[A-Z0-9]{16}$/', + 'PL' => '/^PL\d{2}\d{8}\d{16}$/', + 'PS' => '/^PS\d{2}[A-Z]{4}[A-Z0-9]{21}$/', + 'PT' => '/^PT\d{2}\d{4}\d{4}\d{11}\d{2}$/', + 'RO' => '/^RO\d{2}[A-Z]{4}[A-Z0-9]{16}$/', + 'RS' => '/^RS\d{2}\d{3}\d{13}\d{2}$/', + 'SA' => '/^SA\d{2}\d{2}[A-Z0-9]{18}$/', + 'SE' => '/^SE\d{2}\d{3}\d{16}\d{1}$/', + 'SI' => '/^SI\d{2}\d{5}\d{8}\d{2}$/', + 'SK' => '/^SK\d{2}\d{4}\d{6}\d{10}$/', + 'SM' => '/^SM\d{2}[A-Z]{1}\d{5}\d{5}[A-Z0-9]{12}$/', + 'TN' => '/^TN\d{2}\d{2}\d{3}\d{13}\d{2}$/', + 'TR' => '/^TR\d{2}\d{5}\d{1}[A-Z0-9]{16}$/', + 'VG' => '/^VG\d{2}[A-Z]{4}\d{16}$/', + ); + + /** + * @dataProvider localeDataProvider + */ + public function testIban($locale) + { + $parts = explode('_', $locale); + $countryCode = array_pop($parts); + + $this->loadLocalProviders($locale); + + try { + $iban = $this->faker->bankAccountNumber; + } catch (\InvalidArgumentException $e) { + // Not implemented, nothing to test + return; + } + + // Test format + $this->assertRegExp($this->ibanFormats[$countryCode], $iban); + + // Test checksum + $this->assertTrue(Iban::isValid($iban), "Checksum for $iban is invalid"); + } }