node-paytmpg 5.3.2 → 6.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/app/controllers/adapters/payu.js +251 -0
- package/app/controllers/np_user.controller.js +88 -79
- package/app/controllers/payment_controller.js +213 -84
- package/app/models/np_multidbplugin.js +111 -101
- package/app/views/init.hbs +85 -80
- package/app/views/layouts/index.hbs +46 -51
- package/app/views/result.hbs +43 -47
- package/example.js +33 -50
- package/index.js +75 -15
- package/lib/config/buildConfig.js +113 -0
- package/lib/config/defaults.js +37 -0
- package/lib/config/validator.js +103 -0
- package/lib/services/database.service.js +153 -0
- package/lib/utils/id-generator.js +30 -0
- package/lib/utils/sanitizer.js +25 -0
- package/package.json +44 -42
- package/public/css/style.css +373 -203
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
const crypto = require('crypto');
|
|
2
|
+
const axios = require('axios');
|
|
3
|
+
|
|
4
|
+
class PayU {
|
|
5
|
+
config
|
|
6
|
+
constructor(npconfig) {
|
|
7
|
+
const baseUrl = (npconfig.payu_url || '').replace(/\/$/, '');
|
|
8
|
+
const isSandbox = baseUrl.indexOf('test.payu.in') > -1;
|
|
9
|
+
const verifyUrl = npconfig.payu_verify_url || (isSandbox
|
|
10
|
+
? 'https://test.payu.in/merchant/postservice.php?form=2'
|
|
11
|
+
: 'https://info.payu.in/merchant/postservice.php?form=2');
|
|
12
|
+
this.config = {
|
|
13
|
+
key: npconfig.KEY,
|
|
14
|
+
salt: npconfig.SECRET,
|
|
15
|
+
baseUrl: baseUrl,
|
|
16
|
+
paymentUrl: npconfig.payu_payment_url || (baseUrl ? baseUrl + '/_payment' : ''),
|
|
17
|
+
verifyUrl: verifyUrl
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
normalizeAmount(amount) {
|
|
22
|
+
const value = parseFloat(amount || 0);
|
|
23
|
+
return value.toFixed(2);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
buildRequestHash(payload) {
|
|
27
|
+
const parts = [
|
|
28
|
+
payload.key,
|
|
29
|
+
payload.txnid,
|
|
30
|
+
this.normalizeAmount(payload.amount),
|
|
31
|
+
payload.productinfo,
|
|
32
|
+
payload.firstname,
|
|
33
|
+
payload.email,
|
|
34
|
+
payload.udf1 || '',
|
|
35
|
+
payload.udf2 || '',
|
|
36
|
+
payload.udf3 || '',
|
|
37
|
+
payload.udf4 || '',
|
|
38
|
+
payload.udf5 || '',
|
|
39
|
+
payload.udf6 || '',
|
|
40
|
+
payload.udf7 || '',
|
|
41
|
+
payload.udf8 || '',
|
|
42
|
+
payload.udf9 || '',
|
|
43
|
+
payload.udf10 || '',
|
|
44
|
+
this.config.salt
|
|
45
|
+
];
|
|
46
|
+
return crypto.createHash('sha512').update(parts.join('|')).digest('hex');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
buildResponseHash(data) {
|
|
50
|
+
const amount = this.normalizeAmount(data.amount);
|
|
51
|
+
const sequence = [
|
|
52
|
+
data.additionalCharges || null,
|
|
53
|
+
this.config.salt,
|
|
54
|
+
data.status || '',
|
|
55
|
+
'', '', '', '', '', '', '', '', '', '',
|
|
56
|
+
data.udf5 || '',
|
|
57
|
+
data.udf4 || '',
|
|
58
|
+
data.udf3 || '',
|
|
59
|
+
data.udf2 || '',
|
|
60
|
+
data.udf1 || '',
|
|
61
|
+
data.email || '',
|
|
62
|
+
data.firstname || '',
|
|
63
|
+
data.productinfo || '',
|
|
64
|
+
amount,
|
|
65
|
+
data.txnid || '',
|
|
66
|
+
data.key || ''
|
|
67
|
+
];
|
|
68
|
+
const filtered = sequence.filter((v) => v !== null);
|
|
69
|
+
return crypto.createHash('sha512').update(filtered.join('|')).digest('hex');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
generatePaymentRequest(params) {
|
|
73
|
+
const payload = {
|
|
74
|
+
key: this.config.key,
|
|
75
|
+
txnid: params['ORDER_ID'],
|
|
76
|
+
amount: this.normalizeAmount(params['TXN_AMOUNT']),
|
|
77
|
+
productinfo: params['PRODUCT_NAME'],
|
|
78
|
+
firstname: params['NAME'],
|
|
79
|
+
email: params['EMAIL'],
|
|
80
|
+
phone: params['MOBILE_NO'],
|
|
81
|
+
surl: params['CALLBACK_URL'],
|
|
82
|
+
furl: params['CALLBACK_URL'],
|
|
83
|
+
udf1: params['CUST_ID'] || '',
|
|
84
|
+
udf2: params['ORDER_ID'] || '',
|
|
85
|
+
service_provider: 'payu_paisa'
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
payload.hash = this.buildRequestHash(payload);
|
|
89
|
+
|
|
90
|
+
const formFields = Object.keys(payload).map((key) => {
|
|
91
|
+
return "<input type='hidden' name='" + key + "' value='" + payload[key] + "' />";
|
|
92
|
+
}).join('');
|
|
93
|
+
const html = `<form action='${this.config.paymentUrl}' method='post' id='payu_payment_form' style='display:none'>${formFields}</form><script>document.getElementById('payu_payment_form').submit();</script>`;
|
|
94
|
+
|
|
95
|
+
return { html: html, payload: payload };
|
|
96
|
+
}
|
|
97
|
+
decodeTransactionResponse(txnDataBase64FromPayu) {
|
|
98
|
+
const txnDataJson = Buffer.from(txnDataBase64FromPayu, 'base64').toString('utf-8');
|
|
99
|
+
return JSON.parse(txnDataJson);
|
|
100
|
+
}
|
|
101
|
+
async verifyResult(req) {
|
|
102
|
+
const originalBody = req.body || {};
|
|
103
|
+
const lookupId = originalBody.txnid || req.query.order_id;
|
|
104
|
+
const statusResp = await this.checkBqrTxnStatus(lookupId);
|
|
105
|
+
|
|
106
|
+
let resData = null;
|
|
107
|
+
if (!resData && statusResp && statusResp.transaction_details) {
|
|
108
|
+
const td = statusResp.transaction_details;
|
|
109
|
+
// try direct lookup by lookupId
|
|
110
|
+
if (td[lookupId]) {
|
|
111
|
+
resData = td[lookupId];
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
// find entry where txnid matches lookupId or key ends with lookupId
|
|
115
|
+
for (const k of Object.keys(td)) {
|
|
116
|
+
const t = td[k];
|
|
117
|
+
if (!t) continue;
|
|
118
|
+
if ((t.txnid && t.txnid.toString() === lookupId) || k.toString().endsWith(lookupId)) {
|
|
119
|
+
resData = t;
|
|
120
|
+
break;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Determine source for status and mapping (prefer decoded resData)
|
|
127
|
+
const source = resData || (statusResp || {}) || originalBody;
|
|
128
|
+
|
|
129
|
+
const msg = (statusResp?.msg || '').toString();
|
|
130
|
+
const statusText = (source.status || source.unmappedstatus || msg || '').toString().toLowerCase();
|
|
131
|
+
let status = 'TXN_FAILURE';
|
|
132
|
+
if (statusText.includes('success') || statusText.includes('completed') || statusText.includes('captured')) {
|
|
133
|
+
status = 'TXN_SUCCESS';
|
|
134
|
+
}
|
|
135
|
+
else if (statusText.includes('pending')) {
|
|
136
|
+
status = 'TXN_PENDING';
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const orderId = (source.udf2 || source.order_id || source.txnid || lookupId).toString();
|
|
140
|
+
const txnId = source.mihpayid || source.txnid || null;
|
|
141
|
+
|
|
142
|
+
return {
|
|
143
|
+
STATUS: status,
|
|
144
|
+
ORDERID: orderId,
|
|
145
|
+
TXNID: txnId,
|
|
146
|
+
data: resData || statusResp || originalBody,
|
|
147
|
+
cancelled: source.unmappedstatus?.toLowerCase()?.includes('cancelled') || false
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
async getPaymentStatus(txnId) {
|
|
152
|
+
const verifyPayload = new URLSearchParams();
|
|
153
|
+
verifyPayload.append('key', this.config.key || '');
|
|
154
|
+
verifyPayload.append('command', 'verify_payment');
|
|
155
|
+
verifyPayload.append('var1', txnId || '');
|
|
156
|
+
const hashString = [this.config.key, 'verify_payment', txnId, this.config.salt].join('|');
|
|
157
|
+
verifyPayload.append('hash', crypto.createHash('sha512').update(hashString).digest('hex'));
|
|
158
|
+
|
|
159
|
+
try {
|
|
160
|
+
const response = await axios.post(this.config.verifyUrl, verifyPayload.toString(), {
|
|
161
|
+
headers: {
|
|
162
|
+
'Content-Type': 'application/x-www-form-urlencoded'
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
return response.data;
|
|
166
|
+
}
|
|
167
|
+
catch (e) {
|
|
168
|
+
return { error: e.message };
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
async postCommand(command, transactionId) {
|
|
173
|
+
const payload = new URLSearchParams();
|
|
174
|
+
payload.append('key', this.config.key || '');
|
|
175
|
+
payload.append('command', command || '');
|
|
176
|
+
payload.append('var1', transactionId || '');
|
|
177
|
+
|
|
178
|
+
// build hash: key|command|var1...|salt
|
|
179
|
+
const hashParts = [this.config.key, command, transactionId, this.config.salt];
|
|
180
|
+
const hashString = hashParts.join('|');
|
|
181
|
+
payload.append('hash', crypto.createHash('sha512').update(hashString).digest('hex'));
|
|
182
|
+
|
|
183
|
+
try {
|
|
184
|
+
const response = await axios.post(this.config.verifyUrl, payload.toString(), {
|
|
185
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
|
|
186
|
+
});
|
|
187
|
+
return response.data;
|
|
188
|
+
}
|
|
189
|
+
catch (e) {
|
|
190
|
+
return { error: e.message };
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* https://docs.payu.in/reference/transaction-status-check-api-2#sample-request
|
|
196
|
+
* @param {*} transactionId mandatory
|
|
197
|
+
* @param {*} paymentmode optional
|
|
198
|
+
* @param {*} productype optional
|
|
199
|
+
* @returns
|
|
200
|
+
*/
|
|
201
|
+
async checkBqrTxnStatus(transactionId) {
|
|
202
|
+
return this.postCommand('verify_payment', transactionId);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
renderProcessingPage(params, paymentReq, res, loadingSVG) {
|
|
206
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
207
|
+
res.write(`<html><head><title>Merchant Checkout Page</title></head><body><center><h1>Processing ! Please do not refresh this page...</h1><br>${paymentReq.html}<br><br>${loadingSVG}</center></body></html>`);
|
|
208
|
+
res.end();
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
renderError(params, error, res) {
|
|
212
|
+
console.log('ERROR:::', error, '\n');
|
|
213
|
+
res.status(500);
|
|
214
|
+
let formFields = '';
|
|
215
|
+
const errorResp = {
|
|
216
|
+
TXNID: 'na',
|
|
217
|
+
STATUS: 'TXN_FAILURE',
|
|
218
|
+
CANCELLED: 'cancelled',
|
|
219
|
+
ORDERID: params['ORDER_ID']
|
|
220
|
+
};
|
|
221
|
+
Object.keys(errorResp).forEach((key) => {
|
|
222
|
+
formFields += "<input type='hidden' name='" + key + "' value='" + errorResp[key] + "' >";
|
|
223
|
+
});
|
|
224
|
+
formFields += "<input type='hidden' name='CHECKSUMHASH' value='" + (params['CHECKSUM'] || '') + "' >";
|
|
225
|
+
|
|
226
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
227
|
+
res.write(`<html>
|
|
228
|
+
|
|
229
|
+
<head>
|
|
230
|
+
<title>Merchant Checkout Error</title>
|
|
231
|
+
</head>
|
|
232
|
+
|
|
233
|
+
<body>
|
|
234
|
+
<center>
|
|
235
|
+
<h1>Something went wrong. Please wait you will be redirected automatically...</h1>
|
|
236
|
+
</center>
|
|
237
|
+
<form method="post" action="${params['CALLBACK_URL']}" name="f1">${formFields}</form>
|
|
238
|
+
<script type="text/javascript">document.f1.submit();</script>
|
|
239
|
+
</body>
|
|
240
|
+
|
|
241
|
+
</html>`);
|
|
242
|
+
res.end();
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
processWebhook(req, res) {
|
|
246
|
+
res.status(201);
|
|
247
|
+
res.send({ message: 'Webhook not implemented for PayU' });
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
module.exports = PayU;
|
|
@@ -1,80 +1,89 @@
|
|
|
1
|
-
var User
|
|
2
|
-
var Transaction = require('../models/np_transaction.model.js');
|
|
3
|
-
var IDLEN = 10
|
|
4
|
-
function makeid(length) {
|
|
5
|
-
var text = "";
|
|
6
|
-
var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
|
7
|
-
|
|
8
|
-
for (var i = 0; i < length; i++)
|
|
9
|
-
text += possible.charAt(Math.floor(Math.random() * possible.length));
|
|
10
|
-
|
|
11
|
-
return text;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
module.exports = function (app, callbacks) {
|
|
15
|
-
var module = {};
|
|
16
|
-
var config = (app.get('np_config'))
|
|
17
|
-
|
|
18
|
-
let usingMultiDbOrm = false;
|
|
19
|
-
if (config.db_url) {
|
|
20
|
-
User = require('../models/np_user.model.js');
|
|
21
|
-
usingMultiDbOrm = false;
|
|
22
|
-
} else if (app.multidborm) {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
User
|
|
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
|
-
|
|
1
|
+
var User;
|
|
2
|
+
var Transaction = require('../models/np_transaction.model.js');
|
|
3
|
+
var IDLEN = 10;
|
|
4
|
+
function makeid(length) {
|
|
5
|
+
var text = "";
|
|
6
|
+
var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
|
7
|
+
|
|
8
|
+
for (var i = 0; i < length; i++)
|
|
9
|
+
text += possible.charAt(Math.floor(Math.random() * possible.length));
|
|
10
|
+
|
|
11
|
+
return text;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
module.exports = function (app, callbacks) {
|
|
15
|
+
var module = {};
|
|
16
|
+
var config = (app.get('np_config'))
|
|
17
|
+
|
|
18
|
+
let usingMultiDbOrm = false;
|
|
19
|
+
if (config.db_url) {
|
|
20
|
+
User = require('../models/np_user.model.js');
|
|
21
|
+
usingMultiDbOrm = false;
|
|
22
|
+
} else if (app.multidborm) {
|
|
23
|
+
const sample = {
|
|
24
|
+
"id": "user_aB3dE9xY1Z",
|
|
25
|
+
"name": "tset",
|
|
26
|
+
"email": "testgmailcom",
|
|
27
|
+
"phone": "12345678",
|
|
28
|
+
"createdAt": "stringlarge",
|
|
29
|
+
"updatedAt": "stringlarge",
|
|
30
|
+
"returnUrl": "stringlarge"
|
|
31
|
+
}
|
|
32
|
+
User = require('../models/np_multidbplugin.js')('npusers', app.multidborm, sample);
|
|
33
|
+
User.db = app.multidborm;
|
|
34
|
+
User.modelname = 'npusers'
|
|
35
|
+
User.idFieldName = 'id'
|
|
36
|
+
app.NPUser = User;
|
|
37
|
+
usingMultiDbOrm = true;
|
|
38
|
+
}
|
|
39
|
+
module.create = (userData, cb) => {
|
|
40
|
+
|
|
41
|
+
User.findOne({ email: userData.email }, function (err, user) {
|
|
42
|
+
if (user) {
|
|
43
|
+
|
|
44
|
+
// console.log("User Update : ",userData.name );
|
|
45
|
+
var myquery = { email: userData.email };
|
|
46
|
+
|
|
47
|
+
var objForUpdate = user;
|
|
48
|
+
|
|
49
|
+
if (userData.email && userData.email.indexOf("@") !== -1) objForUpdate.email = userData.email;
|
|
50
|
+
if (userData.phone && userData.phone.length > 2) objForUpdate.phone = userData.phone;
|
|
51
|
+
if (userData.name && userData.name.length > 2) objForUpdate.name = userData.name;
|
|
52
|
+
delete objForUpdate._id;
|
|
53
|
+
var newvalues = { $set: objForUpdate };
|
|
54
|
+
//console.log("User Old : ",userData.name);
|
|
55
|
+
User.updateOne(myquery, newvalues, function (err, saveRes) {
|
|
56
|
+
if (err) cb({
|
|
57
|
+
message: err.message || "Some error occurred while updating users."
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
// console.log("Sendiing callback")
|
|
61
|
+
cb(user);
|
|
62
|
+
// console.log("sent callback")
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
} else {
|
|
67
|
+
|
|
68
|
+
// console.log("User New : ",userData.name);
|
|
69
|
+
|
|
70
|
+
userData.id = "user_" + makeid(IDLEN);
|
|
71
|
+
var userTask = new User(userData);
|
|
72
|
+
userTask.save()
|
|
73
|
+
.then(user => {
|
|
74
|
+
// console.log("Sendiing callback")
|
|
75
|
+
cb(user);
|
|
76
|
+
// console.log("sent callback")
|
|
77
|
+
|
|
78
|
+
}).catch(err => {
|
|
79
|
+
return cb(err);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
}, usingMultiDbOrm ? User : undefined);
|
|
85
|
+
|
|
86
|
+
};
|
|
87
|
+
return module;
|
|
88
|
+
|
|
80
89
|
}
|