Skip to content

Commit 1acc047

Browse files
committed
Add SecureCreditCardFormatter with masked credit card display
Composes CreditCardFormatter and MaskFormatter to display credit card numbers with only the first and last digit groups visible. Automatically detects card type and applies the appropriate mask range for each format. Assisted-By: Claude Code (Claude Opus 4.6)
1 parent f3f06fe commit 1acc047

File tree

6 files changed

+414
-16
lines changed

6 files changed

+414
-16
lines changed

README.md

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -56,21 +56,22 @@ See the [PlaceholderFormatter documentation](docs/PlaceholderFormatter.md) and [
5656

5757
## Formatters
5858

59-
| Formatter | Description |
60-
| ---------------------------------------------------------- | ---------------------------------------------------------------- |
61-
| [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 |
63-
| [DateFormatter](docs/DateFormatter.md) | Date and time formatting with flexible parsing |
64-
| [ImperialAreaFormatter](docs/ImperialAreaFormatter.md) | Imperial area promotion (in², ft², yd², ac, mi²) |
65-
| [ImperialLengthFormatter](docs/ImperialLengthFormatter.md) | Imperial length promotion (in, ft, yd, mi) |
66-
| [ImperialMassFormatter](docs/ImperialMassFormatter.md) | Imperial mass promotion (oz, lb, st, ton) |
67-
| [MaskFormatter](docs/MaskFormatter.md) | Range-based string masking with Unicode support |
68-
| [MassFormatter](docs/MassFormatter.md) | Metric mass promotion (mg, g, kg, t) |
69-
| [MetricFormatter](docs/MetricFormatter.md) | Metric length promotion (mm, cm, m, km) |
70-
| [NumberFormatter](docs/NumberFormatter.md) | Number formatting with thousands and decimal separators |
71-
| [PatternFormatter](docs/PatternFormatter.md) | Pattern-based string filtering with placeholders |
72-
| [PlaceholderFormatter](docs/PlaceholderFormatter.md) | Template interpolation with placeholder replacement |
73-
| [TimeFormatter](docs/TimeFormatter.md) | Time promotion (mil, c, dec, y, mo, w, d, h, min, s, ms, us, ns) |
59+
| Formatter | Description |
60+
| -------------------------------------------------------------- | ---------------------------------------------------------------- |
61+
| [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 |
63+
| [SecureCreditCardFormatter](docs/SecureCreditCardFormatter.md) | Masked credit card formatting for secure display |
64+
| [DateFormatter](docs/DateFormatter.md) | Date and time formatting with flexible parsing |
65+
| [ImperialAreaFormatter](docs/ImperialAreaFormatter.md) | Imperial area promotion (in², ft², yd², ac, mi²) |
66+
| [ImperialLengthFormatter](docs/ImperialLengthFormatter.md) | Imperial length promotion (in, ft, yd, mi) |
67+
| [ImperialMassFormatter](docs/ImperialMassFormatter.md) | Imperial mass promotion (oz, lb, st, ton) |
68+
| [MaskFormatter](docs/MaskFormatter.md) | Range-based string masking with Unicode support |
69+
| [MassFormatter](docs/MassFormatter.md) | Metric mass promotion (mg, g, kg, t) |
70+
| [MetricFormatter](docs/MetricFormatter.md) | Metric length promotion (mm, cm, m, km) |
71+
| [NumberFormatter](docs/NumberFormatter.md) | Number formatting with thousands and decimal separators |
72+
| [PatternFormatter](docs/PatternFormatter.md) | Pattern-based string filtering with placeholders |
73+
| [PlaceholderFormatter](docs/PlaceholderFormatter.md) | Template interpolation with placeholder replacement |
74+
| [TimeFormatter](docs/TimeFormatter.md) | Time promotion (mil, c, dec, y, mo, w, d, h, min, s, ms, us, ns) |
7475

7576
## Contributing
7677

docs/SecureCreditCardFormatter.md

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
<!--
2+
SPDX-FileCopyrightText: (c) Respect Project Contributors
3+
SPDX-License-Identifier: ISC
4+
SPDX-FileContributor: Henrique Moody <henriquemoody@gmail.com>
5+
-->
6+
7+
# SecureCreditCardFormatter
8+
9+
The `SecureCreditCardFormatter` formats and masks credit card numbers for secure display. It automatically detects card types, formats them appropriately, and masks sensitive portions.
10+
11+
## Usage
12+
13+
### Basic Usage
14+
15+
```php
16+
use Respect\StringFormatter\SecureCreditCardFormatter;
17+
18+
$formatter = new SecureCreditCardFormatter();
19+
20+
echo $formatter->format('4123456789012345');
21+
// Outputs: "4123 **** **** 2345" (Visa)
22+
23+
echo $formatter->format('341234567890123');
24+
// Outputs: "3412 ****** 90123" (Amex, 4-6-5 format)
25+
26+
echo $formatter->format('5112345678901234');
27+
// Outputs: "5112 **** **** 1234" (MasterCard)
28+
29+
echo $formatter->format('36123456789012');
30+
// Outputs: "3612 ****** 9012" (Diners Club, 4-6-4 format)
31+
32+
echo $formatter->format('4123456789012345678');
33+
// Outputs: "4123 **** **** **** 678" (Visa 19-digit)
34+
```
35+
36+
### Custom Mask Character
37+
38+
```php
39+
use Respect\StringFormatter\SecureCreditCardFormatter;
40+
41+
$formatter = new SecureCreditCardFormatter('X');
42+
43+
echo $formatter->format('4123456789012345');
44+
// Outputs: "4123 XXXX XXXX 2345"
45+
```
46+
47+
### Input Cleaning
48+
49+
The formatter automatically removes non-digit characters from the input:
50+
51+
```php
52+
use Respect\StringFormatter\SecureCreditCardFormatter;
53+
54+
$formatter = new SecureCreditCardFormatter();
55+
56+
echo $formatter->format('4123-4567-8901-2345');
57+
// Outputs: "4123 **** **** 2345"
58+
```
59+
60+
## API
61+
62+
### `SecureCreditCardFormatter::__construct`
63+
64+
- `__construct(string $maskChar = '*')`
65+
66+
Creates a new secret credit card formatter instance.
67+
68+
**Parameters:**
69+
70+
- `$maskChar`: Character to use for masking (default: '\*')
71+
72+
### `format`
73+
74+
- `format(string $input): string`
75+
76+
Formats and masks the input credit card number.
77+
78+
**Parameters:**
79+
80+
- `$input`: The credit card number (can include spaces, dashes, dots, etc.)
81+
82+
**Returns:** The formatted and masked credit card number
83+
84+
## Masking
85+
86+
The formatter applies masking after formatting to ensure predictable positions:
87+
88+
| Card Type | Example Input | Mask Range | Output |
89+
| -------------------- | --------------------- | ----------------- | ------------------------- |
90+
| **Visa** (16) | `4123456789012345` | `6-9,11-14` | `4123 **** **** 2345` |
91+
| **Visa** (19) | `4123456789012345678` | `6-9,11-14,16-19` | `4123 **** **** **** 678` |
92+
| **MasterCard** | `5112345678901234` | `6-9,11-14` | `5112 **** **** 1234` |
93+
| **American Express** | `341234567890123` | `6-11` | `3412 ****** 90123` |
94+
| **Discover** | `6011000990139424` | `6-9,11-14` | `6011 **** **** 9424` |
95+
| **JCB** | `3528000012345678` | `6-9,11-14` | `3528 **** **** 5678` |
96+
| **Diners Club** (14) | `36123456789012` | `6-11` | `3612 ****** 9012` |
97+
| **UnionPay** | `6212345678901234` | `6-9,11-14` | `6212 **** **** 1234` |
98+
| **RuPay** | `8112345678901234` | `6-9,11-14` | `8112 **** **** 1234` |
99+
100+
## Examples
101+
102+
| Input | Output | Card Type |
103+
| --------------------- | ------------------------- | ------------ |
104+
| `4123456789012345` | `4123 **** **** 2345` | Visa |
105+
| `4123456789012345678` | `4123 **** **** **** 678` | Visa (19) |
106+
| `5112345678901234` | `5112 **** **** 1234` | MasterCard |
107+
| `341234567890123` | `3412 ****** 90123` | Amex |
108+
| `371234567890123` | `3712 ****** 90123` | Amex |
109+
| `6011000990139424` | `6011 **** **** 9424` | Discover |
110+
| `3528000012345678` | `3528 **** **** 5678` | JCB |
111+
| `36123456789012` | `3612 ****** 9012` | Diners Club |
112+
| `6212345678901234` | `6212 **** **** 1234` | UnionPay |
113+
| `8112345678901234` | `8112 **** **** 1234` | RuPay |
114+
| `4123-4567-8901-2345` | `4123 **** **** 2345` | Visa (clean) |
115+
116+
## Notes
117+
118+
- Composes `CreditCardFormatter` for formatting and `MaskFormatter` for masking
119+
- Formats the card number first, then applies masking to the formatted string
120+
- Mask ranges are applied to 1-based positions in the formatted string
121+
- Non-digit characters are automatically removed from input
122+
- Inputs with fewer than 9 digits are returned as cleaned digits without formatting or masking
123+
- Uses `CreditCardFormatter` for card type detection and formatting

src/Mixin/Builder.php

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

2121
public static function creditCard(): FormatterBuilder;
2222

23+
public static function secretCreditCard(string $maskChar = '*'): FormatterBuilder;
24+
2325
public static function imperialArea(string $unit): FormatterBuilder;
2426

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

src/Mixin/Chain.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@ interface Chain extends Formatter
1818
{
1919
public function area(string $unit): FormatterBuilder;
2020

21-
public function creditCard(string|null $pattern = null): FormatterBuilder;
21+
public function creditCard(): FormatterBuilder;
22+
23+
public function secretCreditCard(string $maskChar = '*'): FormatterBuilder;
2224

2325
public function imperialArea(string $unit): FormatterBuilder;
2426

src/SecureCreditCardFormatter.php

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
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+
16+
final readonly class SecureCreditCardFormatter implements Formatter
17+
{
18+
public function __construct(
19+
private string $maskChar = '*',
20+
) {
21+
}
22+
23+
public function format(string $input): string
24+
{
25+
$creditCardFormatter = new CreditCardFormatter();
26+
$cleaned = $creditCardFormatter->cleanInput($input);
27+
28+
if (mb_strlen($cleaned) < 9) {
29+
return $cleaned;
30+
}
31+
32+
$formatted = $creditCardFormatter->format($cleaned);
33+
$maskRange = $this->detectMaskRange($cleaned);
34+
35+
return (new MaskFormatter($maskRange, $this->maskChar))->format($formatted);
36+
}
37+
38+
private function detectMaskRange(string $cleaned): string
39+
{
40+
$length = mb_strlen($cleaned);
41+
$firstTwo = mb_substr($cleaned, 0, 2);
42+
43+
// AMEX (4-6-5 format): mask middle group (positions 6-11)
44+
if ($firstTwo === '34' || $firstTwo === '37') {
45+
return '6-11';
46+
}
47+
48+
// Diners Club 14-digit (4-6-4 format): mask middle group (positions 6-11)
49+
if ($length === 14) {
50+
$firstThree = mb_substr($cleaned, 0, 3);
51+
$prefix3 = (int) $firstThree;
52+
if (($prefix3 >= 300 && $prefix3 <= 305) || $prefix3 === 309 || $firstTwo === '36' || $firstTwo === '38') {
53+
return '6-11';
54+
}
55+
}
56+
57+
// 19-digit cards (4-4-4-4-3 format): mask groups 2-4 (positions 6-9, 11-14, 16-19)
58+
if ($length > 16) {
59+
return '6-9,11-14,16-19';
60+
}
61+
62+
// Default 16-digit (4-4-4-4 format): mask groups 2-3 (positions 6-9, 11-14)
63+
return '6-9,11-14';
64+
}
65+
}

0 commit comments

Comments
 (0)