bitcore-wallet-client-dash
Version:
Client for bitcore-wallet-service-dash
249 lines (200 loc) • 6.88 kB
JavaScript
var $ = require('preconditions').singleton();
var Bitcore = require('@dashevo/dashcore-lib');
var BitcorePayPro = require('@dashevo/dashcore-payment-protocol');
var PayPro = {};
PayPro._nodeRequest = function(opts, cb) {
opts.agent = false;
var http = opts.httpNode || (opts.proto === 'http' ? require("http") : require("https"));
var fn = opts.method == 'POST' ? 'post' : 'get';
http[fn](opts, function(res) {
if (res.statusCode != 200)
return cb(new Error('HTTP Request Error'));
var data = []; // List of Buffer objects
res.on("data", function(chunk) {
data.push(chunk); // Append Buffer object
});
res.on("end", function() {
data = Buffer.concat(data); // Make one large Buffer of it
return cb(null, data);
});
});
};
PayPro._browserRequest = function(opts, cb) {
var method = (opts.method || 'GET').toUpperCase();
var url = opts.url;
var req = opts;
req.headers = req.headers || {};
req.body = req.body || req.data || '';
var xhr = opts.xhr || new XMLHttpRequest();
xhr.open(method, url, true);
Object.keys(req.headers).forEach(function(key) {
var val = req.headers[key];
if (key === 'Content-Length') return;
if (key === 'Content-Transfer-Encoding') return;
xhr.setRequestHeader(key, val);
});
xhr.responseType = 'arraybuffer';
xhr.onload = function(event) {
var response = xhr.response;
return cb(null, new Uint8Array(response));
};
xhr.onerror = function(event) {
var status;
if (xhr.status === 0 || !xhr.statusText) {
status = 'HTTP Request Error';
} else {
status = xhr.statusText;
}
return cb(new Error(status));
};
if (req.body) {
xhr.send(req.body);
} else {
xhr.send(null);
}
};
var getHttp = function(opts) {
var match = opts.url.match(/^((http[s]?):\/)?\/?([^:\/\s]+)((\/\w+)*\/)([\w\-\.]+[^#?\s]+)(.*)?(#[\w\-]+)?$/);
opts.proto = RegExp.$2;
opts.host = RegExp.$3;
opts.path = RegExp.$4 + RegExp.$6;
if (opts.http) return opts.http;
var env = opts.env;
if (!env)
env = (process && !process.browser) ? 'node' : 'browser';
return (env == "node") ? PayPro._nodeRequest : http = PayPro._browserRequest;;
};
PayPro.get = function(opts, cb) {
$.checkArgument(opts && opts.url);
var http = getHttp(opts);
opts.headers = opts.headers || {
'Accept': BitcorePayPro.PAYMENT_REQUEST_CONTENT_TYPE,
'Content-Type': 'application/octet-stream',
};
http(opts, function(err, dataBuffer) {
if (err) return cb(err);
var request, verified, signature, serializedDetails;
try {
var body = BitcorePayPro.PaymentRequest.decode(dataBuffer);
request = (new BitcorePayPro()).makePaymentRequest(body);
signature = request.get('signature');
serializedDetails = request.get('serialized_payment_details');
// Verify the signature
verified = request.verify(true);
} catch (e) {
return cb(new Error('Could not parse payment protocol: ' + e));
}
// Get the payment details
var decodedDetails = BitcorePayPro.PaymentDetails.decode(serializedDetails);
var pd = new BitcorePayPro();
pd = pd.makePaymentDetails(decodedDetails);
var outputs = pd.get('outputs');
if (outputs.length > 1)
return cb(new Error('Payment Protocol Error: Requests with more that one output are not supported'))
var output = outputs[0];
var amount = output.get('amount').toNumber();
var network = pd.get('network') == 'test' ? 'testnet' : 'livenet';
// We love payment protocol
var offset = output.get('script').offset;
var limit = output.get('script').limit;
// NOTE: For some reason output.script.buffer
// is only an ArrayBuffer
var buffer = new Buffer(new Uint8Array(output.get('script').buffer));
var scriptBuf = buffer.slice(offset, limit);
var addr = new Bitcore.Address.fromScript(new Bitcore.Script(scriptBuf), network);
var md = pd.get('merchant_data');
if (md) {
md = md.toString();
}
var ok = verified.verified;
var caName;
if (verified.isChain) {
ok = ok && verified.chainVerified;
}
return cb(null, {
verified: ok,
caTrusted: verified.caTrusted,
caName: verified.caName,
selfSigned: verified.selfSigned,
expires: pd.get('expires'),
memo: pd.get('memo'),
time: pd.get('time'),
merchant_data: md,
toAddress: addr.toString(),
amount: amount,
network: network,
domain: opts.host,
url: opts.url,
});
});
};
PayPro._getPayProRefundOutputs = function(addrStr, amount) {
amount = amount.toString(10);
var output = new BitcorePayPro.Output();
var addr = new Bitcore.Address(addrStr);
var s;
if (addr.isPayToPublicKeyHash()) {
s = Bitcore.Script.buildPublicKeyHashOut(addr);
} else if (addr.isPayToScriptHash()) {
s = Bitcore.Script.buildScriptHashOut(addr);
} else {
throw new Error('Unrecognized address type ' + addr.type);
}
// console.log('PayPro refund address set to:', addrStr,s);
output.set('script', s.toBuffer());
output.set('amount', amount);
return [output];
};
PayPro._createPayment = function(merchant_data, rawTx, refundAddr, amountSat) {
var pay = new BitcorePayPro();
pay = pay.makePayment();
if (merchant_data) {
merchant_data = new Buffer(merchant_data);
pay.set('merchant_data', merchant_data);
}
var txBuf = new Buffer(rawTx, 'hex');
pay.set('transactions', [txBuf]);
var refund_outputs = this._getPayProRefundOutputs(refundAddr, amountSat);
if (refund_outputs)
pay.set('refund_to', refund_outputs);
// Unused for now
// options.memo = '';
// pay.set('memo', options.memo);
pay = pay.serialize();
var buf = new ArrayBuffer(pay.length);
var view = new Uint8Array(buf);
for (var i = 0; i < pay.length; i++) {
view[i] = pay[i];
}
return view;
};
PayPro.send = function(opts, cb) {
$.checkArgument(opts.merchant_data)
.checkArgument(opts.url)
.checkArgument(opts.rawTx)
.checkArgument(opts.refundAddr)
.checkArgument(opts.amountSat);
var payment = PayPro._createPayment(opts.merchant_data, opts.rawTx, opts.refundAddr, opts.amountSat);
var http = getHttp(opts);
opts.method = 'POST';
opts.headers = opts.headers || {
'Accept': BitcorePayPro.PAYMENT_ACK_CONTENT_TYPE,
'Content-Type': BitcorePayPro.PAYMENT_CONTENT_TYPE,
// 'Content-Type': 'application/octet-stream',
};
opts.body = payment;
http(opts, function(err, rawData) {
if (err) return cb(err);
var memo;
if (rawData) {
try {
var data = BitcorePayPro.PaymentACK.decode(rawData);
var pp = new BitcorePayPro();
var ack = pp.makePaymentACK(data);
memo = ack.get('memo');
} catch (e) {};
}
return cb(null, rawData, memo);
});
};
module.exports = PayPro;