diff options
author | Sebastian <sebasjm@gmail.com> | 2023-01-20 15:44:53 -0300 |
---|---|---|
committer | Sebastian <sebasjm@gmail.com> | 2023-01-20 15:45:02 -0300 |
commit | 03b12d2b27e9d4038e2b02b303a0401160ebc632 (patch) | |
tree | 2ea4f87a03ffc955ebeaf090d7ed6316c5c006f2 | |
parent | 5f31dad2d3af80ab0f53cc52a8740f9a37ca0e75 (diff) |
fix wrong fee calculation
6 files changed, 260 insertions, 283 deletions
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/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> + ); +} |