/* 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 { Amounts, HttpStatusCode, TranslatedString, parsePaytoUri, parseWithdrawUri, stringifyWithdrawUri } from "@gnu-taler/taler-util"; import { ErrorType, RequestError, notify, notifyError, notifyInfo, useTranslationContext, utils } from "@gnu-taler/web-util/browser"; import { useBackendContext } from "../../context/backend.js"; import { useAccessAPI, useAccessAnonAPI, useAccountDetails, useWithdrawalDetails } from "../../hooks/access.js"; import { Props, State } from "./index.js"; import { useSettings } from "../../hooks/settings.js"; import { buildRequestErrorMessage, undefinedIfEmpty } from "../../utils.js"; import { useEffect, useMemo, useState } from "preact/hooks"; import { getInitialBackendBaseURL } from "../../hooks/backend.js"; export function useComponentState({ currency, onClose,goToConfirmOperation }: Props): utils.RecursiveState { const { i18n } = useTranslationContext(); const [settings, updateSettings] = useSettings() const { createWithdrawal } = useAccessAPI(); const { abortWithdrawal, confirmWithdrawal } = useAccessAnonAPI(); const [busy, setBusy] = useState>() const amount = settings.maxWithdrawalAmount async function doSilentStart() { //FIXME: if amount is not enough use balance const parsedAmount = Amounts.parseOrThrow(`${currency}:${amount}`) try { const result = await createWithdrawal({ amount: Amounts.stringify(parsedAmount), }); const uri = parseWithdrawUri(result.data.taler_withdraw_uri); if (!uri) { return notifyError( i18n.str`Server responded with an invalid withdraw URI`, i18n.str`Withdraw URI: ${result.data.taler_withdraw_uri}`); } else { updateSettings("currentWithdrawalOperationId", uri.withdrawalOperationId) } } catch (error) { if (error instanceof RequestError) { notify( buildRequestErrorMessage(i18n, error.cause, { onClientError: (status) => status === HttpStatusCode.Forbidden ? i18n.str`The operation was rejected due to insufficient funds` : undefined, }), ); } else { notifyError( i18n.str`Operation failed, please report`, (error instanceof Error ? error.message : JSON.stringify(error)) as TranslatedString ) } } } const withdrawalOperationId = settings.currentWithdrawalOperationId useEffect(() => { if (withdrawalOperationId === undefined) { doSilentStart() } }, [settings.fastWithdrawal, amount]) const baseUrl = getInitialBackendBaseURL() if (!withdrawalOperationId) { return { status: "loading", error: undefined } } const wid = withdrawalOperationId async function doAbort() { try { setBusy({}) await abortWithdrawal(wid); onClose(); } catch (error) { if (error instanceof RequestError) { notify( buildRequestErrorMessage(i18n, error.cause, { onClientError: (status) => status === HttpStatusCode.Conflict ? i18n.str`The reserve operation has been confirmed previously and can't be aborted` : undefined, }), ); } else { notifyError( i18n.str`Operation failed, please report`, (error instanceof Error ? error.message : JSON.stringify(error)) as TranslatedString ) } } setBusy(undefined) } async function doConfirm() { try { setBusy({}) await confirmWithdrawal(wid); notifyInfo(i18n.str`Wire transfer completed!`) } catch (error) { if (error instanceof RequestError) { notify( buildRequestErrorMessage(i18n, error.cause, { onClientError: (status) => status === HttpStatusCode.Conflict ? i18n.str`The withdrawal has been aborted previously and can't be confirmed` : status === HttpStatusCode.UnprocessableEntity ? i18n.str`The withdraw operation cannot be confirmed because no exchange and reserve public key selection happened before` : undefined, }), ); } else { notifyError( i18n.str`Operation failed, please report`, (error instanceof Error ? error.message : JSON.stringify(error)) as TranslatedString ) } } setBusy(undefined) } const bankIntegrationApiBaseUrl = `${baseUrl}/integration-api` const uri = stringifyWithdrawUri({ bankIntegrationApiBaseUrl, withdrawalOperationId, }); const parsedUri = parseWithdrawUri(uri); if (!parsedUri) { return { status: "invalid-withdrawal", error: undefined, uri, onClose, } } return (): utils.RecursiveState => { const result = useWithdrawalDetails(withdrawalOperationId); if (!result.ok) { if (result.loading) { return { status: "loading", error: undefined } } return { status: "loading-error", error: result } } const { data } = result; if (data.aborted) { return { status: "aborted", error: undefined, onClose, } } if (data.confirmation_done) { if (!settings.showWithdrawalSuccess) { updateSettings("currentWithdrawalOperationId", undefined) onClose() } return { status: "confirmed", error: undefined, onClose: async () => { updateSettings("currentWithdrawalOperationId", undefined) onClose() }, } } if (!data.selection_done) { return { status: "ready", error: undefined, uri: parsedUri, onClose: async () => { await doAbort() updateSettings("currentWithdrawalOperationId", undefined) onClose() }, onAbort: doAbort, } } if (!data.selected_reserve_pub) { return { status: "invalid-reserve", error: undefined, reserve: data.selected_reserve_pub, onClose, } } const account = !data.selected_exchange_account ? undefined : parsePaytoUri(data.selected_exchange_account) if (!account) { return { status: "invalid-payto", error: undefined, payto: data.selected_exchange_account, onClose, } } // goToConfirmOperation(withdrawalOperationId) return { status: "need-confirmation", error: undefined, onAbort: async () => { await doAbort() updateSettings("currentWithdrawalOperationId", undefined) onClose() }, busy: !!busy, onConfirm: doConfirm } } }