diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7a8a0ad --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +composer.phar +composer.lock +vendor diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..45a9d0f --- /dev/null +++ b/composer.json @@ -0,0 +1,22 @@ +{ + "name": "ariselseng/norwegianbanks", + "autoload": { + "psr-4": { + "Ariselseng\\NorwegianBanks\\": "src/" + } + }, + "require": { + "phpoffice/phpspreadsheet": "^1.9", + "komakino/modulus11": "^1.0", + "desarrolla2/cache": "^3.0", + "guzzlehttp/guzzle": "^6.5" + }, + "require-dev": { + "phpunit/phpunit": "4.*" + }, + "autoload-dev": { + "psr-4": { + "Ariselseng\\NorwegianBanks\\Tests\\": "tests/" + } + } +} diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 0000000..b423a7e --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,19 @@ + + + + + ./tests/ + + + diff --git a/src/NorwegianBank.php b/src/NorwegianBank.php new file mode 100644 index 0000000..2bf939b --- /dev/null +++ b/src/NorwegianBank.php @@ -0,0 +1,21 @@ +bankCode = $bankCode; + $this->bankName = $bankName; + $this->prefixes = $prefixes; + } + public function addPrefix(string $prefix) { + $this->prefixes[] = $prefix; + } +} diff --git a/src/NorwegianBanks.php b/src/NorwegianBanks.php new file mode 100644 index 0000000..5e98cd6 --- /dev/null +++ b/src/NorwegianBanks.php @@ -0,0 +1,139 @@ +xlsFilePath = sys_get_temp_dir() . '/.cache-norwegianbanks-norwegian-iban-bic-table.xls'; + $this->processData(); + } + + private function downloadData() + { + + $xlsFileExists = file_exists($this->xlsFilePath); + if ($xlsFileExists && filemtime($this->xlsFilePath) <= (time() - self::xlsFileTtl)) { + $headers = [ + 'If-Modified-Since' => gmdate('D, d M Y H:i:s T', filemtime($this->xlsFilePath)), + ]; + } else if (!$xlsFileExists) { + $headers = null; + + } else { + return; + } + + $response = (new \GuzzleHttp\Client())->get(self::XlsFileUrl, [ + 'headers' => $headers, + ]); + + if ($response->getStatusCode() === 200 && $response->getBody()->getSize() > 0) { + file_put_contents($this->xlsFilePath, $response->getBody()->getContents(), LOCK_EX); + } + } + + private function processData() + { + $this->downloadData(); + $cacheDir = sys_get_temp_dir() . '/phpcache-norwegianbanks-' . filemtime($this->xlsFilePath); + $hasCacheDir = file_exists($cacheDir); + if (!$hasCacheDir) { + $hasCacheDir = mkdir($cacheDir); + } + + if ($hasCacheDir) { + $cache = new FileCache($cacheDir); + $banks = $cache->get('banks'); + } + + if (!$hasCacheDir || !isset($banks)) { + $banks = []; + $prefixToBankCode = []; + $reader = \PhpOffice\PhpSpreadsheet\IOFactory::createReader("Xls"); + $reader->setReadDataOnly(true); + + $spreadsheet = $reader->load($this->xlsFilePath); + $worksheet = $spreadsheet->getActiveSheet(); + $rows = $worksheet->toArray(null, false, false, false); + + for ($i = 0; $i < count($rows); $i++) { + if (is_null($rows[$i][1])) { + continue; + } + if (!isset($banks[$rows[$i][1]])) { + $banks[$rows[$i][1]] = new NorwegianBank($rows[$i][1], $rows[$i][2], [$rows[$i][0]]); + } else { + $banks[$rows[$i][1]]->addPrefix($rows[$i][0]); + } + $prefixToBankCode[$rows[$i][0]] = $rows[$i][1]; + } + + if ($hasCacheDir) { + $cache->set('banks', $banks, self::xlsFileTtl); + $cache->set('prefixToBankCode', $prefixToBankCode, self::xlsFileTtl); + } + + $this->banks = $banks; + $this->prefixToBankCode = $prefixToBankCode; + } else { + $this->banks = $banks; + $this->prefixToBankCode = $cache->get('prefixToBankCode'); + } + } + public function getBankCodeByPrefix(string $prefix) + { + + if (!isset($this->prefixToBankCode[$prefix])) { + return null; + } + return $this->prefixToBankCode[$prefix]; + } + + public function getBankByAccountNumber(string $account) + { + $prefix = substr($account, 0, 4); + $bankCode = $this->getBankCodeByPrefix($prefix); + if (is_null($bankCode)) { + return null; + } + return $this->banks[$this->getBankCodeByPrefix($prefix)]; + } + + public function getFormattedAccountNumber(string $unformattedAccount, string $delimiter = '.') + { + $onlyDigits = preg_replace('/[^0-9]/', '', $unformattedAccount); + return substr($onlyDigits, 0, 4) . $delimiter . substr($onlyDigits, 4, 2) . $delimiter . substr($onlyDigits, 6); + } + + public function validateAccountNumber(string $account) + { + $mod11 = Modulus11::validate($account); + + if (!$mod11) { + return false; + } + + return !is_null($this->getBankByAccountNumber(substr($account, 0, 4))); + } +} + +class NorwegianBanksStatic +{ + public static function __callStatic($method, $args) + { + $obj = new NorwegianBanks(); + return $obj->$method(...$args); + } +} diff --git a/tests/NorwegianBanksTest.php b/tests/NorwegianBanksTest.php new file mode 100644 index 0000000..5a4126e --- /dev/null +++ b/tests/NorwegianBanksTest.php @@ -0,0 +1,78 @@ + 'DNBANOKK', + 'number' => '1594 22 87248' + ], + [ + 'bankCode' => 'NDEANOKK', + 'number' => '61050659274' + ], + [ + 'bankCode' => 'SPSONO22', + 'number' => '3000.27.79419' + ], + ]; + protected $notRealAccountNumber = '1234.56.78903'; + protected $notRealAccountNumberWithSpaces = '1234 56 78903'; + protected $notRealAccountNumberUnformatted = '12345678903'; + + private $norwegianBanks; + + public function __construct() + { + $this->norwegianBanks = new NorwegianBanks(); + } + + public function testGetFormattedAccountNumber() + { + $this->assertEquals($this->notRealAccountNumber, $this->norwegianBanks->getFormattedAccountNumber($this->notRealAccountNumberUnformatted)); + $this->assertEquals($this->notRealAccountNumber, NorwegianBanksStatic::getFormattedAccountNumber($this->notRealAccountNumberUnformatted)); + $this->assertEquals($this->notRealAccountNumberWithSpaces, $this->norwegianBanks->getFormattedAccountNumber($this->notRealAccountNumberUnformatted, ' ')); + $this->assertEquals($this->notRealAccountNumberWithSpaces, NorwegianBanksStatic::getFormattedAccountNumber($this->notRealAccountNumberUnformatted, ' ')); + } + + public function testGetBankCodeByPrefix() + { + foreach ($this->accounts as $account) { + $this->assertEquals($account['bankCode'], $this->norwegianBanks->getBankCodeByPrefix(substr($account['number'], 0, 4))); + $this->assertEquals($account['bankCode'], NorwegianBanksStatic::getBankCodeByPrefix(substr($account['number'], 0, 4))); + } + $this->assertEquals(null, $this->norwegianBanks->getBankCodeByPrefix('0000')); + $this->assertEquals(null, NorwegianBanksStatic::getBankCodeByPrefix('0000')); + } + + public function testGetBankByAccountNumber() + { + + foreach ($this->accounts as $account) { + $this->assertAttributeEquals($account['bankCode'], 'bankCode', $this->norwegianBanks->getBankByAccountNumber($account['number'])); + $this->assertAttributeEquals($account['bankCode'], 'bankCode', NorwegianBanksStatic::getBankByAccountNumber($account['number'])); + } + + $this->assertEquals(null, $this->norwegianBanks->getBankByAccountNumber($this->notRealAccountNumber)); + $this->assertEquals(null, NorwegianBanksStatic::getBankByAccountNumber($this->notRealAccountNumber)); + } + + public function testValidate() + { + + foreach ($this->accounts as $account) { + $this->assertTrue($this->norwegianBanks->validateAccountNumber($account['number'])); + $this->assertTrue(NorwegianBanksStatic::validateAccountNumber($account['number'])); + + } + + $this->assertFalse($this->norwegianBanks->validateAccountNumber($this->notRealAccountNumber)); + $this->assertFalse(NorwegianBanksStatic::validateAccountNumber($this->notRealAccountNumber)); + + } +}