//------------------------------------------------------------------------------------ //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 = "exampleRef1017"; //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 /acquiring/ 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 or Acquiring 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 /* After the sensitive card data has been encrypted, you will register the external card with a POST request to /acquiring/register. */ 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}/acquiring/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) }); } } //------------------------------------------------------------------------------------ //Pull From Card /* Once the card is registered, you may request pull transactions using a /acquiring/pull request in order to transfer funds from the external card into your settlement account. */ async function pullFromCard(authToken, cFiCardId, amount, reference) { const data = { "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}/acquiring/pull`, 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 /* You may query the current status of up to 20 card transactions at a time using an /acquiring/transactions-query request. It is recommended that you run an /acquiring/transactions-query only once or twice per day. The conditions under which an /acquiring/transactions-query should be made are as follows: * A status change was expected to have already occurred (For example, a card transaction was sent. It is now 12 hours later and a status change to "Complete" or "Declined" was expected to have already occurred.) * Do not send multiple queries for the same transaction within a 12 hour period. If a query confirms that the status of a card transaction has not moved when expected, contact customer support with the cFiTransactionId, current status, expected status, and date that the card transaction was created. * If you have more than one card transaction to query, do not send multiple queries with one transaction at a time. The queries should be sent in batches of 20 cFiTransactionIds/reference IDs per request. If you have less than 20 transactions to query, they can all be queried in a single request. */ async function query(authToken, cFiTransactionIds) { const data = { "cFiTransactionIds": [...cFiTransactionIds] }; const config = { method: 'POST', url: `${CONNECTFI_BASE_URL}/acquiring/transactions-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) }); } } //------------------------------------------------------------------------------------ //List /* It is possible to list up to 1000 card transactions at a time using search criteria with an /acquiring/list request. Search criteria may include a date range, a status value, or a combination of these search criteria. Use case examples for an /acquiring/list request include but are not limited to the following. * Listing all card transactions that still have an "Initiated" status. * Listing all card transactions that have a "Declined" status with a date range of "2023-05-10T12:00:30.000Z" to "2023-05-11T12:00:00.000Z". * Listing all transactions that have a "Completed" status in batches of 150. (You can use the "numberOfRecords" property and the "skipRecords" property to list up to 1000 transactions at a time or to skip a certain number of transactions.) */ async function list(authToken, status, numRecords, numToSkip) { const data = { "dateCreateFrom": `${new Date(moment(new Date()).add(-1, "hour")).toISOString()}`, "dateCreateTo": `${new Date(moment(new Date()).add(1, "hour")).toISOString()}`, "status": status, "numberOfRecords": numRecords, "skipRecords": numToSkip }; const config = { method: 'POST', url: `${CONNECTFI_BASE_URL}/acquiring/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) }); } } //------------------------------------------------------------------------------------ //Reverse Transaction /* It is possible to reverse a pull transaction using a POST request to /acquiring/reversal. */ async function reverseTransaction(authToken, cFiTransactionId) { const data = { "cFiTransactionId": cFiTransactionId }; const config = { method: 'POST', url: `${CONNECTFI_BASE_URL}/acquiring/reversal`, 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 acquiringWalkthrough() { //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 /acquiring/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 /acquiring/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`); //Pull From Card console.log(`Start /acquiring/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 /acquiring/pull example.\n`); //Query console.log(`Start /acquiring/transactions-query.\n`); let pushPullQueryResult; if (authToken && pullFromCardResult && pullFromCardResult.cFiTransactionId) { pushPullQueryResult = await query(authToken, [pullFromCardResult.cFiTransactionId]); } else { console.log(`Authorization token and cFiTransactionId(s) are required.\n`) } if (pushPullQueryResult) { console.log(`Successfully queried transactions: ${JSON.stringify(pushPullQueryResult)}\n`); } console.log(`End /acquiring/transactions-query.\n`); //List console.log(`Start /acquiring/list example.\n`); let listTransactionsResult; if (authToken) { listTransactionsResult = await list(authToken, "Complete", 5, 0); } else { console.log(`Authorization token is required.\n`) } if (listTransactionsResult) { console.log(`Successfully retrieved list of transactions matching criteria: ${JSON.stringify(listTransactionsResult)}\n`); } console.log(`End /acquiring/list example.\n`); //Reverse pull transaction console.log(`Start /acquiring/reversal example.\n`); let reverseResult; if (authToken && pullFromCardResult && pullFromCardResult.cFiTransactionId) { reverseResult = await reverseTransaction(authToken, pullFromCardResult.cFiTransactionId); } else { console.log(`Authorization token and cFiTransactionId are required.\n`) } if (reverseResult) { console.log(`Successfully reversed pull transaction: ${JSON.stringify(reverseResult)}\n`); } console.log(`End /acquiring/reversal.\n`); } if (process.env.CONNECTFI_CLIENTID && process.env.CONNECTFI_PASSWORD && process.env.CONNECTFI_BASE_URL) { acquiringWalkthrough(); } else { console.log("Before running the walkthrough, set the required .env variables."); }