From 5fc8f95a5d4ce8dea03b2dbec7eb5c37e7ff3f15 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Fri, 9 Dec 2022 12:15:15 -0300 Subject: simplify directories --- .../src/pages/WithdrawalConfirmationQuestion.tsx | 327 +++++++++++++++++++++ 1 file changed, 327 insertions(+) create mode 100644 packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx (limited to 'packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx') diff --git a/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx b/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx new file mode 100644 index 000000000..8cfdd4e9f --- /dev/null +++ b/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx @@ -0,0 +1,327 @@ +/* + 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 { Logger } from "@gnu-taler/taler-util"; +import { Fragment, h, VNode } from "preact"; +import { StateUpdater } from "preact/hooks"; +import { useBackendContext } from "../context/backend.js"; +import { PageStateType, usePageContext } from "../context/pageState.js"; +import { + InternationalizationAPI, + useTranslationContext, +} from "@gnu-taler/web-util/lib/index.browser"; +import { BackendState } from "../hooks/backend.js"; +import { prepareHeaders } from "../utils.js"; + +const logger = new Logger("WithdrawalConfirmationQuestion"); + +/** + * Additional authentication required to complete the operation. + * Not providing a back button, only abort. + */ +export function WithdrawalConfirmationQuestion(): VNode { + const { pageState, pageStateSetter } = usePageContext(); + const backend = useBackendContext(); + const { i18n } = useTranslationContext(); + const captchaNumbers = { + a: Math.floor(Math.random() * 10), + b: Math.floor(Math.random() * 10), + }; + let captchaAnswer = ""; + + return ( + +

{i18n.str`Confirm Withdrawal`}

+
+
+
+
+

{i18n.str`Authorize withdrawal by solving challenge`}

+

+ +   + { + captchaAnswer = e.currentTarget.value; + }} + /> +

+

+ +   + +

+
+
+
+

+ + A this point, a real bank would ask for an additional + authentication proof (PIN/TAN, one time password, ..), instead + of a simple calculation. + +

+
+
+
+
+ ); +} + +/** + * This function confirms a withdrawal operation AFTER + * the wallet has given the exchange's payment details + * to the bank (via the Integration API). Such details + * can be given by scanning a QR code or by passing the + * raw taler://withdraw-URI to the CLI wallet. + * + * This function will set the confirmation status in the + * 'page state' and let the related components refresh. + */ +async function confirmWithdrawalCall( + backendState: BackendState, + withdrawalId: string | undefined, + pageStateSetter: StateUpdater, + i18n: InternationalizationAPI, +): Promise { + if (backendState.status === "loggedOut") { + logger.error("No credentials found."); + pageStateSetter((prevState) => ({ + ...prevState, + + error: { + title: i18n.str`No credentials found.`, + }, + })); + return; + } + if (typeof withdrawalId === "undefined") { + logger.error("No withdrawal ID found."); + pageStateSetter((prevState) => ({ + ...prevState, + + error: { + title: i18n.str`No withdrawal ID found.`, + }, + })); + return; + } + let res: Response; + try { + const { username, password } = backendState; + const headers = prepareHeaders(username, password); + /** + * NOTE: tests show that when a same object is being + * POSTed, caching might prevent same requests from being + * made. Hence, trying to POST twice the same amount might + * get silently ignored. + * + * headers.append("cache-control", "no-store"); + * headers.append("cache-control", "no-cache"); + * headers.append("pragma", "no-cache"); + * */ + + // Backend URL must have been stored _with_ a final slash. + const url = new URL( + `access-api/accounts/${backendState.username}/withdrawals/${withdrawalId}/confirm`, + backendState.url, + ); + res = await fetch(url.href, { + method: "POST", + headers, + }); + } catch (error) { + logger.error("Could not POST withdrawal confirmation to the bank", error); + pageStateSetter((prevState) => ({ + ...prevState, + + error: { + title: i18n.str`Could not confirm the withdrawal`, + description: (error as any).error.description, + debug: JSON.stringify(error), + }, + })); + return; + } + if (!res || !res.ok) { + const response = await res.json(); + // assume not ok if res is null + logger.error( + `Withdrawal confirmation gave response error (${res.status})`, + res.statusText, + ); + pageStateSetter((prevState) => ({ + ...prevState, + + error: { + title: i18n.str`Withdrawal confirmation gave response error`, + debug: JSON.stringify(response), + }, + })); + return; + } + logger.trace("Withdrawal operation confirmed!"); + pageStateSetter((prevState) => { + const { talerWithdrawUri, ...rest } = prevState; + return { + ...rest, + + info: i18n.str`Withdrawal confirmed!`, + }; + }); +} + +/** + * Abort a withdrawal operation via the Access API's /abort. + */ +async function abortWithdrawalCall( + backendState: BackendState, + withdrawalId: string | undefined, + pageStateSetter: StateUpdater, + i18n: InternationalizationAPI, +): Promise { + if (backendState.status === "loggedOut") { + logger.error("No credentials found."); + pageStateSetter((prevState) => ({ + ...prevState, + + error: { + title: i18n.str`No credentials found.`, + }, + })); + return; + } + if (typeof withdrawalId === "undefined") { + logger.error("No withdrawal ID found."); + pageStateSetter((prevState) => ({ + ...prevState, + + error: { + title: i18n.str`No withdrawal ID found.`, + }, + })); + return; + } + let res: Response; + try { + const { username, password } = backendState; + const headers = prepareHeaders(username, password); + /** + * NOTE: tests show that when a same object is being + * POSTed, caching might prevent same requests from being + * made. Hence, trying to POST twice the same amount might + * get silently ignored. Needs more observation! + * + * headers.append("cache-control", "no-store"); + * headers.append("cache-control", "no-cache"); + * headers.append("pragma", "no-cache"); + * */ + + // Backend URL must have been stored _with_ a final slash. + const url = new URL( + `access-api/accounts/${backendState.username}/withdrawals/${withdrawalId}/abort`, + backendState.url, + ); + res = await fetch(url.href, { method: "POST", headers }); + } catch (error) { + logger.error("Could not abort the withdrawal", error); + pageStateSetter((prevState) => ({ + ...prevState, + + error: { + title: i18n.str`Could not abort the withdrawal.`, + description: (error as any).error.description, + debug: JSON.stringify(error), + }, + })); + return; + } + if (!res.ok) { + const response = await res.json(); + logger.error( + `Withdrawal abort gave response error (${res.status})`, + res.statusText, + ); + pageStateSetter((prevState) => ({ + ...prevState, + + error: { + title: i18n.str`Withdrawal abortion failed.`, + description: response.error.description, + debug: JSON.stringify(response), + }, + })); + return; + } + logger.trace("Withdrawal operation aborted!"); + pageStateSetter((prevState) => { + const { ...rest } = prevState; + return { + ...rest, + + info: i18n.str`Withdrawal aborted!`, + }; + }); +} -- cgit v1.2.3