diff --git a/src/js/controllers/amazonCards.js b/src/js/controllers/amazonCards.js index c0e42b791..42bef3fd7 100644 --- a/src/js/controllers/amazonCards.js +++ b/src/js/controllers/amazonCards.js @@ -51,8 +51,8 @@ angular.module('copayApp.controllers').controller('amazonCardsController', remove: true }, function(err) { updateGiftCards(); - return; }); + return; } amazonService.savePendingGiftCard(newData, null, function(err) { @@ -85,11 +85,11 @@ angular.module('copayApp.controllers').controller('amazonCardsController', }; $scope.$on("$ionicView.beforeEnter", function(event, data) { - $scope.cardClaimCode = data.stateParams.cardClaimCode; + $scope.invoiceId = data.stateParams.invoiceId; updateGiftCards(function() { - if ($scope.cardClaimCode) { + if ($scope.invoiceId) { var card = lodash.find($scope.giftCards, { - claimCode: $scope.cardClaimCode + invoiceId: $scope.invoiceId }); if (lodash.isEmpty(card)) { popupService.showAlert(null, 'Card not found'); diff --git a/src/js/controllers/buyAmazon.js b/src/js/controllers/buyAmazon.js index 15fe08aab..1f1cd9cb2 100644 --- a/src/js/controllers/buyAmazon.js +++ b/src/js/controllers/buyAmazon.js @@ -1,29 +1,41 @@ 'use strict'; -angular.module('copayApp.controllers').controller('buyAmazonController', function($scope, $log, $state, $timeout, $filter, $ionicHistory, $ionicConfig, lodash, amazonService, popupService, profileService, ongoingProcess, configService, walletService, payproService, bwcError, externalLinkService, platformInfo) { +angular.module('copayApp.controllers').controller('buyAmazonController', function($scope, $log, $state, $timeout, $filter, $ionicHistory, $ionicConfig, lodash, amazonService, popupService, profileService, ongoingProcess, configService, walletService, payproService, bwcError, externalLinkService, platformInfo, gettextCatalog, txFormatService) { var amount; var currency; - $scope.isCordova = platformInfo.isCordova; + var createdTx; + var message; + var invoiceId; + var configWallet = configService.getSync().wallet; + $scope.isCordova = platformInfo.isCordova; $scope.openExternalLink = function(url) { externalLinkService.open(url); }; - var showErrorAndBack = function(msg, err) { + var _resetValues = function() { + $scope.totalAmountStr = $scope.amount = $scope.invoiceFee = $scope.networkFee = $scope.totalAmount = $scope.wallet = null; + createdTx = message = invoiceId = null; + }; + + var showErrorAndBack = function(title, msg) { + title = title || gettextCatalog.getString('Error'); $scope.sendStatus = ''; - $log.error(err); - err = err && err.errors ? err.errors[0].message : err; - popupService.showAlert(msg, err, function() { + $log.error(msg); + msg = (msg && msg.errors) ? msg.errors[0].message : msg; + popupService.showAlert(title, msg, function() { $ionicHistory.goBack(); }); }; - var showError = function(msg, err) { + var showError = function(title, msg, cb) { + cb = cb || function() {}; + title = title || gettextCatalog.getString('Error'); $scope.sendStatus = ''; - $log.error(err); - err = err && err.errors ? err.errors[0].message : (err || ''); - popupService.showAlert(msg, err); + $log.error(msg); + msg = (msg && msg.errors) ? msg.errors[0].message : msg; + popupService.showAlert(title, msg, cb); }; var publishAndSign = function(wallet, txp, onSendStatusChange, cb) { @@ -51,6 +63,111 @@ angular.module('copayApp.controllers').controller('buyAmazonController', functio } }; + var satToFiat = function(sat, cb) { + txFormatService.toFiat(sat, $scope.currencyIsoCode, function(value) { + return cb(value); + }); + }; + + var setTotalAmount = function(amountSat, invoiceFeeSat, networkFeeSat) { + satToFiat(amountSat, function(a) { + $scope.amount = Number(a); + + satToFiat(invoiceFeeSat, function(i) { + $scope.invoiceFee = Number(i); + + satToFiat(networkFeeSat, function(n) { + $scope.networkFee = Number(n); + $scope.totalAmount = $scope.amount + $scope.invoiceFee + $scope.networkFee; + $timeout(function() { + $scope.$digest(); + }); + }); + }); + }); + }; + + var createInvoice = function(data, cb) { + amazonService.createBitPayInvoice(data, function(err, dataInvoice) { + if (err) { + var err_title = gettextCatalog.getString('Error creating the invoice'); + var err_msg; + if (err && err.message && err.message.match(/suspended/i)) { + err_title = gettextCatalog.getString('Service not available'); + err_msg = gettextCatalog.getString('Amazon.com Gift Card Service is not available at this moment. Please try back later.'); + } else if (err && err.message) { + err_msg = err.message; + } else { + err_msg = gettextCatalog.getString('Could not access Gift Card Service'); + }; + + return cb({ + title: err_title, + message: err_msg + }); + } + + var accessKey = dataInvoice ? dataInvoice.accessKey : null; + + if (!accessKey) { + return cb({ + message: gettextCatalog.getString('No access key defined') + }); + } + + amazonService.getBitPayInvoice(dataInvoice.invoiceId, function(err, invoice) { + if (err) { + return cb({ + message: gettextCatalog.getString('Could not get the invoice') + }); + } + + return cb(null, invoice, accessKey); + }); + }); + }; + + var createTx = function(wallet, invoice, message, cb) { + var payProUrl = (invoice && invoice.paymentUrls) ? invoice.paymentUrls.BIP73 : null; + + if (!payProUrl) { + return cb({ + title: gettextCatalog.getString('Error in Payment Protocol'), + message: gettextCatalog.getString('Invalid URL') + }); + } + + var outputs = []; + var toAddress = invoice.bitcoinAddress; + var amountSat = parseInt(invoice.btcDue * 100000000); // BTC to Satoshi + + outputs.push({ + 'toAddress': toAddress, + 'amount': amountSat, + 'message': message + }); + + var txp = { + toAddress: toAddress, + amount: amountSat, + outputs: outputs, + message: message, + payProUrl: payProUrl, + excludeUnconfirmedUtxos: configWallet.spendUnconfirmed ? false : true, + feeLevel: configWallet.settings.feeLevel || 'normal' + }; + + walletService.createTx(wallet, txp, function(err, ctxp) { + if (err) { + return cb({ + title: gettextCatalog.getString('Could not create transaction'), + message: bwcError.msg(err) + }); + } + return cb(null, ctxp); + }); + }; + var checkTransaction = lodash.throttle(function(count, dataSrc) { amazonService.createGiftCard(dataSrc, function(err, giftCard) { $log.debug("creating gift card " + count); @@ -58,7 +175,7 @@ angular.module('copayApp.controllers').controller('buyAmazonController', functio ongoingProcess.set('buyingGiftCard', false, statusChangeHandler); giftCard = {}; giftCard.status = 'FAILURE'; - showError('Error creating gift card', err); + showError(gettextCatalog.getString('Error creating gift card'), err); } if (giftCard.status == 'PENDING' && count < 3) { @@ -83,9 +200,9 @@ angular.module('copayApp.controllers').controller('buyAmazonController', functio }, function(err) { $log.error(err); ongoingProcess.set('buyingGiftCard', false, statusChangeHandler); - showError('Gift card expired'); - return; + showError(null, gettextCatalog.getString('Gift card expired')); }); + return; } amazonService.savePendingGiftCard(newData, null, function(err) { @@ -98,6 +215,57 @@ angular.module('copayApp.controllers').controller('buyAmazonController', functio 'leading': true }); + var initialize = function(wallet) { + var parsedAmount = txFormatService.parseAmount(amount, currency); + $scope.currencyIsoCode = parsedAmount.alternativeIsoCode; + $scope.amountUnitStr = parsedAmount.amountUnitStr; + var dataSrc = { + amount: parsedAmount.amount, + currency: parsedAmount.currency, + uuid: wallet.id + }; + ongoingProcess.set('loadingTxInfo', true); + createInvoice(dataSrc, function(err, invoice, accessKey) { + if (err) { + ongoingProcess.set('loadingTxInfo', false); + showErrorAndBack(err.title, err.message); + return; + } + // Sometimes API does not return this element; + invoice['buyerPaidBtcMinerFee'] = invoice.buyerPaidBtcMinerFee || 0; + var invoiceFeeSat = (invoice.buyerPaidBtcMinerFee * 100000000).toFixed(); + + message = gettextCatalog.getString("Amazon.com Gift Card {{amountStr}}", { + amountStr: $scope.amountUnitStr + }); + + createTx(wallet, invoice, message, function(err, ctxp) { + ongoingProcess.set('loadingTxInfo', false); + if (err) { + _resetValues(); + showError(err.title, err.message); + return; + } + + // Save in memory + createdTx = ctxp; + invoiceId = invoice.id; + + createdTx['giftData'] = { + currency: dataSrc.currency, + amount: dataSrc.amount, + uuid: dataSrc.uuid, + accessKey: accessKey, + invoiceId: invoice.id, + invoiceUrl: invoice.url, + invoiceTime: invoice.invoiceTime + }; + $scope.totalAmountStr = txFormatService.formatAmountStr(ctxp.amount); + setTotalAmount(parsedAmount.amountSat, invoiceFeeSat, ctxp.fee); + }); + }); + }; + $scope.$on("$ionicView.beforeLeave", function(event, data) { $ionicConfig.views.swipeBackEnabled(true); }); @@ -110,13 +278,16 @@ angular.module('copayApp.controllers').controller('buyAmazonController', functio amount = data.stateParams.amount; currency = data.stateParams.currency; - if (amount > 1000) { - showErrorAndBack('Purchase Amount is limited to USD 1000 per day'); + $scope.limitPerDayMessage = gettextCatalog.getString('Purchase Amount is limited to {{limitPerDay}} {{currency}} per day', { + limitPerDay: amazonService.limitPerDay, + currency: currency + }); + + if (amount > amazonService.limitPerDay) { + showErrorAndBack(null, $scope.limitPerDayMessage); return; } - $scope.amountUnitStr = $filter('formatFiatAmount')(amount) + ' ' + currency; - $scope.network = amazonService.getNetwork(); $scope.wallets = profileService.getWallets({ onlyComplete: true, @@ -124,135 +295,47 @@ angular.module('copayApp.controllers').controller('buyAmazonController', functio hasFunds: true }); if (lodash.isEmpty($scope.wallets)) { - showErrorAndBack('No wallets with funds'); + showErrorAndBack(null, gettextCatalog.getString('No wallets available')); return; } - $scope.wallet = $scope.wallets[0]; // Default first wallet + $scope.onWalletSelect($scope.wallets[0]); // Default first wallet }); $scope.buyConfirm = function() { - - var message = 'Buy gift card for ' + amount + ' ' + currency; - var okText = 'Confirm'; - var cancelText = 'Cancel'; - popupService.showConfirm(null, message, okText, cancelText, function(ok) { - if (!ok) return; - - var config = configService.getSync(); - var configWallet = config.wallet; - var walletSettings = configWallet.settings; - // Get first wallet as UUID - var uuid = $scope.wallet.id; - var dataSrc = { - currency: currency, - amount: amount, - uuid: uuid - }; + if (!createdTx) { + showError(null, gettextCatalog.getString('Transaction has not been created')); + return; + } + var title = gettextCatalog.getString('Confirm'); + var okText = gettextCatalog.getString('Ok'); + var cancelText = gettextCatalog.getString('Cancel'); + popupService.showConfirm(title, message, okText, cancelText, function(ok) { + if (!ok) { + $scope.sendStatus = ''; + return; + } ongoingProcess.set('buyingGiftCard', true, statusChangeHandler); - amazonService.createBitPayInvoice(dataSrc, function(err, dataInvoice) { + publishAndSign($scope.wallet, createdTx, function() {}, function(err, txSent) { if (err) { + _resetValues(); ongoingProcess.set('buyingGiftCard', false, statusChangeHandler); - - if (err && err.message && err.message.match(/suspended/i)) { - showError('Service not available', 'Amazon Gift Card Service is not available at this moment. Please try back later.'); - } else { - showError('Could not access Gift Card Service', err); - }; - + showError(gettextCatalog.getString('Could not send transaction'), err); return; } - - var accessKey = dataInvoice ? dataInvoice.accessKey : null; - - if (!accessKey) { - ongoingProcess.set('buyingGiftCard', false, statusChangeHandler); - showError('No access key defined'); - return; - } - - amazonService.getBitPayInvoice(dataInvoice.invoiceId, function(err, invoice) { - if (err) { - ongoingProcess.set('buyingGiftCard', false, statusChangeHandler); - showError('Error getting BitPay invoice', err); - return; - } - - var payProUrl = (invoice && invoice.paymentUrls) ? invoice.paymentUrls.BIP73 : null; - - if (!payProUrl) { - ongoingProcess.set('buyingGiftCard', false, statusChangeHandler); - showError('Error fetching invoice'); - return; - } - - payproService.getPayProDetails(payProUrl, function(err, payProDetails) { - if (err) { - ongoingProcess.set('buyingGiftCard', false, statusChangeHandler); - showError('Error fetching payment info', bwcError.msg(err)); - return; - } - - var outputs = []; - var toAddress = payProDetails.toAddress; - var amountSat = payProDetails.amount; - var comment = amount + ' ' + currency + ' Amazon.com Gift Card'; - - outputs.push({ - 'toAddress': toAddress, - 'amount': amountSat, - 'message': comment - }); - - var txp = { - toAddress: toAddress, - amount: amountSat, - outputs: outputs, - message: comment, - payProUrl: payProUrl, - excludeUnconfirmedUtxos: configWallet.spendUnconfirmed ? false : true, - feeLevel: walletSettings.feeLevel || 'normal' - }; - - walletService.createTx($scope.wallet, txp, function(err, ctxp) { - if (err) { - ongoingProcess.set('buyingGiftCard', false, statusChangeHandler); - showError('Could not create transaction', bwcError.msg(err)); - return; - } - publishAndSign($scope.wallet, ctxp, function() {}, function(err, txSent) { - if (err) { - ongoingProcess.set('buyingGiftCard', false, statusChangeHandler); - showError('Could not send transaction', err); - return; - } - $log.debug('Transaction broadcasted. Waiting for confirmation...'); - var invoiceId = JSON.parse(payProDetails.merchant_data).invoiceId; - var dataSrc = { - currency: currency, - amount: amount, - uuid: uuid, - accessKey: accessKey, - invoiceId: invoice.id, - invoiceUrl: payProUrl, - invoiceTime: invoice.invoiceTime - }; - checkTransaction(1, dataSrc); - }); - }); - }, true); // Disable loader - }); + checkTransaction(1, createdTx.giftData); }); }); }; $scope.showWalletSelector = function() { - $scope.walletSelectorTitle = 'Buy from'; + $scope.walletSelectorTitle = gettextCatalog.getString('Buy from'); $scope.showWallets = true; }; $scope.onWalletSelect = function(wallet) { $scope.wallet = wallet; + initialize(wallet); }; $scope.goBackHome = function() { @@ -262,14 +345,13 @@ angular.module('copayApp.controllers').controller('buyAmazonController', functio historyRoot: true }); $ionicHistory.clearHistory(); - var claimCode = $scope.amazonGiftCard ? $scope.amazonGiftCard.claimCode : null; $state.go('tabs.home').then(function() { $ionicHistory.nextViewOptions({ disableAnimate: true }); $state.transitionTo('tabs.giftcards.amazon').then(function() { $state.transitionTo('tabs.giftcards.amazon.cards', { - cardClaimCode: claimCode + invoiceId: invoiceId }); }); }); diff --git a/src/js/routes.js b/src/js/routes.js index 9e3556020..b557a6282 100644 --- a/src/js/routes.js +++ b/src/js/routes.js @@ -1049,7 +1049,7 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr } }, params: { - cardClaimCode: null + invoiceId: null } }) .state('tabs.giftcards.amazon.amount', { diff --git a/src/js/services/amazonService.js b/src/js/services/amazonService.js index c0dee597e..54d119c55 100644 --- a/src/js/services/amazonService.js +++ b/src/js/services/amazonService.js @@ -3,6 +3,8 @@ angular.module('copayApp.services').factory('amazonService', function($http, $lo var root = {}; var credentials = {}; + root.limitPerDay = 1000; + /* * Development: 'testnet' * Production: 'livenet' diff --git a/www/views/buyAmazon.html b/www/views/buyAmazon.html index 04440fa9e..6fbe3690e 100644 --- a/www/views/buyAmazon.html +++ b/www/views/buyAmazon.html @@ -2,7 +2,7 @@ - Buy + {{'Buy'|translate}} @@ -19,14 +19,14 @@
{{amountUnitStr}}
- Purchase Amount is limited to USD 1000 per day + {{limitPerDayMessage}}
-
From
+
From
@@ -35,6 +35,36 @@
+
+
+ Details +
+
+ Gift card + + {{amount | currency:'$ ':2}} {{currencyIsoCode}} + +
+
+ Invoice Fee + + {{invoiceFee | currency:'$ ':2}} {{currencyIsoCode}} + +
+
+ Network Fee + + {{networkFee | currency:'$ ':2}} {{currencyIsoCode}} + +
+
+ Total + + {{totalAmount | currency:'$ ':2}} {{currencyIsoCode}} + ({{totalAmountStr}}) + +
+
* Amazon.com is not a sponsor of this promotion. @@ -56,30 +86,29 @@ ng-click="buyConfirm()" ng-if="!isCordova" click-send-status="sendStatus" - is-disabled="!wallet"> - Confirm purchase + is-disabled="!wallet || !totalAmountStr"> + {{'Confirm purchase'|translate}} - Slide to buy + slide-send-status="sendStatus"> + {{'Slide to buy'|translate}} - + Your purchase could not be completed - + Your purchase was added to the list of pending - - Bought {{amazonGiftCard.amount}} {{amazonGiftCard.currency}} + + Bought {{amountUnitStr}} -
+
Gift card generated and ready to use.