From 03b12d2b27e9d4038e2b02b303a0401160ebc632 Mon Sep 17 00:00:00 2001
From: Sebastian <sebasjm@gmail.com>
Date: Fri, 20 Jan 2023 15:44:53 -0300
Subject: fix wrong fee calculation

---
 .../src/wallet/Transaction.tsx                     | 453 ++++++++++-----------
 1 file changed, 212 insertions(+), 241 deletions(-)

(limited to 'packages/taler-wallet-webextension/src/wallet/Transaction.tsx')

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>
+  );
+}
-- 
cgit v1.2.3

