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,
codecForAmountString,
} from "./amounts.js";
import {
AbsoluteTime,
codecForAbsoluteTime,
codecForTimestamp,
TalerProtocolDuration,
TalerProtocolTimestamp,
} from "./time.js";
import { BackupRecovery } from "./backup-types.js";
import {
buildCodecForObject,
codecForString,
codecOptional,
buildCodecForUnion,
Codec,
codecForList,
codecForAny,
codecForBoolean,
codecForConstString,
codecForAny,
buildCodecForUnion,
codecForNumber,
codecForList,
codecForMap,
codecForNumber,
codecForString,
codecOptional,
} 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 {
AmountString,
AuditorDenomSig,
@ -64,14 +62,16 @@ import {
UnblindedSignature,
} from "./taler-types.js";
import {
OrderShortInfo,
AbsoluteTime,
codecForAbsoluteTime,
codecForTimestamp,
TalerProtocolDuration,
TalerProtocolTimestamp,
} from "./time.js";
import {
codecForOrderShortInfo,
OrderShortInfo,
} 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.

View File

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

View File

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

View File

@ -9,7 +9,7 @@
"private": false,
"scripts": {
"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",
"compile": "tsc && ./build-fast-with-linaria.mjs",
"prepare": "pnpm compile",

View File

@ -19,13 +19,14 @@ import {
NotificationType,
Transaction,
} from "@gnu-taler/taler-util";
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { Fragment, h, JSX, VNode } from "preact";
import { useEffect } from "preact/hooks";
import { useTranslationContext } from "../context/translation.js";
import { useAsyncAsHook } from "../hooks/useAsyncAsHook.js";
import { Avatar } from "../mui/Avatar.js";
import { Typography } from "../mui/Typography.js";
import * as wxApi from "../wxApi.js";
import { wxApi } from "../wxApi.js";
import Banner from "./Banner.js";
import { Time } from "./Time.js";
@ -34,14 +35,14 @@ interface Props extends JSX.HTMLAttributes {
}
export function PendingTransactions({ goToTransaction }: Props): VNode {
const state = useAsyncAsHook(wxApi.getTransactions);
const state = useAsyncAsHook(() =>
wxApi.wallet.call(WalletApiOperation.GetTransactions, {}),
);
useEffect(() => {
return wxApi.onUpdateNotification(
return wxApi.listener.onUpdateNotification(
[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/>
*/
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 { Fragment, h, VNode } from "preact";
import { useState } from "preact/hooks";
@ -26,9 +29,9 @@ import { useTranslationContext } from "../context/translation.js";
import { HookError, useAsyncAsHook } from "../hooks/useAsyncAsHook.js";
import { ButtonHandler } from "../mui/handlers.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 { Link, LinkPrimary } from "./styled/index.js";
import { Link } from "./styled/index.js";
const ContractTermsTable = styled.table`
width: 100%;
@ -99,7 +102,9 @@ function useComponentState({ proposalId }: Props, api: typeof wxApi): State {
const [show, setShow] = useState(false);
const hook = useAsyncAsHook(async () => {
if (!show) return undefined;
return await api.getContractTermsDetails(proposalId);
return await api.wallet.call(WalletApiOperation.GetContractTermsDetails, {
proposalId,
});
}, [show]);
const hideHandler = {

View File

@ -18,7 +18,7 @@ import { Loading } from "../../components/Loading.js";
import { HookError } from "../../hooks/useAsyncAsHook.js";
import { ToggleHandler } from "../../mui/handlers.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 { TermsState } from "./utils.js";
import {
@ -26,7 +26,7 @@ import {
LoadingUriView,
ShowButtonsAcceptedTosView,
ShowButtonsNonAcceptedTosView,
ShowTosContentView,
ShowTosContentView
} from "./views.js";
export interface Props {

View File

@ -14,9 +14,10 @@
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 { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js";
import * as wxApi from "../../wxApi.js";
import { wxApi } from "../../wxApi.js";
import { Props, State } from "./index.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
*/
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);
@ -72,10 +76,16 @@ export function useComponentState(
try {
if (accepted) {
await api.setExchangeTosAccepted(exchangeUrl, state.version);
api.wallet.call(WalletApiOperation.SetExchangeTosAccepted, {
exchangeBaseUrl: exchangeUrl,
etag: state.version
})
} else {
// mark as not accepted
await api.setExchangeTosAccepted(exchangeUrl, undefined);
api.wallet.call(WalletApiOperation.SetExchangeTosAccepted, {
exchangeBaseUrl: exchangeUrl,
etag: undefined
})
}
// setAccepted(accepted);
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 { ButtonHandler } from "../../mui/handlers.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 { 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/>
*/
import { Amounts, CreateDepositGroupResponse } from "@gnu-taler/taler-util";
import { useState } from "preact/hooks";
import { Amounts } from "@gnu-taler/taler-util";
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js";
import * as wxApi from "../../wxApi.js";
import { wxApi } from "../../wxApi.js";
import { Props, State } from "./index.js";
export function useComponentState(
@ -29,10 +29,10 @@ export function useComponentState(
if (!amountStr) throw Error("ERROR_NO-AMOUNT-FOR-DEPOSIT");
const amount = Amounts.parse(amountStr);
if (!amount) throw Error("ERROR_INVALID-AMOUNT-FOR-DEPOSIT");
const deposit = await api.prepareDeposit(
talerDepositUri,
Amounts.stringify(amount),
);
const deposit = await api.wallet.call(WalletApiOperation.PrepareDeposit, {
amount: Amounts.stringify(amount),
depositPaytoUri: talerDepositUri,
});
return { deposit, uri: talerDepositUri, amount };
});
@ -46,7 +46,10 @@ export function useComponentState(
const { deposit, uri, amount } = info.response;
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);
}

View File

@ -19,43 +19,40 @@
* @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 { mountHook } from "../../test-utils.js";
import { createWalletApiMock } from "../../test-utils.js";
import { useComponentState } from "./state.js";
describe("Deposit CTA states", () => {
it("should tell the user that the URI is missing", async () => {
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
const { handler, mock } = createWalletApiMock();
const props = {
talerDepositUri: undefined,
amountStr: undefined,
cancel: async () => {
null;
},
onSuccess: async () => {
null;
},
}
const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } =
mountHook(() =>
useComponentState(
{
talerDepositUri: undefined,
amountStr: undefined,
cancel: async () => {
null;
},
onSuccess: async () => {
null;
},
},
{
prepareRefund: async () => ({}),
applyRefund: async () => ({}),
onUpdateNotification: async () => ({}),
} as any,
),
useComponentState(props, mock),
);
{
const { status } = getLastResultOrThrow();
const { status } = pullLastResultOrThrow();
expect(status).equals("loading");
}
await waitNextUpdate();
expect(await waitForStateUpdate()).true;
{
const { status, error } = getLastResultOrThrow();
const { status, error } = pullLastResultOrThrow();
expect(status).equals("loading-uri");
@ -64,44 +61,41 @@ describe("Deposit CTA states", () => {
if (error.operational) expect.fail();
expect(error.message).eq("ERROR_NO-URI-FOR-DEPOSIT");
}
await assertNoPendingUpdate();
expect(handler.getCallingQueueState()).eq("empty")
});
it("should be ready after loading", async () => {
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
const { handler, mock } = createWalletApiMock();
handler.addWalletCallResponse(WalletApiOperation.PrepareDeposit, undefined, {
effectiveDepositAmount: Amounts.parseOrThrow("EUR:1"),
totalDepositCost: Amounts.parseOrThrow("EUR:1.2"),
});
const props = {
talerDepositUri: "payto://refund/asdasdas",
amountStr: "EUR:1",
cancel: async () => {
null;
},
onSuccess: async () => {
null;
},
}
const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } =
mountHook(() =>
useComponentState(
{
talerDepositUri: "payto://refund/asdasdas",
amountStr: "EUR:1",
cancel: async () => {
null;
},
onSuccess: async () => {
null;
},
},
{
prepareDeposit: async () =>
({
effectiveDepositAmount: Amounts.parseOrThrow("EUR:1"),
totalDepositCost: Amounts.parseOrThrow("EUR:1.2"),
} as PrepareDepositResponse as any),
createDepositGroup: async () => ({}),
} as any,
),
useComponentState(props, mock),
);
{
const { status } = getLastResultOrThrow();
const { status } = pullLastResultOrThrow();
expect(status).equals("loading");
}
await waitNextUpdate();
expect(await waitForStateUpdate()).true;
{
const state = getLastResultOrThrow();
const state = pullLastResultOrThrow();
if (state.status !== "ready") expect.fail();
if (state.error) expect.fail();
@ -112,5 +106,6 @@ describe("Deposit CTA states", () => {
}
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 { ExchangeSelectionPage } from "../../wallet/ExchangeSelection/index.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 { LoadingUriView, ReadyView } from "./views.js";

View File

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

View File

@ -18,13 +18,13 @@ import {
AbsoluteTime,
AmountJson,
PreparePayResult,
TalerErrorDetail,
TalerErrorDetail
} from "@gnu-taler/taler-util";
import { Loading } from "../../components/Loading.js";
import { HookError } from "../../hooks/useAsyncAsHook.js";
import { ButtonHandler } from "../../mui/handlers.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 { LoadingUriView, ReadyView } from "./views.js";

View File

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

View File

@ -15,21 +15,16 @@
*/
import {
AmountJson,
ConfirmPayResult,
PreparePayResult,
PreparePayResultAlreadyConfirmed,
PreparePayResultInsufficientBalance,
PreparePayResultPaymentPossible,
AmountJson, PreparePayResult,
PreparePayResultAlreadyConfirmed, PreparePayResultPaymentPossible
} from "@gnu-taler/taler-util";
import { TalerError } from "@gnu-taler/taler-wallet-core";
import { Loading } from "../../components/Loading.js";
import { HookError } from "../../hooks/useAsyncAsHook.js";
import { ButtonHandler } from "../../mui/handlers.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 { LoadingUriView, BaseView } from "./views.js";
import { BaseView, LoadingUriView } from "./views.js";
export interface Props {
talerPayUri?: string;

View File

@ -15,19 +15,16 @@
*/
import {
AmountJson,
Amounts,
ConfirmPayResult,
ConfirmPayResultType,
Amounts, ConfirmPayResultType,
NotificationType,
PreparePayResultType,
TalerErrorCode,
TalerErrorCode
} 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 { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js";
import { ButtonHandler } from "../../mui/handlers.js";
import * as wxApi from "../../wxApi.js";
import { wxApi } from "../../wxApi.js";
import { Props, State } from "./index.js";
export function useComponentState(
@ -38,16 +35,17 @@ export function useComponentState(
const hook = useAsyncAsHook(async () => {
if (!talerPayUri) throw Error("ERROR_NO-URI-FOR-PAYMENT");
const payStatus = await api.preparePay(talerPayUri);
const balance = await api.getBalance();
return { payStatus, balance, uri: talerPayUri };
});
useEffect(() => {
api.onUpdateNotification([NotificationType.CoinWithdrawn], () => {
hook?.retry();
const payStatus = await api.wallet.call(WalletApiOperation.PreparePayForUri, {
talerPayUri: talerPayUri
});
});
const balance = await api.wallet.call(WalletApiOperation.GetBalances, {});
return { payStatus, balance, uri: talerPayUri };
}, []);
useEffect(() => api.listener.onUpdateNotification(
[NotificationType.CoinWithdrawn],
hook?.retry
), [hook]);
const hookResponse = !hook || hook.hasError ? undefined : hook.response;
@ -127,7 +125,9 @@ export function useComponentState(
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
if (res.type !== ConfirmPayResultType.Done) {
throw TalerError.fromUncheckedDetail({

View File

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

View File

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

View File

@ -19,13 +19,13 @@ import { Loading } from "../../components/Loading.js";
import { HookError } from "../../hooks/useAsyncAsHook.js";
import { ButtonHandler } from "../../mui/handlers.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 {
IgnoredView,
InProgressView,
LoadingUriView,
ReadyView,
ReadyView
} from "./views.js";
export interface Props {

View File

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

View File

@ -21,18 +21,19 @@
import {
AmountJson,
Amounts,
NotificationType,
PrepareRefundResult,
Amounts, NotificationType, OrderShortInfo, PrepareRefundResult
} from "@gnu-taler/taler-util";
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { expect } from "chai";
import { mountHook } from "../../test-utils.js";
import { SubsHandler } from "../Payment/test.js";
import { createWalletApiMock } from "../../test-utils.js";
import { useComponentState } from "./state.js";
describe("Refund CTA states", () => {
it("should tell the user that the URI is missing", async () => {
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
const { handler, mock } = createWalletApiMock();
const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } =
mountHook(() =>
useComponentState(
{
@ -44,24 +45,25 @@ describe("Refund CTA states", () => {
null;
},
},
{
prepareRefund: async () => ({}),
applyRefund: async () => ({}),
onUpdateNotification: async () => ({}),
} as any,
mock
// {
// prepareRefund: async () => ({}),
// applyRefund: async () => ({}),
// onUpdateNotification: async () => ({}),
// } as any,
),
);
{
const { status, error } = getLastResultOrThrow();
const { status, error } = pullLastResultOrThrow();
expect(status).equals("loading");
expect(error).undefined;
}
await waitNextUpdate();
expect(await waitForStateUpdate()).true;
{
const { status, error } = getLastResultOrThrow();
const { status, error } = pullLastResultOrThrow();
expect(status).equals("loading-uri");
if (!error) expect.fail();
@ -71,55 +73,76 @@ describe("Refund CTA states", () => {
}
await assertNoPendingUpdate();
expect(handler.getCallingQueueState()).eq("empty")
});
it("should be ready after loading", async () => {
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
const { handler, mock } = createWalletApiMock();
const props = {
talerRefundUri: "taler://refund/asdasdas",
cancel: async () => {
null;
},
onSuccess: async () => {
null;
},
}
handler.addWalletCallResponse(WalletApiOperation.PrepareRefund, undefined, {
awaiting: "EUR:2",
effectivePaid: "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 OrderShortInfo,
})
const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } =
mountHook(() =>
useComponentState(
{
talerRefundUri: "taler://refund/asdasdas",
cancel: async () => {
null;
},
onSuccess: async () => {
null;
},
},
{
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,
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(error).undefined;
}
await waitNextUpdate();
expect(await waitForStateUpdate()).true;
{
const state = getLastResultOrThrow();
const state = pullLastResultOrThrow();
if (state.status !== "ready") expect.fail();
if (state.error) expect.fail();
@ -131,58 +154,101 @@ describe("Refund CTA states", () => {
}
await assertNoPendingUpdate();
expect(handler.getCallingQueueState()).eq("empty")
});
it("should be ignored after clicking the ignore button", async () => {
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
const { handler, mock } = createWalletApiMock();
const props = {
talerRefundUri: "taler://refund/asdasdas",
cancel: async () => {
null;
},
onSuccess: async () => {
null;
},
}
handler.addWalletCallResponse(WalletApiOperation.PrepareRefund, undefined, {
awaiting: "EUR:2",
effectivePaid: "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 OrderShortInfo,
})
// handler.addWalletCall(WalletApiOperation.ApplyRefund)
// handler.addWalletCall(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",
// },
// orderId: "orderId1",
// summary: "the summary",
// } as OrderShortInfo,
// })
const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } =
mountHook(() =>
useComponentState(
{
talerRefundUri: "taler://refund/asdasdas",
cancel: async () => {
null;
},
onSuccess: async () => {
null;
},
},
{
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,
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(error).undefined;
}
await waitNextUpdate();
expect(await waitForStateUpdate()).true;
{
const state = getLastResultOrThrow();
const state = pullLastResultOrThrow();
if (state.status !== "ready") expect.fail();
if (state.error) expect.fail();
if (state.status !== "ready") {
expect(state).eq({})
return;
}
if (state.error) {
expect(state).eq({})
return;
}
expect(state.accept.onClick).not.undefined;
expect(state.merchantName).eq("the merchant name");
expect(state.orderId).eq("orderId1");
@ -192,113 +258,145 @@ describe("Refund CTA states", () => {
state.ignore.onClick();
}
await waitNextUpdate();
expect(await waitForStateUpdate()).true;
{
const state = getLastResultOrThrow();
const state = pullLastResultOrThrow();
if (state.status !== "ignored") expect.fail();
if (state.error) expect.fail();
if (state.status !== "ignored") {
expect(state).eq({})
return;
}
if (state.error) {
expect(state).eq({})
return;
}
expect(state.merchantName).eq("the merchant name");
}
await assertNoPendingUpdate();
expect(handler.getCallingQueueState()).eq("empty")
});
it("should be in progress when doing refresh", async () => {
let granted = Amounts.getZero("EUR");
const unit: AmountJson = { currency: "EUR", value: 1, fraction: 0 };
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 { handler, mock } = createWalletApiMock();
const props = {
talerRefundUri: "taler://refund/asdasdas",
cancel: async () => {
null;
},
onSuccess: async () => {
null;
},
}
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
handler.addWalletCallResponse(WalletApiOperation.PrepareRefund, undefined, {
awaiting: "EUR:2",
effectivePaid: "EUR:2",
gone: "EUR:0",
granted: "EUR:0",
pending: true,
proposalId: "1",
info: {
contractTermsHash: "123",
merchant: {
name: "the merchant name",
},
orderId: "orderId1",
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",
},
orderId: "orderId1",
summary: "the summary",
} as OrderShortInfo,
})
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(
{
talerRefundUri: "taler://refund/asdasdas",
cancel: async () => {
null;
},
onSuccess: async () => {
null;
},
},
{
prepareRefund: async () =>
({
awaiting: Amounts.stringify(awaiting),
effectivePaid: "EUR:2",
gone: "EUR:0",
granted: Amounts.stringify(granted),
pending,
proposalId: "1",
info: {
contractTermsHash: "123",
merchant: {
name: "the merchant name",
},
orderId: "orderId1",
summary: "the summary",
},
} as PrepareRefundResult as any),
applyRefund: async () => ({}),
onUpdateNotification: subscriptions.saveSubscription,
} as any,
props, mock
),
);
{
const { status, error } = getLastResultOrThrow();
const { status, error } = pullLastResultOrThrow();
expect(status).equals("loading");
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();
expect(state.merchantName).eq("the merchant name");
expect(state.products).undefined;
expect(state.amount).deep.eq(Amounts.parseOrThrow("EUR:2"));
// 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();
expect(state.merchantName).eq("the merchant name");
expect(state.products).undefined;
expect(state.amount).deep.eq(Amounts.parseOrThrow("EUR:2"));
// 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();
expect(state.merchantName).eq("the merchant name");
expect(state.products).undefined;
@ -306,5 +404,6 @@ describe("Refund CTA states", () => {
}
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 { ButtonHandler } from "../../mui/handlers.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 {
AcceptedView,
IgnoredView,
LoadingUriView,
ReadyView,
ReadyView
} from "./views.js";
export interface Props {

View File

@ -15,20 +15,18 @@
*/
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 * as wxApi from "../../wxApi.js";
import { wxApi } from "../../wxApi.js";
import { Props, State } from "./index.js";
export function useComponentState(
{ talerTipUri, onCancel, onSuccess }: Props,
api: typeof wxApi,
): State {
const [tipIgnored, setTipIgnored] = useState(false);
const tipInfo = useAsyncAsHook(async () => {
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 };
});
@ -48,7 +46,7 @@ export function useComponentState(
const { tip } = tipInfo.response;
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
tipInfo.retry();
@ -65,13 +63,6 @@ export function useComponentState(
},
};
if (tipIgnored) {
return {
status: "ignored",
...baseInfo,
};
}
if (tip.accepted) {
return {
status: "accepted",

View File

@ -19,14 +19,18 @@
* @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 { mountHook } from "../../test-utils.js";
import { createWalletApiMock } from "../../test-utils.js";
import { useComponentState } from "./state.js";
describe("Tip CTA states", () => {
it("should tell the user that the URI is missing", async () => {
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
const { handler, mock } = createWalletApiMock();
const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } =
mountHook(() =>
useComponentState(
{
@ -38,23 +42,20 @@ describe("Tip CTA states", () => {
null;
},
},
{
prepareTip: async () => ({}),
acceptTip: async () => ({}),
} as any,
mock,
),
);
{
const { status, error } = getLastResultOrThrow();
const { status, error } = pullLastResultOrThrow();
expect(status).equals("loading");
expect(error).undefined;
}
await waitNextUpdate();
expect(await waitForStateUpdate()).true;
{
const { status, error } = getLastResultOrThrow();
const { status, error } = pullLastResultOrThrow();
expect(status).equals("loading-uri");
if (!error) expect.fail();
@ -64,12 +65,26 @@ describe("Tip CTA states", () => {
}
await assertNoPendingUpdate();
expect(handler.getCallingQueueState()).eq("empty")
});
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(() =>
useComponentState(
{
@ -81,58 +96,79 @@ describe("Tip CTA states", () => {
null;
},
},
{
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,
mock,
),
);
{
const { status, error } = getLastResultOrThrow();
const { status, error } = pullLastResultOrThrow();
expect(status).equals("loading");
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();
expect(state.amount).deep.eq(Amounts.parseOrThrow("EUR:1"));
expect(state.merchantBaseUrl).eq("merchant url");
expect(state.exchangeBaseUrl).eq("exchange url");
if (state.accept.onClick === undefined) expect.fail();
handler.addWalletCallResponse(WalletApiOperation.AcceptTip);
state.accept.onClick();
}
await waitNextUpdate();
{
const state = getLastResultOrThrow();
handler.addWalletCallResponse(WalletApiOperation.PrepareTip, undefined, {
accepted: true,
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();
expect(state.amount).deep.eq(Amounts.parseOrThrow("EUR:1"));
expect(state.merchantBaseUrl).eq("merchant url");
expect(state.exchangeBaseUrl).eq("exchange url");
}
await assertNoPendingUpdate();
expect(handler.getCallingQueueState()).eq("empty")
});
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(() =>
useComponentState(
{
@ -144,52 +180,48 @@ describe("Tip CTA states", () => {
null;
},
},
{
prepareTip: async () =>
({
exchangeBaseUrl: "exchange url",
merchantBaseUrl: "merchant url",
tipAmountEffective: "EUR:1",
walletTipId: "tip_id",
} as PrepareTipResult as any),
acceptTip: async () => ({}),
} as any,
mock,
),
);
{
const { status, error } = getLastResultOrThrow();
const { status, error } = pullLastResultOrThrow();
expect(status).equals("loading");
expect(error).undefined;
}
await waitNextUpdate();
expect(await waitForStateUpdate()).true;
{
const state = getLastResultOrThrow();
const state = pullLastResultOrThrow();
if (state.status !== "ready") expect.fail();
if (state.error) expect.fail();
expect(state.amount).deep.eq(Amounts.parseOrThrow("EUR:1"));
expect(state.merchantBaseUrl).eq("merchant 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();
expect(handler.getCallingQueueState()).eq("empty")
});
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(() =>
useComponentState(
{
@ -201,30 +233,20 @@ describe("Tip CTA states", () => {
null;
},
},
{
prepareTip: async () =>
({
accepted: true,
exchangeBaseUrl: "exchange url",
merchantBaseUrl: "merchant url",
tipAmountEffective: "EUR:1",
walletTipId: "tip_id",
} as PrepareTipResult as any),
acceptTip: async () => ({}),
} as any,
mock,
),
);
{
const { status, error } = getLastResultOrThrow();
const { status, error } = pullLastResultOrThrow();
expect(status).equals("loading");
expect(error).undefined;
}
await waitNextUpdate();
expect(await waitForStateUpdate()).true;
{
const state = getLastResultOrThrow();
const state = pullLastResultOrThrow();
if (state.status !== "accepted") expect.fail();
if (state.error) expect.fail();
@ -233,5 +255,6 @@ describe("Tip CTA states", () => {
expect(state.exchangeBaseUrl).eq("exchange url");
}
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 { ButtonHandler, TextFieldHandler } from "../../mui/handlers.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 { LoadingUriView, ReadyView } from "./views.js";

View File

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

View File

@ -17,13 +17,13 @@
import {
AbsoluteTime,
AmountJson,
TalerErrorDetail,
TalerErrorDetail
} from "@gnu-taler/taler-util";
import { Loading } from "../../components/Loading.js";
import { HookError } from "../../hooks/useAsyncAsHook.js";
import { ButtonHandler } from "../../mui/handlers.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 { LoadingUriView, ReadyView } from "./views.js";

View File

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

View File

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

View File

@ -19,13 +19,13 @@ import {
AmountJson,
Amounts,
ExchangeListItem,
ExchangeTosStatus,
ExchangeTosStatus
} 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 { useAsyncAsHook } from "../../hooks/useAsyncAsHook.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";
type RecursiveState<S extends object> = S | (() => RecursiveState<S>);
@ -35,7 +35,7 @@ export function useComponentStateFromParams(
api: typeof wxApi,
): RecursiveState<State> {
const uriInfoHook = useAsyncAsHook(async () => {
const exchanges = await api.listExchanges();
const exchanges = await api.wallet.call(WalletApiOperation.ListExchanges, {});
return { amount: Amounts.parseOrThrow(amount), exchanges };
});
@ -58,11 +58,11 @@ export function useComponentStateFromParams(
transactionId: string;
confirmTransferUrl: string | undefined;
}> {
const res = await api.acceptManualWithdrawal(
exchange,
Amounts.stringify(chosenAmount),
ageRestricted,
);
const res = await api.wallet.call(WalletApiOperation.AcceptManualWithdrawal, {
exchangeBaseUrl: exchange,
amount: Amounts.stringify(chosenAmount),
restrictAge: ageRestricted,
});
return {
confirmTransferUrl: undefined,
transactionId: res.transactionId,
@ -93,16 +93,15 @@ export function useComponentStateFromURI(
const uriInfoHook = useAsyncAsHook(async () => {
if (!talerWithdrawUri) throw Error("ERROR_NO-URI-FOR-WITHDRAWAL");
const uriInfo = await api.getWithdrawalDetailsForUri({
const uriInfo = await api.wallet.call(WalletApiOperation.GetWithdrawalDetailsForUri, {
talerWithdrawUri,
});
const exchanges = await api.listExchanges();
const { amount, defaultExchangeBaseUrl } = uriInfo;
return {
talerWithdrawUri,
amount: Amounts.parseOrThrow(amount),
thisExchange: defaultExchangeBaseUrl,
exchanges,
exchanges: uriInfo.possibleExchanges,
};
});
@ -118,7 +117,7 @@ export function useComponentStateFromURI(
const uri = uriInfoHook.response.talerWithdrawUri;
const chosenAmount = uriInfoHook.response.amount;
const defaultExchange = uriInfoHook.response.thisExchange;
const exchangeList = uriInfoHook.response.exchanges.exchanges;
const exchangeList = uriInfoHook.response.exchanges;
async function doManagedWithdraw(
exchange: string,
@ -127,7 +126,11 @@ export function useComponentStateFromURI(
transactionId: string;
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 {
confirmTransferUrl: res.confirmTransferUrl,
transactionId: res.transactionId,
@ -186,7 +189,7 @@ function exchangeSelectionState(
* about the withdrawal
*/
const amountHook = useAsyncAsHook(async () => {
const info = await api.getWithdrawalDetailsForAmount({
const info = await api.wallet.call(WalletApiOperation.GetWithdrawalDetailsForAmount, {
exchangeBaseUrl: currentExchange.exchangeBaseUrl,
amount: Amounts.stringify(chosenAmount),
restrictAge: ageRestricted,
@ -261,10 +264,10 @@ function exchangeSelectionState(
//TODO: calculate based on exchange info
const ageRestriction = ageRestrictionEnabled
? {
list: ageRestrictionOptions,
value: String(ageRestricted),
onChange: async (v: string) => setAgeRestricted(parseInt(v, 10)),
}
list: ageRestrictionOptions,
value: String(ageRestricted),
onChange: async (v: string) => setAgeRestricted(parseInt(v, 10)),
}
: undefined;
return {

View File

@ -21,16 +21,12 @@
import {
Amounts,
ExchangeEntryStatus,
ExchangeFullDetails,
ExchangeListItem,
ExchangesListResponse,
ExchangeTosStatus,
GetExchangeTosResult,
ManualWithdrawalDetails,
ExchangeEntryStatus, ExchangeListItem, ExchangeTosStatus
} from "@gnu-taler/taler-util";
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { expect } from "chai";
import { mountHook } from "../../test-utils.js";
import { createWalletApiMock } from "../../test-utils.js";
import { useComponentStateFromURI } from "./state.js";
const exchanges: ExchangeListItem[] = [
@ -65,39 +61,32 @@ const exchanges: ExchangeListItem[] = [
describe("Withdraw CTA states", () => {
it("should tell the user that the URI is missing", async () => {
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
const { handler, mock } = createWalletApiMock();
const props = {
talerWithdrawUri: undefined,
cancel: async () => {
null;
},
onSuccess: async () => {
null;
},
}
const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } =
mountHook(() =>
useComponentStateFromURI(
{
talerWithdrawUri: undefined,
cancel: async () => {
null;
},
onSuccess: async () => {
null;
},
},
{
listExchanges: async () => ({ exchanges }),
getWithdrawalDetailsForAmount: async ({
talerWithdrawUri,
}: any) => ({
amount: "ARS:2",
possibleExchanges: exchanges,
}),
} as any,
props, mock
),
);
{
const { status } = getLastResultOrThrow();
const { status } = pullLastResultOrThrow();
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 (!error) expect.fail();
@ -107,40 +96,41 @@ describe("Withdraw CTA states", () => {
}
await assertNoPendingUpdate();
expect(handler.getCallingQueueState()).eq("empty")
});
it("should tell the user that there is not known exchange", async () => {
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
const { handler, mock } = createWalletApiMock();
const props = {
talerWithdrawUri: "taler-withdraw://",
cancel: async () => {
null;
},
onSuccess: async () => {
null;
},
}
handler.addWalletCallResponse(WalletApiOperation.GetWithdrawalDetailsForUri, undefined, {
amount: "EUR:2",
possibleExchanges: [],
})
const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } =
mountHook(() =>
useComponentStateFromURI(
{
talerWithdrawUri: "taler-withdraw://",
cancel: async () => {
null;
},
onSuccess: async () => {
null;
},
},
{
listExchanges: async () => ({ exchanges }),
getWithdrawalDetailsForUri: async ({ talerWithdrawUri }: any) => ({
amount: "EUR:2",
possibleExchanges: [],
}),
} as any,
props, mock
),
);
{
const { status } = getLastResultOrThrow();
const { status } = pullLastResultOrThrow();
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");
@ -148,65 +138,60 @@ describe("Withdraw CTA states", () => {
}
await assertNoPendingUpdate();
expect(handler.getCallingQueueState()).eq("empty")
});
it("should be able to withdraw if tos are ok", async () => {
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
const { handler, mock } = createWalletApiMock();
const props = {
talerWithdrawUri: "taler-withdraw://",
cancel: async () => {
null;
},
onSuccess: async () => {
null;
},
}
handler.addWalletCallResponse(WalletApiOperation.GetWithdrawalDetailsForUri, undefined, {
amount: "ARS:2",
possibleExchanges: exchanges,
defaultExchangeBaseUrl: exchanges[0].exchangeBaseUrl
})
handler.addWalletCallResponse(WalletApiOperation.GetWithdrawalDetailsForAmount, undefined, {
amountRaw: "ARS:2",
amountEffective: "ARS:2",
paytoUris: ["payto://"],
tosAccepted: true,
ageRestrictionOptions: []
})
const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } =
mountHook(() =>
useComponentStateFromURI(
{
talerWithdrawUri: "taler-withdraw://",
cancel: async () => {
null;
},
onSuccess: async () => {
null;
},
},
{
listExchanges: async () => ({ exchanges }),
getWithdrawalDetailsForUri: async ({ talerWithdrawUri }: any) => ({
amount: "ARS:2",
possibleExchanges: exchanges,
defaultExchangeBaseUrl: exchanges[0].exchangeBaseUrl,
}),
getWithdrawalDetailsForAmount:
async (): Promise<ManualWithdrawalDetails> =>
({
amountRaw: "ARS:2",
amountEffective: "ARS:2",
} as any),
getExchangeTos: async (): Promise<GetExchangeTosResult> => ({
contentType: "text",
content: "just accept",
acceptedEtag: "v1",
currentEtag: "v1",
tosStatus: ExchangeTosStatus.Accepted,
}),
} as any,
props, mock
),
);
{
const { status, error } = getLastResultOrThrow();
const { status, error } = pullLastResultOrThrow();
expect(status).equals("loading");
expect(error).undefined;
}
await waitNextUpdate();
expect(await waitForStateUpdate()).true;
{
const { status, error } = getLastResultOrThrow();
const { status, error } = pullLastResultOrThrow();
expect(status).equals("loading");
expect(error).undefined;
}
await waitNextUpdate();
expect(await waitForStateUpdate()).true;
{
const state = getLastResultOrThrow();
const state = pullLastResultOrThrow();
expect(state.status).equals("success");
if (state.status !== "success") return;
@ -218,82 +203,72 @@ describe("Withdraw CTA states", () => {
}
await assertNoPendingUpdate();
expect(handler.getCallingQueueState()).eq("empty")
});
it("should be accept the tos before withdraw", async () => {
const listExchangesResponse: ExchangesListResponse = {
exchanges: exchanges.map((e) => ({
...e,
tosStatus: ExchangeTosStatus.New,
})),
};
function updateAcceptedVersionToCurrentVersion(): void {
listExchangesResponse.exchanges = listExchangesResponse.exchanges.map(
(e) => ({
...e,
tosStatus: ExchangeTosStatus.Accepted,
}),
);
it("should accept the tos before withdraw", async () => {
const { handler, mock } = createWalletApiMock();
const props = {
talerWithdrawUri: "taler-withdraw://",
cancel: async () => {
null;
},
onSuccess: async () => {
null;
},
}
const exchangeWithNewTos = exchanges.map((e) => ({
...e,
tosStatus: ExchangeTosStatus.New,
}));
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
handler.addWalletCallResponse(WalletApiOperation.GetWithdrawalDetailsForUri, undefined, {
amount: "ARS:2",
possibleExchanges: exchangeWithNewTos,
defaultExchangeBaseUrl: exchangeWithNewTos[0].exchangeBaseUrl
})
handler.addWalletCallResponse(WalletApiOperation.GetWithdrawalDetailsForAmount, undefined, {
amountRaw: "ARS:2",
amountEffective: "ARS:2",
paytoUris: ["payto://"],
tosAccepted: false,
ageRestrictionOptions: []
})
handler.addWalletCallResponse(WalletApiOperation.GetWithdrawalDetailsForUri, undefined, {
amount: "ARS:2",
possibleExchanges: exchanges,
defaultExchangeBaseUrl: exchanges[0].exchangeBaseUrl
})
const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } =
mountHook(() =>
useComponentStateFromURI(
{
talerWithdrawUri: "taler-withdraw://",
cancel: async () => {
null;
},
onSuccess: async () => {
null;
},
},
{
listExchanges: async () => listExchangesResponse,
getWithdrawalDetailsForUri: async ({ talerWithdrawUri }: any) => ({
amount: "ARS:2",
possibleExchanges: exchanges,
defaultExchangeBaseUrl: exchanges[0].exchangeBaseUrl,
}),
getWithdrawalDetailsForAmount:
async (): Promise<ManualWithdrawalDetails> =>
({
amountRaw: "ARS:2",
amountEffective: "ARS:2",
} as any),
getExchangeTos: async (): Promise<GetExchangeTosResult> => ({
contentType: "text",
content: "just accept",
acceptedEtag: "v1",
currentEtag: "v2",
tosStatus: ExchangeTosStatus.Changed,
}),
setExchangeTosAccepted: async () => ({}),
} as any,
props, mock
),
);
{
const { status, error } = getLastResultOrThrow();
const { status, error } = pullLastResultOrThrow();
expect(status).equals("loading");
expect(error).undefined;
}
await waitNextUpdate();
expect(await waitForStateUpdate()).true;
{
const { status, error } = getLastResultOrThrow();
const { status, error } = pullLastResultOrThrow();
expect(status).equals("loading");
expect(error).undefined;
}
await waitNextUpdate();
expect(await waitForStateUpdate()).true;
{
const state = getLastResultOrThrow();
const state = pullLastResultOrThrow();
expect(state.status).equals("success");
if (state.status !== "success") return;
@ -303,14 +278,14 @@ describe("Withdraw CTA states", () => {
expect(state.doWithdrawal.onClick).undefined;
updateAcceptedVersionToCurrentVersion();
// updateAcceptedVersionToCurrentVersion();
state.onTosUpdate();
}
await waitNextUpdate();
expect(await waitForStateUpdate()).true;
{
const state = getLastResultOrThrow();
const state = pullLastResultOrThrow();
expect(state.status).equals("success");
if (state.status !== "success") return;
@ -322,5 +297,6 @@ describe("Withdraw CTA states", () => {
}
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
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 { useEffect, useMemo, useState } from "preact/hooks";
import * as wxApi from "../wxApi.js";
export interface HookOk<T> {
hasError: false;

View File

@ -14,11 +14,11 @@
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 { 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 {
const [enabled, setEnabled] = useState(false);
@ -31,7 +31,7 @@ export function useAutoOpenPermissions(): ToggleHandler {
useEffect(() => {
async function getValue(): Promise<void> {
const res = await wxApi.containsHeaderListener();
const res = await wxApi.background.containsHeaderListener();
setEnabled(res.newValue);
}
getValue();
@ -59,11 +59,11 @@ async function handleAutoOpenPerm(
onChange(false);
throw lastError;
}
const res = await wxApi.toggleHeaderListener(granted);
const res = await wxApi.background.toggleHeaderListener(granted);
onChange(res.newValue);
} else {
try {
await wxApi.toggleHeaderListener(false).then((r) => onChange(r.newValue));
await wxApi.background.toggleHeaderListener(false).then((r) => onChange(r.newValue));
} catch (e) {
console.log(e);
}

View File

@ -14,8 +14,9 @@
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 * as wxApi from "../wxApi.js";
import { wxApi } from "../wxApi.js";
export interface BackupDeviceName {
name: string;
@ -31,10 +32,10 @@ export function useBackupDeviceName(): BackupDeviceName {
useEffect(() => {
async function run(): Promise<void> {
//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> {
await wxApi.setWalletDeviceId(newName);
await wxApi.wallet.call(WalletApiOperation.SetWalletDeviceId, { walletDeviceId: 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/>
*/
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 { 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 {
const [enabled, setEnabled] = useState(false);
@ -31,7 +31,7 @@ export function useClipboardPermissions(): ToggleHandler {
useEffect(() => {
async function getValue(): Promise<void> {
const res = await wxApi.containsHeaderListener();
const res = await wxApi.background.containsHeaderListener();
setEnabled(res.newValue);
}
getValue();
@ -66,7 +66,7 @@ async function handleClipboardPerm(
onChange(granted);
} else {
try {
await wxApi.toggleHeaderListener(false).then((r) => onChange(r.newValue));
await wxApi.background.toggleHeaderListener(false).then((r) => onChange(r.newValue));
} catch (e) {
console.log(e);
}

View File

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

View File

@ -14,9 +14,9 @@
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 * as wxApi from "../wxApi.js";
import { wxApi } from "../wxApi.js";
export interface ProviderStatus {
info?: ProviderInfo;
@ -30,7 +30,7 @@ export function useProviderStatus(url: string): ProviderStatus | undefined {
useEffect(() => {
async function run(): Promise<void> {
//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(
(p) => p.syncProviderBaseUrl === url,
@ -39,13 +39,17 @@ export function useProviderStatus(url: string): ProviderStatus | undefined {
async function sync(): Promise<void> {
if (info) {
await wxApi.syncOneProvider(info.syncProviderBaseUrl);
await wxApi.wallet.call(WalletApiOperation.RunBackupCycle, {
providers: [info.syncProviderBaseUrl]
});
}
}
async function remove(): Promise<void> {
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);
{
const [url] = getLastResultOrThrow();
const [url] = pullLastResultOrThrow();
expect(url).undefined;
}
await waitNextUpdate("waiting for useEffect");
expect(await waitForStateUpdate()).true;
{
const [url, setDismissed] = getLastResultOrThrow();
const [url, setDismissed] = pullLastResultOrThrow();
expect(url).deep.equals({
location: "clipboard",
uri: "qwe",
@ -50,10 +50,10 @@ describe("useTalerActionURL hook", () => {
setDismissed(true);
}
await waitNextUpdate("after dismiss");
expect(await waitForStateUpdate()).true;
{
const [url] = getLastResultOrThrow();
const [url] = pullLastResultOrThrow();
if (url !== undefined) throw Error("invalid");
expect(url).undefined;
}

View File

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

View File

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

View File

@ -14,6 +14,8 @@
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 {
ComponentChildren,
Fragment,
@ -24,6 +26,7 @@ import {
VNode,
} from "preact";
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.
// 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> {
unmount: () => void;
getLastResultOrThrow: () => Exclude<T, VoidFunction>;
pullLastResultOrThrow: () => Exclude<T, VoidFunction>;
assertNoPendingUpdate: () => void;
waitNextUpdate: (s?: string) => Promise<void>;
// waitNextUpdate: (s?: string) => Promise<void>;
waitForStateUpdate: () => Promise<boolean>;
}
const isNode = typeof window === "undefined";
@ -97,9 +101,6 @@ export function mountHook<T extends object>(
callback: () => RecursiveState<T>,
Context?: ({ children }: { children: any }) => VNode,
): Mounted<T> {
// const result: { current: T | null } = {
// current: null
// }
let lastResult: Exclude<T, VoidFunction> | Error | null = null;
const listener: Array<() => void> = [];
@ -132,23 +133,6 @@ export function mountHook<T extends object>(
? 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 parentElement = isNode ? customElement : document.createElement("div");
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;
lastResult = null;
return copy;
}
function getLastResultOrThrow(): Exclude<T, VoidFunction> {
const r = getLastResult();
function pullLastResultOrThrow(): Exclude<T, VoidFunction> {
const r = pullLastResult();
if (r instanceof Error) throw r;
if (!r) throw Error("there was no last result");
return r;
@ -194,15 +178,137 @@ export function mountHook<T extends object>(
});
});
const r = getLastResult();
const r = pullLastResult();
if (r)
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 {
unmount,
getLastResultOrThrow,
waitNextUpdate,
pullLastResultOrThrow,
waitForStateUpdate,
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 { 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 {
ButtonHandler,
SelectFieldHandler,
TextFieldHandler,
TextFieldHandler
} 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 {
currency: string;

View File

@ -15,19 +15,17 @@
*/
import { parsePaytoUri, stringifyPaytoUri } from "@gnu-taler/taler-util";
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { useState } from "preact/hooks";
import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js";
import * as wxApi from "../../wxApi.js";
import { wxApi } from "../../wxApi.js";
import { Props, State } from "./index.js";
export function useComponentState(
{ currency, onAccountAdded, onCancel }: Props,
api: typeof wxApi,
): State {
const hook = useAsyncAsHook(async () => {
const { accounts } = await api.listKnownBankAccounts(currency);
return { accounts };
});
const hook = useAsyncAsHook(() => api.wallet.call(WalletApiOperation.ListKnownBankAccounts, { currency }));
const [payto, setPayto] = useState("");
const [alias, setAlias] = useState("");
@ -61,7 +59,10 @@ export function useComponentState(
async function addAccount(): Promise<void> {
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);
}
@ -69,10 +70,10 @@ export function useComponentState(
payto === ""
? undefined
: !uri
? "the uri is not ok"
: found
? "that account is already present"
: undefined;
? "the uri is not ok"
: found
? "that account is already present"
: undefined;
const unableToAdd = !type || !alias || paytoUriError;

View File

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

View File

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

View File

@ -14,26 +14,25 @@
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 { HookError } from "../../hooks/useAsyncAsHook.js";
import {
ButtonHandler,
SelectFieldHandler,
TextFieldHandler
} from "../../mui/handlers.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 {
AmountOrCurrencyErrorView,
LoadingErrorView,
NoAccountToDepositView,
NoEnoughBalanceView,
ReadyView,
ReadyView
} 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 {
amount?: string;

View File

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

View File

@ -20,101 +20,108 @@
*/
import {
Amounts,
Balance,
BalancesResponse,
DepositGroupFees,
Amounts, DepositGroupFees,
parsePaytoUri,
stringifyPaytoUri,
stringifyPaytoUri
} from "@gnu-taler/taler-util";
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
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";
const currency = "EUR";
const withoutFee = async (): Promise<DepositGroupFees> => ({
const withoutFee = (): DepositGroupFees => ({
coin: Amounts.parseOrThrow(`${currency}:0`),
wire: Amounts.parseOrThrow(`${currency}:0`),
refresh: Amounts.parseOrThrow(`${currency}:0`),
});
const withSomeFee = async (): Promise<DepositGroupFees> => ({
const withSomeFee = (): DepositGroupFees => ({
coin: Amounts.parseOrThrow(`${currency}:1`),
wire: 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", () => {
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(() =>
useComponentState(
{ currency, onCancel: nullFunction, onSuccess: nullFunction },
{
getBalance: async () =>
({
balances: [{ available: `${currency}:0` }],
} as Partial<BalancesResponse>),
listKnownBankAccounts: async () => ({ accounts: {} }),
} as Partial<typeof wxApi> as any,
props, mock
),
);
{
const { status } = getLastResultOrThrow();
const { status } = pullLastResultOrThrow();
expect(status).equal("loading");
}
await waitNextUpdate();
expect(await waitForStateUpdate()).true;
{
const { status } = getLastResultOrThrow();
const { status } = pullLastResultOrThrow();
expect(status).equal("no-enough-balance");
}
await assertNoPendingUpdate();
expect(handler.getCallingQueueState()).eq("empty")
});
// it("should have status 'no-accounts' when balance is not empty and accounts is empty", async () => {
// const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
// mountHook(() =>
// useComponentState({ currency, onCancel: nullFunction, onSuccess: nullFunction }, {
// getBalance: async () =>
// ({
// balances: [{ available: `${currency}:1` }],
// } as Partial<BalancesResponse>),
// listKnownBankAccounts: async () => ({ accounts: {} }),
// } as Partial<typeof wxApi> as any),
// );
it("should have status 'no-accounts' when balance is not empty and accounts is empty", async () => {
const { handler, mock } = createWalletApiMock();
const props = { currency, onCancel: nullFunction, onSuccess: nullFunction }
// {
// const { status } = getLastResultOrThrow();
// expect(status).equal("loading");
// }
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: []
});
const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } =
mountHook(() =>
useComponentState(
props, mock
)
);
// await waitNextUpdate();
// {
// const r = getLastResultOrThrow();
// if (r.status !== "no-accounts") expect.fail();
// expect(r.cancelHandler.onClick).not.undefined;
// }
{
const { status } = pullLastResultOrThrow();
expect(status).equal("loading");
}
// await assertNoPendingUpdate();
// });
expect(await waitForStateUpdate()).true;
{
const r = pullLastResultOrThrow();
if (r.status !== "no-accounts") expect.fail();
// expect(r.cancelHandler.onClick).not.undefined;
}
await assertNoPendingUpdate();
expect(handler.getCallingQueueState()).eq("empty")
});
const ibanPayto = {
uri: parsePaytoUri("payto://iban/ES8877998399652238")!,
@ -130,29 +137,38 @@ describe("DepositPage states", () => {
};
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(() =>
useComponentState(
{ currency, onCancel: nullFunction, onSuccess: nullFunction },
{
getBalance: async () =>
({
balances: [{ available: `${currency}:1` }],
} as Partial<BalancesResponse>),
listKnownBankAccounts: async () => ({ accounts: [ibanPayto] }),
} as Partial<typeof wxApi> as any,
props, mock
),
);
{
const { status } = getLastResultOrThrow();
const { status } = pullLastResultOrThrow();
expect(status).equal("loading");
}
await waitNextUpdate();
expect(await waitForStateUpdate()).true;
{
const r = getLastResultOrThrow();
const r = pullLastResultOrThrow();
if (r.status !== "ready") expect.fail();
expect(r.cancelHandler.onClick).not.undefined;
expect(r.currency).eq(currency);
@ -162,33 +178,46 @@ describe("DepositPage states", () => {
}
await assertNoPendingUpdate();
expect(handler.getCallingQueueState()).eq("empty")
});
it.skip("should not be able to deposit more than the balance ", async () => {
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
it("should not be able to deposit more than the balance ", async () => {
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(() =>
useComponentState(
{ currency, onCancel: nullFunction, onSuccess: nullFunction },
{
getBalance: async () =>
({
balances: [{ available: `${currency}:1` }],
} as Partial<BalancesResponse>),
listKnownBankAccounts: async () => ({ accounts: [ibanPayto] }),
getFeeForDeposit: withoutFee,
} as Partial<typeof wxApi> as any,
props, mock
),
);
{
const { status } = getLastResultOrThrow();
const { status } = pullLastResultOrThrow();
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();
expect(r.cancelHandler.onClick).not.undefined;
expect(r.currency).eq(currency);
@ -196,32 +225,20 @@ describe("DepositPage states", () => {
expect(r.amount.value).eq("0");
expect(r.depositHandler.onClick).undefined;
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();
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.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.account.value).eq(accountSelected);
expect(r.amount.value).eq("0");
expect(r.totalFee).deep.eq(Amounts.parseOrThrow(`${currency}:0`));
expect(r.depositHandler.onClick).undefined;
}
@ -229,31 +246,110 @@ describe("DepositPage states", () => {
await assertNoPendingUpdate();
});
it.skip("should calculate the fee upon entering amount ", async () => {
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
// it("should calculate the fee upon entering amount ", async () => {
// const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } =
// mountHook(() =>
// useComponentState(
// { currency, onCancel: nullFunction, onSuccess: nullFunction },
// {
// getBalance: async () =>
// ({
// balances: [{ available: `${currency}:1` }],
// } 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(accountSelected);
// expect(r.amount.value).eq("0");
// expect(r.depositHandler.onClick).undefined;
// expect(r.totalFee).deep.eq(Amounts.parseOrThrow(`${currency}:0`));
// r.amount.onInput("10");
// }
// expect(await waitForStateUpdate()).true;
// {
// 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("10");
// expect(r.totalFee).deep.eq(Amounts.parseOrThrow(`${currency}:0`));
// expect(r.depositHandler.onClick).undefined;
// r.amount.onInput("3");
// }
// expect(await waitForStateUpdate()).true;
// {
// 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())
const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } =
mountHook(() =>
useComponentState(
{ currency, onCancel: nullFunction, onSuccess: nullFunction },
{
getBalance: async () =>
({
balances: [{ available: `${currency}:1` }],
} as Partial<BalancesResponse>),
listKnownBankAccounts: async () => ({ accounts: [ibanPayto] }),
getFeeForDeposit: withSomeFee,
} as Partial<typeof wxApi> as any,
props, mock
),
);
{
const { status } = getLastResultOrThrow();
const { status } = pullLastResultOrThrow();
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();
expect(r.cancelHandler.onClick).not.undefined;
expect(r.currency).eq(currency);
@ -261,267 +357,42 @@ describe("DepositPage states", () => {
expect(r.amount.value).eq("0");
expect(r.depositHandler.onClick).undefined;
expect(r.totalFee).deep.eq(Amounts.parseOrThrow(`${currency}:0`));
expect(r.account.onChange).not.undefined;
r.account.onChange!(accountSelected)
}
expect(await waitForStateUpdate()).true;
{
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("0");
expect(r.depositHandler.onClick).undefined;
expect(r.totalFee).deep.eq(Amounts.parseOrThrow(`${currency}:3`));
r.amount.onInput("10");
}
await waitNextUpdate();
expect(await waitForStateUpdate()).true;
{
const r = getLastResultOrThrow();
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("");
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.totalFee).deep.eq(Amounts.parseOrThrow(`${currency}:3`));
expect(r.totalToDeposit).deep.eq(Amounts.parseOrThrow(`${currency}:7`));
expect(r.depositHandler.onClick).undefined;
}
await assertNoPendingUpdate();
});
it.skip("should calculate the fee upon selecting account ", async () => {
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
mountHook(() =>
useComponentState(
{ currency, onCancel: nullFunction, onSuccess: nullFunction },
{
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();
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`));
expect(r.totalToDeposit).deep.eq(Amounts.parseOrThrow(`${currency}:0`));
if (r.account.onChange === undefined) expect.fail();
r.account.onChange(stringifyPaytoUri(ibanPayto.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(ibanPayto.uri));
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;
}
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.totalToDeposit).deep.eq(Amounts.parseOrThrow(`${currency}:0`));
expect(r.depositHandler.onClick).undefined;
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(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;
}
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.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).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();
expect(handler.getCallingQueueState()).eq("empty")
});
});

