224 lines
7.0 KiB
JavaScript
224 lines
7.0 KiB
JavaScript
/**
|
|
* Uses the LexisNexis tracesmart IDU-AML SOAP api for verification
|
|
*/
|
|
'use strict';
|
|
|
|
var Q = require('q');
|
|
var _ = require('lodash');
|
|
var soap = require('soap');
|
|
var debug = require('debug')('utils:diligence:tracesmart');
|
|
var config = require(global.configFile);
|
|
var errors = require(global.pathPrefix + '../utils/diligence/diligence_errors.js');
|
|
var adminNotifier = require(global.pathPrefix + '../utils/adminNotifier.js');
|
|
|
|
const Request = require('./tracesmart-idu-aml/request.js');
|
|
|
|
module.exports = {
|
|
verifyIdentity: verifyIdentity
|
|
};
|
|
|
|
/**
|
|
* Verifies the identity of the person using the provided information
|
|
*
|
|
* @param {Object} client - The client object for this person
|
|
* @param {Object} address - The address object for this _person_ (not credit card!)
|
|
*
|
|
* @return {Promise} - A promise for the completion of the verification
|
|
*/
|
|
function verifyIdentity(client, address) {
|
|
debug('Verify identity');
|
|
|
|
const soapClientP = Q.nfcall(soap.createClient, config.tracesmartIduAmlUrl);
|
|
|
|
let request = new Request(client);
|
|
request.Person.applyClient(client);
|
|
request.Person.applyResidentialAddress(address);
|
|
|
|
let responseP = soapClientP.then((soapClient) => {
|
|
debug('SOAP Client Found');
|
|
|
|
//
|
|
// The soap client splits out the members of the object into individual
|
|
// calls to the soap object (which we don't want). So we wrap our request
|
|
// in a wrapper so it passes on our mega-object.
|
|
//
|
|
const wrappedRequest = {
|
|
params: request.getRequest() //getTestParams() //request
|
|
};
|
|
|
|
debug('Send request to tracesmart:');
|
|
|
|
// Make the call
|
|
return Q.ninvoke(
|
|
soapClient,
|
|
'IDUProcess',
|
|
request.getRequest()
|
|
).then((result) => {
|
|
debug('RESPONSE OK:');
|
|
return result[0];
|
|
}).catch((err) => {
|
|
debug('ERROR: ', err);
|
|
return err;
|
|
});
|
|
});
|
|
|
|
var convertP = responseP.then((response) => tidyResponse(response));
|
|
|
|
//
|
|
// Look at the results and decide if they are a pass or fail.
|
|
// Very basic criteria for now.
|
|
//
|
|
var resultP = convertP.then((converted) => {
|
|
if (
|
|
_.isObject(converted) &&
|
|
_.isObject(converted.Results) &&
|
|
_.isObject(converted.Results.Summary) &&
|
|
converted.Results.Summary.ResultText !== 'FAIL'
|
|
) {
|
|
let response = {
|
|
Smartscore: converted.Results.Summary.Smartscore,
|
|
ID: converted.Results.Summary.ID,
|
|
IKey: converted.Results.Summary.IKey,
|
|
ProfileURL: converted.Results.Summary.ProfileURL,
|
|
Warnings: []
|
|
};
|
|
if (converted.Results.Summary.ResultText === 'REFER') {
|
|
response.Warnings.push(errors.WARNINGS.REFER);
|
|
}
|
|
if (_.isArray(converted.Results.Sanction) && converted.Results.Sanction.length) {
|
|
response.Warnings.push(errors.WARNINGS.PEPS);
|
|
|
|
/**
|
|
* The items in the `Sanction` field may be PEPs or Sanctions.
|
|
* If they are sanctions this is a higher level issue
|
|
*/
|
|
for (let i = 0; i < converted.Results.Sanction.length; ++i) {
|
|
if (converted.Results.Sanction[i].Type === 'SANCTION') {
|
|
response.Warnings.push(errors.WARNINGS.SANCTIONS);
|
|
break; // Only need to find one to add the status
|
|
}
|
|
}
|
|
}
|
|
|
|
debug('Result: ', JSON.stringify(converted));
|
|
|
|
/**
|
|
* Potentially notify if credits are running out
|
|
*/
|
|
adminNotifier.notifyCredits('tracesmart', converted.Results.Summary.Credits);
|
|
|
|
return Q.resolve(response);
|
|
} else {
|
|
//
|
|
// Treat everything else as a fail for now
|
|
//
|
|
return Q.reject({
|
|
name: errors.ERRORS.VERIFICATION_FAILED
|
|
});
|
|
}
|
|
});
|
|
|
|
return resultP;
|
|
}
|
|
|
|
/**
|
|
* This function tidies up a SOAP response to be a simpler JS object without all
|
|
* the SOAP related attributes.
|
|
*
|
|
* @example
|
|
*
|
|
* SOAP response of:
|
|
*
|
|
* {
|
|
* "Status": {
|
|
* "attributes": {
|
|
* "xsi:type": "xsd:boolean"
|
|
* },
|
|
* "$value": "true"
|
|
* },
|
|
* "ID": {
|
|
* "attributes": {
|
|
* "xsi:type": "xsd:string"
|
|
* },
|
|
* "$value": "1234567890"
|
|
* },
|
|
* "Smartscore": {
|
|
* "attributes": {
|
|
* "xsi:type": "xsd:int"
|
|
* },
|
|
* "$value": "55"
|
|
* }
|
|
* }
|
|
*
|
|
* is transformed to:
|
|
*
|
|
* {
|
|
* "Status": true,
|
|
* "ID": "1234567890",
|
|
* "Smartscore": 55
|
|
* }
|
|
*
|
|
* Note that types are applied as appropriate for the related SOAP type attribute.
|
|
*
|
|
* @param {Object} response - The soap response from the tracesmart interface
|
|
* @returns {any} - The simplified response
|
|
*/
|
|
function tidyResponse(response) {
|
|
let tidied = null;
|
|
let type = null;
|
|
if (_.isObject(response.attributes)) {
|
|
type = response.attributes['xsi:type'];
|
|
}
|
|
|
|
//
|
|
// If this is a basic type then we just return the $value (which may not exist)
|
|
//
|
|
if (_.startsWith(type, 'xsd:') && _.isUndefined(response.$value)) {
|
|
//
|
|
// Don't coerce basic types with an undefined $value.
|
|
// Note: all Basic types start with 'xsd:'
|
|
//
|
|
tidied = undefined;
|
|
} else if (type === 'xsd:string') {
|
|
tidied = '' + response.$value; // Coerce to string
|
|
} else if (type === 'xsd:boolean') {
|
|
tidied = !!(response.$value); // Coerce to boolean
|
|
} else if (type === 'xsd:int') {
|
|
tidied = +(response.$value); // Coerce to number
|
|
} else if (type === 'SOAP-ENC:Array') {
|
|
//
|
|
// Arrays are a bit special in that they only have one other key, and
|
|
// if the array is length 1, the related value won't actually be an array!
|
|
//
|
|
tidied = []; // Default to empty array
|
|
_.forOwn(response, function(value, key) {
|
|
if (key === 'attributes') {
|
|
return; // Do nothing with attributes
|
|
} else if (_.isArray(value)) {
|
|
// This is an actual array of values so iterate it and push
|
|
// them into our response
|
|
_.forEach(value, (arrayItem) => {
|
|
tidied.push(tidyResponse(arrayItem));
|
|
});
|
|
} else if (_.isObject(value)) {
|
|
// Length 1 arrays are not in an array, so just push this item in
|
|
tidied.push(tidyResponse(value));
|
|
}
|
|
});
|
|
} else {
|
|
//
|
|
// We assume it is an object and iterate through and tidy them
|
|
//
|
|
tidied = {}; // Default to empty object
|
|
_.forOwn(response, function(value, key) {
|
|
if (key === 'attributes') {
|
|
return; // Do nothing with attributes
|
|
} else {
|
|
tidied[key] = tidyResponse(value);
|
|
}
|
|
});
|
|
}
|
|
|
|
return tidied;
|
|
}
|