/*
This file is part of GNU Taler
(C) 2022 Taler Systems S.A.
GNU 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.
GNU 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
GNU Taler; see the file COPYING. If not, see
*/
import {
AbsoluteTime,
AmountJson,
Amounts,
Location,
MerchantInfo,
NotificationType,
OrderShortInfo,
parsePaytoUri,
PaytoUri,
stringifyPaytoUri,
TalerProtocolTimestamp,
Transaction,
TransactionDeposit,
TransactionRefresh,
TransactionRefund,
TransactionTip,
TransactionType,
TranslatedString,
WithdrawalType,
} from "@gnu-taler/taler-util";
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { styled } from "@linaria/react";
import { differenceInSeconds } from "date-fns";
import { ComponentChildren, Fragment, h, VNode } from "preact";
import { useEffect, useState } from "preact/hooks";
import emptyImg from "../../static/img/empty.png";
import { Amount } from "../components/Amount.js";
import { BankDetailsByPaytoType } from "../components/BankDetailsByPaytoType.js";
import { CopyButton } from "../components/CopyButton.js";
import { AlertView, ErrorAlertView } from "../components/CurrentAlerts.js";
import { Loading } from "../components/Loading.js";
import { Kind, Part, PartCollapsible, PartPayto } from "../components/Part.js";
import { QR } from "../components/QR.js";
import { ShowFullContractTermPopup } from "../components/ShowFullContractTermPopup.js";
import {
CenteredDialog,
InfoBox,
ListOfProducts,
Overlay,
Row,
SmallLightText,
SubTitle,
WarningBox,
} from "../components/styled/index.js";
import { Time } from "../components/Time.js";
import { alertFromError } from "../context/alert.js";
import { useBackendContext } from "../context/backend.js";
import { useTranslationContext } from "../context/translation.js";
import { useAsyncAsHook } from "../hooks/useAsyncAsHook.js";
import { Button } from "../mui/Button.js";
import { Pages } from "../NavigationBar.js";
interface Props {
tid: string;
goToWalletHistory: (currency?: string) => Promise;
}
export function TransactionPage({
tid: transactionId,
goToWalletHistory,
}: Props): VNode {
const { i18n } = useTranslationContext();
const api = useBackendContext();
const state = useAsyncAsHook(
() =>
api.wallet.call(WalletApiOperation.GetTransactionById, {
transactionId,
}),
[transactionId],
);
useEffect(() =>
api.listener.onUpdateNotification(
[NotificationType.WithdrawGroupFinished],
state?.retry,
),
);
if (!state) {
return ;
}
if (state.hasError) {
return (
);
}
const currency = Amounts.parse(state.response.amountRaw)?.currency;
return (
{
null;
}}
onDelete={async () => {
await api.wallet.call(WalletApiOperation.DeleteTransaction, {
transactionId,
});
goToWalletHistory(currency);
}}
onRetry={async () => {
await api.wallet.call(WalletApiOperation.RetryTransaction, {
transactionId,
});
goToWalletHistory(currency);
}}
onRefund={async (purchaseId) => {
await api.wallet.call(WalletApiOperation.ApplyRefundFromPurchaseId, {
purchaseId,
});
}}
onBack={() => goToWalletHistory(currency)}
/>
);
}
export interface WalletTransactionProps {
transaction: Transaction;
onSend: () => Promise;
onDelete: () => Promise;
onRetry: () => Promise;
onRefund: (id: string) => Promise;
onBack: () => Promise;
}
const PurchaseDetailsTable = styled.table`
width: 100%;
& > tr > td:nth-child(2n) {
text-align: right;
}
`;
export function TransactionView({
transaction,
onDelete,
onRetry,
onSend,
onRefund,
}: WalletTransactionProps): VNode {
const [confirmBeforeForget, setConfirmBeforeForget] = useState(false);
async function doCheckBeforeForget(): Promise {
if (
transaction.pending &&
transaction.type === TransactionType.Withdrawal
) {
setConfirmBeforeForget(true);
} else {
onDelete();
}
}
const SHOWING_RETRY_THRESHOLD_SECS = 30;
const { i18n } = useTranslationContext();
function TransactionTemplate({
children,
}: {
children: ComponentChildren;
}): VNode {
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.pending &&
differenceInSeconds(new Date(), transaction.timestamp.t_s * 1000) >
SHOWING_RETRY_THRESHOLD_SECS);
return (
{transaction?.error ? (
) : undefined}
{transaction.pending && (
This transaction is not completed
)}
{children}
);
}
if (transaction.type === TransactionType.Withdrawal) {
const total = Amounts.parseOrThrow(transaction.amountEffective);
const chosen = Amounts.parseOrThrow(transaction.amountRaw);
return (
{confirmBeforeForget ? (
Caution!
If you have already wired money to the exchange you will loose
the chance to get the coins form it.
) : undefined}
{transaction.exchangeBaseUrl}
{!transaction.pending ? undefined : transaction.withdrawalDetails
.type === WithdrawalType.ManualTransfer ? (
Make sure to use the correct subject, otherwise the money will
not arrive in this wallet.
) : (
{!transaction.withdrawalDetails.confirmed &&
transaction.withdrawalDetails.bankConfirmationUrl ? (
Wire transfer need a confirmation. Go to the
bank site{" "}
and check wire transfer operation to exchange account is
complete.
) : undefined}
{transaction.withdrawalDetails.confirmed && (
Bank has confirmed the wire transfer. Waiting for the exchange
to send the coins
)}
)}
}
/>
);
}
if (transaction.type === TransactionType.Payment) {
const pendingRefund =
transaction.refundPending === undefined
? undefined
: Amounts.parseOrThrow(transaction.refundPending);
const price = {
raw: Amounts.parseOrThrow(transaction.amountRaw),
effective: Amounts.parseOrThrow(transaction.amountEffective),
};
const refund = {
raw: Amounts.parseOrThrow(transaction.totalRefundRaw),
effective: Amounts.parseOrThrow(transaction.totalRefundEffective),
};
const total = Amounts.sub(price.effective, refund.effective).amount;
return (
{transaction.info.fulfillmentUrl ? (
{transaction.info.summary}
) : (
transaction.info.summary
)}
{transaction.refunds.length > 0 ? (
{transaction.refunds.map((r, i) => {
return (
);
})}
}
kind="neutral"
/>
) : undefined}
{pendingRefund !== undefined && Amounts.isNonZero(pendingRefund) && (
Merchant created a refund for this order but was not automatically
picked up.
}
kind="positive"
/>