Skip to content

Commit aa05a24

Browse files
Merge pull request #11 from TheDragonCode/1.x
Added ability to generate and validate alphanumeric numbers
2 parents 0033579 + aba69f2 commit aa05a24

File tree

17 files changed

+423
-46
lines changed

17 files changed

+423
-46
lines changed

README.md

Lines changed: 52 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -76,27 +76,6 @@ CardNumber::isValid('2730 1684 6416 1841', 'visa'); // false
7676
CardNumber::isValid('2201 6868 4646 8444', 'visa'); // false
7777
```
7878

79-
List of available card types:
80-
81-
| Bank Name | Card Type | Enum Type |
82-
|--------------------|----------------------|------------------------------------------------------------|
83-
| AmericanExpress | `amex` | `DragonCode\CardNumber\Enums\CardType::americanExpress` |
84-
| Dankort | `dankort` | `DragonCode\CardNumber\Enums\CardType::dankort` |
85-
| DinersClub | `dinersclub` | `DragonCode\CardNumber\Enums\CardType::dinersClub` |
86-
| Discovery | `discovery` | `DragonCode\CardNumber\Enums\CardType::discovery` |
87-
| Forbrugsforeningen | `forbrugsforeningen` | `DragonCode\CardNumber\Enums\CardType::forbrugsforeningen` |
88-
| HiperCard | `hipercard` | `DragonCode\CardNumber\Enums\CardType::hiperCard` |
89-
| Jcb | `jcb` | `DragonCode\CardNumber\Enums\CardType::jcb` |
90-
| Maestro | `maestro` | `DragonCode\CardNumber\Enums\CardType::maestro` |
91-
| MasterCard | `mastercard` | `DragonCode\CardNumber\Enums\CardType::masterCard` |
92-
| Mir | `mir` | `DragonCode\CardNumber\Enums\CardType::mir` |
93-
| Ralf Ringer | `ralfringer` | `DragonCode\CardNumber\Enums\CardType::ralfRinger` |
94-
| Troy | `troy` | `DragonCode\CardNumber\Enums\CardType::troy` |
95-
| Unionpay | `unionpay` | `DragonCode\CardNumber\Enums\CardType::unionPay` |
96-
| Visa | `visa` | `DragonCode\CardNumber\Enums\CardType::visa` |
97-
| VisaElectron | `visaelectron` | `DragonCode\CardNumber\Enums\CardType::visaElectron` |
98-
| Yves Rocher | `yvesrocher` | `DragonCode\CardNumber\Enums\CardType::yvesRocher` |
99-
10079
You can also check for invalid numbers:
10180

10281
```php
@@ -121,6 +100,44 @@ CardNumber::isInvalid('5580-4733x7202_47 33'); // false
121100
CardNumber::isInvalid('5580-4733x7202_47 32'); // true
122101
```
123102

103+
In addition to numerical values, you can also validate number-letter combinations. For example:
104+
105+
```php
106+
use DragonCode\CardNumber\CardNumber;
107+
use DragonCode\CardNumber\Enums\CardType;
108+
109+
CardNumber::isValid('EKN-OSX', CardType::chars); // true
110+
CardNumber::isValid('EKN-56X', CardType::chars); // true
111+
112+
CardNumber::isValid('ekn-osx', 'chars'); // true
113+
CardNumber::isValid('ekn-56x', 'chars'); // true
114+
115+
CardNumber::isValid('EKN-OSX'); // false
116+
CardNumber::isValid('EKN-56X'); // false
117+
```
118+
119+
List of available card types:
120+
121+
| Type | Card Type | Enum Type |
122+
|--------------------|----------------------|------------------------------------------------------------|
123+
| AmericanExpress | `amex` | `DragonCode\CardNumber\Enums\CardType::americanExpress` |
124+
| Chars Number | `chars` | `DragonCode\CardNumber\Enums\CardType::chars` |
125+
| Dankort | `dankort` | `DragonCode\CardNumber\Enums\CardType::dankort` |
126+
| DinersClub | `dinersclub` | `DragonCode\CardNumber\Enums\CardType::dinersClub` |
127+
| Discovery | `discovery` | `DragonCode\CardNumber\Enums\CardType::discovery` |
128+
| Forbrugsforeningen | `forbrugsforeningen` | `DragonCode\CardNumber\Enums\CardType::forbrugsforeningen` |
129+
| HiperCard | `hipercard` | `DragonCode\CardNumber\Enums\CardType::hiperCard` |
130+
| Jcb | `jcb` | `DragonCode\CardNumber\Enums\CardType::jcb` |
131+
| Maestro | `maestro` | `DragonCode\CardNumber\Enums\CardType::maestro` |
132+
| MasterCard | `mastercard` | `DragonCode\CardNumber\Enums\CardType::masterCard` |
133+
| Mir | `mir` | `DragonCode\CardNumber\Enums\CardType::mir` |
134+
| Ralf Ringer | `ralfringer` | `DragonCode\CardNumber\Enums\CardType::ralfRinger` |
135+
| Troy | `troy` | `DragonCode\CardNumber\Enums\CardType::troy` |
136+
| Unionpay | `unionpay` | `DragonCode\CardNumber\Enums\CardType::unionPay` |
137+
| Visa | `visa` | `DragonCode\CardNumber\Enums\CardType::visa` |
138+
| VisaElectron | `visaelectron` | `DragonCode\CardNumber\Enums\CardType::visaElectron` |
139+
| Yves Rocher | `yvesrocher` | `DragonCode\CardNumber\Enums\CardType::yvesRocher` |
140+
124141
### Generation
125142

126143
You can also easily generate any numbers using the Luhn algorithm:
@@ -192,6 +209,20 @@ CardNumber::generate(558047337202473, $formatter); // 5580/473372/024733
192209
> * `DragonCode\CardNumber\Formatters\DefaultFormatter`
193210
> * `DragonCode\CardNumber\Formatters\BankFormatter`
194211
> * `DragonCode\CardNumber\Formatters\LoyaltyFormatter`
212+
> * `DragonCode\CardNumber\Formatters\LoyaltyCharFormatter`
213+
214+
In addition to numeric formatters, you can also use number-letter combinations.
215+
For example, using the "LoyaltyCharsFormatter" formatter, you can generate a letter code instead of a numeric number,
216+
which will be valid when verified by the Luhn's algorithm:
217+
218+
```php
219+
use DragonCode\CardNumber\CardNumber;
220+
use DragonCode\CardNumber\Formatters\LoyaltyCharFormatter;
221+
222+
$formatter = LoyaltyCharFormatter::create();
223+
224+
CardNumber::generate(345678123, $formatter); // KN-OSXY-AEKF
225+
```
195226

196227
### Factories
197228

src/CardNumber.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
namespace DragonCode\CardNumber;
66

77
use DragonCode\CardNumber\Cards\AmericanExpress;
8+
use DragonCode\CardNumber\Cards\Chars;
89
use DragonCode\CardNumber\Cards\Dankort;
910
use DragonCode\CardNumber\Cards\DefaultCard;
1011
use DragonCode\CardNumber\Cards\DinersClub;
@@ -33,6 +34,7 @@ public static function isValid(int|string $number, CardType|string|null $cardTyp
3334
{
3435
return match (static::detectCardType($cardType)) {
3536
CardType::americanExpress => AmericanExpress::isValid($number),
37+
CardType::chars => Chars::isValid($number),
3638
CardType::dankort => Dankort::isValid($number),
3739
CardType::dinersClub => DinersClub::isValid($number),
3840
CardType::discovery => Discovery::isValid($number),

src/Cards/Card.php

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,17 @@
44

55
namespace DragonCode\CardNumber\Cards;
66

7+
use DragonCode\CardNumber\Concerns\Stringable;
78
use DragonCode\CardNumber\Services\Validator;
89

910
use function in_array;
10-
use function mb_strlen;
1111
use function preg_match;
1212
use function preg_replace;
1313

1414
abstract class Card
1515
{
16+
use Stringable;
17+
1618
protected static ?string $pattern = null;
1719

1820
protected static array $numberLength = [16];
@@ -28,7 +30,11 @@ public static function isValid(int|string $cardNumber): bool
2830

2931
protected static function isValidLength(string $number): bool
3032
{
31-
return in_array(static::length($number), static::$numberLength, true);
33+
if ($length = static::$numberLength) {
34+
return in_array(static::length($number), $length, true);
35+
}
36+
37+
return true;
3238
}
3339

3440
protected static function isValidPattern(string $number): bool
@@ -45,11 +51,6 @@ protected static function isValidNumber(string $number): bool
4551
return (new Validator())->isValid($number);
4652
}
4753

48-
protected static function length(string $number): int
49-
{
50-
return mb_strlen($number, 'utf8');
51-
}
52-
5354
protected static function clear(int|string $number): string
5455
{
5556
return preg_replace('/\D/', '', (string) $number);

src/Cards/Chars.php

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace DragonCode\CardNumber\Cards;
6+
7+
use DragonCode\CardNumber\Concerns\Charsable;
8+
9+
use function array_keys;
10+
use function array_values;
11+
use function implode;
12+
use function preg_match;
13+
use function preg_replace;
14+
use function str_replace;
15+
16+
class Chars extends Card
17+
{
18+
use Charsable;
19+
20+
protected static array $numberLength = [];
21+
22+
public static function isValid(int|string $cardNumber): bool
23+
{
24+
$number = static::clear($cardNumber);
25+
26+
return static::isValidLength($number)
27+
&& static::isValidChars($number)
28+
&& static::isValidPattern($number)
29+
&& static::isValidNumber($number);
30+
}
31+
32+
protected static function isValidNumber(string $number): bool
33+
{
34+
return parent::isValidNumber(
35+
str_replace(array_values(static::$chars), array_keys(static::$chars), $number)
36+
);
37+
}
38+
39+
protected static function clear(int|string $number): string
40+
{
41+
return preg_replace('/[^\w]/', '', static::lower($number));
42+
}
43+
44+
protected static function isValidChars(string $number): bool
45+
{
46+
$chars = static::charsPattern();
47+
48+
return ! preg_match("/[^$chars\\d]/", $number);
49+
}
50+
51+
protected static function charsPattern(): string
52+
{
53+
return implode('', array_values(static::$chars));
54+
}
55+
}

src/Concerns/Charsable.php

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace DragonCode\CardNumber\Concerns;
6+
7+
trait Charsable
8+
{
9+
protected static array $chars = [
10+
0 => 'f',
11+
1 => 'a',
12+
2 => 'e',
13+
3 => 'k',
14+
4 => 'n',
15+
5 => 'o',
16+
6 => 's',
17+
7 => 'x',
18+
8 => 'y',
19+
9 => 'z',
20+
];
21+
}

src/Concerns/Stringable.php

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,31 @@
55
namespace DragonCode\CardNumber\Concerns;
66

77
use function mb_strlen;
8+
use function mb_strtolower;
9+
use function mb_strtoupper;
810
use function str_pad;
911

1012
use const STR_PAD_LEFT;
1113

1214
trait Stringable
1315
{
14-
protected function length(string $number, int $offset = 0): int
16+
protected static function length(string $value, int $offset = 0): int
1517
{
16-
return mb_strlen($number, 'utf8') + $offset;
18+
return mb_strlen($value, 'utf8') + $offset;
1719
}
1820

19-
protected function strPad(int|string $string, int $length): string
21+
protected static function strPad(int|string $value, int $length): string
2022
{
21-
return str_pad((string) $string, $length, '0', STR_PAD_LEFT);
23+
return str_pad((string) $value, $length, '0', STR_PAD_LEFT);
24+
}
25+
26+
protected static function lower(int|string $value): string
27+
{
28+
return mb_strtolower((string) $value, 'utf8');
29+
}
30+
31+
protected static function upper(int|string $value): string
32+
{
33+
return mb_strtoupper((string) $value, 'utf8');
2234
}
2335
}

src/Enums/CardType.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ enum CardType: string
1111
use Values;
1212

1313
case americanExpress = 'amex';
14+
case chars = 'chars';
1415
case dankort = 'dankort';
1516
case dinersClub = 'dinersclub';
1617
case discovery = 'discovery';

src/Factories/BankFactory.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,16 +25,16 @@ public function paymentType(int|string $type): static
2525

2626
public function bank(int|string $id, int|string $info, int|string $program): static
2727
{
28-
$this->bankNumber = $this->strPad($id, 3);
29-
$this->bankInfo = $this->strPad($info, 2);
30-
$this->bankProgram = $this->strPad($program, 2);
28+
$this->bankNumber = static::strPad($id, 3);
29+
$this->bankInfo = static::strPad($info, 2);
30+
$this->bankProgram = static::strPad($program, 2);
3131

3232
return $this;
3333
}
3434

3535
public function client(int|string $number): static
3636
{
37-
$this->clientId = $this->strPad($number, 7);
37+
$this->clientId = static::strPad($number, 7);
3838

3939
return $this;
4040
}

src/Factories/CustomerFactory.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,14 @@ public function __construct()
2121

2222
public function level(int $level): static
2323
{
24-
$this->level = $this->strPad($level, 2);
24+
$this->level = static::strPad($level, 2);
2525

2626
return $this;
2727
}
2828

2929
public function customer(int $customerId): static
3030
{
31-
$this->customerId = $this->strPad($customerId, 6);
31+
$this->customerId = static::strPad($customerId, 6);
3232

3333
return $this;
3434
}

src/Formatters/Formatter.php

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,14 @@ abstract class Formatter
2626

2727
public function format(string $number): string
2828
{
29-
$number = $this->strPad($number, $this->minCardLength);
30-
$length = $this->length($number);
29+
$number = static::strPad($number, $this->minCardLength);
30+
$length = static::length($number);
3131

32+
return $this->pretty($number, $length);
33+
}
34+
35+
protected function pretty(string $number, int $length): string
36+
{
3237
return match (true) {
3338
$length <= 4 => $number,
3439
$length <= 6 => $this->split($number, 3),

0 commit comments

Comments
 (0)