From d5bba630a35fff72b11273fb5e62c2208f9e1f5b Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Mon, 14 Aug 2017 04:16:12 +0200 Subject: implement returning coins to user's account --- src/webex/messages.ts | 8 + src/webex/pages/confirm-create-reserve.html | 30 +-- src/webex/pages/popup.tsx | 44 +++-- src/webex/pages/return-coins.html | 19 ++ src/webex/pages/return-coins.tsx | 271 ++++++++++++++++++++++++++++ src/webex/style/wallet.css | 16 ++ src/webex/wxApi.ts | 27 ++- src/webex/wxBackend.ts | 17 +- 8 files changed, 381 insertions(+), 51 deletions(-) create mode 100644 src/webex/pages/return-coins.html create mode 100644 src/webex/pages/return-coins.tsx (limited to 'src/webex') diff --git a/src/webex/messages.ts b/src/webex/messages.ts index bf9ca00b0..d7ecd06a1 100644 --- a/src/webex/messages.ts +++ b/src/webex/messages.ts @@ -168,6 +168,14 @@ export interface MessageMap { request: { }; response: void; }; + "get-sender-wire-infos": { + request: { }; + response: void; + }; + "return-coins": { + request: { }; + response: void; + }; } /** diff --git a/src/webex/pages/confirm-create-reserve.html b/src/webex/pages/confirm-create-reserve.html index 493a6fb5f..17daf4dde 100644 --- a/src/webex/pages/confirm-create-reserve.html +++ b/src/webex/pages/confirm-create-reserve.html @@ -6,40 +6,12 @@ Taler Wallet: Select Taler Provider - + - - diff --git a/src/webex/pages/popup.tsx b/src/webex/pages/popup.tsx index f1f0353ad..7d12d365e 100644 --- a/src/webex/pages/popup.tsx +++ b/src/webex/pages/popup.tsx @@ -219,22 +219,26 @@ class WalletBalanceView extends React.Component { this.unmount = true; } - updateBalance() { - chrome.runtime.sendMessage({type: "balances"}, (resp) => { + async updateBalance() { + let balance: WalletBalance; + try { + balance = await wxApi.getBalance(); + } catch (e) { if (this.unmount) { return; } - if (resp.error) { - this.gotError = true; - console.error("could not retrieve balances", resp); - this.setState({}); - return; - } - this.gotError = false; - console.log("got wallet", resp); - this.balance = resp; + this.gotError = true; + console.error("could not retrieve balances", e); this.setState({}); - }); + return; + } + if (this.unmount) { + return; + } + this.gotError = false; + console.log("got balance", balance); + this.balance = balance; + this.setState({}); } renderEmpty(): JSX.Element { @@ -308,8 +312,8 @@ class WalletBalanceView extends React.Component { } console.log(wallet); let paybackAvailable = false; - const listing = Object.keys(wallet).map((key) => { - const entry: WalletBalanceEntry = wallet[key]; + const listing = Object.keys(wallet.byCurrency).map((key) => { + const entry: WalletBalanceEntry = wallet.byCurrency[key]; if (entry.paybackAmount.value !== 0 || entry.paybackAmount.fraction !== 0) { paybackAvailable = true; } @@ -321,14 +325,16 @@ class WalletBalanceView extends React.Component {

); }); - const link = chrome.extension.getURL("/src/webex/pages/auditors.html"); - const linkElem = Trusted Auditors and Exchanges; - const paybackLinkElem = Trusted Auditors and Exchanges; + const makeLink = (page: string, name: string) => { + const url = chrome.extension.getURL(`/src/webex/pages/${page}`); + return
{name}
; + }; return (
{listing.length > 0 ? listing : this.renderEmpty()} - {paybackAvailable && paybackLinkElem} - {linkElem} + {paybackAvailable && makeLink("payback", i18n.str`Payback`)} + {makeLink("return-coins.html#dissolve", i18n.str`Return Electronic Cash to Bank Account`)} + {makeLink("auditors.html", i18n.str`Manage Trusted Auditors and Exchanges`)}
); } diff --git a/src/webex/pages/return-coins.html b/src/webex/pages/return-coins.html new file mode 100644 index 000000000..c0ab218d2 --- /dev/null +++ b/src/webex/pages/return-coins.html @@ -0,0 +1,19 @@ + + + + + + Taler Wallet: Return Coins to Bank Account + + + + + + + + + + +
+ + diff --git a/src/webex/pages/return-coins.tsx b/src/webex/pages/return-coins.tsx new file mode 100644 index 000000000..1fdadd2e9 --- /dev/null +++ b/src/webex/pages/return-coins.tsx @@ -0,0 +1,271 @@ +/* + This file is part of TALER + (C) 2017 Inria + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, see + */ + +/** + * View and edit auditors. + * + * @author Florian Dold + */ + + +/** + * Imports. + */ + +import { + AmountJson, + Amounts, + SenderWireInfos, + WalletBalance, +} from "../../types"; + +import * as i18n from "../../i18n"; + +import * as wire from "../../wire"; + +import { + getBalance, + getSenderWireInfos, + returnCoins, +} from "../wxApi"; + +import { renderAmount } from "../renderHtml"; + +import * as React from "react"; +import * as ReactDOM from "react-dom"; + +interface ReturnSelectionItemProps extends ReturnSelectionListProps { + exchangeUrl: string; + senderWireInfos: SenderWireInfos; +} + +interface ReturnSelectionItemState { + selectedValue: string; + supportedWires: object[]; + selectedWire: string; + currency: string; +} + +class ReturnSelectionItem extends React.Component { + constructor(props: ReturnSelectionItemProps) { + super(props); + const exchange = this.props.exchangeUrl; + const wireTypes = this.props.senderWireInfos.exchangeWireTypes; + const supportedWires = this.props.senderWireInfos.senderWires.filter((x) => { + return wireTypes[exchange] && wireTypes[exchange].indexOf((x as any).type) >= 0; + }); + this.state = { + currency: props.balance.byExchange[props.exchangeUrl].available.currency, + selectedValue: Amounts.toFloat(props.balance.byExchange[props.exchangeUrl].available).toString(), + selectedWire: "", + supportedWires, + }; + } + render(): JSX.Element { + const exchange = this.props.exchangeUrl; + const byExchange = this.props.balance.byExchange; + const wireTypes = this.props.senderWireInfos.exchangeWireTypes; + return ( +
+

Exchange {exchange}

+

Available amount: {renderAmount(byExchange[exchange].available)}

+

Supported wire methods: {wireTypes[exchange].length ? wireTypes[exchange].join(", ") : "none"}

+

Wire {""} + this.setState({selectedValue: evt.target.value})} + style={{textAlign: "center"}} + /> {this.props.balance.byExchange[exchange].available.currency} {""} + to account {""} + . +

+ {this.state.selectedWire + ? + : null} +
+ ); + } + + select() { + let val: number; + let selectedWire: number; + try { + val = Number.parseFloat(this.state.selectedValue); + selectedWire = Number.parseInt(this.state.selectedWire); + } catch (e) { + console.error(e); + return; + } + this.props.selectDetail({ + amount: Amounts.fromFloat(val, this.state.currency), + exchange: this.props.exchangeUrl, + senderWire: this.state.supportedWires[selectedWire], + }); + } +} + +interface ReturnSelectionListProps { + balance: WalletBalance; + senderWireInfos: SenderWireInfos; + selectDetail(d: SelectedDetail): void; +} + +class ReturnSelectionList extends React.Component { + render(): JSX.Element { + const byExchange = this.props.balance.byExchange; + const exchanges = Object.keys(byExchange); + if (!exchanges.length) { + return

Currently no funds available to transfer.

; + } + return ( +
+ {exchanges.map((e) => )} +
+ ); + } +} + +interface SelectedDetail { + amount: AmountJson; + senderWire: any; + exchange: string; +} + + +interface ReturnConfirmationProps { + detail: SelectedDetail; + cancel(): void; + confirm(): void; +} + +class ReturnConfirmation extends React.Component { + render() { + return ( +
+

Please confirm if you want to transmit {renderAmount(this.props.detail.amount)} at {""} + {this.props.detail.exchange} to account {""} + {wire.summarizeWire(this.props.detail.senderWire)}. +

+ + +
+ ); + } +} + +interface ReturnCoinsState { + balance: WalletBalance | undefined; + senderWireInfos: SenderWireInfos | undefined; + selectedReturn: SelectedDetail | undefined; + /** + * Last confirmed detail, so we can show a nice box. + */ + lastConfirmedDetail: SelectedDetail | undefined; +} + +class ReturnCoins extends React.Component { + constructor() { + super(); + const port = chrome.runtime.connect(); + port.onMessage.addListener((msg: any) => { + if (msg.notify) { + console.log("got notified"); + this.update(); + } + }); + this.update(); + this.state = {} as any; + } + + async update() { + const balance = await getBalance(); + const senderWireInfos = await getSenderWireInfos(); + console.log("got swi", senderWireInfos); + console.log("got bal", balance); + this.setState({ balance, senderWireInfos }); + } + + selectDetail(d: SelectedDetail) { + this.setState({selectedReturn: d}); + } + + async confirm() { + const selectedReturn = this.state.selectedReturn; + if (!selectedReturn) { + return; + } + await returnCoins(selectedReturn); + await this.update(); + this.setState({selectedReturn: undefined, lastConfirmedDetail: selectedReturn}); + } + + async cancel() { + this.setState({selectedReturn: undefined, lastConfirmedDetail: undefined}); + } + + render() { + const balance = this.state.balance; + const senderWireInfos = this.state.senderWireInfos; + if (!balance || !senderWireInfos) { + return ...; + } + if (this.state.selectedReturn) { + return ( +
+ this.cancel()} + confirm={() => this.confirm()} + /> +
+ ); + } + return ( +
+

Wire electronic cash back to own bank account

+

You can send coins back into your own bank account. Note that + you're acting as a merchant when doing this, and thus the same fees apply.

+ {this.state.lastConfirmedDetail + ?

Transfer of {renderAmount(this.state.lastConfirmedDetail.amount)} successfully initiated.

+ : null} + this.selectDetail(d)} + balance={balance} + senderWireInfos={senderWireInfos} /> +
+ ); + } +} + + +function main() { + ReactDOM.render(, document.getElementById("container")!); +} + +document.addEventListener("DOMContentLoaded", main); diff --git a/src/webex/style/wallet.css b/src/webex/style/wallet.css index 5773eb396..61dd611e9 100644 --- a/src/webex/style/wallet.css +++ b/src/webex/style/wallet.css @@ -1,3 +1,8 @@ +body { + font-size: 100%; + overflow-y: scroll; +} + #main { border: solid 1px black; border-radius: 10px; @@ -235,3 +240,14 @@ a.actionLink { font-weight: bold; background: #00FA9A; } + + +a.opener { + color: black; +} +.opener-open::before { + content: "\25bc" +} +.opener-collapsed::before { + content: "\25b6 " +} diff --git a/src/webex/wxApi.ts b/src/webex/wxApi.ts index 9d8ba4d1d..1371e27e4 100644 --- a/src/webex/wxApi.ts +++ b/src/webex/wxApi.ts @@ -31,9 +31,11 @@ import { DenominationRecord, ExchangeRecord, PreCoinRecord, + QueryPaymentResult, ReserveCreationInfo, ReserveRecord, - QueryPaymentResult, + SenderWireInfos, + WalletBalance, } from "../types"; import { MessageType, MessageMap } from "./messages"; @@ -296,3 +298,26 @@ export function createReserve(args: { amount: AmountJson, exchange: string, send export function resetDb(): Promise { return callBackend("reset-db", { }); } + +/** + * Get balances for all currencies/exchanges. + */ +export function getBalance(): Promise { + return callBackend("balances", { }); +} + + +/** + * Get possible sender wire infos for getting money + * wired from an exchange. + */ +export function getSenderWireInfos(): Promise { + return callBackend("get-sender-wire-infos", { }); +} + +/** + * Return coins to a bank account. + */ +export function returnCoins(args: { amount: AmountJson, exchange: string, senderWire: object }): Promise { + return callBackend("return-coins", args); +} diff --git a/src/webex/wxBackend.ts b/src/webex/wxBackend.ts index 261477386..974bcb3c2 100644 --- a/src/webex/wxBackend.ts +++ b/src/webex/wxBackend.ts @@ -32,12 +32,13 @@ import { } from "../query"; import { AmountJson, + ConfirmReserveRequest, + CreateReserveRequest, Notifier, ProposalRecord, + ReturnCoinsRequest, } from "../types"; import { - ConfirmReserveRequest, - CreateReserveRequest, Stores, WALLET_DB_VERSION, Wallet, @@ -278,6 +279,18 @@ function handleMessage(sender: MessageSender, } return needsWallet().paymentSucceeded(contractTermsHash, merchantSig); } + case "get-sender-wire-infos": { + return needsWallet().getSenderWireInfos(); + } + case "return-coins": { + const d = { + amount: detail.amount, + exchange: detail.exchange, + senderWire: detail.senderWire, + }; + const req = ReturnCoinsRequest.checked(d); + return needsWallet().returnCoins(req); + } case "check-upgrade": { let dbResetRequired = false; if (!currentWallet) { -- cgit v1.2.3