aboutsummaryrefslogtreecommitdiff
path: root/src/webex
diff options
context:
space:
mode:
authorFlorian Dold <florian.dold@gmail.com>2017-08-14 04:16:12 +0200
committerFlorian Dold <florian.dold@gmail.com>2017-08-14 04:16:12 +0200
commitd5bba630a35fff72b11273fb5e62c2208f9e1f5b (patch)
tree3397a580d663161be1ba7c46df368ac10d566cdc /src/webex
parent419a05e801da688a1d0917a6bf16d468e6362a3d (diff)
downloadwallet-core-d5bba630a35fff72b11273fb5e62c2208f9e1f5b.tar.xz
implement returning coins to user's account
Diffstat (limited to 'src/webex')
-rw-r--r--src/webex/messages.ts8
-rw-r--r--src/webex/pages/confirm-create-reserve.html30
-rw-r--r--src/webex/pages/popup.tsx44
-rw-r--r--src/webex/pages/return-coins.html19
-rw-r--r--src/webex/pages/return-coins.tsx271
-rw-r--r--src/webex/style/wallet.css16
-rw-r--r--src/webex/wxApi.ts27
-rw-r--r--src/webex/wxBackend.ts17
8 files changed, 381 insertions, 51 deletions
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 @@
<title>Taler Wallet: Select Taler Provider</title>
<link rel="icon" href="/img/icon.png">
- <link rel="stylesheet" type="text/css" href="../style/wallet.css">
<link rel="stylesheet" type="text/css" href="../style/pure.css">
+ <link rel="stylesheet" type="text/css" href="../style/wallet.css">
<script src="/dist/page-common-bundle.js"></script>
<script src="/dist/confirm-create-reserve-bundle.js"></script>
- <style>
- body {
- font-size: 100%;
- overflow-y: scroll;
- }
- .button-success {
- background: rgb(28, 184, 65); /* this is a green */
- color: white;
- border-radius: 4px;
- text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2);
- }
- .button-secondary {
- background: rgb(66, 184, 221); /* this is a light blue */
- color: white;
- border-radius: 4px;
- text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2);
- }
- a.opener {
- color: black;
- }
- .opener-open::before {
- content: "\25bc"
- }
- .opener-collapsed::before {
- content: "\25b6 "
- }
- </style>
-
</head>
<body>
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<any, any> {
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<any, any> {
}
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<any, any> {
</p>
);
});
- const link = chrome.extension.getURL("/src/webex/pages/auditors.html");
- const linkElem = <a className="actionLink" href={link} target="_blank">Trusted Auditors and Exchanges</a>;
- const paybackLinkElem = <a className="actionLink" href={link} target="_blank">Trusted Auditors and Exchanges</a>;
+ const makeLink = (page: string, name: string) => {
+ const url = chrome.extension.getURL(`/src/webex/pages/${page}`);
+ return <div><a className="actionLink" href={url} target="_blank">{name}</a></div>;
+ };
return (
<div>
{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`)}
</div>
);
}
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 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <meta charset="UTF-8">
+ <title>Taler Wallet: Return Coins to Bank Account</title>
+
+ <link rel="stylesheet" type="text/css" href="../style/pure.css">
+ <link rel="stylesheet" type="text/css" href="../style/wallet.css">
+
+ <link rel="icon" href="/img/icon.png">
+
+ <script src="/dist/page-common-bundle.js"></script>
+ <script src="/dist/return-coins-bundle.js"></script>
+
+ <body>
+ <div id="container"></div>
+ </body>
+</html>
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 <http://www.gnu.org/licenses/>
+ */
+
+/**
+ * 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<ReturnSelectionItemProps, ReturnSelectionItemState> {
+ 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 (
+ <div key={exchange}>
+ <h2>Exchange {exchange}</h2>
+ <p>Available amount: {renderAmount(byExchange[exchange].available)}</p>
+ <p>Supported wire methods: {wireTypes[exchange].length ? wireTypes[exchange].join(", ") : "none"}</p>
+ <p>Wire {""}
+ <input
+ type="text"
+ size={this.state.selectedValue.length || 1}
+ value={this.state.selectedValue}
+ onChange={(evt) => this.setState({selectedValue: evt.target.value})}
+ style={{textAlign: "center"}}
+ /> {this.props.balance.byExchange[exchange].available.currency} {""}
+ to account {""}
+ <select value={this.state.selectedWire} onChange={(evt) => this.setState({selectedWire: evt.target.value})}>
+ <option style={{display: "none"}}>Select account</option>
+ {this.state.supportedWires.map((w, n) =>
+ <option value={n.toString()} key={JSON.stringify(w)}>{n+1}: {wire.summarizeWire(w)}</option>
+ )}
+ </select>.
+ </p>
+ {this.state.selectedWire
+ ? <button className="pure-button button-success" onClick={() => this.select()}>
+ {i18n.str`Wire to bank account`}
+ </button>
+ : null}
+ </div>
+ );
+ }
+
+ 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<ReturnSelectionListProps, {}> {
+ render(): JSX.Element {
+ const byExchange = this.props.balance.byExchange;
+ const exchanges = Object.keys(byExchange);
+ if (!exchanges.length) {
+ return <p className="errorbox">Currently no funds available to transfer.</p>;
+ }
+ return (
+ <div>
+ {exchanges.map((e) => <ReturnSelectionItem key={e} exchangeUrl={e} {...this.props} />)}
+ </div>
+ );
+ }
+}
+
+interface SelectedDetail {
+ amount: AmountJson;
+ senderWire: any;
+ exchange: string;
+}
+
+
+interface ReturnConfirmationProps {
+ detail: SelectedDetail;
+ cancel(): void;
+ confirm(): void;
+}
+
+class ReturnConfirmation extends React.Component<ReturnConfirmationProps, {}> {
+ render() {
+ return (
+ <div>
+ <p>Please confirm if you want to transmit <strong>{renderAmount(this.props.detail.amount)}</strong> at {""}
+ {this.props.detail.exchange} to account {""}
+ <strong style={{whiteSpace: "nowrap"}}>{wire.summarizeWire(this.props.detail.senderWire)}</strong>.
+ </p>
+ <button className="pure-button button-success" onClick={() => this.props.confirm()}>
+ {i18n.str`Confirm`}
+ </button>
+ <button className="pure-button" onClick={() => this.props.cancel()}>
+ {i18n.str`Cancel`}
+ </button>
+ </div>
+ );
+ }
+}
+
+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<any, ReturnCoinsState> {
+ 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 <span>...</span>;
+ }
+ if (this.state.selectedReturn) {
+ return (
+ <div id="main">
+ <ReturnConfirmation
+ detail={this.state.selectedReturn}
+ cancel={() => this.cancel()}
+ confirm={() => this.confirm()}
+ />
+ </div>
+ );
+ }
+ return (
+ <div id="main">
+ <h1>Wire electronic cash back to own bank account</h1>
+ <p>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.</p>
+ {this.state.lastConfirmedDetail
+ ? <p className="okaybox">Transfer of {renderAmount(this.state.lastConfirmedDetail.amount)} successfully initiated.</p>
+ : null}
+ <ReturnSelectionList
+ selectDetail={(d) => this.selectDetail(d)}
+ balance={balance}
+ senderWireInfos={senderWireInfos} />
+ </div>
+ );
+ }
+}
+
+
+function main() {
+ ReactDOM.render(<ReturnCoins />, 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<void> {
return callBackend("reset-db", { });
}
+
+/**
+ * Get balances for all currencies/exchanges.
+ */
+export function getBalance(): Promise<WalletBalance> {
+ return callBackend("balances", { });
+}
+
+
+/**
+ * Get possible sender wire infos for getting money
+ * wired from an exchange.
+ */
+export function getSenderWireInfos(): Promise<SenderWireInfos> {
+ return callBackend("get-sender-wire-infos", { });
+}
+
+/**
+ * Return coins to a bank account.
+ */
+export function returnCoins(args: { amount: AmountJson, exchange: string, senderWire: object }): Promise<void> {
+ 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) {