UNPKG

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
"use strict"; 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;