/* 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, TalerError, TranslatedString, parsePaytoUri, parseWithdrawUri, stringifyWithdrawUri } from "@gnu-taler/taler-util"; import { RequestError, notify, notifyError, notifyInfo, useTranslationContext, utils } from "@gnu-taler/web-util/browser"; import { useEffect, useState } from "preact/hooks"; import { useBankCoreApiContext } from "../../context/config.js"; import { useWithdrawalDetails } from "../../hooks/access.js"; import { useBackendState } from "../../hooks/backend.js"; import { useSettings } from "../../hooks/settings.js"; import { buildRequestErrorMessage, withRuntimeErrorHandling } from "../../utils.js"; import { Props, State } from "./index.js"; import { assertUnreachable } from "../WithdrawalOperationPage.js"; import { mutate } from "swr"; export function useComponentState({ currency, onClose }: Props): utils.RecursiveState { const { i18n } = useTranslationContext(); const [settings, updateSettings] = useSettings() const { state: credentials } = useBackendState() const creds = credentials.status !== "loggedIn" ? undefined : credentials const { api } = useBankCoreApiContext() // 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}`) if (!creds) return; await withRuntimeErrorHandling(i18n, async () => { const resp = await api.createWithdrawal(creds, { amount: Amounts.stringify(parsedAmount), }); if (resp.type === "fail") { switch (resp.case) { case "insufficient-funds": return notify({ type: "error", title: i18n.str`The operation was rejected due to insufficient funds`, description: resp.detail.hint as TranslatedString, debug: resp.detail, }); default: assertUnreachable(resp.case) } } const uri = parseWithdrawUri(resp.body.taler_withdraw_uri); if (!uri) { return notifyError( i18n.str`Server responded with an invalid withdraw URI`, i18n.str`Withdraw URI: ${resp.body.taler_withdraw_uri}`); } else { updateSettings("currentWithdrawalOperationId", uri.withdrawalOperationId) } }) } const withdrawalOperationId = settings.currentWithdrawalOperationId useEffect(() => { if (withdrawalOperationId === undefined) { doSilentStart() } }, [settings.fastWithdrawal, amount]) if (!withdrawalOperationId) { return { status: "loading", error: undefined } } const wid = withdrawalOperationId async function doAbort() { await withRuntimeErrorHandling(i18n, async () => { const resp = await api.abortWithdrawalById(wid); if (resp.type === "ok") { updateSettings("currentWithdrawalOperationId", undefined) onClose(); } 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`, description: resp.detail.hint as TranslatedString, debug: resp.detail, }) default: assertUnreachable(resp.case) } } }) } async function doConfirm() { setBusy({}) await withRuntimeErrorHandling(i18n, async () => { const resp = await api.confirmWithdrawalById(wid); if (resp.type === "ok") { mutate(() => true)//clean withdrawal state 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, }) default: assertUnreachable(resp) } } }) setBusy(undefined) } const uri = stringifyWithdrawUri({ bankIntegrationApiBaseUrl: api.getIntegrationAPI().baseUrl, withdrawalOperationId, }); const parsedUri = parseWithdrawUri(uri); if (!parsedUri) { return { status: "invalid-withdrawal", error: undefined, uri, onClose, } } return (): utils.RecursiveState => { const result = useWithdrawalDetails(withdrawalOperationId); const shouldCreateNewOperation = result && !(result instanceof TalerError) useEffect(() => { if (shouldCreateNewOperation) { doSilentStart() } }, []) if (!result) { return { status: "loading", error: undefined } } if (result instanceof TalerError) { return { status: "loading-error", error: result } } if (result.type === "fail") { switch (result.case) { case "not-found": { return { status: "aborted", error: undefined, onClose: async () => { updateSettings("currentWithdrawalOperationId", undefined) onClose() }, } } default: assertUnreachable(result.case) } } const { body: data } = result; if (data.aborted) { return { status: "aborted", error: undefined, onClose: async () => { updateSettings("currentWithdrawalOperationId", 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: doAbort, 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, } } return { status: "need-confirmation", error: undefined, onAbort: doAbort, busy: !!busy, onConfirm: doConfirm } } }