/*
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
}
}
}