View File

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

View File

@ -21,7 +21,10 @@ import {
ExchangeListItem,
NotificationType,
} 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 { Fragment, h, VNode } from "preact";
import { useEffect, useRef, useState } from "preact/hooks";
@ -33,8 +36,7 @@ import { useAsyncAsHook } from "../hooks/useAsyncAsHook.js";
import { useDiagnostics } from "../hooks/useDiagnostics.js";
import { Button } from "../mui/Button.js";
import { Grid } from "../mui/Grid.js";
import { Paper } from "../mui/Paper.js";
import * as wxApi from "../wxApi.js";
import { wxApi } from "../wxApi.js";
export function DeveloperPage(): VNode {
const [status, timedOut] = useDiagnostics();
@ -44,9 +46,12 @@ export function DeveloperPage(): VNode {
listenAllEvents.includes = (e) => e !== "waiting-for-retry"; // includes every event
const response = useAsyncAsHook(async () => {
const op = await wxApi.getPendingOperations();
const c = await wxApi.dumpCoins();
const ex = await wxApi.listExchanges();
const op = await wxApi.wallet.call(
WalletApiOperation.GetPendingOperations,
{},
);
const c = await wxApi.wallet.call(WalletApiOperation.DumpCoins, {});
const ex = await wxApi.wallet.call(WalletApiOperation.ListExchanges, {});
return {
operations: op.pendingOperations,
coins: c.coins,
@ -55,9 +60,10 @@ export function DeveloperPage(): VNode {
});
useEffect(() => {
return wxApi.onUpdateNotification(listenAllEvents, () => {
response?.retry();
});
return wxApi.listener.onUpdateNotification(
listenAllEvents,
response?.retry,
);
});
const nonResponse = { operations: [], coins: [], exchanges: [] };
@ -76,7 +82,7 @@ export function DeveloperPage(): VNode {
coins={coins}
exchanges={exchanges}
onDownloadDatabase={async () => {
const db = await wxApi.exportDB();
const db = await wxApi.wallet.call(WalletApiOperation.ExportDb, {});
return JSON.stringify(db);
}}
/>
@ -131,7 +137,9 @@ export function View({
}
const fileRef = useRef<HTMLInputElement>(null);
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 money_by_exchange = coins.reduce(
@ -169,7 +177,7 @@ export function View({
onClick={() =>
confirmReset(
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={() =>
confirmReset(
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 { 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 { wxApi } from "../../wxApi.js";
import { useComponentState } from "./state.js";
import { LoadingUriView, ReadyView } from "./views.js";
export interface Props {
p: string;

View File

@ -14,7 +14,7 @@
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";
export function useComponentState({ p }: Props, api: typeof wxApi): State {

View File

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

View File

@ -18,15 +18,14 @@ import {
DenomOperationMap,
ExchangeFullDetails,
ExchangeListItem,
FeeDescriptionPair,
FeeDescriptionPair
} from "@gnu-taler/taler-util";
import { Loading } from "../../components/Loading.js";
import { TermsState } from "../../components/TermsOfService/utils.js";
import { HookError } from "../../hooks/useAsyncAsHook.js";
import { State as SelectExchangeState } from "../../hooks/useSelectedExchange.js";
import { ButtonHandler, SelectFieldHandler } from "../../mui/handlers.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 {
ComparingView,
@ -34,7 +33,7 @@ import {
NoExchangesView,
PrivacyContentView,
ReadyView,
TosContentView,
TosContentView
} from "./views.js";
export interface Props {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -22,77 +22,16 @@
* Imports.
*/
import {
AcceptExchangeTosRequest,
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,
CoreApiResponse, Logger, NotificationType, WalletDiagnostics
} from "@gnu-taler/taler-util";
import {
AddBackupProviderRequest,
BackupInfo,
PendingOperationsResponse,
RemoveBackupProviderRequest,
TalerError,
WalletApiOperation,
WalletContractData,
WalletCoreApiClient,
TalerError, WalletCoreApiClient,
WalletCoreOpKeys,
WalletCoreRequestType,
WalletCoreResponseType,
WalletCoreResponseType
} from "@gnu-taler/taler-wallet-core";
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 {
/**
* Pay for a proposal.
*/
export function confirmPay(
proposalId: string,
sessionId: string | undefined,
): Promise<ConfirmPayResult> {
return wxClient.call(WalletApiOperation.ConfirmPay, {
proposalId,
sessionId,
});
}
public resetDb(): Promise<void> {
return callBackend("reset-db", {});
}
/**
* Check upgrade information
*/
export function checkUpgrade(): Promise<UpgradeResponse> {
return callBackend("check-upgrade", {});
}
public containsHeaderListener(): Promise<ExtendedPermissionsResponse> {
return callBackend("containsHeaderListener", {});
}
/**
* Reset database
*/
export function resetDb(): Promise<void> {
return callBackend("reset-db", {});
}
public getDiagnostics(): Promise<WalletDiagnostics> {
return callBackend("wxGetDiagnostics", {});
}
/**
* Reset database
*/
export function runGarbageCollector(): Promise<void> {
return callBackend("run-gc", {});
}
public toggleHeaderListener(
value: boolean,
): Promise<ExtendedPermissionsResponse> {
return callBackend("toggleHeaderListener", { value });
}
export function getFeeForDeposit(
depositPaytoUri: string,
amount: AmountString,
): Promise<DepositGroupFees> {
return callBackend("getFeeForDeposit", {
depositPaytoUri,
amount,
} as GetFeeForDepositRequest);
}
public runGarbageCollector(): Promise<void> {
return callBackend("run-gc", {});
}
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", {});
}
/**
* Get diagnostics information
*/
export function toggleHeaderListener(
value: boolean,
): Promise<ExtendedPermissionsResponse> {
return callBackend("toggleHeaderListener", { value });
}
/**
* Get diagnostics information
*/
export function containsHeaderListener(): Promise<ExtendedPermissionsResponse> {
return callBackend("containsHeaderListener", {});
}
/**
* Get diagnostics information
*/
export function getWithdrawalDetailsForUri(
req: GetWithdrawalDetailsForUriRequest,
): Promise<WithdrawUriInfoResponse> {
return callBackend("getWithdrawalDetailsForUri", req);
}
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(
function onUpdateNotification(
messageTypes: Array<NotificationType>,
doCallback: () => void,
doCallback: undefined | (() => void),
): () => void {
//if no callback, then ignore
if (!doCallback) return () => {
return
};
const onNewMessage = (message: MessageFromBackend): void => {
const shouldNotify = messageTypes.includes(message.type);
if (shouldNotify) {
@ -551,39 +148,11 @@ export function onUpdateNotification(
return platform.listenToWalletBackground(onNewMessage);
}
export function initiatePeerPushPayment(
req: InitiatePeerPushPaymentRequest,
): Promise<InitiatePeerPushPaymentResponse> {
return callBackend("initiatePeerPushPayment", req);
}
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 const wxApi = {
wallet: new WxWalletCoreApiClient(),
background: new BackgroundApiClient(),
listener: {
onUpdateNotification
}
}
export function getTransactionById(tid: string): Promise<Transaction> {
return callBackend("getTransactionById", {
transactionId: tid,
});
}