├── .gitignore ├── LICENSE ├── README.md ├── _config.yml ├── emv.js ├── package.json ├── sample.js ├── tags.js └── util.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | npm-debug.log 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016, Namvar 2 | 3 | Permission to use, copy, modify, and/or distribute this software for any 4 | purpose with or without fee is hereby granted, provided that the above 5 | copyright notice and this permission notice appear in all copies. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Node EMV parser 2 | 3 | EMV stands for Europay, MasterCard, and Visa, the three companies that originally created the standard. The standard is now managed by EMVCo, a consortium with control split equally among Visa, Mastercard, JCB, American Express, China UnionPay, and Discover. 4 | 5 | EMV is standard to use smart cards in payment industry, chip card, EFT POS terminals and transaction processing. EMV data will be encoded in TLV(Tag, Length, Value) format, so that we need a sdk to work with such data. Node-emv is a library written in Javascript to provide parsing facility for EMV data, searching EMV tags and describing activities in EMV standard, like Terminal Verification Result, Appliction Interchange Profile, Cardholder Verification Method and so on. 6 | 7 | 8 | 9 | 10 | ## Installation 11 | 12 | `$ npm install node-emv` 13 | 14 | 15 | 16 | 17 | ## Usage 18 | 19 | ```javascript 20 | var emv = require('node-emv'); 21 | 22 | // Parsing EMV data 23 | emv.parse('9F34030200009F26087DE7FED1071C1A279F270180', function(data){ 24 | if(data != null){ 25 | console.log(data); 26 | } 27 | }); 28 | 29 | ``` 30 | 31 | ```javascript 32 | 33 | // Parsing and describing EMV data 34 | emv.describe('9F34030200009F26087DE7FED1071C1A279F270180', function(data){ 35 | if(data != null){ 36 | console.log(data); 37 | } 38 | }); 39 | 40 | ``` 41 | 42 | ```javascript 43 | 44 | // Lookup an EMV tag in node-emv dictionary 45 | emv.lookup('9F10', function(data){ 46 | if(data.length > 0){ 47 | // console.log(data[0].tag + ' ' + data[0].name); 48 | console.log(data); 49 | } 50 | }); 51 | 52 | ``` 53 | 54 | ```javascript 55 | 56 | // Describing Terminal Verification Result(TVR) 57 | emv.tvr('8000048000', function(data){ 58 | if(data.length > 0){ 59 | console.log(data); 60 | } 61 | }); 62 | 63 | ``` 64 | 65 | ```javascript 66 | 67 | // Try to get information about Aplication Interchange Profile(AIP) 68 | emv.aip('0040', function(data){ 69 | if(data.length > 0){ 70 | console.log(data); 71 | } 72 | }); 73 | 74 | ``` 75 | 76 | ```javascript 77 | 78 | //Application Usage Control tag 79 | emv.auc('2A7F', function(data){ 80 | if(data.length > 0){ 81 | console.log(data); 82 | } 83 | }); 84 | 85 | 86 | ``` 87 | 88 | ## Sample application 89 | 90 | Please check out a desktop application which is using node-emv [here](https://github.com/mhdnamvar/emv-desktop-app). 91 | 92 | 93 | ## Contributing 94 | 95 | 1. Fork it! 96 | 2. Create your feature branch: `git checkout -b my-new-feature` 97 | 3. Commit your changes: `git commit -am 'Add some feature'` 98 | 4. Push to the branch: `git push origin my-new-feature` 99 | 5. Submit a pull request :D 100 | 101 | 102 | ## License 103 | 104 | MIT 105 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-cayman -------------------------------------------------------------------------------- /emv.js: -------------------------------------------------------------------------------- 1 | var util = require('./util.js'); 2 | var emv_tags = require('./tags.js'); 3 | 4 | function lookupKernel( tag, kernel, callback ){ 5 | var found = emv_tags.filter(function(item) { 6 | if(item.tag == tag && item.kernel.toUpperCase() == kernel.toUpperCase()) 7 | return true; 8 | }); 9 | callback (found.length && found[0].name); 10 | }; 11 | 12 | function getValue( tag, emv_objects, callback ){ 13 | var found = emv_objects.filter(function(item) { 14 | if(item.tag == tag) 15 | callback( item.value ); 16 | }); 17 | }; 18 | function getElement( tag, emv_objects, callback ){ 19 | var found = emv_objects.filter(function(item) { 20 | if(item.tag == tag) 21 | callback( item ); 22 | }); 23 | }; 24 | 25 | function parse(emv_data, callback){ 26 | var emv_objects = []; 27 | while(emv_data.length > 0){ 28 | var tag_bin = util.Hex2Bin(emv_data.substring(0, 2)); 29 | tag_bin = util.pad(tag_bin, 8); 30 | //console.log(tag_bin); 31 | var tag_limit = 2; 32 | var tag_class = tag_bin.substring(0, tag_limit); 33 | var tag_constructed = tag_bin.substring(2, 3); 34 | var tag_number = tag_bin.substring(3, 8); 35 | var tag_octet = ''; 36 | 37 | if (tag_number == '11111'){ 38 | do { 39 | // at least one more byte 40 | tag_limit += 2; 41 | tag_octet = util.Hex2Bin(emv_data.substring(tag_limit-2, tag_limit)) 42 | tag_octet = util.pad(tag_octet, 8); 43 | 44 | } while (tag_octet.substring(0,1) == '1') 45 | //console.log('constructed tag'); 46 | tag_bin = util.Hex2Bin(emv_data.substring(0, tag_limit)); 47 | tag_bin = util.pad(tag_bin, 8*(tag_limit/2)); 48 | tag_number = tag_bin.substring(3, 8*(tag_limit/2)); 49 | } 50 | 51 | var tag = util.Bin2Hex(tag_class + tag_constructed + tag_number).toUpperCase(); 52 | var lenHex = emv_data.substring(tag.length, tag.length + 2); 53 | 54 | var lenBin = util.pad(util.Hex2Bin(lenHex), 8); 55 | var byteToBeRead = 0; 56 | var len = util.Hex2Dec(lenHex) * 2; 57 | var offset = tag.length + 2 + len; 58 | 59 | if(lenHex.substring(0, 1) == "8"){ 60 | byteToBeRead = util.Hex2Dec(lenHex.substring(1, 2)); 61 | lenHex = emv_data.substring(tag.length, tag.length + 2 + byteToBeRead*2) 62 | console.log('lenHex: ' + lenHex + ' lenBin: '+ lenBin +' ---> len is more than 1 byte'); 63 | // lenHex = emv_data.substring(tag.length, tag.length + 4); 64 | len = util.Hex2Dec(lenHex.substring(2)) * 2; 65 | offset = tag.length + 2 + (byteToBeRead*2)+len; 66 | console.log('length (decimals): ' + len + ' offset: '+ offset +' - this is for the substring of emv_data'); 67 | } 68 | 69 | var value = emv_data.substring(tag.length + 2 + (byteToBeRead*2), offset); 70 | 71 | if (tag_constructed == '1') { 72 | parse(value, function(innerTags) { 73 | value = innerTags; 74 | }); 75 | } 76 | 77 | emv_objects.push( { 'tag': tag, 'length': lenHex, 'value' : value} ); 78 | emv_data = emv_data.substring(offset); 79 | } 80 | 81 | callback(emv_objects); 82 | 83 | }; 84 | 85 | function describeKernel(emv_data, kernel, callback){ 86 | var emv_objects = []; 87 | parse(emv_data, function(tlv_list){ 88 | if(tlv_list != null){ 89 | for(var i=0; i < tlv_list.length; i++){ 90 | lookupKernel(tlv_list[i].tag, kernel, function(data){ 91 | var inner_list = tlv_list[i].value; 92 | if( Array.isArray(inner_list) ){ 93 | for(var j=0; j < inner_list.length; j++){ 94 | lookupKernel(inner_list[j].tag, kernel, function(jdata){ 95 | if(jdata){ 96 | inner_list[j].description = jdata; 97 | } 98 | }); 99 | } 100 | } 101 | if(data){ 102 | tlv_list[i].description = data; 103 | } 104 | 105 | emv_objects.push( tlv_list[i] ); 106 | }); 107 | } 108 | callback(emv_objects); 109 | } 110 | }); 111 | }; 112 | 113 | function aip(aip_data, callback){ 114 | var aip_bin = util.pad(util.Hex2Bin(aip_data), 16); 115 | var data = []; 116 | var Byte1 = [ 117 | { 'bit' : '8' , 'value' : aip_bin.substring(0, 1), 'description' : 'XDA supported'}, 118 | { 'bit' : '7' , 'value' : aip_bin.substring(1, 2), 'description' : 'SDA supported'}, 119 | { 'bit' : '6' , 'value' : aip_bin.substring(2, 3), 'description' : 'DDA supported'}, 120 | { 'bit' : '5' , 'value' : aip_bin.substring(3, 4), 'description' : 'Cardholder verification is supported' } , 121 | { 'bit' : '4' , 'value' : aip_bin.substring(4, 5), 'description' : 'Terminal risk Management is to be performed' } , 122 | { 'bit' : '3' , 'value' : aip_bin.substring(5, 6), 'description' : 'Issuer authentication is supported'} , 123 | { 'bit' : '2' , 'value' : aip_bin.substring(6, 7), 'description' : 'On device cardholder verification is supported' } , 124 | { 'bit' : '1' , 'value' : aip_bin.substring(7, 8), 'description' : 'CDA supported'} 125 | ]; 126 | var Byte2 = [ 127 | { 'bit' : '8' , 'value' : aip_bin.substring(8, 9), 'description' : 'EMV mode is supported'}, 128 | { 'bit' : '7' , 'value' : aip_bin.substring(9, 10), 'description' : 'RFU'}, 129 | { 'bit' : '6' , 'value' : aip_bin.substring(10, 11), 'description' : 'RFU'}, 130 | { 'bit' : '5' , 'value' : aip_bin.substring(11, 12), 'description' : 'RFU'}, 131 | { 'bit' : '4' , 'value' : aip_bin.substring(12, 13), 'description' : 'RFU'}, 132 | { 'bit' : '3' , 'value' : aip_bin.substring(13, 14), 'description' : 'RFU'}, 133 | { 'bit' : '2' , 'value' : aip_bin.substring(14, 15), 'description' : 'RFU'}, 134 | { 'bit' : '1' , 'value' : aip_bin.substring(15, 16), 'description' : 'Relay resistance protocol is supported'}, 135 | ]; 136 | data.push(Byte1, Byte2); 137 | callback(data); 138 | }; 139 | 140 | 141 | 142 | function cvm(cvm_data, callback){ 143 | var cvm_bin = util.Hex2Bin(cvm_data); 144 | var data = []; 145 | 146 | data.push(cvm_bin.substring(1, 2) == '0' ? 'Fail cardholder verification if this CVM is unsuccessful' : 'Apply succeeding CV Rule if this CVM is unsuccessful'); 147 | 148 | var cvm_code = cvm_bin.substring(2, 8); 149 | if(cvm_code == '000000') 150 | data.push('Fail CVM processing'); 151 | else if(cvm_code == '000001') 152 | data.push('Plaintext PIN verification performed by ICC'); 153 | else if(cvm_code == '000010') 154 | data.push('Enciphered PIN verified online'); 155 | else if(cvm_code == '000011') 156 | data.push('Plaintext PIN verification performed by ICC and signature (paper)'); 157 | else if(cvm_code == '000100') 158 | data.push('Enciphered PIN verification performed by ICC'); 159 | else if(cvm_code == '000101') 160 | data.push('Enciphered PIN verification performed by ICC and signature (paper)'); 161 | else if(cvm_code == '011110') 162 | data.push('Signature (paper)'); 163 | else if(cvm_code == '011111') 164 | data.push('No CVM required'); 165 | else if(cvm_code.startsWith('10')) 166 | data.push('Values in the range 100000-101111 reserved for use by the individual payment systems'); 167 | else if(cvm_code.startsWith('11')) 168 | data.push('Values in the range 110000-111110 reserved for use by the issuer'); 169 | else if(cvm_code == '11111') 170 | data.push('This value is not available for use'); 171 | 172 | 173 | var condition_code = cvm.substring(2, 4); 174 | if(condition_code == '00') 175 | data.push('Condition: Always'); 176 | else if(condition_code == '01') 177 | data.push('Condition: If unattended cash'); 178 | else if(condition_code == '02') 179 | data.push('Condition: If not unattended cash and not manual cash and not purchase with cashback'); 180 | else if(condition_code == '03') 181 | data.push('Condition: If terminal supports the CVM'); 182 | else if(condition_code == '04') 183 | data.push('Condition: If manual cash'); 184 | else if(condition_code == '05') 185 | data.push('Condition: If purchase with cashback'); 186 | else if(condition_code == '06') 187 | data.push('Condition: If transaction is in the application currency 21 and is under X value (see section 10.5 for a discussion of “X”)'); 188 | else if(condition_code == '07') 189 | data.push('Condition: If transaction is in the application currency and is over X value'); 190 | else if(condition_code == '08') 191 | data.push('Condition: If transaction is in the application currency and is under Y value (see section 10.5 for a discussion of \'Y\')'); 192 | else if(condition_code == '09') 193 | data.push('Condition: If transaction is in the application currency and is over Y value'); 194 | else if( util.Hex2Dec('0A') <= util.Hex2Dec(condition_code) && util.Hex2Dec(condition_code) <= util.Hex2Dec('7F')) 195 | data.push('RFU'); 196 | else if( util.Hex2Dec('80') <= util.Hex2Dec(condition_code) && util.Hex2Dec(condition_code) <= util.Hex2Dec('FF')) 197 | data.push('Reserved for use by individual payment systems'); 198 | callback(data); 199 | }; 200 | 201 | 202 | function auc(auc_data, callback){ 203 | var auc_bin = util.Hex2Bin(auc_data); 204 | var data = []; 205 | var Byte1 = [ 206 | { 'bit' : '8' , 'value' : auc_bin.substring(0, 1), 'description' : 'Valid for domestic cash transactions'}, 207 | { 'bit' : '7' , 'value' : auc_bin.substring(1, 2), 'description' : 'Valid for international cash transactions'}, 208 | { 'bit' : '6' , 'value' : auc_bin.substring(2, 3), 'description' : 'Valid for domestic goods'}, 209 | { 'bit' : '5' , 'value' : auc_bin.substring(3, 4), 'description' : 'Valid for international goods' } , 210 | { 'bit' : '4' , 'value' : auc_bin.substring(4, 5), 'description' : 'Valid for domestic services' } , 211 | { 'bit' : '3' , 'value' : auc_bin.substring(5, 6), 'description' : 'Valid for international services'} , 212 | { 'bit' : '2' , 'value' : auc_bin.substring(6, 7), 'description' : 'Valid at ATMs'} , 213 | { 'bit' : '1' , 'value' : auc_bin.substring(7, 8), 'description' : 'Valid at terminals other than ATMs'} 214 | ]; 215 | 216 | var Byte2 = [ 217 | { 'bit' : '8' , 'value' : auc_bin.substring(8, 9), 'description' : 'Domestic cashback allowed'}, 218 | { 'bit' : '7' , 'value' : auc_bin.substring(9, 10), 'description' : 'International cashback allowed'}, 219 | { 'bit' : '6' , 'value' : auc_bin.substring(10, 11), 'description' : 'RFU'}, 220 | { 'bit' : '5' , 'value' : auc_bin.substring(11, 12), 'description' : 'RFU'}, 221 | { 'bit' : '4' , 'value' : auc_bin.substring(12, 13), 'description' : 'RFU'}, 222 | { 'bit' : '3' , 'value' : auc_bin.substring(13, 14), 'description' : 'RFU'}, 223 | { 'bit' : '2' , 'value' : auc_bin.substring(14, 15), 'description' : 'RFU'}, 224 | { 'bit' : '1' , 'value' : auc_bin.substring(15, 16), 'description' : 'RFU'}, 225 | ]; 226 | data.push(Byte1, Byte2); 227 | callback(data); 228 | }; 229 | 230 | 231 | function tsi(tsi_data, callback){ 232 | var tsi_bin = util.Hex2Bin(tsi_data); 233 | var data = []; 234 | var Byte1 = [ 235 | { 'bit' : '8' , 'value' : tsi_bin.substring(0, 1), 'description' : 'Offline data authentication was performed' }, 236 | { 'bit' : '7' , 'value' : tsi_bin.substring(1, 2), 'description' : 'Cardholder verification was performed' }, 237 | { 'bit' : '6' , 'value' : tsi_bin.substring(2, 3), 'description' : 'Card risk management was performed' }, 238 | { 'bit' : '5' , 'value' : tsi_bin.substring(3, 4), 'description' : 'Issuer authentication was performed' } , 239 | { 'bit' : '4' , 'value' : tsi_bin.substring(4, 5), 'description' : 'Terminal risk management was performed' } , 240 | { 'bit' : '3' , 'value' : tsi_bin.substring(5, 6), 'description' : 'Script processing was performed' } , 241 | { 'bit' : '2' , 'value' : tsi_bin.substring(6, 7), 'description' : 'RFU' } , 242 | { 'bit' : '1' , 'value' : tsi_bin.substring(7, 8), 'description' : 'RFU' } 243 | ]; 244 | 245 | var Byte2 = [ 246 | { 'bit' : '8' , 'value' : tsi_bin.substring(8, 9), 'description' : 'RFU' }, 247 | { 'bit' : '7' , 'value' : tsi_bin.substring(9, 10), 'description' : 'RFU' }, 248 | { 'bit' : '6' , 'value' : tsi_bin.substring(10, 11), 'description' : 'RFU' }, 249 | { 'bit' : '5' , 'value' : tsi_bin.substring(11, 12), 'description' : 'RFU' }, 250 | { 'bit' : '4' , 'value' : tsi_bin.substring(12, 13), 'description' : 'RFU' }, 251 | { 'bit' : '3' , 'value' : tsi_bin.substring(13, 14), 'description' : 'RFU' }, 252 | { 'bit' : '2' , 'value' : tsi_bin.substring(14, 15), 'description' : 'RFU' }, 253 | { 'bit' : '1' , 'value' : tsi_bin.substring(15, 16), 'description' : 'RFU' }, 254 | ]; 255 | data.push(Byte1, Byte2); 256 | callback(data); 257 | }; 258 | 259 | function to_bits(hexData, callback){ 260 | var bin_data = util.Hex2Bin(hexData); 261 | var data = []; 262 | var no_bytes = bin_data.length / 8; 263 | for(var i = 0; i < no_bytes; i++){ 264 | var str = '{ byte : '+ i + ', bits : ['; 265 | for(var j = 0; j < 8; j++){ 266 | str += ' { bit : '+ j + ', value : ' + bin_data.substring(i, i + 1) + ' }' ; 267 | if(j != 7) str += ','; 268 | } 269 | str += ']}' 270 | data.push( str ); 271 | } 272 | callback(data); 273 | }; 274 | 275 | 276 | function tvr(tvr_data, callback){ 277 | var tvr_bin = util.Hex2Bin(tvr_data); 278 | var data = []; 279 | var Byte1 = [ 280 | { 'bit' : '8' , 'value' : tvr_bin.substring(0, 1), 'description' : 'Offline data authentication was not performed' }, 281 | { 'bit' : '7' , 'value' : tvr_bin.substring(1, 2), 'description' : 'SDA failed' }, 282 | { 'bit' : '6' , 'value' : tvr_bin.substring(2, 3), 'description' : 'ICC data missing' }, 283 | { 'bit' : '5' , 'value' : tvr_bin.substring(3, 4), 'description' : 'Card appears on terminal exception file' }, 284 | { 'bit' : '4' , 'value' : tvr_bin.substring(4, 5), 'description' : 'DDA failed' }, 285 | { 'bit' : '3' , 'value' : tvr_bin.substring(5, 6), 'description' : 'CDA failed' }, 286 | { 'bit' : '2' , 'value' : tvr_bin.substring(6, 7), 'description' : 'RFU' }, 287 | { 'bit' : '1' , 'value' : tvr_bin.substring(7, 8), 'description' : 'RFU' } 288 | ]; 289 | 290 | var Byte2 = [ 291 | { 'bit' : '8' , 'value' : tvr_bin.substring(8, 9), 'description' : 'ICC and terminal have different application versions' }, 292 | { 'bit' : '7' , 'value' : tvr_bin.substring(9, 10), 'description' : 'Expired application' }, 293 | { 'bit' : '6' , 'value' : tvr_bin.substring(10, 11), 'description' : 'Application not yet effective' }, 294 | { 'bit' : '5' , 'value' : tvr_bin.substring(11, 12), 'description' : 'Requested service not allowed for card product' }, 295 | { 'bit' : '4' , 'value' : tvr_bin.substring(12, 13), 'description' : 'New card' }, 296 | { 'bit' : '3' , 'value' : tvr_bin.substring(13, 14), 'description' : 'RFU' }, 297 | { 'bit' : '2' , 'value' : tvr_bin.substring(14, 15), 'description' : 'RFU' }, 298 | { 'bit' : '1' , 'value' : tvr_bin.substring(15, 16), 'description' : 'RFU' } 299 | ]; 300 | 301 | var Byte3 = [ 302 | { 'bit' : '8' , 'value' : tvr_bin.substring(16, 17), 'description' : 'Cardholder verification was not successful' }, 303 | { 'bit' : '7' , 'value' : tvr_bin.substring(17, 18), 'description' : 'Unrecognised CVM' }, 304 | { 'bit' : '6' , 'value' : tvr_bin.substring(18, 19), 'description' : 'PIN Try Limit exceeded' }, 305 | { 'bit' : '5' , 'value' : tvr_bin.substring(19, 20), 'description' : 'PIN entry required and PIN pad not present or not working' }, 306 | { 'bit' : '4' , 'value' : tvr_bin.substring(20, 21), 'description' : 'PIN entry required, PIN pad present, but PIN was not entered' }, 307 | { 'bit' : '3' , 'value' : tvr_bin.substring(21, 22), 'description' : 'Online PIN entered' }, 308 | { 'bit' : '2' , 'value' : tvr_bin.substring(22, 23), 'description' : 'RFU' }, 309 | { 'bit' : '1' , 'value' : tvr_bin.substring(23, 24), 'description' : 'RFU' } 310 | ]; 311 | 312 | var Byte4 = [ 313 | { 'bit' : '8' , 'value' : tvr_bin.substring(24, 25), 'description' : 'Transaction exceeds floor limit' }, 314 | { 'bit' : '7' , 'value' : tvr_bin.substring(25, 26), 'description' : 'Lower consecutive offline limit exceeded' }, 315 | { 'bit' : '6' , 'value' : tvr_bin.substring(26, 27), 'description' : 'Upper consecutive offline limit exceeded' }, 316 | { 'bit' : '5' , 'value' : tvr_bin.substring(27, 28), 'description' : 'Transaction selected randomly for online processing' }, 317 | { 'bit' : '4' , 'value' : tvr_bin.substring(28, 29), 'description' : 'Merchant forced transaction online' }, 318 | { 'bit' : '3' , 'value' : tvr_bin.substring(29, 30), 'description' : 'RFU' }, 319 | { 'bit' : '2' , 'value' : tvr_bin.substring(30, 31), 'description' : 'RFU' }, 320 | { 'bit' : '1' , 'value' : tvr_bin.substring(31, 32), 'description' : 'RFU' } 321 | ]; 322 | 323 | var Byte5 = [ 324 | { 'bit' : '8' , 'value' : tvr_bin.substring(32, 33), 'description' : 'Default TDOL used' }, 325 | { 'bit' : '7' , 'value' : tvr_bin.substring(33, 34), 'description' : 'Issuer authentication failed' }, 326 | { 'bit' : '6' , 'value' : tvr_bin.substring(34, 35), 'description' : 'Script processing failed before final GENERATE AC' }, 327 | { 'bit' : '5' , 'value' : tvr_bin.substring(35, 36), 'description' : 'Script processing failed after final GENERATE AC' }, 328 | { 'bit' : '4' , 'value' : tvr_bin.substring(36, 37), 'description' : 'RFU' }, 329 | { 'bit' : '3' , 'value' : tvr_bin.substring(37, 38), 'description' : 'RFU' }, 330 | { 'bit' : '2' , 'value' : tvr_bin.substring(38, 39), 'description' : 'RFU' }, 331 | { 'bit' : '1' , 'value' : tvr_bin.substring(39, 40), 'description' : 'RFU' } 332 | ]; 333 | 334 | data.push(Byte1, Byte2, Byte3, Byte4, Byte5); 335 | callback(data); 336 | }; 337 | 338 | 339 | 340 | module.exports={ 341 | parse : function(emv_data, callback){ parse(emv_data, callback); }, 342 | describe : function(emv_data, callback){ describeKernel(emv_data, "Generic", callback); }, 343 | lookup : function(emv_tag, callback){ lookupKernel(emv_tag, "Generic",callback); }, 344 | describeKernel : function(emv_data, kernel, callback){ describeKernel(emv_data, kernel, callback); }, 345 | lookupKernel : function(emv_tag, kernel, callback){ lookupKernel(emv_tag, kernel, callback); }, 346 | getValue : function(emv_tag, emv_objects, callback){ getValue(emv_tag, emv_objects, callback); }, 347 | getElement : function(emv_tag, emv_objects, callback){ getElement(emv_tag, emv_objects, callback); }, 348 | aip : function(aip_data, callback){ aip(aip_data, callback); }, 349 | auc : function(auc_data, callback){ auc(auc_data, callback); }, 350 | cvm : function(cvm_data, callback){ cvm(cvm_data, callback); }, 351 | tvr : function(tvr_data, callback){ tvr(tvr_data, callback); }, 352 | tsi : function(tsi_data, callback){ tvr(tsi_data, callback); } 353 | }; 354 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-emv", 3 | "version": "1.0.23", 4 | "description": "The EMV parser library", 5 | "main": "emv.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/mhdnamvar/node-emv.git" 12 | }, 13 | "keywords": [ 14 | "EMV", 15 | "Parser", 16 | "TLV", 17 | "BER-TLV", 18 | "Payment", 19 | "Banking", 20 | "Finance", 21 | "Smart Card", 22 | "EMV Tag", 23 | "EFT", 24 | "POS", 25 | "Terminal", 26 | "Transaction", 27 | "Chip Card", 28 | "cardholder", 29 | "Issuer", 30 | "Acquirer", 31 | "Cryptogram", 32 | "ISO 8583", 33 | "ISO 7816", 34 | "Personalization" 35 | ], 36 | "author": "Mohammad Namvar, namvar@gmail.com", 37 | "license": "MIT", 38 | "bugs": { 39 | "url": "https://github.com/mhdnamvar/node-emv/issues" 40 | }, 41 | "homepage": "https://github.com/mhdnamvar/node-emv#readme" 42 | } 43 | -------------------------------------------------------------------------------- /sample.js: -------------------------------------------------------------------------------- 1 | var emv = require('./emv.js'); 2 | 3 | // Parsing EMV data 4 | emv.describe('4F07A00000000430605F2A02097882025C008407A0000000043060950500800080009A031508069C01009F02060000000001019F080200009F090200009F10120114000100000000000000E0DB2E438900FF9F1A0205289F1E0830303030303030309F2608C49EDC5F41B0A4C89F2701809F3303E0F0C89F34034403029F3501009F360201149F37048B3441139F4104000000009F5301529F0306000000000000', function(data){ 5 | if(data != null){ 6 | console.log(data); 7 | } 8 | }); 9 | 10 | emv.describeKernel('5F300202269F420207805F280207805F560354544F9F51020780DF30033335309F57025454DF2A08028C815CD6C196464F07A00000000310105A0843031911000000285F2015434152444831314445522F53434F54313142414E4B5F24031701315F25031610015F3401015F54006F3A8407A0000000031010A52F500A564953412044454249549F120E424E5320564953412044454249548701019F1101015F2D02656E9F38039F1A02DF26100BE9B311B479246F2FF0EFBCE66530ADDF2710F475419E68BCC99FA503A2207EE3DF16DF2810673B2CE34B178CF00CA71EED0EE94652DF291025DFFE03E5A3264B3DFA6FBFA186D69ADF20034AE700DF2103971254DF220391501DDF2403507AD257134303191100000028D17012269010035000000F82023800880101940C0801010018010301100102009F1F1639303130303030303030303030333530303030303030DF1F820280353F6C63F80790A74353F97A9BDEB07E6354BE034691D8FA1CCD067E4BCC636004F479A499F2FAF1B260AD6DE3DCA4FAC3CBE40148FF654E3DB20B50CAFD7026FD5F8FEBF1BEF361D8DE2E58D8E2AF43A0833A6C7BD6045677E5379A8381408096E7174D5BA71B98C79C6A73C3DEC1219D7CDEE844E0FBF6E7EB772DB8E157CEF7EE3416B2E606D06D3C28EF6BF0F48C544B8C36FD7AA2B2583E19E7476323F9C51A0D82C76B724FBBBEF57BC0EB64066A23D08A7FBAA00D5AD0866CF643D940B95D0332774C7A7B931CCD7D7CE34ECDB43342141638C893D869B7CE7E82CB74A6DC42F439D95F6000B9D55EFFECFDF7D07A6A46A985686950E381867B807B4F5D2A1C23281C788B34D257F65A3DC081307BA7AFD8E0C5D2DD952A6FBF42C2383B57778C21125AE4A7A76D6366249D743D075D0094DB1B7E4B22B31FA20C77A7FF2E8CC834FDE83447CE7A91ED088BDC392B6305F4653075BA596EB8F42ACC8B4C2D694093B2B285AB61C78A8A77CDAA001334E1519F7124DDE255EC656EE94F8DE26B9D95AAD0D08D02DFC9B37DA09FA8825E4A4F3AB261D15014D12E7DE38EED1ABE6AE1F1C71F13A5A868990E06B70FD82E7E6D6ED19A679AC9CCA4DB6313285FB1C22B62148844B6D92B6D112B019EC2FF4560C7CE37B12812DF0B0864B1F98EEE90CC21FD91980DF258CBF063CFDA52639A4E49A6468EA157790E69579D8919FEAFB3122BF6E963EF5114A406D6AA73DBC848EA840C4853612CD6E329ADD2535FFA3CE0EE96EB2BF8B041606A0A859923289380B008EC439D11ECC859C1C35935A3328DA577F0F07729ACC65E3361293680109E1B3E28DBFD77A592437F8D9FB8FD10E8DF767D7C3C29F37B9A029FA9C170D32B9E048618C32E986B4A329F1007060112030000009F4681B01448D0AF8BDD55A621B32CA1585541B01B328B92932858D4B803256DA88789FC97628026638126F780B955AE1D24FA1FD95E12EFF2ECCB01BC4B6C99353557B44B5C2D6B374AF4E5E858B26F72B1ED819AD54F97FC6610751ECCB9851CB91DD50B72F77A716FF19B262A0B2E3DE3690B28062A781D518272B84BCDA8C599112D9CB9B8CD9E3209940B6B93216EC18258EEDC79BEED3CDBE58F752DFD943DBA6E151731FF7F42DA53D84D749A2915ED169F4701039F48008C159F02069F03069F1A0295055F2A029A039C019F37048D198A029F02069F03069F1A0295055F2A029A039C019F370491088E160000000000000000020102044403410342031E031F028F01929081B0B790C6A1786C13F10D836E1951B57FD3FF42B913A3BB8A5BAA4BA47B2CF2B642AB683840D8BF571D93907E109B7FF066BA09D9C75CE43F1C5F0F798273A864181F22491FF085DCA3EEDA1089BC7C22CE23CAD473A86A9100EC1715262CC1F1C256C41C3B93A92D962FF94221C13C96213821C5854A7C77DDFBB49BC4351153FAF08736464837C104A37A42037BF22DBE5DE1FC78733ABA4A22D1559A0859051625FEB13378184B19D17B48BF7AC5CB65922445D1F27B4E4C1CE19B9F122B06A2EA287A9739083383C9B1066F60C2A7DF1F4DD5649F7D9F0702FF009F0802009A9F0D05BC58ECA8009F0E0500000000009F0F05BC78FCF8009F1401009F1701039F2301009F26009F2701009F3201039F360200009F4401029F49039F37049F4A01829F5206C3380B900A009F5501E09F560180DF030103DF2C0103DF2F00DF540112DF550101DFFFFFFF080112DFFFFFFF560CDF110100DF210100DF310100DFFFFFFF5714DF110100DF210100DF310100DF510100DF610100DFFFFFFF581BDF1106000000000000DF2106000000000000DF3106000000000000E53BDF2F025A00DF2F035F3400DF2F035F2400DF2F039F0700DF2F035F2800DF2F035F2500DF2F039F0E00DF2F039F0F00DF2F039F0D00DF2F039F4A00DF1A0105DF1B0113E21F94009F5D01019F63009F68009F69009F6B009F6C009F6E009F380082008E00FF010DFF020A0402DF2AFF0403040101 ', "visa", function(data){ 11 | if(data != null){ 12 | console.log(data); 13 | } 14 | }); 15 | 16 | // Parsing EMV data - more than 1 byte length 17 | // emv.parse('4F07A00000000430605F2A02097882025C008407A0000000043060950500800080009A031508069C01009F02060000000001019F080200009F090200009F10120114000100000000000000E0DB2E438900FF9F1A0205289F1E0830303030303030309F2608C49EDC5F41B0A4C89F2701809F3303E0F0C89F34034403029F3501009F360201149F37048B3441139F4104000000009F5301529F0306000000000000', function(data){ 18 | // if(data != null){ 19 | // console.log(data); 20 | // } 21 | // }); 22 | 23 | // // Parsing and describing EMV data 24 | // emv.describe('9F34030200009F26087DE7FED1071C1A279F270180', function(data){ 25 | // if(data != null){ 26 | // console.log(data); 27 | // } 28 | // }); 29 | // 30 | // 31 | // // Lookup an EMV tag in node-emv dictionary 32 | // emv.lookup('9F10', function(data){ 33 | // if(data.length > 0){ 34 | // // console.log(data[0].tag + ' ' + data[0].name); 35 | // console.log(data); 36 | // } 37 | // }); 38 | // 39 | // 40 | // // Describing Terminal Verification Result(TVR) 41 | // emv.tvr('8000048000', function(data){ 42 | // if(data.length > 0){ 43 | // console.log(data); 44 | // } 45 | // }); 46 | // 47 | // 48 | // // Try to get information about Aplication Interchange Profile(AIP) 49 | // emv.aip('0040', function(data){ 50 | // if(data.length > 0){ 51 | // console.log(data); 52 | // } 53 | // }); 54 | // 55 | // //Application Usage Control tag 56 | // emv.auc('2A7F', function(data){ 57 | // if(data.length > 0){ 58 | // console.log(data); 59 | // } 60 | // }); 61 | -------------------------------------------------------------------------------- /util.js: -------------------------------------------------------------------------------- 1 | exports.checkBin = function(n){ 2 | return/^[01]{1,64}$/.test(n); 3 | } 4 | 5 | exports.checkDec = function(n){ 6 | return/^[0-9]{1,64}$/.test(n); 7 | } 8 | 9 | exports.checkHex = function(n){ 10 | return/^[0-9A-Fa-f]{1,64}$/.test(n); 11 | } 12 | 13 | exports.pad = function(s,z){ 14 | s = "" + s; 15 | return s.length < z ? this.pad("0" + s, z) : s; 16 | } 17 | 18 | exports.unpad = function(s){ 19 | s = "" + s; 20 | return s.replace(/^0+/,''); 21 | } 22 | 23 | exports.Dec2Bin = function(n){ 24 | if(!this.checkDec(n) || n < 0) 25 | return 0; 26 | return n.toString(2); 27 | } 28 | exports.Dec2Hex = function (n){ 29 | if(!this.checkDec(n) || n < 0) 30 | return 0; 31 | return n.toString(16); 32 | } 33 | 34 | exports.Bin2Dec = function(n){ 35 | if(!this.checkBin(n)) 36 | return 0; 37 | return parseInt(n,2).toString(10); 38 | } 39 | 40 | exports.Bin2Hex = function(n){ 41 | if(!this.checkBin(n)) 42 | return 0; 43 | return parseInt(n,2).toString(16); 44 | } 45 | 46 | exports.Hex2Bin = function(n){ 47 | if(!this.checkHex(n)) 48 | return 0; 49 | return parseInt(n,16).toString(2); 50 | } 51 | 52 | exports.Hex2Dec = function(n){ 53 | if(!this.checkHex(n)) 54 | return 0; 55 | return parseInt(n,16).toString(10); 56 | } 57 | 58 | 59 | exports.Hex2Ascii = function(hexx) { 60 | var hex = hexx.toString();//force conversion 61 | var str = ''; 62 | for (var i = 0; i < hex.length; i += 2) 63 | str += String.fromCharCode(parseInt(hex.substr(i, 2), 16)); 64 | return str; 65 | } 66 | 67 | 68 | exports.replaceAt = function(str, index, character) { 69 | var str = str.toString();//force conversion 70 | return str.substr(0, index) + character + str.substr(index+character.length); 71 | } 72 | --------------------------------------------------------------------------------