Wallet/js/controllers/send.js

573 lines
16 KiB
JavaScript
Raw Normal View History

2014-03-26 09:18:42 -03:00
'use strict';
2014-06-12 17:42:26 -03:00
var bitcore = require('bitcore');
2014-09-05 16:16:06 -07:00
var preconditions = require('preconditions').singleton();
2014-03-26 09:18:42 -03:00
2014-06-03 17:42:36 -03:00
angular.module('copayApp.controllers').controller('SendController',
2015-01-03 20:32:31 -03:00
function($scope, $rootScope, $window, $timeout, $modal, $filter, notification, isMobile, rateService, txStatus, isCordova) {
$scope.init = function() {
2014-12-09 16:55:32 -03:00
var w = $rootScope.wallet;
preconditions.checkState(w);
2014-12-30 10:37:04 -03:00
preconditions.checkState(w.settings.unitToSatoshi);
2015-01-03 19:41:01 -03:00
$scope.isMobile = isMobile.any();
$scope.isWindowsPhoneApp = isMobile.Windows() && isCordova;
$rootScope.wpInputFocused = false;
2014-12-30 10:37:04 -03:00
$scope.isShared = w.isShared();
2014-12-31 01:37:47 -03:00
$scope.requiresMultipleSignatures = w.requiresMultipleSignatures();
2015-01-03 19:41:01 -03:00
$rootScope.title = $scope.requiresMultipleSignatures ? 'Send Proposal' : 'Send';
$scope.loading = false;
$scope.error = $scope.success = null;
$scope.alternativeName = w.settings.alternativeName;
$scope.alternativeIsoCode = w.settings.alternativeIsoCode;
$scope.isRateAvailable = false;
$scope.rateService = rateService;
$scope.showScanner = false;
$scope.myId = w.getMyCopayerId();
$scope.isMobile = isMobile.any();
if ($rootScope.pendingPayment) {
2014-12-09 15:46:03 -03:00
$timeout(function() {
$scope.setFromUri($rootScope.pendingPayment)
$rootScope.pendingPayment = null;
2014-12-09 16:55:32 -03:00
}, 100);
}
$scope.setInputs();
$scope.setScanner();
2014-11-29 18:35:48 -03:00
rateService.whenAvailable(function() {
$scope.isRateAvailable = true;
$scope.$digest();
2014-09-10 15:09:11 -03:00
});
2015-01-03 19:41:01 -03:00
};
if (isCordova) {
var openScannerCordova = $rootScope.$on('dataScanned', function(event, data) {
$scope.sendForm.address.$setViewValue(data);
$scope.sendForm.address.$render();
});
$scope.$on('$destroy', function() {
openScannerCordova();
});
}
2015-01-03 19:41:01 -03:00
$scope.formFocus = function(what) {
if (!$scope.isWindowsPhoneApp) return
if (!what) {
$rootScope.wpInputFocused = false;
$scope.hideAddress = false;
$scope.hideAmount = false;
} else {
$rootScope.wpInputFocused = true;
if (what == 'amount') {
$scope.hideAddress = true;
} else if (what == 'msg') {
$scope.hideAddress = true;
$scope.hideAmount = true;
}
}
$timeout(function() {
$rootScope.$digest();
2015-01-03 21:04:00 -03:00
}, 1);
2015-01-03 19:41:01 -03:00
};
2014-10-29 18:34:59 -03:00
$scope.setInputs = function() {
2014-12-09 16:55:32 -03:00
var w = $rootScope.wallet;
var unitToSat = w.settings.unitToSatoshi;
2015-01-30 12:25:54 -03:00
var satToUnit = 1 / unitToSat;
/**
* Setting the two related amounts as properties prevents an infinite
* recursion for watches while preserving the original angular updates
*
*/
Object.defineProperty($scope,
2014-12-08 23:45:34 -03:00
"_alternative", {
get: function() {
2014-12-08 23:45:34 -03:00
return this.__alternative;
},
set: function(newValue) {
2014-12-08 23:45:34 -03:00
this.__alternative = newValue;
if (typeof(newValue) === 'number' && $scope.isRateAvailable) {
this._amount = parseFloat(
2015-01-30 12:25:54 -03:00
(rateService.fromFiat(newValue, $scope.alternativeIsoCode) * satToUnit).toFixed(w.settings.unitDecimals), 10);
} else {
this._amount = 0;
}
},
enumerable: true,
configurable: true
});
Object.defineProperty($scope,
2014-12-08 23:45:34 -03:00
"_amount", {
get: function() {
2014-12-08 23:45:34 -03:00
return this.__amount;
},
set: function(newValue) {
2014-12-08 23:45:34 -03:00
this.__amount = newValue;
if (typeof(newValue) === 'number' && $scope.isRateAvailable) {
2014-12-08 23:45:34 -03:00
this.__alternative = parseFloat(
(rateService.toFiat(newValue * unitToSat, $scope.alternativeIsoCode)).toFixed(2), 10);
} else {
2014-12-08 23:45:34 -03:00
this.__alternative = 0;
}
},
enumerable: true,
configurable: true
});
Object.defineProperty($scope,
2014-12-08 23:45:34 -03:00
"_address", {
get: function() {
2014-12-08 23:45:34 -03:00
return this.__address;
},
set: function(newValue) {
2014-12-08 23:45:34 -03:00
this.__address = $scope.onAddressChange(newValue);
},
enumerable: true,
configurable: true
});
};
2014-11-12 18:32:18 -03:00
$scope.setScanner = function() {
2014-12-08 19:31:15 -03:00
navigator.getUserMedia = navigator.getUserMedia ||
navigator.webkitGetUserMedia || navigator.mozGetUserMedia ||
navigator.msGetUserMedia;
2014-12-08 19:31:15 -03:00
window.URL = window.URL || window.webkitURL ||
window.mozURL || window.msURL;
if (!window.cordova && !navigator.getUserMedia)
$scope.disableScanner = 1;
};
2014-09-08 17:07:43 -03:00
2014-12-08 23:45:34 -03:00
$scope.setError = function(err) {
2014-12-09 17:22:58 -03:00
var w = $rootScope.wallet;
2014-12-08 23:45:34 -03:00
copay.logger.warn(err);
var msg = err.toString();
if (msg.match('BIG'))
2014-11-23 15:52:39 -03:00
msg = 'The transaction have too many inputs. Try creating many transactions for smaller amounts'
if (msg.match('totalNeededAmount') || msg.match('unspent not set'))
msg = 'Insufficient funds'
2014-12-05 17:59:15 -03:00
2014-12-09 01:29:06 -03:00
if (msg.match('expired'))
msg = 'The payment request has expired';
2014-12-31 01:37:47 -03:00
var message = 'The transaction' + ($scope.requiresMultipleSignatures ? ' proposal' : '') +
2014-11-23 15:52:39 -03:00
' could not be created: ' + msg;
$scope.error = message;
2014-12-08 23:45:34 -03:00
2014-12-09 01:29:06 -03:00
$timeout(function() {
2014-12-08 23:45:34 -03:00
$scope.$digest();
2014-12-09 01:29:06 -03:00
}, 1);
};
$scope.submitForm = function(form) {
2014-12-09 16:55:32 -03:00
var w = $rootScope.wallet;
var unitToSat = w.settings.unitToSatoshi;
2014-12-09 01:29:06 -03:00
if (form.$invalid) {
2014-11-03 16:06:17 -03:00
$scope.error = 'Unable to send transaction proposal';
return;
}
2014-04-24 22:43:19 -03:00
$scope.loading = true;
2015-01-03 20:32:31 -03:00
$scope.creatingTX = true;
if ($scope.isWindowsPhoneApp)
$rootScope.wpInputFocused = true;
$timeout(function () {
var comment = form.comment.$modelValue;
var merchantData = $scope._merchantData;
var address, amount;
if (!merchantData) {
2015-01-03 20:32:31 -03:00
address = form.address.$modelValue;
amount = parseInt((form.amount.$modelValue * unitToSat).toFixed(0));
}
w.spend({
2015-01-03 20:32:31 -03:00
merchantData: merchantData,
toAddress: address,
amountSat: amount,
comment: comment,
}, function (err, txid, status) {
2015-01-03 20:32:31 -03:00
$scope.loading = false;
$scope.creatingTX = false;
if ($scope.isWindowsPhoneApp)
$rootScope.wpInputFocused = false;
if (err)
return $scope.setError(err);
txStatus.notify(status);
$scope.resetForm();
});
2015-01-04 12:39:08 -03:00
}, 1);
2014-05-06 17:02:49 -03:00
};
// QR code Scanner
var cameraInput;
var video;
var canvas;
var $video;
var context;
var localMediaStream;
var _scan = function(evt) {
if ($scope.isMobile) {
$scope.scannerLoading = true;
var files = evt.target.files;
if (files.length === 1 && files[0].type.indexOf('image/') === 0) {
var file = files[0];
var reader = new FileReader();
reader.onload = (function(theFile) {
return function(e) {
var mpImg = new MegaPixImage(file);
2014-06-12 17:42:26 -03:00
mpImg.render(canvas, {
maxWidth: 200,
maxHeight: 200,
orientation: 6
});
2014-05-06 17:02:49 -03:00
$timeout(function() {
qrcode.width = canvas.width;
qrcode.height = canvas.height;
qrcode.imagedata = context.getImageData(0, 0, qrcode.width, qrcode.height);
try {
qrcode.decode();
} catch (e) {
2014-06-03 18:38:56 -03:00
// error decoding QR
2014-05-06 17:02:49 -03:00
}
}, 1500);
};
})(file);
// Read in the file as a data URL
reader.readAsDataURL(file);
}
} else {
if (localMediaStream) {
context.drawImage(video, 0, 0, 300, 225);
try {
qrcode.decode();
2014-06-12 17:42:26 -03:00
} catch (e) {
2014-05-06 17:02:49 -03:00
//qrcodeError(e);
}
}
$timeout(_scan, 500);
}
};
var _successCallback = function(stream) {
video.src = (window.URL && window.URL.createObjectURL(stream)) || stream;
localMediaStream = stream;
video.play();
$timeout(_scan, 1000);
};
var _scanStop = function() {
$scope.scannerLoading = false;
$scope.showScanner = false;
if (!$scope.isMobile) {
if (localMediaStream && localMediaStream.stop) localMediaStream.stop();
localMediaStream = null;
video.src = '';
}
};
var _videoError = function(err) {
_scanStop();
};
qrcode.callback = function(data) {
_scanStop();
2014-05-07 11:21:30 -03:00
$scope.$apply(function() {
$scope.sendForm.address.$setViewValue(data);
2014-11-03 15:13:53 -03:00
$scope.sendForm.address.$render();
2014-05-07 11:21:30 -03:00
});
2014-05-06 17:02:49 -03:00
};
$scope.cancelScanner = function() {
_scanStop();
};
$scope.openScanner = function() {
$scope.showScanner = true;
// Wait a moment until the canvas shows
$timeout(function() {
canvas = document.getElementById('qr-canvas');
context = canvas.getContext('2d');
if ($scope.isMobile) {
cameraInput = document.getElementById('qrcode-camera');
cameraInput.addEventListener('change', _scan, false);
} else {
video = document.getElementById('qrcode-scanner-video');
$video = angular.element(video);
canvas.width = 300;
canvas.height = 225;
context.clearRect(0, 0, 300, 225);
2014-04-24 22:43:19 -03:00
2014-06-12 17:42:26 -03:00
navigator.getUserMedia({
video: true
}, _successCallback, _videoError);
2014-05-06 17:02:49 -03:00
}
}, 500);
2014-04-24 22:43:19 -03:00
};
2014-06-18 01:00:32 -03:00
2014-11-12 18:32:18 -03:00
$scope.setTopAmount = function() {
2014-12-09 16:55:32 -03:00
var w = $rootScope.wallet;
2014-12-08 23:45:34 -03:00
var form = $scope.sendForm;
2014-12-09 16:55:32 -03:00
if (form) {
form.amount.$setViewValue(w.balanceInfo.topAmount);
form.amount.$render();
form.amount.$isValid = true;
}
2014-06-19 15:55:04 -03:00
};
$scope.setForm = function(to, amount, comment) {
var form = $scope.sendForm;
2014-12-09 01:29:06 -03:00
if (to) {
form.address.$setViewValue(to);
form.address.$isValid = true;
form.address.$render();
$scope.lockAddress = true;
}
2014-12-08 19:31:15 -03:00
if (amount) {
2014-12-09 01:29:06 -03:00
form.amount.$setViewValue("" + amount);
2014-12-08 19:31:15 -03:00
form.amount.$isValid = true;
2014-12-08 23:45:34 -03:00
form.amount.$render();
2014-12-08 19:31:15 -03:00
$scope.lockAmount = true;
}
2014-12-08 19:31:15 -03:00
if (comment) {
2014-12-08 23:45:34 -03:00
form.comment.$setViewValue(comment);
form.comment.$isValid = true;
form.comment.$render();
2014-12-08 19:31:15 -03:00
}
};
2014-12-10 18:18:28 -03:00
$scope.resetForm = function() {
var form = $scope.sendForm;
$scope.fetchingURL = null;
2014-12-09 01:29:06 -03:00
$scope._merchantData = $scope._domain = null;
2014-12-08 23:45:34 -03:00
$scope.lockAddress = false;
$scope.lockAmount = false;
2014-12-09 01:29:06 -03:00
$scope._amount = $scope._address = null;
form.amount.$pristine = true;
form.amount.$setViewValue('');
form.amount.$render();
2014-12-09 01:29:06 -03:00
form.comment.$setViewValue('');
form.comment.$render();
form.$setPristine();
2014-12-08 23:45:34 -03:00
2014-12-09 12:47:19 -03:00
if (form.address) {
form.address.$pristine = true;
form.address.$setViewValue('');
form.address.$render();
}
2014-12-09 01:29:06 -03:00
$timeout(function() {
2014-12-08 23:45:34 -03:00
$rootScope.$digest();
2014-12-09 01:29:06 -03:00
}, 1);
};
2014-12-09 01:29:06 -03:00
var $oscope = $scope;
$scope.openPPModal = function(merchantData) {
var ModalInstanceCtrl = function($scope, $modalInstance) {
2014-12-09 01:29:06 -03:00
$scope.md = merchantData;
$scope.alternative = $oscope._alternative;
$scope.alternativeIsoCode = $oscope.alternativeIsoCode;
$scope.isRateAvailable = $oscope.isRateAvailable;
$scope.cancel = function() {
$modalInstance.dismiss('cancel');
};
};
$modal.open({
templateUrl: 'views/modals/paypro.html',
windowClass: 'tiny',
controller: ModalInstanceCtrl,
});
};
2014-12-08 19:31:15 -03:00
$scope.setFromPayPro = function(uri) {
2014-12-19 19:10:34 -03:00
var isChromeApp = window.chrome && chrome.runtime && chrome.runtime.id;
if (isChromeApp) {
$scope.error = 'Payment Protocol not yet supported on ChromeApp';
return;
}
2014-12-09 16:55:32 -03:00
var w = $rootScope.wallet;
2015-01-30 12:25:54 -03:00
var satToUnit = 1 / w.settings.unitToSatoshi;
2014-12-08 19:31:15 -03:00
$scope.fetchingURL = uri;
2014-11-20 03:10:43 -03:00
$scope.loading = true;
2014-12-19 19:10:34 -03:00
// Payment Protocol URI (BIP-72)
2014-12-08 19:31:15 -03:00
w.fetchPaymentRequest({
url: uri
2014-11-23 15:52:39 -03:00
}, function(err, merchantData) {
2014-11-20 03:10:43 -03:00
$scope.loading = false;
$scope.fetchingURL = null;
if (err) {
2014-12-09 01:29:06 -03:00
copay.logger.warn(err);
2014-12-10 18:18:28 -03:00
$scope.resetForm();
2014-12-10 20:59:05 -03:00
var msg = err.toString();
if (msg.match('HTTP')) {
msg = 'Could not fetch payment information';
}
$scope.error = msg;
} else {
2014-12-09 01:29:06 -03:00
$scope._merchantData = merchantData;
$scope._domain = merchantData.domain;
2015-01-30 12:25:54 -03:00
$scope.setForm(null, (merchantData.total * satToUnit).toFixed(w.settings.unitDecimals));
}
});
};
2014-12-08 19:31:15 -03:00
$scope.setFromUri = function(uri) {
2014-12-19 16:01:58 -03:00
function sanitizeUri(uri) {
// Fixes when a region uses comma to separate decimals
var regex = /[\?\&]amount=(\d+([\,\.]\d+)?)/i;
var match = regex.exec(uri);
if (!match || match.length === 0) {
return uri;
}
var value = match[0].replace(',', '.');
var newUri = uri.replace(regex, value);
return newUri;
};
2015-01-29 17:48:28 -03:00
var w = $rootScope.wallet;
2015-01-30 12:25:54 -03:00
var satToUnit = 1 / w.settings.unitToSatoshi;
2014-12-08 19:31:15 -03:00
var form = $scope.sendForm;
2014-12-19 16:01:58 -03:00
uri = sanitizeUri(uri);
2014-12-08 19:31:15 -03:00
var parsed = new bitcore.BIP21(uri);
if (!parsed.isValid() || !parsed.address.isValid()) {
$scope.error = 'Invalid bitcoin URL';
form.address.$isValid = false;
return uri;
};
var addr = parsed.address.toString();
if (parsed.data.merchant)
return $scope.setFromPayPro(parsed.data.merchant);
var amount = (parsed.data && parsed.data.amount) ?
2015-01-30 12:25:54 -03:00
((parsed.data.amount * 100000000).toFixed(0) * satToUnit).toFixed(w.settings.unitDecimals): 0;
2014-12-08 19:31:15 -03:00
$scope.setForm(addr, amount, parsed.data.message, true);
return addr;
};
$scope.onAddressChange = function(value) {
$scope.error = $scope.success = null;
if (!value) return '';
if (value.indexOf('bitcoin:') === 0) {
return $scope.setFromUri(value);
} else if (/^https?:\/\//.test(value)) {
return $scope.setFromPayPro(value);
}
2014-12-09 15:13:17 -03:00
2014-12-08 19:31:15 -03:00
return value;
};
$scope.openAddressBook = function() {
2014-12-09 16:55:32 -03:00
var w = $rootScope.wallet;
var modalInstance = $modal.open({
templateUrl: 'views/modals/address-book.html',
windowClass: 'large',
controller: function($scope, $modalInstance) {
$scope.showForm = null;
$scope.addressBook = w.addressBook;
$scope.hasEntry = function() {
return _.keys($scope.addressBook).length > 0 ? true : false;
};
$scope.toggleAddressBookEntry = function(key) {
w.toggleAddressBookEntry(key);
};
$scope.copyToSend = function(addr) {
$modalInstance.close(addr);
};
2014-12-11 02:02:12 -03:00
$scope.cancel = function(form) {
$scope.error = $scope.success = null;
$scope.toggleForm();
};
$scope.toggleForm = function() {
$scope.showForm = !$scope.showForm;
};
2014-12-09 12:47:19 -03:00
// TODO change to modal
$scope.submitAddressBook = function(form) {
if (form.$invalid) {
return;
}
$timeout(function() {
var errorMsg;
var entry = {
"address": form.newaddress.$modelValue,
"label": form.newlabel.$modelValue
};
try {
w.setAddressBook(entry.address, entry.label);
} catch (e) {
2014-12-09 12:47:19 -03:00
copay.logger.warn(e);
errorMsg = e.message;
}
if (errorMsg) {
$scope.error = errorMsg;
} else {
$scope.toggleForm();
2014-12-09 12:47:19 -03:00
notification.success('Entry created', 'New addressbook entry created')
}
$rootScope.$digest();
2014-12-09 12:47:19 -03:00
}, 1);
return;
};
$scope.close = function() {
$modalInstance.dismiss('cancel');
};
},
});
modalInstance.result.then(function(addr) {
2014-12-09 15:13:17 -03:00
$scope.setForm(addr);
});
};
2014-09-03 16:24:25 -03:00
});