summaryrefslogtreecommitdiff
path: root/packages/taler-wallet-webextension/src/cta/InvoiceCreate/state.ts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/taler-wallet-webextension/src/cta/InvoiceCreate/state.ts')
-rw-r--r--packages/taler-wallet-webextension/src/cta/InvoiceCreate/state.ts196
1 files changed, 196 insertions, 0 deletions
diff --git a/packages/taler-wallet-webextension/src/cta/InvoiceCreate/state.ts b/packages/taler-wallet-webextension/src/cta/InvoiceCreate/state.ts
new file mode 100644
index 000000000..daa3ee76d
--- /dev/null
+++ b/packages/taler-wallet-webextension/src/cta/InvoiceCreate/state.ts
@@ -0,0 +1,196 @@
+/*
+ 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/>
+ */
+
+/* eslint-disable react-hooks/rules-of-hooks */
+import { Amounts, TalerProtocolTimestamp } from "@gnu-taler/taler-util";
+import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
+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 { useTranslationContext } from "@gnu-taler/web-util/browser";
+import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js";
+import { useSelectedExchange } from "../../hooks/useSelectedExchange.js";
+import { RecursiveState } from "../../utils/index.js";
+import { Props, State } from "./index.js";
+
+export function useComponentState({
+ amount: amountStr,
+ onClose,
+ onSuccess,
+}: Props): RecursiveState<State> {
+ const amount = Amounts.parseOrThrow(amountStr);
+ const api = useBackendContext();
+
+ const hook = useAsyncAsHook(() =>
+ api.wallet.call(WalletApiOperation.ListExchanges, {}),
+ );
+ const { i18n } = useTranslationContext();
+
+ if (!hook) {
+ return {
+ status: "loading",
+ error: undefined,
+ };
+ }
+ if (hook.hasError) {
+ return {
+ status: "error",
+ error: alertFromError(
+ i18n,
+ i18n.str`Could not load the list of exchanges`,
+ hook,
+ ),
+ };
+ }
+ // if (hook.hasError) {
+ // return {
+ // status: "loading-uri",
+ // error: hook,
+ // };
+ // }
+
+ const exchangeList = hook.response.exchanges;
+
+ return () => {
+ const [subject, setSubject] = useState<string | undefined>();
+ const [timestamp, setTimestamp] = useState<string | undefined>();
+ const { pushAlertOnError } = useAlertContext();
+
+ const selectedExchange = useSelectedExchange({
+ currency: amount.currency,
+ defaultExchange: undefined,
+ list: exchangeList,
+ });
+
+ if (selectedExchange.status !== "ready") {
+ return selectedExchange;
+ }
+
+ const exchange = selectedExchange.selected;
+
+ const hook = useAsyncAsHook(async () => {
+ const resp = await api.wallet.call(
+ WalletApiOperation.CheckPeerPullCredit,
+ {
+ amount: amountStr,
+ exchangeBaseUrl: exchange.exchangeBaseUrl,
+ },
+ );
+ return resp;
+ });
+
+ if (!hook) {
+ return {
+ status: "loading",
+ error: undefined,
+ };
+ }
+
+ if (hook.hasError) {
+ return {
+ status: "error",
+ error: alertFromError(
+ i18n,
+ i18n.str`Could not load the invoice status`,
+ hook,
+ ),
+ };
+ // return {
+ // status: "loading-uri",
+ // error: hook,
+ // };
+ }
+
+ const { amountEffective, amountRaw } = hook.response;
+ const requestAmount = Amounts.parseOrThrow(amountRaw);
+ const toBeReceived = Amounts.parseOrThrow(amountEffective);
+
+ 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.InitiatePeerPullCredit,
+ {
+ exchangeBaseUrl: exchange.exchangeBaseUrl,
+ partialContractTerms: {
+ amount: Amounts.stringify(amount),
+ summary: subject,
+ purse_expiration,
+ },
+ },
+ );
+
+ onSuccess(resp.transactionId);
+ }
+ const unableToCreate =
+ !subject || Amounts.isZero(amount) || !purse_expiration;
+
+ return {
+ status: "ready",
+ 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);
+ }),
+ },
+ doSelectExchange: selectedExchange.doSelect,
+ exchangeUrl: exchange.exchangeBaseUrl,
+ create: {
+ onClick: unableToCreate ? undefined : pushAlertOnError(accept),
+ },
+ cancel: {
+ onClick: pushAlertOnError(onClose),
+ },
+ requestAmount,
+ toBeReceived,
+ error: undefined,
+ };
+ };
+}