refactor PayPro tests

This commit is contained in:
Matias Alejo Garcia 2014-11-23 00:29:50 -03:00
parent e9005c2ca0
commit ae0dd40903
7 changed files with 345 additions and 1131 deletions

View File

@ -168,7 +168,7 @@ angular.module('copayApp.controllers').controller('SendController',
toAddress: address,
amountSat: amount,
comment: commentText,
url: (payInfo && payInfo.merchant) ? url : null,
url: (payInfo && payInfo.merchant) ? payInfo.merchant : null,
}, $scope._afterSend);
};

View File

@ -77,7 +77,7 @@ TxProposal.prototype._checkPayPro = function() {
if (ppOut.address !== txOut.address)
throw new Error('PayPro: Wrong out address in Tx');
if (ppOut.amountSatStr !== txOut.amountSatStr)
if (ppOut.amountSatStr !== txOut.amountSatStr + '')
throw new Error('PayPro: Wrong amount in Tx');
};
@ -112,8 +112,13 @@ TxProposal.prototype.trimForStorage = function() {
};
TxProposal.prototype.addMerchantData = function(merchantData) {
preconditions.checkArgument(merchantData.pr);
preconditions.checkArgument(merchantData.request_url);
var m = _.clone(merchantData);
if (!this.paymentProtocolURL)
this.paymentProtocolURL = m.request_url;
// remove unneeded data
m.raw = m.pr.pki_data = m.pr.signature = undefined;
this.merchant = m;

View File

@ -1535,12 +1535,9 @@ Wallet.prototype.fetchPaymentRequest = function(options, cb) {
.success(function(rawData) {
log.info('PayPro Request done successfully. Parsing response')
var data = PayPro.PaymentRequest.decode(rawData);
var paypro = new PayPro();
var pr = paypro.makePaymentRequest(data);
var merchantData, err;
try {
merchantData = self.parsePaymentRequest(options, pr);
merchantData = self.parsePaymentRequest(options, rawData);
} catch (e) {
err = e
};
@ -1550,7 +1547,6 @@ Wallet.prototype.fetchPaymentRequest = function(options, cb) {
})
.error(function(data, status) {
log.debug('Server did not return PaymentRequest.\nXHR status: ' + status);
preconditions.checkState(options.fetch);
return cb(new Error('Status: ' + status));
});
};
@ -1635,18 +1631,16 @@ Wallet.prototype._addOutputsToMerchantData = function(merchantData) {
/**
* @desc Analyzes a payment request and generate merchantData
* @param {Object} options
* @param {PayProRequest} pr
* @param {string} pr.payment_details_version
* @param {string} pr.pki_type
* @param {Object} pr.data
* @param {string} pr.serialized_payment_details
* @param {string} pr.signature
* @param {string} options.memo
* @param {string} options.comment
* @param {string} options.url url where the pay request was acquired
* @param {string} options.amount Only used if pay requesst allow user to set the amount
* @param {PayProRequest} rawData
*/
Wallet.prototype.parsePaymentRequest = function(options, pr) {
Wallet.prototype.parsePaymentRequest = function(options, rawData) {
var self = this;
var data = PayPro.PaymentRequest.decode(rawData);
var paypro = new PayPro();
var pr = paypro.makePaymentRequest(data);
var ver = pr.get('payment_details_version');
var pki_type = pr.get('pki_type');
var pki_data = pr.get('pki_data');
@ -2132,8 +2126,7 @@ Wallet.prototype.removeTxWithSpentInputs = function(cb) {
*/
Wallet.prototype.createTx = function(opts, cb) {
preconditions.checkArgument(_.isObject(opts));
preconditions.checkArgument(opts.amountSat);
log.debug(opts);
log.debug('create Options', opts);
var self = this;
var toAddress = opts.toAddress;
@ -2142,23 +2135,26 @@ Wallet.prototype.createTx = function(opts, cb) {
var url = opts.url;
if (url && !opts.merchantData) {
w.fetchPaymentRequest({
return self.fetchPaymentRequest({
url: url,
memo: comment,
amount: amountSat,
}, function(err, merchantData) {
if (err) return cb(err);
opts.merchantData = merchantData;
opts.amountSat = merchantData.outs[0].address;
opts.toAddress = merchantData.outs[0].amount;
self.createTx(opts, cb);
opts.toAddress = merchantData.outs[0].address;
opts.amountSat = parseInt(merchantData.outs[0].amountSatStr);
return self.createTx(opts, cb);
});
};
preconditions.checkArgument(amountSat);
preconditions.checkArgument(toAddress);
preconditions.checkArgument(amountSat, 'no amount');
preconditions.checkArgument(toAddress, 'no address');
this.getUnspent(function(err, safeUnspent) {
if (err) return cb(new Error('Could not get list of UTXOs'));
if (err) {
log.info(err);
return cb(new Error('CreateTx: Could not get list of UTXOs'));
}
var ntxid, txp;
try {
@ -2168,8 +2164,9 @@ Wallet.prototype.createTx = function(opts, cb) {
return cb(e);
}
if (opts.merchantData)
if (opts.merchantData) {
txp.addMerchantData(opts.merchantData);
}
var ntxid = self.txProposals.add(txp);
log.debug('TXP Added: ', ntxid);
@ -2207,9 +2204,10 @@ Wallet.prototype.createTxProposal = function(toAddress, amountSat, comment, utxo
var pkr = this.publicKeyRing;
var priv = this.privateKey;
toAddress = sanitize(toAddress);
var addr = sanitize(toAddress);
preconditions.checkState(addr && addr.data && addr.isValid(), 'Bad address:' + addr.toString());
preconditions.checkArgument(toAddress.network().name === this.getNetworkName(), 'networkname mismatch');
preconditions.checkArgument(addr.network().name === this.getNetworkName(), 'networkname mismatch');
preconditions.checkState(pkr.isComplete(), 'pubkey ring incomplete');
preconditions.checkState(priv, 'no private key');
@ -2229,7 +2227,7 @@ Wallet.prototype.createTxProposal = function(toAddress, amountSat, comment, utxo
var b = new Builder(builderOpts)
.setUnspent(utxos)
.setOutputs([{
address: toAddress.data,
address: addr.data,
amountSatStr: amountSat,
}]);

75
js/util/HTTP.js Normal file
View File

@ -0,0 +1,75 @@
module.exports = {
request: function(options, callback) {
preconditions.checkArgument(_.isObject(options));
options.method = options.method || 'GET';
options.headers = options.headers || {};
var ret = {
success: function(cb) {
this._success = cb;
return this;
},
error: function(cb) {
this._error = cb;
return this;
},
_success: function() {;
},
_error: function(_, err) {
throw err;
}
};
var method = (options.method || 'GET').toUpperCase();
var url = options.url;
var req = options;
req.headers = req.headers || {};
req.body = req.body || req.data || {};
var 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);
});
if (req.responseType) {
xhr.responseType = req.responseType;
}
xhr.onload = function(event) {
var response = xhr.response;
var buf = new Uint8Array(response);
var headers = {};
(xhr.getAllResponseHeaders() || '').replace(
/(?:\r?\n|^)([^:\r\n]+): *([^\r\n]+)/g,
function($0, $1, $2) {
headers[$1.toLowerCase()] = $2;
}
);
return ret._success(buf, xhr.status, headers, options);
};
xhr.onerror = function(event) {
var status;
if (xhr.status === 0 || !xhr.statusText) {
status = 'HTTP Request Error: This endpoint likely does not support cross-origin requests.';
} else {
status = xhr.statusText;
}
return ret._error(null, status, null, options);
};
if (req.body) {
xhr.send(req.body);
} else {
xhr.send(null);
}
return ret;
},
};

View File

@ -1,793 +0,0 @@
'use strict';
var Wallet = copay.Wallet;
var PrivateKey = copay.PrivateKey;
var Network = requireMock('FakeNetwork');
var Blockchain = requireMock('FakeBlockchain');
var TransactionBuilder = bitcore.TransactionBuilder;
var Transaction = bitcore.Transaction;
var Address = bitcore.Address;
var PayPro = bitcore.PayPro;
var bignum = bitcore.Bignum;
var startServer = copay.FakePayProServer; // TODO should be require('./mocks/FakePayProServer');
var server;
var walletConfig = {
requiredCopayers: 1,
totalCopayers: 1,
spendUnconfirmed: true,
reconnectDelay: 100,
networkName: 'testnet'
};
var getNewEpk = function() {
return new PrivateKey({
networkName: walletConfig.networkName,
})
.deriveBIP45Branch()
.extendedPublicKeyString();
};
describe('PayPro (in Wallet) model', function() {
if (!is_browser) {
var createW = function(N, conf) {
var c = JSON.parse(JSON.stringify(conf || walletConfig));
if (!N) N = c.totalCopayers;
var mainPrivateKey = new copay.PrivateKey({
networkName: walletConfig.networkName
});
var mainCopayerEPK = mainPrivateKey.deriveBIP45Branch().extendedPublicKeyString();
c.privateKey = mainPrivateKey;
c.publicKeyRing = new copay.PublicKeyRing({
networkName: c.networkName,
requiredCopayers: Math.min(N, c.requiredCopayers),
totalCopayers: N,
});
c.publicKeyRing.addCopayer(mainCopayerEPK);
c.txProposals = new copay.TxProposals({
networkName: c.networkName,
});
var network = new Network(walletConfig.network);
var blockchain = new Blockchain(walletConfig.blockchain);
c.network = network;
c.blockchain = blockchain;
c.addressBook = {
'2NFR2kzH9NUdp8vsXTB4wWQtTtzhpKxsyoJ': {
label: 'John',
copayerId: '026a55261b7c898fff760ebe14fd22a71892295f3b49e0ca66727bc0a0d7f94d03',
createdTs: 1403102115,
hidden: false
},
'2MtP8WyiwG7ZdVWM96CVsk2M1N8zyfiVQsY': {
label: 'Jennifer',
copayerId: '032991f836543a492bd6d0bb112552bfc7c5f3b7d5388fcbcbf2fbb893b44770d7',
createdTs: 1403103115,
hidden: false
}
};
c.networkName = walletConfig.networkName;
c.version = '0.0.1';
c.network = sinon.stub();
c.network.setHexNonce = sinon.stub();
c.network.setHexNonces = sinon.stub();
c.network.getHexNonce = sinon.stub();
c.network.getHexNonces = sinon.stub();
c.network.send = sinon.stub();
return new Wallet(c);
}
var unspentTest = [{
"address": "dummy",
"scriptPubKey": "dummy",
"txid": "2ac165fa7a3a2b535d106a0041c7568d03b531e58aeccdd3199d7289ab12cfc1",
"vout": 1,
"amount": 10,
"confirmations": 7
}];
var createW2 = function(privateKeys, N, conf) {
if (!N) N = 3;
var w = createW(N, conf);
should.exist(w);
var pkr = w.publicKeyRing;
for (var i = 0; i < N - 1; i++) {
if (privateKeys) {
var k = privateKeys[i];
pkr.addCopayer(k ? k.deriveBIP45Branch().extendedPublicKeyString() : getNewEpk());
} else {
pkr.addCopayer(getNewEpk());
}
}
return w;
};
var cachedW2 = null;
var cachedW2obj = null;
var cachedCreateW2 = function() {
if (!cachedW2) {
cachedW2 = createW2();
cachedW2obj = cachedW2.toObj();
cachedW2obj.opts.reconnectDelay = 100;
}
Wallet._newAsync = sinon.stub().returns(new Network(walletConfig.network));
Wallet._newInsight = sinon.stub().returns(new Blockchain(walletConfig.blockchain));
var w = Wallet.fromObj(cachedW2obj, {
blockchainOpts: {},
networkOpts: {},
});
return w;
};
var createWallet = function() {
var w = cachedCreateW2();
unspentTest[0].address = w.publicKeyRing.getAddress(1, true, w.publicKey).toString();
unspentTest[0].scriptPubKey = w.publicKeyRing.getScriptPubKeyHex(1, true, w.publicKey);
w.getUnspent = sinon.stub().yields(null, unspentTest, unspentTest);
return w;
};
it('#start the example server', function(done) {
startServer(function(err, s) {
if (err) return done(err);
server = s;
server.uri = 'https://localhost:8080/-';
done();
});
});
var pr;
var ppw;
ppw = createWallet();
it('#retrieve a payment request message via http', function(done) {
var w = ppw;
should.exist(w);
var req = {
headers: {
'Host': 'localhost:8080',
'Accept': PayPro.PAYMENT_REQUEST_CONTENT_TYPE + ', ' + PayPro.PAYMENT_ACK_CONTENT_TYPE,
'Content-Type': 'application/octet-stream',
'Content-Length': '0'
},
socket: {
remoteAddress: 'localhost',
remotePort: 8080
},
body: {}
};
Object.keys(req.headers).forEach(function(key) {
req.headers[key.toLowerCase()] = req.headers[key];
});
server.GET['/-/request'](req, function(err, res, body) {
var data = PayPro.PaymentRequest.decode(body);
pr = new PayPro();
pr = pr.makePaymentRequest(data);
done();
});
});
it('#send a payment message via http', function(done) {
var w = ppw;
should.exist(w);
var ver = pr.get('payment_details_version');
var pki_type = pr.get('pki_type');
var pki_data = pr.get('pki_data');
var details = pr.get('serialized_payment_details');
var sig = pr.get('signature');
var certs = PayPro.X509Certificates.decode(pki_data);
certs = certs.certificate;
var verified = pr.verify();
if (!verified) {
return done(new Error('Server sent a bad signature.'));
}
details = PayPro.PaymentDetails.decode(details);
var pd = new PayPro();
pd = pd.makePaymentDetails(details);
var network = pd.get('network');
var outputs = pd.get('outputs');
var time = pd.get('time');
var expires = pd.get('expires');
var memo = pd.get('memo');
var payment_url = pd.get('payment_url');
var merchant_data = pd.get('merchant_data');
var priv = w.privateKey;
var pkr = w.publicKeyRing;
var opts = {
remainderOut: {
address: w._doGenerateAddress(true).toString()
}
};
var outs = [];
outputs.forEach(function(output) {
var amount = output.get('amount');
var script = {
offset: output.get('script').offset,
limit: output.get('script').limit,
buffer: new Buffer(new Uint8Array(
output.get('script').buffer))
};
// big endian
var v = new Buffer(8);
v[0] = (amount.high >> 24) & 0xff;
v[1] = (amount.high >> 16) & 0xff;
v[2] = (amount.high >> 8) & 0xff;
v[3] = (amount.high >> 0) & 0xff;
v[4] = (amount.low >> 24) & 0xff;
v[5] = (amount.low >> 16) & 0xff;
v[6] = (amount.low >> 8) & 0xff;
v[7] = (amount.low >> 0) & 0xff;
var s = script.buffer.slice(script.offset, script.limit);
var net = network === 'main' ? 'livenet' : 'testnet';
var addr = bitcore.Address.fromScriptPubKey(new bitcore.Script(s), net);
outs.push({
address: addr[0].toString(),
amountSatStr: bitcore.Bignum.fromBuffer(v, {
// XXX for some reason, endian is ALWAYS 'big'
// in node (in the browser it behaves correctly)
endian: 'big',
size: 1
}).toString(10)
});
});
var b = new bitcore.TransactionBuilder(opts)
.setUnspent(unspentTest)
.setOutputs(outs);
outputs.forEach(function(output, i) {
var script = {
offset: output.get('script').offset,
limit: output.get('script').limit,
buffer: new Buffer(new Uint8Array(
output.get('script').buffer))
};
var s = script.buffer.slice(script.offset, script.limit);
b.tx.outs[i].s = s;
});
var selectedUtxos = b.getSelectedUnspent();
var inputChainPaths = selectedUtxos.map(function(utxo) {
return pkr.pathForAddress(utxo.address);
});
b = b.setHashToScriptMap(pkr.getRedeemScriptMap(inputChainPaths));
if (priv) {
var keys = priv.getForPaths(inputChainPaths);
var signed = b.sign(keys);
}
var tx = b.build();
var refund_outputs = [];
var refund_to = w.publicKeyRing.getPubKeys(0, false, w.getMyCopayerId())[0];
var total = outputs.reduce(function(total, _, i) {
// XXX reverse endianness to work around bignum bug:
var txv = tx.outs[i].v;
var v = new Buffer(8);
for (var j = 0; j < 8; j++) v[j] = txv[7 - j];
return total.add(bignum.fromBuffer(v, {
endian: 'big',
size: 1
}));
}, bitcore.Bignum('0', 10));
var rpo = new PayPro();
rpo = rpo.makeOutput();
rpo.set('amount', +total.toString(10));
rpo.set('script',
Buffer.concat([
new Buffer([
118, // OP_DUP
169, // OP_HASH160
76, // OP_PUSHDATA1
20, // number of bytes
]),
// needs to be ripesha'd
bitcore.util.sha256ripe160(refund_to),
new Buffer([
136, // OP_EQUALVERIFY
172 // OP_CHECKSIG
])
])
);
refund_outputs.push(rpo.message);
var pay = new PayPro();
pay = pay.makePayment();
pay.set('merchant_data', new Buffer([0, 1]));
pay.set('transactions', [tx.serialize()]);
pay.set('refund_to', refund_outputs);
pay.set('memo', 'Hi server, I would like to give you some money.');
pay = pay.serialize();
var req = {
headers: {
'Host': 'localhost:8080',
'Accept': PayPro.PAYMENT_REQUEST_CONTENT_TYPE + ', ' + PayPro.PAYMENT_ACK_CONTENT_TYPE,
'Content-Type': PayPro.PAYMENT_CONTENT_TYPE,
'Content-Length': pay.length + ''
},
socket: {
remoteAddress: 'localhost',
remotePort: 8080
},
body: pay,
data: pay
};
Object.keys(req.headers).forEach(function(key) {
req.headers[key.toLowerCase()] = req.headers[key];
});
server.POST['/-/pay'](req, function(err, res, body) {
if (err) return done(err);
var data = PayPro.PaymentACK.decode(body);
var ack = new PayPro();
ack = ack.makePaymentACK(data);
var payment = ack.get('payment');
var memo = ack.get('memo');
payment = PayPro.Payment.decode(payment);
var pay = new PayPro();
payment = pay.makePayment(payment);
var tx = payment.message.transactions[0];
if (!tx) {
return done(new Error('No tx in payment ACK.'));
}
if (tx.buffer) {
tx.buffer = new Buffer(new Uint8Array(tx.buffer));
tx.buffer = tx.buffer.slice(tx.offset, tx.limit);
var ptx = new bitcore.Transaction();
ptx.parse(tx.buffer);
tx = ptx;
}
var ackTotal = outputs.reduce(function(total, _, i) {
// XXX reverse endianness to work around bignum bug:
var txv = tx.outs[i].v;
var v = new Buffer(8);
for (var j = 0; j < 8; j++) v[j] = txv[7 - j];
return total.add(bignum.fromBuffer(v, {
endian: 'big',
size: 1
}));
}, bitcore.Bignum('0', 10));
ackTotal.toString(10).should.equal(total.toString(10));
done();
});
});
it('#retrieve a payment request message via http', function(done) {
var w = ppw;
should.exist(w);
var req = {
headers: {
'Host': 'localhost:8080',
'Accept': PayPro.PAYMENT_REQUEST_CONTENT_TYPE + ', ' + PayPro.PAYMENT_ACK_CONTENT_TYPE,
'Content-Type': 'application/octet-stream',
'Content-Length': '0'
},
socket: {
remoteAddress: 'localhost',
remotePort: 8080
},
body: {}
};
Object.keys(req.headers).forEach(function(key) {
req.headers[key.toLowerCase()] = req.headers[key];
});
server.GET['/-/request'](req, function(err, res, body) {
var data = PayPro.PaymentRequest.decode(body);
pr = new PayPro();
pr = pr.makePaymentRequest(data);
done();
});
});
it('#send a payment message via http', function(done) {
var w = ppw;
should.exist(w);
var ver = pr.get('payment_details_version');
var pki_type = pr.get('pki_type');
var pki_data = pr.get('pki_data');
var details = pr.get('serialized_payment_details');
var sig = pr.get('signature');
var certs = PayPro.X509Certificates.decode(pki_data);
certs = certs.certificate;
var verified = pr.verify();
if (!verified) {
return done(new Error('Server sent a bad signature.'));
}
details = PayPro.PaymentDetails.decode(details);
var pd = new PayPro();
pd = pd.makePaymentDetails(details);
var network = pd.get('network');
var outputs = pd.get('outputs');
var time = pd.get('time');
var expires = pd.get('expires');
var memo = pd.get('memo');
var payment_url = pd.get('payment_url');
var merchant_data = pd.get('merchant_data');
var priv = w.privateKey;
var pkr = w.publicKeyRing;
var opts = {
remainderOut: {
address: w._doGenerateAddress(true).toString()
}
};
var outs = [];
outputs.forEach(function(output) {
var amount = output.get('amount');
var script = {
offset: output.get('script').offset,
limit: output.get('script').limit,
buffer: new Buffer(new Uint8Array(
output.get('script').buffer))
};
// big endian
var v = new Buffer(8);
v[0] = (amount.high >> 24) & 0xff;
v[1] = (amount.high >> 16) & 0xff;
v[2] = (amount.high >> 8) & 0xff;
v[3] = (amount.high >> 0) & 0xff;
v[4] = (amount.low >> 24) & 0xff;
v[5] = (amount.low >> 16) & 0xff;
v[6] = (amount.low >> 8) & 0xff;
v[7] = (amount.low >> 0) & 0xff;
var s = script.buffer.slice(script.offset, script.limit);
var net = network === 'main' ? 'livenet' : 'testnet';
var addr = bitcore.Address.fromScriptPubKey(new bitcore.Script(s), net);
outs.push({
address: addr[0].toString(),
amountSatStr: bitcore.Bignum.fromBuffer(v, {
// XXX for some reason, endian is ALWAYS 'big'
// in node (in the browser it behaves correctly)
endian: 'big',
size: 1
}).toString(10)
});
});
var b = new bitcore.TransactionBuilder(opts)
.setUnspent(unspentTest)
.setOutputs(outs);
outputs.forEach(function(output, i) {
var script = {
offset: output.get('script').offset,
limit: output.get('script').limit,
buffer: new Buffer(new Uint8Array(
output.get('script').buffer))
};
var s = script.buffer.slice(script.offset, script.limit);
b.tx.outs[i].s = s;
});
var selectedUtxos = b.getSelectedUnspent();
var inputChainPaths = selectedUtxos.map(function(utxo) {
return pkr.pathForAddress(utxo.address);
});
b = b.setHashToScriptMap(pkr.getRedeemScriptMap(inputChainPaths));
if (priv) {
var keys = priv.getForPaths(inputChainPaths);
var signed = b.sign(keys);
}
var tx = b.build();
var refund_outputs = [];
var refund_to = w.publicKeyRing.getPubKeys(0, false, w.getMyCopayerId())[0];
var total = outputs.reduce(function(total, _, i) {
// XXX reverse endianness to work around bignum bug:
var txv = tx.outs[i].v;
var v = new Buffer(8);
for (var j = 0; j < 8; j++) v[j] = txv[7 - j];
return total.add(bignum.fromBuffer(v, {
endian: 'big',
size: 1
}));
}, bitcore.Bignum('0', 10));
var rpo = new PayPro();
rpo = rpo.makeOutput();
rpo.set('amount', +total.toString(10));
rpo.set('script',
Buffer.concat([
new Buffer([
118, // OP_DUP
169, // OP_HASH160
76, // OP_PUSHDATA1
20, // number of bytes
]),
// needs to be ripesha'd
bitcore.util.sha256ripe160(refund_to),
new Buffer([
136, // OP_EQUALVERIFY
172 // OP_CHECKSIG
])
])
);
refund_outputs.push(rpo.message);
var pay = new PayPro();
pay = pay.makePayment();
pay.set('merchant_data', new Buffer([0, 1]));
pay.set('transactions', [tx.serialize()]);
pay.set('refund_to', refund_outputs);
pay.set('memo', 'Hi server, I would like to give you some money.');
pay = pay.serialize();
var req = {
headers: {
'Host': 'localhost:8080',
'Accept': PayPro.PAYMENT_REQUEST_CONTENT_TYPE + ', ' + PayPro.PAYMENT_ACK_CONTENT_TYPE,
'Content-Type': PayPro.PAYMENT_CONTENT_TYPE,
'Content-Length': pay.length + ''
},
socket: {
remoteAddress: 'localhost',
remotePort: 8080
},
body: pay,
data: pay
};
Object.keys(req.headers).forEach(function(key) {
req.headers[key.toLowerCase()] = req.headers[key];
});
server.POST['/-/pay'](req, function(err, res, body) {
if (err) return done(err);
var data = PayPro.PaymentACK.decode(body);
var ack = new PayPro();
ack = ack.makePaymentACK(data);
var payment = ack.get('payment');
var memo = ack.get('memo');
payment = PayPro.Payment.decode(payment);
var pay = new PayPro();
payment = pay.makePayment(payment);
var tx = payment.message.transactions[0];
if (!tx) {
return done(new Error('No tx in payment ACK.'));
}
if (tx.buffer) {
tx.buffer = new Buffer(new Uint8Array(tx.buffer));
tx.buffer = tx.buffer.slice(tx.offset, tx.limit);
var ptx = new bitcore.Transaction();
ptx.parse(tx.buffer);
tx = ptx;
}
var ackTotal = outputs.reduce(function(total, _, i) {
// XXX reverse endianness to work around bignum bug:
var txv = tx.outs[i].v;
var v = new Buffer(8);
for (var j = 0; j < 8; j++) v[j] = txv[7 - j];
return total.add(bignum.fromBuffer(v, {
endian: 'big',
size: 1
}));
}, bitcore.Bignum('0', 10));
ackTotal.toString(10).should.equal(total.toString(10));
should.exist(ack);
memo.should.equal('Thank you for your payment!');
done();
});
});
ppw = createWallet();
it('#retrieve a payment request message via model', function(done) {
var w = ppw;
should.exist(w);
// Caches Payment Request but does not add TX proposal
w.fetchPaymentTx({
uri: 'https://localhost:8080/-/request'
}, function(err, merchantData) {
if (err) return done(err);
merchantData.pr.pd.payment_url.should.equal('https://localhost:8080/-/pay');
return done();
});
});
it('#add tx proposal based on payment message via model', function(done) {
var w = ppw;
should.exist(w);
var options = {
uri: 'https://localhost:8080/-/request'
};
var req = w.paymentRequests[options.uri];
should.exist(req);
delete w.paymentRequests[options.uri];
w.receivePaymentRequest(options, req.pr, function(err, ntxid, merchantData) {
should.equal(err, null);
should.exist(ntxid);
should.exist(merchantData);
w._ntxid = ntxid;
merchantData.pr.pd.payment_url.should.equal('https://localhost:8080/-/pay');
return done();
});
});
it('#add tx proposal based on payment message via model ', function(done) {
var w = ppw;
should.exist(w);
w.sendPaymentTx(w._ntxid, function(txid, merchantData) {
should.exist(txid);
should.exist(merchantData);
should.exist(merchantData.ack);
merchantData.ack.memo.should.equal('Thank you for your payment!');
return done();
});
});
it('#send a payment request using payment api', function(done) {
var w = createWallet();
should.exist(w);
var uri = 'bitcoin:2NBzZdFBoQymDgfzH2Pmnthser1E71MmU47?amount=0.00003&r=' + server.uri + '/request';
var memo = 'Hello, server. I\'d like to make a payment.';
w.createPaymentTx({
uri: uri,
memo: memo
}, function(err, ntxid, merchantData) {
should.equal(err, null);
should.exist(ntxid);
should.exist(merchantData);
if (w.isShared()) {
return done();
} else {
w.sendPaymentTx(ntxid, {
memo: memo
}, function(txid, merchantData) {
should.exist(txid);
should.exist(merchantData);
return done();
});
}
});
});
it('#send a payment request with merchant prefix', function(done) {
var w = createWallet();
should.exist(w);
var address = 'Merchant: ' + server.uri + '/request\nMemo: foo';
var commentText = 'Hello, server. I\'d like to make a payment.';
var uri;
// Replicates code in controllers/send.js:
if (address.indexOf('bitcoin:') === 0) {
uri = new bitcore.BIP21(address).data;
} else if (address.indexOf('Merchant: ') === 0) {
uri = address.split(/\s+/)[1];
}
w.createPaymentTx({
uri: uri,
memo: commentText
}, function(err, ntxid, merchantData) {
should.equal(err, null);
if (w.isShared()) {
should.exist(ntxid);
should.exist(merchantData);
return done();
} else {
should.exist(merchantData);
w.sendTx(ntxid, function(txid, merchantData) {
should.exist(txid);
should.exist(merchantData);
return done();
});
}
});
});
it('#send a payment request with bitcoin uri', function(done) {
var w = createWallet();
should.exist(w);
var address = 'bitcoin:2NBzZdFBoQymDgfzH2Pmnthser1E71MmU47?amount=0.00003&r=' + server.uri + '/request';
var commentText = 'Hello, server. I\'d like to make a payment.';
w.createPaymentTx({
uri: address,
memo: commentText
}, function(err, ntxid, merchantData) {
should.equal(err, null);
if (w.isShared()) {
should.exist(ntxid);
should.exist(merchantData);
return done();
} else {
w.sendTx(ntxid, function(txid, merchantData) {
should.exist(txid);
should.exist(merchantData);
return done();
});
}
});
});
it('#close payment server', function(done) {
server.close(function() {
return done();
});
});
}
});

View File

@ -5,6 +5,7 @@ var PrivateKey = copay.PrivateKey;
var Network = requireMock('FakeNetwork');
var Blockchain = requireMock('FakeBlockchain');
var Builder = requireMock('FakeBuilder');
var FakePayProServer = requireMock('FakePayProServer');
var TransactionBuilder = bitcore.TransactionBuilder;
var Transaction = bitcore.Transaction;
var Address = bitcore.Address;
@ -58,6 +59,15 @@ var addCopayers = function(w) {
describe('Wallet model', function() {
var sandbox;
beforeEach(function() {
sandbox = sinon.sandbox.create();
});
afterEach(function() {
sandbox.restore();
});
it('should fail to create an instance', function() {
(function() {
new Wallet(walletConfig)
@ -210,6 +220,8 @@ describe('Wallet model', function() {
blockchainOpts: {},
networkOpts: {},
});
if (w.httpUtil.request.restore)
w.httpUtil.request.restore();
return w;
};
@ -913,17 +925,123 @@ describe('Wallet model', function() {
});
});
describe('#fetchPaymentRequest', function() {
it('should fetch a payment request', function(done) {
var w = cachedCreateW2();
sinon.stub(w, 'parsePaymentRequest').returns({
hola: 1
});
var opts = {
a: 1,
url: 'http://xxx',
};
var rawData ='wqer';
var e = sinon.stub();
e.error = sinon.stub();
var s = sinon.stub();
s.success = sinon.stub().yields(rawData).returns(e);
sinon.stub(w.httpUtil,'request').returns(s);
w.fetchPaymentRequest(opts, function(err, merchantData){
should.not.exist(err);
should.exist(merchantData);
w.parsePaymentRequest.firstCall.args.should.deep.equal([opts,rawData]);
done();
});
});
it('should return error on fetch error', function(done) {
var w = cachedCreateW2();
var opts = {
a: 1,
url: 'http://xxx',
};
var rawData ='wqer';
var e = sinon.stub();
e.error = sinon.stub().yields(null, 'status');
var s = sinon.stub();
s.success = sinon.stub().returns(e);
sinon.stub(w.httpUtil,'request').returns(s);
w.fetchPaymentRequest(opts, function(err, merchantData){
err.toString().should.contain('status');
done();
});
});
});
// TODO parsePaymentRequest should have more tests,
// FakePayProServer.getRequest should be parametrizable
describe('#parsePaymentRequest', function() {
it('should parse a Payment Request', function() {
var now = Date.now()/1000;
var w = cachedCreateW2();
var opts = {
url: 'http://xxx',
};
var data = FakePayProServer.getRequest();
var md = w.parsePaymentRequest(opts,data);
md.outs.should.deep.equal(FakePayProServer.outs);
md.request_url.should.equal(opts.url);
md.pr.untrusted.should.equal(true);
md.expires.should.be.above(now);
});
});
describe('#createTx', function() {
it('should fail if insight server is down', function(done) {
var w = cachedCreateW2();
var utxo = createUTXO(w);
w.blockchain.fixUnspent(utxo);
sinon.stub(w, 'getUnspent').yields('error', null);
w.createTx({
toAddress: toAddress,
amountSat: amountSatStr,
}, function(err, ntxid) {
chai.expect(err.message).to.equal('Could not get list of UTXOs');
err.message.should.contain('UTXOs');
done();
});
});
it('should fail with broken PayPro', function(done) {
var w = cachedCreateW2();
var utxo = createUTXO(w);
w.blockchain.fixUnspent(utxo);
sinon.stub(w, 'fetchPaymentRequest').yields('error');
w.createTx({
url: 'test',
}, function(err, ntxid) {
should.exist(err);
done();
});
});
it('should create a TX with PayPro', function(done) {
var w = cachedCreateW2();
var utxo = createUTXO(w);
w.blockchain.fixUnspent(utxo);
sinon.stub(w, 'fetchPaymentRequest').yields(null, {
outs: [{
address: 'n2Wz7KjyzBJVaNMBN88Lj1YUHMDZSAGeMV',
amountSatStr: '123400',
}],
request_url: 'url',
pr: {
signature: '123',
},
total: '123400',
});
w.createTx({
url: 'test',
}, function(err, ntxid) {
should.not.exist(err);
done();
});
});

View File

@ -1,94 +1,14 @@
'use strict';
var is_browser = typeof process == 'undefined'
|| typeof process.versions === 'undefined';
var bitcore = bitcore || require('bitcore');
var Buffer = bitcore.Buffer;
var PayPro = bitcore.PayPro;
if (is_browser) {
var copay = require('copay'); //browser
} else {
var copay = require('../../copay'); //node
}
var Wallet = copay.Wallet;
var x509 = {
priv: ''
+ 'LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcEFJQkFBS0NBUUVBeFRKdUsyYUdM'
+ 'bjFkWEpLRGg0TXdQTFVrbDNISTVwR25HNWFjNGwvMGlobXE4Y3dDCitGVlBnWk1TNTlheWtpc0Ir'
+ 'ekM3dnR2a0prL2J2K0JTT1g3b3hkSXN1TDNkS1FGcHVYWFZmcmRiOTV3WW40TSsKL25qRWhYTWxo'
+ 'Vk1IL09DaUFnOUpLaFRLV0w2R1JXWkFBaEE3bEJSaGdTTkRUaVRDNTFDYmlLN3hBNnBONCt0UQpI'
+ 'eG9tSlBYclpSa2JCMmtsT2ZXd2J2OTNZM0oxS0ZEK2kwUE1RSEx3N3JoRXVteEM5MytISFVWWVZI'
+ 'N0gxVFBaCkgxYmRVSkowMmdRZXlsSnNzWUNKeWRaUHpOVC96dXRzL0tKV2RSdjVseHdHOXU5dE1O'
+ 'TWdoSmJtQWFNa01HaSsKbzdQTkV5UDNxSEZyWXBZaHM1cHFMSE1STkI3OFFNOUllTmpMRndJREFR'
+ 'QUJBb0lCQVFERVJyalBiQUdjbmwxaAorZGIrOTczNGZ0aElBUkpWSko1dTRFK1JKcThSRWhGTEVL'
+ 'UFlKNW0yUC94dVZBMXpYV2xnYXhaRUZ6d1VRaUpZCjdsOEpLVjlwSHhReVlaQ1M4dndYZzhpWGtz'
+ 'dndQaWRvQmN1YW4vd0RWQ1FCZXk2VkxjVXpSYUd1Ui9sTHNYK1YKN2Z0QjBvUnFsSXFrYmNQZE1N'
+ 'dnFUeG93UnVoUG11Q3JWVGpPNHBiTnFuU09OUExPaUovRkFYYjJwZnpGZnBCUgpHeCtFTW16d2Ur'
+ 'SEZuSkJHRGhIWjk5bm4vVEJmYUp6TlZDcURZLzNid3o1WDdIUU5ZN1QrSnlUVUZzZVE5NHhzCnpy'
+ 'a2lidGRmVGNUanB1K1VoWm80c1p6Q3IrZkhHWm9FOUdEUHF0ZDRnQ3ByazRFS0pzbXFCRVN4QlhT'
+ 'RGhZZ04KOXBVRDM4c1pBb0dCQU9yZkRqdDZaL0ZDamFuVThXek5GaWYrOVQxQTJ4b013RDVWU2xN'
+ 'dVJyWW1HbGZyMEM5TQpmMUVvZ2l2dVRrYnA3cmtnZFRhWVRTYndmTnFaQkt4Y3R5YzdCaGRwWnhE'
+ 'RVdKa2Z5cThxVngvem1Cek1JK1ZzCjJLYi9hcHZXcmJlb3NET0NyeUg1YzhKc1VUOXhUWDNYYnhF'
+ 'anlPSlFCU1lHRE1qUHlKNkU5czZMQW9HQkFOYnYKd2d0S2Nra0tLbDJhNXZzaGR2RENnNnFLL1Fn'
+ 'T20vNktUSlVKRVNqaHoydFIrZlBWUjcwVEg5UmhoVFJscERXQgpCd3oyU2NCc1RRNDIvTGsxRnky'
+ 'MFQvck12S3VmSEw1VE1BNGZ6NWRxMUxIbmN6ejZVazVnWEtBT09rUjlVdVhpClR0eTNoREcyQkM4'
+ 'Nk1LTVJ4SjUxRWJxam94d0VSMTAwU2FuTVBmTWxBb0dBSUhLY1pyOHNhUHBHMC9XbFBPREEKZE5v'
+ 'V1MxWVFidkxnQkR5SVBpR2doejJRV2lFcjY3em53ZkNVdXpqNiszVUtFKzFXQkNyYVRjemZrdHVj'
+ 'OTZyLwphcDRPNDJFZWFnU1dNT0ZoZ1AyYWQ4R1JmRGovcEl4N0NlY3pkVUFkVThnc1A1R0lYR3M0'
+ 'QU40eUEwL0Y0dUxHCloxbklRT3ZKS2syZnFvWjZNdHd2dEswQ2dZRUFnSjdGTGVDRTkzUmYyZGdD'
+ 'ZFRHWGJZZlpKc3M1bEFLNkV0NUwKNmJ1ZFN5dWw1Z0VPWkgyekNsQlJjZFJSMUFNbSt1V1ZoSW8x'
+ 'cERLckFlQ2g1MnIvemRmakxLQXNIejkrQWQ3aQpHUEdzVmw0Vm5jaDFTMzQ0bHJKUGUzQklLZ2dj'
+ 'L1hncDNTYnNzcHJMY2orT0wyZElrOUpXbzZ1Y3hmMUJmMkwwCjJlbGhBUWtDZ1lCWHN5elZWL1pK'
+ 'cVhOcFdDZzU1TDNVRm9UTHlLU3FsVktNM1dpRzVCS240QWF6VkNITCtHUVUKeHd4U2dSOWZRNElu'
+ 'dStyUHJOM0lteWswbEtQR0Y5U3pDUlJUaUpGUjcyc05xbE82bDBWOENXUkFQVFBKY2dxVgoxVThO'
+ 'SEs4YjNaaUlvR0orbXNOenBkeHJqNjJIM0E2K1krQXNOWTRTbVVUWEg5eWpnK251a2c9PQotLS0t'
+ 'LUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo=',
pub: ''
+ 'LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FR'
+ 'OEFNSUlCQ2dLQ0FRRUF4VEp1SzJhR0xuMWRYSktEaDRNdwpQTFVrbDNISTVwR25HNWFjNGwvMGlo'
+ 'bXE4Y3dDK0ZWUGdaTVM1OWF5a2lzQit6Qzd2dHZrSmsvYnYrQlNPWDdvCnhkSXN1TDNkS1FGcHVY'
+ 'WFZmcmRiOTV3WW40TSsvbmpFaFhNbGhWTUgvT0NpQWc5SktoVEtXTDZHUldaQUFoQTcKbEJSaGdT'
+ 'TkRUaVRDNTFDYmlLN3hBNnBONCt0UUh4b21KUFhyWlJrYkIya2xPZld3YnY5M1kzSjFLRkQraTBQ'
+ 'TQpRSEx3N3JoRXVteEM5MytISFVWWVZIN0gxVFBaSDFiZFVKSjAyZ1FleWxKc3NZQ0p5ZFpQek5U'
+ 'L3p1dHMvS0pXCmRSdjVseHdHOXU5dE1OTWdoSmJtQWFNa01HaStvN1BORXlQM3FIRnJZcFloczVw'
+ 'cUxITVJOQjc4UU05SWVOakwKRndJREFRQUIKLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tCg==',
der: ''
+ 'MIIDBjCCAe4CCQDI2qWdA3/VpDANBgkqhkiG9w0BAQUFADBFMQswCQYDVQQGEwJBVTETMBEGA1UE'
+ 'CAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMB4XDTE0MDcx'
+ 'NjAxMzM1MVoXDTE1MDcxNjAxMzM1MVowRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3Rh'
+ 'dGUxITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDCCASIwDQYJKoZIhvcNAQEBBQAD'
+ 'ggEPADCCAQoCggEBAMUybitmhi59XVySg4eDMDy1JJdxyOaRpxuWnOJf9IoZqvHMAvhVT4GTEufW'
+ 'spIrAfswu77b5CZP27/gUjl+6MXSLLi93SkBabl11X63W/ecGJ+DPv54xIVzJYVTB/zgogIPSSoU'
+ 'yli+hkVmQAIQO5QUYYEjQ04kwudQm4iu8QOqTePrUB8aJiT162UZGwdpJTn1sG7/d2NydShQ/otD'
+ 'zEBy8O64RLpsQvd/hx1FWFR+x9Uz2R9W3VCSdNoEHspSbLGAicnWT8zU/87rbPyiVnUb+ZccBvbv'
+ 'bTDTIISW5gGjJDBovqOzzRMj96hxa2KWIbOaaixzETQe/EDPSHjYyxcCAwEAATANBgkqhkiG9w0B'
+ 'AQUFAAOCAQEAL6AMMfC3TlRcmsIgHxjVD4XYtISlldnrn2X9zvFbJKCpNy8XQQosQxrhyfzPHQKj'
+ 'lS2L/KCGMnjx9QkYD2Hlp1MJ1uVv9888th/gcZOv3Or3hQyi5K1Sh5xCG+69lUOqUEGu9B4irsqo'
+ 'FomQVbQolSy+t4apdJi7kuEDwFDk4gZiVEfsuX+naN5a6pCnWnhX1Vf4fKwfkLobKKXm2zQVsjxl'
+ 'wBAqOEmJGDLoRMXH56qJnEZ/dqsczaJOHQSi9mFEHL0r5rsEDTT5AVxdnBfNnyGaCH7/zANEko+F'
+ 'GBj1JdJaJgFTXdbxDoyoPTPD+LJqSK5XYToo46y/T0u9CLveNA==',
pem: ''
+ 'LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURCakNDQWU0Q0NRREkycVdkQTMvVnBEQU5C'
+ 'Z2txaGtpRzl3MEJBUVVGQURCRk1Rc3dDUVlEVlFRR0V3SkIKVlRFVE1CRUdBMVVFQ0F3S1UyOXRa'
+ 'UzFUZEdGMFpURWhNQjhHQTFVRUNnd1lTVzUwWlhKdVpYUWdWMmxrWjJsMApjeUJRZEhrZ1RIUmtN'
+ 'QjRYRFRFME1EY3hOakF4TXpNMU1Wb1hEVEUxTURjeE5qQXhNek0xTVZvd1JURUxNQWtHCkExVUVC'
+ 'aE1DUVZVeEV6QVJCZ05WQkFnTUNsTnZiV1V0VTNSaGRHVXhJVEFmQmdOVkJBb01HRWx1ZEdWeWJt'
+ 'VjAKSUZkcFpHZHBkSE1nVUhSNUlFeDBaRENDQVNJd0RRWUpLb1pJaHZjTkFRRUJCUUFEZ2dFUEFE'
+ 'Q0NBUW9DZ2dFQgpBTVV5Yml0bWhpNTlYVnlTZzRlRE1EeTFKSmR4eU9hUnB4dVduT0pmOUlvWnF2'
+ 'SE1BdmhWVDRHVEV1ZldzcElyCkFmc3d1NzdiNUNaUDI3L2dVamwrNk1YU0xMaTkzU2tCYWJsMTFY'
+ 'NjNXL2VjR0orRFB2NTR4SVZ6SllWVEIvemcKb2dJUFNTb1V5bGkraGtWbVFBSVFPNVFVWVlFalEw'
+ 'NGt3dWRRbTRpdThRT3FUZVByVUI4YUppVDE2MlVaR3dkcApKVG4xc0c3L2QyTnlkU2hRL290RHpF'
+ 'Qnk4TzY0Ukxwc1F2ZC9oeDFGV0ZSK3g5VXoyUjlXM1ZDU2ROb0VIc3BTCmJMR0FpY25XVDh6VS84'
+ 'N3JiUHlpVm5VYitaY2NCdmJ2YlREVElJU1c1Z0dqSkRCb3ZxT3p6Uk1qOTZoeGEyS1cKSWJPYWFp'
+ 'eHpFVFFlL0VEUFNIall5eGNDQXdFQUFUQU5CZ2txaGtpRzl3MEJBUVVGQUFPQ0FRRUFMNkFNTWZD'
+ 'MwpUbFJjbXNJZ0h4alZENFhZdElTbGxkbnJuMlg5enZGYkpLQ3BOeThYUVFvc1F4cmh5ZnpQSFFL'
+ 'amxTMkwvS0NHCk1uang5UWtZRDJIbHAxTUoxdVZ2OTg4OHRoL2djWk92M09yM2hReWk1SzFTaDV4'
+ 'Q0crNjlsVU9xVUVHdTlCNGkKcnNxb0ZvbVFWYlFvbFN5K3Q0YXBkSmk3a3VFRHdGRGs0Z1ppVkVm'
+ 'c3VYK25hTjVhNnBDblduaFgxVmY0Zkt3ZgprTG9iS0tYbTJ6UVZzanhsd0JBcU9FbUpHRExvUk1Y'
+ 'SDU2cUpuRVovZHFzY3phSk9IUVNpOW1GRUhMMHI1cnNFCkRUVDVBVnhkbkJmTm55R2FDSDcvekFO'
+ 'RWtvK0ZHQmoxSmRKYUpnRlRYZGJ4RG95b1BUUEQrTEpxU0s1WFlUb28KNDZ5L1QwdTlDTHZlTkE9'
+ 'PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg=='
priv: '' + 'LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcEFJQkFBS0NBUUVBeFRKdUsyYUdM' + 'bjFkWEpLRGg0TXdQTFVrbDNISTVwR25HNWFjNGwvMGlobXE4Y3dDCitGVlBnWk1TNTlheWtpc0Ir' + 'ekM3dnR2a0prL2J2K0JTT1g3b3hkSXN1TDNkS1FGcHVYWFZmcmRiOTV3WW40TSsKL25qRWhYTWxo' + 'Vk1IL09DaUFnOUpLaFRLV0w2R1JXWkFBaEE3bEJSaGdTTkRUaVRDNTFDYmlLN3hBNnBONCt0UQpI' + 'eG9tSlBYclpSa2JCMmtsT2ZXd2J2OTNZM0oxS0ZEK2kwUE1RSEx3N3JoRXVteEM5MytISFVWWVZI' + 'N0gxVFBaCkgxYmRVSkowMmdRZXlsSnNzWUNKeWRaUHpOVC96dXRzL0tKV2RSdjVseHdHOXU5dE1O' + 'TWdoSmJtQWFNa01HaSsKbzdQTkV5UDNxSEZyWXBZaHM1cHFMSE1STkI3OFFNOUllTmpMRndJREFR' + 'QUJBb0lCQVFERVJyalBiQUdjbmwxaAorZGIrOTczNGZ0aElBUkpWSko1dTRFK1JKcThSRWhGTEVL' + 'UFlKNW0yUC94dVZBMXpYV2xnYXhaRUZ6d1VRaUpZCjdsOEpLVjlwSHhReVlaQ1M4dndYZzhpWGtz' + 'dndQaWRvQmN1YW4vd0RWQ1FCZXk2VkxjVXpSYUd1Ui9sTHNYK1YKN2Z0QjBvUnFsSXFrYmNQZE1N' + 'dnFUeG93UnVoUG11Q3JWVGpPNHBiTnFuU09OUExPaUovRkFYYjJwZnpGZnBCUgpHeCtFTW16d2Ur' + 'SEZuSkJHRGhIWjk5bm4vVEJmYUp6TlZDcURZLzNid3o1WDdIUU5ZN1QrSnlUVUZzZVE5NHhzCnpy' + 'a2lidGRmVGNUanB1K1VoWm80c1p6Q3IrZkhHWm9FOUdEUHF0ZDRnQ3ByazRFS0pzbXFCRVN4QlhT' + 'RGhZZ04KOXBVRDM4c1pBb0dCQU9yZkRqdDZaL0ZDamFuVThXek5GaWYrOVQxQTJ4b013RDVWU2xN' + 'dVJyWW1HbGZyMEM5TQpmMUVvZ2l2dVRrYnA3cmtnZFRhWVRTYndmTnFaQkt4Y3R5YzdCaGRwWnhE' + 'RVdKa2Z5cThxVngvem1Cek1JK1ZzCjJLYi9hcHZXcmJlb3NET0NyeUg1YzhKc1VUOXhUWDNYYnhF' + 'anlPSlFCU1lHRE1qUHlKNkU5czZMQW9HQkFOYnYKd2d0S2Nra0tLbDJhNXZzaGR2RENnNnFLL1Fn' + 'T20vNktUSlVKRVNqaHoydFIrZlBWUjcwVEg5UmhoVFJscERXQgpCd3oyU2NCc1RRNDIvTGsxRnky' + 'MFQvck12S3VmSEw1VE1BNGZ6NWRxMUxIbmN6ejZVazVnWEtBT09rUjlVdVhpClR0eTNoREcyQkM4' + 'Nk1LTVJ4SjUxRWJxam94d0VSMTAwU2FuTVBmTWxBb0dBSUhLY1pyOHNhUHBHMC9XbFBPREEKZE5v' + 'V1MxWVFidkxnQkR5SVBpR2doejJRV2lFcjY3em53ZkNVdXpqNiszVUtFKzFXQkNyYVRjemZrdHVj' + 'OTZyLwphcDRPNDJFZWFnU1dNT0ZoZ1AyYWQ4R1JmRGovcEl4N0NlY3pkVUFkVThnc1A1R0lYR3M0' + 'QU40eUEwL0Y0dUxHCloxbklRT3ZKS2syZnFvWjZNdHd2dEswQ2dZRUFnSjdGTGVDRTkzUmYyZGdD' + 'ZFRHWGJZZlpKc3M1bEFLNkV0NUwKNmJ1ZFN5dWw1Z0VPWkgyekNsQlJjZFJSMUFNbSt1V1ZoSW8x' + 'cERLckFlQ2g1MnIvemRmakxLQXNIejkrQWQ3aQpHUEdzVmw0Vm5jaDFTMzQ0bHJKUGUzQklLZ2dj' + 'L1hncDNTYnNzcHJMY2orT0wyZElrOUpXbzZ1Y3hmMUJmMkwwCjJlbGhBUWtDZ1lCWHN5elZWL1pK' + 'cVhOcFdDZzU1TDNVRm9UTHlLU3FsVktNM1dpRzVCS240QWF6VkNITCtHUVUKeHd4U2dSOWZRNElu' + 'dStyUHJOM0lteWswbEtQR0Y5U3pDUlJUaUpGUjcyc05xbE82bDBWOENXUkFQVFBKY2dxVgoxVThO' + 'SEs4YjNaaUlvR0orbXNOenBkeHJqNjJIM0E2K1krQXNOWTRTbVVUWEg5eWpnK251a2c9PQotLS0t' + 'LUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo=',
pub: '' + 'LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FR' + 'OEFNSUlCQ2dLQ0FRRUF4VEp1SzJhR0xuMWRYSktEaDRNdwpQTFVrbDNISTVwR25HNWFjNGwvMGlo' + 'bXE4Y3dDK0ZWUGdaTVM1OWF5a2lzQit6Qzd2dHZrSmsvYnYrQlNPWDdvCnhkSXN1TDNkS1FGcHVY' + 'WFZmcmRiOTV3WW40TSsvbmpFaFhNbGhWTUgvT0NpQWc5SktoVEtXTDZHUldaQUFoQTcKbEJSaGdT' + 'TkRUaVRDNTFDYmlLN3hBNnBONCt0UUh4b21KUFhyWlJrYkIya2xPZld3YnY5M1kzSjFLRkQraTBQ' + 'TQpRSEx3N3JoRXVteEM5MytISFVWWVZIN0gxVFBaSDFiZFVKSjAyZ1FleWxKc3NZQ0p5ZFpQek5U' + 'L3p1dHMvS0pXCmRSdjVseHdHOXU5dE1OTWdoSmJtQWFNa01HaStvN1BORXlQM3FIRnJZcFloczVw' + 'cUxITVJOQjc4UU05SWVOakwKRndJREFRQUIKLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tCg==',
der: '' + 'MIIDBjCCAe4CCQDI2qWdA3/VpDANBgkqhkiG9w0BAQUFADBFMQswCQYDVQQGEwJBVTETMBEGA1UE' + 'CAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMB4XDTE0MDcx' + 'NjAxMzM1MVoXDTE1MDcxNjAxMzM1MVowRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3Rh' + 'dGUxITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDCCASIwDQYJKoZIhvcNAQEBBQAD' + 'ggEPADCCAQoCggEBAMUybitmhi59XVySg4eDMDy1JJdxyOaRpxuWnOJf9IoZqvHMAvhVT4GTEufW' + 'spIrAfswu77b5CZP27/gUjl+6MXSLLi93SkBabl11X63W/ecGJ+DPv54xIVzJYVTB/zgogIPSSoU' + 'yli+hkVmQAIQO5QUYYEjQ04kwudQm4iu8QOqTePrUB8aJiT162UZGwdpJTn1sG7/d2NydShQ/otD' + 'zEBy8O64RLpsQvd/hx1FWFR+x9Uz2R9W3VCSdNoEHspSbLGAicnWT8zU/87rbPyiVnUb+ZccBvbv' + 'bTDTIISW5gGjJDBovqOzzRMj96hxa2KWIbOaaixzETQe/EDPSHjYyxcCAwEAATANBgkqhkiG9w0B' + 'AQUFAAOCAQEAL6AMMfC3TlRcmsIgHxjVD4XYtISlldnrn2X9zvFbJKCpNy8XQQosQxrhyfzPHQKj' + 'lS2L/KCGMnjx9QkYD2Hlp1MJ1uVv9888th/gcZOv3Or3hQyi5K1Sh5xCG+69lUOqUEGu9B4irsqo' + 'FomQVbQolSy+t4apdJi7kuEDwFDk4gZiVEfsuX+naN5a6pCnWnhX1Vf4fKwfkLobKKXm2zQVsjxl' + 'wBAqOEmJGDLoRMXH56qJnEZ/dqsczaJOHQSi9mFEHL0r5rsEDTT5AVxdnBfNnyGaCH7/zANEko+F' + 'GBj1JdJaJgFTXdbxDoyoPTPD+LJqSK5XYToo46y/T0u9CLveNA==',
pem: '' + 'LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURCakNDQWU0Q0NRREkycVdkQTMvVnBEQU5C' + 'Z2txaGtpRzl3MEJBUVVGQURCRk1Rc3dDUVlEVlFRR0V3SkIKVlRFVE1CRUdBMVVFQ0F3S1UyOXRa' + 'UzFUZEdGMFpURWhNQjhHQTFVRUNnd1lTVzUwWlhKdVpYUWdWMmxrWjJsMApjeUJRZEhrZ1RIUmtN' + 'QjRYRFRFME1EY3hOakF4TXpNMU1Wb1hEVEUxTURjeE5qQXhNek0xTVZvd1JURUxNQWtHCkExVUVC' + 'aE1DUVZVeEV6QVJCZ05WQkFnTUNsTnZiV1V0VTNSaGRHVXhJVEFmQmdOVkJBb01HRWx1ZEdWeWJt' + 'VjAKSUZkcFpHZHBkSE1nVUhSNUlFeDBaRENDQVNJd0RRWUpLb1pJaHZjTkFRRUJCUUFEZ2dFUEFE' + 'Q0NBUW9DZ2dFQgpBTVV5Yml0bWhpNTlYVnlTZzRlRE1EeTFKSmR4eU9hUnB4dVduT0pmOUlvWnF2' + 'SE1BdmhWVDRHVEV1ZldzcElyCkFmc3d1NzdiNUNaUDI3L2dVamwrNk1YU0xMaTkzU2tCYWJsMTFY' + 'NjNXL2VjR0orRFB2NTR4SVZ6SllWVEIvemcKb2dJUFNTb1V5bGkraGtWbVFBSVFPNVFVWVlFalEw' + 'NGt3dWRRbTRpdThRT3FUZVByVUI4YUppVDE2MlVaR3dkcApKVG4xc0c3L2QyTnlkU2hRL290RHpF' + 'Qnk4TzY0Ukxwc1F2ZC9oeDFGV0ZSK3g5VXoyUjlXM1ZDU2ROb0VIc3BTCmJMR0FpY25XVDh6VS84' + 'N3JiUHlpVm5VYitaY2NCdmJ2YlREVElJU1c1Z0dqSkRCb3ZxT3p6Uk1qOTZoeGEyS1cKSWJPYWFp' + 'eHpFVFFlL0VEUFNIall5eGNDQXdFQUFUQU5CZ2txaGtpRzl3MEJBUVVGQUFPQ0FRRUFMNkFNTWZD' + 'MwpUbFJjbXNJZ0h4alZENFhZdElTbGxkbnJuMlg5enZGYkpLQ3BOeThYUVFvc1F4cmh5ZnpQSFFL' + 'amxTMkwvS0NHCk1uang5UWtZRDJIbHAxTUoxdVZ2OTg4OHRoL2djWk92M09yM2hReWk1SzFTaDV4' + 'Q0crNjlsVU9xVUVHdTlCNGkKcnNxb0ZvbVFWYlFvbFN5K3Q0YXBkSmk3a3VFRHdGRGs0Z1ppVkVm' + 'c3VYK25hTjVhNnBDblduaFgxVmY0Zkt3ZgprTG9iS0tYbTJ6UVZzanhsd0JBcU9FbUpHRExvUk1Y' + 'SDU2cUpuRVovZHFzY3phSk9IUVNpOW1GRUhMMHI1cnNFCkRUVDVBVnhkbkJmTm55R2FDSDcvekFO' + 'RWtvK0ZHQmoxSmRKYUpnRlRYZGJ4RG95b1BUUEQrTEpxU0s1WFlUb28KNDZ5L1QwdTlDTHZlTkE9' + 'PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg=='
};
x509.priv = new Buffer(x509.priv, 'base64');
@ -96,234 +16,125 @@ x509.pub = new Buffer(x509.pub, 'base64');
x509.der = new Buffer(x509.der, 'base64');
x509.pem = new Buffer(x509.pem, 'base64');
function startServer(cb) {
if (Wallet.request._server) {
setTimeout(function() {
return cb(null, Wallet.request._server);
}, 1);
return;
}
module.exports = {
outs: [{
address: 'mkYn9qmYwMZfovTb6cd7yCGeNozqUyyhK7',
amountSatStr: '3000'
}],
getRequest: function() {
var old = Wallet.request;
var uid = 0;
var server = {
GET: {
var outputs = [];
/**
* Receive "I want to pay"
*/
[2000, 1000].forEach(function(value) {
var po = new PayPro();
po = po.makeOutput();
// number of satoshis to be paid
po.set('amount', value);
'/-/request': function(req, cb) {
var res = {
statusCode: 200,
headers: {},
body: {}
};
var uid = 0;
var outputs = [];
[2000, 1000].forEach(function(value) {
var po = new PayPro();
po = po.makeOutput();
// number of satoshis to be paid
po.set('amount', value);
// a TxOut script where the payment should be sent. similar to OP_CHECKSIG
po.set('script', new Buffer([
118, // OP_DUP
169, // OP_HASH160
76, // OP_PUSHDATA1
20, // number of bytes
55,
48,
254,
188,
186,
4,
186,
208,
205,
71,
108,
251,
130,
15,
156,
55,
215,
70,
111,
217,
136, // OP_EQUALVERIFY
172 // OP_CHECKSIG
]));
outputs.push(po.message);
});
/**
* Payment Details
*/
var mdata = new Buffer([0]);
uid++;
if (uid > 0xffff) {
throw new Error('UIDs bigger than 0xffff not supported.');
} else if (uid > 0xff) {
mdata = new Buffer([(uid >> 8) & 0xff, (uid >> 0) & 0xff])
} else {
mdata = new Buffer([0, uid])
}
var now = Date.now() / 1000 | 0;
var pd = new PayPro();
pd = pd.makePaymentDetails();
pd.set('network', 'test');
pd.set('outputs', outputs);
pd.set('time', now);
pd.set('expires', now + 60 * 60 * 24);
pd.set('memo', 'Hello, this is the server, we would like some money.');
var port = +req.headers.host.split(':')[1] || server.port;
pd.set('payment_url', 'https://localhost:' + port + '/-/pay');
pd.set('merchant_data', mdata);
/*
* PaymentRequest
*/
var cr = new PayPro();
cr = cr.makeX509Certificates();
cr.set('certificate', [x509.der]);
// We send the PaymentRequest to the customer
var pr = new PayPro();
pr = pr.makePaymentRequest();
pr.set('payment_details_version', 1);
pr.set('pki_type', 'x509+sha256');
pr.set('pki_data', cr.serialize());
pr.set('serialized_payment_details', pd.serialize());
pr.sign(x509.priv);
pr = pr.serialize();
// BIP-71 - set the content-type
res.headers['Content-Type'] = PayPro.PAYMENT_REQUEST_CONTENT_TYPE;
res.headers['Content-Length'] = pr.length + '';
res.headers['Content-Transfer-Encoding'] = 'binary';
res.body = pr;
return cb(null, res, res.body);
},
},
/**
* Receive Payment
*/
POST: {
'/-/pay': function(req, cb) {
var body = req.body;
var res = {
statusCode: 200,
headers: {},
body: {}
};
body = PayPro.Payment.decode(body);
var pay = new PayPro();
pay = pay.makePayment(body);
var merchant_data = pay.get('merchant_data');
var transactions = pay.get('transactions');
var refund_to = pay.get('refund_to');
var memo = pay.get('memo');
// We send this to the customer after receiving a Payment
// Then we propogate the transaction through bitcoin network
var ack = new PayPro();
ack = ack.makePaymentACK();
ack.set('payment', pay.message);
ack.set('memo', 'Thank you for your payment!');
ack = ack.serialize();
// BIP-71 - set the content-type
res.headers['Content-Type'] = PayPro.PAYMENT_ACK_CONTENT_TYPE;
res.headers['Content-Length'] = ack.length + '';
res.headers['Content-Transfer-Encoding'] = 'binary';
transactions = transactions.map(function(tx) {
tx.buffer = new Buffer(new Uint8Array(tx.buffer));
tx.buffer = tx.buffer.slice(tx.offset, tx.limit);
var ptx = new bitcore.Transaction();
ptx.parse(tx.buffer);
return ptx;
});
res.body = ack;
return cb(null, res, res.body);
}
},
listen: function(port, cb) {
if (cb) return cb();
},
close: function(cb) {
Wallet.request = old;
return cb();
}
};
Wallet.request = function(options) {
var ret = {
success: function(cb) {
this._success = cb;
return this;
},
error: function(cb) {
this._error = cb;
return this;
},
_success: function() {
;
},
_error: function(_, err) {
throw err;
}
};
var method = (options.method || 'GET').toUpperCase();
var uri = options.uri || options.url;
var path = uri.replace(/^https?:\/\/[^\/]+/, '');
var req = options;
req.headers = req.headers || {};
req.body = req.data || req.body || {};
req.socket = {
remoteAddress: 'localhost'
};
req.headers['Host'] = 'localhost:8080';
Object.keys(req.headers).forEach(function(key) {
req.headers[key] = req.headers[key] + '';
req.headers[key.toLowerCase()] = req.headers[key] + '';
// TODO use bitcore / script!!
// a TxOut script where the payment should be sent. similar to OP_CHECKSIG
po.set('script', new Buffer([
118, // OP_DUP
169, // OP_HASH160
76, // OP_PUSHDATA1
20, // number of bytes
55,
48,
254,
188,
186,
4,
186,
208,
205,
71,
108,
251,
130,
15,
156,
55,
215,
70,
111,
217,
136, // OP_EQUALVERIFY
172 // OP_CHECKSIG
]));
outputs.push(po.message);
});
setTimeout(function() {
server[method][path](req, function(err, res, body) {
if (err) return ret._error(null, err, null, options);
Object.keys(res.headers).forEach(function(key) {
res.headers[key] = res.headers[key] + '';
res.headers[key.toLowerCase()] = res.headers[key] + '';
});
return ret._success(body, res.statusCode, res.headers, options);
});
}, 1);
return ret;
};
Wallet.request._server = server;
/**
* Payment Details
*/
setTimeout(function() {
return cb(null, server);
}, 1);
}
var mdata = new Buffer([0]);
uid++;
if (uid > 0xffff) {
throw new Error('UIDs bigger than 0xffff not supported.');
} else if (uid > 0xff) {
mdata = new Buffer([(uid >> 8) & 0xff, (uid >> 0) & 0xff])
} else {
mdata = new Buffer([0, uid])
}
var now = Date.now() / 1000 | 0;
var pd = new PayPro();
pd = pd.makePaymentDetails();
pd.set('network', 'test');
pd.set('outputs', outputs);
pd.set('time', now);
pd.set('expires', now + 60 * 60 * 24);
pd.set('memo', 'Hello, this is the server, we would like some money.');
pd.set('payment_url', 'https://pay_url');
pd.set('merchant_data', mdata);
module.exports = startServer;
/*
* PaymentRequest
*/
var cr = new PayPro();
cr = cr.makeX509Certificates();
cr.set('certificate', [x509.der]);
// We send the PaymentRequest to the customer
var pr = new PayPro();
pr = pr.makePaymentRequest();
pr.set('payment_details_version', 1);
pr.set('pki_type', 'x509+sha256');
pr.set('pki_data', cr.serialize());
pr.set('serialized_payment_details', pd.serialize());
pr.sign(x509.priv);
return pr.serialize();
},
processPayment: function(payment) {
body = PayPro.Payment.decode(payment);
var pay = new PayPro();
pay = pay.makePayment(body);
var merchant_data = pay.get('merchant_data');
var transactions = pay.get('transactions');
var refund_to = pay.get('refund_to');
var memo = pay.get('memo');
// We send this to the customer after receiving a Payment
// Then we propogate the transaction through bitcoin network
var ack = new PayPro();
ack = ack.makePaymentACK();
ack.set('payment', pay.message);
ack.set('memo', 'Thank you for your payment!');
ack = ack.serialize();
transactions = transactions.map(function(tx) {
tx.buffer = new Buffer(new Uint8Array(tx.buffer));
tx.buffer = tx.buffer.slice(tx.offset, tx.limit);
var ptx = new bitcore.Transaction();
ptx.parse(tx.buffer);
return ptx;
});
return ack;
},
};