2021-06-16 23:21:03 +02:00
|
|
|
/*
|
2022-06-06 17:05:26 +02:00
|
|
|
This file is part of GNU Taler
|
|
|
|
(C) 2022 Taler Systems S.A.
|
2021-06-16 23:21:03 +02:00
|
|
|
|
2022-06-06 17:05:26 +02:00
|
|
|
GNU Taler is free software; you can redistribute it and/or modify it under the
|
2021-06-16 23:21:03 +02:00
|
|
|
terms of the GNU General Public License as published by the Free Software
|
|
|
|
Foundation; either version 3, or (at your option) any later version.
|
|
|
|
|
2022-06-06 17:05:26 +02:00
|
|
|
GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
|
2021-06-16 23:21:03 +02:00
|
|
|
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
|
2022-06-06 17:05:26 +02:00
|
|
|
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
2021-06-16 23:21:03 +02:00
|
|
|
*/
|
|
|
|
|
2021-11-15 15:18:58 +01:00
|
|
|
import {
|
2022-03-18 15:32:41 +01:00
|
|
|
AbsoluteTime,
|
2022-05-26 20:55:14 +02:00
|
|
|
AmountJson,
|
2021-11-15 15:18:58 +01:00
|
|
|
Amounts,
|
2023-01-13 20:09:33 +01:00
|
|
|
ExtendedStatus,
|
2022-05-26 20:55:14 +02:00
|
|
|
Location,
|
2022-08-08 19:09:28 +02:00
|
|
|
MerchantInfo,
|
2021-11-19 18:51:27 +01:00
|
|
|
NotificationType,
|
2022-08-08 19:09:28 +02:00
|
|
|
OrderShortInfo,
|
2021-11-19 18:51:27 +01:00
|
|
|
parsePaytoUri,
|
2022-05-29 06:23:15 +02:00
|
|
|
PaytoUri,
|
|
|
|
stringifyPaytoUri,
|
2022-05-26 20:55:14 +02:00
|
|
|
TalerProtocolTimestamp,
|
2021-11-15 15:18:58 +01:00
|
|
|
Transaction,
|
2022-05-26 20:55:14 +02:00
|
|
|
TransactionDeposit,
|
|
|
|
TransactionRefresh,
|
|
|
|
TransactionRefund,
|
|
|
|
TransactionTip,
|
2021-11-15 15:18:58 +01:00
|
|
|
TransactionType,
|
2023-01-09 12:38:48 +01:00
|
|
|
TranslatedString,
|
2021-11-19 18:51:27 +01:00
|
|
|
WithdrawalType,
|
2021-11-15 15:18:58 +01:00
|
|
|
} from "@gnu-taler/taler-util";
|
2022-10-25 17:23:08 +02:00
|
|
|
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
|
2022-05-26 20:55:14 +02:00
|
|
|
import { styled } from "@linaria/react";
|
2021-12-06 14:31:19 +01:00
|
|
|
import { differenceInSeconds } from "date-fns";
|
2021-11-19 18:51:27 +01:00
|
|
|
import { ComponentChildren, Fragment, h, VNode } from "preact";
|
2022-04-26 03:37:41 +02:00
|
|
|
import { useEffect, useState } from "preact/hooks";
|
2021-10-27 20:13:35 +02:00
|
|
|
import emptyImg from "../../static/img/empty.png";
|
2022-04-11 16:33:55 +02:00
|
|
|
import { Amount } from "../components/Amount.js";
|
2022-03-29 04:41:07 +02:00
|
|
|
import { BankDetailsByPaytoType } from "../components/BankDetailsByPaytoType.js";
|
2022-11-07 19:11:45 +01:00
|
|
|
import { CopyButton } from "../components/CopyButton.js";
|
2023-01-11 19:26:07 +01:00
|
|
|
import { AlertView, ErrorAlertView } from "../components/CurrentAlerts.js";
|
2022-03-29 04:41:07 +02:00
|
|
|
import { Loading } from "../components/Loading.js";
|
2022-05-26 20:55:14 +02:00
|
|
|
import { Kind, Part, PartCollapsible, PartPayto } from "../components/Part.js";
|
2022-08-31 05:20:35 +02:00
|
|
|
import { QR } from "../components/QR.js";
|
2022-08-08 19:09:28 +02:00
|
|
|
import { ShowFullContractTermPopup } from "../components/ShowFullContractTermPopup.js";
|
2021-11-15 15:18:58 +01:00
|
|
|
import {
|
2021-11-19 18:51:27 +01:00
|
|
|
CenteredDialog,
|
|
|
|
InfoBox,
|
2021-11-15 15:18:58 +01:00
|
|
|
ListOfProducts,
|
2021-11-19 18:51:27 +01:00
|
|
|
Overlay,
|
2022-05-26 20:55:14 +02:00
|
|
|
Row,
|
2021-11-15 15:18:58 +01:00
|
|
|
SmallLightText,
|
2022-03-28 19:03:59 +02:00
|
|
|
SubTitle,
|
2021-11-15 15:18:58 +01:00
|
|
|
WarningBox,
|
2022-03-29 04:41:07 +02:00
|
|
|
} from "../components/styled/index.js";
|
|
|
|
import { Time } from "../components/Time.js";
|
2023-01-13 20:09:33 +01:00
|
|
|
import { alertFromError, useAlertContext } from "../context/alert.js";
|
2022-12-15 21:11:24 +01:00
|
|
|
import { useBackendContext } from "../context/backend.js";
|
2022-03-29 04:41:07 +02:00
|
|
|
import { useTranslationContext } from "../context/translation.js";
|
2022-04-26 04:07:31 +02:00
|
|
|
import { useAsyncAsHook } from "../hooks/useAsyncAsHook.js";
|
2022-06-01 20:47:47 +02:00
|
|
|
import { Button } from "../mui/Button.js";
|
2023-01-13 20:09:33 +01:00
|
|
|
import { SafeHandler } from "../mui/handlers.js";
|
2022-05-14 23:09:33 +02:00
|
|
|
import { Pages } from "../NavigationBar.js";
|
2021-06-16 23:21:03 +02:00
|
|
|
|
2022-01-25 14:29:29 +01:00
|
|
|
interface Props {
|
|
|
|
tid: string;
|
2022-06-01 20:47:47 +02:00
|
|
|
goToWalletHistory: (currency?: string) => Promise<void>;
|
2022-01-25 14:29:29 +01:00
|
|
|
}
|
2022-04-26 03:37:41 +02:00
|
|
|
|
2022-10-25 17:23:08 +02:00
|
|
|
export function TransactionPage({
|
|
|
|
tid: transactionId,
|
|
|
|
goToWalletHistory,
|
|
|
|
}: Props): VNode {
|
2022-03-14 19:20:32 +01:00
|
|
|
const { i18n } = useTranslationContext();
|
2022-12-15 21:11:24 +01:00
|
|
|
const api = useBackendContext();
|
2022-10-25 17:23:08 +02:00
|
|
|
const state = useAsyncAsHook(
|
|
|
|
() =>
|
2022-12-15 21:11:24 +01:00
|
|
|
api.wallet.call(WalletApiOperation.GetTransactionById, {
|
2022-10-25 17:23:08 +02:00
|
|
|
transactionId,
|
|
|
|
}),
|
|
|
|
[transactionId],
|
|
|
|
);
|
2022-04-26 03:37:41 +02:00
|
|
|
|
2022-10-25 17:23:08 +02:00
|
|
|
useEffect(() =>
|
2022-12-15 21:11:24 +01:00
|
|
|
api.listener.onUpdateNotification(
|
2022-06-09 19:16:28 +02:00
|
|
|
[NotificationType.WithdrawGroupFinished],
|
2022-10-25 17:23:08 +02:00
|
|
|
state?.retry,
|
|
|
|
),
|
|
|
|
);
|
2021-06-16 23:21:03 +02:00
|
|
|
|
2021-11-19 18:51:27 +01:00
|
|
|
if (!state) {
|
2022-01-25 14:29:29 +01:00
|
|
|
return <Loading />;
|
2021-07-13 20:33:28 +02:00
|
|
|
}
|
2021-11-19 18:51:27 +01:00
|
|
|
|
|
|
|
if (state.hasError) {
|
|
|
|
return (
|
2023-01-10 00:20:09 +01:00
|
|
|
<ErrorAlertView
|
|
|
|
error={alertFromError(
|
2023-01-09 12:38:48 +01:00
|
|
|
i18n.str`Could not load transaction information`,
|
|
|
|
state,
|
|
|
|
)}
|
2022-01-25 14:29:29 +01:00
|
|
|
/>
|
2021-11-19 18:51:27 +01:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2022-01-25 14:29:29 +01:00
|
|
|
const currency = Amounts.parse(state.response.amountRaw)?.currency;
|
2021-11-19 18:51:27 +01:00
|
|
|
|
2021-11-15 15:18:58 +01:00
|
|
|
return (
|
|
|
|
<TransactionView
|
2021-11-19 18:51:27 +01:00
|
|
|
transaction={state.response}
|
2022-09-01 13:42:18 +02:00
|
|
|
onSend={async () => {
|
|
|
|
null;
|
|
|
|
}}
|
2023-01-13 20:09:33 +01:00
|
|
|
onCancel={async () => {
|
|
|
|
await api.wallet.call(WalletApiOperation.AbortTransaction, {
|
|
|
|
transactionId,
|
|
|
|
});
|
|
|
|
goToWalletHistory(currency);
|
|
|
|
}}
|
2022-10-25 17:23:08 +02:00
|
|
|
onDelete={async () => {
|
2022-12-15 21:11:24 +01:00
|
|
|
await api.wallet.call(WalletApiOperation.DeleteTransaction, {
|
2022-10-25 17:23:08 +02:00
|
|
|
transactionId,
|
|
|
|
});
|
|
|
|
goToWalletHistory(currency);
|
|
|
|
}}
|
|
|
|
onRetry={async () => {
|
2022-12-15 21:11:24 +01:00
|
|
|
await api.wallet.call(WalletApiOperation.RetryTransaction, {
|
2022-10-25 17:23:08 +02:00
|
|
|
transactionId,
|
|
|
|
});
|
|
|
|
goToWalletHistory(currency);
|
|
|
|
}}
|
|
|
|
onRefund={async (purchaseId) => {
|
2022-12-15 21:11:24 +01:00
|
|
|
await api.wallet.call(WalletApiOperation.ApplyRefundFromPurchaseId, {
|
2022-10-25 17:23:08 +02:00
|
|
|
purchaseId,
|
|
|
|
});
|
|
|
|
}}
|
2022-01-25 14:29:29 +01:00
|
|
|
onBack={() => goToWalletHistory(currency)}
|
2021-11-15 15:18:58 +01:00
|
|
|
/>
|
|
|
|
);
|
2021-06-16 23:21:03 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
export interface WalletTransactionProps {
|
2021-10-15 00:37:18 +02:00
|
|
|
transaction: Transaction;
|
2022-09-01 13:42:18 +02:00
|
|
|
onSend: () => Promise<void>;
|
2023-01-13 20:09:33 +01:00
|
|
|
onCancel: () => Promise<void>;
|
2022-06-01 20:47:47 +02:00
|
|
|
onDelete: () => Promise<void>;
|
|
|
|
onRetry: () => Promise<void>;
|
|
|
|
onRefund: (id: string) => Promise<void>;
|
|
|
|
onBack: () => Promise<void>;
|
2021-06-16 23:21:03 +02:00
|
|
|
}
|
|
|
|
|
2022-05-26 20:55:14 +02:00
|
|
|
const PurchaseDetailsTable = styled.table`
|
|
|
|
width: 100%;
|
|
|
|
|
|
|
|
& > tr > td:nth-child(2n) {
|
|
|
|
text-align: right;
|
|
|
|
}
|
|
|
|
`;
|
|
|
|
|
2023-01-13 20:09:33 +01:00
|
|
|
type TransactionTemplateProps = Omit<
|
|
|
|
Omit<WalletTransactionProps, "onRefund">,
|
|
|
|
"onBack"
|
|
|
|
> & {
|
|
|
|
children: ComponentChildren;
|
|
|
|
};
|
|
|
|
|
|
|
|
function TransactionTemplate({
|
2021-11-15 15:18:58 +01:00
|
|
|
transaction,
|
|
|
|
onDelete,
|
|
|
|
onRetry,
|
2022-09-01 13:42:18 +02:00
|
|
|
onSend,
|
2023-01-13 20:09:33 +01:00
|
|
|
onCancel,
|
|
|
|
children,
|
|
|
|
}: TransactionTemplateProps): VNode {
|
|
|
|
const { i18n } = useTranslationContext();
|
2021-11-19 18:51:27 +01:00
|
|
|
const [confirmBeforeForget, setConfirmBeforeForget] = useState(false);
|
2023-01-13 20:09:33 +01:00
|
|
|
const [confirmBeforeCancel, setConfirmBeforeCancel] = useState(false);
|
|
|
|
const { safely } = useAlertContext();
|
2021-12-06 14:31:19 +01:00
|
|
|
|
2022-06-01 20:47:47 +02:00
|
|
|
async function doCheckBeforeForget(): Promise<void> {
|
2021-11-19 18:51:27 +01:00
|
|
|
if (
|
2023-01-13 20:09:33 +01:00
|
|
|
transaction.extendedStatus === ExtendedStatus.Pending &&
|
2021-11-19 18:51:27 +01:00
|
|
|
transaction.type === TransactionType.Withdrawal
|
|
|
|
) {
|
|
|
|
setConfirmBeforeForget(true);
|
|
|
|
} else {
|
|
|
|
onDelete();
|
|
|
|
}
|
|
|
|
}
|
2021-12-06 14:31:19 +01:00
|
|
|
|
2023-01-13 20:09:33 +01:00
|
|
|
async function doCheckBeforeCancel(): Promise<void> {
|
|
|
|
setConfirmBeforeCancel(true);
|
2021-07-13 20:33:28 +02:00
|
|
|
}
|
2021-08-24 20:16:11 +02:00
|
|
|
|
2023-01-13 20:09:33 +01:00
|
|
|
const SHOWING_RETRY_THRESHOLD_SECS = 30;
|
|
|
|
|
|
|
|
const showSend = false;
|
|
|
|
// (transaction.type === TransactionType.PeerPullCredit ||
|
|
|
|
// transaction.type === TransactionType.PeerPushDebit) &&
|
|
|
|
// !transaction.info.completed;
|
|
|
|
const showRetry =
|
|
|
|
transaction.error !== undefined ||
|
|
|
|
transaction.timestamp.t_s === "never" ||
|
|
|
|
(transaction.extendedStatus === ExtendedStatus.Pending &&
|
|
|
|
differenceInSeconds(new Date(), transaction.timestamp.t_s * 1000) >
|
|
|
|
SHOWING_RETRY_THRESHOLD_SECS);
|
|
|
|
|
|
|
|
const transactionStillActive =
|
|
|
|
transaction.extendedStatus !== ExtendedStatus.Aborted &&
|
|
|
|
transaction.extendedStatus !== ExtendedStatus.Done &&
|
|
|
|
transaction.extendedStatus !== ExtendedStatus.Failed;
|
|
|
|
return (
|
|
|
|
<Fragment>
|
|
|
|
<section style={{ padding: 8, textAlign: "center" }}>
|
|
|
|
{transaction?.error ? (
|
|
|
|
transaction.error.code === 7025 ? (
|
|
|
|
<AlertView
|
|
|
|
alert={{
|
|
|
|
type: "warning",
|
|
|
|
message: i18n.str`KYC check required for the transaction to complete`,
|
|
|
|
description:
|
|
|
|
transaction.error.kycUrl &&
|
|
|
|
typeof transaction.error.kycUrl === "string" ? (
|
|
|
|
<div>
|
|
|
|
<i18n.Translate>
|
|
|
|
Follow this link to the{` `}
|
|
|
|
<a href={transaction.error.kycUrl}>KYC verifier</a>
|
|
|
|
</i18n.Translate>
|
|
|
|
</div>
|
|
|
|
) : (
|
|
|
|
i18n.str`No more information has been provided`
|
|
|
|
),
|
|
|
|
}}
|
|
|
|
/>
|
|
|
|
) : (
|
|
|
|
<ErrorAlertView
|
|
|
|
error={alertFromError(
|
|
|
|
i18n.str`There was an error trying to complete the transaction`,
|
|
|
|
transaction.error,
|
|
|
|
)}
|
|
|
|
/>
|
|
|
|
)
|
|
|
|
) : undefined}
|
|
|
|
{transaction.extendedStatus === ExtendedStatus.Pending && (
|
|
|
|
<WarningBox>
|
|
|
|
<i18n.Translate>This transaction is not completed</i18n.Translate>
|
|
|
|
</WarningBox>
|
|
|
|
)}
|
2021-11-19 18:51:27 +01:00
|
|
|
{confirmBeforeForget ? (
|
|
|
|
<Overlay>
|
|
|
|
<CenteredDialog>
|
2022-02-23 19:18:37 +01:00
|
|
|
<header>
|
2022-02-23 19:44:14 +01:00
|
|
|
<i18n.Translate>Caution!</i18n.Translate>
|
2022-02-23 19:18:37 +01:00
|
|
|
</header>
|
2021-11-19 18:51:27 +01:00
|
|
|
<section>
|
2022-02-23 19:44:14 +01:00
|
|
|
<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.
|
2022-02-23 19:44:14 +01:00
|
|
|
</i18n.Translate>
|
2021-11-19 18:51:27 +01:00
|
|
|
</section>
|
|
|
|
<footer>
|
2022-06-01 20:47:47 +02:00
|
|
|
<Button
|
|
|
|
variant="contained"
|
|
|
|
color="secondary"
|
2023-01-13 20:09:33 +01:00
|
|
|
onClick={
|
|
|
|
(async () =>
|
|
|
|
setConfirmBeforeForget(false)) as SafeHandler<void>
|
|
|
|
}
|
2022-06-01 20:47:47 +02:00
|
|
|
>
|
2022-02-23 19:44:14 +01:00
|
|
|
<i18n.Translate>Cancel</i18n.Translate>
|
2021-11-19 18:51:27 +01:00
|
|
|
</Button>
|
|
|
|
|
2023-01-13 20:09:33 +01:00
|
|
|
<Button
|
|
|
|
variant="contained"
|
|
|
|
color="error"
|
|
|
|
onClick={safely(
|
|
|
|
onDelete,
|
|
|
|
i18n.str`Could not forget transaction`,
|
|
|
|
)}
|
|
|
|
>
|
2022-02-23 19:44:14 +01:00
|
|
|
<i18n.Translate>Confirm</i18n.Translate>
|
2022-06-01 20:47:47 +02:00
|
|
|
</Button>
|
2021-11-19 18:51:27 +01:00
|
|
|
</footer>
|
|
|
|
</CenteredDialog>
|
|
|
|
</Overlay>
|
|
|
|
) : undefined}
|
2023-01-13 20:09:33 +01:00
|
|
|
{confirmBeforeCancel ? (
|
|
|
|
<Overlay>
|
|
|
|
<CenteredDialog>
|
|
|
|
<header>
|
|
|
|
<i18n.Translate>Caution!</i18n.Translate>
|
|
|
|
</header>
|
|
|
|
<section>
|
|
|
|
<i18n.Translate>
|
|
|
|
Doing a cancelation while the transaction still active might
|
|
|
|
result in lost coins. Do you still want to cancel the
|
|
|
|
transaction?
|
|
|
|
</i18n.Translate>
|
|
|
|
</section>
|
|
|
|
<footer>
|
|
|
|
<Button
|
|
|
|
variant="contained"
|
|
|
|
color="secondary"
|
|
|
|
onClick={
|
|
|
|
(async () =>
|
|
|
|
setConfirmBeforeCancel(false)) as SafeHandler<void>
|
|
|
|
}
|
|
|
|
>
|
|
|
|
<i18n.Translate>No</i18n.Translate>
|
|
|
|
</Button>
|
|
|
|
|
|
|
|
<Button
|
|
|
|
variant="contained"
|
|
|
|
color="error"
|
|
|
|
onClick={safely(
|
|
|
|
onCancel,
|
|
|
|
i18n.str`Could not cancel the active transaction`,
|
|
|
|
)}
|
|
|
|
>
|
|
|
|
<i18n.Translate>Yes</i18n.Translate>
|
|
|
|
</Button>
|
|
|
|
</footer>
|
|
|
|
</CenteredDialog>
|
|
|
|
</Overlay>
|
|
|
|
) : undefined}
|
|
|
|
</section>
|
|
|
|
<section>{children}</section>
|
|
|
|
<footer>
|
|
|
|
<div>
|
|
|
|
{showSend ? (
|
|
|
|
<Button
|
|
|
|
variant="contained"
|
|
|
|
onClick={safely(onSend, i18n.str`Could not send`)}
|
|
|
|
>
|
|
|
|
<i18n.Translate>Send</i18n.Translate>
|
|
|
|
</Button>
|
|
|
|
) : null}
|
|
|
|
</div>
|
|
|
|
<div>
|
|
|
|
{showRetry ? (
|
|
|
|
<Button
|
|
|
|
variant="contained"
|
|
|
|
onClick={safely(onRetry, i18n.str`Could not retry`)}
|
|
|
|
>
|
|
|
|
<i18n.Translate>Retry</i18n.Translate>
|
|
|
|
</Button>
|
|
|
|
) : null}
|
|
|
|
{transactionStillActive ? (
|
|
|
|
<Button
|
|
|
|
variant="contained"
|
|
|
|
color="error"
|
|
|
|
onClick={doCheckBeforeCancel as SafeHandler<void>}
|
|
|
|
>
|
|
|
|
<i18n.Translate>Cancel</i18n.Translate>
|
|
|
|
</Button>
|
|
|
|
) : (
|
|
|
|
<Button
|
|
|
|
variant="contained"
|
|
|
|
color="error"
|
|
|
|
onClick={doCheckBeforeForget as SafeHandler<void>}
|
|
|
|
>
|
|
|
|
<i18n.Translate>Forget</i18n.Translate>
|
|
|
|
</Button>
|
|
|
|
)}
|
|
|
|
</div>
|
|
|
|
</footer>
|
|
|
|
</Fragment>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
export function TransactionView({
|
|
|
|
transaction,
|
|
|
|
onDelete,
|
|
|
|
onRetry,
|
|
|
|
onSend,
|
|
|
|
onRefund,
|
|
|
|
onCancel,
|
|
|
|
}: WalletTransactionProps): VNode {
|
|
|
|
const { i18n } = useTranslationContext();
|
|
|
|
const { safely } = useAlertContext();
|
|
|
|
|
|
|
|
if (transaction.type === TransactionType.Withdrawal) {
|
|
|
|
const total = Amounts.parseOrThrow(transaction.amountEffective);
|
|
|
|
const chosen = Amounts.parseOrThrow(transaction.amountRaw);
|
|
|
|
return (
|
|
|
|
<TransactionTemplate
|
|
|
|
transaction={transaction}
|
|
|
|
onDelete={onDelete}
|
|
|
|
onRetry={onRetry}
|
|
|
|
onSend={onSend}
|
|
|
|
onCancel={onCancel}
|
|
|
|
>
|
2022-05-26 20:55:14 +02:00
|
|
|
<Header
|
|
|
|
timestamp={transaction.timestamp}
|
|
|
|
type={i18n.str`Withdrawal`}
|
|
|
|
total={total}
|
|
|
|
kind="positive"
|
|
|
|
>
|
|
|
|
{transaction.exchangeBaseUrl}
|
|
|
|
</Header>
|
|
|
|
|
2023-01-13 20:09:33 +01:00
|
|
|
{transaction.extendedStatus !==
|
|
|
|
ExtendedStatus.Pending ? undefined : transaction.withdrawalDetails
|
2022-05-26 20:55:14 +02:00
|
|
|
.type === WithdrawalType.ManualTransfer ? (
|
|
|
|
<Fragment>
|
|
|
|
<BankDetailsByPaytoType
|
|
|
|
amount={chosen}
|
|
|
|
exchangeBaseUrl={transaction.exchangeBaseUrl}
|
|
|
|
payto={parsePaytoUri(
|
|
|
|
transaction.withdrawalDetails.exchangePaytoUris[0],
|
|
|
|
)}
|
|
|
|
subject={transaction.withdrawalDetails.reservePub}
|
|
|
|
/>
|
2022-11-07 19:11:45 +01:00
|
|
|
<table>
|
|
|
|
<tbody>
|
|
|
|
<tr>
|
|
|
|
<td>
|
|
|
|
<pre>
|
|
|
|
<b>
|
|
|
|
<a
|
|
|
|
target="_bank"
|
|
|
|
rel="noreferrer"
|
|
|
|
title="RFC 8905 for designating targets for payments"
|
|
|
|
href="https://tools.ietf.org/html/rfc8905"
|
|
|
|
>
|
|
|
|
Payto URI
|
|
|
|
</a>
|
|
|
|
</b>
|
|
|
|
</pre>
|
|
|
|
</td>
|
2022-11-07 19:14:40 +01:00
|
|
|
<td width="100%" style={{ wordBreak: "break-all" }}>
|
2022-11-07 19:11:45 +01:00
|
|
|
{transaction.withdrawalDetails.exchangePaytoUris[0]}
|
|
|
|
</td>
|
|
|
|
<td>
|
|
|
|
<CopyButton
|
|
|
|
getContent={() =>
|
|
|
|
transaction.withdrawalDetails.type ===
|
|
|
|
WithdrawalType.ManualTransfer
|
|
|
|
? transaction.withdrawalDetails.exchangePaytoUris[0]
|
|
|
|
: ""
|
|
|
|
}
|
|
|
|
/>
|
|
|
|
</td>
|
|
|
|
</tr>
|
|
|
|
</tbody>
|
|
|
|
</table>
|
2022-05-26 20:55:14 +02:00
|
|
|
<WarningBox>
|
|
|
|
<i18n.Translate>
|
|
|
|
Make sure to use the correct subject, otherwise the money will
|
|
|
|
not arrive in this wallet.
|
|
|
|
</i18n.Translate>
|
|
|
|
</WarningBox>
|
|
|
|
</Fragment>
|
|
|
|
) : (
|
|
|
|
<Fragment>
|
|
|
|
{!transaction.withdrawalDetails.confirmed &&
|
|
|
|
transaction.withdrawalDetails.bankConfirmationUrl ? (
|
|
|
|
<InfoBox>
|
|
|
|
<div style={{ display: "block" }}>
|
2022-02-23 19:44:14 +01:00
|
|
|
<i18n.Translate>
|
2022-11-18 17:26:48 +01:00
|
|
|
Wire transfer need a confirmation. Go to the
|
2022-02-23 19:18:37 +01:00
|
|
|
<a
|
|
|
|
href={transaction.withdrawalDetails.bankConfirmationUrl}
|
|
|
|
target="_blank"
|
|
|
|
rel="noreferrer"
|
2022-05-26 20:55:14 +02:00
|
|
|
style={{ display: "inline" }}
|
2022-02-23 19:18:37 +01:00
|
|
|
>
|
2022-02-23 19:44:14 +01:00
|
|
|
<i18n.Translate>bank site</i18n.Translate>
|
2022-05-26 20:55:14 +02:00
|
|
|
</a>{" "}
|
2022-11-18 17:26:48 +01:00
|
|
|
and check wire transfer operation to exchange account is
|
|
|
|
complete.
|
2022-02-23 19:44:14 +01:00
|
|
|
</i18n.Translate>
|
2022-05-26 20:55:14 +02:00
|
|
|
</div>
|
|
|
|
</InfoBox>
|
|
|
|
) : undefined}
|
|
|
|
{transaction.withdrawalDetails.confirmed && (
|
|
|
|
<InfoBox>
|
|
|
|
<i18n.Translate>
|
|
|
|
Bank has confirmed the wire transfer. Waiting for the exchange
|
|
|
|
to send the coins
|
|
|
|
</i18n.Translate>
|
|
|
|
</InfoBox>
|
|
|
|
)}
|
2021-11-19 18:51:27 +01:00
|
|
|
</Fragment>
|
|
|
|
)}
|
2021-11-15 15:18:58 +01:00
|
|
|
<Part
|
2023-01-09 12:38:48 +01:00
|
|
|
title={i18n.str`Details`}
|
2022-08-10 16:50:46 +02:00
|
|
|
text={
|
|
|
|
<WithdrawDetails
|
|
|
|
amount={{
|
|
|
|
effective: Amounts.parseOrThrow(transaction.amountEffective),
|
|
|
|
raw: Amounts.parseOrThrow(transaction.amountRaw),
|
|
|
|
}}
|
|
|
|
/>
|
|
|
|
}
|
2021-11-15 15:18:58 +01:00
|
|
|
/>
|
|
|
|
</TransactionTemplate>
|
|
|
|
);
|
2021-06-16 23:21:03 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if (transaction.type === TransactionType.Payment) {
|
2022-05-14 23:09:33 +02:00
|
|
|
const pendingRefund =
|
|
|
|
transaction.refundPending === undefined
|
|
|
|
? undefined
|
|
|
|
: Amounts.parseOrThrow(transaction.refundPending);
|
2022-05-26 20:55:14 +02:00
|
|
|
|
2022-08-08 19:09:28 +02:00
|
|
|
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;
|
2022-05-26 20:55:14 +02:00
|
|
|
|
2021-11-15 15:18:58 +01:00
|
|
|
return (
|
2023-01-13 20:09:33 +01:00
|
|
|
<TransactionTemplate
|
|
|
|
transaction={transaction}
|
|
|
|
onDelete={onDelete}
|
|
|
|
onRetry={onRetry}
|
|
|
|
onSend={onSend}
|
|
|
|
onCancel={onCancel}
|
|
|
|
>
|
2022-05-26 20:55:14 +02:00
|
|
|
<Header
|
|
|
|
timestamp={transaction.timestamp}
|
|
|
|
total={total}
|
|
|
|
type={i18n.str`Payment`}
|
2021-11-15 15:18:58 +01:00
|
|
|
kind="negative"
|
2022-05-26 20:55:14 +02:00
|
|
|
>
|
|
|
|
{transaction.info.fulfillmentUrl ? (
|
|
|
|
<a
|
|
|
|
href={transaction.info.fulfillmentUrl}
|
|
|
|
target="_bank"
|
|
|
|
rel="noreferrer"
|
|
|
|
>
|
|
|
|
{transaction.info.summary}
|
|
|
|
</a>
|
|
|
|
) : (
|
|
|
|
transaction.info.summary
|
|
|
|
)}
|
|
|
|
</Header>
|
|
|
|
<br />
|
2022-05-29 06:23:15 +02:00
|
|
|
{transaction.refunds.length > 0 ? (
|
|
|
|
<Part
|
2023-01-09 12:38:48 +01:00
|
|
|
title={i18n.str`Refunds`}
|
2022-05-29 06:23:15 +02:00
|
|
|
text={
|
|
|
|
<table>
|
|
|
|
{transaction.refunds.map((r, i) => {
|
|
|
|
return (
|
|
|
|
<tr key={i}>
|
|
|
|
<td>
|
2022-09-13 16:07:39 +02:00
|
|
|
<i18n.Translate>
|
|
|
|
{<Amount value={r.amountEffective} />}{" "}
|
|
|
|
<a
|
|
|
|
href={Pages.balanceTransaction({
|
|
|
|
tid: r.transactionId,
|
|
|
|
})}
|
|
|
|
>
|
|
|
|
was refunded
|
|
|
|
</a>{" "}
|
|
|
|
on{" "}
|
|
|
|
{
|
|
|
|
<Time
|
|
|
|
timestamp={AbsoluteTime.fromTimestamp(
|
|
|
|
r.timestamp,
|
|
|
|
)}
|
|
|
|
format="dd MMMM yyyy"
|
|
|
|
/>
|
|
|
|
}
|
|
|
|
</i18n.Translate>
|
2022-05-29 06:23:15 +02:00
|
|
|
</td>
|
|
|
|
</tr>
|
|
|
|
);
|
|
|
|
})}
|
|
|
|
</table>
|
|
|
|
}
|
|
|
|
kind="neutral"
|
|
|
|
/>
|
|
|
|
) : undefined}
|
2022-05-26 20:55:14 +02:00
|
|
|
{pendingRefund !== undefined && Amounts.isNonZero(pendingRefund) && (
|
|
|
|
<InfoBox>
|
|
|
|
<i18n.Translate>
|
|
|
|
Merchant created a refund for this order but was not automatically
|
|
|
|
picked up.
|
|
|
|
</i18n.Translate>
|
2022-05-14 23:09:33 +02:00
|
|
|
<Part
|
2023-01-09 12:38:48 +01:00
|
|
|
title={i18n.str`Offer`}
|
2022-05-26 20:55:14 +02:00
|
|
|
text={<Amount value={pendingRefund} />}
|
2022-05-14 23:09:33 +02:00
|
|
|
kind="positive"
|
|
|
|
/>
|
2022-05-26 20:55:14 +02:00
|
|
|
<div>
|
|
|
|
<div />
|
|
|
|
<div>
|
2022-06-01 20:47:47 +02:00
|
|
|
<Button
|
|
|
|
variant="contained"
|
2023-01-13 20:09:33 +01:00
|
|
|
onClick={safely(
|
|
|
|
() => onRefund(transaction.proposalId),
|
|
|
|
i18n.str`Could not refund`,
|
|
|
|
)}
|
2022-06-01 20:47:47 +02:00
|
|
|
>
|
2022-05-26 20:55:14 +02:00
|
|
|
<i18n.Translate>Accept</i18n.Translate>
|
2022-06-01 20:47:47 +02:00
|
|
|
</Button>
|
2022-05-26 20:55:14 +02:00
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</InfoBox>
|
2022-05-14 23:09:33 +02:00
|
|
|
)}
|
2022-02-23 19:18:37 +01:00
|
|
|
<Part
|
2023-01-09 12:38:48 +01:00
|
|
|
title={i18n.str`Merchant`}
|
2022-08-08 19:09:28 +02:00
|
|
|
text={<MerchantDetails merchant={transaction.info.merchant} />}
|
2021-11-15 15:18:58 +01:00
|
|
|
kind="neutral"
|
|
|
|
/>
|
|
|
|
<Part
|
2023-01-09 12:38:48 +01:00
|
|
|
title={i18n.str`Invoice ID`}
|
|
|
|
text={transaction.info.orderId as TranslatedString}
|
2022-02-23 19:18:37 +01:00
|
|
|
kind="neutral"
|
|
|
|
/>
|
|
|
|
<Part
|
2023-01-09 12:38:48 +01:00
|
|
|
title={i18n.str`Details`}
|
2022-08-08 19:09:28 +02:00
|
|
|
text={
|
|
|
|
<PurchaseDetails
|
|
|
|
price={price}
|
|
|
|
refund={refund}
|
|
|
|
info={transaction.info}
|
|
|
|
proposalId={transaction.proposalId}
|
|
|
|
/>
|
|
|
|
}
|
2021-11-15 15:18:58 +01:00
|
|
|
kind="neutral"
|
|
|
|
/>
|
|
|
|
</TransactionTemplate>
|
|
|
|
);
|
2021-06-16 23:21:03 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if (transaction.type === TransactionType.Deposit) {
|
2022-05-26 20:55:14 +02:00
|
|
|
const total = Amounts.parseOrThrow(transaction.amountRaw);
|
2022-05-03 05:16:03 +02:00
|
|
|
const payto = parsePaytoUri(transaction.targetPaytoUri);
|
2021-11-15 15:18:58 +01:00
|
|
|
return (
|
2023-01-13 20:09:33 +01:00
|
|
|
<TransactionTemplate
|
|
|
|
transaction={transaction}
|
|
|
|
onDelete={onDelete}
|
|
|
|
onRetry={onRetry}
|
|
|
|
onSend={onSend}
|
|
|
|
onCancel={onCancel}
|
|
|
|
>
|
2022-05-26 20:55:14 +02:00
|
|
|
<Header
|
|
|
|
timestamp={transaction.timestamp}
|
|
|
|
type={i18n.str`Deposit`}
|
|
|
|
total={total}
|
|
|
|
kind="negative"
|
|
|
|
>
|
2022-05-29 06:23:15 +02:00
|
|
|
{!payto ? transaction.targetPaytoUri : <NicePayto payto={payto} />}
|
2022-05-26 20:55:14 +02:00
|
|
|
</Header>
|
2022-05-29 06:23:15 +02:00
|
|
|
{payto && <PartPayto payto={payto} kind="neutral" />}
|
2021-11-15 15:18:58 +01:00
|
|
|
<Part
|
2023-01-09 12:38:48 +01:00
|
|
|
title={i18n.str`Details`}
|
2022-05-26 20:55:14 +02:00
|
|
|
text={<DepositDetails transaction={transaction} />}
|
2021-12-23 19:17:36 +01:00
|
|
|
kind="neutral"
|
2021-11-15 15:18:58 +01:00
|
|
|
/>
|
2022-11-17 21:07:24 +01:00
|
|
|
<Part
|
2023-01-09 12:38:48 +01:00
|
|
|
title={i18n.str`Wire transfer deadline`}
|
2022-11-17 21:07:24 +01:00
|
|
|
text={
|
|
|
|
<Time
|
|
|
|
timestamp={AbsoluteTime.fromTimestamp(
|
|
|
|
transaction.wireTransferDeadline,
|
|
|
|
)}
|
|
|
|
format="dd MMMM yyyy 'at' HH:mm"
|
|
|
|
/>
|
|
|
|
}
|
|
|
|
kind="neutral"
|
|
|
|
/>
|
2021-11-15 15:18:58 +01:00
|
|
|
</TransactionTemplate>
|
|
|
|
);
|
2021-06-16 23:21:03 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if (transaction.type === TransactionType.Refresh) {
|
2022-05-26 20:55:14 +02:00
|
|
|
const total = Amounts.sub(
|
2021-06-21 01:37:35 +02:00
|
|
|
Amounts.parseOrThrow(transaction.amountRaw),
|
|
|
|
Amounts.parseOrThrow(transaction.amountEffective),
|
2021-11-15 15:18:58 +01:00
|
|
|
).amount;
|
2022-05-26 20:55:14 +02:00
|
|
|
|
2021-11-15 15:18:58 +01:00
|
|
|
return (
|
2023-01-13 20:09:33 +01:00
|
|
|
<TransactionTemplate
|
|
|
|
transaction={transaction}
|
|
|
|
onDelete={onDelete}
|
|
|
|
onRetry={onRetry}
|
|
|
|
onSend={onSend}
|
|
|
|
onCancel={onCancel}
|
|
|
|
>
|
2022-05-26 20:55:14 +02:00
|
|
|
<Header
|
|
|
|
timestamp={transaction.timestamp}
|
|
|
|
type={i18n.str`Refresh`}
|
|
|
|
total={total}
|
2021-11-15 15:18:58 +01:00
|
|
|
kind="negative"
|
2022-05-26 20:55:14 +02:00
|
|
|
>
|
|
|
|
{transaction.exchangeBaseUrl}
|
|
|
|
</Header>
|
|
|
|
<Part
|
2023-01-09 12:38:48 +01:00
|
|
|
title={i18n.str`Details`}
|
2022-05-26 20:55:14 +02:00
|
|
|
text={<RefreshDetails transaction={transaction} />}
|
2021-11-15 15:18:58 +01:00
|
|
|
/>
|
|
|
|
</TransactionTemplate>
|
|
|
|
);
|
2021-06-16 23:21:03 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if (transaction.type === TransactionType.Tip) {
|
2022-05-26 20:55:14 +02:00
|
|
|
const total = Amounts.parseOrThrow(transaction.amountEffective);
|
|
|
|
|
2021-11-15 15:18:58 +01:00
|
|
|
return (
|
2023-01-13 20:09:33 +01:00
|
|
|
<TransactionTemplate
|
|
|
|
transaction={transaction}
|
|
|
|
onDelete={onDelete}
|
|
|
|
onRetry={onRetry}
|
|
|
|
onSend={onSend}
|
|
|
|
onCancel={onCancel}
|
|
|
|
>
|
2022-05-26 20:55:14 +02:00
|
|
|
<Header
|
|
|
|
timestamp={transaction.timestamp}
|
|
|
|
type={i18n.str`Tip`}
|
|
|
|
total={total}
|
|
|
|
kind="positive"
|
|
|
|
>
|
|
|
|
{transaction.merchantBaseUrl}
|
|
|
|
</Header>
|
|
|
|
{/* <Part
|
2023-01-09 12:38:48 +01:00
|
|
|
title={i18n.str`Merchant`}
|
2022-08-08 19:09:28 +02:00
|
|
|
text={<MerchantDetails merchant={transaction.merchant} />}
|
2022-05-26 20:55:14 +02:00
|
|
|
kind="neutral"
|
|
|
|
/> */}
|
|
|
|
<Part
|
2023-01-09 12:38:48 +01:00
|
|
|
title={i18n.str`Details`}
|
2022-05-26 20:55:14 +02:00
|
|
|
text={<TipDetails transaction={transaction} />}
|
2021-11-15 15:18:58 +01:00
|
|
|
/>
|
|
|
|
</TransactionTemplate>
|
|
|
|
);
|
2021-06-16 23:21:03 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if (transaction.type === TransactionType.Refund) {
|
2022-05-26 20:55:14 +02:00
|
|
|
const total = Amounts.parseOrThrow(transaction.amountEffective);
|
2021-11-15 15:18:58 +01:00
|
|
|
return (
|
2023-01-13 20:09:33 +01:00
|
|
|
<TransactionTemplate
|
|
|
|
transaction={transaction}
|
|
|
|
onDelete={onDelete}
|
|
|
|
onRetry={onRetry}
|
|
|
|
onSend={onSend}
|
|
|
|
onCancel={onCancel}
|
|
|
|
>
|
2022-05-26 20:55:14 +02:00
|
|
|
<Header
|
|
|
|
timestamp={transaction.timestamp}
|
|
|
|
type={i18n.str`Refund`}
|
|
|
|
total={total}
|
2021-11-15 15:18:58 +01:00
|
|
|
kind="positive"
|
2022-05-26 20:55:14 +02:00
|
|
|
>
|
|
|
|
{transaction.info.summary}
|
|
|
|
</Header>
|
|
|
|
|
2022-02-23 19:18:37 +01:00
|
|
|
<Part
|
2023-01-09 12:38:48 +01:00
|
|
|
title={i18n.str`Merchant`}
|
|
|
|
text={transaction.info.merchant.name as TranslatedString}
|
2021-11-15 15:18:58 +01:00
|
|
|
kind="neutral"
|
|
|
|
/>
|
|
|
|
<Part
|
2023-01-09 12:38:48 +01:00
|
|
|
title={i18n.str`Original order ID`}
|
2022-03-17 19:00:34 +01:00
|
|
|
text={
|
2022-05-14 23:09:33 +02:00
|
|
|
<a
|
2022-06-02 17:20:36 +02:00
|
|
|
href={Pages.balanceTransaction({
|
|
|
|
tid: transaction.refundedTransactionId,
|
|
|
|
})}
|
2022-05-14 23:09:33 +02:00
|
|
|
>
|
2022-05-26 20:55:14 +02:00
|
|
|
{transaction.info.orderId}
|
2022-05-14 23:09:33 +02:00
|
|
|
</a>
|
2022-03-17 19:00:34 +01:00
|
|
|
}
|
2022-02-23 19:18:37 +01:00
|
|
|
kind="neutral"
|
|
|
|
/>
|
|
|
|
<Part
|
2023-01-09 12:38:48 +01:00
|
|
|
title={i18n.str`Purchase summary`}
|
|
|
|
text={transaction.info.summary as TranslatedString}
|
2021-11-15 15:18:58 +01:00
|
|
|
kind="neutral"
|
|
|
|
/>
|
2022-05-26 20:55:14 +02:00
|
|
|
<Part
|
2023-01-09 12:38:48 +01:00
|
|
|
title={i18n.str`Details`}
|
2022-05-26 20:55:14 +02:00
|
|
|
text={<RefundDetails transaction={transaction} />}
|
|
|
|
/>
|
2021-11-15 15:18:58 +01:00
|
|
|
</TransactionTemplate>
|
|
|
|
);
|
|
|
|
}
|
2021-06-16 23:21:03 +02:00
|
|
|
|
2022-08-31 05:20:35 +02:00
|
|
|
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} />
|
2023-01-13 20:09:33 +01:00
|
|
|
<Button onClick={copy as SafeHandler<void>}>
|
2022-09-13 16:07:39 +02:00
|
|
|
<i18n.Translate>copy</i18n.Translate>
|
|
|
|
</Button>
|
2023-01-13 20:09:33 +01:00
|
|
|
<Button onClick={toggle as SafeHandler<void>}>
|
2022-09-13 16:07:39 +02:00
|
|
|
<i18n.Translate>hide qr</i18n.Translate>
|
|
|
|
</Button>
|
2022-08-31 05:20:35 +02:00
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
return (
|
|
|
|
<div>
|
|
|
|
<div>{text.substring(0, 64)}...</div>
|
2023-01-13 20:09:33 +01:00
|
|
|
<Button onClick={copy as SafeHandler<void>}>
|
2022-09-13 16:07:39 +02:00
|
|
|
<i18n.Translate>copy</i18n.Translate>
|
|
|
|
</Button>
|
2023-01-13 20:09:33 +01:00
|
|
|
<Button onClick={toggle as SafeHandler<void>}>
|
2022-09-13 16:07:39 +02:00
|
|
|
<i18n.Translate>show qr</i18n.Translate>
|
|
|
|
</Button>
|
2022-08-31 05:20:35 +02:00
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (transaction.type === TransactionType.PeerPullCredit) {
|
|
|
|
const total = Amounts.parseOrThrow(transaction.amountEffective);
|
|
|
|
return (
|
2023-01-13 20:09:33 +01:00
|
|
|
<TransactionTemplate
|
|
|
|
transaction={transaction}
|
|
|
|
onDelete={onDelete}
|
|
|
|
onRetry={onRetry}
|
|
|
|
onSend={onSend}
|
|
|
|
onCancel={onCancel}
|
|
|
|
>
|
2022-08-31 05:20:35 +02:00
|
|
|
<Header
|
|
|
|
timestamp={transaction.timestamp}
|
|
|
|
type={i18n.str`Credit`}
|
|
|
|
total={total}
|
|
|
|
kind="positive"
|
|
|
|
>
|
2022-09-13 16:07:39 +02:00
|
|
|
<i18n.Translate>Invoice</i18n.Translate>
|
2022-08-31 05:20:35 +02:00
|
|
|
</Header>
|
|
|
|
|
2022-09-01 13:42:18 +02:00
|
|
|
{transaction.info.summary ? (
|
|
|
|
<Part
|
2023-01-09 12:38:48 +01:00
|
|
|
title={i18n.str`Subject`}
|
|
|
|
text={transaction.info.summary as TranslatedString}
|
2022-09-01 13:42:18 +02:00
|
|
|
kind="neutral"
|
|
|
|
/>
|
|
|
|
) : undefined}
|
2022-08-31 05:20:35 +02:00
|
|
|
<Part
|
2023-01-09 12:38:48 +01:00
|
|
|
title={i18n.str`Exchange`}
|
|
|
|
text={transaction.exchangeBaseUrl as TranslatedString}
|
2022-08-31 05:20:35 +02:00
|
|
|
kind="neutral"
|
|
|
|
/>
|
2023-01-13 20:09:33 +01:00
|
|
|
{transaction.extendedStatus ===
|
|
|
|
ExtendedStatus.Pending /** pending is not-pay */ && (
|
2022-09-01 13:42:18 +02:00
|
|
|
<Part
|
2023-01-09 12:38:48 +01:00
|
|
|
title={i18n.str`URI`}
|
2022-09-01 13:42:18 +02:00
|
|
|
text={<ShowQrWithCopy text={transaction.talerUri} />}
|
|
|
|
kind="neutral"
|
|
|
|
/>
|
|
|
|
)}
|
2022-08-31 05:20:35 +02:00
|
|
|
<Part
|
2023-01-09 12:38:48 +01:00
|
|
|
title={i18n.str`Details`}
|
2022-08-31 05:20:35 +02:00
|
|
|
text={
|
|
|
|
<InvoiceDetails
|
|
|
|
amount={{
|
|
|
|
effective: Amounts.parseOrThrow(transaction.amountEffective),
|
|
|
|
raw: Amounts.parseOrThrow(transaction.amountRaw),
|
|
|
|
}}
|
|
|
|
/>
|
|
|
|
}
|
|
|
|
/>
|
|
|
|
</TransactionTemplate>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (transaction.type === TransactionType.PeerPullDebit) {
|
|
|
|
const total = Amounts.parseOrThrow(transaction.amountEffective);
|
|
|
|
return (
|
2023-01-13 20:09:33 +01:00
|
|
|
<TransactionTemplate
|
|
|
|
transaction={transaction}
|
|
|
|
onDelete={onDelete}
|
|
|
|
onRetry={onRetry}
|
|
|
|
onSend={onSend}
|
|
|
|
onCancel={onCancel}
|
|
|
|
>
|
2022-08-31 05:20:35 +02:00
|
|
|
<Header
|
|
|
|
timestamp={transaction.timestamp}
|
|
|
|
type={i18n.str`Debit`}
|
|
|
|
total={total}
|
|
|
|
kind="negative"
|
|
|
|
>
|
2022-09-13 16:07:39 +02:00
|
|
|
<i18n.Translate>Invoice</i18n.Translate>
|
2022-08-31 05:20:35 +02:00
|
|
|
</Header>
|
|
|
|
|
2022-09-01 13:42:18 +02:00
|
|
|
{transaction.info.summary ? (
|
|
|
|
<Part
|
2023-01-09 12:38:48 +01:00
|
|
|
title={i18n.str`Subject`}
|
|
|
|
text={transaction.info.summary as TranslatedString}
|
2022-09-01 13:42:18 +02:00
|
|
|
kind="neutral"
|
|
|
|
/>
|
|
|
|
) : undefined}
|
2022-08-31 05:20:35 +02:00
|
|
|
<Part
|
2023-01-09 12:38:48 +01:00
|
|
|
title={i18n.str`Exchange`}
|
|
|
|
text={transaction.exchangeBaseUrl as TranslatedString}
|
2022-08-31 05:20:35 +02:00
|
|
|
kind="neutral"
|
|
|
|
/>
|
|
|
|
<Part
|
2023-01-09 12:38:48 +01:00
|
|
|
title={i18n.str`Details`}
|
2022-08-31 05:20:35 +02:00
|
|
|
text={
|
|
|
|
<InvoiceDetails
|
|
|
|
amount={{
|
|
|
|
effective: Amounts.parseOrThrow(transaction.amountEffective),
|
|
|
|
raw: Amounts.parseOrThrow(transaction.amountRaw),
|
|
|
|
}}
|
|
|
|
/>
|
|
|
|
}
|
|
|
|
/>
|
|
|
|
</TransactionTemplate>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
if (transaction.type === TransactionType.PeerPushDebit) {
|
|
|
|
const total = Amounts.parseOrThrow(transaction.amountEffective);
|
|
|
|
return (
|
2023-01-13 20:09:33 +01:00
|
|
|
<TransactionTemplate
|
|
|
|
transaction={transaction}
|
|
|
|
onDelete={onDelete}
|
|
|
|
onRetry={onRetry}
|
|
|
|
onSend={onSend}
|
|
|
|
onCancel={onCancel}
|
|
|
|
>
|
2022-08-31 05:20:35 +02:00
|
|
|
<Header
|
|
|
|
timestamp={transaction.timestamp}
|
|
|
|
type={i18n.str`Debit`}
|
|
|
|
total={total}
|
|
|
|
kind="negative"
|
|
|
|
>
|
2022-09-13 16:07:39 +02:00
|
|
|
<i18n.Translate>Transfer</i18n.Translate>
|
2022-08-31 05:20:35 +02:00
|
|
|
</Header>
|
|
|
|
|
2022-09-01 13:42:18 +02:00
|
|
|
{transaction.info.summary ? (
|
|
|
|
<Part
|
2023-01-09 12:38:48 +01:00
|
|
|
title={i18n.str`Subject`}
|
|
|
|
text={transaction.info.summary as TranslatedString}
|
2022-09-01 13:42:18 +02:00
|
|
|
kind="neutral"
|
|
|
|
/>
|
|
|
|
) : undefined}
|
2022-08-31 05:20:35 +02:00
|
|
|
<Part
|
2023-01-09 12:38:48 +01:00
|
|
|
title={i18n.str`Exchange`}
|
|
|
|
text={transaction.exchangeBaseUrl as TranslatedString}
|
2022-08-31 05:20:35 +02:00
|
|
|
kind="neutral"
|
|
|
|
/>
|
2022-09-16 21:04:41 +02:00
|
|
|
{/* {transaction.pending && ( //pending is not-received
|
2022-09-16 21:03:58 +02:00
|
|
|
)} */}
|
2022-09-16 21:04:41 +02:00
|
|
|
<Part
|
2023-01-09 12:38:48 +01:00
|
|
|
title={i18n.str`URI`}
|
2022-09-16 21:04:41 +02:00
|
|
|
text={<ShowQrWithCopy text={transaction.talerUri} />}
|
|
|
|
kind="neutral"
|
|
|
|
/>
|
2022-08-31 05:20:35 +02:00
|
|
|
<Part
|
2023-01-09 12:38:48 +01:00
|
|
|
title={i18n.str`Details`}
|
2022-08-31 05:20:35 +02:00
|
|
|
text={
|
|
|
|
<TransferDetails
|
|
|
|
amount={{
|
|
|
|
effective: Amounts.parseOrThrow(transaction.amountEffective),
|
|
|
|
raw: Amounts.parseOrThrow(transaction.amountRaw),
|
|
|
|
}}
|
|
|
|
/>
|
|
|
|
}
|
|
|
|
/>
|
|
|
|
</TransactionTemplate>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (transaction.type === TransactionType.PeerPushCredit) {
|
|
|
|
const total = Amounts.parseOrThrow(transaction.amountEffective);
|
|
|
|
return (
|
2023-01-13 20:09:33 +01:00
|
|
|
<TransactionTemplate
|
|
|
|
transaction={transaction}
|
|
|
|
onDelete={onDelete}
|
|
|
|
onRetry={onRetry}
|
|
|
|
onSend={onSend}
|
|
|
|
onCancel={onCancel}
|
|
|
|
>
|
2022-08-31 05:20:35 +02:00
|
|
|
<Header
|
|
|
|
timestamp={transaction.timestamp}
|
|
|
|
type={i18n.str`Credit`}
|
|
|
|
total={total}
|
|
|
|
kind="positive"
|
|
|
|
>
|
2022-09-13 16:07:39 +02:00
|
|
|
<i18n.Translate>Transfer</i18n.Translate>
|
2022-08-31 05:20:35 +02:00
|
|
|
</Header>
|
|
|
|
|
2022-09-01 13:42:18 +02:00
|
|
|
{transaction.info.summary ? (
|
|
|
|
<Part
|
2023-01-09 12:38:48 +01:00
|
|
|
title={i18n.str`Subject`}
|
|
|
|
text={transaction.info.summary as TranslatedString}
|
2022-09-01 13:42:18 +02:00
|
|
|
kind="neutral"
|
|
|
|
/>
|
|
|
|
) : undefined}
|
2022-08-31 05:20:35 +02:00
|
|
|
<Part
|
2023-01-09 12:38:48 +01:00
|
|
|
title={i18n.str`Exchange`}
|
|
|
|
text={transaction.exchangeBaseUrl as TranslatedString}
|
2022-08-31 05:20:35 +02:00
|
|
|
kind="neutral"
|
|
|
|
/>
|
|
|
|
<Part
|
2023-01-09 12:38:48 +01:00
|
|
|
title={i18n.str`Details`}
|
2022-08-31 05:20:35 +02:00
|
|
|
text={
|
|
|
|
<TransferDetails
|
|
|
|
amount={{
|
|
|
|
effective: Amounts.parseOrThrow(transaction.amountEffective),
|
|
|
|
raw: Amounts.parseOrThrow(transaction.amountRaw),
|
|
|
|
}}
|
|
|
|
/>
|
|
|
|
}
|
|
|
|
/>
|
|
|
|
</TransactionTemplate>
|
|
|
|
);
|
|
|
|
}
|
2021-11-16 17:59:53 +01:00
|
|
|
return <div />;
|
2021-06-16 23:21:03 +02:00
|
|
|
}
|
2022-05-26 20:55:14 +02:00
|
|
|
|
2022-08-08 19:09:28 +02:00
|
|
|
export function MerchantDetails({
|
|
|
|
merchant,
|
|
|
|
}: {
|
|
|
|
merchant: MerchantInfo;
|
|
|
|
}): VNode {
|
|
|
|
return (
|
|
|
|
<div style={{ display: "flex", flexDirection: "row" }}>
|
|
|
|
{merchant.logo && (
|
|
|
|
<div>
|
|
|
|
<img
|
|
|
|
src={merchant.logo}
|
|
|
|
style={{ width: 64, height: 64, margin: 4 }}
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
)}
|
|
|
|
<div>
|
|
|
|
<p style={{ marginTop: 0 }}>{merchant.name}</p>
|
|
|
|
{merchant.website && (
|
|
|
|
<a
|
|
|
|
href={merchant.website}
|
|
|
|
target="_blank"
|
|
|
|
style={{ textDecorationColor: "gray" }}
|
|
|
|
rel="noreferrer"
|
|
|
|
>
|
|
|
|
<SmallLightText>{merchant.website}</SmallLightText>
|
|
|
|
</a>
|
|
|
|
)}
|
|
|
|
{merchant.email && (
|
|
|
|
<a
|
|
|
|
href={`mailto:${merchant.email}`}
|
|
|
|
style={{ textDecorationColor: "gray" }}
|
|
|
|
>
|
|
|
|
<SmallLightText>{merchant.email}</SmallLightText>
|
|
|
|
</a>
|
|
|
|
)}
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2022-05-26 20:55:14 +02:00
|
|
|
function DeliveryDetails({
|
|
|
|
date,
|
|
|
|
location,
|
|
|
|
}: {
|
|
|
|
date: TalerProtocolTimestamp | undefined;
|
|
|
|
location: Location | undefined;
|
|
|
|
}): VNode {
|
|
|
|
const { i18n } = useTranslationContext();
|
|
|
|
return (
|
|
|
|
<PurchaseDetailsTable>
|
|
|
|
{location && (
|
|
|
|
<Fragment>
|
|
|
|
{location.country && (
|
|
|
|
<tr>
|
|
|
|
<td>
|
|
|
|
<i18n.Translate>Country</i18n.Translate>
|
|
|
|
</td>
|
|
|
|
<td>{location.country}</td>
|
|
|
|
</tr>
|
|
|
|
)}
|
|
|
|
{location.address_lines && (
|
|
|
|
<tr>
|
|
|
|
<td>
|
|
|
|
<i18n.Translate>Address lines</i18n.Translate>
|
|
|
|
</td>
|
|
|
|
<td>{location.address_lines}</td>
|
|
|
|
</tr>
|
|
|
|
)}
|
|
|
|
{location.building_number && (
|
|
|
|
<tr>
|
|
|
|
<td>
|
|
|
|
<i18n.Translate>Building number</i18n.Translate>
|
|
|
|
</td>
|
|
|
|
<td>{location.building_number}</td>
|
|
|
|
</tr>
|
|
|
|
)}
|
|
|
|
{location.building_name && (
|
|
|
|
<tr>
|
|
|
|
<td>
|
|
|
|
<i18n.Translate>Building name</i18n.Translate>
|
|
|
|
</td>
|
|
|
|
<td>{location.building_name}</td>
|
|
|
|
</tr>
|
|
|
|
)}
|
|
|
|
{location.street && (
|
|
|
|
<tr>
|
|
|
|
<td>
|
|
|
|
<i18n.Translate>Street</i18n.Translate>
|
|
|
|
</td>
|
|
|
|
<td>{location.street}</td>
|
|
|
|
</tr>
|
|
|
|
)}
|
|
|
|
{location.post_code && (
|
|
|
|
<tr>
|
|
|
|
<td>
|
|
|
|
<i18n.Translate>Post code</i18n.Translate>
|
|
|
|
</td>
|
|
|
|
<td>{location.post_code}</td>
|
|
|
|
</tr>
|
|
|
|
)}
|
|
|
|
{location.town_location && (
|
|
|
|
<tr>
|
|
|
|
<td>
|
|
|
|
<i18n.Translate>Town location</i18n.Translate>
|
|
|
|
</td>
|
|
|
|
<td>{location.town_location}</td>
|
|
|
|
</tr>
|
|
|
|
)}
|
|
|
|
{location.town && (
|
|
|
|
<tr>
|
|
|
|
<td>
|
|
|
|
<i18n.Translate>Town</i18n.Translate>
|
|
|
|
</td>
|
|
|
|
<td>{location.town}</td>
|
|
|
|
</tr>
|
|
|
|
)}
|
|
|
|
{location.district && (
|
|
|
|
<tr>
|
|
|
|
<td>
|
|
|
|
<i18n.Translate>District</i18n.Translate>
|
|
|
|
</td>
|
|
|
|
<td>{location.district}</td>
|
|
|
|
</tr>
|
|
|
|
)}
|
|
|
|
{location.country_subdivision && (
|
|
|
|
<tr>
|
|
|
|
<td>
|
|
|
|
<i18n.Translate>Country subdivision</i18n.Translate>
|
|
|
|
</td>
|
|
|
|
<td>{location.country_subdivision}</td>
|
|
|
|
</tr>
|
|
|
|
)}
|
|
|
|
</Fragment>
|
|
|
|
)}
|
|
|
|
|
|
|
|
{!location || !date ? undefined : (
|
|
|
|
<tr>
|
|
|
|
<td colSpan={2}>
|
|
|
|
<hr />
|
|
|
|
</td>
|
|
|
|
</tr>
|
|
|
|
)}
|
|
|
|
{date && (
|
|
|
|
<Fragment>
|
|
|
|
<tr>
|
2022-09-13 16:07:39 +02:00
|
|
|
<td>
|
|
|
|
<i18n.Translate>Date</i18n.Translate>
|
|
|
|
</td>
|
2022-05-26 20:55:14 +02:00
|
|
|
<td>
|
|
|
|
<Time
|
|
|
|
timestamp={AbsoluteTime.fromTimestamp(date)}
|
|
|
|
format="dd MMMM yyyy, HH:mm"
|
|
|
|
/>
|
|
|
|
</td>
|
|
|
|
</tr>
|
|
|
|
</Fragment>
|
|
|
|
)}
|
|
|
|
</PurchaseDetailsTable>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2022-08-10 16:50:46 +02:00
|
|
|
export function ExchangeDetails({ exchange }: { exchange: string }): VNode {
|
|
|
|
return (
|
|
|
|
<div>
|
|
|
|
<p style={{ marginTop: 0 }}>
|
|
|
|
<a rel="noreferrer" target="_blank" href={exchange}>
|
|
|
|
{exchange}
|
|
|
|
</a>
|
|
|
|
</p>
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2022-08-08 19:09:28 +02:00
|
|
|
export interface AmountWithFee {
|
|
|
|
effective: AmountJson;
|
|
|
|
raw: AmountJson;
|
|
|
|
}
|
2022-08-10 16:50:46 +02:00
|
|
|
|
2022-08-31 05:20:35 +02:00
|
|
|
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]
|
|
|
|
.map((a) => Amounts.maxFractionalDigits(a))
|
|
|
|
.reduce((c, p) => Math.max(c, p), 0);
|
|
|
|
|
|
|
|
return (
|
|
|
|
<PurchaseDetailsTable>
|
|
|
|
<tr>
|
2022-09-13 16:07:39 +02:00
|
|
|
<td>
|
|
|
|
<i18n.Translate>Invoice</i18n.Translate>
|
|
|
|
</td>
|
2022-08-31 05:20:35 +02:00
|
|
|
<td>
|
|
|
|
<Amount value={amount.raw} maxFracSize={maxFrac} />
|
|
|
|
</td>
|
|
|
|
</tr>
|
|
|
|
|
|
|
|
{Amounts.isNonZero(fee) && (
|
|
|
|
<tr>
|
2022-09-13 16:07:39 +02:00
|
|
|
<td>
|
|
|
|
<i18n.Translate>Transaction fees</i18n.Translate>
|
|
|
|
</td>
|
2022-08-31 05:20:35 +02:00
|
|
|
<td>
|
|
|
|
<Amount value={fee} negative maxFracSize={maxFrac} />
|
|
|
|
</td>
|
|
|
|
</tr>
|
|
|
|
)}
|
|
|
|
<tr>
|
|
|
|
<td colSpan={2}>
|
|
|
|
<hr />
|
|
|
|
</td>
|
|
|
|
</tr>
|
|
|
|
<tr>
|
2022-09-13 16:07:39 +02:00
|
|
|
<td>
|
|
|
|
<i18n.Translate>Total</i18n.Translate>
|
|
|
|
</td>
|
2022-08-31 05:20:35 +02:00
|
|
|
<td>
|
|
|
|
<Amount value={amount.effective} maxFracSize={maxFrac} />
|
|
|
|
</td>
|
|
|
|
</tr>
|
|
|
|
</PurchaseDetailsTable>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
export function TransferDetails({ amount }: { amount: AmountWithFee }): VNode {
|
|
|
|
const { i18n } = useTranslationContext();
|
|
|
|
|
|
|
|
const fee = Amounts.sub(amount.raw, amount.effective).amount;
|
|
|
|
|
|
|
|
const maxFrac = [amount.raw, amount.effective, fee]
|
|
|
|
.map((a) => Amounts.maxFractionalDigits(a))
|
|
|
|
.reduce((c, p) => Math.max(c, p), 0);
|
|
|
|
|
|
|
|
return (
|
|
|
|
<PurchaseDetailsTable>
|
|
|
|
<tr>
|
2022-09-13 16:07:39 +02:00
|
|
|
<td>
|
|
|
|
<i18n.Translate>Transfer</i18n.Translate>
|
|
|
|
</td>
|
2022-08-31 05:20:35 +02:00
|
|
|
<td>
|
|
|
|
<Amount value={amount.raw} maxFracSize={maxFrac} />
|
|
|
|
</td>
|
|
|
|
</tr>
|
|
|
|
|
|
|
|
{Amounts.isNonZero(fee) && (
|
|
|
|
<tr>
|
2022-09-13 16:07:39 +02:00
|
|
|
<td>
|
|
|
|
<i18n.Translate>Transaction fees</i18n.Translate>
|
|
|
|
</td>
|
2022-08-31 05:20:35 +02:00
|
|
|
<td>
|
|
|
|
<Amount value={fee} negative maxFracSize={maxFrac} />
|
|
|
|
</td>
|
|
|
|
</tr>
|
|
|
|
)}
|
|
|
|
<tr>
|
|
|
|
<td colSpan={2}>
|
|
|
|
<hr />
|
|
|
|
</td>
|
|
|
|
</tr>
|
|
|
|
<tr>
|
2022-09-13 16:07:39 +02:00
|
|
|
<td>
|
|
|
|
<i18n.Translate>Total</i18n.Translate>
|
|
|
|
</td>
|
2022-08-31 05:20:35 +02:00
|
|
|
<td>
|
|
|
|
<Amount value={amount.effective} maxFracSize={maxFrac} />
|
|
|
|
</td>
|
|
|
|
</tr>
|
|
|
|
</PurchaseDetailsTable>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2022-08-10 16:50:46 +02:00
|
|
|
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]
|
|
|
|
.map((a) => Amounts.maxFractionalDigits(a))
|
|
|
|
.reduce((c, p) => Math.max(c, p), 0);
|
|
|
|
|
|
|
|
return (
|
|
|
|
<PurchaseDetailsTable>
|
|
|
|
<tr>
|
2022-09-13 16:07:39 +02:00
|
|
|
<td>
|
|
|
|
<i18n.Translate>Withdraw</i18n.Translate>
|
|
|
|
</td>
|
2022-08-10 16:50:46 +02:00
|
|
|
<td>
|
|
|
|
<Amount value={amount.raw} maxFracSize={maxFrac} />
|
|
|
|
</td>
|
|
|
|
</tr>
|
|
|
|
|
|
|
|
{Amounts.isNonZero(fee) && (
|
|
|
|
<tr>
|
2022-09-13 16:07:39 +02:00
|
|
|
<td>
|
|
|
|
<i18n.Translate>Transaction fees</i18n.Translate>
|
|
|
|
</td>
|
2022-08-10 16:50:46 +02:00
|
|
|
<td>
|
|
|
|
<Amount value={fee} negative maxFracSize={maxFrac} />
|
|
|
|
</td>
|
|
|
|
</tr>
|
|
|
|
)}
|
|
|
|
<tr>
|
|
|
|
<td colSpan={2}>
|
|
|
|
<hr />
|
|
|
|
</td>
|
|
|
|
</tr>
|
|
|
|
<tr>
|
2022-09-13 16:07:39 +02:00
|
|
|
<td>
|
|
|
|
<i18n.Translate>Total</i18n.Translate>
|
|
|
|
</td>
|
2022-08-10 16:50:46 +02:00
|
|
|
<td>
|
|
|
|
<Amount value={amount.effective} maxFracSize={maxFrac} />
|
|
|
|
</td>
|
|
|
|
</tr>
|
|
|
|
</PurchaseDetailsTable>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2022-08-08 19:09:28 +02:00
|
|
|
export function PurchaseDetails({
|
|
|
|
price,
|
|
|
|
refund,
|
|
|
|
info,
|
|
|
|
proposalId,
|
2022-05-26 20:55:14 +02:00
|
|
|
}: {
|
2022-08-08 19:09:28 +02:00
|
|
|
price: AmountWithFee;
|
|
|
|
refund?: AmountWithFee;
|
|
|
|
info: OrderShortInfo;
|
|
|
|
proposalId: string;
|
2022-05-26 20:55:14 +02:00
|
|
|
}): VNode {
|
|
|
|
const { i18n } = useTranslationContext();
|
|
|
|
|
2022-08-08 19:09:28 +02:00
|
|
|
const partialFee = Amounts.sub(price.effective, price.raw).amount;
|
2022-05-26 20:55:14 +02:00
|
|
|
|
2022-08-08 19:09:28 +02:00
|
|
|
const refundFee = !refund
|
2022-11-02 17:42:14 +01:00
|
|
|
? Amounts.zeroOfCurrency(price.effective.currency)
|
2022-08-08 19:09:28 +02:00
|
|
|
: Amounts.sub(refund.raw, refund.effective).amount;
|
2022-05-26 20:55:14 +02:00
|
|
|
|
|
|
|
const fee = Amounts.sum([partialFee, refundFee]).amount;
|
|
|
|
|
2022-08-08 19:09:28 +02:00
|
|
|
const hasProducts = info.products && info.products.length > 0;
|
2022-05-26 20:55:14 +02:00
|
|
|
|
|
|
|
const hasShipping =
|
2022-08-08 19:09:28 +02:00
|
|
|
info.delivery_date !== undefined || info.delivery_location !== undefined;
|
2022-05-26 20:55:14 +02:00
|
|
|
|
|
|
|
const showLargePic = (): void => {
|
|
|
|
return;
|
|
|
|
};
|
|
|
|
|
2022-08-08 19:09:28 +02:00
|
|
|
const total = !refund
|
|
|
|
? price.effective
|
|
|
|
: Amounts.sub(price.effective, refund.effective).amount;
|
2022-05-26 20:55:14 +02:00
|
|
|
|
|
|
|
return (
|
|
|
|
<PurchaseDetailsTable>
|
|
|
|
<tr>
|
2022-09-13 16:07:39 +02:00
|
|
|
<td>
|
|
|
|
<i18n.Translate>Price</i18n.Translate>
|
|
|
|
</td>
|
2022-05-26 20:55:14 +02:00
|
|
|
<td>
|
2022-08-08 19:09:28 +02:00
|
|
|
<Amount value={price.raw} />
|
2022-05-26 20:55:14 +02:00
|
|
|
</td>
|
|
|
|
</tr>
|
|
|
|
|
2022-08-08 19:09:28 +02:00
|
|
|
{refund && Amounts.isNonZero(refund.raw) && (
|
2022-05-26 20:55:14 +02:00
|
|
|
<tr>
|
2022-09-13 16:07:39 +02:00
|
|
|
<td>
|
|
|
|
<i18n.Translate>Refunded</i18n.Translate>
|
|
|
|
</td>
|
2022-05-26 20:55:14 +02:00
|
|
|
<td>
|
2022-08-08 19:09:28 +02:00
|
|
|
<Amount value={refund.raw} negative />
|
2022-05-26 20:55:14 +02:00
|
|
|
</td>
|
|
|
|
</tr>
|
|
|
|
)}
|
|
|
|
{Amounts.isNonZero(fee) && (
|
|
|
|
<tr>
|
2022-09-13 16:07:39 +02:00
|
|
|
<td>
|
|
|
|
<i18n.Translate>Transaction fees</i18n.Translate>
|
|
|
|
</td>
|
2022-05-26 20:55:14 +02:00
|
|
|
<td>
|
|
|
|
<Amount value={fee} />
|
|
|
|
</td>
|
|
|
|
</tr>
|
|
|
|
)}
|
|
|
|
<tr>
|
|
|
|
<td colSpan={2}>
|
|
|
|
<hr />
|
|
|
|
</td>
|
|
|
|
</tr>
|
|
|
|
<tr>
|
2022-09-13 16:07:39 +02:00
|
|
|
<td>
|
|
|
|
<i18n.Translate>Total</i18n.Translate>
|
|
|
|
</td>
|
2022-05-26 20:55:14 +02:00
|
|
|
<td>
|
|
|
|
<Amount value={total} />
|
|
|
|
</td>
|
|
|
|
</tr>
|
|
|
|
{hasProducts && (
|
|
|
|
<tr>
|
|
|
|
<td colSpan={2}>
|
|
|
|
<PartCollapsible
|
|
|
|
big
|
2023-01-09 12:38:48 +01:00
|
|
|
title={i18n.str`Products`}
|
2022-05-26 20:55:14 +02:00
|
|
|
text={
|
|
|
|
<ListOfProducts>
|
2022-08-08 19:09:28 +02:00
|
|
|
{info.products?.map((p, k) => (
|
2022-05-26 20:55:14 +02:00
|
|
|
<Row 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>
|
|
|
|
</Row>
|
|
|
|
))}
|
|
|
|
</ListOfProducts>
|
|
|
|
}
|
|
|
|
/>
|
|
|
|
</td>
|
|
|
|
</tr>
|
|
|
|
)}
|
|
|
|
{hasShipping && (
|
|
|
|
<tr>
|
|
|
|
<td colSpan={2}>
|
|
|
|
<PartCollapsible
|
|
|
|
big
|
2023-01-09 12:38:48 +01:00
|
|
|
title={i18n.str`Delivery`}
|
2022-05-26 20:55:14 +02:00
|
|
|
text={
|
|
|
|
<DeliveryDetails
|
2022-08-08 19:09:28 +02:00
|
|
|
date={info.delivery_date}
|
|
|
|
location={info.delivery_location}
|
2022-05-26 20:55:14 +02:00
|
|
|
/>
|
|
|
|
}
|
|
|
|
/>
|
|
|
|
</td>
|
|
|
|
</tr>
|
|
|
|
)}
|
2022-08-08 19:09:28 +02:00
|
|
|
<tr>
|
|
|
|
<td>
|
|
|
|
<ShowFullContractTermPopup proposalId={proposalId} />
|
|
|
|
</td>
|
|
|
|
</tr>
|
2022-05-26 20:55:14 +02:00
|
|
|
</PurchaseDetailsTable>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
function RefundDetails({
|
|
|
|
transaction,
|
|
|
|
}: {
|
|
|
|
transaction: TransactionRefund;
|
|
|
|
}): VNode {
|
|
|
|
const { i18n } = useTranslationContext();
|
|
|
|
|
2022-06-09 18:37:33 +02:00
|
|
|
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);
|
2022-05-26 20:55:14 +02:00
|
|
|
|
|
|
|
return (
|
|
|
|
<PurchaseDetailsTable>
|
|
|
|
<tr>
|
2022-09-13 16:07:39 +02:00
|
|
|
<td>
|
|
|
|
<i18n.Translate>Amount</i18n.Translate>
|
|
|
|
</td>
|
2022-05-26 20:55:14 +02:00
|
|
|
<td>
|
2022-06-09 18:37:33 +02:00
|
|
|
<Amount value={transaction.amountRaw} maxFracSize={maxFrac} />
|
2022-05-26 20:55:14 +02:00
|
|
|
</td>
|
|
|
|
</tr>
|
|
|
|
|
|
|
|
{Amounts.isNonZero(fee) && (
|
|
|
|
<tr>
|
2022-09-13 16:07:39 +02:00
|
|
|
<td>
|
|
|
|
<i18n.Translate>Transaction fees</i18n.Translate>
|
|
|
|
</td>
|
2022-05-26 20:55:14 +02:00
|
|
|
<td>
|
2022-06-09 18:37:33 +02:00
|
|
|
<Amount value={fee} negative maxFracSize={maxFrac} />
|
2022-05-26 20:55:14 +02:00
|
|
|
</td>
|
|
|
|
</tr>
|
|
|
|
)}
|
|
|
|
<tr>
|
|
|
|
<td colSpan={2}>
|
|
|
|
<hr />
|
|
|
|
</td>
|
|
|
|
</tr>
|
|
|
|
<tr>
|
2022-09-13 16:07:39 +02:00
|
|
|
<td>
|
|
|
|
<i18n.Translate>Total</i18n.Translate>
|
|
|
|
</td>
|
2022-05-26 20:55:14 +02:00
|
|
|
<td>
|
2022-06-09 18:37:33 +02:00
|
|
|
<Amount value={transaction.amountEffective} maxFracSize={maxFrac} />
|
2022-05-26 20:55:14 +02:00
|
|
|
</td>
|
|
|
|
</tr>
|
|
|
|
</PurchaseDetailsTable>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
function DepositDetails({
|
|
|
|
transaction,
|
|
|
|
}: {
|
|
|
|
transaction: TransactionDeposit;
|
|
|
|
}): VNode {
|
|
|
|
const { i18n } = useTranslationContext();
|
2022-06-09 18:37:33 +02:00
|
|
|
const r = Amounts.parseOrThrow(transaction.amountRaw);
|
|
|
|
const e = Amounts.parseOrThrow(transaction.amountEffective);
|
2022-11-07 23:29:47 +01:00
|
|
|
const fee = Amounts.sub(e, r).amount;
|
2022-05-26 20:55:14 +02:00
|
|
|
|
2022-06-09 18:37:33 +02:00
|
|
|
const maxFrac = [r, e, fee]
|
|
|
|
.map((a) => Amounts.maxFractionalDigits(a))
|
|
|
|
.reduce((c, p) => Math.max(c, p), 0);
|
2022-05-26 20:55:14 +02:00
|
|
|
|
|
|
|
return (
|
|
|
|
<PurchaseDetailsTable>
|
|
|
|
<tr>
|
2022-09-13 16:07:39 +02:00
|
|
|
<td>
|
|
|
|
<i18n.Translate>Amount</i18n.Translate>
|
|
|
|
</td>
|
2022-05-26 20:55:14 +02:00
|
|
|
<td>
|
2022-06-09 18:37:33 +02:00
|
|
|
<Amount value={transaction.amountRaw} maxFracSize={maxFrac} />
|
2022-05-26 20:55:14 +02:00
|
|
|
</td>
|
|
|
|
</tr>
|
|
|
|
|
|
|
|
{Amounts.isNonZero(fee) && (
|
|
|
|
<tr>
|
2022-09-13 16:07:39 +02:00
|
|
|
<td>
|
|
|
|
<i18n.Translate>Transaction fees</i18n.Translate>
|
|
|
|
</td>
|
2022-05-26 20:55:14 +02:00
|
|
|
<td>
|
2022-11-07 23:29:47 +01:00
|
|
|
<Amount value={fee} maxFracSize={maxFrac} />
|
2022-05-26 20:55:14 +02:00
|
|
|
</td>
|
|
|
|
</tr>
|
|
|
|
)}
|
|
|
|
<tr>
|
|
|
|
<td colSpan={2}>
|
|
|
|
<hr />
|
|
|
|
</td>
|
|
|
|
</tr>
|
|
|
|
<tr>
|
2022-09-13 16:07:39 +02:00
|
|
|
<td>
|
|
|
|
<i18n.Translate>Total transfer</i18n.Translate>
|
|
|
|
</td>
|
2022-05-26 20:55:14 +02:00
|
|
|
<td>
|
2022-06-09 18:37:33 +02:00
|
|
|
<Amount value={transaction.amountEffective} maxFracSize={maxFrac} />
|
2022-05-26 20:55:14 +02:00
|
|
|
</td>
|
|
|
|
</tr>
|
|
|
|
</PurchaseDetailsTable>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
function RefreshDetails({
|
|
|
|
transaction,
|
|
|
|
}: {
|
|
|
|
transaction: TransactionRefresh;
|
|
|
|
}): VNode {
|
|
|
|
const { i18n } = useTranslationContext();
|
|
|
|
|
2022-06-09 18:37:33 +02:00
|
|
|
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);
|
2022-05-26 20:55:14 +02:00
|
|
|
|
|
|
|
return (
|
|
|
|
<PurchaseDetailsTable>
|
|
|
|
<tr>
|
2022-09-13 16:07:39 +02:00
|
|
|
<td>
|
|
|
|
<i18n.Translate>Amount</i18n.Translate>
|
|
|
|
</td>
|
2022-05-26 20:55:14 +02:00
|
|
|
<td>
|
2022-06-09 18:37:33 +02:00
|
|
|
<Amount value={transaction.amountRaw} maxFracSize={maxFrac} />
|
|
|
|
</td>
|
|
|
|
</tr>
|
|
|
|
<tr>
|
2022-09-13 16:07:39 +02:00
|
|
|
<td>
|
|
|
|
<i18n.Translate>Transaction fees</i18n.Translate>
|
|
|
|
</td>
|
2022-06-09 18:37:33 +02:00
|
|
|
<td>
|
|
|
|
<Amount value={fee} negative maxFracSize={maxFrac} />
|
2022-05-26 20:55:14 +02:00
|
|
|
</td>
|
|
|
|
</tr>
|
|
|
|
<tr>
|
|
|
|
<td colSpan={2}>
|
|
|
|
<hr />
|
|
|
|
</td>
|
|
|
|
</tr>
|
|
|
|
<tr>
|
2022-09-13 16:07:39 +02:00
|
|
|
<td>
|
|
|
|
<i18n.Translate>Total</i18n.Translate>
|
|
|
|
</td>
|
2022-05-26 20:55:14 +02:00
|
|
|
<td>
|
2022-06-09 18:37:33 +02:00
|
|
|
<Amount value={transaction.amountEffective} maxFracSize={maxFrac} />
|
2022-05-26 20:55:14 +02:00
|
|
|
</td>
|
|
|
|
</tr>
|
|
|
|
</PurchaseDetailsTable>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
function TipDetails({ transaction }: { transaction: TransactionTip }): VNode {
|
|
|
|
const { i18n } = useTranslationContext();
|
|
|
|
|
2022-06-09 18:37:33 +02:00
|
|
|
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);
|
2022-05-26 20:55:14 +02:00
|
|
|
|
|
|
|
return (
|
|
|
|
<PurchaseDetailsTable>
|
|
|
|
<tr>
|
2022-09-13 16:07:39 +02:00
|
|
|
<td>
|
|
|
|
<i18n.Translate>Amount</i18n.Translate>
|
|
|
|
</td>
|
2022-05-26 20:55:14 +02:00
|
|
|
<td>
|
2022-06-09 18:37:33 +02:00
|
|
|
<Amount value={transaction.amountRaw} maxFracSize={maxFrac} />
|
2022-05-26 20:55:14 +02:00
|
|
|
</td>
|
|
|
|
</tr>
|
|
|
|
|
|
|
|
{Amounts.isNonZero(fee) && (
|
|
|
|
<tr>
|
2022-09-13 16:07:39 +02:00
|
|
|
<td>
|
|
|
|
<i18n.Translate>Transaction fees</i18n.Translate>
|
|
|
|
</td>
|
2022-05-26 20:55:14 +02:00
|
|
|
<td>
|
2022-06-09 18:37:33 +02:00
|
|
|
<Amount value={fee} negative maxFracSize={maxFrac} />
|
2022-05-26 20:55:14 +02:00
|
|
|
</td>
|
|
|
|
</tr>
|
|
|
|
)}
|
|
|
|
<tr>
|
|
|
|
<td colSpan={2}>
|
|
|
|
<hr />
|
|
|
|
</td>
|
|
|
|
</tr>
|
|
|
|
<tr>
|
2022-09-13 16:07:39 +02:00
|
|
|
<td>
|
|
|
|
<i18n.Translate>Total</i18n.Translate>
|
|
|
|
</td>
|
2022-05-26 20:55:14 +02:00
|
|
|
<td>
|
2022-06-09 18:37:33 +02:00
|
|
|
<Amount value={transaction.amountEffective} maxFracSize={maxFrac} />
|
2022-05-26 20:55:14 +02:00
|
|
|
</td>
|
|
|
|
</tr>
|
|
|
|
</PurchaseDetailsTable>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
function Header({
|
|
|
|
timestamp,
|
|
|
|
total,
|
|
|
|
children,
|
|
|
|
kind,
|
|
|
|
type,
|
|
|
|
}: {
|
|
|
|
timestamp: TalerProtocolTimestamp;
|
|
|
|
total: AmountJson;
|
|
|
|
children: ComponentChildren;
|
|
|
|
kind: Kind;
|
2023-01-09 12:38:48 +01:00
|
|
|
type: TranslatedString;
|
2022-05-26 20:55:14 +02:00
|
|
|
}): VNode {
|
|
|
|
return (
|
|
|
|
<div
|
|
|
|
style={{
|
|
|
|
display: "flex",
|
|
|
|
justifyContent: "space-between",
|
|
|
|
flexDirection: "row",
|
|
|
|
}}
|
|
|
|
>
|
|
|
|
<div>
|
|
|
|
<SubTitle>{children}</SubTitle>
|
|
|
|
<Time
|
|
|
|
timestamp={AbsoluteTime.fromTimestamp(timestamp)}
|
|
|
|
format="dd MMMM yyyy, HH:mm"
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
<div>
|
|
|
|
<SubTitle>
|
|
|
|
<Part
|
|
|
|
title={type}
|
2022-06-09 18:37:33 +02:00
|
|
|
text={<Amount value={total} negative={kind === "negative"} />}
|
2022-05-26 20:55:14 +02:00
|
|
|
kind={kind}
|
|
|
|
/>
|
|
|
|
</SubTitle>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
2022-05-29 06:23:15 +02:00
|
|
|
|
|
|
|
function NicePayto({ payto }: { payto: PaytoUri }): VNode {
|
|
|
|
if (payto.isKnown) {
|
|
|
|
switch (payto.targetType) {
|
|
|
|
case "bitcoin": {
|
|
|
|
return <div>{payto.targetPath.substring(0, 20)}...</div>;
|
|
|
|
}
|
|
|
|
case "x-taler-bank": {
|
|
|
|
const url = new URL("/", `https://${payto.host}`);
|
|
|
|
return (
|
|
|
|
<Fragment>
|
|
|
|
<div>{payto.account}</div>
|
|
|
|
<SmallLightText>
|
|
|
|
<a href={url.href} target="_bank" rel="noreferrer">
|
|
|
|
{url.toString()}
|
|
|
|
</a>
|
|
|
|
</SmallLightText>
|
|
|
|
</Fragment>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
case "iban": {
|
|
|
|
return <div>{payto.targetPath.substring(0, 20)}</div>;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return <Fragment>{stringifyPaytoUri(payto)}</Fragment>;
|
|
|
|
}
|