//------------------------------------------------------------------------------------ //npm install --save axios moment dotenv base64url const axios = require("axios"); const moment = require("moment"); const base64url = require("base64url").default; const fs = require("fs"); const crypto = require("crypto"); //node standard crypto require('dotenv').config() //------------------------------------------------------------------------------------ /* BEFORE RUNNING THIS EXAMPLE: * Set KEYID, 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_REFERENCE_ID to a unique identifier. */ const CONNECTFI_CLIENTID = process.env.CONNECTFI_CLIENTID; const CONNECTFI_PASSWORD = process.env.CONNECTFI_PASSWORD; const CONNECTFI_BASE_URL = process.env.CONNECTFI_BASE_URL; const KEYID = process.env.KEYID; const UNIQUE_REFERENCE_ID = "exampleRef1016"; //Update this value so that it is a unique ID before running //------------------------------------------------------------------------------------ //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) }); } } //------------------------------------------------------------------------------------ //Encrypt Card Data /* Before making any push or pull requests, you will need to encrypt the sensitive card data (including PAN, expiration date, and CVV) and register the card with connectFi. Instructions on how to encrypt the card data can be found in the "Encrypting Card Data" section of the Push To/Pull From Cards API Documentation. */ async function getEncryptedCard() { const myKey = fs.readFileSync("./public.key"); const pan = "9400111999999990"; //test card const expiryYYYYMM = "203012"; const cvv = "123"; const rawData = `${pan}|${expiryYYYYMM}|${cvv}`; //1234567890123456|202211|123 const encryptedData = crypto.publicEncrypt({ key: myKey, padding: crypto.constants.RSA_PKCS1_OAEP_PADDING, oaepHash: "sha256", }, Buffer.from(rawData) ) const result = base64url(encryptedData); return result; } //------------------------------------------------------------------------------------ //Register Card /* Before push to card or pull from card transactions can be performed through the unified payments endpoint, the encrypted external card used must be registered in our system using a POST request to the /transfer-to/combo/card/register endpoint. */ async function registerCard(authToken, reference, encryptedCardData) { const data = { "reference": reference, "card": { "encryptedData": encryptedCardData, "keyId": KEYID }, "owner": { "name": { "first": "John", "middle": "M", "last": "Doe", "suffix": "III" }, "address": { "addressLine1": "1346 Pleasant Ave", "addressLine2": "Apt A123", "city": "Salt Lake City", "state": "UT", "postalCode": "12345", "country": "US" }, "phone": { "countryCode": "1", "number": "5556667777" } } } ; const config = { method: 'POST', url: `${CONNECTFI_BASE_URL}/transfer-to/combo/card/register`, 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) }); } } //------------------------------------------------------------------------------------ //Get Card /* Previously registered card details can be retrieved using a GET request to /transfer-to/card/get/:cardId. When registering a card (or when retrieving the card details with a GET request), a successful response body will include boolean values indicating whether the card can accept push/pull transactions, the card expiration date, and the last four digits of the card. */ async function getCard(authToken, cFiCardId) { const config = { method: 'GET', url: `${CONNECTFI_BASE_URL}/transfer-to/card/get/${cFiCardId}`, headers: { 'x-connectfi-token': authToken //previously obtained authorization token is required } }; 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) }); } } //------------------------------------------------------------------------------------ //Push To Card /* ACH transfers, push to card transactions, pull from card transactions, and wire transfers are each possible through a /transfer-to/combo/create request. The request body for each type of possible transaction is the same as when creating a new transaction using the respective single service endpoints, with the addition of a transferType property that is used to specified the type of transaction required. For example, an ACH transfer using the /transfer-to/combo/create endpoint would have the same request body format as an ACH transfer using the /transfer-to/ach/create endpoint, with the addition of the field: `"transferType": "ach"`. A push or pull card transaction using the /transfer-to/combo/create endpoint would have the same request body format as a push or pull transaction using the /transfer-to/card/push (or pull) endpoint, with the addition of the field: `"transferType": "push" //or pull`. Likewise, a wire transaction using the /transfer-to/combo/create endpoint would have the same request body format as a wire transaction using the /transfer-to/wire endpoint, with the addition of the field: `"transferType": "wire"`. The following is an example of a push to card transaction using the unified payments endpoint. */ async function pushToCard(authToken, cFiCardId, amount, reference) { const data = { "transferType": "push", "reference": reference, "cFiCardId": cFiCardId, "amount": amount, "currency": "USD", "narrative": "For invoice #123", "softDescriptor": { "name": "Sample Merchant", "address": { "addressLine1": "1346 Pleasant Ave", "addressLine2": "Apt A123", "city": "Salt Lake City", "state": "PA", "postalCode": "12345", "country": "US" }, "phone": { "countryCode": "1", "number": "5556667777" }, "email": "merchant@sample.com" } }; const config = { method: 'POST', url: `${CONNECTFI_BASE_URL}/transfer-to/combo/create`, 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) }); } } //------------------------------------------------------------------------------------ //Pull From Card /* The following is an example of a pull from card transaction using the unified payments endpoint. */ async function pullFromCard(authToken, cFiCardId, amount, reference) { const data = { "transferType": "pull", "reference": reference, "cFiCardId": cFiCardId, "amount": amount, "currency": "USD", "narrative": "For invoice #123", "softDescriptor": { "name": "Sample Merchant", "address": { "addressLine1": "1346 Pleasant Ave", "addressLine2": "Apt A123", "city": "Salt Lake City", "state": "PA", "postalCode": "12345", "country": "US" }, "phone": { "countryCode": "1", "number": "5556667777" }, "email": "merchant@sample.com" } }; const config = { method: 'POST', url: `${CONNECTFI_BASE_URL}/transfer-to/combo/create`, 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) }); } } //------------------------------------------------------------------------------------ //ACH /* The following is an example of an ACH transaction using the unified payments endpoint. */ async function sendACHPayment(authToken, amount, reference) { const data = { "transferType": "ach", "reference": reference, "effectiveEntryDate": `${moment(new Date()).add(1, "day").format("YYYY-MM-DD")}`, //dated for next day "standardEntryClassCode": "CCD", "individualName": "John Smith", "routingNumber": "053207766", "DFIAccountNumber": "123456789", "accountType": "Checking", "transactionType": "Debit", "amount": amount, "currency": "USD", "companyEntryDescription": "PAYMENT", "webhookUrl": `https://your_webhook_url/${reference}` }; const config = { method: 'POST', url: `${CONNECTFI_BASE_URL}/transfer-to/combo/create`, 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) }); } } //------------------------------------------------------------------------------------ //Wire /* The following is an example of a wire transaction using the unified payments endpoint. */ async function sendWirePayment(authToken, amount, reference) { const data = { "transferType": "wire", "reference": reference, "amount": amount, "currency": "USD", "endUserAccount": "1234567890123456", "originator": { "commonName": "John Doe", "legalName": "John Doe", "address": { "addressLine1": "999 Main Street", "addressLine2": "Ste. A", "city": "San Francisco", "state": "CA", "postalCode": "12345", "country": "US" } }, "beneficiary": { "bankName": "Unicorn Bank", "bankABANumber": "122244184", "accountNumber": "1234567890123456", "name": "John Smith", "address": { "addressLine1": "1346 Pleasant Ave", "addressLine2": "Apt A123", "city": "Salt Lake City", "state": "UT", "postalCode": "12345", "country": "US" } }, "narrative": "Invoice #123 for May", "memos": [ "For spare parts per contract #123", "Delivered Salt Lake City UT 12345" ], "webhookURL": `https://your_webhook_url/${reference}` }; const config = { method: 'POST', url: `${CONNECTFI_BASE_URL}/transfer-to/combo/create`, 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) }); } } //------------------------------------------------------------------------------------ //Query /* Once unified payments transactions have been created, they can be queried using the /transfer-to/combo/query endpoint. The query request body will contain an array of objects with a transferType property and either a reference ID property or a cFiTransactionId property. All included objects must contain the same ID type (either reference IDs or cFiTransactionIds). A transaction created through the /transfer-to/combo/create endpoint is eligible for any applicable action available through its respective single service endpoints. ACH transactions are eligible for /transfer-to/ach endpoint actions, push or pull transactions are eligible for /transfer-to/card actions, etc. */ async function query(authToken, cFiTransactionIdObjects) { const data = { "cFiTransactionIds": [...cFiTransactionIdObjects] }; const config = { method: 'POST', url: `${CONNECTFI_BASE_URL}/transfer-to/combo/query`, 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 unifiedWalkthrough() { //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`); //Register Card console.log(`Start /transfer-to/combo/card/register example.\n`); let registerResult; if (authToken) { registerResult = await registerCard(authToken, UNIQUE_REFERENCE_ID, `${await getEncryptedCard()}`); } else { console.log(`Authorization token is required.\n`) } if (registerResult) { console.log(`Successfully registered external card: ${JSON.stringify(registerResult)}\n`); } console.log(`End /transfer-to/combo/card/register example.\n`); //Get Card console.log(`Start /transfer-to/card/get/:cardId example.\n`); let getCardResult; if (authToken && registerResult && registerResult.cFiCardId) { getCardResult = await getCard(authToken, registerResult.cFiCardId); } else { console.log(`Authorization token and a valid cFiCardId are required.\n`) } if (getCardResult) { console.log(`Successfully retrieved card details: ${JSON.stringify(getCardResult)}\n`); } console.log(`End /transfer-to/card/get/:cardId example.\n`); //Push To Card console.log(`Start /transfer-to/combo/create PUSH example.\n`); let pushToCardResult; if (authToken && registerResult && registerResult.cFiCardId) { pushToCardResult = await pushToCard(authToken, registerResult.cFiCardId, 1.51, `Push${UNIQUE_REFERENCE_ID}`); } else { console.log(`Authorization token and a valid cFiCardId are required.\n`) } if (pushToCardResult) { console.log(`Successfully pushed funds to card: ${JSON.stringify(pushToCardResult)}\n`); } console.log(`End /transfer-to/combo/create PUSH example.\n`); //Pull From Card console.log(`Start /transfer-to/combo/create PULL example.\n`); let pullFromCardResult; if (authToken && registerResult && registerResult.cFiCardId) { pullFromCardResult = await pullFromCard(authToken, registerResult.cFiCardId, 1.01, `Pull${UNIQUE_REFERENCE_ID}`); } else { console.log(`Authorization token and a valid cFiCardId are required.\n`) } if (pullFromCardResult) { console.log(`Successfully pulled funds from card: ${JSON.stringify(pullFromCardResult)}\n`); } console.log(`End /transfer-to/combo/create PULL example.\n`); //ACH console.log(`Start /transfer-to/combo/create ACH example.\n`); let achTransactionResult; if (authToken) { achTransactionResult = await sendACHPayment(authToken, 1.01, `ACH${UNIQUE_REFERENCE_ID}`); } else { console.log(`Authorization token is required.\n`) } if (achTransactionResult) { console.log(`Successfully created ACH payment: ${JSON.stringify(achTransactionResult)}\n`); } console.log(`End /transfer-to/combo/create ACH example.\n`); //Wire console.log(`Start /transfer-to/combo/create WIRE example.\n`); let wireTransactionResult; if (authToken) { wireTransactionResult = await sendWirePayment(authToken, 1.01, `Wire${UNIQUE_REFERENCE_ID}`); } else { console.log(`Authorization token is required.\n`) } if (wireTransactionResult) { console.log(`Successfully created wire transaction: ${JSON.stringify(wireTransactionResult)}\n`); } console.log(`End /transfer-to/combo/create WIRE example.\n`); //Query console.log(`Start /transfer-to/combo/query.\n`); let queryResult; if (authToken && pushToCardResult && pushToCardResult.cFiTransactionId && pullFromCardResult && pullFromCardResult.cFiTransactionId && achTransactionResult && achTransactionResult.cFiTransactionId && wireTransactionResult && wireTransactionResult.cFiTransactionId) { queryResult = await query(authToken, [ { transferType: "push", cFiTransactionId: pushToCardResult.cFiTransactionId }, { transferType: "pull", cFiTransactionId: pullFromCardResult.cFiTransactionId }, { transferType: "ach", cFiTransactionId: achTransactionResult.cFiTransactionId }, { transferType: "wire", cFiTransactionId: wireTransactionResult.cFiTransactionId } ]); } else { console.log(`Authorization token and cFiTransactionId(s) are required.\n`) } if (queryResult) { console.log(`Successfully queried transactions: ${JSON.stringify(queryResult)}\n`); } console.log(`End /transfer-to/card/transactions-query.\n`); } if (process.env.KEYID && process.env.CONNECTFI_CLIENTID && process.env.CONNECTFI_PASSWORD && process.env.CONNECTFI_BASE_URL) { unifiedWalkthrough(); } else { console.log("Before running the walkthrough, set the required .env variables."); }