diff options
Diffstat (limited to 'packages/taler-wallet-webextension/src/cta/Withdraw')
5 files changed, 120 insertions, 9 deletions
diff --git a/packages/taler-wallet-webextension/src/cta/Withdraw/index.ts b/packages/taler-wallet-webextension/src/cta/Withdraw/index.ts index 04713f3c4..1f8745a5d 100644 --- a/packages/taler-wallet-webextension/src/cta/Withdraw/index.ts +++ b/packages/taler-wallet-webextension/src/cta/Withdraw/index.ts @@ -38,7 +38,7 @@ import { ErrorAlertView } from "../../components/CurrentAlerts.js"; import { ErrorAlert } from "../../context/alert.js"; import { ExchangeSelectionPage } from "../../wallet/ExchangeSelection/index.js"; import { NoExchangesView } from "../../wallet/ExchangeSelection/views.js"; -import { SelectAmountView, SuccessView } from "./views.js"; +import { FinalStateOperation, SelectAmountView, SuccessView } from "./views.js"; export interface PropsFromURI { talerWithdrawUri: string | undefined; @@ -60,6 +60,7 @@ export type State = | SelectExchangeState.NoExchangeFound | SelectExchangeState.Selecting | State.SelectAmount + | State.AlreadyCompleted | State.Success; export namespace State { @@ -80,6 +81,12 @@ export namespace State { amount: AmountFieldHandler; currency: string; } + export interface AlreadyCompleted { + status: "already-completed"; + operationState: "confirmed" | "aborted" | "selected"; + confirmTransferUrl?: string, + error: undefined; + } export type Success = { status: "success"; @@ -116,6 +123,7 @@ const viewMapping: StateViewMap<State> = { "no-exchange-found": NoExchangesView, "selecting-exchange": ExchangeSelectionPage, success: SuccessView, + "already-completed": FinalStateOperation, }; export const WithdrawPageFromURI = compose( diff --git a/packages/taler-wallet-webextension/src/cta/Withdraw/state.ts b/packages/taler-wallet-webextension/src/cta/Withdraw/state.ts index 7bff13e51..bf460834d 100644 --- a/packages/taler-wallet-webextension/src/cta/Withdraw/state.ts +++ b/packages/taler-wallet-webextension/src/cta/Withdraw/state.ts @@ -21,11 +21,12 @@ import { ExchangeFullDetails, ExchangeListItem, ExchangeTosStatus, + NotificationType, TalerError, parseWithdrawExchangeUri, } from "@gnu-taler/taler-util"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; -import { useEffect, useState } from "preact/hooks"; +import { useEffect, useState, useMemo, useCallback } from "preact/hooks"; import { alertFromError, useAlertContext } from "../../context/alert.js"; import { useBackendContext } from "../../context/backend.js"; import { useTranslationContext } from "@gnu-taler/web-util/browser"; @@ -208,17 +209,40 @@ export function useComponentStateFromURI({ WalletApiOperation.GetWithdrawalDetailsForUri, { talerWithdrawUri, + notifyChangeFromPendingTimeoutMs: 30 * 1000 }, ); - const { amount, defaultExchangeBaseUrl, possibleExchanges } = uriInfo; + const { amount, defaultExchangeBaseUrl, possibleExchanges, operationId, confirmTransferUrl, status } = uriInfo; + const transaction = await api.wallet.call( + WalletApiOperation.GetWithdrawalTransactionByUri, + { talerWithdrawUri }, + ); return { talerWithdrawUri, + operationId, + status, + transaction, + confirmTransferUrl, amount: Amounts.parseOrThrow(amount), thisExchange: defaultExchangeBaseUrl, exchanges: possibleExchanges, }; }); + const readyToListen = uriInfoHook && !uriInfoHook.hasError + + useEffect(() => { + if (!uriInfoHook) { + return; + } + return api.listener.onUpdateNotification( + [NotificationType.WithdrawalOperationTransition], + () => { + uriInfoHook.retry() + }, + ); + }, [readyToListen]); + if (!uriInfoHook) return { status: "loading", error: undefined }; if (uriInfoHook.hasError) { @@ -257,8 +281,20 @@ export function useComponentStateFromURI({ }; } - return () => - exchangeSelectionState( + if (uriInfoHook.response.status !== "pending") { + if (uriInfoHook.response.transaction) { + onSuccess(uriInfoHook.response.transaction.transactionId) + } + return { + status: "already-completed", + operationState: uriInfoHook.response.status, + confirmTransferUrl: uriInfoHook.response.confirmTransferUrl, + error: undefined, + } + } + + return useCallback(() => { + return exchangeSelectionState( doManagedWithdraw, cancel, onSuccess, @@ -267,6 +303,7 @@ export function useComponentStateFromURI({ exchangeList, defaultExchange, ); + }, []) } type ManualOrManagedWithdrawFunction = ( @@ -294,7 +331,7 @@ function exchangeSelectionState( return selectedExchange; } - return (): State.Success | State.LoadingUriError | State.Loading => { + return useCallback((): State.Success | State.LoadingUriError | State.Loading => { const { i18n } = useTranslationContext(); const { pushAlertOnError } = useAlertContext(); const [ageRestricted, setAgeRestricted] = useState(0); @@ -428,5 +465,5 @@ function exchangeSelectionState( }, cancel, }; - }; + }, []); } diff --git a/packages/taler-wallet-webextension/src/cta/Withdraw/stories.tsx b/packages/taler-wallet-webextension/src/cta/Withdraw/stories.tsx index a3127fafc..29f39054f 100644 --- a/packages/taler-wallet-webextension/src/cta/Withdraw/stories.tsx +++ b/packages/taler-wallet-webextension/src/cta/Withdraw/stories.tsx @@ -23,7 +23,7 @@ import { CurrencySpecification, ExchangeListItem } from "@gnu-taler/taler-util"; import * as tests from "@gnu-taler/web-util/testing"; import { nullFunction } from "../../mui/handlers.js"; // import { TermsState } from "../../utils/index.js"; -import { SuccessView } from "./views.js"; +import { SuccessView, FinalStateOperation } from "./views.js"; export default { title: "withdraw", @@ -67,6 +67,23 @@ export const TermsOfServiceNotYetLoaded = tests.createExample(SuccessView, { chooseCurrencies: [], }); +export const AlreadyAborted = tests.createExample(FinalStateOperation, { + error: undefined, + status: "already-completed", + operationState: "aborted" +}); +export const AlreadySelected = tests.createExample(FinalStateOperation, { + error: undefined, + status: "already-completed", + operationState: "selected" +}); +export const AlreadyConfirmed = tests.createExample(FinalStateOperation, { + error: undefined, + status: "already-completed", + operationState: "confirmed" +}); + + export const WithSomeFee = tests.createExample(SuccessView, { error: undefined, status: "success", diff --git a/packages/taler-wallet-webextension/src/cta/Withdraw/test.ts b/packages/taler-wallet-webextension/src/cta/Withdraw/test.ts index 3493415d9..f90f7bed7 100644 --- a/packages/taler-wallet-webextension/src/cta/Withdraw/test.ts +++ b/packages/taler-wallet-webextension/src/cta/Withdraw/test.ts @@ -111,10 +111,19 @@ describe("Withdraw CTA states", () => { WalletApiOperation.GetWithdrawalDetailsForUri, undefined, { + status: "pending", + operationId: "123", amount: "EUR:2" as AmountString, possibleExchanges: [], }, ); + handler.addWalletCallResponse( + WalletApiOperation.GetWithdrawalTransactionByUri, + undefined, + { + transactionId: "123" + } as any, + ); const hookBehavior = await tests.hookBehaveLikeThis( useComponentStateFromURI, @@ -147,12 +156,21 @@ describe("Withdraw CTA states", () => { WalletApiOperation.GetWithdrawalDetailsForUri, undefined, { + status: "pending", + operationId: "123", amount: "ARS:2" as AmountString, possibleExchanges: exchanges, defaultExchangeBaseUrl: exchanges[0].exchangeBaseUrl, }, ); handler.addWalletCallResponse( + WalletApiOperation.GetWithdrawalTransactionByUri, + undefined, + { + transactionId: "123" + } as any, + ); + handler.addWalletCallResponse( WalletApiOperation.GetWithdrawalDetailsForAmount, undefined, { @@ -217,6 +235,8 @@ describe("Withdraw CTA states", () => { WalletApiOperation.GetWithdrawalDetailsForUri, undefined, { + status: "pending", + operationId: "123", amount: "ARS:2" as AmountString, possibleExchanges: exchangeWithNewTos, defaultExchangeBaseUrl: exchangeWithNewTos[0].exchangeBaseUrl, @@ -245,6 +265,8 @@ describe("Withdraw CTA states", () => { WalletApiOperation.GetWithdrawalDetailsForUri, undefined, { + status: "pending", + operationId: "123", amount: "ARS:2" as AmountString, possibleExchanges: exchanges, defaultExchangeBaseUrl: exchanges[0].exchangeBaseUrl, diff --git a/packages/taler-wallet-webextension/src/cta/Withdraw/views.tsx b/packages/taler-wallet-webextension/src/cta/Withdraw/views.tsx index 748b65817..bd9f75696 100644 --- a/packages/taler-wallet-webextension/src/cta/Withdraw/views.tsx +++ b/packages/taler-wallet-webextension/src/cta/Withdraw/views.tsx @@ -21,7 +21,7 @@ import { Amount } from "../../components/Amount.js"; import { Part } from "../../components/Part.js"; import { QR } from "../../components/QR.js"; import { SelectList } from "../../components/SelectList.js"; -import { Input, LinkSuccess, SvgIcon } from "../../components/styled/index.js"; +import { Input, LinkSuccess, SvgIcon, WarningBox } from "../../components/styled/index.js"; import { TermsOfService } from "../../components/TermsOfService/index.js"; import { useTranslationContext } from "@gnu-taler/web-util/browser"; import { Button } from "../../mui/Button.js"; @@ -35,6 +35,33 @@ import { State } from "./index.js"; import { Grid } from "../../mui/Grid.js"; import { AmountField } from "../../components/AmountField.js"; +export function FinalStateOperation(state: State.AlreadyCompleted): VNode { + const { i18n } = useTranslationContext(); + + switch (state.operationState) { + case "confirmed": return <WarningBox> + <div style={{ justifyContent: "center", lineHeight: "25px" }}> + <i18n.Translate>This operation has already been completed by another wallet.</i18n.Translate> + </div> + </WarningBox> + case "aborted": return <WarningBox> + <div style={{ justifyContent: "center", lineHeight: "25px" }}> + <i18n.Translate>This operation has already been aborted</i18n.Translate> + </div> + </WarningBox> + case "selected": return <WarningBox> + <div style={{ justifyContent: "center", lineHeight: "25px" }}> + <i18n.Translate>This operation has already been used by another wallet.</i18n.Translate> + </div> + <div style={{ justifyContent: "center", lineHeight: "25px" }}> + <i18n.Translate>It can be confirmed in</i18n.Translate> <a target="_bank" rel="noreferrer" href={state.confirmTransferUrl}> + <i18n.Translate>this page</i18n.Translate> + </a> + </div> + </WarningBox> + } +} + export function SuccessView(state: State.Success): VNode { const { i18n } = useTranslationContext(); // const currentTosVersionIsAccepted = |