//------------------------------------------------------------------------------------ //npm install --save axios dotenv lodash moment const axios = require("axios"); require('dotenv').config() const lodash = require("lodash"); const moment = require("moment"); //------------------------------------------------------------------------------------ /* BEFORE RUNNING THIS EXAMPLE: * Set AMPLIFI_BASE_URL and PAN in an .env file. (Speak to a support representative to be issued client credentials and URL after receiving access to the sandbox.) * Set DEVICE_TAG using value obtained from onboarding a prospect. */ const AMPLIFI_BASE_URL = process.env.AMPLIFI_BASE_URL; const PAN = process.env.PAN; const DEVICE_TAG = "my_deviceTag1713"; //use same deviceTag from onboarding prospect const AFIACCOUNTID = "qwegalv2jpwm7vgwd"; //use AFiAccountId that belongs to user corresponding to DEVICE_TAG const todaysDate = new Date(); const dtsValueString = (todaysDate).valueOf().toString(); const accountNumber = lodash.padStart(dtsValueString, 16, "0"); //simulate accountNumber for external bank account const credentialsTestChannel = { channel: "test", deviceTag: DEVICE_TAG, deviceData: { "platform": "test" } }; //------------------------------------------------------------------------------------ //Get Authorization Token /* The ampliFi system exposes a REST API and expects calls directly from the front-end. After onboarding, ampliFi authenticates each individual customer and performs transactions only explicitly allowed to that specific customer. Requests must have a valid authorization token in the request headers, so your first request in any workflow should be to /token in order to receive an authorization token. A valid token should be included in the headers of all subsequent requests. */ async function getAuthToken(credentials) { const data = credentials; const config = { method: 'PUT', url: `${AMPLIFI_BASE_URL}/token`, headers: { 'Content-Type': "application/json" }, data }; let result; try { result = await axios.request(config); if (result.status === 200) { return Promise.resolve(result.data); } } catch (err) { console.log({ errCode: err.code, responseStatus: err.response && err.response.status, data: err.response && JSON.stringify(err.response.data) }); } } //------------------------------------------------------------------------------------ //Sync External Accounts /* The /externalaccounts/sync endpoint will allow you to add, modify, or list external accounts. The action taken will depend on the request body. * To add a new account, you will include the external account details as an external account object in the externalAccounts array request body property. The external account object properties included will vary depending on the type of account. Do not include the AFiExternalAccountId property when adding a new external account. * To modify an existing external account, you will include an external account object in the externalAccounts array containing the AFiExternalAccountId of the external account to be modified and any allowed changes, such as the name property, comment property, or isActive property. * To list external accounts, the externalAccounts property will be an empty array. */ async function syncExternalAccounts(authToken, requesttype, AFiExternalAccountId) { const dataOptions = { newExternalDebitCardAccount: { //Request body of /externalaccounts/sync for a new external debit card "externalAccounts": [ { "pan": PAN, "expiryYYYYMM": "202610", "cvv": "123", "name_on_card": "John Doe", "type": "DEBITCARD" } ] }, newExternalBankAccount: { //Request body of /externalaccounts/sync for a new external bank account "externalAccounts": [ { "isActive": true, "accountNumber": accountNumber, "routingNumber": "031101279", "type": "CHECKING", "title": "John Testman", "bankName": "Bank of America", "isOwn": true, "countryCode": "USA", "currency": "USD", "comment": "External checking account" } ] }, modifyExternalAccount: { //Request body of /externalaccounts/sync to modify an external account "externalAccounts": [ { "AFiExternalAccountId": AFiExternalAccountId, "backOfficeId": "connectFiBOIS", "backOfficeName": "connectFiBOIS", "isActive": true, "name": `BANameTest${(todaysDate / 1).toString()}`, "dtsModified": new Date(), "comment": "New comment" } ] }, listExternalAccounts: { //Request body of /externalaccounts/sync to list external accounts "externalAccounts": [] } }; const config = { method: 'POST', url: `${AMPLIFI_BASE_URL}/externalaccounts/sync`, headers: { 'Content-Type': "application/json", 'token': authToken //previously obtained authorization token is required }, data: dataOptions[requesttype] }; let result; try { result = await axios.request(config); if (result.status === 200) { return Promise.resolve(result.data); } } catch (err) { console.log({ errCode: err.code, responseStatus: err.response && err.response.status, data: err.response && JSON.stringify(err.response.data) }); } } //------------------------------------------------------------------------------------ //Verify External Account /* Account will be verified using microdeposits. */ async function verifyExternalAccount(authToken, AFiExternalAccountId) { const data = { AFiExternalAccountId, "amount1": 1.01, "amount2": 1.01 }; const config = { method: 'POST', url: `${AMPLIFI_BASE_URL}/externalaccount/verify`, headers: { 'Content-Type': "application/json", '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); } } catch (err) { console.log({ errCode: err.code, responseStatus: err.response && err.response.status, data: err.response && JSON.stringify(err.response.data) }); } } //------------------------------------------------------------------------------------ //Pull From External Account /* The /externalaccount/pull endpoint will pull funds from the specified external account and deposit the funds into the account specified by AFiAccountId or the user's main account. The amount transferred may be subject to configured maximums and/or minimums. It is possible to create a recurring transaction by using "isRecurring": true. */ async function pullExternalAccount(authToken, AFiExternalAccountId, AFiAccountId) { const data = { "AFiExternalAccountId": AFiExternalAccountId, "AFiAccountId": AFiAccountId, "amount": 20, "currency": "USD", "isRecurring": false }; const config = { method: 'POST', url: `${AMPLIFI_BASE_URL}/externalaccount/pull`, headers: { 'Content-Type': "application/json", '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); } } catch (err) { console.log({ errCode: err.code, responseStatus: err.response && err.response.status, data: err.response && JSON.stringify(err.response.data) }); } } //------------------------------------------------------------------------------------ //Push to External Account /* The /externalaccount/push endpoint will push funds to the specified external account after withdrawing the funds from the account specified by AFiAccountId or the user's main account. The amount transferred may be subject to configured maximums and/or minimums. It is possible to create a recurring transaction by using "isRecurring": true. */ async function pushExternalAccount(authToken, AFiExternalAccountId, AFiAccountId) { const data = { "AFiExternalAccountId": AFiExternalAccountId, "AFiAccountId": AFiAccountId, "amount": 0.01, "currency": "USD", "recurring": false }; const config = { method: 'POST', url: `${AMPLIFI_BASE_URL}/externalaccount/push`, headers: { 'Content-Type': "application/json", '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); } } catch (err) { console.log({ errCode: err.code, responseStatus: err.response && err.response.status, data: err.response && JSON.stringify(err.response.data) }); } } //------------------------------------------------------------------------------------ //Autofunding From External Account /* The /externalaccount/autofunding endpoint can be used to set up recurring topup transactions. Auto-funding will transfer the specified set amount on a regular schedule, or cadence. This example will transfer $100 from the specified external account when the user's main account reaches a "when_low" threshold of $10. */ async function autofundingExternalAccount(authToken, AFiExternalAccountId) { const data = { "AFiExternalAccountId": AFiExternalAccountId, "cadence": "when_low", "amount": 100, "currency": "USD", "lowAmount": 10.00 }; const config = { method: 'POST', url: `${AMPLIFI_BASE_URL}/externalaccount/autofunding`, headers: { 'Content-Type': "application/json", '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); } } catch (err) { console.log({ errCode: err.code, responseStatus: err.response && err.response.status, data: err.response && JSON.stringify(err.response.data) }); } } async function removeResponseB64Images(response) { let shortenedExternalAccounts = []; //Removing B64 images from external accounts to shorten response if (response && response.externalAccounts && !lodash.isEmpty(response.externalAccounts)) { response.externalAccounts.forEach((externalAccount) => { shortenedExternalAccounts.push({ ...externalAccount, logoB64: externalAccount.logoB64 ? "" : undefined }) }); } return { ...response, externalAccounts: shortenedExternalAccounts }; } async function findExternalAccount(response, propertyType, propertyValue) { const externalAccount = response.externalAccounts.find((externalAccount) => { const createdAfterDate = moment(todaysDate).add(-1, "second") .toISOString(); const todaysISODate = moment(todaysDate).toISOString(); return externalAccount[propertyType] === propertyValue && externalAccount["dtsCreated"] >= createdAfterDate; }); return externalAccount; } //------------------------------------------------------------------------------------ //Run the walkthrough async function ampliFiExternalAccountsWalkthrough() { //Get Authorization Token credentialsTestChannel console.log(`Start /token example credentialsTestChannel.\n`); const authObjectTest = await getAuthToken(credentialsTestChannel); let authToken = undefined; if (authObjectTest) { console.log(`Successfully obtained test user authorization: ${JSON.stringify(authObjectTest)}\n`); authToken = authObjectTest.token; console.log(`Authorization token: ${authToken}\n`); } else { console.log(`Error getting Test authorization token\n`) } console.log(`End /token example credentialsTestChannel.\n`); //Sync External Accounts newExternalDebitCardAccount console.log(`Start /externalaccounts/sync newExternalDebitCardAccount example.\n`); let syncEAsResultDC; let aFiEAIdDC if (authToken) { const result = await syncExternalAccounts(authToken, "newExternalDebitCardAccount", undefined); syncEAsResultDC = result && await removeResponseB64Images(result); } else { console.log(`Authorization token is required.\n`) } if (syncEAsResultDC) { console.log(`Successfully synced external account: ${JSON.stringify(syncEAsResultDC)}\n`); const aFiEA = await findExternalAccount(syncEAsResultDC, "panMasked", `${PAN.substring(0, 1)}...${PAN.substring(PAN.length - 4)}`); aFiEAIdDC = aFiEA && aFiEA.AFiExternalAccountId; console.log(`AFiExternalAccountId for new debit card: ${aFiEAIdDC}\n`); } console.log(`End /externalaccounts/sync newExternalDebitCardAccount example.\n`); //Sync External Accounts newExternalBankAccount console.log(`Start /externalaccounts/sync newExternalBankAccount example.\n`); let syncEAsResultBA; let aFiEAIdBA if (authToken) { const result = await syncExternalAccounts(authToken, "newExternalBankAccount", undefined); syncEAsResultBA = result && await removeResponseB64Images(result); } else { console.log(`Authorization token is required.\n`) } if (syncEAsResultBA) { console.log(`Successfully synced external account: ${JSON.stringify(syncEAsResultBA)}\n`); const aFiEA = await findExternalAccount(syncEAsResultBA, "accountNumber", accountNumber); aFiEAIdBA = aFiEA && aFiEA.AFiExternalAccountId; console.log(`AFiExternalAccountId for new bank account: ${aFiEAIdBA}\n`); } console.log(`End /externalaccounts/sync newExternalBankAccount example.\n`); //Sync External Accounts modifyExternalAccount console.log(`Start /externalaccounts/sync modifyExternalAccount example.\n`); let syncEAsResultMod; if (authToken) { const result = await syncExternalAccounts(authToken, "modifyExternalAccount", aFiEAIdBA); syncEAsResultMod = result && await removeResponseB64Images(result); } else { console.log(`Authorization token is required.\n`) } if (syncEAsResultMod) { console.log(`Successfully modified external account: ${JSON.stringify(syncEAsResultMod)}\n`); const aFiEA = await findExternalAccount(syncEAsResultMod, "AFiExternalAccountId", aFiEAIdBA); console.log(`Modified account: ${JSON.stringify(aFiEA)}\n`); } console.log(`End /externalaccounts/sync modifyExternalAccount example.\n`); //Sync External Accounts listExternalAccounts console.log(`Start /externalaccounts/sync listExternalAccounts example.\n`); let syncEAsResultList; if (authToken) { const result = await syncExternalAccounts(authToken, "listExternalAccounts", undefined); syncEAsResultList = result && await removeResponseB64Images(result); } else { console.log(`Authorization token is required.\n`) } if (syncEAsResultList) { console.log(`Successfully listed external accounts: ${JSON.stringify(syncEAsResultList)}\n`); } console.log(`End /externalaccounts/sync listExternalAccounts example.\n`); //Verify External Account console.log(`Start /externalaccounts/verify example.\n`); let verifyEAsResult; if (authToken) { verifyEAsResult = await verifyExternalAccount(authToken, aFiEAIdBA); } else { console.log(`Authorization token is required.\n`) } if (verifyEAsResult) { console.log(`Successfully verified external accounts: ${JSON.stringify(verifyEAsResult)}\n`); } console.log(`End /externalaccounts/verify example.\n`); //Pull From External Account console.log(`Start /externalaccount/pull example.\n`); let pullEAsResult; if (authToken) { pullEAsResult = await pullExternalAccount(authToken, aFiEAIdBA, AFIACCOUNTID); } else { console.log(`Authorization token is required.\n`) } if (pullEAsResult) { console.log(`Successfully pulled from external account: ${JSON.stringify(pullEAsResult)}\n`); } console.log(`End /externalaccounts/pull example.\n`); //Push From External Account console.log(`Start /externalaccount/push example.\n`); let pushEAsResult; if (authToken) { pushEAsResult = await pushExternalAccount(authToken, aFiEAIdBA, AFIACCOUNTID); } else { console.log(`Authorization token is required.\n`) } if (pushEAsResult) { console.log(`Successfully pushed from external account: ${JSON.stringify(pushEAsResult)}\n`); } console.log(`End /externalaccounts/push example.\n`); //Auto-funding From External Account console.log(`Start /externalaccount/autofunding example.\n`); let autofundingEAsResult; if (authToken) { autofundingEAsResult = await autofundingExternalAccount(authToken, aFiEAIdBA); } else { console.log(`Authorization token is required.\n`) } if (autofundingEAsResult) { console.log(`Successfully set up autofunding from external account: ${JSON.stringify(autofundingEAsResult)}\n`); } console.log(`End /externalaccounts/autofunding example.\n`); } if (process.env.PAN && process.env.AMPLIFI_BASE_URL) { ampliFiExternalAccountsWalkthrough(); } else { console.log("Before running the walkthrough, set the required .env variables."); }