Merge pull request #1229 from chjj/paypro

WIP Reimplement Payment Protocol
This commit is contained in:
Manuel Aráoz 2014-09-08 19:18:41 -07:00
commit f6e9084548
7 changed files with 44270 additions and 547 deletions

View file

@ -103,7 +103,7 @@ angular.module('copayApp.controllers').controller('SendController',
window.URL = window.URL || window.webkitURL || window.mozURL || window.msURL; window.URL = window.URL || window.webkitURL || window.mozURL || window.msURL;
$scope.isMobile = isMobile.any(); $scope.isMobile = isMobile.any();
if (!window.cordova && !navigator.getUserMedia) if (!window.cordova && !navigator.getUserMedia)
$scope.disableScanner =1; $scope.disableScanner =1;
$scope.submitForm = function(form) { $scope.submitForm = function(form) {
@ -169,26 +169,32 @@ angular.module('copayApp.controllers').controller('SendController',
$rootScope.pendingPayment = null; $rootScope.pendingPayment = null;
} }
// XXX Payment Protocol is temporarily disabled. var uri;
// var uri; if (address.indexOf('bitcoin:') === 0) {
// if (address.indexOf('bitcoin:') === 0) { uri = new bitcore.BIP21(address).data;
// uri = new bitcore.BIP21(address).data; } else if (/^https?:\/\//.test(address)) {
// } else if (/^https?:\/\//.test(address)) { uri = {
// uri = { merchant: address
// merchant: address };
// }; }
// }
//
// if (uri && uri.merchant) {
// w.createPaymentTx({
// uri: uri.merchant,
// memo: commentText
// }, done);
// } else {
// w.createTx(address, amount, commentText, done);
// }
w.createTx(address, amount, commentText, done); // If we're setting the domain, ignore the change.
if ($rootScope.merchant
&& $rootScope.merchant.domain
&& address === $rootScope.merchant.domain) {
uri = {
merchant: $rootScope.merchant.request_url
};
}
if (uri && uri.merchant) {
w.createPaymentTx({
uri: uri.merchant,
memo: commentText
}, done);
} else {
w.createTx(address, amount, commentText, done);
}
// reset fields // reset fields
$scope.address = $scope.amount = $scope.commentText = null; $scope.address = $scope.amount = $scope.commentText = null;
@ -464,6 +470,13 @@ angular.module('copayApp.controllers').controller('SendController',
var value = scope.address || ''; var value = scope.address || '';
var uri; var uri;
// If we're setting the domain, ignore the change.
if ($rootScope.merchant
&& $rootScope.merchant.domain
&& value === $rootScope.merchant.domain) {
return;
}
if (value.indexOf('bitcoin:') === 0) { if (value.indexOf('bitcoin:') === 0) {
uri = new bitcore.BIP21(value).data; uri = new bitcore.BIP21(value).data;
} else if (/^https?:\/\//.test(value)) { } else if (/^https?:\/\//.test(value)) {
@ -520,12 +533,18 @@ angular.module('copayApp.controllers').controller('SendController',
return; return;
} }
var url = merchantData.request_url;
var domain = /^(?:https?)?:\/\/([^\/:]+).*$/.exec(url)[1];
merchantData.unitTotal = (+merchantData.total / config.unitToSatoshi) + ''; merchantData.unitTotal = (+merchantData.total / config.unitToSatoshi) + '';
merchantData.expiration = new Date( merchantData.expiration = new Date(
merchantData.pr.pd.expires * 1000).toISOString(); merchantData.pr.pd.expires * 1000).toISOString();
merchantData.domain = domain;
$rootScope.merchant = merchantData; $rootScope.merchant = merchantData;
scope.sendForm.address.$setViewValue(domain);
scope.sendForm.address.$render();
scope.sendForm.address.$isValid = true; scope.sendForm.address.$isValid = true;
scope.sendForm.amount.$setViewValue(merchantData.unitTotal); scope.sendForm.amount.$setViewValue(merchantData.unitTotal);
@ -537,6 +556,14 @@ angular.module('copayApp.controllers').controller('SendController',
var unregister = scope.$watch('address', function() { var unregister = scope.$watch('address', function() {
var val = scope.sendForm.address.$viewValue || ''; var val = scope.sendForm.address.$viewValue || '';
var uri; var uri;
// If we're setting the domain, ignore the change.
if ($rootScope.merchant
&& $rootScope.merchant.domain
&& val === $rootScope.merchant.domain) {
uri = {
merchant: $rootScope.merchant.request_url
};
}
if (val.indexOf('bitcoin:') === 0) { if (val.indexOf('bitcoin:') === 0) {
uri = new bitcore.BIP21(val).data; uri = new bitcore.BIP21(val).data;
} else if (/^https?:\/\//.test(val)) { } else if (/^https?:\/\//.test(val)) {
@ -564,9 +591,4 @@ angular.module('copayApp.controllers').controller('SendController',
}); });
}; };
// XXX Payment Protocol is temporarily disabled.
$scope.onChanged = function() {
;
};
}); });

View file

@ -1,7 +1,7 @@
'use strict'; 'use strict';
angular.module('copayApp.directives') angular.module('copayApp.directives')
.directive('validAddress', function() { .directive('validAddress', ['$rootScope', function($rootScope) {
var bitcore = require('bitcore'); var bitcore = require('bitcore');
var Address = bitcore.Address; var Address = bitcore.Address;
var bignum = bitcore.Bignum; var bignum = bitcore.Bignum;
@ -11,6 +11,14 @@ angular.module('copayApp.directives')
link: function(scope, elem, attrs, ctrl) { link: function(scope, elem, attrs, ctrl) {
var validator = function(value) { var validator = function(value) {
// If we're setting the domain, ignore the change.
if ($rootScope.merchant
&& $rootScope.merchant.domain
&& value === $rootScope.merchant.domain) {
ctrl.$setValidity('validAddress', true);
return value;
}
// Regular url // Regular url
if (/^https?:\/\//.test(value)) { if (/^https?:\/\//.test(value)) {
ctrl.$setValidity('validAddress', true); ctrl.$setValidity('validAddress', true);
@ -35,7 +43,7 @@ angular.module('copayApp.directives')
ctrl.$formatters.unshift(validator); ctrl.$formatters.unshift(validator);
} }
}; };
}) }])
.directive('enoughAmount', ['$rootScope', .directive('enoughAmount', ['$rootScope',
function($rootScope) { function($rootScope) {
var bitcore = require('bitcore'); var bitcore = require('bitcore');

View file

@ -108,8 +108,8 @@ inherits(Wallet, events.EventEmitter);
Wallet.builderOpts = { Wallet.builderOpts = {
lockTime: null, lockTime: null,
signhash: bitcore.Transaction.SIGNHASH_ALL, signhash: bitcore.Transaction.SIGNHASH_ALL,
fee: null, fee: undefined,
feeSat: null, feeSat: undefined,
}; };
/** /**
@ -607,8 +607,8 @@ Wallet.prototype.getMyCopayerIdPriv = function() {
*/ */
Wallet.prototype.getSecretNumber = function() { Wallet.prototype.getSecretNumber = function() {
if (this.secretNumber) return this.secretNumber; if (this.secretNumber) return this.secretNumber;
this.secretNumber = Wallet.getRandomNumber(); this.secretNumber = Wallet.getRandomNumber();
return this.secretNumber; return this.secretNumber;
}; };
/** /**
@ -1284,21 +1284,13 @@ Wallet.prototype.receivePaymentRequest = function(options, pr, cb) {
}; };
} }
var trusted = certs.map(function(cert) {
var der = cert.toString('hex');
var pem = PayPro.prototype._DERtoPEM(der, 'CERTIFICATE');
return PayPro.RootCerts.getTrusted(pem);
}).filter(Boolean);
// Verify Signature // Verify Signature
var verified = pr.verify(); var trust = pr.verify(true);
if (!verified) { if (!trust.verified) {
return cb(new Error('Server sent a bad signature.')); return cb(new Error('Server sent a bad signature.'));
} }
var ca = trusted[0];
details = PayPro.PaymentDetails.decode(details); details = PayPro.PaymentDetails.decode(details);
var pd = new PayPro(); var pd = new PayPro();
pd = pd.makePaymentDetails(details); pd = pd.makePaymentDetails(details);
@ -1338,8 +1330,9 @@ Wallet.prototype.receivePaymentRequest = function(options, pr, cb) {
merchant_data: merchant_data.toString('hex') merchant_data: merchant_data.toString('hex')
}, },
signature: sig.toString('hex'), signature: sig.toString('hex'),
ca: ca, ca: trust.caName,
untrusted: !ca untrusted: !trust.caTrusted,
selfSigned: trust.selfSigned
}, },
request_url: options.uri, request_url: options.uri,
total: bignum('0', 10).toString(10), total: bignum('0', 10).toString(10),
@ -1703,7 +1696,8 @@ Wallet.prototype.verifyPaymentRequest = function(ntxid) {
pr = pr.makePaymentRequest(data); pr = pr.makePaymentRequest(data);
// Verify the signature so we know this is the real request. // Verify the signature so we know this is the real request.
if (!pr.verify()) { var trust = pr.verify(true);
if (!trust.verified) {
// Signature does not match cert. It may have // Signature does not match cert. It may have
// been modified by an untrustworthy person. // been modified by an untrustworthy person.
// We should not sign this transaction proposal! // We should not sign this transaction proposal!
@ -2005,7 +1999,7 @@ Wallet.prototype.removeTxWithSpentInputs = function(cb) {
var proposalsChanged = false; var proposalsChanged = false;
this.blockchain.getUnspent(this.getAddressesStr(), function(err, unspentList) { this.blockchain.getUnspent(this.getAddressesStr(), function(err, unspentList) {
if (err) return cb(err); if (err) return cb(err);
unspentList.forEach(function (unspent) { unspentList.forEach(function (unspent) {
inputs.forEach(function (input) { inputs.forEach(function (input) {
input.unspent = input.unspent || (input.txid === unspent.txid && input.vout === unspent.vout); input.unspent = input.unspent || (input.txid === unspent.txid && input.vout === unspent.vout);

File diff suppressed because one or more lines are too long

View file

@ -1 +0,0 @@
bitcore-0.1.35-paypro.js

44166
lib/bitcore.js Normal file

File diff suppressed because one or more lines are too long

View file

@ -42,7 +42,6 @@
], ],
"devDependencies": { "devDependencies": {
"async": "0.9.0", "async": "0.9.0",
"bitcore": "0.1.35",
"blanket": "1.1.6", "blanket": "1.1.6",
"browser-pack": "2.0.1", "browser-pack": "2.0.1",
"browser-request": "0.3.2", "browser-request": "0.3.2",
@ -63,6 +62,7 @@
"grunt-contrib-uglify": "^0.5.1", "grunt-contrib-uglify": "^0.5.1",
"grunt-contrib-watch": "0.5.3", "grunt-contrib-watch": "0.5.3",
"grunt-markdown": "0.5.0", "grunt-markdown": "0.5.0",
"bitcore": "0.1.36",
"grunt-mocha-test": "0.8.2", "grunt-mocha-test": "0.8.2",
"grunt-shell": "0.6.4", "grunt-shell": "0.6.4",
"grunt-angular-gettext": "^0.2.15", "grunt-angular-gettext": "^0.2.15",

View file

@ -8,29 +8,28 @@
<div ng-show="txs.length != 0" class="large-12 line-dashed" style="padding: 0;"></div> <div ng-show="txs.length != 0" class="large-12 line-dashed" style="padding: 0;"></div>
<h1>{{title|translate}}</h1> <h1>{{title|translate}}</h1>
<div class="row collapse m0">
<div class="large-6 columns"> <div class="large-6 columns">
<form name="sendForm" ng-submit="submitForm(sendForm)" novalidate> <form name="sendForm" ng-submit="submitForm(sendForm)" novalidate>
<div class="row collapse"> <div class="row">
<div class="large-12 columns"> <div class="large-12 columns">
<div class="row collapse"> <div class="row collapse">
<label for="address"><span translate>To address</span> <label for="address">To address
<small translate ng-hide="!sendForm.address.$pristine || address">required</small> <small ng-hide="!sendForm.address.$pristine || address">required</small>
<small translate class="is-valid" ng-show="!sendForm.address.$invalid && address">valid!</small> <small class="is-valid" ng-show="!sendForm.address.$invalid && address">valid!</small>
<small translate class="has-error" ng-show="sendForm.address.$invalid && address"> <small class="has-error" ng-show="sendForm.address.$invalid && address">
not valid</small> not valid</small>
</label> </label>
<div class="small-10 columns"> <div class="small-10 columns">
<input type="text" id="address" name="address" ng-disabled="loading" <input type="text" id="address" name="address" ng-disabled="loading || !!$root.merchant"
placeholder="{{'Send to'|translate}}" ng-model="address" ng-change="onChanged()" valid-address required> placeholder="Send to" ng-model="address" ng-change="onChanged()" valid-address required>
<small class="icon-input" ng-show="!sendForm.address.$invalid && address"><i class="fi-check"></i></small> <small class="icon-input" ng-show="!sendForm.address.$invalid && address"><i class="fi-check"></i></small>
<small class="icon-input" ng-show="sendForm.address.$invalid && address"><i class="fi-x"></i></small> <small class="icon-input" ng-show="sendForm.address.$invalid && address"><i class="fi-x"></i></small>
</div> </div>
<div class="small-2 columns" ng-hide="disableScanner" ng-hide="showScanner"> <div class="small-2 columns" ng-hide="showScanner">
<a class="postfix button black" ng-click="openScanner()"><i class="fi-camera"></i></a> <a class="postfix button black" ng-click="openScanner()"><i class="fi-camera"></i></a>
</div> </div>
<div class="small-2 columns" ng-show="showScanner"> <div class="small-2 columns" ng-show="showScanner">
<a translate class="postfix button warning" ng-click="cancelScanner()">Cancel</a> <a class="postfix button warning" ng-click="cancelScanner()">Cancel</a>
</div> </div>
</div> </div>
<div id="scanner" class="row" ng-if="showScanner"> <div id="scanner" class="row" ng-if="showScanner">
@ -53,30 +52,30 @@
</div> </div>
</div> </div>
<div class="row collapse"> <div class="row">
<div class="large-5 medium-5 columns"> <div class="large-6 medium-6 columns">
<div class="row collapse"> <div class="row collapse">
<label for="amount"><span translate>Amount</span> <label for="amount">Amount
<small translate ng-hide="!sendForm.amount.$pristine">required</small> <small ng-hide="!sendForm.amount.$pristine">required</small>
<small translate class="is-valid" ng-show="!sendForm.amount.$invalid && !sendForm.amount.$pristine">Valid</small> <small class="is-valid" ng-show="!sendForm.amount.$invalid && !sendForm.amount.$pristine">Valid</small>
<small translate class="has-error" ng-show="sendForm.amount.$invalid && !sendForm.amount.$pristine && !notEnoughAmount"> <small class="has-error" ng-show="sendForm.amount.$invalid && !sendForm.amount.$pristine && !notEnoughAmount">
Not valid Not valid
</small> </small>
<small translate ng-show="notEnoughAmount" class="has-error">Insufficient funds</small> <small ng-show="notEnoughAmount" class="has-error">Insufficient funds</small>
</label> </label>
<div class="small-9 columns"> <div class="small-9 columns">
<input type="number" id="amount" <input type="number" id="amount"
ng-disabled="loading || ($root.merchant && +$root.merchant.total > 0) || $root.merchantError" ng-disabled="loading || ($root.merchant && +$root.merchant.total > 0) || $root.merchantError"
name="amount" placeholder="{{'Amount'|translate}}" ng-model="amount" name="amount" placeholder="Amount" ng-model="amount"
min="{{minAmount}}" max="10000000000" enough-amount required min="{{minAmount}}" max="10000000000" enough-amount required
autocomplete="off" autocomplete="off"
> >
<small class="icon-input" ng-show="!sendForm.amount.$invalid && amount"><i class="fi-check"></i></small> <small class="icon-input" ng-show="!sendForm.amount.$invalid && amount"><i class="fi-check"></i></small>
<small class="icon-input" ng-show="sendForm.amount.$invalid && !sendForm.amount.$pristine && !notEnoughAmount"><i class="fi-x"></i></small> <small class="icon-input" ng-show="sendForm.amount.$invalid && !sendForm.amount.$pristine && !notEnoughAmount"><i class="fi-x"></i></small>
<a class="small input-note" title="{{'Send all funds'|translate}}" <a class="small input-note" title="Send all funds"
ng-show="$root.availableBalance > 0 && (!$root.merchant || +$root.merchant.total === 0)" ng-show="$root.availableBalance > 0 && (!$root.merchant || +$root.merchant.total === 0)"
ng-click="topAmount(sendForm)"> ng-click="topAmount(sendForm)">
<span translate>Use all funds</span> ({{getAvailableAmount()}} {{$root.unitName}}) Use all funds ({{getAvailableAmount()}} {{$root.unitName}})
</a> </a>
</div> </div>
<div class="small-3 columns"> <div class="small-3 columns">
@ -86,11 +85,11 @@
</div> </div>
<div class="large-6 medium-6 columns"> <div class="large-6 medium-6 columns">
<div class="row collapse"> <div class="row collapse">
<label for="alternative"><span translate>Amount in</span> {{ alternativeName }} </label> <label for="alternative">Amount in {{ alternativeName }} </label>
<div class="small-9 columns"> <div class="small-9 columns">
<input type="number" id="alternative_amount" <input type="number" id="alternative_amount"
ng-disabled="loading || !isRateAvailable " ng-disabled="loading || !isRateAvailable || ($root.merchant && +$root.merchant.total > 0) || $root.merchantError"
name="alternative" placeholder="{{'Amount'|translate}}" ng-model="alternative" name="alternative" placeholder="Amount" ng-model="alternative"
required required
autocomplete="off" autocomplete="off"
> >
@ -102,37 +101,37 @@
</div> </div>
</div> </div>
<div class="row collapse" ng-show="wallet.isShared()"> <div class="row" ng-show="wallet.isShared()">
<div class="large-12 columns"> <div class="large-12 columns">
<div class="row collapse"> <div class="row collapse">
<label for="comment"><span translate>Notes</span> <label for="comment">Note
<small translate ng-hide="!sendForm.comment.$pristine">optional</small> <small ng-hide="!sendForm.comment.$pristine">optional</small>
<small translate class="has-error" ng-show="sendForm.comment.$invalid && !sendForm.comment.$pristine">too long!</small> <small class="has-error" ng-show="sendForm.comment.$invalid && !sendForm.comment.$pristine">too long!</small>
</label> </label>
<div class="large-12 columns"> <div class="large-12 columns">
<textarea id="comment" ng-disabled="loading" <textarea id="comment" ng-disabled="loading"
name="comment" placeholder="{{'Leave a private message to your copayers'|translate}}" ng-model="commentText" ng-maxlength="100"></textarea> name="comment" placeholder="Leave a private message to your copayers" ng-model="commentText" ng-maxlength="100"></textarea>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div class="row collapse"> <div class="row">
<div class="large-5 medium-6 small-12 columns"> <div class="large-5 medium-3 small-4 columns">
<button translate type="submit" class="button primary expand text-center" ng-disabled="sendForm.$invalid || loading" loading="Sending"> <button type="submit" class="button primary expand text-center" ng-disabled="sendForm.$invalid || loading" loading="Sending">
Send Send
</button> </button>
</div> </div>
</div> </div>
</form> </form>
</div> </div>
</div><!-- end of row -->
<div class="large-6 columns show-for-large-up" ng-show="!!$root.merchant"> <div class="large-6 columns show-for-large-up" ng-show="!!$root.merchant">
<div class="send-note"> <div class="send-note">
<h6>Send to</h6> <h6>Send to</h6>
<p class="text-gray" ng-class="{'hidden': sendForm.address.$invalid || !address}"> <p class="text-gray" ng-class="{'hidden': sendForm.address.$invalid || !address}"
{{address}}&nbsp; title="{{$root.merchant.request_url}}">
{{$root.merchant.domain}}&nbsp;
</p> </p>
<h6>Total amount for this transaction:</h6> <h6>Total amount for this transaction:</h6>
<p class="text-gray" ng-class="{'hidden': sendForm.amount.$invalid || !amount > 0}"> <p class="text-gray" ng-class="{'hidden': sendForm.amount.$invalid || !amount > 0}">