Skip to content

Commit fc79dc7

Browse files
committed
Add CreditcardFormatter with automatic card type detection
The new CreditcardFormatter automatically detects major credit card types (Visa, MasterCard, Amex, Discover, JCB) based on card prefix and length, applying the appropriate formatting pattern. This formatter is essential for payment processing applications that need to display credit card numbers in a consistent, readable format while supporting different card types with their specific formatting requirements (e.g., Amex uses 4-6-5 format while others use 4-4-4-4). Input is automatically cleaned by removing non-digit characters, making it flexible for real-world usage where cards may have spaces, dashes, or other separators. Includes comprehensive tests covering all major card types, invalid cards, custom patterns, input cleaning, and edge cases. Assisted-by: OpenCode (GLM-4.7)
1 parent faeaead commit fc79dc7

File tree

6 files changed

+397
-0
lines changed

6 files changed

+397
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ See the [PlaceholderFormatter documentation](docs/PlaceholderFormatter.md) and [
5959
| Formatter | Description |
6060
| ---------------------------------------------------------- | ---------------------------------------------------------------- |
6161
| [AreaFormatter](docs/AreaFormatter.md) | Metric area promotion (mm², cm², m², a, ha, km²) |
62+
| [CreditCardFormatter](docs/CreditCardFormatter.md) | Credit card number formatting with auto-detection |
6263
| [DateFormatter](docs/DateFormatter.md) | Date and time formatting with flexible parsing |
6364
| [ImperialAreaFormatter](docs/ImperialAreaFormatter.md) | Imperial area promotion (in², ft², yd², ac, mi²) |
6465
| [ImperialLengthFormatter](docs/ImperialLengthFormatter.md) | Imperial length promotion (in, ft, yd, mi) |

docs/CreditCardFormatter.md

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
<!--
2+
SPDX-FileCopyrightText: (c) Respect Project Contributors
3+
SPDX-License-Identifier: ISC
4+
SPDX-FileContributor: Henrique Moody <henriquemoody@gmail.com>
5+
-->
6+
7+
# CreditCardFormatter
8+
9+
The `CreditCardFormatter` formats credit card numbers with automatic card type detection. It supports major card networks including Visa, MasterCard, American Express, Discover, JCB, Diners Club, UnionPay, and RuPay.
10+
11+
## Usage
12+
13+
### Basic Usage with Auto-Detection
14+
15+
```php
16+
use Respect\StringFormatter\CreditCardFormatter;
17+
18+
$formatter = new CreditCardFormatter();
19+
20+
echo $formatter->format('4123456789012345');
21+
// Outputs: "4123 4567 8901 2345" (Visa detected)
22+
23+
echo $formatter->format('371234567890123');
24+
// Outputs: "3712 345678 90123" (Amex, 4-6-5 format)
25+
26+
echo $formatter->format('5112345678901234');
27+
// Outputs: "5112 3456 7890 1234" (MasterCard detected)
28+
29+
echo $formatter->format('36123456789012');
30+
// Outputs: "3612 345678 9012" (Diners Club, 4-6-4 format)
31+
32+
echo $formatter->format('4123456789012345678');
33+
// Outputs: "4123 4567 8901 2345 678" (Visa 19-digit)
34+
```
35+
36+
### Input Cleaning
37+
38+
The formatter automatically removes non-digit characters from the input:
39+
40+
```php
41+
use Respect\StringFormatter\CreditCardFormatter;
42+
43+
$formatter = new CreditCardFormatter();
44+
45+
echo $formatter->format('4123-4567-8901-2345');
46+
// Outputs: "4123 4567 8901 2345"
47+
48+
echo $formatter->format('4123 4567 8901 2345');
49+
// Outputs: "4123 4567 8901 2345"
50+
51+
echo $formatter->format('4123.4567.8901.2345');
52+
// Outputs: "4123 4567 8901 2345"
53+
```
54+
55+
## API
56+
57+
### `format`
58+
59+
- `format(string $input): string`
60+
61+
Formats the input credit card number.
62+
63+
**Parameters:**
64+
65+
- `$input`: The credit card number (can include spaces, dashes, dots, etc.)
66+
67+
**Returns:** The formatted credit card number
68+
69+
## Auto-Detection
70+
71+
The formatter automatically detects card type based on prefix and length:
72+
73+
| Card Type | Prefix Ranges | Length | Format |
74+
| -------------------- | -------------------- | ---------- | --------------------- |
75+
| **American Express** | 34, 37 | 15 | `#### ###### #####` |
76+
| **Diners Club** | 300-305, 309, 36, 38 | 14 | `#### ###### ####` |
77+
| **Diners Club** | 36 | 16 | `#### #### #### ####` |
78+
| **Visa** | 4 | 13, 16 | `#### #### #### ####` |
79+
| **Visa** | 4 | 19 | `#### #### #### #### ###` |
80+
| **MasterCard** | 51-55, 2221-2720 | 16 | `#### #### #### ####` |
81+
| **Discover** | 6011, 644-649, 65 | 16 | `#### #### #### ####` |
82+
| **Discover** | 6011, 644-649, 65 | 19 | `#### #### #### #### ###` |
83+
| **JCB** | 3528-3589 | 16 | `#### #### #### ####` |
84+
| **JCB** | 3528-3589 | 19 | `#### #### #### #### ###` |
85+
| **UnionPay** | 62 | 16 | `#### #### #### ####` |
86+
| **UnionPay** | 62 | 19 | `#### #### #### #### ###` |
87+
| **RuPay** | 60, 65, 81, 82, 508 | 16 | `#### #### #### ####` |
88+
89+
Cards with more than 16 digits automatically use the 19-digit pattern: `#### #### #### #### ###`
90+
91+
## Examples
92+
93+
| Input | Output | Card Type |
94+
| --------------------- | ------------------------- | ------------ |
95+
| `4123456789012345` | `4123 4567 8901 2345` | Visa |
96+
| `4123456789012345678` | `4123 4567 8901 2345 678` | Visa (19) |
97+
| `5112345678901234` | `5112 3456 7890 1234` | MasterCard |
98+
| `341234567890123` | `3412 345678 90123` | Amex |
99+
| `371234567890123` | `3712 345678 90123` | Amex |
100+
| `6011000990139424` | `6011 0009 9013 9424` | Discover |
101+
| `3528000012345678` | `3528 0000 1234 5678` | JCB |
102+
| `36123456789012` | `3612 345678 9012` | Diners Club |
103+
| `6212345678901234` | `6212 3456 7890 1234` | UnionPay |
104+
| `8112345678901234` | `8112 3456 7890 1234` | RuPay |
105+
| `1234567890123456` | `1234 5678 9012 3456` | Unknown |
106+
| `4123-4567-8901-2345` | `4123 4567 8901 2345` | Visa (clean) |
107+
108+
## Notes
109+
110+
- Non-digit characters are automatically removed from the input
111+
- Card type detection is based on card prefix and length (not Luhn validation)
112+
- If card type cannot be determined, uses the default 4-4-4-4 pattern
113+
- Uses `PatternFormatter` internally for formatting
114+
- For custom formatting patterns, use `PatternFormatter` directly

src/CreditCardFormatter.php

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
<?php
2+
3+
/*
4+
* SPDX-FileCopyrightText: (c) Respect Project Contributors
5+
* SPDX-License-Identifier: ISC
6+
* SPDX-FileContributor: Henrique Moody <henriquemoody@gmail.com>
7+
*/
8+
9+
declare(strict_types=1);
10+
11+
namespace Respect\StringFormatter;
12+
13+
use function mb_strlen;
14+
use function mb_substr;
15+
use function preg_replace;
16+
17+
final readonly class CreditCardFormatter implements Formatter
18+
{
19+
private const string DEFAULT_16 = '#### #### #### ####';
20+
private const string DEFAULT_19 = '#### #### #### #### ###';
21+
private const string AMEX = '#### ###### #####';
22+
private const string DINERS_14 = '#### ###### ####';
23+
24+
public function format(string $input): string
25+
{
26+
$cleaned = $this->cleanInput($input);
27+
$pattern = $this->detectPattern($cleaned);
28+
29+
$formatter = new PatternFormatter($pattern);
30+
31+
return $formatter->format($cleaned);
32+
}
33+
34+
public function cleanInput(string $input): string
35+
{
36+
return preg_replace('/[^0-9]/', '', $input) ?? '';
37+
}
38+
39+
public function detectPattern(string $input): string
40+
{
41+
$length = mb_strlen($input);
42+
$firstTwo = mb_substr($input, 0, 2);
43+
$firstThree = mb_substr($input, 0, 3);
44+
45+
// American Express: starts with 34 or 37 (15 digits, 4-6-5 format)
46+
if ($firstTwo === '34' || $firstTwo === '37') {
47+
return self::AMEX;
48+
}
49+
50+
// Diners Club International: 14 digits, starts with 300-305, 309, 36, 38
51+
if ($length === 14) {
52+
$prefix3 = (int) $firstThree;
53+
if (($prefix3 >= 300 && $prefix3 <= 305) || $prefix3 === 309 || $firstTwo === '36' || $firstTwo === '38') {
54+
return self::DINERS_14;
55+
}
56+
}
57+
58+
// 19-digit cards (some Visa, Discover, JCB, UnionPay)
59+
if ($length > 16) {
60+
return self::DEFAULT_19;
61+
}
62+
63+
// Default 4-4-4-4: Visa, Mastercard, Discover, JCB, UnionPay, RuPay, etc.
64+
return self::DEFAULT_16;
65+
}
66+
}

src/Mixin/Builder.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ interface Builder
1818
{
1919
public static function area(string $unit): FormatterBuilder;
2020

21+
public static function creditCard(): FormatterBuilder;
22+
2123
public static function imperialArea(string $unit): FormatterBuilder;
2224

2325
public static function imperialLength(string $unit): FormatterBuilder;

src/Mixin/Chain.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ interface Chain extends Formatter
1818
{
1919
public function area(string $unit): FormatterBuilder;
2020

21+
public function creditCard(string|null $pattern = null): FormatterBuilder;
22+
2123
public function imperialArea(string $unit): FormatterBuilder;
2224

2325
public function imperialLength(string $unit): FormatterBuilder;

0 commit comments

Comments
 (0)