New flow to link the bitpay card

This commit is contained in:
Gustavo Maximiliano Cortez 2016-10-06 19:23:39 -03:00
parent 4f5c814c50
commit 35cd6ce4cc
No known key found for this signature in database
GPG Key ID: 15EDAD8D9F2EB1AF
19 changed files with 390 additions and 389 deletions

2
.gitignore vendored
View File

@ -50,7 +50,7 @@ build/Release
node_modules node_modules
bower_components bower_components
angular-bitcore-wallet-client/angular-bitcore-wallet-client.js angular-bitcore-wallet-client/angular-bitcore-wallet-client.js
angular-pbkdf2/angular-pbkdf2.js angular-bitauth/angular-bitauth.js
# Users Environment Variables # Users Environment Variables
.lock-wscript .lock-wscript

View File

@ -128,7 +128,7 @@ module.exports = function(grunt) {
'bower_components/angular-md5/angular-md5.js', 'bower_components/angular-md5/angular-md5.js',
'bower_components/angular-mocks/angular-mocks.js', 'bower_components/angular-mocks/angular-mocks.js',
'bower_components/ngtouch/src/ngTouch.js', 'bower_components/ngtouch/src/ngTouch.js',
'angular-pbkdf2/angular-pbkdf2.js', 'angular-bitauth/angular-bitauth.js',
'angular-bitcore-wallet-client/angular-bitcore-wallet-client.js' 'angular-bitcore-wallet-client/angular-bitcore-wallet-client.js'
], ],
dest: 'www/lib/angular.js' dest: 'www/lib/angular.js'
@ -251,7 +251,7 @@ module.exports = function(grunt) {
dist: { dist: {
files: { files: {
'angular-bitcore-wallet-client/angular-bitcore-wallet-client.js': ['angular-bitcore-wallet-client/index.js'], 'angular-bitcore-wallet-client/angular-bitcore-wallet-client.js': ['angular-bitcore-wallet-client/index.js'],
'angular-pbkdf2/angular-pbkdf2.js': ['angular-pbkdf2/index.js'] 'angular-bitauth/angular-bitauth.js': ['angular-bitauth/index.js']
}, },
} }
} }

18
angular-bitauth/index.js vendored Normal file
View File

@ -0,0 +1,18 @@
var bitauthModule = angular.module('bitauthModule', []);
var bitauth = require('../node_modules/bitauth');
bitauthModule.constant('MODULE_VERSION', '1.0.0');
bitauthModule.provider("bitauthService", function() {
var provider = {};
provider.$get = function() {
var service = {};
service = bitauth;
return service;
};
return provider;
});

View File

@ -1,18 +0,0 @@
var pbkdf2Module = angular.module('pbkdf2Module', []);
var pbkdf2Sync = require('../node_modules/pbkdf2').pbkdf2Sync;
pbkdf2Module.constant('MODULE_VERSION', '1.0.0');
pbkdf2Module.provider("pbkdf2Service", function() {
var provider = {};
provider.$get = function() {
var service = {};
service.pbkdf2Sync = pbkdf2Sync;
return service;
};
return provider;
});

View File

@ -46,6 +46,7 @@
"angular": "1.4.6", "angular": "1.4.6",
"angular-mocks": "1.4.10", "angular-mocks": "1.4.10",
"bhttp": "^1.2.1", "bhttp": "^1.2.1",
"bitauth": "^0.3.2",
"bitcore-wallet-client": "4.2.1", "bitcore-wallet-client": "4.2.1",
"bower": "^1.7.9", "bower": "^1.7.9",
"chai": "^3.5.0", "chai": "^3.5.0",
@ -80,7 +81,6 @@
"karma-sinon": "^1.0.5", "karma-sinon": "^1.0.5",
"load-grunt-tasks": "^3.5.0", "load-grunt-tasks": "^3.5.0",
"mocha": "^2.4.5", "mocha": "^2.4.5",
"pbkdf2": "^3.0.4",
"phantomjs-prebuilt": "^2.1.7", "phantomjs-prebuilt": "^2.1.7",
"shelljs": "^0.3.0", "shelljs": "^0.3.0",
"xcode": "^0.8.2" "xcode": "^0.8.2"

View File

