Improved bitpay account pairing and management of paired state and data.
This commit is contained in:
parent
08447a13fd
commit
8178bf1201
24 changed files with 733 additions and 232 deletions
|
|
@ -1,5 +1,5 @@
|
|||
'use strict';
|
||||
angular.module('copayApp.controllers').controller('bitpayCardIntroController', function($scope, $log, $state, $ionicHistory, storageService, externalLinkService, bitpayCardService, gettextCatalog, popupService, appIdentityService, bitpayService) {
|
||||
angular.module('copayApp.controllers').controller('bitpayCardIntroController', function($scope, $log, $state, $ionicHistory, storageService, externalLinkService, bitpayCardService, gettextCatalog, popupService, bitpayAccountService) {
|
||||
|
||||
$scope.$on("$ionicView.beforeEnter", function(event, data) {
|
||||
if (data.stateParams && data.stateParams.secret) {
|
||||
|
|
@ -9,7 +9,7 @@ angular.module('copayApp.controllers').controller('bitpayCardIntroController', f
|
|||
otp: data.stateParams.otp
|
||||
};
|
||||
var pairingReason = gettextCatalog.getString('add your BitPay Visa<sup>®</sup> card(s)');
|
||||
bitpayService.pair(pairData, pairingReason, function(err, paired, apiContext) {
|
||||
bitpayAccountService.pair(pairData, pairingReason, function(err, paired, apiContext) {
|
||||
if (err) {
|
||||
popupService.showAlert(gettextCatalog.getString('Error'), err);
|
||||
return;
|
||||
|
|
@ -22,29 +22,28 @@ angular.module('copayApp.controllers').controller('bitpayCardIntroController', f
|
|||
}
|
||||
// Set flag for nextStep
|
||||
storageService.setNextStep('BitpayCard', 'true', function(err) {});
|
||||
// Save data
|
||||
bitpayCardService.setBitpayDebitCards(data, function(err) {
|
||||
if (err) return;
|
||||
$ionicHistory.nextViewOptions({
|
||||
disableAnimate: true
|
||||
});
|
||||
$state.go('tabs.home').then(function() {
|
||||
if (data.cards[0]) {
|
||||
$state.transitionTo('tabs.bitpayCard', {
|
||||
id: data.cards[0].id
|
||||
});
|
||||
}
|
||||
});
|
||||
$ionicHistory.nextViewOptions({
|
||||
disableAnimate: true
|
||||
});
|
||||
$state.go('tabs.home').then(function() {
|
||||
if (data.cards[0]) {
|
||||
$state.transitionTo('tabs.bitpayCard', {
|
||||
id: data.cards[0].id
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
appIdentityService.getIdentity(bitpayService.getEnvironment(), function(err, appIdentity) {
|
||||
if (err) popupService.showAlert(null, err);
|
||||
else $log.info('App identity: OK');
|
||||
});
|
||||
}
|
||||
|
||||
bitpayAccountService.getAccounts(function(err, accounts) {
|
||||
if (err) {
|
||||
popupService.showAlert(gettextCatalog.getString('Error'), err);
|
||||
return;
|
||||
}
|
||||
$scope.accounts = accounts;
|
||||
});
|
||||
});
|
||||
|
||||
$scope.bitPayCardInfo = function() {
|
||||
|
|
@ -58,7 +57,37 @@ angular.module('copayApp.controllers').controller('bitpayCardIntroController', f
|
|||
};
|
||||
|
||||
$scope.connectBitPayCard = function() {
|
||||
var url = 'https://bitpay.com/visa/dashboard/add-to-bitpay-wallet-confirm';
|
||||
externalLinkService.open(url);
|
||||
if ($scope.accounts.length == 0) {
|
||||
startPairBitPayAccount();
|
||||
} else {
|
||||
showAccountSelector();
|
||||
}
|
||||
};
|
||||
|
||||
var startPairBitPayAccount = function() {
|
||||
var url = 'https://bitpay.com/visa/dashboard/add-to-bitpay-wallet-confirm';
|
||||
externalLinkService.open(url);
|
||||
};
|
||||
|
||||
var showAccountSelector = function() {
|
||||
$scope.accountSelectorTitle = gettextCatalog.getString('From BitPay account');
|
||||
$scope.showAccounts = ($scope.accounts != undefined);
|
||||
};
|
||||
|
||||
$scope.onAccountSelect = function(account) {
|
||||
if (account == undefined) {
|
||||
startPairBitPayAccount();
|
||||
} else {
|
||||
bitpayCardService.fetchBitpayDebitCards(account.apiContext, function(err, data) {
|
||||
if (err) {
|
||||
popupService.showAlert(gettextCatalog.getString('Error'), err);
|
||||
return;
|
||||
}
|
||||
storageService.setNextStep('BitpayCard', 'true', function(err) {
|
||||
$state.go('tabs.home');
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,34 +0,0 @@
|
|||
'use strict';
|
||||
|
||||
angular.module('copayApp.controllers').controller('preferencesBitpayCardController',
|
||||
function($scope, $state, $timeout, $ionicHistory, bitpayCardService, popupService, gettextCatalog) {
|
||||
|
||||
$scope.remove = function(card) {
|
||||
var msg = gettextCatalog.getString('Are you sure you would like to remove your BitPay Card ({{lastFourDigits}}) from this device?', {
|
||||
lastFourDigits: card.lastFourDigits
|
||||
});
|
||||
popupService.showConfirm(null, msg, null, null, function(res) {
|
||||
if (res) remove(card);
|
||||
});
|
||||
};
|
||||
|
||||
var remove = function(card) {
|
||||
bitpayCardService.remove(card, function(err) {
|
||||
if (err) {
|
||||
return popupService.showAlert(gettextCatalog.getString('Error'), gettextCatalog.getString('Could not remove card'));
|
||||
}
|
||||
$ionicHistory.clearHistory();
|
||||
$timeout(function() {
|
||||
$state.go('tabs.home');
|
||||
}, 100);
|
||||
});
|
||||
};
|
||||
|
||||
$scope.$on("$ionicView.beforeEnter", function(event, data) {
|
||||
bitpayCardService.getBitpayDebitCards(function(err, data) {
|
||||
if (err) return;
|
||||
$scope.bitpayCards = data;
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
75
src/js/controllers/preferencesBitpayServices.js
Normal file
75
src/js/controllers/preferencesBitpayServices.js
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
'use strict';
|
||||
|
||||
angular.module('copayApp.controllers').controller('preferencesBitpayServicesController',
|
||||
function($rootScope, $scope, $state, $timeout, $ionicHistory, bitpayAccountService, bitpayCardService, popupService, gettextCatalog) {
|
||||
|
||||
$scope.removeAccount = function(account) {
|
||||
var title = gettextCatalog.getString('Remove BitPay Account?');
|
||||
var msg = gettextCatalog.getString('Removing your BitPay account will remove all associated BitPay account data from this device.<br/><br/>Are you sure you would like to remove your BitPay Account ({{email}}) from this device?', {
|
||||
email: account.email
|
||||
});
|
||||
popupService.showConfirm(title, msg, null, null, function(res) {
|
||||
if (res) {
|
||||
removeAccount(account);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
$scope.removeCard = function(card) {
|
||||
var title = gettextCatalog.getString('Remove BitPay Card?');
|
||||
var msg = gettextCatalog.getString('Are you sure you would like to remove your BitPay Card ({{lastFourDigits}}) from this device?', {
|
||||
lastFourDigits: card.lastFourDigits
|
||||
});
|
||||
popupService.showConfirm(title, msg, null, null, function(res) {
|
||||
if (res) {
|
||||
removeCard(card);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
var removeAccount = function(account) {
|
||||
bitpayAccountService.removeAccount(account, function(err) {
|
||||
if (err) {
|
||||
return popupService.showAlert(gettextCatalog.getString('Error'), gettextCatalog.getString('Could not remove account'));
|
||||
}
|
||||
setScope(function() {
|
||||
// If there are no paired accounts then change views.
|
||||
if ($scope.bitpayAccounts.length == 0) {
|
||||
$state.go('tabs.settings').then(function() {
|
||||
$ionicHistory.clearHistory();
|
||||
$state.go('tabs.home');
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
var removeCard = function(card) {
|
||||
bitpayCardService.removeCard(card, function(err) {
|
||||
if (err) {
|
||||
return popupService.showAlert(gettextCatalog.getString('Error'), gettextCatalog.getString('Could not remove card'));
|
||||
}
|
||||
setScope();
|
||||
});
|
||||
};
|
||||
|
||||
var setScope = function(cb) {
|
||||
bitpayAccountService.getAccounts(function(err, data) {
|
||||
if (err) return;
|
||||
$scope.bitpayAccounts = data;
|
||||
|
||||
bitpayCardService.getBitpayDebitCards(function(err, data) {
|
||||
if (err) return;
|
||||
$scope.bitpayCards = data;
|
||||
if (cb) {
|
||||
cb();
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
$scope.$on("$ionicView.beforeEnter", function(event, data) {
|
||||
setScope();
|
||||
});
|
||||
|
||||
});
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
'use strict';
|
||||
|
||||
angular.module('copayApp.controllers').controller('tabSettingsController', function($scope, appConfigService, $ionicModal, $log, lodash, uxLanguage, platformInfo, profileService, feeService, configService, externalLinkService, bitpayCardService, storageService, glideraService, gettextCatalog) {
|
||||
angular.module('copayApp.controllers').controller('tabSettingsController', function($scope, appConfigService, $ionicModal, $log, lodash, uxLanguage, platformInfo, profileService, feeService, configService, externalLinkService, bitpayAccountService, bitpayCardService, storageService, glideraService, gettextCatalog) {
|
||||
|
||||
var updateConfig = function() {
|
||||
var isCordova = platformInfo.isCordova;
|
||||
|
|
@ -27,6 +27,11 @@ angular.module('copayApp.controllers').controller('tabSettingsController', funct
|
|||
$scope.bitpayCardEnabled = config.bitpayCard.enabled;
|
||||
$scope.glideraEnabled = config.glidera.enabled && !isWindowsPhoneApp;
|
||||
|
||||
bitpayAccountService.getAccounts(function(err, data) {
|
||||
if (err) $log.error(err);
|
||||
$scope.bitpayAccounts = !lodash.isEmpty(data);
|
||||
});
|
||||
|
||||
if ($scope.bitpayCardEnabled) {
|
||||
bitpayCardService.getBitpayDebitCards(function(err, cards) {
|
||||
if (err) $log.error(err);
|
||||
|
|
|
|||
28
src/js/directives/accountSelector.js
Normal file
28
src/js/directives/accountSelector.js
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
'use strict';
|
||||
|
||||
angular.module('copayApp.directives')
|
||||
.directive('accountSelector', function($timeout) {
|
||||
return {
|
||||
restrict: 'E',
|
||||
templateUrl: 'views/includes/accountSelector.html',
|
||||
transclude: true,
|
||||
scope: {
|
||||
title: '=accountSelectorTitle',
|
||||
show: '=accountSelectorShow',
|
||||
accounts: '=accountSelectorAccounts',
|
||||
selectedAccount: '=accountSelectorSelectedAccount',
|
||||
onSelect: '=accountSelectorOnSelect'
|
||||
},
|
||||
link: function(scope, element, attrs) {
|
||||
scope.hide = function() {
|
||||
scope.show = false;
|
||||
};
|
||||
scope.selectAccount = function(account) {
|
||||
$timeout(function() {
|
||||
scope.hide();
|
||||
}, 100);
|
||||
scope.onSelect(account);
|
||||
};
|
||||
}
|
||||
};
|
||||
});
|
||||
|
|
@ -1045,12 +1045,12 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr
|
|||
paypro: null
|
||||
}
|
||||
})
|
||||
.state('tabs.preferences.bitpayCard', {
|
||||
url: '/bitpay-card',
|
||||
.state('tabs.preferences.bitpayServices', {
|
||||
url: '/bitpay-services',
|
||||
views: {
|
||||
'tab-settings@tabs': {
|
||||
controller: 'preferencesBitpayCardController',
|
||||
templateUrl: 'views/preferencesBitpayCard.html'
|
||||
controller: 'preferencesBitpayServicesController',
|
||||
templateUrl: 'views/preferencesBitpayServices.html'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
|||
206
src/js/services/bitpayAccountService.js
Normal file
206
src/js/services/bitpayAccountService.js
Normal file
|
|
@ -0,0 +1,206 @@
|
|||
'use strict';
|
||||
|
||||
angular.module('copayApp.services').factory('bitpayAccountService', function($log, lodash, platformInfo, appIdentityService, bitpayService, bitpayCardService, storageService, gettextCatalog, popupService) {
|
||||
var root = {};
|
||||
|
||||
/*
|
||||
* Pair this app with the bitpay server using the specified pairing data.
|
||||
* An app identity will be created if one does not already exist.
|
||||
* Pairing data is provided by an input URI provided by the bitpay server.
|
||||
*
|
||||
* pairData - data needed to complete the pairing process
|
||||
* {
|
||||
* secret: shared pairing secret
|
||||
* email: email address associated with bitpay account
|
||||
* otp: two-factor one-time use password
|
||||
* }
|
||||
*
|
||||
* pairingReason - text string to be embedded into popup message. If `null` then the reason
|
||||
* message is not shown to the UI.
|
||||
* "To {{reason}} you must pair this app with your BitPay account ({{email}})."
|
||||
*
|
||||
* cb - callback after completion
|
||||
* callback(err, paired, apiContext)
|
||||
*
|
||||
* err - something unexpected happened which prevented the pairing
|
||||
*
|
||||
* paired - boolean indicating whether the pairing was compledted by the user
|
||||
*
|
||||
* apiContext - the context needed for making future api calls
|
||||
* {
|
||||
* token: api token for use in future calls
|
||||
* pairData: the input pair data
|
||||
* appIdentity: the identity of this app
|
||||
* }
|
||||
*/
|
||||
root.pair = function(pairData, pairingReason, cb) {
|
||||
checkOtp(pairData, function(otp) {
|
||||
pairData.otp = otp;
|
||||
var deviceName = 'Unknown device';
|
||||
if (platformInfo.isNW) {
|
||||
deviceName = require('os').platform();
|
||||
} else if (platformInfo.isCordova) {
|
||||
deviceName = device.model;
|
||||
}
|
||||
var json = {
|
||||
method: 'createToken',
|
||||
params: {
|
||||
secret: pairData.secret,
|
||||
version: 2,
|
||||
deviceName: deviceName,
|
||||
code: pairData.otp
|
||||
}
|
||||
};
|
||||
|
||||
bitpayService.postAuth(json, function(data) {
|
||||
if (data && data.data.error) {
|
||||
return cb(data.data.error);
|
||||
}
|
||||
var apiContext = {
|
||||
token: data.data.data,
|
||||
pairData: pairData,
|
||||
appIdentity: data.appIdentity
|
||||
};
|
||||
$log.info('BitPay service BitAuth create token: SUCCESS');
|
||||
|
||||
fetchBasicInfo(apiContext, function(err, basicInfo) {
|
||||
if (err) return cb(err);
|
||||
var title = gettextCatalog.getString('Add BitPay Account?');
|
||||
var msgDetail = 'Add this BitPay account ({{email}})?';
|
||||
if (pairingReason) {
|
||||
msgDetail = 'To {{reason}} you must first add your BitPay account.<br/><br/>{{email}}';
|
||||
}
|
||||
var msg = gettextCatalog.getString(msgDetail, {
|
||||
reason: pairingReason,
|
||||
email: pairData.email
|
||||
});
|
||||
var ok = gettextCatalog.getString('Add Account');
|
||||
var cancel = gettextCatalog.getString('Go back');
|
||||
popupService.showConfirm(title, msg, ok, cancel, function(res) {
|
||||
if (res) {
|
||||
var acctData = {
|
||||
token: apiContext.token,
|
||||
email: pairData.email,
|
||||
givenName: basicInfo.givenName,
|
||||
familyName: basicInfo.familyName
|
||||
};
|
||||
setBitpayAccount(acctData, function(err) {
|
||||
return cb(err, true, apiContext);
|
||||
});
|
||||
} else {
|
||||
$log.info('User cancelled BitPay pairing process');
|
||||
return cb(null, false);
|
||||
}
|
||||
});
|
||||
});
|
||||
}, function(data) {
|
||||
return cb(_setError('BitPay service BitAuth create token: ERROR ', data));
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
var checkOtp = function(pairData, cb) {
|
||||
if (pairData.otp) {
|
||||
var msg = gettextCatalog.getString('Enter Two Factor for your BitPay account');
|
||||
popupService.showPrompt(null, msg, null, function(res) {
|
||||
cb(res);
|
||||
});
|
||||
} else {
|
||||
cb();
|
||||
}
|
||||
};
|
||||
|
||||
var fetchBasicInfo = function(apiContext, cb) {
|
||||
var json = {
|
||||
method: 'getBasicInfo'
|
||||
};
|
||||
// Get basic account information
|
||||
bitpayService.post('/api/v2/' + apiContext.token, json, function(data) {
|
||||
if (data && data.data.error) return cb(data.data.error);
|
||||
$log.info('BitPay Account Get Basic Info: SUCCESS');
|
||||
return cb(null, data.data.data);
|
||||
}, function(data) {
|
||||
return cb(_setError('BitPay Account Error: Get Basic Info', data));
|
||||
});
|
||||
};
|
||||
|
||||
// Returns account objects as stored.
|
||||
root.getAccountsAsStored = function(cb) {
|
||||
storageService.getBitpayAccounts(bitpayService.getEnvironment().network, cb);
|
||||
};
|
||||
|
||||
// Returns an array where each element represents an account including all information required for fetching data
|
||||
// from the server for each account (apiContext).
|
||||
root.getAccounts = function(cb) {
|
||||
root.getAccountsAsStored(function(err, accounts) {
|
||||
if (err || !accounts) {
|
||||
return cb(err, []);
|
||||
}
|
||||
appIdentityService.getIdentity(bitpayService.getEnvironment().network, function(err, appIdentity) {
|
||||
if (err) {
|
||||
return cb(err);
|
||||
}
|
||||
|
||||
var accountsArray = [];
|
||||
lodash.forEach(Object.keys(accounts), function(key) {
|
||||
accounts[key].bitpayDebitCards = accounts[key]['bitpayDebitCards-' + bitpayService.getEnvironment().network];
|
||||
accounts[key].email = key;
|
||||
accounts[key].firstName = accounts[key]['basicInfo-' + bitpayService.getEnvironment().network].givenName;
|
||||
accounts[key].lastName = accounts[key]['basicInfo-' + bitpayService.getEnvironment().network].familyName;
|
||||
accounts[key].apiContext = {
|
||||
token: accounts[key]['bitpayApi-' + bitpayService.getEnvironment().network].token,
|
||||
pairData: {
|
||||
email: key
|
||||
},
|
||||
appIdentity: appIdentity
|
||||
};
|
||||
|
||||
// Remove environment keyed attributes.
|
||||
delete accounts[key]['bitpayApi-' + bitpayService.getEnvironment().network];
|
||||
delete accounts[key]['bitpayDebitCards-' + bitpayService.getEnvironment().network];
|
||||
|
||||
accountsArray.push(accounts[key]);
|
||||
});
|
||||
return cb(null, accountsArray);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
var setBitpayAccount = function(account, cb) {
|
||||
var data = JSON.stringify(account);
|
||||
storageService.setBitpayAccount(bitpayService.getEnvironment().network, data, function(err) {
|
||||
if (err) {
|
||||
return cb(err);
|
||||
}
|
||||
return cb();
|
||||
});
|
||||
};
|
||||
|
||||
root.removeAccount = function(account, cb) {
|
||||
storageService.removeBitpayAccount(bitpayService.getEnvironment().network, account, function(err) {
|
||||
if (err) {
|
||||
$log.error('Error removing BitPay account: ' + err);
|
||||
// Continue, try to remove next step if necessary
|
||||
}
|
||||
storageService.getBitpayDebitCards(bitpayService.getEnvironment().network, function(err, cards) {
|
||||
if (err) {
|
||||
$log.error('Error attempting to get BitPay debit cards after account removal: ' + err);
|
||||
}
|
||||
if (cards.length == 0) {
|
||||
storageService.removeNextStep('BitpayCard', cb);
|
||||
} else {
|
||||
cb();
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
var _setError = function(msg, e) {
|
||||
$log.error(msg);
|
||||
var error = (e && e.data && e.data.error) ? e.data.error : msg;
|
||||
return error;
|
||||
};
|
||||
|
||||
return root;
|
||||
|
||||
});
|
||||
|
|
@ -47,7 +47,14 @@ angular.module('copayApp.services').factory('bitpayCardService', function($log,
|
|||
bitpayService.post('/api/v2/' + apiContext.token, json, function(data) {
|
||||
if (data && data.data.error) return cb(data.data.error);
|
||||
$log.info('BitPay Get Debit Cards: SUCCESS');
|
||||
return cb(data.data.error, {token: apiContext.token, cards: data.data.data, email: apiContext.pairData.email});
|
||||
// Cache card data in storage
|
||||
var cardData = {
|
||||
cards: data.data.data,
|
||||
email: apiContext.pairData.email
|
||||
}
|
||||
root.setBitpayDebitCards(cardData, function(err) {
|
||||
return cb(err, {token: apiContext.token, cards: data.data.data, email: apiContext.pairData.email});
|
||||
});
|
||||
}, function(data) {
|
||||
return cb(_setError('BitPay Card Error: Get Debit Cards', data));
|
||||
});
|
||||
|
|
@ -178,12 +185,24 @@ angular.module('copayApp.services').factory('bitpayCardService', function($log,
|
|||
});
|
||||
};
|
||||
|
||||
root.remove = function(card, cb) {
|
||||
root.removeCard = function(card, cb) {
|
||||
storageService.removeBitpayDebitCard(bitpayService.getEnvironment().network, card, function(err) {
|
||||
if (err) {
|
||||
$log.error('Error removing BitPay debit card: ' + err);
|
||||
// Continue, try to remove/cleanup card history
|
||||
// Continue, try to remove/cleanup next step and card history
|
||||
}
|
||||
// Next two items in parallel
|
||||
//
|
||||
// If there are no more cards in storage then re-enable the next step entry
|
||||
storageService.getBitpayDebitCards(bitpayService.getEnvironment().network, function(err, cards) {
|
||||
if (err) {
|
||||
$log.error('Error getting BitPay debit cards after remove: ' + err);
|
||||
// Continue, try to remove next step if necessary
|
||||
}
|
||||
if (cards.length == 0) {
|
||||
storageService.removeNextStep('BitpayCard', cb);
|
||||
}
|
||||
});
|
||||
storageService.removeBitpayDebitCardHistory(bitpayService.getEnvironment().network, card, function(err) {
|
||||
if (err) {
|
||||
$log.error('Error removing BitPay debit card transaction history: ' + err);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
'use strict';
|
||||
|
||||
angular.module('copayApp.services').factory('bitpayService', function($log, $http, platformInfo, appIdentityService, bitauthService, storageService, gettextCatalog, popupService) {
|
||||
angular.module('copayApp.services').factory('bitpayService', function($log, $http, appIdentityService, bitauthService) {
|
||||
var root = {};
|
||||
|
||||
var NETWORK = 'livenet';
|
||||
|
|
@ -12,96 +12,6 @@ angular.module('copayApp.services').factory('bitpayService', function($log, $htt
|
|||
};
|
||||
};
|
||||
|
||||
/*
|
||||
* Pair this app with the bitpay server using the specified pairing data.
|
||||
* An app identity will be created if one does not already exist.
|
||||
* Pairing data is provided by an input URI provided by the bitpay server.
|
||||
*
|
||||
* pairData - data needed to complete the pairing process
|
||||
* {
|
||||
* secret: shared pairing secret
|
||||
* email: email address associated with bitpay account
|
||||
* otp: two-factor one-time use password
|
||||
* }
|
||||
*
|
||||
* pairingReason - text string to be embedded into popup message. If `null` then the reason
|
||||
* message is not shown to the UI.
|
||||
* "To {{reason}} you must pair this app with your BitPay account ({{email}})."
|
||||
*
|
||||
* cb - callback after completion
|
||||
* callback(err, paired, apiContext)
|
||||
*
|
||||
* err - something unexpected happened which prevented the pairing
|
||||
*
|
||||
* paired - boolean indicating whether the pairing was compledted by the user
|
||||
*
|
||||
* apiContext - the context needed for making future api calls
|
||||
* {
|
||||
* token: api token for use in future calls
|
||||
* pairData: the input pair data
|
||||
* appIdentity: the identity of this app
|
||||
* }
|
||||
*/
|
||||
root.pair = function(pairData, pairingReason, cb) {
|
||||
checkOtp(pairData, function(otp) {
|
||||
pairData.otp = otp;
|
||||
var deviceName = 'Unknown device';
|
||||
if (platformInfo.isNW) {
|
||||
deviceName = require('os').platform();
|
||||
} else if (platformInfo.isCordova) {
|
||||
deviceName = device.model;
|
||||
}
|
||||
var json = {
|
||||
method: 'createToken',
|
||||
params: {
|
||||
secret: pairData.secret,
|
||||
version: 2,
|
||||
deviceName: deviceName,
|
||||
code: pairData.otp
|
||||
}
|
||||
};
|
||||
appIdentityService.getIdentity(root.getEnvironment().network, function(err, appIdentity) {
|
||||
if (err) return cb(err);
|
||||
$http(_postAuth('/api/v2/', json, appIdentity)).then(function(data) {
|
||||
if (data && data.data.error) return cb(data.data.error);
|
||||
$log.info('BitPay service BitAuth create token: SUCCESS');
|
||||
var title = gettextCatalog.getString('Add BitPay Account?');
|
||||
var msgDetail = 'Add this BitPay account ({{email}})?';
|
||||
if (pairingReason) {
|
||||
msgDetail = 'To {{reason}} you must first add your BitPay account.<br/><br/>{{email}}';
|
||||
}
|
||||
var msg = gettextCatalog.getString(msgDetail, {
|
||||
reason: pairingReason,
|
||||
email: pairData.email
|
||||
});
|
||||
var ok = gettextCatalog.getString('Add Account');
|
||||
var cancel = gettextCatalog.getString('Go back');
|
||||
popupService.showConfirm(title, msg, ok, cancel, function(res) {
|
||||
if (res) {
|
||||
var acctData = {
|
||||
token: data.data.data,
|
||||
email: pairData.email
|
||||
};
|
||||
setBitpayAccount(acctData, function(err) {
|
||||
if (err) return cb(err);
|
||||
return cb(null, true, {
|
||||
token: acctData.token,
|
||||
pairData: pairData,
|
||||
appIdentity: appIdentity
|
||||
});
|
||||
});
|
||||
} else {
|
||||
$log.info('User cancelled BitPay pairing process');
|
||||
return cb(null, false);
|
||||
}
|
||||
});
|
||||
}, function(data) {
|
||||
return cb(_setError('BitPay service BitAuth create token: ERROR ', data));
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
root.get = function(endpoint, successCallback, errorCallback) {
|
||||
$http(_get(endpoint)).then(function(data) {
|
||||
successCallback(data);
|
||||
|
|
@ -112,7 +22,9 @@ angular.module('copayApp.services').factory('bitpayService', function($log, $htt
|
|||
|
||||
root.post = function(endpoint, json, successCallback, errorCallback) {
|
||||
appIdentityService.getIdentity(root.getEnvironment().network, function(err, appIdentity) {
|
||||
if (err) return errorCallback(err);
|
||||
if (err) {
|
||||
return errorCallback(err);
|
||||
}
|
||||
$http(_post(endpoint, json, appIdentity)).then(function(data) {
|
||||
successCallback(data);
|
||||
}, function(data) {
|
||||
|
|
@ -121,22 +33,17 @@ angular.module('copayApp.services').factory('bitpayService', function($log, $htt
|
|||
});
|
||||
};
|
||||
|
||||
var checkOtp = function(pairData, cb) {
|
||||
if (pairData.otp) {
|
||||
var msg = gettextCatalog.getString('Enter Two Factor for your BitPay account');
|
||||
popupService.showPrompt(null, msg, null, function(res) {
|
||||
cb(res);
|
||||
root.postAuth = function(json, successCallback, errorCallback) {
|
||||
appIdentityService.getIdentity(root.getEnvironment().network, function(err, appIdentity) {
|
||||
if (err) {
|
||||
return errorCallback(err);
|
||||
}
|
||||
$http(_postAuth('/api/v2/', json, appIdentity)).then(function(data) {
|
||||
data.appIdentity = appIdentity;
|
||||
successCallback(data);
|
||||
}, function(data) {
|
||||
errorCallback(data);
|
||||
});
|
||||
} else {
|
||||
cb();
|
||||
}
|
||||
};
|
||||
|
||||
var setBitpayAccount = function(accountData, cb) {
|
||||
var data = JSON.stringify(accountData);
|
||||
storageService.setBitpayAccount(root.getEnvironment().network, data, function(err) {
|
||||
if (err) return cb(err);
|
||||
return cb();
|
||||
});
|
||||
};
|
||||
|
||||
|
|
@ -184,12 +91,6 @@ angular.module('copayApp.services').factory('bitpayService', function($log, $htt
|
|||
return ret;
|
||||
};
|
||||
|
||||
var _setError = function(msg, e) {
|
||||
$log.error(msg);
|
||||
var error = (e && e.data && e.data.error) ? e.data.error : msg;
|
||||
return error;
|
||||
};
|
||||
|
||||
return root;
|
||||
|
||||
});
|
||||
|
|
|
|||
|
|
@ -220,6 +220,9 @@ angular.module('copayApp.services').factory('configService', function(storageSer
|
|||
if (!configCache.pushNotifications) {
|
||||
configCache.pushNotifications = defaultConfig.pushNotifications;
|
||||
}
|
||||
if (!configCache.bitpayAccount) {
|
||||
configCache.bitpayAccount = defaultConfig.bitpayAccount;
|
||||
}
|
||||
|
||||
} else {
|
||||
configCache = lodash.clone(defaultConfig);
|
||||
|
|
|
|||
|
|
@ -102,9 +102,10 @@ angular.module('copayApp.services')
|
|||
// match the upgrader key.
|
||||
//
|
||||
var _upgraders = {
|
||||
'00_bitpayDebitCards' : _upgrade_bitpayDebitCards, // 2016-11: Upgrade bitpayDebitCards-x to bitpayAccounts-x
|
||||
'01_bitpayCardCredentials' : _upgrade_bitpayCardCredentials, // 2016-11: Upgrade bitpayCardCredentials-x to appIdentity-x
|
||||
'02_bitpayAccounts' : _upgrade_bitpayAccounts // 2016-12: Upgrade tpayAccounts-x to bitpayAccounts-v2-x
|
||||
'00_bitpayDebitCards' : _upgrade_bitpayDebitCards, // 2016-11: Upgrade bitpayDebitCards-x to bitpayAccounts-x
|
||||
'01_bitpayCardCredentials' : _upgrade_bitpayCardCredentials, // 2016-11: Upgrade bitpayCardCredentials-x to appIdentity-x
|
||||
'02_bitpayAccounts' : _upgrade_bitpayAccounts, // 2016-12: Upgrade bitpayAccounts-x to bitpayAccounts-v2-x
|
||||
'03_bitpayAccounts' : _upgrade_bitpayAccounts_basicInfo // 2017-01: Upgrade bitpayAccounts-v2-x to bitpayAccounts-v3-x
|
||||
};
|
||||
|
||||
function _upgrade_bitpayDebitCards(key, network, cb) {
|
||||
|
|
@ -184,6 +185,44 @@ angular.module('copayApp.services')
|
|||
});
|
||||
});
|
||||
};
|
||||
|
||||
function _upgrade_bitpayAccounts_basicInfo(key, network, cb) {
|
||||
key += '-' + network;
|
||||
storage.get(key, function(err, data) {
|
||||
if (err) return cb(err);
|
||||
if (lodash.isString(data)) {
|
||||
data = JSON.parse(data);
|
||||
}
|
||||
data = data || {};
|
||||
var upgraded = '';
|
||||
Object.keys(data).forEach(function(key) {
|
||||
// Keys are account emails
|
||||
if (!data[key]['basicInfo-' + network]) {
|
||||
// Needs upgrade
|
||||
upgraded += ' ' + key;
|
||||
var acctData = {
|
||||
email: key,
|
||||
basicInfo: {
|
||||
givenName: key, // Just set the first name to the account email
|
||||
familyName: ''
|
||||
}
|
||||
};
|
||||
_03_setBitpayAccount(network, acctData, function(err) {
|
||||
if (err) return cb(err);
|
||||
});
|
||||
}
|
||||
});
|
||||
// Remove obsolete key.
|
||||
storage.remove('bitpayAccounts-v2-' + network, function() {
|
||||
if (upgraded.length > 0) {
|
||||
cb(null, 'upgraded to \'bitpayAccounts-v3-' + network + '\':' + upgraded);
|
||||
} else {
|
||||
cb();
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
|
|
@ -248,6 +287,24 @@ angular.module('copayApp.services')
|
|||
storage.set('bitpayAccounts-v2-' + network, JSON.stringify(bitpayAccounts), cb);
|
||||
});
|
||||
};
|
||||
|
||||
var _03_setBitpayAccount = function(network, data, cb) {
|
||||
if (lodash.isString(data)) {
|
||||
data = JSON.parse(data);
|
||||
}
|
||||
data = data || {};
|
||||
if (lodash.isEmpty(data) || !data.email) return cb('No account to set');
|
||||
storage.get('bitpayAccounts-v2-' + network, function(err, bitpayAccounts) {
|
||||
if (err) return cb(err);
|
||||
if (lodash.isString(bitpayAccounts)) {
|
||||
bitpayAccounts = JSON.parse(bitpayAccounts);
|
||||
}
|
||||
bitpayAccounts = bitpayAccounts || {};
|
||||
bitpayAccounts[data.email] = bitpayAccounts[data.email] || {};
|
||||
bitpayAccounts[data.email]['basicInfo-' + network] = data.basicInfo;
|
||||
storage.set('bitpayAccounts-v3-' + network, JSON.stringify(bitpayAccounts), cb);
|
||||
});
|
||||
};
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
|
@ -589,7 +646,7 @@ angular.module('copayApp.services')
|
|||
}
|
||||
data = data || {};
|
||||
if (lodash.isEmpty(data) || !data.email) return cb('Cannot set cards: no account to set');
|
||||
storage.get('bitpayAccounts-v2-' + network, function(err, bitpayAccounts) {
|
||||
storage.get('bitpayAccounts-v3-' + network, function(err, bitpayAccounts) {
|
||||
if (err) return cb(err);
|
||||
if (lodash.isString(bitpayAccounts)) {
|
||||
bitpayAccounts = JSON.parse(bitpayAccounts);
|
||||
|
|
@ -597,7 +654,7 @@ angular.module('copayApp.services')
|
|||
bitpayAccounts = bitpayAccounts || {};
|
||||
bitpayAccounts[data.email] = bitpayAccounts[data.email] || {};
|
||||
bitpayAccounts[data.email]['bitpayDebitCards-' + network] = data.cards;
|
||||
storage.set('bitpayAccounts-v2-' + network, JSON.stringify(bitpayAccounts), cb);
|
||||
storage.set('bitpayAccounts-v3-' + network, JSON.stringify(bitpayAccounts), cb);
|
||||
});
|
||||
};
|
||||
|
||||
|
|
@ -610,7 +667,7 @@ angular.module('copayApp.services')
|
|||
// email: account email
|
||||
// ]
|
||||
root.getBitpayDebitCards = function(network, cb) {
|
||||
storage.get('bitpayAccounts-v2-' + network, function(err, bitpayAccounts) {
|
||||
storage.get('bitpayAccounts-v3-' + network, function(err, bitpayAccounts) {
|
||||
if (lodash.isString(bitpayAccounts)) {
|
||||
bitpayAccounts = JSON.parse(bitpayAccounts);
|
||||
}
|
||||
|
|
@ -640,7 +697,7 @@ angular.module('copayApp.services')
|
|||
}
|
||||
card = card || {};
|
||||
if (lodash.isEmpty(card) || !card.eid) return cb('No card to remove');
|
||||
storage.get('bitpayAccounts-v2-' + network, function(err, bitpayAccounts) {
|
||||
storage.get('bitpayAccounts-v3-' + network, function(err, bitpayAccounts) {
|
||||
if (err) cb(err);
|
||||
if (lodash.isString(bitpayAccounts)) {
|
||||
bitpayAccounts = JSON.parse(bitpayAccounts);
|
||||
|
|
@ -655,16 +712,7 @@ angular.module('copayApp.services')
|
|||
data.cards = newCards;
|
||||
data.email = email;
|
||||
root.setBitpayDebitCards(network, data, function(err) {
|
||||
if (err) cb(err);
|
||||
// If there are no more cards in storage then re-enable the next step entry.
|
||||
root.getBitpayDebitCards(network, function(err, cards) {
|
||||
if (err) cb(err);
|
||||
if (cards.length == 0) {
|
||||
root.removeNextStep('BitpayCard', cb);
|
||||
} else {
|
||||
cb();
|
||||
}
|
||||
});
|
||||
cb(err);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -680,7 +728,7 @@ angular.module('copayApp.services')
|
|||
}
|
||||
data = data || {};
|
||||
if (lodash.isEmpty(data) || !data.email) return cb('No account to set');
|
||||
storage.get('bitpayAccounts-v2-' + network, function(err, bitpayAccounts) {
|
||||
storage.get('bitpayAccounts-v3-' + network, function(err, bitpayAccounts) {
|
||||
if (err) return cb(err);
|
||||
if (lodash.isString(bitpayAccounts)) {
|
||||
bitpayAccounts = JSON.parse(bitpayAccounts);
|
||||
|
|
@ -689,7 +737,11 @@ angular.module('copayApp.services')
|
|||
bitpayAccounts[data.email] = bitpayAccounts[data.email] || {};
|
||||
bitpayAccounts[data.email]['bitpayApi-' + network] = bitpayAccounts[data.email]['bitpayApi-' + network] || {};
|
||||
bitpayAccounts[data.email]['bitpayApi-' + network].token = data.token;
|
||||
storage.set('bitpayAccounts-v2-' + network, JSON.stringify(bitpayAccounts), cb);
|
||||
|
||||
bitpayAccounts[data.email]['basicInfo-' + network] = bitpayAccounts[data.email]['basicInfo-' + network] || {};
|
||||
bitpayAccounts[data.email]['basicInfo-' + network].givenName = data.givenName;
|
||||
bitpayAccounts[data.email]['basicInfo-' + network].familyName = data.familyName;
|
||||
storage.set('bitpayAccounts-v3-' + network, JSON.stringify(bitpayAccounts), cb);
|
||||
});
|
||||
};
|
||||
|
||||
|
|
@ -714,7 +766,7 @@ angular.module('copayApp.services')
|
|||
// }
|
||||
// }
|
||||
root.getBitpayAccounts = function(network, cb) {
|
||||
storage.get('bitpayAccounts-v2-' + network, function(err, bitpayAccounts) {
|
||||
storage.get('bitpayAccounts-v3-' + network, function(err, bitpayAccounts) {
|
||||
if (err) return cb(err);
|
||||
if (lodash.isString(bitpayAccounts)) {
|
||||
bitpayAccounts = JSON.parse(bitpayAccounts);
|
||||
|
|
@ -723,6 +775,28 @@ angular.module('copayApp.services')
|
|||
});
|
||||
};
|
||||
|
||||
// account: {
|
||||
// email: account email
|
||||
// apiContext: the context needed for making future api calls
|
||||
// bitpayDebitCards: an array of cards
|
||||
// }
|
||||
root.removeBitpayAccount = function(network, account, cb) {
|
||||
if (lodash.isString(account)) {
|
||||
account = JSON.parse(account);
|
||||
}
|
||||
account = account || {};
|
||||
if (lodash.isEmpty(account)) return cb('No account to remove');
|
||||
storage.get('bitpayAccounts-v3-' + network, function(err, bitpayAccounts) {
|
||||
if (err) cb(err);
|
||||
if (lodash.isString(bitpayAccounts)) {
|
||||
bitpayAccounts = JSON.parse(bitpayAccounts);
|
||||
}
|
||||
bitpayAccounts = bitpayAccounts || {};
|
||||
delete bitpayAccounts[account.email];
|
||||
storage.set('bitpayAccounts-v3-' + network, JSON.stringify(bitpayAccounts), cb);
|
||||
});
|
||||
};
|
||||
|
||||
root.setAppIdentity = function(network, data, cb) {
|
||||
storage.set('appIdentity-' + network, data, cb);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
#bitpayCard-intro {
|
||||
@extend .deflash-blue;
|
||||
background: url(../img/onboarding-welcome-bg.png), linear-gradient(to bottom, rgba(30, 49, 134, 1) 0%, rgba(17, 27, 73, 1) 100%);
|
||||
background-position: top center;
|
||||
background-size: contain;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
#bitpayCardPreferences {
|
||||
#bitpayServicesPreferences {
|
||||
.item {
|
||||
.item-title {
|
||||
display: block;
|
||||
|
|
@ -14,9 +14,13 @@
|
|||
&.item-icon-right {
|
||||
.icon-hotspot {
|
||||
right: 0px;
|
||||
padding-left: 11px;
|
||||
padding-right: 11px;
|
||||
}
|
||||
padding-left: 50px;
|
||||
}
|
||||
}
|
||||
.icon-unlink {
|
||||
background-image: url("../img/icon-unlink.svg");
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
79
src/sass/views/includes/accountSelector.scss
Normal file
79
src/sass/views/includes/accountSelector.scss
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
account-selector {
|
||||
|
||||
$border-color: #EFEFEF;
|
||||
text-align: left;
|
||||
|
||||
.bp-action-sheet__sheet {
|
||||
padding-left: 2rem;
|
||||
padding-right: .75rem;
|
||||
}
|
||||
|
||||
.account-selector {
|
||||
.account {
|
||||
border: 0;
|
||||
padding-right: 0;
|
||||
padding-top: 0;
|
||||
padding-left: 65px;
|
||||
padding-bottom: 0;
|
||||
margin-bottom: 1px;
|
||||
overflow: visible;
|
||||
|
||||
> i {
|
||||
padding: 0;
|
||||
margin-left: -5px;
|
||||
|
||||
> img {
|
||||
height: 39px;
|
||||
width: 39px;
|
||||
padding: 4px;
|
||||
background-color: $royal;
|
||||
|
||||
&.icon-add {
|
||||
background-color: $light-gray;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
.account-inner {
|
||||
display: flex;
|
||||
position: relative;
|
||||
padding-top: 16px;
|
||||
padding-bottom: 16px;
|
||||
|
||||
&::after {
|
||||
display: block;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
background: $border-color;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
content: '';
|
||||
}
|
||||
|
||||
.check {
|
||||
padding: 0 1.2rem;
|
||||
}
|
||||
}
|
||||
.account-details {
|
||||
flex-grow: 1;
|
||||
|
||||
.account-name {
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
|
||||
.account-email {
|
||||
color: #3A3A3A;
|
||||
font-family: "Roboto-Light";
|
||||
}
|
||||
|
||||
.account-add {
|
||||
padding-bottom: 16px;
|
||||
padding-top: 11px;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,8 +1,7 @@
|
|||
.settings {
|
||||
@extend .deflash-blue;
|
||||
.icon-bitpay-card {
|
||||
background-image: url("../img/icon-card.svg");
|
||||
background-color: #1e3186;
|
||||
.icon-bitpay {
|
||||
background-image: url("../img/icon-bitpay.svg");
|
||||
}
|
||||
.item {
|
||||
color: $dark-gray;
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
@import "advancedSettings";
|
||||
@import "bitpayCard";
|
||||
@import "bitpayCardIntro";
|
||||
@import "bitpayCardPreferences";
|
||||
@import "bitpayServicesPreferences";
|
||||
@import "address-book";
|
||||
@import "addresses";
|
||||
@import "wallet-backup-phrase";
|
||||
|
|
@ -41,6 +41,7 @@
|
|||
@import "includes/tx-status";
|
||||
@import "includes/itemSelector";
|
||||
@import "includes/walletSelector";
|
||||
@import "includes/accountSelector";
|
||||
@import "integrations/coinbase";
|
||||
@import "integrations/glidera";
|
||||
@import "custom-amount";
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue