/* 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, KnownBankAccountsInfo, parsePaytoUri, PaytoUri, stringifyPaytoUri, TransactionAmountMode } from "@gnu-taler/taler-util"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { useTranslationContext } from "@gnu-taler/web-util/browser"; import { useState } from "preact/hooks"; import { alertFromError, useAlertContext } from "../../context/alert.js"; import { useBackendContext } from "../../context/backend.js"; import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js"; import { RecursiveState } from "../../utils/index.js"; import { Props, State } from "./index.js"; export function useComponentState({ amount: amountStr, onCancel, onSuccess, }: Props): RecursiveState { const api = useBackendContext(); const { i18n } = useTranslationContext(); const { pushAlertOnError } = useAlertContext(); const parsed = amountStr === undefined ? undefined : Amounts.parse(amountStr); const currency = parsed !== undefined ? parsed.currency : undefined; const hook = useAsyncAsHook(async () => { const { balances } = await api.wallet.call( WalletApiOperation.GetBalances, {}, ); const { accounts } = await api.wallet.call( WalletApiOperation.ListKnownBankAccounts, { currency }, ); return { accounts, balances }; }); const initialValue = parsed !== undefined ? parsed : currency !== undefined ? Amounts.zeroOfCurrency(currency) : undefined; // const [accountIdx, setAccountIdx] = useState(0); const [selectedAccount, setSelectedAccount] = useState(); const [addingAccount, setAddingAccount] = useState(false); if (!currency) { return { status: "amount-or-currency-error", error: undefined, }; } if (!hook) { return { status: "loading", error: undefined, }; } if (hook.hasError) { return { status: "error", error: alertFromError( i18n, i18n.str`Could not load balance information`, hook, ), }; } const { accounts, balances } = hook.response; async function updateAccountFromList(accountStr: string): Promise { const uri = !accountStr ? undefined : parsePaytoUri(accountStr); if (uri) { setSelectedAccount(uri); } } if (addingAccount) { return { status: "manage-account", error: undefined, currency, onAccountAdded: (p: string) => { updateAccountFromList(p); setAddingAccount(false); hook.retry(); }, onCancel: () => { setAddingAccount(false); hook.retry(); }, }; } const bs = balances.filter((b) => b.available.startsWith(currency)); const balance = bs.length > 0 ? Amounts.parseOrThrow(bs[0].available) : Amounts.zeroOfCurrency(currency); if (Amounts.isZero(balance)) { return { status: "no-enough-balance", error: undefined, currency, }; } if (accounts.length === 0) { return { status: "no-accounts", error: undefined, currency, onAddAccount: { onClick: pushAlertOnError(async () => { setAddingAccount(true); }), }, }; } const firstAccount = accounts[0].uri; const currentAccount = !selectedAccount ? firstAccount : selectedAccount; const zero = Amounts.zeroOfCurrency(currency) return (): State => { const [instructed, setInstructed] = useState( {amount: initialValue ?? zero, type: TransactionAmountMode.Raw}, ); const amountStr = Amounts.stringify(instructed.amount); const depositPaytoUri = stringifyPaytoUri(currentAccount); const hook = useAsyncAsHook(async () => { const fee = await api.wallet.call( WalletApiOperation.ConvertDepositAmount, { amount: amountStr, type: instructed.type, depositPaytoUri, }, ); return { fee }; }, [amountStr, depositPaytoUri]); if (!hook) { return { status: "loading", error: undefined, }; } if (hook.hasError) { return { status: "error", error: alertFromError( i18n, i18n.str`Could not load fee for amount ${amountStr}`, hook, ), }; } const { fee } = hook.response; const accountMap = createLabelsForBankAccount(accounts); const totalFee = fee !== undefined ? Amounts.sub(fee.effectiveAmount, fee.rawAmount).amount : Amounts.zeroOfCurrency(currency); const totalToDeposit = Amounts.parseOrThrow(fee.rawAmount); const totalEffective = Amounts.parseOrThrow(fee.effectiveAmount); const isDirty = instructed.amount !== initialValue; const amountError = !isDirty ? undefined : Amounts.cmp(balance, totalEffective) === -1 ? `Too much, your current balance is ${Amounts.stringifyValue(balance)}` : undefined; const unableToDeposit = Amounts.isZero(totalToDeposit) || //deposit may be zero because of fee fee === undefined || //no fee calculated yet amountError !== undefined; //amount field may be invalid async function doSend(): Promise { if (!currency) return; const depositPaytoUri = stringifyPaytoUri(currentAccount); const amountStr = Amounts.stringify(totalEffective); await api.wallet.call(WalletApiOperation.CreateDepositGroup, { amount: amountStr, depositPaytoUri, }); onSuccess(currency); } return { status: "ready", error: undefined, currency, amount: { value: totalEffective, onInput: pushAlertOnError(async (a) => setInstructed({ amount: a, type: TransactionAmountMode.Effective, })), error: amountError, }, totalToDeposit: { value: totalToDeposit, onInput: pushAlertOnError(async (a) => setInstructed({ amount: a, type: TransactionAmountMode.Raw, })), error: amountError, }, onAddAccount: { onClick: pushAlertOnError(async () => { setAddingAccount(true); }), }, account: { list: accountMap, value: stringifyPaytoUri(currentAccount), onChange: pushAlertOnError(updateAccountFromList), }, currentAccount, cancelHandler: { onClick: pushAlertOnError(async () => { onCancel(currency); }), }, depositHandler: { onClick: unableToDeposit ? undefined : pushAlertOnError(doSend), }, totalFee, }; }; } export function labelForAccountType(id: string): string { switch (id) { case "": return "Choose one"; case "x-taler-bank": return "Taler Bank"; case "bitcoin": return "Bitcoin"; case "iban": return "IBAN"; default: return id; } } export function createLabelsForBankAccount( knownBankAccounts: Array, ): { [value: string]: string } { const initialList: Record = {}; if (!knownBankAccounts.length) return initialList; return knownBankAccounts.reduce((prev, cur) => { prev[stringifyPaytoUri(cur.uri)] = cur.alias; return prev; }, initialList); }