/*
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 { RequestError, notify, notifyError, notifyInfo, useTranslationContext, utils } from "@gnu-taler/web-util/browser";
import { useEffect, useState } from "preact/hooks";
import { useAccessAPI, useAccessAnonAPI, useWithdrawalDetails } from "../../hooks/access.js";
import { getInitialBackendBaseURL } from "../../hooks/backend.js";
import { useSettings } from "../../hooks/settings.js";
import { buildRequestErrorMessage } from "../../utils.js";
import { Props, State } from "./index.js";
export function useComponentState({ currency, onClose }: 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);
if (!settings.showWithdrawalSuccess) {
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}/taler-integration`
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);
const shouldCreateNewOperation = !result.ok && !result.loading && result.info.status === HttpStatusCode.NotFound
useEffect(() => {
if (shouldCreateNewOperation) {
doSilentStart()
}
}, [])
if (!result.ok) {
if (result.loading) {
return {
status: "loading",
error: undefined
}
}
if (result.info.status === HttpStatusCode.NotFound) {
return {
status: "loading",
error: undefined,
}
}
return {
status: "loading-error",
error: result
}
}
const { 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: 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
}
}
}