Improved bitpay account pairing and management of paired state and data.

This commit is contained in:
Andy Phillipson 2017-01-06 12:11:47 -05:00
commit 8178bf1201
24 changed files with 733 additions and 232 deletions

View file

@ -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>&reg;</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');
});
});
}
};
});

View file

@ -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;
});
});
});

View 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();
});
});

View file

@ -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);

View 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);
};
}
};
});

View file

@ -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'
}
}
});

View 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;
});

View file

@ -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);

View file

@ -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;
});

View file

@ -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);

View file

@ -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);
};

View file

@ -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;

View file

@ -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;
}
}
}

View 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;
}
}
}
}

View file

@ -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;

View file

@ -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";