(function(root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define(['superagent', 'querystring'], factory);
} else if (typeof module === 'object' && module.exports) {
// CommonJS-like environments that support module.exports, like Node.
module.exports = factory(require('logtown').getLogger('orbipay-paymentsapi-client/src/ApiClient'), require('superagent'), require('querystring'), require('./CommonUtil'), require('crypto'));
} else {
// Browser globals (root is window)
if (!root.OrbipayPaymentsapiClient) {
root.OrbipayPaymentsapiClient = {};
}
root.OrbipayPaymentsapiClient.ApiClient = factory(root.superagent, root.querystring);
}
}(this, function(logger, superagent, querystring, CommonUtil, crypto) {
'use strict';
/**
* @module ApiClient
*/
/**
* <h3 style="color:red"> This class subject to change without prior notice, Please dont use this class directly. </h3>
* Manages low level client-server communications, parameter marshalling, etc. There should not be any need for an
* application to use this class directly - the *Api and model classes provide the public API for the service. The
* contents of this file should be regarded as internal but are documented for completeness.
* @alias module:ApiClient
* @class
*/
var exports = function(clientSignatureKey, basePath, clientApiKey, authScheme) {
/**
* The base URL against which to resolve every API call's (relative) path.
* @type {String}
* @default https://api.orbipay.com/payments/v1
*/
this.basePath = basePath;
/**
* The authentication methods to be included for all API calls.
* @type {Array.<String>}
*/
this.authentications = {
};
/**
* The default HTTP headers to be included for all API calls.
* @type {Array.<String>}
* @default {}
*/
this.defaultHeaders = {};
/**
* The default HTTP timeout for all API calls.
* @type {Number}
* @default 60
*/
this.timeout = 60;
/**
* If set to false an additional timestamp parameter is added to all API GET calls to
* prevent browser caching
* @type {Boolean}
* @default true
*/
this.cache = true;
/**
* If set to true, the client will save the cookies from each server
* response, and return them in the next request.
* @default false
*/
this.enableCookies = false;
/*
* Used to save and return cookies in a node.js (non-browser) setting,
* if this.enableCookies is set to true.
*/
if (typeof window === 'undefined') {
this.agent = new superagent.agent();
}
/*
* Allow user to override superagent agent
*/
this.requestAgent = null;
this.clientSignatureKey = clientSignatureKey;
this.clientApiKey = clientApiKey;
this.authScheme = authScheme;
};
/**
* Returns a string representation for an actual parameter.
* @param param The actual parameter.
* @returns {String} The string representation of <code>param</code>.
*/
exports.prototype.paramToString = function(param) {
if (param === undefined || param === null) {
return '';
}
if (param instanceof Date) {
return param.toJSON();
}
return param.toString();
};
/**
* Builds full URL by appending the given path to the base URL and replacing path parameter place-holders with parameter values.
* NOTE: query parameters are not handled here.
* @param {String} path The path to append to the base URL.
* @param {Object} pathParams The parameter values to append.
* @returns {String} The encoded path with parameter values substituted.
*/
exports.prototype.buildUrl = function(path, pathParams) {
if (!path.match(/^\//)) {
path = '/' + path;
}
var url = this.basePath + path;
var _this = this;
url = url.replace(/\{([\w-]+)\}/g, function(fullMatch, key) {
var value;
if (pathParams.hasOwnProperty(key)) {
value = _this.paramToString(pathParams[key]);
if(exports.isEmptyString(value)){
logger.error('Missing the required parameter ' + key);
throw new Error('Missing the required parameter ' + key);
}
} else {
value = fullMatch;
}
return encodeURIComponent(value);
});
return url;
};
/**
* Checks whether the given content type represents JSON.<br>
* JSON content type examples:<br>
* <ul>
* <li>application/json</li>
* <li>application/json; charset=UTF8</li>
* <li>APPLICATION/JSON</li>
* </ul>
* @param {String} contentType The MIME content type to check.
* @returns {Boolean} <code>true</code> if <code>contentType</code> represents JSON, otherwise <code>false</code>.
*/
exports.prototype.isJsonMime = function(contentType) {
return Boolean(contentType != null && contentType.match(/^application\/json(;.*)?$/i));
};
/**
* Chooses a content type from the given array, with JSON preferred; i.e. return JSON if included, otherwise return the first.
* @param {Array.<String>} contentTypes
* @returns {String} The chosen content type, preferring JSON.
*/
exports.prototype.jsonPreferredMime = function(contentTypes) {
for (var i = 0; i < contentTypes.length; i++) {
if (this.isJsonMime(contentTypes[i])) {
return contentTypes[i];
}
}
return contentTypes[0];
};
/**
* Checks whether the given parameter value represents file-like content.
* @param param The parameter to check.
* @returns {Boolean} <code>true</code> if <code>param</code> represents a file.
*/
exports.prototype.isFileParam = function(param) {
// fs.ReadStream in Node.js and Electron (but not in runtime like browserify)
if (typeof require === 'function') {
var fs;
try {
fs = require('fs');
} catch (err) {}
if (fs && fs.ReadStream && param instanceof fs.ReadStream) {
return true;
}
}
// Buffer in Node.js
if (typeof Buffer === 'function' && param instanceof Buffer) {
return true;
}
// Blob in browser
if (typeof Blob === 'function' && param instanceof Blob) {
return true;
}
// File in browser (it seems File object is also instance of Blob, but keep this for safe)
if (typeof File === 'function' && param instanceof File) {
return true;
}
return false;
};
/**
* Normalizes parameter values:
* <ul>
* <li>remove nils</li>
* <li>keep files and arrays</li>
* <li>format to string with `paramToString` for other cases</li>
* </ul>
* @param {Object.<String, Object>} params The parameters as object properties.
* @returns {Object.<String, Object>} normalized parameters.
*/
exports.prototype.normalizeParams = function(params) {
var newParams = {};
for (var key in params) {
if (params.hasOwnProperty(key) && params[key] != undefined && params[key] != null) {
var value = params[key];
if (this.isFileParam(value) || Array.isArray(value)) {
newParams[key] = value;
} else {
newParams[key] = this.paramToString(value);
}
}
}
return newParams;
};
/**
* Enumeration of collection format separator strategies.
* @enum {String}
* @readonly
*/
exports.CollectionFormatEnum = {
/**
* Comma-separated values. Value: <code>csv</code>
* @const
*/
CSV: ',',
/**
* Space-separated values. Value: <code>ssv</code>
* @const
*/
SSV: ' ',
/**
* Tab-separated values. Value: <code>tsv</code>
* @const
*/
TSV: '\t',
/**
* Pipe(|)-separated values. Value: <code>pipes</code>
* @const
*/
PIPES: '|',
/**
* Native array. Value: <code>multi</code>
* @const
*/
MULTI: 'multi'
};
/**
* Builds a string representation of an array-type actual parameter, according to the given collection format.
* @param {Array} param An array parameter.
* @param {module:ApiClient.CollectionFormatEnum} collectionFormat The array element separator strategy.
* @returns {String|Array} A string representation of the supplied collection, using the specified delimiter. Returns
* <code>param</code> as is if <code>collectionFormat</code> is <code>multi</code>.
*/
exports.prototype.buildCollectionParam = function buildCollectionParam(param, collectionFormat) {
if (param == null) {
return null;
}
switch (collectionFormat) {
case 'csv':
return param.map(this.paramToString).join(',');
case 'ssv':
return param.map(this.paramToString).join(' ');
case 'tsv':
return param.map(this.paramToString).join('\t');
case 'pipes':
return param.map(this.paramToString).join('|');
case 'multi':
// return the array directly as SuperAgent will handle it as expected
return param.map(this.paramToString);
default:
throw new Error('Unknown collection format: ' + collectionFormat);
}
};
/**
* Applies authentication headers to the request.
* @param {Object} request The request object created by a <code>superagent()</code> call.
* @param {Array.<String>} authNames An array of authentication method names.
*/
exports.prototype.applyAuthToRequest = function(request, authNames) {
var _this = this;
authNames.forEach(function(authName) {
var auth = _this.authentications[authName];
switch (auth.type) {
case 'basic':
if (auth.username || auth.password) {
request.auth(auth.username || '', auth.password || '');
}
break;
case 'apiKey':
if (auth.apiKey) {
var data = {};
if (auth.apiKeyPrefix) {
data[auth.name] = auth.apiKeyPrefix + ' ' + auth.apiKey;
} else {
data[auth.name] = auth.apiKey;
}
if (auth['in'] === 'header') {
request.set(data);
} else {
request.query(data);
}
}
break;
case 'oauth2':
if (auth.accessToken) {
request.set({'Authorization': 'Bearer ' + auth.accessToken});
}
break;
default:
throw new Error('Unknown authentication type: ' + auth.type);
}
});
};
/**
* Deserializes an HTTP response body into a value of the specified type.
* @param {Object} response A SuperAgent response object.
* @param {(String|Array.<String>|Object.<String, Object>|Function)} returnType The type to return. Pass a string for simple types
* or the constructor function for a complex type. Pass an array containing the type name to return an array of that type. To
* return an object, pass an object with one property whose name is the key type and whose value is the corresponding value type:
* all properties on <code>data<code> will be converted to this type.
* @returns A value of the specified type.
*/
exports.prototype.deserialize = function deserialize(response, returnType) {
if (response == null || returnType == null || response.status == 204) {
return null;
}
// Rely on SuperAgent for parsing response body.
// See http://visionmedia.github.io/superagent/#parsing-response-bodies
var data = response.body;
if (data == null || (typeof data === 'object' && typeof data.length === 'undefined' && !Object.keys(data).length)) {
// SuperAgent does not always produce a body; use the unparsed response as a fallback
data = response.text;
}
return exports.convertToType(data, returnType);
};
/**
* Callback function to receive the result of the operation.
* @callback module:ApiClient~callApiCallback
* @param {String} error Error message, if any.
* @param data The data returned by the service call.
* @param {String} response The complete HTTP response.
*/
/**
* Invokes the REST service using the supplied settings and parameters.
* @param {String} path The base URL to invoke.
* @param {String} httpMethod The HTTP method to use.
* @param {Object.<String, String>} pathParams A map of path parameters and their values.
* @param {Object.<String, Object>} queryParams A map of query parameters and their values.
* @param {Object.<String, Object>} collectionQueryParams A map of collection query parameters and their values.
* @param {Object.<String, Object>} headerParams A map of header parameters and their values.
* @param {Object.<String, Object>} formParams A map of form parameters and their values.
* @param {Object} bodyParam The value to pass as the request body.
* @param {Array.<String>} authNames An array of authentication type names.
* @param {Array.<String>} contentTypes An array of request MIME types.
* @param {Array.<String>} accepts An array of acceptable response MIME types.
* @param {(String|Array|ObjectFunction)} returnType The required type to return; can be a string for simple types or the
* constructor for a complex type.
* @param {module:ApiClient~callApiCallback} callback The callback function.
* @returns {Object} The SuperAgent request object.
*/
exports.prototype.callApi = function callApi(path, httpMethod, pathParams,
queryParams, headerParams, formParams, bodyParam, authNames, contentTypes, accepts,
returnType, callback) {
var _this = this;
var url = this.buildUrl(path, pathParams);
logger.debug('HTTP URL: ' + url);
logger.debug('HTTP Method: ' + httpMethod);
// added by us
var resourcePath = this.resourcePath(path, pathParams);
if(this.clientApiKey == null || this.clientApiKey == ''){
this.clientApiKey = headerParams['client_key'];
}
if(this.authScheme == null || this.authScheme == '' || this.authScheme == 'OPAY1-HMAC-SHA256'){
var authorizationData = exports.computeOPAY1HMACSHA256Hash(resourcePath, httpMethod, queryParams, headerParams, formParams, bodyParam, this.clientSignatureKey);
var clientHash = authorizationData['hash'];
headerParams['Authorization'] = 'OPAY1-HMAC-SHA256 Credential=' + this.clientApiKey + ',Signature=' + clientHash.trim() + '';
}else if(this.authScheme == 'OPAY2-HMAC-SHA256'){
var authorizationData = exports.computeOPAY2HMACSHA256Hash(headerParams['client_key'], this.clientApiKey,this.clientSignatureKey, headerParams['timestamp']);
var clientHash = authorizationData['hash'];
headerParams['Authorization'] = 'OPAY2-HMAC-SHA256 Credential=' + this.clientApiKey + ',Signature=' + clientHash.trim() + '';
}else if(this.authScheme == 'OPAY3-HMAC-SHA256'){
headerParams['Authorization'] = 'OPAY3-HMAC-SHA256 Credential=' + this.clientApiKey;
}else{
logger.debug('invalid auth_scheme received');
throw new Error('invalid auth_scheme received');
}
headerParams['User-Agent'] = 'orbipay-paymentsapi-client/1.14.0/Node';
var request = superagent(httpMethod, url);
// apply authentications
this.applyAuthToRequest(request, authNames);
// set query parameters
if (httpMethod.toUpperCase() === 'GET' && this.cache === false) {
queryParams['_'] = new Date().getTime();
}
request.query(this.normalizeParams(queryParams));
logger.debug('Query Params: ', JSON.stringify(queryParams));
// set header parameters
request.set(this.defaultHeaders).set(this.normalizeParams(headerParams));
logger.debug('Request Headers: ', JSON.stringify(headerParams));
logger.debug('Request Body: ', JSON.stringify(bodyParam, CommonUtil.maskSensitiveInfo));
// set requestAgent if it is set by user
if (this.requestAgent) {
request.agent(this.requestAgent);
}
if (process.env['ORBIPAY_PAYMENTS_API_TIMEOUT_SECONDS']) {
this.timeout = process.env['ORBIPAY_PAYMENTS_API_TIMEOUT_SECONDS'];
logger.debug('HTTP TIMEOUT in seconds : ' + this.timeout);
}
else{
logger.warn('Timeout environment variable, ORBIPAY_PAYMENTS_API_TIMEOUT_SECONDS is not found setting default value in seconds: ', this.timeout);
}
request.timeout(this.timeout*1000);
var contentType = this.jsonPreferredMime(contentTypes);
if (contentType) {
// Issue with superagent and multipart/form-data (https://github.com/visionmedia/superagent/issues/746)
if(contentType !== 'multipart/form-data') {
request.type(contentType);
}
} else if (!request.header['Content-Type']) {
request.type('application/json');
}
if (contentType === 'application/x-www-form-urlencoded') {
request.send(querystring.stringify(this.normalizeParams(formParams)));
} else if (contentType === 'multipart/form-data') {
var _formParams = this.normalizeParams(formParams);
for (var formParamKey in _formParams) {
if (_formParams.hasOwnProperty(formParamKey)) {
if (this.isFileParam(_formParams[formParamKey])) {
// file field
request.attach(formParamKey, _formParams[formParamKey]);
} else {
request.field(formParamKey, _formParams[formParamKey]);
}
}
}
} else if (bodyParam) {
request.send(bodyParam);
}
var accept = this.jsonPreferredMime(accepts);
if (accept) {
request.accept(accept);
}
if (returnType === 'Blob') {
request.responseType('blob');
} else if (returnType === 'String') {
request.responseType('string');
}
// Attach previously saved cookies, if enabled
if (this.enableCookies){
if (typeof window === 'undefined') {
this.agent.attachCookies(request);
}
else {
request.withCredentials();
}
}
if (callback) {
logger.info('Sending Request to Service');
request.end(function (error, response) {
var data = null;
if (!error) {
try {
data = _this.deserialize(response, returnType);
logger.info('Receive Response from Service : HTTP Status Code :', response.status);
logger.debug('HTTP Response Body :', JSON.stringify(data, CommonUtil.maskSensitiveInfo));
if (_this.enableCookies && typeof window === 'undefined') {
_this.agent.saveCookies(response);
}
} catch (err) {
error = err;
}
}
if (error) {
logger.info('Receive Response from Service : HTTP Status Code :', error.status);
logger.error('HTTP Error code :', error.code);
logger.error('HTTP Error errno :', error.errno);
logger.error('HTTP Error message :', error.message);
if(response && response.text) {
logger.debug('HTTP Response Body :', response.text);
}
else{
logger.warn('HTTP Body is not received ');
}
}
response = response || {};
logger.debug('Response Headers :', JSON.stringify(response['header']));
if(response['header']) {
logger.info('request_uuid :', response['header']['request_uuid']);
}
callback(error, data, response);
});
}
return request;
};
/**
* Parses an ISO-8601 string representation of a date value.
* @param {String} str The date value as a string.
* @returns {Date} The parsed date object.
*/
exports.parseDate = function(str) {
return new Date(str.replace(/T/i, ' '));
};
/**
* Converts a value to the specified type.
* @param {(String|Object)} data The data to convert, as a string or object.
* @param {(String|Array.<String>|Object.<String, Object>|Function)} type The type to return. Pass a string for simple types
* or the constructor function for a complex type. Pass an array containing the type name to return an array of that type. To
* return an object, pass an object with one property whose name is the key type and whose value is the corresponding value type:
* all properties on <code>data<code> will be converted to this type.
* @returns An instance of the specified type or null or undefined if data is null or undefined.
*/
exports.convertToType = function(data, type) {
if (data === null || data === undefined)
return data;
switch (type) {
case 'Boolean':
return Boolean(data);
case 'Integer':
return parseInt(data, 10);
case 'Number':
return parseFloat(data);
case 'String':
if(typeof data === 'string'){
return String(data);
}
else{
return data;
}
case 'Date':
return this.parseDate(String(data));
case 'Blob':
return data;
default:
if (type === Object) {
// generic object, return directly
return data;
} else if (typeof type === 'function') {
// for model type like: User
return type.constructFromObject(data);
} else if (Array.isArray(type)) {
// for array type like: ['String']
var itemType = type[0];
return data.map(function(item) {
return exports.convertToType(item, itemType);
});
} else if (typeof type === 'object') {
// for plain object type like: {'String': 'Integer'}
var keyType, valueType;
for (var j in type) {
if (type.hasOwnProperty(j)) {
keyType = j;
valueType = type[j];
break;
}
}
var result = {};
for (var k in data) {
if (data.hasOwnProperty(k)) {
var key = exports.convertToType(k, keyType);
var value = exports.convertToType(data[k], valueType);
result[key] = value;
}
}
return result;
} else {
// for unknown type, return the data directly
return data;
}
}
};
/**
* Constructs a new map or array model from REST data.
* @param data {Object|Array} The REST data.
* @param obj {Object|Array} The target object or array.
*/
exports.constructFromObject = function(data, obj, itemType) {
if (Array.isArray(data)) {
for (var i = 0; i < data.length; i++) {
if (data.hasOwnProperty(i))
obj[i] = exports.convertToType(data[i], itemType);
}
} else {
for (var k in data) {
if (data.hasOwnProperty(k))
obj[k] = exports.convertToType(data[k], itemType);
}
}
};
exports.prototype.resourcePath = function (path, pathParams) {
if (!path.match(/^\//)) {
path = '/' + path;
}
var url = path;
var _this = this;
url = url.replace(/{([\w-]+)}/g, function (fullMatch, key) {
var value;
if (pathParams.hasOwnProperty(key)) {
value = _this.paramToString(pathParams[key]);
} else {
value = fullMatch;
}
return encodeURIComponent(value);
});
return url;
};
exports.getFormString = function (params) {
var key;
var response = {};
var paramString = '';
var logArray = [];
for (key in params) {
if (params.hasOwnProperty(key)) {
var value = params[key];
if (Array.isArray(value)) {
for (var k in value) {
if (value.hasOwnProperty(k)) {
var v = value[k];
if (!exports.isNullOrUndefined(v)) {
paramString = paramString + (key.trim() + '=' + v) + '&';
logArray.push({
'key': key,
'value': v
});
}
}
}
} else {
if (!exports.isNullOrUndefined(value)) {
paramString = paramString + (key + '=' + value) + '&';
logArray.push({
'key': key,
'value': value
});
}
}
}
}
response['paramString'] = paramString.substring(0, paramString.length - 1);
response['logArray'] = logArray;
return response;
};
exports.isNullOrUndefined = function (data) {
return (typeof(data) === 'undefined' || data === null);
};
exports.isEmptyString = function (string) {
return (string === undefined || string === null || string === '');
};
exports.getQueryString = function (inparams) {
var key;
var trimmedValue;
var paramString = '';
var filteredKeys = [];
var params = {};
for (key in inparams) {
if (inparams.hasOwnProperty(key) && !exports.isEmptyString(key) && !exports.isEmptyString(key.trim()) && inparams[key] !== undefined && inparams[key] !== null) {
params[key.trim()] = inparams[key];
filteredKeys.push(key.trim());
}
}
//sorting Keys
filteredKeys.sort();
for (var i = 0; i < filteredKeys.length; i++) {
key = filteredKeys[i];
var value = params[key];
if (Array.isArray(value)) {
value = value.map(Function.prototype.call, String.prototype.trim);
//sorting values
value.sort();
for (var k in value) {
if (value.hasOwnProperty(k)) {
var v = value[k];
if (!exports.isEmptyString(v)) {
trimmedValue = v.trim();
if (!exports.isEmptyString(trimmedValue)) {
paramString = paramString + (key.trim() + '=' + trimmedValue) + '&';
}
}
}
}
} else {
if (!exports.isEmptyString(value)) {
trimmedValue = value.trim();
if (!exports.isEmptyString(trimmedValue)) {
paramString = paramString + (key.trim() + '=' + trimmedValue) + '&';
}
}
}
}
return paramString.substring(0, paramString.length - 1);
};
exports.getHeaderString = function (inparams) {
var key;
var trimmedValue;
var paramString = '';
var filteredKeys = [];
var params = {};
for (key in inparams) {
if (inparams.hasOwnProperty(key) && !exports.isEmptyString(key) && !exports.isEmptyString(key.trim()) && exports.isValidHeaderParam(key.trim()) && inparams[key] !== undefined && inparams[key] !== null) {
params[key.trim()] = inparams[key];
filteredKeys.push(key.trim());
}
}
//sorting Keys
filteredKeys.sort();
for (var i = 0; i < filteredKeys.length; i++) {
key = filteredKeys[i];
var value = params[key];
if (!exports.isEmptyString(value)) {
trimmedValue = value.trim();
if (!exports.isEmptyString(trimmedValue)) {
paramString = paramString + (key.trim() + '=' + trimmedValue) + '&';
}
}
}
return paramString.substring(0, paramString.length - 1);
};
exports.computeOPAY1HMACSHA256Hash = function (resourcePath, method, queryParams, headerParams, formParams, body, signatureKey) {
if(signatureKey == null || signatureKey == '' ){
throw new Error('Invalid value for secret');
}
var textToHash = '';
textToHash = textToHash + method.toUpperCase() + ':';
textToHash = textToHash + '/payments/v1' + decodeURIComponent(resourcePath) + ':';
textToHash = textToHash + exports.getQueryString(queryParams) + ':';
textToHash = textToHash + exports.getHeaderString(headerParams) + ':';
if (exports.isNonEmptyObject(body)) {
textToHash = textToHash + JSON.stringify(body);
logger.debug('HTTP Request Body :', JSON.stringify(body, CommonUtil.maskSensitiveInfo));
} else {
var formData = exports.getFormString(formParams);
textToHash = textToHash + formData['paramString'];
logger.debug('Form Params :', CommonUtil.maskSensitiveFormParams(formData['logArray']));
}
var hash = exports.generateHmacHash(signatureKey, textToHash).trim();
var response = {};
response['hash'] = hash;
return response;
};
exports.computeOPAY2HMACSHA256Hash = function (clientKey, apiKey, secretKey, timestamp) {
if(secretKey == null || secretKey == '' ){
throw new Error('Invalid value for secret');
}
var textToHash = '';
textToHash = textToHash + clientKey + ':';
textToHash = textToHash + apiKey + ':';
textToHash = textToHash + secretKey + ':';
textToHash = textToHash + timestamp;
var hash = exports.generateHmacHash(secretKey, textToHash).trim();
var response = {};
response['hash'] = hash;
return response;
};
exports.isNonEmptyObject = function (obj) {
return (obj !== null && typeof obj === 'object' && Object.keys(obj).length > 0);
};
exports.generateHmacHash = function (key, text) {
return crypto.createHmac('sha256', new Buffer(key, 'utf-8')).update(new Buffer(text, 'utf-8')).digest('base64');
};
exports.isValidHeaderParam = function (headerParam) {
return ['product' , 'trace_id' , 'idempotent_request_key' , 'channel' , 'requestor' , 'client_key' , 'X-OPAY-Headers' , 'requestor_type' , 'timestamp'].indexOf(headerParam) !== -1;
};
/**
* The default API client implementation.
* @type {module:ApiClient}
*/
exports.instance = new exports();
return exports;
}));