add UX to payment acknoledged

This commit is contained in:
Matias Alejo Garcia 2014-11-23 19:19:05 -03:00
parent 9b327cd458
commit 6462968d5e
4 changed files with 80 additions and 77 deletions

View File

@ -402,6 +402,9 @@ Identity.prototype.bindWallet = function(w) {
w.on('txProposalsUpdated', function() {
Identity.storeWalletDebounced(self, w);
});
w.on('paymentAck', function() {
Identity.storeWalletDebounced(self, w);
});
w.on('newAddresses', function() {
Identity.storeWalletDebounced(self, w);
});

View File

@ -13,7 +13,7 @@ var preconditions = require('preconditions').instance();
var TX_MAX_SIZE_KB = 50;
var VERSION = 1;
var CORE_FIELDS = ['builderObj', 'inputChainPaths', 'version', 'comment', 'paymentProtocolURL'];
var CORE_FIELDS = ['builderObj', 'inputChainPaths', 'version', 'comment', 'paymentProtocolURL', 'paymentAckMemo'];
function TxProposal(opts) {
@ -38,6 +38,7 @@ function TxProposal(opts) {
this.comment = opts.comment || null;
this.readonly = opts.readonly || null;
this.merchant = opts.merchant || null;
this.paymentAckMemo = null;
this.paymentProtocolURL = opts.paymentProtocolURL || null;
if (opts.creator) {

View File

@ -1505,13 +1505,10 @@ Wallet.prototype.broadcastTx = function(ntxid, cb) {
if (!tx.isComplete())
throw new Error('Tx is not complete. Can not broadcast');
log.info('Wallet:' + this.id + ' Broadcasting Transaction ntxid:' + ntxid);
var serializedTx = tx.serialize();
if (txp.merchant) {
this.sendPaymentTx(ntxid, serializedTx);
}
log.info('Wallet:' + this.id + ' Broadcasting Transaction ntxid:' + ntxid);
var txHex = serializedTx.toString('hex');
log.debug('\tRaw transaction: ', txHex);
@ -1522,10 +1519,17 @@ Wallet.prototype.broadcastTx = function(ntxid, cb) {
if (txid) {
log.debug('Wallet:' + self.getName() + ' broadcasted a TX. BITCOIND txid:', txid);
var txp = self.txProposals.get(ntxid);
txp.setSent(txid);
self.sendTxProposal(ntxid);
self.emitAndKeepAlive('txProposalsUpdated');
// PAYPRO: Payment message is optional, only if payment_url is set
// This is async. and will notify and update txp async.
if (txp.merchant && txp.merchant.pr.pd.payment_url) {
self.sendPaymentTx(ntxid, serializedTx);
}
return cb(null, txid, Wallet.TX_BROADCASTED);
} else {
log.info('Wallet:' + self.getName() + '. Sent failed. Checking if the TX was sent already');
@ -1731,6 +1735,58 @@ Wallet.prototype.parsePaymentRequest = function(options, rawData) {
return merchantData;
};
/**
* _getPayProRefundOutputs
* Create refund address for PayPro.
* Uses current transaction's change address.
*
* @param txp
* @return {undefined}
*/
Wallet.prototype._getPayProRefundOutputs = function(txp) {
var pkr = this.publicKeyRing;
var index = pkr.getHDParams(this.publicKey);
var amount = +txp.merchant.total.toString(10);
var output = new PayPro.Output();
var script = pkr.getScriptPubKeyHex(index.changeIndex, true, this.pubkey);
output.set('script',new Buffer(script, 'hex'));
output.set('amount', amount);
return [output];
};
Wallet.prototype._createPaymentTx = function(txp, txHex) {
var refund_outputs = this._getPayProRefundOutputs(txp);
// We send this to the serve after receiving a PaymentRequest
var pay = new PayPro();
pay = pay.makePayment();
var merchant_data = txp.merchant.pr.pd.merchant_data;
if (merchant_data) {
merchant_data = new Buffer(merchant_data, 'hex');
pay.set('merchant_data', merchant_data);
}
pay.set('transactions', [txHex]);
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;
};
/**
* @desc Send a payment transaction to a server, complying with BIP70
*
@ -1741,73 +1797,10 @@ Wallet.prototype.parsePaymentRequest = function(options, rawData) {
*/
Wallet.prototype.sendPaymentTx = function(ntxid, txHex) {
var self = this;
var refund_outputs = [];
options.refund_to = this.publicKeyRing.getPubKeys(0, false, this.getMyCopayerId())[0];
if (options.refund_to) {
var total = txp.merchant.pr.pd.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
}));
}, bignum('0', 10));
var rpo = new PayPro();
rpo = rpo.makeOutput();
// XXX Bad - the amount *has* to be a Number in protobufjs
// Possibly does not matter - server can ignore the amount anyway.
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(options.refund_to),
new Buffer([
136, // OP_EQUALVERIFY
172 // OP_CHECKSIG
])
])
);
refund_outputs.push(rpo.message);
}
// We send this to the serve after receiving a PaymentRequest
var pay = new PayPro();
pay = pay.makePayment();
var merchant_data = txp.merchant.pr.pd.merchant_data;
if (merchant_data) {
merchant_data = new Buffer(merchant_data, 'hex');
pay.set('merchant_data', merchant_data);
}
pay.set('transactions', [serializedTx]);
pay.set('refund_to', refund_outputs);
// Unused for now
// options.memo = '';
// pay.set('memo', options.memo);
pay = pay.serialize();
log.debug('Sending Payment Message:', pay.toString('hex'));
var buf = new ArrayBuffer(pay.length);
var view = new Uint8Array(buf);
for (var i = 0; i < pay.length; i++) {
view[i] = pay[i];
}
var txp = this.txProposals.get(ntxid);
var data = this._createPaymentTx(txp, txHex);
log.debug('Sending Payment Message to merchant server');
var postInfo = {
method: 'POST',
url: txp.merchant.pr.pd.payment_url,
@ -1821,7 +1814,7 @@ Wallet.prototype.sendPaymentTx = function(ntxid, txHex) {
},
// Technically how this should be done via XHR (used to
// be the ArrayBuffer, now you send the View instead).
data: view,
data: data,
responseType: 'arraybuffer'
};
@ -1832,6 +1825,8 @@ Wallet.prototype.sendPaymentTx = function(ntxid, txHex) {
var ack = paypro.makePaymentACK(data);
var memo = ack.get('memo');
log.debug('Payment Acknowledged!: %s', memo);
txp.paymentAckMemo = memo;
self.sendTxProposal(ntxid);
self.emitAndKeepAlive('paymentACK', memo);
})
.error(function(data, status) {
@ -2131,13 +2126,13 @@ Wallet.prototype.spend = function(opts, cb) {
};
/**
* _newAddress
* _getAddress
* Returns an Address object from an address string or a BIP21 URL.*
* @param address
* @return { bitcore.Address }
*/
Wallet._newAddress = function(address) {
Wallet._getAddress = function(address) {
if (/ ^ bitcoin: /g.test(address)) {
return new BIP21(address).address;
}
@ -2186,7 +2181,7 @@ Wallet.prototype._createTxProposal = function(toAddress, amountSat, comment, utx
var pkr = this.publicKeyRing;
var priv = this.privateKey;
var addr = Wallet._newAddress(toAddress);
var addr = Wallet._getAddress(toAddress);
preconditions.checkState(addr && addr.data && addr.isValid(), 'Bad address:' + addr.toString());

View File

@ -136,6 +136,10 @@ angular.module('copayApp.services')
root.updateTxsAndBalance(w);
});
w.on('paymentACK', function(memo) {
notification.success('Payment Acknowledged', memo);
});
w.on('txProposalEvent', function(e) {
root.updateTxsAndBalance(w);