gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[taler-wallet-core] branch master updated (81dda3b6b -> 03b12d2b2)


From: gnunet
Subject: [taler-wallet-core] branch master updated (81dda3b6b -> 03b12d2b2)
Date: Fri, 20 Jan 2023 19:45:14 +0100

This is an automated email from the git hooks/post-receive script.

sebasjm pushed a change to branch master
in repository wallet-core.

    from 81dda3b6b fix: effective amount is higher that raw amount in transfer
     new 7ea8321dd introducing getBalanceDetail for getting all 
depositable/transferable amount for a currency
     new 5f31dad2d also merge refund when getting transactionById
     new 03b12d2b2 fix wrong fee calculation

The 3 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 packages/taler-util/src/wallet-types.ts            |  21 +-
 .../taler-wallet-core/src/operations/balance.ts    |  50 ++-
 .../taler-wallet-core/src/operations/deposits.ts   |   1 -
 .../src/operations/transactions.ts                 |  33 +-
 packages/taler-wallet-core/src/wallet-api-types.ts |  21 +-
 packages/taler-wallet-core/src/wallet.ts           |  21 +-
 .../src/cta/InvoiceCreate/views.tsx                |  12 +-
 .../src/cta/Payment/views.tsx                      |  19 +-
 .../src/cta/TransferCreate/views.tsx               |   8 +-
 .../src/cta/Withdraw/views.tsx                     |  17 +-
 .../src/platform/chrome.ts                         |  34 +-
 .../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 +-
 .../src/wallet/Transaction.tsx                     | 453 ++++++++++-----------
 packages/taler-wallet-webextension/src/wxApi.ts    |   4 +-
 .../taler-wallet-webextension/src/wxBackend.ts     |  14 +-
 20 files changed, 432 insertions(+), 324 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<GetBalanceDetailRequest> =>
+  buildCodecForObject<GetBalanceDetailRequest>()
+    .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<UserAttentionsRequest> =>
     )
     .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<MerchantPaymentBalanceDetails> {
+  const exchanges: { exchangeBaseUrl: string; exchangePub: string }[] = [];
+  const wires = new Array<string>();
+  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/operations/transactions.ts 
b/packages/taler-wallet-core/src/operations/transactions.ts
index 083b6618a..9951fd6b2 100644
--- a/packages/taler-wallet-core/src/operations/transactions.ts
+++ b/packages/taler-wallet-core/src/operations/transactions.ts
@@ -286,11 +286,6 @@ export async function getTransactionById(
         const purchase = await tx.purchases.get(proposalId);
         if (!purchase) throw Error("not found");
 
-        const theRefund = Object.values(purchase.refunds).find(
-          (r) => `${r.executionTime.t_s}` === executionTimeStr,
-        );
-        if (!theRefund) throw Error("not found");
-
         const t = await tx.tombstones.get(
           makeTombstoneId(
             TombstoneTag.DeleteRefund,
@@ -299,17 +294,41 @@ export async function getTransactionById(
           ),
         );
         if (t) throw Error("deleted");
+
+        const filteredRefunds = await Promise.all(
+          Object.values(purchase.refunds).map(async (r) => {
+            const t = await tx.tombstones.get(
+              makeTombstoneId(
+                TombstoneTag.DeleteRefund,
+                purchase.proposalId,
+                `${r.executionTime.t_s}`,
+              ),
+            );
+            if (!t) return r;
+            return undefined;
+          }),
+        );
+
+        const cleanRefunds = filteredRefunds.filter(
+          (x): x is WalletRefundItem => !!x,
+        );
+
         const download = await expectProposalDownload(ws, purchase, tx);
         const contractData = download.contractData;
         const refunds = mergeRefundByExecutionTime(
-          [theRefund],
+          cleanRefunds,
           Amounts.zeroOfAmount(contractData.amount),
         );
 
+        const theRefund = refunds.find(
+          (r) => `${r.executionTime.t_s}` === executionTimeStr,
+        );
+        if (!theRefund) throw Error("not found");
+
         return buildTransactionForRefund(
           purchase,
           contractData,
-          refunds[0],
+          theRefund,
           undefined,
         );
       });
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<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,
         });
       }
