rap2hpoutre/laravel-credit-card-validator
Laravel validator rule for credit card numbers that checks card validity and detects the brand (Visa, Mastercard, Amex, and more). Drop it into validation rules to verify cards with Luhn checks and type detection.
Install
composer require rap2hpoutre/laravel-credit-card-validator
Laravel 5.5+ auto-discovers the service provider — there is no config file to publish and nothing to register manually. If you are on an older Laravel, or discovery is disabled, add the provider in config/app.php.
Where to look first
Open vendor/rap2hpoutre/laravel-credit-card-validator/src/ and read the service provider. Two things to confirm before you build on it:
credit_card).Visa vs visa, MasterCard vs Mastercard).Because the package is small and last released in 2020, the source is short enough to audit in a few minutes — do that once rather than guessing at behavior.
First use case — a checkout FormRequest:
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class CheckoutRequest extends FormRequest
{
public function rules(): array
{
return [
'card_number' => ['required', 'string', 'credit_card'],
'name_on_card' => ['required', 'string', 'max:255'],
];
}
}
public function store(CheckoutRequest $request)
{
// $request->validated('card_number') is now Luhn-valid
}
Invalid or mistyped numbers return a normal ValidationException (HTTP 422 / redirect with errors), so it drops straight into existing form flows.
Type-checked numbers. Where supported, pass the expected brand as an argument so a Visa number submitted to a "Mastercard" flow is rejected:
'card_number' => ['required', 'credit_card:Visa'],
Confirm the argument syntax against the package source, then centralize brand strings in a constant or enum so they don't drift across requests.
Normalize input before validating. Card fields get pasted with spaces and dashes. Strip them in prepareForValidation() rather than trusting users:
protected function prepareForValidation(): void
{
if ($this->filled('card_number')) {
$this->merge([
'card_number' => preg_replace('/\D+/', '', (string) $this->input('card_number')),
]);
}
}
Pair it with the non-number checks. The rule only covers the number. Add expiry and CVC as separate rules:
'card_number' => ['required', 'credit_card'],
'exp_month' => ['required', 'integer', 'between:1,12'],
'exp_year' => ['required', 'integer', 'min:' . now()->year],
'cvc' => ['required', 'digits_between:3,4'],
Custom messages. Either per-request:
public function messages(): array
{
return [
'card_number.credit_card' => 'That card number doesn\'t look right — please check it.',
];
}
…or globally in lang/en/validation.php with a credit_card key, so every form inherits the copy. Use the per-request override where the wording differs (e.g., "Please re-enter the card number").
API / Livewire reuse. The rule is just a string, so it works identically in $request->validate([...]), Livewire component rules(), or a queued job's validator. Extract a shared trait or rules class so the checkout form and the API endpoint cannot diverge.
Test with known numbers. In feature tests, exercise both sides of the check:
// Luhn-valid
$this->post('/checkout', ['card_number' => '4242424242424242'])->assertSessionHasNoErrors();
$this->post('/checkout', ['card_number' => '5555555555554444'])->assertSessionHasNoErrors();
$this->post('/checkout', ['card_number' => '378282246310005'])->assertSessionHasNoErrors();
// Bad checksum
$this->post('/checkout', ['card_number' => '4242424242424241'])->assertSessionHasErrors('card_number');
// Formatted input, after normalization
$this->post('/checkout', ['card_number' => '4242 4242 4242 4242'])->assertSessionHasNoErrors();
Integration tip with a payment provider. Validate locally before you call Stripe/PayPal. You get an instant, cheap 422 with a field-level error instead of a provider round-trip and an opaque decline — and you keep obviously malformed numbers out of your gateway logs. The gateway remains authoritative; this rule is just a pre-filter.
prepareForValidation(), 4242 4242 4242 4242 and 4242-4242-4242-4242 will be rejected even though they're valid cards. Normalize first; this is the single most common source of "the rule is broken" bug reports.0000000000000000. Add digits_between:13,19 and/or an explicit non-zero check if your UI can submit junk like that.Mastercard vs MasterCard vs mastercard will silently reject valid cards. Read the package's brand map once and use those literals.Validator::extend / rule registration) has been stable, so it generally still works on modern Laravel — but verify it loads on your PHP/Laravel combination before committing to it, and add a feature test that pins the behavior. If a future Laravel breaks it, the rule is a dozen lines of Luhn logic — fork it or inline the rule rather than waiting for upstream.composer dump-autoload and confirm the provider is listed in config/app.php. With auto-discovery, clear bootstrap/cache/packages.php and config.php.required alongside credit_card unless the field is genuinely optional.Rule object when you need brand-plus-gateway logic in one place, and consider adding a small card_type rule that reuses the package's detector to persist the brand (visa, mastercard) alongside the last four — useful for display and for routing.How can I help you explore Laravel packages today?