mirror of https://github.com/BTCPrivate/copay.git
Improved bitpay account pairing and management of paired state and data.
This commit is contained in:
parent
15d12823ab
commit
63bc3d8f63
|
@ -1,5 +1,5 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
angular.module('copayApp.controllers').controller('bitpayCardIntroController', function($scope, $log, $state, $ionicHistory, storageService, externalLinkService, bitpayCardService, gettextCatalog, popupService, appIdentityService, bitpayService, lodash) {
|
angular.module('copayApp.controllers').controller('bitpayCardIntroController', function($scope, $log, $state, $ionicHistory, storageService, externalLinkService, bitpayCardService, gettextCatalog, popupService, bitpayAccountService) {
|
||||||
|
|
||||||
$scope.$on("$ionicView.beforeEnter", function(event, data) {
|
$scope.$on("$ionicView.beforeEnter", function(event, data) {
|
||||||
if (data.stateParams && data.stateParams.secret) {
|
if (data.stateParams && data.stateParams.secret) {
|
||||||
|
@ -8,11 +8,8 @@ angular.module('copayApp.controllers').controller('bitpayCardIntroController', f
|
||||||
email: data.stateParams.email,
|
email: data.stateParams.email,
|
||||||
otp: data.stateParams.otp
|
otp: data.stateParams.otp
|
||||||
};
|
};
|
||||||
|
var pairingReason = gettextCatalog.getString('add your BitPay Visa card(s)');
|
||||||
var pairingReason = gettextCatalog.getString('BitPay Visa card');
|
bitpayAccountService.pair(pairData, pairingReason, function(err, paired, apiContext) {
|
||||||
|
|
||||||
bitpayService.pair(pairData, pairingReason, function(err, paired, apiContext) {
|
|
||||||
|
|
||||||
if (err) {
|
if (err) {
|
||||||
popupService.showAlert(gettextCatalog.getString('Error pairing Bitpay Account'), err);
|
popupService.showAlert(gettextCatalog.getString('Error pairing Bitpay Account'), err);
|
||||||
return;
|
return;
|
||||||
|
@ -25,26 +22,28 @@ angular.module('copayApp.controllers').controller('bitpayCardIntroController', f
|
||||||
}
|
}
|
||||||
// Set flag for nextStep
|
// Set flag for nextStep
|
||||||
storageService.setNextStep('BitpayCard', 'true', function(err) {});
|
storageService.setNextStep('BitpayCard', 'true', function(err) {});
|
||||||
|
|
||||||
$ionicHistory.nextViewOptions({
|
$ionicHistory.nextViewOptions({
|
||||||
disableAnimate: true
|
disableAnimate: true
|
||||||
});
|
});
|
||||||
$state.go('tabs.home').then(function() {
|
$state.go('tabs.home').then(function() {
|
||||||
if (cards[0]) {
|
if (data.cards[0]) {
|
||||||
$state.transitionTo('tabs.bitpayCard', {
|
$state.transitionTo('tabs.bitpayCard', {
|
||||||
id: cards[0].id
|
id: data.cards[0].id
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
appIdentityService.getIdentity(bitpayService.getEnvironment().network, 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() {
|
$scope.bitPayCardInfo = function() {
|
||||||
|
@ -58,7 +57,37 @@ angular.module('copayApp.controllers').controller('bitpayCardIntroController', f
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.connectBitPayCard = function() {
|
$scope.connectBitPayCard = function() {
|
||||||
|
if ($scope.accounts.length == 0) {
|
||||||
|
startPairBitPayAccount();
|
||||||
|
} else {
|
||||||
|
showAccountSelector();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var startPairBitPayAccount = function() {
|
||||||
var url = 'https://bitpay.com/visa/dashboard/add-to-bitpay-wallet-confirm';
|
var url = 'https://bitpay.com/visa/dashboard/add-to-bitpay-wallet-confirm';
|
||||||
externalLinkService.open(url);
|
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');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -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';
|
'use strict';
|
||||||
|
|
||||||
angular.module('copayApp.controllers').controller('tabSettingsController', function($scope, appConfigService, $log, lodash, uxLanguage, platformInfo, profileService, feeService, configService, externalLinkService, bitpayCardService, storageService, glideraService, coinbaseService, gettextCatalog, buyAndSellService) {
|
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 updateConfig = function() {
|
||||||
var isCordova = platformInfo.isCordova;
|
var isCordova = platformInfo.isCordova;
|
||||||
|
@ -25,12 +25,27 @@ angular.module('copayApp.controllers').controller('tabSettingsController', funct
|
||||||
isoCode: config.wallet.settings.alternativeIsoCode
|
isoCode: config.wallet.settings.alternativeIsoCode
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO move this to a generic service
|
$scope.bitpayCardEnabled = config.bitpayCard.enabled;
|
||||||
bitpayCardService.getCards(function(err, cards) {
|
$scope.glideraEnabled = config.glidera.enabled && !isWindowsPhoneApp;
|
||||||
|
|
||||||
|
bitpayAccountService.getAccounts(function(err, data) {
|
||||||
if (err) $log.error(err);
|
if (err) $log.error(err);
|
||||||
$scope.bitpayCards = cards && cards.length > 0;
|
$scope.bitpayAccounts = !lodash.isEmpty(data);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if ($scope.bitpayCardEnabled) {
|
||||||
|
bitpayCardService.getBitpayDebitCards(function(err, cards) {
|
||||||
|
if (err) $log.error(err);
|
||||||
|
$scope.bitpayCards = cards && cards.length > 0;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($scope.glideraEnabled) {
|
||||||
|
storageService.getGlideraToken(glideraService.getEnvironment(), function(err, token) {
|
||||||
|
if (err) $log.error(err);
|
||||||
|
$scope.glideraToken = token;
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
|
@ -1078,12 +1078,12 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr
|
||||||
paypro: null
|
paypro: null
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.state('tabs.preferences.bitpayCard', {
|
.state('tabs.preferences.bitpayServices', {
|
||||||
url: '/bitpay-card',
|
url: '/bitpay-services',
|
||||||
views: {
|
views: {
|
||||||
'tab-settings@tabs': {
|
'tab-settings@tabs': {
|
||||||
controller: 'preferencesBitpayCardController',
|
controller: 'preferencesBitpayServicesController',
|
||||||
templateUrl: 'views/preferencesBitpayCard.html'
|
templateUrl: 'views/preferencesBitpayServices.html'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -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,28 +47,13 @@ angular.module('copayApp.services').factory('bitpayCardService', function($log,
|
||||||
bitpayService.post('/api/v2/' + apiContext.token, json, function(data) {
|
bitpayService.post('/api/v2/' + apiContext.token, json, function(data) {
|
||||||
if (data && data.data.error) return cb(data.data.error);
|
if (data && data.data.error) return cb(data.data.error);
|
||||||
$log.info('BitPay Get Debit Cards: SUCCESS');
|
$log.info('BitPay Get Debit Cards: SUCCESS');
|
||||||
|
// Cache card data in storage
|
||||||
var cards = [];
|
var cardData = {
|
||||||
|
cards: data.data.data,
|
||||||
lodash.each(data.data.data, function(x) {
|
email: apiContext.pairData.email
|
||||||
var n = {};
|
}
|
||||||
|
root.setBitpayDebitCards(cardData, function(err) {
|
||||||
if (!x.eid || !x.id || !x.lastFourDigits || !x.token) {
|
return cb(err, {token: apiContext.token, cards: data.data.data, email: apiContext.pairData.email});
|
||||||
$log.warn('BAD data from Bitpay card' + JSON.stringify(x));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
n.eid = x.eid;
|
|
||||||
n.id = x.id;
|
|
||||||
n.lastFourDigits = x.lastFourDigits;
|
|
||||||
n.token = x.token;
|
|
||||||
cards.push(n);
|
|
||||||
});
|
|
||||||
|
|
||||||
storageService.setBitpayDebitCards(bitpayService.getEnvironment().network, apiContext.pairData.email, cards, function(err) {
|
|
||||||
register();
|
|
||||||
|
|
||||||
return cb(err, cards);
|
|
||||||
});
|
});
|
||||||
}, function(data) {
|
}, function(data) {
|
||||||
return cb(_setError('BitPay Card Error: Get Debit Cards', data));
|
return cb(_setError('BitPay Card Error: Get Debit Cards', data));
|
||||||
|
@ -201,15 +186,32 @@ angular.module('copayApp.services').factory('bitpayCardService', function($log,
|
||||||
}, cb);
|
}, cb);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
root.removeCard = function(card, cb) {
|
||||||
root.remove = function(cardId, cb) {
|
storageService.removeBitpayDebitCard(bitpayService.getEnvironment().network, card, function(err) {
|
||||||
storageService.removeBitpayDebitCard(bitpayService.getEnvironment().network, cardId, function(err) {
|
|
||||||
if (err) {
|
if (err) {
|
||||||
$log.error('Error removing BitPay debit card: ' + err);
|
$log.error('Error removing BitPay debit card: ' + err);
|
||||||
return cb(err);
|
// Continue, try to remove/cleanup next step and card history
|
||||||
}
|
}
|
||||||
register();
|
// Next two items in parallel
|
||||||
storageService.removeBalanceCache(cardId, cb);
|
//
|
||||||
|
// 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);
|
||||||
|
return cb(err);
|
||||||
|
}
|
||||||
|
$log.info('Successfully removed BitPay debit card');
|
||||||
|
return cb();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
angular.module('copayApp.services').factory('bitpayService', function($log, $http, platformInfo, appIdentityService, bitauthService, storageService, gettextCatalog, popupService, ongoingProcess) {
|
angular.module('copayApp.services').factory('bitpayService', function($log, $http, appIdentityService, bitauthService) {
|
||||||
var root = {};
|
var root = {};
|
||||||
|
|
||||||
var NETWORK = 'livenet';
|
var NETWORK = 'livenet';
|
||||||
|
@ -12,99 +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);
|
|
||||||
ongoingProcess.set('fetchingBitPayAccount', true);
|
|
||||||
$http(_postAuth('/api/v2/', json, appIdentity)).then(function(data) {
|
|
||||||
ongoingProcess.set('fetchingBitPayAccount', false);
|
|
||||||
|
|
||||||
if (data && data.data.error) return cb(data.data.error);
|
|
||||||
$log.info('BitPay service BitAuth create token: SUCCESS');
|
|
||||||
var title = gettextCatalog.getString('Link BitPay Account?');
|
|
||||||
var msgDetail = 'Link BitPay account ({{email}})?';
|
|
||||||
if (pairingReason) {
|
|
||||||
msgDetail = 'To add your {{reason}} please link your BitPay account {{email}}';
|
|
||||||
}
|
|
||||||
var msg = gettextCatalog.getString(msgDetail, {
|
|
||||||
reason: pairingReason,
|
|
||||||
email: pairData.email
|
|
||||||
});
|
|
||||||
var ok = gettextCatalog.getString('Confirm');
|
|
||||||
var cancel = gettextCatalog.getString('Cancel');
|
|
||||||
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) {
|
root.get = function(endpoint, successCallback, errorCallback) {
|
||||||
$http(_get(endpoint)).then(function(data) {
|
$http(_get(endpoint)).then(function(data) {
|
||||||
successCallback(data);
|
successCallback(data);
|
||||||
|
@ -115,7 +22,9 @@ angular.module('copayApp.services').factory('bitpayService', function($log, $htt
|
||||||
|
|
||||||
root.post = function(endpoint, json, successCallback, errorCallback) {
|
root.post = function(endpoint, json, successCallback, errorCallback) {
|
||||||
appIdentityService.getIdentity(root.getEnvironment().network, function(err, appIdentity) {
|
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) {
|
$http(_post(endpoint, json, appIdentity)).then(function(data) {
|
||||||
successCallback(data);
|
successCallback(data);
|
||||||
}, function(data) {
|
}, function(data) {
|
||||||
|
@ -124,22 +33,20 @@ angular.module('copayApp.services').factory('bitpayService', function($log, $htt
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
var checkOtp = function(pairData, cb) {
|
root.postAuth = function(json, successCallback, errorCallback) {
|
||||||
if (pairData.otp) {
|
appIdentityService.getIdentity(root.getEnvironment().network, function(err, appIdentity) {
|
||||||
var msg = gettextCatalog.getString('Enter Two Factor for your BitPay account');
|
if (err) {
|
||||||
popupService.showPrompt(null, msg, null, function(res) {
|
return errorCallback(err);
|
||||||
cb(res);
|
}
|
||||||
|
$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) {
|
|
||||||
storageService.setBitpayAccount(root.getEnvironment().network, accountData, cb);
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
var _get = function(endpoint) {
|
var _get = function(endpoint) {
|
||||||
return {
|
return {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
|
@ -184,12 +91,6 @@ angular.module('copayApp.services').factory('bitpayService', function($log, $htt
|
||||||
return ret;
|
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;
|
return root;
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -190,6 +190,9 @@ angular.module('copayApp.services').factory('configService', function(storageSer
|
||||||
if (!configCache.pushNotifications) {
|
if (!configCache.pushNotifications) {
|
||||||
configCache.pushNotifications = defaultConfig.pushNotifications;
|
configCache.pushNotifications = defaultConfig.pushNotifications;
|
||||||
}
|
}
|
||||||
|
if (!configCache.bitpayAccount) {
|
||||||
|
configCache.bitpayAccount = defaultConfig.bitpayAccount;
|
||||||
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
configCache = lodash.clone(defaultConfig);
|
configCache = lodash.clone(defaultConfig);
|
||||||
|
|
|
@ -362,17 +362,25 @@ angular.module('copayApp.services')
|
||||||
// lastFourDigits: card number
|
// lastFourDigits: card number
|
||||||
// token: card token
|
// token: card token
|
||||||
// ]
|
// ]
|
||||||
root.setBitpayDebitCards = function(network, email, cards, cb) {
|
// email: account email
|
||||||
|
// token: account token
|
||||||
root.getBitpayAccounts(network, function(err, allAccounts) {
|
// }
|
||||||
|
root.setBitpayDebitCards = function(network, data, cb) {
|
||||||
|
if (lodash.isString(data)) {
|
||||||
|
data = JSON.parse(data);
|
||||||
|
}
|
||||||
|
data = data || {};
|
||||||
|
if (lodash.isEmpty(data) || !data.email) return cb('Cannot set cards: no account to set');
|
||||||
|
storage.get('bitpayAccounts-v3-' + network, function(err, bitpayAccounts) {
|
||||||
if (err) return cb(err);
|
if (err) return cb(err);
|
||||||
|
|
||||||
if (!allAccounts[email]) {
|
if (!allAccounts[email]) {
|
||||||
return cb('Cannot set cards for unknown account ' + email);
|
return cb('Cannot set cards for unknown account ' + email);
|
||||||
}
|
}
|
||||||
|
bitpayAccounts = bitpayAccounts || {};
|
||||||
allAccounts[email].cards = cards;
|
bitpayAccounts[data.email] = bitpayAccounts[data.email] || {};
|
||||||
storage.set('bitpayAccounts-v2-' + network, allAccounts, cb);
|
bitpayAccounts[data.email]['bitpayDebitCards-' + network] = data.cards;
|
||||||
|
storage.set('bitpayAccounts-v2-' + network, JSON.stringify(bitpayAccounts), cb);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -385,7 +393,6 @@ angular.module('copayApp.services')
|
||||||
// email: account email
|
// email: account email
|
||||||
// ]
|
// ]
|
||||||
root.getBitpayDebitCards = function(network, cb) {
|
root.getBitpayDebitCards = function(network, cb) {
|
||||||
|
|
||||||
root.getBitpayAccounts(network, function(err, allAccounts) {
|
root.getBitpayAccounts(network, function(err, allAccounts) {
|
||||||
if (err) return cb(err);
|
if (err) return cb(err);
|
||||||
|
|
||||||
|
@ -510,6 +517,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) {
|
root.setAppIdentity = function(network, data, cb) {
|
||||||
storage.set('appIdentity-' + network, data, cb);
|
storage.set('appIdentity-' + network, data, cb);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
#bitpayCard-intro {
|
#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: 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-position: top center;
|
||||||
background-size: contain;
|
background-size: contain;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
#bitpayCardPreferences {
|
#bitpayServicesPreferences {
|
||||||
.item {
|
.item {
|
||||||
.item-title {
|
.item-title {
|
||||||
display: block;
|
display: block;
|
||||||
|
@ -14,9 +14,13 @@
|
||||||
&.item-icon-right {
|
&.item-icon-right {
|
||||||
.icon-hotspot {
|
.icon-hotspot {
|
||||||
right: 0px;
|
right: 0px;
|
||||||
padding-left: 11px;
|
padding-left: 50px;
|
||||||
padding-right: 11px;
|
}
|
||||||
}
|
}
|
||||||
|
.icon-unlink {
|
||||||
|
background-image: url("../img/icon-unlink.svg");
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: center;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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 {
|
.settings {
|
||||||
@extend .deflash-blue;
|
@extend .deflash-blue;
|
||||||
.icon-bitpay-card {
|
.icon-bitpay {
|
||||||
background-image: url("../img/icon-card.svg");
|
background-image: url("../img/icon-bitpay.svg");
|
||||||
background-color: #1e3186;
|
|
||||||
}
|
}
|
||||||
.item {
|
.item {
|
||||||
color: $dark-gray;
|
color: $dark-gray;
|
||||||
|
|
|
@ -14,8 +14,8 @@
|
||||||
@import "advancedSettings";
|
@import "advancedSettings";
|
||||||
@import "bitpayCard";
|
@import "bitpayCard";
|
||||||
@import "bitpayCardIntro";
|
@import "bitpayCardIntro";
|
||||||
@import "bitpayCardPreferences";
|
|
||||||
@import "buyandsell";
|
@import "buyandsell";
|
||||||
|
@import "bitpayServicesPreferences";
|
||||||
@import "address-book";
|
@import "address-book";
|
||||||
@import "addresses";
|
@import "addresses";
|
||||||
@import "wallet-backup-phrase";
|
@import "wallet-backup-phrase";
|
||||||
|
@ -42,5 +42,6 @@
|
||||||
@import "includes/tx-status";
|
@import "includes/tx-status";
|
||||||
@import "includes/itemSelector";
|
@import "includes/itemSelector";
|
||||||
@import "includes/walletSelector";
|
@import "includes/walletSelector";
|
||||||
|
@import "includes/accountSelector";
|
||||||
@import "integrations/integrations";
|
@import "integrations/integrations";
|
||||||
@import "custom-amount";
|
@import "custom-amount";
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg width="1200px" height="1200px" viewBox="0 0 1200 1200" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<!-- Generator: Sketch 41.2 (35397) - http://www.bohemiancoding.com/sketch -->
|
||||||
|
<title>Untitled</title>
|
||||||
|
<desc>Created with Sketch.</desc>
|
||||||
|
<defs></defs>
|
||||||
|
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||||
|
<g id="Group" transform="translate(196.000000, 110.000000)" fill="#FFFFFF">
|
||||||
|
<path d="M412.2,529.7 C266.2,529.7 147.3,410.9 147.3,264.8 C147.3,118.8 266.1,5.68434189e-14 412.2,5.68434189e-14 C558.2,0 677,118.8 677,264.8 C677,410.9 558.2,529.7 412.2,529.7 Z M412.2,59 C298.7,59 206.4,151.3 206.4,264.8 C206.4,378.3 298.7,470.6 412.2,470.6 C525.7,470.6 618,378.3 618,264.8 C618,151.4 525.7,59 412.2,59 Z" id="Shape"></path>
|
||||||
|
<path d="M151.9,976.1 C131.7,976.1 73.9,972.9 38.3,939.7 C-2.2,902 -0.4,839.9 1.1,821.7 C16.3,640.2 202.7,486.9 408.1,486.9 C492.4,486.9 573.6,512.1 642.8,559.6 C652.1,566 661.2,572.8 670,579.9 C682.6,590.2 684.5,608.8 674.3,621.4 C664,634 645.4,635.9 632.8,625.7 C625.3,619.6 617.4,613.7 609.5,608.2 C550.1,567.4 480.5,545.8 408.2,545.8 C232.2,545.8 72.8,674.4 60,826.5 C58,850.8 62.9,881.9 78.6,896.4 C98.7,915.2 143.4,917.5 157.3,916.8 L647,916.8 C663.3,916.8 676.5,930 676.5,946.3 C676.5,962.6 663.2,976 646.9,976 L158.6,976 C157.7,976.1 155.3,976.1 151.9,976.1 Z" id="Shape"></path>
|
||||||
|
<path d="M773.3,949.6 C773.3,962.3 763,972.7 750.2,972.7 L750.2,972.7 C737.5,972.7 727.1,962.4 727.1,949.6 L727.1,635.9 C727.1,623.2 737.4,612.8 750.2,612.8 L750.2,612.8 C762.9,612.8 773.3,623.1 773.3,635.9 L773.3,949.6 Z" id="Shape"></path>
|
||||||
|
<path d="M750.2,980 C733.4,980 719.8,966.3 719.8,949.6 L719.8,635.9 C719.8,619.1 733.5,605.5 750.2,605.5 C767,605.5 780.6,619.2 780.6,635.9 L780.6,949.6 C780.7,966.3 767,980 750.2,980 Z M750.2,620.2 C741.5,620.2 734.5,627.2 734.5,635.9 L734.5,949.6 C734.5,958.2 741.5,965.3 750.2,965.3 C758.8,965.3 765.9,958.3 765.9,949.6 L765.9,635.9 C765.9,627.2 758.9,620.2 750.2,620.2 Z" id="Shape"></path>
|
||||||
|
<path d="M907.1,769.7 C919.8,769.7 930.2,780 930.2,792.8 L930.2,792.8 C930.2,805.5 919.9,815.9 907.1,815.9 L593.4,815.9 C580.7,815.9 570.3,805.6 570.3,792.8 L570.3,792.8 C570.3,780.1 580.6,769.7 593.4,769.7 L907.1,769.7 Z" id="Shape"></path>
|
||||||
|
<path d="M907.1,823.2 L593.4,823.2 C576.6,823.2 563,809.5 563,792.8 C563,776 576.7,762.4 593.4,762.4 L907.1,762.4 C923.9,762.4 937.5,776.1 937.5,792.8 C937.5,809.5 923.9,823.2 907.1,823.2 Z M593.4,777 C584.8,777 577.7,784 577.7,792.7 C577.7,801.3 584.7,808.4 593.4,808.4 L907.1,808.4 C915.7,808.4 922.8,801.4 922.8,792.7 C922.8,784 915.8,777 907.1,777 L593.4,777 Z" id="Shape"></path>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 2.8 KiB |
|
@ -0,0 +1,14 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg width="1200px" height="1200px" viewBox="0 0 1200 1200" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<!-- Generator: Sketch 41.2 (35397) - http://www.bohemiancoding.com/sketch -->
|
||||||
|
<title>Untitled</title>
|
||||||
|
<desc>Created with Sketch.</desc>
|
||||||
|
<defs></defs>
|
||||||
|
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||||
|
<g id="Group" transform="translate(196.000000, 110.000000)" fill="#FFFFFF">
|
||||||
|
<path d="M412.2,529.7 C266.2,529.7 147.3,410.9 147.3,264.8 C147.3,118.8 266.1,5.68434189e-14 412.2,5.68434189e-14 C558.2,0 677,118.8 677,264.8 C677,410.9 558.2,529.7 412.2,529.7 Z M412.2,59 C298.7,59 206.4,151.3 206.4,264.8 C206.4,378.3 298.7,470.6 412.2,470.6 C525.7,470.6 618,378.3 618,264.8 C618,151.4 525.7,59 412.2,59 Z" id="Shape"></path>
|
||||||
|
<path d="M151.9,976.1 C131.7,976.1 73.9,972.9 38.3,939.7 C-2.2,902 -0.4,839.9 1.1,821.7 C16.3,640.2 202.7,486.9 408.1,486.9 C492.4,486.9 573.6,512.1 642.8,559.6 C652.1,566 661.2,572.8 670,579.9 C682.6,590.2 684.5,608.8 674.3,621.4 C664,634 645.4,635.9 632.8,625.7 C625.3,619.6 617.4,613.7 609.5,608.2 C550.1,567.4 480.5,545.8 408.2,545.8 C232.2,545.8 72.8,674.4 60,826.5 C58,850.8 62.9,881.9 78.6,896.4 C98.7,915.2 143.4,917.5 157.3,916.8 L647,916.8 C663.3,916.8 676.5,930 676.5,946.3 C676.5,962.6 663.2,976 646.9,976 L158.6,976 C157.7,976.1 155.3,976.1 151.9,976.1 Z" id="Shape"></path>
|
||||||
|
<path d="M280.9,976.1 C260.7,976.1 202.9,972.9 167.3,939.7 C126.8,902 128.6,839.9 130.1,821.7 C145.3,640.2 331.7,486.9 537.1,486.9 C621.4,486.9 702.6,512.1 771.8,559.6 C781.1,566 790.2,572.8 799,579.9 C811.6,590.2 813.5,608.8 803.3,621.4 C793,634 774.4,635.9 761.8,625.7 C754.3,619.6 746.4,613.7 738.5,608.2 C679.1,567.4 609.5,545.8 537.2,545.8 C361.2,545.8 201.8,674.4 189,826.5 C187,850.8 191.9,881.9 207.6,896.4 C227.7,915.2 272.4,917.5 286.3,916.8 L776,916.8 C792.3,916.8 805.5,930 805.5,946.3 C805.5,962.6 792.2,976 775.9,976 L287.6,976 C286.7,976.1 284.3,976.1 280.9,976.1 Z" id="Shape-Copy" transform="translate(469.680886, 731.500000) scale(-1, 1) translate(-469.680886, -731.500000) "></path>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 2.2 KiB |
|
@ -0,0 +1,26 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg width="20px" height="20px" viewBox="0 0 20 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<!-- Generator: Sketch 41.2 (35397) - http://www.bohemiancoding.com/sketch -->
|
||||||
|
<title>icon-link</title>
|
||||||
|
<desc>Created with Sketch.</desc>
|
||||||
|
<defs></defs>
|
||||||
|
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||||
|
<g id="icon-link" transform="translate(1.000000, 1.000000)" stroke="#666677">
|
||||||
|
<g id="Page-1">
|
||||||
|
<g id="Icons">
|
||||||
|
<g id="ui-24px-outline-2_link-69" transform="translate(0.384615, 0.000000)">
|
||||||
|
<g id="Group-2" transform="translate(12.000000, 12.000000)">
|
||||||
|
<path d="M0.3,5.7 L5.7,0.3" id="Shape" transform="translate(3.000000, 3.000000) scale(-1, 1) translate(-3.000000, -3.000000) "></path>
|
||||||
|
<path d="M0.3,5.7 L5.7,0.3" id="Shape"></path>
|
||||||
|
</g>
|
||||||
|
<g id="Group">
|
||||||
|
<path d="M8.1,3.6 L10.35,1.35 C12.06,-0.36 14.94,-0.36 16.65,1.35 L16.65,1.35 C18.36,3.06 18.36,5.94 16.65,7.65 L14.4,9.9" id="Shape"></path>
|
||||||
|
<path d="M9.9,14.4 L7.65,16.65 C5.94,18.36 3.06,18.36 1.35,16.65 L1.35,16.65 C-0.36,14.94 -0.36,12.06 1.35,10.35 L3.6,8.1" id="Shape"></path>
|
||||||
|
<path d="M6.3,11.7 L11.7,6.3" id="Shape"></path>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.6 KiB |
|
@ -39,4 +39,11 @@
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</ion-content>
|
</ion-content>
|
||||||
|
<account-selector
|
||||||
|
account-selector-title="accountSelectorTitle"
|
||||||
|
account-selector-accounts="accounts"
|
||||||
|
account-selector-selected-account="account"
|
||||||
|
account-selector-show="showAccounts"
|
||||||
|
account-selector-on-select="onAccountSelect">
|
||||||
|
</account-selector>
|
||||||
</ion-view>
|
</ion-view>
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
<action-sheet action-sheet-show="show" class="account-selector">
|
||||||
|
<img class="back-arrow" src="img/icon-back-arrow.svg" ng-click="hide()">
|
||||||
|
<div class="header">{{title}}</div>
|
||||||
|
<a ng-repeat="a in accounts track by $index"
|
||||||
|
class="item item-icon-left item-big-icon-left item-icon-right account" ng-click="selectAccount(a)">
|
||||||
|
<i class="icon big-icon-svg">
|
||||||
|
<img src="img/icon-account.svg" class="bg">
|
||||||
|
</i>
|
||||||
|
<div class="account-inner">
|
||||||
|
<div class="account-details">
|
||||||
|
<div class="account-name">
|
||||||
|
{{a.firstName}} {{a.lastName}}
|
||||||
|
</div>
|
||||||
|
<p class="account-email" ng-if="a.email">
|
||||||
|
<span>{{a.email}}</span>
|
||||||
|
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
<a class="item item-icon-left item-big-icon-left item-icon-right account" ng-click="selectAccount()">
|
||||||
|
<i class="icon big-icon-svg">
|
||||||
|
<img src="img/icon-account-add.svg" class="bg icon-add">
|
||||||
|
</i>
|
||||||
|
<div class="account-inner">
|
||||||
|
<div class="account-details">
|
||||||
|
<div class="account-add" translate>
|
||||||
|
Add account
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
</action-sheet>
|
|
@ -1,24 +0,0 @@
|
||||||
<ion-view id="bitpayCardPreferences">
|
|
||||||
<ion-nav-bar class="bar-royal">
|
|
||||||
<ion-nav-back-button>
|
|
||||||
</ion-nav-back-button>
|
|
||||||
<ion-nav-title>BitPay Visa® Cards</ion-nav-title>
|
|
||||||
</ion-nav-bar>
|
|
||||||
|
|
||||||
<ion-content>
|
|
||||||
<div class="list">
|
|
||||||
<div class="item item-divider" translate>
|
|
||||||
Cards
|
|
||||||
</div>
|
|
||||||
<div class="item item-icon-right" ng-repeat="card in bitpayCards">
|
|
||||||
<span class="item-title">
|
|
||||||
xxxx-xxxx-xxxx-{{card.lastFourDigits}}
|
|
||||||
</span>
|
|
||||||
<span class="item-subtitle">
|
|
||||||
{{card.email}}
|
|
||||||
</span>
|
|
||||||
<i class="icon ion-trash-b icon-hotspot assertive" ng-click="remove(card)"></i>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</ion-content>
|
|
||||||
</ion-view>
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
<ion-view id="bitpayServicesPreferences">
|
||||||
|
<ion-nav-bar class="bar-royal">
|
||||||
|
<ion-nav-back-button>
|
||||||
|
</ion-nav-back-button>
|
||||||
|
<ion-nav-title>BitPay</ion-nav-title>
|
||||||
|
</ion-nav-bar>
|
||||||
|
<ion-content>
|
||||||
|
<div class="list" ng-if="bitpayCards.length > 0">
|
||||||
|
<div class="item item-divider" translate>
|
||||||
|
BitPay Visa® Cards
|
||||||
|
</div>
|
||||||
|
<div class="item item-icon-right" ng-repeat="card in bitpayCards">
|
||||||
|
<span class="item-title">
|
||||||
|
xxxx-xxxx-xxxx-{{card.lastFourDigits}}
|
||||||
|
</span>
|
||||||
|
<span class="item-subtitle">
|
||||||
|
{{card.email}}
|
||||||
|
</span>
|
||||||
|
<div class="icon icon-unlink icon-hotspot" ng-click="removeCard(card)"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="list">
|
||||||
|
<div class="item item-divider" translate>
|
||||||
|
Accounts
|
||||||
|
</div>
|
||||||
|
<div class="item item-icon-right" ng-repeat="account in bitpayAccounts">
|
||||||
|
<span class="item-title">
|
||||||
|
{{account.firstName}} {{account.lastName}}
|
||||||
|
</span>
|
||||||
|
<span class="item-subtitle">
|
||||||
|
{{account.email}}
|
||||||
|
</span>
|
||||||
|
<div class="icon icon-unlink icon-hotspot" ng-click="removeAccount(account)"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ion-content>
|
||||||
|
</ion-view>
|
|
@ -117,12 +117,12 @@
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<a class=" item item-icon-left item-icon-right"
|
<a class=" item item-icon-left item-icon-right"
|
||||||
ng-if="bitpayCards"
|
ng-if="bitpayAccounts || (bitpayCardEnabled && bitpayCards)"
|
||||||
ui-sref="tabs.preferences.bitpayCard">
|
ui-sref="tabs.preferences.bitpayServices">
|
||||||
<i class="icon big-icon-svg circle">
|
<i class="icon big-icon-svg circle">
|
||||||
<div class="bg icon-bitpay-card"></div>
|
<div class="bg icon-bitpay"></div>
|
||||||
</i>
|
</i>
|
||||||
<span>BitPay Visa® Card</span>
|
<span>BitPay</span>
|
||||||
<i class="icon bp-arrow-right"></i>
|
<i class="icon bp-arrow-right"></i>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue