From 7ea8321ddd2d56f43dceaa18340f1d1c39a83e76 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Fri, 20 Jan 2023 15:41:08 -0300 Subject: introducing getBalanceDetail for getting all depositable/transferable amount for a currency --- packages/taler-util/src/wallet-types.ts | 21 ++++++--- .../taler-wallet-core/src/operations/balance.ts | 50 ++++++++++++++++++---- .../taler-wallet-core/src/operations/deposits.ts | 1 - packages/taler-wallet-core/src/wallet-api-types.ts | 21 ++++++--- packages/taler-wallet-core/src/wallet.ts | 21 ++++++--- .../src/wallet/DestinationSelection/index.ts | 1 + .../src/wallet/DestinationSelection/state.ts | 20 +++++++++ .../src/wallet/DestinationSelection/stories.tsx | 2 + .../src/wallet/DestinationSelection/test.ts | 10 +++++ .../src/wallet/DestinationSelection/views.tsx | 11 ++++- .../src/wallet/History.tsx | 4 +- packages/taler-wallet-webextension/src/wxApi.ts | 4 +- .../taler-wallet-webextension/src/wxBackend.ts | 14 ++++-- 13 files changed, 146 insertions(+), 34 deletions(-) diff --git a/packages/taler-util/src/wallet-types.ts b/packages/taler-util/src/wallet-types.ts index fc383acb0..50cb50f30 100644 --- a/packages/taler-util/src/wallet-types.ts +++ b/packages/taler-util/src/wallet-types.ts @@ -105,6 +105,16 @@ export class CreateReserveResponse { reservePub: string; } +export interface GetBalanceDetailRequest { + currency: string; +} + +export const codecForGetBalanceDetailRequest = (): Codec => + buildCodecForObject() + .property("currency", codecForString()) + .build("GetBalanceDetailRequest"); + + export interface Balance { available: AmountString; pendingIncoming: AmountString; @@ -215,11 +225,11 @@ export interface CoinDumpJson { withdrawal_reserve_pub: string | undefined; coin_status: CoinStatus; spend_allocation: - | { - id: string; - amount: string; - } - | undefined; + | { + id: string; + amount: string; + } + | undefined; /** * Information about the age restriction */ @@ -1792,6 +1802,7 @@ export const codecForUserAttentionsRequest = (): Codec => ) .build("UserAttentionsRequest"); + export interface UserAttentionsRequest { priority?: AttentionPriority; } diff --git a/packages/taler-wallet-core/src/operations/balance.ts b/packages/taler-wallet-core/src/operations/balance.ts index 27b801804..50613d0aa 100644 --- a/packages/taler-wallet-core/src/operations/balance.ts +++ b/packages/taler-wallet-core/src/operations/balance.ts @@ -48,14 +48,12 @@ */ import { AmountJson, - BalancesResponse, Amounts, - Logger, - AuditorHandle, - ExchangeHandle, + BalancesResponse, canonicalizeBaseUrl, + GetBalanceDetailRequest, + Logger, parsePaytoUri, - TalerErrorCode, } from "@gnu-taler/taler-util"; import { AllowedAuditorInfo, @@ -63,11 +61,10 @@ import { RefreshGroupRecord, WalletStoresV1, } from "../db.js"; -import { GetReadOnlyAccess } from "../util/query.js"; import { InternalWalletState } from "../internal-wallet-state.js"; -import { getExchangeDetails } from "./exchanges.js"; import { checkLogicInvariant } from "../util/invariants.js"; -import { TalerError } from "../errors.js"; +import { GetReadOnlyAccess } from "../util/query.js"; +import { getExchangeDetails } from "./exchanges.js"; /** * Logger. @@ -429,6 +426,43 @@ export async function getMerchantPaymentBalanceDetails( return d; } +export async function getBalanceDetail( + ws: InternalWalletState, + req: GetBalanceDetailRequest, +): Promise { + const exchanges: { exchangeBaseUrl: string; exchangePub: string }[] = []; + const wires = new Array(); + await ws.db + .mktx((x) => [x.exchanges, x.exchangeDetails]) + .runReadOnly(async (tx) => { + const allExchanges = await tx.exchanges.iter().toArray(); + for (const e of allExchanges) { + const details = await getExchangeDetails(tx, e.baseUrl); + if (!details || req.currency !== details.currency) { + continue; + } + details.wireInfo.accounts.forEach((a) => { + const payto = parsePaytoUri(a.payto_uri); + if (payto && !wires.includes(payto.targetType)) { + wires.push(payto.targetType); + } + }); + exchanges.push({ + exchangePub: details.masterPublicKey, + exchangeBaseUrl: e.baseUrl, + }); + } + }); + + return await getMerchantPaymentBalanceDetails(ws, { + currency: req.currency, + acceptedAuditors: [], + acceptedExchanges: exchanges, + acceptedWireMethods: wires, + minAge: 0, + }); +} + export interface PeerPaymentRestrictionsForBalance { currency: string; restrictExchangeTo?: string; diff --git a/packages/taler-wallet-core/src/operations/deposits.ts b/packages/taler-wallet-core/src/operations/deposits.ts index f3ec81686..e97738b55 100644 --- a/packages/taler-wallet-core/src/operations/deposits.ts +++ b/packages/taler-wallet-core/src/operations/deposits.ts @@ -457,7 +457,6 @@ export async function prepareDepositGroup( }; } - export async function createDepositGroup( ws: InternalWalletState, req: CreateDepositGroupRequest, diff --git a/packages/taler-wallet-core/src/wallet-api-types.ts b/packages/taler-wallet-core/src/wallet-api-types.ts index 88ae3a5c1..f14018401 100644 --- a/packages/taler-wallet-core/src/wallet-api-types.ts +++ b/packages/taler-wallet-core/src/wallet-api-types.ts @@ -24,7 +24,7 @@ * Imports. */ import { - AbortTransactionRequest as AbortTransactionRequest, + AbortTransactionRequest, AcceptBankIntegratedWithdrawalRequest, AcceptExchangeTosRequest, AcceptManualWithdrawalRequest, @@ -56,6 +56,7 @@ import { ExchangesListResponse, ForceRefreshRequest, ForgetKnownBankAccountsRequest, + GetBalanceDetailRequest, GetContractTermsDetailsRequest, GetExchangeTosRequest, GetExchangeTosResult, @@ -66,14 +67,12 @@ import { InitiatePeerPullPaymentResponse, InitiatePeerPushPaymentRequest, InitiatePeerPushPaymentResponse, + InitRequest, InitResponse, IntegrationTestArgs, KnownBankAccounts, ListKnownBankAccountsRequest, ManualWithdrawalDetails, - UserAttentionsCountResponse, - UserAttentionsRequest, - UserAttentionsResponse, PrepareDepositRequest, PrepareDepositResponse, PreparePayRequest, @@ -99,14 +98,16 @@ import { TransactionByIdRequest, TransactionsRequest, TransactionsResponse, + UserAttentionByIdRequest, + UserAttentionsCountResponse, + UserAttentionsRequest, + UserAttentionsResponse, WalletBackupContentV1, WalletCoreVersion, WalletCurrencyInfo, WithdrawFakebankRequest, WithdrawTestBalanceRequest, WithdrawUriInfoResponse, - UserAttentionByIdRequest, - InitRequest, } from "@gnu-taler/taler-util"; import { WalletContractData } from "./db.js"; import { @@ -116,6 +117,7 @@ import { RemoveBackupProviderRequest, RunBackupCycleRequest, } from "./operations/backup/index.js"; +import { MerchantPaymentBalanceDetails } from "./operations/balance.js"; import { PendingOperationsResponse as PendingTasksResponse } from "./pending-types.js"; export enum WalletApiOperation { @@ -138,6 +140,7 @@ export enum WalletApiOperation { GetWithdrawalDetailsForAmount = "getWithdrawalDetailsForAmount", AcceptManualWithdrawal = "acceptManualWithdrawal", GetBalances = "getBalances", + GetBalanceDetail = "getBalanceDetail", GetUserAttentionRequests = "getUserAttentionRequests", GetUserAttentionUnreadCount = "getUserAttentionUnreadCount", MarkAttentionRequestAsRead = "markAttentionRequestAsRead", @@ -221,6 +224,11 @@ export type GetBalancesOp = { request: EmptyObject; response: BalancesResponse; }; +export type GetBalancesDetailOp = { + op: WalletApiOperation.GetBalanceDetail; + request: GetBalanceDetailRequest; + response: MerchantPaymentBalanceDetails; +}; // group: Managing Transactions @@ -831,6 +839,7 @@ export type WalletOperations = { [WalletApiOperation.ConfirmPay]: ConfirmPayOp; [WalletApiOperation.AbortTransaction]: AbortTransactionOp; [WalletApiOperation.GetBalances]: GetBalancesOp; + [WalletApiOperation.GetBalanceDetail]: GetBalancesDetailOp; [WalletApiOperation.GetTransactions]: GetTransactionsOp; [WalletApiOperation.GetTransactionById]: GetTransactionByIdOp; [WalletApiOperation.RetryPendingNow]: RetryPendingNowOp; diff --git a/packages/taler-wallet-core/src/wallet.ts b/packages/taler-wallet-core/src/wallet.ts index 24c7f7b9e..f1ed592bd 100644 --- a/packages/taler-wallet-core/src/wallet.ts +++ b/packages/taler-wallet-core/src/wallet.ts @@ -45,6 +45,7 @@ import { codecForDeleteTransactionRequest, codecForForceRefreshRequest, codecForForgetKnownBankAccounts, + codecForGetBalanceDetailRequest, codecForGetContractTermsDetails, codecForGetExchangeTosRequest, codecForGetFeeForDeposit, @@ -87,6 +88,7 @@ import { ExchangesListResponse, ExchangeTosStatusDetails, FeeDescription, + GetBalanceDetailRequest, GetExchangeTosResult, InitResponse, j2s, @@ -154,7 +156,11 @@ import { runBackupCycle, } from "./operations/backup/index.js"; import { setWalletDeviceId } from "./operations/backup/state.js"; -import { getBalances } from "./operations/balance.js"; +import { + getBalanceDetail, + getBalances, + getMerchantPaymentBalanceDetails, +} from "./operations/balance.js"; import { getExchangeTosStatus, makeExchangeListItem, @@ -948,9 +954,9 @@ async function dumpCoins(ws: InternalWalletState): Promise { ageCommitmentProof: c.ageCommitmentProof, spend_allocation: c.spendAllocation ? { - amount: c.spendAllocation.amount, - id: c.spendAllocation.id, - } + amount: c.spendAllocation.amount, + id: c.spendAllocation.id, + } : undefined, }); } @@ -1111,6 +1117,10 @@ async function dispatchRequestInternal( case WalletApiOperation.GetBalances: { return await getBalances(ws); } + case WalletApiOperation.GetBalanceDetail: { + const req = codecForGetBalanceDetailRequest().decode(payload); + return await getBalanceDetail(ws, req); + } case WalletApiOperation.GetUserAttentionRequests: { const req = codecForUserAttentionsRequest().decode(payload); return await getUserAttentions(ws, req); @@ -1350,7 +1360,8 @@ async function dispatchRequestInternal( { amount: Amounts.stringify(amount), reserve_pub: wres.reservePub, - debit_account: "payto://x-taler-bank/localhost/testdebtor?receiver-name=Foo", + debit_account: + "payto://x-taler-bank/localhost/testdebtor?receiver-name=Foo", }, ); const fbResp = await readSuccessResponseJsonOrThrow(fbReq, codecForAny()); diff --git a/packages/taler-wallet-webextension/src/wallet/DestinationSelection/index.ts b/packages/taler-wallet-webextension/src/wallet/DestinationSelection/index.ts index bd6b32e78..9a0ba1d88 100644 --- a/packages/taler-wallet-webextension/src/wallet/DestinationSelection/index.ts +++ b/packages/taler-wallet-webextension/src/wallet/DestinationSelection/index.ts @@ -66,6 +66,7 @@ export namespace State { error: undefined; type: Props["type"]; selectCurrency: ButtonHandler; + sendAll: ButtonHandler; previous: Contact[]; goToBank: ButtonHandler; goToWallet: ButtonHandler; diff --git a/packages/taler-wallet-webextension/src/wallet/DestinationSelection/state.ts b/packages/taler-wallet-webextension/src/wallet/DestinationSelection/state.ts index d5015ae1d..a921d32cb 100644 --- a/packages/taler-wallet-webextension/src/wallet/DestinationSelection/state.ts +++ b/packages/taler-wallet-webextension/src/wallet/DestinationSelection/state.ts @@ -27,10 +27,21 @@ import { Contact, Props, State } from "./index.js"; export function useComponentState(props: Props): RecursiveState { const api = useBackendContext(); const { pushAlertOnError } = useAlertContext(); + const parsedInitialAmount = !props.amount ? undefined : Amounts.parse(props.amount); + const hook = useAsyncAsHook(async () => { + if (!parsedInitialAmount) return undefined; + const resp = await api.wallet.call(WalletApiOperation.GetBalanceDetail, { + currency: parsedInitialAmount.currency, + }); + return resp; + }); + + const total = hook && !hook.hasError ? hook.response : undefined; + // const initialCurrency = parsedInitialAmount?.currency; const [amount, setAmount] = useState( @@ -120,6 +131,14 @@ export function useComponentState(props: Props): RecursiveState { props.goToWalletBankDeposit(currencyAndAmount); }), }, + sendAll: { + onClick: + total === undefined + ? undefined + : pushAlertOnError(async () => { + setAmount(total.balanceMerchantDepositable); + }), + }, goToWallet: { onClick: invalid ? undefined @@ -143,6 +162,7 @@ export function useComponentState(props: Props): RecursiveState { setAmount(undefined); }), }, + sendAll: {}, goToBank: { onClick: invalid ? undefined diff --git a/packages/taler-wallet-webextension/src/wallet/DestinationSelection/stories.tsx b/packages/taler-wallet-webextension/src/wallet/DestinationSelection/stories.tsx index 111f47776..247affec6 100644 --- a/packages/taler-wallet-webextension/src/wallet/DestinationSelection/stories.tsx +++ b/packages/taler-wallet-webextension/src/wallet/DestinationSelection/stories.tsx @@ -35,6 +35,7 @@ export const GetCash = tests.createExample(ReadyView, { }, }, goToBank: {}, + sendAll: {}, goToWallet: {}, previous: [], selectCurrency: {}, @@ -49,6 +50,7 @@ export const SendCash = tests.createExample(ReadyView, { }, }, goToBank: {}, + sendAll: {}, goToWallet: {}, previous: [], selectCurrency: {}, diff --git a/packages/taler-wallet-webextension/src/wallet/DestinationSelection/test.ts b/packages/taler-wallet-webextension/src/wallet/DestinationSelection/test.ts index b079ef0e8..c6a57270b 100644 --- a/packages/taler-wallet-webextension/src/wallet/DestinationSelection/test.ts +++ b/packages/taler-wallet-webextension/src/wallet/DestinationSelection/test.ts @@ -118,6 +118,16 @@ describe("Destination selection states", () => { expect(state.goToBank.onClick).not.eq(undefined); expect(state.goToWallet.onClick).not.eq(undefined); + expect(state.amountHandler.value).deep.eq( + Amounts.parseOrThrow("ARS:2"), + ); + }, + (state) => { + if (state.status !== "ready") expect.fail(); + if (state.error) expect.fail(); + expect(state.goToBank.onClick).not.eq(undefined); + expect(state.goToWallet.onClick).not.eq(undefined); + expect(state.amountHandler.value).deep.eq( Amounts.parseOrThrow("ARS:2"), ); diff --git a/packages/taler-wallet-webextension/src/wallet/DestinationSelection/views.tsx b/packages/taler-wallet-webextension/src/wallet/DestinationSelection/views.tsx index 8a7a1fa97..0649fd12f 100644 --- a/packages/taler-wallet-webextension/src/wallet/DestinationSelection/views.tsx +++ b/packages/taler-wallet-webextension/src/wallet/DestinationSelection/views.tsx @@ -17,6 +17,7 @@ import { styled } from "@linaria/react"; import { Fragment, h, VNode } from "preact"; import { AmountField } from "../../components/AmountField.js"; +import { JustInDevMode } from "../../components/JustInDevMode.js"; import { SelectList } from "../../components/SelectList.js"; import { Input, @@ -283,6 +284,7 @@ export function ReadySendView({ goToBank, goToWallet, previous, + sendAll, }: State.Ready): VNode { const { i18n } = useTranslationContext(); @@ -292,13 +294,18 @@ export function ReadySendView({ Specify the amount and the destination -
+ -
+ + + + {previous.length > 0 ? ( diff --git a/packages/taler-wallet-webextension/src/wallet/History.tsx b/packages/taler-wallet-webextension/src/wallet/History.tsx index 1d51f835a..f2a239f83 100644 --- a/packages/taler-wallet-webextension/src/wallet/History.tsx +++ b/packages/taler-wallet-webextension/src/wallet/History.tsx @@ -111,8 +111,10 @@ export function HistoryView({ balances: Balance[]; }): VNode { const { i18n } = useTranslationContext(); - const currencies = balances.map((b) => b.available.split(":")[0]); const { pushAlertOnError } = useAlertContext(); + const currencies = balances + .filter((b) => Amounts.isNonZero(b.available)) + .map((b) => b.available.split(":")[0]); const defaultCurrencyIndex = currencies.findIndex( (c) => c === defaultCurrency, diff --git a/packages/taler-wallet-webextension/src/wxApi.ts b/packages/taler-wallet-webextension/src/wxApi.ts index 001f77934..c064d7111 100644 --- a/packages/taler-wallet-webextension/src/wxApi.ts +++ b/packages/taler-wallet-webextension/src/wxApi.ts @@ -88,8 +88,8 @@ export interface BackgroundOperations { }; setLoggingLevel: { request: { - tag?: string, - level: LogLevel + tag?: string; + level: LogLevel; }; response: void; }; diff --git a/packages/taler-wallet-webextension/src/wxBackend.ts b/packages/taler-wallet-webextension/src/wxBackend.ts index 1bfee1064..2055953e3 100644 --- a/packages/taler-wallet-webextension/src/wxBackend.ts +++ b/packages/taler-wallet-webextension/src/wxBackend.ts @@ -186,12 +186,18 @@ const backendHandlers: BackendHandlerType = { setLoggingLevel, }; -async function setLoggingLevel({ tag, level }: { tag?: string, level: LogLevel }): Promise { - logger.info(`setting ${tag} to ${level}`) +async function setLoggingLevel({ + tag, + level, +}: { + tag?: string; + level: LogLevel; +}): Promise { + logger.info(`setting ${tag} to ${level}`); if (!tag) { - setGlobalLogLevelFromString(level) + setGlobalLogLevelFromString(level); } else { - setLogLevelFromString(tag, level) + setLogLevelFromString(tag, level); } } -- cgit v1.2.3