/*
This file is part of GNU Taler
(C) 2022-2024 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,
TalerCoreBankErrorsByMethod,
TalerCorebankApi,
TalerError,
assertUnreachable,
parsePaytoUri,
parseWithdrawUri,
stringifyWithdrawUri,
} from "@gnu-taler/taler-util";
import { utils } from "@gnu-taler/web-util/browser";
import { useEffect, useState } from "preact/hooks";
import { mutate } from "swr";
import { useBankCoreApiContext } from "@gnu-taler/web-util/browser";
import { useWithdrawalDetails } from "../../hooks/account.js";
import { useSessionState } from "../../hooks/session.js";
import { useBankState } from "../../hooks/bank-state.js";
import { usePreferences } from "../../hooks/preferences.js";
import { Props, State } from "./index.js";
import { useSettingsContext } from "../../context/settings.js";
export function useComponentState({
currency,
routeClose,
onAbort,
focus,
routeHere,
onAuthorizationRequired,
}: Props): utils.RecursiveState {
const [preference] = usePreferences();
const settings = useSettingsContext();
const [bankState, updateBankState] = useBankState();
const { state: credentials } = useSessionState();
const creds = credentials.status !== "loggedIn" ? undefined : credentials;
const {
lib: { bank },
} = useBankCoreApiContext();
const [failure, setFailure] = useState<
TalerCoreBankErrorsByMethod<"createWithdrawal"> | undefined
>();
const amount = settings.defaultSuggestedAmount;
async function doSilentStart() {
// FIXME: if amount is not enough use balance
const parsedAmount = Amounts.parseOrThrow(`${currency}:${amount}`);
if (!creds) return;
const params: TalerCorebankApi.BankAccountCreateWithdrawalRequest =
preference.fastWithdrawalForm
? {
suggested_amount: Amounts.stringify(parsedAmount),
}
: {
amount: Amounts.stringify(parsedAmount),
};
const resp = await bank.createWithdrawal(creds, params);
if (resp.type === "fail") {
setFailure(resp);
return;
}
updateBankState("currentWithdrawalOperationId", resp.body.withdrawal_id);
}
const withdrawalOperationId = bankState.currentWithdrawalOperationId;
useEffect(() => {
if (withdrawalOperationId === undefined) {
doSilentStart();
}
}, [preference.fastWithdrawalForm, 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 bank.abortWithdrawalById(creds, wid);
if (resp.type === "ok") {
// updateBankState("currentWithdrawalOperationId", undefined)
onAbort();
} else {
return resp;
}
}
async function doConfirm(): Promise<
TalerCoreBankErrorsByMethod<"confirmWithdrawalById"> | undefined
> {
if (!creds) return;
const resp = await bank.confirmWithdrawalById(creds, wid);
if (resp.type === "ok") {
mutate(() => true); //clean withdrawal state
} else {
return resp;
}
}
const uri = stringifyWithdrawUri({
bankIntegrationApiBaseUrl: bank.getIntegrationAPI().href,
withdrawalOperationId,
});
const parsedUri = parseWithdrawUri(uri);
if (!parsedUri) {
return {
status: "invalid-withdrawal",
error: undefined,
uri,
};
}
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,
routeClose,
};
}
default:
assertUnreachable(result);
}
}
const { body: data } = result;
if (data.status === "aborted") {
return {
status: "aborted",
error: undefined,
routeClose,
};
}
if (data.status === "confirmed") {
if (!preference.showWithdrawalSuccess) {
updateBankState("currentWithdrawalOperationId", undefined);
// onClose()
}
return {
status: "confirmed",
error: undefined,
routeClose,
};
}
if (data.status === "pending") {
return {
status: "ready",
error: undefined,
uri: parsedUri,
routeClose,
focus,
onAbort: !creds
? async () => {
onAbort();
return undefined;
}
: doAbort,
};
}
if (!data.selected_reserve_pub) {
return {
status: "invalid-reserve",
error: undefined,
reserve: data.selected_reserve_pub,
};
}
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,
};
}
return {
status: "need-confirmation",
error: undefined,
routeHere,
onAuthorizationRequired,
account: data.username,
id: withdrawalOperationId,
onAbort: !creds ? undefined : doAbort,
onConfirm: !creds ? undefined : doConfirm,
};
};
}