-
-
Notifications
You must be signed in to change notification settings - Fork 60
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Add the support for SWIFT Business Identifier Code (BIC) format
Signed-off-by: codisart <[email protected]>
- Loading branch information
Showing
3 changed files
with
237 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
# BIC Validator | ||
|
||
`Laminas\Validator\BusinessIdentifierCode` validates if a given value | ||
**could be a ["Business Identifier Code" (BIC)](https://www.swift.com/standards/data-standards/bic)** | ||
as defined by [ISO 9362](https://wikipedia.org/wiki/ISO_9362). | ||
A BIC is a unique identification code for financial and non-financial institutions. | ||
|
||
## Supported options | ||
|
||
There are no additional supported options for the `BusinessIdentifierCode` validator. | ||
|
||
## BIC validation | ||
|
||
BICs should be a string which length should be equal to 8 or 11. | ||
* The 4 first characters can only be letters and it is used to identify a bank or an institution. | ||
* The following 2 characters can only be letters too and it should be a country code assigned within ISO 3166-1 alpha-2. | ||
The only exception is the code 'XK' used for the Republic of Kosovo. | ||
* The following 2 characters can be letters or digits. It is used to represent a location (like a city) | ||
* The last 3 characters are optional and can be letters or digits. It can represent a branche office. The code 'XXX' is | ||
often used to represent the ain office when the 11 characters code is used. | ||
|
||
## Basic usage | ||
```php | ||
$validator = new Laminas\Validator\BusinessIdentifierCode(); | ||
|
||
if ($validator->isValid('DEUTDEFF')) { | ||
// bic appears to be valid | ||
} else { | ||
// bic is invalid; print the reasons | ||
} | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
<?php | ||
|
||
/** | ||
* @see https://github.com/laminas/laminas-validator for the canonical source repository | ||
* @copyright https://github.com/laminas/laminas-validator/blob/master/COPYRIGHT.md | ||
* @license https://github.com/laminas/laminas-validator/blob/master/LICENSE.md New BSD License | ||
*/ | ||
|
||
namespace Laminas\Validator; | ||
|
||
use Laminas\Stdlib\ArrayUtils; | ||
use Traversable; | ||
|
||
class BusinessIdentifierCode extends AbstractValidator | ||
{ | ||
public const INVALID = 'valueNotBic'; | ||
public const NOT_STRING = 'valueNotString'; | ||
public const NOT_VALID_COUNTRY = 'valueNotCountry'; | ||
|
||
/** | ||
* @var string[] | ||
*/ | ||
protected $messageTemplates = [ | ||
self::NOT_STRING => 'Invalid type given; string expected', | ||
self::INVALID => 'Invalid BIC format', | ||
self::NOT_VALID_COUNTRY => 'Invalid country code', | ||
]; | ||
|
||
private const REGEX_BIC = '/^[a-z]{4}(?<country>[a-z]{2})[0-9a-z]{2}([0-9a-z]{3})?$/i'; | ||
|
||
/** | ||
* List of all country codes defined by ISO 3166-1 alpha-2 | ||
* https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2#Current_codes | ||
* | ||
* @var array<ISO 3166-1 alpha-2> | ||
*/ | ||
private const ISO_COUNTRIES = [ | ||
'AD', 'AE', 'AF', 'AG', 'AI', 'AL', 'AM', 'AO', 'AQ', 'AR', 'AS', 'AT', 'AU', 'AW', 'AX', 'AZ', 'BA', 'BB', | ||
'BD', 'BE', 'BF', 'BG', 'BH', 'BI', 'BJ', 'BL', 'BM', 'BN', 'BO', 'BQ', 'BQ', 'BR', 'BS', 'BT', 'BV', 'BW', | ||
'BY', 'BZ', 'CA', 'CC', 'CD', 'CF', 'CG', 'CH', 'CI', 'CK', 'CL', 'CM', 'CN', 'CO', 'CR', 'CU', 'CV', 'CW', | ||
'CX', 'CY', 'CZ', 'DE', 'DJ', 'DK', 'DM', 'DO', 'DZ', 'EC', 'EE', 'EG', 'EH', 'ER', 'ES', 'ET', 'FI', 'FJ', | ||
'FK', 'FM', 'FO', 'FR', 'GA', 'GB', 'GD', 'GE', 'GF', 'GG', 'GH', 'GI', 'GL', 'GM', 'GN', 'GP', 'GQ', 'GR', | ||
'GS', 'GT', 'GU', 'GW', 'GY', 'HK', 'HM', 'HN', 'HR', 'HT', 'HU', 'ID', 'IE', 'IL', 'IM', 'IN', 'IO', 'IQ', | ||
'IR', 'IS', 'IT', 'JE', 'JM', 'JO', 'JP', 'KE', 'KG', 'KH', 'KI', 'KM', 'KN', 'KP', 'KR', 'KW', 'KY', 'KZ', | ||
'LA', 'LB', 'LC', 'LI', 'LK', 'LR', 'LS', 'LT', 'LU', 'LV', 'LY', 'MA', 'MC', 'MD', 'ME', 'MF', 'MG', 'MH', | ||
'MK', 'ML', 'MM', 'MN', 'MO', 'MP', 'MQ', 'MR', 'MS', 'MT', 'MU', 'MV', 'MW', 'MX', 'MY', 'MZ', 'NA', 'NC', | ||
'NE', 'NF', 'NG', 'NI', 'NL', 'NO', 'NP', 'NR', 'NU', 'NZ', 'OM', 'PA', 'PE', 'PF', 'PG', 'PH', 'PK', 'PL', | ||
'PM', 'PN', 'PR', 'PS', 'PT', 'PW', 'PY', 'QA', 'RE', 'RO', 'RS', 'RU', 'RW', 'SA', 'SB', 'SC', 'SD', 'SE', | ||
'SG', 'SH', 'SI', 'SJ', 'SK', 'SL', 'SM', 'SN', 'SO', 'SR', 'SS', 'ST', 'SV', 'SX', 'SY', 'SZ', 'TC', 'TD', | ||
'TF', 'TG', 'TH', 'TJ', 'TK', 'TL', 'TM', 'TN', 'TO', 'TR', 'TT', 'TV', 'TW', 'TZ', 'UA', 'UG', 'UM', 'US', | ||
'UY', 'UZ', 'VA', 'VC', 'VE', 'VG', 'VI', 'VN', 'VU', 'WF', 'WS', 'YE', 'YT', 'ZA', 'ZM', 'ZW', | ||
]; | ||
|
||
/** | ||
* This code is the only one used by SWIFT that is not defined by ISO 3166-1 alpha-2 | ||
* https://en.wikipedia.org/wiki/ISO_9362 | ||
* | ||
* @var string | ||
*/ | ||
private const KOSOVO_EXCEPTION = 'XK'; | ||
|
||
/** | ||
* @inheritDoc | ||
*/ | ||
public function isValid($value) : bool | ||
{ | ||
if (! is_string($value)) { | ||
$this->error(self::NOT_STRING); | ||
return false; | ||
} | ||
|
||
if (empty($value) | ||
|| ! preg_match(self::REGEX_BIC, $value, $matches) | ||
) { | ||
$this->error(self::INVALID); | ||
return false; | ||
} | ||
|
||
if (! $this->isSwiftValidCountry($matches['country'])) { | ||
$this->error(self::NOT_VALID_COUNTRY); | ||
return false; | ||
} | ||
|
||
return true; | ||
} | ||
|
||
private function isSwiftValidCountry(string $countryCode) : bool | ||
{ | ||
$countryCode = strtoupper($countryCode); | ||
return in_array($countryCode, self::ISO_COUNTRIES, true) | ||
|| $countryCode === self::KOSOVO_EXCEPTION; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
<?php | ||
|
||
/** | ||
* @see https://github.com/laminas/laminas-validator for the canonical source repository | ||
* @copyright https://github.com/laminas/laminas-validator/blob/master/COPYRIGHT.md | ||
* @license https://github.com/laminas/laminas-validator/blob/master/LICENSE.md New BSD License | ||
*/ | ||
|
||
namespace LaminasTest\Validator; | ||
|
||
use PHPUnit\Framework\TestCase; | ||
use Laminas\Validator\BusinessIdentifierCode; | ||
|
||
class BusinessIdentifierCodeTest extends TestCase | ||
{ | ||
public function successProvider() : array | ||
{ | ||
return [ | ||
// UPPERCASE | ||
'BANQUE ATLANTIQUE COTE D\'IVOIRE, ABIDJAN, Cote d\'Ivoire' => ['ATCICIAB'], | ||
'BANQUE NATIONALE DU CANADA, MONTREAL, Canada' => ['BNDCCAMMINT'], | ||
'BDO UNIBANK, INC., MANILA, Philippines' => ['BNORPHMM'], | ||
'FEDERATION DES CAISSES DESJARDINS DU QUEBEC, LEVIS, Canada' => ['CCDQCAMM'], | ||
'COMMERZBANK AG, FRANKFURT AM MAIN, Germany' => ['COBADEFF'], | ||
'DANSKE BANK A/S, COPENHAGEN, Denmark' => ['DABADKKK'], | ||
'DEUTSCHE BANK AG, FRANKFURT AM MAIN, Germany' => ['DEUTDEFF'], | ||
'DB PRIVAT-UND FIRMENKUNDENBANK, DUSSELDORF, Germany' => ['DEUTDEDBDUE'], | ||
'DAH SING BANK (CHINA) LIMITED, SHANGHAI, China' => ['DSBACNBXSHA'], | ||
'FINANCIERE DES PAIEMENTS ELECTRONIQUES, CHARENTON LE PONT, France' => ['FPELFR21XXX'], | ||
'BNP PARIBAS FORTIS, BRUSSELS, Belgium' => ['GEBABEBB'], | ||
'PROCREDIT BANK SH.A KOSOVO, HEAD QUARTER, PRISTINA, Kosovo' => ['MBKOXKPRXXX'], | ||
'NEDBANK LIMITED, JOHANNESBURG, South Africa' => ['NEDSZAJJ'], | ||
'LA BANQUE POSTALE, MONTPELLIER, France' => ['PSSTFRPPMON'], | ||
|
||
// lowercase | ||
'DEUTSCHE BANK AG, BAD HOMBURG, Germany' => ['deutdeff500'], | ||
'NEDBANK LIMITED, JOHANNESBURG, South Africa (primary office)' => ['nedszajjxxx'], | ||
'LA BANQUE POSTALE, NANTES, France' => ['psstfrppnte'], | ||
'UNICREDIT S.P.A., MILANO, Italy' => ['uncritmm'], | ||
]; | ||
} | ||
|
||
/** | ||
* @dataProvider successProvider | ||
*/ | ||
public function testValidateSuccess(string $code) | ||
{ | ||
$validator = new BusinessIdentifierCode(); | ||
self::assertTrue($validator->isValid($code)); | ||
} | ||
|
||
public function notAStringProvider() : array | ||
{ | ||
return [ | ||
'number' => [123], | ||
'array' => [['DEUTDEFF']], | ||
'object' => [new \stdClass()], | ||
]; | ||
} | ||
|
||
/** | ||
* @dataProvider notAStringProvider | ||
*/ | ||
public function testNotAStringFailure($code) | ||
{ | ||
$validator = new BusinessIdentifierCode(); | ||
self::assertFalse($validator->isValid($code)); | ||
self::assertCount(1, $validator->getMessages()); | ||
self::assertSame( | ||
'Invalid type given; string expected', | ||
$validator->getMessages()[BusinessIdentifierCode::NOT_STRING] | ||
); | ||
} | ||
|
||
public function notBicFormatProvider() : array | ||
{ | ||
return [ | ||
'too short' => ['SHORT'], | ||
'too long' => ['SOLOOOOOOOOOOOOOOOOOOONG'], | ||
'use numbers for country code' => ['DEUT12AA'], | ||
]; | ||
} | ||
|
||
/** | ||
* @dataProvider notBicFormatProvider | ||
*/ | ||
public function testNotBicFormatFailure(string $code) | ||
{ | ||
$validator = new BusinessIdentifierCode(); | ||
self::assertFalse($validator->isValid($code)); | ||
self::assertCount(1, $validator->getMessages()); | ||
self::assertSame('Invalid BIC format', $validator->getMessages()[BusinessIdentifierCode::INVALID]); | ||
} | ||
|
||
public function notSwiftCountryCodeProvider() : array | ||
{ | ||
return [ | ||
'AA is not a assigned code' => ['DEUTAAFF'], | ||
'UK is not the iso code for the united kingdom' => ['ABCDUKFF'], | ||
]; | ||
} | ||
|
||
/** | ||
* @dataProvider notSwiftCountryCodeProvider | ||
*/ | ||
public function testNotSwiftCountryCodeFailure(string $code) | ||
{ | ||
$validator = new BusinessIdentifierCode(); | ||
self::assertFalse($validator->isValid($code)); | ||
self::assertCount(1, $validator->getMessages()); | ||
self::assertSame('Invalid country code', $validator->getMessages()[BusinessIdentifierCode::NOT_VALID_COUNTRY]); | ||
} | ||
} |