Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 44 additions & 1 deletion lib/infrastructure/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,20 @@ function defNn(v) {
return v !== undefined && v !== null;
};

function isEmptyObject(o){
if (o === null){
return true;
}
if (typeof(o)==='object'&&o.length===0){
return true;
}
if (JSON.stringify(o)==="{}"){
return true;
}
return false;
}


var Helpers = {

isNumeric: isNumeric,
Expand All @@ -20,6 +34,8 @@ var Helpers = {

defNn: defNn,

isEmptyObject: isEmptyObject,

hydrateCardData:
function hydrateCardData(obj) {
var result = {};
Expand All @@ -39,6 +55,21 @@ var Helpers = {
return undefined;
},

hydrateAdditionalTxnFields:
function hydrateAdditionalTxnFields(obj){
var result = {};
if (typeof(obj)==='string'){
obj = {'description':obj};
}
if (obj) {
if (objProp(obj,'description')) { result.Description = obj.description; }
if (objProp(obj,'invoiceId')) { result.InvoiceNbr = obj.invoiceId; }
if (objProp(obj,'customerId')) { result.CustomerId = obj.customerId; }
return result;
}
return undefined;
},

hydrateCardHolderData:
function hydrateCardHolderData(cardHolder) {
var result = {};
Expand Down Expand Up @@ -94,7 +125,7 @@ var Helpers = {
return 'Refund';
case 'CreditReversal':
return 'Reverse';
case 'creditAuth':
case 'CreditAuth':
return 'Authorize';
case 'CreditAccountVerify':
return 'Verify';
Expand All @@ -110,6 +141,12 @@ var Helpers = {
return 'ManageTokens';
case 'SecurityError':
return 'SecurityError';
case 'ReportBatchDetail':
return 'BatchDetail';
case 'ReportBatchHistory':
return 'BatchHistory';
case 'ReportBatchSummary':
return 'BatchSummary';
default:
return null;
}
Expand Down Expand Up @@ -142,6 +179,12 @@ var Helpers = {
return 'BatchClose';
case 'SecurityError':
return "SecurityError";
case 'BatchDetail':
return 'ReportBatchDetail';
case 'BatchHistory':
return 'ReportBatchHistory';
case 'BatchSummary':
return 'ReportBatchSummary';
default:
return '';
}
Expand Down
43 changes: 26 additions & 17 deletions lib/services/secure-submit/hps-credit-service.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,23 +50,27 @@ function HpsCreditService(hpsConfig, soapUri) {
* @return {Object} exports for chaining
*/
var chargeWithCard =
function chargeWithCard(amount, currency, card, cardHolder, requestMultiUseToken, memo, callback) {
function chargeWithCard(amount, currency, card, cardHolder, requestMultiUseToken, additionalTxnFields, callback) {
var schema = porticoSchema.requestType('CreditSale'),
tx = {};

if (hlp.defNn(card)) {
tx.CardData = {};
tx.CardData.ManualEntry = hlp.hydrateCardData(card);
if (typeof(card)==='string'){
tx.CardData.TrackData = card;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For track data, we'll need to have the ability to set the method attribute of the TrackData data field as either swipe or proximity to allow for contactless as well, so it may be better to pass the track data as an object similar to our other SDKs. You can reference our .NET SDK: Data Class, Hydration.

Also, our gateway supports encrypted track data (a large portion of the readers are encrypted with Heartland's End-To-End Encryption [E3]), so it would be nice to have this capability as well. You can reference our .NET SDK: Data Class, Hydration.

} else {
tx.CardData.ManualEntry = hlp.hydrateCardData(card);
schema.properties.CardData.required = ['ManualEntry'];
schema.properties.CardData.properties.ManualEntry.required = ['CardNbr', 'ExpMonth', 'ExpYear', 'CVV2'];
}
if(hlp.defNn(requestMultiUseToken)) tx.CardData.TokenRequest = (requestMultiUseToken === true) ? 'Y': 'N';
}
if (hlp.defNn(amount)) tx.Amt = amount;
if (hlp.defNn(cardHolder)) tx.CardHolderData = hlp.hydrateCardHolderData(cardHolder);
if (hlp.defNn(memo)) tx.AdditionalTxnFields = { 'Description': memo };
if (hlp.defNn(amount)) tx.Amt = amount;
if (hlp.defNn(cardHolder)) tx.CardHolderData = hlp.hydrateCardHolderData(cardHolder);
if (hlp.defNn(additionalTxnFields)) tx.AdditionalTxnFields = hlp.hydrateAdditionalTxnFields(additionalTxnFields);
tx.AllowDup = 'Y';

schema.required = ['Amt'];
schema.properties.CardData.required = ['ManualEntry'];
schema.properties.CardData.properties.ManualEntry.required = ['CardNbr', 'ExpMonth', 'ExpYear', 'CVV2'];

if (tv4.validate(tx, schema)) {
gateway.submitTransaction({'CreditSale':{'Block1':tx}}, function (err, result) {
Expand Down Expand Up @@ -191,23 +195,27 @@ function HpsCreditService(hpsConfig, soapUri) {
* @return {Object} exports for chaining
*/
var authorizeWithCard =
function authorizeWithCard(amount, currency, card, cardHolder, requestMultiUseToken, memo, callback) {
function authorizeWithCard(amount, currency, card, cardHolder, requestMultiUseToken, additionalTxnFields, callback) {
var schema = porticoSchema.requestType('CreditAuth'),
tx = {};

if (hlp.defNn(card)) {
tx.CardData = {};
tx.CardData.ManualEntry = hlp.hydrateCardData(card);
if (typeof(card)==='string'){
tx.CardData.TrackData = card;
} else {
tx.CardData.ManualEntry = hlp.hydrateCardData(card);
schema.properties.CardData.required = ['ManualEntry'];
schema.properties.CardData.properties.ManualEntry.required = ['CardNbr', 'ExpMonth', 'ExpYear', 'CVV2'];
}
if(hlp.defNn(requestMultiUseToken)) tx.CardData.TokenRequest = (requestMultiUseToken === true) ? 'Y': 'N';
}
if (hlp.defNn(amount)) tx.Amt = amount;
if (hlp.defNn(cardHolder)) tx.CardHolderData = hlp.hydrateCardHolderData(cardHolder);
if (hlp.defNn(memo)) tx.AdditionalTxnFields = { 'Description': memo };
if (hlp.defNn(amount)) tx.Amt = amount;
if (hlp.defNn(cardHolder)) tx.CardHolderData = hlp.hydrateCardHolderData(cardHolder);
if (hlp.defNn(additionalTxnFields)) tx.AdditionalTxnFields = hlp.hydrateAdditionalTxnFields(additionalTxnFields);
tx.AllowDup = 'Y';

schema.required = ['Amt'];
schema.properties.CardData.required = ['ManualEntry'];
schema.properties.CardData.properties.ManualEntry.required = ['CardNbr', 'ExpMonth', 'ExpYear'];

if (tv4.validate(tx, schema)) {
gateway.submitTransaction({'CreditAuth':{'Block1':tx}}, function (err, result) {
Expand Down Expand Up @@ -859,10 +867,11 @@ function HpsCreditService(hpsConfig, soapUri) {
transactionId: t.GatewayTxnId,
originalTransactionId: t.OriginalGatewayTxnId,
maskedCardNumber: t.MaskedCardNbr,
responseCode: t.IssuerRspCode,
responseText: t.IssuerRspText,
responseCode: hlp.isEmptyObject(t.IssuerRspCode)?(hlp.isEmptyObject(t.GatewayRspCode)?'':t.GatewayRspCode):t.IssuerRspCode,
responseText: hlp.isEmptyObject(t.IssuerRspText)?(hlp.isEmptyObject(t.GatewayRspMsg)?'':t.GatewayRspMsg):t.IssuerRspText,
amount: t.Amt,
settlementAmount: t.SettlementAmt,
status: t.Status,
transactionUtcDate: t.TxnUtcDT,
transactionType: filterBy || hlp.serviceNameToTransactionType(t.ServiceName),
exceptions: (t.GatewayRspCode !== '0' || t.IssuerRspCode !== '00') ? {
Expand Down Expand Up @@ -948,7 +957,7 @@ function HpsCreditService(hpsConfig, soapUri) {
result = {
transactionId: t.GatewayTxnId,
originalTransactionId: t.OriginalGatewayTxnId,
settlementAmount: t.SettlementAmt,
settlementAmount: t.Data.SettlementAmt,
authorizedAmount: t.Data.AuthAmt,
authorizationCode: t.Data.AuthCode,
avsResultCode: t.Data.AVSRsltCode,
Expand Down
83 changes: 83 additions & 0 deletions test/portico-services/helpers-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
'use strict';

var assert = require('assert'),
schema = require('../../lib/infrastructure/validation/portico-schema'),
helpers = require('../../lib/infrastructure/helpers');

exports.isEmptyObject = {
onNull: function () {
var rc = helpers.isEmptyObject(null);
assert.equal(rc,true,'null should return true');
},

onEmpty: function(){
var rc = helpers.isEmptyObject({});
assert.equal(rc,true,'{} should return true');
},

onNotObject: function() {
var rc = helpers.isEmptyObject('');
assert.equal(rc,false,'\'\' should return false');
},

onString: function(){
var rc = helpers.isEmptyObject(' ');
assert.equal(rc,false,'\' \' should return false');
},

onNotEmpty: function(){
var rc = helpers.isEmptyObject({x:0});
assert.equal(rc,false,'{x:0} should return false');
}
};

exports.validateRequestTypes = {
validObjects: function(){
requestTypes.map(rt=>{
var rc = schema.requestType(rt);
assert.equal(helpers.isEmptyObject(rc),false, rt + ' should be a valid type');
assert.equal(isRequestObject(rc),true, rt + ' should be a valid request object');
});
},
validTransactions: function(){
requestTypes.map(rt=>{
var rc = helpers.serviceNameToTransactionType(rt);
assert.notEqual(rc,null, rt + ' should have a Transaction Type');
});
},

validServiceNames: function(){
requestTypes.map(rt=>{
var rc = helpers.serviceNameToTransactionType(rt);
var rc2 = helpers.transactionTypeToServiceName(rc);
assert.notEqual(rc2,null,rt + ' should be a valid Service Name');
assert.equal(rt,rc2, rt + ' should be the same as ' + rc2);
});
}
};

function isRequestObject(o){
var rc = false;
if (!helpers.isEmptyObject(o)){
if (o.type==='object'&&!helpers.isEmptyObject(o.properties)){
rc = true;
}
}
return rc;
}

var requestTypes = [
'CreditSale',
'CreditAuth',
'CreditAccountVerify',
'CreditAddToBatch',
'CreditReturn',
'CreditReversal',
'ReportActivity',
'ReportBatchDetail',
'ReportBatchHistory',
'ReportBatchSummary',
//'ReportOpenAuths',
'ReportTxnDetail',
'ManageTokens'
]