jlorente/php-credit-cards
A PHP package for validating credit card numbers and detecting card brands using length checks, Luhn algorithm, and issuer patterns. Includes Laravel validation support, making it easy to reject invalid payment details and identify Visa, Mastercard, Amex, and more.
Install the package with Composer:
composer require jlorente/php-credit-cards
Where to look first:
src/CreditCardValidator.php — the main entry point (isValid, isValidType, getType, getTypes, formatting).src/Cards/ — one class per brand, each defining prefix patterns and length rules. This is where brand behavior actually lives.tests/ — the fastest source of truth for accepted test numbers, argument order, and edge cases (e.g., Amex, Maestro, Elo, Hipercard).composer.json — confirm required PHP version before adding it to an older application.First use case — validate a card number and detect its brand at checkout:
use Jlorente\CreditCards\CreditCardValidator;
$validator = new CreditCardValidator();
$number = preg_replace('/\D+/', '', $request->input('card_number')); // "4111 1111 1111 1111" -> digits
if ($validator->isValid($number)) {
$type = $validator->getType($number); // e.g. "Visa"
}
The validator is stateless and dependency-free, so a single instance can be reused across a request. It does not perform network calls, BIN lookups, or authorization — it only validates structure and brand.
Normalize once, validate everywhere. Card input arrives with spaces, hyphens, or mixed formatting. Strip non-digits at the boundary (form request, controller, or a small CardNumber value object) and pass a clean digit string into the validator. This makes storage, logging, and re-validation consistent.
Brand whitelisting for a payment provider. Most processors accept only a subset of brands. Validate structurally, then check the detected brand against an allow-list:
$validator = new CreditCardValidator();
$number = preg_replace('/\D+/', '', $input);
$type = $validator->getType($number);
$accepted = ['Visa', 'MasterCard', 'AmericanExpress'];
if ($validator->isValid($number) && in_array($type, $accepted, true)) {
// proceed to tokenization
}
isValidType('Visa', $number) is the direct alternative when you already know the expected brand. Confirm the argument order against the tests before committing it to a custom rule.
Laravel custom rule. Wrap the package so validation messages and normalization live in one place:
use Illuminate\Contracts\Validation\Rule;
use Jlorente\CreditCards\CreditCardValidator;
class ValidCardNumber implements Rule
{
public function passes($attribute, $value): bool
{
$number = preg_replace('/\D+/', '', (string) $value);
return strlen($number) >= 12
&& (new CreditCardValidator())->isValid($number);
}
public function message(): string
{
return 'The :attribute is not a valid card number.';
}
}
Use it as 'card_number' => ['required', 'string', new ValidCardNumber]. If you also need brand enforcement, add a second rule that checks getType().
Service-layer wrapper. In larger applications, hide the package behind your own CardValidator service. This gives you a stable API if the library is swapped, and a single place to add metrics, logging, or brand policy. Avoid sprinkling new CreditCardValidator() through controllers.
Formatting for display. Use the package’s formatting support for readable output, but treat formatting as presentation only — never re-validate a formatted string without normalizing first. For masking, keep a prefix and the last four digits:
$masked = substr($number, 0, 6)
. str_repeat('*', max(0, strlen($number) - 10))
. substr($number, -4);
Group digits according to brand when displaying (Amex 4-6-5, Diners 4-6-4, most others 4-4-4-4, with 19-digit variants needing a trailing group).
Testing. Keep a fixture set of known-good and known-bad numbers per brand and test your wrapper, not just the package. Include boundary lengths (shortest and longest valid per brand), separators, and near-miss prefixes that should not match.
isValid() as authorization.strlen($number) === 16 in your own wrapper — rely on the brand rules and only add a sanity bound.getType() (e.g., Visa, MasterCard, AmericanExpress) in allow-lists, and compare with strict in_array(..., true).src/Cards/. Adding a brand generally means adding a card class with prefix/length rules and registering it alongside the existing ones. Check the validator’s configuration and the tests for the expected registration mechanism before extending.composer.json and run your fixture tests after any upgrade, since brand prefix tables and supported-brand lists evolve.How can I help you explore Laravel packages today?