/* 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, FailCasesByMethod, HttpStatusCode, TalerCoreBankErrorsByMethod, TalerError, TalerErrorDetail, TranslatedString, parsePaytoUri, parseWithdrawUri, stringifyWithdrawUri } from "@gnu-taler/taler-util"; import { notify, notifyError, notifyInfo, useTranslationContext, utils } from "@gnu-taler/web-util/browser"; import { useEffect, useState } from "preact/hooks"; import { mutate } from "swr"; 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 { assertUnreachable } from "../WithdrawalOperationPage.js"; import { Props, State } from "./index.js"; export function useComponentState({ currency, onClose }: Props): utils.RecursiveState { const [settings, updateSettings] = usePreferences() const { state: credentials } = useBackendState() const creds = credentials.status !== "loggedIn" ? undefined : credentials const { api } = useBankCoreApiContext() const [busy, setBusy] = useState>() const [failure, setFailure] = useState | undefined>() const amount = settings.maxWithdrawalAmount async function doSilentStart() { //FIXME: if amount is not enough use balance const parsedAmount = Amounts.parseOrThrow(`${currency}:${amount}`) if (!creds) return; const resp = await api.createWithdrawal(creds, { amount: Amounts.stringify(parsedAmount), }); if (resp.type === "fail") { setFailure(resp) return; } updateSettings("currentWithdrawalOperationId", resp.body.withdrawal_id) } const withdrawalOperationId = settings.currentWithdrawalOperationId useEffect(() => { if (withdrawalOperationId === undefined) { doSilentStart() } }, [settings.fastWithdrawal, amount]) if (failure) { return { status: "failed", error: failure } } if (!withdrawalOperationId) { return { status: "loading", error: undefined } } const wid = withdrawalOperationId async function doAbort() { if (!creds) return; const resp = await api.abortWithdrawalById(creds, wid); if (resp.type === "ok") { updateSettings("currentWithdrawalOperationId", undefined) onClose(); } else { return resp; } } async function doConfirm(): Promise | undefined> { if (!creds) return; setBusy({}) const resp = await api.confirmWithdrawalById(creds, wid); setBusy(undefined) if (resp.type === "ok") { mutate(() => true)//clean withdrawal state } else { return resp; } } 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 HttpStatusCode.BadRequest: case HttpStatusCode.NotFound: { return { status: "aborted", error: undefined, onClose: async () => { updateSettings("currentWithdrawalOperationId", undefined) onClose() }, } } default: assertUnreachable(result) } } const { body: data } = result; if (data.status === "aborted") { return { status: "aborted", error: undefined, onClose: async () => { updateSettings("currentWithdrawalOperationId", undefined) onClose() }, } } if (data.status === "confirmed") { if (!settings.showWithdrawalSuccess) { updateSettings("currentWithdrawalOperationId", undefined) onClose() } return { status: "confirmed", error: undefined, onClose: async () => { updateSettings("currentWithdrawalOperationId", undefined) onClose() }, } } if (data.status === "pending") { return { status: "ready", error: undefined, uri: parsedUri, onClose: !creds ? (async () => { onClose(); return undefined }) : 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, account: data.username, onAbort: !creds ? undefined : doAbort, busy: !!busy, onConfirm: !creds ? undefined : doConfirm } } }