2,547 bytes · the TypeScript implementation · view raw
/** * The Luhn (mod 10) checksum, as used by payment card numbers, IMEIs, * SIM ICCIDs and a long tail of national identifiers. * * Luhn is a *transcription* check. It catches a single mistyped digit and * most adjacent transpositions before a request leaves the building. It says * nothing about whether the card exists, is open, belongs to the person * typing it, or has any money behind it. Only the acquirer can answer that, * so never render "card valid" on the strength of this function. */exportfunction isLuhn(value: string): boolean {
// Non-strings are answered rather than thrown at: "is this valid?" is a// question, and "no" is a complete answer to it.if (typeof value !== "string") returnfalse;
const digits: number[] = [];
for (const ch of value) {
// Card numbers are printed and pasted in four-digit groups, so tolerating// the separators here saves every caller from writing the same strip.if (ch === " " || ch === "-") continue;
if (ch < "0" || ch > "9") returnfalse;
digits.push(ch.charCodeAt(0) - 48);
}
// A lone digit satisfies the arithmetic whenever it is 0, which would make// this function a rubber stamp for a stray keystroke. No identifier scheme// issues one-digit numbers, so two is the honest floor.if (digits.length < 2) returnfalse;
let total = 0;
let position = 0;
for (let i = digits.length - 1; i >= 0; i--, position++) {
let d = digits[i];
if (position % 2 === 1) {
d *= 2;
// Doubling can only reach 18, so subtracting 9 is the same as summing// the two decimal digits, without the string round trip.if (d > 9) d -= 9;
}
total += d;
}
return total % 10 === 0;
}
/** * The digit that would make `prefix` pass the Luhn check. * * Useful for generating test data and for completing a partially known * number; it is the inverse of the check above, not a second opinion on it. */exportfunction luhnCheckDigit(prefix: string): number {
const digits: number[] = [];
for (const ch of prefix) {
if (ch === " " || ch === "-") continue;
if (ch < "0" || ch > "9") return -1;
digits.push(ch.charCodeAt(0) - 48);
}
if (digits.length === 0) return -1;
let total = 0;
let position = 0;
// The check digit will occupy position 0, so the prefix starts at 1.for (let i = digits.length - 1; i >= 0; i--, position++) {
let d = digits[i];
if (position % 2 === 0) {
d *= 2;
if (d > 9) d -= 9;
}
total += d;
}
return (10 - (total % 10)) % 10;
}