/* 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 { ExchangeWithdrawDetails } from '@gnu-taler/taler-wallet-core/src/operations/withdraw'; import { useState } from "preact/hooks"; import { Fragment } from 'preact/jsx-runtime'; import { CheckboxOutlined } from '../components/CheckboxOutlined'; import { ExchangeXmlTos } from '../components/ExchangeToS'; import { LogoHeader } from '../components/LogoHeader'; import { Part } from '../components/Part'; import { SelectList } from '../components/SelectList'; import { ButtonSuccess, ButtonWarning, LinkSuccess, LinkWarning, TermsOfService, WalletAction } from '../components/styled'; import { useAsyncAsHook } from '../hooks/useAsyncAsHook'; import { acceptWithdrawal, getExchangeWithdrawalInfo, getWithdrawalDetailsForUri, setExchangeTosAccepted, listExchanges } from "../wxApi"; import { wxMain } from '../wxBackend.js'; interface Props { talerWithdrawUri?: string; } export interface ViewProps { details: ExchangeWithdrawDetails; amount: AmountJson; onSwitchExchange: (ex: string) => void; onWithdraw: () => Promise; onReview: (b: boolean) => void; onAccept: (b: boolean) => void; reviewing: boolean; accepted: boolean; confirmed: boolean; terms: { value?: TermsDocument; status: TermsStatus; }, knownExchanges: ExchangeListItem[] }; type TermsStatus = 'new' | 'accepted' | 'changed' | 'notfound'; type TermsDocument = TermsDocumentXml | TermsDocumentHtml; interface TermsDocumentXml { type: 'xml', document: Document, } interface TermsDocumentHtml { type: 'html', href: string, } function amountToString(text: AmountJson) { const aj = Amounts.jsonifyAmount(text) const amount = Amounts.stringifyValue(aj) return `${amount} ${aj.currency}` } export function View({ details, knownExchanges, amount, onWithdraw, onSwitchExchange, terms, reviewing, onReview, onAccept, accepted, confirmed }: ViewProps) { const needsReview = terms.status === 'changed' || terms.status === 'new' const [switchingExchange, setSwitchingExchange] = useState(undefined) const exchanges = knownExchanges.reduce((prev, ex) => ({ ...prev, [ex.exchangeBaseUrl]: ex.exchangeBaseUrl }), {}) return (

{i18n.str`Digital cash withdrawal`}

{Amounts.isNonZero(details.withdrawFee) && }
{!reviewing &&
{switchingExchange !== undefined ?

This is the list of known exchanges

onSwitchExchange(switchingExchange)}> {i18n.str`Confirm exchange selection`}
: setSwitchingExchange("")}> {i18n.str`Switch exchange`} }
} {!reviewing && accepted &&
onReview(true)} > {i18n.str`Show terms of service`}
} {reviewing &&
{terms.status !== 'accepted' && terms.value && terms.value.type === 'xml' && }
} {reviewing && accepted &&
onReview(false)} > {i18n.str`Hide terms of service`}
} {(reviewing || accepted) &&
{ onAccept(!accepted) onReview(false) }} />
} {/** * Main action section */}
{terms.status === 'new' && !accepted && !reviewing && onReview(true)} > {i18n.str`Review exchange terms of service`} } {terms.status === 'changed' && !accepted && onReview(true)} > {i18n.str`Review new version of terms of service`} } {(terms.status === 'accepted' || (needsReview && accepted)) && {i18n.str`Confirm withdrawal`} } {terms.status === 'notfound' && {i18n.str`Exchange doesn't have terms of service`} }
) } export function WithdrawPageWithParsedURI({ uri, uriInfo }: { uri: string, uriInfo: WithdrawUriInfoResponse }) { const [customExchange, setCustomExchange] = useState(undefined) const [errorAccepting, setErrorAccepting] = useState(undefined) const [reviewing, setReviewing] = useState(false) const [accepted, setAccepted] = useState(false) const [confirmed, setConfirmed] = useState(false) const knownExchangesHook = useAsyncAsHook(() => 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 = customExchange || uriInfo.defaultExchangeBaseUrl || thisCurrencyExchanges[0]?.exchangeBaseUrl const detailsHook = useAsyncAsHook(async () => { if (!exchange) throw Error('no default exchange') return getExchangeWithdrawalInfo({ exchangeBaseUrl: exchange, amount: withdrawAmount, tosAcceptedFormat: ['text/json', 'text/xml', 'text/pdf'] }) }) if (!detailsHook) { return Getting withdrawal details.; } if (detailsHook.hasError) { return Problems getting details: {detailsHook.message}; } const details = detailsHook.response const onAccept = async (): Promise => { try { await setExchangeTosAccepted(details.exchangeInfo.baseUrl, details.tosRequested?.tosEtag) setAccepted(true) } catch (e) { if (e instanceof Error) { setErrorAccepting(e.message) } } } const onWithdraw = async (): Promise => { setConfirmed(true) console.log("accepting exchange", details.exchangeDetails.exchangeBaseUrl); try { const res = await acceptWithdrawal(uri, details.exchangeInfo.baseUrl); console.log("accept withdrawal response", res); if (res.confirmTransferUrl) { document.location.href = res.confirmTransferUrl; } } catch (e) { setConfirmed(false) } }; let termsContent: TermsDocument | undefined = undefined; if (details.tosRequested) { if (details.tosRequested.tosContentType === 'text/xml') { try { const document = new DOMParser().parseFromString(details.tosRequested.tosText, "text/xml") termsContent = { type: 'xml', document } } catch (e) { console.log(e) debugger; } } } const status: TermsStatus = !termsContent ? 'notfound' : ( !details.exchangeDetails.termsOfServiceAcceptedEtag ? 'new' : ( details.tosRequested?.tosEtag !== details.exchangeDetails.termsOfServiceAcceptedEtag ? 'changed' : 'accepted' )) return } export function WithdrawPage({ talerWithdrawUri }: Props): JSX.Element { const uriInfoHook = useAsyncAsHook(() => !talerWithdrawUri ? Promise.reject(undefined) : getWithdrawalDetailsForUri({ talerWithdrawUri }) ) if (!talerWithdrawUri) { return missing withdraw uri; } if (!uriInfoHook) { return Loading...; } if (uriInfoHook.hasError) { return This URI is not valid anymore: {uriInfoHook.message}; } return }