bc-payments-sdk
Version:
BetterCommerce's Payments NodeJS SDK is a complete solution for storefront clients that integrate payments. `bc-payments-sdk` is a single point interface for storefront clients for interacting with payment gateways.
241 lines (240 loc) • 12.3 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const agentkeepalive_1 = __importDefault(require("agentkeepalive"));
const api_1 = __importDefault(require("../../../../base/api"));
const BCEnvironment_1 = require("../../../../base/config/BCEnvironment");
const APIException_1 = require("../../../../base/entity/exception/APIException");
const AuthenticationException_1 = require("../../../../base/entity/exception/AuthenticationException");
const InvalidRequestException_1 = require("../../../../base/entity/exception/InvalidRequestException");
const RequestMethod_1 = require("../../../../constants/enums/RequestMethod");
const guid_1 = require("../../../../types/guid");
// Create a reusable connection instance that can be passed around to different controllers
const keepAliveAgent = new agentkeepalive_1.default({
maxSockets: 128,
maxFreeSockets: 128, // or 128 / os.cpus().length if running node across multiple CPUs
timeout: 60000, // active socket keepalive for 60 seconds
freeSocketTimeout: 30000, // free socket keepalive for 30 seconds
});
// HTTPS agent
const httpsKeepAliveAgent = new agentkeepalive_1.default.HttpsAgent({
maxSockets: 128, // or 128 / os.cpus().length if running node across multiple CPUs
maxFreeSockets: 128, // or 128 / os.cpus().length if running node across multiple CPUs
timeout: 60000, // active socket keepalive for 30 seconds
freeSocketTimeout: 30000, // free socket keepalive for 30 seconds
});
const SingletonFactory = (function () {
let accessToken = '';
const axiosInstance = api_1.default.create({
// Create an agent for both HTTP and HTTPS
httpAgent: keepAliveAgent,
httpsAgent: httpsKeepAliveAgent,
baseURL: BCEnvironment_1.BCEnvironment.baseApiUrl,
withCredentials: true,
});
const getToken = () => BCEnvironment_1.BCEnvironment.getApiToken() ? BCEnvironment_1.BCEnvironment.getApiToken() : accessToken;
const setToken = (token) => (accessToken = token);
const clearToken = () => (accessToken = '');
axiosInstance.interceptors.request.use((config) => {
const token = getToken();
//this is to be changed when we implement currency / language switcher
if (token) {
config.headers['Authorization'] = 'Bearer ' + token;
}
return config;
}, (err) => Promise.reject(err));
/**
* Creates an interceptor that will catch 401 errors and try to refresh the
* token by calling the token endpoint with the client credentials.
*
* If the token refresh is successful, it will retry the original request with
* the new access token. If the token refresh fails, it will reject the promise
* with the error.
*
* The interceptor will be ejected after a 401 error is caught to prevent
* infinite loops in case the token refresh also returns a 401 error.
*
* This interceptor will be recreated after the promise returned by this
* function is resolved or rejected.
*/
function createAxiosResponseInterceptor() {
const interceptor = axiosInstance.interceptors.response.use((response) => response, (error) => {
var _a;
// Reject promise if usual error
if (((_a = error === null || error === void 0 ? void 0 : error.response) === null || _a === void 0 ? void 0 : _a.status) !== 401) {
return Promise.reject(error);
}
/*
* When response code is 401, try to refresh the token.
* Eject the interceptor so it doesn't loop in case
* token refresh causes the 401 response
*/
axiosInstance.interceptors.response.eject(interceptor);
// return getAuthToken().finally(createAxiosResponseInterceptor)
if (BCEnvironment_1.BCEnvironment.getApiToken()) {
const url = new URL('admin/auth/refresh', BCEnvironment_1.BCEnvironment.getBaseAuthUrl());
const refreshToken = BCEnvironment_1.BCEnvironment.getRefreshToken();
return axiosInstance({
url: url.href,
method: RequestMethod_1.RequestMethod.POST,
data: { refreshToken },
}).then((res) => {
BCEnvironment_1.BCEnvironment.setApiToken(res.data.accessToken);
BCEnvironment_1.BCEnvironment.setRefreshToken(res.data.refreshToken);
setToken(res.data.accessToken);
error.response.config.headers['Authorization'] = `Bearer ${res.data.accessToken}`;
return axiosInstance(error.response.config);
}).catch((error) => {
//@TODO redirect here to Login page
return Promise.reject(error);
}).finally(createAxiosResponseInterceptor);
}
else {
const url = new URL('oAuth/token', BCEnvironment_1.BCEnvironment.getBaseAuthUrl());
return axiosInstance({
url: url.href,
method: RequestMethod_1.RequestMethod.POST,
data: `client_id=${BCEnvironment_1.BCEnvironment.getClientId()}&client_secret=${BCEnvironment_1.BCEnvironment.getSharedSecret()}&grant_type=client_credentials`,
}).then((res) => {
setToken(res.data.access_token);
error.response.config.headers['Authorization'] = `Bearer ${res.data.access_token}`;
return axiosInstance(error.response.config);
}).catch((error) => {
//@TODO redirect here to Login page
return Promise.reject(error);
}).finally(createAxiosResponseInterceptor);
}
});
}
createAxiosResponseInterceptor();
return { axiosInstance };
})();
const axiosInstance = SingletonFactory.axiosInstance;
Object.freeze(axiosInstance);
/**
* Makes an HTTP request using the specified parameters and returns the response data or an error object.
*
* @param {string} url - The endpoint URL for the request.
* @param {string} method - The HTTP method to use (e.g., 'post', 'get').
* @param {object} data - The data to send in the request body (for POST/PUT requests).
* @param {object} params - The URL parameters to include in the request.
* @param {object} headers - The headers to include in the request.
* @param {object} cookies - Cookies to use for setting additional headers like Currency, Language, etc.
* @param {string} baseUrl - The base URL to use if not specified in the environment config.
*
* @returns {Promise<any>} The response data if the request is successful, or an error object if it fails.
*
* @throws {InvalidRequestException} If the response status is 400 or 404.
* @throws {AuthenticationException} If the response status is 401.
* @throws {APIException} For any other non-2xx response status.
*/
const fetcher = async ({ url = '', method = 'post', data = {}, params = {}, headers = {}, cookies = {}, baseUrl = "", logRequest = false, }) => {
const computedUrl = new URL(url, baseUrl || BCEnvironment_1.BCEnvironment.getBaseApiUrl());
const newConfig = {
Currency: cookies.Currency || BCEnvironment_1.BCEnvironment.getDefaultCurrency(),
Language: cookies.Language || BCEnvironment_1.BCEnvironment.getDefaultLanguage(),
Country: cookies.Country || BCEnvironment_1.BCEnvironment.getDefaultCountry(),
DeviceId: (cookies === null || cookies === void 0 ? void 0 : cookies.deviceId) || "",
SessionId: (cookies === null || cookies === void 0 ? void 0 : cookies.sessionId) || "",
CompanyId: (cookies === null || cookies === void 0 ? void 0 : cookies.CompanyId) && (cookies === null || cookies === void 0 ? void 0 : cookies.CompanyId) != guid_1.Guid.empty
? cookies === null || cookies === void 0 ? void 0 : cookies.CompanyId
: guid_1.Guid.empty,
ClientIP: (cookies === null || cookies === void 0 ? void 0 : cookies.ClientIP) || "",
};
// Pass UserToken if received in from the consuming application (server-side)
let userToken = null;
if (cookies === null || cookies === void 0 ? void 0 : cookies["ut"]) {
userToken = cookies === null || cookies === void 0 ? void 0 : cookies["ut"];
}
const config = { method: method, url: computedUrl.href, headers: Object.assign(Object.assign(Object.assign({}, headers), newConfig), { UserToken: userToken }), };
if (Object.keys(params).length) {
config.params = params;
}
if (data && Object.keys(data).length) {
config.data = data;
}
//console.log(config)
try {
const response = await axiosInstance(config);
if (logRequest) {
logRequestAndResponse(config, response);
}
let responseCode = response.status;
let responseBody = response.data;
if (responseCode >= 200 && responseCode < 300) {
return responseBody;
}
else {
let status = undefined;
let errorCode = undefined;
let errorMessage = undefined;
if (responseBody != undefined) {
if ("status" in responseBody != undefined) {
status = responseBody.status;
}
if ("error_code" in responseBody != undefined) {
errorCode = responseBody.error_code;
}
if ("error_message" in responseBody != undefined) {
errorMessage = responseBody.error_message;
}
}
switch (responseCode) {
case 400:
case 404:
throw new InvalidRequestException_1.InvalidRequestException(responseCode, status, errorCode, errorMessage);
case 401:
throw new AuthenticationException_1.AuthenticationException(responseCode, status, errorCode, errorMessage);
default:
throw new APIException_1.APIException(responseCode, "internal_error", "internal_error", "Something went wrong.");
}
}
}
catch (error) {
if (logRequest) {
logRequestAndResponse(config, error);
}
let errorData = {};
if (error.response) {
//errorData = error.response;
errorData = {
//headers: error.response.headers,
status: error.response.status,
data: error.response.data,
};
// The request was made and the server responded with a status code
// that falls out of the range of 2xx
console.log(error.response.data);
console.log(error.response.status);
console.log(error.response.headers);
}
else if (error.request) {
errorData = error.request;
// The request was made but no response was received
// `error.request` is an instance of XMLHttpRequest in the browser and an instance of
// http.ClientRequest in node.js
console.log(error.request);
}
else {
errorData = error.message;
// Something happened in setting up the request that triggered an Error
console.log('Error: ' + error.message);
}
return { hasError: true, error: errorData };
//console.log(error, 'error inside fetcher');
//throw new Error(error.response.data.message);
}
};
const logRequestAndResponse = (config, response) => {
var _a;
const requestLog = {
timestamp: new Date().toISOString(),
request: { method: (_a = config.method) === null || _a === void 0 ? void 0 : _a.toUpperCase(), url: config.url, headers: config.headers, params: config.params, data: config.data },
response: response ? { status: response.status, headers: response.headers, data: response.data } : undefined
};
const logMessage = JSON.stringify(requestLog, null, 2);
console.log(logMessage);
};
exports.default = fetcher;