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

581 lines
17 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,
NotificationType,
parsePaytoUri,
2021-11-15 15:18:58 +01:00
Transaction,
TransactionType,
WithdrawalType,
2021-11-15 15:18:58 +01:00
} from "@gnu-taler/taler-util";
2021-12-06 14:31:19 +01:00
import { differenceInSeconds } from "date-fns";
import { ComponentChildren, Fragment, h, VNode } from "preact";
import { useState } from "preact/hooks";
2021-10-27 20:13:35 +02:00
import emptyImg from "../../static/img/empty.png";
import { BankDetailsByPaytoType } from "../components/BankDetailsByPaytoType";
2021-11-29 18:11:32 +01:00
import { ErrorTalerOperation } from "../components/ErrorTalerOperation";
2022-01-25 14:29:29 +01:00
import { Loading } from "../components/Loading";
import { LoadingError } from "../components/LoadingError";
import { Part } from "../components/Part";
2021-11-15 15:18:58 +01:00
import {
2021-11-16 17:59:53 +01:00
Button,
ButtonDestructive,
2021-11-15 15:18:58 +01:00
ButtonPrimary,
CenteredDialog,
InfoBox,
2021-11-15 15:18:58 +01:00
ListOfProducts,
Overlay,
2021-11-15 15:18:58 +01:00
RowBorderGray,
SmallLightText,
WarningBox,
} from "../components/styled";
2021-11-16 17:59:53 +01:00
import { Time } from "../components/Time";
import { useAsyncAsHook } from "../hooks/useAsyncAsHook";
2021-10-27 20:13:35 +02:00
import * as wxApi from "../wxApi";
2022-01-25 14:29:29 +01:00
interface Props {
tid: string;
goToWalletHistory: (currency?: string) => void;
}
export function TransactionPage({ tid, goToWalletHistory }: Props): VNode {
async function getTransaction(): Promise<Transaction> {
const res = await wxApi.getTransactions();
const ts = res.transactions.filter((t) => t.transactionId === tid);
if (ts.length > 1) throw Error("more than one transaction with this id");
if (ts.length === 1) {
return ts[0];
}
throw Error("no transaction found");
}
const state = useAsyncAsHook(getTransaction, [
NotificationType.WithdrawGroupFinished,
]);
if (!state) {
2022-01-25 14:29:29 +01:00
return <Loading />;
2021-07-13 20:33:28 +02:00
}
if (state.hasError) {
return (
2022-01-25 14:29:29 +01:00
<LoadingError
2022-02-23 19:18:37 +01:00
title={
<i18n.Translate>
Could not load the transaction information
</i18n.Translate>
2022-02-23 19:18:37 +01:00
}
2022-01-25 14:29:29 +01:00
error={state}
/>
);
}
2022-01-25 14:29:29 +01:00
const currency = Amounts.parse(state.response.amountRaw)?.currency;
2021-11-15 15:18:58 +01:00
return (
<TransactionView
transaction={state.response}
2022-01-25 14:29:29 +01:00
onDelete={() =>
wxApi.deleteTransaction(tid).then(() => goToWalletHistory(currency))
}
onRetry={() =>
wxApi.retryTransaction(tid).then(() => goToWalletHistory(currency))
}
onBack={() => goToWalletHistory(currency)}
2021-11-15 15:18:58 +01:00
/>
);
}
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,
2021-11-16 17:59:53 +01:00
}: WalletTransactionProps): VNode {
const [confirmBeforeForget, setConfirmBeforeForget] = useState(false);
2021-12-06 14:31:19 +01:00
function doCheckBeforeForget(): void {
if (
transaction.pending &&
transaction.type === TransactionType.Withdrawal
) {
setConfirmBeforeForget(true);
} else {
onDelete();
}
}
2021-12-06 14:31:19 +01:00
function TransactionTemplate({
children,
}: {
children: ComponentChildren;
}): VNode {
2021-12-06 14:31:19 +01:00
const showRetry =
transaction.error !== undefined ||
transaction.timestamp.t_ms === "never" ||
(transaction.pending &&
differenceInSeconds(new Date(), transaction.timestamp.t_ms) > 10);
2021-11-15 15:18:58 +01:00
return (
<Fragment>
2021-11-15 15:18:58 +01:00
<section style={{ padding: 8, textAlign: "center" }}>
2021-11-29 18:11:32 +01:00
<ErrorTalerOperation
2022-02-23 19:18:37 +01:00
title={
<i18n.Translate>
2022-02-23 19:18:37 +01:00
There was an error trying to complete the transaction
</i18n.Translate>
2022-02-23 19:18:37 +01:00
}
2021-11-29 18:11:32 +01:00
error={transaction?.error}
/>
2021-11-15 15:18:58 +01:00
{transaction.pending && (
2022-02-23 19:18:37 +01:00
<WarningBox>
<i18n.Translate>This transaction is not completed</i18n.Translate>
2022-02-23 19:18:37 +01:00
</WarningBox>
2021-11-15 15:18:58 +01:00
)}
</section>
<section>
<div style={{ textAlign: "center" }}>{children}</div>
</section>
<footer>
2021-11-16 17:59:53 +01:00
<Button onClick={onBack}>
&lt; <i18n.Translate> Back </i18n.Translate>
2021-11-16 17:59:53 +01:00
</Button>
2021-11-15 15:18:58 +01:00
<div>
2021-12-06 14:31:19 +01:00
{showRetry ? (
2021-11-15 15:18:58 +01:00
<ButtonPrimary onClick={onRetry}>
<i18n.Translate>Retry</i18n.Translate>
2021-11-15 15:18:58 +01:00
</ButtonPrimary>
) : null}
<ButtonDestructive onClick={doCheckBeforeForget}>
<i18n.Translate>Forget</i18n.Translate>
2021-11-16 17:59:53 +01:00
</ButtonDestructive>
2021-11-15 15:18:58 +01:00
</div>
</footer>
</Fragment>
2021-11-15 15:18:58 +01:00
);
2021-07-13 20:33:28 +02:00
}
2021-08-24 20:16:11 +02:00
2021-11-16 17:59:53 +01:00
function amountToString(text: AmountLike): string {
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>
{confirmBeforeForget ? (
<Overlay>
<CenteredDialog>
2022-02-23 19:18:37 +01:00
<header>
<i18n.Translate>Caution!</i18n.Translate>
2022-02-23 19:18:37 +01:00
</header>
<section>
<i18n.Translate>
2022-02-23 19:18:37 +01:00
If you have already wired money to the exchange you will loose
the chance to get the coins form it.
</i18n.Translate>
</section>
<footer>
<Button onClick={() => setConfirmBeforeForget(false)}>
<i18n.Translate>Cancel</i18n.Translate>
</Button>
<ButtonDestructive onClick={onDelete}>
<i18n.Translate>Confirm</i18n.Translate>
</ButtonDestructive>
</footer>
</CenteredDialog>
</Overlay>
) : undefined}
2022-02-23 19:18:37 +01:00
<h2>
<i18n.Translate>Withdrawal</i18n.Translate>
2022-02-23 19:18:37 +01:00
</h2>
2021-11-16 17:59:53 +01:00
<Time timestamp={transaction.timestamp} format="dd MMMM yyyy, HH:mm" />
{transaction.pending ? (
transaction.withdrawalDetails.type ===
WithdrawalType.ManualTransfer ? (
<Fragment>
<BankDetailsByPaytoType
amount={amountToString(transaction.amountRaw)}
exchangeBaseUrl={transaction.exchangeBaseUrl}
payto={parsePaytoUri(
transaction.withdrawalDetails.exchangePaytoUris[0],
)}
subject={transaction.withdrawalDetails.reservePub}
/>
<p>
<WarningBox>
<i18n.Translate>
2022-02-23 19:18:37 +01:00
Make sure to use the correct subject, otherwise the money
will not arrive in this wallet.
</i18n.Translate>
</WarningBox>
</p>
<Part
big
title={<i18n.Translate>Total withdrawn</i18n.Translate>}
text={amountToString(transaction.amountEffective)}
kind="positive"
/>
<Part
big
title={<i18n.Translate>Exchange fee</i18n.Translate>}
text={amountToString(fee)}
kind="negative"
/>
</Fragment>
) : (
<Fragment>
{!transaction.withdrawalDetails.confirmed &&
transaction.withdrawalDetails.bankConfirmationUrl ? (
<InfoBox>
<i18n.Translate>
2022-02-23 19:18:37 +01:00
The bank is waiting for confirmation. Go to the
<a
href={transaction.withdrawalDetails.bankConfirmationUrl}
target="_blank"
rel="noreferrer"
>
<i18n.Translate>bank site</i18n.Translate>
2022-02-23 19:18:37 +01:00
</a>
</i18n.Translate>
</InfoBox>
) : undefined}
{transaction.withdrawalDetails.confirmed && (
2022-02-23 19:18:37 +01:00
<InfoBox>
<i18n.Translate>
Waiting for the coins to arrive
</i18n.Translate>
2022-02-23 19:18:37 +01:00
</InfoBox>
)}
<Part
big
title={<i18n.Translate>Total withdrawn</i18n.Translate>}
text={amountToString(transaction.amountEffective)}
kind="positive"
/>
<Part
big
title={<i18n.Translate>Chosen amount</i18n.Translate>}
text={amountToString(transaction.amountRaw)}
kind="neutral"
/>
<Part
big
title={<i18n.Translate>Exchange fee</i18n.Translate>}
text={amountToString(fee)}
kind="negative"
/>
</Fragment>
)
) : (
<Fragment>
<Part
big
title={<i18n.Translate>Total withdrawn</i18n.Translate>}
text={amountToString(transaction.amountEffective)}
kind="positive"
/>
<Part
big
title={<i18n.Translate>Chosen amount</i18n.Translate>}
text={amountToString(transaction.amountRaw)}
kind="neutral"
/>
<Part
big
title={<i18n.Translate>Exchange fee</i18n.Translate>}
text={amountToString(fee)}
kind="negative"
/>
</Fragment>
)}
2021-11-15 15:18:58 +01:00
<Part
title={<i18n.Translate>Exchange</i18n.Translate>}
2021-11-15 15:18:58 +01:00
text={new URL(transaction.exchangeBaseUrl).hostname}
kind="neutral"
/>
</TransactionTemplate>
);
}
2021-11-16 17:59:53 +01:00
const showLargePic = (): void => {
return;
};
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>
2022-02-23 19:18:37 +01:00
<h2>
<i18n.Translate>Payment</i18n.Translate>
2022-02-23 19:18:37 +01:00
</h2>
2021-11-16 17:59:53 +01:00
<Time timestamp={transaction.timestamp} format="dd MMMM yyyy, HH:mm" />
2021-11-15 15:18:58 +01:00
<br />
<Part
big
title={<i18n.Translate>Total paid</i18n.Translate>}
2021-11-15 15:18:58 +01:00
text={amountToString(transaction.amountEffective)}
kind="negative"
/>
<Part
big
title={<i18n.Translate>Purchase amount</i18n.Translate>}
2021-11-15 15:18:58 +01:00
text={amountToString(transaction.amountRaw)}
kind="neutral"
/>
<Part
2022-02-23 19:18:37 +01:00
big
title={<i18n.Translate>Fee</i18n.Translate>}
2022-02-23 19:18:37 +01:00
text={amountToString(fee)}
kind="negative"
/>
<Part
title={<i18n.Translate>Merchant</i18n.Translate>}
2021-11-15 15:18:58 +01:00
text={transaction.info.merchant.name}
kind="neutral"
/>
<Part
title={<i18n.Translate>Purchase</i18n.Translate>}
2022-02-23 19:18:37 +01:00
text={transaction.info.summary}
kind="neutral"
/>
<Part
title={<i18n.Translate>Receipt</i18n.Translate>}
2021-11-15 15:18:58 +01:00
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.amountEffective),
2021-12-23 19:17:36 +01:00
Amounts.parseOrThrow(transaction.amountRaw),
2021-11-15 15:18:58 +01:00
).amount;
return (
<TransactionTemplate>
2022-02-23 19:18:37 +01:00
<h2>
<i18n.Translate>Deposit</i18n.Translate>
2022-02-23 19:18:37 +01:00
</h2>
2021-11-16 17:59:53 +01:00
<Time timestamp={transaction.timestamp} format="dd MMMM yyyy, HH:mm" />
2021-11-15 15:18:58 +01:00
<br />
<Part
big
title={<i18n.Translate>Total send</i18n.Translate>}
2021-11-15 15:18:58 +01:00
text={amountToString(transaction.amountEffective)}
2021-12-23 19:17:36 +01:00
kind="neutral"
2021-11-15 15:18:58 +01:00
/>
<Part
big
title={<i18n.Translate>Deposit amount</i18n.Translate>}
2021-11-15 15:18:58 +01:00
text={amountToString(transaction.amountRaw)}
2021-12-23 19:17:36 +01:00
kind="positive"
2021-11-15 15:18:58 +01:00
/>
2022-02-23 19:18:37 +01:00
<Part
big
title={<i18n.Translate>Fee</i18n.Translate>}
2022-02-23 19:18:37 +01:00
text={amountToString(fee)}
kind="negative"
/>
2021-11-15 15:18:58 +01:00
</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>
2022-02-23 19:18:37 +01:00
<h2>
<i18n.Translate>Refresh</i18n.Translate>
2022-02-23 19:18:37 +01:00
</h2>
2021-11-16 17:59:53 +01:00
<Time timestamp={transaction.timestamp} format="dd MMMM yyyy, HH:mm" />
2021-11-15 15:18:58 +01:00
<br />
<Part
big
title={<i18n.Translate>Total refresh</i18n.Translate>}
2021-11-15 15:18:58 +01:00
text={amountToString(transaction.amountEffective)}
kind="negative"
/>
<Part
big
title={<i18n.Translate>Refresh amount</i18n.Translate>}
2021-11-15 15:18:58 +01:00
text={amountToString(transaction.amountRaw)}
kind="neutral"
/>
2022-02-23 19:18:37 +01:00
<Part
big
title={<i18n.Translate>Fee</i18n.Translate>}
2022-02-23 19:18:37 +01:00
text={amountToString(fee)}
kind="negative"
/>
2021-11-15 15:18:58 +01:00
</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>
2022-02-23 19:18:37 +01:00
<h2>
<i18n.Translate>Tip</i18n.Translate>
2022-02-23 19:18:37 +01:00
</h2>
2021-11-16 17:59:53 +01:00
<Time timestamp={transaction.timestamp} format="dd MMMM yyyy, HH:mm" />
2021-11-15 15:18:58 +01:00
<br />
<Part
big
title={<i18n.Translate>Total tip</i18n.Translate>}
2021-11-15 15:18:58 +01:00
text={amountToString(transaction.amountEffective)}
kind="positive"
/>
<Part
big
title={<i18n.Translate>Received amount</i18n.Translate>}
2021-11-15 15:18:58 +01:00
text={amountToString(transaction.amountRaw)}
kind="neutral"
/>
2022-02-23 19:18:37 +01:00
<Part
big
title={<i18n.Translate>Fee</i18n.Translate>}
2022-02-23 19:18:37 +01:00
text={amountToString(fee)}
kind="negative"
/>
2021-11-15 15:18:58 +01:00
</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>
2022-02-23 19:18:37 +01:00
<h2>
<i18n.Translate>Refund</i18n.Translate>
2022-02-23 19:18:37 +01:00
</h2>
2021-11-16 17:59:53 +01:00
<Time timestamp={transaction.timestamp} format="dd MMMM yyyy, HH:mm" />
2021-11-15 15:18:58 +01:00
<br />
<Part
big
title={<i18n.Translate>Total refund</i18n.Translate>}
2021-11-15 15:18:58 +01:00
text={amountToString(transaction.amountEffective)}
kind="positive"
/>
<Part
big
title={<i18n.Translate>Refund amount</i18n.Translate>}
2021-11-15 15:18:58 +01:00
text={amountToString(transaction.amountRaw)}
kind="neutral"
/>
<Part
2022-02-23 19:18:37 +01:00
big
title={<i18n.Translate>Fee</i18n.Translate>}
2022-02-23 19:18:37 +01:00
text={amountToString(fee)}
kind="negative"
/>
<Part
title={<i18n.Translate>Merchant</i18n.Translate>}
2021-11-15 15:18:58 +01:00
text={transaction.info.merchant.name}
kind="neutral"
/>
<Part
title={<i18n.Translate>Purchase</i18n.Translate>}
2022-02-23 19:18:37 +01:00
text={transaction.info.summary}
kind="neutral"
/>
<Part
title={<i18n.Translate>Receipt</i18n.Translate>}
2021-11-15 15:18:58 +01:00
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-16 17:59:53 +01:00
return <div />;
}