diff --git a/js/controllers/send.js b/js/controllers/send.js
index 22a92fb4f..d210d368f 100644
--- a/js/controllers/send.js
+++ b/js/controllers/send.js
@@ -111,8 +111,12 @@ angular.module('copayApp.controllers').controller('SendController',
$rootScope.pendingPayment = null;
}
- var uri = address.indexOf('bitcoin:') === 0
- && copay.HDPath.parseBitcoinURI(address);
+ var uri;
+ if (address.indexOf('bitcoin:') === 0) {
+ uri = copay.HDPath.parseBitcoinURI(address);
+ } else if (address.indexOf('Merchant: ') === 0) {
+ uri = { merchant: address.split(' ')[1] };
+ }
if (uri && uri.merchant) {
w.createPaymentTx({
diff --git a/js/directives.js b/js/directives.js
index 4a76f80a3..7c09521ac 100644
--- a/js/directives.js
+++ b/js/directives.js
@@ -14,6 +14,8 @@ angular.module('copayApp.directives')
var validator = function(value) {
var uri = copay.HDPath.parseBitcoinURI(value);
+ window._rootScope = scope;
+
// Is this a payment protocol URI (BIP-72)?
if (uri && uri.merchant) {
scope.wallet.fetchPaymentTx(uri.merchant, function(err, merchantData) {
@@ -44,6 +46,9 @@ angular.module('copayApp.directives')
// XXX There needs to be a better way to do this:
total = +total / config.unitToSatoshi;
+ var sendForm = angular.element(
+ document.getElementsByName('sendForm')[0]);
+
var address = angular.element(
document.querySelector('input#address'));
@@ -75,7 +80,13 @@ angular.module('copayApp.directives')
document.querySelector('[title="Send all funds"]'));
sendall.attr('class', sendall.attr('class') + ' hidden');
- address.on('change', function(ev) {
+ // Reset all the changes from the payment protocol weirdness.
+ // XXX use ng-change attr instead
+ //address.attr('ng-change', 'ppChange()');
+ //scope.ppChange = scope.ppChange || function() {
+ //address.on('change', function(ev) {
+ scope.$on('change', function(ev) {
+ //scope.$watch('address', function(newValue, oldValue) {
var val = address.val();
var uri = copay.HDPath.parseBitcoinURI(val || '');
if (!uri || !uri.merchant) {
@@ -85,6 +96,13 @@ angular.module('copayApp.directives')
if (amount.attr('disabled') === false) {
submit.attr('disabled', true);
}
+ sendto.html(sendto.html().replace(/
Server:.*$/, ''));
+ if (!/hidden/.test(tamount.attr('class'))) {
+ tamount.attr(tamount.attr('class') + ' hidden');
+ }
+ if (submit.attr('disabled') === false) {
+ submit.attr('disabled', true);
+ }
if (/ hidden$/.test(sendall.attr('class'))) {
sendall.attr('class',
sendall.attr('class').replace(' hidden', ''));
@@ -92,9 +110,22 @@ angular.module('copayApp.directives')
}
// TODO: Check paymentRequest expiration,
// delete if beyond expiration date.
+ //};
+ //});
});
+ //scope.$apply(); // scope.$digest();
ctrl.$setValidity('validAddress', true);
+
+ scope.sendForm.$valid = true;
+ scope.sendForm.$invalid = false;
+ scope.sendForm.$pristine = true;
+ scope.sendForm.address.$valid = true;
+ scope.sendForm.address.$invalid = false;
+ scope.sendForm.address.$pristine = true;
+ scope.sendForm.amount.$valid = true;
+ scope.sendForm.amount.$invalid = false;
+ scope.sendForm.amount.$pristine = true;
});
ctrl.$setValidity('validAddress', true);
diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js
index 66079d834..c1ceab8c9 100644
--- a/js/models/core/Wallet.js
+++ b/js/models/core/Wallet.js
@@ -25,15 +25,9 @@ var TxProposals = require('./TxProposals');
var PrivateKey = require('./PrivateKey');
var copayConfig = require('../../../config');
-if (typeof window !== 'undefined') {
- var G = window;
-} else {
- var G = global;
-}
-
-if (typeof angular !== 'undefined') {
- G.$http = G.$http || angular.bootstrap().get('$http');
-}
+var G = typeof window !== 'undefined'
+ ? window
+ : global;
function Wallet(opts) {
var self = this;
@@ -808,8 +802,9 @@ Wallet.prototype.createPaymentTx = function(options, cb) {
headers: {
'Accept': PayPro.PAYMENT_REQUEST_CONTENT_TYPE
+ ', ' + PayPro.PAYMENT_ACK_CONTENT_TYPE,
- 'Content-Type': 'application/octet-stream',
- 'Content-Length': 0
+ 'Content-Type': 'application/octet-stream'
+ // XHR does not allow these:
+ // 'Content-Length': 0
},
responseType: 'arraybuffer'
})
@@ -978,13 +973,13 @@ Wallet.prototype.sendPaymentTx = function(ntxid, options, cb) {
if (options.refund_to) {
// pubkey needs to be ripesha'd
- options.refund_to = bitcore.sha256ripe160(options.refund_to);
+ options.refund_to = bitcore.util.sha256ripe160(options.refund_to);
var total = txp.merchant.pr.pd.outputs.reduce(function(total, _, i) {
return total.add(bignum.fromBuffer(tx.outs[i].v, {
endian: 'little',
size: 1
}));
- }, bugnum('0', 10));
+ }, bignum('0', 10));
var rpo = new PayPro();
rpo = rpo.makeOutput();
// XXX Bad - the amount *has* to be a Number in protobufjs
@@ -1021,6 +1016,29 @@ Wallet.prototype.sendPaymentTx = function(ntxid, options, cb) {
pay.set('memo', options.memo);
+ pay = pay.serialize();
+
+ this.log(pay);
+ this.log(pay.toString('hex'));
+
+ // https://www.google.com/search?q=angular+%24http+ArrayBuffer+in+body
+ // https://github.com/feross/buffer/blob/master/index.js
+ // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Typed_arrays
+
+ // var view = new Uint8Array(new ArrayBuffer(pay.length));
+ // Buffer._augment(view);
+ // pay = pay.copy(view);
+
+ // var view = new Uint8Array(new ArrayBuffer(pay.length));
+ // view.set(Array.prototype.slice.call(pay), 0);
+ // pay = view;
+
+ var buf = new ArrayBuffer(pay.length);
+ var view = new Uint8Array(buf);
+ for (var i = 0; i < pay.length; i++) {
+ view[i] = pay[i];
+ }
+
return $http({
method: 'POST',
url: txp.merchant.pr.pd.payment_url,
@@ -1028,11 +1046,16 @@ Wallet.prototype.sendPaymentTx = function(ntxid, options, cb) {
// BIP-71
'Accept': PayPro.PAYMENT_REQUEST_CONTENT_TYPE
+ ', ' + PayPro.PAYMENT_ACK_CONTENT_TYPE,
- 'Content-Type': PayPro.PAYMENT_CONTENT_TYPE,
- 'Content-Length': pay.length + '',
- 'Content-Transfer-Encoding': 'binary'
+ 'Content-Type': PayPro.PAYMENT_CONTENT_TYPE
+ // XHR does not allow these:
+ // 'Content-Length': (pay.byteLength || pay.length) + '',
+ // 'Content-Transfer-Encoding': 'binary'
},
- body: pay.serialize(),
+ // data: pay,
+ // data: pay,
+ // data: view,
+ data: buf, // Technically how this should be done.
+ // requestType: 'arraybuffer',
responseType: 'arraybuffer'
})
.success(function(data, status, headers, config) {
@@ -1060,7 +1083,13 @@ Wallet.prototype.receivePaymentRequestACK = function(tx, txp, ack, cb) {
payment = pay.makePayment(payment);
var tx = payment.message.transactions[0];
+
+ if (!tx) {
+ return cb();
+ }
+
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);
@@ -1551,4 +1580,100 @@ Wallet.prototype.verifySignedJson = function(senderId, payload, signature) {
return v;
}
+// NOTE: Angular $http module does not send ArrayBuffers correctly, so we're
+// not going to use it. We'll have to write our own. Otherwise, we could
+// hex-encoded our messages and decode them on the other side, but that
+// deviates from BIP-70 slightly.
+// if (typeof angular !== 'undefined') {
+// G.$http = G.$http || angular.bootstrap().get('$http');
+// }
+
+G.$http = G.$http || function $http(options, callback) {
+ if (typeof options === 'string') {
+ options = { uri: 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 uri = options.uri || options.url;
+ var req = options;
+
+ req.headers = req.headers || {};
+ req.body = req.body || {};
+
+ if (typeof XMLHttpRequest !== 'undefined') {
+ var xhr = new XMLHttpRequest();
+ xhr.open(method, uri, true);
+
+ Object.keys(options.headers).forEach(function(key) {
+ var val = options.headers[key];
+ if (key === 'Content-Length') return;
+ if (key === 'Content-Transfer-Encoding') return;
+ xhr.setRequestHeader(key, val);
+ });
+
+ // For older browsers (binary data):
+ // xhr.overrideMimeType('text/plain; charset=x-user-defined');
+
+ // Newer browsers (binary data):
+ // xhr.responseType = 'arraybuffer';
+
+ if (options.responseType) {
+ xhr.responseType = options.responseType;
+ }
+
+ // xhr.onreadystatechange = function() {
+ // if (xhr.readyState == 4) {
+ // ;
+ // }
+ // };
+
+ 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) {
+ return ret._error(null, new Error(event.message), null, options);
+ };
+
+ if (options.data || options.body) {
+ xhr.send(options.data || options.body);
+ } else {
+ xhr.send(null);
+ }
+
+ return ret;
+ }
+
+ return ret;
+};
+
module.exports = require('soop')(Wallet);