forked from bitpay/jsonPaymentProtocol
-
Notifications
You must be signed in to change notification settings - Fork 4
/
index.js
163 lines (138 loc) · 5.09 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
'use strict';
//Native
const crypto = require('crypto');
const query = require('querystring');
const url = require('url');
const util = require('util');
//Modules
const _ = require('lodash');
const request = require('request');
function PaymentProtocol(options) {
this.options = _.merge({
strictSSL: true
}, options);
}
/**
* Makes a request to the given url and returns the raw JSON string retrieved as well as the headers
* @param paymentUrl {string} the payment protocol specific url
* @param callback {function} (err, body, headers)
*/
PaymentProtocol.prototype.getRawPaymentRequest = function getRawPaymentRequest(paymentUrl, callback) {
let paymentUrlObject = url.parse(paymentUrl);
//Detect 'bitcoin:' urls and extract payment-protocol section
if (paymentUrlObject.protocol !== 'http' && paymentUrlObject.protocol !== 'https') {
let uriQuery = query.decode(paymentUrlObject.query);
if (!uriQuery.r) {
return callback(new Error('Invalid payment protocol url'));
}
else {
paymentUrl = uriQuery.r;
}
}
let requestOptions = _.merge(this.options, {
url: paymentUrl,
headers: {
'Accept': 'application/payment-request'
}
});
request.get(requestOptions, (err, response) => {
if (err) {
return callback(err);
}
if (response.statusCode !== 200) {
return callback(new Error(response.body.toString()));
}
return callback(null, {rawBody: response.body, headers: response.headers});
});
};
/**
* Makes a request to the given url and returns the raw JSON string retrieved as well as the headers
* @param url {string} the payment protocol specific url (https)
*/
PaymentProtocol.prototype.getRawPaymentRequestAsync = util.promisify(PaymentProtocol.prototype.getRawPaymentRequest);
/**
* Given a raw payment protocol body, parses it and validates it against the digest header
* @param rawBody {string} Raw JSON string retrieved from the payment protocol server
* @param headers {object} Headers sent by the payment protocol server
* @param callback {function} (err, paymentRequest)
*/
PaymentProtocol.prototype.parsePaymentRequest = function parsePaymentRequest(rawBody, headers, callback) {
let paymentRequest;
if (!rawBody) {
return callback(new Error('Parameter rawBody is required'));
}
if (!headers) {
return callback(new Error('Parameter headers is required'));
}
try {
paymentRequest = JSON.parse(rawBody);
}
catch (e) {
return callback(new Error(`Unable to parse request - ${e}`));
}
if (!headers.digest) {
return callback(new Error('Digest missing from response headers'));
}
let digest = headers.digest.split('=')[1];
let hash = crypto.createHash('sha256').update(rawBody, 'utf8').digest('hex');
if (digest !== hash) {
return callback(new Error(`Response body hash does not match digest header. Actual: ${hash} Expected: ${digest}`));
}
return callback(null, paymentRequest);
};
/**
* Given a raw payment protocol body, parses it and validates it against the digest header
* @param rawBody {string} Raw JSON string retrieved from the payment protocol server
* @param headers {object} Headers sent by the payment protocol server
*/
PaymentProtocol.prototype.parsePaymentRequestAsync = util.promisify(PaymentProtocol.prototype.parsePaymentRequest);
/**
* Sends a given payment to the server for validation
* @param currency {string} Three letter currency code of proposed transaction (ie BTC, BCH)
* @param signedRawTransaction {string} Hexadecimal format raw signed transaction
* @param url {string} the payment protocol specific url (https)
* @param callback {function} (err, response)
*/
PaymentProtocol.prototype.sendPayment = function sendPayment(currency, signedRawTransaction, url, callback) {
let paymentResponse;
//Basic sanity checks
if (typeof signedRawTransaction !== 'string') {
return callback(new Error('signedRawTransaction must be a string'));
}
if (!/^[0-9a-f]+$/i.test(signedRawTransaction)) {
return callback(new Error('signedRawTransaction must be in hexadecimal format'));
}
let requestOptions = _.merge(this.options, {
url: url,
headers: {
'Content-Type': 'application/payment'
},
body: JSON.stringify({
currency: currency,
transactions: [signedRawTransaction]
})
});
request.post(requestOptions, (err, response) => {
if (err) {
return callback(err);
}
if (response.statusCode !== 200) {
return callback(new Error(response.body.toString()));
}
try {
paymentResponse = JSON.parse(response.body);
}
catch (e) {
return callback(new Error('Unable to parse response from server'));
}
callback(null, paymentResponse);
});
};
/**
* Sends a given payment to the server for validation
* @param currency {string} Three letter currency code of proposed transaction (ie BTC, BCH)
* @param signedRawTransaction {string} Hexadecimal format raw signed transaction
* @param url {string} the payment protocol specific url (https)
*/
PaymentProtocol.prototype.sendPaymentAsync = util.promisify(PaymentProtocol.prototype.sendPayment);
module.exports = PaymentProtocol;