taler-typescript-core

Wallet core logic and WebUIs for various components
Log | Files | Refs | Submodules | README | LICENSE

commit 0c4bcac46aab100d8b43510bcdd8f1746bd485c6
parent acdf884eae79284eb5fb88ff076b90f645e64733
Author: Sebastian <sebasjm@taler-systems.com>
Date:   Mon,  1 Dec 2025 12:23:13 -0300

fix #10679

Diffstat:
Mpackages/taler-util/src/types-taler-wallet.ts | 11+++++++++++
Mpackages/taler-wallet-core/src/balance.ts | 22++++++++++++++++++++++
Mpackages/taler-wallet-webextension/src/wallet/DestinationSelection/index.ts | 1+
Mpackages/taler-wallet-webextension/src/wallet/DestinationSelection/state.ts | 42++++++++++++++++++++++++++++++++++++------
Mpackages/taler-wallet-webextension/src/wallet/DestinationSelection/views.tsx | 48+++++++++++++++++++++++++++---------------------
5 files changed, 97 insertions(+), 27 deletions(-)

diff --git a/packages/taler-util/src/types-taler-wallet.ts b/packages/taler-util/src/types-taler-wallet.ts @@ -577,6 +577,17 @@ export interface BalancesResponse { donauSummary?: DonauSummaryItem[]; } +export interface BalancesScopeRequest { + /** Electronic cash balances, per currency scope. */ + balances: WalletBalance[]; + /* Summary of donations, per donau/year/currency. */ + donauSummary?: DonauSummaryItem[]; +} +export interface BalancesScopeResponse { + /** Electronic cash balance. */ + balance: WalletBalance; +} + export const codecForBalance = (): Codec<WalletBalance> => buildCodecForObject<WalletBalance>() .property("scopeInfo", codecForAny()) // FIXME diff --git a/packages/taler-wallet-core/src/balance.ts b/packages/taler-wallet-core/src/balance.ts @@ -58,6 +58,7 @@ import { assertUnreachable, BalanceFlag, BalancesResponse, + BalancesScopeRequest, checkDbInvariant, DonauSummaryItem, GetBalanceDetailRequest, @@ -738,6 +739,27 @@ export async function getBalances( return wbal; } +/** + * Get detailed balance information of one exchange and currency. + */ +export async function getBalanceOfScope( + wex: WalletExecutionContext, + req: BalancesScopeRequest, +): Promise<BalancesResponse> { + logger.trace("starting to compute balance"); + + const wbal = await wex.db.runAllStoresReadOnlyTx({}, async (tx) => { + return getBalancesInsideTransaction(wex, tx); + }); + + wbal.balances.find((w) =>{ + w.scopeInfo + }) + logger.trace("finished computing wallet balance"); + + return wbal; +} + export interface PaymentRestrictionsForBalance { currency: string; diff --git a/packages/taler-wallet-webextension/src/wallet/DestinationSelection/index.ts b/packages/taler-wallet-webextension/src/wallet/DestinationSelection/index.ts @@ -76,6 +76,7 @@ export namespace State { previous: WalletBankAccountInfo[]; goToBank: ButtonHandler; goToWallet: ButtonHandler; + disablePeerPayments: boolean; } } diff --git a/packages/taler-wallet-webextension/src/wallet/DestinationSelection/state.ts b/packages/taler-wallet-webextension/src/wallet/DestinationSelection/state.ts @@ -21,7 +21,9 @@ import { ScopeType, parseScopeInfoShort, stringifyScopeInfoShort, - PaytoString + PaytoString, + ScopeInfo, + WalletBalance, } from "@gnu-taler/taler-util"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { useTranslationContext } from "@gnu-taler/web-util/browser"; @@ -35,19 +37,26 @@ import { Props, State } from "./index.js"; export function useComponentState(props: Props): RecursiveState<State> { const api = useBackendContext(); const { pushAlertOnError } = useAlertContext(); - const { i18n } = useTranslationContext(); const [scope, setScope] = useState(props.scope); const hook = useAsyncAsHook(async () => { - const resp = await api.wallet.call( + const { accounts } = await api.wallet.call( WalletApiOperation.ListBankAccounts, {}, ); - return resp + const { balances } = await api.wallet.call( + WalletApiOperation.GetBalances, + {}, + ); + + return { accounts, balances }; }); - const previous: WalletBankAccountInfo[] = props.type === "send" && hook && !hook.hasError ? hook.response.accounts : []; + const previous: WalletBankAccountInfo[] = + props.type === "send" && hook && !hook.hasError + ? hook.response.accounts + : []; if (!scope) { return () => { @@ -120,6 +129,11 @@ export function useComponentState(props: Props): RecursiveState<State> { }; } + const disablePeerPayments: boolean = + (hook && !hook.hasError + ? getBalanceForScope(hook.response.balances, scope)?.disablePeerPayments + : undefined) ?? false; + switch (props.type) { case "send": return { @@ -139,6 +153,7 @@ export function useComponentState(props: Props): RecursiveState<State> { props.goToWalletWalletSend(scope); }), }, + disablePeerPayments, type: props.type, }; case "get": @@ -151,15 +166,30 @@ export function useComponentState(props: Props): RecursiveState<State> { props.goToWalletManualWithdraw(scope); }), }, - onSelectAccount: () => { }, + onSelectAccount: () => {}, goToWallet: { onClick: pushAlertOnError(async () => { props.goToWalletWalletInvoice(scope); }), }, + disablePeerPayments, type: props.type, }; default: assertUnreachable(props); } } + +function getBalanceForScope( + balances: WalletBalance[], + scope: ScopeInfo, +): WalletBalance | undefined { + return balances.find((b) => { + const bs = b.scopeInfo; + if (bs.type !== scope.type) return false; + if (bs.currency !== scope.currency) return false; + if (scope.type === ScopeType.Global || bs.type === ScopeType.Global) return false; + if (scope.url !== bs.url) return false; + return true; + }); +} diff --git a/packages/taler-wallet-webextension/src/wallet/DestinationSelection/views.tsx b/packages/taler-wallet-webextension/src/wallet/DestinationSelection/views.tsx @@ -18,7 +18,7 @@ import { WalletBankAccountInfo, parsePaytoUri, stringifyPaytoUri, - assertUnreachable + assertUnreachable, } from "@gnu-taler/taler-util"; import { useTranslationContext } from "@gnu-taler/web-util/browser"; import { styled } from "@linaria/react"; @@ -197,6 +197,7 @@ export function ReadyGetView({ goToBank, goToWallet, previous, + disablePeerPayments, }: State.Ready): VNode { const { i18n } = useTranslationContext(); @@ -241,16 +242,18 @@ export function ReadyGetView({ </Button> </Paper> </Grid> - <Grid item xs={1}> - <Paper style={{ padding: 8 }}> - <p> - <i18n.Translate>From another wallet</i18n.Translate> - </p> - <Button onClick={goToWallet.onClick}> - <i18n.Translate>Invoice</i18n.Translate> - </Button> - </Paper> - </Grid> + {disablePeerPayments ? undefined : ( + <Grid item xs={1}> + <Paper style={{ padding: 8 }}> + <p> + <i18n.Translate>From another wallet</i18n.Translate> + </p> + <Button onClick={goToWallet.onClick}> + <i18n.Translate>Invoice</i18n.Translate> + </Button> + </Paper> + </Grid> + )} <Grid item xs={1}> <Paper style={{ padding: 8 }}> <p> @@ -274,6 +277,7 @@ export function ReadySendView({ onSelectAccount, goToWallet, previous, + disablePeerPayments }: State.Ready): VNode { const { i18n } = useTranslationContext(); @@ -321,16 +325,18 @@ export function ReadySendView({ </Button> </Paper> </Grid> - <Grid item xs={1}> - <Paper style={{ padding: 8 }}> - <p> - <i18n.Translate>To another wallet</i18n.Translate> - </p> - <Button onClick={goToWallet.onClick}> - <i18n.Translate>Send</i18n.Translate> - </Button> - </Paper> - </Grid> + {disablePeerPayments ? undefined : ( + <Grid item xs={1}> + <Paper style={{ padding: 8 }}> + <p> + <i18n.Translate>To another wallet</i18n.Translate> + </p> + <Button onClick={goToWallet.onClick}> + <i18n.Translate>Send</i18n.Translate> + </Button> + </Paper> + </Grid> + )} <Grid item xs={1}> <Paper style={{ padding: 8 }}> <p>