From 03b12d2b27e9d4038e2b02b303a0401160ebc632 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Fri, 20 Jan 2023 15:44:53 -0300 Subject: fix wrong fee calculation --- .../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/Transaction.tsx | 453 ++++++++++----------- 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 */ +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={ } /> 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 ( @@ -68,7 +69,7 @@ export function BaseView(state: SupportedStates): VNode { title={i18n.str`Details`} text={ */ +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={ } /> 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 */ -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={ } /> 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 ( {transaction.exchangeBaseUrl} @@ -417,7 +415,7 @@ export function TransactionView({ .type === WithdrawalType.ManualTransfer ? ( } /> @@ -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 (
@@ -632,8 +621,8 @@ export function TransactionView({ title={i18n.str`Details`} text={ @@ -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({
{!payto ? transaction.targetPaytoUri : } @@ -671,7 +659,11 @@ export function TransactionView({ {payto && } } + text={ + + } 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 ( {transaction.exchangeBaseUrl}
} + text={ + + } /> ); } if (transaction.type === TransactionType.Tip) { - const total = Amounts.parseOrThrow(transaction.amountEffective); - return ( {transaction.merchantBaseUrl} @@ -767,14 +756,15 @@ export function TransactionView({ /> */} } + text={ + + } /> ); } if (transaction.type === TransactionType.Refund) { - const total = Amounts.parseOrThrow(transaction.amountEffective); return ( {transaction.info.summary} @@ -817,48 +807,17 @@ export function TransactionView({ /> } + text={ + + } /> ); } - function ShowQrWithCopy({ text }: { text: string }): VNode { - const [showing, setShowing] = useState(false); - async function copy(): Promise { - navigator.clipboard.writeText(text); - } - async function toggle(): Promise { - setShowing((s) => !s); - } - if (showing) { - return ( -
- - - -
- ); - } - return ( -
-
{text.substring(0, 64)}...
- - -
- ); - } - if (transaction.type === TransactionType.PeerPullCredit) { - const total = Amounts.parseOrThrow(transaction.amountEffective); return ( Invoice @@ -900,10 +859,7 @@ export function TransactionView({ title={i18n.str`Details`} text={ } /> @@ -912,7 +868,6 @@ export function TransactionView({ } if (transaction.type === TransactionType.PeerPullDebit) { - const total = Amounts.parseOrThrow(transaction.amountEffective); return ( Invoice @@ -946,16 +901,14 @@ export function TransactionView({ title={i18n.str`Details`} text={ } /> ); } + if (transaction.type === TransactionType.PeerPushDebit) { const total = Amounts.parseOrThrow(transaction.amountEffective); return ( @@ -998,10 +951,7 @@ export function TransactionView({ title={i18n.str`Details`} text={ } /> @@ -1010,7 +960,6 @@ export function TransactionView({ } if (transaction.type === TransactionType.PeerPushCredit) { - const total = Amounts.parseOrThrow(transaction.amountEffective); return ( Transfer @@ -1044,17 +993,14 @@ export function TransactionView({ title={i18n.str`Details`} text={ } /> ); } - return
; + 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 ( @@ -1251,17 +1215,17 @@ export function InvoiceDetails({ amount }: { amount: AmountWithFee }): VNode { Invoice - + - {Amounts.isNonZero(fee) && ( + {Amounts.isNonZero(amount.fee) && ( - Transaction fees + Fees - + )} @@ -1275,7 +1239,7 @@ export function InvoiceDetails({ amount }: { amount: AmountWithFee }): VNode { Total - + @@ -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 ( @@ -1298,17 +1256,17 @@ export function TransferDetails({ amount }: { amount: AmountWithFee }): VNode { Transfer - + - {Amounts.isNonZero(fee) && ( + {Amounts.isNonZero(amount.fee) && ( - Transaction fees + Fees - + )} @@ -1322,7 +1280,7 @@ export function TransferDetails({ amount }: { amount: AmountWithFee }): VNode { Total - + @@ -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 ( @@ -1345,17 +1303,17 @@ export function WithdrawDetails({ amount }: { amount: AmountWithFee }): VNode { Withdraw - + - {Amounts.isNonZero(fee) && ( + {Amounts.isNonZero(amount.fee) && ( - Transaction fees + Fees - + )} @@ -1369,7 +1327,7 @@ export function WithdrawDetails({ amount }: { amount: AmountWithFee }): VNode { Total - + @@ -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 ( @@ -1417,43 +1365,73 @@ export function PurchaseDetails({ Price - + - - {refund && Amounts.isNonZero(refund.raw) && ( - - - Refunded - - - - - - )} - {Amounts.isNonZero(fee) && ( + {Amounts.isNonZero(price.fee) && ( Transaction fees - + )} - - -
- - - - - Total - - - - - + {effectiveRefund && Amounts.isNonZero(effectiveRefund) ? ( + + + +
+ + + + + Subtotal + + + + + + + + Refunded + + + + + + + +
+ + + + + Total + + + + + +
+ ) : ( + + + +
+ + + + + Total + + + + + +
+ )} {hasProducts && ( @@ -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 ( - Amount + Refund - + - {Amounts.isNonZero(fee) && ( + {Amounts.isNonZero(amount.fee) && ( - Transaction fees + Fees - + )} @@ -1554,45 +1520,34 @@ function RefundDetails({ Total - + ); } -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 ( - Amount + Deposit - + - {Amounts.isNonZero(fee) && ( + {Amounts.isNonZero(amount.fee) && ( - Transaction fees + Fees - + )} @@ -1606,43 +1561,32 @@ function DepositDetails({ Total transfer - + ); } -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 ( - Amount + Refresh - + - Transaction fees + Fees - + @@ -1655,42 +1599,34 @@ function RefreshDetails({ Total - + ); } -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 ( - Amount + Tip - + - {Amounts.isNonZero(fee) && ( + {Amounts.isNonZero(amount.fee) && ( - Transaction fees + Fees - + )} @@ -1704,7 +1640,7 @@ function TipDetails({ transaction }: { transaction: TransactionTip }): VNode { Total - + @@ -1778,3 +1714,38 @@ function NicePayto({ payto }: { payto: PaytoUri }): VNode { } return {stringifyPaytoUri(payto)}; } + +function ShowQrWithCopy({ text }: { text: string }): VNode { + const [showing, setShowing] = useState(false); + const { i18n } = useTranslationContext(); + async function copy(): Promise { + navigator.clipboard.writeText(text); + } + async function toggle(): Promise { + setShowing((s) => !s); + } + if (showing) { + return ( +
+ + + +
+ ); + } + return ( +
+
{text.substring(0, 64)}...
+ + +
+ ); +} -- cgit v1.2.3