How to Validate a Credit Card Number (2026)
Credit card number validation is essential for any payment form, QA testing workflow, or data quality pipeline. This guide covers the complete validation pipeline: the Luhn algorithm (mod-10 checksum), card brand detection by BIN/IIN prefix, length validation, and practical code examples in JavaScript and Python.
Three Layers of Credit Card Validation
Validating a credit card number is not a single check. There are three independent layers, each catching different types of errors:
| Layer | What It Checks | What It Catches |
|---|---|---|
| 1. Luhn Algorithm | Mod-10 checksum of the full number | Typos, single-digit errors, adjacent transpositions |
| 2. Brand + Length | Prefix and length match the card brand rules | Wrong-format numbers, truncated input |
| 3. BIN/IIN Lookup | First 6-8 digits identify the issuing bank | Invalid issuer, suspicious patterns |
The Luhn Algorithm — Step by Step
The Luhn algorithm (also called "mod-10" or "modulus 10") was invented by IBM scientist Hans Peter Luhn in 1954. It is used in every credit card number worldwide, plus IMEI, ICCID, and other identification numbers.
How it works:
- Starting from the rightmost digit (the check digit), moving left, double every second digit.
- If doubling produces a number greater than 9, subtract 9 (or add the two digits together: e.g., 8 × 2 = 16 → 1 + 6 = 7).
- Sum all the digits (both doubled and non-doubled).
- If the total modulo 10 equals 0, the number is valid.
Worked example: 4532 0151 1283 0366
Number: 4 5 3 2 0 1 5 1 1 2 8 3 0 3 6 6
Position: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
Doubling: 8 5 6 2 0 1 10 1 2 2 16 3 0 3 12 6
Adjusted: 8 5 6 2 0 1 1 1 2 2 7 3 0 3 3 6
---
Sum = 8+5+6+2+0+1+1+1+2+2+7+3+0+3+3+6 = 50
50 mod 10 = 0 → VALID
Card Brand Detection by Prefix
Each card brand has a specific prefix (BIN/IIN range) and expected length. You should check these before running the Luhn algorithm to catch obviously wrong formats:
| Brand | Prefix (BIN) | Length | Example |
|---|---|---|---|
| Visa | 4 | 13, 16, or 19 | 4xxx xxxx xxxx xxxx |
| Mastercard | 51-55, 2221-2720 | 16 | 5xxx xxxx xxxx xxxx |
| American Express | 34, 37 | 15 | 3xxx xxxxxx xxxxx |
| Discover | 6011, 65, 644-649 | 16-19 | 6xxx xxxx xxxx xxxx |
| JCB | 3528-3589 | 16-19 | 35xx xxxx xxxx xxxx |
| Diners Club | 300-305, 36, 38, 39 | 14-19 | 3xxx xxxxxx xxxx |
| UnionPay | 62 | 16-19 | 62xx xxxx xxxx xxxx |
JavaScript Implementation
function luhnCheck(cardNumber) {
// Remove spaces and dashes
const digits = cardNumber.replace(/[\s-]/g, '');
if (!/^\d+$/.test(digits)) return false;
let sum = 0;
let isEven = false;
// Process from right to left
for (let i = digits.length - 1; i >= 0; i--) {
let digit = parseInt(digits[i], 10);
if (isEven) {
digit *= 2;
if (digit > 9) digit -= 9;
}
sum += digit;
isEven = !isEven;
}
return sum % 10 === 0;
}
function detectBrand(cardNumber) {
const num = cardNumber.replace(/[\s-]/g, '');
if (/^4/.test(num)) return 'Visa';
if (/^(5[1-5]|2[2-7]\d{2})/.test(num)) return 'Mastercard';
if (/^3[47]/.test(num)) return 'American Express';
if (/^(6011|65|64[4-9])/.test(num)) return 'Discover';
if (/^35(2[89]|[3-8]\d)/.test(num)) return 'JCB';
if (/^3[0-5]|36|38|39/.test(num)) return 'Diners Club';
if (/^62/.test(num)) return 'UnionPay';
return 'Unknown';
}
// Usage
console.log(luhnCheck('4532 0151 1283 0366')); // true
console.log(detectBrand('4532 0151 1283 0366')); // 'Visa'
Python Implementation
def luhn_check(card_number: str) -> bool:
"""Validate a number using the Luhn algorithm."""
digits = card_number.replace(' ', '').replace('-', '')
if not digits.isdigit():
return False
total = 0
is_even = False
for digit in reversed(digits):
d = int(digit)
if is_even:
d *= 2
if d > 9:
d -= 9
total += d
is_even = not is_even
return total % 10 == 0
def detect_brand(card_number: str) -> str:
"""Detect card brand from BIN prefix."""
num = card_number.replace(' ', '').replace('-', '')
if num.startswith('4'):
return 'Visa'
if num[:2] in {'51','52','53','54','55'} or (2221 <= int(num[:4]) <= 2720):
return 'Mastercard'
if num[:2] in {'34', '37'}:
return 'American Express'
if num.startswith(('6011', '65', '644', '645', '646', '647', '648', '649')):
return 'Discover'
if num.startswith('62'):
return 'UnionPay'
return 'Unknown'
# Usage
print(luhn_check('4532 0151 1283 0366')) # True
print(detect_brand('4532 0151 1283 0366')) # Visa
Form Validation Best Practices
When building a credit card input form, follow these UX and security guidelines:
- Auto-format as you type — insert spaces every 4 digits to improve readability
- Detect brand in real-time — show the card brand logo based on the first 1-2 digits
- Validate on blur, not on every keystroke — avoid showing errors while the user is still typing
- Never store raw card numbers — use a PCI-compliant payment processor's tokenization (Stripe, Braintree, etc.)
- Never send card numbers to your server — use the processor's client-side SDK to tokenize directly
- Use inputmode="numeric" on mobile to show the numeric keyboard
- Trim spaces before validation — users may copy-paste with spaces
What BIN/IIN Lookup Adds
BIN (Bank Identification Number, first 6 digits) or IIN (Issuer Identification Number, first 8 digits) lookup goes beyond structural validation — it identifies the issuing bank, card type (debit/credit), country, and card level (platinum, gold, business, etc.).
Common use cases for BIN lookup:
- Surcharge detection (debit vs credit card processing fees differ)
- Fraud scoring (unusual issuing country for the billing address)
- Routing logic (domestic vs international card networks)
- User experience (auto-select currency based on card country)
Frequently Asked Questions
Does a valid Luhn check mean the credit card is real?
No. The Luhn algorithm only verifies that the number is structurally well-formed. It does not confirm the account exists, is active, or has funds. Only a payment processor can verify the account through an authorization request. Luhn validation is for catching typos and data entry errors, not for fraud prevention.
Can I generate test credit card numbers?
Yes. Enter any partial number (without the last digit) in the CardWise Luhn Validator and select "Generate Check Digit" mode. The tool will compute the correct Luhn check digit and append it. This is useful for QA testing of payment forms without using real card data. Most payment processors also provide designated test card numbers (e.g., Stripe's 4242 4242 4242 4242).
What is the difference between Luhn validation and a $0 authorization?
Luhn validation is a client-side mathematical check that costs nothing and catches typos. A $0 (or $1) authorization is a real request to the card network and issuing bank that confirms the account exists and is in good standing. It requires PCI compliance and a payment processor account. Use Luhn for form validation and $0 authorization for account verification.
Is it safe to enter my credit card number in an online validator?
Only if the validator is client-side. CardWise Luhn Validator processes everything in your browser — your card number never leaves your device. Many other online validators send your input to a remote server. Always check for a privacy/client-side statement before entering sensitive numbers.