Reputation: 11962
How can I check if a string is a valid EAN / GTIN barcode in JavaScript?
I need checks for EAN8, EAN12, EAN13, EAN14, EAN18 and also GTIN12, GTIN13, GTIN14.
Upvotes: 18
Views: 30501
Reputation: 408
If you only need UPC and EAN you might consider using this simple function.
function validateUpcEan(code) {
let c = code.split(""), d = Number(c.pop()), o = e = 0;
for (i = 0; i < c.length; i++) {
if (i % 2 === 0)
o += Number(c[i]);
else
e += Number(c[i]);
}
return ((o * (code.length == 12 ? 3 : 1)) + (e * (code.length == 13 ? 3 : 1)) + d) % 10 === 0;
}
Upvotes: 0
Reputation: 3000
I managed to create a simplified barcode validation function testing the EAN digit check (Modulo-10 algorithm) which is used across different numeric barcodes and for book ISBN numbers.
It works with any EAN barcodes of any length like: GTIN-8, GTIN-12 (UPC), GTIN-13 (EAN), ISBN, GTIN-14 (ITF-14) and GTIN18 (SSCC)
function isValidBarcode(number) {
const checkDigit = String(number).slice(0, -1).split('').reverse().reduce((sum, v, i) => sum + v * (i % 2 || 3), 0)*9%10
return /^\d+$/.test(number) && String(checkDigit) === String(number).at(-1)
}
If you're targeting a specific length of barcode you can also check that:
const isValidBarcodeAndSize = [12,13].includes(String(myBarcode).length) && isValidBarcode(myBarcode)
Upvotes: 0
Reputation: 10340
GS1 US published the check digit calculation algorithm for GTIN. It uses padding to calculate various barcodes and is actually a lot simpler than other methods I found above here.
It works with GTIN barcodes: GTIN-8, GTIN-12 (UPC), GTIN-13 (EAN) and GTIN-14 (ITF-14).
function isValidBarcode(value) {
// We only allow correct length barcodes
if (!value.match(/^(\d{8}|\d{12,14})$/)) {
return false;
}
const paddedValue = value.padStart(14, '0');
let result = 0;
for (let i = 0; i < paddedValue.length - 1; i += 1) {
result += parseInt(paddedValue.charAt(i), 10) * ((i % 2 === 0) ? 3 : 1);
}
return ((10 - (result % 10)) % 10) === parseInt(paddedValue.charAt(13), 10);
}
Upvotes: 5
Reputation: 22138
This is what I came up with:
/**
* Test a string for valid EAN5 EAN8 EAN13 EAN14 EAN18
* @see: https://www.activebarcode.com/codes/ean13.html
* @param {string} ean A string to be tested
* @return {boolean} true for a valid EAN
* @author Vitim.us <https://stackoverflow.com/a/65928239/938822>
*/
function isValidEAN(ean) {
function testChecksum(ean) {
const digits = ean.slice(0, -1);
const checkDigit = ean.slice(-1) | 0;
let sum = 0;
for (let i = digits.length - 1; i >= 0; i--) {
sum += (digits.charAt(i) * (1 + (2 * (i % 2)))) | 0;
}
sum = (10 - (sum % 10)) % 10;
return sum === checkDigit;
}
ean = String(ean);
const isValidLength = ean.length === 18 || ean.length === 14 || ean.length === 13 || ean.length === 8 || ean.length === 5;
return isValidLength && /^\d+$/.test(ean) && testChecksum(ean);
}
Upvotes: 2
Reputation: 9668
I am not sure why, but @doms solution did not work correctly for me. Also I would like to both calculate new codes as well as verify old ones. I ended up with this, that I have verified to be working in my browsers atleast:
function eanCheckDigit(s){
var result = 0;
for (let counter = s.length-1; counter >=0; counter--){
result = result + parseInt(s.charAt(counter)) * (1+(2*(counter % 2)));
}
return (10 - (result % 10)) % 10;
}
2020 Update - Had to add let
in front of counter otherwise it was saying counter was not defined.
2020 2nd Update - After lots of fighting with this, I realized this formula only works for UPC's passed in that are 10(or even digits in length). If you pass one in that is 11 digits, this doesn't work. So I've modified it to work with any length UPC. Let me know if this can be written cleaner.
function eanCheckDigit(s){
let result = 0;
let i = 1;
for (let counter = s.length-1; counter >=0; counter--){
result = result + parseInt(s.charAt(counter)) * (1+(2*(i % 2)));
i++;
}
return (10 - (result % 10)) % 10;
}
Upvotes: 18
Reputation: 507
Here is my solution, checking for different length barcodes using the specification to calculate the check digit at the end (see note):
// ean/gtin validation for 8, 12, 13 & 14 digit barcodes
function codeOnBlur(barcode) {
var barcodeLengthArr = [8, 12, 13, 14];
var allowedChars = new RegExp(/\d{8,14}/); // >7 & <15
// put numbers in array and convert to type Int.
var barcodeArray = barcode.split('');
for( var i = 0; i < barcodeArray.length; i++) {
barcodeArray[i] = parseInt(barcodeArray[i], 10);
}
// get the last digit for checking later
var checkDigit = barcodeArray.slice(-1)[0];
// we'll need a to compare it to this:
var remainder = 0;
// check if input (barcode) is in the array and check against the regex.
if (($.inArray(barcode.length, barcodeLengthArr) > -1) && (allowedChars.test(barcode))) {
console.log("barcodeArray ", barcodeArray, " :: checkDigit ", checkDigit);
// Pop the last item from the barcode array, test if the length is
// odd or even (see note on calculating the check digit) and
// multiply each item in array based in position:
var total = 0;
barcodeArray.pop();
// odd length after pop
if (barcodeArray.length % 2 === 1) {
for (var i = barcodeArray.length - 1; i >= 0; i--) {
barcodeArray[i] = i % 2 === 0 ? barcodeArray[i] * 3 : barcodeArray[i] * 1;
total += barcodeArray[i];
}
// even length after pop
} else if (barcodeArray.length % 2 === 0) {
for (var i = barcodeArray.length - 1; i >= 0; i--) {
barcodeArray[i] = i % 2 === 0 ? barcodeArray[i] * 1 : barcodeArray[i] * 3;
total += barcodeArray[i];
}
} else {
// validation passed = false
}
// calculate the remainder of totalrounded up to nearest multiple of 10:
remainder = (Math.ceil((total + 1) / 10) * 10) - total;
console.log("loop total = ", total, ", remainder: ", remainder);
if ( remainder === checkDigit ) {
//validation passed = true;
return;
} else {
//validation passed = false;
}
} else {
//validation Passed = false;
}
}
I'm certain this code can be tidied up some :)
Manually checking the "integrity bit" or check digit:
barcode: 13: 4 0 1 1 2 0 0 2 9 6 9 0 8
8: 5 0 8 1 8 9 0 7
multiplier: 3 1 3 1 3 1 3 1 3 1 3 1 check digit
To take the 8 digit code working backwards:
0*1 + 9*3 + 8*1 + 1*3 + 8*1 + 0*3 + 5*1 = 73
Difference from 73 to 80 is 7 (the specification will have you round up to
the nearest power of 10).
7 is both the check digit and the remainder of 80-73.
Upvotes: 1
Reputation: 1212
I'm sorry if this code is a little too long, but this is what I have for verifying an EAN13 barcode:
function isBarcode(barcode) {
if (typeof barcode === 'number') {
throw 'RuntimeError: Barcode MUST NOT be in number format'
} else if (barcode.length!==12) {
throw 'RuntimeError: String length is not 12'
};
var _= barcode.toString().split("")
var _1 = 0
var _2 = 0
var __
for ($=0;$<=10;++$) {
_1+=+_[$]
};for ($=10;$>=0;$-=2) {
_2+=+_[$]
};_2*=2
var _3 = _1+_2
__=+_3.toString().substring(1,2)
if (__>9) {
__=+_3.toString().substring(1,2)
} else if (__===0) {
__=10
};
__=10-__
if (__===+_[11]) {
return true
}
return false
};
Upvotes: -1
Reputation: 139
Here is a short version that can check if the EAN13 check digit is valid:
var checkSum = ean.split('').reduce(function(p,v,i) {
return i % 2 == 0 ? p + 1 * v : p + 3 * v;
}, 0);
if (checkSum % 10 != 0) {
alert('error');
}
Upvotes: 9
Reputation: 11962
EDIT I also created a npm module, which can be found on github.
I created a small library, which supports EAN8, EAN12, EAN13, EAN14, EAN18, GTIN12, GTIN13 and GTIN14.
It works inside node.js and all modern browsers.
barcoder.js:
/*!
* Barcoder
* Copyright (c) 2013 mifitto GmbH <[email protected]>
* MIT Licensed
*/
(function() {
'use strict';
/**
* Library version.
*/
var version = '1.1.0';
/**
* Supported formats
*/
var minValidLength = 6;
var maxValidLength = 18;
var usualValidChars = /^\d+$/;
var formats = {
'ean8' : { validChars : /^\d+$/, validLength : 8 },
'ean12' : { validChars : /^\d+$/, validLength : 12 },
'ean13' : { validChars : /^\d+$/, validLength : 13 },
'ean14' : { validChars : /^\d+$/, validLength : 14 },
'ean18' : { validChars : /^\d+$/, validLength : 18 },
'gtin12' : { validChars : /^\d+$/, validLength : 12 },
'gtin13' : { validChars : /^\d+$/, validLength : 13 },
'gtin14' : { validChars : /^\d+$/, validLength : 14 }
};
/**
* Validates the checksum (Modulo 10)
* GTIN implementation factor 3
*
* @param {String} value The barcode to validate
* @return {Boolean}
* @api private
*/
var validateGtin = function( value ) {
var barcode = value.substring( 0, value.length - 1 );
var checksum = parseInt( value.substring( value.length - 1 ), 10 );
var calcSum = 0;
var calcChecksum = 0;
barcode.split('').map(function( number, index ) {
number = parseInt( number, 10 );
if ( value.length % 2 === 0 ) {
index += 1;
}
if ( index % 2 === 0 ) {
calcSum += number;
}
else {
calcSum += number * 3;
}
});
calcSum %= 10;
calcChecksum = (calcSum === 0) ? 0 : (10 - calcSum);
if ( calcChecksum !== checksum ) {
return false;
}
return true;
};
/**
* Barcoder class
*
* @param {string} format See formats
* @param {Object} options Valid option `enableZeroPadding`, defaults to `true`
* @api public
*/
var Barcoder = function ( format, options ) {
if ( format && !formats[format] ) throw new Error( '"format" invalid' );
this.format = (format) ? formats[format] : 'autoSelect';
this.options = (options) ? options : { enableZeroPadding : true };
if ( !this.options.enableZeroPadding ) {
this.options.enableZeroPadding = true;
}
};
/**
* Validates a barcode
*
* @param {string} barcode EAN/GTIN barcode
* @return {Boolean}
* @api public
*/
Barcoder.prototype.validate = function( barcode ) {
var self = this;
if ( self.format === 'autoSelect' ) {
if ( barcode.length < minValidLength || barcode.length > maxValidLength ) {
return false;
}
var isValidGtin = validateGtin( barcode );
var paddedBarcode = barcode;
var successfullyPadded = false;
if ( !isValidGtin ) {
var possiblyMissingZeros = maxValidLength - barcode.length;
while( possiblyMissingZeros-- ) {
paddedBarcode = '0' + paddedBarcode;
if ( validateGtin( paddedBarcode ) ) {
isValidGtin = true;
successfullyPadded = true;
break;
}
}
}
return {
possibleType: (barcode.length > 8) ? 'GTIN' + barcode.length : 'EAN8 / padded GTIN',
isValid: isValidGtin
};
}
var validChars = self.format.validChars;
var validLength = self.format.validLength;
var enableZeroPadding = self.options.enableZeroPadding;
if ( validChars.exec( barcode ) === null ) {
return false;
}
if ( enableZeroPadding && barcode.length < validLength ) {
var missingZeros = validLength - barcode.length;
while( missingZeros-- ) {
barcode = '0' + barcode;
}
}
else if ( !enableZeroPadding && barcode.length != validLength ) {
return false;
}
else if ( barcode.length > validLength ) {
return false;
}
return validateGtin( barcode );
};
/**
* Export
*/
if ( 'undefined' !== typeof module && module.exports ) {
module.exports = Barcoder;
exports.version = version;
}
if ( 'undefined' === typeof ender ) {
this['Barcoder'] = Barcoder;
}
if ( 'function' === typeof define && define.amd ) {
define('Barcoder', [], function () {
return Barcoder;
});
}
}).call( this );
Installation:
$ npm install barcoder
Usage:
var Barcoder = require('barcoder');
var ean1 = '0016T20054453';
var ean2 = '9330071314999';
var validator = new Barcoder('ean13');
console.log( '%s ean1 is valid: %s', ean1, validator.validate( ean1 ) );
console.log( '%s ean2 is valid: %s', ean1, validator.validate( ean2 ) );
// or /w automatic type selection
validator = new Barcoder();
var validation1 = validator.validate( ean1 );
var validation2 = validator.validate( ean2 );
console.log( '%s is valid: %s and has guessed type: %s', ean1, validation1.isValid, validation1.possibleType );
console.log( '%s is valid: %s and has guessed type: %s', ean2, validation2.isValid, validation2.possibleType );
Upvotes: 33