cherry-pick: using new wallet typed api missing test

This commit is contained in:
Sebastian 2022-10-25 12:23:08 -03:00
parent 5fcd434f66
commit a286649b0a
No known key found for this signature in database
GPG Key ID: BE4FF68352439FC1
64 changed files with 1608 additions and 1938 deletions

View File

@ -32,26 +32,24 @@ import {
codecForAmountJson, codecForAmountJson,
codecForAmountString, codecForAmountString,
} from "./amounts.js"; } from "./amounts.js";
import { import { BackupRecovery } from "./backup-types.js";
AbsoluteTime,
codecForAbsoluteTime,
codecForTimestamp,
TalerProtocolDuration,
TalerProtocolTimestamp,
} from "./time.js";
import { import {
buildCodecForObject, buildCodecForObject,
codecForString, buildCodecForUnion,
codecOptional,
Codec, Codec,
codecForList, codecForAny,
codecForBoolean, codecForBoolean,
codecForConstString, codecForConstString,
codecForAny, codecForList,
buildCodecForUnion,
codecForNumber,
codecForMap, codecForMap,
codecForNumber,
codecForString,
codecOptional,
} from "./codec.js"; } from "./codec.js";
import { VersionMatchResult } from "./libtool-version.js";
import { PaytoUri } from "./payto.js";
import { AgeCommitmentProof } from "./taler-crypto.js";
import { TalerErrorCode } from "./taler-error-codes.js";
import { import {
AmountString, AmountString,
AuditorDenomSig, AuditorDenomSig,
@ -64,14 +62,16 @@ import {
UnblindedSignature, UnblindedSignature,
} from "./taler-types.js"; } from "./taler-types.js";
import { import {
OrderShortInfo, AbsoluteTime,
codecForAbsoluteTime,
codecForTimestamp,
TalerProtocolDuration,
TalerProtocolTimestamp,
} from "./time.js";
import {
codecForOrderShortInfo, codecForOrderShortInfo,
OrderShortInfo,
} from "./transactions-types.js"; } from "./transactions-types.js";
import { BackupRecovery } from "./backup-types.js";
import { PaytoUri } from "./payto.js";
import { TalerErrorCode } from "./taler-error-codes.js";
import { AgeCommitmentProof } from "./taler-crypto.js";
import { VersionMatchResult } from "./libtool-version.js";
/** /**
* Identifier for a transaction in the wallet. * Identifier for a transaction in the wallet.

View File

@ -35,6 +35,7 @@ import {
AcceptWithdrawalResponse, AcceptWithdrawalResponse,
AddExchangeRequest, AddExchangeRequest,
AddKnownBankAccountsRequest, AddKnownBankAccountsRequest,
ApplyDevExperimentRequest,
ApplyRefundFromPurchaseIdRequest, ApplyRefundFromPurchaseIdRequest,
ApplyRefundRequest, ApplyRefundRequest,
ApplyRefundResponse, ApplyRefundResponse,
@ -98,12 +99,12 @@ import {
WithdrawTestBalanceRequest, WithdrawTestBalanceRequest,
WithdrawUriInfoResponse, WithdrawUriInfoResponse,
} from "@gnu-taler/taler-util"; } from "@gnu-taler/taler-util";
import { ApplyDevExperimentRequest } from "@gnu-taler/taler-util";
import { WalletContractData } from "./db.js"; import { WalletContractData } from "./db.js";
import { import {
AddBackupProviderRequest, AddBackupProviderRequest,
BackupInfo, BackupInfo,
RemoveBackupProviderRequest, RemoveBackupProviderRequest,
RunBackupCycleRequest,
} from "./operations/backup/index.js"; } from "./operations/backup/index.js";
import { PendingOperationsResponse as PendingTasksResponse } from "./pending-types.js"; import { PendingOperationsResponse as PendingTasksResponse } from "./pending-types.js";
@ -496,7 +497,7 @@ export type ImportBackupRecoveryOp = {
*/ */
export type RunBackupCycleOp = { export type RunBackupCycleOp = {
op: WalletApiOperation.RunBackupCycle; op: WalletApiOperation.RunBackupCycle;
request: EmptyObject; request: RunBackupCycleRequest;
response: EmptyObject; response: EmptyObject;
}; };

View File

@ -78,7 +78,7 @@ import {
Duration, Duration,
durationFromSpec, durationFromSpec,
durationMin, durationMin,
ExchangeFullDetails, ExchangeDetailedResponse,
ExchangeListItem, ExchangeListItem,
ExchangesListResponse, ExchangesListResponse,
ExchangeTosStatusDetails, ExchangeTosStatusDetails,
@ -664,7 +664,7 @@ async function getExchanges(
async function getExchangeDetailedInfo( async function getExchangeDetailedInfo(
ws: InternalWalletState, ws: InternalWalletState,
exchangeBaseurl: string, exchangeBaseurl: string,
): Promise<ExchangeFullDetails> { ): Promise<ExchangeDetailedResponse> {
//TODO: should we use the forceUpdate parameter? //TODO: should we use the forceUpdate parameter?
const exchange = await ws.db const exchange = await ws.db
.mktx((x) => [ .mktx((x) => [
@ -819,10 +819,12 @@ async function getExchangeDetailedInfo(
); );
return { return {
exchange: {
...exchange.info, ...exchange.info,
denomFees, denomFees,
transferFees, transferFees,
globalFees, globalFees,
},
}; };
} }

View File

@ -9,7 +9,7 @@
"private": false, "private": false,
"scripts": { "scripts": {
"clean": "rimraf dist lib tsconfig.tsbuildinfo", "clean": "rimraf dist lib tsconfig.tsbuildinfo",
"test": "pnpm compile && mocha --enable-source-maps 'dist/**/*.test.js' 'dist/**/test.js'", "test": "pnpm compile && mocha 'dist/**/*.test.js' 'dist/**/test.js'",
"test:coverage": "nyc pnpm test", "test:coverage": "nyc pnpm test",
"compile": "tsc && ./build-fast-with-linaria.mjs", "compile": "tsc && ./build-fast-with-linaria.mjs",
"prepare": "pnpm compile", "prepare": "pnpm compile",

View File

@ -19,13 +19,14 @@ import {
NotificationType, NotificationType,
Transaction, Transaction,
} from "@gnu-taler/taler-util"; } from "@gnu-taler/taler-util";
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { Fragment, h, JSX, VNode } from "preact"; import { Fragment, h, JSX, VNode } from "preact";
import { useEffect } from "preact/hooks"; import { useEffect } from "preact/hooks";
import { useTranslationContext } from "../context/translation.js"; import { useTranslationContext } from "../context/translation.js";
import { useAsyncAsHook } from "../hooks/useAsyncAsHook.js"; import { useAsyncAsHook } from "../hooks/useAsyncAsHook.js";
import { Avatar } from "../mui/Avatar.js"; import { Avatar } from "../mui/Avatar.js";
import { Typography } from "../mui/Typography.js"; import { Typography } from "../mui/Typography.js";
import * as wxApi from "../wxApi.js"; import { wxApi } from "../wxApi.js";
import Banner from "./Banner.js"; import Banner from "./Banner.js";
import { Time } from "./Time.js"; import { Time } from "./Time.js";
@ -34,14 +35,14 @@ interface Props extends JSX.HTMLAttributes {
} }
export function PendingTransactions({ goToTransaction }: Props): VNode { export function PendingTransactions({ goToTransaction }: Props): VNode {
const state = useAsyncAsHook(wxApi.getTransactions); const state = useAsyncAsHook(() =>
wxApi.wallet.call(WalletApiOperation.GetTransactions, {}),
);
useEffect(() => { useEffect(() => {
return wxApi.onUpdateNotification( return wxApi.listener.onUpdateNotification(
[NotificationType.WithdrawGroupFinished], [NotificationType.WithdrawGroupFinished],
() => { state?.retry,
state?.retry();
},
); );
}); });

View File

@ -14,7 +14,10 @@
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/ */
import { AbsoluteTime, Duration, Location } from "@gnu-taler/taler-util"; import { AbsoluteTime, Duration, Location } from "@gnu-taler/taler-util";
import { WalletContractData } from "@gnu-taler/taler-wallet-core"; import {
WalletApiOperation,
WalletContractData,
} from "@gnu-taler/taler-wallet-core";
import { styled } from "@linaria/react"; import { styled } from "@linaria/react";
import { Fragment, h, VNode } from "preact"; import { Fragment, h, VNode } from "preact";
import { useState } from "preact/hooks"; import { useState } from "preact/hooks";
@ -26,9 +29,9 @@ import { useTranslationContext } from "../context/translation.js";
import { HookError, useAsyncAsHook } from "../hooks/useAsyncAsHook.js"; import { HookError, useAsyncAsHook } from "../hooks/useAsyncAsHook.js";
import { ButtonHandler } from "../mui/handlers.js"; import { ButtonHandler } from "../mui/handlers.js";
import { compose, StateViewMap } from "../utils/index.js"; import { compose, StateViewMap } from "../utils/index.js";
import * as wxApi from "../wxApi.js"; import { wxApi } from "../wxApi.js";
import { Amount } from "./Amount.js"; import { Amount } from "./Amount.js";
import { Link, LinkPrimary } from "./styled/index.js"; import { Link } from "./styled/index.js";
const ContractTermsTable = styled.table` const ContractTermsTable = styled.table`
width: 100%; width: 100%;
@ -99,7 +102,9 @@ function useComponentState({ proposalId }: Props, api: typeof wxApi): State {
const [show, setShow] = useState(false); const [show, setShow] = useState(false);
const hook = useAsyncAsHook(async () => { const hook = useAsyncAsHook(async () => {
if (!show) return undefined; if (!show) return undefined;
return await api.getContractTermsDetails(proposalId); return await api.wallet.call(WalletApiOperation.GetContractTermsDetails, {
proposalId,
});
}, [show]); }, [show]);
const hideHandler = { const hideHandler = {

View File

@ -18,7 +18,7 @@ import { Loading } from "../../components/Loading.js";
import { HookError } from "../../hooks/useAsyncAsHook.js"; import { HookError } from "../../hooks/useAsyncAsHook.js";
import { ToggleHandler } from "../../mui/handlers.js"; import { ToggleHandler } from "../../mui/handlers.js";
import { compose, StateViewMap } from "../../utils/index.js"; import { compose, StateViewMap } from "../../utils/index.js";
import * as wxApi from "../../wxApi.js"; import { wxApi } from "../../wxApi.js";
import { useComponentState } from "./state.js"; import { useComponentState } from "./state.js";
import { TermsState } from "./utils.js"; import { TermsState } from "./utils.js";
import { import {
@ -26,7 +26,7 @@ import {
LoadingUriView, LoadingUriView,
ShowButtonsAcceptedTosView, ShowButtonsAcceptedTosView,
ShowButtonsNonAcceptedTosView, ShowButtonsNonAcceptedTosView,
ShowTosContentView, ShowTosContentView
} from "./views.js"; } from "./views.js";
export interface Props { export interface Props {

View File

@ -14,9 +14,10 @@
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/ */
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { useState } from "preact/hooks"; import { useState } from "preact/hooks";
import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js"; import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js";
import * as wxApi from "../../wxApi.js"; import { wxApi } from "../../wxApi.js";
import { Props, State } from "./index.js"; import { Props, State } from "./index.js";
import { buildTermsOfServiceState } from "./utils.js"; import { buildTermsOfServiceState } from "./utils.js";
@ -34,7 +35,10 @@ export function useComponentState(
* For the exchange selected, bring the status of the terms of service * For the exchange selected, bring the status of the terms of service
*/ */
const terms = useAsyncAsHook(async () => { const terms = useAsyncAsHook(async () => {
const exchangeTos = await api.getExchangeTos(exchangeUrl, ["text/xml"]); const exchangeTos = await api.wallet.call(WalletApiOperation.GetExchangeTos, {
exchangeBaseUrl: exchangeUrl,
acceptedFormat: ["text/xml"]
})
const state = buildTermsOfServiceState(exchangeTos); const state = buildTermsOfServiceState(exchangeTos);
@ -72,10 +76,16 @@ export function useComponentState(
try { try {
if (accepted) { if (accepted) {
await api.setExchangeTosAccepted(exchangeUrl, state.version); api.wallet.call(WalletApiOperation.SetExchangeTosAccepted, {
exchangeBaseUrl: exchangeUrl,
etag: state.version
})
} else { } else {
// mark as not accepted // mark as not accepted
await api.setExchangeTosAccepted(exchangeUrl, undefined); api.wallet.call(WalletApiOperation.SetExchangeTosAccepted, {
exchangeBaseUrl: exchangeUrl,
etag: undefined
})
} }
// setAccepted(accepted); // setAccepted(accepted);
if (!readOnly) onChange(accepted); //external update if (!readOnly) onChange(accepted); //external update

View File

@ -19,7 +19,7 @@ import { Loading } from "../../components/Loading.js";
import { HookError } from "../../hooks/useAsyncAsHook.js"; import { HookError } from "../../hooks/useAsyncAsHook.js";
import { ButtonHandler } from "../../mui/handlers.js"; import { ButtonHandler } from "../../mui/handlers.js";
import { compose, StateViewMap } from "../../utils/index.js"; import { compose, StateViewMap } from "../../utils/index.js";
import * as wxApi from "../../wxApi.js"; import { wxApi } from "../../wxApi.js";
import { useComponentState } from "./state.js"; import { useComponentState } from "./state.js";
import { LoadingUriView, ReadyView } from "./views.js"; import { LoadingUriView, ReadyView } from "./views.js";

View File

@ -14,10 +14,10 @@
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/ */
import { Amounts, CreateDepositGroupResponse } from "@gnu-taler/taler-util"; import { Amounts } from "@gnu-taler/taler-util";
import { useState } from "preact/hooks"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js"; import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js";
import * as wxApi from "../../wxApi.js"; import { wxApi } from "../../wxApi.js";
import { Props, State } from "./index.js"; import { Props, State } from "./index.js";
export function useComponentState( export function useComponentState(
@ -29,10 +29,10 @@ export function useComponentState(
if (!amountStr) throw Error("ERROR_NO-AMOUNT-FOR-DEPOSIT"); if (!amountStr) throw Error("ERROR_NO-AMOUNT-FOR-DEPOSIT");
const amount = Amounts.parse(amountStr); const amount = Amounts.parse(amountStr);
if (!amount) throw Error("ERROR_INVALID-AMOUNT-FOR-DEPOSIT"); if (!amount) throw Error("ERROR_INVALID-AMOUNT-FOR-DEPOSIT");
const deposit = await api.prepareDeposit( const deposit = await api.wallet.call(WalletApiOperation.PrepareDeposit, {
talerDepositUri, amount: Amounts.stringify(amount),
Amounts.stringify(amount), depositPaytoUri: talerDepositUri,
); });
return { deposit, uri: talerDepositUri, amount }; return { deposit, uri: talerDepositUri, amount };
}); });
@ -46,7 +46,10 @@ export function useComponentState(
const { deposit, uri, amount } = info.response; const { deposit, uri, amount } = info.response;
async function doDeposit(): Promise<void> { async function doDeposit(): Promise<void> {
const resp = await api.createDepositGroup(uri, Amounts.stringify(amount)); const resp = await api.wallet.call(WalletApiOperation.CreateDepositGroup, {
amount: Amounts.stringify(amount),
depositPaytoUri: uri,
});
onSuccess(resp.transactionId); onSuccess(resp.transactionId);
} }

View File

@ -19,17 +19,17 @@
* @author Sebastian Javier Marchano (sebasjm) * @author Sebastian Javier Marchano (sebasjm)
*/ */
import { Amounts, PrepareDepositResponse } from "@gnu-taler/taler-util"; import { Amounts } from "@gnu-taler/taler-util";
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { expect } from "chai"; import { expect } from "chai";
import { mountHook } from "../../test-utils.js"; import { mountHook } from "../../test-utils.js";
import { createWalletApiMock } from "../../test-utils.js";
import { useComponentState } from "./state.js"; import { useComponentState } from "./state.js";
describe("Deposit CTA states", () => { describe("Deposit CTA states", () => {
it("should tell the user that the URI is missing", async () => { it("should tell the user that the URI is missing", async () => {
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = const { handler, mock } = createWalletApiMock();
mountHook(() => const props = {
useComponentState(
{
talerDepositUri: undefined, talerDepositUri: undefined,
amountStr: undefined, amountStr: undefined,
cancel: async () => { cancel: async () => {
@ -38,24 +38,21 @@ describe("Deposit CTA states", () => {
onSuccess: async () => { onSuccess: async () => {
null; null;
}, },
}, }
{ const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } =
prepareRefund: async () => ({}), mountHook(() =>
applyRefund: async () => ({}), useComponentState(props, mock),
onUpdateNotification: async () => ({}),
} as any,
),
); );
{ {
const { status } = getLastResultOrThrow(); const { status } = pullLastResultOrThrow();
expect(status).equals("loading"); expect(status).equals("loading");
} }
await waitNextUpdate(); expect(await waitForStateUpdate()).true;
{ {
const { status, error } = getLastResultOrThrow(); const { status, error } = pullLastResultOrThrow();
expect(status).equals("loading-uri"); expect(status).equals("loading-uri");
@ -64,15 +61,17 @@ describe("Deposit CTA states", () => {
if (error.operational) expect.fail(); if (error.operational) expect.fail();
expect(error.message).eq("ERROR_NO-URI-FOR-DEPOSIT"); expect(error.message).eq("ERROR_NO-URI-FOR-DEPOSIT");
} }
await assertNoPendingUpdate(); await assertNoPendingUpdate();
expect(handler.getCallingQueueState()).eq("empty")
}); });
it("should be ready after loading", async () => { it("should be ready after loading", async () => {
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = const { handler, mock } = createWalletApiMock();
mountHook(() => handler.addWalletCallResponse(WalletApiOperation.PrepareDeposit, undefined, {
useComponentState( effectiveDepositAmount: Amounts.parseOrThrow("EUR:1"),
{ totalDepositCost: Amounts.parseOrThrow("EUR:1.2"),
});
const props = {
talerDepositUri: "payto://refund/asdasdas", talerDepositUri: "payto://refund/asdasdas",
amountStr: "EUR:1", amountStr: "EUR:1",
cancel: async () => { cancel: async () => {
@ -81,27 +80,22 @@ describe("Deposit CTA states", () => {
onSuccess: async () => { onSuccess: async () => {
null; null;
}, },
}, }
{
prepareDeposit: async () => const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } =
({ mountHook(() =>
effectiveDepositAmount: Amounts.parseOrThrow("EUR:1"), useComponentState(props, mock),
totalDepositCost: Amounts.parseOrThrow("EUR:1.2"),
} as PrepareDepositResponse as any),
createDepositGroup: async () => ({}),
} as any,
),
); );
{ {
const { status } = getLastResultOrThrow(); const { status } = pullLastResultOrThrow();
expect(status).equals("loading"); expect(status).equals("loading");
} }
await waitNextUpdate(); expect(await waitForStateUpdate()).true;
{ {
const state = getLastResultOrThrow(); const state = pullLastResultOrThrow();
if (state.status !== "ready") expect.fail(); if (state.status !== "ready") expect.fail();
if (state.error) expect.fail(); if (state.error) expect.fail();
@ -112,5 +106,6 @@ describe("Deposit CTA states", () => {
} }
await assertNoPendingUpdate(); await assertNoPendingUpdate();
expect(handler.getCallingQueueState()).eq("empty");
}); });
}); });

View File

@ -22,7 +22,7 @@ import { ButtonHandler, TextFieldHandler } from "../../mui/handlers.js";
import { compose, StateViewMap } from "../../utils/index.js"; import { compose, StateViewMap } from "../../utils/index.js";
import { ExchangeSelectionPage } from "../../wallet/ExchangeSelection/index.js"; import { ExchangeSelectionPage } from "../../wallet/ExchangeSelection/index.js";
import { NoExchangesView } from "../../wallet/ExchangeSelection/views.js"; import { NoExchangesView } from "../../wallet/ExchangeSelection/views.js";
import * as wxApi from "../../wxApi.js"; import { wxApi } from "../../wxApi.js";
import { useComponentState } from "./state.js"; import { useComponentState } from "./state.js";
import { LoadingUriView, ReadyView } from "./views.js"; import { LoadingUriView, ReadyView } from "./views.js";

View File

@ -16,11 +16,11 @@
/* eslint-disable react-hooks/rules-of-hooks */ /* eslint-disable react-hooks/rules-of-hooks */
import { Amounts, TalerErrorDetail } from "@gnu-taler/taler-util"; import { Amounts, TalerErrorDetail } from "@gnu-taler/taler-util";
import { TalerError } from "@gnu-taler/taler-wallet-core"; import { TalerError, WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { useState } from "preact/hooks"; import { useState } from "preact/hooks";
import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js"; import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js";
import { useSelectedExchange } from "../../hooks/useSelectedExchange.js"; import { useSelectedExchange } from "../../hooks/useSelectedExchange.js";
import * as wxApi from "../../wxApi.js"; import { wxApi } from "../../wxApi.js";
import { Props, State } from "./index.js"; import { Props, State } from "./index.js";
type RecursiveState<S extends object> = S | (() => RecursiveState<S>); type RecursiveState<S extends object> = S | (() => RecursiveState<S>);
@ -31,7 +31,7 @@ export function useComponentState(
): RecursiveState<State> { ): RecursiveState<State> {
const amount = Amounts.parseOrThrow(amountStr); const amount = Amounts.parseOrThrow(amountStr);
const hook = useAsyncAsHook(api.listExchanges); const hook = useAsyncAsHook(() => api.wallet.call(WalletApiOperation.ListExchanges, {}));
if (!hook) { if (!hook) {
return { return {
@ -69,7 +69,7 @@ export function useComponentState(
async function accept(): Promise<void> { async function accept(): Promise<void> {
try { try {
const resp = await api.initiatePeerPullPayment({ const resp = await api.wallet.call(WalletApiOperation.InitiatePeerPullPayment, {
amount: Amounts.stringify(amount), amount: Amounts.stringify(amount),
exchangeBaseUrl: exchange.exchangeBaseUrl, exchangeBaseUrl: exchange.exchangeBaseUrl,
partialContractTerms: { partialContractTerms: {

View File

@ -18,13 +18,13 @@ import {
AbsoluteTime, AbsoluteTime,
AmountJson, AmountJson,
PreparePayResult, PreparePayResult,
TalerErrorDetail, TalerErrorDetail
} from "@gnu-taler/taler-util"; } from "@gnu-taler/taler-util";
import { Loading } from "../../components/Loading.js"; import { Loading } from "../../components/Loading.js";
import { HookError } from "../../hooks/useAsyncAsHook.js"; import { HookError } from "../../hooks/useAsyncAsHook.js";
import { ButtonHandler } from "../../mui/handlers.js"; import { ButtonHandler } from "../../mui/handlers.js";
import { compose, StateViewMap } from "../../utils/index.js"; import { compose, StateViewMap } from "../../utils/index.js";
import * as wxApi from "../../wxApi.js"; import { wxApi } from "../../wxApi.js";
import { useComponentState } from "./state.js"; import { useComponentState } from "./state.js";
import { LoadingUriView, ReadyView } from "./views.js"; import { LoadingUriView, ReadyView } from "./views.js";

View File

@ -21,12 +21,12 @@ import {
PreparePayResult, PreparePayResult,
PreparePayResultType, PreparePayResultType,
TalerErrorDetail, TalerErrorDetail,
TalerProtocolTimestamp, TalerProtocolTimestamp
} from "@gnu-taler/taler-util"; } from "@gnu-taler/taler-util";
import { TalerError } from "@gnu-taler/taler-wallet-core"; import { TalerError, WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { useEffect, useState } from "preact/hooks"; import { useEffect, useState } from "preact/hooks";
import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js"; import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js";
import * as wxApi from "../../wxApi.js"; import { wxApi } from "../../wxApi.js";
import { Props, State } from "./index.js"; import { Props, State } from "./index.js";
export function useComponentState( export function useComponentState(
@ -34,18 +34,17 @@ export function useComponentState(
api: typeof wxApi, api: typeof wxApi,
): State { ): State {
const hook = useAsyncAsHook(async () => { const hook = useAsyncAsHook(async () => {
const p2p = await api.checkPeerPullPayment({ const p2p = await api.wallet.call(WalletApiOperation.CheckPeerPullPayment, {
talerUri: talerPayPullUri, talerUri: talerPayPullUri,
}); });
const balance = await api.getBalance(); const balance = await api.wallet.call(WalletApiOperation.GetBalances, {});
return { p2p, balance }; return { p2p, balance };
}); });
useEffect(() => { useEffect(() => api.listener.onUpdateNotification(
api.onUpdateNotification([NotificationType.CoinWithdrawn], () => { [NotificationType.CoinWithdrawn],
hook?.retry(); hook?.retry
}); ));
});
const [operationError, setOperationError] = useState< const [operationError, setOperationError] = useState<
TalerErrorDetail | undefined TalerErrorDetail | undefined
@ -64,10 +63,7 @@ export function useComponentState(
}; };
} }
// const { payStatus } = hook.response.p2p;
const { const {
amount: purseAmount,
contractTerms, contractTerms,
peerPullPaymentIncomingId, peerPullPaymentIncomingId,
} = hook.response.p2p; } = hook.response.p2p;
@ -136,17 +132,9 @@ export function useComponentState(
}; };
} }
// if (payStatus.status === PreparePayResultType.AlreadyConfirmed) {
// return {
// status: "confirmed",
// balance: foundAmount,
// ...baseResult,
// };
// }
async function accept(): Promise<void> { async function accept(): Promise<void> {
try { try {
const resp = await api.acceptPeerPullPayment({ const resp = await api.wallet.call(WalletApiOperation.AcceptPeerPullPayment, {
peerPullPaymentIncomingId, peerPullPaymentIncomingId,
}); });
onSuccess(resp.transactionId); onSuccess(resp.transactionId);

View File

@ -15,21 +15,16 @@
*/ */
import { import {
AmountJson, AmountJson, PreparePayResult,
ConfirmPayResult, PreparePayResultAlreadyConfirmed, PreparePayResultPaymentPossible
PreparePayResult,
PreparePayResultAlreadyConfirmed,
PreparePayResultInsufficientBalance,
PreparePayResultPaymentPossible,
} from "@gnu-taler/taler-util"; } from "@gnu-taler/taler-util";
import { TalerError } from "@gnu-taler/taler-wallet-core";
import { Loading } from "../../components/Loading.js"; import { Loading } from "../../components/Loading.js";
import { HookError } from "../../hooks/useAsyncAsHook.js"; import { HookError } from "../../hooks/useAsyncAsHook.js";
import { ButtonHandler } from "../../mui/handlers.js"; import { ButtonHandler } from "../../mui/handlers.js";
import { compose, StateViewMap } from "../../utils/index.js"; import { compose, StateViewMap } from "../../utils/index.js";
import * as wxApi from "../../wxApi.js"; import { wxApi } from "../../wxApi.js";
import { useComponentState } from "./state.js"; import { useComponentState } from "./state.js";
import { LoadingUriView, BaseView } from "./views.js"; import { BaseView, LoadingUriView } from "./views.js";
export interface Props { export interface Props {
talerPayUri?: string; talerPayUri?: string;

View File

@ -15,19 +15,16 @@
*/ */
import { import {
AmountJson, Amounts, ConfirmPayResultType,
Amounts,
ConfirmPayResult,
ConfirmPayResultType,
NotificationType, NotificationType,
PreparePayResultType, PreparePayResultType,
TalerErrorCode, TalerErrorCode
} from "@gnu-taler/taler-util"; } from "@gnu-taler/taler-util";
import { TalerError } from "@gnu-taler/taler-wallet-core"; import { TalerError, WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { useEffect, useState } from "preact/hooks"; import { useEffect, useState } from "preact/hooks";
import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js"; import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js";
import { ButtonHandler } from "../../mui/handlers.js"; import { ButtonHandler } from "../../mui/handlers.js";
import * as wxApi from "../../wxApi.js"; import { wxApi } from "../../wxApi.js";
import { Props, State } from "./index.js"; import { Props, State } from "./index.js";
export function useComponentState( export function useComponentState(
@ -38,16 +35,17 @@ export function useComponentState(
const hook = useAsyncAsHook(async () => { const hook = useAsyncAsHook(async () => {
if (!talerPayUri) throw Error("ERROR_NO-URI-FOR-PAYMENT"); if (!talerPayUri) throw Error("ERROR_NO-URI-FOR-PAYMENT");
const payStatus = await api.preparePay(talerPayUri); const payStatus = await api.wallet.call(WalletApiOperation.PreparePayForUri, {
const balance = await api.getBalance(); talerPayUri: talerPayUri
});
const balance = await api.wallet.call(WalletApiOperation.GetBalances, {});
return { payStatus, balance, uri: talerPayUri }; return { payStatus, balance, uri: talerPayUri };
}); }, []);
useEffect(() => { useEffect(() => api.listener.onUpdateNotification(
api.onUpdateNotification([NotificationType.CoinWithdrawn], () => { [NotificationType.CoinWithdrawn],
hook?.retry(); hook?.retry
}); ), [hook]);
});
const hookResponse = !hook || hook.hasError ? undefined : hook.response; const hookResponse = !hook || hook.hasError ? undefined : hook.response;
@ -127,7 +125,9 @@ export function useComponentState(
hint: `payment is not possible: ${payStatus.status}`, hint: `payment is not possible: ${payStatus.status}`,
}); });
} }
const res = await api.confirmPay(payStatus.proposalId, undefined); const res = await api.wallet.call(WalletApiOperation.ConfirmPay, {
proposalId: payStatus.proposalId,
});
// handle confirm pay // handle confirm pay
if (res.type !== ConfirmPayResultType.Done) { if (res.type !== ConfirmPayResultType.Done) {
throw TalerError.fromUncheckedDetail({ throw TalerError.fromUncheckedDetail({

View File

@ -20,81 +20,44 @@
*/ */
import { import {
AmountJson, Amounts, ConfirmPayResult,
Amounts,
BalancesResponse,
ConfirmPayResult,
ConfirmPayResultType, ConfirmPayResultType,
NotificationType, NotificationType, PreparePayResultInsufficientBalance,
PreparePayResult, PreparePayResultPaymentPossible,
PreparePayResultType, PreparePayResultType
} from "@gnu-taler/taler-util"; } from "@gnu-taler/taler-util";
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { expect } from "chai"; import { expect } from "chai";
import { mountHook } from "../../test-utils.js"; import { mountHook, nullFunction } from "../../test-utils.js";
import { createWalletApiMock } from "../../test-utils.js";
import { useComponentState } from "./state.js"; import { useComponentState } from "./state.js";
import * as wxApi from "../../wxApi.js";
const nullFunction: any = () => null;
type VoidFunction = () => void;
type Subs = {
[key in NotificationType]?: VoidFunction;
};
export class SubsHandler {
private subs: Subs = {};
constructor() {
this.saveSubscription = this.saveSubscription.bind(this);
}
saveSubscription(
messageTypes: NotificationType[],
callback: VoidFunction,
): VoidFunction {
messageTypes.forEach((m) => {
this.subs[m] = callback;
});
return nullFunction;
}
notifyEvent(event: NotificationType): void {
const cb = this.subs[event];
if (cb === undefined)
expect.fail(`Expected to have a subscription for ${event}`);
cb();
}
}
describe("Payment CTA states", () => { describe("Payment CTA states", () => {
it("should tell the user that the URI is missing", async () => { it("should tell the user that the URI is missing", async () => {
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = const { handler, mock } = createWalletApiMock();
mountHook(() => const props = {
useComponentState(
{
talerPayUri: undefined, talerPayUri: undefined,
cancel: nullFunction, cancel: nullFunction,
goToWalletManualWithdraw: nullFunction, goToWalletManualWithdraw: nullFunction,
onSuccess: async () => { onSuccess: async () => {
null; null;
}, },
}, }
{ const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } =
onUpdateNotification: nullFunction, mountHook(() =>
} as Partial<typeof wxApi> as any, useComponentState(props, mock),
),
); );
{ {
const { status, error } = getLastResultOrThrow(); const { status, error } = pullLastResultOrThrow();
expect(status).equals("loading"); expect(status).equals("loading");
expect(error).undefined; expect(error).undefined;
} }
await waitNextUpdate(); expect(await waitForStateUpdate()).true;
{ {
const { status, error } = getLastResultOrThrow(); const { status, error } = pullLastResultOrThrow();
expect(status).equals("loading-uri"); expect(status).equals("loading-uri");
if (error === undefined) expect.fail(); if (error === undefined) expect.fail();
@ -103,324 +66,312 @@ describe("Payment CTA states", () => {
} }
await assertNoPendingUpdate(); await assertNoPendingUpdate();
expect(handler.getCallingQueueState()).eq("empty")
}); });
it("should response with no balance", async () => { it("should response with no balance", async () => {
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = const { handler, mock } = createWalletApiMock();
mountHook(() => const props = {
useComponentState(
{
talerPayUri: "taller://pay", talerPayUri: "taller://pay",
cancel: nullFunction, cancel: nullFunction,
goToWalletManualWithdraw: nullFunction, goToWalletManualWithdraw: nullFunction,
onSuccess: async () => { onSuccess: async () => {
null; null;
}, },
}, }
{
onUpdateNotification: nullFunction, handler.addWalletCallResponse(WalletApiOperation.PreparePayForUri, undefined, {
preparePay: async () =>
({
amountRaw: "USD:10",
status: PreparePayResultType.InsufficientBalance, status: PreparePayResultType.InsufficientBalance,
} as Partial<PreparePayResult>), amountRaw: "USD:10",
getBalance: async () => } as PreparePayResultInsufficientBalance)
({ handler.addWalletCallResponse(WalletApiOperation.GetBalances, {}, { balances: [] })
balances: [],
} as Partial<BalancesResponse>), const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } =
} as Partial<typeof wxApi> as any, mountHook(() =>
), useComponentState(props, mock),
); );
{ {
const { status, error } = getLastResultOrThrow(); const { status, error } = pullLastResultOrThrow();
expect(status).equals("loading"); expect(status).equals("loading");
expect(error).undefined; expect(error).undefined;
} }
await waitNextUpdate(); expect(await waitForStateUpdate()).true;
{ {
const r = getLastResultOrThrow(); const r = pullLastResultOrThrow();
if (r.status !== "no-balance-for-currency") expect.fail(); if (r.status !== "no-balance-for-currency") {
expect(r).eq({})
return;
}
expect(r.balance).undefined; expect(r.balance).undefined;
expect(r.amount).deep.equal(Amounts.parseOrThrow("USD:10")); expect(r.amount).deep.equal(Amounts.parseOrThrow("USD:10"));
} }
await assertNoPendingUpdate(); await assertNoPendingUpdate();
expect(handler.getCallingQueueState()).eq("empty")
}); });
it("should not be able to pay if there is no enough balance", async () => { it("should not be able to pay if there is no enough balance", async () => {
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = const { handler, mock } = createWalletApiMock();
mountHook(() => const props = {
useComponentState(
{
talerPayUri: "taller://pay", talerPayUri: "taller://pay",
cancel: nullFunction, cancel: nullFunction,
goToWalletManualWithdraw: nullFunction, goToWalletManualWithdraw: nullFunction,
onSuccess: async () => { onSuccess: async () => {
null; null;
}, },
}, }
{ handler.addWalletCallResponse(WalletApiOperation.PreparePayForUri, undefined, {
onUpdateNotification: nullFunction,
preparePay: async () =>
({
amountRaw: "USD:10",
status: PreparePayResultType.InsufficientBalance, status: PreparePayResultType.InsufficientBalance,
} as Partial<PreparePayResult>), amountRaw: "USD:10",
getBalance: async () => } as PreparePayResultInsufficientBalance)
({ handler.addWalletCallResponse(WalletApiOperation.GetBalances, {}, {
balances: [ balances: [{
{
available: "USD:5", available: "USD:5",
}, hasPendingTransactions: false,
], pendingIncoming: "USD:0",
} as Partial<BalancesResponse>), pendingOutgoing: "USD:0",
} as Partial<typeof wxApi> as any, requiresUserInput: false,
), }]
})
const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } =
mountHook(() =>
useComponentState(props, mock),
); );
{ {
const { status, error } = getLastResultOrThrow(); const { status, error } = pullLastResultOrThrow();
expect(status).equals("loading"); expect(status).equals("loading");
expect(error).undefined; expect(error).undefined;
} }
await waitNextUpdate(); expect(await waitForStateUpdate()).true;
{ {
const r = getLastResultOrThrow(); const r = pullLastResultOrThrow();
if (r.status !== "no-enough-balance") expect.fail(); if (r.status !== "no-enough-balance") expect.fail();
expect(r.balance).deep.equal(Amounts.parseOrThrow("USD:5")); expect(r.balance).deep.equal(Amounts.parseOrThrow("USD:5"));
expect(r.amount).deep.equal(Amounts.parseOrThrow("USD:10")); expect(r.amount).deep.equal(Amounts.parseOrThrow("USD:10"));
} }
await assertNoPendingUpdate(); await assertNoPendingUpdate();
expect(handler.getCallingQueueState()).eq("empty")
}); });
it("should be able to pay (without fee)", async () => { it("should be able to pay (without fee)", async () => {
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = const { handler, mock } = createWalletApiMock();
mountHook(() => const props = {
useComponentState(
{
talerPayUri: "taller://pay", talerPayUri: "taller://pay",
cancel: nullFunction, cancel: nullFunction,
goToWalletManualWithdraw: nullFunction, goToWalletManualWithdraw: nullFunction,
onSuccess: async () => { onSuccess: async () => {
null; null;
}, },
}, }
{ handler.addWalletCallResponse(WalletApiOperation.PreparePayForUri, undefined, {
onUpdateNotification: nullFunction, status: PreparePayResultType.PaymentPossible,
preparePay: async () =>
({
amountRaw: "USD:10", amountRaw: "USD:10",
amountEffective: "USD:10", amountEffective: "USD:10",
status: PreparePayResultType.PaymentPossible, } as PreparePayResultPaymentPossible)
} as Partial<PreparePayResult>), handler.addWalletCallResponse(WalletApiOperation.GetBalances, {}, {
getBalance: async () => balances: [{
({
balances: [
{
available: "USD:15", available: "USD:15",
}, hasPendingTransactions: false,
], pendingIncoming: "USD:0",
} as Partial<BalancesResponse>), pendingOutgoing: "USD:0",
} as Partial<typeof wxApi> as any, requiresUserInput: false,
), }]
})
const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } =
mountHook(() =>
useComponentState(props, mock),
); );
{ {
const { status, error } = getLastResultOrThrow(); const { status, error } = pullLastResultOrThrow();
expect(status).equals("loading"); expect(status).equals("loading");
expect(error).undefined; expect(error).undefined;
} }
await waitNextUpdate(); expect(await waitForStateUpdate()).true;
{ {
const r = getLastResultOrThrow(); const r = pullLastResultOrThrow();
if (r.status !== "ready") expect.fail(); if (r.status !== "ready") {
expect(r).eq({})
return
}
expect(r.balance).deep.equal(Amounts.parseOrThrow("USD:15")); expect(r.balance).deep.equal(Amounts.parseOrThrow("USD:15"));
expect(r.amount).deep.equal(Amounts.parseOrThrow("USD:10")); expect(r.amount).deep.equal(Amounts.parseOrThrow("USD:10"));
// expect(r.totalFees).deep.equal(Amounts.parseOrThrow("USD:0"));
expect(r.payHandler.onClick).not.undefined; expect(r.payHandler.onClick).not.undefined;
} }
await assertNoPendingUpdate(); await assertNoPendingUpdate();
expect(handler.getCallingQueueState()).eq("empty")
}); });
it("should be able to pay (with fee)", async () => { it("should be able to pay (with fee)", async () => {
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = const { handler, mock } = createWalletApiMock();
mountHook(() => const props = {
useComponentState(
{
talerPayUri: "taller://pay", talerPayUri: "taller://pay",
cancel: nullFunction, cancel: nullFunction,
goToWalletManualWithdraw: nullFunction, goToWalletManualWithdraw: nullFunction,
onSuccess: async () => { onSuccess: async () => {
null; null;
}, },
}, }
{ handler.addWalletCallResponse(WalletApiOperation.PreparePayForUri, undefined, {
onUpdateNotification: nullFunction, status: PreparePayResultType.PaymentPossible,
preparePay: async () =>
({
amountRaw: "USD:9", amountRaw: "USD:9",
amountEffective: "USD:10", amountEffective: "USD:10",
status: PreparePayResultType.PaymentPossible, } as PreparePayResultPaymentPossible)
} as Partial<PreparePayResult>), handler.addWalletCallResponse(WalletApiOperation.GetBalances, {}, {
getBalance: async () => balances: [{
({
balances: [
{
available: "USD:15", available: "USD:15",
}, hasPendingTransactions: false,
], pendingIncoming: "USD:0",
} as Partial<BalancesResponse>), pendingOutgoing: "USD:0",
} as Partial<typeof wxApi> as any, requiresUserInput: false,
}]
})
const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } =
mountHook(() =>
useComponentState(
props,
mock
), ),
); );
{ {
const { status, error } = getLastResultOrThrow(); const { status, error } = pullLastResultOrThrow();
expect(status).equals("loading"); expect(status).equals("loading");
expect(error).undefined; expect(error).undefined;
} }
await waitNextUpdate(); expect(await waitForStateUpdate()).true;
{ {
const r = getLastResultOrThrow(); const r = pullLastResultOrThrow();
if (r.status !== "ready") expect.fail(); if (r.status !== "ready") expect.fail();
expect(r.balance).deep.equal(Amounts.parseOrThrow("USD:15")); expect(r.balance).deep.equal(Amounts.parseOrThrow("USD:15"));
expect(r.amount).deep.equal(Amounts.parseOrThrow("USD:9")); expect(r.amount).deep.equal(Amounts.parseOrThrow("USD:9"));
// expect(r.totalFees).deep.equal(Amounts.parseOrThrow("USD:1"));
expect(r.payHandler.onClick).not.undefined; expect(r.payHandler.onClick).not.undefined;
} }
await assertNoPendingUpdate(); await assertNoPendingUpdate();
expect(handler.getCallingQueueState()).eq("empty")
}); });
it("should get confirmation done after pay successfully", async () => { it("should get confirmation done after pay successfully", async () => {
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = const { handler, mock } = createWalletApiMock();
mountHook(() => const props = {
useComponentState(
{
talerPayUri: "taller://pay", talerPayUri: "taller://pay",
cancel: nullFunction, cancel: nullFunction,
goToWalletManualWithdraw: nullFunction, goToWalletManualWithdraw: nullFunction,
onSuccess: async () => { onSuccess: async () => {
null; null;
}, },
}, }
{ handler.addWalletCallResponse(WalletApiOperation.PreparePayForUri, undefined, {
onUpdateNotification: nullFunction, status: PreparePayResultType.PaymentPossible,
preparePay: async () =>
({
amountRaw: "USD:9", amountRaw: "USD:9",
amountEffective: "USD:10", amountEffective: "USD:10",
status: PreparePayResultType.PaymentPossible, } as PreparePayResultPaymentPossible)
} as Partial<PreparePayResult>),
getBalance: async () => handler.addWalletCallResponse(WalletApiOperation.GetBalances, {}, {
({ balances: [{
balances: [
{
available: "USD:15", available: "USD:15",
}, hasPendingTransactions: false,
], pendingIncoming: "USD:0",
} as Partial<BalancesResponse>), pendingOutgoing: "USD:0",
confirmPay: async () => requiresUserInput: false,
({ }]
})
handler.addWalletCallResponse(WalletApiOperation.ConfirmPay, undefined, {
type: ConfirmPayResultType.Done, type: ConfirmPayResultType.Done,
contractTerms: {}, contractTerms: {},
} as Partial<ConfirmPayResult>), } as ConfirmPayResult)
} as Partial<typeof wxApi> as any,
const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } =
mountHook(() =>
useComponentState(
props, mock
), ),
); );
{ {
const { status, error } = getLastResultOrThrow(); const { status, error } = pullLastResultOrThrow();
expect(status).equals("loading"); expect(status).equals("loading");
expect(error).undefined; expect(error).undefined;
} }
await waitNextUpdate(); expect(await waitForStateUpdate()).true;
{ {
const r = getLastResultOrThrow(); const r = pullLastResultOrThrow();
if (r.status !== "ready") expect.fail(); if (r.status !== "ready") {
expect(r).eq({})
return;
}
expect(r.balance).deep.equal(Amounts.parseOrThrow("USD:15")); expect(r.balance).deep.equal(Amounts.parseOrThrow("USD:15"));
expect(r.amount).deep.equal(Amounts.parseOrThrow("USD:9")); expect(r.amount).deep.equal(Amounts.parseOrThrow("USD:9"));
// expect(r.totalFees).deep.equal(Amounts.parseOrThrow("USD:1"));
if (r.payHandler.onClick === undefined) expect.fail(); if (r.payHandler.onClick === undefined) expect.fail();
r.payHandler.onClick(); r.payHandler.onClick();
} }
// await waitNextUpdate();
// {
// const r = getLastResultOrThrow();
// if (r.status !== "completed") expect.fail();
// expect(r.balance).deep.equal(Amounts.parseOrThrow("USD:15"));
// expect(r.amount).deep.equal(Amounts.parseOrThrow("USD:9"));
// // expect(r.totalFees).deep.equal(Amounts.parseOrThrow("USD:1"));
// // if (r.payResult.type !== ConfirmPayResultType.Done) expect.fail();
// // expect(r.payResult.contractTerms).not.undefined;
// // expect(r.payHandler.onClick).undefined;
// }
await assertNoPendingUpdate(); await assertNoPendingUpdate();
expect(handler.getCallingQueueState()).eq("empty")
}); });
it("should not stay in ready state after pay with error", async () => { it("should not stay in ready state after pay with error", async () => {
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = const { handler, mock } = createWalletApiMock();
mountHook(() => const props = {
useComponentState(
{
talerPayUri: "taller://pay", talerPayUri: "taller://pay",
cancel: nullFunction, cancel: nullFunction,
goToWalletManualWithdraw: nullFunction, goToWalletManualWithdraw: nullFunction,
onSuccess: async () => { onSuccess: nullFunction,
null; };
}, handler.addWalletCallResponse(WalletApiOperation.PreparePayForUri, undefined, {
}, status: PreparePayResultType.PaymentPossible,
{
onUpdateNotification: nullFunction,
preparePay: async () =>
({
amountRaw: "USD:9", amountRaw: "USD:9",
amountEffective: "USD:10", amountEffective: "USD:10",
status: PreparePayResultType.PaymentPossible, } as PreparePayResultPaymentPossible)
} as Partial<PreparePayResult>),
getBalance: async () => handler.addWalletCallResponse(WalletApiOperation.GetBalances, {}, {
({ balances: [{
balances: [
{
available: "USD:15", available: "USD:15",
}, hasPendingTransactions: false,
], pendingIncoming: "USD:0",
} as Partial<BalancesResponse>), pendingOutgoing: "USD:0",
confirmPay: async () => requiresUserInput: false,
({ }]
})
handler.addWalletCallResponse(WalletApiOperation.ConfirmPay, undefined, {
type: ConfirmPayResultType.Pending, type: ConfirmPayResultType.Pending,
lastError: { code: 1 }, lastError: { code: 1 },
} as Partial<ConfirmPayResult>), } as ConfirmPayResult)
} as Partial<typeof wxApi> as any,
const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } =
mountHook(() =>
useComponentState(
props, mock
), ),
); );
{ {
const { status, error } = getLastResultOrThrow(); const { status, error } = pullLastResultOrThrow();
expect(status).equals("loading"); expect(status).equals("loading");
expect(error).undefined; expect(error).undefined;
} }
await waitNextUpdate(); expect(await waitForStateUpdate()).true
{ {
const r = getLastResultOrThrow(); const r = pullLastResultOrThrow();
if (r.status !== "ready") expect.fail(); if (r.status !== "ready") expect.fail();
expect(r.balance).deep.equal(Amounts.parseOrThrow("USD:15")); expect(r.balance).deep.equal(Amounts.parseOrThrow("USD:15"));
expect(r.amount).deep.equal(Amounts.parseOrThrow("USD:9")); expect(r.amount).deep.equal(Amounts.parseOrThrow("USD:9"));
@ -429,10 +380,10 @@ describe("Payment CTA states", () => {
r.payHandler.onClick(); r.payHandler.onClick();
} }
await waitNextUpdate(); expect(await waitForStateUpdate()).true
{ {
const r = getLastResultOrThrow(); const r = pullLastResultOrThrow();
if (r.status !== "ready") expect.fail(); if (r.status !== "ready") expect.fail();
expect(r.balance).deep.equal(Amounts.parseOrThrow("USD:15")); expect(r.balance).deep.equal(Amounts.parseOrThrow("USD:15"));
expect(r.amount).deep.equal(Amounts.parseOrThrow("USD:9")); expect(r.amount).deep.equal(Amounts.parseOrThrow("USD:9"));
@ -450,72 +401,91 @@ describe("Payment CTA states", () => {
} }
await assertNoPendingUpdate(); await assertNoPendingUpdate();
expect(handler.getCallingQueueState()).eq("empty")
}); });
it("should update balance if a coins is withdraw", async () => { it("should update balance if a coins is withdraw", async () => {
const subscriptions = new SubsHandler(); const { handler, mock } = createWalletApiMock();
let availableBalance = Amounts.parseOrThrow("USD:10");
function notifyCoinWithdrawn(newAmount: AmountJson): void { const props = {
availableBalance = Amounts.add(availableBalance, newAmount).amount;
subscriptions.notifyEvent(NotificationType.CoinWithdrawn);
}
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
mountHook(() =>
useComponentState(
{
talerPayUri: "taller://pay", talerPayUri: "taller://pay",
cancel: nullFunction, cancel: nullFunction,
goToWalletManualWithdraw: nullFunction, goToWalletManualWithdraw: nullFunction,
onSuccess: async () => { onSuccess: async () => {
null; null;
}, },
}, }
{
onUpdateNotification: subscriptions.saveSubscription, handler.addWalletCallResponse(WalletApiOperation.PreparePayForUri, undefined, {
preparePay: async () => status: PreparePayResultType.PaymentPossible,
({
amountRaw: "USD:9", amountRaw: "USD:9",
amountEffective: "USD:10", amountEffective: "USD:10",
} as PreparePayResultPaymentPossible)
handler.addWalletCallResponse(WalletApiOperation.GetBalances, {}, {
balances: [{
available: "USD:10",
hasPendingTransactions: false,
pendingIncoming: "USD:0",
pendingOutgoing: "USD:0",
requiresUserInput: false,
}]
})
handler.addWalletCallResponse(WalletApiOperation.PreparePayForUri, undefined, {
status: PreparePayResultType.PaymentPossible, status: PreparePayResultType.PaymentPossible,
} as Partial<PreparePayResult>), amountRaw: "USD:9",
getBalance: async () => amountEffective: "USD:10",
({ } as PreparePayResultPaymentPossible)
balances: [
{ handler.addWalletCallResponse(WalletApiOperation.GetBalances, {}, {
available: Amounts.stringify(availableBalance), balances: [{
}, available: "USD:15",
], hasPendingTransactions: false,
} as Partial<BalancesResponse>), pendingIncoming: "USD:0",
} as Partial<typeof wxApi> as any, pendingOutgoing: "USD:0",
requiresUserInput: false,
}]
})
const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } =
mountHook(() =>
useComponentState(
props, mock
), ),
); );
{ {
const { status, error } = getLastResultOrThrow(); const { status, error } = pullLastResultOrThrow();
expect(status).equals("loading"); expect(status).equals("loading");
expect(error).undefined; expect(error).undefined;
} }
await waitNextUpdate(); expect(await waitForStateUpdate()).true;
{ {
const r = getLastResultOrThrow(); const r = pullLastResultOrThrow();
if (r.status !== "ready") expect.fail(); if (r.status !== "ready") {
expect(r).eq({})
return
}
expect(r.balance).deep.equal(Amounts.parseOrThrow("USD:10")); expect(r.balance).deep.equal(Amounts.parseOrThrow("USD:10"));
expect(r.amount).deep.equal(Amounts.parseOrThrow("USD:9")); expect(r.amount).deep.equal(Amounts.parseOrThrow("USD:9"));
// expect(r.totalFees).deep.equal(Amounts.parseOrThrow("USD:1")); // expect(r.totalFees).deep.equal(Amounts.parseOrThrow("USD:1"));
expect(r.payHandler.onClick).not.undefined; expect(r.payHandler.onClick).not.undefined;
notifyCoinWithdrawn(Amounts.parseOrThrow("USD:5")); handler.notifyEventFromWallet(NotificationType.CoinWithdrawn);
} }
await waitNextUpdate(); expect(await waitForStateUpdate()).true;
{ {
const r = getLastResultOrThrow(); const r = pullLastResultOrThrow();
if (r.status !== "ready") expect.fail(); if (r.status !== "ready") {
expect(r).eq({})
return
}
expect(r.balance).deep.equal(Amounts.parseOrThrow("USD:15")); expect(r.balance).deep.equal(Amounts.parseOrThrow("USD:15"));
expect(r.amount).deep.equal(Amounts.parseOrThrow("USD:9")); expect(r.amount).deep.equal(Amounts.parseOrThrow("USD:9"));
// expect(r.totalFees).deep.equal(Amounts.parseOrThrow("USD:1")); // expect(r.totalFees).deep.equal(Amounts.parseOrThrow("USD:1"));
@ -523,5 +493,6 @@ describe("Payment CTA states", () => {
} }
await assertNoPendingUpdate(); await assertNoPendingUpdate();
expect(handler.getCallingQueueState()).eq("empty")
}); });
}); });

View File

@ -14,12 +14,11 @@
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/ */
import { AmountJson } from "@gnu-taler/taler-util";
import { Loading } from "../../components/Loading.js"; import { Loading } from "../../components/Loading.js";
import { HookError } from "../../hooks/useAsyncAsHook.js"; import { HookError } from "../../hooks/useAsyncAsHook.js";
import { ButtonHandler } from "../../mui/handlers.js"; import { ButtonHandler } from "../../mui/handlers.js";
import { compose, StateViewMap } from "../../utils/index.js"; import { compose, StateViewMap } from "../../utils/index.js";
import * as wxApi from "../../wxApi.js"; import { wxApi } from "../../wxApi.js";
import { useComponentState } from "./state.js"; import { useComponentState } from "./state.js";
import { LoadingUriView, ReadyView } from "./views.js"; import { LoadingUriView, ReadyView } from "./views.js";

View File

@ -16,8 +16,7 @@
import { parseRecoveryUri } from "@gnu-taler/taler-util"; import { parseRecoveryUri } from "@gnu-taler/taler-util";
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import * as wxApi from "../../wxApi.js"; import { wxApi } from "../../wxApi.js";
import { wxClient } from "../../wxApi.js";
import { Props, State } from "./index.js"; import { Props, State } from "./index.js";
export function useComponentState( export function useComponentState(
@ -49,7 +48,7 @@ export function useComponentState(
const recovery = info; const recovery = info;
async function recoverBackup(): Promise<void> { async function recoverBackup(): Promise<void> {
await wxClient.call(WalletApiOperation.ImportBackupRecovery, { recovery }); await wxApi.wallet.call(WalletApiOperation.ImportBackupRecovery, { recovery });
onSuccess(); onSuccess();
} }

View File

@ -19,13 +19,13 @@ import { Loading } from "../../components/Loading.js";
import { HookError } from "../../hooks/useAsyncAsHook.js"; import { HookError } from "../../hooks/useAsyncAsHook.js";
import { ButtonHandler } from "../../mui/handlers.js"; import { ButtonHandler } from "../../mui/handlers.js";
import { compose, StateViewMap } from "../../utils/index.js"; import { compose, StateViewMap } from "../../utils/index.js";
import * as wxApi from "../../wxApi.js"; import { wxApi } from "../../wxApi.js";
import { useComponentState } from "./state.js"; import { useComponentState } from "./state.js";
import { import {
IgnoredView, IgnoredView,
InProgressView, InProgressView,
LoadingUriView, LoadingUriView,
ReadyView, ReadyView
} from "./views.js"; } from "./views.js";
export interface Props { export interface Props {

View File

@ -15,9 +15,10 @@
*/ */
import { Amounts, NotificationType } from "@gnu-taler/taler-util"; import { Amounts, NotificationType } from "@gnu-taler/taler-util";
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { useEffect, useState } from "preact/hooks"; import { useEffect, useState } from "preact/hooks";
import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js"; import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js";
import * as wxApi from "../../wxApi.js"; import { wxApi } from "../../wxApi.js";
import { Props, State } from "./index.js"; import { Props, State } from "./index.js";
export function useComponentState( export function useComponentState(
@ -28,15 +29,14 @@ export function useComponentState(
const info = useAsyncAsHook(async () => { const info = useAsyncAsHook(async () => {
if (!talerRefundUri) throw Error("ERROR_NO-URI-FOR-REFUND"); if (!talerRefundUri) throw Error("ERROR_NO-URI-FOR-REFUND");
const refund = await api.prepareRefund({ talerRefundUri }); const refund = await api.wallet.call(WalletApiOperation.PrepareRefund, { talerRefundUri });
return { refund, uri: talerRefundUri }; return { refund, uri: talerRefundUri };
}); });
useEffect(() => { useEffect(() => api.listener.onUpdateNotification(
api.onUpdateNotification([NotificationType.RefreshMelted], () => { [NotificationType.RefreshMelted],
info?.retry(); info?.retry)
}); );
});
if (!info) { if (!info) {
return { status: "loading", error: undefined }; return { status: "loading", error: undefined };
@ -51,7 +51,9 @@ export function useComponentState(
const { refund, uri } = info.response; const { refund, uri } = info.response;
const doAccept = async (): Promise<void> => { const doAccept = async (): Promise<void> => {
const res = await api.applyRefund(uri); const res = await api.wallet.call(WalletApiOperation.ApplyRefund, {
talerRefundUri: uri
});
onSuccess(res.transactionId); onSuccess(res.transactionId);
}; };

View File

@ -21,18 +21,19 @@
import { import {
AmountJson, AmountJson,
Amounts, Amounts, NotificationType, OrderShortInfo, PrepareRefundResult
NotificationType,
PrepareRefundResult,
} from "@gnu-taler/taler-util"; } from "@gnu-taler/taler-util";
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { expect } from "chai"; import { expect } from "chai";
import { mountHook } from "../../test-utils.js"; import { mountHook } from "../../test-utils.js";
import { SubsHandler } from "../Payment/test.js"; import { createWalletApiMock } from "../../test-utils.js";
import { useComponentState } from "./state.js"; import { useComponentState } from "./state.js";
describe("Refund CTA states", () => { describe("Refund CTA states", () => {
it("should tell the user that the URI is missing", async () => { it("should tell the user that the URI is missing", async () => {
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = const { handler, mock } = createWalletApiMock();
const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } =
mountHook(() => mountHook(() =>
useComponentState( useComponentState(
{ {
@ -44,24 +45,25 @@ describe("Refund CTA states", () => {
null; null;
}, },
}, },
{ mock
prepareRefund: async () => ({}), // {
applyRefund: async () => ({}), // prepareRefund: async () => ({}),
onUpdateNotification: async () => ({}), // applyRefund: async () => ({}),
} as any, // onUpdateNotification: async () => ({}),
// } as any,
), ),
); );
{ {
const { status, error } = getLastResultOrThrow(); const { status, error } = pullLastResultOrThrow();
expect(status).equals("loading"); expect(status).equals("loading");
expect(error).undefined; expect(error).undefined;
} }
await waitNextUpdate(); expect(await waitForStateUpdate()).true;
{ {
const { status, error } = getLastResultOrThrow(); const { status, error } = pullLastResultOrThrow();
expect(status).equals("loading-uri"); expect(status).equals("loading-uri");
if (!error) expect.fail(); if (!error) expect.fail();
@ -71,13 +73,12 @@ describe("Refund CTA states", () => {
} }
await assertNoPendingUpdate(); await assertNoPendingUpdate();
expect(handler.getCallingQueueState()).eq("empty")
}); });
it("should be ready after loading", async () => { it("should be ready after loading", async () => {
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = const { handler, mock } = createWalletApiMock();
mountHook(() => const props = {
useComponentState(
{
talerRefundUri: "taler://refund/asdasdas", talerRefundUri: "taler://refund/asdasdas",
cancel: async () => { cancel: async () => {
null; null;
@ -85,12 +86,11 @@ describe("Refund CTA states", () => {
onSuccess: async () => { onSuccess: async () => {
null; null;
}, },
}, }
{
prepareRefund: async () => handler.addWalletCallResponse(WalletApiOperation.PrepareRefund, undefined, {
({
effectivePaid: "EUR:2",
awaiting: "EUR:2", awaiting: "EUR:2",
effectivePaid: "EUR:2",
gone: "EUR:0", gone: "EUR:0",
granted: "EUR:0", granted: "EUR:0",
pending: false, pending: false,
@ -102,24 +102,47 @@ describe("Refund CTA states", () => {
}, },
orderId: "orderId1", orderId: "orderId1",
summary: "the summary", summary: "the summary",
}, } as OrderShortInfo,
} as PrepareRefundResult as any), })
applyRefund: async () => ({}),
onUpdateNotification: async () => ({}), const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } =
} as any, mountHook(() =>
useComponentState(
props, mock
// {
// prepareRefund: async () =>
// ({
// effectivePaid: "EUR:2",
// awaiting: "EUR:2",
// gone: "EUR:0",
// granted: "EUR:0",
// pending: false,
// proposalId: "1",
// info: {
// contractTermsHash: "123",
// merchant: {
// name: "the merchant name",
// },
// orderId: "orderId1",
// summary: "the summary",
// },
// } as PrepareRefundResult as any),
// applyRefund: async () => ({}),
// onUpdateNotification: async () => ({}),
// } as any,
), ),
); );
{ {
const { status, error } = getLastResultOrThrow(); const { status, error } = pullLastResultOrThrow();
expect(status).equals("loading"); expect(status).equals("loading");
expect(error).undefined; expect(error).undefined;
} }
await waitNextUpdate(); expect(await waitForStateUpdate()).true;
{ {
const state = getLastResultOrThrow(); const state = pullLastResultOrThrow();
if (state.status !== "ready") expect.fail(); if (state.status !== "ready") expect.fail();
if (state.error) expect.fail(); if (state.error) expect.fail();
@ -131,13 +154,12 @@ describe("Refund CTA states", () => {
} }
await assertNoPendingUpdate(); await assertNoPendingUpdate();
expect(handler.getCallingQueueState()).eq("empty")
}); });
it("should be ignored after clicking the ignore button", async () => { it("should be ignored after clicking the ignore button", async () => {
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = const { handler, mock } = createWalletApiMock();
mountHook(() => const props = {
useComponentState(
{
talerRefundUri: "taler://refund/asdasdas", talerRefundUri: "taler://refund/asdasdas",
cancel: async () => { cancel: async () => {
null; null;
@ -145,12 +167,11 @@ describe("Refund CTA states", () => {
onSuccess: async () => { onSuccess: async () => {
null; null;
}, },
}, }
{
prepareRefund: async () => handler.addWalletCallResponse(WalletApiOperation.PrepareRefund, undefined, {
({
effectivePaid: "EUR:2",
awaiting: "EUR:2", awaiting: "EUR:2",
effectivePaid: "EUR:2",
gone: "EUR:0", gone: "EUR:0",
granted: "EUR:0", granted: "EUR:0",
pending: false, pending: false,
@ -162,27 +183,72 @@ describe("Refund CTA states", () => {
}, },
orderId: "orderId1", orderId: "orderId1",
summary: "the summary", summary: "the summary",
}, } as OrderShortInfo,
} as PrepareRefundResult as any), })
applyRefund: async () => ({}), // handler.addWalletCall(WalletApiOperation.ApplyRefund)
onUpdateNotification: async () => ({}), // handler.addWalletCall(WalletApiOperation.PrepareRefund, undefined, {
} as any, // awaiting: "EUR:1",
// effectivePaid: "EUR:2",
// gone: "EUR:0",
// granted: "EUR:1",
// pending: true,
// proposalId: "1",
// info: {
// contractTermsHash: "123",
// merchant: {
// name: "the merchant name",
// },
// orderId: "orderId1",
// summary: "the summary",
// } as OrderShortInfo,
// })
const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } =
mountHook(() =>
useComponentState(
props, mock
// {
// prepareRefund: async () =>
// ({
// effectivePaid: "EUR:2",
// awaiting: "EUR:2",
// gone: "EUR:0",
// granted: "EUR:0",
// pending: false,
// proposalId: "1",
// info: {
// contractTermsHash: "123",
// merchant: {
// name: "the merchant name",
// },
// orderId: "orderId1",
// summary: "the summary",
// },
// } as PrepareRefundResult as any),
// applyRefund: async () => ({}),
// onUpdateNotification: async () => ({}),
// } as any,
), ),
); );
{ {
const { status, error } = getLastResultOrThrow(); const { status, error } = pullLastResultOrThrow();
expect(status).equals("loading"); expect(status).equals("loading");
expect(error).undefined; expect(error).undefined;
} }
await waitNextUpdate(); expect(await waitForStateUpdate()).true;
{ {
const state = getLastResultOrThrow(); const state = pullLastResultOrThrow();
if (state.status !== "ready") expect.fail(); if (state.status !== "ready") {
if (state.error) expect.fail(); expect(state).eq({})
return;
}
if (state.error) {
expect(state).eq({})
return;
}
expect(state.accept.onClick).not.undefined; expect(state.accept.onClick).not.undefined;
expect(state.merchantName).eq("the merchant name"); expect(state.merchantName).eq("the merchant name");
expect(state.orderId).eq("orderId1"); expect(state.orderId).eq("orderId1");
@ -192,39 +258,29 @@ describe("Refund CTA states", () => {
state.ignore.onClick(); state.ignore.onClick();
} }
await waitNextUpdate(); expect(await waitForStateUpdate()).true;
{ {
const state = getLastResultOrThrow(); const state = pullLastResultOrThrow();
if (state.status !== "ignored") expect.fail(); if (state.status !== "ignored") {
if (state.error) expect.fail(); expect(state).eq({})
return;
}
if (state.error) {
expect(state).eq({})
return;
}
expect(state.merchantName).eq("the merchant name"); expect(state.merchantName).eq("the merchant name");
} }
await assertNoPendingUpdate(); await assertNoPendingUpdate();
expect(handler.getCallingQueueState()).eq("empty")
}); });
it("should be in progress when doing refresh", async () => { it("should be in progress when doing refresh", async () => {
let granted = Amounts.getZero("EUR"); const { handler, mock } = createWalletApiMock();
const unit: AmountJson = { currency: "EUR", value: 1, fraction: 0 }; const props = {
const refunded: AmountJson = { currency: "EUR", value: 2, fraction: 0 };
let awaiting: AmountJson = refunded;
let pending = true;
const subscriptions = new SubsHandler();
function notifyMelt(): void {
granted = Amounts.add(granted, unit).amount;
pending = granted.value < refunded.value;
awaiting = Amounts.sub(refunded, granted).amount;
subscriptions.notifyEvent(NotificationType.RefreshMelted);
}
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
mountHook(() =>
useComponentState(
{
talerRefundUri: "taler://refund/asdasdas", talerRefundUri: "taler://refund/asdasdas",
cancel: async () => { cancel: async () => {
null; null;
@ -232,15 +288,14 @@ describe("Refund CTA states", () => {
onSuccess: async () => { onSuccess: async () => {
null; null;
}, },
}, }
{
prepareRefund: async () => handler.addWalletCallResponse(WalletApiOperation.PrepareRefund, undefined, {
({ awaiting: "EUR:2",
awaiting: Amounts.stringify(awaiting),
effectivePaid: "EUR:2", effectivePaid: "EUR:2",
gone: "EUR:0", gone: "EUR:0",
granted: Amounts.stringify(granted), granted: "EUR:0",
pending, pending: true,
proposalId: "1", proposalId: "1",
info: { info: {
contractTermsHash: "123", contractTermsHash: "123",
@ -249,56 +304,99 @@ describe("Refund CTA states", () => {
}, },
orderId: "orderId1", orderId: "orderId1",
summary: "the summary", summary: "the summary",
} as OrderShortInfo,
})
handler.addWalletCallResponse(WalletApiOperation.PrepareRefund, undefined, {
awaiting: "EUR:1",
effectivePaid: "EUR:2",
gone: "EUR:0",
granted: "EUR:1",
pending: true,
proposalId: "1",
info: {
contractTermsHash: "123",
merchant: {
name: "the merchant name",
}, },
} as PrepareRefundResult as any), orderId: "orderId1",
applyRefund: async () => ({}), summary: "the summary",
onUpdateNotification: subscriptions.saveSubscription, } as OrderShortInfo,
} as any, })
handler.addWalletCallResponse(WalletApiOperation.PrepareRefund, undefined, {
awaiting: "EUR:0",
effectivePaid: "EUR:2",
gone: "EUR:0",
granted: "EUR:2",
pending: false,
proposalId: "1",
info: {
contractTermsHash: "123",
merchant: {
name: "the merchant name",
},
orderId: "orderId1",
summary: "the summary",
} as OrderShortInfo,
})
const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } =
mountHook(() =>
useComponentState(
props, mock
), ),
); );
{ {
const { status, error } = getLastResultOrThrow(); const { status, error } = pullLastResultOrThrow();
expect(status).equals("loading"); expect(status).equals("loading");
expect(error).undefined; expect(error).undefined;
} }
await waitNextUpdate(); expect(await waitForStateUpdate()).true;
{ {
const state = getLastResultOrThrow(); const state = pullLastResultOrThrow();
if (state.status !== "in-progress") expect.fail("1"); if (state.status !== "in-progress") {
expect(state).eq({})
return;
}
if (state.error) expect.fail(); if (state.error) expect.fail();
expect(state.merchantName).eq("the merchant name"); expect(state.merchantName).eq("the merchant name");
expect(state.products).undefined; expect(state.products).undefined;
expect(state.amount).deep.eq(Amounts.parseOrThrow("EUR:2")); expect(state.amount).deep.eq(Amounts.parseOrThrow("EUR:2"));
// expect(state.progress).closeTo(1 / 3, 0.01) // expect(state.progress).closeTo(1 / 3, 0.01)
notifyMelt(); handler.notifyEventFromWallet(NotificationType.RefreshMelted)
} }
await waitNextUpdate(); expect(await waitForStateUpdate()).true;
{ {
const state = getLastResultOrThrow(); const state = pullLastResultOrThrow();
if (state.status !== "in-progress") expect.fail("2"); if (state.status !== "in-progress") {
expect(state).eq({})
return;
}
if (state.error) expect.fail(); if (state.error) expect.fail();
expect(state.merchantName).eq("the merchant name"); expect(state.merchantName).eq("the merchant name");
expect(state.products).undefined; expect(state.products).undefined;
expect(state.amount).deep.eq(Amounts.parseOrThrow("EUR:2")); expect(state.amount).deep.eq(Amounts.parseOrThrow("EUR:2"));
// expect(state.progress).closeTo(2 / 3, 0.01) // expect(state.progress).closeTo(2 / 3, 0.01)
notifyMelt(); handler.notifyEventFromWallet(NotificationType.RefreshMelted)
} }
await waitNextUpdate(); expect(await waitForStateUpdate()).true;
{ {
const state = getLastResultOrThrow(); const state = pullLastResultOrThrow();
if (state.status !== "ready") expect.fail("3"); if (state.status !== "ready") {
expect(state).eq({})
return;
}
if (state.error) expect.fail(); if (state.error) expect.fail();
expect(state.merchantName).eq("the merchant name"); expect(state.merchantName).eq("the merchant name");
expect(state.products).undefined; expect(state.products).undefined;
@ -306,5 +404,6 @@ describe("Refund CTA states", () => {
} }
await assertNoPendingUpdate(); await assertNoPendingUpdate();
expect(handler.getCallingQueueState()).eq("empty")
}); });
}); });

View File

@ -19,13 +19,13 @@ import { Loading } from "../../components/Loading.js";
import { HookError } from "../../hooks/useAsyncAsHook.js"; import { HookError } from "../../hooks/useAsyncAsHook.js";
import { ButtonHandler } from "../../mui/handlers.js"; import { ButtonHandler } from "../../mui/handlers.js";
import { compose, StateViewMap } from "../../utils/index.js"; import { compose, StateViewMap } from "../../utils/index.js";
import * as wxApi from "../../wxApi.js"; import { wxApi } from "../../wxApi.js";
import { useComponentState } from "./state.js"; import { useComponentState } from "./state.js";
import { import {
AcceptedView, AcceptedView,
IgnoredView, IgnoredView,
LoadingUriView, LoadingUriView,
ReadyView, ReadyView
} from "./views.js"; } from "./views.js";
export interface Props { export interface Props {

View File

@ -15,20 +15,18 @@
*/ */
import { Amounts } from "@gnu-taler/taler-util"; import { Amounts } from "@gnu-taler/taler-util";
import { useState } from "preact/hooks"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js"; import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js";
import * as wxApi from "../../wxApi.js"; import { wxApi } from "../../wxApi.js";
import { Props, State } from "./index.js"; import { Props, State } from "./index.js";
export function useComponentState( export function useComponentState(
{ talerTipUri, onCancel, onSuccess }: Props, { talerTipUri, onCancel, onSuccess }: Props,
api: typeof wxApi, api: typeof wxApi,
): State { ): State {
const [tipIgnored, setTipIgnored] = useState(false);
const tipInfo = useAsyncAsHook(async () => { const tipInfo = useAsyncAsHook(async () => {
if (!talerTipUri) throw Error("ERROR_NO-URI-FOR-TIP"); if (!talerTipUri) throw Error("ERROR_NO-URI-FOR-TIP");
const tip = await api.prepareTip({ talerTipUri }); const tip = await api.wallet.call(WalletApiOperation.PrepareTip, { talerTipUri });
return { tip }; return { tip };
}); });
@ -48,7 +46,7 @@ export function useComponentState(
const { tip } = tipInfo.response; const { tip } = tipInfo.response;
const doAccept = async (): Promise<void> => { const doAccept = async (): Promise<void> => {
const res = await api.acceptTip({ walletTipId: tip.walletTipId }); const res = await api.wallet.call(WalletApiOperation.AcceptTip, { walletTipId: tip.walletTipId });
//FIX: this may not be seen since we are moving to the success also //FIX: this may not be seen since we are moving to the success also
tipInfo.retry(); tipInfo.retry();
@ -65,13 +63,6 @@ export function useComponentState(
}, },
}; };
if (tipIgnored) {
return {
status: "ignored",
...baseInfo,
};
}
if (tip.accepted) { if (tip.accepted) {
return { return {
status: "accepted", status: "accepted",

View File

@ -19,14 +19,18 @@
* @author Sebastian Javier Marchano (sebasjm) * @author Sebastian Javier Marchano (sebasjm)
*/ */
import { Amounts, PrepareTipResult } from "@gnu-taler/taler-util"; import { Amounts } from "@gnu-taler/taler-util";
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { expect } from "chai"; import { expect } from "chai";
import { mountHook } from "../../test-utils.js"; import { mountHook } from "../../test-utils.js";
import { createWalletApiMock } from "../../test-utils.js";
import { useComponentState } from "./state.js"; import { useComponentState } from "./state.js";
describe("Tip CTA states", () => { describe("Tip CTA states", () => {
it("should tell the user that the URI is missing", async () => { it("should tell the user that the URI is missing", async () => {
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = const { handler, mock } = createWalletApiMock();
const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } =
mountHook(() => mountHook(() =>
useComponentState( useComponentState(
{ {
@ -38,23 +42,20 @@ describe("Tip CTA states", () => {
null; null;
}, },
}, },
{ mock,
prepareTip: async () => ({}),
acceptTip: async () => ({}),
} as any,
), ),
); );
{ {
const { status, error } = getLastResultOrThrow(); const { status, error } = pullLastResultOrThrow();
expect(status).equals("loading"); expect(status).equals("loading");
expect(error).undefined; expect(error).undefined;
} }
await waitNextUpdate(); expect(await waitForStateUpdate()).true;
{ {
const { status, error } = getLastResultOrThrow(); const { status, error } = pullLastResultOrThrow();
expect(status).equals("loading-uri"); expect(status).equals("loading-uri");
if (!error) expect.fail(); if (!error) expect.fail();
@ -64,12 +65,26 @@ describe("Tip CTA states", () => {
} }
await assertNoPendingUpdate(); await assertNoPendingUpdate();
expect(handler.getCallingQueueState()).eq("empty")
}); });
it("should be ready for accepting the tip", async () => { it("should be ready for accepting the tip", async () => {
let tipAccepted = false;
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = const { handler, mock } = createWalletApiMock();
handler.addWalletCallResponse(WalletApiOperation.PrepareTip, undefined, {
accepted: false,
exchangeBaseUrl: "exchange url",
merchantBaseUrl: "merchant url",
tipAmountEffective: "EUR:1",
walletTipId: "tip_id",
expirationTimestamp: {
t_s: 1
},
tipAmountRaw: ""
});
const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } =
mountHook(() => mountHook(() =>
useComponentState( useComponentState(
{ {
@ -81,58 +96,79 @@ describe("Tip CTA states", () => {
null; null;
}, },
}, },
{ mock,
prepareTip: async () =>
({
accepted: tipAccepted,
exchangeBaseUrl: "exchange url",
merchantBaseUrl: "merchant url",
tipAmountEffective: "EUR:1",
walletTipId: "tip_id",
} as PrepareTipResult as any),
acceptTip: async () => {
tipAccepted = true;
},
} as any,
), ),
); );
{ {
const { status, error } = getLastResultOrThrow(); const { status, error } = pullLastResultOrThrow();
expect(status).equals("loading"); expect(status).equals("loading");
expect(error).undefined; expect(error).undefined;
} }
await waitNextUpdate(); expect(await waitForStateUpdate()).true;
{ {
const state = getLastResultOrThrow(); const state = pullLastResultOrThrow();
if (state.status !== "ready") expect.fail(); if (state.status !== "ready") {
expect(state).eq({ status: "ready" })
return;
}
if (state.error) expect.fail(); if (state.error) expect.fail();
expect(state.amount).deep.eq(Amounts.parseOrThrow("EUR:1")); expect(state.amount).deep.eq(Amounts.parseOrThrow("EUR:1"));
expect(state.merchantBaseUrl).eq("merchant url"); expect(state.merchantBaseUrl).eq("merchant url");
expect(state.exchangeBaseUrl).eq("exchange url"); expect(state.exchangeBaseUrl).eq("exchange url");
if (state.accept.onClick === undefined) expect.fail(); if (state.accept.onClick === undefined) expect.fail();
handler.addWalletCallResponse(WalletApiOperation.AcceptTip);
state.accept.onClick(); state.accept.onClick();
} }
await waitNextUpdate(); handler.addWalletCallResponse(WalletApiOperation.PrepareTip, undefined, {
{ accepted: true,
const state = getLastResultOrThrow(); exchangeBaseUrl: "exchange url",
merchantBaseUrl: "merchant url",
tipAmountEffective: "EUR:1",
walletTipId: "tip_id",
expirationTimestamp: {
t_s: 1
},
tipAmountRaw: ""
});
expect(await waitForStateUpdate()).true;
if (state.status !== "accepted") expect.fail(); {
const state = pullLastResultOrThrow();
if (state.status !== "accepted") {
expect(state).eq({ status: "accepted" })
return;
}
if (state.error) expect.fail(); if (state.error) expect.fail();
expect(state.amount).deep.eq(Amounts.parseOrThrow("EUR:1")); expect(state.amount).deep.eq(Amounts.parseOrThrow("EUR:1"));
expect(state.merchantBaseUrl).eq("merchant url"); expect(state.merchantBaseUrl).eq("merchant url");
expect(state.exchangeBaseUrl).eq("exchange url"); expect(state.exchangeBaseUrl).eq("exchange url");
} }
await assertNoPendingUpdate(); await assertNoPendingUpdate();
expect(handler.getCallingQueueState()).eq("empty")
}); });
it("should be ignored after clicking the ignore button", async () => { it("should be ignored after clicking the ignore button", async () => {
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = const { handler, mock } = createWalletApiMock();
handler.addWalletCallResponse(WalletApiOperation.PrepareTip, undefined, {
exchangeBaseUrl: "exchange url",
merchantBaseUrl: "merchant url",
tipAmountEffective: "EUR:1",
walletTipId: "tip_id",
accepted: false,
expirationTimestamp: {
t_s: 1,
},
tipAmountRaw: ""
});
const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } =
mountHook(() => mountHook(() =>
useComponentState( useComponentState(
{ {
@ -144,52 +180,48 @@ describe("Tip CTA states", () => {
null; null;
}, },
}, },
{ mock,
prepareTip: async () =>
({
exchangeBaseUrl: "exchange url",
merchantBaseUrl: "merchant url",
tipAmountEffective: "EUR:1",
walletTipId: "tip_id",
} as PrepareTipResult as any),
acceptTip: async () => ({}),
} as any,
), ),
); );
{ {
const { status, error } = getLastResultOrThrow(); const { status, error } = pullLastResultOrThrow();
expect(status).equals("loading"); expect(status).equals("loading");
expect(error).undefined; expect(error).undefined;
} }
await waitNextUpdate(); expect(await waitForStateUpdate()).true;
{ {
const state = getLastResultOrThrow(); const state = pullLastResultOrThrow();
if (state.status !== "ready") expect.fail(); if (state.status !== "ready") expect.fail();
if (state.error) expect.fail(); if (state.error) expect.fail();
expect(state.amount).deep.eq(Amounts.parseOrThrow("EUR:1")); expect(state.amount).deep.eq(Amounts.parseOrThrow("EUR:1"));
expect(state.merchantBaseUrl).eq("merchant url"); expect(state.merchantBaseUrl).eq("merchant url");
expect(state.exchangeBaseUrl).eq("exchange url"); expect(state.exchangeBaseUrl).eq("exchange url");
// if (state.ignore.onClick === undefined) expect.fail();
// state.ignore.onClick();
} }
// await waitNextUpdate();
// {
// const state = getLastResultOrThrow();
// if (state.status !== "ignored") expect.fail();
// if (state.error) expect.fail();
// }
await assertNoPendingUpdate(); await assertNoPendingUpdate();
expect(handler.getCallingQueueState()).eq("empty")
}); });
it("should render accepted if the tip has been used previously", async () => { it("should render accepted if the tip has been used previously", async () => {
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = const { handler, mock } = createWalletApiMock();
handler.addWalletCallResponse(WalletApiOperation.PrepareTip, undefined, {
accepted: true,
exchangeBaseUrl: "exchange url",
merchantBaseUrl: "merchant url",
tipAmountEffective: "EUR:1",
walletTipId: "tip_id",
expirationTimestamp: {
t_s: 1,
},
tipAmountRaw: "",
});
const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } =
mountHook(() => mountHook(() =>
useComponentState( useComponentState(
{ {
@ -201,30 +233,20 @@ describe("Tip CTA states", () => {
null; null;
}, },
}, },
{ mock,
prepareTip: async () =>
({
accepted: true,
exchangeBaseUrl: "exchange url",
merchantBaseUrl: "merchant url",
tipAmountEffective: "EUR:1",
walletTipId: "tip_id",
} as PrepareTipResult as any),
acceptTip: async () => ({}),
} as any,
), ),
); );
{ {
const { status, error } = getLastResultOrThrow(); const { status, error } = pullLastResultOrThrow();
expect(status).equals("loading"); expect(status).equals("loading");
expect(error).undefined; expect(error).undefined;
} }
await waitNextUpdate(); expect(await waitForStateUpdate()).true;
{ {
const state = getLastResultOrThrow(); const state = pullLastResultOrThrow();
if (state.status !== "accepted") expect.fail(); if (state.status !== "accepted") expect.fail();
if (state.error) expect.fail(); if (state.error) expect.fail();
@ -233,5 +255,6 @@ describe("Tip CTA states", () => {
expect(state.exchangeBaseUrl).eq("exchange url"); expect(state.exchangeBaseUrl).eq("exchange url");
} }
await assertNoPendingUpdate(); await assertNoPendingUpdate();
expect(handler.getCallingQueueState()).eq("empty")
}); });
}); });

View File

@ -19,7 +19,7 @@ import { Loading } from "../../components/Loading.js";
import { HookError } from "../../hooks/useAsyncAsHook.js"; import { HookError } from "../../hooks/useAsyncAsHook.js";
import { ButtonHandler, TextFieldHandler } from "../../mui/handlers.js"; import { ButtonHandler, TextFieldHandler } from "../../mui/handlers.js";
import { compose, StateViewMap } from "../../utils/index.js"; import { compose, StateViewMap } from "../../utils/index.js";
import * as wxApi from "../../wxApi.js"; import { wxApi } from "../../wxApi.js";
import { useComponentState } from "./state.js"; import { useComponentState } from "./state.js";
import { LoadingUriView, ReadyView } from "./views.js"; import { LoadingUriView, ReadyView } from "./views.js";

View File

@ -15,9 +15,9 @@
*/ */
import { Amounts, TalerErrorDetail } from "@gnu-taler/taler-util"; import { Amounts, TalerErrorDetail } from "@gnu-taler/taler-util";
import { TalerError } from "@gnu-taler/taler-wallet-core"; import { TalerError, WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { useState } from "preact/hooks"; import { useState } from "preact/hooks";
import * as wxApi from "../../wxApi.js"; import { wxApi } from "../../wxApi.js";
import { Props, State } from "./index.js"; import { Props, State } from "./index.js";
export function useComponentState( export function useComponentState(
@ -33,7 +33,7 @@ export function useComponentState(
async function accept(): Promise<void> { async function accept(): Promise<void> {
try { try {
const resp = await api.initiatePeerPushPayment({ const resp = await api.wallet.call(WalletApiOperation.InitiatePeerPushPayment, {
amount: Amounts.stringify(amount), amount: Amounts.stringify(amount),
partialContractTerms: { partialContractTerms: {
summary: subject, summary: subject,

View File

@ -17,13 +17,13 @@
import { import {
AbsoluteTime, AbsoluteTime,
AmountJson, AmountJson,
TalerErrorDetail, TalerErrorDetail
} from "@gnu-taler/taler-util"; } from "@gnu-taler/taler-util";
import { Loading } from "../../components/Loading.js"; import { Loading } from "../../components/Loading.js";
import { HookError } from "../../hooks/useAsyncAsHook.js"; import { HookError } from "../../hooks/useAsyncAsHook.js";
import { ButtonHandler } from "../../mui/handlers.js"; import { ButtonHandler } from "../../mui/handlers.js";
import { compose, StateViewMap } from "../../utils/index.js"; import { compose, StateViewMap } from "../../utils/index.js";
import * as wxApi from "../../wxApi.js"; import { wxApi } from "../../wxApi.js";
import { useComponentState } from "./state.js"; import { useComponentState } from "./state.js";
import { LoadingUriView, ReadyView } from "./views.js"; import { LoadingUriView, ReadyView } from "./views.js";

View File

@ -18,12 +18,12 @@ import {
AbsoluteTime, AbsoluteTime,
Amounts, Amounts,
TalerErrorDetail, TalerErrorDetail,
TalerProtocolTimestamp, TalerProtocolTimestamp
} from "@gnu-taler/taler-util"; } from "@gnu-taler/taler-util";
import { TalerError } from "@gnu-taler/taler-wallet-core"; import { TalerError, WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { useState } from "preact/hooks"; import { useState } from "preact/hooks";
import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js"; import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js";
import * as wxApi from "../../wxApi.js"; import { wxApi } from "../../wxApi.js";
import { Props, State } from "./index.js"; import { Props, State } from "./index.js";
export function useComponentState( export function useComponentState(
@ -31,7 +31,7 @@ export function useComponentState(
api: typeof wxApi, api: typeof wxApi,
): State { ): State {
const hook = useAsyncAsHook(async () => { const hook = useAsyncAsHook(async () => {
return await api.checkPeerPushPayment({ return await api.wallet.call(WalletApiOperation.CheckPeerPushPayment, {
talerUri: talerPayPushUri, talerUri: talerPayPushUri,
}); });
}, []); }, []);
@ -53,7 +53,6 @@ export function useComponentState(
} }
const { const {
amount: purseAmount,
contractTerms, contractTerms,
peerPushPaymentIncomingId, peerPushPaymentIncomingId,
} = hook.response; } = hook.response;
@ -65,7 +64,7 @@ export function useComponentState(
async function accept(): Promise<void> { async function accept(): Promise<void> {
try { try {
const resp = await api.acceptPeerPushPayment({ const resp = await api.wallet.call(WalletApiOperation.AcceptPeerPushPayment, {
peerPushPaymentIncomingId, peerPushPaymentIncomingId,
}); });
onSuccess(resp.transactionId); onSuccess(resp.transactionId);

View File

@ -20,15 +20,15 @@ import { HookError } from "../../hooks/useAsyncAsHook.js";
import { State as SelectExchangeState } from "../../hooks/useSelectedExchange.js"; import { State as SelectExchangeState } from "../../hooks/useSelectedExchange.js";
import { ButtonHandler, SelectFieldHandler } from "../../mui/handlers.js"; import { ButtonHandler, SelectFieldHandler } from "../../mui/handlers.js";
import { compose, StateViewMap } from "../../utils/index.js"; import { compose, StateViewMap } from "../../utils/index.js";
import * as wxApi from "../../wxApi.js"; import { wxApi } from "../../wxApi.js";
import { import {
useComponentStateFromParams, useComponentStateFromParams,
useComponentStateFromURI, useComponentStateFromURI
} from "./state.js"; } from "./state.js";
import { ExchangeSelectionPage } from "../../wallet/ExchangeSelection/index.js"; import { ExchangeSelectionPage } from "../../wallet/ExchangeSelection/index.js";
import { LoadingInfoView, LoadingUriView, SuccessView } from "./views.js";
import { NoExchangesView } from "../../wallet/ExchangeSelection/views.js"; import { NoExchangesView } from "../../wallet/ExchangeSelection/views.js";
import { LoadingInfoView, LoadingUriView, SuccessView } from "./views.js";
export interface PropsFromURI { export interface PropsFromURI {
talerWithdrawUri: string | undefined; talerWithdrawUri: string | undefined;

View File

@ -19,13 +19,13 @@ import {
AmountJson, AmountJson,
Amounts, Amounts,
ExchangeListItem, ExchangeListItem,
ExchangeTosStatus, ExchangeTosStatus
} from "@gnu-taler/taler-util"; } from "@gnu-taler/taler-util";
import { TalerError } from "@gnu-taler/taler-wallet-core"; import { TalerError, WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { useState } from "preact/hooks"; import { useState } from "preact/hooks";
import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js"; import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js";
import { useSelectedExchange } from "../../hooks/useSelectedExchange.js"; import { useSelectedExchange } from "../../hooks/useSelectedExchange.js";
import * as wxApi from "../../wxApi.js"; import { wxApi } from "../../wxApi.js";
import { PropsFromParams, PropsFromURI, State } from "./index.js"; import { PropsFromParams, PropsFromURI, State } from "./index.js";
type RecursiveState<S extends object> = S | (() => RecursiveState<S>); type RecursiveState<S extends object> = S | (() => RecursiveState<S>);
@ -35,7 +35,7 @@ export function useComponentStateFromParams(
api: typeof wxApi, api: typeof wxApi,
): RecursiveState<State> { ): RecursiveState<State> {
const uriInfoHook = useAsyncAsHook(async () => { const uriInfoHook = useAsyncAsHook(async () => {
const exchanges = await api.listExchanges(); const exchanges = await api.wallet.call(WalletApiOperation.ListExchanges, {});
return { amount: Amounts.parseOrThrow(amount), exchanges }; return { amount: Amounts.parseOrThrow(amount), exchanges };
}); });
@ -58,11 +58,11 @@ export function useComponentStateFromParams(
transactionId: string; transactionId: string;
confirmTransferUrl: string | undefined; confirmTransferUrl: string | undefined;
}> { }> {
const res = await api.acceptManualWithdrawal( const res = await api.wallet.call(WalletApiOperation.AcceptManualWithdrawal, {
exchange, exchangeBaseUrl: exchange,
Amounts.stringify(chosenAmount), amount: Amounts.stringify(chosenAmount),
ageRestricted, restrictAge: ageRestricted,
); });
return { return {
confirmTransferUrl: undefined, confirmTransferUrl: undefined,
transactionId: res.transactionId, transactionId: res.transactionId,
@ -93,16 +93,15 @@ export function useComponentStateFromURI(
const uriInfoHook = useAsyncAsHook(async () => { const uriInfoHook = useAsyncAsHook(async () => {
if (!talerWithdrawUri) throw Error("ERROR_NO-URI-FOR-WITHDRAWAL"); if (!talerWithdrawUri) throw Error("ERROR_NO-URI-FOR-WITHDRAWAL");
const uriInfo = await api.getWithdrawalDetailsForUri({ const uriInfo = await api.wallet.call(WalletApiOperation.GetWithdrawalDetailsForUri, {
talerWithdrawUri, talerWithdrawUri,
}); });
const exchanges = await api.listExchanges();
const { amount, defaultExchangeBaseUrl } = uriInfo; const { amount, defaultExchangeBaseUrl } = uriInfo;
return { return {
talerWithdrawUri, talerWithdrawUri,
amount: Amounts.parseOrThrow(amount), amount: Amounts.parseOrThrow(amount),
thisExchange: defaultExchangeBaseUrl, thisExchange: defaultExchangeBaseUrl,
exchanges, exchanges: uriInfo.possibleExchanges,
}; };
}); });
@ -118,7 +117,7 @@ export function useComponentStateFromURI(
const uri = uriInfoHook.response.talerWithdrawUri; const uri = uriInfoHook.response.talerWithdrawUri;
const chosenAmount = uriInfoHook.response.amount; const chosenAmount = uriInfoHook.response.amount;
const defaultExchange = uriInfoHook.response.thisExchange; const defaultExchange = uriInfoHook.response.thisExchange;
const exchangeList = uriInfoHook.response.exchanges.exchanges; const exchangeList = uriInfoHook.response.exchanges;
async function doManagedWithdraw( async function doManagedWithdraw(
exchange: string, exchange: string,
@ -127,7 +126,11 @@ export function useComponentStateFromURI(
transactionId: string; transactionId: string;
confirmTransferUrl: string | undefined; confirmTransferUrl: string | undefined;
}> { }> {
const res = await api.acceptWithdrawal(uri, exchange, ageRestricted); const res = await api.wallet.call(WalletApiOperation.AcceptBankIntegratedWithdrawal, {
exchangeBaseUrl: exchange,
talerWithdrawUri: uri,
restrictAge: ageRestricted
});
return { return {
confirmTransferUrl: res.confirmTransferUrl, confirmTransferUrl: res.confirmTransferUrl,
transactionId: res.transactionId, transactionId: res.transactionId,
@ -186,7 +189,7 @@ function exchangeSelectionState(
* about the withdrawal * about the withdrawal
*/ */
const amountHook = useAsyncAsHook(async () => { const amountHook = useAsyncAsHook(async () => {
const info = await api.getWithdrawalDetailsForAmount({ const info = await api.wallet.call(WalletApiOperation.GetWithdrawalDetailsForAmount, {
exchangeBaseUrl: currentExchange.exchangeBaseUrl, exchangeBaseUrl: currentExchange.exchangeBaseUrl,
amount: Amounts.stringify(chosenAmount), amount: Amounts.stringify(chosenAmount),
restrictAge: ageRestricted, restrictAge: ageRestricted,

View File

@ -21,16 +21,12 @@
import { import {
Amounts, Amounts,
ExchangeEntryStatus, ExchangeEntryStatus, ExchangeListItem, ExchangeTosStatus
ExchangeFullDetails,
ExchangeListItem,
ExchangesListResponse,
ExchangeTosStatus,
GetExchangeTosResult,
ManualWithdrawalDetails,
} from "@gnu-taler/taler-util"; } from "@gnu-taler/taler-util";
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { expect } from "chai"; import { expect } from "chai";
import { mountHook } from "../../test-utils.js"; import { mountHook } from "../../test-utils.js";
import { createWalletApiMock } from "../../test-utils.js";
import { useComponentStateFromURI } from "./state.js"; import { useComponentStateFromURI } from "./state.js";
const exchanges: ExchangeListItem[] = [ const exchanges: ExchangeListItem[] = [
@ -65,10 +61,8 @@ const exchanges: ExchangeListItem[] = [
describe("Withdraw CTA states", () => { describe("Withdraw CTA states", () => {
it("should tell the user that the URI is missing", async () => { it("should tell the user that the URI is missing", async () => {
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = const { handler, mock } = createWalletApiMock();
mountHook(() => const props = {
useComponentStateFromURI(
{
talerWithdrawUri: undefined, talerWithdrawUri: undefined,
cancel: async () => { cancel: async () => {
null; null;
@ -76,28 +70,23 @@ describe("Withdraw CTA states", () => {
onSuccess: async () => { onSuccess: async () => {
null; null;
}, },
}, }
{ const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } =
listExchanges: async () => ({ exchanges }), mountHook(() =>
getWithdrawalDetailsForAmount: async ({ useComponentStateFromURI(
talerWithdrawUri, props, mock
}: any) => ({
amount: "ARS:2",
possibleExchanges: exchanges,
}),
} as any,
), ),
); );
{ {
const { status } = getLastResultOrThrow(); const { status } = pullLastResultOrThrow();
expect(status).equals("loading"); expect(status).equals("loading");
} }
await waitNextUpdate(); expect(await waitForStateUpdate()).true;
{ {
const { status, error } = getLastResultOrThrow(); const { status, error } = pullLastResultOrThrow();
if (status != "uri-error") expect.fail(); if (status != "uri-error") expect.fail();
if (!error) expect.fail(); if (!error) expect.fail();
@ -107,13 +96,12 @@ describe("Withdraw CTA states", () => {
} }
await assertNoPendingUpdate(); await assertNoPendingUpdate();
expect(handler.getCallingQueueState()).eq("empty")
}); });
it("should tell the user that there is not known exchange", async () => { it("should tell the user that there is not known exchange", async () => {
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = const { handler, mock } = createWalletApiMock();
mountHook(() => const props = {
useComponentStateFromURI(
{
talerWithdrawUri: "taler-withdraw://", talerWithdrawUri: "taler-withdraw://",
cancel: async () => { cancel: async () => {
null; null;
@ -121,26 +109,28 @@ describe("Withdraw CTA states", () => {
onSuccess: async () => { onSuccess: async () => {
null; null;
}, },
}, }
{ handler.addWalletCallResponse(WalletApiOperation.GetWithdrawalDetailsForUri, undefined, {
listExchanges: async () => ({ exchanges }),
getWithdrawalDetailsForUri: async ({ talerWithdrawUri }: any) => ({
amount: "EUR:2", amount: "EUR:2",
possibleExchanges: [], possibleExchanges: [],
}), })
} as any,
const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } =
mountHook(() =>
useComponentStateFromURI(
props, mock
), ),
); );
{ {
const { status } = getLastResultOrThrow(); const { status } = pullLastResultOrThrow();
expect(status).equals("loading", "1"); expect(status).equals("loading", "1");
} }
await waitNextUpdate(); expect(await waitForStateUpdate()).true;
{ {
const { status, error } = getLastResultOrThrow(); const { status, error } = pullLastResultOrThrow();
expect(status).equals("no-exchange", "3"); expect(status).equals("no-exchange", "3");
@ -148,13 +138,12 @@ describe("Withdraw CTA states", () => {
} }
await assertNoPendingUpdate(); await assertNoPendingUpdate();
expect(handler.getCallingQueueState()).eq("empty")
}); });
it("should be able to withdraw if tos are ok", async () => { it("should be able to withdraw if tos are ok", async () => {
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = const { handler, mock } = createWalletApiMock();
mountHook(() => const props = {
useComponentStateFromURI(
{
talerWithdrawUri: "taler-withdraw://", talerWithdrawUri: "taler-withdraw://",
cancel: async () => { cancel: async () => {
null; null;
@ -162,51 +151,47 @@ describe("Withdraw CTA states", () => {
onSuccess: async () => { onSuccess: async () => {
null; null;
}, },
}, }
{ handler.addWalletCallResponse(WalletApiOperation.GetWithdrawalDetailsForUri, undefined, {
listExchanges: async () => ({ exchanges }),
getWithdrawalDetailsForUri: async ({ talerWithdrawUri }: any) => ({
amount: "ARS:2", amount: "ARS:2",
possibleExchanges: exchanges, possibleExchanges: exchanges,
defaultExchangeBaseUrl: exchanges[0].exchangeBaseUrl, defaultExchangeBaseUrl: exchanges[0].exchangeBaseUrl
}), })
getWithdrawalDetailsForAmount: handler.addWalletCallResponse(WalletApiOperation.GetWithdrawalDetailsForAmount, undefined, {
async (): Promise<ManualWithdrawalDetails> =>
({
amountRaw: "ARS:2", amountRaw: "ARS:2",
amountEffective: "ARS:2", amountEffective: "ARS:2",
} as any), paytoUris: ["payto://"],
getExchangeTos: async (): Promise<GetExchangeTosResult> => ({ tosAccepted: true,
contentType: "text", ageRestrictionOptions: []
content: "just accept", })
acceptedEtag: "v1",
currentEtag: "v1", const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } =
tosStatus: ExchangeTosStatus.Accepted, mountHook(() =>
}), useComponentStateFromURI(
} as any, props, mock
), ),
); );
{ {
const { status, error } = getLastResultOrThrow(); const { status, error } = pullLastResultOrThrow();
expect(status).equals("loading"); expect(status).equals("loading");
expect(error).undefined; expect(error).undefined;
} }
await waitNextUpdate(); expect(await waitForStateUpdate()).true;
{ {
const { status, error } = getLastResultOrThrow(); const { status, error } = pullLastResultOrThrow();
expect(status).equals("loading"); expect(status).equals("loading");
expect(error).undefined; expect(error).undefined;
} }
await waitNextUpdate(); expect(await waitForStateUpdate()).true;
{ {
const state = getLastResultOrThrow(); const state = pullLastResultOrThrow();
expect(state.status).equals("success"); expect(state.status).equals("success");
if (state.status !== "success") return; if (state.status !== "success") return;
@ -218,29 +203,12 @@ describe("Withdraw CTA states", () => {
} }
await assertNoPendingUpdate(); await assertNoPendingUpdate();
expect(handler.getCallingQueueState()).eq("empty")
}); });
it("should be accept the tos before withdraw", async () => { it("should accept the tos before withdraw", async () => {
const listExchangesResponse: ExchangesListResponse = { const { handler, mock } = createWalletApiMock();
exchanges: exchanges.map((e) => ({ const props = {
...e,
tosStatus: ExchangeTosStatus.New,
})),
};
function updateAcceptedVersionToCurrentVersion(): void {
listExchangesResponse.exchanges = listExchangesResponse.exchanges.map(
(e) => ({
...e,
tosStatus: ExchangeTosStatus.Accepted,
}),
);
}
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
mountHook(() =>
useComponentStateFromURI(
{
talerWithdrawUri: "taler-withdraw://", talerWithdrawUri: "taler-withdraw://",
cancel: async () => { cancel: async () => {
null; null;
@ -248,52 +216,59 @@ describe("Withdraw CTA states", () => {
onSuccess: async () => { onSuccess: async () => {
null; null;
}, },
}, }
{ const exchangeWithNewTos = exchanges.map((e) => ({
listExchanges: async () => listExchangesResponse, ...e,
getWithdrawalDetailsForUri: async ({ talerWithdrawUri }: any) => ({ tosStatus: ExchangeTosStatus.New,
}));
handler.addWalletCallResponse(WalletApiOperation.GetWithdrawalDetailsForUri, undefined, {
amount: "ARS:2", amount: "ARS:2",
possibleExchanges: exchanges, possibleExchanges: exchangeWithNewTos,
defaultExchangeBaseUrl: exchanges[0].exchangeBaseUrl, defaultExchangeBaseUrl: exchangeWithNewTos[0].exchangeBaseUrl
}), })
getWithdrawalDetailsForAmount: handler.addWalletCallResponse(WalletApiOperation.GetWithdrawalDetailsForAmount, undefined, {
async (): Promise<ManualWithdrawalDetails> =>
({
amountRaw: "ARS:2", amountRaw: "ARS:2",
amountEffective: "ARS:2", amountEffective: "ARS:2",
} as any), paytoUris: ["payto://"],
getExchangeTos: async (): Promise<GetExchangeTosResult> => ({ tosAccepted: false,
contentType: "text", ageRestrictionOptions: []
content: "just accept", })
acceptedEtag: "v1",
currentEtag: "v2",
tosStatus: ExchangeTosStatus.Changed, handler.addWalletCallResponse(WalletApiOperation.GetWithdrawalDetailsForUri, undefined, {
}), amount: "ARS:2",
setExchangeTosAccepted: async () => ({}), possibleExchanges: exchanges,
} as any, defaultExchangeBaseUrl: exchanges[0].exchangeBaseUrl
})
const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } =
mountHook(() =>
useComponentStateFromURI(
props, mock
), ),
); );
{ {
const { status, error } = getLastResultOrThrow(); const { status, error } = pullLastResultOrThrow();
expect(status).equals("loading"); expect(status).equals("loading");
expect(error).undefined; expect(error).undefined;
} }
await waitNextUpdate(); expect(await waitForStateUpdate()).true;
{ {
const { status, error } = getLastResultOrThrow(); const { status, error } = pullLastResultOrThrow();
expect(status).equals("loading"); expect(status).equals("loading");
expect(error).undefined; expect(error).undefined;
} }
await waitNextUpdate(); expect(await waitForStateUpdate()).true;
{ {
const state = getLastResultOrThrow(); const state = pullLastResultOrThrow();
expect(state.status).equals("success"); expect(state.status).equals("success");
if (state.status !== "success") return; if (state.status !== "success") return;
@ -303,14 +278,14 @@ describe("Withdraw CTA states", () => {
expect(state.doWithdrawal.onClick).undefined; expect(state.doWithdrawal.onClick).undefined;
updateAcceptedVersionToCurrentVersion(); // updateAcceptedVersionToCurrentVersion();
state.onTosUpdate(); state.onTosUpdate();
} }
await waitNextUpdate(); expect(await waitForStateUpdate()).true;
{ {
const state = getLastResultOrThrow(); const state = pullLastResultOrThrow();
expect(state.status).equals("success"); expect(state.status).equals("success");
if (state.status !== "success") return; if (state.status !== "success") return;
@ -322,5 +297,6 @@ describe("Withdraw CTA states", () => {
} }
await assertNoPendingUpdate(); await assertNoPendingUpdate();
expect(handler.getCallingQueueState()).eq("empty")
}); });
}); });

View File

@ -13,10 +13,9 @@
You should have received a copy of the GNU General Public License along with 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/> GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/ */
import { NotificationType, TalerErrorDetail } from "@gnu-taler/taler-util"; import { TalerErrorDetail } from "@gnu-taler/taler-util";
import { TalerError } from "@gnu-taler/taler-wallet-core"; import { TalerError } from "@gnu-taler/taler-wallet-core";
import { useEffect, useMemo, useState } from "preact/hooks"; import { useEffect, useMemo, useState } from "preact/hooks";
import * as wxApi from "../wxApi.js";
export interface HookOk<T> { export interface HookOk<T> {
hasError: false; hasError: false;

View File

@ -14,11 +14,11 @@
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/ */
import { useState, useEffect } from "preact/hooks";
import * as wxApi from "../wxApi.js";
import { platform } from "../platform/api.js";
import { ToggleHandler } from "../mui/handlers.js";
import { TalerError } from "@gnu-taler/taler-wallet-core"; import { TalerError } from "@gnu-taler/taler-wallet-core";
import { useEffect, useState } from "preact/hooks";
import { ToggleHandler } from "../mui/handlers.js";
import { platform } from "../platform/api.js";
import { wxApi } from "../wxApi.js";
export function useAutoOpenPermissions(): ToggleHandler { export function useAutoOpenPermissions(): ToggleHandler {
const [enabled, setEnabled] = useState(false); const [enabled, setEnabled] = useState(false);
@ -31,7 +31,7 @@ export function useAutoOpenPermissions(): ToggleHandler {
useEffect(() => { useEffect(() => {
async function getValue(): Promise<void> { async function getValue(): Promise<void> {
const res = await wxApi.containsHeaderListener(); const res = await wxApi.background.containsHeaderListener();
setEnabled(res.newValue); setEnabled(res.newValue);
} }
getValue(); getValue();
@ -59,11 +59,11 @@ async function handleAutoOpenPerm(
onChange(false); onChange(false);
throw lastError; throw lastError;
} }
const res = await wxApi.toggleHeaderListener(granted); const res = await wxApi.background.toggleHeaderListener(granted);
onChange(res.newValue); onChange(res.newValue);
} else { } else {
try { try {
await wxApi.toggleHeaderListener(false).then((r) => onChange(r.newValue)); await wxApi.background.toggleHeaderListener(false).then((r) => onChange(r.newValue));
} catch (e) { } catch (e) {
console.log(e); console.log(e);
} }

View File

@ -14,8 +14,9 @@
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/ */
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { useEffect, useState } from "preact/hooks"; import { useEffect, useState } from "preact/hooks";
import * as wxApi from "../wxApi.js"; import { wxApi } from "../wxApi.js";
export interface BackupDeviceName { export interface BackupDeviceName {
name: string; name: string;
@ -31,10 +32,10 @@ export function useBackupDeviceName(): BackupDeviceName {
useEffect(() => { useEffect(() => {
async function run(): Promise<void> { async function run(): Promise<void> {
//create a first list of backup info by currency //create a first list of backup info by currency
const status = await wxApi.getBackupInfo(); const status = await wxApi.wallet.call(WalletApiOperation.GetBackupInfo, {});
async function update(newName: string): Promise<void> { async function update(newName: string): Promise<void> {
await wxApi.setWalletDeviceId(newName); await wxApi.wallet.call(WalletApiOperation.SetWalletDeviceId, { walletDeviceId: newName });
setStatus((old) => ({ ...old, name: newName })); setStatus((old) => ({ ...old, name: newName }));
} }

View File

@ -14,11 +14,11 @@
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/ */
import { useState, useEffect } from "preact/hooks";
import * as wxApi from "../wxApi.js";
import { platform } from "../platform/api.js";
import { ToggleHandler } from "../mui/handlers.js";
import { TalerError } from "@gnu-taler/taler-wallet-core"; import { TalerError } from "@gnu-taler/taler-wallet-core";
import { useEffect, useState } from "preact/hooks";
import { ToggleHandler } from "../mui/handlers.js";
import { platform } from "../platform/api.js";
import { wxApi } from "../wxApi.js";
export function useClipboardPermissions(): ToggleHandler { export function useClipboardPermissions(): ToggleHandler {
const [enabled, setEnabled] = useState(false); const [enabled, setEnabled] = useState(false);
@ -31,7 +31,7 @@ export function useClipboardPermissions(): ToggleHandler {
useEffect(() => { useEffect(() => {
async function getValue(): Promise<void> { async function getValue(): Promise<void> {
const res = await wxApi.containsHeaderListener(); const res = await wxApi.background.containsHeaderListener();
setEnabled(res.newValue); setEnabled(res.newValue);
} }
getValue(); getValue();
@ -66,7 +66,7 @@ async function handleClipboardPerm(
onChange(granted); onChange(granted);
} else { } else {
try { try {
await wxApi.toggleHeaderListener(false).then((r) => onChange(r.newValue)); await wxApi.background.toggleHeaderListener(false).then((r) => onChange(r.newValue));
} catch (e) { } catch (e) {
console.log(e); console.log(e);
} }

View File

@ -16,7 +16,7 @@
import { WalletDiagnostics } from "@gnu-taler/taler-util"; import { WalletDiagnostics } from "@gnu-taler/taler-util";
import { useEffect, useState } from "preact/hooks"; import { useEffect, useState } from "preact/hooks";
import * as wxApi from "../wxApi.js"; import { wxApi } from "../wxApi.js";
export function useDiagnostics(): [WalletDiagnostics | undefined, boolean] { export function useDiagnostics(): [WalletDiagnostics | undefined, boolean] {
const [timedOut, setTimedOut] = useState(false); const [timedOut, setTimedOut] = useState(false);
@ -33,7 +33,7 @@ export function useDiagnostics(): [WalletDiagnostics | undefined, boolean] {
} }
}, 1000); }, 1000);
const doFetch = async (): Promise<void> => { const doFetch = async (): Promise<void> => {
const d = await wxApi.getDiagnostics(); const d = await wxApi.background.getDiagnostics();
gotDiagnostics = true; gotDiagnostics = true;
setDiagnostics(d); setDiagnostics(d);
}; };

View File

@ -14,9 +14,9 @@
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/ */
import { ProviderInfo } from "@gnu-taler/taler-wallet-core"; import { ProviderInfo, WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { useEffect, useState } from "preact/hooks"; import { useEffect, useState } from "preact/hooks";
import * as wxApi from "../wxApi.js"; import { wxApi } from "../wxApi.js";
export interface ProviderStatus { export interface ProviderStatus {
info?: ProviderInfo; info?: ProviderInfo;
@ -30,7 +30,7 @@ export function useProviderStatus(url: string): ProviderStatus | undefined {
useEffect(() => { useEffect(() => {
async function run(): Promise<void> { async function run(): Promise<void> {
//create a first list of backup info by currency //create a first list of backup info by currency
const status = await wxApi.getBackupInfo(); const status = await wxApi.wallet.call(WalletApiOperation.GetBackupInfo, {});
const providers = status.providers.filter( const providers = status.providers.filter(
(p) => p.syncProviderBaseUrl === url, (p) => p.syncProviderBaseUrl === url,
@ -39,13 +39,17 @@ export function useProviderStatus(url: string): ProviderStatus | undefined {
async function sync(): Promise<void> { async function sync(): Promise<void> {
if (info) { if (info) {
await wxApi.syncOneProvider(info.syncProviderBaseUrl); await wxApi.wallet.call(WalletApiOperation.RunBackupCycle, {
providers: [info.syncProviderBaseUrl]
});
} }
} }
async function remove(): Promise<void> { async function remove(): Promise<void> {
if (info) { if (info) {
await wxApi.removeProvider(info.syncProviderBaseUrl); await wxApi.wallet.call(WalletApiOperation.RemoveBackupProvider, {
provider: info.syncProviderBaseUrl
});
} }
} }

View File

@ -31,18 +31,18 @@ describe("useTalerActionURL hook", () => {
}); });
}; };
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } =
mountHook(useTalerActionURL, ctx); mountHook(useTalerActionURL, ctx);
{ {
const [url] = getLastResultOrThrow(); const [url] = pullLastResultOrThrow();
expect(url).undefined; expect(url).undefined;
} }
await waitNextUpdate("waiting for useEffect"); expect(await waitForStateUpdate()).true;
{ {
const [url, setDismissed] = getLastResultOrThrow(); const [url, setDismissed] = pullLastResultOrThrow();
expect(url).deep.equals({ expect(url).deep.equals({
location: "clipboard", location: "clipboard",
uri: "qwe", uri: "qwe",
@ -50,10 +50,10 @@ describe("useTalerActionURL hook", () => {
setDismissed(true); setDismissed(true);
} }
await waitNextUpdate("after dismiss"); expect(await waitForStateUpdate()).true;
{ {
const [url] = getLastResultOrThrow(); const [url] = pullLastResultOrThrow();
if (url !== undefined) throw Error("invalid"); if (url !== undefined) throw Error("invalid");
expect(url).undefined; expect(url).undefined;
} }

View File

@ -15,7 +15,7 @@
*/ */
import { useState, useEffect } from "preact/hooks"; import { useState, useEffect } from "preact/hooks";
import { wxClient } from "../wxApi.js"; import { wxApi } from "../wxApi.js";
import { ToggleHandler } from "../mui/handlers.js"; import { ToggleHandler } from "../mui/handlers.js";
import { TalerError, WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { TalerError, WalletApiOperation } from "@gnu-taler/taler-wallet-core";
@ -30,7 +30,7 @@ export function useWalletDevMode(): ToggleHandler {
useEffect(() => { useEffect(() => {
async function getValue(): Promise<void> { async function getValue(): Promise<void> {
const res = await wxClient.call(WalletApiOperation.GetVersion, {}); const res = await wxApi.wallet.call(WalletApiOperation.GetVersion, {});
setEnabled(res.devMode); setEnabled(res.devMode);
} }
getValue(); getValue();
@ -49,7 +49,7 @@ async function handleOpen(
onChange: (value: boolean) => void, onChange: (value: boolean) => void,
): Promise<void> { ): Promise<void> {
const nextValue = !currentValue const nextValue = !currentValue
await wxClient.call(WalletApiOperation.SetDevMode, { devModeEnabled: nextValue }); await wxApi.wallet.call(WalletApiOperation.SetDevMode, { devModeEnabled: nextValue });
onChange(nextValue); onChange(nextValue);
return; return;
} }

View File

@ -15,6 +15,7 @@
*/ */
import { Amounts, Balance, NotificationType } from "@gnu-taler/taler-util"; import { Amounts, Balance, NotificationType } from "@gnu-taler/taler-util";
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { Fragment, h, VNode } from "preact"; import { Fragment, h, VNode } from "preact";
import { useEffect, useState } from "preact/hooks"; import { useEffect, useState } from "preact/hooks";
import { BalanceTable } from "../components/BalanceTable.js"; import { BalanceTable } from "../components/BalanceTable.js";
@ -27,7 +28,7 @@ import { Button } from "../mui/Button.js";
import { ButtonHandler } from "../mui/handlers.js"; import { ButtonHandler } from "../mui/handlers.js";
import { compose, StateViewMap } from "../utils/index.js"; import { compose, StateViewMap } from "../utils/index.js";
import { AddNewActionView } from "../wallet/AddNewActionView.js"; import { AddNewActionView } from "../wallet/AddNewActionView.js";
import * as wxApi from "../wxApi.js"; import { wxApi } from "../wxApi.js";
import { NoBalanceHelp } from "./NoBalanceHelp.js"; import { NoBalanceHelp } from "./NoBalanceHelp.js";
export interface Props { export interface Props {
@ -71,16 +72,16 @@ function useComponentState(
api: typeof wxApi, api: typeof wxApi,
): State { ): State {
const [addingAction, setAddingAction] = useState(false); const [addingAction, setAddingAction] = useState(false);
const state = useAsyncAsHook(api.getBalance); const state = useAsyncAsHook(() =>
api.wallet.call(WalletApiOperation.GetBalances, {}),
useEffect(() => { );
return api.onUpdateNotification(
[NotificationType.WithdrawGroupFinished], useEffect(() =>
() => { api.listener.onUpdateNotification(
state?.retry(); [NotificationType.WithdrawGroupFinished],
}, state?.retry,
),
); );
});
if (!state) { if (!state) {
return { return {

View File

@ -14,6 +14,8 @@
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/ */
import { NotificationType } from "@gnu-taler/taler-util";
import { WalletCoreApiClient, WalletCoreOpKeys, WalletCoreRequestType, WalletCoreResponseType } from "@gnu-taler/taler-wallet-core";
import { import {
ComponentChildren, ComponentChildren,
Fragment, Fragment,
@ -24,6 +26,7 @@ import {
VNode, VNode,
} from "preact"; } from "preact";
import { render as renderToString } from "preact-render-to-string"; import { render as renderToString } from "preact-render-to-string";
import { BackgroundApiClient, wxApi } from "./wxApi.js";
// When doing tests we want the requestAnimationFrame to be as fast as possible. // When doing tests we want the requestAnimationFrame to be as fast as possible.
// without this option the RAF will timeout after 100ms making the tests slower // without this option the RAF will timeout after 100ms making the tests slower
@ -86,9 +89,10 @@ type RecursiveState<S> = S | (() => RecursiveState<S>);
interface Mounted<T> { interface Mounted<T> {
unmount: () => void; unmount: () => void;
getLastResultOrThrow: () => Exclude<T, VoidFunction>; pullLastResultOrThrow: () => Exclude<T, VoidFunction>;
assertNoPendingUpdate: () => void; assertNoPendingUpdate: () => void;
waitNextUpdate: (s?: string) => Promise<void>; // waitNextUpdate: (s?: string) => Promise<void>;
waitForStateUpdate: () => Promise<boolean>;
} }
const isNode = typeof window === "undefined"; const isNode = typeof window === "undefined";
@ -97,9 +101,6 @@ export function mountHook<T extends object>(
callback: () => RecursiveState<T>, callback: () => RecursiveState<T>,
Context?: ({ children }: { children: any }) => VNode, Context?: ({ children }: { children: any }) => VNode,
): Mounted<T> { ): Mounted<T> {
// const result: { current: T | null } = {
// current: null
// }
let lastResult: Exclude<T, VoidFunction> | Error | null = null; let lastResult: Exclude<T, VoidFunction> | Error | null = null;
const listener: Array<() => void> = []; const listener: Array<() => void> = [];
@ -132,23 +133,6 @@ export function mountHook<T extends object>(
? create(Component, {}) ? create(Component, {})
: create(Context, { children: [create(Component, {})] }); : create(Context, { children: [create(Component, {})] });
// waiter callback
async function waitNextUpdate(_label = ""): Promise<void> {
if (_label) _label = `. label: "${_label}"`;
await new Promise((res, rej) => {
const tid = setTimeout(() => {
rej(
Error(`waiting for an update but the hook didn't make one${_label}`),
);
}, 100);
listener.push(() => {
clearTimeout(tid);
res(undefined);
});
});
}
const customElement = {} as Element; const customElement = {} as Element;
const parentElement = isNode ? customElement : document.createElement("div"); const parentElement = isNode ? customElement : document.createElement("div");
if (!isNode) { if (!isNode) {
@ -164,14 +148,14 @@ export function mountHook<T extends object>(
} }
} }
function getLastResult(): Exclude<T | Error | null, VoidFunction> { function pullLastResult(): Exclude<T | Error | null, VoidFunction> {
const copy: Exclude<T | Error | null, VoidFunction> = lastResult; const copy: Exclude<T | Error | null, VoidFunction> = lastResult;
lastResult = null; lastResult = null;
return copy; return copy;
} }
function getLastResultOrThrow(): Exclude<T, VoidFunction> { function pullLastResultOrThrow(): Exclude<T, VoidFunction> {
const r = getLastResult(); const r = pullLastResult();
if (r instanceof Error) throw r; if (r instanceof Error) throw r;
if (!r) throw Error("there was no last result"); if (!r) throw Error("there was no last result");
return r; return r;
@ -194,15 +178,137 @@ export function mountHook<T extends object>(
}); });
}); });
const r = getLastResult(); const r = pullLastResult();
if (r) if (r)
throw Error(`There are still pending results. throw Error(`There are still pending results.
This may happen because the hook did a new update but the test didn't consume the result using getLastResult`); This may happen because the hook did a new update but the test didn't consume the result using pullLastResult`);
} }
async function waitForStateUpdate(): Promise<boolean> {
return await new Promise((res, rej) => {
const tid = setTimeout(() => {
res(false);
}, 10);
listener.push(() => {
clearTimeout(tid);
res(true);
});
});
}
return { return {
unmount, unmount,
getLastResultOrThrow, pullLastResultOrThrow,
waitNextUpdate, waitForStateUpdate,
assertNoPendingUpdate, assertNoPendingUpdate,
}; };
} }
export const nullFunction: any = () => null;
interface MockHandler {
addWalletCallResponse<Op extends WalletCoreOpKeys>(operation: Op,
payload?: Partial<WalletCoreRequestType<Op>>,
response?: WalletCoreResponseType<Op>,
callback?: () => void,
): MockHandler;
getCallingQueueState(): "empty" | string;
notifyEventFromWallet(event: NotificationType): void;
}
type CallRecord = WalletCallRecord | BackgroundCallRecord;
interface WalletCallRecord {
source: "wallet"
callback: () => void;
operation: WalletCoreOpKeys,
payload?: WalletCoreRequestType<WalletCoreOpKeys>,
response?: WalletCoreResponseType<WalletCoreOpKeys>,
}
interface BackgroundCallRecord {
source: "background"
name: string,
args: any,
response: any;
}
type Subscriptions = {
[key in NotificationType]?: VoidFunction;
};
export function createWalletApiMock(): { handler: MockHandler, mock: typeof wxApi } {
const calls = new Array<CallRecord>()
const subscriptions: Subscriptions = {};
const mock: typeof wxApi = {
wallet: new Proxy<WalletCoreApiClient>({} as any, {
get(target, name, receiver) {
const functionName = String(name)
if (functionName !== "call") {
throw Error(`the only method in wallet api should be 'call': ${functionName}`)
}
return function (operation: WalletCoreOpKeys, payload: WalletCoreRequestType<WalletCoreOpKeys>) {
const next = calls.shift()
if (!next) {
throw Error(`wallet operation was called but none was expected: ${operation} (${JSON.stringify(payload, undefined, 2)})`)
}
if (next.source !== "wallet") {
throw Error(`wallet operation expected`)
}
if (operation !== next.operation) {
//more checks, deep check payload
throw Error(`wallet operation doesn't match: expected ${next.operation} actual ${operation}`)
}
next.callback()
return next.response ?? {}
}
}
}),
listener: {
onUpdateNotification(mTypes: NotificationType[], callback: (() => void) | undefined): (() => void) {
mTypes.forEach(m => {
subscriptions[m] = callback
})
return nullFunction
}
},
background: new Proxy<BackgroundApiClient>({} as any, {
get(target, name, receiver) {
const functionName = String(name);
return function (...args: any) {
const next = calls.shift()
if (!next) {
throw Error(`background operation was called but none was expected: ${functionName} (${JSON.stringify(args, undefined, 2)})`)
}
if (next.source !== "background" || functionName !== next.name) {
//more checks, deep check args
throw Error(`background operation doesn't match`)
}
return next.response
}
}
}),
}
const handler: MockHandler = {
addWalletCallResponse(operation, payload, response, cb) {
calls.push({ source: "wallet", operation, payload, response, callback: cb ? cb : () => { null } })
return handler
},
notifyEventFromWallet(event: NotificationType): void {
const callback = subscriptions[event]
if (!callback) throw Error(`Expected to have a subscription for ${event}`);
return callback();
},
getCallingQueueState() {
return calls.length === 0 ? "empty" : `${calls.length} left`;
},
}
return { handler, mock }
}

View File

@ -16,15 +16,15 @@
import { Loading } from "../../components/Loading.js"; import { Loading } from "../../components/Loading.js";
import { HookError } from "../../hooks/useAsyncAsHook.js"; import { HookError } from "../../hooks/useAsyncAsHook.js";
import { compose, StateViewMap } from "../../utils/index.js";
import { LoadingUriView, ReadyView } from "./views.js";
import * as wxApi from "../../wxApi.js";
import { useComponentState } from "./state.js";
import { import {
ButtonHandler, ButtonHandler,
SelectFieldHandler, SelectFieldHandler,
TextFieldHandler, TextFieldHandler
} from "../../mui/handlers.js"; } from "../../mui/handlers.js";
import { compose, StateViewMap } from "../../utils/index.js";
import { wxApi } from "../../wxApi.js";
import { useComponentState } from "./state.js";
import { LoadingUriView, ReadyView } from "./views.js";
export interface Props { export interface Props {
currency: string; currency: string;

View File

@ -15,19 +15,17 @@
*/ */
import { parsePaytoUri, stringifyPaytoUri } from "@gnu-taler/taler-util"; import { parsePaytoUri, stringifyPaytoUri } from "@gnu-taler/taler-util";
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { useState } from "preact/hooks"; import { useState } from "preact/hooks";
import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js"; import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js";
import * as wxApi from "../../wxApi.js"; import { wxApi } from "../../wxApi.js";
import { Props, State } from "./index.js"; import { Props, State } from "./index.js";
export function useComponentState( export function useComponentState(
{ currency, onAccountAdded, onCancel }: Props, { currency, onAccountAdded, onCancel }: Props,
api: typeof wxApi, api: typeof wxApi,
): State { ): State {
const hook = useAsyncAsHook(async () => { const hook = useAsyncAsHook(() => api.wallet.call(WalletApiOperation.ListKnownBankAccounts, { currency }));
const { accounts } = await api.listKnownBankAccounts(currency);
return { accounts };
});
const [payto, setPayto] = useState(""); const [payto, setPayto] = useState("");
const [alias, setAlias] = useState(""); const [alias, setAlias] = useState("");
@ -61,7 +59,10 @@ export function useComponentState(
async function addAccount(): Promise<void> { async function addAccount(): Promise<void> {
if (!uri || found) return; if (!uri || found) return;
await api.addKnownBankAccounts(uri, currency, alias); const normalizedPayto = stringifyPaytoUri(uri);
await api.wallet.call(WalletApiOperation.AddKnownBankAccounts, {
alias, currency, payto: normalizedPayto
});
onAccountAdded(payto); onAccountAdded(payto);
} }

View File

@ -14,11 +14,7 @@
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/ */
import { import { AbsoluteTime, constructRecoveryUri } from "@gnu-taler/taler-util";
AbsoluteTime,
BackupRecovery,
constructRecoveryUri,
} from "@gnu-taler/taler-util";
import { import {
ProviderInfo, ProviderInfo,
ProviderPaymentPaid, ProviderPaymentPaid,
@ -32,8 +28,10 @@ import {
intervalToDuration, intervalToDuration,
} from "date-fns"; } from "date-fns";
import { Fragment, h, VNode } from "preact"; import { Fragment, h, VNode } from "preact";
import { useEffect, useState } from "preact/hooks";
import { Loading } from "../components/Loading.js"; import { Loading } from "../components/Loading.js";
import { LoadingError } from "../components/LoadingError.js"; import { LoadingError } from "../components/LoadingError.js";
import { QR } from "../components/QR.js";
import { import {
BoldLight, BoldLight,
Centered, Centered,
@ -48,10 +46,7 @@ import { useTranslationContext } from "../context/translation.js";
import { useAsyncAsHook } from "../hooks/useAsyncAsHook.js"; import { useAsyncAsHook } from "../hooks/useAsyncAsHook.js";
import { Button } from "../mui/Button.js"; import { Button } from "../mui/Button.js";
import { Pages } from "../NavigationBar.js"; import { Pages } from "../NavigationBar.js";
import * as wxApi from "../wxApi.js"; import { wxApi } from "../wxApi.js";
import { wxClient } from "../wxApi.js";
import { useEffect, useState } from "preact/hooks";
import { QR } from "../components/QR.js";
interface Props { interface Props {
onAddProvider: () => Promise<void>; onAddProvider: () => Promise<void>;
@ -112,7 +107,9 @@ export function ShowRecoveryInfo({
export function BackupPage({ onAddProvider }: Props): VNode { export function BackupPage({ onAddProvider }: Props): VNode {
const { i18n } = useTranslationContext(); const { i18n } = useTranslationContext();
const status = useAsyncAsHook(wxApi.getBackupInfo); const status = useAsyncAsHook(() =>
wxApi.wallet.call(WalletApiOperation.GetBackupInfo, {}),
);
const [recoveryInfo, setRecoveryInfo] = useState<string>(""); const [recoveryInfo, setRecoveryInfo] = useState<string>("");
if (!status) { if (!status) {
return <Loading />; return <Loading />;
@ -127,7 +124,10 @@ export function BackupPage({ onAddProvider }: Props): VNode {
} }
async function getRecoveryInfo(): Promise<void> { async function getRecoveryInfo(): Promise<void> {
const r = await wxClient.call(WalletApiOperation.ExportBackupRecovery, {}); const r = await wxApi.wallet.call(
WalletApiOperation.ExportBackupRecovery,
{},
);
const str = constructRecoveryUri(r); const str = constructRecoveryUri(r);
setRecoveryInfo(str); setRecoveryInfo(str);
} }
@ -157,7 +157,9 @@ export function BackupPage({ onAddProvider }: Props): VNode {
<BackupView <BackupView
providers={providers} providers={providers}
onAddProvider={onAddProvider} onAddProvider={onAddProvider}
onSyncAll={wxApi.syncAllProviders} onSyncAll={async () =>
wxApi.wallet.call(WalletApiOperation.RunBackupCycle, {}).then()
}
onShowInfo={getRecoveryInfo} onShowInfo={getRecoveryInfo}
/> />
); );

View File

@ -34,73 +34,73 @@ const exchangeListEmpty = {};
describe("CreateManualWithdraw states", () => { describe("CreateManualWithdraw states", () => {
it("should set noExchangeFound when exchange list is empty", () => { it("should set noExchangeFound when exchange list is empty", () => {
const { getLastResultOrThrow } = mountHook(() => const { pullLastResultOrThrow } = mountHook(() =>
useComponentState(exchangeListEmpty, undefined, undefined), useComponentState(exchangeListEmpty, undefined, undefined),
); );
const { noExchangeFound } = getLastResultOrThrow(); const { noExchangeFound } = pullLastResultOrThrow();
expect(noExchangeFound).equal(true); expect(noExchangeFound).equal(true);
}); });
it("should set noExchangeFound when exchange list doesn't include selected currency", () => { it("should set noExchangeFound when exchange list doesn't include selected currency", () => {
const { getLastResultOrThrow } = mountHook(() => const { pullLastResultOrThrow } = mountHook(() =>
useComponentState(exchangeListWithARSandUSD, undefined, "COL"), useComponentState(exchangeListWithARSandUSD, undefined, "COL"),
); );
const { noExchangeFound } = getLastResultOrThrow(); const { noExchangeFound } = pullLastResultOrThrow();
expect(noExchangeFound).equal(true); expect(noExchangeFound).equal(true);
}); });
it("should select the first exchange from the list", () => { it("should select the first exchange from the list", () => {
const { getLastResultOrThrow } = mountHook(() => const { pullLastResultOrThrow } = mountHook(() =>
useComponentState(exchangeListWithARSandUSD, undefined, undefined), useComponentState(exchangeListWithARSandUSD, undefined, undefined),
); );
const { exchange } = getLastResultOrThrow(); const { exchange } = pullLastResultOrThrow();
expect(exchange.value).equal("url1"); expect(exchange.value).equal("url1");
}); });
it("should select the first exchange with the selected currency", () => { it("should select the first exchange with the selected currency", () => {
const { getLastResultOrThrow } = mountHook(() => const { pullLastResultOrThrow } = mountHook(() =>
useComponentState(exchangeListWithARSandUSD, undefined, "ARS"), useComponentState(exchangeListWithARSandUSD, undefined, "ARS"),
); );
const { exchange } = getLastResultOrThrow(); const { exchange } = pullLastResultOrThrow();
expect(exchange.value).equal("url2"); expect(exchange.value).equal("url2");
}); });
it("should change the exchange when currency change", async () => { it("should change the exchange when currency change", async () => {
const { getLastResultOrThrow, waitNextUpdate } = mountHook(() => const { pullLastResultOrThrow, waitForStateUpdate } = mountHook(() =>
useComponentState(exchangeListWithARSandUSD, undefined, "ARS"), useComponentState(exchangeListWithARSandUSD, undefined, "ARS"),
); );
{ {
const { exchange, currency } = getLastResultOrThrow(); const { exchange, currency } = pullLastResultOrThrow();
expect(exchange.value).equal("url2"); expect(exchange.value).equal("url2");
if (currency.onChange === undefined) expect.fail(); if (currency.onChange === undefined) expect.fail();
currency.onChange("USD"); currency.onChange("USD");
} }
await waitNextUpdate(); expect(await waitForStateUpdate()).true;
{ {
const { exchange } = getLastResultOrThrow(); const { exchange } = pullLastResultOrThrow();
expect(exchange.value).equal("url1"); expect(exchange.value).equal("url1");
} }
}); });
it("should change the currency when exchange change", async () => { it("should change the currency when exchange change", async () => {
const { getLastResultOrThrow, waitNextUpdate } = mountHook(() => const { pullLastResultOrThrow, waitForStateUpdate } = mountHook(() =>
useComponentState(exchangeListWithARSandUSD, undefined, "ARS"), useComponentState(exchangeListWithARSandUSD, undefined, "ARS"),
); );
{ {
const { exchange, currency } = getLastResultOrThrow(); const { exchange, currency } = pullLastResultOrThrow();
expect(exchange.value).equal("url2"); expect(exchange.value).equal("url2");
expect(currency.value).equal("ARS"); expect(currency.value).equal("ARS");
@ -109,10 +109,10 @@ describe("CreateManualWithdraw states", () => {
exchange.onChange("url1"); exchange.onChange("url1");
} }
await waitNextUpdate(); expect(await waitForStateUpdate()).true;
{ {
const { exchange, currency } = getLastResultOrThrow(); const { exchange, currency } = pullLastResultOrThrow();
expect(exchange.value).equal("url1"); expect(exchange.value).equal("url1");
expect(currency.value).equal("USD"); expect(currency.value).equal("USD");
@ -120,22 +120,22 @@ describe("CreateManualWithdraw states", () => {
}); });
it("should update parsed amount when amount change", async () => { it("should update parsed amount when amount change", async () => {
const { getLastResultOrThrow, waitNextUpdate } = mountHook(() => const { pullLastResultOrThrow, waitForStateUpdate } = mountHook(() =>
useComponentState(exchangeListWithARSandUSD, undefined, "ARS"), useComponentState(exchangeListWithARSandUSD, undefined, "ARS"),
); );
{ {
const { amount, parsedAmount } = getLastResultOrThrow(); const { amount, parsedAmount } = pullLastResultOrThrow();
expect(parsedAmount).equal(undefined); expect(parsedAmount).equal(undefined);
amount.onInput("12"); amount.onInput("12");
} }
await waitNextUpdate(); expect(await waitForStateUpdate()).true;
{ {
const { parsedAmount } = getLastResultOrThrow(); const { parsedAmount } = pullLastResultOrThrow();
expect(parsedAmount).deep.equals({ expect(parsedAmount).deep.equals({
value: 12, value: 12,
@ -146,41 +146,41 @@ describe("CreateManualWithdraw states", () => {
}); });
it("should have an amount field", async () => { it("should have an amount field", async () => {
const { getLastResultOrThrow, waitNextUpdate } = mountHook(() => const { pullLastResultOrThrow, waitForStateUpdate } = mountHook(() =>
useComponentState(exchangeListWithARSandUSD, undefined, "ARS"), useComponentState(exchangeListWithARSandUSD, undefined, "ARS"),
); );
await defaultTestForInputText( await defaultTestForInputText(
waitNextUpdate, waitForStateUpdate,
() => getLastResultOrThrow().amount, () => pullLastResultOrThrow().amount,
); );
}); });
it("should have an exchange selector ", async () => { it("should have an exchange selector ", async () => {
const { getLastResultOrThrow, waitNextUpdate } = mountHook(() => const { pullLastResultOrThrow, waitForStateUpdate } = mountHook(() =>
useComponentState(exchangeListWithARSandUSD, undefined, "ARS"), useComponentState(exchangeListWithARSandUSD, undefined, "ARS"),
); );
await defaultTestForInputSelect( await defaultTestForInputSelect(
waitNextUpdate, waitForStateUpdate,
() => getLastResultOrThrow().exchange, () => pullLastResultOrThrow().exchange,
); );
}); });
it("should have a currency selector ", async () => { it("should have a currency selector ", async () => {
const { getLastResultOrThrow, waitNextUpdate } = mountHook(() => const { pullLastResultOrThrow, waitForStateUpdate } = mountHook(() =>
useComponentState(exchangeListWithARSandUSD, undefined, "ARS"), useComponentState(exchangeListWithARSandUSD, undefined, "ARS"),
); );
await defaultTestForInputSelect( await defaultTestForInputSelect(
waitNextUpdate, waitForStateUpdate,
() => getLastResultOrThrow().currency, () => pullLastResultOrThrow().currency,
); );
}); });
}); });
async function defaultTestForInputText( async function defaultTestForInputText(
awaiter: () => Promise<void>, awaiter: () => Promise<boolean>,
getField: () => TextFieldHandler, getField: () => TextFieldHandler,
): Promise<void> { ): Promise<void> {
let nextValue = ""; let nextValue = "";
@ -191,7 +191,7 @@ async function defaultTestForInputText(
field.onInput(nextValue); field.onInput(nextValue);
} }
await awaiter(); expect(await awaiter()).true;
{ {
const field = getField(); const field = getField();
@ -200,7 +200,7 @@ async function defaultTestForInputText(
} }
async function defaultTestForInputSelect( async function defaultTestForInputSelect(
awaiter: () => Promise<void>, awaiter: () => Promise<boolean>,
getField: () => SelectFieldHandler, getField: () => SelectFieldHandler,
): Promise<void> { ): Promise<void> {
let nextValue = ""; let nextValue = "";
@ -218,7 +218,7 @@ async function defaultTestForInputSelect(
field.onChange(nextValue); field.onChange(nextValue);
} }
await awaiter(); expect(await awaiter()).true;
{ {
const field = getField(); const field = getField();

View File

@ -14,26 +14,25 @@
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/ */
import { AmountJson, PaytoUri } from "@gnu-taler/taler-util";
import { Loading } from "../../components/Loading.js"; import { Loading } from "../../components/Loading.js";
import { HookError } from "../../hooks/useAsyncAsHook.js"; import { HookError } from "../../hooks/useAsyncAsHook.js";
import {
ButtonHandler,
SelectFieldHandler,
TextFieldHandler
} from "../../mui/handlers.js";
import { compose, StateViewMap } from "../../utils/index.js"; import { compose, StateViewMap } from "../../utils/index.js";
import { wxApi } from "../../wxApi.js";
import { AddAccountPage } from "../AddAccount/index.js";
import { useComponentState } from "./state.js";
import { import {
AmountOrCurrencyErrorView, AmountOrCurrencyErrorView,
LoadingErrorView, LoadingErrorView,
NoAccountToDepositView, NoAccountToDepositView,
NoEnoughBalanceView, NoEnoughBalanceView,
ReadyView, ReadyView
} from "./views.js"; } from "./views.js";
import * as wxApi from "../../wxApi.js";
import { useComponentState } from "./state.js";
import { AmountJson, PaytoUri } from "@gnu-taler/taler-util";
import {
ButtonHandler,
SelectFieldHandler,
TextFieldHandler,
ToggleHandler,
} from "../../mui/handlers.js";
import { AddAccountPage } from "../AddAccount/index.js";
export interface Props { export interface Props {
amount?: string; amount?: string;

View File

@ -21,11 +21,12 @@ import {
KnownBankAccountsInfo, KnownBankAccountsInfo,
parsePaytoUri, parsePaytoUri,
PaytoUri, PaytoUri,
stringifyPaytoUri, stringifyPaytoUri
} from "@gnu-taler/taler-util"; } from "@gnu-taler/taler-util";
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { useState } from "preact/hooks"; import { useState } from "preact/hooks";
import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js"; import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js";
import * as wxApi from "../../wxApi.js"; import { wxApi } from "../../wxApi.js";
import { Props, State } from "./index.js"; import { Props, State } from "./index.js";
export function useComponentState( export function useComponentState(
@ -36,8 +37,10 @@ export function useComponentState(
const currency = parsed !== undefined ? parsed.currency : currencyStr; const currency = parsed !== undefined ? parsed.currency : currencyStr;
const hook = useAsyncAsHook(async () => { const hook = useAsyncAsHook(async () => {
const { balances } = await api.getBalance(); const { balances } = await api.wallet.call(WalletApiOperation.GetBalances, {});
const { accounts } = await api.listKnownBankAccounts(currency); const { accounts } = await api.wallet.call(WalletApiOperation.ListKnownBankAccounts, {
currency
});
return { accounts, balances }; return { accounts, balances };
}); });
@ -127,25 +130,29 @@ export function useComponentState(
// const newSelected = !accountMap[accountStr] ? undefined : accountMap[accountStr]; // const newSelected = !accountMap[accountStr] ? undefined : accountMap[accountStr];
// if (!newSelected) return; // if (!newSelected) return;
const uri = !accountStr ? undefined : parsePaytoUri(accountStr); const uri = !accountStr ? undefined : parsePaytoUri(accountStr);
setSelectedAccount(uri);
if (uri && parsedAmount) { if (uri && parsedAmount) {
try { try {
const result = await getFeeForAmount(uri, parsedAmount, api); const result = await getFeeForAmount(uri, parsedAmount, api);
setSelectedAccount(uri);
setFee(result); setFee(result);
} catch (e) { } catch (e) {
console.error(e)
setSelectedAccount(uri);
setFee(undefined); setFee(undefined);
} }
} }
} }
async function updateAmount(numStr: string): Promise<void> { async function updateAmount(numStr: string): Promise<void> {
setAmount(numStr);
const parsed = Amounts.parse(`${currency}:${numStr}`); const parsed = Amounts.parse(`${currency}:${numStr}`);
if (parsed && selectedAccount) { if (parsed && selectedAccount) {
try { try {
const result = await getFeeForAmount(selectedAccount, parsed, api); const result = await getFeeForAmount(selectedAccount, parsed, api);
setAmount(numStr);
setFee(result); setFee(result);
} catch (e) { } catch (e) {
console.error(e)
setAmount(numStr);
setFee(undefined); setFee(undefined);
} }
} }
@ -176,13 +183,16 @@ export function useComponentState(
Amounts.isZero(totalToDeposit) || Amounts.isZero(totalToDeposit) ||
fee === undefined || fee === undefined ||
amountError !== undefined; amountError !== undefined;
// console.log(parsedAmount, selectedAccount, fee, totalToDeposit, amountError)
async function doSend(): Promise<void> { async function doSend(): Promise<void> {
if (!selectedAccount || !parsedAmount || !currency) return; if (!selectedAccount || !parsedAmount || !currency) return;
const account = `payto://${selectedAccount.targetType}/${selectedAccount.targetPath}`; const depositPaytoUri = `payto://${selectedAccount.targetType}/${selectedAccount.targetPath}`;
const amount = Amounts.stringify(parsedAmount); const amount = Amounts.stringify(parsedAmount);
await api.createDepositGroup(account, amount); await api.wallet.call(WalletApiOperation.CreateDepositGroup, {
amount, depositPaytoUri
})
onSuccess(currency); onSuccess(currency);
} }
@ -226,9 +236,11 @@ async function getFeeForAmount(
a: AmountJson, a: AmountJson,
api: typeof wxApi, api: typeof wxApi,
): Promise<DepositGroupFees> { ): Promise<DepositGroupFees> {
const account = `payto://${p.targetType}/${p.targetPath}`; const depositPaytoUri = `payto://${p.targetType}/${p.targetPath}`;
const amount = Amounts.stringify(a); const amount = Amounts.stringify(a);
return await api.getFeeForDeposit(account, amount); return await api.wallet.call(WalletApiOperation.GetFeeForDeposit, {
amount, depositPaytoUri
})
} }
export function labelForAccountType(id: string) { export function labelForAccountType(id: string) {

View File

@ -20,101 +20,108 @@
*/ */
import { import {
Amounts, Amounts, DepositGroupFees,
Balance,
BalancesResponse,
DepositGroupFees,
parsePaytoUri, parsePaytoUri,
stringifyPaytoUri, stringifyPaytoUri
} from "@gnu-taler/taler-util"; } from "@gnu-taler/taler-util";
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { expect } from "chai"; import { expect } from "chai";
import { mountHook } from "../../test-utils.js"; import { createWalletApiMock, mountHook, nullFunction } from "../../test-utils.js";
import * as wxApi from "../../wxApi.js";
import { useComponentState } from "./state.js"; import { useComponentState } from "./state.js";
const currency = "EUR"; const currency = "EUR";
const withoutFee = async (): Promise<DepositGroupFees> => ({ const withoutFee = (): DepositGroupFees => ({
coin: Amounts.parseOrThrow(`${currency}:0`), coin: Amounts.parseOrThrow(`${currency}:0`),
wire: Amounts.parseOrThrow(`${currency}:0`), wire: Amounts.parseOrThrow(`${currency}:0`),
refresh: Amounts.parseOrThrow(`${currency}:0`), refresh: Amounts.parseOrThrow(`${currency}:0`),
}); });
const withSomeFee = async (): Promise<DepositGroupFees> => ({ const withSomeFee = (): DepositGroupFees => ({
coin: Amounts.parseOrThrow(`${currency}:1`), coin: Amounts.parseOrThrow(`${currency}:1`),
wire: Amounts.parseOrThrow(`${currency}:1`), wire: Amounts.parseOrThrow(`${currency}:1`),
refresh: Amounts.parseOrThrow(`${currency}:1`), refresh: Amounts.parseOrThrow(`${currency}:1`),
}); });
const freeJustForIBAN = async (account: string): Promise<DepositGroupFees> =>
/IBAN/i.test(account) ? withSomeFee() : withoutFee();
const someBalance = [
{
available: "EUR:10",
} as Balance,
];
const nullFunction: any = () => null;
type VoidFunction = () => void;
describe("DepositPage states", () => { describe("DepositPage states", () => {
it("should have status 'no-enough-balance' when balance is empty", async () => { it("should have status 'no-enough-balance' when balance is empty", async () => {
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = const { handler, mock } = createWalletApiMock();
const props = { currency, onCancel: nullFunction, onSuccess: nullFunction }
handler.addWalletCallResponse(WalletApiOperation.GetBalances, undefined, {
balances: [{
available: `${currency}:0`,
hasPendingTransactions: false,
pendingIncoming: `${currency}:0`,
pendingOutgoing: `${currency}:0`,
requiresUserInput: false,
}],
})
handler.addWalletCallResponse(WalletApiOperation.ListKnownBankAccounts, undefined, {
accounts: []
});
const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } =
mountHook(() => mountHook(() =>
useComponentState( useComponentState(
{ currency, onCancel: nullFunction, onSuccess: nullFunction }, props, mock
{
getBalance: async () =>
({
balances: [{ available: `${currency}:0` }],
} as Partial<BalancesResponse>),
listKnownBankAccounts: async () => ({ accounts: {} }),
} as Partial<typeof wxApi> as any,
), ),
); );
{ {
const { status } = getLastResultOrThrow(); const { status } = pullLastResultOrThrow();
expect(status).equal("loading"); expect(status).equal("loading");
} }
await waitNextUpdate(); expect(await waitForStateUpdate()).true;
{ {
const { status } = getLastResultOrThrow(); const { status } = pullLastResultOrThrow();
expect(status).equal("no-enough-balance"); expect(status).equal("no-enough-balance");
} }
await assertNoPendingUpdate(); await assertNoPendingUpdate();
expect(handler.getCallingQueueState()).eq("empty")
}); });
// it("should have status 'no-accounts' when balance is not empty and accounts is empty", async () => { it("should have status 'no-accounts' when balance is not empty and accounts is empty", async () => {
// const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = const { handler, mock } = createWalletApiMock();
// mountHook(() => const props = { currency, onCancel: nullFunction, onSuccess: nullFunction }
// useComponentState({ currency, onCancel: nullFunction, onSuccess: nullFunction }, {
// getBalance: async () =>
// ({
// balances: [{ available: `${currency}:1` }],
// } as Partial<BalancesResponse>),
// listKnownBankAccounts: async () => ({ accounts: {} }),
// } as Partial<typeof wxApi> as any),
// );
// { handler.addWalletCallResponse(WalletApiOperation.GetBalances, undefined, {
// const { status } = getLastResultOrThrow(); balances: [{
// expect(status).equal("loading"); available: `${currency}:1`,
// } hasPendingTransactions: false,
pendingIncoming: `${currency}:0`,
pendingOutgoing: `${currency}:0`,
requiresUserInput: false,
}],
})
handler.addWalletCallResponse(WalletApiOperation.ListKnownBankAccounts, undefined, {
accounts: []
});
const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } =
mountHook(() =>
useComponentState(
props, mock
)
);
// await waitNextUpdate(); {
// { const { status } = pullLastResultOrThrow();
// const r = getLastResultOrThrow(); expect(status).equal("loading");
// if (r.status !== "no-accounts") expect.fail(); }
expect(await waitForStateUpdate()).true;
{
const r = pullLastResultOrThrow();
if (r.status !== "no-accounts") expect.fail();
// expect(r.cancelHandler.onClick).not.undefined; // expect(r.cancelHandler.onClick).not.undefined;
// } }
// await assertNoPendingUpdate(); await assertNoPendingUpdate();
// }); expect(handler.getCallingQueueState()).eq("empty")
});
const ibanPayto = { const ibanPayto = {
uri: parsePaytoUri("payto://iban/ES8877998399652238")!, uri: parsePaytoUri("payto://iban/ES8877998399652238")!,
@ -130,29 +137,38 @@ describe("DepositPage states", () => {
}; };
it("should have status 'ready' but unable to deposit ", async () => { it("should have status 'ready' but unable to deposit ", async () => {
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = const { handler, mock } = createWalletApiMock();
const props = { currency, onCancel: nullFunction, onSuccess: nullFunction }
handler.addWalletCallResponse(WalletApiOperation.GetBalances, undefined, {
balances: [{
available: `${currency}:1`,
hasPendingTransactions: false,
pendingIncoming: `${currency}:0`,
pendingOutgoing: `${currency}:0`,
requiresUserInput: false,
}],
})
handler.addWalletCallResponse(WalletApiOperation.ListKnownBankAccounts, undefined, {
accounts: [ibanPayto]
});
const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } =
mountHook(() => mountHook(() =>
useComponentState( useComponentState(
{ currency, onCancel: nullFunction, onSuccess: nullFunction }, props, mock
{
getBalance: async () =>
({
balances: [{ available: `${currency}:1` }],
} as Partial<BalancesResponse>),
listKnownBankAccounts: async () => ({ accounts: [ibanPayto] }),
} as Partial<typeof wxApi> as any,
), ),
); );
{ {
const { status } = getLastResultOrThrow(); const { status } = pullLastResultOrThrow();
expect(status).equal("loading"); expect(status).equal("loading");
} }
await waitNextUpdate(); expect(await waitForStateUpdate()).true;
{ {
const r = getLastResultOrThrow(); const r = pullLastResultOrThrow();
if (r.status !== "ready") expect.fail(); if (r.status !== "ready") expect.fail();
expect(r.cancelHandler.onClick).not.undefined; expect(r.cancelHandler.onClick).not.undefined;
expect(r.currency).eq(currency); expect(r.currency).eq(currency);
@ -162,33 +178,46 @@ describe("DepositPage states", () => {
} }
await assertNoPendingUpdate(); await assertNoPendingUpdate();
expect(handler.getCallingQueueState()).eq("empty")
}); });
it.skip("should not be able to deposit more than the balance ", async () => { it("should not be able to deposit more than the balance ", async () => {
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = const { handler, mock } = createWalletApiMock();
const props = { currency, onCancel: nullFunction, onSuccess: nullFunction }
handler.addWalletCallResponse(WalletApiOperation.GetBalances, undefined, {
balances: [{
available: `${currency}:5`,
hasPendingTransactions: false,
pendingIncoming: `${currency}:0`,
pendingOutgoing: `${currency}:0`,
requiresUserInput: false,
}],
})
handler.addWalletCallResponse(WalletApiOperation.ListKnownBankAccounts, undefined, {
accounts: [ibanPayto]
});
handler.addWalletCallResponse(WalletApiOperation.GetFeeForDeposit, undefined, withoutFee())
handler.addWalletCallResponse(WalletApiOperation.GetFeeForDeposit, undefined, withoutFee())
handler.addWalletCallResponse(WalletApiOperation.GetFeeForDeposit, undefined, withoutFee())
const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } =
mountHook(() => mountHook(() =>
useComponentState( useComponentState(
{ currency, onCancel: nullFunction, onSuccess: nullFunction }, props, mock
{
getBalance: async () =>
({
balances: [{ available: `${currency}:1` }],
} as Partial<BalancesResponse>),
listKnownBankAccounts: async () => ({ accounts: [ibanPayto] }),
getFeeForDeposit: withoutFee,
} as Partial<typeof wxApi> as any,
), ),
); );
{ {
const { status } = getLastResultOrThrow(); const { status } = pullLastResultOrThrow();
expect(status).equal("loading"); expect(status).equal("loading");
} }
await waitNextUpdate(); expect(await waitForStateUpdate()).true;
const accountSelected = stringifyPaytoUri(ibanPayto.uri)
{ {
const r = getLastResultOrThrow(); const r = pullLastResultOrThrow();
if (r.status !== "ready") expect.fail(); if (r.status !== "ready") expect.fail();
expect(r.cancelHandler.onClick).not.undefined; expect(r.cancelHandler.onClick).not.undefined;
expect(r.currency).eq(currency); expect(r.currency).eq(currency);
@ -196,32 +225,20 @@ describe("DepositPage states", () => {
expect(r.amount.value).eq("0"); expect(r.amount.value).eq("0");
expect(r.depositHandler.onClick).undefined; expect(r.depositHandler.onClick).undefined;
expect(r.totalFee).deep.eq(Amounts.parseOrThrow(`${currency}:0`)); expect(r.totalFee).deep.eq(Amounts.parseOrThrow(`${currency}:0`));
expect(r.account.onChange).not.undefined;
r.amount.onInput("10"); r.account.onChange!(accountSelected)
} }
await waitNextUpdate(); expect(await waitForStateUpdate()).true;
{ {
const r = getLastResultOrThrow(); const r = pullLastResultOrThrow();
if (r.status !== "ready") expect.fail(); if (r.status !== "ready") expect.fail();
expect(r.cancelHandler.onClick).not.undefined; expect(r.cancelHandler.onClick).not.undefined;
expect(r.currency).eq(currency); expect(r.currency).eq(currency);
expect(r.account.value).eq(""); expect(r.account.value).eq(accountSelected);
expect(r.amount.value).eq("10"); expect(r.amount.value).eq("0");
expect(r.totalFee).deep.eq(Amounts.parseOrThrow(`${currency}:0`));
expect(r.depositHandler.onClick).undefined;
}
await waitNextUpdate();
{
const r = getLastResultOrThrow();
if (r.status !== "ready") expect.fail();
expect(r.cancelHandler.onClick).not.undefined;
expect(r.currency).eq(currency);
expect(r.account.value).eq("");
expect(r.amount.value).eq("10");
expect(r.totalFee).deep.eq(Amounts.parseOrThrow(`${currency}:0`)); expect(r.totalFee).deep.eq(Amounts.parseOrThrow(`${currency}:0`));
expect(r.depositHandler.onClick).undefined; expect(r.depositHandler.onClick).undefined;
} }
@ -229,100 +246,110 @@ describe("DepositPage states", () => {
await assertNoPendingUpdate(); await assertNoPendingUpdate();
}); });
it.skip("should calculate the fee upon entering amount ", async () => { // it("should calculate the fee upon entering amount ", async () => {
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = // const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } =
mountHook(() => // mountHook(() =>
useComponentState( // useComponentState(
{ currency, onCancel: nullFunction, onSuccess: nullFunction }, // { currency, onCancel: nullFunction, onSuccess: nullFunction },
{ // {
getBalance: async () => // getBalance: async () =>
({ // ({
balances: [{ available: `${currency}:1` }], // balances: [{ available: `${currency}:1` }],
} as Partial<BalancesResponse>), // } as Partial<BalancesResponse>),
listKnownBankAccounts: async () => ({ accounts: [ibanPayto] }), // listKnownBankAccounts: async () => ({ accounts: [ibanPayto] }),
getFeeForDeposit: withSomeFee, // getFeeForDeposit: withSomeFee,
} as Partial<typeof wxApi> as any, // } as Partial<typeof wxApi> as any,
), // ),
); // );
{ // {
const { status } = getLastResultOrThrow(); // const { status } = getLastResultOrThrow();
expect(status).equal("loading"); // expect(status).equal("loading");
} // }
await waitNextUpdate(); // await waitNextUpdate();
{ // {
const r = getLastResultOrThrow(); // const r = getLastResultOrThrow();
if (r.status !== "ready") expect.fail(); // if (r.status !== "ready") expect.fail();
expect(r.cancelHandler.onClick).not.undefined; // expect(r.cancelHandler.onClick).not.undefined;
expect(r.currency).eq(currency); // expect(r.currency).eq(currency);
expect(r.account.value).eq(""); // expect(r.account.value).eq(accountSelected);
expect(r.amount.value).eq("0"); // expect(r.amount.value).eq("0");
expect(r.depositHandler.onClick).undefined; // expect(r.depositHandler.onClick).undefined;
expect(r.totalFee).deep.eq(Amounts.parseOrThrow(`${currency}:0`)); // expect(r.totalFee).deep.eq(Amounts.parseOrThrow(`${currency}:0`));
r.amount.onInput("10"); // r.amount.onInput("10");
} // }
await waitNextUpdate(); // expect(await waitForStateUpdate()).true;
{ // {
const r = getLastResultOrThrow(); // const r = pullLastResultOrThrow();
if (r.status !== "ready") expect.fail(); // if (r.status !== "ready") expect.fail();
expect(r.cancelHandler.onClick).not.undefined; // expect(r.cancelHandler.onClick).not.undefined;
expect(r.currency).eq(currency); // expect(r.currency).eq(currency);
expect(r.account.value).eq(""); // expect(r.account.value).eq(accountSelected);
expect(r.amount.value).eq("10"); // expect(r.amount.value).eq("10");
expect(r.totalFee).deep.eq(Amounts.parseOrThrow(`${currency}:0`)); // expect(r.totalFee).deep.eq(Amounts.parseOrThrow(`${currency}:0`));
expect(r.totalToDeposit).deep.eq(Amounts.parseOrThrow(`${currency}:10`)); // expect(r.depositHandler.onClick).undefined;
expect(r.depositHandler.onClick).undefined;
}
await waitNextUpdate(); // r.amount.onInput("3");
// }
{ // expect(await waitForStateUpdate()).true;
const r = getLastResultOrThrow();
if (r.status !== "ready") expect.fail();
expect(r.cancelHandler.onClick).not.undefined;
expect(r.currency).eq(currency);
expect(r.account.value).eq("");
expect(r.amount.value).eq("10");
expect(r.totalFee).deep.eq(Amounts.parseOrThrow(`${currency}:3`));
expect(r.totalToDeposit).deep.eq(Amounts.parseOrThrow(`${currency}:7`));
expect(r.depositHandler.onClick).undefined;
}
await assertNoPendingUpdate(); // {
// const r = pullLastResultOrThrow();
// if (r.status !== "ready") expect.fail();
// expect(r.cancelHandler.onClick).not.undefined;
// expect(r.currency).eq(currency);
// expect(r.account.value).eq(accountSelected);
// expect(r.amount.value).eq("3");
// expect(r.totalFee).deep.eq(Amounts.parseOrThrow(`${currency}:0`));
// expect(r.depositHandler.onClick).not.undefined;
// }
// await assertNoPendingUpdate();
// expect(handler.getCallingQueueState()).eq("empty")
// });
it("should calculate the fee upon entering amount ", async () => {
const { handler, mock } = createWalletApiMock();
const props = { currency, onCancel: nullFunction, onSuccess: nullFunction }
handler.addWalletCallResponse(WalletApiOperation.GetBalances, undefined, {
balances: [{
available: `${currency}:10`,
hasPendingTransactions: false,
pendingIncoming: `${currency}:0`,
pendingOutgoing: `${currency}:0`,
requiresUserInput: false,
}],
})
handler.addWalletCallResponse(WalletApiOperation.ListKnownBankAccounts, undefined, {
accounts: [ibanPayto]
}); });
handler.addWalletCallResponse(WalletApiOperation.GetFeeForDeposit, undefined, withSomeFee())
handler.addWalletCallResponse(WalletApiOperation.GetFeeForDeposit, undefined, withSomeFee())
it.skip("should calculate the fee upon selecting account ", async () => { const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } =
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
mountHook(() => mountHook(() =>
useComponentState( useComponentState(
{ currency, onCancel: nullFunction, onSuccess: nullFunction }, props, mock
{
getBalance: async () =>
({
balances: [{ available: `${currency}:1` }],
} as Partial<BalancesResponse>),
listKnownBankAccounts: async () => ({
accounts: [ibanPayto, talerBankPayto],
}),
getFeeForDeposit: freeJustForIBAN,
} as Partial<typeof wxApi> as any,
), ),
); );
{ {
const { status } = getLastResultOrThrow(); const { status } = pullLastResultOrThrow();
expect(status).equal("loading"); expect(status).equal("loading");
} }
await waitNextUpdate(); expect(await waitForStateUpdate()).true;
const accountSelected = stringifyPaytoUri(ibanPayto.uri)
{ {
const r = getLastResultOrThrow(); const r = pullLastResultOrThrow();
if (r.status !== "ready") expect.fail(); if (r.status !== "ready") expect.fail();
expect(r.cancelHandler.onClick).not.undefined; expect(r.cancelHandler.onClick).not.undefined;
expect(r.currency).eq(currency); expect(r.currency).eq(currency);
@ -330,198 +357,42 @@ describe("DepositPage states", () => {
expect(r.amount.value).eq("0"); expect(r.amount.value).eq("0");
expect(r.depositHandler.onClick).undefined; expect(r.depositHandler.onClick).undefined;
expect(r.totalFee).deep.eq(Amounts.parseOrThrow(`${currency}:0`)); expect(r.totalFee).deep.eq(Amounts.parseOrThrow(`${currency}:0`));
expect(r.totalToDeposit).deep.eq(Amounts.parseOrThrow(`${currency}:0`)); expect(r.account.onChange).not.undefined;
if (r.account.onChange === undefined) expect.fail(); r.account.onChange!(accountSelected)
r.account.onChange(stringifyPaytoUri(ibanPayto.uri));
} }
await waitNextUpdate(""); expect(await waitForStateUpdate()).true;
{ {
const r = getLastResultOrThrow(); const r = pullLastResultOrThrow();
if (r.status !== "ready") expect.fail(); if (r.status !== "ready") expect.fail();
expect(r.cancelHandler.onClick).not.undefined; expect(r.cancelHandler.onClick).not.undefined;
expect(r.currency).eq(currency); expect(r.currency).eq(currency);
expect(r.account.value).eq(stringifyPaytoUri(ibanPayto.uri)); expect(r.account.value).eq(accountSelected);
expect(r.amount.value).eq("0"); expect(r.amount.value).eq("0");
expect(r.totalFee).deep.eq(Amounts.parseOrThrow(`${currency}:0`));
expect(r.totalToDeposit).deep.eq(Amounts.parseOrThrow(`${currency}:0`));
expect(r.depositHandler.onClick).undefined; expect(r.depositHandler.onClick).undefined;
}
await waitNextUpdate("");
{
const r = getLastResultOrThrow();
if (r.status !== "ready") expect.fail();
expect(r.cancelHandler.onClick).not.undefined;
expect(r.currency).eq(currency);
expect(r.account.value).eq(stringifyPaytoUri(ibanPayto.uri));
expect(r.amount.value).eq("0");
expect(r.totalFee).deep.eq(Amounts.parseOrThrow(`${currency}:3`)); expect(r.totalFee).deep.eq(Amounts.parseOrThrow(`${currency}:3`));
expect(r.totalToDeposit).deep.eq(Amounts.parseOrThrow(`${currency}:0`));
expect(r.depositHandler.onClick).undefined;
r.amount.onInput("10"); r.amount.onInput("10");
} }
await waitNextUpdate(""); expect(await waitForStateUpdate()).true;
{ {
const r = getLastResultOrThrow(); const r = pullLastResultOrThrow();
if (r.status !== "ready") expect.fail(); if (r.status !== "ready") expect.fail();
expect(r.cancelHandler.onClick).not.undefined; expect(r.cancelHandler.onClick).not.undefined;
expect(r.currency).eq(currency); expect(r.currency).eq(currency);
expect(r.account.value).eq(stringifyPaytoUri(ibanPayto.uri)); expect(r.account.value).eq(accountSelected);
expect(r.amount.value).eq("10");
expect(r.totalFee).deep.eq(Amounts.parseOrThrow(`${currency}:3`));
expect(r.totalToDeposit).deep.eq(Amounts.parseOrThrow(`${currency}:7`));
expect(r.depositHandler.onClick).undefined;
}
await waitNextUpdate("");
{
const r = getLastResultOrThrow();
if (r.status !== "ready") expect.fail();
expect(r.cancelHandler.onClick).not.undefined;
expect(r.currency).eq(currency);
expect(r.account.value).eq(stringifyPaytoUri(ibanPayto.uri));
expect(r.amount.value).eq("10");
expect(r.totalFee).deep.eq(Amounts.parseOrThrow(`${currency}:3`));
expect(r.totalToDeposit).deep.eq(Amounts.parseOrThrow(`${currency}:7`));
expect(r.depositHandler.onClick).undefined;
if (r.account.onChange === undefined) expect.fail();
r.account.onChange(stringifyPaytoUri(talerBankPayto.uri));
}
await waitNextUpdate("");
{
const r = getLastResultOrThrow();
if (r.status !== "ready") expect.fail();
expect(r.cancelHandler.onClick).not.undefined;
expect(r.currency).eq(currency);
expect(r.account.value).eq(stringifyPaytoUri(talerBankPayto.uri));
expect(r.amount.value).eq("10");
expect(r.totalFee).deep.eq(Amounts.parseOrThrow(`${currency}:3`));
expect(r.totalToDeposit).deep.eq(Amounts.parseOrThrow(`${currency}:7`));
expect(r.depositHandler.onClick).undefined;
}
await waitNextUpdate("");
{
const r = getLastResultOrThrow();
if (r.status !== "ready") expect.fail();
expect(r.cancelHandler.onClick).not.undefined;
expect(r.currency).eq(currency);
expect(r.account.value).eq(stringifyPaytoUri(talerBankPayto.uri));
expect(r.amount.value).eq("10");
expect(r.totalFee).deep.eq(Amounts.parseOrThrow(`${currency}:0`));
expect(r.totalToDeposit).deep.eq(Amounts.parseOrThrow(`${currency}:10`));
expect(r.depositHandler.onClick).undefined;
}
await assertNoPendingUpdate();
});
it.skip("should be able to deposit if has the enough balance ", async () => {
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
mountHook(() =>
useComponentState(
{ currency, onCancel: nullFunction, onSuccess: nullFunction },
{
getBalance: async () =>
({
balances: [{ available: `${currency}:15` }],
} as Partial<BalancesResponse>),
listKnownBankAccounts: async () => ({ accounts: [ibanPayto] }),
getFeeForDeposit: withSomeFee,
} as Partial<typeof wxApi> as any,
),
);
{
const { status } = getLastResultOrThrow();
expect(status).equal("loading");
}
await waitNextUpdate();
{
const r = getLastResultOrThrow();
if (r.status !== "ready") expect.fail();
expect(r.cancelHandler.onClick).not.undefined;
expect(r.currency).eq(currency);
expect(r.account.value).eq("");
expect(r.amount.value).eq("0");
expect(r.depositHandler.onClick).undefined;
expect(r.totalFee).deep.eq(Amounts.parseOrThrow(`${currency}:0`));
r.amount.onInput("10");
}
await waitNextUpdate();
{
const r = getLastResultOrThrow();
if (r.status !== "ready") expect.fail();
expect(r.cancelHandler.onClick).not.undefined;
expect(r.currency).eq(currency);
expect(r.account.value).eq("");
expect(r.amount.value).eq("10");
expect(r.totalFee).deep.eq(Amounts.parseOrThrow(`${currency}:0`));
expect(r.totalToDeposit).deep.eq(Amounts.parseOrThrow(`${currency}:10`));
expect(r.depositHandler.onClick).undefined;
}
await waitNextUpdate();
{
const r = getLastResultOrThrow();
if (r.status !== "ready") expect.fail();
expect(r.cancelHandler.onClick).not.undefined;
expect(r.currency).eq(currency);
expect(r.account.value).eq("");
expect(r.amount.value).eq("10"); expect(r.amount.value).eq("10");
expect(r.totalFee).deep.eq(Amounts.parseOrThrow(`${currency}:3`)); expect(r.totalFee).deep.eq(Amounts.parseOrThrow(`${currency}:3`));
expect(r.totalToDeposit).deep.eq(Amounts.parseOrThrow(`${currency}:7`)); expect(r.totalToDeposit).deep.eq(Amounts.parseOrThrow(`${currency}:7`));
expect(r.depositHandler.onClick).not.undefined; expect(r.depositHandler.onClick).not.undefined;
r.amount.onInput("13");
}
await waitNextUpdate();
{
const r = getLastResultOrThrow();
if (r.status !== "ready") expect.fail();
expect(r.cancelHandler.onClick).not.undefined;
expect(r.currency).eq(currency);
expect(r.account.value).eq("");
expect(r.amount.value).eq("13");
expect(r.totalFee).deep.eq(Amounts.parseOrThrow(`${currency}:3`));
expect(r.totalToDeposit).deep.eq(Amounts.parseOrThrow(`${currency}:10`));
expect(r.depositHandler.onClick).not.undefined;
}
await waitNextUpdate();
{
const r = getLastResultOrThrow();
if (r.status !== "ready") expect.fail();
expect(r.cancelHandler.onClick).not.undefined;
expect(r.currency).eq(currency);
expect(r.account.value).eq("");
expect(r.amount.value).eq("13");
expect(r.totalFee).deep.eq(Amounts.parseOrThrow(`${currency}:3`));
expect(r.totalToDeposit).deep.eq(Amounts.parseOrThrow(`${currency}:10`));
expect(r.depositHandler.onClick).not.undefined;
} }
await assertNoPendingUpdate(); await assertNoPendingUpdate();
expect(handler.getCallingQueueState()).eq("empty")
}); });
}); });

View File

@ -15,6 +15,7 @@
*/ */
import { Amounts } from "@gnu-taler/taler-util"; import { Amounts } from "@gnu-taler/taler-util";
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { styled } from "@linaria/react"; import { styled } from "@linaria/react";
import { Fragment, h, VNode } from "preact"; import { Fragment, h, VNode } from "preact";
import { useState } from "preact/hooks"; import { useState } from "preact/hooks";
@ -36,7 +37,7 @@ import { TextField } from "../mui/TextField.js";
import { Pages } from "../NavigationBar.js"; import { Pages } from "../NavigationBar.js";
import arrowIcon from "../svg/chevron-down.svg"; import arrowIcon from "../svg/chevron-down.svg";
import bankIcon from "../svg/ri-bank-line.svg"; import bankIcon from "../svg/ri-bank-line.svg";
import * as wxApi from "../wxApi.js"; import { wxApi } from "../wxApi.js";
const Container = styled.div` const Container = styled.div`
display: flex; display: flex;
@ -171,7 +172,9 @@ export function SelectCurrency({
}): VNode { }): VNode {
const { i18n } = useTranslationContext(); const { i18n } = useTranslationContext();
const hook = useAsyncAsHook(wxApi.listExchanges); const hook = useAsyncAsHook(() =>
wxApi.wallet.call(WalletApiOperation.ListExchanges, {}),
);
if (!hook) { if (!hook) {
return <Loading />; return <Loading />;

View File

@ -21,7 +21,10 @@ import {
ExchangeListItem, ExchangeListItem,
NotificationType, NotificationType,
} from "@gnu-taler/taler-util"; } from "@gnu-taler/taler-util";
import { PendingTaskInfo } from "@gnu-taler/taler-wallet-core"; import {
PendingTaskInfo,
WalletApiOperation,
} from "@gnu-taler/taler-wallet-core";
import { format } from "date-fns"; import { format } from "date-fns";
import { Fragment, h, VNode } from "preact"; import { Fragment, h, VNode } from "preact";
import { useEffect, useRef, useState } from "preact/hooks"; import { useEffect, useRef, useState } from "preact/hooks";
@ -33,8 +36,7 @@ import { useAsyncAsHook } from "../hooks/useAsyncAsHook.js";
import { useDiagnostics } from "../hooks/useDiagnostics.js"; import { useDiagnostics } from "../hooks/useDiagnostics.js";
import { Button } from "../mui/Button.js"; import { Button } from "../mui/Button.js";
import { Grid } from "../mui/Grid.js"; import { Grid } from "../mui/Grid.js";
import { Paper } from "../mui/Paper.js"; import { wxApi } from "../wxApi.js";
import * as wxApi from "../wxApi.js";
export function DeveloperPage(): VNode { export function DeveloperPage(): VNode {
const [status, timedOut] = useDiagnostics(); const [status, timedOut] = useDiagnostics();
@ -44,9 +46,12 @@ export function DeveloperPage(): VNode {
listenAllEvents.includes = (e) => e !== "waiting-for-retry"; // includes every event listenAllEvents.includes = (e) => e !== "waiting-for-retry"; // includes every event
const response = useAsyncAsHook(async () => { const response = useAsyncAsHook(async () => {
const op = await wxApi.getPendingOperations(); const op = await wxApi.wallet.call(
const c = await wxApi.dumpCoins(); WalletApiOperation.GetPendingOperations,
const ex = await wxApi.listExchanges(); {},
);
const c = await wxApi.wallet.call(WalletApiOperation.DumpCoins, {});
const ex = await wxApi.wallet.call(WalletApiOperation.ListExchanges, {});
return { return {
operations: op.pendingOperations, operations: op.pendingOperations,
coins: c.coins, coins: c.coins,
@ -55,9 +60,10 @@ export function DeveloperPage(): VNode {
}); });
useEffect(() => { useEffect(() => {
return wxApi.onUpdateNotification(listenAllEvents, () => { return wxApi.listener.onUpdateNotification(
response?.retry(); listenAllEvents,
}); response?.retry,
);
}); });
const nonResponse = { operations: [], coins: [], exchanges: [] }; const nonResponse = { operations: [], coins: [], exchanges: [] };
@ -76,7 +82,7 @@ export function DeveloperPage(): VNode {
coins={coins} coins={coins}
exchanges={exchanges} exchanges={exchanges}
onDownloadDatabase={async () => { onDownloadDatabase={async () => {
const db = await wxApi.exportDB(); const db = await wxApi.wallet.call(WalletApiOperation.ExportDb, {});
return JSON.stringify(db); return JSON.stringify(db);
}} }}
/> />
@ -131,7 +137,9 @@ export function View({
} }
const fileRef = useRef<HTMLInputElement>(null); const fileRef = useRef<HTMLInputElement>(null);
async function onImportDatabase(str: string): Promise<void> { async function onImportDatabase(str: string): Promise<void> {
return wxApi.importDB(JSON.parse(str)); return wxApi.wallet.call(WalletApiOperation.ImportDb, {
dump: JSON.parse(str),
});
} }
const currencies: { [ex: string]: string } = {}; const currencies: { [ex: string]: string } = {};
const money_by_exchange = coins.reduce( const money_by_exchange = coins.reduce(
@ -169,7 +177,7 @@ export function View({
onClick={() => onClick={() =>
confirmReset( confirmReset(
i18n.str`Do you want to IRREVOCABLY DESTROY everything inside your wallet and LOSE ALL YOUR COINS?`, i18n.str`Do you want to IRREVOCABLY DESTROY everything inside your wallet and LOSE ALL YOUR COINS?`,
wxApi.resetDb, () => wxApi.background.resetDb(),
) )
} }
> >
@ -182,7 +190,7 @@ export function View({
onClick={() => onClick={() =>
confirmReset( confirmReset(
i18n.str`TESTING: This may delete all your coin, proceed with caution`, i18n.str`TESTING: This may delete all your coin, proceed with caution`,
wxApi.runGarbageCollector, () => wxApi.background.runGarbageCollector(),
) )
} }
> >

View File

@ -17,9 +17,9 @@
import { Loading } from "../../components/Loading.js"; import { Loading } from "../../components/Loading.js";
import { HookError } from "../../hooks/useAsyncAsHook.js"; import { HookError } from "../../hooks/useAsyncAsHook.js";
import { compose, StateViewMap } from "../../utils/index.js"; import { compose, StateViewMap } from "../../utils/index.js";
import { LoadingUriView, ReadyView } from "./views.js"; import { wxApi } from "../../wxApi.js";
import * as wxApi from "../../wxApi.js";
import { useComponentState } from "./state.js"; import { useComponentState } from "./state.js";
import { LoadingUriView, ReadyView } from "./views.js";
export interface Props { export interface Props {
p: string; p: string;

View File

@ -14,7 +14,7 @@
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/ */
import * as wxApi from "../../wxApi.js"; import { wxApi } from "../../wxApi.js";
import { Props, State } from "./index.js"; import { Props, State } from "./index.js";
export function useComponentState({ p }: Props, api: typeof wxApi): State { export function useComponentState({ p }: Props, api: typeof wxApi): State {

View File

@ -18,11 +18,12 @@ import {
canonicalizeBaseUrl, canonicalizeBaseUrl,
TalerConfigResponse, TalerConfigResponse,
} from "@gnu-taler/taler-util"; } from "@gnu-taler/taler-util";
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { h, VNode } from "preact"; import { h, VNode } from "preact";
import { useState } from "preact/hooks"; import { useState } from "preact/hooks";
import { useAsyncAsHook } from "../hooks/useAsyncAsHook.js"; import { useAsyncAsHook } from "../hooks/useAsyncAsHook.js";
import { queryToSlashKeys } from "../utils/index.js"; import { queryToSlashKeys } from "../utils/index.js";
import * as wxApi from "../wxApi.js"; import { wxApi } from "../wxApi.js";
import { ExchangeAddConfirmPage } from "./ExchangeAddConfirm.js"; import { ExchangeAddConfirmPage } from "./ExchangeAddConfirm.js";
import { ExchangeSetUrlPage } from "./ExchangeSetUrl.js"; import { ExchangeSetUrlPage } from "./ExchangeSetUrl.js";
@ -36,7 +37,9 @@ export function ExchangeAddPage({ currency, onBack }: Props): VNode {
{ url: string; config: TalerConfigResponse } | undefined { url: string; config: TalerConfigResponse } | undefined
>(undefined); >(undefined);
const knownExchangesResponse = useAsyncAsHook(wxApi.listExchanges); const knownExchangesResponse = useAsyncAsHook(() =>
wxApi.wallet.call(WalletApiOperation.ListExchanges, {}),
);
const knownExchanges = !knownExchangesResponse const knownExchanges = !knownExchangesResponse
? [] ? []
: knownExchangesResponse.hasError : knownExchangesResponse.hasError
@ -72,7 +75,7 @@ export function ExchangeAddPage({ currency, onBack }: Props): VNode {
url={verifying.url} url={verifying.url}
onCancel={onBack} onCancel={onBack}
onConfirm={async () => { onConfirm={async () => {
await wxApi.addExchange({ await wxApi.wallet.call(WalletApiOperation.AddExchange, {
exchangeBaseUrl: canonicalizeBaseUrl(verifying.url), exchangeBaseUrl: canonicalizeBaseUrl(verifying.url),
forceUpdate: true, forceUpdate: true,
}); });

View File

@ -18,15 +18,14 @@ import {
DenomOperationMap, DenomOperationMap,
ExchangeFullDetails, ExchangeFullDetails,
ExchangeListItem, ExchangeListItem,
FeeDescriptionPair, FeeDescriptionPair
} from "@gnu-taler/taler-util"; } from "@gnu-taler/taler-util";
import { Loading } from "../../components/Loading.js"; import { Loading } from "../../components/Loading.js";
import { TermsState } from "../../components/TermsOfService/utils.js";
import { HookError } from "../../hooks/useAsyncAsHook.js"; import { HookError } from "../../hooks/useAsyncAsHook.js";
import { State as SelectExchangeState } from "../../hooks/useSelectedExchange.js"; import { State as SelectExchangeState } from "../../hooks/useSelectedExchange.js";
import { ButtonHandler, SelectFieldHandler } from "../../mui/handlers.js"; import { ButtonHandler, SelectFieldHandler } from "../../mui/handlers.js";
import { compose, StateViewMap } from "../../utils/index.js"; import { compose, StateViewMap } from "../../utils/index.js";
import * as wxApi from "../../wxApi.js"; import { wxApi } from "../../wxApi.js";
import { useComponentState } from "./state.js"; import { useComponentState } from "./state.js";
import { import {
ComparingView, ComparingView,
@ -34,7 +33,7 @@ import {
NoExchangesView, NoExchangesView,
PrivacyContentView, PrivacyContentView,
ReadyView, ReadyView,
TosContentView, TosContentView
} from "./views.js"; } from "./views.js";
export interface Props { export interface Props {

View File

@ -15,10 +15,10 @@
*/ */
import { DenomOperationMap, FeeDescription } from "@gnu-taler/taler-util"; import { DenomOperationMap, FeeDescription } from "@gnu-taler/taler-util";
import { createPairTimeline } from "@gnu-taler/taler-wallet-core"; import { createPairTimeline, WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { useState } from "preact/hooks"; import { useState } from "preact/hooks";
import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js"; import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js";
import * as wxApi from "../../wxApi.js"; import { wxApi } from "../../wxApi.js";
import { Props, State } from "./index.js"; import { Props, State } from "./index.js";
export function useComponentState( export function useComponentState(
@ -36,22 +36,20 @@ export function useComponentState(
const [value, setValue] = useState(String(initialValue)); const [value, setValue] = useState(String(initialValue));
const hook = useAsyncAsHook(async () => { const hook = useAsyncAsHook(async () => {
// const { exchanges } = await api.listExchanges();
const selectedIdx = parseInt(value, 10); const selectedIdx = parseInt(value, 10);
const selectedExchange = const selectedExchange =
exchanges.length == 0 ? undefined : exchanges[selectedIdx]; exchanges.length == 0 ? undefined : exchanges[selectedIdx];
const selected = !selectedExchange const selected = !selectedExchange
? undefined ? undefined
: await api.getExchangeDetailedInfo(selectedExchange.exchangeBaseUrl); : await api.wallet.call(WalletApiOperation.GetExchangeDetailedInfo, { exchangeBaseUrl: selectedExchange.exchangeBaseUrl });
const initialExchange = const initialExchange =
selectedIdx === initialValue ? undefined : exchanges[initialValue]; selectedIdx === initialValue ? undefined : exchanges[initialValue];
const original = !initialExchange const original = !initialExchange
? undefined ? undefined
: await api.getExchangeDetailedInfo(initialExchange.exchangeBaseUrl); : await api.wallet.call(WalletApiOperation.GetExchangeDetailedInfo, { exchangeBaseUrl: initialExchange.exchangeBaseUrl });
return { exchanges, selected, original }; return { exchanges, selected: selected?.exchange, original: original?.exchange };
}, [value]); }, [value]);
const [showingTos, setShowingTos] = useState<string | undefined>(undefined); const [showingTos, setShowingTos] = useState<string | undefined>(undefined);

View File

@ -20,6 +20,7 @@ import {
NotificationType, NotificationType,
Transaction, Transaction,
} from "@gnu-taler/taler-util"; } from "@gnu-taler/taler-util";
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { Fragment, h, VNode } from "preact"; import { Fragment, h, VNode } from "preact";
import { useEffect, useState } from "preact/hooks"; import { useEffect, useState } from "preact/hooks";
import { Loading } from "../components/Loading.js"; import { Loading } from "../components/Loading.js";
@ -38,7 +39,7 @@ import { Button } from "../mui/Button.js";
import { NoBalanceHelp } from "../popup/NoBalanceHelp.js"; import { NoBalanceHelp } from "../popup/NoBalanceHelp.js";
import DownloadIcon from "../svg/download_24px.svg"; import DownloadIcon from "../svg/download_24px.svg";
import UploadIcon from "../svg/upload_24px.svg"; import UploadIcon from "../svg/upload_24px.svg";
import * as wxApi from "../wxApi.js"; import { wxApi } from "../wxApi.js";
interface Props { interface Props {
currency?: string; currency?: string;
@ -52,16 +53,14 @@ export function HistoryPage({
}: Props): VNode { }: Props): VNode {
const { i18n } = useTranslationContext(); const { i18n } = useTranslationContext();
const state = useAsyncAsHook(async () => ({ const state = useAsyncAsHook(async () => ({
b: await wxApi.getBalance(), b: await wxApi.wallet.call(WalletApiOperation.GetBalances, {}),
tx: await wxApi.getTransactions(), tx: await wxApi.wallet.call(WalletApiOperation.GetTransactions, {}),
})); }));
useEffect(() => { useEffect(() => {
return wxApi.onUpdateNotification( return wxApi.listener.onUpdateNotification(
[NotificationType.WithdrawGroupFinished], [NotificationType.WithdrawGroupFinished],
() => { state?.retry,
state?.retry();
},
); );
}); });

View File

@ -22,13 +22,14 @@ import {
parsePaytoUri, parsePaytoUri,
PaytoUri, PaytoUri,
} from "@gnu-taler/taler-util"; } from "@gnu-taler/taler-util";
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { h, VNode } from "preact"; import { h, VNode } from "preact";
import { useEffect, useState } from "preact/hooks"; import { useEffect, useState } from "preact/hooks";
import { Loading } from "../components/Loading.js"; import { Loading } from "../components/Loading.js";
import { LoadingError } from "../components/LoadingError.js"; import { LoadingError } from "../components/LoadingError.js";
import { useTranslationContext } from "../context/translation.js"; import { useTranslationContext } from "../context/translation.js";
import { useAsyncAsHook } from "../hooks/useAsyncAsHook.js"; import { useAsyncAsHook } from "../hooks/useAsyncAsHook.js";
import * as wxApi from "../wxApi.js"; import { wxApi } from "../wxApi.js";
import { CreateManualWithdraw } from "./CreateManualWithdraw.js"; import { CreateManualWithdraw } from "./CreateManualWithdraw.js";
import { ReserveCreated } from "./ReserveCreated.js"; import { ReserveCreated } from "./ReserveCreated.js";
@ -50,11 +51,14 @@ export function ManualWithdrawPage({ amount, onCancel }: Props): VNode {
>(undefined); >(undefined);
const [error, setError] = useState<string | undefined>(undefined); const [error, setError] = useState<string | undefined>(undefined);
const state = useAsyncAsHook(wxApi.listExchanges); const state = useAsyncAsHook(() =>
wxApi.wallet.call(WalletApiOperation.ListExchanges, {}),
);
useEffect(() => { useEffect(() => {
return wxApi.onUpdateNotification([NotificationType.ExchangeAdded], () => { return wxApi.listener.onUpdateNotification(
state?.retry(); [NotificationType.ExchangeAdded],
}); state?.retry,
);
}); });
const { i18n } = useTranslationContext(); const { i18n } = useTranslationContext();
@ -63,9 +67,12 @@ export function ManualWithdrawPage({ amount, onCancel }: Props): VNode {
amount: AmountJson, amount: AmountJson,
): Promise<void> { ): Promise<void> {
try { try {
const response = await wxApi.acceptManualWithdrawal( const response = await wxApi.wallet.call(
exchangeBaseUrl, WalletApiOperation.AcceptManualWithdrawal,
Amounts.stringify(amount), {
exchangeBaseUrl: exchangeBaseUrl,
amount: Amounts.stringify(amount),
},
); );
const payto = response.exchangePaytoUris[0]; const payto = response.exchangePaytoUris[0];
const paytoURI = parsePaytoUri(payto); const paytoURI = parsePaytoUri(payto);

View File

@ -34,8 +34,7 @@ import {
import { useTranslationContext } from "../context/translation.js"; import { useTranslationContext } from "../context/translation.js";
import { Button } from "../mui/Button.js"; import { Button } from "../mui/Button.js";
import { queryToSlashConfig } from "../utils/index.js"; import { queryToSlashConfig } from "../utils/index.js";
import * as wxApi from "../wxApi.js"; import { wxApi } from "../wxApi.js";
import { wxClient } from "../wxApi.js";
interface Props { interface Props {
currency: string; currency: string;
@ -71,7 +70,7 @@ export function ProviderAddPage({ onBack }: Props): VNode {
setVerifying(undefined); setVerifying(undefined);
}} }}
onConfirm={() => { onConfirm={() => {
return wxClient return wxApi.wallet
.call(WalletApiOperation.AddBackupProvider, { .call(WalletApiOperation.AddBackupProvider, {
backupProviderBaseUrl: verifying.url, backupProviderBaseUrl: verifying.url,
name: verifying.name, name: verifying.name,

View File

@ -20,6 +20,7 @@ import {
ProviderInfo, ProviderInfo,
ProviderPaymentStatus, ProviderPaymentStatus,
ProviderPaymentType, ProviderPaymentType,
WalletApiOperation,
} from "@gnu-taler/taler-wallet-core"; } from "@gnu-taler/taler-wallet-core";
import { Fragment, h, VNode } from "preact"; import { Fragment, h, VNode } from "preact";
import { ErrorMessage } from "../components/ErrorMessage.js"; import { ErrorMessage } from "../components/ErrorMessage.js";
@ -30,7 +31,7 @@ import { Time } from "../components/Time.js";
import { useTranslationContext } from "../context/translation.js"; import { useTranslationContext } from "../context/translation.js";
import { useAsyncAsHook } from "../hooks/useAsyncAsHook.js"; import { useAsyncAsHook } from "../hooks/useAsyncAsHook.js";
import { Button } from "../mui/Button.js"; import { Button } from "../mui/Button.js";
import * as wxApi from "../wxApi.js"; import { wxApi } from "../wxApi.js";
interface Props { interface Props {
pid: string; pid: string;
@ -41,7 +42,10 @@ export function ProviderDetailPage({ pid: providerURL, onBack }: Props): VNode {
const { i18n } = useTranslationContext(); const { i18n } = useTranslationContext();
async function getProviderInfo(): Promise<ProviderInfo | null> { async function getProviderInfo(): Promise<ProviderInfo | null> {
//create a first list of backup info by currency //create a first list of backup info by currency
const status = await wxApi.getBackupInfo(); const status = await wxApi.wallet.call(
WalletApiOperation.GetBackupInfo,
{},
);
const providers = status.providers.filter( const providers = status.providers.filter(
(p) => p.syncProviderBaseUrl === providerURL, (p) => p.syncProviderBaseUrl === providerURL,
@ -72,8 +76,20 @@ export function ProviderDetailPage({ pid: providerURL, onBack }: Props): VNode {
<ProviderView <ProviderView
url={providerURL} url={providerURL}
info={state.response} info={state.response}
onSync={() => wxApi.syncOneProvider(providerURL)} onSync={async () =>
onDelete={() => wxApi.removeProvider(providerURL).then(onBack)} wxApi.wallet
.call(WalletApiOperation.RunBackupCycle, {
providers: [providerURL],
})
.then()
}
onDelete={() =>
wxApi.wallet
.call(WalletApiOperation.RemoveBackupProvider, {
provider: providerURL,
})
.then(onBack)
}
onBack={onBack} onBack={onBack}
onExtend={async () => { onExtend={async () => {
null; null;

View File

@ -43,7 +43,7 @@ import { useClipboardPermissions } from "../hooks/useClipboardPermissions.js";
import { ToggleHandler } from "../mui/handlers.js"; import { ToggleHandler } from "../mui/handlers.js";
import { Pages } from "../NavigationBar.js"; import { Pages } from "../NavigationBar.js";
import { platform } from "../platform/api.js"; import { platform } from "../platform/api.js";
import { wxClient } from "../wxApi.js"; import { wxApi } from "../wxApi.js";
const GIT_HASH = typeof __GIT_HASH__ !== "undefined" ? __GIT_HASH__ : undefined; const GIT_HASH = typeof __GIT_HASH__ !== "undefined" ? __GIT_HASH__ : undefined;
@ -55,8 +55,8 @@ export function SettingsPage(): VNode {
const webex = platform.getWalletWebExVersion(); const webex = platform.getWalletWebExVersion();
const exchangesHook = useAsyncAsHook(async () => { const exchangesHook = useAsyncAsHook(async () => {
const list = await wxClient.call(WalletApiOperation.ListExchanges, {}); const list = await wxApi.wallet.call(WalletApiOperation.ListExchanges, {});
const version = await wxClient.call(WalletApiOperation.GetVersion, {}); const version = await wxApi.wallet.call(WalletApiOperation.GetVersion, {});
return { exchanges: list.exchanges, version }; return { exchanges: list.exchanges, version };
}); });
const { exchanges, version } = const { exchanges, version } =

View File

@ -34,6 +34,7 @@ import {
TransactionType, TransactionType,
WithdrawalType, WithdrawalType,
} from "@gnu-taler/taler-util"; } from "@gnu-taler/taler-util";
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { styled } from "@linaria/react"; import { styled } from "@linaria/react";
import { differenceInSeconds } from "date-fns"; import { differenceInSeconds } from "date-fns";
import { ComponentChildren, Fragment, h, VNode } from "preact"; import { ComponentChildren, Fragment, h, VNode } from "preact";
@ -62,31 +63,33 @@ import { useTranslationContext } from "../context/translation.js";
import { useAsyncAsHook } from "../hooks/useAsyncAsHook.js"; import { useAsyncAsHook } from "../hooks/useAsyncAsHook.js";
import { Button } from "../mui/Button.js"; import { Button } from "../mui/Button.js";
import { Pages } from "../NavigationBar.js"; import { Pages } from "../NavigationBar.js";
import * as wxApi from "../wxApi.js"; import { wxApi } from "../wxApi.js";
interface Props { interface Props {
tid: string; tid: string;
goToWalletHistory: (currency?: string) => Promise<void>; goToWalletHistory: (currency?: string) => Promise<void>;
} }
async function getTransaction(tid: string): Promise<Transaction> { export function TransactionPage({
const res = await wxApi.getTransactionById(tid); tid: transactionId,
return res; goToWalletHistory,
} }: Props): VNode {
export function TransactionPage({ tid, goToWalletHistory }: Props): VNode {
const { i18n } = useTranslationContext(); const { i18n } = useTranslationContext();
const state = useAsyncAsHook(() => getTransaction(tid), [tid]); const state = useAsyncAsHook(
() =>
useEffect(() => { wxApi.wallet.call(WalletApiOperation.GetTransactionById, {
return wxApi.onUpdateNotification( transactionId,
[NotificationType.WithdrawGroupFinished], }),
() => { [transactionId],
state?.retry(); );
},
useEffect(() =>
wxApi.listener.onUpdateNotification(
[NotificationType.WithdrawGroupFinished],
state?.retry,
),
); );
});
if (!state) { if (!state) {
return <Loading />; return <Loading />;
@ -113,15 +116,23 @@ export function TransactionPage({ tid, goToWalletHistory }: Props): VNode {
onSend={async () => { onSend={async () => {
null; null;
}} }}
onDelete={() => onDelete={async () => {
wxApi.deleteTransaction(tid).then(() => goToWalletHistory(currency)) await wxApi.wallet.call(WalletApiOperation.DeleteTransaction, {
} transactionId,
onRetry={async () => });
await wxApi goToWalletHistory(currency);
.retryTransaction(tid) }}
.then(() => goToWalletHistory(currency)) onRetry={async () => {
} await wxApi.wallet.call(WalletApiOperation.RetryTransaction, {
onRefund={(id) => wxApi.applyRefundFromPurchaseId(id).then()} transactionId,
});
goToWalletHistory(currency);
}}
onRefund={async (purchaseId) => {
await wxApi.wallet.call(WalletApiOperation.ApplyRefundFromPurchaseId, {
purchaseId,
});
}}
onBack={() => goToWalletHistory(currency)} onBack={() => goToWalletHistory(currency)}
/> />
); );

View File

@ -22,77 +22,16 @@
* Imports. * Imports.
*/ */
import { import {
AcceptExchangeTosRequest, CoreApiResponse, Logger, NotificationType, WalletDiagnostics
AcceptManualWithdrawalResult,
AcceptPeerPullPaymentRequest,
AcceptPeerPullPaymentResponse,
AcceptPeerPushPaymentRequest,
AcceptPeerPushPaymentResponse,
AcceptTipRequest,
AcceptTipResponse,
AcceptWithdrawalResponse,
AddExchangeRequest,
AddKnownBankAccountsRequest,
AmountString,
ApplyRefundResponse,
BalancesResponse,
CheckPeerPullPaymentRequest,
CheckPeerPullPaymentResponse,
CheckPeerPushPaymentRequest,
CheckPeerPushPaymentResponse,
CoinDumpJson,
ConfirmPayResult,
CoreApiResponse,
CreateDepositGroupRequest,
CreateDepositGroupResponse,
DeleteTransactionRequest,
DepositGroupFees,
ExchangeFullDetails,
ExchangesListResponse,
ForgetKnownBankAccountsRequest,
GetExchangeTosResult,
GetFeeForDepositRequest,
GetWithdrawalDetailsForAmountRequest,
GetWithdrawalDetailsForUriRequest,
InitiatePeerPullPaymentRequest,
InitiatePeerPullPaymentResponse,
InitiatePeerPushPaymentRequest,
InitiatePeerPushPaymentResponse,
KnownBankAccounts,
Logger,
ManualWithdrawalDetails,
NotificationType,
PaytoUri,
PrepareDepositRequest,
PrepareDepositResponse,
PreparePayResult,
PrepareRefundRequest,
PrepareRefundResult,
PrepareTipRequest,
PrepareTipResult,
RetryTransactionRequest,
SetWalletDeviceIdRequest,
stringifyPaytoUri,
Transaction,
TransactionsResponse,
WalletCoreVersion,
WalletDiagnostics,
WithdrawUriInfoResponse,
} from "@gnu-taler/taler-util"; } from "@gnu-taler/taler-util";
import { import {
AddBackupProviderRequest, TalerError, WalletCoreApiClient,
BackupInfo,
PendingOperationsResponse,
RemoveBackupProviderRequest,
TalerError,
WalletApiOperation,
WalletContractData,
WalletCoreApiClient,
WalletCoreOpKeys, WalletCoreOpKeys,
WalletCoreRequestType, WalletCoreRequestType,
WalletCoreResponseType, WalletCoreResponseType
} from "@gnu-taler/taler-wallet-core"; } from "@gnu-taler/taler-wallet-core";
import { MessageFromBackend, platform } from "./platform/api.js"; import { MessageFromBackend, platform } from "./platform/api.js";
import { nullFunction } from "./test-utils.js";
/** /**
* *
@ -167,381 +106,39 @@ export class WxWalletCoreApiClient implements WalletCoreApiClient {
} }
} }
export const wxClient = new WxWalletCoreApiClient(); export class BackgroundApiClient {
/** public resetDb(): Promise<void> {
* Pay for a proposal.
*/
export function confirmPay(
proposalId: string,
sessionId: string | undefined,
): Promise<ConfirmPayResult> {
return wxClient.call(WalletApiOperation.ConfirmPay, {
proposalId,
sessionId,
});
}
/**
* Check upgrade information
*/
export function checkUpgrade(): Promise<UpgradeResponse> {
return callBackend("check-upgrade", {});
}
/**
* Reset database
*/
export function resetDb(): Promise<void> {
return callBackend("reset-db", {}); return callBackend("reset-db", {});
} }
/** public containsHeaderListener(): Promise<ExtendedPermissionsResponse> {
* Reset database return callBackend("containsHeaderListener", {});
*/
export function runGarbageCollector(): Promise<void> {
return callBackend("run-gc", {});
} }
export function getFeeForDeposit( public getDiagnostics(): Promise<WalletDiagnostics> {
depositPaytoUri: string,
amount: AmountString,
): Promise<DepositGroupFees> {
return callBackend("getFeeForDeposit", {
depositPaytoUri,
amount,
} as GetFeeForDepositRequest);
}
export function prepareDeposit(
depositPaytoUri: string,
amount: AmountString,
): Promise<PrepareDepositResponse> {
return callBackend("prepareDeposit", {
depositPaytoUri,
amount,
} as PrepareDepositRequest);
}
export function createDepositGroup(
depositPaytoUri: string,
amount: AmountString,
): Promise<CreateDepositGroupResponse> {
return callBackend("createDepositGroup", {
depositPaytoUri,
amount,
} as CreateDepositGroupRequest);
}
/**
* Get balances for all currencies/exchanges.
*/
export function getBalance(): Promise<BalancesResponse> {
return callBackend("getBalances", {});
}
export function getContractTermsDetails(
proposalId: string,
): Promise<WalletContractData> {
return callBackend("getContractTermsDetails", { proposalId });
}
/**
* Retrieve the full event history for this wallet.
*/
export function getTransactions(): Promise<TransactionsResponse> {
return callBackend("getTransactions", {});
}
interface CurrencyInfo {
name: string;
baseUrl: string;
pub: string;
}
interface ListOfKnownCurrencies {
auditors: CurrencyInfo[];
exchanges: CurrencyInfo[];
}
/**
* Get a list of currencies from known auditors and exchanges
*/
export function listKnownCurrencies(): Promise<ListOfKnownCurrencies> {
return callBackend("listCurrencies", {}).then((result) => {
const auditors = result.trustedAuditors.map(
(a: Record<string, string>) => ({
name: a.currency,
baseUrl: a.auditorBaseUrl,
pub: a.auditorPub,
}),
);
const exchanges = result.trustedExchanges.map(
(a: Record<string, string>) => ({
name: a.currency,
baseUrl: a.exchangeBaseUrl,
pub: a.exchangeMasterPub,
}),
);
return { auditors, exchanges };
});
}
export function listExchanges(): Promise<ExchangesListResponse> {
return callBackend("listExchanges", {});
}
export function getExchangeDetailedInfo(
exchangeBaseUrl: string,
): Promise<ExchangeFullDetails> {
return callBackend("getExchangeDetailedInfo", {
exchangeBaseUrl,
});
}
export function getVersion(): Promise<WalletCoreVersion> {
return callBackend("getVersion", {});
}
export function listKnownBankAccounts(
currency?: string,
): Promise<KnownBankAccounts> {
return callBackend("listKnownBankAccounts", { currency });
}
export function addKnownBankAccounts(
payto: PaytoUri,
currency: string,
alias: string,
): Promise<void> {
return callBackend("addKnownBankAccounts", {
payto: stringifyPaytoUri(payto),
currency,
alias,
} as AddKnownBankAccountsRequest);
}
export function forgetKnownBankAccounts(payto: string): Promise<void> {
return callBackend("forgetKnownBankAccounts", {
payto,
} as ForgetKnownBankAccountsRequest);
}
/**
* Get information about the current state of wallet backups.
*/
export function getBackupInfo(): Promise<BackupInfo> {
return callBackend("getBackupInfo", {});
}
/**
* Add a backup provider and activate it
*/
export function addBackupProvider(
backupProviderBaseUrl: string,
name: string,
): Promise<void> {
return callBackend("addBackupProvider", {
backupProviderBaseUrl,
activate: true,
name,
} as AddBackupProviderRequest);
}
export function setWalletDeviceId(walletDeviceId: string): Promise<void> {
return callBackend("setWalletDeviceId", {
walletDeviceId,
} as SetWalletDeviceIdRequest);
}
export function syncAllProviders(): Promise<void> {
return callBackend("runBackupCycle", {});
}
export function syncOneProvider(url: string): Promise<void> {
return callBackend("runBackupCycle", { providers: [url] });
}
export function removeProvider(url: string): Promise<void> {
return callBackend("removeBackupProvider", {
provider: url,
} as RemoveBackupProviderRequest);
}
export function extendedProvider(url: string): Promise<void> {
return callBackend("extendBackupProvider", { provider: url });
}
/**
* Retry a transaction
* @param transactionId
* @returns
*/
export function retryTransaction(transactionId: string): Promise<void> {
return callBackend("retryTransaction", {
transactionId,
} as RetryTransactionRequest);
}
/**
* Permanently delete a transaction from the transaction list
*/
export function deleteTransaction(transactionId: string): Promise<void> {
return callBackend("deleteTransaction", {
transactionId,
} as DeleteTransactionRequest);
}
/**
* Download a refund and accept it.
*/
export function applyRefund(
talerRefundUri: string,
): Promise<ApplyRefundResponse> {
return callBackend("applyRefund", { talerRefundUri });
}
/**
* Do refund for purchase.
*/
export function applyRefundFromPurchaseId(
purchaseId: string,
): Promise<ApplyRefundResponse> {
return callBackend("applyRefundFromPurchaseId", { purchaseId });
}
/**
* Get details about a pay operation.
*/
export function preparePay(talerPayUri: string): Promise<PreparePayResult> {
return callBackend("preparePayForUri", { talerPayUri });
}
/**
* Get details about a withdraw operation.
*/
export function acceptWithdrawal(
talerWithdrawUri: string,
selectedExchange: string,
restrictAge?: number,
): Promise<AcceptWithdrawalResponse> {
return callBackend("acceptBankIntegratedWithdrawal", {
talerWithdrawUri,
exchangeBaseUrl: selectedExchange,
restrictAge,
});
}
/**
* Create a reserve into the exchange that expect the amount indicated
* @param exchangeBaseUrl
* @param amount
* @returns
*/
export function acceptManualWithdrawal(
exchangeBaseUrl: string,
amount: string,
restrictAge?: number,
): Promise<AcceptManualWithdrawalResult> {
return callBackend("acceptManualWithdrawal", {
amount,
exchangeBaseUrl,
restrictAge,
});
}
export function setExchangeTosAccepted(
exchangeBaseUrl: string,
etag: string | undefined,
): Promise<void> {
return callBackend("setExchangeTosAccepted", {
exchangeBaseUrl,
etag,
} as AcceptExchangeTosRequest);
}
/**
* Get diagnostics information
*/
export function getDiagnostics(): Promise<WalletDiagnostics> {
return callBackend("wxGetDiagnostics", {}); return callBackend("wxGetDiagnostics", {});
} }
/** public toggleHeaderListener(
* Get diagnostics information
*/
export function toggleHeaderListener(
value: boolean, value: boolean,
): Promise<ExtendedPermissionsResponse> { ): Promise<ExtendedPermissionsResponse> {
return callBackend("toggleHeaderListener", { value }); return callBackend("toggleHeaderListener", { value });
} }
/** public runGarbageCollector(): Promise<void> {
* Get diagnostics information return callBackend("run-gc", {});
*/
export function containsHeaderListener(): Promise<ExtendedPermissionsResponse> {
return callBackend("containsHeaderListener", {});
} }
/**
* Get diagnostics information
*/
export function getWithdrawalDetailsForUri(
req: GetWithdrawalDetailsForUriRequest,
): Promise<WithdrawUriInfoResponse> {
return callBackend("getWithdrawalDetailsForUri", req);
} }
function onUpdateNotification(
export function getWithdrawalDetailsForAmount(
req: GetWithdrawalDetailsForAmountRequest,
): Promise<ManualWithdrawalDetails> {
return callBackend("getWithdrawalDetailsForAmount", req);
}
export function getExchangeTos(
exchangeBaseUrl: string,
acceptedFormat: string[],
): Promise<GetExchangeTosResult> {
return callBackend("getExchangeTos", {
exchangeBaseUrl,
acceptedFormat,
});
}
export function dumpCoins(): Promise<CoinDumpJson> {
return callBackend("dumpCoins", {});
}
export function getPendingOperations(): Promise<PendingOperationsResponse> {
return callBackend("getPendingOperations", {});
}
export function addExchange(req: AddExchangeRequest): Promise<void> {
return callBackend("addExchange", req);
}
export function prepareRefund(
req: PrepareRefundRequest,
): Promise<PrepareRefundResult> {
return callBackend("prepareRefund", req);
}
export function prepareTip(req: PrepareTipRequest): Promise<PrepareTipResult> {
return callBackend("prepareTip", req);
}
export function acceptTip(req: AcceptTipRequest): Promise<AcceptTipResponse> {
return callBackend("acceptTip", req);
}
export function exportDB(): Promise<any> {
return callBackend("exportDb", {});
}
export function importDB(dump: any): Promise<void> {
return callBackend("importDb", { dump });
}
export function onUpdateNotification(
messageTypes: Array<NotificationType>, messageTypes: Array<NotificationType>,
doCallback: () => void, doCallback: undefined | (() => void),
): () => void { ): () => void {
//if no callback, then ignore
if (!doCallback) return () => {
return
};
const onNewMessage = (message: MessageFromBackend): void => { const onNewMessage = (message: MessageFromBackend): void => {
const shouldNotify = messageTypes.includes(message.type); const shouldNotify = messageTypes.includes(message.type);
if (shouldNotify) { if (shouldNotify) {
@ -551,39 +148,11 @@ export function onUpdateNotification(
return platform.listenToWalletBackground(onNewMessage); return platform.listenToWalletBackground(onNewMessage);
} }
export function initiatePeerPushPayment( export const wxApi = {
req: InitiatePeerPushPaymentRequest, wallet: new WxWalletCoreApiClient(),
): Promise<InitiatePeerPushPaymentResponse> { background: new BackgroundApiClient(),
return callBackend("initiatePeerPushPayment", req); listener: {
onUpdateNotification
} }
export function checkPeerPushPayment(
req: CheckPeerPushPaymentRequest,
): Promise<CheckPeerPushPaymentResponse> {
return callBackend("checkPeerPushPayment", req);
}
export function acceptPeerPushPayment(
req: AcceptPeerPushPaymentRequest,
): Promise<AcceptPeerPushPaymentResponse> {
return callBackend("acceptPeerPushPayment", req);
}
export function initiatePeerPullPayment(
req: InitiatePeerPullPaymentRequest,
): Promise<InitiatePeerPullPaymentResponse> {
return callBackend("initiatePeerPullPayment", req);
}
export function checkPeerPullPayment(
req: CheckPeerPullPaymentRequest,
): Promise<CheckPeerPullPaymentResponse> {
return callBackend("checkPeerPullPayment", req);
}
export function acceptPeerPullPayment(
req: AcceptPeerPullPaymentRequest,
): Promise<AcceptPeerPullPaymentResponse> {
return callBackend("acceptPeerPullPayment", req);
} }
export function getTransactionById(tid: string): Promise<Transaction> {
return callBackend("getTransactionById", {
transactionId: tid,
});
}