Skip to content

Instantly share code, notes, and snippets.

@xerosai
Last active October 2, 2020 19:25
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save xerosai/797dcb550d3be17faa88ea564331daad to your computer and use it in GitHub Desktop.
Save xerosai/797dcb550d3be17faa88ea564331daad to your computer and use it in GitHub Desktop.
Helper class in JavaScript that works with the First Atlantic Commerce payment gateway service. Still a work in progress but, I will turn this into an NPM module eventually
/**
* Filename: FACPaymentUtils.js
* Created by: xerosai @ 22/07/2020 11:10 AM
* @author: Simon Neufville <simon@xrscodeworks.com>
*/
const axios = require('axios');
const CryptoJS = require('crypto-js');
const xmlJS = require('xml-js');
const xml2js = require('xml2js');
/**
* Here the FACPaymentDetails would be an object like
{
"FAC_MERCHANT_ID": <MerchantId>,
"PROCESSING_PASSWORD": <ProcessingPassword>,
}
*/
const FACPaymentDetails = process.env.NODE_ENV === 'production' ? require('../config/FACPaymentDetails.live.json') : require('../config/FACPaymentDetails.dev.json');
/**
* @class FACPaymentUtils
* Helper class containing methods to facilitate payments via First Atlantic Commerce
*/
class FACPaymentUtils {
/**
* @property CURRENCY_OPTIONS
* @type {Readonly<*>}
* Currency options for use with FAC
*/
static CURRENCY_OPTIONS = Object.freeze({
jamaicanDollar: {
code: 'JMD',
exponent: 2,
numericCode: 388,
},
unitedStateDollar: {
code: 'USD',
exponent: 2,
numericCode: 840
}
});
/**
* @property PAYMENT_URL_BASE
* @type {string}
* Base url for the first atlantic commerce service
*/
static PAYMENT_URL_BASE = process.env.NODE_ENV === 'production' ? 'https://marlin.firstatlanticcommerce.com/PGServiceXML' : 'https://ecm.firstatlanticcommerce.com/PGServiceXML';
/**
* @static
* @property REQUEST_HEADERS
* @type {Readonly<object>}
* Request header constants to be used when making requests to FAC
*/
static REQUEST_HEADERS = Object.freeze({
'Content-Type': 'application/x-www-form-urlencoded',
});
/**
* @property SERVICE_ACTIONS
* @type {Readonly<{string: string}>}
* Service action constants
*/
static SERVICE_ACTIONS = Object.freeze({
AUTHORIZE: `${FACPaymentUtils.PAYMENT_URL_BASE}/Authorize`,
AUTHORIZE_3DS: `${FACPaymentUtils.PAYMENT_URL_BASE}/Authorize3DS`,
TRANSACTION_STATUS: `${FACPaymentUtils.PAYMENT_URL_BASE}/TransactionStatus`
});
/**
* @static
* @method buildResponse
* @returns {{data: *, success: boolean, error: string}}
* Helper method that builds a standard response object
*/
static buildResponse() {
return {data: undefined, error: undefined, success: false};
}
/**
* @static
* @method convertOrderTotal
* @param orderTotal
* @returns {string|undefined}
* Helper method that converts the order total to the format needed by FAC
*/
static convertOrderTotal({orderTotal}) {
try {
let _totalAsString = (parseFloat(orderTotal).toFixed(2) * 100).toString();
return _totalAsString.padStart(12, '0');
} catch (e) {
return undefined;
}
}
/**
* @static
* @method generateTransactionSignature
* @param orderId
* @param orderTotal
* @param currencyNCode
* @param isKeyCardTransaction
* @returns {Promise<null|string>}
* Helper method that generates a transaction signature
*/
static async generateTransactionSignature({orderId, orderTotal, currencyNCode, isKeyCardTransaction = false}) {
try {
const signatureString = `${isKeyCardTransaction ? FACPaymentDetails.PROCESSING_PASSWORD_NCB : FACPaymentDetails.PROCESSING_PASSWORD}${isKeyCardTransaction ? FACPaymentDetails.FAC_MERCHANT_ID_NCB : FACPaymentDetails.FAC_MERCHANT_ID}464748${orderId}${FACPaymentUtils.convertOrderTotal({orderTotal})}${currencyNCode}`;
console.log('FACPaymentUtils.generateTransactionSignature signatureString: ', signatureString);
return await CryptoJS.SHA1(signatureString).toString();
} catch (e) {
return null;
}
}
/**
* @static
* @method buildPayload
* @param cardInfo
* @param itemData
* @param orderDetail
* @param responseUrl
* @param customData
* @param isKeyCardTransaction
* @returns {string}
* Helper method that converts a JS object representing an order to XML for transport
*/
static async buildPayload({cardInfo, itemData, orderDetail, responseUrl, customData, isKeyCardTransaction = false}) {
const transactionSignature = await FACPaymentUtils.generateTransactionSignature({orderId: orderDetail['_id'], orderTotal: orderDetail['orderTotal'], currencyNCode: FACPaymentUtils.CURRENCY_OPTIONS.jamaicanDollar.numericCode, isKeyCardTransaction});
const buffer = Buffer.from(transactionSignature, 'hex');
const encodedSignature = buffer.toString('base64');
const AttribsBlock = {
_attributes: {xmlns: 'http://schemas.firstatlanticcommerce.com/gateway/data', 'xmlns:i': 'http://www.w3.org/2001/XMLSchema-instance'}
}
const BillingDetailsBlock = {
BillingDetails: {
BillToAddress: {},
BillToAddress2: {},
BillToCity: {},
BillToCountry: {},
BillToFirstName: {},
BillToLastName: {},
BillToState: {},
BillToTelephone: {},
BillToZipPostCode: {},
BillToCounty: {},
BillToMobile: {},
},
}
const CardDetailsBlock = {
CardDetails: {
CardCVV2: {"_text": cardInfo['cardCVC']},
CardExpiryDate: {"_text": String(cardInfo['cardExp']).split('/').join('')},
CardNumber: {"_text": String(cardInfo['cardNumber']).split(' ').join('')},
Installments: {"_text": 0}
}
}
const TransactionDetailsBlock = {
TransactionDetails: {
AcquirerId: {"_text": 464748},
Amount: {"_text": FACPaymentUtils.convertOrderTotal({orderTotal: orderDetail['orderTotal']})},
Currency: {"_text": FACPaymentUtils.CURRENCY_OPTIONS.jamaicanDollar.numericCode},
CurrencyExponent: {"_text": FACPaymentUtils.CURRENCY_OPTIONS.jamaicanDollar.exponent},
CustomData: {"_text": customData},
IPAddress: {"_text": ''},
MerchantId: {"_text": isKeyCardTransaction ? FACPaymentDetails.FAC_MERCHANT_ID_NCB : FACPaymentDetails.FAC_MERCHANT_ID},
OrderNumber: {"_text": String(orderDetail['_id'])},
Signature: {"_text": encodedSignature},
SignatureMethod: {"_text": 'SHA1'},
TransactionCode: {"_text": 8}
}
}
const FraudDetailsBlock = {
FraudDetails: {
SessionId: {}
}
}
const payloadObject = isKeyCardTransaction ? {
AuthorizeRequest: {
...AttribsBlock,
...BillingDetailsBlock,
...CardDetailsBlock,
...TransactionDetailsBlock,
...FraudDetailsBlock
}
} : {
Authorize3DSRequest: {
...AttribsBlock,
...BillingDetailsBlock,
...CardDetailsBlock,
...TransactionDetailsBlock,
MerchantResponseURL: responseUrl ? responseUrl : 'https://ecm.firstatlanticcommerce.com/TestPages/MerchantCheckout/Parser/HttpRequestParser',
...FraudDetailsBlock
}
}
return xmlJS.js2xml(payloadObject, {compact: true, spaces: 4});
}
/**
* @static
* @method performAuthorizeTransaction
* @param cardInfo
* @param orderData
* @param responseUrl
* @param customData
* @param isKeyCardTransaction
* @returns {Promise<{data: *, success: boolean, error: string}>}
* Performs an authorize transaction. For mastercard and visa, uses Authorize3DS and for keyCard uses Authorize
*/
static async performAuthorizeTransaction({cardInfo, orderData, responseUrl, customData = '', isKeyCardTransaction = false}) {
const result = FACPaymentUtils.buildResponse();
try {
const payload = await FACPaymentUtils.buildPayload({cardInfo, orderDetail: orderData, responseUrl, customData, isKeyCardTransaction});
console.log('FACPaymentUtils.performAuthorizeTransaction payload: ', payload);
const headers = {...FACPaymentUtils.REQUEST_HEADERS};
const serviceActionURL = isKeyCardTransaction ? FACPaymentUtils.SERVICE_ACTIONS.AUTHORIZE : FACPaymentUtils.SERVICE_ACTIONS.AUTHORIZE_3DS;
const {data: responseData} = await axios.post(serviceActionURL, payload, {headers});
// parse XML string and extract form data and return it in the result
const parser = new xml2js.Parser();
if (isKeyCardTransaction) {
console.log('FACPaymentUtils.performAuthorizeTransaction response: ', responseData);
const {AuthorizeResponse} = await parser.parseStringPromise(responseData);
console.log('FACPaymentUtils.performAuthorizeTransaction AuthorizeResponse: ', AuthorizeResponse);
const {CreditCardTransactionResults, FraudControlResults, Signature} = AuthorizeResponse;
if (!Array.isArray(CreditCardTransactionResults) && !CreditCardTransactionResults.length) {
result.error = 'Transaction failed';
return result;
}
const {ReasonCode, ReasonCodeDescription, ReferenceNumber, ResponseCode} = CreditCardTransactionResults[0];
if (ReasonCode['0'] !== '1') {
result.error = 'Failed to process transaction';
return result;
}
result.data = {
reasonCode: ReasonCode['0'],
responseCode: ResponseCode['0'],
referenceNumber: ReferenceNumber['0'],
reasonCodeDescription: ReasonCodeDescription['0'],
facTransactionSignature: Signature['0']
}
result.success = true;
} else {
const {Authorize3DSResponse} = await parser.parseStringPromise(responseData);
const {HTMLFormData, ResponseCode, ResponseCodeDescription} = Authorize3DSResponse;
console.log('FACPaymentUtils.performAuthorizeTransaction parsedResponse: ', HTMLFormData['0']);
if (ResponseCodeDescription['0'] !== 'Success') {
result.error = 'Failed to process your transaction';
return result;
}
result.data = HTMLFormData['0'];
result.meta = {
message: 'Process transaction',
};
result.success = true;
}
return result;
} catch (e) {
console.log('FACPaymentUtils.performAuthorizeTransaction error: ', e);
result.error = e.toString();
return result;
}
}
static async getTransactionStatus({transaction}) {}
}
module.exports = FACPaymentUtils;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment