PAYAPPS Example
The code below is an example of a process connecting to PAYAPPS. PAYAPPS authenticates using an OAuth password grant (client credentials over Basic Auth, plus a username/password, with automatic token refresh) rather than MODLR's built-in OAuth 2.0 flow, so this is an Advanced Integration.
INFO
This process requires PAYAPPS client credentials and a username/password, stored as Secrets or Model Variables rather than hardcoded in the process.
Library Code
A small client for the PAYAPPS API, handling authentication (including refreshing an expired token and retrying once) and wrapping authenticated GET requests. Save this as a Process Library and load it with script.library.
js
class PayAppsApi {
/**
* @param {string} basicToken - The base64-encoded client credentials for Basic Authorization.
* @param {string} username - The username for the OAuth password grant.
* @param {string} password - The password for the OAuth password grant.
* @param {string} [baseUrl="https://api.payapps.com/v1"] - The base URL of the PAYAPPS API.
*/
constructor(basicToken, username, password, baseUrl = "https://api.payapps.com/v1") {
this.username = username;
this.password = password;
this.baseUrl = baseUrl;
this.token = null;
this.refreshToken = null;
this.basicToken = datasource.atob(basicToken);
}
/**
* Helper method to create a form-encoded string.
* @private
*/
_encodeForm(params) {
return Object.keys(params)
.map(key => `${encodeURIComponent(key)}=${encodeURIComponent(params[key])}`)
.join('&');
}
/**
* Retrieves and sets the OAuth token for the API.
* @private
*/
_getToken() {
const url = `${this.baseUrl}/oauth/token`;
const headers = {
"Authorization": `Basic ${this.basicToken}`,
"Content-Type": "application/x-www-form-urlencoded"
};
const body = this._encodeForm({
grant_type: "password",
username: this.username,
password: this.password
});
const response = web.request('POST', url, headers, body, false);
if (response.status >= 200 && response.status < 300) {
const data = JSON.parse(response.body);
this.token = data.accessToken;
this.refreshToken = data.refreshToken;
} else {
console.log("Failed to obtain access token. Response body:", response.body);
throw new Error(`Token request failed with status code ${response.status}`);
}
}
/**
* Refreshes the OAuth token using the refresh token.
* @private
*/
_refreshAccessToken() {
const url = `${this.baseUrl}/oauth/refresh-token`;
const headers = {
"Authorization": `Basic ${this.basicToken}`,
"Content-Type": "application/x-www-form-urlencoded"
};
const body = this._encodeForm({
grant_type: "refresh_token",
refresh_token: this.refreshToken
});
const response = web.request('POST', url, headers, body, false);
if (response.status >= 200 && response.status < 300) {
const data = JSON.parse(response.body);
this.token = data.accessToken;
this.refreshToken = data.refreshToken;
} else {
console.log("Failed to refresh token. Response body:", response.body);
throw new Error(`Refresh token request failed with status code ${response.status}`);
}
}
/**
* Makes an HTTP request to the PAYAPPS API, refreshing the token and retrying once if it has expired.
* @private
*/
_fetch(url, method = 'GET', body = null, retryAttempt = 0) {
if (!this.token) {
this._getToken();
}
const absoluteUrl = `${this.baseUrl}/${url}`;
const headers = {
"Authorization": `Bearer ${this.token}`,
"Content-Type": "application/json"
};
const response = web.request(method, absoluteUrl, headers, body ? JSON.stringify(body) : null, false);
if (response.status === 401 && retryAttempt === 0) {
console.log("Access token expired, attempting to refresh...");
this._refreshAccessToken();
return this._fetch(url, method, body, retryAttempt + 1);
}
if (response.status >= 200 && response.status < 300) {
return JSON.parse(response.body);
} else {
console.log("Request failed. Response body:", response.body);
throw new Error(`Request failed with status code ${response.status}`);
}
}
/**
* Makes a GET request to the specified endpoint.
*/
get(url) {
return this._fetch(url, 'GET');
}
}Process Code
js
script.library("api/payapps.js");
function pre() {
// This function is called once before the process is executed.
// Use this to setup prompts.
script.log('process pre-execution parameters parsed.');
}
function begin() {
// This function is called once at the start of the process
script.log('process execution started.');
const basicToken = security.getSecret("PAYAPPS_BASIC_TOKEN");
const username = script.variableGet("PAYAPPS_EMAIL");
const password = script.variableGet("PAYAPPS_PASSWORD");
const api = new PayAppsApi(basicToken, username, password);
try {
const projects = api.get("projects");
console.log(`Projects returned: ${projects.length}`);
console.log(projects);
} catch (e) {
console.log("Something went wrong");
console.log(e);
}
}
function data(record) {
// This function is called once for each line of data on the second cycle
// Use this to build dimensions and push data into cubes
}
function end() {
// This function is called once at the end of the process
script.log('process execution finished.');
}