commit a2b7bf60c4ce56422eb908b1a5574696d58da0dd parent 6b29d873fcd1dcecff5f2957ed6542f8fa5f1089 Author: Florian Dold <florian@dold.me> Date: Fri, 22 Nov 2024 20:17:55 +0100 wallet-core: fix types for known bank accounts The payto URI is always represented as a string in the wallet-core API. Diffstat:
15 files changed, 202 insertions(+), 156 deletions(-)
diff --git a/packages/taler-util/src/types-taler-wallet.ts b/packages/taler-util/src/types-taler-wallet.ts @@ -57,7 +57,7 @@ import { WithdrawalOperationStatus, canonicalizeBaseUrl, } from "./index.js"; -import { PaytoString, PaytoUri, codecForPaytoString } from "./payto.js"; +import { PaytoString, codecForPaytoString } from "./payto.js"; import { QrCodeSpec } from "./qr.js"; import { AgeCommitmentProof } from "./taler-crypto.js"; import { TalerErrorCode } from "./taler-error-codes.js"; @@ -1129,13 +1129,13 @@ export interface WalletCoreVersion { } export interface KnownBankAccountsInfo { - uri: PaytoUri; + paytoUri: string; kycCompleted: boolean; currency: string; alias: string; } -export interface KnownBankAccounts { +export interface ListKnownBankAccountsResponse { accounts: KnownBankAccountsInfo[]; } @@ -1996,26 +1996,26 @@ export const codecForListKnownBankAccounts = .build("ListKnownBankAccountsRequest"); export interface AddKnownBankAccountsRequest { - payto: string; + paytoUri: string; alias: string; currency: string; } export const codecForAddKnownBankAccounts = (): Codec<AddKnownBankAccountsRequest> => buildCodecForObject<AddKnownBankAccountsRequest>() - .property("payto", codecForString()) + .property("paytoUri", codecForString()) .property("alias", codecForString()) .property("currency", codecForString()) .build("AddKnownBankAccountsRequest"); export interface ForgetKnownBankAccountsRequest { - payto: string; + paytoUri: string; } export const codecForForgetKnownBankAccounts = (): Codec<ForgetKnownBankAccountsRequest> => buildCodecForObject<ForgetKnownBankAccountsRequest>() - .property("payto", codecForString()) + .property("paytoUri", codecForString()) .build("ForgetKnownBankAccountsRequest"); export interface GetContractTermsDetailsRequest { diff --git a/packages/taler-wallet-core/src/wallet-api-types.ts b/packages/taler-wallet-core/src/wallet-api-types.ts @@ -105,7 +105,7 @@ import { InitiatePeerPushDebitResponse, IntegrationTestArgs, IntegrationTestV2Args, - KnownBankAccounts, + ListKnownBankAccountsResponse, ListAssociatedRefreshesRequest, ListAssociatedRefreshesResponse, ListExchangesRequest, @@ -703,7 +703,7 @@ export type UpdateExchangeEntryOp = { export type ListKnownBankAccountsOp = { op: WalletApiOperation.ListKnownBankAccounts; request: ListKnownBankAccountsRequest; - response: KnownBankAccounts; + response: ListKnownBankAccountsResponse; }; export type AddKnownBankAccountsOp = { diff --git a/packages/taler-wallet-core/src/wallet.ts b/packages/taler-wallet-core/src/wallet.ts @@ -78,11 +78,11 @@ import { InitResponse, IntegrationTestArgs, IntegrationTestV2Args, - KnownBankAccounts, KnownBankAccountsInfo, ListGlobalCurrencyAuditorsResponse, ListGlobalCurrencyExchangesResponse, ListKnownBankAccountsRequest, + ListKnownBankAccountsResponse, Logger, NotificationType, ObservabilityContext, @@ -505,7 +505,7 @@ export async function getDenomInfo( async function handleListKnownBankAccounts( wex: WalletExecutionContext, req: ListKnownBankAccountsRequest, -): Promise<KnownBankAccounts> { +): Promise<ListKnownBankAccountsResponse> { const accounts: KnownBankAccountsInfo[] = []; const currency = req.currency; await wex.db.runReadOnlyTx({ storeNames: ["bankAccounts"] }, async (tx) => { @@ -517,7 +517,7 @@ async function handleListKnownBankAccounts( const payto = parsePaytoUri(r.uri); if (payto) { accounts.push({ - uri: payto, + paytoUri: r.uri, alias: r.alias, kycCompleted: r.kycCompleted, currency: r.currency, @@ -965,7 +965,7 @@ async function handleAddKnownBankAccount( wex: WalletExecutionContext, req: AddKnownBankAccountsRequest, ): Promise<EmptyObject> { - await addKnownBankAccounts(wex, req.payto, req.alias, req.currency); + await addKnownBankAccounts(wex, req.paytoUri, req.alias, req.currency); return {}; } @@ -973,7 +973,7 @@ async function handleForgetKnownBankAccounts( wex: WalletExecutionContext, req: ForgetKnownBankAccountsRequest, ): Promise<EmptyObject> { - await forgetKnownBankAccounts(wex, req.payto); + await forgetKnownBankAccounts(wex, req.paytoUri); return {}; } diff --git a/packages/taler-wallet-webextension/src/wallet/Application.tsx b/packages/taler-wallet-webextension/src/wallet/Application.tsx @@ -303,7 +303,7 @@ export function Application(): VNode { Pages.ctaDeposit({ scope: encodeCrockForURI(stringifyScopeInfoShort(s)), account: encodeCrockForURI( - stringifyPaytoUri(account), + account, ), }), ) diff --git a/packages/taler-wallet-webextension/src/wallet/DepositPage/index.ts b/packages/taler-wallet-webextension/src/wallet/DepositPage/index.ts @@ -14,7 +14,7 @@ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ -import { AmountJson, PaytoUri, ScopeInfo } from "@gnu-taler/taler-util"; +import { AmountJson, ScopeInfo } from "@gnu-taler/taler-util"; import { ErrorAlertView } from "../../components/CurrentAlerts.js"; import { Loading } from "../../components/Loading.js"; import { ErrorAlert } from "../../context/alert.js"; @@ -34,7 +34,7 @@ import { } from "./views.js"; export interface Props { - scope:ScopeInfo; + scope: ScopeInfo; onCancel: (scope: ScopeInfo) => void; onSuccess: (scope: ScopeInfo) => void; } @@ -63,7 +63,7 @@ export namespace State { status: "manage-account"; error: undefined; scope: ScopeInfo; - onAccountAdded: (p: PaytoUri) => void; + onAccountAdded: (paytoUri: string) => void; onCancel: () => void; } @@ -92,7 +92,7 @@ export namespace State { error: undefined; currency: string; - currentAccount: PaytoUri; + currentAccount: string; totalFee: AmountJson; amount: AmountFieldHandler; diff --git a/packages/taler-wallet-webextension/src/wallet/DepositPage/state.ts b/packages/taler-wallet-webextension/src/wallet/DepositPage/state.ts @@ -17,10 +17,7 @@ import { Amounts, KnownBankAccountsInfo, - parsePaytoUri, - PaytoUri, - stringifyPaytoUri, - TransactionAmountMode + TransactionAmountMode, } from "@gnu-taler/taler-util"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { useTranslationContext } from "@gnu-taler/web-util/browser"; @@ -45,8 +42,7 @@ export function useComponentState({ const hook = useAsyncAsHook(async () => { const { balances } = await api.wallet.call( WalletApiOperation.GetBalances, - { - }, + {}, ); const { accounts } = await api.wallet.call( @@ -57,7 +53,7 @@ export function useComponentState({ return { accounts, balances }; }); - const [selectedAccount, setSelectedAccount] = useState<PaytoUri>(); + const [selectedAccount, setSelectedAccount] = useState<string>(); const [addingAccount, setAddingAccount] = useState(false); @@ -80,7 +76,7 @@ export function useComponentState({ const { accounts, balances } = hook.response; async function updateAccountFromList(accountStr: string): Promise<void> { - const uri = !accountStr ? undefined : parsePaytoUri(accountStr); + const uri = accountStr ?? undefined; if (uri) { setSelectedAccount(uri); } @@ -91,8 +87,8 @@ export function useComponentState({ status: "manage-account", error: undefined, scope, - onAccountAdded: (p: PaytoUri) => { - updateAccountFromList(stringifyPaytoUri(p)); + onAccountAdded: (p: string) => { + updateAccountFromList(p); setAddingAccount(false); hook.retry(); }, @@ -129,14 +125,15 @@ export function useComponentState({ }, }; } - const firstAccount = accounts[0].uri; + const firstAccount = accounts[0].paytoUri; const currentAccount = !selectedAccount ? firstAccount : selectedAccount; return (): State => { - const [instructed, setInstructed] = useState( - { amount: zero, type: TransactionAmountMode.Raw }, - ); + const [instructed, setInstructed] = useState({ + amount: zero, + type: TransactionAmountMode.Raw, + }); const amountStr = Amounts.stringify(instructed.amount); - const depositPaytoUri = stringifyPaytoUri(currentAccount); + const depositPaytoUri = currentAccount; const hook = useAsyncAsHook(async () => { const fee = await api.wallet.call( @@ -195,7 +192,7 @@ export function useComponentState({ async function doSend(): Promise<void> { // if (!currency) return; - const depositPaytoUri = stringifyPaytoUri(currentAccount); + const depositPaytoUri = currentAccount; const amountStr = Amounts.stringify(totalEffective); await api.wallet.call(WalletApiOperation.CreateDepositGroup, { amount: amountStr, @@ -210,18 +207,22 @@ export function useComponentState({ currency: scope.currency, amount: { value: totalEffective, - onInput: pushAlertOnError(async (a) => setInstructed({ - amount: a, - type: TransactionAmountMode.Effective, - })), + onInput: pushAlertOnError(async (a) => + setInstructed({ + amount: a, + type: TransactionAmountMode.Effective, + }), + ), error: amountError, }, totalToDeposit: { value: totalToDeposit, - onInput: pushAlertOnError(async (a) => setInstructed({ - amount: a, - type: TransactionAmountMode.Raw, - })), + onInput: pushAlertOnError(async (a) => + setInstructed({ + amount: a, + type: TransactionAmountMode.Raw, + }), + ), error: amountError, }, onAddAccount: { @@ -231,7 +232,7 @@ export function useComponentState({ }, account: { list: accountMap, - value: stringifyPaytoUri(currentAccount), + value: currentAccount, onChange: pushAlertOnError(updateAccountFromList), }, currentAccount, @@ -269,7 +270,7 @@ export function createLabelsForBankAccount( const initialList: Record<string, string> = {}; if (!knownBankAccounts.length) return initialList; return knownBankAccounts.reduce((prev, cur) => { - prev[stringifyPaytoUri(cur.uri)] = cur.alias; + prev[cur.paytoUri] = cur.alias; return prev; }, initialList); } diff --git a/packages/taler-wallet-webextension/src/wallet/DepositPage/stories.tsx b/packages/taler-wallet-webextension/src/wallet/DepositPage/stories.tsx @@ -19,7 +19,7 @@ * @author Sebastian Javier Marchano (sebasjm) */ -import { Amounts } from "@gnu-taler/taler-util"; +import { Amounts, stringifyPaytoUri } from "@gnu-taler/taler-util"; import * as tests from "@gnu-taler/web-util/testing"; import { nullFunction } from "../../mui/handlers.js"; import { ReadyView } from "./views.js"; @@ -35,13 +35,13 @@ export const WithNoAccountForIBAN = tests.createExample(ReadyView, { value: "", onChange: nullFunction, }, - currentAccount: { + currentAccount: stringifyPaytoUri({ isKnown: true, targetType: "iban", iban: "ABCD1234", params: {}, targetPath: "/ABCD1234", - }, + }), currency: "USD", amount: { onInput: nullFunction, @@ -54,7 +54,7 @@ export const WithNoAccountForIBAN = tests.createExample(ReadyView, { }, totalFee: Amounts.zeroOfCurrency("USD"), totalToDeposit: { - onInput:nullFunction, + onInput: nullFunction, value: Amounts.parseOrThrow("USD:10"), }, // onCalculateFee: alwaysReturnFeeToOne, @@ -67,13 +67,13 @@ export const WithIBANAccountTypeSelected = tests.createExample(ReadyView, { value: "asdlkajsdlk", onChange: nullFunction, }, - currentAccount: { + currentAccount: stringifyPaytoUri({ isKnown: true, targetType: "iban", iban: "ABCD1234", params: {}, targetPath: "/ABCD1234", - }, + }), currency: "USD", amount: { onInput: nullFunction, @@ -86,7 +86,7 @@ export const WithIBANAccountTypeSelected = tests.createExample(ReadyView, { }, totalFee: Amounts.zeroOfCurrency("USD"), totalToDeposit: { - onInput:nullFunction, + onInput: nullFunction, value: Amounts.parseOrThrow("USD:10"), }, // onCalculateFee: alwaysReturnFeeToOne, @@ -99,13 +99,13 @@ export const NewBitcoinAccountTypeSelected = tests.createExample(ReadyView, { value: "asdlkajsdlk", onChange: nullFunction, }, - currentAccount: { + currentAccount: stringifyPaytoUri({ isKnown: true, targetType: "iban", iban: "ABCD1234", params: {}, targetPath: "/ABCD1234", - }, + }), onAddAccount: {}, currency: "USD", amount: { @@ -118,7 +118,7 @@ export const NewBitcoinAccountTypeSelected = tests.createExample(ReadyView, { }, totalFee: Amounts.zeroOfCurrency("USD"), totalToDeposit: { - onInput:nullFunction, + onInput: nullFunction, value: Amounts.parseOrThrow("USD:10"), }, // onCalculateFee: alwaysReturnFeeToOne, diff --git a/packages/taler-wallet-webextension/src/wallet/DepositPage/test.ts b/packages/taler-wallet-webextension/src/wallet/DepositPage/test.ts @@ -23,10 +23,8 @@ import { AmountResponse, Amounts, AmountString, - parsePaytoUri, ScopeInfo, ScopeType, - stringifyPaytoUri } from "@gnu-taler/taler-util"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import * as tests from "@gnu-taler/web-util/testing"; @@ -45,9 +43,8 @@ const withoutFee = (value: number): AmountResponse => ({ const defaultScope: ScopeInfo = { type: ScopeType.Global, - currency -} - + currency, +}; const withSomeFee = (value: number, fee: number): AmountResponse => ({ effectiveAmount: `${currency}:${value}` as AmountString, @@ -56,7 +53,12 @@ const withSomeFee = (value: number, fee: number): AmountResponse => ({ describe("DepositPage states", () => { it("should have status 'no-enough-balance' when balance is empty", async () => { const { handler, TestingContext } = createWalletApiMock(); - const props = { scope: defaultScope, amount, onCancel: nullFunction, onSuccess: nullFunction }; + const props = { + scope: defaultScope, + amount, + onCancel: nullFunction, + onSuccess: nullFunction, + }; handler.addWalletCallResponse(WalletApiOperation.GetBalances, undefined, { balances: [ @@ -99,7 +101,11 @@ describe("DepositPage states", () => { it("should have status 'no-accounts' when balance is not empty and accounts is empty", async () => { const { handler, TestingContext } = createWalletApiMock(); - const props = { scope: defaultScope, onCancel: nullFunction, onSuccess: nullFunction }; + const props = { + scope: defaultScope, + onCancel: nullFunction, + onSuccess: nullFunction, + }; handler.addWalletCallResponse(WalletApiOperation.GetBalances, undefined, { balances: [ @@ -141,13 +147,13 @@ describe("DepositPage states", () => { }); const ibanPayto = { - uri: parsePaytoUri("payto://iban/ES8877998399652238")!, + paytoUri: "payto://iban/ES8877998399652238", kycCompleted: false, currency: "EUR", alias: "my iban account", }; const talerBankPayto = { - uri: parsePaytoUri("payto://x-taler-bank/ES8877998399652238")!, + paytoUri: "payto://x-taler-bank/ES8877998399652238", kycCompleted: false, currency: "EUR", alias: "my taler account", @@ -155,7 +161,11 @@ describe("DepositPage states", () => { it("should have status 'ready' but unable to deposit ", async () => { const { handler, TestingContext } = createWalletApiMock(); - const props = { scope: defaultScope, onCancel: nullFunction, onSuccess: nullFunction }; + const props = { + scope: defaultScope, + onCancel: nullFunction, + onSuccess: nullFunction, + }; handler.addWalletCallResponse(WalletApiOperation.GetBalances, undefined, { balances: [ @@ -197,7 +207,7 @@ describe("DepositPage states", () => { if (state.status !== "ready") expect.fail(); expect(state.cancelHandler.onClick).not.undefined; expect(state.currency).eq(currency); - expect(state.account.value).eq(stringifyPaytoUri(ibanPayto.uri)); + expect(state.account.value).eq(ibanPayto.paytoUri); expect(state.amount.value).deep.eq(Amounts.parseOrThrow("EUR:0")); expect(state.depositHandler.onClick).undefined; }, @@ -211,7 +221,11 @@ describe("DepositPage states", () => { it("should not be able to deposit more than the balance ", async () => { const { handler, TestingContext } = createWalletApiMock(); - const props = { scope: defaultScope, onCancel: nullFunction, onSuccess: nullFunction }; + const props = { + scope: defaultScope, + onCancel: nullFunction, + onSuccess: nullFunction, + }; handler.addWalletCallResponse(WalletApiOperation.GetBalances, undefined, { balances: [ @@ -245,7 +259,7 @@ describe("DepositPage states", () => { withoutFee(0), ); - const accountSelected = stringifyPaytoUri(ibanPayto.uri); + const accountSelected = ibanPayto.paytoUri; const hookBehavior = await tests.hookBehaveLikeThis( useComponentState, @@ -261,7 +275,7 @@ describe("DepositPage states", () => { if (state.status !== "ready") expect.fail(); expect(state.cancelHandler.onClick).not.undefined; expect(state.currency).eq(currency); - expect(state.account.value).eq(stringifyPaytoUri(talerBankPayto.uri)); + expect(state.account.value).eq(talerBankPayto.paytoUri); expect(state.amount.value).deep.eq(Amounts.parseOrThrow("EUR:0")); expect(state.depositHandler.onClick).undefined; expect(state.totalFee).deep.eq(Amounts.parseOrThrow(`${currency}:0`)); @@ -297,7 +311,11 @@ describe("DepositPage states", () => { it("should calculate the fee upon entering amount ", async () => { const { handler, TestingContext } = createWalletApiMock(); - const props = { scope: defaultScope, onCancel: nullFunction, onSuccess: nullFunction }; + const props = { + scope: defaultScope, + onCancel: nullFunction, + onSuccess: nullFunction, + }; handler.addWalletCallResponse(WalletApiOperation.GetBalances, undefined, { balances: [ @@ -327,15 +345,15 @@ describe("DepositPage states", () => { handler.addWalletCallResponse( WalletApiOperation.ConvertDepositAmount, undefined, - withSomeFee(10,3), + withSomeFee(10, 3), ); handler.addWalletCallResponse( WalletApiOperation.ConvertDepositAmount, undefined, - withSomeFee(10,3), + withSomeFee(10, 3), ); - const accountSelected = stringifyPaytoUri(ibanPayto.uri); + const accountSelected = ibanPayto.paytoUri; const hookBehavior = await tests.hookBehaveLikeThis( useComponentState, @@ -351,7 +369,7 @@ describe("DepositPage states", () => { if (state.status !== "ready") expect.fail(); expect(state.cancelHandler.onClick).not.undefined; expect(state.currency).eq(currency); - expect(state.account.value).eq(stringifyPaytoUri(talerBankPayto.uri)); + expect(state.account.value).eq(talerBankPayto.paytoUri); expect(state.amount.value).deep.eq(Amounts.parseOrThrow("EUR:0")); expect(state.depositHandler.onClick).undefined; expect(state.totalFee).deep.eq(Amounts.parseOrThrow(`${currency}:0`)); diff --git a/packages/taler-wallet-webextension/src/wallet/DepositPage/views.tsx b/packages/taler-wallet-webextension/src/wallet/DepositPage/views.tsx @@ -14,13 +14,13 @@ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ -import { Amounts, PaytoUri } from "@gnu-taler/taler-util"; +import { Amounts, parsePaytoUri } from "@gnu-taler/taler-util"; +import { useTranslationContext } from "@gnu-taler/web-util/browser"; import { Fragment, h, VNode } from "preact"; import { AmountField } from "../../components/AmountField.js"; import { ErrorMessage } from "../../components/ErrorMessage.js"; import { SelectList } from "../../components/SelectList.js"; import { Input, SubTitle, WarningBox } from "../../components/styled/index.js"; -import { useTranslationContext } from "@gnu-taler/web-util/browser"; import { Button } from "../../mui/Button.js"; import { Grid } from "../../mui/Grid.js"; import { State } from "./index.js"; @@ -49,31 +49,40 @@ export function NoEnoughBalanceView({ ); } -function AccountDetails({ account }: { account: PaytoUri }): VNode { - if (account.isKnown) { - if (account.targetType === "bitcoin") { +function AccountDetails({ account }: { account: string }): VNode { + const p = parsePaytoUri(account); + if (!p) { + return ( + <dl> + <dt>(Invalid)</dt> + <dd>Invalid payto URI</dd> + </dl> + ); + } + if (p.isKnown) { + if (p.targetType === "bitcoin") { return ( <dl> <dt>Bitcoin</dt> - <dd>{account.targetPath}</dd> + <dd>{p.targetPath}</dd> </dl> ); } - if (account.targetType === "x-taler-bank") { + if (p.targetType === "x-taler-bank") { return ( <dl> <dt>Bank host</dt> - <dd>{account.targetPath.split("/")[0]}</dd> + <dd>{p.targetPath.split("/")[0]}</dd> <dt>Account name</dt> - <dd>{account.targetPath.split("/")[1]}</dd> + <dd>{p.targetPath.split("/")[1]}</dd> </dl> ); } - if (account.targetType === "iban") { + if (p.targetType === "iban") { return ( <dl> <dt>IBAN</dt> - <dd>{account.targetPath}</dd> + <dd>{p.targetPath}</dd> </dl> ); } @@ -156,7 +165,10 @@ export function ReadyView(state: State.Ready): VNode { /> </Grid> <Grid item xs={1}> - <AmountField label={i18n.str`Net amount`} handler={state.totalToDeposit} /> + <AmountField + label={i18n.str`Net amount`} + handler={state.totalToDeposit} + /> </Grid> </Grid> </section> diff --git a/packages/taler-wallet-webextension/src/wallet/DestinationSelection/index.ts b/packages/taler-wallet-webextension/src/wallet/DestinationSelection/index.ts @@ -14,14 +14,15 @@ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ -import { AmountJson, KnownBankAccountsInfo, PaytoUri, ScopeInfo } from "@gnu-taler/taler-util"; +import { + KnownBankAccountsInfo, + PaytoUri, + ScopeInfo, +} from "@gnu-taler/taler-util"; import { ErrorAlertView } from "../../components/CurrentAlerts.js"; import { Loading } from "../../components/Loading.js"; import { ErrorAlert } from "../../context/alert.js"; -import { - AmountFieldHandler, - ButtonHandler -} from "../../mui/handlers.js"; +import { ButtonHandler } from "../../mui/handlers.js"; import { StateViewMap, compose } from "../../utils/index.js"; import { useComponentState } from "./state.js"; import { ReadyView, SelectCurrencyView } from "./views.js"; @@ -31,15 +32,15 @@ export type Props = PropsGet | PropsSend; interface PropsGet { type: "get"; scope?: ScopeInfo; - goToWalletManualWithdraw: (s:ScopeInfo) => void; - goToWalletWalletInvoice: (s:ScopeInfo) => void; + goToWalletManualWithdraw: (s: ScopeInfo) => void; + goToWalletWalletInvoice: (s: ScopeInfo) => void; } interface PropsSend { type: "send"; scope: ScopeInfo; - goToWalletKnownBankDeposit: (s:ScopeInfo, p: PaytoUri) => void; - goToWalletNewBankDeposit: (s:ScopeInfo) => void; - goToWalletWalletSend: (s:ScopeInfo) => void; + goToWalletKnownBankDeposit: (s: ScopeInfo, p: PaytoUri) => void; + goToWalletNewBankDeposit: (s: ScopeInfo) => void; + goToWalletWalletSend: (s: ScopeInfo) => void; } export type State = @@ -70,14 +71,13 @@ export namespace State { status: "ready"; error: undefined; type: Props["type"]; - onSelectAccount: (p:PaytoUri) => void; + onSelectAccount: (p: string) => void; previous: KnownBankAccountsInfo[]; goToBank: ButtonHandler; goToWallet: ButtonHandler; } } - const viewMapping: StateViewMap<State> = { loading: Loading, error: ErrorAlertView, diff --git a/packages/taler-wallet-webextension/src/wallet/DestinationSelection/views.tsx b/packages/taler-wallet-webextension/src/wallet/DestinationSelection/views.tsx @@ -16,7 +16,7 @@ import { KnownBankAccountsInfo, - PaytoUri, + parsePaytoUri, stringifyPaytoUri, } from "@gnu-taler/taler-util"; import { useTranslationContext } from "@gnu-taler/web-util/browser"; @@ -298,7 +298,7 @@ export function ReadySendView({ <RowExample info={info} onClick={() => { - onSelectAccount(info.uri); + onSelectAccount(info.paytoUri); }} // disabled={!amountHandler.onInput} /> @@ -374,7 +374,7 @@ function RowExample({ </MediaLeft> <MediaBody> <span>{info.alias}</span> - <LightText>{describeAccount(info.uri)}</LightText> + <LightText>{describeAccount(info.paytoUri)}</LightText> </MediaBody> <MediaRight> <SvgIcon @@ -388,7 +388,11 @@ function RowExample({ ); } -function describeAccount(p: PaytoUri): string { +function describeAccount(paytoUri: string): string { + const p = parsePaytoUri(paytoUri); + if (!p) { + return `(invalid)`; + } if (!p.isKnown) { return stringifyPaytoUri(p); } diff --git a/packages/taler-wallet-webextension/src/wallet/ManageAccount/index.ts b/packages/taler-wallet-webextension/src/wallet/ManageAccount/index.ts @@ -14,7 +14,7 @@ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ -import { KnownBankAccountsInfo, PaytoUri, ScopeInfo } from "@gnu-taler/taler-util"; +import { KnownBankAccountsInfo, ScopeInfo } from "@gnu-taler/taler-util"; import { ErrorAlertView } from "../../components/CurrentAlerts.js"; import { Loading } from "../../components/Loading.js"; import { ErrorAlert } from "../../context/alert.js"; @@ -29,7 +29,7 @@ import { ReadyView } from "./views.js"; export interface Props { scope: ScopeInfo; - onAccountAdded: (uri: PaytoUri) => void; + onAccountAdded: (uri: string) => void; onCancel: () => void; } diff --git a/packages/taler-wallet-webextension/src/wallet/ManageAccount/state.ts b/packages/taler-wallet-webextension/src/wallet/ManageAccount/state.ts @@ -20,10 +20,10 @@ import { stringifyPaytoUri, } from "@gnu-taler/taler-util"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; +import { useTranslationContext } from "@gnu-taler/web-util/browser"; import { useState } from "preact/hooks"; import { alertFromError, useAlertContext } from "../../context/alert.js"; import { useBackendContext } from "../../context/backend.js"; -import { useTranslationContext } from "@gnu-taler/web-util/browser"; import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js"; import { AccountByType, Props, State } from "./index.js"; @@ -37,11 +37,16 @@ export function useComponentState({ const { i18n } = useTranslationContext(); const hook = useAsyncAsHook(() => - api.wallet.call(WalletApiOperation.ListKnownBankAccounts, { currency: scope.currency }), + api.wallet.call(WalletApiOperation.ListKnownBankAccounts, { + currency: scope.currency, + }), ); const hook2 = useAsyncAsHook(() => - api.wallet.call(WalletApiOperation.GetDepositWireTypesForCurrency, { currency: scope.currency, scopeInfo: scope }), + api.wallet.call(WalletApiOperation.GetDepositWireTypesForCurrency, { + currency: scope.currency, + scopeInfo: scope, + }), ); if (!hook || !hook2) { @@ -57,7 +62,8 @@ export function useComponentState({ error: alertFromError( i18n, i18n.str`Could not load known bank accounts`, - hook), + hook, + ), }; } @@ -67,7 +73,8 @@ export function useComponentState({ error: alertFromError( i18n, i18n.str`Could not load supported wire methods`, - hook2), + hook2, + ), }; } @@ -89,21 +96,19 @@ export function useComponentState({ const [type, setType] = useState(hook2.response.wireTypes[0]); const accountType: Record<string, string> = {}; - hook2.response.wireTypes.forEach(t => { + hook2.response.wireTypes.forEach((t) => { if (t === "iban") { - accountType[t] = "IBAN" + accountType[t] = "IBAN"; } else if (t === "x-taler-bank") { - accountType[t] = "x-taler-bank" + accountType[t] = "x-taler-bank"; } else if (t === "bitcoin") { - accountType[t] = "Bitcoin" + accountType[t] = "Bitcoin"; } }); const uri = parsePaytoUri(payto); const found = - hook.response.accounts.findIndex( - (a) => stringifyPaytoUri(a.uri) === payto, - ) !== -1; + hook.response.accounts.findIndex((a) => a.paytoUri === payto) !== -1; async function addAccount(): Promise<void> { if (!uri || found) return; @@ -112,9 +117,9 @@ export function useComponentState({ await api.wallet.call(WalletApiOperation.AddKnownBankAccounts, { alias, currency: scope.currency, - payto: normalizedPayto, + paytoUri: normalizedPayto, }); - onAccountAdded(uri); + onAccountAdded(normalizedPayto); } const paytoUriError = found ? "that account is already present" : undefined; @@ -129,13 +134,14 @@ export function useComponentState({ }; hook.response.accounts.forEach((acc) => { - accountByType[acc.uri.targetType].push(acc); + const p = parsePaytoUri(acc.paytoUri)!; + accountByType[p.targetType].push(acc); }); async function deleteAccount(account: KnownBankAccountsInfo): Promise<void> { - const payto = stringifyPaytoUri(account.uri); + const payto = account.paytoUri; await api.wallet.call(WalletApiOperation.ForgetKnownBankAccounts, { - payto, + paytoUri: payto, }); hook?.retry(); } @@ -143,7 +149,7 @@ export function useComponentState({ return { status: "ready", error: undefined, - currency:scope.currency, + currency: scope.currency, accountType: { list: accountType, value: type, diff --git a/packages/taler-wallet-webextension/src/wallet/ManageAccount/stories.tsx b/packages/taler-wallet-webextension/src/wallet/ManageAccount/stories.tsx @@ -20,6 +20,7 @@ */ import * as tests from "@gnu-taler/web-util/testing"; +import { stringifyPaytoUri } from "../../../../taler-util/src/payto.js"; import { nullFunction } from "../../mui/handlers.js"; import { ReadyView } from "./views.js"; @@ -54,27 +55,27 @@ export const JustTwoBitcoinAccounts = tests.createExample(ReadyView, { alias: "my bitcoin addr", currency: "BTC", kycCompleted: false, - uri: { + paytoUri: stringifyPaytoUri({ targetType: "bitcoin", segwitAddrs: [], isKnown: true, address: "bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh", targetPath: "bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh", params: {}, - }, + }), }, { alias: "my other addr", currency: "BTC", kycCompleted: true, - uri: { + paytoUri: stringifyPaytoUri({ targetType: "bitcoin", segwitAddrs: [], address: "bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh", isKnown: true, targetPath: "bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh", params: {}, - }, + }), }, ], }, @@ -107,13 +108,13 @@ export const WithAllTypeOfAccounts = tests.createExample(ReadyView, { alias: "my bank", currency: "ARS", kycCompleted: true, - uri: { + paytoUri: stringifyPaytoUri({ targetType: "iban", iban: "ASDQWEQWE", isKnown: true, targetPath: "/ASDQWEQWE", params: {}, - }, + }), }, ], "x-taler-bank": [ @@ -121,14 +122,14 @@ export const WithAllTypeOfAccounts = tests.createExample(ReadyView, { alias: "my xtaler bank", currency: "ARS", kycCompleted: true, - uri: { + paytoUri: stringifyPaytoUri({ targetType: "x-taler-bank", host: "localhost", account: "123", isKnown: true, targetPath: "localhost/123", params: {}, - }, + }), }, ], bitcoin: [ @@ -136,27 +137,27 @@ export const WithAllTypeOfAccounts = tests.createExample(ReadyView, { alias: "my bitcoin addr", currency: "BTC", kycCompleted: false, - uri: { + paytoUri: stringifyPaytoUri({ targetType: "bitcoin", segwitAddrs: [], isKnown: true, address: "bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh", targetPath: "bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh", params: {}, - }, + }), }, { alias: "my other addr", currency: "BTC", kycCompleted: true, - uri: { + paytoUri: stringifyPaytoUri({ targetType: "bitcoin", segwitAddrs: [], isKnown: true, address: "bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh", targetPath: "bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh", params: {}, - }, + }), }, ], }, @@ -189,14 +190,14 @@ export const AddingIbanAccount = tests.createExample(ReadyView, { alias: "my bank", currency: "ARS", kycCompleted: true, - uri: { + paytoUri: stringifyPaytoUri({ targetType: "iban", iban: "ASDQWEQWE", bic: "SANDBOX", isKnown: true, targetPath: "SANDBOX/ASDQWEQWE", params: {}, - }, + }), }, ], "x-taler-bank": [], diff --git a/packages/taler-wallet-webextension/src/wallet/ManageAccount/views.tsx b/packages/taler-wallet-webextension/src/wallet/ManageAccount/views.tsx @@ -17,6 +17,7 @@ import { buildPayto, KnownBankAccountsInfo, + parsePaytoUri, PaytoUriBitcoin, PaytoUriIBAN, PaytoUriTalerBank, @@ -212,7 +213,7 @@ function IbanTable({ </thead> <tbody> {list.map((account) => { - const p = account.uri as PaytoUriIBAN; + const p = parsePaytoUri(account.paytoUri) as PaytoUriIBAN; return ( <tr key={account.alias}> <td>{account.alias}</td> @@ -288,7 +289,7 @@ function TalerBankTable({ </thead> <tbody> {list.map((account) => { - const p = account.uri as PaytoUriTalerBank; + const p = parsePaytoUri(account.paytoUri) as PaytoUriTalerBank; return ( <tr key={account.alias}> <td>{account.alias}</td> @@ -360,7 +361,7 @@ function BitcoinTable({ </thead> <tbody> {list.map((account) => { - const p = account.uri as PaytoUriBitcoin; + const p = parsePaytoUri(account.paytoUri) as PaytoUriBitcoin; return ( <tr key={account.alias}> <td>{account.alias}</td> @@ -431,7 +432,9 @@ function BitcoinAddressAccount({ field }: { field: TextFieldHandler }): VNode { } function undefinedIfEmpty<T extends object>(obj: T): T | undefined { - return Object.keys(obj).some((k) => (obj as Record<string,unknown>)[k] !== undefined) + return Object.keys(obj).some( + (k) => (obj as Record<string, unknown>)[k] !== undefined, + ) ? obj : undefined; } @@ -496,21 +499,22 @@ function IbanAddressAccount({ field }: { field: TextFieldHandler }): VNode { // const [bic, setBic] = useState<string | undefined>(undefined); const [iban, setIban] = useState<string | undefined>(undefined); const [name, setName] = useState<string | undefined>(undefined); - const bic = "" - const errorsFN = (iban:string | undefined, name: string | undefined) => undefinedIfEmpty({ - // bic: !bic - // ? undefined - // : !bicRegex.test(bic) - // ? i18n.str`Invalid bic` - // : undefined, - iban: !iban - ? i18n.str`Can't be empty` - : validateIban(iban).type === "invalid" - ? i18n.str`Invalid iban` - : undefined, - name: !name ? i18n.str`Can't be empty` : undefined, - }); - const errors = errorsFN(iban, name) + const bic = ""; + const errorsFN = (iban: string | undefined, name: string | undefined) => + undefinedIfEmpty({ + // bic: !bic + // ? undefined + // : !bicRegex.test(bic) + // ? i18n.str`Invalid bic` + // : undefined, + iban: !iban + ? i18n.str`Can't be empty` + : validateIban(iban).type === "invalid" + ? i18n.str`Invalid iban` + : undefined, + name: !name ? i18n.str`Can't be empty` : undefined, + }); + const errors = errorsFN(iban, name); function sendUpdateIfNoErrors( bic: string | undefined, @@ -523,7 +527,7 @@ function IbanAddressAccount({ field }: { field: TextFieldHandler }): VNode { p.params["receiver-name"] = name; field.onInput(stringifyPaytoUri(p)); } else { - field.onInput("") + field.onInput(""); } } return (