//------------------------------------------------------------------------------------ //npm install --save axios moment dotenv const axios = require("axios"); const moment = require("moment"); require('dotenv').config() const FormData = require('form-data'); const fs = require('fs'); //------------------------------------------------------------------------------------ /* BEFORE RUNNING THIS EXAMPLE: * Set CONNECTFI_CLIENTID, CONNECTFI_PASSWORD, and CONNECTFI_BASE_URL in an .env file (Speak to a support representative to be issued client credentials and URL after receiving access to the sandbox.) * Set UNIQUE_CUSTOMER_REFERENCE_ID, UNIQUE_KYB_REFERENCE_ID, and UNIQUE_FEIN each to a unique identifier. * Place a test jpg image for the front identification document (such as the front of a Driver's License) with the naming convention DLFront.jpg and a test jpg image for the back identification document (back of the Driver's License) with the naming convention DLBack.jpg in the same folder as this .js file. */ const CONNECTFI_CLIENTID = process.env.CONNECTFI_CLIENTID; const CONNECTFI_PASSWORD = process.env.CONNECTFI_PASSWORD; const CONNECTFI_BASE_URL = process.env.CONNECTFI_BASE_URL; const UNIQUE_CUSTOMER_REFERENCE_ID = "exampleCustRef1006"; //Update this value so that it is a unique ID before running const UNIQUE_KYB_REFERENCE_ID = "exampleKYBRef1006"; //Update this value so that it is a unique ID before running const UNIQUE_FEIN = "000000006"; //Update this value so that it is unique before running const SSN = "000000001"; //Business Representative SSN //------------------------------------------------------------------------------------ //Get Authorization Token /* All other requests must have a valid authorization token in the request headers, so your first request in any workflow should be to /auth/get-token in order to receive an authorization token. A valid token should be included in the headers of all subsequent requests. */ async function getAuthToken() { const data = { "user": { "login": `${CONNECTFI_CLIENTID}`, "password": `${CONNECTFI_PASSWORD}` } }; const config = { method: 'POST', url: `${CONNECTFI_BASE_URL}/auth/get-token`, headers: { 'Content-Type': "application/json" }, data }; let result; try { result = await axios.request(config); if (result.status === 200) { return Promise.resolve(result.data.data); } } catch (err) { console.log({ errCode: err.code, responseStatus: err.response && err.response.status, data: err.response && JSON.stringify(err.response.data) }); } } //------------------------------------------------------------------------------------ //Create (Customer) /* After you have received an authorization token, you may then either initialize a customer, or you may generate and upload a document. (To move a customer through the KYC process, both actions will need to take place). Here is an example of how to initialize a business customer. */ async function initCustomer(authToken) { const data = { "customerReference": UNIQUE_CUSTOMER_REFERENCE_ID, //reference must be unique "customerType": "business", "customer": { "businessName": "ABC123 Inc.", "businessFederalEin": UNIQUE_FEIN, //federal EIN for the business "businessPhones": [ { "type": "PRIMARY", "countryDialingCode": "01", "phoneNumber": "5555555555" } ], "businessAddresses": [ { "type": "PRIMARY", "addressLine1": "123 Main Str.", "addressLine2": "Suite 456", "city": "Harrisburg", "stateCode": "PA", "postalCode": "12345", "countryCodeA3": "USA" } ] }, "businessRepresentatives": [{ "firstName": "John", "lastName": "Doe", "gender": "M", "businessRepresentativeType": "principal_owner", "email": "bus_rep_email_address@email.sample", "phones": [ { "type": "MOBILE", "countryDialingCode": "01", "phoneNumber": "5555555555" } ], "dateOfBirth": "1969-08-04", "citizenshipCountryCodeA3": "USA", "addresses": [ { "type": "PRIMARY", "addressLine1": "123 Main Str.", "addressLine2": "apt 1", "city": "Harrisburg", "stateCode": "PA", "postalCode": "12345", "countryCodeA3": "USA" } ], "identificationDocuments": [ { "type": "SSN", "value": SSN, "issuanceCountryCodeA3": "USA" }, { "type": "DRIVING LICENSE", "value": "PA99999999", "issuanceCountryCodeA3": "USA", "issuanceRegion": "PA", "expiryDate": "2029-12-28" } ] }] }; const config = { method: 'POST', url: `${CONNECTFI_BASE_URL}/customer/init`, headers: { 'Content-Type': "application/json", 'x-connectfi-token': authToken //previously obtained authorization token is required }, data }; let result; try { result = await axios.request(config); if (result.status === 200) { return Promise.resolve(result.data.data); } } catch (err) { console.log({ errCode: err.code, responseStatus: err.response && err.response.status, data: err.response && JSON.stringify(err.response.data) }); } } //------------------------------------------------------------------------------------ //Identification (KYB) /* The Know Your Customer (KYC) or Know Your Business (KYB) service is a process in which various databases are referenced in order to verify a customer’s identity. The customer may or may not pass KYC/KYB right away. Identification document images and/or additional data may need to be uploaded (indicated by a "pending_step_up" status). Once the verification process is complete, expect a callback at the webhook URL endpoint that you included in the initial /akepa/identification request to notify you of whether the customer was approved or declined. KYC/KYB can be performed on a customer multiple times if desired. It is the client's reponsibility to check the KYC/KYB status before requesting card or account products if necessary. When the KYC process begins, the KYC status will be "pending_step_up", indicating that identification documents are expected before moving forward. */ async function identifyKYBCustomer(authToken, customerId) { const data = { "customerId": customerId, //received when initializing the customer with /customer/init "reference": UNIQUE_KYB_REFERENCE_ID, //reference must be unique "webhookURL": `https://your_webhook_url/${UNIQUE_KYB_REFERENCE_ID}` }; const config = { method: 'POST', url: `${CONNECTFI_BASE_URL}/akepa/identification`, headers: { 'Content-Type': "application/json", 'x-connectfi-token': authToken //previously obtained authorization token is required }, data }; let result; try { result = await axios.request(config); if (result.status === 200) { return Promise.resolve(result.data.data); } } catch (err) { console.log({ errCode: err.code, responseStatus: err.response && err.response.status, data: err.response && JSON.stringify(err.response.data) }); } } //------------------------------------------------------------------------------------ //Generate Document (KYC) /* If additional documents are necessary (such as a scanned image of a driver’s license or other identification), first generate a document record using /akepa/document/generate. The response will include a documentId that you will use when uploading the document image. One or more documents may be necessary for the customer to pass KYC/KYB. You will need to generate a separate documentId for each document to be uploaded. For the KYB process, you will need to upload documents for the business representative(s) associated with the business. */ async function generateDocumentId(authToken) { const data = { "workflow": "BUSINESS", "name": "license", "extension": "jpg", "type": "license" }; const config = { method: 'POST', url: `${CONNECTFI_BASE_URL}/akepa/document/generate`, headers: { 'Content-Type': "application/json", 'x-connectfi-token': authToken //previously obtained authorization token is required }, data }; let result; try { result = await axios.request(config); if (result.status === 200) { return Promise.resolve(result.data.data); } } catch (err) { console.log({ errCode: err.code, responseStatus: err.response && err.response.status, data: err.response && JSON.stringify(err.response.data) }); } } //------------------------------------------------------------------------------------ //Upload Document (KYB) /* Use the documentId received from a previous /akepa/document/generate request in order to upload the document image with an /akepa/document/upload request. Only one document image may be uploaded per documentId. */ async function uploadDocument(authToken, documentId, documentPath, documentName) { const fileStream = fs.createReadStream(documentPath); const form = new FormData(); form.append('file', fileStream, documentName); const data = form; const config = { method: 'POST', url: `${CONNECTFI_BASE_URL}/akepa/document/upload/${documentId}`, headers: { ...form.getHeaders(), 'x-connectfi-token': authToken //previously obtained authorization token is required }, data }; let result; try { result = await axios.request(config); if (result.status === 200) { return Promise.resolve(result.data.data); } } catch (err) { console.log({ errCode: err.code, responseStatus: err.response && err.response.status, data: err.response && JSON.stringify(err.response.data) }); } } //------------------------------------------------------------------------------------ //Update (KYB) /* One or more documents may be necessary for the customer to pass KYB. When performing the /akepa/update request, you may link several documents (contained in the documents array) to the customer in one request. However, once the KYB identification application has reached a status of "completed", no more documents can be attached to the application. After updating the KYB journey through an /akepa/update request, if the status is still "pending_step_up", then more documents are expected and you will need to make another /akepa/update request to link the additional documents. For example, if you have only updated the journey with the "back" image of a Driver's License, then you will need to make an additional request to update the journey with the "front" image of the Driver's License. For convenience, it is recommended to include both the "front" and "back" images of the document in the documents array in a single request. For a business customer, the entityId is the ID that was issued to the respective business representative in the /customer/init response. The documents that are uploaded are documents belonging to the business representative, such as a Driver's License. */ async function updateKYC(authToken, customerId, busRepId, applicationId, documentIdFront, documentIdBack) { const data = { "customerId": customerId, //received from /customer/init request "applicationId": applicationId, //received from /akepa/identification request "documents": [ { "entityId": busRepId, //For a business customer, the entityId matches the business representative ID "documentId": documentIdFront, //received from /akepa/document/generate request "view": "front" }, { "entityId": busRepId, //For a business customer, the entityId matches the business representative ID "documentId": documentIdBack, //received from another /akepa/document/generate request "view": "back" } ] }; const config = { method: 'POST', url: `${CONNECTFI_BASE_URL}/akepa/update`, headers: { 'Content-Type': "application/json", 'x-connectfi-token': authToken //previously obtained authorization token is required }, data }; let result; try { result = await axios.request(config); if (result.status === 200) { return Promise.resolve(result.data.data); } } catch (err) { console.log({ errCode: err.code, responseStatus: err.response && err.response.status, data: err.response && JSON.stringify(err.response.data) }); } } //------------------------------------------------------------------------------------ //Status (KYB) /* The status for a specified customer KYB application can be checked at any point after identification initialization through a POST request to /akepa/status. If you included a webhook URL in the /akepa/identification request, then you will automatically receive a callback at your webhook endpoint if the application status changes to "completed" asynchronously. This may happen if your /akepa/update request moved the application status into a "waiting_review" status instead of "completed". When the application with status "waiting_review" is reviewed and completed manually, this triggers the webhook callback. */ async function status(authToken, cFiCustomerId, applicationId) { const data = { "customerId": cFiCustomerId, "applicationId": applicationId }; const config = { method: 'POST', url: `${CONNECTFI_BASE_URL}/akepa/status`, headers: { 'Content-Type': "application/json", 'x-connectfi-token': authToken //previously obtained authorization token is required }, data }; let result; try { result = await axios.request(config); if (result.status === 200) { return Promise.resolve(result.data.data); } } catch (err) { console.log({ errCode: err.code, responseStatus: err.response && err.response.status, data: err.response && JSON.stringify(err.response.data) }); } } //------------------------------------------------------------------------------------ //List /* This endpoint will list applications that match the desired customerId and optional date range. You may also specify a maximum number of records to return using the numberOfRecords property (up to 1000) and you may specify a number of records to skip using the skipRecords property. Combine the numberOfRecords property with the skipRecords property to make displaying application lists in batches easy. For example, if you want to list applications in batches of 5, you can set numberOfRecords to 5 and skipRecords to 0 in the first request. Then, increase skipRecords by 5 in each subsequent request to return the next batch and so on. When making a request, if the number of applications matching the criteria is greater than the numberOfRecords maximum requested, the response body will indicate that there are additional applications ("more": true). If no optional search properties are included, an unfiltered application list will be returned (up to a maximum of 1000). */ async function list(authToken, customerId, numRecords, numToSkip) { const data = { "customerId": customerId, "dateCreateFrom": `${new Date(moment(new Date()).add(-1, "hour")).toISOString()}`, "dateCreateTo": `${new Date(moment(new Date()).add(1, "hour")).toISOString()}`, "numberOfRecords": numRecords, "skipRecords": numToSkip }; const config = { method: 'POST', url: `${CONNECTFI_BASE_URL}/akepa/list`, headers: { 'Content-Type': "application/json", 'x-connectfi-token': authToken //previously obtained authorization token is required }, data }; let result; try { result = await axios.request(config); if (result.status === 200) { return Promise.resolve(result.data.data); } } catch (err) { console.log({ errCode: err.code, responseStatus: err.response && err.response.status, data: err.response && JSON.stringify(err.response.data) }); } } //------------------------------------------------------------------------------------ //Run the walkthrough async function kybWalkthrough() { //Get Authorization Token console.log(`Start /auth/get-token example.\n`); const authObject = await getAuthToken(); let authToken = undefined; if (authObject) { console.log(`Successfully obtained authorization: ${JSON.stringify(authObject)}\n`); authToken = authObject.token; console.log(`Authorization token: ${authToken}\n`); } else { console.log(`Error getting authorization token\n`) } console.log(`End /auth/get-token example.\n`); //Create (Customer) console.log(`Start /customer/init example.\n`); let customerId = undefined; let businessRepresentativeId = undefined; let customerInitResult; if (authToken) { customerInitResult = await initCustomer(authToken); } else { console.log(`Authorization token is required. Customer not initialized.\n`) } if (customerInitResult) { customerId = customerInitResult.customerId; businessRepresentativeId = customerInitResult.businessRepresentatives[0] && customerInitResult.businessRepresentatives[0].id; console.log(`Successfully created customer: ${JSON.stringify(customerInitResult)}\n`); } console.log(`End /customer/init example.\n`); //Identification (KYB) console.log(`Start /akepa/identification example.\n`); let kycIdentificationResult; let applicationId; if (authToken && customerId) { kycIdentificationResult = await identifyKYBCustomer(authToken, customerId); } else { console.log(`Authorization token and customerId are required.\n`) } if (kycIdentificationResult) { applicationId = kycIdentificationResult.applicationId; console.log(`Successfully initialized a KYB identification application journey: ${JSON.stringify(kycIdentificationResult)}\n`); } console.log(`End /akepa/identification example.\n`); //Generate Document Front (KYB) console.log(`Start /akepa/document/generate example Front.\n`); let generateDocumentResult1; let documentIdFront; if (authToken) { generateDocumentResult1 = await generateDocumentId(authToken); } else { console.log(`Authorization token is required.\n`) } if (generateDocumentResult1) { documentIdFront = generateDocumentResult1.id; console.log(`Successfully generated document record: ${JSON.stringify(generateDocumentResult1)}\n`); } console.log(`End /akepa/document/generate example Front.\n`); //Generate Document Back (KYB) console.log(`Start /akepa/document/generate example Back.\n`); let generateDocumentResult2; let documentIdBack; if (authToken) { generateDocumentResult2 = await generateDocumentId(authToken); } else { console.log(`Authorization token is required.\n`) } if (generateDocumentResult2) { documentIdBack = generateDocumentResult2.id; console.log(`Successfully generated document record: ${JSON.stringify(generateDocumentResult2)}\n`); } console.log(`End /akepa/document/generate example Back.\n`); //Upload Document Front (KYB) console.log(`Start /akepa/document/upload/:documentId example Front.\n`); let uploadDocumentResult1; if (authToken && documentIdFront) { uploadDocumentResult1 = await uploadDocument(authToken, documentIdFront, './DLFront.jpg', 'DLFront.jpg'); } else { console.log(`Authorization token and documentIdFront are required.\n`) } if (uploadDocumentResult1) { console.log(`Successfully uploaded the front image for the document: ${JSON.stringify(uploadDocumentResult1)}\n`); } console.log(`End /akepa/document/upload/:documentId example Front.\n`); //Upload Document Back (KYB) console.log(`Start /akepa/document/upload/:documentId example Back.\n`); let uploadDocumentResult2; if (authToken && documentIdBack) { uploadDocumentResult2 = await uploadDocument(authToken, documentIdBack, './DLBack.jpg', 'DLBack.jpg'); } else { console.log(`Authorization token and documentIdBack are required.\n`) } if (uploadDocumentResult2) { console.log(`Successfully uploaded the back image for the document: ${JSON.stringify(uploadDocumentResult2)}\n`); } console.log(`End /akepa/document/upload/:documentId example Back.\n`); //Update (KYB) console.log(`Start /akepa/update example.\n`); let updateKYCResult; if (authToken && customerId && businessRepresentativeId && applicationId && documentIdFront && documentIdBack) { updateKYCResult = await updateKYC(authToken, customerId, businessRepresentativeId, applicationId, documentIdFront, documentIdBack); } else { console.log(`Authorization token, customerId, businessRepresentativeId, applicationId, documentIdFront, and documentIdBack are required.\n`) } if (updateKYCResult) { console.log(`Successfully updated the KYB journey application: ${JSON.stringify(updateKYCResult)}\n`); } console.log(`End /akepa/update example.\n`); //Status (KYB) console.log(`Start /akepa/status example.\n`); let statusResult; if (authToken && customerId && applicationId) { statusResult = await status(authToken, customerId, applicationId); } else { console.log(`Authorization token, customerId, and applicationId are required.\n`) } if (statusResult) { console.log(`Successfully retrieved KYB journey application status: ${JSON.stringify(statusResult)}\n`); } console.log(`End /akepa/status example.\n`); //List (KYC) console.log(`Start /akepa/list example.\n`); let listResult; if (authToken && customerId) { listResult = await list(authToken, customerId); } else { console.log(`Authorization token and customerId are required. List not retrieved.\n`) } if (listResult) { console.log(`Successfully retrieved list of KYB journey applications matching filter: ${JSON.stringify(listResult)}\n`); } console.log(`End /akepa/list example.\n`); } if (process.env.CONNECTFI_CLIENTID && process.env.CONNECTFI_PASSWORD && process.env.CONNECTFI_BASE_URL) { kybWalkthrough(); } else { console.log("Before running the walkthrough, set the required .env variables."); }