diff options
author | Florian Dold <florian.dold@gmail.com> | 2019-08-30 17:27:59 +0200 |
---|---|---|
committer | Florian Dold <florian.dold@gmail.com> | 2019-08-30 17:27:59 +0200 |
commit | 5ec344290efd937fa82c0704bc7c204a0bf14c78 (patch) | |
tree | 7d9594180bbc7b5fa2b4a8dbe24272e7a82301f3 /src/webex | |
parent | defbf625bdef0f8a666b72b8ce99de5e01af6b91 (diff) |
support for tipping protocol changes
Diffstat (limited to 'src/webex')
-rw-r--r-- | src/webex/messages.ts | 6 | ||||
-rw-r--r-- | src/webex/pages/tip.tsx | 203 | ||||
-rw-r--r-- | src/webex/wxApi.ts | 11 | ||||
-rw-r--r-- | src/webex/wxBackend.ts | 11 |
4 files changed, 94 insertions, 137 deletions
diff --git a/src/webex/messages.ts b/src/webex/messages.ts index ca0e1c7e1..f1046d5c7 100644 --- a/src/webex/messages.ts +++ b/src/webex/messages.ts @@ -174,11 +174,11 @@ export interface MessageMap { response: AmountJson; }; "accept-tip": { - request: { tipToken: talerTypes.TipToken }; - response: walletTypes.TipStatus; + request: { talerTipUri: string }; + response: void; }; "get-tip-status": { - request: { tipToken: talerTypes.TipToken }; + request: { talerTipUri: string }; response: walletTypes.TipStatus; }; "clear-notification": { diff --git a/src/webex/pages/tip.tsx b/src/webex/pages/tip.tsx index c13120c43..a3f5c38c3 100644 --- a/src/webex/pages/tip.tsx +++ b/src/webex/pages/tip.tsx @@ -14,7 +14,6 @@ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ - /** * Page shown to the user to confirm creation * of a reserve, usually requested by the bank. @@ -28,152 +27,114 @@ import URI = require("urijs"); import * as i18n from "../../i18n"; -import { - acceptTip, - getReserveCreationInfo, - getTipStatus, -} from "../wxApi"; +import { acceptTip, getReserveCreationInfo, getTipStatus } from "../wxApi"; -import { - WithdrawDetailView, - renderAmount, -} from "../renderHtml"; +import { WithdrawDetailView, renderAmount } from "../renderHtml"; import * as Amounts from "../../amounts"; -import { TipToken } from "../../talerTypes"; -import { ReserveCreationInfo, TipStatus } from "../../walletTypes"; +import { useState, useEffect } from "react"; +import { TipStatus } from "../../walletTypes"; -interface TipDisplayProps { - tipToken: TipToken; +interface LoadingButtonProps { + loading: boolean; } -interface TipDisplayState { - tipStatus?: TipStatus; - rci?: ReserveCreationInfo; - working: boolean; - discarded: boolean; +function LoadingButton( + props: + & React.PropsWithChildren<LoadingButtonProps> + & React.DetailedHTMLProps< + React.ButtonHTMLAttributes<HTMLButtonElement>, + HTMLButtonElement + >, +) { + return ( + <button + className="pure-button pure-button-primary" + type="button" + {...props} + > + {props.loading ? <span><object className="svg-icon svg-baseline" data="/img/spinner-bars.svg" /></span> : null} + {props.children} + </button> + ); } -class TipDisplay extends React.Component<TipDisplayProps, TipDisplayState> { - constructor(props: TipDisplayProps) { - super(props); - this.state = { working: false, discarded: false }; - } - - async update() { - const tipStatus = await getTipStatus(this.props.tipToken); - this.setState({ tipStatus }); - const rci = await getReserveCreationInfo(tipStatus.exchangeUrl, tipStatus.amount); - this.setState({ rci }); - } - - componentDidMount() { - this.update(); - const port = chrome.runtime.connect(); - port.onMessage.addListener((msg: any) => { - if (msg.notify) { - console.log("got notified"); - this.update(); - } - }); - this.update(); - } - - renderExchangeInfo() { - const rci = this.state.rci; - if (!rci) { - return <p>Waiting for info about exchange ...</p>; - } - const totalCost = Amounts.add(rci.overhead, rci.withdrawFee).amount; - return ( - <div> - <p> - The tip is handled by the exchange <strong>{rci.exchangeInfo.baseUrl}</strong>.{" "} - The exchange provider will charge - {" "} - <strong>{renderAmount(totalCost)}</strong> - {" "}. - </p> - <WithdrawDetailView rci={rci} /> - </div> - ); +function TipDisplay(props: { talerTipUri: string }) { + const [tipStatus, setTipStatus] = useState<TipStatus | undefined>(undefined); + const [discarded, setDiscarded] = useState(false); + const [loading, setLoading] = useState(false); + const [finished, setFinished] = useState(false); + + useEffect(() => { + const doFetch = async () => { + const ts = await getTipStatus(props.talerTipUri); + setTipStatus(ts); + }; + doFetch(); + }, []); + + if (discarded) { + return <span>You've discarded the tip.</span>; } - accept() { - this.setState({ working: true}); - acceptTip(this.props.tipToken); + if (finished) { + return <span>Tip has been accepted!</span>; } - discard() { - this.setState({ discarded: true }); + if (!tipStatus) { + return <span>Loading ...</span>; } - render(): JSX.Element { - const ts = this.state.tipStatus; - if (!ts) { - return <p>Processing ...</p>; - } - - const renderAccepted = () => ( - <> - <p>You've accepted this tip! <a href={ts.nextUrl}>Go back to merchant</a></p> - {this.renderExchangeInfo()} - </> - ); - - const renderButtons = () => ( - <> + const discard = () => { + setDiscarded(true); + }; + + const accept = async () => { + setLoading(true); + await acceptTip(props.talerTipUri); + setFinished(true); + }; + + return ( + <div> + <h2>Tip Received!</h2> + <p> + You received a tip of <strong>{renderAmount(tipStatus.amount)}</strong>{" "} + from <span> </span> + <strong>{tipStatus.merchantOrigin}</strong>. + </p> + <p> + The tip is handled by the exchange{" "} + <strong>{tipStatus.exchangeUrl}</strong>. This exchange will charge fees + of <strong>{renderAmount(tipStatus.totalFees)}</strong> for this + operation. + </p> <form className="pure-form"> - <button - className="pure-button pure-button-primary" - type="button" - disabled={!(this.state.rci && this.state.tipStatus && this.state.tipStatus.tipRecord)} - onClick={() => this.accept()}> - { this.state.working - ? <span><object className="svg-icon svg-baseline" data="/img/spinner-bars.svg" /> </span> - : null } - Accept tip - </button> + <LoadingButton loading={loading} onClick={() => accept()}> + AcceptTip + </LoadingButton> {" "} - <button className="pure-button" type="button" onClick={() => this.discard()}> + <button className="pure-button" type="button" onClick={() => discard()}> Discard tip </button> </form> - { this.renderExchangeInfo() } - </> - ); - - const renderDiscarded = () => ( - <p>You've discarded this tip. <a href={ts.nextUrl}>Go back to merchant.</a></p> - ); - - return ( - <div> - <h2>Tip Received!</h2> - <p>You received a tip of <strong>{renderAmount(ts.amount)}</strong> from <span> </span> - <strong>{ts.merchantDomain}</strong>.</p> - { - this.state.discarded - ? renderDiscarded() - : ts.accepted - ? renderAccepted() - : renderButtons() - } - </div> - ); - } + </div> + ); } async function main() { try { const url = new URI(document.location.href); const query: any = URI.parseQuery(url.query()); + const talerTipUri = query.talerTipUri; + if (typeof talerTipUri !== "string") { + throw Error("talerTipUri must be a string"); + } - const tipToken = TipToken.checked(JSON.parse(query.tip_token)); - - ReactDOM.render(<TipDisplay tipToken={tipToken} />, - document.getElementById("container")!); - + ReactDOM.render( + <TipDisplay talerTipUri={talerTipUri} />, + document.getElementById("container")!, + ); } catch (e) { // TODO: provide more context information, maybe factor it out into a // TODO:generic error reporting function or component. diff --git a/src/webex/wxApi.ts b/src/webex/wxApi.ts index feabc7819..fd01aed3f 100644 --- a/src/webex/wxApi.ts +++ b/src/webex/wxApi.ts @@ -45,7 +45,6 @@ import { import { MerchantRefundPermission, - TipToken, } from "../talerTypes"; import { MessageMap, MessageType } from "./messages"; @@ -349,15 +348,15 @@ export function getFullRefundFees(args: { refundPermissions: MerchantRefundPermi /** * Get the status of processing a tip. */ -export function getTipStatus(tipToken: TipToken): Promise<TipStatus> { - return callBackend("get-tip-status", { tipToken }); +export function getTipStatus(talerTipUri: string): Promise<TipStatus> { + return callBackend("get-tip-status", { talerTipUri }); } /** * Mark a tip as accepted by the user. */ -export function acceptTip(tipToken: TipToken): Promise<TipStatus> { - return callBackend("accept-tip", { tipToken }); +export function acceptTip(talerTipUri: string): Promise<void> { + return callBackend("accept-tip", { talerTipUri }); } @@ -423,4 +422,4 @@ export function preparePay(talerPayUri: string) { */ export function acceptWithdrawal(talerWithdrawUri: string, selectedExchange: string) { return callBackend("accept-withdrawal", { talerWithdrawUri, selectedExchange }); -}
\ No newline at end of file +} diff --git a/src/webex/wxBackend.ts b/src/webex/wxBackend.ts index d31ea388d..5bff4fe0a 100644 --- a/src/webex/wxBackend.ts +++ b/src/webex/wxBackend.ts @@ -50,7 +50,6 @@ import * as wxApi from "./wxApi"; import URI = require("urijs"); import Port = chrome.runtime.Port; import MessageSender = chrome.runtime.MessageSender; -import { TipToken } from "../talerTypes"; import { BrowserCryptoWorkerFactory } from "../crypto/cryptoApi"; const NeedsWallet = Symbol("NeedsWallet"); @@ -182,7 +181,7 @@ function handleMessage( return Promise.resolve({ error: "bad url" }); } const amount = AmountJson.checked(detail.amount); - return needsWallet().getReserveCreationInfo(detail.baseUrl, amount); + return needsWallet().getWithdrawDetailsForAmount(detail.baseUrl, amount); } case "get-history": { // TODO: limit history length @@ -295,12 +294,10 @@ function handleMessage( case "accept-refund": return needsWallet().acceptRefund(detail.refundUrl); case "get-tip-status": { - const tipToken = TipToken.checked(detail.tipToken); - return needsWallet().getTipStatus(tipToken); + return needsWallet().getTipStatus(detail.talerTipUri); } case "accept-tip": { - const tipToken = TipToken.checked(detail.tipToken); - return needsWallet().acceptTip(tipToken); + return needsWallet().acceptTip(detail.talerTipUri); } case "clear-notification": { return needsWallet().clearNotification(); @@ -340,7 +337,7 @@ function handleMessage( return needsWallet().benchmarkCrypto(detail.repetitions); } case "get-withdraw-details": { - return needsWallet().getWithdrawDetails( + return needsWallet().getWithdrawDetailsForUri( detail.talerWithdrawUri, detail.maybeSelectedExchange, ); |