/* This file is part of GNU Taler (C) 2022 Taler Systems S.A. GNU 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. GNU 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 GNU Taler; see the file COPYING. If not, see */ import { AmountJson, Logger, PaytoUri, PaytoUriIBAN, PaytoUriTalerBank, TalerError, TranslatedString, WithdrawUriResult } from "@gnu-taler/taler-util"; import { Attention, Loading, LocalNotificationBanner, ShowInputErrorLabel, notifyInfo, useLocalNotification, useTranslationContext } from "@gnu-taler/web-util/browser"; import { ComponentChildren, Fragment, VNode, h } from "preact"; import { useMemo, useState } from "preact/hooks"; import { mutate } from "swr"; import { ErrorLoadingWithDebug } from "../components/ErrorLoadingWithDebug.js"; import { useBankCoreApiContext } from "../context/config.js"; import { useWithdrawalDetails } from "../hooks/access.js"; import { useBackendState } from "../hooks/backend.js"; import { usePreferences } from "../hooks/preferences.js"; import { undefinedIfEmpty } from "../utils.js"; import { LoginForm } from "./LoginForm.js"; import { RenderAmount } from "./PaytoWireTransferForm.js"; import { assertUnreachable } from "./WithdrawalOperationPage.js"; import { OperationNotFound } from "./WithdrawalQRCode.js"; const logger = new Logger("WithdrawalConfirmationQuestion"); interface Props { onAborted: () => void; withdrawUri: WithdrawUriResult; details: { account: PaytoUri, reserve: string, username: string, amount: AmountJson, } } /** * Additional authentication required to complete the operation. * Not providing a back button, only abort. */ export function WithdrawalConfirmationQuestion({ onAborted, details, withdrawUri, }: Props): VNode { const { i18n } = useTranslationContext(); const [settings] = usePreferences() const { state: credentials } = useBackendState(); const creds = credentials.status !== "loggedIn" ? undefined : credentials const withdrawalInfo = useWithdrawalDetails(withdrawUri.withdrawalOperationId) if (!withdrawalInfo) { return } if (withdrawalInfo instanceof TalerError) { return } if (withdrawalInfo.type === "fail") { switch (withdrawalInfo.case) { case "not-found": return case "invalid-id": return default: assertUnreachable(withdrawalInfo) } } const captchaNumbers = useMemo(() => { return { a: Math.floor(Math.random() * 10), b: Math.floor(Math.random() * 10), }; }, []); const [notification, notify, handleError] = useLocalNotification() const { config, api } = useBankCoreApiContext() const [captchaAnswer, setCaptchaAnswer] = useState(); const answer = parseInt(captchaAnswer ?? "", 10); const [busy, setBusy] = useState>() const errors = undefinedIfEmpty({ answer: !captchaAnswer ? i18n.str`Answer the question before continue` : Number.isNaN(answer) ? i18n.str`The answer should be a number` : answer !== captchaNumbers.a + captchaNumbers.b ? i18n.str`The answer "${answer}" to "${captchaNumbers.a} + ${captchaNumbers.b}" is wrong.` : undefined, }) ?? busy; async function doTransfer() { setBusy({}) await handleError(async () => { if (!creds) return; const resp = await api.confirmWithdrawalById(creds, withdrawUri.withdrawalOperationId); if (resp.type === "ok") { mutate(() => true)// clean any info that we have if (!settings.showWithdrawalSuccess) { notifyInfo(i18n.str`Wire transfer completed!`) } } else { switch (resp.case) { case "previously-aborted": return notify({ type: "error", title: i18n.str`The withdrawal has been aborted previously and can't be confirmed`, description: resp.detail.hint as TranslatedString, debug: resp.detail, }); case "no-exchange-or-reserve-selected": return notify({ type: "error", title: i18n.str`The withdraw operation cannot be confirmed because no exchange and reserve public key selection happened before`, description: resp.detail.hint as TranslatedString, debug: resp.detail, }); case "invalid-id": return notify({ type: "error", title: i18n.str`The operation id is invalid.`, description: resp.detail.hint as TranslatedString, debug: resp.detail, }) case "not-found": return notify({ type: "error", title: i18n.str`The operation was not found.`, description: resp.detail.hint as TranslatedString, debug: resp.detail, }) case "insufficient-funds": return notify({ type: "error", title: i18n.str`Your balance is not enough for the operation.`, description: resp.detail.hint as TranslatedString, debug: resp.detail, }) default: assertUnreachable(resp) } } }) setBusy(undefined) } async function doCancel() { setBusy({}) await handleError(async () => { if (!creds) return; const resp = await api.abortWithdrawalById(creds, withdrawUri.withdrawalOperationId); if (resp.type === "ok") { onAborted(); } else { switch (resp.case) { case "previously-confirmed": return notify({ type: "error", title: i18n.str`The reserve operation has been confirmed previously and can't be aborted` }); case "invalid-id": return notify({ type: "error", title: i18n.str`The operation id is invalid.`, description: resp.detail.hint as TranslatedString, debug: resp.detail, }) case "not-found": return notify({ type: "error", title: i18n.str`The operation was not found.`, description: resp.detail.hint as TranslatedString, debug: resp.detail, }) default: { assertUnreachable(resp) } } } }) setBusy(undefined) } return (

Confirm the withdrawal operation

Answer the next question to authorize the wire transfer.

{ e.preventDefault() }} >
{ setCaptchaAnswer(e.currentTarget.value) }} />

Wire transfer details

{((): VNode => { switch (details.account.targetType) { case "iban": { const p = details.account as PaytoUriIBAN const name = p.params["receiver-name"] return
Exchange account
{p.iban}
{name &&
Exchange name
{p.params["receiver-name"]}
}
} case "x-taler-bank": { const p = details.account as PaytoUriTalerBank const name = p.params["receiver-name"] return
Exchange account
{p.account}
{name &&
Exchange name
{p.params["receiver-name"]}
}
} default: return
Exchange account
{details.account.targetPath}
} })()}
Amount
); } export function ShouldBeSameUser({ username, children }: { username: string, children: ComponentChildren }): VNode { const { state: credentials } = useBackendState(); const { i18n } = useTranslationContext() if (credentials.status === "loggedOut") { return } if (credentials.username !== username) { return } return {children} }