fix #7411, also making the backup payment visible
This commit is contained in:
parent
53164dc47b
commit
1a63d56bfd
@ -465,4 +465,4 @@ export const punycode = {
|
||||
encode: encode,
|
||||
toASCII: toASCII,
|
||||
toUnicode: toUnicode,
|
||||
};
|
||||
};
|
||||
|
@ -241,12 +241,18 @@ export interface ConfirmPayResultPending {
|
||||
lastError: TalerErrorDetail | undefined;
|
||||
}
|
||||
|
||||
export const codecForTalerErrorDetail = (): Codec<TalerErrorDetail> =>
|
||||
buildCodecForObject<TalerErrorDetail>()
|
||||
.property("code", codecForNumber())
|
||||
.property("hint", codecOptional(codecForString()))
|
||||
.build("TalerErrorDetail");
|
||||
|
||||
export type ConfirmPayResult = ConfirmPayResultDone | ConfirmPayResultPending;
|
||||
|
||||
export const codecForConfirmPayResultPending =
|
||||
(): Codec<ConfirmPayResultPending> =>
|
||||
buildCodecForObject<ConfirmPayResultPending>()
|
||||
.property("lastError", codecForAny())
|
||||
.property("lastError", codecOptional(codecForTalerErrorDetail()))
|
||||
.property("transactionId", codecForString())
|
||||
.property("type", codecForConstString(ConfirmPayResultType.Pending))
|
||||
.build("ConfirmPayResultPending");
|
||||
|
@ -25,7 +25,9 @@ import { SynchronousCryptoWorkerPlain } from "./synchronousWorkerPlain.js";
|
||||
* The synchronous crypto worker produced by this factory doesn't run in the
|
||||
* background, but actually blocks the caller until the operation is done.
|
||||
*/
|
||||
export class SynchronousCryptoWorkerFactoryPlain implements CryptoWorkerFactory {
|
||||
export class SynchronousCryptoWorkerFactoryPlain
|
||||
implements CryptoWorkerFactory
|
||||
{
|
||||
startWorker(): CryptoWorker {
|
||||
return new SynchronousCryptoWorkerPlain();
|
||||
}
|
||||
|
@ -29,15 +29,18 @@ import {
|
||||
AmountString,
|
||||
BackupRecovery,
|
||||
buildCodecForObject,
|
||||
buildCodecForUnion,
|
||||
bytesToString,
|
||||
canonicalizeBaseUrl,
|
||||
canonicalJson,
|
||||
Codec,
|
||||
codecForAmountString,
|
||||
codecForBoolean,
|
||||
codecForConstString,
|
||||
codecForList,
|
||||
codecForNumber,
|
||||
codecForString,
|
||||
codecForTalerErrorDetail,
|
||||
codecOptional,
|
||||
ConfirmPayResultType,
|
||||
decodeCrock,
|
||||
@ -78,6 +81,7 @@ import {
|
||||
WalletBackupConfState,
|
||||
} from "../../db.js";
|
||||
import { InternalWalletState } from "../../internal-wallet-state.js";
|
||||
import { assertUnreachable } from "../../util/assertUnreachable.js";
|
||||
import {
|
||||
readSuccessResponseJsonOrThrow,
|
||||
readTalerErrorResponse,
|
||||
@ -232,12 +236,6 @@ function deriveBlobSecret(bc: WalletBackupConfState): Uint8Array {
|
||||
|
||||
interface BackupForProviderArgs {
|
||||
backupProviderBaseUrl: string;
|
||||
|
||||
/**
|
||||
* Should we attempt one more upload after trying
|
||||
* to pay?
|
||||
*/
|
||||
retryAfterPayment: boolean;
|
||||
}
|
||||
|
||||
function getNextBackupTimestamp(): TalerProtocolTimestamp {
|
||||
@ -253,7 +251,7 @@ function getNextBackupTimestamp(): TalerProtocolTimestamp {
|
||||
async function runBackupCycleForProvider(
|
||||
ws: InternalWalletState,
|
||||
args: BackupForProviderArgs,
|
||||
): Promise<OperationAttemptResult> {
|
||||
): Promise<OperationAttemptResult<unknown, { talerUri: string }>> {
|
||||
const provider = await ws.db
|
||||
.mktx((x) => [x.backupProviders])
|
||||
.runReadOnly(async (tx) => {
|
||||
@ -339,57 +337,34 @@ async function runBackupCycleForProvider(
|
||||
if (!talerUri) {
|
||||
throw Error("no taler URI available to pay provider");
|
||||
}
|
||||
const res = await preparePayForUri(ws, talerUri);
|
||||
let proposalId = res.proposalId;
|
||||
let doPay = false;
|
||||
switch (res.status) {
|
||||
case PreparePayResultType.InsufficientBalance:
|
||||
// FIXME: record in provider state!
|
||||
logger.warn("insufficient balance to pay for backup provider");
|
||||
proposalId = res.proposalId;
|
||||
break;
|
||||
case PreparePayResultType.PaymentPossible:
|
||||
doPay = true;
|
||||
break;
|
||||
case PreparePayResultType.AlreadyConfirmed:
|
||||
break;
|
||||
}
|
||||
|
||||
// FIXME: check if the provider is overcharging us!
|
||||
//We can't delay downloading the proposal since we need the id
|
||||
//FIXME: check download errors
|
||||
|
||||
const res = await preparePayForUri(ws, talerUri);
|
||||
|
||||
await ws.db
|
||||
.mktx((x) => [x.backupProviders, x.operationRetries])
|
||||
.runReadWrite(async (tx) => {
|
||||
const provRec = await tx.backupProviders.get(provider.baseUrl);
|
||||
checkDbInvariant(!!provRec);
|
||||
const ids = new Set(provRec.paymentProposalIds);
|
||||
ids.add(proposalId);
|
||||
provRec.paymentProposalIds = Array.from(ids).sort();
|
||||
provRec.currentPaymentProposalId = proposalId;
|
||||
// FIXME: allocate error code for this!
|
||||
await tx.backupProviders.put(provRec);
|
||||
const opId = RetryTags.forBackup(provRec);
|
||||
const prov = await tx.backupProviders.get(provider.baseUrl);
|
||||
if (!prov) {
|
||||
logger.warn("backup provider not found anymore");
|
||||
return;
|
||||
}
|
||||
const opId = RetryTags.forBackup(prov);
|
||||
await scheduleRetryInTx(ws, tx, opId);
|
||||
prov.currentPaymentProposalId = res.proposalId;
|
||||
prov.state = {
|
||||
tag: BackupProviderStateTag.Retrying,
|
||||
};
|
||||
await tx.backupProviders.put(prov);
|
||||
});
|
||||
|
||||
if (doPay) {
|
||||
const confirmRes = await confirmPay(ws, proposalId);
|
||||
switch (confirmRes.type) {
|
||||
case ConfirmPayResultType.Pending:
|
||||
logger.warn("payment not yet finished yet");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (args.retryAfterPayment) {
|
||||
return await runBackupCycleForProvider(ws, {
|
||||
...args,
|
||||
retryAfterPayment: false,
|
||||
});
|
||||
}
|
||||
return {
|
||||
type: OperationAttemptResultType.Pending,
|
||||
result: undefined,
|
||||
result: {
|
||||
talerUri,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@ -442,10 +417,7 @@ async function runBackupCycleForProvider(
|
||||
});
|
||||
logger.info("processed existing backup");
|
||||
// Now upload our own, merged backup.
|
||||
return await runBackupCycleForProvider(ws, {
|
||||
...args,
|
||||
retryAfterPayment: false,
|
||||
});
|
||||
return await runBackupCycleForProvider(ws, args);
|
||||
}
|
||||
|
||||
// Some other response that we did not expect!
|
||||
@ -477,7 +449,6 @@ export async function processBackupForProvider(
|
||||
|
||||
return await runBackupCycleForProvider(ws, {
|
||||
backupProviderBaseUrl: provider.baseUrl,
|
||||
retryAfterPayment: true,
|
||||
});
|
||||
}
|
||||
|
||||
@ -540,12 +511,11 @@ export async function runBackupCycle(
|
||||
for (const provider of providers) {
|
||||
await runBackupCycleForProvider(ws, {
|
||||
backupProviderBaseUrl: provider.baseUrl,
|
||||
retryAfterPayment: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
interface SyncTermsOfServiceResponse {
|
||||
export interface SyncTermsOfServiceResponse {
|
||||
// maximum backup size supported
|
||||
storage_limit_in_megabytes: number;
|
||||
|
||||
@ -557,7 +527,7 @@ interface SyncTermsOfServiceResponse {
|
||||
version: string;
|
||||
}
|
||||
|
||||
const codecForSyncTermsOfServiceResponse =
|
||||
export const codecForSyncTermsOfServiceResponse =
|
||||
(): Codec<SyncTermsOfServiceResponse> =>
|
||||
buildCodecForObject<SyncTermsOfServiceResponse>()
|
||||
.property("storage_limit_in_megabytes", codecForNumber())
|
||||
@ -584,10 +554,58 @@ export const codecForAddBackupProviderRequest =
|
||||
.property("activate", codecOptional(codecForBoolean()))
|
||||
.build("AddBackupProviderRequest");
|
||||
|
||||
export type AddBackupProviderResponse =
|
||||
| AddBackupProviderOk
|
||||
| AddBackupProviderPaymentRequired
|
||||
| AddBackupProviderError;
|
||||
|
||||
interface AddBackupProviderOk {
|
||||
status: "ok";
|
||||
}
|
||||
interface AddBackupProviderPaymentRequired {
|
||||
status: "payment-required";
|
||||
talerUri: string;
|
||||
}
|
||||
interface AddBackupProviderError {
|
||||
status: "error";
|
||||
error: TalerErrorDetail;
|
||||
}
|
||||
|
||||
export const codecForAddBackupProviderOk = (): Codec<AddBackupProviderOk> =>
|
||||
buildCodecForObject<AddBackupProviderOk>()
|
||||
.property("status", codecForConstString("ok"))
|
||||
.build("AddBackupProviderOk");
|
||||
|
||||
export const codecForAddBackupProviderPaymenrRequired =
|
||||
(): Codec<AddBackupProviderPaymentRequired> =>
|
||||
buildCodecForObject<AddBackupProviderPaymentRequired>()
|
||||
.property("status", codecForConstString("payment-required"))
|
||||
.property("talerUri", codecForString())
|
||||
.build("AddBackupProviderPaymentRequired");
|
||||
|
||||
export const codecForAddBackupProviderError =
|
||||
(): Codec<AddBackupProviderError> =>
|
||||
buildCodecForObject<AddBackupProviderError>()
|
||||
.property("status", codecForConstString("error"))
|
||||
.property("error", codecForTalerErrorDetail())
|
||||
.build("AddBackupProviderError");
|
||||
|
||||
export const codecForAddBackupProviderResponse =
|
||||
(): Codec<AddBackupProviderResponse> =>
|
||||
buildCodecForUnion<AddBackupProviderResponse>()
|
||||
.discriminateOn("status")
|
||||
.alternative("ok", codecForAddBackupProviderOk())
|
||||
.alternative(
|
||||
"payment-required",
|
||||
codecForAddBackupProviderPaymenrRequired(),
|
||||
)
|
||||
.alternative("error", codecForAddBackupProviderError())
|
||||
.build("AddBackupProviderResponse");
|
||||
|
||||
export async function addBackupProvider(
|
||||
ws: InternalWalletState,
|
||||
req: AddBackupProviderRequest,
|
||||
): Promise<void> {
|
||||
): Promise<AddBackupProviderResponse> {
|
||||
logger.info(`adding backup provider ${j2s(req)}`);
|
||||
await provideBackupState(ws);
|
||||
const canonUrl = canonicalizeBaseUrl(req.backupProviderBaseUrl);
|
||||
@ -618,6 +636,7 @@ export async function addBackupProvider(
|
||||
.mktx((x) => [x.backupProviders])
|
||||
.runReadWrite(async (tx) => {
|
||||
let state: BackupProviderState;
|
||||
//FIXME: what is the difference provisional and ready?
|
||||
if (req.activate) {
|
||||
state = {
|
||||
tag: BackupProviderStateTag.Ready,
|
||||
@ -641,6 +660,39 @@ export async function addBackupProvider(
|
||||
uids: [encodeCrock(getRandomBytes(32))],
|
||||
});
|
||||
});
|
||||
|
||||
return await runFirstBackupCycleForProvider(ws, {
|
||||
backupProviderBaseUrl: canonUrl,
|
||||
});
|
||||
}
|
||||
|
||||
async function runFirstBackupCycleForProvider(
|
||||
ws: InternalWalletState,
|
||||
args: BackupForProviderArgs,
|
||||
): Promise<AddBackupProviderResponse> {
|
||||
const resp = await runBackupCycleForProvider(ws, args);
|
||||
switch (resp.type) {
|
||||
case OperationAttemptResultType.Error:
|
||||
return {
|
||||
status: "error",
|
||||
error: resp.errorDetail,
|
||||
};
|
||||
case OperationAttemptResultType.Finished:
|
||||
return {
|
||||
status: "ok",
|
||||
};
|
||||
case OperationAttemptResultType.Longpoll:
|
||||
throw Error(
|
||||
"unexpected runFirstBackupCycleForProvider result (longpoll)",
|
||||
);
|
||||
case OperationAttemptResultType.Pending:
|
||||
return {
|
||||
status: "payment-required",
|
||||
talerUri: resp.result.talerUri,
|
||||
};
|
||||
default:
|
||||
assertUnreachable(resp);
|
||||
}
|
||||
}
|
||||
|
||||
export async function restoreFromRecoverySecret(): Promise<void> {
|
||||
|
@ -1584,7 +1584,7 @@ export async function runPayForConfirmPay(
|
||||
const numRetry = opRetry?.retryInfo.retryCounter ?? 0;
|
||||
if (
|
||||
res.errorDetail.code ===
|
||||
TalerErrorCode.WALLET_PAY_MERCHANT_SERVER_ERROR &&
|
||||
TalerErrorCode.WALLET_PAY_MERCHANT_SERVER_ERROR &&
|
||||
numRetry < maxRetry
|
||||
) {
|
||||
// Pretend the operation is pending instead of reporting
|
||||
|
@ -106,6 +106,7 @@ import {
|
||||
import { WalletContractData } from "./db.js";
|
||||
import {
|
||||
AddBackupProviderRequest,
|
||||
AddBackupProviderResponse,
|
||||
BackupInfo,
|
||||
RemoveBackupProviderRequest,
|
||||
RunBackupCycleRequest,
|
||||
@ -519,7 +520,7 @@ export type ExportBackupOp = {
|
||||
export type AddBackupProviderOp = {
|
||||
op: WalletApiOperation.AddBackupProvider;
|
||||
request: AddBackupProviderRequest;
|
||||
response: EmptyObject;
|
||||
response: AddBackupProviderResponse;
|
||||
};
|
||||
|
||||
export type RemoveBackupProviderOp = {
|
||||
|
@ -933,9 +933,9 @@ async function dumpCoins(ws: InternalWalletState): Promise<CoinDumpJson> {
|
||||
ageCommitmentProof: c.ageCommitmentProof,
|
||||
spend_allocation: c.spendAllocation
|
||||
? {
|
||||
amount: c.spendAllocation.amount,
|
||||
id: c.spendAllocation.id,
|
||||
}
|
||||
amount: c.spendAllocation.amount,
|
||||
id: c.spendAllocation.id,
|
||||
}
|
||||
: undefined,
|
||||
});
|
||||
}
|
||||
@ -1215,8 +1215,7 @@ async function dispatchRequestInternal<Op extends WalletApiOperation>(
|
||||
}
|
||||
case WalletApiOperation.AddBackupProvider: {
|
||||
const req = codecForAddBackupProviderRequest().decode(payload);
|
||||
await addBackupProvider(ws, req);
|
||||
return {};
|
||||
return await addBackupProvider(ws, req);
|
||||
}
|
||||
case WalletApiOperation.RunBackupCycle: {
|
||||
const req = codecForRunBackupCycle().decode(payload);
|
||||
|
@ -25,7 +25,6 @@
|
||||
* Imports.
|
||||
*/
|
||||
import { h, VNode } from "preact";
|
||||
import { JustInDevMode } from "./components/JustInDevMode.js";
|
||||
import {
|
||||
NavigationHeader,
|
||||
NavigationHeaderHolder,
|
||||
@ -138,14 +137,9 @@ export function PopupNavBar({ path = "" }: { path?: string }): VNode {
|
||||
>
|
||||
<i18n.Translate>Balance</i18n.Translate>
|
||||
</a>
|
||||
<JustInDevMode>
|
||||
<a
|
||||
href={Pages.backup}
|
||||
class={path.startsWith("/backup") ? "active" : ""}
|
||||
>
|
||||
<i18n.Translate>Backup</i18n.Translate>
|
||||
</a>
|
||||
</JustInDevMode>
|
||||
<a href={Pages.backup} class={path.startsWith("/backup") ? "active" : ""}>
|
||||
<i18n.Translate>Backup</i18n.Translate>
|
||||
</a>
|
||||
<div style={{ display: "flex", paddingTop: 4, justifyContent: "right" }}>
|
||||
<a href={Pages.qr}>
|
||||
<SvgIcon
|
||||
@ -177,18 +171,16 @@ export function WalletNavBar({ path = "" }: { path?: string }): VNode {
|
||||
>
|
||||
<i18n.Translate>Balance</i18n.Translate>
|
||||
</a>
|
||||
<JustInDevMode>
|
||||
<a
|
||||
href={Pages.backup}
|
||||
class={path.startsWith("/backup") ? "active" : ""}
|
||||
>
|
||||
<i18n.Translate>Backup</i18n.Translate>
|
||||
</a>
|
||||
<a
|
||||
href={Pages.backup}
|
||||
class={path.startsWith("/backup") ? "active" : ""}
|
||||
>
|
||||
<i18n.Translate>Backup</i18n.Translate>
|
||||
</a>
|
||||
|
||||
<a href={Pages.dev} class={path.startsWith("/dev") ? "active" : ""}>
|
||||
<i18n.Translate>Dev</i18n.Translate>
|
||||
</a>
|
||||
</JustInDevMode>
|
||||
<a href={Pages.dev} class={path.startsWith("/dev") ? "active" : ""}>
|
||||
<i18n.Translate>Dev</i18n.Translate>
|
||||
</a>
|
||||
|
||||
<div
|
||||
style={{ display: "flex", paddingTop: 4, justifyContent: "right" }}
|
||||
|
@ -0,0 +1,31 @@
|
||||
/*
|
||||
This file is part of GNU Taler
|
||||
(C) 2022 Taler Systems S.A.
|
||||
|
||||
GNU Taler is free software; you can redistribute it and/or modify it under the
|
||||
terms of the GNU General Public License as published by the Free Software
|
||||
Foundation; either version 3, or (at your option) any later version.
|
||||
|
||||
GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along with
|
||||
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Sebastian Javier Marchano (sebasjm)
|
||||
*/
|
||||
|
||||
import { createExample } from "../test-utils.js";
|
||||
import { QR } from "./QR.js";
|
||||
|
||||
export default {
|
||||
title: "wallet/qr",
|
||||
};
|
||||
|
||||
export const Restore = createExample(QR, {
|
||||
text: "taler://restore/6J0RZTJC6AV21WXK87BTE67WTHE9P2QSHF2BZXTP7PDZY2ARYBPG@sync1.demo.taler.net,sync2.demo.taler.net,sync1.demo.taler.net,sync3.demo.taler.net",
|
||||
});
|
@ -22,7 +22,7 @@ export function QR({ text }: { text: string }): VNode {
|
||||
const divRef = useRef<HTMLDivElement>(null);
|
||||
useEffect(() => {
|
||||
if (!divRef.current) return;
|
||||
const qr = qrcode(0, "L");
|
||||
const qr = qrcode(0, "H");
|
||||
qr.addData(text);
|
||||
qr.make();
|
||||
divRef.current.innerHTML = qr.createSvgTag({
|
||||
|
@ -26,7 +26,7 @@ import {
|
||||
LoadingUriView,
|
||||
ShowButtonsAcceptedTosView,
|
||||
ShowButtonsNonAcceptedTosView,
|
||||
ShowTosContentView
|
||||
ShowTosContentView,
|
||||
} from "./views.js";
|
||||
|
||||
export interface Props {
|
||||
|
@ -35,10 +35,13 @@ export function useComponentState(
|
||||
* For the exchange selected, bring the status of the terms of service
|
||||
*/
|
||||
const terms = useAsyncAsHook(async () => {
|
||||
const exchangeTos = await api.wallet.call(WalletApiOperation.GetExchangeTos, {
|
||||
exchangeBaseUrl: exchangeUrl,
|
||||
acceptedFormat: ["text/xml"]
|
||||
})
|
||||
const exchangeTos = await api.wallet.call(
|
||||
WalletApiOperation.GetExchangeTos,
|
||||
{
|
||||
exchangeBaseUrl: exchangeUrl,
|
||||
acceptedFormat: ["text/xml"],
|
||||
},
|
||||
);
|
||||
|
||||
const state = buildTermsOfServiceState(exchangeTos);
|
||||
|
||||
@ -78,14 +81,14 @@ export function useComponentState(
|
||||
if (accepted) {
|
||||
api.wallet.call(WalletApiOperation.SetExchangeTosAccepted, {
|
||||
exchangeBaseUrl: exchangeUrl,
|
||||
etag: state.version
|
||||
})
|
||||
etag: state.version,
|
||||
});
|
||||
} else {
|
||||
// mark as not accepted
|
||||
api.wallet.call(WalletApiOperation.SetExchangeTosAccepted, {
|
||||
exchangeBaseUrl: exchangeUrl,
|
||||
etag: undefined
|
||||
})
|
||||
etag: undefined,
|
||||
});
|
||||
}
|
||||
// setAccepted(accepted);
|
||||
if (!readOnly) onChange(accepted); //external update
|
||||
|
@ -24,5 +24,6 @@ import * as a2 from "./PendingTransactions.stories.js";
|
||||
import * as a3 from "./Amount.stories.js";
|
||||
import * as a4 from "./ShowFullContractTermPopup.stories.js";
|
||||
import * as a5 from "./TermsOfService/stories.js";
|
||||
import * as a6 from "./QR.stories";
|
||||
|
||||
export default [a1, a2, a3, a4, a5];
|
||||
export default [a1, a2, a3, a4, a5, a6];
|
||||
|
@ -48,8 +48,9 @@ export const DevContextProviderForTesting = ({
|
||||
value: {
|
||||
devMode: !!value,
|
||||
devModeToggle: {
|
||||
value, button: {}
|
||||
}
|
||||
value,
|
||||
button: {},
|
||||
},
|
||||
},
|
||||
children,
|
||||
});
|
||||
@ -57,7 +58,7 @@ export const DevContextProviderForTesting = ({
|
||||
|
||||
export const DevContextProvider = ({ children }: { children: any }): VNode => {
|
||||
const devModeToggle = useWalletDevMode();
|
||||
const value: Type = { devMode: !!devModeToggle.value, devModeToggle }
|
||||
const value: Type = { devMode: !!devModeToggle.value, devModeToggle };
|
||||
//support for function as children, useful for getting the value right away
|
||||
children =
|
||||
children.length === 1 && typeof children === "function"
|
||||
|
@ -15,7 +15,11 @@
|
||||
*/
|
||||
|
||||
/* eslint-disable react-hooks/rules-of-hooks */
|
||||
import { Amounts, TalerErrorDetail, TalerProtocolTimestamp } from "@gnu-taler/taler-util";
|
||||
import {
|
||||
Amounts,
|
||||
TalerErrorDetail,
|
||||
TalerProtocolTimestamp,
|
||||
} from "@gnu-taler/taler-util";
|
||||
import { TalerError, WalletApiOperation } from "@gnu-taler/taler-wallet-core";
|
||||
import { isFuture, parse } from "date-fns";
|
||||
import { useState } from "preact/hooks";
|
||||
@ -32,7 +36,9 @@ export function useComponentState(
|
||||
): RecursiveState<State> {
|
||||
const amount = Amounts.parseOrThrow(amountStr);
|
||||
|
||||
const hook = useAsyncAsHook(() => api.wallet.call(WalletApiOperation.ListExchanges, {}));
|
||||
const hook = useAsyncAsHook(() =>
|
||||
api.wallet.call(WalletApiOperation.ListExchanges, {}),
|
||||
);
|
||||
|
||||
if (!hook) {
|
||||
return {
|
||||
@ -51,7 +57,7 @@ export function useComponentState(
|
||||
|
||||
return () => {
|
||||
const [subject, setSubject] = useState<string | undefined>();
|
||||
const [timestamp, setTimestamp] = useState<string | undefined>()
|
||||
const [timestamp, setTimestamp] = useState<string | undefined>();
|
||||
|
||||
const [operationError, setOperationError] = useState<
|
||||
TalerErrorDetail | undefined
|
||||
@ -70,45 +76,51 @@ export function useComponentState(
|
||||
const exchange = selectedExchange.selected;
|
||||
|
||||
const hook = useAsyncAsHook(async () => {
|
||||
const resp = await api.wallet.call(WalletApiOperation.PreparePeerPullPayment, {
|
||||
amount: amountStr,
|
||||
exchangeBaseUrl: exchange.exchangeBaseUrl,
|
||||
})
|
||||
return resp
|
||||
})
|
||||
const resp = await api.wallet.call(
|
||||
WalletApiOperation.PreparePeerPullPayment,
|
||||
{
|
||||
amount: amountStr,
|
||||
exchangeBaseUrl: exchange.exchangeBaseUrl,
|
||||
},
|
||||
);
|
||||
return resp;
|
||||
});
|
||||
|
||||
if (!hook) {
|
||||
return {
|
||||
status: "loading",
|
||||
error: undefined
|
||||
}
|
||||
error: undefined,
|
||||
};
|
||||
}
|
||||
if (hook.hasError) {
|
||||
return {
|
||||
status: "loading-uri",
|
||||
error: hook
|
||||
}
|
||||
error: hook,
|
||||
};
|
||||
}
|
||||
|
||||
const { amountEffective, amountRaw } = hook.response
|
||||
const requestAmount = Amounts.parseOrThrow(amountRaw)
|
||||
const toBeReceived = Amounts.parseOrThrow(amountEffective)
|
||||
const { amountEffective, amountRaw } = hook.response;
|
||||
const requestAmount = Amounts.parseOrThrow(amountRaw);
|
||||
const toBeReceived = Amounts.parseOrThrow(amountEffective);
|
||||
|
||||
let purse_expiration: TalerProtocolTimestamp | undefined = undefined
|
||||
let purse_expiration: TalerProtocolTimestamp | undefined = undefined;
|
||||
let timestampError: string | undefined = undefined;
|
||||
|
||||
const t = timestamp === undefined ? undefined : parse(timestamp, "dd/MM/yyyy", new Date())
|
||||
const t =
|
||||
timestamp === undefined
|
||||
? undefined
|
||||
: parse(timestamp, "dd/MM/yyyy", new Date());
|
||||
|
||||
if (t !== undefined) {
|
||||
if (Number.isNaN(t.getTime())) {
|
||||
timestampError = 'Should have the format "dd/MM/yyyy"'
|
||||
timestampError = 'Should have the format "dd/MM/yyyy"';
|
||||
} else {
|
||||
if (!isFuture(t)) {
|
||||
timestampError = 'Should be in the future'
|
||||
timestampError = "Should be in the future";
|
||||
} else {
|
||||
purse_expiration = {
|
||||
t_s: t.getTime() / 1000
|
||||
}
|
||||
t_s: t.getTime() / 1000,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -116,14 +128,17 @@ export function useComponentState(
|
||||
async function accept(): Promise<void> {
|
||||
if (!subject || !purse_expiration) return;
|
||||
try {
|
||||
const resp = await api.wallet.call(WalletApiOperation.InitiatePeerPullPayment, {
|
||||
exchangeBaseUrl: exchange.exchangeBaseUrl,
|
||||
partialContractTerms: {
|
||||
amount: Amounts.stringify(amount),
|
||||
summary: subject,
|
||||
purse_expiration
|
||||
const resp = await api.wallet.call(
|
||||
WalletApiOperation.InitiatePeerPullPayment,
|
||||
{
|
||||
exchangeBaseUrl: exchange.exchangeBaseUrl,
|
||||
partialContractTerms: {
|
||||
amount: Amounts.stringify(amount),
|
||||
summary: subject,
|
||||
purse_expiration,
|
||||
},
|
||||
},
|
||||
});
|
||||
);
|
||||
|
||||
onSuccess(resp.transactionId);
|
||||
} catch (e) {
|
||||
@ -134,12 +149,18 @@ export function useComponentState(
|
||||
throw Error("error trying to accept");
|
||||
}
|
||||
}
|
||||
const unableToCreate = !subject || Amounts.isZero(amount) || !purse_expiration
|
||||
const unableToCreate =
|
||||
!subject || Amounts.isZero(amount) || !purse_expiration;
|
||||
|
||||
return {
|
||||
status: "ready",
|
||||
subject: {
|
||||
error: subject === undefined ? undefined : !subject ? "Can't be empty" : undefined,
|
||||
error:
|
||||
subject === undefined
|
||||
? undefined
|
||||
: !subject
|
||||
? "Can't be empty"
|
||||
: undefined,
|
||||
value: subject ?? "",
|
||||
onInput: async (e) => setSubject(e),
|
||||
},
|
||||
@ -147,8 +168,8 @@ export function useComponentState(
|
||||
error: timestampError,
|
||||
value: timestamp === undefined ? "" : timestamp,
|
||||
onInput: async (e) => {
|
||||
setTimestamp(e)
|
||||
}
|
||||
setTimestamp(e);
|
||||
},
|
||||
},
|
||||
doSelectExchange: selectedExchange.doSelect,
|
||||
exchangeUrl: exchange.exchangeBaseUrl,
|
||||
|
@ -18,7 +18,7 @@ import {
|
||||
AbsoluteTime,
|
||||
AmountJson,
|
||||
PreparePayResult,
|
||||
TalerErrorDetail
|
||||
TalerErrorDetail,
|
||||
} from "@gnu-taler/taler-util";
|
||||
import { Loading } from "../../components/Loading.js";
|
||||
import { HookError } from "../../hooks/useAsyncAsHook.js";
|
||||
|
@ -21,7 +21,7 @@ import {
|
||||
PreparePayResult,
|
||||
PreparePayResultType,
|
||||
TalerErrorDetail,
|
||||
TalerProtocolTimestamp
|
||||
TalerProtocolTimestamp,
|
||||
} from "@gnu-taler/taler-util";
|
||||
import { TalerError, WalletApiOperation } from "@gnu-taler/taler-wallet-core";
|
||||
import { useEffect, useState } from "preact/hooks";
|
||||
@ -41,10 +41,12 @@ export function useComponentState(
|
||||
return { p2p, balance };
|
||||
});
|
||||
|
||||
useEffect(() => api.listener.onUpdateNotification(
|
||||
[NotificationType.CoinWithdrawn],
|
||||
hook?.retry
|
||||
));
|
||||
useEffect(() =>
|
||||
api.listener.onUpdateNotification(
|
||||
[NotificationType.CoinWithdrawn],
|
||||
hook?.retry,
|
||||
),
|
||||
);
|
||||
|
||||
const [operationError, setOperationError] = useState<
|
||||
TalerErrorDetail | undefined
|
||||
@ -63,10 +65,7 @@ export function useComponentState(
|
||||
};
|
||||
}
|
||||
|
||||
const {
|
||||
contractTerms,
|
||||
peerPullPaymentIncomingId,
|
||||
} = hook.response.p2p;
|
||||
const { contractTerms, peerPullPaymentIncomingId } = hook.response.p2p;
|
||||
|
||||
const amountStr: string = contractTerms?.amount;
|
||||
const amount = Amounts.parseOrThrow(amountStr);
|
||||
@ -134,9 +133,12 @@ export function useComponentState(
|
||||
|
||||
async function accept(): Promise<void> {
|
||||
try {
|
||||
const resp = await api.wallet.call(WalletApiOperation.AcceptPeerPullPayment, {
|
||||
peerPullPaymentIncomingId,
|
||||
});
|
||||
const resp = await api.wallet.call(
|
||||
WalletApiOperation.AcceptPeerPullPayment,
|
||||
{
|
||||
peerPullPaymentIncomingId,
|
||||
},
|
||||
);
|
||||
onSuccess(resp.transactionId);
|
||||
} catch (e) {
|
||||
if (e instanceof TalerError) {
|
||||
|
@ -15,8 +15,10 @@
|
||||
*/
|
||||
|
||||
import {
|
||||
AmountJson, PreparePayResult,
|
||||
PreparePayResultAlreadyConfirmed, PreparePayResultPaymentPossible
|
||||
AmountJson,
|
||||
PreparePayResult,
|
||||
PreparePayResultAlreadyConfirmed,
|
||||
PreparePayResultPaymentPossible,
|
||||
} from "@gnu-taler/taler-util";
|
||||
import { Loading } from "../../components/Loading.js";
|
||||
import { HookError } from "../../hooks/useAsyncAsHook.js";
|
||||
|
@ -15,10 +15,11 @@
|
||||
*/
|
||||
|
||||
import {
|
||||
Amounts, ConfirmPayResultType,
|
||||
Amounts,
|
||||
ConfirmPayResultType,
|
||||
NotificationType,
|
||||
PreparePayResultType,
|
||||
TalerErrorCode
|
||||
TalerErrorCode,
|
||||
} from "@gnu-taler/taler-util";
|
||||
import { TalerError, WalletApiOperation } from "@gnu-taler/taler-wallet-core";
|
||||
import { useEffect, useState } from "preact/hooks";
|
||||
@ -35,17 +36,24 @@ export function useComponentState(
|
||||
|
||||
const hook = useAsyncAsHook(async () => {
|
||||
if (!talerPayUri) throw Error("ERROR_NO-URI-FOR-PAYMENT");
|
||||
const payStatus = await api.wallet.call(WalletApiOperation.PreparePayForUri, {
|
||||
talerPayUri: talerPayUri
|
||||
});
|
||||
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]);
|
||||
useEffect(
|
||||
() =>
|
||||
api.listener.onUpdateNotification(
|
||||
[NotificationType.CoinWithdrawn],
|
||||
hook?.retry,
|
||||
),
|
||||
[hook],
|
||||
);
|
||||
|
||||
const hookResponse = !hook || hook.hasError ? undefined : hook.response;
|
||||
|
||||
|
@ -20,11 +20,13 @@
|
||||
*/
|
||||
|
||||
import {
|
||||
Amounts, ConfirmPayResult,
|
||||
Amounts,
|
||||
ConfirmPayResult,
|
||||
ConfirmPayResultType,
|
||||
NotificationType, PreparePayResultInsufficientBalance,
|
||||
NotificationType,
|
||||
PreparePayResultInsufficientBalance,
|
||||
PreparePayResultPaymentPossible,
|
||||
PreparePayResultType
|
||||
PreparePayResultType,
|
||||
} from "@gnu-taler/taler-util";
|
||||
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
|
||||
import { expect } from "chai";
|
||||
@ -42,11 +44,9 @@ describe("Payment CTA states", () => {
|
||||
onSuccess: async () => {
|
||||
null;
|
||||
},
|
||||
}
|
||||
};
|
||||
const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } =
|
||||
mountHook(() =>
|
||||
useComponentState(props, mock),
|
||||
);
|
||||
mountHook(() => useComponentState(props, mock));
|
||||
|
||||
{
|
||||
const { status, error } = pullLastResultOrThrow();
|
||||
@ -66,7 +66,7 @@ describe("Payment CTA states", () => {
|
||||
}
|
||||
|
||||
await assertNoPendingUpdate();
|
||||
expect(handler.getCallingQueueState()).eq("empty")
|
||||
expect(handler.getCallingQueueState()).eq("empty");
|
||||
});
|
||||
|
||||
it("should response with no balance", async () => {
|
||||
@ -78,18 +78,24 @@ describe("Payment CTA states", () => {
|
||||
onSuccess: async () => {
|
||||
null;
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
handler.addWalletCallResponse(WalletApiOperation.PreparePayForUri, undefined, {
|
||||
status: PreparePayResultType.InsufficientBalance,
|
||||
amountRaw: "USD:10",
|
||||
} as PreparePayResultInsufficientBalance)
|
||||
handler.addWalletCallResponse(WalletApiOperation.GetBalances, {}, { balances: [] })
|
||||
handler.addWalletCallResponse(
|
||||
WalletApiOperation.PreparePayForUri,
|
||||
undefined,
|
||||
{
|
||||
status: PreparePayResultType.InsufficientBalance,
|
||||
amountRaw: "USD:10",
|
||||
} as PreparePayResultInsufficientBalance,
|
||||
);
|
||||
handler.addWalletCallResponse(
|
||||
WalletApiOperation.GetBalances,
|
||||
{},
|
||||
{ balances: [] },
|
||||
);
|
||||
|
||||
const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } =
|
||||
mountHook(() =>
|
||||
useComponentState(props, mock),
|
||||
);
|
||||
mountHook(() => useComponentState(props, mock));
|
||||
|
||||
{
|
||||
const { status, error } = pullLastResultOrThrow();
|
||||
@ -102,7 +108,7 @@ describe("Payment CTA states", () => {
|
||||
{
|
||||
const r = pullLastResultOrThrow();
|
||||
if (r.status !== "no-balance-for-currency") {
|
||||
expect(r).eq({})
|
||||
expect(r).eq({});
|
||||
return;
|
||||
}
|
||||
expect(r.balance).undefined;
|
||||
@ -110,7 +116,7 @@ describe("Payment CTA states", () => {
|
||||
}
|
||||
|
||||
await assertNoPendingUpdate();
|
||||
expect(handler.getCallingQueueState()).eq("empty")
|
||||
expect(handler.getCallingQueueState()).eq("empty");
|
||||
});
|
||||
|
||||
it("should not be able to pay if there is no enough balance", async () => {
|
||||
@ -122,25 +128,33 @@ describe("Payment CTA states", () => {
|
||||
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,
|
||||
}]
|
||||
})
|
||||
};
|
||||
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(props, mock),
|
||||
);
|
||||
mountHook(() => useComponentState(props, mock));
|
||||
|
||||
{
|
||||
const { status, error } = pullLastResultOrThrow();
|
||||
@ -158,7 +172,7 @@ describe("Payment CTA states", () => {
|
||||
}
|
||||
|
||||
await assertNoPendingUpdate();
|
||||
expect(handler.getCallingQueueState()).eq("empty")
|
||||
expect(handler.getCallingQueueState()).eq("empty");
|
||||
});
|
||||
|
||||
it("should be able to pay (without fee)", async () => {
|
||||
@ -170,25 +184,33 @@ describe("Payment CTA states", () => {
|
||||
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,
|
||||
}]
|
||||
})
|
||||
};
|
||||
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(props, mock),
|
||||
);
|
||||
mountHook(() => useComponentState(props, mock));
|
||||
|
||||
{
|
||||
const { status, error } = pullLastResultOrThrow();
|
||||
@ -201,8 +223,8 @@ describe("Payment CTA states", () => {
|
||||
{
|
||||
const r = pullLastResultOrThrow();
|
||||
if (r.status !== "ready") {
|
||||
expect(r).eq({})
|
||||
return
|
||||
expect(r).eq({});
|
||||
return;
|
||||
}
|
||||
expect(r.balance).deep.equal(Amounts.parseOrThrow("USD:15"));
|
||||
expect(r.amount).deep.equal(Amounts.parseOrThrow("USD:10"));
|
||||
@ -210,7 +232,7 @@ describe("Payment CTA states", () => {
|
||||
}
|
||||
|
||||
await assertNoPendingUpdate();
|
||||
expect(handler.getCallingQueueState()).eq("empty")
|
||||
expect(handler.getCallingQueueState()).eq("empty");
|
||||
});
|
||||
|
||||
it("should be able to pay (with fee)", async () => {
|
||||
@ -222,29 +244,33 @@ describe("Payment CTA states", () => {
|
||||
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.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(
|
||||
props,
|
||||
mock
|
||||
|
||||
),
|
||||
);
|
||||
mountHook(() => useComponentState(props, mock));
|
||||
|
||||
{
|
||||
const { status, error } = pullLastResultOrThrow();
|
||||
@ -263,7 +289,7 @@ describe("Payment CTA states", () => {
|
||||
}
|
||||
|
||||
await assertNoPendingUpdate();
|
||||
expect(handler.getCallingQueueState()).eq("empty")
|
||||
expect(handler.getCallingQueueState()).eq("empty");
|
||||
});
|
||||
|
||||
it("should get confirmation done after pay successfully", async () => {
|
||||
@ -275,33 +301,39 @@ describe("Payment CTA states", () => {
|
||||
onSuccess: async () => {
|
||||
null;
|
||||
},
|
||||
}
|
||||
handler.addWalletCallResponse(WalletApiOperation.PreparePayForUri, undefined, {
|
||||
status: PreparePayResultType.PaymentPossible,
|
||||
amountRaw: "USD:9",
|
||||
amountEffective: "USD:10",
|
||||
} as PreparePayResultPaymentPossible)
|
||||
};
|
||||
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.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)
|
||||
} as ConfirmPayResult);
|
||||
|
||||
const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } =
|
||||
mountHook(() =>
|
||||
useComponentState(
|
||||
props, mock
|
||||
),
|
||||
);
|
||||
mountHook(() => useComponentState(props, mock));
|
||||
|
||||
{
|
||||
const { status, error } = pullLastResultOrThrow();
|
||||
@ -314,7 +346,7 @@ describe("Payment CTA states", () => {
|
||||
{
|
||||
const r = pullLastResultOrThrow();
|
||||
if (r.status !== "ready") {
|
||||
expect(r).eq({})
|
||||
expect(r).eq({});
|
||||
return;
|
||||
}
|
||||
expect(r.balance).deep.equal(Amounts.parseOrThrow("USD:15"));
|
||||
@ -324,7 +356,7 @@ describe("Payment CTA states", () => {
|
||||
}
|
||||
|
||||
await assertNoPendingUpdate();
|
||||
expect(handler.getCallingQueueState()).eq("empty")
|
||||
expect(handler.getCallingQueueState()).eq("empty");
|
||||
});
|
||||
|
||||
it("should not stay in ready state after pay with error", async () => {
|
||||
@ -335,32 +367,38 @@ describe("Payment CTA states", () => {
|
||||
goToWalletManualWithdraw: nullFunction,
|
||||
onSuccess: nullFunction,
|
||||
};
|
||||
handler.addWalletCallResponse(WalletApiOperation.PreparePayForUri, undefined, {
|
||||
status: PreparePayResultType.PaymentPossible,
|
||||
amountRaw: "USD:9",
|
||||
amountEffective: "USD:10",
|
||||
} as PreparePayResultPaymentPossible)
|
||||
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.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)
|
||||
} as ConfirmPayResult);
|
||||
|
||||
const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } =
|
||||
mountHook(() =>
|
||||
useComponentState(
|
||||
props, mock
|
||||
),
|
||||
);
|
||||
mountHook(() => useComponentState(props, mock));
|
||||
|
||||
{
|
||||
const { status, error } = pullLastResultOrThrow();
|
||||
@ -368,7 +406,7 @@ describe("Payment CTA states", () => {
|
||||
expect(error).undefined;
|
||||
}
|
||||
|
||||
expect(await waitForStateUpdate()).true
|
||||
expect(await waitForStateUpdate()).true;
|
||||
|
||||
{
|
||||
const r = pullLastResultOrThrow();
|
||||
@ -380,7 +418,7 @@ describe("Payment CTA states", () => {
|
||||
r.payHandler.onClick();
|
||||
}
|
||||
|
||||
expect(await waitForStateUpdate()).true
|
||||
expect(await waitForStateUpdate()).true;
|
||||
|
||||
{
|
||||
const r = pullLastResultOrThrow();
|
||||
@ -402,7 +440,7 @@ describe("Payment CTA states", () => {
|
||||
|
||||
await assertNoPendingUpdate();
|
||||
|
||||
expect(handler.getCallingQueueState()).eq("empty")
|
||||
expect(handler.getCallingQueueState()).eq("empty");
|
||||
});
|
||||
|
||||
it("should update balance if a coins is withdraw", async () => {
|
||||
@ -415,46 +453,62 @@ describe("Payment CTA states", () => {
|
||||
onSuccess: async () => {
|
||||
null;
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
handler.addWalletCallResponse(WalletApiOperation.PreparePayForUri, undefined, {
|
||||
status: PreparePayResultType.PaymentPossible,
|
||||
amountRaw: "USD:9",
|
||||
amountEffective: "USD:10",
|
||||
} as PreparePayResultPaymentPossible)
|
||||
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.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.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.GetBalances,
|
||||
{},
|
||||
{
|
||||
balances: [
|
||||
{
|
||||
available: "USD:15",
|
||||
hasPendingTransactions: false,
|
||||
pendingIncoming: "USD:0",
|
||||
pendingOutgoing: "USD:0",
|
||||
requiresUserInput: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
);
|
||||
|
||||
const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } =
|
||||
mountHook(() =>
|
||||
useComponentState(
|
||||
props, mock
|
||||
),
|
||||
);
|
||||
mountHook(() => useComponentState(props, mock));
|
||||
|
||||
{
|
||||
const { status, error } = pullLastResultOrThrow();
|
||||
@ -467,8 +521,8 @@ describe("Payment CTA states", () => {
|
||||
{
|
||||
const r = pullLastResultOrThrow();
|
||||
if (r.status !== "ready") {
|
||||
expect(r).eq({})
|
||||
return
|
||||
expect(r).eq({});
|
||||
return;
|
||||
}
|
||||
expect(r.balance).deep.equal(Amounts.parseOrThrow("USD:10"));
|
||||
expect(r.amount).deep.equal(Amounts.parseOrThrow("USD:9"));
|
||||
@ -483,8 +537,8 @@ describe("Payment CTA states", () => {
|
||||
{
|
||||
const r = pullLastResultOrThrow();
|
||||
if (r.status !== "ready") {
|
||||
expect(r).eq({})
|
||||
return
|
||||
expect(r).eq({});
|
||||
return;
|
||||
}
|
||||
expect(r.balance).deep.equal(Amounts.parseOrThrow("USD:15"));
|
||||
expect(r.amount).deep.equal(Amounts.parseOrThrow("USD:9"));
|
||||
@ -493,6 +547,6 @@ describe("Payment CTA states", () => {
|
||||
}
|
||||
|
||||
await assertNoPendingUpdate();
|
||||
expect(handler.getCallingQueueState()).eq("empty")
|
||||
expect(handler.getCallingQueueState()).eq("empty");
|
||||
});
|
||||
});
|
||||
|
@ -48,7 +48,9 @@ export function useComponentState(
|
||||
const recovery = info;
|
||||
|
||||
async function recoverBackup(): Promise<void> {
|
||||
await wxApi.wallet.call(WalletApiOperation.ImportBackupRecovery, { recovery });
|
||||
await wxApi.wallet.call(WalletApiOperation.ImportBackupRecovery, {
|
||||
recovery,
|
||||
});
|
||||
onSuccess();
|
||||
}
|
||||
|
||||
|
@ -25,7 +25,7 @@ import {
|
||||
IgnoredView,
|
||||
InProgressView,
|
||||
LoadingUriView,
|
||||
ReadyView
|
||||
ReadyView,
|
||||
} from "./views.js";
|
||||
|
||||
export interface Props {
|
||||
|
@ -29,13 +29,17 @@ export function useComponentState(
|
||||
|
||||
const info = useAsyncAsHook(async () => {
|
||||
if (!talerRefundUri) throw Error("ERROR_NO-URI-FOR-REFUND");
|
||||
const refund = await api.wallet.call(WalletApiOperation.PrepareRefund, { talerRefundUri });
|
||||
const refund = await api.wallet.call(WalletApiOperation.PrepareRefund, {
|
||||
talerRefundUri,
|
||||
});
|
||||
return { refund, uri: talerRefundUri };
|
||||
});
|
||||
|
||||
useEffect(() => api.listener.onUpdateNotification(
|
||||
[NotificationType.RefreshMelted],
|
||||
info?.retry)
|
||||
useEffect(() =>
|
||||
api.listener.onUpdateNotification(
|
||||
[NotificationType.RefreshMelted],
|
||||
info?.retry,
|
||||
),
|
||||
);
|
||||
|
||||
if (!info) {
|
||||
@ -52,7 +56,7 @@ export function useComponentState(
|
||||
|
||||
const doAccept = async (): Promise<void> => {
|
||||
const res = await api.wallet.call(WalletApiOperation.ApplyRefund, {
|
||||
talerRefundUri: uri
|
||||
talerRefundUri: uri,
|
||||
});
|
||||
|
||||
onSuccess(res.transactionId);
|
||||
|
@ -21,7 +21,10 @@
|
||||
|
||||
import {
|
||||
AmountJson,
|
||||
Amounts, NotificationType, OrderShortInfo, PrepareRefundResult
|
||||
Amounts,
|
||||
NotificationType,
|
||||
OrderShortInfo,
|
||||
PrepareRefundResult,
|
||||
} from "@gnu-taler/taler-util";
|
||||
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
|
||||
import { expect } from "chai";
|
||||
@ -45,7 +48,7 @@ describe("Refund CTA states", () => {
|
||||
null;
|
||||
},
|
||||
},
|
||||
mock
|
||||
mock,
|
||||
// {
|
||||
// prepareRefund: async () => ({}),
|
||||
// applyRefund: async () => ({}),
|
||||
@ -73,7 +76,7 @@ describe("Refund CTA states", () => {
|
||||
}
|
||||
|
||||
await assertNoPendingUpdate();
|
||||
expect(handler.getCallingQueueState()).eq("empty")
|
||||
expect(handler.getCallingQueueState()).eq("empty");
|
||||
});
|
||||
|
||||
it("should be ready after loading", async () => {
|
||||
@ -86,7 +89,7 @@ describe("Refund CTA states", () => {
|
||||
onSuccess: async () => {
|
||||
null;
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
handler.addWalletCallResponse(WalletApiOperation.PrepareRefund, undefined, {
|
||||
awaiting: "EUR:2",
|
||||
@ -103,12 +106,13 @@ describe("Refund CTA states", () => {
|
||||
orderId: "orderId1",
|
||||
summary: "the summary",
|
||||
} as OrderShortInfo,
|
||||
})
|
||||
});
|
||||
|
||||
const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } =
|
||||
mountHook(() =>
|
||||
useComponentState(
|
||||
props, mock
|
||||
props,
|
||||
mock,
|
||||
// {
|
||||
// prepareRefund: async () =>
|
||||
// ({
|
||||
@ -154,7 +158,7 @@ describe("Refund CTA states", () => {
|
||||
}
|
||||
|
||||
await assertNoPendingUpdate();
|
||||
expect(handler.getCallingQueueState()).eq("empty")
|
||||
expect(handler.getCallingQueueState()).eq("empty");
|
||||
});
|
||||
|
||||
it("should be ignored after clicking the ignore button", async () => {
|
||||
@ -167,7 +171,7 @@ describe("Refund CTA states", () => {
|
||||
onSuccess: async () => {
|
||||
null;
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
handler.addWalletCallResponse(WalletApiOperation.PrepareRefund, undefined, {
|
||||
awaiting: "EUR:2",
|
||||
@ -184,7 +188,7 @@ describe("Refund CTA states", () => {
|
||||
orderId: "orderId1",
|
||||
summary: "the summary",
|
||||
} as OrderShortInfo,
|
||||
})
|
||||
});
|
||||
// handler.addWalletCall(WalletApiOperation.ApplyRefund)
|
||||
// handler.addWalletCall(WalletApiOperation.PrepareRefund, undefined, {
|
||||
// awaiting: "EUR:1",
|
||||
@ -205,7 +209,8 @@ describe("Refund CTA states", () => {
|
||||
const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } =
|
||||
mountHook(() =>
|
||||
useComponentState(
|
||||
props, mock
|
||||
props,
|
||||
mock,
|
||||
// {
|
||||
// prepareRefund: async () =>
|
||||
// ({
|
||||
@ -242,11 +247,11 @@ describe("Refund CTA states", () => {
|
||||
const state = pullLastResultOrThrow();
|
||||
|
||||
if (state.status !== "ready") {
|
||||
expect(state).eq({})
|
||||
expect(state).eq({});
|
||||
return;
|
||||
}
|
||||
if (state.error) {
|
||||
expect(state).eq({})
|
||||
expect(state).eq({});
|
||||
return;
|
||||
}
|
||||
expect(state.accept.onClick).not.undefined;
|
||||
@ -264,18 +269,18 @@ describe("Refund CTA states", () => {
|
||||
const state = pullLastResultOrThrow();
|
||||
|
||||
if (state.status !== "ignored") {
|
||||
expect(state).eq({})
|
||||
expect(state).eq({});
|
||||
return;
|
||||
}
|
||||
if (state.error) {
|
||||
expect(state).eq({})
|
||||
expect(state).eq({});
|
||||
return;
|
||||
}
|
||||
expect(state.merchantName).eq("the merchant name");
|
||||
}
|
||||
|
||||
await assertNoPendingUpdate();
|
||||
expect(handler.getCallingQueueState()).eq("empty")
|
||||
expect(handler.getCallingQueueState()).eq("empty");
|
||||
});
|
||||
|
||||
it("should be in progress when doing refresh", async () => {
|
||||
@ -288,7 +293,7 @@ describe("Refund CTA states", () => {
|
||||
onSuccess: async () => {
|
||||
null;
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
handler.addWalletCallResponse(WalletApiOperation.PrepareRefund, undefined, {
|
||||
awaiting: "EUR:2",
|
||||
@ -305,7 +310,7 @@ describe("Refund CTA states", () => {
|
||||
orderId: "orderId1",
|
||||
summary: "the summary",
|
||||
} as OrderShortInfo,
|
||||
})
|
||||
});
|
||||
handler.addWalletCallResponse(WalletApiOperation.PrepareRefund, undefined, {
|
||||
awaiting: "EUR:1",
|
||||
effectivePaid: "EUR:2",
|
||||
@ -321,7 +326,7 @@ describe("Refund CTA states", () => {
|
||||
orderId: "orderId1",
|
||||
summary: "the summary",
|
||||
} as OrderShortInfo,
|
||||
})
|
||||
});
|
||||
handler.addWalletCallResponse(WalletApiOperation.PrepareRefund, undefined, {
|
||||
awaiting: "EUR:0",
|
||||
effectivePaid: "EUR:2",
|
||||
@ -337,14 +342,10 @@ describe("Refund CTA states", () => {
|
||||
orderId: "orderId1",
|
||||
summary: "the summary",
|
||||
} as OrderShortInfo,
|
||||
})
|
||||
});
|
||||
|
||||
const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } =
|
||||
mountHook(() =>
|
||||
useComponentState(
|
||||
props, mock
|
||||
),
|
||||
);
|
||||
mountHook(() => useComponentState(props, mock));
|
||||
|
||||
{
|
||||
const { status, error } = pullLastResultOrThrow();
|
||||
@ -358,7 +359,7 @@ describe("Refund CTA states", () => {
|
||||
const state = pullLastResultOrThrow();
|
||||
|
||||
if (state.status !== "in-progress") {
|
||||
expect(state).eq({})
|
||||
expect(state).eq({});
|
||||
return;
|
||||
}
|
||||
if (state.error) expect.fail();
|
||||
@ -367,7 +368,7 @@ describe("Refund CTA states", () => {
|
||||
expect(state.amount).deep.eq(Amounts.parseOrThrow("EUR:2"));
|
||||
// expect(state.progress).closeTo(1 / 3, 0.01)
|
||||
|
||||
handler.notifyEventFromWallet(NotificationType.RefreshMelted)
|
||||
handler.notifyEventFromWallet(NotificationType.RefreshMelted);
|
||||
}
|
||||
|
||||
expect(await waitForStateUpdate()).true;
|
||||
@ -376,7 +377,7 @@ describe("Refund CTA states", () => {
|
||||
const state = pullLastResultOrThrow();
|
||||
|
||||
if (state.status !== "in-progress") {
|
||||
expect(state).eq({})
|
||||
expect(state).eq({});
|
||||
return;
|
||||
}
|
||||
if (state.error) expect.fail();
|
||||
@ -385,7 +386,7 @@ describe("Refund CTA states", () => {
|
||||
expect(state.amount).deep.eq(Amounts.parseOrThrow("EUR:2"));
|
||||
// expect(state.progress).closeTo(2 / 3, 0.01)
|
||||
|
||||
handler.notifyEventFromWallet(NotificationType.RefreshMelted)
|
||||
handler.notifyEventFromWallet(NotificationType.RefreshMelted);
|
||||
}
|
||||
|
||||
expect(await waitForStateUpdate()).true;
|
||||
@ -394,7 +395,7 @@ describe("Refund CTA states", () => {
|
||||
const state = pullLastResultOrThrow();
|
||||
|
||||
if (state.status !== "ready") {
|
||||
expect(state).eq({})
|
||||
expect(state).eq({});
|
||||
return;
|
||||
}
|
||||
if (state.error) expect.fail();
|
||||
@ -404,6 +405,6 @@ describe("Refund CTA states", () => {
|
||||
}
|
||||
|
||||
await assertNoPendingUpdate();
|
||||
expect(handler.getCallingQueueState()).eq("empty")
|
||||
expect(handler.getCallingQueueState()).eq("empty");
|
||||
});
|
||||
});
|
||||
|
@ -25,7 +25,7 @@ import {
|
||||
AcceptedView,
|
||||
IgnoredView,
|
||||
LoadingUriView,
|
||||
ReadyView
|
||||
ReadyView,
|
||||
} from "./views.js";
|
||||
|
||||
export interface Props {
|
||||
|
@ -26,7 +26,9 @@ export function useComponentState(
|
||||
): State {
|
||||
const tipInfo = useAsyncAsHook(async () => {
|
||||
if (!talerTipUri) throw Error("ERROR_NO-URI-FOR-TIP");
|
||||
const tip = await api.wallet.call(WalletApiOperation.PrepareTip, { talerTipUri });
|
||||
const tip = await api.wallet.call(WalletApiOperation.PrepareTip, {
|
||||
talerTipUri,
|
||||
});
|
||||
return { tip };
|
||||
});
|
||||
|
||||
@ -46,7 +48,9 @@ export function useComponentState(
|
||||
const { tip } = tipInfo.response;
|
||||
|
||||
const doAccept = async (): Promise<void> => {
|
||||
const res = await api.wallet.call(WalletApiOperation.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,11 +65,10 @@ describe("Tip CTA states", () => {
|
||||
}
|
||||
|
||||
await assertNoPendingUpdate();
|
||||
expect(handler.getCallingQueueState()).eq("empty")
|
||||
expect(handler.getCallingQueueState()).eq("empty");
|
||||
});
|
||||
|
||||
it("should be ready for accepting the tip", async () => {
|
||||
|
||||
const { handler, mock } = createWalletApiMock();
|
||||
|
||||
handler.addWalletCallResponse(WalletApiOperation.PrepareTip, undefined, {
|
||||
@ -79,9 +78,9 @@ describe("Tip CTA states", () => {
|
||||
tipAmountEffective: "EUR:1",
|
||||
walletTipId: "tip_id",
|
||||
expirationTimestamp: {
|
||||
t_s: 1
|
||||
t_s: 1,
|
||||
},
|
||||
tipAmountRaw: ""
|
||||
tipAmountRaw: "",
|
||||
});
|
||||
|
||||
const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } =
|
||||
@ -112,7 +111,7 @@ describe("Tip CTA states", () => {
|
||||
const state = pullLastResultOrThrow();
|
||||
|
||||
if (state.status !== "ready") {
|
||||
expect(state).eq({ status: "ready" })
|
||||
expect(state).eq({ status: "ready" });
|
||||
return;
|
||||
}
|
||||
if (state.error) expect.fail();
|
||||
@ -132,9 +131,9 @@ describe("Tip CTA states", () => {
|
||||
tipAmountEffective: "EUR:1",
|
||||
walletTipId: "tip_id",
|
||||
expirationTimestamp: {
|
||||
t_s: 1
|
||||
t_s: 1,
|
||||
},
|
||||
tipAmountRaw: ""
|
||||
tipAmountRaw: "",
|
||||
});
|
||||
expect(await waitForStateUpdate()).true;
|
||||
|
||||
@ -142,7 +141,7 @@ describe("Tip CTA states", () => {
|
||||
const state = pullLastResultOrThrow();
|
||||
|
||||
if (state.status !== "accepted") {
|
||||
expect(state).eq({ status: "accepted" })
|
||||
expect(state).eq({ status: "accepted" });
|
||||
return;
|
||||
}
|
||||
if (state.error) expect.fail();
|
||||
@ -151,7 +150,7 @@ describe("Tip CTA states", () => {
|
||||
expect(state.exchangeBaseUrl).eq("exchange url");
|
||||
}
|
||||
await assertNoPendingUpdate();
|
||||
expect(handler.getCallingQueueState()).eq("empty")
|
||||
expect(handler.getCallingQueueState()).eq("empty");
|
||||
});
|
||||
|
||||
it("should be ignored after clicking the ignore button", async () => {
|
||||
@ -165,7 +164,7 @@ describe("Tip CTA states", () => {
|
||||
expirationTimestamp: {
|
||||
t_s: 1,
|
||||
},
|
||||
tipAmountRaw: ""
|
||||
tipAmountRaw: "",
|
||||
});
|
||||
|
||||
const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } =
|
||||
@ -203,7 +202,7 @@ describe("Tip CTA states", () => {
|
||||
}
|
||||
|
||||
await assertNoPendingUpdate();
|
||||
expect(handler.getCallingQueueState()).eq("empty")
|
||||
expect(handler.getCallingQueueState()).eq("empty");
|
||||
});
|
||||
|
||||
it("should render accepted if the tip has been used previously", async () => {
|
||||
@ -255,6 +254,6 @@ describe("Tip CTA states", () => {
|
||||
expect(state.exchangeBaseUrl).eq("exchange url");
|
||||
}
|
||||
await assertNoPendingUpdate();
|
||||
expect(handler.getCallingQueueState()).eq("empty")
|
||||
expect(handler.getCallingQueueState()).eq("empty");
|
||||
});
|
||||
});
|
||||
|
@ -14,7 +14,11 @@
|
||||
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
import { Amounts, TalerErrorDetail, TalerProtocolTimestamp } from "@gnu-taler/taler-util";
|
||||
import {
|
||||
Amounts,
|
||||
TalerErrorDetail,
|
||||
TalerProtocolTimestamp,
|
||||
} from "@gnu-taler/taler-util";
|
||||
import { TalerError, WalletApiOperation } from "@gnu-taler/taler-wallet-core";
|
||||
import { format, isFuture, parse } from "date-fns";
|
||||
import { useState } from "preact/hooks";
|
||||
@ -29,52 +33,57 @@ export function useComponentState(
|
||||
const amount = Amounts.parseOrThrow(amountStr);
|
||||
|
||||
const [subject, setSubject] = useState<string | undefined>();
|
||||
const [timestamp, setTimestamp] = useState<string | undefined>()
|
||||
const [timestamp, setTimestamp] = useState<string | undefined>();
|
||||
|
||||
const [operationError, setOperationError] = useState<
|
||||
TalerErrorDetail | undefined
|
||||
>(undefined);
|
||||
|
||||
|
||||
const hook = useAsyncAsHook(async () => {
|
||||
const resp = await api.wallet.call(WalletApiOperation.PreparePeerPushPayment, {
|
||||
amount: amountStr
|
||||
})
|
||||
return resp
|
||||
})
|
||||
const resp = await api.wallet.call(
|
||||
WalletApiOperation.PreparePeerPushPayment,
|
||||
{
|
||||
amount: amountStr,
|
||||
},
|
||||
);
|
||||
return resp;
|
||||
});
|
||||
|
||||
if (!hook) {
|
||||
return {
|
||||
status: "loading",
|
||||
error: undefined
|
||||
}
|
||||
error: undefined,
|
||||
};
|
||||
}
|
||||
if (hook.hasError) {
|
||||
return {
|
||||
status: "loading-uri",
|
||||
error: hook
|
||||
}
|
||||
error: hook,
|
||||
};
|
||||
}
|
||||
|
||||
const { amountEffective, amountRaw } = hook.response
|
||||
const debitAmount = Amounts.parseOrThrow(amountRaw)
|
||||
const toBeReceived = Amounts.parseOrThrow(amountEffective)
|
||||
const { amountEffective, amountRaw } = hook.response;
|
||||
const debitAmount = Amounts.parseOrThrow(amountRaw);
|
||||
const toBeReceived = Amounts.parseOrThrow(amountEffective);
|
||||
|
||||
let purse_expiration: TalerProtocolTimestamp | undefined = undefined
|
||||
let purse_expiration: TalerProtocolTimestamp | undefined = undefined;
|
||||
let timestampError: string | undefined = undefined;
|
||||
|
||||
const t = timestamp === undefined ? undefined : parse(timestamp, "dd/MM/yyyy", new Date())
|
||||
const t =
|
||||
timestamp === undefined
|
||||
? undefined
|
||||
: parse(timestamp, "dd/MM/yyyy", new Date());
|
||||
|
||||
if (t !== undefined) {
|
||||
if (Number.isNaN(t.getTime())) {
|
||||
timestampError = 'Should have the format "dd/MM/yyyy"'
|
||||
timestampError = 'Should have the format "dd/MM/yyyy"';
|
||||
} else {
|
||||
if (!isFuture(t)) {
|
||||
timestampError = 'Should be in the future'
|
||||
timestampError = "Should be in the future";
|
||||
} else {
|
||||
purse_expiration = {
|
||||
t_s: t.getTime() / 1000
|
||||
}
|
||||
t_s: t.getTime() / 1000,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -82,13 +91,16 @@ export function useComponentState(
|
||||
async function accept(): Promise<void> {
|
||||
if (!subject || !purse_expiration) return;
|
||||
try {
|
||||
const resp = await api.wallet.call(WalletApiOperation.InitiatePeerPushPayment, {
|
||||
partialContractTerms: {
|
||||
summary: subject,
|
||||
amount: amountStr,
|
||||
purse_expiration
|
||||
const resp = await api.wallet.call(
|
||||
WalletApiOperation.InitiatePeerPushPayment,
|
||||
{
|
||||
partialContractTerms: {
|
||||
summary: subject,
|
||||
amount: amountStr,
|
||||
purse_expiration,
|
||||
},
|
||||
},
|
||||
});
|
||||
);
|
||||
onSuccess(resp.transactionId);
|
||||
} catch (e) {
|
||||
if (e instanceof TalerError) {
|
||||
@ -99,7 +111,8 @@ export function useComponentState(
|
||||
}
|
||||
}
|
||||
|
||||
const unableToCreate = !subject || Amounts.isZero(amount) || !purse_expiration
|
||||
const unableToCreate =
|
||||
!subject || Amounts.isZero(amount) || !purse_expiration;
|
||||
|
||||
return {
|
||||
status: "ready",
|
||||
@ -107,7 +120,12 @@ export function useComponentState(
|
||||
onClick: onClose,
|
||||
},
|
||||
subject: {
|
||||
error: subject === undefined ? undefined : !subject ? "Can't be empty" : undefined,
|
||||
error:
|
||||
subject === undefined
|
||||
? undefined
|
||||
: !subject
|
||||
? "Can't be empty"
|
||||
: undefined,
|
||||
value: subject ?? "",
|
||||
onInput: async (e) => setSubject(e),
|
||||
},
|
||||
@ -115,8 +133,8 @@ export function useComponentState(
|
||||
error: timestampError,
|
||||
value: timestamp === undefined ? "" : timestamp,
|
||||
onInput: async (e) => {
|
||||
setTimestamp(e)
|
||||
}
|
||||
setTimestamp(e);
|
||||
},
|
||||
},
|
||||
create: {
|
||||
onClick: unableToCreate ? undefined : accept,
|
||||
|
@ -17,7 +17,7 @@
|
||||
import {
|
||||
AbsoluteTime,
|
||||
AmountJson,
|
||||
TalerErrorDetail
|
||||
TalerErrorDetail,
|
||||
} from "@gnu-taler/taler-util";
|
||||
import { Loading } from "../../components/Loading.js";
|
||||
import { HookError } from "../../hooks/useAsyncAsHook.js";
|
||||
|
@ -18,7 +18,7 @@ import {
|
||||
AbsoluteTime,
|
||||
Amounts,
|
||||
TalerErrorDetail,
|
||||
TalerProtocolTimestamp
|
||||
TalerProtocolTimestamp,
|
||||
} from "@gnu-taler/taler-util";
|
||||
import { TalerError, WalletApiOperation } from "@gnu-taler/taler-wallet-core";
|
||||
import { useState } from "preact/hooks";
|
||||
@ -52,10 +52,7 @@ export function useComponentState(
|
||||
};
|
||||
}
|
||||
|
||||
const {
|
||||
contractTerms,
|
||||
peerPushPaymentIncomingId,
|
||||
} = hook.response;
|
||||
const { contractTerms, peerPushPaymentIncomingId } = hook.response;
|
||||
|
||||
const amount: string = contractTerms?.amount;
|
||||
const summary: string | undefined = contractTerms?.summary;
|
||||
@ -64,9 +61,12 @@ export function useComponentState(
|
||||
|
||||
async function accept(): Promise<void> {
|
||||
try {
|
||||
const resp = await api.wallet.call(WalletApiOperation.AcceptPeerPushPayment, {
|
||||
peerPushPaymentIncomingId,
|
||||
});
|
||||
const resp = await api.wallet.call(
|
||||
WalletApiOperation.AcceptPeerPushPayment,
|
||||
{
|
||||
peerPushPaymentIncomingId,
|
||||
},
|
||||
);
|
||||
onSuccess(resp.transactionId);
|
||||
} catch (e) {
|
||||
if (e instanceof TalerError) {
|
||||
|
@ -23,7 +23,7 @@ import { compose, StateViewMap } from "../../utils/index.js";
|
||||
import { wxApi } from "../../wxApi.js";
|
||||
import {
|
||||
useComponentStateFromParams,
|
||||
useComponentStateFromURI
|
||||
useComponentStateFromURI,
|
||||
} from "./state.js";
|
||||
|
||||
import { ExchangeSelectionPage } from "../../wallet/ExchangeSelection/index.js";
|
||||
|
@ -19,7 +19,7 @@ import {
|
||||
AmountJson,
|
||||
Amounts,
|
||||
ExchangeListItem,
|
||||
ExchangeTosStatus
|
||||
ExchangeTosStatus,
|
||||
} from "@gnu-taler/taler-util";
|
||||
import { TalerError, WalletApiOperation } from "@gnu-taler/taler-wallet-core";
|
||||
import { useState } from "preact/hooks";
|
||||
@ -35,7 +35,10 @@ export function useComponentStateFromParams(
|
||||
api: typeof wxApi,
|
||||
): RecursiveState<State> {
|
||||
const uriInfoHook = useAsyncAsHook(async () => {
|
||||
const exchanges = await api.wallet.call(WalletApiOperation.ListExchanges, {});
|
||||
const exchanges = await api.wallet.call(
|
||||
WalletApiOperation.ListExchanges,
|
||||
{},
|
||||
);
|
||||
return { amount: Amounts.parseOrThrow(amount), exchanges };
|
||||
});
|
||||
|
||||
@ -58,11 +61,14 @@ export function useComponentStateFromParams(
|
||||
transactionId: string;
|
||||
confirmTransferUrl: string | undefined;
|
||||
}> {
|
||||
const res = await api.wallet.call(WalletApiOperation.AcceptManualWithdrawal, {
|
||||
exchangeBaseUrl: exchange,
|
||||
amount: Amounts.stringify(chosenAmount),
|
||||
restrictAge: ageRestricted,
|
||||
});
|
||||
const res = await api.wallet.call(
|
||||
WalletApiOperation.AcceptManualWithdrawal,
|
||||
{
|
||||
exchangeBaseUrl: exchange,
|
||||
amount: Amounts.stringify(chosenAmount),
|
||||
restrictAge: ageRestricted,
|
||||
},
|
||||
);
|
||||
return {
|
||||
confirmTransferUrl: undefined,
|
||||
transactionId: res.transactionId,
|
||||
@ -93,9 +99,12 @@ export function useComponentStateFromURI(
|
||||
const uriInfoHook = useAsyncAsHook(async () => {
|
||||
if (!talerWithdrawUri) throw Error("ERROR_NO-URI-FOR-WITHDRAWAL");
|
||||
|
||||
const uriInfo = await api.wallet.call(WalletApiOperation.GetWithdrawalDetailsForUri, {
|
||||
talerWithdrawUri,
|
||||
});
|
||||
const uriInfo = await api.wallet.call(
|
||||
WalletApiOperation.GetWithdrawalDetailsForUri,
|
||||
{
|
||||
talerWithdrawUri,
|
||||
},
|
||||
);
|
||||
const { amount, defaultExchangeBaseUrl } = uriInfo;
|
||||
return {
|
||||
talerWithdrawUri,
|
||||
@ -126,11 +135,14 @@ export function useComponentStateFromURI(
|
||||
transactionId: string;
|
||||
confirmTransferUrl: string | undefined;
|
||||
}> {
|
||||
const res = await api.wallet.call(WalletApiOperation.AcceptBankIntegratedWithdrawal, {
|
||||
exchangeBaseUrl: exchange,
|
||||
talerWithdrawUri: uri,
|
||||
restrictAge: ageRestricted
|
||||
});
|
||||
const res = await api.wallet.call(
|
||||
WalletApiOperation.AcceptBankIntegratedWithdrawal,
|
||||
{
|
||||
exchangeBaseUrl: exchange,
|
||||
talerWithdrawUri: uri,
|
||||
restrictAge: ageRestricted,
|
||||
},
|
||||
);
|
||||
return {
|
||||
confirmTransferUrl: res.confirmTransferUrl,
|
||||
transactionId: res.transactionId,
|
||||
@ -189,11 +201,14 @@ function exchangeSelectionState(
|
||||
* about the withdrawal
|
||||
*/
|
||||
const amountHook = useAsyncAsHook(async () => {
|
||||
const info = await api.wallet.call(WalletApiOperation.GetWithdrawalDetailsForAmount, {
|
||||
exchangeBaseUrl: currentExchange.exchangeBaseUrl,
|
||||
amount: Amounts.stringify(chosenAmount),
|
||||
restrictAge: ageRestricted,
|
||||
});
|
||||
const info = await api.wallet.call(
|
||||
WalletApiOperation.GetWithdrawalDetailsForAmount,
|
||||
{
|
||||
exchangeBaseUrl: currentExchange.exchangeBaseUrl,
|
||||
amount: Amounts.stringify(chosenAmount),
|
||||
restrictAge: ageRestricted,
|
||||
},
|
||||
);
|
||||
|
||||
const withdrawAmount = {
|
||||
raw: Amounts.parseOrThrow(info.amountRaw),
|
||||
@ -264,10 +279,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 {
|
||||
|
@ -21,7 +21,9 @@
|
||||
|
||||
import {
|
||||
Amounts,
|
||||
ExchangeEntryStatus, ExchangeListItem, ExchangeTosStatus
|
||||
ExchangeEntryStatus,
|
||||
ExchangeListItem,
|
||||
ExchangeTosStatus,
|
||||
} from "@gnu-taler/taler-util";
|
||||
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
|
||||
import { expect } from "chai";
|
||||
@ -70,13 +72,9 @@ describe("Withdraw CTA states", () => {
|
||||
onSuccess: async () => {
|
||||
null;
|
||||
},
|
||||
}
|
||||
};
|
||||
const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } =
|
||||
mountHook(() =>
|
||||
useComponentStateFromURI(
|
||||
props, mock
|
||||
),
|
||||
);
|
||||
mountHook(() => useComponentStateFromURI(props, mock));
|
||||
|
||||
{
|
||||
const { status } = pullLastResultOrThrow();
|
||||
@ -96,7 +94,7 @@ describe("Withdraw CTA states", () => {
|
||||
}
|
||||
|
||||
await assertNoPendingUpdate();
|
||||
expect(handler.getCallingQueueState()).eq("empty")
|
||||
expect(handler.getCallingQueueState()).eq("empty");
|
||||
});
|
||||
|
||||
it("should tell the user that there is not known exchange", async () => {
|
||||
@ -109,18 +107,18 @@ describe("Withdraw CTA states", () => {
|
||||
onSuccess: async () => {
|
||||
null;
|
||||
},
|
||||
}
|
||||
handler.addWalletCallResponse(WalletApiOperation.GetWithdrawalDetailsForUri, undefined, {
|
||||
amount: "EUR:2",
|
||||
possibleExchanges: [],
|
||||
})
|
||||
};
|
||||
handler.addWalletCallResponse(
|
||||
WalletApiOperation.GetWithdrawalDetailsForUri,
|
||||
undefined,
|
||||
{
|
||||
amount: "EUR:2",
|
||||
possibleExchanges: [],
|
||||
},
|
||||
);
|
||||
|
||||
const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } =
|
||||
mountHook(() =>
|
||||
useComponentStateFromURI(
|
||||
props, mock
|
||||
),
|
||||
);
|
||||
mountHook(() => useComponentStateFromURI(props, mock));
|
||||
|
||||
{
|
||||
const { status } = pullLastResultOrThrow();
|
||||
@ -138,7 +136,7 @@ describe("Withdraw CTA states", () => {
|
||||
}
|
||||
|
||||
await assertNoPendingUpdate();
|
||||
expect(handler.getCallingQueueState()).eq("empty")
|
||||
expect(handler.getCallingQueueState()).eq("empty");
|
||||
});
|
||||
|
||||
it("should be able to withdraw if tos are ok", async () => {
|
||||
@ -151,26 +149,30 @@ describe("Withdraw CTA states", () => {
|
||||
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: []
|
||||
})
|
||||
};
|
||||
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(
|
||||
props, mock
|
||||
),
|
||||
);
|
||||
mountHook(() => useComponentStateFromURI(props, mock));
|
||||
|
||||
{
|
||||
const { status, error } = pullLastResultOrThrow();
|
||||
@ -203,7 +205,7 @@ describe("Withdraw CTA states", () => {
|
||||
}
|
||||
|
||||
await assertNoPendingUpdate();
|
||||
expect(handler.getCallingQueueState()).eq("empty")
|
||||
expect(handler.getCallingQueueState()).eq("empty");
|
||||
});
|
||||
|
||||
it("should accept the tos before withdraw", async () => {
|
||||
@ -216,38 +218,45 @@ describe("Withdraw CTA states", () => {
|
||||
onSuccess: async () => {
|
||||
null;
|
||||
},
|
||||
}
|
||||
};
|
||||
const exchangeWithNewTos = exchanges.map((e) => ({
|
||||
...e,
|
||||
tosStatus: ExchangeTosStatus.New,
|
||||
}));
|
||||
|
||||
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: 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
|
||||
})
|
||||
handler.addWalletCallResponse(
|
||||
WalletApiOperation.GetWithdrawalDetailsForUri,
|
||||
undefined,
|
||||
{
|
||||
amount: "ARS:2",
|
||||
possibleExchanges: exchanges,
|
||||
defaultExchangeBaseUrl: exchanges[0].exchangeBaseUrl,
|
||||
},
|
||||
);
|
||||
|
||||
const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } =
|
||||
mountHook(() =>
|
||||
useComponentStateFromURI(
|
||||
props, mock
|
||||
),
|
||||
);
|
||||
mountHook(() => useComponentStateFromURI(props, mock));
|
||||
|
||||
{
|
||||
const { status, error } = pullLastResultOrThrow();
|
||||
@ -297,6 +306,6 @@ describe("Withdraw CTA states", () => {
|
||||
}
|
||||
|
||||
await assertNoPendingUpdate();
|
||||
expect(handler.getCallingQueueState()).eq("empty")
|
||||
expect(handler.getCallingQueueState()).eq("empty");
|
||||
});
|
||||
});
|
||||
|
@ -63,7 +63,9 @@ async function handleAutoOpenPerm(
|
||||
onChange(res.newValue);
|
||||
} else {
|
||||
try {
|
||||
await wxApi.background.toggleHeaderListener(false).then((r) => onChange(r.newValue));
|
||||
await wxApi.background
|
||||
.toggleHeaderListener(false)
|
||||
.then((r) => onChange(r.newValue));
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
|
@ -32,10 +32,15 @@ export function useBackupDeviceName(): BackupDeviceName {
|
||||
useEffect(() => {
|
||||
async function run(): Promise<void> {
|
||||
//create a first list of backup info by currency
|
||||
const status = await wxApi.wallet.call(WalletApiOperation.GetBackupInfo, {});
|
||||
const status = await wxApi.wallet.call(
|
||||
WalletApiOperation.GetBackupInfo,
|
||||
{},
|
||||
);
|
||||
|
||||
async function update(newName: string): Promise<void> {
|
||||
await wxApi.wallet.call(WalletApiOperation.SetWalletDeviceId, { walletDeviceId: newName });
|
||||
await wxApi.wallet.call(WalletApiOperation.SetWalletDeviceId, {
|
||||
walletDeviceId: newName,
|
||||
});
|
||||
setStatus((old) => ({ ...old, name: newName }));
|
||||
}
|
||||
|
||||
|
@ -66,7 +66,9 @@ async function handleClipboardPerm(
|
||||
onChange(granted);
|
||||
} else {
|
||||
try {
|
||||
await wxApi.background.toggleHeaderListener(false).then((r) => onChange(r.newValue));
|
||||
await wxApi.background
|
||||
.toggleHeaderListener(false)
|
||||
.then((r) => onChange(r.newValue));
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
|
@ -30,7 +30,10 @@ 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.wallet.call(WalletApiOperation.GetBackupInfo, {});
|
||||
const status = await wxApi.wallet.call(
|
||||
WalletApiOperation.GetBackupInfo,
|
||||
{},
|
||||
);
|
||||
|
||||
const providers = status.providers.filter(
|
||||
(p) => p.syncProviderBaseUrl === url,
|
||||
@ -40,7 +43,7 @@ export function useProviderStatus(url: string): ProviderStatus | undefined {
|
||||
async function sync(): Promise<void> {
|
||||
if (info) {
|
||||
await wxApi.wallet.call(WalletApiOperation.RunBackupCycle, {
|
||||
providers: [info.syncProviderBaseUrl]
|
||||
providers: [info.syncProviderBaseUrl],
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -48,7 +51,7 @@ export function useProviderStatus(url: string): ProviderStatus | undefined {
|
||||
async function remove(): Promise<void> {
|
||||
if (info) {
|
||||
await wxApi.wallet.call(WalletApiOperation.RemoveBackupProvider, {
|
||||
provider: info.syncProviderBaseUrl
|
||||
provider: info.syncProviderBaseUrl,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -48,8 +48,10 @@ async function handleOpen(
|
||||
currentValue: undefined | boolean,
|
||||
onChange: (value: boolean) => void,
|
||||
): Promise<void> {
|
||||
const nextValue = !currentValue
|
||||
await wxApi.wallet.call(WalletApiOperation.SetDevMode, { devModeEnabled: nextValue });
|
||||
const nextValue = !currentValue;
|
||||
await wxApi.wallet.call(WalletApiOperation.SetDevMode, {
|
||||
devModeEnabled: nextValue,
|
||||
});
|
||||
onChange(nextValue);
|
||||
return;
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -32,7 +32,7 @@ setupI18n("en", { en: {} });
|
||||
setupPlatform(chromeAPI);
|
||||
|
||||
function testThisStory(st: any): any {
|
||||
describe(`render examples for ${(st as any).default.title}`, () => {
|
||||
describe(`example "${(st as any).default.title}"`, () => {
|
||||
Object.keys(st).forEach((k) => {
|
||||
const Component = (st as any)[k];
|
||||
if (k === "default" || !Component) return;
|
||||
|
@ -15,7 +15,12 @@
|
||||
*/
|
||||
|
||||
import { NotificationType } from "@gnu-taler/taler-util";
|
||||
import { WalletCoreApiClient, WalletCoreOpKeys, WalletCoreRequestType, WalletCoreResponseType } from "@gnu-taler/taler-wallet-core";
|
||||
import {
|
||||
WalletCoreApiClient,
|
||||
WalletCoreOpKeys,
|
||||
WalletCoreRequestType,
|
||||
WalletCoreResponseType,
|
||||
} from "@gnu-taler/taler-wallet-core";
|
||||
import {
|
||||
ComponentChildren,
|
||||
Fragment,
|
||||
@ -207,7 +212,8 @@ export function mountHook<T extends object>(
|
||||
export const nullFunction: any = () => null;
|
||||
|
||||
interface MockHandler {
|
||||
addWalletCallResponse<Op extends WalletCoreOpKeys>(operation: Op,
|
||||
addWalletCallResponse<Op extends WalletCoreOpKeys>(
|
||||
operation: Op,
|
||||
payload?: Partial<WalletCoreRequestType<Op>>,
|
||||
response?: WalletCoreResponseType<Op>,
|
||||
callback?: () => void,
|
||||
@ -220,16 +226,16 @@ interface MockHandler {
|
||||
|
||||
type CallRecord = WalletCallRecord | BackgroundCallRecord;
|
||||
interface WalletCallRecord {
|
||||
source: "wallet"
|
||||
source: "wallet";
|
||||
callback: () => void;
|
||||
operation: WalletCoreOpKeys,
|
||||
payload?: WalletCoreRequestType<WalletCoreOpKeys>,
|
||||
response?: WalletCoreResponseType<WalletCoreOpKeys>,
|
||||
operation: WalletCoreOpKeys;
|
||||
payload?: WalletCoreRequestType<WalletCoreOpKeys>;
|
||||
response?: WalletCoreResponseType<WalletCoreOpKeys>;
|
||||
}
|
||||
interface BackgroundCallRecord {
|
||||
source: "background"
|
||||
name: string,
|
||||
args: any,
|
||||
source: "background";
|
||||
name: string;
|
||||
args: any;
|
||||
response: any;
|
||||
}
|
||||
|
||||
@ -237,78 +243,112 @@ type Subscriptions = {
|
||||
[key in NotificationType]?: VoidFunction;
|
||||
};
|
||||
|
||||
export function createWalletApiMock(): { handler: MockHandler, mock: typeof wxApi } {
|
||||
const calls = new Array<CallRecord>()
|
||||
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)
|
||||
const functionName = String(name);
|
||||
if (functionName !== "call") {
|
||||
throw Error(`the only method in wallet api should be 'call': ${functionName}`)
|
||||
throw Error(
|
||||
`the only method in wallet api should be 'call': ${functionName}`,
|
||||
);
|
||||
}
|
||||
return function (operation: WalletCoreOpKeys, payload: WalletCoreRequestType<WalletCoreOpKeys>) {
|
||||
const next = calls.shift()
|
||||
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)})`)
|
||||
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`)
|
||||
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}`)
|
||||
throw Error(
|
||||
`wallet operation doesn't match: expected ${next.operation} actual ${operation}`,
|
||||
);
|
||||
}
|
||||
next.callback()
|
||||
next.callback();
|
||||
|
||||
return next.response ?? {}
|
||||
}
|
||||
}
|
||||
return next.response ?? {};
|
||||
};
|
||||
},
|
||||
}),
|
||||
listener: {
|
||||
onUpdateNotification(mTypes: NotificationType[], callback: (() => void) | undefined): (() => void) {
|
||||
mTypes.forEach(m => {
|
||||
subscriptions[m] = callback
|
||||
})
|
||||
return nullFunction
|
||||
}
|
||||
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()
|
||||
const next = calls.shift();
|
||||
if (!next) {
|
||||
throw Error(`background operation was called but none was expected: ${functionName} (${JSON.stringify(args, undefined, 2)})`)
|
||||
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`)
|
||||
throw Error(`background operation doesn't match`);
|
||||
}
|
||||
return next.response
|
||||
}
|
||||
}
|
||||
return next.response;
|
||||
};
|
||||
},
|
||||
}),
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
const handler: MockHandler = {
|
||||
addWalletCallResponse(operation, payload, response, cb) {
|
||||
calls.push({ source: "wallet", operation, payload, response, callback: cb ? cb : () => { null } })
|
||||
return handler
|
||||
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}`);
|
||||
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 }
|
||||
return { handler, mock };
|
||||
}
|
||||
|
@ -26,7 +26,8 @@ function getJsonIfOk(r: Response): Promise<any> {
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
`Try another server: (${r.status}) ${r.statusText || "internal server error"
|
||||
`Try another server: (${r.status}) ${
|
||||
r.statusText || "internal server error"
|
||||
}`,
|
||||
);
|
||||
}
|
||||
@ -109,3 +110,7 @@ export function compose<SType extends { status: string }, PType>(
|
||||
return h();
|
||||
};
|
||||
}
|
||||
|
||||
export function assertUnreachable(x: never): never {
|
||||
throw new Error("Didn't expect to get here");
|
||||
}
|
||||
|
@ -0,0 +1,95 @@
|
||||
/*
|
||||
This file is part of GNU Taler
|
||||
(C) 2022 Taler Systems S.A.
|
||||
|
||||
GNU Taler is free software; you can redistribute it and/or modify it under the
|
||||
terms of the GNU General Public License as published by the Free Software
|
||||
Foundation; either version 3, or (at your option) any later version.
|
||||
|
||||
GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along with
|
||||
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
import {
|
||||
AmountJson,
|
||||
BackupBackupProviderTerms,
|
||||
TalerErrorDetail,
|
||||
} from "@gnu-taler/taler-util";
|
||||
import { SyncTermsOfServiceResponse } from "@gnu-taler/taler-wallet-core";
|
||||
import { Loading } from "../../components/Loading.js";
|
||||
import { HookError } from "../../hooks/useAsyncAsHook.js";
|
||||
import {
|
||||
ButtonHandler,
|
||||
TextFieldHandler,
|
||||
ToggleHandler,
|
||||
} from "../../mui/handlers.js";
|
||||
import { compose, StateViewMap } from "../../utils/index.js";
|
||||
import { wxApi } from "../../wxApi.js";
|
||||
import { useComponentState } from "./state.js";
|
||||
import {
|
||||
LoadingUriView,
|
||||
SelectProviderView,
|
||||
ConfirmProviderView,
|
||||
} from "./views.js";
|
||||
|
||||
export interface Props {
|
||||
currency: string;
|
||||
onBack: () => Promise<void>;
|
||||
onComplete: (pid: string) => Promise<void>;
|
||||
onPaymentRequired: (uri: string) => Promise<void>;
|
||||
}
|
||||
|
||||
export type State =
|
||||
| State.Loading
|
||||
| State.LoadingUriError
|
||||
| State.ConfirmProvider
|
||||
| State.SelectProvider;
|
||||
|
||||
export namespace State {
|
||||
export interface Loading {
|
||||
status: "loading";
|
||||
error: undefined;
|
||||
}
|
||||
|
||||
export interface LoadingUriError {
|
||||
status: "loading-error";
|
||||
error: HookError;
|
||||
}
|
||||
|
||||
export interface ConfirmProvider {
|
||||
status: "confirm-provider";
|
||||
error: undefined | TalerErrorDetail;
|
||||
url: string;
|
||||
provider: SyncTermsOfServiceResponse;
|
||||
tos: ToggleHandler;
|
||||
onCancel: ButtonHandler;
|
||||
onAccept: ButtonHandler;
|
||||
}
|
||||
|
||||
export interface SelectProvider {
|
||||
status: "select-provider";
|
||||
url: TextFieldHandler;
|
||||
urlOk: boolean;
|
||||
name: TextFieldHandler;
|
||||
onConfirm: ButtonHandler;
|
||||
onCancel: ButtonHandler;
|
||||
error: undefined | TalerErrorDetail;
|
||||
}
|
||||
}
|
||||
|
||||
const viewMapping: StateViewMap<State> = {
|
||||
loading: Loading,
|
||||
"loading-error": LoadingUriView,
|
||||
"select-provider": SelectProviderView,
|
||||
"confirm-provider": ConfirmProviderView,
|
||||
};
|
||||
|
||||
export const AddBackupProviderPage = compose(
|
||||
"AddBackupProvider",
|
||||
(p: Props) => useComponentState(p, wxApi),
|
||||
viewMapping,
|
||||
);
|
@ -0,0 +1,260 @@
|
||||
/*
|
||||
This file is part of GNU Taler
|
||||
(C) 2022 Taler Systems S.A.
|
||||
|
||||
GNU Taler is free software; you can redistribute it and/or modify it under the
|
||||
terms of the GNU General Public License as published by the Free Software
|
||||
Foundation; either version 3, or (at your option) any later version.
|
||||
|
||||
GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along with
|
||||
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
import {
|
||||
canonicalizeBaseUrl,
|
||||
Codec,
|
||||
TalerErrorDetail,
|
||||
} from "@gnu-taler/taler-util";
|
||||
import {
|
||||
codecForSyncTermsOfServiceResponse,
|
||||
WalletApiOperation,
|
||||
} from "@gnu-taler/taler-wallet-core";
|
||||
import { useEffect, useState } from "preact/hooks";
|
||||
import { assertUnreachable } from "../../utils/index.js";
|
||||
import { wxApi } from "../../wxApi.js";
|
||||
import { Props, State } from "./index.js";
|
||||
|
||||
type UrlState<T> = UrlOk<T> | UrlError;
|
||||
|
||||
interface UrlOk<T> {
|
||||
status: "ok";
|
||||
result: T;
|
||||
}
|
||||
type UrlError =
|
||||
| UrlNetworkError
|
||||
| UrlClientError
|
||||
| UrlServerError
|
||||
| UrlParsingError
|
||||
| UrlReadError;
|
||||
|
||||
interface UrlNetworkError {
|
||||
status: "network-error";
|
||||
href: string;
|
||||
}
|
||||
interface UrlClientError {
|
||||
status: "client-error";
|
||||
code: number;
|
||||
}
|
||||
interface UrlServerError {
|
||||
status: "server-error";
|
||||
code: number;
|
||||
}
|
||||
interface UrlParsingError {
|
||||
status: "parsing-error";
|
||||
json: any;
|
||||
}
|
||||
interface UrlReadError {
|
||||
status: "url-error";
|
||||
}
|
||||
|
||||
function useDebounceEffect(
|
||||
time: number,
|
||||
cb: undefined | (() => Promise<void>),
|
||||
deps: Array<any>,
|
||||
): void {
|
||||
const [currentTimer, setCurrentTimer] = useState<any>();
|
||||
useEffect(() => {
|
||||
if (currentTimer !== undefined) clearTimeout(currentTimer);
|
||||
if (cb !== undefined) {
|
||||
const tid = setTimeout(cb, time);
|
||||
setCurrentTimer(tid);
|
||||
}
|
||||
}, deps);
|
||||
}
|
||||
|
||||
function useUrlState<T>(
|
||||
host: string | undefined,
|
||||
path: string,
|
||||
codec: Codec<T>,
|
||||
): UrlState<T> | undefined {
|
||||
const [state, setState] = useState<UrlState<T> | undefined>();
|
||||
|
||||
let href: string | undefined;
|
||||
try {
|
||||
if (host) {
|
||||
const isHttps =
|
||||
host.startsWith("https://") && host.length > "https://".length;
|
||||
const isHttp =
|
||||
host.startsWith("http://") && host.length > "http://".length;
|
||||
const withProto = isHttp || isHttps ? host : `https://${host}`;
|
||||
const baseUrl = canonicalizeBaseUrl(withProto);
|
||||
href = new URL(path, baseUrl).href;
|
||||
}
|
||||
} catch (e) {
|
||||
setState({
|
||||
status: "url-error",
|
||||
});
|
||||
}
|
||||
const constHref = href;
|
||||
|
||||
useDebounceEffect(
|
||||
500,
|
||||
constHref == undefined
|
||||
? undefined
|
||||
: async () => {
|
||||
const req = await fetch(constHref).catch((e) => {
|
||||
return setState({
|
||||
status: "network-error",
|
||||
href: constHref,
|
||||
});
|
||||
});
|
||||
if (!req) return;
|
||||
|
||||
if (req.status >= 400 && req.status < 500) {
|
||||
setState({
|
||||
status: "client-error",
|
||||
code: req.status,
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (req.status > 500) {
|
||||
setState({
|
||||
status: "server-error",
|
||||
code: req.status,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const json = await req.json();
|
||||
try {
|
||||
const result = codec.decode(json);
|
||||
setState({ status: "ok", result });
|
||||
} catch (e: any) {
|
||||
setState({ status: "parsing-error", json });
|
||||
}
|
||||
},
|
||||
[host, path],
|
||||
);
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
export function useComponentState(
|
||||
{ currency, onBack, onComplete, onPaymentRequired }: Props,
|
||||
api: typeof wxApi,
|
||||
): State {
|
||||
const [url, setHost] = useState<string | undefined>();
|
||||
const [name, setName] = useState<string | undefined>();
|
||||
const [tos, setTos] = useState(false);
|
||||
const urlState = useUrlState(
|
||||
url,
|
||||
"config",
|
||||
codecForSyncTermsOfServiceResponse(),
|
||||
);
|
||||
const [operationError, setOperationError] = useState<
|
||||
TalerErrorDetail | undefined
|
||||
>();
|
||||
const [showConfirm, setShowConfirm] = useState(false);
|
||||
|
||||
async function addBackupProvider() {
|
||||
if (!url || !name) return;
|
||||
|
||||
const resp = await api.wallet.call(WalletApiOperation.AddBackupProvider, {
|
||||
backupProviderBaseUrl: url,
|
||||
name: name,
|
||||
activate: true,
|
||||
});
|
||||
|
||||
switch (resp.status) {
|
||||
case "payment-required":
|
||||
return onPaymentRequired(resp.talerUri);
|
||||
case "error":
|
||||
return setOperationError(resp.error);
|
||||
case "ok":
|
||||
return onComplete(url);
|
||||
default:
|
||||
assertUnreachable(resp);
|
||||
}
|
||||
}
|
||||
|
||||
if (showConfirm && urlState && urlState.status === "ok") {
|
||||
return {
|
||||
status: "confirm-provider",
|
||||
error: operationError,
|
||||
onAccept: {
|
||||
onClick: !tos ? undefined : addBackupProvider,
|
||||
},
|
||||
onCancel: {
|
||||
onClick: onBack,
|
||||
},
|
||||
provider: urlState.result,
|
||||
tos: {
|
||||
value: tos,
|
||||
button: {
|
||||
onClick: async () => setTos(!tos),
|
||||
},
|
||||
},
|
||||
url: url ?? "",
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
status: "select-provider",
|
||||
error: undefined,
|
||||
name: {
|
||||
value: name || "",
|
||||
onInput: async (e) => setName(e),
|
||||
error:
|
||||
name === undefined ? undefined : !name ? "Can't be empty" : undefined,
|
||||
},
|
||||
onCancel: {
|
||||
onClick: onBack,
|
||||
},
|
||||
onConfirm: {
|
||||
onClick:
|
||||
!urlState || urlState.status !== "ok" || !name
|
||||
? undefined
|
||||
: async () => {
|
||||
setShowConfirm(true);
|
||||
},
|
||||
},
|
||||
urlOk: urlState?.status === "ok",
|
||||
url: {
|
||||
value: url || "",
|
||||
onInput: async (e) => setHost(e),
|
||||
error: errorString(urlState),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function errorString(state: undefined | UrlState<any>): string | undefined {
|
||||
if (!state) return state;
|
||||
switch (state.status) {
|
||||
case "ok":
|
||||
return undefined;
|
||||
case "client-error": {
|
||||
switch (state.code) {
|
||||
case 404:
|
||||
return "Not found";
|
||||
case 401:
|
||||
return "Unauthorized";
|
||||
case 403:
|
||||
return "Forbidden";
|
||||
default:
|
||||
return `Server says it a client error: ${state.code}.`;
|
||||
}
|
||||
}
|
||||
case "server-error":
|
||||
return `Server had a problem ${state.code}.`;
|
||||
case "parsing-error":
|
||||
return `Server response doesn't have the right format.`;
|
||||
case "network-error":
|
||||
return `Unable to connect to ${state.href}.`;
|
||||
case "url-error":
|
||||
return "URL is not complete";
|
||||
}
|
||||
}
|
@ -0,0 +1,109 @@
|
||||
/*
|
||||
This file is part of GNU Taler
|
||||
(C) 2022 Taler Systems S.A.
|
||||
|
||||
GNU Taler is free software; you can redistribute it and/or modify it under the
|
||||
terms of the GNU General Public License as published by the Free Software
|
||||
Foundation; either version 3, or (at your option) any later version.
|
||||
|
||||
GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along with
|
||||
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Sebastian Javier Marchano (sebasjm)
|
||||
*/
|
||||
|
||||
import { createExample } from "../../test-utils.js";
|
||||
import { ConfirmProviderView, SelectProviderView } from "./views.js";
|
||||
|
||||
export default {
|
||||
title: "wallet/backup/confirm",
|
||||
};
|
||||
|
||||
export const DemoService = createExample(ConfirmProviderView, {
|
||||
url: "https://sync.demo.taler.net/",
|
||||
provider: {
|
||||
annual_fee: "KUDOS:0.1",
|
||||
storage_limit_in_megabytes: 20,
|
||||
version: "1",
|
||||
},
|
||||
tos: {
|
||||
button: {},
|
||||
},
|
||||
onAccept: {},
|
||||
onCancel: {},
|
||||
});
|
||||
|
||||
export const FreeService = createExample(ConfirmProviderView, {
|
||||
url: "https://sync.taler:9667/",
|
||||
provider: {
|
||||
annual_fee: "ARS:0",
|
||||
storage_limit_in_megabytes: 20,
|
||||
version: "1",
|
||||
},
|
||||
tos: {
|
||||
button: {},
|
||||
},
|
||||
onAccept: {},
|
||||
onCancel: {},
|
||||
});
|
||||
|
||||
export const Initial = createExample(SelectProviderView, {
|
||||
url: { value: "" },
|
||||
name: { value: "" },
|
||||
onCancel: {},
|
||||
onConfirm: {},
|
||||
});
|
||||
|
||||
export const WithValue = createExample(SelectProviderView, {
|
||||
url: {
|
||||
value: "sync.demo.taler.net",
|
||||
},
|
||||
name: {
|
||||
value: "Demo backup service",
|
||||
},
|
||||
onCancel: {},
|
||||
onConfirm: {},
|
||||
});
|
||||
|
||||
export const WithConnectionError = createExample(SelectProviderView, {
|
||||
url: {
|
||||
value: "sync.demo.taler.net",
|
||||
error: "Network error",
|
||||
},
|
||||
name: {
|
||||
value: "Demo backup service",
|
||||
},
|
||||
onCancel: {},
|
||||
onConfirm: {},
|
||||
});
|
||||
|
||||
export const WithClientError = createExample(SelectProviderView, {
|
||||
url: {
|
||||
value: "sync.demo.taler.net",
|
||||
error: "URL may not be right: (404) Not Found",
|
||||
},
|
||||
name: {
|
||||
value: "Demo backup service",
|
||||
},
|
||||
onCancel: {},
|
||||
onConfirm: {},
|
||||
});
|
||||
|
||||
export const WithServerError = createExample(SelectProviderView, {
|
||||
url: {
|
||||
value: "sync.demo.taler.net",
|
||||
error: "Try another server: (500) Internal Server Error",
|
||||
},
|
||||
name: {
|
||||
value: "Demo backup service",
|
||||
},
|
||||
onCancel: {},
|
||||
onConfirm: {},
|
||||
});
|
@ -0,0 +1,79 @@
|
||||
/*
|
||||
This file is part of GNU Taler
|
||||
(C) 2022 Taler Systems S.A.
|
||||
|
||||
GNU Taler is free software; you can redistribute it and/or modify it under the
|
||||
terms of the GNU General Public License as published by the Free Software
|
||||
Foundation; either version 3, or (at your option) any later version.
|
||||
|
||||
GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along with
|
||||
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Sebastian Javier Marchano (sebasjm)
|
||||
*/
|
||||
|
||||
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
|
||||
import { expect } from "chai";
|
||||
import {
|
||||
createWalletApiMock,
|
||||
mountHook,
|
||||
nullFunction,
|
||||
} from "../../test-utils.js";
|
||||
import { Props } from "./index.js";
|
||||
import { useComponentState } from "./state.js";
|
||||
|
||||
const props: Props = {
|
||||
currency: "KUDOS",
|
||||
onBack: nullFunction,
|
||||
onComplete: nullFunction,
|
||||
onPaymentRequired: nullFunction,
|
||||
};
|
||||
describe("AddBackupProvider states", () => {
|
||||
it("should start in 'select-provider' state", async () => {
|
||||
const { handler, mock } = createWalletApiMock();
|
||||
|
||||
// handler.addWalletCallResponse(
|
||||
// WalletApiOperation.ListKnownBankAccounts,
|
||||
// undefined,
|
||||
// {
|
||||
// accounts: [],
|
||||
// },
|
||||
// );
|
||||
|
||||
const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } =
|
||||
mountHook(() => useComponentState(props, mock));
|
||||
|
||||
{
|
||||
const state = pullLastResultOrThrow();
|
||||
expect(state.status).equal("select-provider");
|
||||
if (state.status !== "select-provider") return;
|
||||
expect(state.name.value).eq("");
|
||||
expect(state.url.value).eq("");
|
||||
}
|
||||
|
||||
//FIXME: this should not make an extra update
|
||||
/**
|
||||
* this may be due to useUrlState because is using an effect over
|
||||
* a dependency with a timeout
|
||||
*/
|
||||
// NOTE: do not remove this comment, keeping as an example
|
||||
// await waitForStateUpdate()
|
||||
// {
|
||||
// const state = pullLastResultOrThrow();
|
||||
// expect(state.status).equal("select-provider");
|
||||
// if (state.status !== "select-provider") return;
|
||||
// expect(state.name.value).eq("")
|
||||
// expect(state.url.value).eq("")
|
||||
// }
|
||||
|
||||
await assertNoPendingUpdate();
|
||||
expect(handler.getCallingQueueState()).eq("empty");
|
||||
});
|
||||
});
|
@ -0,0 +1,172 @@
|
||||
/*
|
||||
This file is part of GNU Taler
|
||||
(C) 2022 Taler Systems S.A.
|
||||
|
||||
GNU Taler is free software; you can redistribute it and/or modify it under the
|
||||
terms of the GNU General Public License as published by the Free Software
|
||||
Foundation; either version 3, or (at your option) any later version.
|
||||
|
||||
GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along with
|
||||
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
import { Amounts } from "@gnu-taler/taler-util";
|
||||
import { Fragment, h, VNode } from "preact";
|
||||
import { Checkbox } from "../../components/Checkbox.js";
|
||||
import { LoadingError } from "../../components/LoadingError.js";
|
||||
import {
|
||||
LightText,
|
||||
SmallLightText,
|
||||
SubTitle,
|
||||
TermsOfService,
|
||||
Title,
|
||||
} from "../../components/styled/index.js";
|
||||
import { useTranslationContext } from "../../context/translation.js";
|
||||
import { Button } from "../../mui/Button.js";
|
||||
import { TextField } from "../../mui/TextField.js";
|
||||
import { State } from "./index.js";
|
||||
|
||||
export function LoadingUriView({ error }: State.LoadingUriError): VNode {
|
||||
const { i18n } = useTranslationContext();
|
||||
|
||||
return (
|
||||
<LoadingError
|
||||
title={<i18n.Translate>Could not load</i18n.Translate>}
|
||||
error={error}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function ConfirmProviderView({
|
||||
url,
|
||||
provider,
|
||||
tos,
|
||||
onCancel,
|
||||
onAccept,
|
||||
}: State.ConfirmProvider): VNode {
|
||||
const { i18n } = useTranslationContext();
|
||||
const noFee = Amounts.isZero(provider.annual_fee);
|
||||
return (
|
||||
<Fragment>
|
||||
<section>
|
||||
<Title>
|
||||
<i18n.Translate>Review terms of service</i18n.Translate>
|
||||
</Title>
|
||||
<div>
|
||||
<i18n.Translate>Provider URL</i18n.Translate>:{" "}
|
||||
<a href={url} target="_blank" rel="noreferrer">
|
||||
{url}
|
||||
</a>
|
||||
</div>
|
||||
<SmallLightText>
|
||||
<i18n.Translate>
|
||||
Please review and accept this provider's terms of service
|
||||
</i18n.Translate>
|
||||
</SmallLightText>
|
||||
<SubTitle>
|
||||
1. <i18n.Translate>Pricing</i18n.Translate>
|
||||
</SubTitle>
|
||||
<p>
|
||||
{noFee ? (
|
||||
<i18n.Translate>free of charge</i18n.Translate>
|
||||
) : (
|
||||
<i18n.Translate>
|
||||
{provider.annual_fee} per year of service
|
||||
</i18n.Translate>
|
||||
)}
|
||||
</p>
|
||||
<SubTitle>
|
||||
2. <i18n.Translate>Storage</i18n.Translate>
|
||||
</SubTitle>
|
||||
<p>
|
||||
<i18n.Translate>
|
||||
{provider.storage_limit_in_megabytes} megabytes of storage per year
|
||||
of service
|
||||
</i18n.Translate>
|
||||
</p>
|
||||
{/* replace with <TermsOfService /> */}
|
||||
<Checkbox
|
||||
label={<i18n.Translate>Accept terms of service</i18n.Translate>}
|
||||
name="terms"
|
||||
onToggle={tos.button.onClick}
|
||||
enabled={tos.value}
|
||||
/>
|
||||
</section>
|
||||
<footer>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="secondary"
|
||||
onClick={onCancel.onClick}
|
||||
>
|
||||
<i18n.Translate>Cancel</i18n.Translate>
|
||||
</Button>
|
||||
<Button variant="contained" color="primary" onClick={onAccept.onClick}>
|
||||
{noFee ? (
|
||||
<i18n.Translate>Add provider</i18n.Translate>
|
||||
) : (
|
||||
<i18n.Translate>Pay</i18n.Translate>
|
||||
)}
|
||||
</Button>
|
||||
</footer>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
export function SelectProviderView({
|
||||
url,
|
||||
name,
|
||||
urlOk,
|
||||
onCancel,
|
||||
onConfirm,
|
||||
}: State.SelectProvider): VNode {
|
||||
const { i18n } = useTranslationContext();
|
||||
return (
|
||||
<Fragment>
|
||||
<section>
|
||||
<Title>
|
||||
<i18n.Translate>Add backup provider</i18n.Translate>
|
||||
</Title>
|
||||
<LightText>
|
||||
<i18n.Translate>
|
||||
Backup providers may charge for their service
|
||||
</i18n.Translate>
|
||||
</LightText>
|
||||
<p>
|
||||
<TextField
|
||||
label={<i18n.Translate>URL</i18n.Translate>}
|
||||
placeholder="https://"
|
||||
color={urlOk ? "success" : undefined}
|
||||
value={url.value}
|
||||
error={url.error}
|
||||
onChange={url.onInput}
|
||||
/>
|
||||
</p>
|
||||
<p>
|
||||
<TextField
|
||||
label={<i18n.Translate>Name</i18n.Translate>}
|
||||
placeholder="provider name"
|
||||
value={name.value}
|
||||
error={name.error}
|
||||
onChange={name.onInput}
|
||||
/>
|
||||
</p>
|
||||
</section>
|
||||
<footer>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="secondary"
|
||||
onClick={onCancel.onClick}
|
||||
>
|
||||
<i18n.Translate>Cancel</i18n.Translate>
|
||||
</Button>
|
||||
<Button variant="contained" color="primary" onClick={onConfirm.onClick}>
|
||||
<i18n.Translate>Next</i18n.Translate>
|
||||
</Button>
|
||||
</footer>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
@ -65,6 +65,7 @@ import { InvoiceCreatePage } from "../cta/InvoiceCreate/index.js";
|
||||
import { TransferPickupPage } from "../cta/TransferPickup/index.js";
|
||||
import { InvoicePayPage } from "../cta/InvoicePay/index.js";
|
||||
import { RecoveryPage } from "../cta/Recovery/index.js";
|
||||
import { AddBackupProviderPage } from "./AddBackupProvider/index.js";
|
||||
|
||||
export function Application(): VNode {
|
||||
const [globalNotification, setGlobalNotification] = useState<
|
||||
@ -221,7 +222,13 @@ export function Application(): VNode {
|
||||
/>
|
||||
<Route
|
||||
path={Pages.backupProviderAdd}
|
||||
component={ProviderAddPage}
|
||||
component={AddBackupProviderPage}
|
||||
onPaymentRequired={(uri: string) =>
|
||||
redirectTo(`${Pages.ctaPay}?talerPayUri=${uri}`)
|
||||
}
|
||||
onComplete={(pid: string) =>
|
||||
redirectTo(Pages.backupProviderDetail({ pid }))
|
||||
}
|
||||
onBack={() => redirectTo(Pages.backup)}
|
||||
/>
|
||||
|
||||
|
@ -20,7 +20,7 @@ import { HookError } from "../../hooks/useAsyncAsHook.js";
|
||||
import {
|
||||
ButtonHandler,
|
||||
SelectFieldHandler,
|
||||
TextFieldHandler
|
||||
TextFieldHandler,
|
||||
} from "../../mui/handlers.js";
|
||||
import { compose, StateViewMap } from "../../utils/index.js";
|
||||
import { wxApi } from "../../wxApi.js";
|
||||
@ -31,7 +31,7 @@ import {
|
||||
LoadingErrorView,
|
||||
NoAccountToDepositView,
|
||||
NoEnoughBalanceView,
|
||||
ReadyView
|
||||
ReadyView,
|
||||
} from "./views.js";
|
||||
|
||||
export interface Props {
|
||||
|
@ -21,7 +21,7 @@ import {
|
||||
KnownBankAccountsInfo,
|
||||
parsePaytoUri,
|
||||
PaytoUri,
|
||||
stringifyPaytoUri
|
||||
stringifyPaytoUri,
|
||||
} from "@gnu-taler/taler-util";
|
||||
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
|
||||
import { useState } from "preact/hooks";
|
||||
@ -37,10 +37,16 @@ export function useComponentState(
|
||||
const currency = parsed !== undefined ? parsed.currency : currencyStr;
|
||||
|
||||
const hook = useAsyncAsHook(async () => {
|
||||
const { balances } = await api.wallet.call(WalletApiOperation.GetBalances, {});
|
||||
const { accounts } = await api.wallet.call(WalletApiOperation.ListKnownBankAccounts, {
|
||||
currency
|
||||
});
|
||||
const { balances } = await api.wallet.call(
|
||||
WalletApiOperation.GetBalances,
|
||||
{},
|
||||
);
|
||||
const { accounts } = await api.wallet.call(
|
||||
WalletApiOperation.ListKnownBankAccounts,
|
||||
{
|
||||
currency,
|
||||
},
|
||||
);
|
||||
|
||||
return { accounts, balances };
|
||||
});
|
||||
@ -120,13 +126,13 @@ export function useComponentState(
|
||||
},
|
||||
};
|
||||
}
|
||||
const firstAccount = accounts[0].uri
|
||||
const firstAccount = accounts[0].uri;
|
||||
const currentAccount = !selectedAccount ? firstAccount : selectedAccount;
|
||||
|
||||
if (fee === undefined && parsedAmount) {
|
||||
getFeeForAmount(currentAccount, parsedAmount, api).then(initialFee => {
|
||||
setFee(initialFee)
|
||||
})
|
||||
getFeeForAmount(currentAccount, parsedAmount, api).then((initialFee) => {
|
||||
setFee(initialFee);
|
||||
});
|
||||
return {
|
||||
status: "loading",
|
||||
error: undefined,
|
||||
@ -177,10 +183,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 || //no amount specified
|
||||
@ -194,8 +200,9 @@ export function useComponentState(
|
||||
const depositPaytoUri = stringifyPaytoUri(currentAccount);
|
||||
const amount = Amounts.stringify(parsedAmount);
|
||||
await api.wallet.call(WalletApiOperation.CreateDepositGroup, {
|
||||
amount, depositPaytoUri
|
||||
})
|
||||
amount,
|
||||
depositPaytoUri,
|
||||
});
|
||||
onSuccess(currency);
|
||||
}
|
||||
|
||||
@ -242,8 +249,9 @@ async function getFeeForAmount(
|
||||
const depositPaytoUri = `payto://${p.targetType}/${p.targetPath}`;
|
||||
const amount = Amounts.stringify(a);
|
||||
return await api.wallet.call(WalletApiOperation.GetFeeForDeposit, {
|
||||
amount, depositPaytoUri
|
||||
})
|
||||
amount,
|
||||
depositPaytoUri,
|
||||
});
|
||||
}
|
||||
|
||||
export function labelForAccountType(id: string) {
|
||||
|
@ -18,7 +18,7 @@ import {
|
||||
DenomOperationMap,
|
||||
ExchangeFullDetails,
|
||||
ExchangeListItem,
|
||||
FeeDescriptionPair
|
||||
FeeDescriptionPair,
|
||||
} from "@gnu-taler/taler-util";
|
||||
import { Loading } from "../../components/Loading.js";
|
||||
import { HookError } from "../../hooks/useAsyncAsHook.js";
|
||||
@ -33,7 +33,7 @@ import {
|
||||
NoExchangesView,
|
||||
PrivacyContentView,
|
||||
ReadyView,
|
||||
TosContentView
|
||||
TosContentView,
|
||||
} from "./views.js";
|
||||
|
||||
export interface Props {
|
||||
|
@ -15,7 +15,10 @@
|
||||
*/
|
||||
|
||||
import { DenomOperationMap, FeeDescription } from "@gnu-taler/taler-util";
|
||||
import { createPairTimeline, WalletApiOperation } 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 { wxApi } from "../../wxApi.js";
|
||||
@ -41,15 +44,23 @@ export function useComponentState(
|
||||
exchanges.length == 0 ? undefined : exchanges[selectedIdx];
|
||||
const selected = !selectedExchange
|
||||
? undefined
|
||||
: await api.wallet.call(WalletApiOperation.GetExchangeDetailedInfo, { exchangeBaseUrl: selectedExchange.exchangeBaseUrl });
|
||||
: await api.wallet.call(WalletApiOperation.GetExchangeDetailedInfo, {
|
||||
exchangeBaseUrl: selectedExchange.exchangeBaseUrl,
|
||||
});
|
||||
|
||||
const initialExchange =
|
||||
selectedIdx === initialValue ? undefined : exchanges[initialValue];
|
||||
const original = !initialExchange
|
||||
? undefined
|
||||
: await api.wallet.call(WalletApiOperation.GetExchangeDetailedInfo, { exchangeBaseUrl: initialExchange.exchangeBaseUrl });
|
||||
: await api.wallet.call(WalletApiOperation.GetExchangeDetailedInfo, {
|
||||
exchangeBaseUrl: initialExchange.exchangeBaseUrl,
|
||||
});
|
||||
|
||||
return { exchanges, selected: selected?.exchange, original: original?.exchange };
|
||||
return {
|
||||
exchanges,
|
||||
selected: selected?.exchange,
|
||||
original: original?.exchange,
|
||||
};
|
||||
}, [value]);
|
||||
|
||||
const [showingTos, setShowingTos] = useState<string | undefined>(undefined);
|
||||
|
@ -161,11 +161,14 @@ export function NoExchangesView({
|
||||
title={<i18n.Translate>Could not find any exchange</i18n.Translate>}
|
||||
/>
|
||||
);
|
||||
|
||||
}
|
||||
return (
|
||||
<ErrorMessage
|
||||
title={<i18n.Translate>Could not find any exchange for the currency {currency}</i18n.Translate>}
|
||||
title={
|
||||
<i18n.Translate>
|
||||
Could not find any exchange for the currency {currency}
|
||||
</i18n.Translate>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -20,7 +20,7 @@ import { HookError } from "../../hooks/useAsyncAsHook.js";
|
||||
import {
|
||||
ButtonHandler,
|
||||
SelectFieldHandler,
|
||||
TextFieldHandler
|
||||
TextFieldHandler,
|
||||
} from "../../mui/handlers.js";
|
||||
import { compose, StateViewMap } from "../../utils/index.js";
|
||||
import { wxApi } from "../../wxApi.js";
|
||||
@ -58,13 +58,13 @@ export namespace State {
|
||||
alias: TextFieldHandler;
|
||||
onAccountAdded: ButtonHandler;
|
||||
onCancel: ButtonHandler;
|
||||
accountByType: AccountByType,
|
||||
deleteAccount: (a: KnownBankAccountsInfo) => Promise<void>,
|
||||
accountByType: AccountByType;
|
||||
deleteAccount: (a: KnownBankAccountsInfo) => Promise<void>;
|
||||
}
|
||||
}
|
||||
|
||||
export type AccountByType = {
|
||||
[key: string]: KnownBankAccountsInfo[]
|
||||
[key: string]: KnownBankAccountsInfo[];
|
||||
};
|
||||
|
||||
const viewMapping: StateViewMap<State> = {
|
||||
|
@ -14,7 +14,11 @@
|
||||
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
import { KnownBankAccountsInfo, parsePaytoUri, stringifyPaytoUri } from "@gnu-taler/taler-util";
|
||||
import {
|
||||
KnownBankAccountsInfo,
|
||||
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";
|
||||
@ -25,7 +29,9 @@ export function useComponentState(
|
||||
{ currency, onAccountAdded, onCancel }: Props,
|
||||
api: typeof wxApi,
|
||||
): State {
|
||||
const hook = useAsyncAsHook(() => api.wallet.call(WalletApiOperation.ListKnownBankAccounts, { currency }));
|
||||
const hook = useAsyncAsHook(() =>
|
||||
api.wallet.call(WalletApiOperation.ListKnownBankAccounts, { currency }),
|
||||
);
|
||||
|
||||
const [payto, setPayto] = useState("");
|
||||
const [alias, setAlias] = useState("");
|
||||
@ -61,34 +67,34 @@ export function useComponentState(
|
||||
|
||||
const normalizedPayto = stringifyPaytoUri(uri);
|
||||
await api.wallet.call(WalletApiOperation.AddKnownBankAccounts, {
|
||||
alias, currency, payto: normalizedPayto
|
||||
alias,
|
||||
currency,
|
||||
payto: normalizedPayto,
|
||||
});
|
||||
onAccountAdded(payto);
|
||||
}
|
||||
|
||||
const paytoUriError =
|
||||
found
|
||||
? "that account is already present"
|
||||
: undefined;
|
||||
const paytoUriError = found ? "that account is already present" : undefined;
|
||||
|
||||
const unableToAdd = !type || !alias || paytoUriError !== undefined || uri === undefined;
|
||||
const unableToAdd =
|
||||
!type || !alias || paytoUriError !== undefined || uri === undefined;
|
||||
|
||||
const accountByType: AccountByType = {
|
||||
iban: [],
|
||||
bitcoin: [],
|
||||
"x-taler-bank": [],
|
||||
}
|
||||
};
|
||||
|
||||
hook.response.accounts.forEach(acc => {
|
||||
accountByType[acc.uri.targetType].push(acc)
|
||||
hook.response.accounts.forEach((acc) => {
|
||||
accountByType[acc.uri.targetType].push(acc);
|
||||
});
|
||||
|
||||
async function deleteAccount(account: KnownBankAccountsInfo): Promise<void> {
|
||||
const payto = stringifyPaytoUri(account.uri);
|
||||
await api.wallet.call(WalletApiOperation.ForgetKnownBankAccounts, {
|
||||
payto
|
||||
})
|
||||
hook?.retry()
|
||||
payto,
|
||||
});
|
||||
hook?.retry();
|
||||
}
|
||||
|
||||
return {
|
||||
|
@ -23,7 +23,7 @@ import { createExample } from "../test-utils.js";
|
||||
import { QrReaderPage } from "./QrReader.js";
|
||||
|
||||
export default {
|
||||
title: "wallet/qr",
|
||||
title: "wallet/qr reader",
|
||||
};
|
||||
|
||||
export const Reading = createExample(QrReaderPage, {});
|
||||
|
@ -25,8 +25,7 @@ import * as a4 from "./DepositPage/stories.js";
|
||||
import * as a5 from "./ExchangeAddConfirm.stories.js";
|
||||
import * as a6 from "./ExchangeAddSetUrl.stories.js";
|
||||
import * as a7 from "./History.stories.js";
|
||||
import * as a8 from "./ProviderAddConfirmProvider.stories.js";
|
||||
import * as a9 from "./ProviderAddSetUrl.stories.js";
|
||||
import * as a8 from "./AddBackupProvider/stories.js";
|
||||
import * as a10 from "./ProviderDetail.stories.js";
|
||||
import * as a11 from "./ReserveCreated.stories.js";
|
||||
import * as a12 from "./Settings.stories.js";
|
||||
@ -47,7 +46,6 @@ export default [
|
||||
a6,
|
||||
a7,
|
||||
a8,
|
||||
a9,
|
||||
a10,
|
||||
a11,
|
||||
a12,
|
||||
|
@ -22,13 +22,17 @@
|
||||
* Imports.
|
||||
*/
|
||||
import {
|
||||
CoreApiResponse, Logger, NotificationType, WalletDiagnostics
|
||||
CoreApiResponse,
|
||||
Logger,
|
||||
NotificationType,
|
||||
WalletDiagnostics,
|
||||
} from "@gnu-taler/taler-util";
|
||||
import {
|
||||
TalerError, 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";
|
||||
@ -107,7 +111,6 @@ export class WxWalletCoreApiClient implements WalletCoreApiClient {
|
||||
}
|
||||
|
||||
export class BackgroundApiClient {
|
||||
|
||||
public resetDb(): Promise<void> {
|
||||
return callBackend("reset-db", {});
|
||||
}
|
||||
@ -129,16 +132,16 @@ export class BackgroundApiClient {
|
||||
public runGarbageCollector(): Promise<void> {
|
||||
return callBackend("run-gc", {});
|
||||
}
|
||||
|
||||
}
|
||||
function onUpdateNotification(
|
||||
messageTypes: Array<NotificationType>,
|
||||
doCallback: undefined | (() => void),
|
||||
): () => void {
|
||||
//if no callback, then ignore
|
||||
if (!doCallback) return () => {
|
||||
return
|
||||
};
|
||||
if (!doCallback)
|
||||
return () => {
|
||||
return;
|
||||
};
|
||||
const onNewMessage = (message: MessageFromBackend): void => {
|
||||
const shouldNotify = messageTypes.includes(message.type);
|
||||
if (shouldNotify) {
|
||||
@ -152,7 +155,6 @@ export const wxApi = {
|
||||
wallet: new WxWalletCoreApiClient(),
|
||||
background: new BackgroundApiClient(),
|
||||
listener: {
|
||||
onUpdateNotification
|
||||
}
|
||||
}
|
||||
|
||||
onUpdateNotification,
|
||||
},
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user