@@ -1111,6 +1117,10 @@ async function dispatchRequestInternal<Op extends 
WalletApiOperation>(
     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<Op extends 
WalletApiOperation>(
         {
           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/cta/InvoiceCreate/views.tsx 
b/packages/taler-wallet-webextension/src/cta/InvoiceCreate/views.tsx
index 71227ace1..e96ee0705 100644
--- a/packages/taler-wallet-webextension/src/cta/InvoiceCreate/views.tsx
+++ b/packages/taler-wallet-webextension/src/cta/InvoiceCreate/views.tsx
@@ -14,6 +14,7 @@
  GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 
+import { Amounts } from "@gnu-taler/taler-util";
 import { format } from "date-fns";
 import { h, VNode } from "preact";
 import { LogoHeader } from "../../components/LogoHeader.js";
@@ -27,7 +28,11 @@ import { useTranslationContext } from 
"../../context/translation.js";
 import { Button } from "../../mui/Button.js";
 import { TextField } from "../../mui/TextField.js";
 import editIcon from "../../svg/edit_24px.svg";
-import { ExchangeDetails, InvoiceDetails } from "../../wallet/Transaction.js";
+import {
+  ExchangeDetails,
+  getAmountWithFee,
+  InvoiceDetails,
+} from "../../wallet/Transaction.js";
 import { State } from "./index.js";
 
 export function ReadyView({
@@ -144,10 +149,7 @@ export function ReadyView({
           title={i18n.str`Details`}
           text={
             <InvoiceDetails
-              amount={{
-                effective: toBeReceived,
-                raw: requestAmount,
-              }}
+              amount={getAmountWithFee(toBeReceived, requestAmount, "credit")}
             />
           }
         />
diff --git a/packages/taler-wallet-webextension/src/cta/Payment/views.tsx 
b/packages/taler-wallet-webextension/src/cta/Payment/views.tsx
index 244ac5886..53bc0c95f 100644
--- a/packages/taler-wallet-webextension/src/cta/Payment/views.tsx
+++ b/packages/taler-wallet-webextension/src/cta/Payment/views.tsx
@@ -27,7 +27,11 @@ import { PaymentButtons } from 
"../../components/PaymentButtons.js";
 import { SuccessBox, WarningBox } from "../../components/styled/index.js";
 import { Time } from "../../components/Time.js";
 import { useTranslationContext } from "../../context/translation.js";
-import { MerchantDetails, PurchaseDetails } from "../../wallet/Transaction.js";
+import {
+  getAmountWithFee,
+  MerchantDetails,
+  PurchaseDetails,
+} from "../../wallet/Transaction.js";
 import { State } from "./index.js";
 
 type SupportedStates =
@@ -41,13 +45,10 @@ export function BaseView(state: SupportedStates): VNode {
 
   const contractTerms: ContractTerms = state.payStatus.contractTerms;
 
-  const price = {
-    raw: state.amount,
-    effective:
-      "amountEffective" in state.payStatus
-        ? Amounts.parseOrThrow(state.payStatus.amountEffective)
-        : state.amount,
-  };
+  const effective =
+    "amountEffective" in state.payStatus
+      ? Amounts.parseOrThrow(state.payStatus.amountEffective)
+      : state.amount;
 
   return (
     <Fragment>
@@ -68,7 +69,7 @@ export function BaseView(state: SupportedStates): VNode {
           title={i18n.str`Details`}
           text={
             <PurchaseDetails
-              price={price}
+              price={getAmountWithFee(effective, state.amount, "debit")}
               info={{
                 ...contractTerms,
                 orderId: contractTerms.order_id,
diff --git 
a/packages/taler-wallet-webextension/src/cta/TransferCreate/views.tsx 
b/packages/taler-wallet-webextension/src/cta/TransferCreate/views.tsx
index 373af8f74..a28b13141 100644
--- a/packages/taler-wallet-webextension/src/cta/TransferCreate/views.tsx
+++ b/packages/taler-wallet-webextension/src/cta/TransferCreate/views.tsx
@@ -14,6 +14,7 @@
  GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 
+import { Amounts } from "@gnu-taler/taler-util";
 import { format } from "date-fns";
 import { h, VNode } from "preact";
 import { ErrorTalerOperation } from "../../components/ErrorTalerOperation.js";
@@ -23,7 +24,7 @@ import { Link, SubTitle, WalletAction } from 
"../../components/styled/index.js";
 import { useTranslationContext } from "../../context/translation.js";
 import { Button } from "../../mui/Button.js";
 import { TextField } from "../../mui/TextField.js";
-import { TransferDetails } from "../../wallet/Transaction.js";
+import { getAmountWithFee, TransferDetails } from 
"../../wallet/Transaction.js";
 import { State } from "./index.js";
 
 export function ReadyView({
@@ -114,10 +115,7 @@ export function ReadyView({
           title={i18n.str`Details`}
           text={
             <TransferDetails
-              amount={{
-                effective: toBeReceived,
-                raw: debitAmount,
-              }}
+              amount={getAmountWithFee(debitAmount, toBeReceived, "debit")}
             />
           }
         />
diff --git a/packages/taler-wallet-webextension/src/cta/Withdraw/views.tsx 
b/packages/taler-wallet-webextension/src/cta/Withdraw/views.tsx
index 1cc87547e..4fb65f06c 100644
--- a/packages/taler-wallet-webextension/src/cta/Withdraw/views.tsx
+++ b/packages/taler-wallet-webextension/src/cta/Withdraw/views.tsx
@@ -14,7 +14,7 @@
  GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 
-import { ExchangeTosStatus } from "@gnu-taler/taler-util";
+import { Amounts, ExchangeTosStatus } from "@gnu-taler/taler-util";
 import { Fragment, h, VNode } from "preact";
 import { useState } from "preact/hooks";
 import { Amount } from "../../components/Amount.js";
@@ -26,7 +26,11 @@ import { TermsOfService } from 
"../../components/TermsOfService/index.js";
 import { useTranslationContext } from "../../context/translation.js";
 import { Button } from "../../mui/Button.js";
 import editIcon from "../../svg/edit_24px.svg";
-import { ExchangeDetails, WithdrawDetails } from "../../wallet/Transaction.js";
+import {
+  ExchangeDetails,
+  getAmountWithFee,
+  WithdrawDetails,
+} from "../../wallet/Transaction.js";
 import { State } from "./index.js";
 
 export function SuccessView(state: State.Success): VNode {
@@ -64,10 +68,11 @@ export function SuccessView(state: State.Success): VNode {
           title={i18n.str`Details`}
           text={
             <WithdrawDetails
-              amount={{
-                effective: state.toBeReceived,
-                raw: state.chosenAmount,
-              }}
+              amount={getAmountWithFee(
+                state.toBeReceived,
+                state.chosenAmount,
+                "credit",
+              )}
             />
           }
         />
diff --git a/packages/taler-wallet-webextension/src/platform/chrome.ts 
b/packages/taler-wallet-webextension/src/platform/chrome.ts
index 23730c2d3..1c5d5532e 100644
--- a/packages/taler-wallet-webextension/src/platform/chrome.ts
+++ b/packages/taler-wallet-webextension/src/platform/chrome.ts
@@ -584,26 +584,26 @@ function setAlertedIcon(): void {
 
 interface OffscreenCanvasRenderingContext2D
   extends CanvasState,
-  CanvasTransform,
-  CanvasCompositing,
-  CanvasImageSmoothing,
-  CanvasFillStrokeStyles,
-  CanvasShadowStyles,
-  CanvasFilters,
-  CanvasRect,
-  CanvasDrawPath,
-  CanvasUserInterface,
-  CanvasText,
-  CanvasDrawImage,
-  CanvasImageData,
-  CanvasPathDrawingStyles,
-  CanvasTextDrawingStyles,
-  CanvasPath {
+    CanvasTransform,
+    CanvasCompositing,
+    CanvasImageSmoothing,
+    CanvasFillStrokeStyles,
+    CanvasShadowStyles,
+    CanvasFilters,
+    CanvasRect,
+    CanvasDrawPath,
+    CanvasUserInterface,
+    CanvasText,
+    CanvasDrawImage,
+    CanvasImageData,
+    CanvasPathDrawingStyles,
+    CanvasTextDrawingStyles,
+    CanvasPath {
   readonly canvas: OffscreenCanvas;
 }
 declare const OffscreenCanvasRenderingContext2D: {
   prototype: OffscreenCanvasRenderingContext2D;
-  new(): OffscreenCanvasRenderingContext2D;
+  new (): OffscreenCanvasRenderingContext2D;
 };
 
 interface OffscreenCanvas extends EventTarget {
@@ -616,7 +616,7 @@ interface OffscreenCanvas extends EventTarget {
 }
 declare const OffscreenCanvas: {
   prototype: OffscreenCanvas;
-  new(width: number, height: number): OffscreenCanvas;
+  new (width: number, height: number): OffscreenCanvas;
 };
 
 function createCanvas(size: number): OffscreenCanvas {
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<State> {
   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<State> {
                 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<State> {
             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({
         <i18n.Translate>Specify the amount and the destination</i18n.Translate>
       </h1>
 
-      <div>
+      <Grid container columns={2} justifyContent="space-between">
         <AmountField
           label={i18n.str`Amount`}
           required
           handler={amountHandler}
         />
-      </div>
+        <JustInDevMode>
+          <Button onClick={sendAll.onClick}>
+            <i18n.Translate>Send all</i18n.Translate>
+          </Button>
+        </JustInDevMode>
+      </Grid>
 
       <Grid container spacing={1} columns={1}>
         {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/wallet/Transaction.tsx 
b/packages/taler-wallet-webextension/src/wallet/Transaction.tsx
index c9e7bbe85..94d853d9a 100644
--- a/packages/taler-wallet-webextension/src/wallet/Transaction.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/Transaction.tsx
@@ -28,17 +28,13 @@ import {
   stringifyPaytoUri,
   TalerProtocolTimestamp,
   Transaction,
-  TransactionDeposit,
-  TransactionRefresh,
-  TransactionRefund,
-  TransactionTip,
   TransactionType,
   TranslatedString,
   WithdrawalType,
 } from "@gnu-taler/taler-util";
 import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
 import { styled } from "@linaria/react";
-import { differenceInSeconds, isAfter, isFuture, isPast } from "date-fns";
+import { differenceInSeconds, isPast } from "date-fns";
 import { ComponentChildren, Fragment, h, VNode } from "preact";
 import { useEffect, useState } from "preact/hooks";
 import emptyImg from "../../static/img/empty.png";
@@ -68,6 +64,7 @@ import { useAsyncAsHook } from "../hooks/useAsyncAsHook.js";
 import { Button } from "../mui/Button.js";
 import { SafeHandler } from "../mui/handlers.js";
 import { Pages } from "../NavigationBar.js";
+import { assertUnreachable } from "../utils/index.js";
 
 interface Props {
   tid: string;
@@ -392,9 +389,10 @@ export function TransactionView({
   const { i18n } = useTranslationContext();
   const { safely } = useAlertContext();
 
+  const raw = Amounts.parseOrThrow(transaction.amountRaw);
+  const effective = Amounts.parseOrThrow(transaction.amountEffective);
+
   if (transaction.type === TransactionType.Withdrawal) {
-    const total = Amounts.parseOrThrow(transaction.amountEffective);
-    const chosen = Amounts.parseOrThrow(transaction.amountRaw);
     return (
       <TransactionTemplate
         transaction={transaction}
@@ -406,7 +404,7 @@ export function TransactionView({
         <Header
           timestamp={transaction.timestamp}
           type={i18n.str`Withdrawal`}
-          total={total}
+          total={effective}
           kind="positive"
         >
           {transaction.exchangeBaseUrl}
@@ -417,7 +415,7 @@ export function TransactionView({
             .type === WithdrawalType.ManualTransfer ? (
           <Fragment>
             <BankDetailsByPaytoType
-              amount={chosen}
+              amount={raw}
               exchangeBaseUrl={transaction.exchangeBaseUrl}
               payto={parsePaytoUri(
                 transaction.withdrawalDetails.exchangePaytoUris[0],
@@ -500,10 +498,7 @@ export function TransactionView({
           title={i18n.str`Details`}
           text={
             <WithdrawDetails
-              amount={{
-                effective: Amounts.parseOrThrow(transaction.amountEffective),
-                raw: Amounts.parseOrThrow(transaction.amountRaw),
-              }}
+              amount={getAmountWithFee(effective, raw, "credit")}
             />
           }
         />
@@ -517,15 +512,9 @@ export function TransactionView({
         ? undefined
         : Amounts.parseOrThrow(transaction.refundPending);
 
-    const price = {
-      raw: Amounts.parseOrThrow(transaction.amountRaw),
-      effective: Amounts.parseOrThrow(transaction.amountEffective),
-    };
-    const refund = {
-      raw: Amounts.parseOrThrow(transaction.totalRefundRaw),
-      effective: Amounts.parseOrThrow(transaction.totalRefundEffective),
-    };
-    const total = Amounts.sub(price.effective, refund.effective).amount;
+    const effectiveRefund = Amounts.parseOrThrow(
+      transaction.totalRefundEffective,
+    );
 
     return (
       <TransactionTemplate
@@ -537,7 +526,7 @@ export function TransactionView({
       >
         <Header
           timestamp={transaction.timestamp}
-          total={total}
+          total={effective}
           type={i18n.str`Payment`}
           kind="negative"
         >
@@ -632,8 +621,8 @@ export function TransactionView({
           title={i18n.str`Details`}
           text={
             <PurchaseDetails
-              price={price}
-              refund={refund}
+              price={getAmountWithFee(effective, raw, "debit")}
+              effectiveRefund={effectiveRefund}
               info={transaction.info}
               proposalId={transaction.proposalId}
             />
@@ -645,7 +634,6 @@ export function TransactionView({
   }
 
   if (transaction.type === TransactionType.Deposit) {
-    const total = Amounts.parseOrThrow(transaction.amountRaw);
     const payto = parsePaytoUri(transaction.targetPaytoUri);
 
     const wireTime = AbsoluteTime.fromTimestamp(
@@ -663,7 +651,7 @@ export function TransactionView({
         <Header
           timestamp={transaction.timestamp}
           type={i18n.str`Deposit`}
-          total={total}
+          total={effective}
           kind="negative"
         >
           {!payto ? transaction.targetPaytoUri : <NicePayto payto={payto} />}
@@ -671,7 +659,11 @@ export function TransactionView({
         {payto && <PartPayto payto={payto} kind="neutral" />}
         <Part
           title={i18n.str`Details`}
-          text={<DepositDetails transaction={transaction} />}
+          text={
+            <DepositDetails
+              amount={getAmountWithFee(effective, raw, "debit")}
+            />
+          }
           kind="neutral"
         />
         {!shouldBeWired ? (
@@ -712,11 +704,6 @@ export function TransactionView({
   }
 
   if (transaction.type === TransactionType.Refresh) {
-    const total = Amounts.sub(
-      Amounts.parseOrThrow(transaction.amountRaw),
-      Amounts.parseOrThrow(transaction.amountEffective),
-    ).amount;
-
     return (
       <TransactionTemplate
         transaction={transaction}
@@ -728,22 +715,24 @@ export function TransactionView({
         <Header
           timestamp={transaction.timestamp}
           type={i18n.str`Refresh`}
-          total={total}
+          total={effective}
           kind="negative"
         >
           {transaction.exchangeBaseUrl}
         </Header>
         <Part
           title={i18n.str`Details`}
-          text={<RefreshDetails transaction={transaction} />}
+          text={
+            <RefreshDetails
+              amount={getAmountWithFee(effective, raw, "debit")}
+            />
+          }
         />
       </TransactionTemplate>
     );
   }
 
   if (transaction.type === TransactionType.Tip) {
-    const total = Amounts.parseOrThrow(transaction.amountEffective);
-
     return (
       <TransactionTemplate
         transaction={transaction}
@@ -755,7 +744,7 @@ export function TransactionView({
         <Header
           timestamp={transaction.timestamp}
           type={i18n.str`Tip`}
-          total={total}
+          total={effective}
           kind="positive"
         >
           {transaction.merchantBaseUrl}
@@ -767,14 +756,15 @@ export function TransactionView({
         /> */}
         <Part
           title={i18n.str`Details`}
-          text={<TipDetails transaction={transaction} />}
+          text={
+            <TipDetails amount={getAmountWithFee(effective, raw, "credit")} />
+          }
         />
       </TransactionTemplate>
     );
   }
 
   if (transaction.type === TransactionType.Refund) {
-    const total = Amounts.parseOrThrow(transaction.amountEffective);
     return (
       <TransactionTemplate
         transaction={transaction}
@@ -786,7 +776,7 @@ export function TransactionView({
         <Header
           timestamp={transaction.timestamp}
           type={i18n.str`Refund`}
-          total={total}
+          total={effective}
           kind="positive"
         >
           {transaction.info.summary}
@@ -817,48 +807,17 @@ export function TransactionView({
         />
         <Part
           title={i18n.str`Details`}
-          text={<RefundDetails transaction={transaction} />}
+          text={
+            <RefundDetails
+              amount={getAmountWithFee(effective, raw, "credit")}
+            />
+          }
         />
       </TransactionTemplate>
     );
   }
 
-  function ShowQrWithCopy({ text }: { text: string }): VNode {
-    const [showing, setShowing] = useState(false);
-    async function copy(): Promise<void> {
-      navigator.clipboard.writeText(text);
-    }
-    async function toggle(): Promise<void> {
-      setShowing((s) => !s);
-    }
-    if (showing) {
-      return (
-        <div>
-          <QR text={text} />
-          <Button onClick={copy as SafeHandler<void>}>
-            <i18n.Translate>copy</i18n.Translate>
-          </Button>
-          <Button onClick={toggle as SafeHandler<void>}>
-            <i18n.Translate>hide qr</i18n.Translate>
-          </Button>
-        </div>
-      );
-    }
-    return (
-      <div>
-        <div>{text.substring(0, 64)}...</div>
-        <Button onClick={copy as SafeHandler<void>}>
-          <i18n.Translate>copy</i18n.Translate>
-        </Button>
-        <Button onClick={toggle as SafeHandler<void>}>
-          <i18n.Translate>show qr</i18n.Translate>
-        </Button>
-      </div>
-    );
-  }
-
   if (transaction.type === TransactionType.PeerPullCredit) {
-    const total = Amounts.parseOrThrow(transaction.amountEffective);
     return (
       <TransactionTemplate
         transaction={transaction}
@@ -870,7 +829,7 @@ export function TransactionView({
         <Header
           timestamp={transaction.timestamp}
           type={i18n.str`Credit`}
-          total={total}
+          total={effective}
           kind="positive"
         >
           <i18n.Translate>Invoice</i18n.Translate>
@@ -900,10 +859,7 @@ export function TransactionView({
           title={i18n.str`Details`}
           text={
             <InvoiceDetails
-              amount={{
-                effective: Amounts.parseOrThrow(transaction.amountEffective),
-                raw: Amounts.parseOrThrow(transaction.amountRaw),
-              }}
+              amount={getAmountWithFee(effective, raw, "credit")}
             />
           }
         />
@@ -912,7 +868,6 @@ export function TransactionView({
   }
 
   if (transaction.type === TransactionType.PeerPullDebit) {
-    const total = Amounts.parseOrThrow(transaction.amountEffective);
     return (
       <TransactionTemplate
         transaction={transaction}
@@ -924,7 +879,7 @@ export function TransactionView({
         <Header
           timestamp={transaction.timestamp}
           type={i18n.str`Debit`}
-          total={total}
+          total={effective}
           kind="negative"
         >
           <i18n.Translate>Invoice</i18n.Translate>
@@ -946,16 +901,14 @@ export function TransactionView({
           title={i18n.str`Details`}
           text={
             <InvoiceDetails
-              amount={{
-                effective: Amounts.parseOrThrow(transaction.amountEffective),
-                raw: Amounts.parseOrThrow(transaction.amountRaw),
-              }}
+              amount={getAmountWithFee(effective, raw, "debit")}
             />
           }
         />
       </TransactionTemplate>
     );
   }
+
   if (transaction.type === TransactionType.PeerPushDebit) {
     const total = Amounts.parseOrThrow(transaction.amountEffective);
     return (
@@ -998,10 +951,7 @@ export function TransactionView({
           title={i18n.str`Details`}
           text={
             <TransferDetails
-              amount={{
-                effective: Amounts.parseOrThrow(transaction.amountEffective),
-                raw: Amounts.parseOrThrow(transaction.amountRaw),
-              }}
+              amount={getAmountWithFee(effective, raw, "debit")}
             />
           }
         />
@@ -1010,7 +960,6 @@ export function TransactionView({
   }
 
   if (transaction.type === TransactionType.PeerPushCredit) {
-    const total = Amounts.parseOrThrow(transaction.amountEffective);
     return (
       <TransactionTemplate
         transaction={transaction}
@@ -1022,7 +971,7 @@ export function TransactionView({
         <Header
           timestamp={transaction.timestamp}
           type={i18n.str`Credit`}
-          total={total}
+          total={effective}
           kind="positive"
         >
           <i18n.Translate>Transfer</i18n.Translate>
@@ -1044,17 +993,14 @@ export function TransactionView({
           title={i18n.str`Details`}
           text={
             <TransferDetails
-              amount={{
-                effective: Amounts.parseOrThrow(transaction.amountEffective),
-                raw: Amounts.parseOrThrow(transaction.amountRaw),
-              }}
+              amount={getAmountWithFee(effective, raw, "credit")}
             />
           }
         />
       </TransactionTemplate>
     );
   }
-  return <div />;
+  assertUnreachable(transaction);
 }
 
 export function MerchantDetails({
@@ -1231,19 +1177,37 @@ export function ExchangeDetails({ exchange }: { 
exchange: string }): VNode {
 }
 
 export interface AmountWithFee {
-  effective: AmountJson;
-  raw: AmountJson;
+  value: AmountJson;
+  fee: AmountJson;
+  total: AmountJson;
+  maxFrac: number;
 }
 
-export function InvoiceDetails({ amount }: { amount: AmountWithFee }): VNode {
-  const { i18n } = useTranslationContext();
-
-  const fee = Amounts.sub(amount.raw, amount.effective).amount;
-
-  const maxFrac = [amount.raw, amount.effective, fee]
+export function getAmountWithFee(
+  effective: AmountJson,
+  raw: AmountJson,
+  direction: "credit" | "debit",
+): AmountWithFee {
+  const fee =
+    direction === "credit"
+      ? Amounts.sub(raw, effective).amount
+      : Amounts.sub(effective, raw).amount;
+
+  const maxFrac = [effective, raw, fee]
     .map((a) => Amounts.maxFractionalDigits(a))
     .reduce((c, p) => Math.max(c, p), 0);
 
+  return {
+    total: effective,
+    value: raw,
+    fee,
+    maxFrac,
+  };
+}
+
+export function InvoiceDetails({ amount }: { amount: AmountWithFee }): VNode {
+  const { i18n } = useTranslationContext();
+
   return (
     <PurchaseDetailsTable>
       <tr>
@@ -1251,17 +1215,17 @@ export function InvoiceDetails({ amount }: { amount: 
AmountWithFee }): VNode {
           <i18n.Translate>Invoice</i18n.Translate>
         </td>
         <td>
-          <Amount value={amount.raw} maxFracSize={maxFrac} />
+          <Amount value={amount.value} maxFracSize={amount.maxFrac} />
         </td>
       </tr>
 
-      {Amounts.isNonZero(fee) && (
+      {Amounts.isNonZero(amount.fee) && (
         <tr>
           <td>
-            <i18n.Translate>Transaction fees</i18n.Translate>
+            <i18n.Translate>Fees</i18n.Translate>
           </td>
           <td>
-            <Amount value={fee} negative maxFracSize={maxFrac} />
+            <Amount value={amount.fee} maxFracSize={amount.maxFrac} />
           </td>
         </tr>
       )}
@@ -1275,7 +1239,7 @@ export function InvoiceDetails({ amount }: { amount: 
AmountWithFee }): VNode {
           <i18n.Translate>Total</i18n.Translate>
         </td>
         <td>
-          <Amount value={amount.effective} maxFracSize={maxFrac} />
+          <Amount value={amount.total} maxFracSize={amount.maxFrac} />
         </td>
       </tr>
     </PurchaseDetailsTable>
@@ -1285,12 +1249,6 @@ export function InvoiceDetails({ amount }: { amount: 
AmountWithFee }): VNode {
 export function TransferDetails({ amount }: { amount: AmountWithFee }): VNode {
   const { i18n } = useTranslationContext();
 
-  const fee = Amounts.sub(amount.effective, amount.raw).amount;
-
-  const maxFrac = [amount.raw, amount.effective, fee]
-    .map((a) => Amounts.maxFractionalDigits(a))
-    .reduce((c, p) => Math.max(c, p), 0);
-
   return (
     <PurchaseDetailsTable>
       <tr>
@@ -1298,17 +1256,17 @@ export function TransferDetails({ amount }: { amount: 
AmountWithFee }): VNode {
           <i18n.Translate>Transfer</i18n.Translate>
         </td>
         <td>
-          <Amount value={amount.raw} maxFracSize={maxFrac} />
+          <Amount value={amount.value} maxFracSize={amount.maxFrac} />
         </td>
       </tr>
 
-      {Amounts.isNonZero(fee) && (
+      {Amounts.isNonZero(amount.fee) && (
         <tr>
           <td>
-            <i18n.Translate>Transaction fees</i18n.Translate>
+            <i18n.Translate>Fees</i18n.Translate>
           </td>
           <td>
-            <Amount value={fee} negative maxFracSize={maxFrac} />
+            <Amount value={amount.fee} maxFracSize={amount.maxFrac} />
           </td>
         </tr>
       )}
@@ -1322,7 +1280,7 @@ export function TransferDetails({ amount }: { amount: 
AmountWithFee }): VNode {
           <i18n.Translate>Total</i18n.Translate>
         </td>
         <td>
-          <Amount value={amount.effective} maxFracSize={maxFrac} />
+          <Amount value={amount.total} maxFracSize={amount.maxFrac} />
         </td>
       </tr>
     </PurchaseDetailsTable>
@@ -1332,12 +1290,12 @@ export function TransferDetails({ amount }: { amount: 
AmountWithFee }): VNode {
 export function WithdrawDetails({ amount }: { amount: AmountWithFee }): VNode {
   const { i18n } = useTranslationContext();
 
-  const fee = Amounts.sub(amount.raw, amount.effective).amount;
-
-  const maxFrac = [amount.raw, amount.effective, fee]
+  const maxFrac = [amount.fee, amount.fee]
     .map((a) => Amounts.maxFractionalDigits(a))
     .reduce((c, p) => Math.max(c, p), 0);
 
+  const total = Amounts.add(amount.value, amount.fee).amount;
+
   return (
     <PurchaseDetailsTable>
       <tr>
@@ -1345,17 +1303,17 @@ export function WithdrawDetails({ amount }: { amount: 
AmountWithFee }): VNode {
           <i18n.Translate>Withdraw</i18n.Translate>
         </td>
         <td>
-          <Amount value={amount.raw} maxFracSize={maxFrac} />
+          <Amount value={amount.value} maxFracSize={amount.maxFrac} />
         </td>
       </tr>
 
-      {Amounts.isNonZero(fee) && (
+      {Amounts.isNonZero(amount.fee) && (
         <tr>
           <td>
-            <i18n.Translate>Transaction fees</i18n.Translate>
+            <i18n.Translate>Fees</i18n.Translate>
           </td>
           <td>
-            <Amount value={fee} negative maxFracSize={maxFrac} />
+            <Amount value={amount.fee} maxFracSize={amount.maxFrac} />
           </td>
         </tr>
       )}
@@ -1369,7 +1327,7 @@ export function WithdrawDetails({ amount }: { amount: 
AmountWithFee }): VNode {
           <i18n.Translate>Total</i18n.Translate>
         </td>
         <td>
-          <Amount value={amount.effective} maxFracSize={maxFrac} />
+          <Amount value={amount.total} maxFracSize={amount.maxFrac} />
         </td>
       </tr>
     </PurchaseDetailsTable>
@@ -1378,24 +1336,18 @@ export function WithdrawDetails({ amount }: { amount: 
AmountWithFee }): VNode {
 
 export function PurchaseDetails({
   price,
-  refund,
+  effectiveRefund,
   info,
   proposalId,
 }: {
   price: AmountWithFee;
-  refund?: AmountWithFee;
+  effectiveRefund?: AmountJson;
   info: OrderShortInfo;
   proposalId: string;
 }): VNode {
   const { i18n } = useTranslationContext();
 
-  const partialFee = Amounts.sub(price.effective, price.raw).amount;
-
-  const refundFee = !refund
-    ? Amounts.zeroOfCurrency(price.effective.currency)
-    : Amounts.sub(refund.raw, refund.effective).amount;
-
-  const fee = Amounts.sum([partialFee, refundFee]).amount;
+  const total = Amounts.add(price.value, price.fee).amount;
 
   const hasProducts = info.products && info.products.length > 0;
 
@@ -1406,10 +1358,6 @@ export function PurchaseDetails({
     return;
   };
 
-  const total = !refund
-    ? price.effective
-    : Amounts.sub(price.effective, refund.effective).amount;
-
   return (
     <PurchaseDetailsTable>
       <tr>
@@ -1417,43 +1365,73 @@ export function PurchaseDetails({
           <i18n.Translate>Price</i18n.Translate>
         </td>
         <td>
-          <Amount value={price.raw} />
+          <Amount value={price.value} />
         </td>
       </tr>
-
-      {refund && Amounts.isNonZero(refund.raw) && (
-        <tr>
-          <td>
-            <i18n.Translate>Refunded</i18n.Translate>
-          </td>
-          <td>
-            <Amount value={refund.raw} negative />
-          </td>
-        </tr>
-      )}
-      {Amounts.isNonZero(fee) && (
+      {Amounts.isNonZero(price.fee) && (
         <tr>
           <td>
             <i18n.Translate>Transaction fees</i18n.Translate>
           </td>
           <td>
-            <Amount value={fee} />
+            <Amount value={price.fee} />
           </td>
         </tr>
       )}
-      <tr>
-        <td colSpan={2}>
-          <hr />
-        </td>
-      </tr>
-      <tr>
-        <td>
-          <i18n.Translate>Total</i18n.Translate>
-        </td>
-        <td>
-          <Amount value={total} />
-        </td>
-      </tr>
+      {effectiveRefund && Amounts.isNonZero(effectiveRefund) ? (
+        <Fragment>
+          <tr>
+            <td colSpan={2}>
+              <hr />
+            </td>
+          </tr>
+          <tr>
+            <td>
+              <i18n.Translate>Subtotal</i18n.Translate>
+            </td>
+            <td>
+              <Amount value={price.total} />
+            </td>
+          </tr>
+          <tr>
+            <td>
+              <i18n.Translate>Refunded</i18n.Translate>
+            </td>
+            <td>
+              <Amount value={effectiveRefund} negative />
+            </td>
+          </tr>
+          <tr>
+            <td colSpan={2}>
+              <hr />
+            </td>
+          </tr>
+          <tr>
+            <td>
+              <i18n.Translate>Total</i18n.Translate>
+            </td>
+            <td>
+              <Amount value={Amounts.sub(total, effectiveRefund).amount} />
+            </td>
+          </tr>
+        </Fragment>
+      ) : (
+        <Fragment>
+          <tr>
+            <td colSpan={2}>
+              <hr />
+            </td>
+          </tr>
+          <tr>
+            <td>
+              <i18n.Translate>Total</i18n.Translate>
+            </td>
+            <td>
+              <Amount value={price.total} />
+            </td>
+          </tr>
+        </Fragment>
+      )}
       {hasProducts && (
         <tr>
           <td colSpan={2}>
@@ -1508,39 +1486,27 @@ export function PurchaseDetails({
   );
 }
 
-function RefundDetails({
-  transaction,
-}: {
-  transaction: TransactionRefund;
-}): VNode {
+function RefundDetails({ amount }: { amount: AmountWithFee }): VNode {
   const { i18n } = useTranslationContext();
 
-  const r = Amounts.parseOrThrow(transaction.amountRaw);
-  const e = Amounts.parseOrThrow(transaction.amountEffective);
-  const fee = Amounts.sub(r, e).amount;
-
-  const maxFrac = [r, e, fee]
-    .map((a) => Amounts.maxFractionalDigits(a))
-    .reduce((c, p) => Math.max(c, p), 0);
-
   return (
     <PurchaseDetailsTable>
       <tr>
         <td>
-          <i18n.Translate>Amount</i18n.Translate>
+          <i18n.Translate>Refund</i18n.Translate>
         </td>
         <td>
-          <Amount value={transaction.amountRaw} maxFracSize={maxFrac} />
+          <Amount value={amount.value} maxFracSize={amount.maxFrac} />
         </td>
       </tr>
 
-      {Amounts.isNonZero(fee) && (
+      {Amounts.isNonZero(amount.fee) && (
         <tr>
           <td>
-            <i18n.Translate>Transaction fees</i18n.Translate>
+            <i18n.Translate>Fees</i18n.Translate>
           </td>
           <td>
-            <Amount value={fee} negative maxFracSize={maxFrac} />
+            <Amount value={amount.fee} maxFracSize={amount.maxFrac} />
           </td>
         </tr>
       )}
@@ -1554,45 +1520,34 @@ function RefundDetails({
           <i18n.Translate>Total</i18n.Translate>
         </td>
         <td>
-          <Amount value={transaction.amountEffective} maxFracSize={maxFrac} />
+          <Amount value={amount.total} maxFracSize={amount.maxFrac} />
         </td>
       </tr>
     </PurchaseDetailsTable>
   );
 }
 
-function DepositDetails({
-  transaction,
-}: {
-  transaction: TransactionDeposit;
-}): VNode {
+function DepositDetails({ amount }: { amount: AmountWithFee }): VNode {
   const { i18n } = useTranslationContext();
-  const r = Amounts.parseOrThrow(transaction.amountRaw);
-  const e = Amounts.parseOrThrow(transaction.amountEffective);
-  const fee = Amounts.sub(e, r).amount;
-
-  const maxFrac = [r, e, fee]
-    .map((a) => Amounts.maxFractionalDigits(a))
-    .reduce((c, p) => Math.max(c, p), 0);
 
   return (
     <PurchaseDetailsTable>
       <tr>
         <td>
-          <i18n.Translate>Amount</i18n.Translate>
+          <i18n.Translate>Deposit</i18n.Translate>
         </td>
         <td>
-          <Amount value={transaction.amountRaw} maxFracSize={maxFrac} />
+          <Amount value={amount.value} maxFracSize={amount.maxFrac} />
         </td>
       </tr>
 
-      {Amounts.isNonZero(fee) && (
+      {Amounts.isNonZero(amount.fee) && (
         <tr>
           <td>
-            <i18n.Translate>Transaction fees</i18n.Translate>
+            <i18n.Translate>Fees</i18n.Translate>
           </td>
           <td>
-            <Amount value={fee} maxFracSize={maxFrac} />
+            <Amount value={amount.fee} maxFracSize={amount.maxFrac} />
           </td>
         </tr>
       )}
@@ -1606,43 +1561,32 @@ function DepositDetails({
           <i18n.Translate>Total transfer</i18n.Translate>
         </td>
         <td>
-          <Amount value={transaction.amountEffective} maxFracSize={maxFrac} />
+          <Amount value={amount.total} maxFracSize={amount.maxFrac} />
         </td>
       </tr>
     </PurchaseDetailsTable>
   );
 }
-function RefreshDetails({
-  transaction,
-}: {
-  transaction: TransactionRefresh;
-}): VNode {
-  const { i18n } = useTranslationContext();
-
-  const r = Amounts.parseOrThrow(transaction.amountRaw);
-  const e = Amounts.parseOrThrow(transaction.amountEffective);
-  const fee = Amounts.sub(r, e).amount;
 
-  const maxFrac = [r, e, fee]
-    .map((a) => Amounts.maxFractionalDigits(a))
-    .reduce((c, p) => Math.max(c, p), 0);
+function RefreshDetails({ amount }: { amount: AmountWithFee }): VNode {
+  const { i18n } = useTranslationContext();
 
   return (
     <PurchaseDetailsTable>
       <tr>
         <td>
-          <i18n.Translate>Amount</i18n.Translate>
+          <i18n.Translate>Refresh</i18n.Translate>
         </td>
         <td>
-          <Amount value={transaction.amountRaw} maxFracSize={maxFrac} />
+          <Amount value={amount.value} maxFracSize={amount.maxFrac} />
         </td>
       </tr>
       <tr>
         <td>
-          <i18n.Translate>Transaction fees</i18n.Translate>
+          <i18n.Translate>Fees</i18n.Translate>
         </td>
         <td>
-          <Amount value={fee} negative maxFracSize={maxFrac} />
+          <Amount value={amount.fee} maxFracSize={amount.maxFrac} />
         </td>
       </tr>
       <tr>
@@ -1655,42 +1599,34 @@ function RefreshDetails({
           <i18n.Translate>Total</i18n.Translate>
         </td>
         <td>
-          <Amount value={transaction.amountEffective} maxFracSize={maxFrac} />
+          <Amount value={amount.total} maxFracSize={amount.maxFrac} />
         </td>
       </tr>
     </PurchaseDetailsTable>
   );
 }
 
-function TipDetails({ transaction }: { transaction: TransactionTip }): VNode {
+function TipDetails({ amount }: { amount: AmountWithFee }): VNode {
   const { i18n } = useTranslationContext();
 
-  const r = Amounts.parseOrThrow(transaction.amountRaw);
-  const e = Amounts.parseOrThrow(transaction.amountEffective);
-  const fee = Amounts.sub(r, e).amount;
-
-  const maxFrac = [r, e, fee]
-    .map((a) => Amounts.maxFractionalDigits(a))
-    .reduce((c, p) => Math.max(c, p), 0);
-
   return (
     <PurchaseDetailsTable>
       <tr>
         <td>
-          <i18n.Translate>Amount</i18n.Translate>
+          <i18n.Translate>Tip</i18n.Translate>
         </td>
         <td>
-          <Amount value={transaction.amountRaw} maxFracSize={maxFrac} />
+          <Amount value={amount.value} maxFracSize={amount.maxFrac} />
         </td>
       </tr>
 
-      {Amounts.isNonZero(fee) && (
+      {Amounts.isNonZero(amount.fee) && (
         <tr>
           <td>
-            <i18n.Translate>Transaction fees</i18n.Translate>
+            <i18n.Translate>Fees</i18n.Translate>
           </td>
           <td>
-            <Amount value={fee} negative maxFracSize={maxFrac} />
+            <Amount value={amount.fee} maxFracSize={amount.maxFrac} />
           </td>
         </tr>
       )}
@@ -1704,7 +1640,7 @@ function TipDetails({ transaction }: { transaction: 
TransactionTip }): VNode {
           <i18n.Translate>Total</i18n.Translate>
         </td>
         <td>
-          <Amount value={transaction.amountEffective} maxFracSize={maxFrac} />
+          <Amount value={amount.total} maxFracSize={amount.maxFrac} />
         </td>
       </tr>
     </PurchaseDetailsTable>
@@ -1778,3 +1714,38 @@ function NicePayto({ payto }: { payto: PaytoUri }): 
VNode {
   }
   return <Fragment>{stringifyPaytoUri(payto)}</Fragment>;
 }
+
+function ShowQrWithCopy({ text }: { text: string }): VNode {
+  const [showing, setShowing] = useState(false);
+  const { i18n } = useTranslationContext();
+  async function copy(): Promise<void> {
+    navigator.clipboard.writeText(text);
+  }
+  async function toggle(): Promise<void> {
+    setShowing((s) => !s);
+  }
+  if (showing) {
+    return (
+      <div>
+        <QR text={text} />
+        <Button onClick={copy as SafeHandler<void>}>
+          <i18n.Translate>copy</i18n.Translate>
+        </Button>
+        <Button onClick={toggle as SafeHandler<void>}>
+          <i18n.Translate>hide qr</i18n.Translate>
+        </Button>
+      </div>
+    );
+  }
+  return (
+    <div>
+      <div>{text.substring(0, 64)}...</div>
+      <Button onClick={copy as SafeHandler<void>}>
+        <i18n.Translate>copy</i18n.Translate>
+      </Button>
+      <Button onClick={toggle as SafeHandler<void>}>
+        <i18n.Translate>show qr</i18n.Translate>
+      </Button>
+    </div>
+  );
+}
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<void> {
-  logger.info(`setting ${tag} to ${level}`)
+async function setLoggingLevel({
+  tag,
+  level,
+}: {
+  tag?: string;
+  level: LogLevel;
+}): Promise<void> {
+  logger.info(`setting ${tag} to ${level}`);
   if (!tag) {
-    setGlobalLogLevelFromString(level)
+    setGlobalLogLevelFromString(level);
   } else {
-    setLogLevelFromString(tag, level)
+    setLogLevelFromString(tag, level);
   }
 }
 

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]