├── .gitignore ├── package.json ├── index.js ├── README.md └── test.js /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cc-luhn", 3 | "description": "Calculate or verify the credit card check digit (luhn)", 4 | "version": "1.0.0", 5 | "repository": "paylike/luhn", 6 | "scripts": { 7 | "test": "node test" 8 | }, 9 | "devDependencies": { 10 | "tape": "^4.5.1" 11 | }, 12 | "keywords": [ "luhn", "credit card", "validate" ], 13 | "license": "MIT" 14 | } 15 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = luhn; 4 | 5 | function luhn( a, b ){ 6 | if (a === true) 7 | return b.slice(-1) === calculate(b.slice(0, -1)); 8 | 9 | return calculate(b || a); 10 | } 11 | 12 | function calculate( numbers ){ 13 | var length = numbers.length; 14 | 15 | // Step 1 + 2 16 | var product = numbers.split('').reduce(function( p, c, i ){ 17 | var isAlternate = (i + length) % 2 === 1; 18 | 19 | return p + (isAlternate ? sumDigits((+c) * 2) : +c); 20 | }, 0); 21 | 22 | // Step 3 23 | return ((10 - (product % 10)) % 10)+''; 24 | } 25 | 26 | function sumDigits( number ){ 27 | return (number+'').split('').reduce(sum, 0); 28 | } 29 | 30 | function sum( a, b ){ 31 | return +a + +b; 32 | } 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Calculate/verify check digit (luhn) of credit cards 2 | 3 | ```js 4 | // Is the card number valid? 5 | var card = '6123451234567893'; 6 | 7 | luhn(true, pan); // true 8 | 9 | 10 | // Calculate check digit 11 | 12 | // MII + IIN + "Individual account number" 13 | var incomplete = '612345123456789'; 14 | 15 | luhn(incomplete); // "3" 16 | ``` 17 | 18 | Implemented according to ISO/IEC 7812-1:2015(E) which goes: 19 | 20 | 1. Double the value of alternate digits beginning with the first right-hand 21 | digit (low order). 22 | 23 | 2. Add the individual digits comprising the products obtained in Step 1 to 24 | each of the unaffected digits in the original number. 25 | 26 | 3. Subtract the total obtained in Step 2 from the next higher number ending in 27 | 0 [this is the equivalent of calculating the “tens complement” of the 28 | low-order digit (unit digit) of the total]. If the total obtained in Step 2 29 | is a number ending in zero (30, 40, etc.), the check digit is 0. 30 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const test = require('tape'); 4 | const luhn = require('./'); 5 | 6 | test(function( t ){ 7 | const pans = [ 8 | // from the ISO standard 9 | '6123451234567893', 10 | 11 | // http://www.paypalobjects.com/en_US/vhelp/paypalmanager_help/credit_card_numbers.htm 12 | '4012111111111111', 13 | '378282246310005', 14 | '371449635398431', 15 | '378734493671000', 16 | '5610591081018250', 17 | '30569309025904', 18 | '38520000023237', 19 | '6011111111111117', 20 | '6011000990139424', 21 | '3530111333300000', 22 | '3566002020360505', 23 | '5555555555554444', 24 | '5105105105105100', 25 | '4111111111111111', 26 | '4012888888881881', 27 | '4222222222222', 28 | '5019717010103742', 29 | '6331101999990016', 30 | ]; 31 | 32 | const invalid = [ 33 | '76009244561', 34 | '4571736004738485', 35 | ]; 36 | 37 | pans.forEach(function( pan ){ 38 | t.equal(luhn(true, pan), true, pan+' is valid'); 39 | t.equal(luhn(pan.slice(0, -1)), pan.slice(-1)); 40 | }); 41 | 42 | invalid.forEach(function( pan ){ 43 | t.equal(luhn(true, pan), false, pan+' is invalid'); 44 | }); 45 | 46 | t.end(); 47 | }); 48 | --------------------------------------------------------------------------------