2021-06-16 23:21:03 +02:00
|
|
|
/*
|
|
|
|
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";
|
2021-11-16 17:59:53 +01:00
|
|
|
import { h, VNode } from "preact";
|
2021-11-15 15:18:58 +01:00
|
|
|
import { route } from "preact-router";
|
2021-06-16 23:21:03 +02:00
|
|
|
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";
|
2021-09-08 20:30:32 +02:00
|
|
|
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,
|
|
|
|
ListOfProducts,
|
|
|
|
RowBorderGray,
|
|
|
|
SmallLightText,
|
|
|
|
WalletBox,
|
|
|
|
WarningBox,
|
|
|
|
} from "../components/styled";
|
2021-11-16 17:59:53 +01:00
|
|
|
import { Time } from "../components/Time";
|
2021-10-27 20:13:35 +02:00
|
|
|
import { Pages } from "../NavigationBar";
|
|
|
|
import * as wxApi from "../wxApi";
|
2021-06-16 23:21:03 +02:00
|
|
|
|
2021-11-16 17:59:53 +01:00
|
|
|
export function TransactionPage({ tid }: { tid: string }): VNode {
|
2021-11-15 15:18:58 +01:00
|
|
|
const [transaction, setTransaction] = useState<Transaction | undefined>(
|
|
|
|
undefined,
|
|
|
|
);
|
2021-06-16 23:21:03 +02:00
|
|
|
|
|
|
|
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);
|
2021-06-16 23:21:03 +02:00
|
|
|
if (ts.length === 1) {
|
|
|
|
setTransaction(ts[0]);
|
|
|
|
} else {
|
|
|
|
route(Pages.history);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
fetchData();
|
2021-10-27 20:13:35 +02:00
|
|
|
}, [tid]);
|
2021-06-16 23:21:03 +02:00
|
|
|
|
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}
|
2021-11-16 17:59:53 +01:00
|
|
|
onDelete={() => wxApi.deleteTransaction(tid).then(() => history.go(-1))}
|
|
|
|
onRetry={() => wxApi.retryTransaction(tid).then(() => history.go(-1))}
|
2021-11-15 15:18:58 +01:00
|
|
|
onBack={() => {
|
|
|
|
route(Pages.history);
|
|
|
|
}}
|
|
|
|
/>
|
|
|
|
);
|
2021-06-16 23:21:03 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
export interface WalletTransactionProps {
|
2021-10-15 00:37:18 +02:00
|
|
|
transaction: Transaction;
|
|
|
|
onDelete: () => void;
|
|
|
|
onRetry: () => void;
|
|
|
|
onBack: () => void;
|
2021-06-16 23:21:03 +02:00
|
|
|
}
|
|
|
|
|
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 {
|
|
|
|
function TransactionTemplate({ children }: { children: VNode[] }): VNode {
|
2021-11-15 15:18:58 +01:00
|
|
|
return (
|
|
|
|
<WalletBox>
|
|
|
|
<section style={{ padding: 8, textAlign: "center" }}>
|
|
|
|
<ErrorMessage title={transaction?.error?.hint} />
|
|
|
|
{transaction.pending && (
|
2021-11-16 17:59:53 +01:00
|
|
|
<WarningBox>
|
|
|
|
This transaction is not completed
|
|
|
|
<a href="">more info...</a>
|
|
|
|
</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}>
|
|
|
|
<i18n.Translate> < Back </i18n.Translate>
|
|
|
|
</Button>
|
2021-11-15 15:18:58 +01:00
|
|
|
<div>
|
|
|
|
{transaction?.error ? (
|
|
|
|
<ButtonPrimary onClick={onRetry}>
|
|
|
|
<i18n.Translate>retry</i18n.Translate>
|
|
|
|
</ButtonPrimary>
|
|
|
|
) : null}
|
2021-11-16 17:59:53 +01:00
|
|
|
<ButtonDestructive onClick={onDelete}>
|
|
|
|
<i18n.Translate> Forget </i18n.Translate>
|
|
|
|
</ButtonDestructive>
|
2021-11-15 15:18:58 +01:00
|
|
|
</div>
|
|
|
|
</footer>
|
|
|
|
</WalletBox>
|
|
|
|
);
|
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
|
|
|
}
|
|
|
|
|
2021-06-16 23:21:03 +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>
|
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
|
2021-11-16 17:59:53 +01:00
|
|
|
big
|
2021-11-15 15:18:58 +01:00
|
|
|
title="Total withdrawn"
|
|
|
|
text={amountToString(transaction.amountEffective)}
|
|
|
|
kind="positive"
|
|
|
|
/>
|
|
|
|
<Part
|
2021-11-16 17:59:53 +01:00
|
|
|
big
|
2021-11-15 15:18:58 +01:00
|
|
|
title="Chosen amount"
|
|
|
|
text={amountToString(transaction.amountRaw)}
|
|
|
|
kind="neutral"
|
|
|
|
/>
|
2021-11-16 17:59:53 +01:00
|
|
|
<Part
|
|
|
|
big
|
|
|
|
title="Exchange fee"
|
|
|
|
text={amountToString(fee)}
|
|
|
|
kind="negative"
|
|
|
|
/>
|
2021-11-15 15:18:58 +01:00
|
|
|
<Part
|
|
|
|
title="Exchange"
|
|
|
|
text={new URL(transaction.exchangeBaseUrl).hostname}
|
|
|
|
kind="neutral"
|
|
|
|
/>
|
|
|
|
</TransactionTemplate>
|
|
|
|
);
|
2021-06-16 23:21:03 +02:00
|
|
|
}
|
|
|
|
|
2021-11-16 17:59:53 +01:00
|
|
|
const showLargePic = (): void => {
|
|
|
|
return;
|
|
|
|
};
|
2021-06-21 01:37:35 +02:00
|
|
|
|
2021-06-16 23:21:03 +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>
|
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="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>
|
|
|
|
);
|
2021-06-16 23:21:03 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
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>
|
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="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>
|
|
|
|
);
|
2021-06-16 23:21:03 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
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>
|
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="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>
|
|
|
|
);
|
2021-06-16 23:21:03 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
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>
|
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="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>
|
|
|
|
);
|
2021-06-16 23:21:03 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
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>
|
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="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-06-16 23:21:03 +02:00
|
|
|
|
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-06-16 23:21:03 +02:00
|
|
|
|
2021-11-16 17:59:53 +01:00
|
|
|
return <div />;
|
2021-06-16 23:21:03 +02:00
|
|
|
}
|