diff options
Diffstat (limited to 'packages/taler-wallet-webextension/src/cta/TransferCreate/state.ts')
-rw-r--r-- | packages/taler-wallet-webextension/src/cta/TransferCreate/state.ts | 185 |
1 files changed, 185 insertions, 0 deletions
diff --git a/packages/taler-wallet-webextension/src/cta/TransferCreate/state.ts b/packages/taler-wallet-webextension/src/cta/TransferCreate/state.ts new file mode 100644 index 000000000..f092801ed --- /dev/null +++ b/packages/taler-wallet-webextension/src/cta/TransferCreate/state.ts @@ -0,0 +1,185 @@ +/* + 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 <http://www.gnu.org/licenses/> + */ + +import { + AmountString, + Amounts, + TalerErrorCode, + TalerProtocolTimestamp, +} from "@gnu-taler/taler-util"; +import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; +import { useTranslationContext } from "@gnu-taler/web-util/browser"; +import { isFuture, parse } from "date-fns"; +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 { BackgroundError, WxApiType } from "../../wxApi.js"; +import { Props, State } from "./index.js"; + +export function useComponentState({ + amount: amountStr, + onClose, + onSuccess, +}: Props): State { + const api = useBackendContext(); + const { pushAlertOnError } = useAlertContext(); + const amount = Amounts.parseOrThrow(amountStr); + const { i18n } = useTranslationContext(); + + const [subject, setSubject] = useState<string | undefined>(); + const [timestamp, setTimestamp] = useState<string | undefined>(); + + const hook = useAsyncAsHook(async () => { + const resp = await checkPeerPushDebitAndCheckMax(api, amountStr); + return resp; + }); + + if (!hook) { + return { + status: "loading", + error: undefined, + }; + } + if (hook.hasError) { + return { + status: "error", + error: alertFromError( + i18n, + i18n.str`Could not load the max amount to transfer`, + hook, + ), + }; + } + + const { amountEffective, amountRaw } = hook.response; + const debitAmount = Amounts.parseOrThrow(amountEffective); + const toBeReceived = Amounts.parseOrThrow(amountRaw); + + let purse_expiration: TalerProtocolTimestamp | undefined = undefined; + let timestampError: string | undefined = undefined; + + const t = + timestamp === undefined + ? undefined + : parse(timestamp, "dd/MM/yyyy", new Date()); + + if (t !== undefined) { + if (Number.isNaN(t.getTime())) { + timestampError = 'Should have the format "dd/MM/yyyy"'; + } else { + if (!isFuture(t)) { + timestampError = "Should be in the future"; + } else { + purse_expiration = { + t_s: t.getTime() / 1000, + }; + } + } + } + + async function accept(): Promise<void> { + if (!subject || !purse_expiration) return; + const resp = await api.wallet.call( + WalletApiOperation.InitiatePeerPushDebit, + { + partialContractTerms: { + summary: subject, + amount: amountStr, + purse_expiration, + }, + }, + ); + onSuccess(resp.transactionId); + } + + const unableToCreate = + !subject || Amounts.isZero(amount) || !purse_expiration; + + return { + status: "ready", + cancel: { + onClick: pushAlertOnError(onClose), + }, + subject: { + error: + subject === undefined + ? undefined + : !subject + ? "Can't be empty" + : undefined, + value: subject ?? "", + onInput: pushAlertOnError(async (e) => setSubject(e)), + }, + expiration: { + error: timestampError, + value: timestamp === undefined ? "" : timestamp, + onInput: pushAlertOnError(async (e) => { + setTimestamp(e); + }), + }, + create: { + onClick: unableToCreate ? undefined : pushAlertOnError(accept), + }, + debitAmount, + toBeReceived, + error: undefined, + }; +} + +async function checkPeerPushDebitAndCheckMax( + api: WxApiType, + amountState: AmountString, +) { + // FIXME : https://bugs.gnunet.org/view.php?id=7872 + try { + return await api.wallet.call(WalletApiOperation.CheckPeerPushDebit, { + amount: amountState, + }); + } catch (e) { + if (!(e instanceof BackgroundError)) { + throw e; + } + if ( + !e.hasErrorCode( + TalerErrorCode.WALLET_PEER_PUSH_PAYMENT_INSUFFICIENT_BALANCE, + ) + ) { + throw e; + } + const material = Amounts.parseOrThrow( + e.errorDetail.insufficientBalanceDetails.balanceMaterial, + ); + const amount = Amounts.parseOrThrow(amountState); + const gap = Amounts.sub( + amount, + Amounts.parseOrThrow( + e.errorDetail.insufficientBalanceDetails.maxEffectiveSpendAmount, + ), + ).amount; + const newAmount = Amounts.sub(material, gap).amount; + if (Amounts.cmp(newAmount, amount) === 0) { + //insufficient balance and the exception didn't give + //a good response that allow us to try again + throw e; + } + if (Amounts.cmp(newAmount, amount) === 1) { + //how can this happen? + throw e; + } + return checkPeerPushDebitAndCheckMax(api, Amounts.stringify(newAmount)); + } +} |