Merge branch 'master' of github.com:bitpay/copay into feature/better-buy-and-sell

This commit is contained in:
Jason Dreyzehner 2017-02-01 17:38:57 -05:00
commit aaf9008394
41 changed files with 937 additions and 1022 deletions

View File

@ -21,8 +21,8 @@
"windowsAppId": "2d1002d7-ee34-4f60-bd29-0c871ba0c195",
"pushSenderId": "1036948132229",
"description": "Secure Bitcoin Wallet",
"version": "1.3.2",
"androidVersion": "132000",
"version": "1.3.4",
"androidVersion": "134000",
"_extraCSS": null,
"_enabledExtensions": {
"coinbase": true,

View File

@ -3,27 +3,18 @@
angular.module('copayApp.controllers').controller('advancedSettingsController', function($scope, $rootScope, $log, $window, lodash, configService, uxLanguage, platformInfo, pushNotificationsService, profileService, feeService, storageService, $ionicHistory, $timeout, $ionicScrollDelegate) {
var updateConfig = function() {
var config = configService.getSync();
$scope.spendUnconfirmed = {
value: config.wallet.spendUnconfirmed
};
$scope.bitpayCardEnabled = {
value: config.bitpayCard.enabled
};
$scope.amazonEnabled = {
value: config.amazon.enabled
};
$scope.glideraEnabled = {
value: config.glidera.enabled
};
$scope.coinbaseEnabled = {
value: config.coinbaseV2
};
$scope.recentTransactionsEnabled = {
value: config.recentTransactions.enabled
};
$scope.hideNextSteps = {
value: config.hideNextSteps.enabled
};
};
$scope.spendUnconfirmedChange = function() {
@ -37,42 +28,11 @@ angular.module('copayApp.controllers').controller('advancedSettingsController',
});
};
$scope.bitpayCardChange = function() {
$scope.nextStepsChange = function() {
var opts = {
bitpayCard: {
enabled: $scope.bitpayCardEnabled.value
}
};
configService.set(opts, function(err) {
if (err) $log.debug(err);
});
};
$scope.amazonChange = function() {
var opts = {
amazon: {
enabled: $scope.amazonEnabled.value
}
};
configService.set(opts, function(err) {
if (err) $log.debug(err);
});
};
$scope.glideraChange = function() {
var opts = {
glidera: {
enabled: $scope.glideraEnabled.value
}
};
configService.set(opts, function(err) {
if (err) $log.debug(err);
});
};
$scope.coinbaseChange = function() {
var opts = {
coinbaseV2: $scope.coinbaseEnabled.value
hideNextSteps: {
enabled: $scope.hideNextSteps.value
},
};
configService.set(opts, function(err) {
if (err) $log.debug(err);

View File

@ -9,16 +9,6 @@ angular.module('copayApp.controllers').controller('bitpayCardController', functi
};
$scope.network = bitpayService.getEnvironment().network;
var updateHistoryFromCache = function(cb) {
bitpayCardService.getBitpayDebitCardsHistory($scope.cardId, function(err, data) {
if (err ||  lodash.isEmpty(data)) return cb();
$scope.historyCached = true;
self.bitpayCardTransactionHistory = data.transactions;
self.bitpayCardCurrentBalance = data.balance;
return cb();
});
};
var setDateRange = function(preset) {
var startDate, endDate;
preset = preset ||  'last30Days';
@ -45,13 +35,19 @@ angular.module('copayApp.controllers').controller('bitpayCardController', functi
};
var setGetStarted = function(history, cb) {
if (lodash.isEmpty(history.transactionList)) {
var dateRange = setDateRange('all');
bitpayCardService.getHistory($scope.cardId, dateRange, function(err, history) {
if (lodash.isEmpty(history.transactionList)) return cb(true);
return cb(false);
});
} else return cb(false);
// Is the card new?
if (!lodash.isEmpty(history.transactionList))
return cb();
var dateRange = setDateRange('all');
bitpayCardService.getHistory($scope.cardId, dateRange, function(err, history) {
if (!err && lodash.isEmpty(history.transactionList))
self.getStated=true;
return cb();
});
};
this.update = function() {
@ -59,18 +55,18 @@ angular.module('copayApp.controllers').controller('bitpayCardController', functi
$scope.loadingHistory = true;
bitpayCardService.getHistory($scope.cardId, dateRange, function(err, history) {
$scope.loadingHistory = false;
if (err) {
$log.error(err);
self.bitpayCardTransactionHistory = null;
self.bitpayCardCurrentBalance = null;
self.balance = null;
popupService.showAlert(gettextCatalog.getString('Error'), gettextCatalog.getString('Could not get transactions'));
return;
}
setGetStarted(history, function(getStarted) {
self.getStarted = getStarted;
setGetStarted(history, function() {
var txs = lodash.clone(history.txs);
runningBalance = parseFloat(history.endingBalance);
@ -83,18 +79,21 @@ angular.module('copayApp.controllers').controller('bitpayCardController', functi
_runningBalance(txs[i]);
}
self.bitpayCardTransactionHistory = txs;
self.bitpayCardCurrentBalance = history.currentCardBalance;
self.balance = history.currentCardBalance;
self.updatedOn = null;
if ($scope.dateRange.value == 'last30Days') {
$log.debug('BitPay Card: store cache history');
var cacheHistory = {
balance: history.currentCardBalance,
transactions: history.txs
};
bitpayCardService.setBitpayDebitCardsHistory($scope.cardId, cacheHistory, {}, function(err) {
if (err) $log.error(err);
$scope.historyCached = true;
});
// TODO?
// $log.debug('BitPay Card: storing cache history');
// var cacheHistory = {
// balance: history.currentCardBalance,
// transactions: history.txs
// };
// bitpayCardService.setHistory($scope.cardId, cacheHistory, {}, function(err) {
// if (err) $log.error(err);
// $scope.historyCached = true;
// });
}
$timeout(function() {
$scope.$apply();
@ -136,24 +135,26 @@ angular.module('copayApp.controllers').controller('bitpayCardController', functi
$scope.$on("$ionicView.beforeEnter", function(event, data) {
$scope.cardId = data.stateParams.id;
if (!$scope.cardId) {
var msg = gettextCatalog.getString('Bad param');
$ionicHistory.nextViewOptions({
disableAnimate: true
});
$state.go('tabs.home');
popupService.showAlert(gettextCatalog.getString('Error'), msg);
} else {
updateHistoryFromCache(function() {
self.update();
});
bitpayCardService.getBitpayDebitCards(function(err, cards) {
if (err) return;
$scope.card = lodash.find(cards, function(card) {
return card.eid == $scope.cardId;
});
});
}
});
bitpayCardService.get({
cardId: $scope.cardId,
noRefresh: true,
}, function(err, cards) {
if (cards && cards[0]) {
self.lastFourDigits = cards[0].lastFourDigits;
self.balance = cards[0].balance;
self.updatedOn = cards[0].updatedOn;
}
self.update();
});
});
});

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, appIdentityService, bitpayService, lodash) {
$scope.$on("$ionicView.beforeEnter", function(event, data) {
if (data.stateParams && data.stateParams.secret) {
@ -18,27 +18,23 @@ angular.module('copayApp.controllers').controller('bitpayCardIntroController', f
return;
}
if (paired) {
bitpayCardService.fetchBitpayDebitCards(apiContext, function(err, data) {
bitpayCardService.sync(apiContext, function(err, cards) {
if (err) {
popupService.showAlert(gettextCatalog.getString('Error fetching Debit Cards'), err);
popupService.showAlert(gettextCatalog.getString('Error updating Debit Cards'), err);
return;
}
// 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 (cards[0]) {
$state.transitionTo('tabs.bitpayCard', {
id: cards[0].id
});
}
});
});
}

View File

@ -0,0 +1,14 @@
'use strict';
angular.module('copayApp.controllers').controller('buyAndSellCardController', function($scope, nextStepsService, $ionicScrollDelegate, buyAndSellService) {
$scope.services = buyAndSellService.getLinked();
$scope.toggle = function() {
$scope.hide = !$scope.hide;
$timeout(function() {
$ionicScrollDelegate.resize();
$scope.$apply();
}, 10);
};
});

View File

@ -34,8 +34,6 @@ angular.module('copayApp.controllers').controller('buyCoinbaseController', funct
};
$scope.$on("$ionicView.beforeEnter", function(event, data) {
coinbaseService.setCredentials();
$scope.isFiat = data.stateParams.currency ? true : false;
[amount, currency, $scope.amountUnitStr] = coinbaseService.parseAmount(
data.stateParams.amount,

View File

@ -1,14 +1,11 @@
'use strict';
angular.module('copayApp.controllers').controller('buyandsellController', function($scope, $ionicHistory, configService) {
angular.module('copayApp.controllers').controller('buyandsellController', function($scope, $ionicHistory, buyAndSellService, lodash) {
$scope.$on("$ionicView.beforeEnter", function(event, data) {
configService.whenAvailable(function(config) {
$scope.isCoinbaseEnabled = config.coinbaseV2;
$scope.isGlideraEnabled = config.glidera.enabled;
$scope.services = buyAndSellService.get();
if (!$scope.isCoinbaseEnabled && !$scope.isGlideraEnabled)
$ionicHistory.goBack();
});
if (lodash.isEmpty($scope.services))
$ionicHistory.goBack();
});
});

View File

@ -137,7 +137,6 @@ angular.module('copayApp.controllers').controller('coinbaseController', function
var self = this;
$scope.$on("$ionicView.beforeEnter", function(event, data) {
$scope.showOauthForm = false;
coinbaseService.setCredentials();
if (data.stateParams && data.stateParams.code) {
coinbaseService.getStoredToken(function(at) {
if (!at) self.submitOauthCode(data.stateParams.code);

View File

@ -0,0 +1,16 @@
'use strict';
angular.module('copayApp.controllers').controller('homeIntegrationsController', function($scope, homeIntegrationsService, $ionicScrollDelegate, $timeout) {
$scope.hide = false;
$scope.services = homeIntegrationsService.get();
$scope.toggle = function() {
$scope.hide = !$scope.hide;
$timeout(function() {
$ionicScrollDelegate.resize();
$scope.$apply();
}, 10);
};
});

View File

@ -0,0 +1,16 @@
'use strict';
angular.module('copayApp.controllers').controller('nextStepsController', function($scope, nextStepsService, $ionicScrollDelegate, $timeout) {
$scope.hide = false;
$scope.services = nextStepsService.get();
$scope.toggle = function() {
$scope.hide = !$scope.hide;
$timeout(function() {
$ionicScrollDelegate.resize();
$scope.$apply();
}, 10);
};
});

View File

@ -1,19 +1,21 @@
'use strict';
angular.module('copayApp.controllers').controller('preferencesBitpayCardController',
function($scope, $state, $timeout, $ionicHistory, bitpayCardService, popupService, gettextCatalog) {
function($scope, $state, $timeout, $ionicHistory, bitpayCardService, popupService, gettextCatalog, $log) {
$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);
$log.info('Removing bitpay card:' + card.eid)
if (res)
remove(card.eid);
});
};
var remove = function(card) {
bitpayCardService.remove(card, function(err) {
var remove = function(cardEid) {
bitpayCardService.remove(cardEid, function(err) {
if (err) {
return popupService.showAlert(gettextCatalog.getString('Error'), gettextCatalog.getString('Could not remove card'));
}
@ -25,8 +27,9 @@ angular.module('copayApp.controllers').controller('preferencesBitpayCardControll
};
$scope.$on("$ionicView.beforeEnter", function(event, data) {
bitpayCardService.getBitpayDebitCards(function(err, data) {
bitpayCardService.getCards(function(err, data) {
if (err) return;
$scope.bitpayCards = data;
});
});

View File

@ -16,7 +16,6 @@ angular.module('copayApp.controllers').controller('preferencesCoinbaseController
};
$scope.$on("$ionicView.enter", function(event, data){
coinbaseService.setCredentials();
ongoingProcess.set('connectingCoinbase', true);
coinbaseService.init(function(err, data) {
if (err || lodash.isEmpty(data)) {

View File

@ -117,8 +117,6 @@ angular.module('copayApp.controllers').controller('sellCoinbaseController', func
};
$scope.$on("$ionicView.beforeEnter", function(event, data) {
coinbaseService.setCredentials();
$scope.isFiat = data.stateParams.currency ? true : false;
[amount, currency, $scope.amountUnitStr] = coinbaseService.parseAmount(
data.stateParams.amount,

View File

@ -1,7 +1,7 @@
'use strict';
angular.module('copayApp.controllers').controller('tabHomeController',
function($rootScope, $timeout, $scope, $state, $stateParams, $ionicModal, $ionicScrollDelegate, $window, gettextCatalog, lodash, popupService, ongoingProcess, externalLinkService, latestReleaseService, profileService, walletService, configService, $log, platformInfo, storageService, txpModalService, appConfigService, bitpayCardService, startupService, addressbookService, feedbackService, bwcError, coinbaseService) {
function($rootScope, $timeout, $scope, $state, $stateParams, $ionicModal, $ionicScrollDelegate, $window, gettextCatalog, lodash, popupService, ongoingProcess, externalLinkService, latestReleaseService, profileService, walletService, configService, $log, platformInfo, storageService, txpModalService, appConfigService, startupService, addressbookService, feedbackService, bwcError, nextStepsService, buyAndSellService, homeIntegrationsService, bitpayCardService) {
var wallet;
var listeners = [];
var notifications = [];
@ -87,10 +87,7 @@ angular.module('copayApp.controllers').controller('tabHomeController',
var wallet = profileService.getWallet(walletId);
updateWallet(wallet);
if ($scope.recentTransactionsEnabled) getNotifications();
if ($scope.coinbaseEnabled && type == 'NewBlock' && n && n.data && n.data.network == 'livenet') {
// Update Coinbase
coinbaseService.updatePendingTransactions();
}
}),
$rootScope.$on('Local/TxAction', function(e, walletId) {
$log.debug('Got action for wallet ' + walletId);
@ -100,31 +97,29 @@ angular.module('copayApp.controllers').controller('tabHomeController',
})
];
$scope.buyAndSellItems = buyAndSellService.getLinked();
$scope.homeIntegrations = homeIntegrationsService.get();
bitpayCardService.get({}, function(err, cards) {
$scope.bitpayCardItems = cards;
});
configService.whenAvailable(function() {
nextStep(function() {
var config = configService.getSync();
var isWindowsPhoneApp = platformInfo.isWP && platformInfo.isCordova;
var config = configService.getSync();
$scope.recentTransactionsEnabled = config.recentTransactions.enabled;
if ($scope.recentTransactionsEnabled) getNotifications();
$scope.glideraEnabled = config.glidera.enabled && !isWindowsPhoneApp;
$scope.coinbaseEnabled = config.coinbaseV2 && !isWindowsPhoneApp;
$scope.amazonEnabled = config.amazon.enabled;
$scope.bitpayCardEnabled = config.bitpayCard.enabled;
if (config.hideNextSteps.enabled) {
$scope.nextStepsItems = null;
} else {
$scope.nextStepsItems = nextStepsService.get();
}
var buyAndSellEnabled = !$scope.externalServices.BuyAndSell && ($scope.glideraEnabled || $scope.coinbaseEnabled);
var amazonEnabled = !$scope.externalServices.AmazonGiftCards && $scope.amazonEnabled;
var bitpayCardEnabled = !$scope.externalServices.BitpayCard && $scope.bitpayCardEnabled;
$scope.nextStepEnabled = buyAndSellEnabled || amazonEnabled || bitpayCardEnabled;
$scope.recentTransactionsEnabled = config.recentTransactions.enabled;
if ($scope.recentTransactionsEnabled) getNotifications();
if ($scope.bitpayCardEnabled) bitpayCardCache();
$timeout(function() {
$ionicScrollDelegate.resize();
$scope.$apply();
}, 10);
});
$timeout(function() {
$ionicScrollDelegate.resize();
$scope.$apply();
}, 10);
});
});
@ -223,6 +218,9 @@ angular.module('copayApp.controllers').controller('tabHomeController',
} else {
wallet.error = null;
wallet.status = status;
// TODO service refactor? not in profile service
profileService.setLastKnownBalance(wallet.id, wallet.status.totalBalanceStr, function() {});
}
if (++j == i) {
updateTxps();
@ -269,46 +267,6 @@ angular.module('copayApp.controllers').controller('tabHomeController',
});
};
var nextStep = function(cb) {
var i = 0;
var services = ['AmazonGiftCards', 'BitpayCard', 'BuyAndSell'];
lodash.each(services, function(service) {
storageService.getNextStep(service, function(err, value) {
$scope.externalServices[service] = value == 'true' ? true : false;
if (++i == services.length) return cb();
});
});
};
$scope.shouldHideNextSteps = function() {
$scope.hideNextSteps = !$scope.hideNextSteps;
$timeout(function() {
$ionicScrollDelegate.resize();
$scope.$apply();
}, 10);
};
var bitpayCardCache = function() {
bitpayCardService.getBitpayDebitCards(function(err, data) {
if (err) return;
if (lodash.isEmpty(data)) {
$scope.bitpayCards = null;
return;
}
$scope.bitpayCards = data;
$timeout(function() {
$scope.$digest();
}, 100);
});
bitpayCardService.getBitpayDebitCardsHistory(null, function(err, data) {
if (err) return;
if (lodash.isEmpty(data)) {
$scope.cardsHistory = null;
return;
}
$scope.cardsHistory = data;
});
};
$scope.onRefresh = function() {
$timeout(function() {

View File

@ -1,6 +1,6 @@
'use strict';
angular.module('copayApp.controllers').controller('tabSettingsController', function($scope, appConfigService, $log, lodash, uxLanguage, platformInfo, profileService, feeService, configService, externalLinkService, bitpayCardService, storageService, glideraService, coinbaseService, gettextCatalog) {
angular.module('copayApp.controllers').controller('tabSettingsController', function($scope, appConfigService, $log, lodash, uxLanguage, platformInfo, profileService, feeService, configService, externalLinkService, bitpayCardService, storageService, glideraService, coinbaseService, gettextCatalog, buyAndSellService) {
var updateConfig = function() {
var isCordova = platformInfo.isCordova;
@ -16,6 +16,7 @@ angular.module('copayApp.controllers').controller('tabSettingsController', funct
$scope.currentFeeLevel = feeService.getCurrentFeeLevel();
$scope.wallets = profileService.getWallets();
$scope.buyAndSellServices = buyAndSellService.getLinked();
configService.whenAvailable(function(config) {
$scope.unitName = config.wallet.settings.unitName;
@ -24,30 +25,11 @@ angular.module('copayApp.controllers').controller('tabSettingsController', funct
isoCode: config.wallet.settings.alternativeIsoCode
};
$scope.bitpayCardEnabled = config.bitpayCard.enabled;
$scope.glideraEnabled = config.glidera.enabled && !isWindowsPhoneApp;
$scope.coinbaseEnabled = config.coinbaseV2 && !isWindowsPhoneApp;
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;
});
}
if ($scope.coinbaseEnabled) {
coinbaseService.setCredentials();
coinbaseService.getStoredToken(function(at) {
$scope.coinbaseToken = at;
});
}
// TODO move this to a generic service
bitpayCardService.getCards(function(err, cards) {
if (err) $log.error(err);
$scope.bitpayCards = cards && cards.length > 0;
});
});
};

View File

@ -1088,7 +1088,7 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr
}
});
})
.run(function($rootScope, $state, $location, $log, $timeout, $ionicHistory, $ionicPlatform, $window, appConfigService, lodash, platformInfo, profileService, uxLanguage, gettextCatalog, openURLService, storageService, scannerService) {
.run(function($rootScope, $state, $location, $log, $timeout, $ionicHistory, $ionicPlatform, $window, appConfigService, lodash, platformInfo, profileService, uxLanguage, gettextCatalog, openURLService, storageService, scannerService, /* plugins START HERE => */ coinbaseService, glideraService, amazonService, bitpayCardService) {
uxLanguage.init();
@ -1153,50 +1153,40 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr
});
}
$log.info('Verifying storage...');
storageService.verify(function(err) {
$log.info('Init profile...');
// Try to open local profile
profileService.loadAndBindProfile(function(err) {
$ionicHistory.nextViewOptions({
disableAnimate: true
});
if (err) {
$log.error('Storage failed to verify: ' + err);
// TODO - what next?
} else {
$log.info('Storage OK');
}
$log.info('Init profile...');
// Try to open local profile
profileService.loadAndBindProfile(function(err) {
$ionicHistory.nextViewOptions({
disableAnimate: true
});
if (err) {
if (err.message && err.message.match('NOPROFILE')) {
$log.debug('No profile... redirecting');
if (err.message && err.message.match('NOPROFILE')) {
$log.debug('No profile... redirecting');
$state.go('onboarding.welcome');
} else if (err.message && err.message.match('NONAGREEDDISCLAIMER')) {
if (lodash.isEmpty(profileService.getWallets())) {
$log.debug('No wallets and no disclaimer... redirecting');
$state.go('onboarding.welcome');
} else if (err.message && err.message.match('NONAGREEDDISCLAIMER')) {
if (lodash.isEmpty(profileService.getWallets())) {
$log.debug('No wallets and no disclaimer... redirecting');
$state.go('onboarding.welcome');
} else {
$log.debug('Display disclaimer... redirecting');
$state.go('onboarding.disclaimer', {
resume: true
});
}
} else {
throw new Error(err); // TODO
$log.debug('Display disclaimer... redirecting');
$state.go('onboarding.disclaimer', {
resume: true
});
}
} else {
profileService.storeProfileIfDirty();
$log.debug('Profile loaded ... Starting UX.');
scannerService.gentleInitialize();
$state.go('tabs.home');
throw new Error(err); // TODO
}
} else {
profileService.storeProfileIfDirty();
$log.debug('Profile loaded ... Starting UX.');
scannerService.gentleInitialize();
$state.go('tabs.home');
}
// After everything have been loaded, initialize handler URL
$timeout(function() {
openURLService.init();
}, 1000);
});
// After everything have been loaded, initialize handler URL
$timeout(function() {
openURLService.init();
}, 1000);
});
});

View File

@ -1,24 +1,36 @@
'use strict';
angular.module('copayApp.services').factory('amazonService', function($http, $log, lodash, moment, storageService, configService, platformInfo) {
angular.module('copayApp.services').factory('amazonService', function($http, $log, lodash, moment, storageService, configService, platformInfo, nextStepsService, homeIntegrationsService) {
var root = {};
var credentials = {};
var _setCredentials = function() {
/*
* Development: 'testnet'
* Production: 'livenet'
*/
credentials.NETWORK = 'livenet';
/*
* Development: 'testnet'
* Production: 'livenet'
*/
credentials.NETWORK = 'livenet';
//credentials.NETWORK = 'testnet';
if (credentials.NETWORK == 'testnet') {
credentials.BITPAY_API_URL = "https://test.bitpay.com";
} else {
credentials.BITPAY_API_URL = "https://bitpay.com";
};
if (credentials.NETWORK == 'testnet') {
credentials.BITPAY_API_URL = "https://test.bitpay.com";
} else {
credentials.BITPAY_API_URL = "https://bitpay.com";
};
var homeItem = {
name: 'amazon',
title: 'Amazon Gift Cards',
icon: 'icon-amazon',
sref: 'tabs.giftcards.amazon',
};
var nextStepItem = {
name: 'amazon',
title: 'Buy a gift card',
icon: 'icon-amazon',
sref: 'tabs.giftcards.amazon',
};
var _getBitPay = function(endpoint) {
_setCredentials();
return {
method: 'GET',
url: credentials.BITPAY_API_URL + endpoint,
@ -29,7 +41,6 @@ angular.module('copayApp.services').factory('amazonService', function($http, $lo
};
var _postBitPay = function(endpoint, data) {
_setCredentials();
return {
method: 'POST',
url: credentials.BITPAY_API_URL + endpoint,
@ -41,7 +52,6 @@ angular.module('copayApp.services').factory('amazonService', function($http, $lo
};
root.getNetwork = function() {
_setCredentials();
return credentials.NETWORK;
};
@ -65,12 +75,12 @@ angular.module('copayApp.services').factory('amazonService', function($http, $lo
inv = JSON.stringify(inv);
storageService.setAmazonGiftCards(network, inv, function(err) {
homeIntegrationsService.register(homeItem);
nextStepsService.unregister(nextStepItem.name);
return cb(err);
});
});
// Show pending task from the UI
storageService.setNextStep('AmazonGiftCards', 'true', function(err) {});
};
root.getPendingGiftCards = function(cb) {
@ -144,6 +154,16 @@ angular.module('copayApp.services').factory('amazonService', function($http, $lo
});
};
return root;
var register = function() {
storageService.getAmazonGiftCards(root.getNetwork(), function(err, giftCards) {
if (giftCards) {
homeIntegrationsService.register(homeItem);
} else {
nextStepsService.register(nextStepItem);
}
});
};
register();
return root;
});

View File

@ -1,6 +1,6 @@
'use strict';
angular.module('copayApp.services').factory('bitpayCardService', function($log, $rootScope, lodash, storageService, bitauthService, platformInfo, moment, appIdentityService, bitpayService) {
angular.module('copayApp.services').factory('bitpayCardService', function($log, $rootScope, lodash, storageService, bitauthService, platformInfo, moment, appIdentityService, bitpayService, nextStepsService) {
var root = {};
var _setError = function(msg, e) {
@ -10,7 +10,7 @@ angular.module('copayApp.services').factory('bitpayCardService', function($log,
};
var _processTransactions = function(invoices, history) {
invoices = invoices || [];
invoices = invoices ||  [];
for (var i = 0; i < invoices.length; i++) {
var matched = false;
for (var j = 0; j < history.length; j++) {
@ -22,8 +22,8 @@ angular.module('copayApp.services').factory('bitpayCardService', function($log,
if (!matched && isInvoiceLessThanOneDayOld) {
var isInvoiceUnderpaid = invoices[i].exceptionStatus === 'paidPartial';
if(['paid', 'confirmed', 'complete'].indexOf(invoices[i].status) >= 0
|| (invoices[i].status === 'invalid' || isInvoiceUnderpaid)) {
if (['paid', 'confirmed', 'complete'].indexOf(invoices[i].status) >= 0 ||
(invoices[i].status === 'invalid' || isInvoiceUnderpaid)) {
history.unshift({
timestamp: new Date(invoices[i].invoiceTime),
@ -39,7 +39,7 @@ angular.module('copayApp.services').factory('bitpayCardService', function($log,
return history;
};
root.fetchBitpayDebitCards = function(apiContext, cb) {
root.sync = function(apiContext, cb) {
var json = {
method: 'getDebitCards'
};
@ -47,65 +47,109 @@ 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});
var cards = [];
lodash.each(data.data.data, function(x) {
var n = {};
if (!x.eid || !x.id || !x.lastFourDigits || !x.token) {
$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) {
return cb(_setError('BitPay Card Error: Get Debit Cards', data));
});
};
root.getHistory = function(cardId, params, cb) {
// opts: range
root.getHistory = function(cardId, opts, cb) {
var invoices, transactions;
params = params || {};
opts = opts || {};
var json = {
method: 'getInvoiceHistory',
params: JSON.stringify(params)
params: JSON.stringify(opts)
};
appIdentityService.getIdentity(bitpayService.getEnvironment().network, function(err, appIdentity) {
if (err) return cb(err);
root.getBitpayDebitCards(function(err, data) {
root.getCards(function(err, data) {
if (err) return cb(err);
var card = lodash.find(data, {id : cardId});
if (!card) return cb(_setError('Card not found'));
var card = lodash.find(data, {
id: cardId
});
if (!card)
return cb(_setError('Card not found'));
// Get invoices
bitpayService.post('/api/v2/' + card.token, json, function(data) {
$log.info('BitPay Get Invoices: SUCCESS');
invoices = data.data.data || [];
if (lodash.isEmpty(invoices)) $log.info('No invoices');
if (lodash.isEmpty(invoices))
$log.info('No invoices');
json = {
method: 'getTransactionHistory',
params: JSON.stringify(params)
params: JSON.stringify(opts)
};
// Get transactions list
bitpayService.post('/api/v2/' + card.token, json, function(data) {
$log.info('BitPay Get Transactions: SUCCESS');
transactions = data.data.data || {};
transactions['txs'] = _processTransactions(invoices, transactions.transactionList);
root.setLastKnownBalance(cardId, transactions.currentCardBalance, function() {});
return cb(data.data.error, transactions);
}, function(data) {
return cb(_setError('BitPay Card Error: Get Transactions', data));
});
}, function(data) {
return cb(_setError('BitPay Card Error: Get Invoices', data));
return cb(_setError('BitPay Card Error: Get Invoices', data));
});
});
});
};
root.topUp = function(cardId, params, cb) {
params = params || {};
root.topUp = function(cardId, opts, cb) {
opts = opts || {};
var json = {
method: 'generateTopUpInvoice',
params: JSON.stringify(params)
params: JSON.stringify(opts)
};
appIdentityService.getIdentity(bitpayService.getEnvironment().network, function(err, appIdentity) {
if (err) return cb(err);
root.getBitpayDebitCards(function(err, data) {
root.getCards(function(err, data) {
if (err) return cb(err);
var card = lodash.find(data, {id : cardId});
if (!card) return cb(_setError('Card not found'));
var card = lodash.find(data, {
id: cardId
});
if (!card)
return cb(_setError('Card not found'));
bitpayService.post('/api/v2/' + card.token, json, function(data) {
$log.info('BitPay TopUp: SUCCESS');
if(data.data.error) {
if (data.data.error) {
return cb(data.data.error);
} else {
return cb(null, data.data.data.invoice);
@ -126,75 +170,50 @@ angular.module('copayApp.services').factory('bitpayCardService', function($log,
});
};
root.getBitpayDebitCards = function(cb) {
storageService.getBitpayDebitCards(bitpayService.getEnvironment().network, function(err, data) {
if (err) return cb(err);
if (lodash.isString(data)) {
data = JSON.parse(data);
}
data = data || {};
return cb(null, data);
});
// get all cards, for all accounts.
root.getCards = function(cb) {
storageService.getBitpayDebitCards(bitpayService.getEnvironment().network, cb);
};
root.setBitpayDebitCards = function(data, cb) {
data = JSON.stringify(data);
storageService.setBitpayDebitCards(bitpayService.getEnvironment().network, data, function(err) {
if (err) return cb(err);
root.getLastKnownBalance = function(cardId, cb) {
storageService.getBalanceCache(cardId, cb);
};
root.addLastKnownBalance = function(card, cb) {
var now = Math.floor(Date.now() / 1000);
var showRange = 600; // 10min;
root.getLastKnownBalance(card.eid, function(err, data) {
if (data) {
data = JSON.parse(data);
card.balance = data.balance;
card.updatedOn = (data.updatedOn < now - showRange) ? data.updatedOn : null;
}
return cb();
});
};
root.getBitpayDebitCardsHistory = function(cardId, cb) {
storageService.getBitpayDebitCardsHistory(bitpayService.getEnvironment().network, function(err, data) {
if (err) return cb(err);
if (lodash.isString(data)) {
data = JSON.parse(data);
}
data = data || {};
if (cardId) data = data[cardId];
return cb(null, data);
});
root.setLastKnownBalance = function(cardId, balance, cb) {
storageService.setBalanceCache(cardId, {
balance: balance,
updatedOn: Math.floor(Date.now() / 1000),
}, cb);
};
root.setBitpayDebitCardsHistory = function(cardId, data, opts, cb) {
storageService.getBitpayDebitCardsHistory(bitpayService.getEnvironment().network, function(err, oldData) {
if (lodash.isString(oldData)) {
oldData = JSON.parse(oldData);
}
if (lodash.isString(data)) {
data = JSON.parse(data);
}
var inv = oldData || {};
inv[cardId] = data;
if (opts && opts.remove) {
delete(inv[cardId]);
}
inv = JSON.stringify(inv);
storageService.setBitpayDebitCardsHistory(bitpayService.getEnvironment().network, inv, function(err) {
return cb(err);
});
});
};
root.remove = function(card, cb) {
storageService.removeBitpayDebitCard(bitpayService.getEnvironment().network, card, function(err) {
root.remove = function(cardId, cb) {
storageService.removeBitpayDebitCard(bitpayService.getEnvironment().network, cardId, function(err) {
if (err) {
$log.error('Error removing BitPay debit card: ' + err);
// Continue, try to remove/cleanup card history
return cb(err);
}
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();
});
register();
storageService.removeBalanceCache(cardId, cb);
});
};
root.getRates = function(currency, cb) {
bitpayService.get('/rates/' + currency, function(data) {
$log.info('BitPay Get Rates: SUCCESS');
@ -204,6 +223,39 @@ angular.module('copayApp.services').factory('bitpayCardService', function($log,
});
};
root.get = function(opts, cb) {
root.getCards(function(err, cards) {
if (err) return;
if (lodash.isEmpty(cards)) {
return cb();
}
if (opts.cardId) {
cards = lodash.filter(cards, function(x) {
return opts.cardId == x.eid;
});
}
// Async, no problem
lodash.each(cards, function(x) {
root.addLastKnownBalance(x, function() {});
// async refresh
if (!opts.noRefresh) {
root.getHistory(x.id, {}, function(err, data) {
if (err) return;
root.addLastKnownBalance(x, function() {});
});
}
});
return cb(null, cards);
});
};
/*
* CONSTANTS
*/
@ -1241,6 +1293,25 @@ angular.module('copayApp.services').factory('bitpayCardService', function($log,
'bp002': 'default'
};
var nextStepItem = {
name: 'bitpaycard',
title: 'Add Bitpay VISA Card',
icon: 'icon-bitpay-card',
sref: 'tabs.bitpayCardIntro',
};
var register = function() {
root.getCards(function(err, cards) {
if (lodash.isEmpty(cards)) {
nextStepsService.register(nextStepItem);
} else {
nextStepsService.unregister(nextStepItem.name);
}
});
};
register();
return root;
});

View File

@ -136,13 +136,10 @@ angular.module('copayApp.services').factory('bitpayService', function($log, $htt
};
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();
});
storageService.setBitpayAccount(root.getEnvironment().network, accountData, cb);
};
var _get = function(endpoint) {
return {
method: 'GET',

View File

@ -0,0 +1,73 @@
'use strict';
angular.module('copayApp.services').factory('buyAndSellService', function($log, nextStepsService, lodash, $ionicScrollDelegate, $timeout) {
var root = {};
var services = [];
var linkedServices = [];
root.update = function() {
var newLinked = lodash.filter(services, function(x) {
return x.linked;
});
// This is to preserve linkedServices pointer
while (linkedServices.length)
linkedServices.pop();
while (newLinked.length)
linkedServices.push(newLinked.pop());
//
$log.debug('buyAndSell Service, updating nextSteps. linked/total: ' + linkedServices.length + '/' + services.length);
if (linkedServices.length == 0) {
nextStepsService.register({
title: 'Buy and Sell',
name: 'buyandsell',
icon: 'icon-buy-bitcoin',
sref: 'tabs.buyandsell',
});
} else {
nextStepsService.unregister({
name: 'buyandsell',
});
};
$timeout(function() {
$ionicScrollDelegate.resize();
}, 10);
};
var updateNextStepsDebunced = lodash.debounce(root.update, 1000);
root.register = function(serviceInfo) {
services.push(serviceInfo);
$log.info('Adding Buy and Sell service:' + serviceInfo.name + ' linked:' + serviceInfo.linked);
updateNextStepsDebunced();
};
root.updateLink = function(name, linked) {
var service = lodash.find(services, function(x) {
return x.name == name;
});
$log.info('Updating Buy and Sell service:' + name + ' linked:' + linked);
service.linked = linked
root.update();
};
root.get = function() {
return services;
};
root.getLinked = function() {
return linkedServices;
};
return root;
});

View File

@ -44,7 +44,7 @@ angular.module('copayApp.services')
body = gettextCatalog.getString('Insufficient funds');
break;
case 'CONNECTION_ERROR':
body = gettextCatalog.getString('Network connection error');
body = gettextCatalog.getString('Network error');
break;
case 'NOT_FOUND':
body = gettextCatalog.getString('Wallet service not found');

View File

@ -1,37 +1,32 @@
'use strict';
angular.module('copayApp.services').factory('coinbaseService', function($http, $log, $window, $filter, platformInfo, lodash, storageService, configService, appConfigService, txFormatService) {
angular.module('copayApp.services').factory('coinbaseService', function($http, $log, $window, $filter, platformInfo, lodash, storageService, configService, appConfigService, txFormatService, buyAndSellService, $rootScope) {
var root = {};
var credentials = {};
var isCordova = platformInfo.isCordova;
var isNW = platformInfo.isNW;
var isWindowsPhoneApp = platformInfo.isWP && platformInfo.isCordova;
root.priceSensitivity = [
{
value: 0.5,
name: '0.5%'
},
{
value: 1,
name: '1%'
},
{
value: 2,
name: '2%'
},
{
value: 5,
name: '5%'
},
{
value: 10,
name: '10%'
}
];
root.priceSensitivity = [{
value: 0.5,
name: '0.5%'
}, {
value: 1,
name: '1%'
}, {
value: 2,
name: '2%'
}, {
value: 5,
name: '5%'
}, {
value: 10,
name: '10%'
}];
root.selectedPriceSensitivity = root.priceSensitivity[1];
root.setCredentials = function() {
var setCredentials = function() {
if (!$window.externalServices || !$window.externalServices.coinbase) {
return;
@ -46,19 +41,19 @@ angular.module('copayApp.services').factory('coinbaseService', function($http, $
credentials.NETWORK = 'livenet';
// Coinbase permissions
credentials.SCOPE = ''
+ 'wallet:accounts:read,'
+ 'wallet:addresses:read,'
+ 'wallet:addresses:create,'
+ 'wallet:user:read,'
+ 'wallet:user:email,'
+ 'wallet:buys:read,'
+ 'wallet:buys:create,'
+ 'wallet:sells:read,'
+ 'wallet:sells:create,'
+ 'wallet:transactions:read,'
+ 'wallet:transactions:send,'
+ 'wallet:payment-methods:read';
credentials.SCOPE = '' +
'wallet:accounts:read,' +
'wallet:addresses:read,' +
'wallet:addresses:create,' +
'wallet:user:read,' +
'wallet:user:email,' +
'wallet:buys:read,' +
'wallet:buys:create,' +
'wallet:sells:read,' +
'wallet:sells:create,' +
'wallet:transactions:read,' +
'wallet:transactions:send,' +
'wallet:payment-methods:read';
// NW has a bug with Window Object
if (isCordova) {
@ -72,8 +67,7 @@ angular.module('copayApp.services').factory('coinbaseService', function($http, $
credentials.API = coinbase.sandbox.api;
credentials.CLIENT_ID = coinbase.sandbox.client_id;
credentials.CLIENT_SECRET = coinbase.sandbox.client_secret;
}
else {
} else {
credentials.HOST = coinbase.production.host;
credentials.API = coinbase.production.api;
credentials.CLIENT_ID = coinbase.production.client_id;
@ -85,6 +79,7 @@ angular.module('copayApp.services').factory('coinbaseService', function($http, $
if (data && data.access_token && data.refresh_token) {
storageService.setCoinbaseToken(credentials.NETWORK, data.access_token, function() {
storageService.setCoinbaseRefreshToken(credentials.NETWORK, data.refresh_token, function() {
buyAndSellService.updateLink('coinbase', true);
return cb(null, data.access_token);
});
});
@ -107,8 +102,8 @@ angular.module('copayApp.services').factory('coinbaseService', function($http, $
root.getAvailableCurrency = function() {
var config = configService.getSync().wallet.settings;
// ONLY "USD"
switch(config.alternativeIsoCode) {
default : return 'USD'
switch (config.alternativeIsoCode) {
default: return 'USD'
};
};
@ -141,14 +136,14 @@ angular.module('copayApp.services').factory('coinbaseService', function($http, $
}
root.getOauthCodeUrl = function() {
return credentials.HOST
+ '/oauth/authorize?response_type=code&client_id='
+ credentials.CLIENT_ID
+ '&redirect_uri='
+ credentials.REDIRECT_URI
+ '&state=SECURE_RANDOM&scope='
+ credentials.SCOPE
+ '&meta[send_limit_amount]=1000&meta[send_limit_currency]=USD&meta[send_limit_period]=day';
return credentials.HOST +
'/oauth/authorize?response_type=code&client_id=' +
credentials.CLIENT_ID +
'&redirect_uri=' +
credentials.REDIRECT_URI +
'&state=SECURE_RANDOM&scope=' +
credentials.SCOPE +
'&meta[send_limit_amount]=1000&meta[send_limit_currency]=USD&meta[send_limit_period]=day';
};
root.getToken = function(code, cb) {
@ -160,9 +155,9 @@ angular.module('copayApp.services').factory('coinbaseService', function($http, $
'Accept': 'application/json'
},
data: {
grant_type : 'authorization_code',
grant_type: 'authorization_code',
code: code,
client_id : credentials.CLIENT_ID,
client_id: credentials.CLIENT_ID,
client_secret: credentials.CLIENT_SECRET,
redirect_uri: credentials.REDIRECT_URI
}
@ -171,7 +166,6 @@ angular.module('copayApp.services').factory('coinbaseService', function($http, $
$http(req).then(function(data) {
$log.info('Coinbase Authorization Access Token: SUCCESS');
// Show pending task from the UI
storageService.setNextStep('BuyAndSell', 'true', function(err) {});
_afterTokenReceived(data.data, cb);
}, function(data) {
$log.error('Coinbase Authorization Access Token: ERROR ' + data.statusText);
@ -188,8 +182,8 @@ angular.module('copayApp.services').factory('coinbaseService', function($http, $
'Accept': 'application/json'
},
data: {
grant_type : 'refresh_token',
client_id : credentials.CLIENT_ID,
grant_type: 'refresh_token',
client_id: credentials.CLIENT_ID,
client_secret: credentials.CLIENT_SECRET,
redirect_uri: credentials.REDIRECT_URI,
refresh_token: refreshToken
@ -219,6 +213,19 @@ angular.module('copayApp.services').factory('coinbaseService', function($http, $
});
};
root.isActive = function(cb) {
if (isWindowsPhoneApp)
return cb();
if (lodash.isEmpty(credentials.CLIENT_ID))
return cb();
storageService.getCoinbaseToken(credentials.NETWORK, function(err, accessToken) {
return cb(err, !!accessToken);
});
}
root.init = lodash.throttle(function(cb) {
if (lodash.isEmpty(credentials.CLIENT_ID)) {
return cb('Coinbase is Disabled');
@ -238,7 +245,10 @@ angular.module('copayApp.services').factory('coinbaseService', function($http, $
if (err) return cb(err);
_getMainAccountId(newToken, function(err, accountId) {
if (err) return cb(err);
return cb(null, {accessToken: newToken, accountId: accountId});
return cb(null, {
accessToken: newToken,
accountId: accountId
});
});
});
});
@ -246,7 +256,10 @@ angular.module('copayApp.services').factory('coinbaseService', function($http, $
return cb(err);
}
} else {
return cb(null, {accessToken: accessToken, accountId: accountId});
return cb(null, {
accessToken: accessToken,
accountId: accountId
});
}
});
}
@ -410,7 +423,7 @@ angular.module('copayApp.services').factory('coinbaseService', function($http, $
var data = {
amount: data.amount,
currency: data.currency,
payment_method: data.payment_method || null,
payment_method: data.payment_method ||  null,
commit: data.commit || false,
quote: data.quote || false
};
@ -598,9 +611,10 @@ angular.module('copayApp.services').factory('coinbaseService', function($http, $
};
root.updatePendingTransactions = lodash.throttle(function() {
$log.debug('Updating pending transactions...');
root.setCredentials();
var pendingTransactions = { data: {} };
$log.debug('Updating coinbase pending transactions...');
var pendingTransactions = {
data: {}
};
root.getPendingTransactions(pendingTransactions);
}, 20000);
@ -707,6 +721,7 @@ angular.module('copayApp.services').factory('coinbaseService', function($http, $
root.logout = function(cb) {
storageService.removeCoinbaseToken(credentials.NETWORK, function() {
buyAndSellService.updateLink('coinbase', false);
storageService.removeCoinbaseRefreshToken(credentials.NETWORK, function() {
storageService.removeCoinbaseTxs(credentials.NETWORK, function() {
return cb();
@ -715,6 +730,33 @@ angular.module('copayApp.services').factory('coinbaseService', function($http, $
});
};
return root;
var register = function() {
root.isActive(function(err, isActive){
if (err) return;
buyAndSellService.register({
name: 'coinbase',
logo: 'img/coinbase-logo.png',
location: '33 Countries',
sref: 'tabs.buyandsell.coinbase',
configSref: 'tabs.preferences.coinbase',
linked: isActive,
});
});
};
setCredentials();
register();
$rootScope.$on('bwsEvent', function(e, walletId, type, n) {
if (type == 'NewBlock' && n && n.data && n.data.network == 'livenet') {
root.isActive(function(err,isActive){
// Update Coinbase
if (isActive)
root.updatePendingTransactions();
});
}
});
return root;
});

View File

@ -43,26 +43,14 @@ angular.module('copayApp.services').factory('configService', function(storageSer
},
// External services
glidera: {
enabled: true,
testnet: false
},
coinbaseV2: true,
bitpayCard: {
enabled: true
},
amazon: {
enabled: true
},
recentTransactions: {
enabled: true,
},
hideNextSteps: {
enabled: false,
},
rates: {
url: 'https://insight.bitpay.com:443/api/rates',
},
@ -191,18 +179,11 @@ angular.module('copayApp.services').factory('configService', function(storageSer
if (!configCache.wallet.settings.unitCode) {
configCache.wallet.settings.unitCode = defaultConfig.wallet.settings.unitCode;
}
if (!configCache.glidera) {
configCache.glidera = defaultConfig.glidera;
}
if (!configCache.coinbaseV2) {
configCache.coinbaseV2 = defaultConfig.coinbaseV2;
}
if (!configCache.amazon) {
configCache.amazon = defaultConfig.amazon;
}
if (!configCache.bitpayCard) {
configCache.bitpayCard = defaultConfig.bitpayCard;
if (!configCache.hideNextSteps) {
configCache.hideNextSteps = defaultConfig.hideNextSteps;
}
if (!configCache.recentTransactions) {
configCache.recentTransactions = defaultConfig.recentTransactions;
}

View File

@ -93,7 +93,7 @@ angular.module('copayApp.services')
if (lodash.isObject(v))
v = JSON.stringify(v);
if (!lodash.isString(v)) {
if (v && !lodash.isString(v)) {
v = v.toString();
}

View File

@ -1,11 +1,12 @@
'use strict';
angular.module('copayApp.services').factory('glideraService', function($http, $log, $window, platformInfo, storageService) {
angular.module('copayApp.services').factory('glideraService', function($http, $log, $window, platformInfo, storageService, buyAndSellService) {
var root = {};
var credentials = {};
var isCordova = platformInfo.isCordova;
var isWindowsPhoneApp = platformInfo.isWP && platformInfo.isCordova;
var _setCredentials = function() {
var setCredentials = function() {
if (!$window.externalServices || !$window.externalServices.glidera) {
return;
}
@ -17,6 +18,7 @@ angular.module('copayApp.services').factory('glideraService', function($http, $l
* Production: 'livenet'
*/
credentials.NETWORK = 'livenet';
//credentials.NETWORK = 'testnet';
if (credentials.NETWORK == 'testnet') {
credentials.HOST = glidera.sandbox.host;
@ -44,7 +46,6 @@ angular.module('copayApp.services').factory('glideraService', function($http, $l
};
root.getEnvironment = function() {
_setCredentials();
return credentials.NETWORK;
};
@ -57,19 +58,17 @@ angular.module('copayApp.services').factory('glideraService', function($http, $l
}
root.getOauthCodeUrl = function() {
_setCredentials();
return credentials.HOST + '/oauth2/auth?response_type=code&client_id=' + credentials.CLIENT_ID + '&redirect_uri=' + credentials.REDIRECT_URI;
};
root.removeToken = function(cb) {
_setCredentials();
storageService.removeGlideraToken(credentials.NETWORK, function() {
buyAndSellService.updateLink('glidera', false);
return cb();
});
};
root.getToken = function(code, cb) {
_setCredentials();
var req = {
method: 'POST',
url: credentials.HOST + '/api/v1/oauth/token',
@ -98,7 +97,6 @@ angular.module('copayApp.services').factory('glideraService', function($http, $l
};
var _get = function(endpoint, token) {
_setCredentials();
return {
method: 'GET',
url: credentials.HOST + '/api/v1' + endpoint,
@ -216,7 +214,6 @@ angular.module('copayApp.services').factory('glideraService', function($http, $l
};
var _post = function(endpoint, token, twoFaCode, data) {
_setCredentials();
return {
method: 'POST',
url: credentials.HOST + '/api/v1' + endpoint,
@ -293,7 +290,6 @@ angular.module('copayApp.services').factory('glideraService', function($http, $l
};
root.init = function(accessToken, cb) {
_setCredentials();
$log.debug('Init Glidera...');
var glidera = {
@ -312,6 +308,8 @@ angular.module('copayApp.services').factory('glideraService', function($http, $l
getToken(function(err, accessToken) {
if (err || !accessToken) return cb();
else {
buyAndSellService.updateLink('glidera', true);
root.getAccessTokenPermissions(accessToken, function(err, p) {
if (err) {
return cb(err);
@ -325,6 +323,25 @@ angular.module('copayApp.services').factory('glideraService', function($http, $l
});
};
return root;
var register = function() {
if (isWindowsPhoneApp) return;
storageService.getGlideraToken(credentials.NETWORK, function(err, token) {
if (err) return cb(err);
buyAndSellService.register({
name: 'glidera',
logo: 'img/glidera-logo.png',
location: 'US Only',
sref: 'tabs.buyandsell.glidera',
configSref: 'tabs.preferences.glidera',
linked: !!token,
});
});
};
setCredentials();
register();
return root;
});

View File

@ -0,0 +1,23 @@
'use strict';
angular.module('copayApp.services').factory('', function(configService, $log) {
var root = {};
var services = [];
root.register = function(serviceInfo) {
$log.info('Adding homeIntegration entry:' + serviceInfo.name);
services.push(serviceInfo);
};
root.unregister = function(serviceName) {
services = lodash.filter(services, function(x) {
return x.name != serviceName
});
};
root.get = function() {
return services;
};
return root;
});

View File

@ -0,0 +1,23 @@
'use strict';
angular.module('copayApp.services').factory('homeIntegrationsService', function(configService, $log) {
var root = {};
var services = [];
root.register = function(serviceInfo) {
$log.info('Adding home Integrations entry:' + serviceInfo.name);
services.push(serviceInfo);
};
root.unregister = function(serviceName) {
services = lodash.filter(services, function(x) {
return x.name != serviceName
});
};
root.get = function() {
return services;
};
return root;
});

View File

@ -43,16 +43,17 @@ angular.module('copayApp.services')
};
root.set = function(k, v, cb) {
if (lodash.isObject(v)) {
v = JSON.stringify(v);
}
if (v && !lodash.isString(v)) {
v = v.toString();
}
if (isChromeApp || isNW) {
var obj = {};
if (lodash.isObject(v)) {
v = JSON.stringify(v);
}
if (!lodash.isString(v)) {
v = v.toString();
}
obj[k] = v;
chrome.storage.local.set(obj, cb);
@ -60,7 +61,6 @@ angular.module('copayApp.services')
ls.setItem(k, v);
return cb();
}
};
root.remove = function(k, cb) {

View File

@ -0,0 +1,40 @@
'use strict';
angular.module('copayApp.services').factory('nextStepsService', function(configService, $log, lodash) {
var root = {};
var services = [];
root.register = function(serviceInfo) {
$log.info('Adding NextSteps entry:' + serviceInfo.name);
if (!lodash.find(services, function(x) {
return x.name == serviceInfo.name;
})) {
services.push(serviceInfo);
}
};
root.unregister = function(serviceName) {
var newS = lodash.filter(services, function(x) {
return x.name != serviceName;
});
// Found?
if (newS.length == services.length) return;
$log.info('Removing NextSteps entry:' + serviceName);
// This is to preserve services pointer
while (services.length)
services.pop();
while (newS.length)
services.push(newS.pop());
};
root.get = function() {
return services;
};
return root;
});

View File

@ -84,6 +84,7 @@ angular.module('copayApp.services').factory('openURLService', function($rootScop
$log.debug('Registering Browser handlers base:' + base);
navigator.registerProtocolHandler('bitcoin', url, 'Copay Bitcoin Handler');
navigator.registerProtocolHandler('web+copay', url, 'Copay Wallet Handler');
navigator.registerProtocolHandler('web+bitpay', url, 'Bitpay Wallet Handler');
}
}
};

View File

@ -747,6 +747,31 @@ angular.module('copayApp.services')
storageService.storeProfile(root.profile, cb);
};
root.getLastKnownBalance = function(wid, cb) {
storageService.getBalanceCache(wid, cb);
};
root.addLastKnownBalance = function(wallet, cb) {
var now = Math.floor(Date.now() / 1000);
var showRange = 600; // 10min;
root.getLastKnownBalance(wallet.id, function(err, data) {
if (data) {
data = JSON.parse(data);
wallet.cachedBalance = data.balance;
wallet.cachedBalanceUpdatedOn = (data.updatedOn < now - showRange) ? data.updatedOn : null;
}
return cb();
});
};
root.setLastKnownBalance = function(wid, balance, cb) {
storageService.setBalanceCache(wid, {
balance: balance,
updatedOn: Math.floor(Date.now() / 1000),
}, cb);
};
root.getWallets = function(opts) {
if (opts && !lodash.isObject(opts))
@ -780,6 +805,12 @@ angular.module('copayApp.services')
});
} else {}
// Add cached balance async
lodash.each(ret, function(x) {
root.addLastKnownBalance(x, function() {});
});
return lodash.sortBy(ret, [
function(x) {
@ -796,7 +827,7 @@ angular.module('copayApp.services')
root.getNotifications = function(opts, cb) {
opts = opts || {};
var TIME_STAMP = 60 * 60 * 6;
var TIME_STAMP = 60 * 60 * 6;
var MAX = 30;
var typeFilter = {

View File

@ -24,6 +24,9 @@ angular.module('copayApp.services')
}, cb);
};
// This is only used in Copay, we used to encrypt profile
// using device's UUID.
var decryptOnMobile = function(text, cb) {
var json;
try {
@ -74,312 +77,8 @@ angular.module('copayApp.services')
});
};
////////////////////////////////////////////////////////////////////////////
//
// UPGRADING STORAGE
//
// Upgraders are executed in numerical order per the '##_' object key prefix. Each upgrader will run.
// Each upgrader should detect storage configuraton and fail-safe; no upgrader should damage the ability
// of another to function properly (in order). Each upgrader should upgrade storage incrementally; storage
// upgrade is not complete until all upgraders have run.
//
// 1. Write a function to upgrade the desired storage key(s). The function should have the protocol:
//
// _upgrade_x(key, network, cb), where:
//
// `x` is the name of the storage key
// `key` is the name of the storage key being upgraded
// `network` is one of 'livenet', 'testnet'
//
// 2. Add the storage key to `_upgraders` object using the name of the key as the `_upgrader` object key
// with the value being the name of the upgrade function (e.g., _upgrade_x). In order to avoid conflicts
// when a storage key is involved in multiple upgraders as well as predicte the order in which upgrades
// occur the `_upgrader` object key should be prefixed with '##_' (e.g., '01_') to create a unique and
// sortable name. This format is interpreted by the _upgrade() function.
//
// 3. Any dependency functions called by upgraders should be copied/factored out and remain unchanged as
// long as the upgrader remains in effect. By convention the dependency function is prefixed by '##_' to
// 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 bitpayAccounts-x to bitpayAccounts-v2-x
'03_bitpayAccounts-v2' : _validate_bitpayAccounts_v2 // 2017-01: Validate keys on bitpayAccounts-v2-x, remove if not valid
};
function _upgrade_bitpayDebitCards(key, network, cb) {
key += '-' + network;
storage.get(key, function(err, data) {
if (err) return cb(err);
if (data != null) {
// Needs upgrade
if (lodash.isString(data)) {
data = JSON.parse(data);
}
data = data || {};
_00_setBitpayDebitCards(network, data, function(err) {
if (err) return cb(err);
storage.remove(key, function() {
cb(null, 'replaced with \'bitpayAccounts\'');
});
});
} else {
cb();
}
});
};
function _upgrade_bitpayCardCredentials(key, network, cb) {
key += '-' + network;
storage.get(key, function(err, data) {
if (err) return cb(err);
if (data != null) {
// Needs upgrade
_01_setAppIdentity(network, data, function(err) {
if (err) return cb(err);
storage.remove(key, function() {
cb(null, 'replaced with \'appIdentity\'');
});
});
} else {
cb();
}
});
};
function _upgrade_bitpayAccounts(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 = '';
_asyncEach(Object.keys(data), function(key, callback) {
// Keys are account emails
if (!data[key]['bitpayApi-' + network]) {
// Needs upgrade
upgraded += ' ' + key;
var acctData = {
acct: data[key],
token: data[key]['bitpayDebitCards-' + network].token,
email: key
};
_02_setBitpayAccount(network, acctData, function(err) {
if (err) return cb(err);
_02_setBitpayDebitCards(network, data[key]['bitpayDebitCards-' + network], function(err) {
if (err) return cb(err);
callback();
});
});
}
callback();
}, function() {
// done
// Remove obsolete key.
storage.remove('bitpayAccounts-' + network, function() {
if (upgraded.length > 0) {
cb(null, 'upgraded to \'bitpayAccounts-v2-' + network + '\':' + upgraded);
} else {
cb();
}
});
});
});
};
function _validate_bitpayAccounts_v2(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 verified = '';
var toRemove = [];
_asyncEach(Object.keys(data), function(key, callback) {
// Verify account API data
if (!data[key]['bitpayApi-' + network] ||
!data[key]['bitpayApi-' + network].token) {
// Invalid entry - one or more keys are missing
toRemove.push(key);
return callback();
}
// Verify debit cards
if (Array.isArray(data[key]['bitpayDebitCards-' + network])) {
for (var i = 0; i < data[key]['bitpayDebitCards-' + network].length; i++) {
if (!data[key]['bitpayDebitCards-' + network][i].token ||
!data[key]['bitpayDebitCards-' + network][i].eid ||
!data[key]['bitpayDebitCards-' + network][i].id ||
!data[key]['bitpayDebitCards-' + network][i].lastFourDigits) {
// Invalid entry - one or more keys are missing
toRemove.push(key);
return callback();
}
}
}
verified += ' ' + key;
return callback();
}, function() {
// done, remove invalid account entrys
if (toRemove.length > 0) {
var removed = '';
for (var i = 0; i < toRemove.length; i++) {
removed += ' ' + toRemove[i];
delete data[toRemove[i]];
}
storage.set('bitpayAccounts-v2-' + network, JSON.stringify(data), function(err) {
if (err) return cb(err);
// Ensure next step for cards is visible
storage.get('bitpayAccounts-v2-' + network, function(err, data) {
if (err) return cb(err);
if (lodash.isEmpty(data)) {
root.removeNextStep('BitpayCard', function(err) {});
}
});
cb(null, 'removed invalid account records, please re-pair cards for these accounts:' + removed + '; ' +
'the following accounts validated OK: ' + (verified.length > 0 ? verified : 'none'));
});
} else {
cb(null, (verified.length > 0 ? 'accounts OK: ' + verified : 'no accounts found'));
}
});
});
};
//
////////////////////////////////////////////////////////////////////////////
//
// UPGRADER DEPENDENCIES
// These functions remain as long as the upgrader remains in effect.
//
var _00_setBitpayDebitCards = function(network, data, cb) {
if (lodash.isString(data)) {
data = JSON.parse(data);
}
data = data || {};
if (lodash.isEmpty(data) || !data.email) return cb('No card(s) to set');
storage.get('bitpayAccounts-' + 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]['bitpayDebitCards-' + network] = data;
storage.set('bitpayAccounts-' + network, JSON.stringify(bitpayAccounts), cb);
});
};
var _01_setAppIdentity = function(network, data, cb) {
storage.set('appIdentity-' + network, data, cb);
};
var _02_setBitpayAccount = function(network, data, cb) {
if (lodash.isString(data)) {
data = JSON.parse(data);
}
data = data || {};
if (lodash.isEmpty(data) || !data.email || !data.acct) 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] = data.acct;
bitpayAccounts[data.email]['bitpayApi-' + network] = {};
bitpayAccounts[data.email]['bitpayApi-' + network].token = data.token;
storage.set('bitpayAccounts-v2-' + network, JSON.stringify(bitpayAccounts), cb);
});
};
var _02_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-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]['bitpayDebitCards-' + network] = data.cards;
storage.set('bitpayAccounts-v2-' + network, JSON.stringify(bitpayAccounts), cb);
});
};
//
////////////////////////////////////////////////////////////////////////////
// IMPORTANT: This function is designed to block execution until it completes.
// Ideally storage should not be used until it has been verified.
root.verify = function(cb) {
_upgrade(function(err) {
cb(err);
});
};
function _handleUpgradeError(key, err) {
$log.error('Failed to upgrade storage for \'' + key + '\': ' + err);
};
function _handleUpgradeSuccess(key, msg) {
$log.info('Storage upgraded for \'' + key + '\': ' + msg);
};
// IMPORTANT: This function is designed to block execution until it completes.
// Ideally storage should not be used until it has been verified.
function _upgrade(cb) {
var errorCount = 0;
var errorMessage = undefined;
var keys = Object.keys(_upgraders).sort();
var networks = ['livenet', 'testnet'];
_asyncEach(keys, function(key, callback_keys) {
_asyncEach(networks, function(network, callback_networks) {
var storagekey = key.split('_')[1];
_upgraders[key](storagekey, network, function(err, msg) {
if (err) {
_handleUpgradeError(storagekey + '-' + network, err);
errorCount++;
errorMessage = errorCount + ' storage upgrade failures';
}
if (msg) _handleUpgradeSuccess(storagekey + '-' + network, msg);
callback_networks();
});
}, function() {
// done - networks
callback_keys();
});
}, function() {
//done - keys
cb(errorMessage);
});
};
function _asyncEach(iterableList, callback, done) {
var i = -1;
var length = iterableList.length;
function loop() {
i++;
if (i === length) {
done();
return;
} else if (i < length) {
callback(iterableList[i], loop);
} else {
return;
}
}
loop();
};
// This is only use in Copay, for very old instalations
// in which we use to use localStorage instead of fileStorage
root.tryToMigrate = function(cb) {
if (!shouldUseFileStorage) return cb();
@ -645,51 +344,35 @@ angular.module('copayApp.services')
storage.remove('coinbaseTxs-' + network, cb);
};
root.setBitpayDebitCardsHistory = function(network, data, cb) {
storage.set('bitpayDebitCardsHistory-' + network, data, cb);
root.setBalanceCache = function(cardId, data, cb) {
storage.set('balanceCache-' + cardId, data, cb);
};
root.getBitpayDebitCardsHistory = function(network, cb) {
storage.get('bitpayDebitCardsHistory-' + network, cb);
root.getBalanceCache = function(cardId, cb) {
storage.get('balanceCache-' + cardId, cb);
};
root.removeBitpayDebitCardHistory = function(network, card, cb) {
root.getBitpayDebitCardsHistory(network, function(err, data) {
if (err) return cb(err);
if (lodash.isString(data)) {
data = JSON.parse(data);
}
data = data || {};
delete data[card.eid];
root.setBitpayDebitCardsHistory(network, JSON.stringify(data), cb);
});
root.removeBalanceCache = function(cardId, cb) {
storage.remove('balanceCache-' + cardId, cb);
};
// data: {
// cards: [
// eid: card id
// id: card id
// lastFourDigits: card number
// token: card token
// ]
// email: account email
// token: account token
// }
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-v2-' + network, function(err, bitpayAccounts) {
root.setBitpayDebitCards = function(network, email, cards, cb) {
root.getBitpayAccounts(network, function(err, allAccounts) {
if (err) return cb(err);
if (lodash.isString(bitpayAccounts)) {
bitpayAccounts = JSON.parse(bitpayAccounts);
if (!allAccounts[email]) {
return cb('Cannot set cards for unknown account ' + email);
}
bitpayAccounts = bitpayAccounts || {};
bitpayAccounts[data.email] = bitpayAccounts[data.email] || {};
bitpayAccounts[data.email]['bitpayDebitCards-' + network] = data.cards;
storage.set('bitpayAccounts-v2-' + network, JSON.stringify(bitpayAccounts), cb);
allAccounts[email].cards = cards;
storage.set('bitpayAccounts-v2-' + network, allAccounts, cb);
});
};
@ -702,24 +385,24 @@ angular.module('copayApp.services')
// email: account email
// ]
root.getBitpayDebitCards = function(network, cb) {
storage.get('bitpayAccounts-v2-' + network, function(err, bitpayAccounts) {
if (lodash.isString(bitpayAccounts)) {
bitpayAccounts = JSON.parse(bitpayAccounts);
}
bitpayAccounts = bitpayAccounts || {};
var cards = [];
_asyncEach(Object.keys(bitpayAccounts), function(email, callback) {
// For the UI, add the account email to the card object.
var acctCards = bitpayAccounts[email]['bitpayDebitCards-' + network] || [];
for (var i = 0; i < acctCards.length; i++) {
acctCards[i].email = email;
}
cards = cards.concat(acctCards);
callback();
}, function() {
// done
cb(err, cards);
root.getBitpayAccounts(network, function(err, allAccounts) {
if (err) return cb(err);
var allCards = [];
lodash.each(allAccounts, function(account, email) {
// Add account's email to card list, for convenience
var cards = lodash.clone(account.cards);
lodash.each(cards, function(x) {
x.email = email;
});
allCards = allCards.concat(cards);
});
return cb(null, allCards);
});
};
@ -729,95 +412,101 @@ angular.module('copayApp.services')
// lastFourDigits: card number
// token: card token
// }
root.removeBitpayDebitCard = function(network, card, cb) {
if (lodash.isString(card)) {
card = JSON.parse(card);
}
card = card || {};
if (lodash.isEmpty(card) || !card.eid) return cb('No card to remove');
storage.get('bitpayAccounts-v2-' + network, function(err, bitpayAccounts) {
if (err) cb(err);
if (lodash.isString(bitpayAccounts)) {
bitpayAccounts = JSON.parse(bitpayAccounts);
}
bitpayAccounts = bitpayAccounts || {};
_asyncEach(Object.keys(bitpayAccounts), function(email, callback) {
var data = bitpayAccounts[email]['bitpayDebitCards-' + network];
var newCards = lodash.reject(data, {
'eid': card.eid
});
data = {};
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', callback());
} else {
callback()
}
});
});
}, function() {
// done
cb();
});
});
};
root.removeBitpayDebitCard = function(network, cardEid, cb) {
// data: {
// email: account email
// token: account token
// }
root.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]['bitpayApi-' + network] = bitpayAccounts[data.email]['bitpayApi-' + network] || {};
bitpayAccounts[data.email]['bitpayApi-' + network].token = data.token;
storage.set('bitpayAccounts-v2-' + network, JSON.stringify(bitpayAccounts), cb);
root.getBitpayAccounts(network, function(err, allAccounts){
lodash.each(allAccounts, function(account){
account.cards = lodash.reject(account.cards, {
'eid': cardEid
});
});
storage.set('bitpayAccounts-v2-' + network, allAccounts, cb);
});
};
// cb(err, accounts)
// accounts: {
// email_1: {
// bitpayApi-<network>: {
// token: account token
// }
// bitpayDebitCards-<network>: {
// token: account token
// cards: {
// <card-data>
// }
// }
// ...
// email_n: {
// bitpayApi-<network>: {
// token: account token
// }
// bitpayDebitCards-<network>: {
// token: account token
// cards: {
// <card-data>
// }
// }
// }
//
root.getBitpayAccounts = function(network, cb) {
storage.get('bitpayAccounts-v2-' + network, function(err, bitpayAccounts) {
storage.get('bitpayAccounts-v2-' + network, function(err, allAccountsStr) {
if (err) return cb(err);
if (lodash.isString(bitpayAccounts)) {
bitpayAccounts = JSON.parse(bitpayAccounts);
}
cb(err, bitpayAccounts);
var allAccounts = {};
try {
allAccounts = JSON.parse(allAccountsStr);
} catch (e) {};
var anyMigration;
lodash.each(allAccounts, function(account, email) {
// Migrate old `'bitpayApi-' + network` key, if exists
if (!account.token && account['bitpayApi-' + network].token) {
$log.info('Migrating all bitpayApi-network branch');
account.token = account['bitpayApi-' + network].token;
account.cards = lodash.clone(account['bitpayApi-' + network].cards);
if (!account.cards) {
account.cards = lodash.clone(account['bitpayDebitCards-' + network]);
}
delete account['bitpayDebitCards-' + network];
delete account['bitpayApi-' + network];
anyMigration = true;
}
});
if (anyMigration) {
storage.set('bitpayAccounts-v2-' + network, allAccounts, function(){
return cb(err, allAccounts);
});
} else
return cb(err, allAccounts);
});
};
// data: {
// email: account email
// token: account token
// }
root.setBitpayAccount = function(network, data, cb) {
if (!lodash.isObject(data) || !data.email || !data.token)
return cb('No account to set');
var email = data.email;
var token = data.token;
root.getBitpayAccounts(network, function(err, allAccounts) {
if (err) return cb(err);
var account = allAccounts[email] || {};
account.token = token;
allAccounts[email] = account;
$log.info('Storing BitPay accounts with new account:' + email);
storage.set('bitpayAccounts-v2-' + network, allAccounts, cb);
});
};
@ -828,10 +517,7 @@ angular.module('copayApp.services')
root.getAppIdentity = function(network, cb) {
storage.get('appIdentity-' + network, function(err, data) {
if (err) return cb(err);
if (lodash.isString(data)) {
data = JSON.parse(data);
}
cb(err, data);
cb(err, JSON.parse(data || '{}'));
});
};

View File

@ -7,24 +7,6 @@
<ion-content>
<div class="settings-list list">
<div class="item item-divider" translate>Enabled Integrations</div>
<ion-toggle ng-show="!isWP" ng-model="bitpayCardEnabled.value" toggle-class="toggle-balanced" ng-change="bitpayCardChange()">
<span class="toggle-label" translate>Enable BitPay Card Integration</span>
</ion-toggle>
<ion-toggle ng-show="!isWP" ng-model="amazonEnabled.value" toggle-class="toggle-balanced" ng-change="amazonChange()">
<span class="toggle-label" translate>Enable Amazon Integration</span>
</ion-toggle>
<ion-toggle ng-show="!isWP" ng-model="glideraEnabled.value" toggle-class="toggle-balanced" ng-change="glideraChange()">
<span class="toggle-label" translate>Enable Glidera Service</span>
</ion-toggle>
<ion-toggle ng-show="!isWP" ng-model="coinbaseEnabled.value" toggle-class="toggle-balanced" ng-change="coinbaseChange()">
<span class="toggle-label" translate>Enable Coinbase Service</span>
</ion-toggle>
<div class="item item-divider" translate>Wallet Operation</div>
<ion-toggle class="has-comment" ng-model="spendUnconfirmed.value" toggle-class="toggle-balanced" ng-change="spendUnconfirmedChange()">
@ -41,6 +23,13 @@
If enabled, the Recent Transactions card - a list of transactions occuring across all wallets - will appear in the Home tab.
</div>
<ion-toggle ng-model="hideNextSteps.value" toggle-class="toggle-balanced" ng-change="nextStepsChange()">
<span class="toggle-label" translate>Hide Next Steps Card</span>
</ion-toggle>
</div>
</ion-content>
</ion-view>

View File

@ -2,7 +2,7 @@
<ion-nav-bar class="bar-royal">
<ion-nav-back-button>
</ion-nav-back-button>
<ion-nav-title>BitPay Visa<sup>&reg;</sup> Card ({{card.lastFourDigits}})</ion-nav-title>
<ion-nav-title>BitPay Visa<sup>&reg;</sup> Card ({{bitpayCard.lastFourDigits}})</ion-nav-title>
</ion-nav-bar>
<ion-content>
@ -14,8 +14,13 @@
<div class="amount-wrapper" ng-show="!error">
<div class="amount-bg"></div>
<div class="amount">
<div ng-if="bitpayCard.bitpayCardCurrentBalance" ng-click="bitpayCard.update()">
<div class="amount__balance">${{bitpayCard.bitpayCardCurrentBalance}}</div>
<div ng-if="bitpayCard.balance" ng-click="bitpayCard.update()">
<div class="amount__balance">${{bitpayCard.balance}}</div>
<div class="size-12 m5r" ng-if="bitpayCard.updatedOn">
{{bitpayCard.updatedOn * 1000 | amTimeAgo}}
</div>
<a class="button button-primary button-small m5t size-14"
style="padding: 0.5em 1em;"
ui-sref="tabs.bitpayCard.amount({'cardId': cardId, 'toName': 'BitPay Card'})">
@ -23,7 +28,7 @@
{{'Add Funds'|translate}}
</a>
</div>
<div ng-if="!bitpayCard.bitpayCardCurrentBalance" class="m10t">
<div ng-if="!bitpayCard.balance" class="m10t">
<strong class="size-36">...</strong>
</div>
</div>

View File

@ -14,17 +14,12 @@
<div class="explain-description" translate>Buy and sell bitcoin directly from your wallet by connecting your exchange accounts.</div>
</div>
<div class="item item-divider"></div>
<a class="item item-icon-right" ui-sref="tabs.buyandsell.coinbase" ng-show="isCoinbaseEnabled">
<img src="img/coinbase-logo.png" width="90">
<span class="item-note" translate>33 Countries</span>
<i class="icon bp-arrow-right"></i>
</a>
<a class="item item-icon-right" ui-sref="tabs.buyandsell.glidera" ng-show="isGlideraEnabled">
<img src="img/glidera-logo.png" width="90">
<span class="item-note" translate>US Only</span>
<i class="icon bp-arrow-right"></i>
</a>
<div ng-repeat="service in services">
<div class="item item-icon-right" ui-sref="{{service.sref}}">
<img ng-src="{{service.logo}}" width="90">
<span class="item-note" translate>{{service.location}}</span>
<i class="icon bp-arrow-right"></i>
</div>
</div>
</ion-content>
</ion-view>

View File

@ -0,0 +1,21 @@
<div class="list card">
<div class="item item-icon-right item-heading">
<span translate>Cards</span>
<a ui-sref="tabs.bitpayCardIntro"><i class="icon ion-ios-plus-empty list-add-button"></i></a>
</div>
<div>
<a ng-repeat="card in bitpayCardItems track by $index"
ui-sref="tabs.bitpayCard({id:card.id})"
class="item item-sub item-icon-left item-big-icon-left item-icon-right">
<i class="icon big-icon-svg">
<div class="bg icon-bitpay-card"></div>
</i>
<span>BitPay Visa&reg; Card ({{card.lastFourDigits}})</span>
<p>{{card.balance ? '$' + card.balance : 'Add funds to get started'|translate}} {{card.updatedOn ? (' &middot; ' + (card.updatedOn * 1000 | amTimeAgo)) : ''}}</p>
<i class="icon bp-arrow-right"></i>
</a>
</div>
</div>

View File

@ -0,0 +1,13 @@
<div class="list card" ng-controller="buyAndSellCardController">
<div class="item item-sub item-icon-right item-heading">
<span translate>Buy &amp; Sell Bitcoin</span>
<a ui-sref="tabs.buyandsell"><i class="icon ion-ios-plus-empty list-add-button"></i></a>
</div>
<div ng-repeat="service in services">
<a ui-sref="{{service.sref}}" class="item item-extra-padding item-sub item-icon-right">
<img ng-src="{{service.logo}}" width="90">
<i class="icon bp-arrow-right"></i>
</a>
</div>
</div>

View File

@ -0,0 +1,20 @@
<div class="list card" ng-controller="homeIntegrationsController">
<div class="item item-icon-right item-heading" ng-click="toggle()" >
<span translate>Services</span>
<i class="icon bp-arrow-up" ng-show="!hide"></i>
<i class="icon bp-arrow-down" ng-show="hide"></i>
</div>
<div ng-show="!hide">
<div ng-repeat="service in services track by $index">
<a ui-sref="{{service.sref}}" class="item item-sub item-icon-left item-big-icon-left item-icon-right next-step">
<i class="icon big-icon-svg">
<div class="bg {{service.icon}}"></div>
</i>
<span>{{service.title || service.name}}</span>
<i class="icon bp-arrow-right"></i>
</a>
</div>
</div>
</div>

View File

@ -0,0 +1,19 @@
<div class="list card" ng-controller="nextStepsController">
<div class="item item-icon-right item-heading" ng-click="toggle()" >
<span translate>Next steps</span>
<i class="icon bp-arrow-up" ng-show="!hide"></i>
<i class="icon bp-arrow-down" ng-show="hide"></i>
</div>
<div ng-show="!hide">
<div ng-repeat="service in services track by $index">
<a ui-sref="{{service.sref}}" class="item item-sub item-icon-left item-big-icon-left item-icon-right next-step">
<i class="icon big-icon-svg">
<div class="bg {{service.icon}}"></div>
</i>
<span>{{service.title || service.name}}</span>
<i class="icon bp-arrow-right"></i>
</a>
</div>
</div>
</div>

View File

@ -90,7 +90,8 @@
Incomplete
</span>
<span ng-if="wallet.isComplete()">
<span ng-if="!wallet.balanceHidden">{{wallet.status.totalBalanceStr}}</span>
<span ng-if="!wallet.balanceHidden"> {{wallet.status.totalBalanceStr ? wallet.status.totalBalanceStr : ( wallet.cachedBalance ? wallet.cachedBalance + (wallet.cachedBalanceUpdatedOn ? ' &middot; ' + ( wallet.cachedBalanceUpdatedOn * 1000 | amTimeAgo) : '') : '' ) }} </span>
<span ng-if="wallet.balanceHidden" translate>[Balance Hidden]</span>
<span class="tab-home__wallet__multisig-number" ng-if="wallet.n > 1">
{{wallet.m}}-of-{{wallet.n}}
@ -104,84 +105,10 @@
</a>
</div>
</div>
<div class="list card" ng-if="bitpayCardEnabled && bitpayCards[0] && externalServices.BitpayCard">
<div class="item item-icon-right item-heading">
<span translate>Cards</span>
<a ui-sref="tabs.bitpayCardIntro"><i class="icon ion-ios-plus-empty list-add-button"></i></a>
</div>
<div>
<a ng-repeat="card in bitpayCards"
ui-sref="tabs.bitpayCard({id:card.id})"
class="item item-sub item-icon-left item-big-icon-left item-icon-right">
<i class="icon big-icon-svg">
<div class="bg icon-bitpay-card"></div>
</i>
<span>BitPay Visa&reg; Card ({{card.lastFourDigits}})</span>
<p>{{cardsHistory[card.id].balance ? '$' + cardsHistory[card.id].balance : 'Add funds to get started'|translate}}</p>
<i class="icon bp-arrow-right"></i>
</a>
</div>
</div>
<div class="list card" ng-if="wallets[0] && externalServices.BuyAndSell && (glideraEnabled || coinbaseEnabled)">
<div class="item item-sub item-icon-right item-heading">
<span translate>Buy &amp; Sell Bitcoin</span>
<a ui-sref="tabs.buyandsell"><i class="icon ion-ios-plus-empty list-add-button"></i></a>
</div>
<div>
<a ng-if="glideraEnabled" ui-sref="tabs.buyandsell.glidera" class="item item-extra-padding item-sub item-icon-right">
<img src="img/glidera-logo.png" width="90"/>
<i class="icon bp-arrow-right"></i>
</a>
<a ng-if="coinbaseEnabled" ui-sref="tabs.buyandsell.coinbase" class="item item-extra-padding item-sub item-icon-right">
<img src="img/coinbase-logo.png" width="90">
<i class="icon bp-arrow-right"></i>
</a>
</div>
</div>
<div class="list card" ng-if="wallets[0] && externalServices.AmazonGiftCards && amazonEnabled">
<a class="item item-sub item-icon-left item-icon-right item-big-icon-left" ui-sref="tabs.giftcards.amazon">
<i class="icon big-icon-svg">
<div class="bg icon-amazon"></div>
</i>
<span>Amazon.com Gift Cards</span>
<i class="icon bp-arrow-right"></i>
</a>
</div>
<div class="list card"
ng-show="nextStepEnabled && wallets[0]">
<div class="item item-icon-right item-heading" ng-click="shouldHideNextSteps()">
<span translate>Next steps</span>
<i class="icon bp-arrow-up" ng-show="!hideNextSteps"></i>
<i class="icon bp-arrow-down" ng-show="hideNextSteps"></i>
</div>
<div ng-show="!hideNextSteps">
<a ui-sref="tabs.bitpayCardIntro" ng-if="!externalServices.BitpayCard && bitpayCardEnabled" class="item item-sub item-icon-left item-big-icon-left item-icon-right next-step">
<i class="icon big-icon-svg">
<div class="bg icon-bitpay-card"></div>
</i>
<span translate>Add BitPay Visa&reg; Card</span>
<i class="icon bp-arrow-right"></i>
</a>
<a ng-if="!externalServices.BuyAndSell && (coinbaseEnabled || glideraEnabled)" ui-sref="tabs.buyandsell" class="item item-sub item-icon-left item-big-icon-left item-icon-right next-step">
<i class="icon big-icon-svg">
<div class="bg icon-buy-bitcoin"></div>
</i>
<span translate>Buy or Sell Bitcoin</span>
<i class="icon bp-arrow-right"></i>
</a>
<a ui-sref="tabs.giftcards.amazon" ng-if="!externalServices.AmazonGiftCards && amazonEnabled" class="item item-sub item-icon-left item-big-icon-left item-icon-right next-step">
<i class="icon big-icon-svg">
<div class="bg icon-amazon"></div>
</i>
<span translate>Buy a gift card</span>
<i class="icon bp-arrow-right"></i>
</a>
</div>
</div>
<div class="ng-hide" ng-show="wallets[0] && bitpayCardItems.length>0" ng-include="'views/includes/bitpayCardsCard.html'"></div>
<div class="ng-hide" ng-show="wallets[0] && buyAndSellItems.length>0" ng-include="'views/includes/buyAndSellCard.html'"></div>
<div class="ng-hide" ng-show="homeIntegrations.length>0" ng-include="'views/includes/homeIntegrations.html'"></div>
<div class="ng-hide" ng-show="nextStepsItems.length>0" ng-include="'views/includes/nextSteps.html'"></div>
</ion-content>
</ion-view>

View File

@ -117,7 +117,7 @@
</a>
<a class=" item item-icon-left item-icon-right"
ng-if="bitpayCardEnabled && bitpayCards"
ng-if="bitpayCards"
ui-sref="tabs.preferences.bitpayCard">
<i class="icon big-icon-svg circle">
<div class="bg icon-bitpay-card"></div>
@ -126,19 +126,13 @@
<i class="icon bp-arrow-right"></i>
</a>
<a class="item item-icon-right"
ng-if="glideraEnabled && glideraToken"
ui-sref="tabs.preferences.glidera">
<img src="img/glidera-logo.png" width="90"/>
<i class="icon bp-arrow-right"></i>
</a>
<a class="item item-icon-right"
ng-if="coinbaseEnabled && coinbaseToken"
ui-sref="tabs.preferences.coinbase">
<img src="img/coinbase-logo.png" width="90"/>
<i class="icon bp-arrow-right"></i>
</a>
<div ng-repeat="service in buyAndSellServices">
<a class="item item-icon-right"
ui-sref="{{service.configSref}}">
<img ng-src="{{service.logo}}" width="90">
<i class="icon bp-arrow-right"></i>
</a>
</div>
<div class="item item-divider"></div>