Luhn Algorithm: Validating Uzcard, Humo and Visa Card Numbers
Practice5 minInnovialabs editorial team
In short
The Luhn algorithm is a simple checksum that catches most typos in a card number before the form is submitted; it cannot tell whether a card exists — only the payment provider can.
What the Luhn algorithm checks
The Luhn algorithm (mod 10) is a checksum formula. The last digit of a card number is chosen so that a sum calculated by a specific rule is divisible by 10. If a user mistypes one digit or swaps two neighbouring digits, the sum almost always stops adding up.
What Luhn does not do:
- check that the card exists and is active;
- check the balance, expiry date or owner;
- protect against fraud.
It is a user-experience check: flag a typo immediately instead of after a round trip to the provider.
How the sum is calculated
- Walk the digits from right to left. The last digit is the check digit; leave it as is.
- Multiply every second digit (second from the right, fourth from the right and so on) by 2.
- If the result is greater than 9, subtract 9 (the same as adding its digits: 16 → 1 + 6 = 7).
- Add up all the digits.
- The number is valid if the sum is divisible by 10.
Example with 79927398713: right to left we get 3, 2, 7, 7, 9, 6, 7, 4, 9, 9, 7. The sum is 70, divisible by 10, so the number passes.
A known limitation: swapping adjacent 09 ↔ 90 goes unnoticed.
JavaScript implementation
function luhnValid(input) {
const digits = String(input).replace(/[\s-]/g, "");
if (!/^[0-9]{12,19}$/.test(digits)) return false;
let sum = 0;
let double = false;
for (let i = digits.length - 1; i >= 0; i--) {
let d = digits.charCodeAt(i) - 48;
if (double) {
d *= 2;
if (d > 9) d -= 9;
}
sum += d;
double = !double;
}
return sum % 10 === 0;
}
luhnValid("4111 1111 1111 1111"); // true
luhnValid("4111 1111 1111 1112"); // false
Python implementation
import re
def luhn_valid(number: str) -> bool:
digits = re.sub(r"[\s-]", "", number)
if not re.fullmatch(r"[0-9]{12,19}", digits):
return False
total = 0
for i, ch in enumerate(reversed(digits)):
d = int(ch)
if i % 2 == 1:
d *= 2
if d > 9:
d -= 9
total += d
return total % 10 == 0
assert luhn_valid("4111 1111 1111 1111")
assert not luhn_valid("4111-1111-1111-1112")
Note that digits are matched with [0-9], not \d. In Python, \d also matches digits from other scripts by default.
Card type by BIN
The first digits of the number — the BIN (IIN) — identify the payment network and the issuing bank. For an interface hint (a logo, the mask length) a prefix is usually enough:
| Network | Typical prefix |
|---|---|
| Humo | 9860 |
| Uzcard | 8600 |
| Visa | 4 |
| Mastercard | 51–55, 2221–2720 |
function detectBrand(digits) {
if (digits.startsWith("9860")) return "humo";
if (digits.startsWith("8600")) return "uzcard";
if (digits.startsWith("4")) return "visa";
const p2 = Number(digits.slice(0, 2));
const p4 = Number(digits.slice(0, 4));
if ((p2 >= 51 && p2 <= 55) || (p4 >= 2221 && p4 <= 2720)) return "mastercard";
return "unknown";
}
Keep in mind:
- BIN ranges change; new ranges and co-badged cards appear. Keep the prefix table in config rather than scattered through the code.
- Check longer prefixes before shorter ones.
- For local networks, confirm in your provider's documentation whether the Luhn check applies to their numbers. If you are not sure, show a warning rather than blocking submission.
unknownis not an error. Do not stop a user from paying with a card your code does not recognise.
Input mask
Grouping by 4 digits makes the number much easier to check by eye:
const format = (v) =>
v.replace(/[^0-9]/g, "").slice(0, 19).replace(/(\d{4})(?=\d)/g, "$1 ");
Practical tips:
- Use
<input inputmode="numeric" autocomplete="cc-number">, nottype="number": the latter mangles leading zeros and shows spinner arrows. - Accept pasted values with spaces and dashes — clean them instead of rejecting them.
- When reformatting, preserve the caret position, or it jumps to the end when the user edits in the middle.
- Run the Luhn check once the full length is entered, not on every keystroke.
Real validation happens at the provider
Client-side checks only filter typos. Whether the card exists, the balance, 3-D Secure or SMS confirmation and limits are checked by the payment provider.
- Where possible, use the provider's payment page or hosted fields and tokenization, so the card number never passes through your server.
- Do not store the full card number in your database or logs. Handling card data is governed by the PCI DSS standard.
- Repeat the check on the server if the number does reach you: client code is easy to bypass.