//------------------------------------------------------------------------------------ //npm install --save axios dotenv lodash moment crypto-js const axios = require("axios"); require('dotenv').config() const lodash = require("lodash"); const moment = require("moment"); const CryptoJS = require("crypto-js"); //------------------------------------------------------------------------------------ /* BEFORE RUNNING THIS EXAMPLE: * Set AMPLIFI_BASE_URL, AMPLIFI_TEST_DEVICETAG, AFICOMPANYID, and SERVER_SECRET in an .env file. (Speak to a support representative to be issued client credentials and URL after receiving access to the sandbox.) * AMPLIFI_TEST_DEVICETAG, AFICOMPANYID, and SERVER_SECRET are obtained from onboarding a prospect. */ const AMPLIFI_BASE_URL = process.env.AMPLIFI_BASE_URL; const SERVER_SECRET = process.env.SERVER_SECRET; //obtained when onboarding prospect const DEVICE_TAG = process.env.AMPLIFI_TEST_DEVICETAG; //deviceTag from onboarding prospect const AFICOMPANYID = process.env.AFICOMPANYID; //existing account of the onboarded business user matching the SERVER_SECRET and DEVICE_TAG const dtsValueString = (new Date()).valueOf().toString(); const INT_TAG = lodash.padStart(dtsValueString, 10, "0") + Math.floor(Math.random() * 1000); //Unique tag to identify a generic request and avoid duplicates, typically a string of digits const halfRef = `some1RANDOM2string${(dtsValueString / 1).toString()}`; const cryptotext = CryptoJS.AES.encrypt(DEVICE_TAG + dtsValueString, SERVER_SECRET).toString(); const cardReference = "123"; const credentialsTestChannel = { channel: "test", deviceTag: DEVICE_TAG, deviceData: { "platform": "test" } }; const credentialsAndroidChannel = { "channel": "android_v1", "dtsValueString": dtsValueString, "deviceTag": DEVICE_TAG, "socket": { halfRef: halfRef }, "cryptotext": cryptotext }; const credentialsIOSChannel = { "channel": "ios_v1", "dtsValueString": dtsValueString, "deviceTag": DEVICE_TAG, "socket": { halfRef: halfRef }, "cryptotext": cryptotext }; const credentialsBrowserChannel = { "channel": "browser", "deviceId": DEVICE_TAG }; //------------------------------------------------------------------------------------ //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) }); } } //------------------------------------------------------------------------------------ //Open Card /* A new card may be opened using the "ccc_admin_new_account_self" generic request handler and the PUT /requests endpoint. This particular type of generic request opens a new card for a business user, so the AFiCompanyId is also required. Opening cards and accounts is a segment specific process, so it is handled through a generic request. Generic Requests can be defined to open vAccounts for businesses or individuals, debit cards for businesses or individuals, or credit cards for individuals. In addition, cards or accounts can be specific to family members, business representatives, employees, etc. This is just an illustrative example of one type of card that can be opened. */ async function openCard(authToken, intTag, AFiCompanyId) { const data = { typeId: "ccc_admin_new_account_self", intTag, payload: { AFiCompanyId } } const config = { method: 'PUT', url: `${AMPLIFI_BASE_URL}/requests`, 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) }); } } //------------------------------------------------------------------------------------ //List Cards /* This endpoint returns an array of records for all cards that belong to this user. */ async function listCards(authToken) { const config = { method: 'GET', url: `${AMPLIFI_BASE_URL}/cards`, headers: { 'Content-Type': "application/json", '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); } } catch (err) { console.log({ errCode: err.code, responseStatus: err.response && err.response.status, data: err.response && JSON.stringify(err.response.data) }); } } //------------------------------------------------------------------------------------ //Get Card /* This endpoint returns a record for a single card, identified by a path parameter, belonging to the logged in user. Some fields may vary depending on the requirements of the back office. Also, some fields such as name and pin are stored by the back office only and are not saved by ampliFi. */ async function getCard(authToken, AFiCardId) { const config = { method: 'GET', url: `${AMPLIFI_BASE_URL}/cards/${AFiCardId}`, //or /card/${AFiCardId} headers: { 'Content-Type': "application/json", '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); } } catch (err) { console.log({ errCode: err.code, responseStatus: err.response && err.response.status, data: err.response && JSON.stringify(err.response.data) }); } } //------------------------------------------------------------------------------------ //Modify Card /* This endpoint modifies the properties of a card, which is identified by the path parameter. Only the properties to be changed should be included in the payload, and changes will only be made to properties that are permitted to change. */ async function modifyCard(authToken, AFiCardId) { const data = { "name": `testModified${cardReference}`, "isMain": true }; const config = { method: 'POST', url: `${AMPLIFI_BASE_URL}/cards/${AFiCardId}`, //or /card/${AFiCardId} 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) }); } } //------------------------------------------------------------------------------------ //Activate Card /* This endpoint activates a card, if it has not already been activated. The card is identified by the path parameter. If the card was previously activated, then this call has no effect, and the card record is returned. If the call succeeds, the record for the newly activated card is returned with the status set to "active" and the isActive property set to true. */ async function activateCard(authToken, AFiCardId) { data = { //none }; const config = { method: 'POST', url: `${AMPLIFI_BASE_URL}/cards/${AFiCardId}/activate`, //or /card/${AFiCardId}/activate 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) }); } } //------------------------------------------------------------------------------------ //Modify Card PIN /* This endpoint changes a card's PIN. The card is identified by a path parameter, and the new PIN must be given as 8 digits in the JSON formatted body of the request. However, only digits 3-6 will be extracted and sent to the back office as the actual PIN. The PIN is stored by the back office and cannot be retrieved. */ async function modifyCardPIN(authToken, AFiCardId, newPIN) { data = { "pin": newPIN }; const config = { method: 'POST', url: `${AMPLIFI_BASE_URL}/card/${AFiCardId}/pin`, 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) }); } } //------------------------------------------------------------------------------------ //Toggle Card On/Off /* Turning a card off means no transactions will be authorized. The card status will be set to "blocked" and the isActive property will be set to false. Turning a card on means that transactions can be authorized. The card status will be set to "active" and the isActive property will be set to true. The card is identified by a path parameter. There is no request body. */ async function toggleCardOnOff(authToken, AFiCardId, OnOff = "on") { data = { //none }; const config = { method: 'POST', url: `${AMPLIFI_BASE_URL}/cards/${AFiCardId}/${OnOff}`, //or /card/${AFiCardId}/${OnOff} 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) }); } } //------------------------------------------------------------------------------------ //Reissue Card /* This endpoint reissues a card when the existing card is lost, damaged or stolen. The card is identified by a path parameter. The reason for the reissue is given in the request body and can be either "lost" or "stolen". If the reason is "stolen", then the card will be given a new number. The reissue reason "lost" means that the card will have the same card number, but it may get a different expiration date. Unless the card number was compromised one way or another, use "lost". */ async function reissueCard(authToken, AFiCardId) { data = { reason: "lost" }; const config = { method: 'POST', url: `${AMPLIFI_BASE_URL}/card/${AFiCardId}/reissue`, 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) }); } } //------------------------------------------------------------------------------------ //Close Card /* This endpoint closes a card. The card is identified by the path parameter. */ async function closeCard(authToken, AFiCardId) { const config = { method: 'DELETE', url: `${AMPLIFI_BASE_URL}/cards/${AFiCardId}`, //or /card/${AFiCardId} headers: { 'Content-Type': "application/json", '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); } } 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 ampliFiCardsWalkthrough() { //Get Authorization Token console.log(`Start /token example.\n`); const authObjectTest = await getAuthToken(credentialsAndroidChannel); let authToken = undefined; if (authObjectTest) { console.log(`Successfully obtained user authorization: ${JSON.stringify(authObjectTest)}\n`); authToken = authObjectTest.token; console.log(`Authorization token: ${authToken}\n`); } else { console.log(`Error getting authorization token\n`) } console.log(`End /token example.\n`); //Open Card console.log(`Start PUT /requests example.\n`); let openTempCardResult; if (authToken) { openTempCardResult = await openCard(authToken, INT_TAG, AFICOMPANYID); } else { console.log(`Authorization token is required.\n`) } if (openTempCardResult) { console.log(`Successfully created new business debit card: ${JSON.stringify(openTempCardResult)}\n`); } console.log(`End PUT /requests example.\n`); //List Cards console.log(`Start GET /cards example.\n`); let listCardsResult; let AFiCardId if (authToken) { listCardsResult = await listCards(authToken); } else { console.log(`Authorization token is required.\n`) } if (listCardsResult) { console.log(`Successfully listed cards (if any): ${JSON.stringify(listCardsResult)}\n`); //Find the card we created earlier const testDate = moment(new Date()).add(-1, "minute"); const AFiCard = lodash.find(listCardsResult.cards, (card) => { return card.isActivated === false && card.isActive === null && moment(card.dtsOpened) > testDate; }); console.log(AFiCard ? `AFiCard: ${JSON.stringify(AFiCard)}\n` : `No card found.`) AFiCardId = AFiCard ? AFiCard.AFiCardId : undefined; console.log(AFiCardId ? `AFiCardId: ${AFiCardId}\n` : `No card id found.`) } console.log(`End GET /cards example.\n`); //Get Card console.log(`Start GET /cards/:AFiCardId example.\n`); let getCardResult; if (authToken && AFiCardId) { getCardResult = await getCard(authToken, AFiCardId); } else { console.log(`Authorization token and AFiCardId are required.\n`) } if (getCardResult) { console.log(`Successfully found card: ${JSON.stringify(getCardResult)}\n`); } console.log(`End GET /cards/:AFiCardId example.\n`); //Modify Card console.log(`Start POST /cards/:AFiCardId example.\n`); let modifyCardResult; if (authToken && AFiCardId) { modifyCardResult = await modifyCard(authToken, AFiCardId); } else { console.log(`Authorization token and AFiCardId are required.\n`) } if (modifyCardResult) { console.log(`Successfully modified card: ${JSON.stringify(modifyCardResult)}\n`); } console.log(`End POST /cards/:AFiCardId example.\n`); //Activate Card console.log(`Start POST /cards/:AFiCardId/activate example.\n`); let activateCardResult; if (authToken && AFiCardId) { activateCardResult = await activateCard(authToken, AFiCardId); } else { console.log(`Authorization token and AFiCardId are required.\n`) } if (activateCardResult) { console.log(`Successfully activated card: ${JSON.stringify(activateCardResult)}\n`); } console.log(`End POST /cards/:AFiCardId/activate example.\n`); //Modify Card PIN console.log(`Start POST /card/:AFiCardId/pin example.\n`); let modifyCardPINResult; if (authToken && AFiCardId) { modifyCardPINResult = await modifyCardPIN(authToken, AFiCardId, "99123499"); } else { console.log(`Authorization token and AFiCardId are required.\n`) } if (modifyCardPINResult) { console.log(`Successfully modified card PIN: ${JSON.stringify(modifyCardPINResult)}\n`); } console.log(`End GET POST /card/:AFiCardId/pin example.\n`); //Toggle Card Off console.log(`Start POST /cards/:AFiCardId/:OnOff example.\n`); let toggleCardOffResult; if (authToken && AFiCardId) { toggleCardOffResult = await toggleCardOnOff(authToken, AFiCardId, "off"); } else { console.log(`Authorization token and AFiCardId are required.\n`) } if (toggleCardOffResult) { console.log(`Successfully toggled card off: ${JSON.stringify(toggleCardOffResult)}\n`); } console.log(`End /POST /cards/:AFiCardId/:OnOff example.\n`); //Toggle Card On console.log(`Start POST /cards/:AFiCardId/:OnOff example.\n`); let toggleCardOnResult; if (authToken && AFiCardId) { toggleCardOnResult = await toggleCardOnOff(authToken, AFiCardId, "on"); } else { console.log(`Authorization token and AFiCardId are required.\n`) } if (toggleCardOnResult) { console.log(`Successfully toggled card on: ${JSON.stringify(toggleCardOnResult)}\n`); } console.log(`End /POST /cards/:AFiCardId/:OnOff example.\n`); //Reissue Card console.log(`Start POST /card/:AFiCardId/reissue example.\n`); let reissueCardResult; if (authToken && AFiCardId) { reissueCardResult = await reissueCard(authToken, AFiCardId); } else { console.log(`Authorization token and AFiCardId are required.\n`) } if (reissueCardResult) { console.log(`Successfully reissued card: ${JSON.stringify(reissueCardResult)}\n`); } console.log(`End /POST /card/:AFiCardId/reissue example.\n`); //Close Card console.log(`Start DELETE /cards/:AFiCardId example.\n`); let closeCardResult; if (authToken && AFiCardId) { closeCardResult = await closeCard(authToken, AFiCardId); } else { console.log(`Authorization token and AFiCardId are required.\n`) } if (closeCardResult) { console.log(`Successfully closed card: ${JSON.stringify(closeCardResult)}\n`); } console.log(`End DELETE /cards/:AFiCardId example.\n`); } if (process.env.AMPLIFI_BASE_URL) { ampliFiCardsWalkthrough(); } else { console.log("Before running the walkthrough, set the required .env variables."); }