laravel-validation-rules/credit-card
Laravel validation rules for credit cards: validate card numbers with Luhn, detect major brands, and check expiration dates and CVV codes. Drop-in rules for form requests and validators, with clear error messages and support for common card types.
composer require laravel-validation-rules/credit-card — Laravel package discovery registers the service provider, so no manual wiring is needed.src/Rules/ — that's the whole surface area of the package. There is essentially no configuration file to publish.credit_card rule, then constrain the network with credit_card_type.// app/Http/Requests/StorePaymentMethodRequest.php
public function rules(): array
{
return [
'card_number' => ['required', 'string', 'credit_card'],
'card_type' => ['required', 'string', 'credit_card_type:visa,mastercard,amex'],
];
}
Return generic messages ("The card number is invalid.") rather than echoing the input back — you do not want a PAN in a validation error or log line.
Form requests are the right home. Keep the rule strings in a FormRequest (or a dedicated PaymentMethodRequest) rather than in controllers, so the same rules can be reused by a JSON API endpoint and a Blade form.
Normalise before you validate. Users paste 4111 1111 1111 1111 or 4111-1111-1111-1111. Strip whitespace and dashes in prepareForValidation() so the rule sees a clean digits-only string, and store the normalised value, not the raw input:
protected function prepareForValidation(): void
{
$this->merge([
'card_number' => preg_replace('/\D+/', '', (string) $this->input('card_number')),
]);
}
Layer the rules. credit_card answers "is this a structurally valid number?" — it does not tell you the network, the expiry, or the CVV. Combine it with credit_card_type:* to enforce what you actually accept, and validate expiry/CVV as separate fields so the error messages point at the right input.
Gate on declared type only as a hint. Run credit_card_type against the detected number, not against a user-selected dropdown — otherwise a user can submit a valid Visa while claiming it's an Amex and you'll branch your payment gateway call incorrectly.
Test with known-good fixtures. Keep a small set of publicly documented test numbers (one per network) plus a deliberately bad Luhn checksum, and assert both the pass and the fail path in a Pest/PHPUnit feature test. After 1.12.3, also add a test that an array or null fails validation rather than blowing up.
Don't reinvent tokenisation. This package validates shape; it does not touch a gateway. In practice you validate, then immediately hand off to Stripe/Cashier and persist only the token plus last four digits.
null, int) could throw a TypeError and surface as a 500. It now fails validation normally. Two consequences: (a) any test that asserted an exception for malformed input must be updated to assert a validation error, and (b) if you were relying on the exception to mask a bug in your input handling, that bug is now silent — check your nullable/sometimes usage is deliberate.nullable/required and string before credit_card so the intent is explicit and you don't rely on the rule's internal type coercion. ['nullable', 'string', 'credit_card'] on an optional field is the safe shape.credit_card rule means the digits are well-formed — nothing more. Never treat it as authorisation, and never persist the full PAN; keep it in memory, hand it to the gateway, discard it. Watch for card numbers leaking into Log::info($request->all()) or ->validate() exception payloads in debug output.attributes()/messages() entry so the user sees "card number" rather than card_number, and keep the message non-revealing about why it failed.Rule class that delegates to the package's validator object and returns your own ValidationRule — that keeps you upgradeable.How can I help you explore Laravel packages today?