summaryrefslogtreecommitdiff
path: root/packages/taler-wallet-webextension/src/cta/TransferCreate/state.ts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/taler-wallet-webextension/src/cta/TransferCreate/state.ts')
-rw-r--r--packages/taler-wallet-webextension/src/cta/TransferCreate/state.ts185
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));
+ }
+}