jlorente/laravel-credit-cards
Laravel Credit Cards is a Laravel package that adds credit card validation rules, brand detection, and helper utilities to your app. Validate card numbers, identify Visa, Mastercard, Amex and other brands, and format or obfuscate numbers with framework-native tools.
Install via Composer and let Laravel auto-discovery register the service provider:
composer require jlorente/laravel-credit-cards
If the package ships a config file, publish it once and commit it:
php artisan vendor:publish --provider="Jlorente\Laravel\CreditCards\CreditCardsServiceProvider"
Look first at three things, in this order: the published config/credit-cards.php (accepted brands, whether brand checks are enforced), the service provider (which validation rule aliases are registered), and the underlying jlorente/php-credit-cards card class that the rules delegate to. The Laravel package is a thin rule layer on top of that class — if you understand the card object's isValid()/brand methods, you understand the package.
First practical use case — a form request for a checkout step:
public function rules(): array
{
return [
'card_number' => ['required', 'string', 'credit_card'],
'expiry_month' => ['required', 'integer', 'between:1,12'],
'expiry_year' => ['required', 'integer', 'min:'.now()->year],
'cvv' => ['required', 'digits_between:3,4'],
];
}
That single credit_card rule gives you Luhn validation plus brand recognition in one pass. Confirm the exact alias in the README — small packages like this sometimes register credit_card_number or require a parameter list for allowed brands. Once it works in a form request, you have covered 80% of realistic usage.
Card validation is a request-shaping concern. Put it in form requests (or a dedicated PaymentRequest), not scattered across controllers or payment services. When the rule fires, the payload is already clean by the time it reaches your gateway integration.
Users paste numbers with spaces, hyphens, or a stray leading quote. Strip non-digits in prepareForValidation() so the rule sees a clean string:
protected function prepareForValidation(): void
{
$this->merge([
'card_number' => preg_replace('/\D+/', '', (string) $this->card_number),
]);
}
Do this once. If you normalize inside every consumer, you will eventually miss one.
If you only take Visa and Mastercard, say so — either in config or as rule parameters — rather than validating the number and then rejecting the brand downstream. A rejected-brand error at the form level is far cheaper than a gateway round-trip:
'card_number' => ['required', 'string', 'credit_card:visa,mastercard'],
Check the README for the exact syntax; some builds read the accepted list from config instead of rule parameters.
The package returns brand identifiers (visa, mastercard, amex, etc.). Your gateway may want master, american_express, or numeric codes. Wrap the mapping in a small service so a future gateway migration is a one-file change:
class CardBrandMapper
{
public function toGateway(?string $brand): string
{
return match ($brand) {
'visa' => 'VISA',
'mastercard' => 'MASTER',
'amex' => 'AMEX',
default => throw new \InvalidArgumentException("Unsupported brand: {$brand}"),
};
}
}
Some flows (webhooks, admin retries, API endpoints) don't run through form requests. Wrap the underlying CreditCard object in a CardValidator service so both paths share one implementation and you can fake it in tests.
Maintain a fixture set: valid Visa/Mastercard/Amex test numbers, numbers that pass Luhn but are for unsupported brands, too-short/too-long numbers, digit-stripped inputs, and empty strings. These fixtures double as your contract test after any dependency bump — including the 1.0.3 release, which is a php-credit-cards version bump and can silently shift BIN tables.
isValid() can still be declined, expired, stolen, or blocked. Never treat a green form request as a green charge.string before credit_card. Passing an integer or array into the rule can throw instead of failing gracefully.php artisan config:cache again (or config:clear in dev). Stale brand lists produce confusing "valid card rejected" reports.'mastercard' in five places.digits_between:3,4) rather than assuming the package enforces brand-specific CVV lengths. Expiry needs a real future-date check — min:now()->year alone accepts a card that expired last month.prepareForValidation(); mismatch is the single most common expiry-validation bug.dd(). Log last-four plus brand only. The package does not reduce your PCI DSS obligations.php-credit-cards version is the product; a bump can change accepted BIN ranges, brand-detection outcomes, and possibly the PHP/Laravel floor. Review the lockfile diff and re-run your fixtures — don't assume behavior is unchanged because the release note is one line.^1.0 constraint, and if you upgrade majors, verify the alias strings in your form requests actually resolve before deploying.How can I help you explore Laravel packages today?