add UX to payment acknoledged
This commit is contained in:
parent
9b327cd458
commit
6462968d5e
4 changed files with 63 additions and 60 deletions
|
|
@ -402,6 +402,9 @@ Identity.prototype.bindWallet = function(w) {
|
||||||
w.on('txProposalsUpdated', function() {
|
w.on('txProposalsUpdated', function() {
|
||||||
Identity.storeWalletDebounced(self, w);
|
Identity.storeWalletDebounced(self, w);
|
||||||
});
|
});
|
||||||
|
w.on('paymentAck', function() {
|
||||||
|
Identity.storeWalletDebounced(self, w);
|
||||||
|
});
|
||||||
w.on('newAddresses', function() {
|
w.on('newAddresses', function() {
|
||||||
Identity.storeWalletDebounced(self, w);
|
Identity.storeWalletDebounced(self, w);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ var preconditions = require('preconditions').instance();
|
||||||
|
|
||||||
var TX_MAX_SIZE_KB = 50;
|
var TX_MAX_SIZE_KB = 50;
|
||||||
var VERSION = 1;
|
var VERSION = 1;
|
||||||
var CORE_FIELDS = ['builderObj', 'inputChainPaths', 'version', 'comment', 'paymentProtocolURL'];
|
var CORE_FIELDS = ['builderObj', 'inputChainPaths', 'version', 'comment', 'paymentProtocolURL', 'paymentAckMemo'];
|
||||||
|
|
||||||
|
|
||||||
function TxProposal(opts) {
|
function TxProposal(opts) {
|
||||||
|
|
@ -38,6 +38,7 @@ function TxProposal(opts) {
|
||||||
this.comment = opts.comment || null;
|
this.comment = opts.comment || null;
|
||||||
this.readonly = opts.readonly || null;
|
this.readonly = opts.readonly || null;
|
||||||
this.merchant = opts.merchant || null;
|
this.merchant = opts.merchant || null;
|
||||||
|
this.paymentAckMemo = null;
|
||||||
this.paymentProtocolURL = opts.paymentProtocolURL || null;
|
this.paymentProtocolURL = opts.paymentProtocolURL || null;
|
||||||
|
|
||||||
if (opts.creator) {
|
if (opts.creator) {
|
||||||
|
|
|
||||||
|
|
@ -1505,13 +1505,10 @@ Wallet.prototype.broadcastTx = function(ntxid, cb) {
|
||||||
if (!tx.isComplete())
|
if (!tx.isComplete())
|
||||||
throw new Error('Tx is not complete. Can not broadcast');
|
throw new Error('Tx is not complete. Can not broadcast');
|
||||||
|
|
||||||
log.info('Wallet:' + this.id + ' Broadcasting Transaction ntxid:' + ntxid);
|
|
||||||
|
|
||||||
var serializedTx = tx.serialize();
|
var serializedTx = tx.serialize();
|
||||||
|
|
||||||
if (txp.merchant) {
|
log.info('Wallet:' + this.id + ' Broadcasting Transaction ntxid:' + ntxid);
|
||||||
this.sendPaymentTx(ntxid, serializedTx);
|
|
||||||
}
|
|
||||||
|
|
||||||
var txHex = serializedTx.toString('hex');
|
var txHex = serializedTx.toString('hex');
|
||||||
log.debug('\tRaw transaction: ', txHex);
|
log.debug('\tRaw transaction: ', txHex);
|
||||||
|
|
@ -1522,10 +1519,17 @@ Wallet.prototype.broadcastTx = function(ntxid, cb) {
|
||||||
|
|
||||||
if (txid) {
|
if (txid) {
|
||||||
log.debug('Wallet:' + self.getName() + ' broadcasted a TX. BITCOIND txid:', txid);
|
log.debug('Wallet:' + self.getName() + ' broadcasted a TX. BITCOIND txid:', txid);
|
||||||
var txp = self.txProposals.get(ntxid);
|
|
||||||
txp.setSent(txid);
|
txp.setSent(txid);
|
||||||
self.sendTxProposal(ntxid);
|
self.sendTxProposal(ntxid);
|
||||||
self.emitAndKeepAlive('txProposalsUpdated');
|
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);
|
return cb(null, txid, Wallet.TX_BROADCASTED);
|
||||||
} else {
|
} else {
|
||||||
log.info('Wallet:' + self.getName() + '. Sent failed. Checking if the TX was sent already');
|
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;
|
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
|
* @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) {
|
Wallet.prototype.sendPaymentTx = function(ntxid, txHex) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
var txp = this.txProposals.get(ntxid);
|
||||||
|
var data = this._createPaymentTx(txp, txHex);
|
||||||
|
|
||||||
var refund_outputs = [];
|
log.debug('Sending Payment Message to merchant server');
|
||||||
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 postInfo = {
|
var postInfo = {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: txp.merchant.pr.pd.payment_url,
|
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
|
// Technically how this should be done via XHR (used to
|
||||||
// be the ArrayBuffer, now you send the View instead).
|
// be the ArrayBuffer, now you send the View instead).
|
||||||
data: view,
|
data: data,
|
||||||
responseType: 'arraybuffer'
|
responseType: 'arraybuffer'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -1832,6 +1825,8 @@ Wallet.prototype.sendPaymentTx = function(ntxid, txHex) {
|
||||||
var ack = paypro.makePaymentACK(data);
|
var ack = paypro.makePaymentACK(data);
|
||||||
var memo = ack.get('memo');
|
var memo = ack.get('memo');
|
||||||
log.debug('Payment Acknowledged!: %s', memo);
|
log.debug('Payment Acknowledged!: %s', memo);
|
||||||
|
txp.paymentAckMemo = memo;
|
||||||
|
self.sendTxProposal(ntxid);
|
||||||
self.emitAndKeepAlive('paymentACK', memo);
|
self.emitAndKeepAlive('paymentACK', memo);
|
||||||
})
|
})
|
||||||
.error(function(data, status) {
|
.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.*
|
* Returns an Address object from an address string or a BIP21 URL.*
|
||||||
* @param address
|
* @param address
|
||||||
* @return { bitcore.Address }
|
* @return { bitcore.Address }
|
||||||
*/
|
*/
|
||||||
|
|
||||||
Wallet._newAddress = function(address) {
|
Wallet._getAddress = function(address) {
|
||||||
if (/ ^ bitcoin: /g.test(address)) {
|
if (/ ^ bitcoin: /g.test(address)) {
|
||||||
return new BIP21(address).address;
|
return new BIP21(address).address;
|
||||||
}
|
}
|
||||||
|
|
@ -2186,7 +2181,7 @@ Wallet.prototype._createTxProposal = function(toAddress, amountSat, comment, utx
|
||||||
|
|
||||||
var pkr = this.publicKeyRing;
|
var pkr = this.publicKeyRing;
|
||||||
var priv = this.privateKey;
|
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());
|
preconditions.checkState(addr && addr.data && addr.isValid(), 'Bad address:' + addr.toString());
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -136,6 +136,10 @@ angular.module('copayApp.services')
|
||||||
root.updateTxsAndBalance(w);
|
root.updateTxsAndBalance(w);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
w.on('paymentACK', function(memo) {
|
||||||
|
notification.success('Payment Acknowledged', memo);
|
||||||
|
});
|
||||||
|
|
||||||
w.on('txProposalEvent', function(e) {
|
w.on('txProposalEvent', function(e) {
|
||||||
|
|
||||||
root.updateTxsAndBalance(w);
|
root.updateTxsAndBalance(w);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue