wallet-core/packages/taler-wallet-webextension/src/wallet/Transaction.tsx

396 lines
11 KiB
TypeScript
Raw Normal View History

/*
This file is part of TALER
(C) 2016 GNUnet e.V.
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
Foundation; either version 3, or (at your option) any later version.
TALER is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
2021-11-15 15:18:58 +01:00
import {
AmountLike,
Amounts,
i18n,
Transaction,
TransactionType,
} from "@gnu-taler/taler-util";
import { format } from "date-fns";
2021-11-15 15:18:58 +01:00
import { JSX, VNode, h } from "preact";
import { route } from "preact-router";
import { useEffect, useState } from "preact/hooks";
2021-10-27 20:13:35 +02:00
import emptyImg from "../../static/img/empty.png";
2021-07-13 20:33:28 +02:00
import { ErrorMessage } from "../components/ErrorMessage";
import { Part } from "../components/Part";
2021-11-15 15:18:58 +01:00
import {
ButtonBox,
ButtonBoxDestructive,
ButtonPrimary,
FontIcon,
ListOfProducts,
RowBorderGray,
SmallLightText,
WalletBox,
WarningBox,
} from "../components/styled";
2021-10-27 20:13:35 +02:00
import { Pages } from "../NavigationBar";
import * as wxApi from "../wxApi";
2021-11-15 15:18:58 +01:00
export function TransactionPage({ tid }: { tid: string }): JSX.Element {
const [transaction, setTransaction] = useState<Transaction | undefined>(
undefined,
);
useEffect(() => {
const fetchData = async (): Promise<void> => {
const res = await wxApi.getTransactions();
2021-11-15 15:18:58 +01:00
const ts = res.transactions.filter((t) => t.transactionId === tid);
if (ts.length === 1) {
setTransaction(ts[0]);
} else {
route(Pages.history);
}
};
fetchData();
2021-10-27 20:13:35 +02:00
}, [tid]);
2021-07-13 20:33:28 +02:00
if (!transaction) {
2021-11-15 15:18:58 +01:00
return (
<div>
<i18n.Translate>Loading ...</i18n.Translate>
</div>
);
2021-07-13 20:33:28 +02:00
}
2021-11-15 15:18:58 +01:00
return (
<TransactionView
transaction={transaction}
onDelete={() => wxApi.deleteTransaction(tid).then((_) => history.go(-1))}
onRetry={() => wxApi.retryTransaction(tid).then((_) => history.go(-1))}
onBack={() => {
route(Pages.history);
}}
/>
);
}
export interface WalletTransactionProps {
2021-10-15 00:37:18 +02:00
transaction: Transaction;
onDelete: () => void;
onRetry: () => void;
onBack: () => void;
}
2021-11-15 15:18:58 +01:00
export function TransactionView({
transaction,
onDelete,
onRetry,
onBack,
}: WalletTransactionProps) {
2021-08-24 20:16:11 +02:00
function TransactionTemplate({ children }: { children: VNode[] }) {
2021-11-15 15:18:58 +01:00
return (
<WalletBox>
<section style={{ padding: 8, textAlign: "center" }}>
<ErrorMessage title={transaction?.error?.hint} />
{transaction.pending && (
<WarningBox>This transaction is not completed</WarningBox>
)}
</section>
<section>
<div style={{ textAlign: "center" }}>{children}</div>
</section>
<footer>
<ButtonBox onClick={onBack}>
<i18n.Translate>
{" "}
<FontIcon>&#x2190;</FontIcon>{" "}
</i18n.Translate>
</ButtonBox>
<div>
{transaction?.error ? (
<ButtonPrimary onClick={onRetry}>
<i18n.Translate>retry</i18n.Translate>
</ButtonPrimary>
) : null}
<ButtonBoxDestructive onClick={onDelete}>
<i18n.Translate>&#x1F5D1;</i18n.Translate>
</ButtonBoxDestructive>
</div>
</footer>
</WalletBox>
);
2021-07-13 20:33:28 +02:00
}
2021-08-24 20:16:11 +02:00
function amountToString(text: AmountLike) {
2021-11-15 15:18:58 +01:00
const aj = Amounts.jsonifyAmount(text);
const amount = Amounts.stringifyValue(aj);
return `${amount} ${aj.currency}`;
2021-08-24 20:16:11 +02:00
}
if (transaction.type === TransactionType.Withdrawal) {
2021-06-21 01:37:35 +02:00
const fee = Amounts.sub(
Amounts.parseOrThrow(transaction.amountRaw),
Amounts.parseOrThrow(transaction.amountEffective),
2021-11-15 15:18:58 +01:00
).amount;
return (
<TransactionTemplate>
<h2>Withdrawal</h2>
<div>
{transaction.timestamp.t_ms === "never"
? "never"
: format(transaction.timestamp.t_ms, "dd MMMM yyyy, HH:mm")}
</div>
<br />
<Part
title="Total withdrawn"
text={amountToString(transaction.amountEffective)}
kind="positive"
/>
<Part
title="Chosen amount"
text={amountToString(transaction.amountRaw)}
kind="neutral"
/>
<Part title="Exchange fee" text={amountToString(fee)} kind="negative" />
<Part
title="Exchange"
text={new URL(transaction.exchangeBaseUrl).hostname}
kind="neutral"
/>
</TransactionTemplate>
);
}
2021-11-15 15:18:58 +01:00
const showLargePic = () => {};
2021-06-21 01:37:35 +02:00
if (transaction.type === TransactionType.Payment) {
2021-06-21 01:37:35 +02:00
const fee = Amounts.sub(
Amounts.parseOrThrow(transaction.amountEffective),
Amounts.parseOrThrow(transaction.amountRaw),
2021-11-15 15:18:58 +01:00
).amount;
2021-08-24 20:16:11 +02:00
2021-11-15 15:18:58 +01:00
return (
<TransactionTemplate>
<h2>Payment </h2>
<div>
{transaction.timestamp.t_ms === "never"
? "never"
: format(transaction.timestamp.t_ms, "dd MMMM yyyy, HH:mm")}
</div>
<br />
<Part
big
title="Total paid"
text={amountToString(transaction.amountEffective)}
kind="negative"
/>
<Part
big
title="Purchase amount"
text={amountToString(transaction.amountRaw)}
kind="neutral"
/>
<Part big title="Fee" text={amountToString(fee)} kind="negative" />
<Part
title="Merchant"
text={transaction.info.merchant.name}
kind="neutral"
/>
<Part title="Purchase" text={transaction.info.summary} kind="neutral" />
<Part
title="Receipt"
text={`#${transaction.info.orderId}`}
kind="neutral"
/>
<div>
{transaction.info.products && transaction.info.products.length > 0 && (
<ListOfProducts>
{transaction.info.products.map((p, k) => (
<RowBorderGray key={k}>
<a href="#" onClick={showLargePic}>
<img src={p.image ? p.image : emptyImg} />
</a>
<div>
{p.quantity && p.quantity > 0 && (
<SmallLightText>
x {p.quantity} {p.unit}
</SmallLightText>
)}
<div>{p.description}</div>
</div>
</RowBorderGray>
))}
</ListOfProducts>
)}
</div>
</TransactionTemplate>
);
}
if (transaction.type === TransactionType.Deposit) {
2021-06-21 01:37:35 +02:00
const fee = Amounts.sub(
Amounts.parseOrThrow(transaction.amountRaw),
Amounts.parseOrThrow(transaction.amountEffective),
2021-11-15 15:18:58 +01:00
).amount;
return (
<TransactionTemplate>
<h2>Deposit </h2>
<div>
{transaction.timestamp.t_ms === "never"
? "never"
: format(transaction.timestamp.t_ms, "dd MMMM yyyy, HH:mm")}
</div>
<br />
<Part
big
title="Total deposit"
text={amountToString(transaction.amountEffective)}
kind="negative"
/>
<Part
big
title="Purchase amount"
text={amountToString(transaction.amountRaw)}
kind="neutral"
/>
<Part big title="Fee" text={amountToString(fee)} kind="negative" />
</TransactionTemplate>
);
}
if (transaction.type === TransactionType.Refresh) {
2021-06-21 01:37:35 +02:00
const fee = Amounts.sub(
Amounts.parseOrThrow(transaction.amountRaw),
Amounts.parseOrThrow(transaction.amountEffective),
2021-11-15 15:18:58 +01:00
).amount;
return (
<TransactionTemplate>
<h2>Refresh</h2>
<div>
{transaction.timestamp.t_ms === "never"
? "never"
: format(transaction.timestamp.t_ms, "dd MMMM yyyy, HH:mm")}
</div>
<br />
<Part
big
title="Total refresh"
text={amountToString(transaction.amountEffective)}
kind="negative"
/>
<Part
big
title="Refresh amount"
text={amountToString(transaction.amountRaw)}
kind="neutral"
/>
<Part big title="Fee" text={amountToString(fee)} kind="negative" />
</TransactionTemplate>
);
}
if (transaction.type === TransactionType.Tip) {
2021-06-21 01:37:35 +02:00
const fee = Amounts.sub(
Amounts.parseOrThrow(transaction.amountRaw),
Amounts.parseOrThrow(transaction.amountEffective),
2021-11-15 15:18:58 +01:00
).amount;
return (
<TransactionTemplate>
<h2>Tip</h2>
<div>
{transaction.timestamp.t_ms === "never"
? "never"
: format(transaction.timestamp.t_ms, "dd MMMM yyyy, HH:mm")}
</div>
<br />
<Part
big
title="Total tip"
text={amountToString(transaction.amountEffective)}
kind="positive"
/>
<Part
big
title="Received amount"
text={amountToString(transaction.amountRaw)}
kind="neutral"
/>
<Part big title="Fee" text={amountToString(fee)} kind="negative" />
</TransactionTemplate>
);
}
if (transaction.type === TransactionType.Refund) {
2021-06-21 01:37:35 +02:00
const fee = Amounts.sub(
Amounts.parseOrThrow(transaction.amountRaw),
Amounts.parseOrThrow(transaction.amountEffective),
2021-11-15 15:18:58 +01:00
).amount;
return (
<TransactionTemplate>
<h2>Refund</h2>
<div>
{transaction.timestamp.t_ms === "never"
? "never"
: format(transaction.timestamp.t_ms, "dd MMMM yyyy, HH:mm")}
</div>
<br />
<Part
big
title="Total refund"
text={amountToString(transaction.amountEffective)}
kind="positive"
/>
<Part
big
title="Refund amount"
text={amountToString(transaction.amountRaw)}
kind="neutral"
/>
<Part big title="Fee" text={amountToString(fee)} kind="negative" />
<Part
title="Merchant"
text={transaction.info.merchant.name}
kind="neutral"
/>
<Part title="Purchase" text={transaction.info.summary} kind="neutral" />
<Part
title="Receipt"
text={`#${transaction.info.orderId}`}
kind="neutral"
/>
2021-11-15 15:18:58 +01:00
<p>{transaction.info.summary}</p>
<div>
{transaction.info.products && transaction.info.products.length > 0 && (
<ListOfProducts>
{transaction.info.products.map((p, k) => (
<RowBorderGray key={k}>
<a href="#" onClick={showLargePic}>
<img src={p.image ? p.image : emptyImg} />
</a>
<div>
{p.quantity && p.quantity > 0 && (
<SmallLightText>
x {p.quantity} {p.unit}
</SmallLightText>
)}
<div>{p.description}</div>
</div>
</RowBorderGray>
))}
</ListOfProducts>
)}
</div>
</TransactionTemplate>
);
}
2021-11-15 15:18:58 +01:00
return <div></div>;
}