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;
$scope.isMobile = isMobile.any();
if (!window.cordova && !navigator.getUserMedia)
if (!window.cordova && !navigator.getUserMedia)
$scope.disableScanner =1;
$scope.submitForm = function(form) {
@ -169,26 +169,32 @@ angular.module('copayApp.controllers').controller('SendController',
$rootScope.pendingPayment = null;
}
// XXX Payment Protocol is temporarily disabled.
// var uri;
// if (address.indexOf('bitcoin:') === 0) {
// uri = new bitcore.BIP21(address).data;
// } else if (/^https?:\/\//.test(address)) {
// uri = {
// merchant: address
// };
// }
//
// if (uri && uri.merchant) {
// w.createPaymentTx({
// uri: uri.merchant,
// memo: commentText
// }, done);
// } else {
// w.createTx(address, amount, commentText, done);
// }
var uri;
if (address.indexOf('bitcoin:') === 0) {
uri = new bitcore.BIP21(address).data;
} else if (/^https?:\/\//.test(address)) {
uri = {
merchant: address
};
}
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
$scope.address = $scope.amount = $scope.commentText = null;
@ -464,6 +470,13 @@ angular.module('copayApp.controllers').controller('SendController',
var value = scope.address || '';
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) {
uri = new bitcore.BIP21(value).data;
} else if (/^https?:\/\//.test(value)) {
@ -520,12 +533,18 @@ angular.module('copayApp.controllers').controller('SendController',
return;
}
var url = merchantData.request_url;
var domain = /^(?:https?)?:\/\/([^\/:]+).*$/.exec(url)[1];
merchantData.unitTotal = (+merchantData.total / config.unitToSatoshi) + '';
merchantData.expiration = new Date(
merchantData.pr.pd.expires * 1000).toISOString();
merchantData.domain = domain;
$rootScope.merchant = merchantData;
scope.sendForm.address.$setViewValue(domain);
scope.sendForm.address.$render();
scope.sendForm.address.$isValid = true;
scope.sendForm.amount.$setViewValue(merchantData.unitTotal);
@ -537,6 +556,14 @@ angular.module('copayApp.controllers').controller('SendController',
var unregister = scope.$watch('address', function() {
var val = scope.sendForm.address.$viewValue || '';
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) {
uri = new bitcore.BIP21(val).data;
} 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';
angular.module('copayApp.directives')
.directive('validAddress', function() {
.directive('validAddress', ['$rootScope', function($rootScope) {
var bitcore = require('bitcore');
var Address = bitcore.Address;
var bignum = bitcore.Bignum;
@ -11,6 +11,14 @@ angular.module('copayApp.directives')
link: function(scope, elem, attrs, ctrl) {
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
if (/^https?:\/\//.test(value)) {
ctrl.$setValidity('validAddress', true);
@ -35,7 +43,7 @@ angular.module('copayApp.directives')
ctrl.$formatters.unshift(validator);
}
};
})
}])
.directive('enoughAmount', ['$rootScope',
function($rootScope) {
var bitcore = require('bitcore');

View file

@ -108,8 +108,8 @@ inherits(Wallet, events.EventEmitter);
Wallet.builderOpts = {
lockTime: null,
signhash: bitcore.Transaction.SIGNHASH_ALL,
fee: null,
feeSat: null,
fee: undefined,
feeSat: undefined,
};
/**
@ -607,8 +607,8 @@ Wallet.prototype.getMyCopayerIdPriv = function() {
*/
Wallet.prototype.getSecretNumber = function() {
if (this.secretNumber) return this.secretNumber;
this.secretNumber = Wallet.getRandomNumber();
return this.secretNumber;
this.secretNumber = Wallet.getRandomNumber();
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
var verified = pr.verify();
var trust = pr.verify(true);
if (!verified) {
if (!trust.verified) {
return cb(new Error('Server sent a bad signature.'));
}
var ca = trusted[0];
details = PayPro.PaymentDetails.decode(details);
var pd = new PayPro();
pd = pd.makePaymentDetails(details);
@ -1338,8 +1330,9 @@ Wallet.prototype.receivePaymentRequest = function(options, pr, cb) {
merchant_data: merchant_data.toString('hex')
},
signature: sig.toString('hex'),
ca: ca,
untrusted: !ca
ca: trust.caName,
untrusted: !trust.caTrusted,
selfSigned: trust.selfSigned
},
request_url: options.uri,
total: bignum('0', 10).toString(10),
@ -1703,7 +1696,8 @@ Wallet.prototype.verifyPaymentRequest = function(ntxid) {
pr = pr.makePaymentRequest(data);
// 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
// been modified by an untrustworthy person.
// We should not sign this transaction proposal!
@ -2005,7 +1999,7 @@ Wallet.prototype.removeTxWithSpentInputs = function(cb) {
var proposalsChanged = false;
this.blockchain.getUnspent(this.getAddressesStr(), function(err, unspentList) {
if (err) return cb(err);
unspentList.forEach(function (unspent) {
inputs.forEach(function (input) {
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": {
"async": "0.9.0",
"bitcore": "0.1.35",
"blanket": "1.1.6",
"browser-pack": "2.0.1",
"browser-request": "0.3.2",
@ -63,6 +62,7 @@
"grunt-contrib-uglify": "^0.5.1",
"grunt-contrib-watch": "0.5.3",
"grunt-markdown": "0.5.0",
"bitcore": "0.1.36",
"grunt-mocha-test": "0.8.2",
"grunt-shell": "0.6.4",
"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>
<h1>{{title|translate}}</h1>
<div class="row collapse m0">
<div class="large-6 columns">
<form name="sendForm" ng-submit="submitForm(sendForm)" novalidate>
<div class="row collapse">
<div class="row">
<div class="large-12 columns">
<div class="row collapse">
<label for="address"><span translate>To address</span>
<small translate ng-hide="!sendForm.address.$pristine || address">required</small>
<small translate class="is-valid" ng-show="!sendForm.address.$invalid && address">valid!</small>
<small translate class="has-error" ng-show="sendForm.address.$invalid && address">
<label for="address">To address
<small ng-hide="!sendForm.address.$pristine || address">required</small>
<small class="is-valid" ng-show="!sendForm.address.$invalid && address">valid!</small>
<small class="has-error" ng-show="sendForm.address.$invalid && address">
not valid</small>
</label>
<div class="small-10 columns">
<input type="text" id="address" name="address" ng-disabled="loading"
placeholder="{{'Send to'|translate}}" ng-model="address" ng-change="onChanged()" valid-address required>
<input type="text" id="address" name="address" ng-disabled="loading || !!$root.merchant"
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-x"></i></small>
</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>
</div>
<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 id="scanner" class="row" ng-if="showScanner">
@ -53,30 +52,30 @@
</div>
</div>
<div class="row collapse">
<div class="large-5 medium-5 columns">
<div class="row">
<div class="large-6 medium-6 columns">
<div class="row collapse">
<label for="amount"><span translate>Amount</span>
<small translate ng-hide="!sendForm.amount.$pristine">required</small>
<small translate 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">
<label for="amount">Amount
<small ng-hide="!sendForm.amount.$pristine">required</small>
<small class="is-valid" ng-show="!sendForm.amount.$invalid && !sendForm.amount.$pristine">Valid</small>
<small class="has-error" ng-show="sendForm.amount.$invalid && !sendForm.amount.$pristine && !notEnoughAmount">
Not valid
</small>
<small translate ng-show="notEnoughAmount" class="has-error">Insufficient funds</small>
<small ng-show="notEnoughAmount" class="has-error">Insufficient funds</small>
</label>
<div class="small-9 columns">
<input type="number" id="amount"
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
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 && !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-click="topAmount(sendForm)">
<span translate>Use all funds</span> ({{getAvailableAmount()}} {{$root.unitName}})
Use all funds ({{getAvailableAmount()}} {{$root.unitName}})
</a>
</div>
<div class="small-3 columns">
@ -86,11 +85,11 @@
</div>
<div class="large-6 medium-6 columns">
<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">
<input type="number" id="alternative_amount"
ng-disabled="loading || !isRateAvailable "
name="alternative" placeholder="{{'Amount'|translate}}" ng-model="alternative"
ng-disabled="loading || !isRateAvailable || ($root.merchant && +$root.merchant.total > 0) || $root.merchantError"
name="alternative" placeholder="Amount" ng-model="alternative"
required
autocomplete="off"
>
@ -102,37 +101,37 @@
</div>
</div>
<div class="row collapse" ng-show="wallet.isShared()">
<div class="row" ng-show="wallet.isShared()">
<div class="large-12 columns">
<div class="row collapse">
<label for="comment"><span translate>Notes</span>
<small translate ng-hide="!sendForm.comment.$pristine">optional</small>
<small translate class="has-error" ng-show="sendForm.comment.$invalid && !sendForm.comment.$pristine">too long!</small>
<label for="comment">Note
<small ng-hide="!sendForm.comment.$pristine">optional</small>
<small class="has-error" ng-show="sendForm.comment.$invalid && !sendForm.comment.$pristine">too long!</small>
</label>
<div class="large-12 columns">
<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 class="row collapse">
<div class="large-5 medium-6 small-12 columns">
<button translate type="submit" class="button primary expand text-center" ng-disabled="sendForm.$invalid || loading" loading="Sending">
<div class="row">
<div class="large-5 medium-3 small-4 columns">
<button type="submit" class="button primary expand text-center" ng-disabled="sendForm.$invalid || loading" loading="Sending">
Send
</button>
</div>
</div>
</form>
</div>
</div><!-- end of row -->
<div class="large-6 columns show-for-large-up" ng-show="!!$root.merchant">
<div class="send-note">
<h6>Send to</h6>
<p class="text-gray" ng-class="{'hidden': sendForm.address.$invalid || !address}">
{{address}}&nbsp;
<p class="text-gray" ng-class="{'hidden': sendForm.address.$invalid || !address}"
title="{{$root.merchant.request_url}}">
{{$root.merchant.domain}}&nbsp;
</p>
<h6>Total amount for this transaction:</h6>
<p class="text-gray" ng-class="{'hidden': sendForm.amount.$invalid || !amount > 0}">