UNPKG

copay-crown

Version:

A Secure Crown Wallet

469 lines (406 loc) 16.2 kB
import { Component } from '@angular/core'; import { TranslateService } from '@ngx-translate/core'; import { Events, ModalController, NavController, NavParams } from 'ionic-angular'; import * as _ from 'lodash'; import * as moment from 'moment'; import { Logger } from '../../../../providers/logger/logger'; // Pages import { FinishModalPage } from '../../../finish/finish'; import { FeeWarningPage } from '../../../send/fee-warning/fee-warning'; import { AmazonPage } from '../amazon'; // Provider import { AmazonProvider } from '../../../../providers/amazon/amazon'; import { BwcErrorProvider } from '../../../../providers/bwc-error/bwc-error'; import { BwcProvider } from '../../../../providers/bwc/bwc'; import { ConfigProvider } from '../../../../providers/config/config'; import { EmailNotificationsProvider } from '../../../../providers/email-notifications/email-notifications'; import { ExternalLinkProvider } from '../../../../providers/external-link/external-link'; import { OnGoingProcessProvider } from "../../../../providers/on-going-process/on-going-process"; import { PopupProvider } from '../../../../providers/popup/popup'; import { ProfileProvider } from '../../../../providers/profile/profile'; import { TxFormatProvider } from '../../../../providers/tx-format/tx-format'; import { WalletProvider } from '../../../../providers/wallet/wallet'; @Component({ selector: 'page-buy-amazon', templateUrl: 'buy-amazon.html', }) export class BuyAmazonPage { private bitcoreCash: any; private amount: number; private currency: string; private createdTx: any; private message: string; private invoiceId: string; private configWallet: any; private currencyIsoCode: string; private FEE_TOO_HIGH_LIMIT_PER: number; public wallet: any; public wallets: any; public totalAmountStr: string; public invoiceFee: number; public networkFee: number; public totalAmount: number; public amazonGiftCard: any; public amountUnitStr: string; public limitPerDayMessage: string; public network: string; public walletSelectorTitle: string; constructor( private amazonProvider: AmazonProvider, private bwcErrorProvider: BwcErrorProvider, private bwcProvider: BwcProvider, private configProvider: ConfigProvider, private emailNotificationsProvider: EmailNotificationsProvider, private events: Events, private externalLinkProvider: ExternalLinkProvider, private logger: Logger, private modalCtrl: ModalController, private navCtrl: NavController, private navParams: NavParams, private onGoingProcessProvider: OnGoingProcessProvider, private popupProvider: PopupProvider, private profileProvider: ProfileProvider, private txFormatProvider: TxFormatProvider, private walletProvider: WalletProvider, private translate: TranslateService ) { this.FEE_TOO_HIGH_LIMIT_PER = 15; this.configWallet = this.configProvider.get().wallet; this.amazonGiftCard = null; this.bitcoreCash = this.bwcProvider.getBitcoreCash(); } ionViewDidLoad() { this.logger.info('ionViewDidLoad BuyAmazonPage'); } ionViewWillEnter() { this.amount = this.navParams.data.amount; this.currency = this.navParams.data.currency; let limitPerDay = this.amazonProvider.limitPerDay; this.limitPerDayMessage = "Purchase Amount is limited to " + limitPerDay + " " + this.currency + " per day"; // TODO: translate if (this.amount > this.amazonProvider.limitPerDay) { this.showErrorAndBack(null, this.limitPerDayMessage); return; } this.network = this.amazonProvider.getNetwork(); this.wallets = this.profileProvider.getWallets({ onlyComplete: true, network: this.network, hasFunds: true, }); if (_.isEmpty(this.wallets)) { this.showErrorAndBack(null, this.translate.instant('No wallets available')); return; } this.showWallets(); // Show wallet selector } private checkFeeHigh(amount: number, fee: number) { let per = fee / (amount + fee) * 100; if (per > this.FEE_TOO_HIGH_LIMIT_PER) { let feeWarningModal = this.modalCtrl.create(FeeWarningPage, {}, { showBackdrop: false, enableBackdropDismiss: false }); feeWarningModal.present(); } } public openExternalLink(url: string) { this.externalLinkProvider.open(url); } private _resetValues() { this.totalAmountStr = this.invoiceFee = this.networkFee = this.totalAmount = this.wallet = null; this.createdTx = this.message = this.invoiceId = null; } private showErrorAndBack(title: string, msg: any) { title = title ? title : this.translate.instant('Error'); this.logger.error(msg); msg = (msg && msg.errors) ? msg.errors[0].message : msg; this.popupProvider.ionicAlert(title, msg).then(() => { this.navCtrl.pop(); }); } private showError = function (title: string, msg: any): Promise<any> { return new Promise((resolve, reject) => { title = title || this.translate.instant('Error'); this.logger.error(msg); msg = (msg && msg.errors) ? msg.errors[0].message : msg; this.popupProvider.ionicAlert(title, msg).then(() => { return resolve(); }); }); } private publishAndSign(wallet: any, txp: any): Promise<any> { return new Promise((resolve, reject) => { if (!wallet.canSign() && !wallet.isPrivKeyExternal()) { let err = this.translate.instant('No signing proposal: No private key'); this.logger.info(err); return reject(err); } this.walletProvider.publishAndSign(wallet, txp).then((txp: any) => { this.onGoingProcessProvider.clear(); return resolve(txp); }).catch((err: any) => { this.onGoingProcessProvider.clear(); return reject(err); }); }); } private satToFiat(coin: string, sat: number): Promise<any> { return new Promise((resolve, reject) => { this.txFormatProvider.toFiat(coin, sat, this.currencyIsoCode).then((value: string) => { return resolve(value); }); }); } private setTotalAmount(wallet: any, amountSat: number, invoiceFeeSat: number, networkFeeSat: number) { this.satToFiat(wallet.coin, amountSat).then((a: string) => { this.amount = Number(a); this.satToFiat(wallet.coin, invoiceFeeSat).then((i: string) => { this.invoiceFee = Number(i); this.satToFiat(wallet.coin, networkFeeSat).then((n: string) => { this.networkFee = Number(n); this.totalAmount = this.amount + this.invoiceFee + this.networkFee; }); }); }); } private isCryptoCurrencySupported(wallet: any, invoice: any) { let COIN = wallet.coin.toUpperCase(); if (!invoice['supportedTransactionCurrencies'][COIN]) return false; return invoice['supportedTransactionCurrencies'][COIN].enabled; } private createInvoice(data: any): Promise<any> { return new Promise((resolve, reject) => { this.amazonProvider.createBitPayInvoice(data, (err: any, dataInvoice: any) => { if (err) { let err_title = this.translate.instant('Error creating the invoice'); let err_msg; if (err && err.message && err.message.match(/suspended/i)) { err_title = this.translate.instant('Service not available'); err_msg = this.translate.instant('Amazon.com is not available at this moment. Please try back later.'); } else if (err && err.message) { err_msg = err.message; } else { err_msg = this.translate.instant('Could not access to Amazon.com'); }; return reject({ title: err_title, message: err_msg }); } let accessKey = dataInvoice ? dataInvoice.accessKey : null; if (!accessKey) { return reject({ message: this.translate.instant('No access key defined') }); } this.amazonProvider.getBitPayInvoice(dataInvoice.invoiceId, (err: any, invoice: any) => { if (err) { return reject({ message: this.translate.instant('Could not get the invoice') }); } return resolve({ invoice, accessKey }); }); }); }); } private createTx(wallet: any, invoice: any, message: string): Promise<any> { let COIN = wallet.coin.toUpperCase(); return new Promise((resolve, reject) => { let payProUrl = (invoice && invoice.paymentCodes) ? invoice.paymentCodes[COIN].BIP73 : null; if (!payProUrl) { return reject({ title: this.translate.instant('Error in Payment Protocol'), message: this.translate.instant('Invalid URL') }); } let outputs = []; let toAddress = invoice.addresses[COIN]; let amountSat = invoice.paymentTotals[COIN]; outputs.push({ 'toAddress': toAddress, 'amount': amountSat, 'message': message }); let txp = { toAddress, amount: amountSat, outputs, message, payProUrl, excludeUnconfirmedUtxos: this.configWallet.spendUnconfirmed ? false : true, feeLevel: this.configWallet.settings.feeLevel ? this.configWallet.settings.feeLevel : 'normal' }; txp['origToAddress'] = txp.toAddress; if (wallet.coin && wallet.coin == 'bch') { // Use legacy address txp.toAddress = this.bitcoreCash.Address(txp.toAddress).toString(); txp.outputs[0].toAddress = txp.toAddress; } this.walletProvider.createTx(wallet, txp).then((ctxp: any) => { return resolve(ctxp); }).catch((err: any) => { return reject({ title: this.translate.instant('Could not create transaction'), message: this.bwcErrorProvider.msg(err) }); }); }); } private checkTransaction = _.throttle((count: number, dataSrc: any) => { this.amazonProvider.createGiftCard(dataSrc, (err, giftCard) => { this.logger.debug("creating gift card " + count); if (err) { giftCard = giftCard || {}; giftCard['status'] = 'FAILURE'; } let now = moment().unix() * 1000; let newData = giftCard; newData.invoiceId = dataSrc.invoiceId; newData.accessKey = dataSrc.accessKey; newData.invoiceUrl = dataSrc.invoiceUrl; newData.amount = dataSrc.amount; newData.date = dataSrc.invoiceTime || now; newData.uuid = dataSrc.uuid; if (newData.status == 'expired') { this.amazonProvider.savePendingGiftCard(newData, { remove: true }, (err: any) => { this.logger.error(err); this.onGoingProcessProvider.clear(); this.showError(null, this.translate.instant('Gift card expired')); }); return; } if (giftCard.status == 'PENDING' && count < 3) { this.logger.debug("Waiting for payment confirmation"); this.amazonProvider.savePendingGiftCard(newData, null, (err: any) => { this.logger.debug("Saving gift card with status: " + newData.status); }); this.checkTransaction(count + 1, dataSrc); return; } this.amazonProvider.savePendingGiftCard(newData, null, (err: any) => { this.onGoingProcessProvider.clear(); this.logger.debug("Saved new gift card with status: " + newData.status); this.amazonGiftCard = newData; this.openFinishModal(); }); }); }, 15000, { 'leading': true }); private initialize(wallet: any): void { let COIN = wallet.coin.toUpperCase(); let email = this.emailNotificationsProvider.getEmailIfEnabled(); let parsedAmount = this.txFormatProvider.parseAmount(wallet.coin, this.amount, this.currency); this.currencyIsoCode = parsedAmount.currency; this.amountUnitStr = parsedAmount.amountUnitStr; let dataSrc = { amount: parsedAmount.amount, currency: parsedAmount.currency, uuid: wallet.id, email, buyerSelectedTransactionCurrency: COIN }; this.onGoingProcessProvider.set('loadingTxInfo'); this.createInvoice(dataSrc).then((data: any) => { let invoice = data.invoice; let accessKey = data.accessKey; if (!this.isCryptoCurrencySupported(wallet, invoice)) { this.onGoingProcessProvider.clear(); let msg = this.translate.instant('Purchases with this cryptocurrency is not enabled'); this.showErrorAndBack(null, msg); return; } // Sometimes API does not return this element; invoice['minerFees'][COIN]['totalFee'] = invoice.minerFees[COIN].totalFee || 0; let invoiceFeeSat = invoice.minerFees[COIN].totalFee; this.message = this.amountUnitStr + " for Amazon.com Gift Card"; // TODO: translate this.createTx(wallet, invoice, this.message).then((ctxp: any) => { this.onGoingProcessProvider.clear(); // Save in memory this.createdTx = ctxp; this.invoiceId = invoice.id; this.createdTx.giftData = { currency: dataSrc.currency, amount: dataSrc.amount, uuid: dataSrc.uuid, accessKey, invoiceId: invoice.id, invoiceUrl: invoice.url, invoiceTime: invoice.invoiceTime }; this.totalAmountStr = this.txFormatProvider.formatAmountStr(wallet.coin, ctxp.amount); // Warn: fee too high this.checkFeeHigh(Number(parsedAmount.amountSat), Number(invoiceFeeSat) + Number(ctxp.fee)); this.setTotalAmount(wallet, parsedAmount.amountSat, invoiceFeeSat, ctxp.fee); }).catch((err: any) => { this.onGoingProcessProvider.clear(); this._resetValues(); this.showError(err.title, err.message); return; }); }).catch((err: any) => { this.onGoingProcessProvider.clear(); this.showErrorAndBack(err.title, err.message); return; }); } public buyConfirm() { if (!this.createdTx) { this.showError(null, this.translate.instant('Transaction has not been created')); return; } let title = this.translate.instant('Confirm'); let okText = this.translate.instant('OK'); let cancelText = this.translate.instant('Cancel'); this.popupProvider.ionicConfirm(title, this.message, okText, cancelText).then((ok) => { if (!ok) { return; } this.publishAndSign(this.wallet, this.createdTx).then((txSent) => { this.onGoingProcessProvider.set('buyingGiftCard'); this.checkTransaction(1, this.createdTx.giftData); }).catch((err: any) => { this._resetValues(); this.showError(this.translate.instant('Could not send transaction'), err); return; }); }); } public onWalletSelect(wallet: any): void { this.wallet = wallet; this.initialize(wallet); } public showWallets(): void { let id = this.wallet ? this.wallet.credentials.walletId : null; this.events.publish('showWalletsSelectorEvent', this.wallets, id, 'Buy from'); this.events.subscribe('selectWalletEvent', (wallet: any) => { if (!_.isEmpty(wallet)) this.onWalletSelect(wallet); this.events.unsubscribe('selectWalletEvent'); }); } private openFinishModal(): void { let finishComment: string; let cssClass: string; if (this.amazonGiftCard.status == 'FAILURE') { finishComment = 'Your purchase could not be completed'; cssClass = 'danger'; } if (this.amazonGiftCard.status == 'PENDING') { finishComment = 'Your purchase was added to the list of pending'; cssClass = 'warning'; } if (this.amazonGiftCard.status == 'SUCCESS') { finishComment = 'Bought ' + this.amountUnitStr; } if (this.amazonGiftCard.status == 'SUCCESS') { finishComment = 'Gift card generated and ready to use.'; } let finishText = ''; let modal = this.modalCtrl.create(FinishModalPage, { finishText, finishComment, cssClass }, { showBackdrop: true, enableBackdropDismiss: false }); modal.present(); modal.onDidDismiss(() => { this.navCtrl.popToRoot({ animate: false }); this.navCtrl.push(AmazonPage, { invoiceId: this.invoiceId }); }); } }