jsonPaymentProtocol/index.js

164 lines
5.1 KiB
JavaScript

'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;