Last active
October 2, 2020 19:25
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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