Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 16 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,20 +56,22 @@ See the [PlaceholderFormatter documentation](docs/PlaceholderFormatter.md) and [

## Formatters

| Formatter | Description |
| ---------------------------------------------------------- | ---------------------------------------------------------------- |
| [AreaFormatter](docs/AreaFormatter.md) | Metric area promotion (mm², cm², m², a, ha, km²) |
| [DateFormatter](docs/DateFormatter.md) | Date and time formatting with flexible parsing |
| [ImperialAreaFormatter](docs/ImperialAreaFormatter.md) | Imperial area promotion (in², ft², yd², ac, mi²) |
| [ImperialLengthFormatter](docs/ImperialLengthFormatter.md) | Imperial length promotion (in, ft, yd, mi) |
| [ImperialMassFormatter](docs/ImperialMassFormatter.md) | Imperial mass promotion (oz, lb, st, ton) |
| [MaskFormatter](docs/MaskFormatter.md) | Range-based string masking with Unicode support |
| [MassFormatter](docs/MassFormatter.md) | Metric mass promotion (mg, g, kg, t) |
| [MetricFormatter](docs/MetricFormatter.md) | Metric length promotion (mm, cm, m, km) |
| [NumberFormatter](docs/NumberFormatter.md) | Number formatting with thousands and decimal separators |
| [PatternFormatter](docs/PatternFormatter.md) | Pattern-based string filtering with placeholders |
| [PlaceholderFormatter](docs/PlaceholderFormatter.md) | Template interpolation with placeholder replacement |
| [TimeFormatter](docs/TimeFormatter.md) | Time promotion (mil, c, dec, y, mo, w, d, h, min, s, ms, us, ns) |
| Formatter | Description |
| -------------------------------------------------------------- | ---------------------------------------------------------------- |
| [AreaFormatter](docs/AreaFormatter.md) | Metric area promotion (mm², cm², m², a, ha, km²) |
| [CreditCardFormatter](docs/CreditCardFormatter.md) | Credit card number formatting with auto-detection |
| [DateFormatter](docs/DateFormatter.md) | Date and time formatting with flexible parsing |
| [ImperialAreaFormatter](docs/ImperialAreaFormatter.md) | Imperial area promotion (in², ft², yd², ac, mi²) |
| [ImperialLengthFormatter](docs/ImperialLengthFormatter.md) | Imperial length promotion (in, ft, yd, mi) |
| [ImperialMassFormatter](docs/ImperialMassFormatter.md) | Imperial mass promotion (oz, lb, st, ton) |
| [MaskFormatter](docs/MaskFormatter.md) | Range-based string masking with Unicode support |
| [MassFormatter](docs/MassFormatter.md) | Metric mass promotion (mg, g, kg, t) |
| [MetricFormatter](docs/MetricFormatter.md) | Metric length promotion (mm, cm, m, km) |
| [NumberFormatter](docs/NumberFormatter.md) | Number formatting with thousands and decimal separators |
| [PatternFormatter](docs/PatternFormatter.md) | Pattern-based string filtering with placeholders |
| [PlaceholderFormatter](docs/PlaceholderFormatter.md) | Template interpolation with placeholder replacement |
| [SecureCreditCardFormatter](docs/SecureCreditCardFormatter.md) | Masked credit card formatting for secure display |
| [TimeFormatter](docs/TimeFormatter.md) | Time promotion (mil, c, dec, y, mo, w, d, h, min, s, ms, us, ns) |

## Contributing

Expand Down
114 changes: 114 additions & 0 deletions docs/CreditCardFormatter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
<!--
SPDX-FileCopyrightText: (c) Respect Project Contributors
SPDX-License-Identifier: ISC
SPDX-FileContributor: Henrique Moody <henriquemoody@gmail.com>
-->

# CreditCardFormatter

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.

## Usage

### Basic Usage with Auto-Detection

```php
use Respect\StringFormatter\CreditCardFormatter;

$formatter = new CreditCardFormatter();

echo $formatter->format('4123456789012345');
// Outputs: "4123 4567 8901 2345" (Visa detected)

echo $formatter->format('371234567890123');
// Outputs: "3712 345678 90123" (Amex, 4-6-5 format)

echo $formatter->format('5112345678901234');
// Outputs: "5112 3456 7890 1234" (MasterCard detected)

echo $formatter->format('36123456789012');
// Outputs: "3612 345678 9012" (Diners Club, 4-6-4 format)

echo $formatter->format('4123456789012345678');
// Outputs: "4123 4567 8901 2345 678" (Visa 19-digit)
```

### Input Cleaning

The formatter automatically removes non-digit characters from the input:

```php
use Respect\StringFormatter\CreditCardFormatter;

$formatter = new CreditCardFormatter();

echo $formatter->format('4123-4567-8901-2345');
// Outputs: "4123 4567 8901 2345"

echo $formatter->format('4123 4567 8901 2345');
// Outputs: "4123 4567 8901 2345"

echo $formatter->format('4123.4567.8901.2345');
// Outputs: "4123 4567 8901 2345"
```

## API

### `format`

- `format(string $input): string`

Formats the input credit card number.

**Parameters:**

- `$input`: The credit card number (can include spaces, dashes, dots, etc.)

**Returns:** The formatted credit card number

## Auto-Detection

The formatter automatically detects card type based on prefix and length:

| Card Type | Prefix Ranges | Length | Format |
| -------------------- | -------------------- | ---------- | --------------------- |
| **American Express** | 34, 37 | 15 | `#### ###### #####` |
| **Diners Club** | 300-305, 309, 36, 38 | 14 | `#### ###### ####` |
| **Diners Club** | 36 | 16 | `#### #### #### ####` |
| **Visa** | 4 | 13, 16 | `#### #### #### ####` |
| **Visa** | 4 | 19 | `#### #### #### #### ###` |
| **MasterCard** | 51-55, 2221-2720 | 16 | `#### #### #### ####` |
| **Discover** | 6011, 644-649, 65 | 16 | `#### #### #### ####` |
| **Discover** | 6011, 644-649, 65 | 19 | `#### #### #### #### ###` |
| **JCB** | 3528-3589 | 16 | `#### #### #### ####` |
| **JCB** | 3528-3589 | 19 | `#### #### #### #### ###` |
| **UnionPay** | 62 | 16 | `#### #### #### ####` |
| **UnionPay** | 62 | 19 | `#### #### #### #### ###` |
| **RuPay** | 60, 65, 81, 82, 508 | 16 | `#### #### #### ####` |

Cards with more than 16 digits automatically use the 19-digit pattern: `#### #### #### #### ###`

## Examples

| Input | Output | Card Type |
| --------------------- | ------------------------- | ------------ |
| `4123456789012345` | `4123 4567 8901 2345` | Visa |
| `4123456789012345678` | `4123 4567 8901 2345 678` | Visa (19) |
| `5112345678901234` | `5112 3456 7890 1234` | MasterCard |
| `341234567890123` | `3412 345678 90123` | Amex |
| `371234567890123` | `3712 345678 90123` | Amex |
| `6011000990139424` | `6011 0009 9013 9424` | Discover |
| `3528000012345678` | `3528 0000 1234 5678` | JCB |
| `36123456789012` | `3612 345678 9012` | Diners Club |
| `6212345678901234` | `6212 3456 7890 1234` | UnionPay |
| `8112345678901234` | `8112 3456 7890 1234` | RuPay |
| `1234567890123456` | `1234 5678 9012 3456` | Unknown |
| `4123-4567-8901-2345` | `4123 4567 8901 2345` | Visa (clean) |

## Notes

- Non-digit characters are automatically removed from the input
- Card type detection is based on card prefix and length (not Luhn validation)
- If card type cannot be determined, uses the default 4-4-4-4 pattern
- Uses `PatternFormatter` internally for formatting
- For custom formatting patterns, use `PatternFormatter` directly
123 changes: 123 additions & 0 deletions docs/SecureCreditCardFormatter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
<!--
SPDX-FileCopyrightText: (c) Respect Project Contributors
SPDX-License-Identifier: ISC
SPDX-FileContributor: Henrique Moody <henriquemoody@gmail.com>
-->

# SecureCreditCardFormatter

The `SecureCreditCardFormatter` formats and masks credit card numbers for secure display. It automatically detects card types, formats them appropriately, and masks sensitive portions.

## Usage

### Basic Usage

```php
use Respect\StringFormatter\SecureCreditCardFormatter;

$formatter = new SecureCreditCardFormatter();

echo $formatter->format('4123456789012345');
// Outputs: "4123 **** **** 2345" (Visa)

echo $formatter->format('341234567890123');
// Outputs: "3412 ****** 90123" (Amex, 4-6-5 format)

echo $formatter->format('5112345678901234');
// Outputs: "5112 **** **** 1234" (MasterCard)

echo $formatter->format('36123456789012');
// Outputs: "3612 ****** 9012" (Diners Club, 4-6-4 format)

echo $formatter->format('4123456789012345678');
// Outputs: "4123 **** **** **** 678" (Visa 19-digit)
```

### Custom Mask Character

```php
use Respect\StringFormatter\SecureCreditCardFormatter;

$formatter = new SecureCreditCardFormatter('X');

echo $formatter->format('4123456789012345');
// Outputs: "4123 XXXX XXXX 2345"
```

### Input Cleaning

The formatter automatically removes non-digit characters from the input:

```php
use Respect\StringFormatter\SecureCreditCardFormatter;

$formatter = new SecureCreditCardFormatter();

echo $formatter->format('4123-4567-8901-2345');
// Outputs: "4123 **** **** 2345"
```

## API

### `SecureCreditCardFormatter::__construct`

- `__construct(string $maskChar = '*')`

Creates a new secure credit card formatter instance.

**Parameters:**

- `$maskChar`: Character to use for masking (default: '\*')

### `format`

- `format(string $input): string`

Formats and masks the input credit card number.

**Parameters:**

- `$input`: The credit card number (can include spaces, dashes, dots, etc.)

**Returns:** The formatted and masked credit card number

## Masking

The formatter applies masking after formatting to ensure predictable positions:

| Card Type | Example Input | Mask Range | Output |
| -------------------- | --------------------- | ----------------- | ------------------------- |
| **Visa** (16) | `4123456789012345` | `6-9,11-14` | `4123 **** **** 2345` |
| **Visa** (19) | `4123456789012345678` | `6-9,11-14,16-19` | `4123 **** **** **** 678` |
| **MasterCard** | `5112345678901234` | `6-9,11-14` | `5112 **** **** 1234` |
| **American Express** | `341234567890123` | `6-11` | `3412 ****** 90123` |
| **Discover** | `6011000990139424` | `6-9,11-14` | `6011 **** **** 9424` |
| **JCB** | `3528000012345678` | `6-9,11-14` | `3528 **** **** 5678` |
| **Diners Club** (14) | `36123456789012` | `6-11` | `3612 ****** 9012` |
| **UnionPay** | `6212345678901234` | `6-9,11-14` | `6212 **** **** 1234` |
| **RuPay** | `8112345678901234` | `6-9,11-14` | `8112 **** **** 1234` |

## Examples

| Input | Output | Card Type |
| --------------------- | ------------------------- | ------------ |
| `4123456789012345` | `4123 **** **** 2345` | Visa |
| `4123456789012345678` | `4123 **** **** **** 678` | Visa (19) |
| `5112345678901234` | `5112 **** **** 1234` | MasterCard |
| `341234567890123` | `3412 ****** 90123` | Amex |
| `371234567890123` | `3712 ****** 90123` | Amex |
| `6011000990139424` | `6011 **** **** 9424` | Discover |
| `3528000012345678` | `3528 **** **** 5678` | JCB |
| `36123456789012` | `3612 ****** 9012` | Diners Club |
| `6212345678901234` | `6212 **** **** 1234` | UnionPay |
| `8112345678901234` | `8112 **** **** 1234` | RuPay |
| `4123-4567-8901-2345` | `4123 **** **** 2345` | Visa (clean) |

## Notes

- Composes `CreditCardFormatter` for formatting and `MaskFormatter` for masking
- Formats the card number first, then applies masking to the formatted string
- Mask ranges are applied to 1-based positions in the formatted string
- Non-digit characters are automatically removed from input
- Inputs with fewer than 9 digits are returned as cleaned digits without formatting or masking
- Uses `CreditCardFormatter` for card type detection and formatting
66 changes: 66 additions & 0 deletions src/CreditCardFormatter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<?php

/*
* SPDX-FileCopyrightText: (c) Respect Project Contributors
* SPDX-License-Identifier: ISC
* SPDX-FileContributor: Henrique Moody <henriquemoody@gmail.com>
*/

declare(strict_types=1);

namespace Respect\StringFormatter;

use function mb_strlen;
use function mb_substr;
use function preg_replace;

final readonly class CreditCardFormatter implements Formatter
{
private const string DEFAULT_16 = '#### #### #### ####';
private const string DEFAULT_19 = '#### #### #### #### ###';
private const string AMEX = '#### ###### #####';
private const string DINERS_14 = '#### ###### ####';

public function format(string $input): string
{
$cleaned = $this->cleanInput($input);
$pattern = $this->detectPattern($cleaned);

$formatter = new PatternFormatter($pattern);

return $formatter->format($cleaned);
}

public function cleanInput(string $input): string
{
return preg_replace('/[^0-9]/', '', $input) ?? '';
}

public function detectPattern(string $input): string
{
$length = mb_strlen($input);
$firstTwo = mb_substr($input, 0, 2);
$firstThree = mb_substr($input, 0, 3);

// American Express: starts with 34 or 37 (15 digits, 4-6-5 format)
if ($firstTwo === '34' || $firstTwo === '37') {
return self::AMEX;
}

// Diners Club International: 14 digits, starts with 300-305, 309, 36, 38
if ($length === 14) {
$prefix3 = (int) $firstThree;
if (($prefix3 >= 300 && $prefix3 <= 305) || $prefix3 === 309 || $firstTwo === '36' || $firstTwo === '38') {
return self::DINERS_14;
}
}

// 19-digit cards (some Visa, Discover, JCB, UnionPay)
if ($length > 16) {
return self::DEFAULT_19;
}

// Default 4-4-4-4: Visa, Mastercard, Discover, JCB, UnionPay, RuPay, etc.
return self::DEFAULT_16;
}
}
4 changes: 4 additions & 0 deletions src/Mixin/Builder.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ interface Builder
{
public static function area(string $unit): FormatterBuilder;

public static function creditCard(): FormatterBuilder;

public static function secureCreditCard(string $maskChar = '*'): FormatterBuilder;

public static function imperialArea(string $unit): FormatterBuilder;

public static function imperialLength(string $unit): FormatterBuilder;
Expand Down
4 changes: 4 additions & 0 deletions src/Mixin/Chain.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ interface Chain extends Formatter
{
public function area(string $unit): FormatterBuilder;

public function creditCard(): FormatterBuilder;

public function secureCreditCard(string $maskChar = '*'): FormatterBuilder;

public function imperialArea(string $unit): FormatterBuilder;

public function imperialLength(string $unit): FormatterBuilder;
Expand Down
Loading