/* 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 */ /** * Page shown to the user to confirm creation * of a reserve, usually requested by the bank. * * @author Florian Dold */ import { AmountJson, Amounts, ExchangeListItem, i18n, WithdrawUriInfoResponse, } from "@gnu-taler/taler-util"; import { OperationFailedError } from "@gnu-taler/taler-wallet-core"; import { Fragment, h, VNode } from "preact"; import { useState } from "preact/hooks"; import { Loading } from "../components/Loading"; import { LoadingError } from "../components/LoadingError"; import { ErrorTalerOperation } from "../components/ErrorTalerOperation"; import { LogoHeader } from "../components/LogoHeader"; import { Part } from "../components/Part"; import { SelectList } from "../components/SelectList"; import { ButtonSuccess, ButtonWarning, LinkSuccess, WalletAction, } from "../components/styled"; import { useAsyncAsHook } from "../hooks/useAsyncAsHook"; import { amountToString, buildTermsOfServiceState, TermsState, } from "../utils/index"; import * as wxApi from "../wxApi"; import { TermsOfServiceSection } from "./TermsOfServiceSection"; interface Props { talerWithdrawUri?: string; } export interface ViewProps { withdrawalFee: AmountJson; exchangeBaseUrl?: string; amount: AmountJson; onSwitchExchange: (ex: string) => void; onWithdraw: () => Promise; onReview: (b: boolean) => void; onAccept: (b: boolean) => void; reviewing: boolean; reviewed: boolean; terms: TermsState; knownExchanges: ExchangeListItem[]; } export function View({ withdrawalFee, exchangeBaseUrl, knownExchanges, amount, onWithdraw, onSwitchExchange, terms, reviewing, onReview, onAccept, reviewed, }: ViewProps): VNode { const [withdrawError, setWithdrawError] = useState< OperationFailedError | undefined >(undefined); const [confirmDisabled, setConfirmDisabled] = useState(false); const needsReview = terms.status === "changed" || terms.status === "new"; const [switchingExchange, setSwitchingExchange] = useState(false); const [nextExchange, setNextExchange] = useState( undefined, ); const exchanges = knownExchanges .filter((e) => e.currency === amount.currency) .reduce( (prev, ex) => ({ ...prev, [ex.exchangeBaseUrl]: ex.exchangeBaseUrl }), {}, ); async function doWithdrawAndCheckError() { try { setConfirmDisabled(true); await onWithdraw(); } catch (e) { if (e instanceof OperationFailedError) { setWithdrawError(e); } setConfirmDisabled(false); } } return (

{i18n.str`Digital cash withdrawal`}

{withdrawError && ( )}
{Amounts.isNonZero(withdrawalFee) && ( )} {exchangeBaseUrl && ( )}
{!reviewing && (
{switchingExchange ? (
{ if (nextExchange !== undefined) { onSwitchExchange(nextExchange); } setSwitchingExchange(false); }} > {nextExchange === undefined ? i18n.str`Cancel exchange selection` : i18n.str`Confirm exchange selection`}
) : ( setSwitchingExchange(true)}> {i18n.str`Switch exchange`} )}
)}
{(terms.status === "accepted" || (needsReview && reviewed)) && ( {i18n.str`Confirm withdrawal`} )} {terms.status === "notfound" && ( {i18n.str`Withdraw anyway`} )}
); } export function WithdrawPageWithParsedURI({ uri, uriInfo, }: { uri: string; uriInfo: WithdrawUriInfoResponse; }): VNode { const [customExchange, setCustomExchange] = useState( undefined, ); // const [errorAccepting, setErrorAccepting] = useState( // undefined, // ); const [reviewing, setReviewing] = useState(false); const [reviewed, setReviewed] = useState(false); const knownExchangesHook = useAsyncAsHook(() => wxApi.listExchanges()); const knownExchanges = !knownExchangesHook || knownExchangesHook.hasError ? [] : knownExchangesHook.response.exchanges; const withdrawAmount = Amounts.parseOrThrow(uriInfo.amount); const thisCurrencyExchanges = knownExchanges.filter( (ex) => ex.currency === withdrawAmount.currency, ); const exchange: string | undefined = customExchange ?? uriInfo.defaultExchangeBaseUrl ?? (thisCurrencyExchanges[0] ? thisCurrencyExchanges[0].exchangeBaseUrl : undefined); const detailsHook = useAsyncAsHook(async () => { if (!exchange) throw Error("no default exchange"); const tos = await wxApi.getExchangeTos(exchange, ["text/xml"]); const tosState = buildTermsOfServiceState(tos); const info = await wxApi.getExchangeWithdrawalInfo({ exchangeBaseUrl: exchange, amount: withdrawAmount, tosAcceptedFormat: ["text/xml"], }); return { tos: tosState, info }; }); if (!detailsHook) { return ; } if (detailsHook.hasError) { return ( ); } const details = detailsHook.response; const onAccept = async (): Promise => { if (!exchange) return; try { await wxApi.setExchangeTosAccepted(exchange, details.tos.version); setReviewed(true); } catch (e) { if (e instanceof Error) { //FIXME: uncomment this and display error // setErrorAccepting(e.message); } } }; const onWithdraw = async (): Promise => { if (!exchange) return; console.log("accepting exchange", exchange); const res = await wxApi.acceptWithdrawal(uri, exchange); console.log("accept withdrawal response", res); if (res.confirmTransferUrl) { document.location.href = res.confirmTransferUrl; } }; return ( ); } export function WithdrawPage({ talerWithdrawUri }: Props): VNode { const uriInfoHook = useAsyncAsHook(() => !talerWithdrawUri ? Promise.reject(undefined) : wxApi.getWithdrawalDetailsForUri({ talerWithdrawUri }), ); if (!talerWithdrawUri) { return ( missing withdraw uri ); } if (!uriInfoHook) { return ; } if (uriInfoHook.hasError) { return ( ); } return ( ); }