diff options
Diffstat (limited to 'src/webex/pages')
-rw-r--r-- | src/webex/pages/confirm-create-reserve.tsx | 47 | ||||
-rw-r--r-- | src/webex/pages/error.tsx | 97 | ||||
-rw-r--r-- | src/webex/pages/refund.html | 18 | ||||
-rw-r--r-- | src/webex/pages/refund.tsx | 138 |
4 files changed, 241 insertions, 59 deletions
diff --git a/src/webex/pages/confirm-create-reserve.tsx b/src/webex/pages/confirm-create-reserve.tsx index 4e3b6748f..7d543860f 100644 --- a/src/webex/pages/confirm-create-reserve.tsx +++ b/src/webex/pages/confirm-create-reserve.tsx @@ -41,7 +41,7 @@ import { getReserveCreationInfo, } from "../wxApi"; -import {renderAmount} from "../renderHtml"; +import {Collapsible, renderAmount} from "../renderHtml"; import * as React from "react"; import * as ReactDOM from "react-dom"; @@ -80,40 +80,6 @@ class EventTrigger { } -interface CollapsibleState { - collapsed: boolean; -} - -interface CollapsibleProps { - initiallyCollapsed: boolean; - title: string; -} - -class Collapsible extends React.Component<CollapsibleProps, CollapsibleState> { - constructor(props: CollapsibleProps) { - super(props); - this.state = { collapsed: props.initiallyCollapsed }; - } - render() { - const doOpen = (e: any) => { - this.setState({collapsed: false}); - e.preventDefault(); - }; - const doClose = (e: any) => { - this.setState({collapsed: true}); - e.preventDefault(); - }; - if (this.state.collapsed) { - return <h2><a className="opener opener-collapsed" href="#" onClick={doOpen}>{this.props.title}</a></h2>; - } - return ( - <div> - <h2><a className="opener opener-open" href="#" onClick={doClose}>{this.props.title}</a></h2> - {this.props.children} - </div> - ); - } -} function renderAuditorDetails(rci: ReserveCreationInfo|null) { console.log("rci", rci); @@ -405,7 +371,7 @@ class ExchangeSelection extends ImplicitStateComponent<ExchangeSelectionProps> { if (this.statusString()) { return ( <p> - <strong style={{color: "red"}}>{i18n.str`A problem occured, see below. ${this.statusString()}`}</strong> + <strong style={{color: "red"}}>{this.statusString()}</strong> </p> ); } @@ -549,12 +515,9 @@ class ExchangeSelection extends ImplicitStateComponent<ExchangeSelectionProps> { console.dir(r); } catch (e) { console.log("get exchange info rejected", e); - if (e.hasOwnProperty("httpStatus")) { - this.statusString(`Error: request failed with status ${e.httpStatus}`); - } else if (e.hasOwnProperty("errorResponse")) { - const resp = e.errorResponse; - this.statusString(`Error: ${resp.error} (${resp.hint})`); - } + this.statusString(`Error: ${e.message}`); + // Re-try every 5 seconds as long as there is a problem + setTimeout(() => this.statusString() ? this.forceReserveUpdate() : undefined, 5000); } } diff --git a/src/webex/pages/error.tsx b/src/webex/pages/error.tsx index e86b6cf4c..2edef5e5b 100644 --- a/src/webex/pages/error.tsx +++ b/src/webex/pages/error.tsx @@ -22,40 +22,103 @@ * @author Florian Dold */ + import * as React from "react"; import * as ReactDOM from "react-dom"; import URI = require("urijs"); +import * as wxApi from "../wxApi"; + +import { Collapsible } from "../renderHtml"; + interface ErrorProps { - message: string; + report: any; } class ErrorView extends React.Component<ErrorProps, { }> { render(): JSX.Element { - return ( - <div> - An error occurred: {this.props.message} - </div> - ); + const report = this.props.report; + if (!report) { + return ( + <div id="main"> + <h1>Error Report Not Found</h1> + <p>This page is supposed to display an error reported by the GNU Taler wallet, + but the corresponding error report can't be found.</p> + <p>Maybe the error occured before the browser was restarted or the wallet was reloaded.</p> + </div> + ); + } + try { + switch (report.name) { + case "pay-post-failed": { + const summary = report.contractTerms.summary || report.contractTerms.order_id; + return ( + <div id="main"> + <h1>Failed to send payment</h1> + <p>Failed to send payment for <strong>{summary}</strong> to merchant <strong>{report.contractTerms.merchant.name}</strong>.</p> + <p>You can <a href={report.contractTerms.fulfillment_url}>retry</a> the payment. If this problem persists, + please contact the mechant with the error details below.</p> + <Collapsible initiallyCollapsed={true} title="Error Details"> + <pre> + {JSON.stringify(report, null, " ")} + </pre> + </Collapsible> + </div> + ); + } + default: + return ( + <div id="main"> + <h1>Unknown Error</h1> + The GNU Taler wallet reported an unknown error. Here are the details: + <pre> + {JSON.stringify(report, null, " ")} + </pre> + </div> + ); + } + } catch (e) { + return ( + <div id="main"> + <h1>Error</h1> + The GNU Taler wallet reported an error. Here are the details: + <pre> + {JSON.stringify(report, null, " ")} + </pre> + A detailed error report could not be generated: + <pre> + {e.toString()} + </pre> + </div> + ); + } } } async function main() { - try { - const url = new URI(document.location.href); - const query: any = URI.parseQuery(url.query()); + const url = new URI(document.location.href); + const query: any = URI.parseQuery(url.query()); - const message: string = query.message || "unknown error"; + const container = document.getElementById("container"); + if (!container) { + console.error("fatal: can't mount component, countainer missing"); + return; + } - ReactDOM.render(<ErrorView message={message} />, document.getElementById( - "container")!); + // report that we'll render, either looked up from the + // logging module or synthesized here for fixed/fatal errors + let report; - } catch (e) { - // TODO: provide more context information, maybe factor it out into a - // TODO:generic error reporting function or component. - document.body.innerText = `Fatal error: "${e.message}".`; - console.error(`got error "${e.message}"`, e); + const reportUid: string = query.reportUid; + if (!reportUid) { + report = { + name: "missing-error", + }; + } else { + report = await wxApi.getReport(reportUid); } + + ReactDOM.render(<ErrorView report={report} />, container); } document.addEventListener("DOMContentLoaded", () => main()); diff --git a/src/webex/pages/refund.html b/src/webex/pages/refund.html new file mode 100644 index 000000000..f97dc9d6c --- /dev/null +++ b/src/webex/pages/refund.html @@ -0,0 +1,18 @@ +<!DOCTYPE html> +<html> + +<head> + <meta charset="UTF-8"> + <title>Taler Wallet: Refund Status</title> + + <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/refund-bundle.js"></script> + + <body> + <div id="container"></div> + </body> +</html> diff --git a/src/webex/pages/refund.tsx b/src/webex/pages/refund.tsx new file mode 100644 index 000000000..b9506bf29 --- /dev/null +++ b/src/webex/pages/refund.tsx @@ -0,0 +1,138 @@ +/* + This file is part of TALER + (C) 2015-2016 GNUnet e.V. + + 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/> + */ + + +/** + * Page that shows refund status for purchases. + * + * @author Florian Dold + */ + + +import * as React from "react"; +import * as ReactDOM from "react-dom"; +import URI = require("urijs"); + +import * as wxApi from "../wxApi"; +import * as types from "../../types"; + +import { AmountDisplay } from "../renderHtml"; + +interface RefundStatusViewProps { + contractTermsHash: string; +} + +interface RefundStatusViewState { + purchase?: types.PurchaseRecord; + gotResult: boolean; +} + + +const RefundDetail = ({purchase}: {purchase: types.PurchaseRecord}) => { + const pendingKeys = Object.keys(purchase.refundsPending); + const doneKeys = Object.keys(purchase.refundsDone); + if (pendingKeys.length == 0 && doneKeys.length == 0) { + return <p>No refunds</p>; + } + + const currency = { ...purchase.refundsDone, ...purchase.refundsPending }[([...pendingKeys, ...doneKeys][0])].refund_amount.currency; + if (!currency) { + throw Error("invariant"); + } + + let amountPending = types.Amounts.getZero(currency); + let feesPending = types.Amounts.getZero(currency) + for (let k of pendingKeys) { + amountPending = types.Amounts.add(amountPending, purchase.refundsPending[k].refund_amount).amount; + feesPending = types.Amounts.add(feesPending, purchase.refundsPending[k].refund_fee).amount; + } + let amountDone = types.Amounts.getZero(currency); + let feesDone = types.Amounts.getZero(currency); + for (let k of doneKeys) { + amountDone = types.Amounts.add(amountDone, purchase.refundsDone[k].refund_amount).amount; + feesDone = types.Amounts.add(feesDone, purchase.refundsDone[k].refund_fee).amount; + } + + return ( + <div> + <p>Refund fully received: <AmountDisplay amount={amountDone} /> (refund fees: <AmountDisplay amount={feesDone} />)</p> + <p>Refund incoming: <AmountDisplay amount={amountPending} /> (refund fees: <AmountDisplay amount={feesPending} />)</p> + </div> + ); +}; + +class RefundStatusView extends React.Component<RefundStatusViewProps, RefundStatusViewState> { + + constructor(props: RefundStatusViewProps) { + super(props); + this.state = { gotResult: false }; + } + + componentDidMount() { + this.update(); + const port = chrome.runtime.connect(); + port.onMessage.addListener((msg: any) => { + if (msg.notify) { + console.log("got notified"); + this.update(); + } + }); + } + + render(): JSX.Element { + const purchase = this.state.purchase; + if (!purchase) { + if (this.state.gotResult) { + return <span>No purchase with contract terms hash {this.props.contractTermsHash} found</span>; + } else { + return <span>...</span>; + } + } + const merchantName = purchase.contractTerms.merchant.name || "(unknown)"; + const summary = purchase.contractTerms.summary || purchase.contractTerms.order_id; + return ( + <div id="main"> + <h1>Refund Status</h1> + <p>Status of purchase <strong>{summary}</strong> from merchant <strong>{merchantName}</strong> (order id {purchase.contractTerms.order_id}).</p> + <p>Total amount: <AmountDisplay amount={purchase.contractTerms.amount} /></p> + {purchase.finished ? <RefundDetail purchase={purchase} /> : <p>Purchase not completed.</p>} + </div> + ); + } + + async update() { + const purchase = await wxApi.getPurchase(this.props.contractTermsHash); + console.log("got purchase", purchase); + this.setState({ purchase, gotResult: true }); + } +} + + +async function main() { + const url = new URI(document.location.href); + const query: any = URI.parseQuery(url.query()); + + const container = document.getElementById("container"); + if (!container) { + console.error("fatal: can't mount component, countainer missing"); + return; + } + + const contractTermsHash = query.contractTermsHash || "(none)"; + ReactDOM.render(<RefundStatusView contractTermsHash={contractTermsHash} />, container); +} + +document.addEventListener("DOMContentLoaded", () => main()); |