Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

iin-checker

Package Overview
Dependencies
Maintainers
1
Versions
10
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

iin-checker - npm Package Compare versions

Comparing version 0.1.9 to 0.6.0

.eslintrc

3

CHANGELOG.md
# Changelog
## **0.2.0**
- Updating dependencies to current stable versions and updating tests to match.
## **0.1.9**

@@ -4,0 +7,0 @@ - [**#25**](https://github.com/Shortbreaks/iinChecker/issues/25) Switched the order of provider checking to get better API output

25

configs/providers.js
module.exports = [
{
name: "PAYOUT",
domain: "https://bins.payout.com",
path: "/api/v1/bins/",
map: function( returnedData, nullValue ) {
return {
iin: returnedData.bin,
brand: returnedData.brand,
issuer: returnedData.issuer,
type: returnedData.type || nullValue,
category: nullValue,
country: returnedData.country_code
};
}
},
{
name: "RIBBON",

@@ -11,6 +26,6 @@ domain: "https://bins.ribbon.co",

issuer: returnedData.issuer,
type: ( returnedData.type ? returnedData.type : nullValue ),
type: returnedData.type || nullValue,
category: nullValue,
country: returnedData.country_code
}
};
}

@@ -27,8 +42,8 @@ },

issuer: returnedData.bank,
type: returnedData.card_type,
type: returnedData.card_type || nullValue,
category: returnedData.card_category,
country: returnedData.country_code
}
};
}
}
]
];

@@ -14,43 +14,43 @@ /* jslint node: true */

// project configuration
grunt.initConfig( {
jshint: {
options: {
jshintrc: '.jshintrc'
},
core: {
src: ['Gruntfile.js', 'lib/**/*.js']
},
test: {
src: ['test/**/*.js']
},
json: {
src: ['package.json', 'lib/**/*.json', 'test/**/*.json']
}
},
jscs: {
options: {
config: '.jscsrc'
},
files: {
src: ['<%= jshint.core.src %>', '<%= jshint.test.src %>']
}
},
mochaTest: {
options: {
reporter: 'spec'
},
src: ['test/**/*.js']
}
} );
// project configuration
grunt.initConfig( {
jshint: {
options: {
jshintrc: '.jshintrc'
},
core: {
src: ['Gruntfile.js', 'lib/**/*.js']
},
test: {
src: ['test/**/*.js']
},
json: {
src: ['package.json', 'lib/**/*.json', 'test/**/*.json']
}
},
jscs: {
options: {
config: '.jscsrc'
},
files: {
src: ['<%= jshint.core.src %>', '<%= jshint.test.src %>']
}
},
mochaTest: {
options: {
reporter: 'spec'
},
src: ['test/**/*.js']
}
} );
// load tasks from the specified grunt plugins...
grunt.loadNpmTasks( 'grunt-contrib-jshint' );
grunt.loadNpmTasks( 'grunt-jscs' );
grunt.loadNpmTasks( 'grunt-mocha-test' );
// load tasks from the specified grunt plugins...
grunt.loadNpmTasks( 'grunt-contrib-jshint' );
grunt.loadNpmTasks( 'grunt-jscs' );
grunt.loadNpmTasks( 'grunt-mocha-test' );
// register task alias'
grunt.registerTask( 'default', ['jshint', 'jscs'] );
grunt.registerTask( 'test', ['mochaTest'] );
// register task alias'
grunt.registerTask( 'default', ['jshint', 'jscs'] );
grunt.registerTask( 'test', ['mochaTest'] );
};
};

@@ -1,8 +0,2 @@

/**
* @name /index.js
* @description Nodejs style iinChecker include
* @author Simon Wood <simon.wood@holidayextras.com>
*/
module.exports = require( './lib/iinChecker' );
'use strict';
module.exports = require( './lib/iinChecker' );

