Skip to content

Commit

Permalink
Created Hungarian IBAN adapter and validator
Browse files Browse the repository at this point in the history
  • Loading branch information
hubipe committed Apr 1, 2022
1 parent 409a387 commit d44893c
Show file tree
Hide file tree
Showing 5 changed files with 255 additions and 1 deletion.
81 changes: 81 additions & 0 deletions src/Iban/HungarianIbanAdapter.php
@@ -0,0 +1,81 @@
<?php

namespace Rikudou\Iban\Iban;

use InvalidArgumentException;
use Rikudou\Iban\Helper\ToStringIbanTrait;
use Rikudou\Iban\Helper\Utils;
use Rikudou\Iban\Validator\CompoundValidator;
use Rikudou\Iban\Validator\GenericIbanValidator;
use Rikudou\Iban\Validator\HungarianIbanValidator;
use Rikudou\Iban\Validator\ValidatorInterface;

class HungarianIbanAdapter implements IbanInterface
{
use ToStringIbanTrait;

/**
* @var string
*/
private $account;

/**
* @var string|NULL
*/
private $iban = null;

public function __construct(string $account)
{
$this->account = $account;
}

/**
* Returns the resulting IBAN.
*
* @return string
*/
public function asString(): string
{
if ($this->iban === null) {
$accountNumber = strtoupper((string) preg_replace('/[\s\-]+/', '', $this->account));

if (!in_array(strlen($accountNumber), [16, 24])) {
throw new InvalidArgumentException('Account number length is not valid. It either has to consists of 16 or 24 numbers.');
}

$accountNumber = str_pad($accountNumber, 24, '0');

$checkString = (string) preg_replace_callback(['/[A-Z]/', '/^[0]+/'], function ($matches) {
if (substr($matches[0], 0, 1) !== '0') { // may be multiple leading 0's
return base_convert($matches[0], 36, 10);
}

return '';
}, $accountNumber . 'HU00');

$mod = (int) Utils::bcmod($checkString, '97');
$code = (string) (98 - $mod);

$this->iban = sprintf(
'HU%s%s',
str_pad($code, 2, '0', STR_PAD_LEFT),
$accountNumber
);
}

return $this->iban;
}

/**
* Returns the validator that checks whether the IBAN is valid.
*
* @return ValidatorInterface|NULL
*/
public function getValidator(): ?ValidatorInterface
{
return new CompoundValidator(
new HungarianIbanValidator($this->account),
new GenericIbanValidator($this)
);
}
}
60 changes: 60 additions & 0 deletions src/Validator/HungarianIbanValidator.php
@@ -0,0 +1,60 @@
<?php

namespace Rikudou\Iban\Validator;

/**
* @see https://www.ecbs.org/Download/Tr201v3.9.pdf
*/
class HungarianIbanValidator implements ValidatorInterface
{
private const WEIGHTS = [
9,
7,
3,
1,
];

/** @var string */
private $accountNumber;

public function __construct(string $accountNumber)
{
$this->accountNumber = (string) preg_replace('/[\s\-]+/', '', $accountNumber);
}

public function isValid(): bool
{
if (!in_array(strlen($this->accountNumber), [16, 24])) {
return false;
}

$fullAccountNumber = str_pad($this->accountNumber, 24, '0');

$bankBranchPart = substr($fullAccountNumber, 0, 8);
$accountNumberPart = substr($fullAccountNumber, 8);

return $this->checkGroup($bankBranchPart)
&& $this->checkGroup($accountNumberPart);
}

private function checkGroup(string $group): bool
{
$length = strlen($group) - 1;
$expectedChecksum = (int) substr($group, $length, 1);

$sum = 0;
for ($i = 0; $i < $length; $i++) {
$weight = self::WEIGHTS[$i % count(self::WEIGHTS)];
$sum += (int) $group[$i] * $weight;
}

$lastDigit = $sum % 10;
if ($lastDigit === 0) {
$lastDigit = 10;
}

$actualChecksum = 10 - $lastDigit;

return $actualChecksum === $expectedChecksum;
}
}
50 changes: 50 additions & 0 deletions tests/HungarianIbanAdapterTest.php
@@ -0,0 +1,50 @@
<?php

namespace Rikudou\Iban\Tests;

use PHPUnit\Framework\TestCase;
use Rikudou\Iban\Iban\HungarianIbanAdapter;
use Rikudou\Iban\Validator\ValidatorInterface;

class HungarianIbanAdapterTest extends TestCase
{
public function testAccountsWithoutPrefix()
{
$accounts = [
// same account, different formats
'11773016-11111018' => 'HU42 1177 3016 1111 1018 0000 0000',
'11773016 11111018' => 'HU42 1177 3016 1111 1018 0000 0000',
'1177301611111018' => 'HU42 1177 3016 1111 1018 0000 0000',
'11773016-11111018-00000000' => 'HU42 1177 3016 1111 1018 0000 0000',
'11773016 11111018 00000000' => 'HU42 1177 3016 1111 1018 0000 0000',
'117730161111101800000000' => 'HU42 1177 3016 1111 1018 0000 0000',

'12092309-80000008' => 'HU43 1209 2309 8000 0008 0000 0000',
'12092309-80000008-00000000' => 'HU43 1209 2309 8000 0008 0000 0000',
'12092309-00582130-00400001' => 'HU49 1209 2309 0058 2130 0040 0001',
'10918001-00000117-21150000' => 'HU38 1091 8001 0000 0117 2115 0000',
];

foreach ($accounts as $account => $iban) {
$iban = str_replace(' ', '', $iban);

$this->assertEquals($iban, $this->getAdapter($account)->asString());
$this->assertEquals($iban, strval($this->getAdapter($account)));
}
}

public function testGetValidator()
{
$this->assertInstanceOf(ValidatorInterface::class, $this->getAdapter(1325090010, 3030)->getValidator());
}

/**
* @param string $account
*
* @return HungarianIbanAdapter
*/
private function getAdapter(string $account): HungarianIbanAdapter
{
return new HungarianIbanAdapter($account);
}
}
63 changes: 63 additions & 0 deletions tests/HungarianIbanValidatorTest.php
@@ -0,0 +1,63 @@
<?php

namespace Rikudou\Iban\Tests;

use PHPUnit\Framework\TestCase;
use Rikudou\Iban\Validator\HungarianIbanValidator;

class HungarianIbanValidatorTest extends TestCase
{
public function testIsValid()
{
foreach ($this->getAccounts() as $account) {
$this->assertEquals($account['valid'], (new HungarianIbanValidator($account['account']))->isValid());
}
}

private function getAccounts()
{
return [
// short account
[
'account' => '11773016-11111018',
'valid' => true,
],
[
'account' => '12092309-80000008',
'valid' => true,
],
// long account
[
'account' => '11773016-11111018-00000000',
'valid' => true,
],
[
'account' => '12092309-00582130-00400001',
'valid' => true,
],
[
'account' => '10918001-00000117-21150000',
'valid' => true,
],
// long account, spaces instead of dashes
[
'account' => '11773016 11111018 00000000',
'valid' => true,
],
// random invalid account
[
'account' => '12345678-00000005-87654321',
'valid' => false,
],
// wrong length
[
'account' => '11773016-11111018-0000001',
'valid' => false,
],
[
'account' => '11773016-11111018-000000015',
'valid' => false,
],
];
}
}
2 changes: 1 addition & 1 deletion tests/UtilsTest.php
Expand Up @@ -44,7 +44,7 @@ public function testBcmodNegativeNumber()

public function testBcmodLeadingZero()
{
for($i = 0; $i < 3; $i++) {
for ($i = 0; $i < 3; $i++) {
$this->assertEquals('1', Utils::bcmod('011', '02', 2 << $i));
}
}
Expand Down

0 comments on commit d44893c

Please sign in to comment.