@ -12,7 +12,7 @@ var modules = [
'ngCsv', 'ngCsv',
'angular-md5', 'angular-md5',
'bwcModule', 'bwcModule',
'pbkdf2Module', 'bitauthModule',
'copayApp.filters', 'copayApp.filters',
'copayApp.services', 'copayApp.services',
'copayApp.controllers', 'copayApp.controllers',

View File

@ -17,13 +17,13 @@ angular.module('copayApp.controllers').controller('amountController', function($
$scope.$on("$ionicView.beforeEnter", function(event, data) { $scope.$on("$ionicView.beforeEnter", function(event, data) {
$scope.isWallet = data.stateParams.isWallet; $scope.isWallet = data.stateParams.isWallet;
$scope.isCard = data.stateParams.isCard; $scope.cardId = data.stateParams.cardId;
$scope.toAddress = data.stateParams.toAddress; $scope.toAddress = data.stateParams.toAddress;
$scope.toName = data.stateParams.toName; $scope.toName = data.stateParams.toName;
$scope.toEmail = data.stateParams.toEmail; $scope.toEmail = data.stateParams.toEmail;
$scope.showAlternativeAmount = !!$scope.isCard; $scope.showAlternativeAmount = !!$scope.cardId;
if (!$scope.isCard && !$stateParams.toAddress) { if (!$scope.cardId && !$stateParams.toAddress) {
$log.error('Bad params at amount') $log.error('Bad params at amount')
throw ('bad params'); throw ('bad params');
} }
@ -189,7 +189,7 @@ angular.module('copayApp.controllers').controller('amountController', function($
$scope.finish = function() { $scope.finish = function() {
var _amount = evaluate(format($scope.amount)); var _amount = evaluate(format($scope.amount));
if ($scope.isCard) { if ($scope.cardId) {
var amountUSD = $scope.showAlternativeAmount ? _amount : $filter('formatFiatAmount')(toFiat(_amount)); var amountUSD = $scope.showAlternativeAmount ? _amount : $filter('formatFiatAmount')(toFiat(_amount));
var dataSrc = { var dataSrc = {
@ -199,7 +199,7 @@ angular.module('copayApp.controllers').controller('amountController', function($
ongoingProcess.set('Processing Transaction...', true); ongoingProcess.set('Processing Transaction...', true);
$timeout(function() { $timeout(function() {
bitpayCardService.topUp(dataSrc, function(err, invoiceId) { bitpayCardService.topUp($scope.cardId, dataSrc, function(err, invoiceId) {
if (err) { if (err) {
ongoingProcess.set('Processing Transaction...', false); ongoingProcess.set('Processing Transaction...', false);
popupService.showAlert(gettextCatalog.getString('Error'), bwcError.msg(err)); popupService.showAlert(gettextCatalog.getString('Error'), bwcError.msg(err));
@ -215,7 +215,7 @@ angular.module('copayApp.controllers').controller('amountController', function($
var payProUrl = data.paymentUrls.BIP73; var payProUrl = data.paymentUrls.BIP73;
$state.transitionTo('tabs.bitpayCard.confirm', { $state.transitionTo('tabs.bitpayCard.confirm', {
isCard: $scope.isCard, cardId: $scope.cardId,
toName: $scope.toName, toName: $scope.toName,
paypro: payProUrl paypro: payProUrl
}); });

View File

@ -1,40 +1,18 @@
'use strict'; 'use strict';
angular.module('copayApp.controllers').controller('bitpayCardController', function($scope, $timeout, $log, lodash, bitpayCardService, configService, profileService, walletService, ongoingProcess, pbkdf2Service, moment, popupService, gettextCatalog, bwcError) { angular.module('copayApp.controllers').controller('bitpayCardController', function($scope, $timeout, $log, $state, lodash, bitpayCardService, configService, profileService, walletService, ongoingProcess, moment, popupService, gettextCatalog, bwcError, $ionicHistory) {
var self = this; var self = this;
$scope.dateRange = 'last30Days'; $scope.dateRange = 'last30Days';
$scope.network = bitpayCardService.getEnvironment(); $scope.network = bitpayCardService.getEnvironment();
/*
bitpayCardService.getCacheData(function(err, data) { bitpayCardService.getCacheData(function(err, data) {
if (err || lodash.isEmpty(data)) return; if (err || lodash.isEmpty(data)) return;
$scope.bitpayCardCached = true;
self.bitpayCardTransactionHistory = data.transactions; self.bitpayCardTransactionHistory = data.transactions;
self.bitpayCardCurrentBalance = data.balance; self.bitpayCardCurrentBalance = data.balance;
}); });
*/
var processTransactions = function(invoices, history) {
for (var i = 0; i < invoices.length; i++) {
var matched = false;
for (var j = 0; j < history.length; j++) {
if (history[j].description[0].indexOf(invoices[i].id) > -1) {
matched = true;
}
}
if (!matched && ['paid', 'confirmed', 'complete'].indexOf(invoices[i].status) > -1) {
history.unshift({
timestamp: invoices[i].invoiceTime,
description: invoices[i].itemDesc,
amount: invoices[i].price,
type: '00611 = Client Funded Deposit',
pending: true,
status: invoices[i].status
});
}
}
return history;
};
var setDateRange = function(preset) { var setDateRange = function(preset) {
var startDate, endDate; var startDate, endDate;
@ -63,92 +41,23 @@ angular.module('copayApp.controllers').controller('bitpayCardController', functi
this.update = function() { this.update = function() {
var dateRange = setDateRange($scope.dateRange); var dateRange = setDateRange($scope.dateRange);
self.loadingSession = true;
bitpayCardService.isAuthenticated(function(err, bpSession) { $scope.loadingHistory = true;
self.loadingSession = false; bitpayCardService.getHistory($scope.cardId, dateRange, function(err, history) {
if (err) { $scope.loadingHistory = false;
if (err || history.error) {
$log.error(err || history.error);
$scope.error = gettextCatalog.getString('Could not get transactions');
return; return;
} }
self.bitpayCardAuthenticated = bpSession.isAuthenticated; self.bitpayCardTransactionHistory = history.transactionList;
self.bitpayCardTwoFactorPending = bpSession.twoFactorPending ? true : false; self.bitpayCardCurrentBalance = history.currentCardBalance;
if (self.bitpayCardTwoFactorPending) return; var cacheData = {
balance: self.bitpayCardCurrentBalance,
if (self.bitpayCardAuthenticated) { transactions: self.bitpayCardTransactionHistory
$scope.loadingHistory = true; };
bitpayCardService.invoiceHistory(function(err, invoices) {
if (err) $log.error(err);
bitpayCardService.transactionHistory(dateRange, function(err, history) {
$scope.loadingHistory = false;
if (err) {
popupService.showAlert(gettextCatalog.getString('Error'), gettextCatalog.getString('Could not get transactions'));
return;
}
self.bitpayCardTransactionHistory = processTransactions(invoices, history.transactionList);
self.bitpayCardCurrentBalance = history.currentCardBalance;
var cacheData = {
balance: self.bitpayCardCurrentBalance,
transactions: self.bitpayCardTransactionHistory
};
bitpayCardService.setCacheData(cacheData, function(err) {
$scope.bitpayCardCached = true;
if (err) $log.error(err);
});
});
});
}
$timeout(function() {
$scope.$apply();
});
});
};
this.authenticate = function(email, password) {
var data = {
emailAddress : email,
hashedPassword : pbkdf2Service.pbkdf2Sync(password, '..............', 200, 64).toString('hex')
};
// POST /authenticate
// emailAddress:
// hashedPassword:
self.authenticating = true;
bitpayCardService.authenticate(data, function(err, auth) {
self.authenticating = false;
if (err && err.data && err.data.error && !err.data.error.twoFactorPending) {
popupService.showAlert(gettextCatalog.getString('Error'), err.statusText || err.data.error || 'Authentiation error');
return;
}
self.update();
$timeout(function() {
$scope.$apply();
}, 100);
});
};
this.authenticate2FA = function(twoFactorCode) {
var data = {
twoFactorCode : twoFactorCode
};
self.authenticating = true;
bitpayCardService.authenticate2FA(data, function(err, auth) {
self.authenticating = false;
if (err) {
popupService.showAlert(gettextCatalog.getString('Error'), gettextCatalog.getString('Authentiation error'));
return;
}
self.update();
$timeout(function() {
$scope.$apply();
}, 100);
}); });
}; };
@ -174,8 +83,18 @@ angular.module('copayApp.controllers').controller('bitpayCardController', functi
return tx.description; return tx.description;
}; };
$scope.$on("$ionicView.beforeEnter", function(event, data){ $scope.$on("$ionicView.beforeEnter", function(event, data) {
self.update(); $scope.cardId = data.stateParams.id;
if (!$scope.cardId) {
var msg = gettextCatalog.getString('Bad param');
$ionicHistory.nextViewOptions({
disableAnimate: true
});
$state.go('tabs.home');
popupService.showAlert(null, msg);
} else {
self.update();
}
}); });
}); });

View File

@ -1,5 +1,5 @@
'use strict'; 'use strict';
angular.module('copayApp.controllers').controller('bitpayCardIntroController', function($scope, $state, $timeout, $ionicHistory, storageService, externalLinkService, bitpayCardService) { angular.module('copayApp.controllers').controller('bitpayCardIntroController', function($scope, $state, $timeout, $ionicHistory, storageService, externalLinkService, bitpayCardService, gettextCatalog, popupService) {
$scope.$on("$ionicView.beforeEnter", function(event, data) { $scope.$on("$ionicView.beforeEnter", function(event, data) {
$scope.data = { $scope.data = {
@ -13,6 +13,39 @@ angular.module('copayApp.controllers').controller('bitpayCardIntroController', f
spaceBetween: 100 spaceBetween: 100
}; };
if (data.stateParams && data.stateParams.secret) {
var obj = {
secret: data.stateParams.secret,
email: data.stateParams.email,
otp: data.stateParams.otp
};
bitpayCardService.bitAuthPair(obj, function(err, data) {
if (err) {
popupService.showAlert(null, err);
return;
}
var title = gettextCatalog.getString('Add BitPay Cards?');
var msg = gettextCatalog.getString('Would you like to add this account to your wallet?');
var ok = gettextCatalog.getString('Add cards');
var cancel = gettextCatalog.getString('Go back');
popupService.showConfirm(title, msg, ok, cancel, function(res) {
if (res) {
// Save data
bitpayCardService.setBitpayDebitCards(data, function(err) {
if (err) return;
$ionicHistory.nextViewOptions({
disableAnimate: true
});
$state.go('tabs.home');
});
}
});
});
} else {
// TODO
}
/*
storageService.getNextStep('BitpayCard', function(err, value) { storageService.getNextStep('BitpayCard', function(err, value) {
if (value) { if (value) {
$ionicHistory.nextViewOptions({ $ionicHistory.nextViewOptions({
@ -24,6 +57,7 @@ angular.module('copayApp.controllers').controller('bitpayCardIntroController', f
}, 100); }, 100);
} }
}); });
*/
}); });
$scope.orderBitPayCard = function() { $scope.orderBitPayCard = function() {
@ -32,7 +66,11 @@ angular.module('copayApp.controllers').controller('bitpayCardIntroController', f
externalLinkService.open(url, target); externalLinkService.open(url, target);
}; };
$scope.connectBitPayCard = function() {}; $scope.connectBitPayCard = function() {
var url = 'https://bitpay.com/visa/login';
var target = '_system';
externalLinkService.open(url, target);
};
$scope.goBack = function() { $scope.goBack = function() {
if ($scope.data.index != 0) $scope.slider.slidePrev(); if ($scope.data.index != 0) $scope.slider.slidePrev();

View File

@ -8,7 +8,7 @@ angular.module('copayApp.controllers').controller('confirmController', function(
$scope.$on("$ionicView.beforeEnter", function(event, data) { $scope.$on("$ionicView.beforeEnter", function(event, data) {
$scope.isWallet = data.stateParams.isWallet; $scope.isWallet = data.stateParams.isWallet;
$scope.isCard = data.stateParams.isCard; $scope.cardId = data.stateParams.cardId;
$scope.toAmount = data.stateParams.toAmount; $scope.toAmount = data.stateParams.toAmount;
$scope.toAddress = data.stateParams.toAddress; $scope.toAddress = data.stateParams.toAddress;
$scope.toName = data.stateParams.toName; $scope.toName = data.stateParams.toName;

View File

@ -6,7 +6,6 @@ angular.module('copayApp.controllers').controller('tabHomeController',
var listeners = []; var listeners = [];
var notifications = []; var notifications = [];
$scope.externalServices = {}; $scope.externalServices = {};
$scope.bitpayCardEnabled = true; // TODO
$scope.openTxpModal = txpModalService.open; $scope.openTxpModal = txpModalService.open;
$scope.version = $window.version; $scope.version = $window.version;
$scope.name = $window.appConfig.nameCase; $scope.name = $window.appConfig.nameCase;
@ -203,10 +202,16 @@ angular.module('copayApp.controllers').controller('tabHomeController',
}; };
var bitpayCardCache = function() { var bitpayCardCache = function() {
bitpayCardService.getBitpayDebitCards(function(err, data) {
if (err) return;
$scope.bitpayCards = data.cards;
});
/*
bitpayCardService.getCacheData(function(err, data) { bitpayCardService.getCacheData(function(err, data) {
if (err ||  lodash.isEmpty(data)) return; if (err ||  lodash.isEmpty(data)) return;
$scope.bitpayCard = data; $scope.bitpayCard = data;
}); });
*/
}; };
$scope.onRefresh = function() { $scope.onRefresh = function() {
@ -215,7 +220,6 @@ angular.module('copayApp.controllers').controller('tabHomeController',
}; };
$scope.$on("$ionicView.enter", function(event, data) { $scope.$on("$ionicView.enter", function(event, data) {
$scope.bitpayCard = null;
nextStep(); nextStep();
updateAllWallets(); updateAllWallets();

View File

@ -847,7 +847,7 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr
*/ */
.state('tabs.bitpayCardIntro', { .state('tabs.bitpayCardIntro', {
url: '/bitpay-card-intro', url: '/bitpay-card-intro/:secret/:email/:otp',
views: { views: {
'tab-home@tabs': { 'tab-home@tabs': {
controller: 'bitpayCardIntroController', controller: 'bitpayCardIntroController',
@ -856,7 +856,7 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr
} }
}) })
.state('tabs.bitpayCard', { .state('tabs.bitpayCard', {
url: '/bitpay-card', url: '/bitpay-card/:id',
views: { views: {
'tab-home@tabs': { 'tab-home@tabs': {
controller: 'bitpayCardController', controller: 'bitpayCardController',
@ -866,7 +866,7 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr
} }
}) })
.state('tabs.bitpayCard.amount', { .state('tabs.bitpayCard.amount', {
url: '/amount/:isCard/:toName', url: '/amount/:cardId/:toName',
views: { views: {
'tab-home@tabs': { 'tab-home@tabs': {
controller: 'amountController', controller: 'amountController',
@ -875,7 +875,7 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr
} }
}) })
.state('tabs.bitpayCard.confirm', { .state('tabs.bitpayCard.confirm', {
url: '/confirm/:isCard/:toAddress/:toName/:toAmount/:toEmail/:description/:paypro', url: '/confirm/:cardId/:toAddress/:toName/:toAmount/:toEmail/:description/:paypro',
views: { views: {
'tab-home@tabs': { 'tab-home@tabs': {
controller: 'confirmController', controller: 'confirmController',

View File

@ -1,9 +1,10 @@
'use strict'; 'use strict';
angular.module('copayApp.services').factory('bitpayCardService', function($http, $log, lodash, storageService) { angular.module('copayApp.services').factory('bitpayCardService', function($http, $log, lodash, storageService, bitauthService, platformInfo) {
var root = {}; var root = {};
var credentials = {}; var credentials = {};
var bpSession = {}; var bpSession = {};
var pubkey, sin;
var _setCredentials = function() { var _setCredentials = function() {
/* /*
@ -12,16 +13,26 @@ angular.module('copayApp.services').factory('bitpayCardService', function($http,
*/ */
credentials.NETWORK = 'livenet'; credentials.NETWORK = 'livenet';
if (credentials.NETWORK == 'testnet') { if (credentials.NETWORK == 'testnet') {
credentials.BITPAY_PRIV_KEY = '';
credentials.BITPAY_API_URL = 'https://test.bitpay.com'; credentials.BITPAY_API_URL = 'https://test.bitpay.com';
} }
else { else {
credentials.BITPAY_PRIV_KEY = '';
credentials.BITPAY_API_URL = 'https://bitpay.com'; credentials.BITPAY_API_URL = 'https://bitpay.com';
}
try {
pubkey = bitauthService.getPublicKeyFromPrivateKey(credentials.BITPAY_PRIV_KEY);
sin = bitauthService.getSinFromPublicKey(pubkey);
}
catch (e) {
$log.error(e);
}; };
}; };
var _setError = function(msg, e) { var _setError = function(msg, e) {
$log.error(msg); $log.error(msg);
return e; var error = e.data ? e.data.error : msg;
return error;
}; };
var _getUser = function(cb) { var _getUser = function(cb) {
@ -92,40 +103,163 @@ angular.module('copayApp.services').factory('bitpayCardService', function($http,
return credentials.NETWORK; return credentials.NETWORK;
}; };
root.topUp = function(data, cb) { root.getApiUrl = function() {
var dataSrc = { _setCredentials();
amount: data.amount, return credentials.BITPAY_API_URL;
currency: data.currency };
var _postBitAuth = function(endpoint, data) {
var dataToSign = credentials.BITPAY_API_URL + endpoint + JSON.stringify(data);
var signedData = bitauthService.sign(dataToSign, credentials.BITPAY_PRIV_KEY);
return {
method: 'POST',
url: credentials.BITPAY_API_URL + endpoint,
headers: {
'content-type': 'application/json',
'x-identity': pubkey,
'x-signature': signedData
},
data: data
}; };
$http(_postBitPay('/visa-api/topUp', dataSrc)).then(function(data) { };
$log.info('BitPay TopUp: SUCCESS');
return cb(null, data.data.data.invoice); var _afterBitAuthSuccess = function(obj, cb) {
var data = {
method: 'getTokens'
};
// Get tokens
$http(_postBitAuth('/api/v2/', data)).then(function(data) {
$log.info('BitPay Get Tokens: SUCCESS');
var token = lodash.find(data.data.data, 'visaUser');
if (lodash.isEmpty(token)) return cb(_setError('No token for visaUser'));
token = token.visaUser;
data['method'] = 'getDebitCards';
// Get Debit Cards
$http(_postBitAuth('/api/v2/' + token, data)).then(function(data) {
$log.info('BitPay Get Debit Cards: SUCCESS');
var cards = data.data.data;
return cb(null, {token: token, cards: cards, email: obj.email});
}, function(data) {
return cb(_setError('BitPay Card Error: Get Debit Cards', data));
});
}, function(data) { }, function(data) {
return cb(_setError('BitPay Card Error: TopUp', data)); return cb(_setError('BitPay Card Error: Get Token', data));
}); });
}; };
root.transactionHistory = function(dateRange, cb) { root.bitAuthPair = function(obj, cb) {
var params; _getSession(function(err, session) {
if (!dateRange.startDate) { if (err) return cb(err);
params = ''; var deviceName = 'Unknow device';
} else { if (platformInfo.isNW) {
params = '/?startDate=' + dateRange.startDate + '&endDate=' + dateRange.endDate; deviceName = require('os').platform();
} else if (platformInfo.isCordova) {
deviceName = device.model;
}
var userData = {
csrf: session.csrfToken,
secret: obj.secret,
deviceName: deviceName,
code: obj.otp
};
var dataToSign = credentials.BITPAY_API_URL + '/visa-api/validateBitAuthPairingCode' + JSON.stringify(userData);
var signedData = bitauthService.sign(dataToSign, credentials.BITPAY_PRIV_KEY);
$http({
method: 'POST',
url: credentials.BITPAY_API_URL + '/visa-api/validateBitAuthPairingCode',
headers: {
'content-type': 'application/json',
'x-csrf-token': session.csrfToken,
'x-identity': pubkey,
'x-signature': signedData
},
data: userData
}).then(function(data) {
$log.info('BitPay Card BitAuth: SUCCESS');
// Set an UI flag
storageService.setNextStep('BitpayCard', true, function(err) {});
// Get cards
_afterBitAuthSuccess(obj, cb);
}, function(data) {
return cb(_setError('BitPay Card Error: BitAuth', data));
});
});
};
var _processTransactions = function(invoices, history) {
invoices = invoices || [];
for (var i = 0; i < invoices.length; i++) {
var matched = false;
for (var j = 0; j < history.length; j++) {
if (history[j].description[0].indexOf(invoices[i].id) > -1) {
matched = true;
}
}
if (!matched && ['paid', 'confirmed', 'complete'].indexOf(invoices[i].status) > -1) {
history.unshift({
timestamp: invoices[i].invoiceTime,
description: invoices[i].itemDesc,
amount: invoices[i].price,
type: '00611 = Client Funded Deposit',
pending: true,
status: invoices[i].status
});
}
} }
$http(_getBitPay('/visa-api/transactionHistory' + params)).then(function(data) { return history;
$log.info('BitPay Get Transaction History: SUCCESS'); };
return cb(null, data.data.data);
}, function(data) { root.getHistory = function(cardId, params, cb) {
return cb(_setError('BitPay Card Error: Get Transaction History', data)); params = params || {};
var json = {};
// json = {
// method: 'getInvoiceHistory'
// };
root.getBitpayDebitCards(function(err, data) {
var card = lodash.find(data.cards, {id : cardId});
if (!card) return cb(_setError('No card available'));
// Get invoices
// $http(_postBitAuth('/api/v2/' + card.token, json)).then(function(data) {
// var invoices = data.data.data;
json = {
method: 'getTransactionHistory',
params: JSON.stringify(params)
};
// Get transactions list
$http(_postBitAuth('/api/v2/' + card.token, json)).then(function(data) {
$log.info('BitPay Get Transactions: SUCCESS');
var history = data.data.data || data.data;
// history['txs'] = _processTransactions(invoices, history.transactionList);
return cb(null, history);
}, function(data) {
return cb(_setError('BitPay Card Error: Get Transactions', data));
});
// }, function(data) {
// return cb(_setError('BitPay Card Error: Get Invoices', data));
// });
}); });
}; };
root.invoiceHistory = function(cb) { root.topUp = function(cardId, params, cb) {
$http(_getBitPay('/visa-api/invoiceHistory')).then(function(data) { var json = {
$log.info('BitPay Get Invoice History: SUCCESS'); method: 'generateTopUpInvoice',
return cb(null, data.data.data); params: JSON.stringify(params)
}, function(data) { };
return cb(_setError('BitPay Card Error: Get Invoice History', data)); root.getBitpayDebitCards(function(err, data) {
var card = lodash.find(data.cards, {id : cardId});
if (!card) return cb(_setError('No card available'));
$http(_postBitAuth('/api/v2/' + card.token, json)).then(function(data) {
$log.info('BitPay TopUp: SUCCESS');
var invoiceId = data.data.data.invoice;
return cb(null, invoiceId);
}, function(data) {
return cb(_setError('BitPay Card Error: TopUp', data));
});
}); });
}; };
@ -138,58 +272,9 @@ angular.module('copayApp.services').factory('bitpayCardService', function($http,
}); });
}; };
root.authenticate = function(userData, cb) { root.getBitpayDebitCards = function(cb) {
_setUser(userData, function(err) {
$http(_postBitPay('/visa-api/authenticate', userData)).then(function(data) {
$log.info('BitPay Authenticate: SUCCESS');
_getSession(function(err, session) {
if (err) return cb(err);
return cb(null, session);
});
}, function(data) {
if (data && data.data && data.data.error.twoFactorPending) {
$log.error('BitPay Card needs 2FA Authentication');
_getSession(function(err, session) {
if (err) return cb(err);
return cb(null, session);
});
} else {
return cb(data);
}
});
});
};
root.authenticate2FA = function(userData, cb) {
$http(_postBitPay('/visa-api/verify-two-factor', userData)).then(function(data) {
$log.info('BitPay 2FA: SUCCESS');
return cb(null, data);
}, function(data) {
return cb(_setError('BitPay Card Error: 2FA', data));
});
};
root.isAuthenticated = function(cb) {
_getSession(function(err, session) {
if (err) return cb(err);
if (!session.isAuthenticated) {
_getUser(function(err, user) {
if (err) return cb(err);
if (lodash.isEmpty(user)) return cb(null, session);
root.authenticate(user, function(err, session) {
if (err) return cb(err);
return cb(null, session);
});
});
} else {
return cb(null, session);
}
});
};
root.getCacheData = function(cb) {
_setCredentials(); _setCredentials();
storageService.getBitpayCardCache(credentials.NETWORK, function(err, data) { storageService.getBitpayDebitCards(credentials.NETWORK, function(err, data) {
if (err) return cb(err); if (err) return cb(err);
if (lodash.isString(data)) { if (lodash.isString(data)) {
data = JSON.parse(data); data = JSON.parse(data);
@ -199,18 +284,18 @@ angular.module('copayApp.services').factory('bitpayCardService', function($http,
}); });
}; };
root.setCacheData = function(data, cb) { root.setBitpayDebitCards = function(data, cb) {
_setCredentials(); _setCredentials();
data = JSON.stringify(data); data = JSON.stringify(data);
storageService.setBitpayCardCache(credentials.NETWORK, data, function(err) { storageService.setBitpayDebitCards(credentials.NETWORK, data, function(err) {
if (err) return cb(err); if (err) return cb(err);
return cb(); return cb();
}); });
}; };
root.removeCacheData = function(cb) { root.removeBitpayDebitCards = function(cb) {
_setCredentials(); _setCredentials();
storageService.removeBitpayCardCache(credentials.NETWORK, function(err) { storageService.removeBitpayDebitCards(credentials.NETWORK, function(err) {
if (err) return cb(err); if (err) return cb(err);
return cb(); return cb();
}); });
@ -218,14 +303,8 @@ angular.module('copayApp.services').factory('bitpayCardService', function($http,
root.logout = function(cb) { root.logout = function(cb) {
_setCredentials(); _setCredentials();
root.removeCacheData(function() {}); storageService.removeBitpayDebitCards(credentials.NETWORK, function(err) {
storageService.removeBitpayCard(credentials.NETWORK, function(err) { $log.info('BitPay Logout: SUCCESS');
$http(_getBitPay('/visa-api/logout')).then(function(data) {
$log.info('BitPay Logout: SUCCESS');
return cb(data);
}, function(data) {
return cb(_setError('BitPay Card Error: Logout ', data));
});
}); });
}; };

View File

@ -23,6 +23,16 @@ angular.module('copayApp.services').factory('incomingData', function($log, $ioni
return newUri; return newUri;
}; };
function getParameterByName(name, url) {
if (!url) return;
name = name.replace(/[\[\]]/g, "\\$&");
var regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"),
results = regex.exec(url);
if (!results) return null;
if (!results[2]) return '';
return decodeURIComponent(results[2].replace(/\+/g, " "));
}
// data extensions for Payment Protocol with non-backwards-compatible request // data extensions for Payment Protocol with non-backwards-compatible request
if ((/^bitcoin:\?r=[\w+]/).exec(data)) { if ((/^bitcoin:\?r=[\w+]/).exec(data)) {
data = decodeURIComponent(data.replace('bitcoin:?r=', '')); data = decodeURIComponent(data.replace('bitcoin:?r=', ''));
@ -33,7 +43,6 @@ angular.module('copayApp.services').factory('incomingData', function($log, $ioni
return true; return true;
} }
data = sanitizeUri(data); data = sanitizeUri(data);
// BIP21 // BIP21
@ -87,6 +96,21 @@ angular.module('copayApp.services').factory('incomingData', function($log, $ioni
} else if (data && data.indexOf($window.appConfig.name + '://coinbase')==0) { } else if (data && data.indexOf($window.appConfig.name + '://coinbase')==0) {
return $state.go('uricoinbase', {url: data}); return $state.go('uricoinbase', {url: data});
// BitPayCard Authentication
} else if (data && data.indexOf($window.appConfig.name + '://')==0) {
var secret = getParameterByName('secret', data);
var email = getParameterByName('email', data);
var otp = getParameterByName('otp', data);
$state.go('tabs.home');
$timeout(function() {
$state.transitionTo('tabs.bitpayCardIntro', {
secret: secret,
email: email,
otp: otp
});
}, 100);
return true;
// Join // Join
} else if (data && data.match(/^copay:[0-9A-HJ-NP-Za-km-z]{70,80}$/)) { } else if (data && data.match(/^copay:[0-9A-HJ-NP-Za-km-z]{70,80}$/)) {
$state.go('tabs.home'); $state.go('tabs.home');

View File

@ -337,16 +337,16 @@ angular.module('copayApp.services')
storage.remove('bitpayCard-' + network, cb); storage.remove('bitpayCard-' + network, cb);
}; };
root.setBitpayCardCache = function(network, data, cb) { root.setBitpayDebitCards = function(network, data, cb) {
storage.set('bitpayCardCache-' + network, data, cb); storage.set('bitpayDebitCards-' + network, data, cb);
}; };
root.getBitpayCardCache = function(network, cb) { root.getBitpayDebitCards = function(network, cb) {
storage.get('bitpayCardCache-' + network, cb); storage.get('bitpayDebitCards-' + network, cb);
}; };
root.removeBitpayCardCache = function(network, cb) { root.removeBitpayDebitCards = function(network, cb) {
storage.remove('bitpayCardCache-' + network, cb); storage.remove('bitpayDebitCards-' + network, cb);
}; };
root.removeAllWalletData = function(walletId, cb) { root.removeAllWalletData = function(walletId, cb) {

View File

@ -12,15 +12,15 @@
<div> <div>
<div class="item item-no-bottom-border" translate>Recipient</div> <div class="item item-no-bottom-border" translate>Recipient</div>
<div class="item item-text-wrap item-icon-left bitcoin-address" ng-class="{'item-big-icon-left':isCard}"> <div class="item item-text-wrap item-icon-left bitcoin-address" ng-class="{'item-big-icon-left':cardId}">
<i ng-if="isWallet" class="icon ion-briefcase size-21"></i> <i ng-if="isWallet" class="icon ion-briefcase size-21"></i>
<span ng-if="!isWallet"> <span ng-if="!isWallet">
<gravatar ng-if="!isCard" class="send-gravatar" name="{{toName}}" width="30" email="{{toEmail}}"></gravatar> <gravatar ng-if="!cardId" class="send-gravatar" name="{{toName}}" width="30" email="{{toEmail}}"></gravatar>
<i ng-if="isCard" class="icon big-icon-svg"> <i ng-if="cardId" class="icon big-icon-svg">
<div class="bg icon-bitpay-card"></div> <div class="bg icon-bitpay-card"></div>
</i> </i>
</span> </span>
<span ng-class="{'m10l':isCard}">{{toName || toAddress}}</span> <span ng-class="{'m10l':cardId}">{{toName || toAddress}}</span>
</div> </div>
</div> </div>

View File

@ -3,8 +3,8 @@
<ion-nav-back-button> <ion-nav-back-button>
</ion-nav-back-button> </ion-nav-back-button>
<ion-nav-title>BitPay Card</ion-nav-title> <ion-nav-title>BitPay Card</ion-nav-title>
<ion-nav-buttons side="secondary" ng-show="bitpayCard.bitpayCardAuthenticated"> <ion-nav-buttons side="secondary">
<button class="button back-button" ui-sref="tabs.bitpayCard.preferences"> <button class="button back-button" ng-show="!error" ui-sref="tabs.bitpayCard.preferences">
<i class="icon ion-ios-gear-outline"></i> <i class="icon ion-ios-gear-outline"></i>
</button> </button>
</ion-nav-buttons> </ion-nav-buttons>
@ -16,147 +16,76 @@
Sandbox version. Only for testing purpose Sandbox version. Only for testing purpose
</div> </div>
<div class="m20t text-center size-12 text-gray" ng-show="!bitpayCard.bitpayCardAuthenticated && bitpayCard.loadingSession"> <div class="oh pr" ng-show="!error">
Loading... <div class="amount">
</div> <div ng-if="bitpayCard.bitpayCardCurrentBalance" ng-click="bitpayCard.update()">
<div class="size-36 m20b">${{bitpayCard.bitpayCardCurrentBalance}}</div>
<div ng-show="!bitpayCard.bitpayCardAuthenticated && !bitpayCard.loadingSession"> <a class="button button-positive button-small" ui-sref="tabs.bitpayCard.amount({'cardId': cardId, 'toName': 'BitPay Card'})">
<div class="text-center m20t"> <i class="icon ion-ios-plus-empty"></i> {{'Add Funds'|translate}}
<img src="img/bitpay-card-visa.svg" width="200"> </a>
</div> </div>
<h4 class="text-center"> <div ng-if="!bitpayCard.bitpayCardCurrentBalance" class="m10t">
<span ng-show="!bitpayCard.bitpayCardTwoFactorPending">Login to your account</span> <strong class="size-36">...</strong>
<span ng-show="bitpayCard.bitpayCardTwoFactorPending">2-Step Verification</span>
</h4>
<form
ng-show="!bitpayCard.bitpayCardTwoFactorPending"
name="authenticateForm"
ng-submit="bitpayCard.authenticate(email, password)"
novalidate>
<div class="card list">
<label class="item item-input item-stacked-label">
<span class="input-label">Email</span>
<input name="email"
type="email"
ng-model="email"
ng-disabled="bitpayCard.authenticating"
required>
</label>
<label class="item item-input item-stacked-label">
<span class="input-label">Password</span>
<input name="password"
type="password"
ng-model="password"
ng-disabled="bitpayCard.authenticating"
required>
</label>
</div>
<input class="button button-block button-positive"
type="submit"
ng-disabled="!authenticateForm.$valid || bitpayCard.authenticating"
value="Login">
</form>
<p ng-show="bitpayCard.bitpayCardTwoFactorPending" class="size-12 text-center text-gray">
Enter the verification code generated by the authenticator app on your phone.
</p>
<form
ng-show="bitpayCard.bitpayCardTwoFactorPending"
name="authenticate2FAForm"
ng-submit="bitpayCard.authenticate2FA(twoFactorCode)"
novalidate>
<div class="list">
<label class="item item-input item-stacked-label">
<span class="input-label">Verification Code</span>
<input name="twoFactorCode"
type="text"
ng-model="twoFactorCode"
ng-disabled="bitpayCard.authenticating"
required>
</label>
</div>
<input class="button button-block button-positive"
type="submit"
ng-disabled="!authenticate2FAForm.$valid || bitpayCard.authenticating"
value="Login">
</form>
</div>
<div ng-show="bitpayCard.bitpayCardAuthenticated">
<div class="oh pr">
<div class="amount">
<div ng-if="bitpayCard.bitpayCardCurrentBalance" ng-click="bitpayCard.update()">
<div class="size-36 m20b">${{bitpayCard.bitpayCardCurrentBalance}}</div>
<a class="button button-positive button-small" ui-sref="tabs.bitpayCard.amount({'isCard': true, 'toName': 'BitPay Card'})">
<i class="icon ion-ios-plus-empty"></i> {{'Add Funds'|translate}}
</a>
</div>
<div ng-if="!bitpayCard.bitpayCardCurrentBalance" class="m10t">
<strong class="size-36">...</strong>
</div>
</div>
<div class="wallet-details-wallet-info">
<img style="height:0.6em" ng-show="loadingHistory" src="img/icon-sync-white.svg">
</div> </div>
</div> </div>
<div class="wallet-details-wallet-info">
<img style="height:0.6em" ng-show="loadingHistory" src="img/icon-sync-white.svg">
</div>
</div>
<div ng-show="error" class="text-center m10t assertive">
{{error}}
</div>
<div
class="m10t text-center padding"
ng-if="!loadingHistory && !bitpayCard.bitpayCardTransactionHistory[0] && !error">
<i class="icon ion-ios-arrow-thin-up size-24"></i>
<h1>Get started</h1>
<h4>Your BitPay Card is ready. Add funds to your card to start using your card at stores and ATMs worldwide.</h4>
</div>
<div class="list" ng-show="bitpayCard.bitpayCardTransactionHistory[0]">
<div class="item item-divider">
<select class="select-style" ng-model="dateRange" ng-change="bitpayCard.update(dateRange)">
<option value="last30Days">Recent Activity</option>
<option value="lastMonth">Last Month</option>
<option value="all">All Activity</option>
</select>
</div>
<div <div
class="m10t text-center padding" ng-repeat="tx in bitpayCard.bitpayCardTransactionHistory | orderBy: ['pending','-timestamp']"
ng-if="!bitpayCardCached && !loadingHistory && !bitpayCard.bitpayCardTransactionHistory[0]"> class="item row"
<i class="icon ion-ios-arrow-thin-up size-24"></i> ng-init="bitpayCard.getMerchantInfo(tx)">
<h1>Get started</h1> <div class="col col-10" ng-init="icon = bitpayCard.getIconName(tx)">
<h4>Your BitPay Card is ready. Add funds to your card to start using your card at stores and ATMs worldwide.</h4> <img class="m5t" ng-src="img/mcc-icons/{{icon}}.svg" width="22">
</div> </div>
<div class="list" ng-show="bitpayCardCached"> <div class="col">
<div class="item item-divider"> <div class="size-12 text-bold">
<select class="select-style" ng-model="dateRange" ng-change="bitpayCard.update(dateRange)"> {{tx.merchant.name}}
<option value="last30Days">Recent Activity</option> </div>
<option value="lastMonth">Last Month</option> <div class="size-12">
<option value="all">All Activity</option> {{tx.merchant.city}}, {{tx.merchant.state}}
</select> </div>
</div> </div>
<div <div
ng-repeat="tx in bitpayCard.bitpayCardTransactionHistory | orderBy: ['pending','-timestamp']" ng-init="desc = bitpayCard.processDescription(tx)"
class="item row" class="col size-12">
ng-init="bitpayCard.getMerchantInfo(tx)"> {{desc}}
<div class="col col-10" ng-init="icon = bitpayCard.getIconName(tx)"> </div>
<img class="m5t" ng-src="img/mcc-icons/{{icon}}.svg" width="22"> <div class="col">
</div> <img ng-show="!tx.pending" ng-src="img/check.svg" width="14">
<img ng-show="tx.pending" ng-src="img/sync.svg" width="14">
<div class="col"> </div>
<div class="size-12 text-bold"> <div class="col text-right size-12 text-gray">
{{tx.merchant.name}} <div class="size-14"
</div> ng-class="{
<div class="size-12"> 'text-success': tx.amount.indexOf('-') == -1 && !tx.pending,
{{tx.merchant.city}}, {{tx.merchant.state}} 'text-gray': tx.amount.indexOf('-') == -1 && tx.pending}">
</div> {{tx.amount | currency:'$':2 }}
</div>
<div
ng-init="desc = bitpayCard.processDescription(tx)"
class="col size-12">
{{desc}}
</div>
<div class="col">
<img ng-show="!tx.pending" ng-src="img/check.svg" width="14">
<img ng-show="tx.pending" ng-src="img/sync.svg" width="14">
</div>
<div class="col text-right size-12 text-gray">
<div class="size-14"
ng-class="{
'text-success': tx.amount.indexOf('-') == -1 && !tx.pending,
'text-gray': tx.amount.indexOf('-') == -1 && tx.pending}">
{{tx.amount | currency:'$':2 }}
</div>
<time>{{tx.timestamp | amTimeAgo}}</time>
</div> </div>
<time>{{tx.timestamp | amTimeAgo}}</time>
</div> </div>
</div> </div>
</div> </div>

View File

@ -23,7 +23,16 @@
<div class="item"> <div class="item">
<span class="label" translate>To</span> <span class="label" translate>To</span>
<span class="payment-proposal-to" copy-to-clipboard="toAddress"> <span class="payment-proposal-to" copy-to-clipboard="toAddress">
<img src="img/icon-bitcoin-small.svg"> <img ng-if="isWallet" src="img/icon-bitcoin-small.svg">
<i ng-if="cardId" class="icon big-icon-svg">
<div class="bg icon-bitpay-card"></div>
</i>
<span ng-show="toName">{{toName}}</span>
<div ng-show="_paypro" ng-click="openPPModal(_paypro)">
<i ng-show="_paypro.verified && _paypro.caTrusted" class="ion-locked" style="color:green"></i>
<i ng-show="!_paypro.caTrusted" class="ion-unlocked" style="color:red"></i>
{{_paypro.domain}}
</div>
<contact class="ellipsis" address="{{toAddress}}">{{toAddress}}</contact> <contact class="ellipsis" address="{{toAddress}}">{{toAddress}}</contact>
<!-- <contact ng-if="!tx.hasMultiplesOutputs" class="ellipsis" address="{{toAddress}}"></contact> <!-- <contact ng-if="!tx.hasMultiplesOutputs" class="ellipsis" address="{{toAddress}}"></contact>
<span ng-if="tx.hasMultiplesOutputs" translate>Multiple recipients</span> --> <span ng-if="tx.hasMultiplesOutputs" translate>Multiple recipients</span> -->

View File

@ -78,7 +78,6 @@
{{wallet.m}}-of-{{wallet.n}} {{wallet.m}}-of-{{wallet.n}}
</span> </span>
</span> </span>
<p> <p>
<span ng-if="!wallet.isComplete()" class="assertive" translate> <span ng-if="!wallet.isComplete()" class="assertive" translate>
Incomplete Incomplete
@ -91,15 +90,15 @@
</p> </p>
<i class="icon bp-arrow-right"></i> <i class="icon bp-arrow-right"></i>
</a> </a>
<a ui-sref="tabs.bitpayCard" <a ng-repeat="card in bitpayCards"
ng-if="wallets[0] && externalServices.BitpayCard && bitpayCardEnabled" ui-sref="tabs.bitpayCard({id:card.id})"
class="item item-sub item-icon-left item-big-icon-left item-icon-right"> ng-if="bitpayCardEnabled && bitpayCards[0]"
class="item item-icon-left item-big-icon-left item-icon-right">
<i class="icon big-icon-svg"> <i class="icon big-icon-svg">
<div class="bg icon-bitpay-card"></div> <div class="bg icon-bitpay-card"></div>
</i> </i>
<h2>BitPay Card</h2> <h2>BitPay Card</h2>
<p ng-if="!bitpayCard" translate>Add funds to get started</p> <p>{{card.lastFourDigits}}</p>
<span ng-if="bitpayCard">${{bitpayCard.balance}}</span>
<i class="icon bp-arrow-right"></i> <i class="icon bp-arrow-right"></i>
</a> </a>
</div> </div>