@@ -6,3 +6,4 @@ {

"PARAMETER_IIN_IS_NOT_LONG_ENOUGH" : "Parameter \"iin\" in not long enough (6 characters)",
"PARAMETER_IIN_IS_INVALID" : "Parameter \"iin\" is invalid",
"PARAMETER_CALLBACK_NOT_FUNCTION": "Parameter \"callback\" is not [function]"
}

@@ -0,148 +1,299 @@

'use strict';
( function() {
// get some stuff we need to support IIN Checker
var _ = require( 'lodash' );
var request = require( 'request' );
var EventEmitter = require( 'events' ).EventEmitter;
/**
* @name /lib/iinChecker.js
* @description Issuer identification number checker which returns details about a credit/debit card
* @author Simon Wood <simon.wood@holidayextras.com>
*/
var iinChecker = function( iinOptions ) {
// Load all of our providers into an array that we can loop over.
this.providers = require( '../configs/providers' );
( function() {
// get some stuff we need to support IIN Checker
var _ = require( 'lodash' );
var request = require( 'request' );
// Load all of our providers into an array that we can loop over.
var providers = require( '../configs/providers' );
// default options
this.options = {
language: 'en',
cache: false,
timeout: 1000
};
var iinChecker = function( inOptions ) {
// default options
this.options = {
language: 'en'
};
// if there's any overriding options, blat them over the defaults
this.options = _.extend( this.options, iinOptions );
// if there's any overriding options, blat them over the defaults
this.options = _.extend( this.options, inOptions );
// Define our constants for comparison
this.nullValue = 'UNKNOWN'; /* Value incase services are offline or return null */
this.brands = {
VISA: 'VISA',
MASTERCARD: 'MASTERCARD',
AMEX: 'AMERICAN EXPRESS',
DISCOVER: 'DISCOVER',
DINERS: 'DINERS CLUB',
JCB: 'JCB',
MAESTRO: 'MAESTRO',
LASER: 'LASER',
UNKNOWN: this.nullValue
};
// DANKORT what do we do with this?
// CHINA UNION PAY
// SOLO
// ELECTRON ??? Don't support? Visa Electron
// Define our constants for comparison
this.nullValue = 'UNKNOWN'; /* Value incase services are offline or return null */
this.brands = {
VISA: 'VISA',
MASTERCARD: 'MASTERCARD',
AMEX: 'AMERICAN EXPRESS',
DISCOVER: 'DISCOVER',
DINERS: 'DINERS CLUB',
JCB: 'JCB',
MAESTRO: 'MAESTRO',
LASER: 'LASER',
UNKNOWN: this.nullValue
};
// DANKORT what do we do with this?
// CHINA UNION PAY
// SOLO
// ELECTRON ??? Don't support? Visa Electron
// DINERS has been added for RegEx, but both Robbin and BinList return these cards as DISCOVER
// TODO: Change this later to be consitent across RegEx and providers
// DINERS has been added for RegEx, but both Robbin and BinList return these cards as DISCOVER
// TODO: Change this later to be consitent across RegEx and providers
this.types = {
DEBIT: 'DEBIT',
CREDIT: 'CREDIT',
UNKNOWN: this.nullValue
};
this.types = {
DEBIT: 'DEBIT',
CREDIT: 'CREDIT',
UNKNOWN: this.nullValue
};
// now fetch the language settings based on the requested language
this.options.messages = require( './i18n/' + this.options.language ); // ISO 639‑1
// now fetch the language settings based on the requested language
this.options.messages = require( './i18n/' + this.options.language ); // ISO 639‑1
this.isCacheable = function( cardInfo ) {
var self = this;
// we don't have a cache
if ( !self.options.cache ) {
return false;
}
// we don't want to cache cards we don't know the brand for
if ( !cardInfo.brand || cardInfo.brand === self.brands.UNKNOWN ) {
return false;
}
this.makeRequest = function( iin, providerCount, callback ) {
var self = this;
var cardInfo;
var providerDetails = providers[providerCount];
request( { url: providerDetails.domain + providerDetails.path + iin, json: true }, function( error, response, body ) {
if ( !error && response.statusCode === 200 ) {
// Remap the reply to match our schema
cardInfo = providerDetails.map( body, self.nullValue );
// send the results back a-la-node
callback( null, cardInfo );
} else {
if( ++providerCount < providers.length ) {
self.makeRequest( iin, providerCount, callback );
} else {
// Maybe one last attempt here with RegEx? If that does not work then error
var patterns = require( '../configs/patterns' );
// we don't want to cache cards we don't know the type of
if ( !cardInfo.type || cardInfo.type === this.nullValue ) {
return false;
}
// Set out null info object
cardInfo = {
iin: iin,
brand: null,
issuer: self.nullValue,
type: self.nullValue,
category: self.nullValue,
country: self.nullValue
};
// we can cache this card
return true;
};
_.each( patterns, function( pattern ) {
var regex = new RegExp( pattern.expression );
if( !cardInfo.brand && regex.test( iin ) ) {
cardInfo.brand = self.brands[pattern.name];
}
} );
this.flipBrandWhereNecessary = function( cardInfo ) {
var self = this;
var firstNumber = parseInt( cardInfo.iin.charAt(0), 10 );
if ( cardInfo.brand === self.brands.MASTERCARD && firstNumber === 4 ) {
cardInfo.brand = self.brands.VISA;
} else if ( cardInfo.brand === self.brands.VISA && firstNumber === 5 ) {
cardInfo.brand = self.brands.MASTERCARD;
}
return cardInfo;
};
};
// See if RegEx worked
if ( cardInfo.brand && cardInfo.brand !== self.brands.UNKNOWN ) {
callback( null, cardInfo );
} else {
// Return the err in the error callback, or if no error send back the http status code
callback( error || response.statusCode );
}
}
}
} );
};
};
// create a shorthand then export the IinChecker object for Nodejs
exports = module.exports = iinChecker;
// create a shorthand then export the IinChecker object for Nodejs
exports = module.exports = iinChecker;
/**
* Lookup a card by its IIN and return a card object
*
* @param {String} iin the number to check
* @param {Function} callback function to return the card details/error
* @return {Boolean} returns true unless there is no callback supplied
*/
iinChecker.prototype.lookup = function( iin, callback ) {
var self = this;
var emitter = new EventEmitter();
/**
* Lookup a card by its IIN and return a card object
*
* @param {String} iin
* @param {Function} callback
* @return {Object}
*/
iinChecker.prototype.lookup = function( iin, callback ) {
try {
function doRequest( iinToLookup, provider ) {
var cardInfo;
if ( _.isUndefined( iin ) ) {
throw new TypeError( this.options.messages.PARAMETER_IIN_IS_UNDEFINED );
} else {
if ( _.isEmpty( iin ) ) {
throw new TypeError( this.options.messages.PARAMETER_IIN_IS_EMPTY );
} else {
// convert input to string incase a int is passed in
iin = String( iin );
var _request = request( { url: provider.domain + provider.path + iinToLookup, json: true, timeout: self.options.timeout }, function( error, response, body ) {
// the provider errored or our iin was bad
if ( error || response.statusCode !== 200 ) {
// fire off an event so we can handle the failed request
emitter.emit( 'requestFailed', iinToLookup, _request );
return false;
}
// Remap the reply to match our schema
cardInfo = provider.map( body, self.nullValue );
// we have some issues with binlist's correctness, some of their binranges
// starting with 5 are being returned as VISA when they are actually MASTERCARD
cardInfo = self.flipBrandWhereNecessary( cardInfo );
// if caching is on, provider request is only attempted if card details were not
// in the cache now we have the details set it.
if ( self.isCacheable( cardInfo ) ) {
self.options.cache.set( iinToLookup, cardInfo );
}
// fire off an event so we know that we have completed our request
emitter.emit( 'requestComplete', cardInfo );
return true;
});
// First up. Make sure we are passing a number in.
if( _.isNaN( parseInt( iin ) ) ) {
throw new TypeError( this.options.messages.PARAMETER_IIN_IS_NOT_A_NUMBER );
}
// fire off an event so we know that the request has started
emitter.emit( 'requestStarted', _request, iinToLookup );
}
// Now make sure that we are passing in 6 characters
if( iin.length !== 6 ) {
throw new TypeError( this.options.messages.PARAMETER_IIN_IS_NOT_LONG_ENOUGH );
}
// Loop through the providers and make a request to each of them
function doLookup( providersToCheck, iinToCheck ) {
providersToCheck.forEach( function( element ) {
doRequest( iinToCheck, element );
});
}
}
}
if ( !_.isFunction( callback ) ) {
throw new TypeError( this.options.messages.PARAMETER_CALLBACK_NOT_FUNCTION );
}
this.makeRequest( iin, 0, callback );
} catch ( err ) {
// Return the err in the error callback, if not possible return it.
if ( callback ) {
callback( err );
} else {
return err;
}
}
};
} )();
// Do a RegEx lookup on a iin
function doRegex( iinToLookup ) {
var cardInfo;
var patterns = require( '../configs/patterns' );
// Set out null info object
cardInfo = {
iin: iinToLookup,
brand: self.nullValue,
issuer: self.nullValue,
type: self.nullValue,
category: self.nullValue,
country: self.nullValue
};
_.each( patterns, function( pattern ) {
var regex = new RegExp( pattern.expression );
if ( cardInfo.brand === self.nullValue && regex.test( iinToLookup ) ) {
cardInfo.brand = self.brands[pattern.name];
}
} );
// See if RegEx worked
if ( cardInfo.brand && cardInfo.brand !== self.brands.UNKNOWN ) {
// Our RegEx has work, so we fire off an event so we know about it
emitter.emit( 'regexResult', cardInfo );
} else {
// The RegEx was unable to figure out what the card was so we will fire off an event so we know about it and deal with it
emitter.emit( 'returnError', new TypeError(self.options.messages.PARAMETER_IIN_IS_INVALID), iinToLookup );
}
}
// Bind our listeners here so we can handle events
function bindListeners( listenerCallback ) {
// we need to keep track of any requests we have
var currentRequests = [];
function stopListening() {
emitter.removeAllListeners();
}
function clearRequestQueue() {
_.invokeMap( currentRequests, 'abort' );
}
// listen to the "requestStarted" event - here we add requests into our "currentRequests" queue so we can keep track of them
emitter.on( 'requestStarted', function( theRequest ) {
// add this request to our queue
currentRequests.push( theRequest );
});
// listen to the "requestComplete" event - the request has completed successfully if we have enough information
// we can stop listening to any more events and call the callback with the resulting cardInfo
emitter.on( 'requestComplete', function( cardInfo ) {
// is this the event we're interested in?...
if ( cardInfo.iin === iin ) {
// ...yes, do we have what we need?...
if ( cardInfo.brand && cardInfo.brand !== self.brands.UNKNOWN ) {
//...yes we do! So we can stop listening to events
stopListening();
// we also don't care about any other requests that may be running
clearRequestQueue();
// finally fire off the callback with the card details
listenerCallback( null, cardInfo );
return true;
}
}
return true;
});
// listen to the "requestFailed" event - our request failed, so either the iin we have is bad or the provider is unavailable
emitter.on( 'requestFailed', function( iinToLookup, theRequest ) {
// make sure we are listening to a relevant event
if ( iinToLookup === iin ) {
// ok, our request failed we should remove it from the "currentRequests" queue
var index = currentRequests.indexOf( theRequest );
if ( index > -1 ) {
currentRequests.splice( index, 1 );
}
// if we have no requests the the queue then we should do a RegEx lookup as a last resort
if ( currentRequests.length === 0 ) {
doRegex( iin );
}
}
});
// listen to the "regexResult" event - we have a result from
emitter.on( 'regexResult', function( cardInfo ) {
if ( cardInfo.iin === iin ) {
// we don't want to worry about any more events
stopListening();
// now clean up any requests so we're not waiting around
clearRequestQueue();
// everything else has failed and we're left with the RegEx result - let's just send that back
return listenerCallback( null, cardInfo );
}
return true;
});
emitter.on( 'returnError', function( error, iinToLookup ) {
if ( iinToLookup === iin ) {
// we don't want to worry about any more events
stopListening();
clearRequestQueue();
// if we've made it here then we can only return and error
return listenerCallback( error );
}
return true;
});
}
try {
if ( _.isUndefined( iin ) ) {
throw new TypeError( this.options.messages.PARAMETER_IIN_IS_UNDEFINED );
} else {
if ( _.isEmpty( iin ) ) {
throw new TypeError( this.options.messages.PARAMETER_IIN_IS_EMPTY );
} else {
// convert input to string in case a int is passed in
iin = String( iin );
// First up. Make sure we are passing a number in.
if ( _.isNaN( parseInt( iin, 10 ) ) ) {
throw new TypeError( this.options.messages.PARAMETER_IIN_IS_NOT_A_NUMBER );
}
// Now make sure that we are passing in 6 characters
if ( iin.length !== 6 ) {
throw new TypeError( this.options.messages.PARAMETER_IIN_IS_NOT_LONG_ENOUGH );
}
}
}
if ( !_.isFunction( callback ) ) {
throw new TypeError( this.options.messages.PARAMETER_CALLBACK_NOT_FUNCTION );
}
bindListeners( callback );
// if cache is turned on attempt to get card details from cache
if ( self.options.cache ) {
self.options.cache.get( iin )
.then( function( data ) {
// card details from cache
callback( null, data );
return true;
} )
.catch( function() {
// attempt to get from the providers if not in cache
doLookup( self.providers, iin );
} );
} else {
// we are not using the cache, so do a lookup
doLookup( self.providers, iin );
}
} catch ( err ) {
// Return the err in the error callback, if not possible return it.
if ( callback ) {
callback( err );
return false;
}
return err;
}
return true;
};
} )();
{
"name": "iin-checker",
"description": "Issuer identification number checker which returns details about a credit/debit card",
"version": "0.1.9",
"version": "0.6.0",
"homepage": "https://github.com/Shortbreaks/iinChecker",

@@ -20,19 +20,24 @@ "author": "Simon Wood <simon.wood@holidayextras.com> (https://github.com/Shortbreaks)",

"scripts": {
"test": "grunt test"
"test": "node_modules/deployment-helpers/nodeApps/preRelease.sh && node_modules/.bin/make-up lib config controllers flows plugins test && istanbul cover _mocha -- test/**/*Test.js -R spec"
},
"devDependencies": {
"chai": "^1.9.1",
"coveralls": "^2.11.1",
"grunt": "~0.4.3",
"grunt-contrib-jshint": "^0.10.0",
"grunt-jscs": "^0.8.1",
"grunt-mocha-test": "^0.12.2",
"istanbul": "^0.3.0",
"mocha": "^2.0.1",
"nock": "^0.48.2"
"chai": "3.2.0",
"coveralls": "2.11.3",
"deployment-helpers": "git+https://github.com/holidayextras/deployment-helpers.git",
"dirty-chai": "^1.2.2",
"grunt": "0.4.5",
"grunt-contrib-jshint": "0.11.2",
"grunt-jscs": "2.0.0",
"grunt-mocha-test": "0.12.7",
"istanbul": "0.3.17",
"make-up": ">=9.1.0",
"mocha": "2.2.5",
"nock": "8.0.0",
"q": "^1.4.1",
"sinon": "1.12.2"
},
"dependencies": {
"lodash": "^2.4.1",
"request": "^2.40.0"
"lodash": "4.13.1",
"request": "2.72.0"
}
}
# IIN Checker for payment cards
[![Circle CI](https://circleci.com/gh/holidayextras/iinChecker/tree/master.svg?style=svg&circle-token=af8da07bbe8bddd209990d0f9f0c0a2f39db99df)](https://circleci.com/gh/holidayextras/iinChecker)
[![Dependency status](https://david-dm.org/holidayextras/iinChecker/status.png)](https://david-dm.org/holidayextras/iinChecker#info=dependencies&view=table)
[![Dev Dependency Status](https://david-dm.org/holidayextras/iinChecker/dev-status.png)](https://david-dm.org/holidayextras/iinChecker#info=devDependencies&view=table)
[![Build Status](https://travis-ci.org/Shortbreaks/iinChecker.png)](https://travis-ci.org/Shortbreaks/iinChecker)
[![Coverage Status](https://coveralls.io/repos/Shortbreaks/iinChecker/badge.png?branch=master)](https://coveralls.io/r/Shortbreaks/iinChecker?branch=master)
[![Dependency status](https://david-dm.org/Shortbreaks/iinChecker/status.png)](https://david-dm.org/Shortbreaks/iinChecker#info=dependencies&view=table)
[![Dev Dependency Status](https://david-dm.org/Shortbreaks/iinChecker/dev-status.png)](https://david-dm.org/Shortbreaks/iinChecker#info=devDependencies&view=table)
[![NPM](https://nodei.co/npm/iin-checker.png)](https://nodei.co/npm/iin-checker/)

@@ -34,13 +32,28 @@

var IinChecker = require( 'iin-checker' );
// Initialise with default options no caching
var iin = new IinChecker( {} );
// Initialise with caching
var iin = new IinChecker( {
cache: {
set: function( iin, cardDetails ) {...},
get: function( iin ) {..}
}
} );
iin.lookup( '543210', function( err, result ) {
if ( err ) {
console.log( 'Error:', err );
} else {
console.log( 'Result:', result );
}
if ( err ) {
console.log( 'Error:', err );
} else {
console.log( 'Result:', result );
}
} );
```
### Caching
Caching is turned off by default.
It can be turned on by passing in options a cache object with your functions to set and get the cache'.
### Card Type Detection

@@ -55,8 +68,8 @@

iin.lookup( '543210', function( err, result ) {
if ( err ) {
console.log( 'Error:', err );
} else {
var isDebit = ( result.type === iin.types.DEBIT )
console.log( 'Debit?:', isDebit );
}
if ( err ) {
console.log( 'Error:', err );
} else {
var isDebit = ( result.type === iin.types.DEBIT )
console.log( 'Debit?:', isDebit );
}
} );

@@ -76,8 +89,8 @@ ```

iin.lookup( '543210', function( err, result ) {
if ( err ) {
console.log( 'Error:', err );
} else {
var isMastercard = ( result.brand === iin.brands.MASTERCARD )
console.log( 'Mastercard?:', isMastercard );
}
if ( err ) {
console.log( 'Error:', err );
} else {
var isMastercard = ( result.brand === iin.brands.MASTERCARD )
console.log( 'Mastercard?:', isMastercard );
}
} );

@@ -92,5 +105,4 @@ ```

## License
Copyright (c) 2014 Shortbreaks
Copyright (c) 2016 Holiday Extras Ltd
Licensed under the MIT license.

@@ -97,0 +109,0 @@

@@ -11,4 +11,3 @@ {

"latitude": "38",
"longitude": "-97",
"query_time": "321.45us"
"longitude": "-97"
}

@@ -11,4 +11,3 @@ {

"latitude": "53",
"longitude": "-8",
"query_time": "285.736us"
"longitude": "-8"
}

@@ -11,4 +11,3 @@ {

"latitude": "54",
"longitude": "-2",
"query_time": "289.586us"
"longitude": "-2"
}

@@ -7,3 +7,4 @@ {

"type": "CREDIT",
"country_code": "US"
"country_code": "US",
"is_prepaid": false
}

@@ -7,3 +7,4 @@ {

"type": "DEBIT",
"country_code": "IE"
"country_code": "IE",
"is_prepaid": false
}

@@ -7,3 +7,4 @@ {

"type": "CREDIT",
"country_code": "GB"
"country_code": "GB",
"is_prepaid": false
}

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc