forked from shaynair/moneris-js
-
Notifications
You must be signed in to change notification settings - Fork 5
/
index.js
168 lines (160 loc) · 5.79 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
'use strict'
// Imports
const xml = require('xml2js')
const request = require('request-promise')
const Promise = require('bluebird');
var $q = require('q');//yes using two promise libraries, but will replace bluebird with q soon.
const numeral = require('numeral');
Promise.promisifyAll(xml)
// Globals (from Moneris PHP API)
const globals = require('./constants/globals.json')
// Intermediaries
const xmlBuilder = new xml.Builder()
xmlBuilder.options.rootName = 'request';
//--
var cleanse = function(str,spaces){
if(spaces){
str = String(str).split(' ').join('');
}
return (str ? String(str).split('/').join('').split('=').join('').split('*').join('').split('!').join('').split('-').join('').trim() : '');
};
module.exports = function(configuration){
function send (req, extended) {
if(extended === undefined) {
extended = null;
}
if (!configuration || !req || !req.type || !configuration.store_id || !configuration.api_token) {
return Promise.reject(new TypeError('Requires country_code, store_id, api_token'))
}
if (configuration.country_code) {
configuration.country_code = configuration.country_code.toUpperCase()
if (configuration.country_code !== 'CA' && !globals.hasOwnProperty(configuration.country_code + '_HOST')) {
return Promise.reject(new TypeError('Invalid country code'))
}
}
let data = {
store_id: configuration.store_id,
api_token: configuration.api_token
}
if (req.type === 'attribute_query' || req.type === 'session_query') {
data.risk = {}
data.risk[req.type] = req
} else {
data[req.type] = req
}
if (extended) {
for (let key in extended) {
if (extended.hasOwnProperty(key) && !data.hasOwnProperty(key)) {
data[key] = extended[key]
}
}
}
let prefix = ''
if (!!configuration.country_code && configuration.country_code !== 'CA') {
prefix += configuration.country_code + '_'
}
let hostPrefix = prefix
let filePrefix = prefix
if (configuration.test) {
hostPrefix += 'TEST_'
}
if (req.type === 'acs' || req.type === 'txn') {
filePrefix += 'MPI_'
}
const options = {
uri: globals.PROTOCOL + '://' + globals[hostPrefix + 'HOST'] + ':' + globals.PORT + globals[filePrefix + 'FILE'],
method: 'POST',
body: xmlBuilder.buildObject(data),
headers: {
'User-Agent': globals.API_VERSION
},
timeout: globals.CLIENT_TIMEOUT * 1000
}
return request(options)
.then(res => xml.parseStringAsync(res))
.then(res => Array.isArray(res.response.receipt) ? res.response.receipt[0] : res.response.receipt)
};
//Check out -> https://developer.moneris.com/More/Testing/Penny%20Value%20Simulator , for more "magic" amounts to force different responses.
var firstElement = function(arr,assertion){
return Array.isArray(arr) && arr.length>0 && arr[0] ? (assertion ? arr[0]===assertion: arr[0]) : null;
}
var normalizeExpiry = function(format,expiry){
if(typeof format==='string' && format.toLowerCase().split('/').join('')==='mmyy'){
return expiry.toString().split('').slice(2,4).join('')+expiry.toString().split('').slice(0,2).join('');
}
else {
return expiry;
}
}
var pay = function(args){
var pan = cleanse(args.card,true);
//Moneris takes expiry date format YY/MM, however most websites use the MM/YY format, you can specificy this in a verbose manner in the configuration by setting configuration.expiryFormat = 'MM/YY'; or the default 'YY/MM';
var expdate = normalizeExpiry(configuration.expiryFormat,cleanse(args.expiry,true));
var amount = args.amount;
//--Moneris is super picky about formating..
if(args.forceDecline && configuration.test){
amount = 0.05;
}
amount = (amount ? numeral(amount).format('0.00'): false)
//
var suffix = (new Date()).getTime()+'-'+Math.ceil(Math.random()*10000);
var order_id = args.order_id || cleanse(configuration.app_name,true)+'-Purchase-'+suffix;
var cust_id = args.cust_id || 'customer-'+suffix;
var dynamic_descriptor = args.description || args.dynamic_descriptor || 'purchase';
var purchase = {
type: 'purchase',
cust_id,
order_id,
amount,
pan,
expdate,
crypt_type: 7,
dynamic_descriptor,
status_check: false
};
return $q.fcall(function(){
if(!amount || !pan || !expdate){
throw {
code: null,
msg: 'INVALID_INPUTS'
}
}
return send(purchase)
.then(function(result){
var fe = firstElement; //shorthand
var code = fe(result.ResponseCode);
var status = {
msg: cleanse(fe(result.Message)),
code,
reference: fe(result.ReferenceNum),
iso: fe(result.ISO),
receipt: fe(result.ReceiptId),
raw: result,
isVisa: fe(result.CardType,"V"),
isMasterCard: fe(result.CardType,"M"),
isVisaDebit: fe(result.IsVisaDebit,"true"),
authCode: fe(result.AuthCode),
timeout: fe(result.TimedOut,"true"),
date: fe(result.TransDate),
time: fe(result.TransTime),
custId: cust_id,
orderId: order_id
};
var approved = !status.timeout && ((code)=="00" || code ? parseInt(code)<50 : false );
return $q.fcall(function(){
if(approved){
return status;
}
else {
throw {
code: status.code,
msg: (status.timeout ? 'TIMEOUT': status.msg) || 'DECLINED',
raw: result
}
}
})
})
})
};
return { send, pay }
}