UNPKG

atm-fits

Version:
269 lines (223 loc) 8.86 kB
function FITsService(settings, log, trace){ this.FITs = settings.get('FITs'); if(!this.FITs) this.FITs = {}; /** * [d2h convert decimal string to hex string] * @param {[type]} d [decimal from 0 to 255] * @return {[type]} [hex string, padded with zeroes] */ this.d2h = function(d) { if(d < 10) return '0' + (+d).toString(16).toUpperCase(); else return (+d).toString(16).toUpperCase(); }; /** * [leftpad padd the string with zeroes from left] * @param {[type]} string [description] * @return {[type]} [description] */ this.leftpad = function(string){ if(string.length % 3 === 1) return '00' + string; else if (string.length % 3 === 2) return '0' + string; else return string } /** * [decimal2hex convert decimal string to hex string. * The FIT data is sent to the terminal in decimal, so * to construct the FIT Data load message, each digit triplet must be * converted from decimal to hex, producing a two‐character string * in the range 00‐FF.] * @param {[type]} decimal [decimal string received in the FIT message from host, e.g. 065 136 037 255 255] * @return {[type]} [hex string representation, e.g. 41 88 25 FF FF] */ this.decimal2hex = function(decimal){ var padded = this.leftpad(decimal) var hex = ''; for (var i = padded.length - 3; i >= 0; i -= 3) hex = this.d2h(padded.substr(i, 3)).concat(hex); return hex; } this.parseFIT = function(data){ var parsed = {}; if(!data){ if(log) log.info('FITsService.parseFIT(): empty data'); return false; } var i = 0; var field_length = 0; // Insitution ID index field_length = 1.5 * 2; // The field in NDC manual is referred as '2 Digits', assuming 2 hex digits, which is 3 decimal digits. parsed.PIDDX = data.substr(i, field_length); i += field_length + 3; // Institution ID field_length = 1.5 * 10; // 10 digits parsed.PFIID = this.decimal2hex(data.substr(i, field_length)); i += field_length; // PSTDX (Indirect Next State Index) field_length = 1.5 * 2; parsed.PSTDX = this.decimal2hex(data.substr(i, field_length)); i += field_length; // PAGDX (Algorithm/Bank ID Index) parsed.PAGDX = this.decimal2hex(data.substr(i, field_length)); i += field_length; // PMXPN (Maximum PIN Digits Entered) - Maximum number of PIN digits allowed for the cardholder to enter parsed.PMXPN = this.decimal2hex(data.substr(i, field_length)) i += field_length; // PCKLN (Maximum PIN Digits Checked) - Number of digits used for local PIN check parsed.PCKLN = this.decimal2hex(data.substr(i, field_length)) i += field_length; // PINPD (PIN Pad) - Character used to pad PIN for transmission to the host and the encryption method used parsed.PINPD = this.decimal2hex(data.substr(i, field_length)) i += field_length; // PANDX (PAN Data Index) - Index for location of PAN (Personal Account Number) on card parsed.PANDX = this.decimal2hex(data.substr(i, field_length)) i += field_length; // PANLN (PAN Data Length) - PAN data field length parsed.PANLN = this.decimal2hex(data.substr(i, field_length)) i += field_length; // PANPD (PAN Pad) - Character used to pad PAN field for encryption parsed.PANPD = this.decimal2hex(data.substr(i, field_length)) i += field_length; // PRCNT (Track 3 PIN retry count index) - Index for PIN retry count field on card parsed.PRCNT = this.decimal2hex(data.substr(i, field_length)) i += field_length; // POFDX (PIN offset ind) - Index for PIN offset field on card parsed.POFDX = this.decimal2hex(data.substr(i, field_length)) i += field_length; // PDCTB (Decimalisation table) - Decimalisation table used in encryption process field_length = 1.5 * 16; // 16 digits parsed.PDCTB = this.decimal2hex(data.substr(i, field_length)) i += field_length; // PEKEY (Encrypted PIN key) - DES‐Encrypted PIN key field_length = 1.5 * 16; // 16 digits parsed.PEKEY = this.decimal2hex(data.substr(i, field_length)) i += field_length; // PINDX (Index reference point) - Track and index reference point information for all card‐related entries in FIT field_length = 1.5 * 6; // 6 digits parsed.PINDX = this.decimal2hex(data.substr(i, field_length)) i += field_length; // PLNDX (Language code index) - Index for language code on card field_length = 1.5 * 2; // 2 digits parsed.PLNDX = this.decimal2hex(data.substr(i, field_length)) i += field_length; // PMMSR (CIM86 sensor flag) - Flag to identify the location of the CIM86 sensor in the FIT. Not supported by Advance NDC parsed.PMMSR = this.decimal2hex(data.substr(i, field_length)) i += field_length; // Reserved field_length = 1.5 * 6; // 6 digits reserved = this.decimal2hex(data.substr(i, field_length)) i += field_length; // PBFMT (PIN Block format) - Selects PIN block format for remote PIN verification field_length = 1.5 * 2; // 2 digits parsed.PBFMT = this.decimal2hex(data.substr(i, field_length)) i += field_length; return parsed; } /** * [addFIT description] * @param {[type]} FIT [description] * @return {boolean} [true if state was successfully added, false otherwise] */ this.addFIT = function(FIT){ var parsed = this.parseFIT(FIT); if(parsed){ this.FITs[parsed.PIDDX] = parsed; if(log && trace) log.info('\tFIT ' + parsed.PIDDX + ' processed (FITs overall: ' + Object.keys(this.FITs).length + '):' + trace.object(parsed)); settings.set('FITs', this.FITs); return true; } else{ if(log) log.info('FITsService.addFIT(): not added'); return false; } }; /** * [matchCardnumberWithMask match card number with a wildcard hexadecimal mask * * The terminal checks whether the Financial Institution Identification number found using * PIDDX matches this field. If it does, it uses this FIT. If it does not, it * checks the next FIT. The range of each digit in this field is 0‐9 or F * hex. If F hex is used, the corresponding position of the identification * number on the card is not compared.] * * @param {[type]} cardnumber [16-character card number] * @param {[type]} mask [10-character BIN mask, e.g. 418825FFFF] * @return {[type]} [true if there is a match, false otherwise] */ this.matchCardnumberWithMask = function(cardnumber, mask){ for(var i = 0; i < mask.length; i++){ if( cardnumber[i] !== mask[i] && mask[i] !== 'F'){ return false; } } return true; } /** * [getInstitutionByCardnumber description] * @param {[type]} cardnumber [cardnumber to check] * @return {[type]} [Matched FIT institution ID or undefined if no FIT found] */ this.getInstitutionByCardnumber = function(cardnumber){ var matched_institution = Number.MAX_VALUE; for (var item in this.FITs) if(this.matchCardnumberWithMask(cardnumber, this.FITs[item].PFIID) && matched_institution > this.FITs[item].PIDDX) matched_institution = this.FITs[item].PSTDX; if (matched_institution !== Number.MAX_VALUE) return matched_institution }; this.getMaxPINLength = function(cardnumber){ var matched_institution = Number.MAX_VALUE; var max_pin; for (var item in this.FITs) if(this.matchCardnumberWithMask(cardnumber, this.FITs[item].PFIID) && matched_institution > this.FITs[item].PIDDX){ matched_institution = this.FITs[item].PSTDX; max_pin = this.FITs[item].PMXPN[1]; } if (matched_institution !== Number.MAX_VALUE) return max_pin } /** * [get get array of FITs, ordered by PIDDX] * @return {[type]} [arrary of FITs, sorted by PIDDX] */ this.get = function(){ var sorted_keys = []; for(var key in this.FITs) { sorted_keys[sorted_keys.length] = key; } sorted_keys.sort(); var sorted_fits = []; for(var i = 0; i < sorted_keys.length; i++){ sorted_fits.push(this.FITs[sorted_keys[i]]); } return sorted_fits; } /** * [add description] * @param {[type]} data [array of data to add] * @return {boolean} [true if data were successfully added, false otherwise] */ this.add = function(data){ if(typeof data === 'object') { for (var i = 0; i < data.length; i++){ if(!this.addFIT(data[i])){ if(log) log.info('Error processing FIT ' + data[i] ); return false; } } return true; } else if (typeof data === 'string') { return this.addFIT(data); } }; } module.exports = FITsService