/** * 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; }