p2p tx rendering
This commit is contained in:
parent
7dc66c2441
commit
d84424202d
@ -471,7 +471,7 @@ async function getMergeReserveInfo(
|
||||
export async function acceptPeerPushPayment(
|
||||
ws: InternalWalletState,
|
||||
req: AcceptPeerPushPaymentRequest,
|
||||
) {
|
||||
): Promise<void> {
|
||||
const peerInc = await ws.db
|
||||
.mktx((x) => ({ peerPushPaymentIncoming: x.peerPushPaymentIncoming }))
|
||||
.runReadOnly(async (tx) => {
|
||||
|
@ -108,16 +108,20 @@ export const Pages = {
|
||||
"/settings/exchange/add/:currency?",
|
||||
),
|
||||
|
||||
invoice: pageDefinition<{ amount?: string }>("/invoice/:amount?"),
|
||||
send: pageDefinition<{ amount?: string }>("/send/:amount?"),
|
||||
|
||||
cta: pageDefinition<{ action: string }>("/cta/:action"),
|
||||
ctaPay: "/cta/pay",
|
||||
ctaRefund: "/cta/refund",
|
||||
ctaTips: "/cta/tip",
|
||||
ctaWithdraw: "/cta/withdraw",
|
||||
ctaDeposit: "/cta/deposit",
|
||||
|
||||
ctaInvoiceCreate: pageDefinition<{ amount?: string }>(
|
||||
"/cta/invoice/create/:amount?",
|
||||
),
|
||||
ctaTransferCreate: pageDefinition<{ amount?: string }>(
|
||||
"/cta/transfer/create/:amount?",
|
||||
),
|
||||
ctaInvoicePay: "/cta/invoice/pay",
|
||||
ctaTransferPickup: "/cta/transfer/pickup",
|
||||
ctaWithdrawManual: pageDefinition<{ amount?: string }>(
|
||||
"/cta/manual-withdraw/:amount?",
|
||||
),
|
||||
|
@ -113,8 +113,58 @@ export function TransactionItem(props: { tx: Transaction }): VNode {
|
||||
pending={tx.pending}
|
||||
/>
|
||||
);
|
||||
default:
|
||||
throw Error("unsupported transaction type");
|
||||
case TransactionType.PeerPullCredit:
|
||||
return (
|
||||
<TransactionLayout
|
||||
id={tx.transactionId}
|
||||
amount={tx.amountEffective}
|
||||
debitCreditIndicator={"credit"}
|
||||
title={"Invoice credit"}
|
||||
timestamp={AbsoluteTime.fromTimestamp(tx.timestamp)}
|
||||
iconPath={"I"}
|
||||
pending={tx.pending}
|
||||
/>
|
||||
);
|
||||
case TransactionType.PeerPullDebit:
|
||||
return (
|
||||
<TransactionLayout
|
||||
id={tx.transactionId}
|
||||
amount={tx.amountEffective}
|
||||
debitCreditIndicator={"debit"}
|
||||
title={"Invoice debit"}
|
||||
timestamp={AbsoluteTime.fromTimestamp(tx.timestamp)}
|
||||
iconPath={"I"}
|
||||
pending={tx.pending}
|
||||
/>
|
||||
);
|
||||
case TransactionType.PeerPushCredit:
|
||||
return (
|
||||
<TransactionLayout
|
||||
id={tx.transactionId}
|
||||
amount={tx.amountEffective}
|
||||
debitCreditIndicator={"credit"}
|
||||
title={"Transfer credit"}
|
||||
timestamp={AbsoluteTime.fromTimestamp(tx.timestamp)}
|
||||
iconPath={"T"}
|
||||
pending={tx.pending}
|
||||
/>
|
||||
);
|
||||
case TransactionType.PeerPushDebit:
|
||||
return (
|
||||
<TransactionLayout
|
||||
id={tx.transactionId}
|
||||
amount={tx.amountEffective}
|
||||
debitCreditIndicator={"debit"}
|
||||
title={"Transfer debit"}
|
||||
timestamp={AbsoluteTime.fromTimestamp(tx.timestamp)}
|
||||
iconPath={"T"}
|
||||
pending={tx.pending}
|
||||
/>
|
||||
);
|
||||
default: {
|
||||
const pe: never = tx;
|
||||
throw Error(`unsupported transaction type ${pe}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,79 @@
|
||||
/*
|
||||
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 <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
import { Loading } from "../../components/Loading.js";
|
||||
import { HookError } from "../../hooks/useAsyncAsHook.js";
|
||||
import { compose, StateViewMap } from "../../utils/index.js";
|
||||
import { LoadingUriView, ReadyView, ShowQrView } from "./views.js";
|
||||
import * as wxApi from "../../wxApi.js";
|
||||
import { useComponentState } from "./state.js";
|
||||
import { AmountJson, TalerErrorDetail } from "@gnu-taler/taler-util";
|
||||
import { ButtonHandler, SelectFieldHandler, TextFieldHandler } from "../../mui/handlers.js";
|
||||
|
||||
export interface Props {
|
||||
amount: string;
|
||||
}
|
||||
|
||||
export type State =
|
||||
| State.Loading
|
||||
| State.LoadingUriError
|
||||
| State.ShowQr
|
||||
| State.Ready;
|
||||
|
||||
export namespace State {
|
||||
|
||||
export interface Loading {
|
||||
status: "loading";
|
||||
error: undefined;
|
||||
}
|
||||
|
||||
export interface LoadingUriError {
|
||||
status: "loading-uri";
|
||||
error: HookError;
|
||||
}
|
||||
|
||||
export interface BaseInfo {
|
||||
error: undefined;
|
||||
}
|
||||
export interface ShowQr extends BaseInfo {
|
||||
status: "show-qr";
|
||||
talerUri: string;
|
||||
close: () => void;
|
||||
}
|
||||
export interface Ready extends BaseInfo {
|
||||
status: "ready";
|
||||
showQr: ButtonHandler;
|
||||
copyToClipboard: ButtonHandler;
|
||||
subject: TextFieldHandler;
|
||||
toBeReceived: AmountJson,
|
||||
chosenAmount: AmountJson,
|
||||
exchangeUrl: string;
|
||||
invalid: boolean;
|
||||
error: undefined;
|
||||
operationError?: TalerErrorDetail;
|
||||
}
|
||||
}
|
||||
|
||||
const viewMapping: StateViewMap<State> = {
|
||||
loading: Loading,
|
||||
"loading-uri": LoadingUriView,
|
||||
"show-qr": ShowQrView,
|
||||
"ready": ReadyView,
|
||||
};
|
||||
|
||||
|
||||
export const InvoiceCreatePage = compose("InvoiceCreatePage", (p: Props) => useComponentState(p, wxApi), viewMapping)
|
||||
|
@ -0,0 +1,113 @@
|
||||
/*
|
||||
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 <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
import { Amounts, TalerErrorDetail } from "@gnu-taler/taler-util";
|
||||
import { TalerError } from "@gnu-taler/taler-wallet-core";
|
||||
import { useState } from "preact/hooks";
|
||||
import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js";
|
||||
import * as wxApi from "../../wxApi.js";
|
||||
import { Props, State } from "./index.js";
|
||||
|
||||
export function useComponentState(
|
||||
{ amount: amountStr }: Props,
|
||||
api: typeof wxApi,
|
||||
): State {
|
||||
const amount = Amounts.parseOrThrow(amountStr)
|
||||
|
||||
const [subject, setSubject] = useState("");
|
||||
const [talerUri, setTalerUri] = useState("")
|
||||
|
||||
const hook = useAsyncAsHook(api.listExchanges);
|
||||
const [exchangeIdx, setExchangeIdx] = useState("0")
|
||||
const [operationError, setOperationError] = useState<TalerErrorDetail | undefined>(undefined)
|
||||
|
||||
|
||||
if (!hook) {
|
||||
return {
|
||||
status: "loading",
|
||||
error: undefined,
|
||||
}
|
||||
}
|
||||
if (hook.hasError) {
|
||||
return {
|
||||
status: "loading-uri",
|
||||
error: hook,
|
||||
};
|
||||
}
|
||||
|
||||
if (talerUri) {
|
||||
return {
|
||||
status: "show-qr",
|
||||
talerUri,
|
||||
error: undefined,
|
||||
close: () => { null },
|
||||
// chosenAmount: amount,
|
||||
// toBeReceived: amount,
|
||||
}
|
||||
}
|
||||
|
||||
const exchanges = hook.response.exchanges.filter(e => e.currency === amount.currency);
|
||||
const exchangeMap = exchanges.reduce((prev, cur, idx) => ({ ...prev, [String(idx)]: cur.exchangeBaseUrl }), {} as Record<string, string>)
|
||||
const selected = exchanges[Number(exchangeIdx)];
|
||||
|
||||
async function accept(): Promise<string> {
|
||||
try {
|
||||
|
||||
const resp = await api.initiatePeerPullPayment({
|
||||
amount: Amounts.stringify(amount),
|
||||
exchangeBaseUrl: selected.exchangeBaseUrl,
|
||||
partialContractTerms: {
|
||||
summary: subject
|
||||
}
|
||||
})
|
||||
return resp.talerUri
|
||||
} catch (e) {
|
||||
if (e instanceof TalerError) {
|
||||
setOperationError(e.errorDetail)
|
||||
}
|
||||
console.error(e)
|
||||
throw Error("error trying to accept")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return {
|
||||
status: "ready",
|
||||
subject: {
|
||||
error: !subject ? "cant be empty" : undefined,
|
||||
value: subject,
|
||||
onInput: async (e) => setSubject(e)
|
||||
},
|
||||
invalid: !subject || Amounts.isZero(amount),
|
||||
exchangeUrl: selected.exchangeBaseUrl,
|
||||
copyToClipboard: {
|
||||
onClick: async () => {
|
||||
const uri = await accept();
|
||||
navigator.clipboard.writeText(uri || "");
|
||||
}
|
||||
},
|
||||
showQr: {
|
||||
onClick: async () => {
|
||||
const uri = await accept();
|
||||
setTalerUri(uri)
|
||||
}
|
||||
},
|
||||
chosenAmount: amount,
|
||||
toBeReceived: amount,
|
||||
error: undefined,
|
||||
operationError
|
||||
}
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
/*
|
||||
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 <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Sebastian Javier Marchano (sebasjm)
|
||||
*/
|
||||
|
||||
import { createExample } from "../../test-utils.js";
|
||||
import { ReadyView, ShowQrView } from "./views.js";
|
||||
|
||||
export default {
|
||||
title: "wallet/invoice create",
|
||||
};
|
||||
|
||||
export const ShowQr = createExample(ShowQrView, {
|
||||
talerUri:
|
||||
"taler://pay-pull/exchange.taler.ar/HS585JK0QCXHJ8Z8QWZA3EBAY5WY7XNC1RR2MHJXSH2Z4WP0YPJ0",
|
||||
close: () => {
|
||||
null;
|
||||
},
|
||||
});
|
||||
|
||||
export const Ready = createExample(ReadyView, {
|
||||
chosenAmount: {
|
||||
currency: "ARS",
|
||||
value: 1,
|
||||
fraction: 0,
|
||||
},
|
||||
toBeReceived: {
|
||||
currency: "ARS",
|
||||
value: 1,
|
||||
fraction: 0,
|
||||
},
|
||||
exchangeUrl: "https://exchange.taler.ar",
|
||||
subject: {
|
||||
value: "some subject",
|
||||
onInput: async () => {
|
||||
null;
|
||||
},
|
||||
},
|
||||
copyToClipboard: {},
|
||||
showQr: {},
|
||||
});
|
@ -0,0 +1,150 @@
|
||||
/*
|
||||
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 <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
import { h, VNode } from "preact";
|
||||
import { LoadingError } from "../../components/LoadingError.js";
|
||||
import { LogoHeader } from "../../components/LogoHeader.js";
|
||||
import { Part } from "../../components/Part.js";
|
||||
import { QR } from "../../components/QR.js";
|
||||
import {
|
||||
Link,
|
||||
SubTitle,
|
||||
SvgIcon,
|
||||
WalletAction,
|
||||
} from "../../components/styled/index.js";
|
||||
import { useTranslationContext } from "../../context/translation.js";
|
||||
import { Button } from "../../mui/Button.js";
|
||||
import { Grid } from "../../mui/Grid.js";
|
||||
import { TextField } from "../../mui/TextField.js";
|
||||
import editIcon from "../../svg/edit_24px.svg";
|
||||
import { ExchangeDetails, InvoiceDetails } from "../../wallet/Transaction.js";
|
||||
import { State } from "./index.js";
|
||||
|
||||
export function LoadingUriView({ error }: State.LoadingUriError): VNode {
|
||||
const { i18n } = useTranslationContext();
|
||||
|
||||
return (
|
||||
<LoadingError
|
||||
title={<i18n.Translate>Could not load</i18n.Translate>}
|
||||
error={error}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function ShowQrView({ talerUri, close }: State.ShowQr): VNode {
|
||||
const { i18n } = useTranslationContext();
|
||||
return (
|
||||
<WalletAction>
|
||||
<LogoHeader />
|
||||
<SubTitle>
|
||||
<i18n.Translate>Digital invoice</i18n.Translate>
|
||||
</SubTitle>
|
||||
<section>
|
||||
<p>Scan this QR code with the wallet</p>
|
||||
<QR text={talerUri} />
|
||||
</section>
|
||||
<section>
|
||||
<Link upperCased onClick={close}>
|
||||
<i18n.Translate>Close</i18n.Translate>
|
||||
</Link>
|
||||
</section>
|
||||
</WalletAction>
|
||||
);
|
||||
}
|
||||
|
||||
export function ReadyView({
|
||||
invalid,
|
||||
exchangeUrl,
|
||||
subject,
|
||||
showQr,
|
||||
operationError,
|
||||
copyToClipboard,
|
||||
toBeReceived,
|
||||
chosenAmount,
|
||||
}: State.Ready): VNode {
|
||||
const { i18n } = useTranslationContext();
|
||||
|
||||
return (
|
||||
<WalletAction>
|
||||
<LogoHeader />
|
||||
<SubTitle>
|
||||
<i18n.Translate>Digital invoice</i18n.Translate>
|
||||
</SubTitle>
|
||||
<section style={{ textAlign: "left" }}>
|
||||
<TextField
|
||||
label="Subject"
|
||||
variant="filled"
|
||||
error={!!subject.error}
|
||||
required
|
||||
fullWidth
|
||||
value={subject.value}
|
||||
onChange={subject.onInput}
|
||||
/>
|
||||
|
||||
<Part
|
||||
title={
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<i18n.Translate>Exchange</i18n.Translate>
|
||||
<Link>
|
||||
<SvgIcon
|
||||
title="Edit"
|
||||
dangerouslySetInnerHTML={{ __html: editIcon }}
|
||||
color="black"
|
||||
/>
|
||||
</Link>
|
||||
</div>
|
||||
}
|
||||
text={<ExchangeDetails exchange={exchangeUrl} />}
|
||||
kind="neutral"
|
||||
big
|
||||
/>
|
||||
|
||||
<Part
|
||||
title={<i18n.Translate>Details</i18n.Translate>}
|
||||
text={
|
||||
<InvoiceDetails
|
||||
amount={{
|
||||
effective: toBeReceived,
|
||||
raw: chosenAmount,
|
||||
}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</section>
|
||||
<section>
|
||||
<p>How do you want to send the invoice?</p>
|
||||
|
||||
<Grid item container columns={1} spacing={1}>
|
||||
<Grid item xs={1}>
|
||||
<Button disabled={invalid} onClick={copyToClipboard.onClick}>
|
||||
Copy request URI to clipboard
|
||||
</Button>
|
||||
</Grid>
|
||||
<Grid item xs={1}>
|
||||
<Button disabled={invalid} onClick={showQr.onClick}>
|
||||
Show QR
|
||||
</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</section>
|
||||
</WalletAction>
|
||||
);
|
||||
}
|
@ -14,17 +14,17 @@
|
||||
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
import { AmountJson, TalerErrorDetail } from "@gnu-taler/taler-util";
|
||||
import { Loading } from "../../components/Loading.js";
|
||||
import { HookError } from "../../hooks/useAsyncAsHook.js";
|
||||
import { ButtonHandler } from "../../mui/handlers.js";
|
||||
import { compose, StateViewMap } from "../../utils/index.js";
|
||||
import { LoadingUriView, ReadyView } from "./views.js";
|
||||
import * as wxApi from "../../wxApi.js";
|
||||
import { useComponentState } from "./state.js";
|
||||
import { AmountJson } from "@gnu-taler/taler-util";
|
||||
import { TextFieldHandler } from "../../mui/handlers.js";
|
||||
import { LoadingUriView, ReadyView } from "./views.js";
|
||||
|
||||
export interface Props {
|
||||
p: string;
|
||||
talerPayPullUri: string;
|
||||
}
|
||||
|
||||
export type State =
|
||||
@ -49,9 +49,10 @@ export namespace State {
|
||||
}
|
||||
export interface Ready extends BaseInfo {
|
||||
status: "ready";
|
||||
amount: AmountJson;
|
||||
subject: TextFieldHandler;
|
||||
amount: AmountJson,
|
||||
error: undefined;
|
||||
accept: ButtonHandler;
|
||||
operationError?: TalerErrorDetail;
|
||||
}
|
||||
}
|
||||
|
||||
@ -62,5 +63,5 @@ const viewMapping: StateViewMap<State> = {
|
||||
};
|
||||
|
||||
|
||||
export const InvoicePage = compose("InvoicePage", (p: Props) => useComponentState(p, wxApi), viewMapping)
|
||||
export const InvoicePayPage = compose("InvoicePayPage", (p: Props) => useComponentState(p, wxApi), viewMapping)
|
||||
|
@ -14,21 +14,23 @@
|
||||
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
import { Amounts } from "@gnu-taler/taler-util";
|
||||
import { Amounts, TalerErrorDetail } from "@gnu-taler/taler-util";
|
||||
import { TalerError } from "@gnu-taler/taler-wallet-core";
|
||||
import { useState } from "preact/hooks";
|
||||
import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js";
|
||||
import * as wxApi from "../../wxApi.js";
|
||||
import { Props, State } from "./index.js";
|
||||
|
||||
export function useComponentState(
|
||||
{ p }: Props,
|
||||
{ talerPayPullUri }: Props,
|
||||
api: typeof wxApi,
|
||||
): State {
|
||||
const [subject, setSubject] = useState("");
|
||||
const amount = Amounts.parseOrThrow("ARS:0")
|
||||
|
||||
const hook = useAsyncAsHook(api.listExchanges);
|
||||
const [exchangeIdx, setExchangeIdx] = useState("0")
|
||||
const hook = useAsyncAsHook(async () => {
|
||||
return await api.checkPeerPullPayment({
|
||||
talerUri: talerPayPullUri
|
||||
})
|
||||
}, [])
|
||||
const [operationError, setOperationError] = useState<TalerErrorDetail | undefined>(undefined)
|
||||
|
||||
if (!hook) {
|
||||
return {
|
||||
@ -43,24 +45,30 @@ export function useComponentState(
|
||||
};
|
||||
}
|
||||
|
||||
const exchanges = hook.response.exchanges;
|
||||
const exchangeMap = exchanges.reduce((prev, cur, idx) => ({ ...prev, [cur.exchangeBaseUrl]: String(idx) }), {} as Record<string, string>)
|
||||
const selected = exchanges[Number(exchangeIdx)];
|
||||
const { amount, peerPullPaymentIncomingId } = hook.response
|
||||
|
||||
async function accept(): Promise<void> {
|
||||
try {
|
||||
const resp = await api.acceptPeerPullPayment({
|
||||
peerPullPaymentIncomingId
|
||||
})
|
||||
} catch (e) {
|
||||
if (e instanceof TalerError) {
|
||||
setOperationError(e.errorDetail)
|
||||
}
|
||||
console.error(e)
|
||||
throw Error("error trying to accept")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return {
|
||||
status: "ready",
|
||||
exchange: {
|
||||
list: exchangeMap,
|
||||
value: exchangeIdx,
|
||||
onChange: async (v) => {
|
||||
setExchangeIdx(v)
|
||||
}
|
||||
},
|
||||
subject: {
|
||||
value: subject,
|
||||
onInput: async (e) => setSubject(e)
|
||||
},
|
||||
amount,
|
||||
amount: Amounts.parseOrThrow(amount),
|
||||
error: undefined,
|
||||
accept: {
|
||||
onClick: accept
|
||||
},
|
||||
operationError
|
||||
}
|
||||
}
|
@ -23,7 +23,14 @@ import { createExample } from "../../test-utils.js";
|
||||
import { ReadyView } from "./views.js";
|
||||
|
||||
export default {
|
||||
title: "wallet/invoice",
|
||||
title: "wallet/invoice payment",
|
||||
};
|
||||
|
||||
export const Ready = createExample(ReadyView, {});
|
||||
export const Ready = createExample(ReadyView, {
|
||||
amount: {
|
||||
currency: "ARS",
|
||||
value: 1,
|
||||
fraction: 0,
|
||||
},
|
||||
accept: {},
|
||||
});
|
@ -0,0 +1,85 @@
|
||||
/*
|
||||
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 <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
import { h, VNode } from "preact";
|
||||
import { Amount } from "../../components/Amount.js";
|
||||
import { ErrorTalerOperation } from "../../components/ErrorTalerOperation.js";
|
||||
import { LoadingError } from "../../components/LoadingError.js";
|
||||
import { LogoHeader } from "../../components/LogoHeader.js";
|
||||
import { Part } from "../../components/Part.js";
|
||||
import { QR } from "../../components/QR.js";
|
||||
import {
|
||||
Link,
|
||||
SubTitle,
|
||||
SvgIcon,
|
||||
WalletAction,
|
||||
} from "../../components/styled/index.js";
|
||||
import { useTranslationContext } from "../../context/translation.js";
|
||||
import { Button } from "../../mui/Button.js";
|
||||
import { Grid } from "../../mui/Grid.js";
|
||||
import { TextField } from "../../mui/TextField.js";
|
||||
import editIcon from "../../svg/edit_24px.svg";
|
||||
import { ExchangeDetails, InvoiceDetails } from "../../wallet/Transaction.js";
|
||||
import { State } from "./index.js";
|
||||
|
||||
export function LoadingUriView({ error }: State.LoadingUriError): VNode {
|
||||
const { i18n } = useTranslationContext();
|
||||
|
||||
return (
|
||||
<LoadingError
|
||||
title={<i18n.Translate>Could not load</i18n.Translate>}
|
||||
error={error}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function ReadyView({
|
||||
operationError,
|
||||
accept,
|
||||
amount,
|
||||
}: State.Ready): VNode {
|
||||
const { i18n } = useTranslationContext();
|
||||
|
||||
return (
|
||||
<WalletAction>
|
||||
<LogoHeader />
|
||||
<SubTitle>
|
||||
<i18n.Translate>Digital invoice</i18n.Translate>
|
||||
</SubTitle>
|
||||
{operationError && (
|
||||
<ErrorTalerOperation
|
||||
title={
|
||||
<i18n.Translate>
|
||||
Could not finish the payment operation
|
||||
</i18n.Translate>
|
||||
}
|
||||
error={operationError}
|
||||
/>
|
||||
)}
|
||||
<section style={{ textAlign: "left" }}>
|
||||
<Part
|
||||
title={<i18n.Translate>Amount</i18n.Translate>}
|
||||
text={<Amount value={amount} />}
|
||||
/>
|
||||
</section>
|
||||
<section>
|
||||
<Button variant="contained" color="success" onClick={accept.onClick}>
|
||||
<i18n.Translate>Pay</i18n.Translate>
|
||||
</Button>
|
||||
</section>
|
||||
</WalletAction>
|
||||
);
|
||||
}
|
@ -0,0 +1,78 @@
|
||||
/*
|
||||
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 <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
import { Loading } from "../../components/Loading.js";
|
||||
import { HookError } from "../../hooks/useAsyncAsHook.js";
|
||||
import { compose, StateViewMap } from "../../utils/index.js";
|
||||
import { LoadingUriView, ReadyView, ShowQrView } from "./views.js";
|
||||
import * as wxApi from "../../wxApi.js";
|
||||
import { useComponentState } from "./state.js";
|
||||
import { AmountJson, TalerErrorDetail } from "@gnu-taler/taler-util";
|
||||
import { ButtonHandler, SelectFieldHandler, TextFieldHandler } from "../../mui/handlers.js";
|
||||
|
||||
export interface Props {
|
||||
amount: string;
|
||||
}
|
||||
|
||||
export type State =
|
||||
| State.Loading
|
||||
| State.LoadingUriError
|
||||
| State.ShowQr
|
||||
| State.Ready;
|
||||
|
||||
export namespace State {
|
||||
|
||||
export interface Loading {
|
||||
status: "loading";
|
||||
error: undefined;
|
||||
}
|
||||
|
||||
export interface LoadingUriError {
|
||||
status: "loading-uri";
|
||||
error: HookError;
|
||||
}
|
||||
|
||||
export interface BaseInfo {
|
||||
error: undefined;
|
||||
}
|
||||
export interface ShowQr extends BaseInfo {
|
||||
status: "show-qr";
|
||||
talerUri: string;
|
||||
close: () => void;
|
||||
}
|
||||
export interface Ready extends BaseInfo {
|
||||
status: "ready";
|
||||
showQr: ButtonHandler;
|
||||
invalid: boolean;
|
||||
copyToClipboard: ButtonHandler;
|
||||
toBeReceived: AmountJson,
|
||||
chosenAmount: AmountJson,
|
||||
subject: TextFieldHandler,
|
||||
error: undefined;
|
||||
operationError?: TalerErrorDetail;
|
||||
}
|
||||
}
|
||||
|
||||
const viewMapping: StateViewMap<State> = {
|
||||
loading: Loading,
|
||||
"loading-uri": LoadingUriView,
|
||||
"show-qr": ShowQrView,
|
||||
"ready": ReadyView,
|
||||
};
|
||||
|
||||
|
||||
export const TransferCreatePage = compose("TransferCreatePage", (p: Props) => useComponentState(p, wxApi), viewMapping)
|
||||
|
@ -0,0 +1,86 @@
|
||||
/*
|
||||
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 <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
import { Amounts, TalerErrorDetail } from "@gnu-taler/taler-util";
|
||||
import { TalerError } from "@gnu-taler/taler-wallet-core";
|
||||
import { useState } from "preact/hooks";
|
||||
import * as wxApi from "../../wxApi.js";
|
||||
import { Props, State } from "./index.js";
|
||||
|
||||
export function useComponentState(
|
||||
{ amount: amountStr }: Props,
|
||||
api: typeof wxApi,
|
||||
): State {
|
||||
const amount = Amounts.parseOrThrow(amountStr)
|
||||
|
||||
const [subject, setSubject] = useState("");
|
||||
const [talerUri, setTalerUri] = useState("")
|
||||
const [operationError, setOperationError] = useState<TalerErrorDetail | undefined>(undefined)
|
||||
|
||||
|
||||
if (talerUri) {
|
||||
return {
|
||||
status: "show-qr",
|
||||
talerUri,
|
||||
error: undefined,
|
||||
close: () => { null },
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async function accept(): Promise<string> {
|
||||
try {
|
||||
|
||||
const resp = await api.initiatePeerPushPayment({
|
||||
amount: Amounts.stringify(amount),
|
||||
partialContractTerms: {
|
||||
summary: subject
|
||||
}
|
||||
})
|
||||
return resp.talerUri
|
||||
} catch (e) {
|
||||
if (e instanceof TalerError) {
|
||||
setOperationError(e.errorDetail)
|
||||
}
|
||||
console.error(e)
|
||||
throw Error("error trying to accept")
|
||||
}
|
||||
}
|
||||
return {
|
||||
status: "ready",
|
||||
invalid: !subject || Amounts.isZero(amount),
|
||||
subject: {
|
||||
value: subject,
|
||||
onInput: async (e) => setSubject(e)
|
||||
},
|
||||
copyToClipboard: {
|
||||
onClick: async () => {
|
||||
const uri = await accept();
|
||||
navigator.clipboard.writeText(uri || "");
|
||||
}
|
||||
},
|
||||
showQr: {
|
||||
onClick: async () => {
|
||||
const uri = await accept();
|
||||
setTalerUri(uri)
|
||||
}
|
||||
},
|
||||
chosenAmount: amount,
|
||||
toBeReceived: amount,
|
||||
error: undefined,
|
||||
operationError
|
||||
}
|
||||
}
|
@ -0,0 +1,56 @@
|
||||
/*
|
||||
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 <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Sebastian Javier Marchano (sebasjm)
|
||||
*/
|
||||
|
||||
import { createExample } from "../../test-utils.js";
|
||||
import { ReadyView, ShowQrView } from "./views.js";
|
||||
|
||||
export default {
|
||||
title: "wallet/transfer create",
|
||||
};
|
||||
|
||||
export const ShowQr = createExample(ShowQrView, {
|
||||
talerUri:
|
||||
"taler://pay-push/exchange.taler.ar/HS585JK0QCXHJ8Z8QWZA3EBAY5WY7XNC1RR2MHJXSH2Z4WP0YPJ0",
|
||||
close: () => {
|
||||
null;
|
||||
},
|
||||
});
|
||||
|
||||
export const Ready = createExample(ReadyView, {
|
||||
chosenAmount: {
|
||||
currency: "ARS",
|
||||
value: 1,
|
||||
fraction: 0,
|
||||
},
|
||||
toBeReceived: {
|
||||
currency: "ARS",
|
||||
value: 1,
|
||||
fraction: 0,
|
||||
},
|
||||
copyToClipboard: {},
|
||||
showQr: {},
|
||||
subject: {
|
||||
value: "the subject",
|
||||
onInput: async () => {
|
||||
null;
|
||||
},
|
||||
},
|
||||
});
|
@ -14,24 +14,18 @@
|
||||
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
import { Amounts } from "@gnu-taler/taler-util";
|
||||
import { useState } from "preact/hooks";
|
||||
import * as wxApi from "../../wxApi.js";
|
||||
import { Props, State } from "./index.js";
|
||||
/**
|
||||
*
|
||||
* @author Sebastian Javier Marchano (sebasjm)
|
||||
*/
|
||||
|
||||
import { expect } from "chai";
|
||||
|
||||
describe("test description", () => {
|
||||
|
||||
it("should assert", () => {
|
||||
|
||||
expect([]).deep.equals([])
|
||||
});
|
||||
})
|
||||
|
||||
export function useComponentState(
|
||||
{ p }: Props,
|
||||
api: typeof wxApi,
|
||||
): State {
|
||||
const [subject, setSubject] = useState("");
|
||||
const amount = Amounts.parseOrThrow("ARS:0")
|
||||
return {
|
||||
status: "ready",
|
||||
subject: {
|
||||
value: subject,
|
||||
onInput: async (e) => setSubject(e)
|
||||
},
|
||||
amount,
|
||||
error: undefined,
|
||||
}
|
||||
}
|
@ -0,0 +1,117 @@
|
||||
/*
|
||||
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 <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
import { h, VNode } from "preact";
|
||||
import { LoadingError } from "../../components/LoadingError.js";
|
||||
import { LogoHeader } from "../../components/LogoHeader.js";
|
||||
import { Part } from "../../components/Part.js";
|
||||
import { QR } from "../../components/QR.js";
|
||||
import { Link, SubTitle, WalletAction } from "../../components/styled/index.js";
|
||||
import { useTranslationContext } from "../../context/translation.js";
|
||||
import { Button } from "../../mui/Button.js";
|
||||
import { Grid } from "../../mui/Grid.js";
|
||||
import { TextField } from "../../mui/TextField.js";
|
||||
import { TransferDetails } from "../../wallet/Transaction.js";
|
||||
import { State } from "./index.js";
|
||||
|
||||
export function LoadingUriView({ error }: State.LoadingUriError): VNode {
|
||||
const { i18n } = useTranslationContext();
|
||||
|
||||
return (
|
||||
<LoadingError
|
||||
title={<i18n.Translate>Could not load</i18n.Translate>}
|
||||
error={error}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function ShowQrView({ talerUri, close }: State.ShowQr): VNode {
|
||||
const { i18n } = useTranslationContext();
|
||||
return (
|
||||
<WalletAction>
|
||||
<LogoHeader />
|
||||
<SubTitle>
|
||||
<i18n.Translate>Digital invoice</i18n.Translate>
|
||||
</SubTitle>
|
||||
<section>
|
||||
<p>Scan this QR code with the wallet</p>
|
||||
<QR text={talerUri} />
|
||||
</section>
|
||||
<section>
|
||||
<Link upperCased onClick={close}>
|
||||
<i18n.Translate>Close</i18n.Translate>
|
||||
</Link>
|
||||
</section>
|
||||
</WalletAction>
|
||||
);
|
||||
}
|
||||
|
||||
export function ReadyView({
|
||||
subject,
|
||||
toBeReceived,
|
||||
chosenAmount,
|
||||
showQr,
|
||||
copyToClipboard,
|
||||
invalid,
|
||||
}: State.Ready): VNode {
|
||||
const { i18n } = useTranslationContext();
|
||||
return (
|
||||
<WalletAction>
|
||||
<LogoHeader />
|
||||
<SubTitle>
|
||||
<i18n.Translate>Digital cash transfer</i18n.Translate>
|
||||
</SubTitle>
|
||||
<section style={{ textAlign: "left" }}>
|
||||
<TextField
|
||||
label="Subject"
|
||||
variant="filled"
|
||||
error={!!subject.error}
|
||||
required
|
||||
fullWidth
|
||||
value={subject.value}
|
||||
onChange={subject.onInput}
|
||||
/>
|
||||
<Part
|
||||
title={<i18n.Translate>Details</i18n.Translate>}
|
||||
text={
|
||||
<TransferDetails
|
||||
amount={{
|
||||
effective: toBeReceived,
|
||||
raw: chosenAmount,
|
||||
}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</section>
|
||||
<section>
|
||||
<p>How do you want to transfer?</p>
|
||||
|
||||
<Grid item container columns={1} spacing={1}>
|
||||
<Grid item xs={1}>
|
||||
<Button disabled={invalid} onClick={copyToClipboard.onClick}>
|
||||
Copy transfer URI to clipboard
|
||||
</Button>
|
||||
</Grid>
|
||||
<Grid item xs={1}>
|
||||
<Button disabled={invalid} onClick={showQr.onClick}>
|
||||
Show QR
|
||||
</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</section>
|
||||
</WalletAction>
|
||||
);
|
||||
}
|
@ -14,17 +14,17 @@
|
||||
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
import { AmountJson, TalerErrorDetail } from "@gnu-taler/taler-util";
|
||||
import { Loading } from "../../components/Loading.js";
|
||||
import { HookError } from "../../hooks/useAsyncAsHook.js";
|
||||
import { ButtonHandler } from "../../mui/handlers.js";
|
||||
import { compose, StateViewMap } from "../../utils/index.js";
|
||||
import { LoadingUriView, ReadyView } from "./views.js";
|
||||
import * as wxApi from "../../wxApi.js";
|
||||
import { useComponentState } from "./state.js";
|
||||
import { AmountJson } from "@gnu-taler/taler-util";
|
||||
import { SelectFieldHandler, TextFieldHandler } from "../../mui/handlers.js";
|
||||
import { LoadingUriView, ReadyView } from "./views.js";
|
||||
|
||||
export interface Props {
|
||||
p: string;
|
||||
talerPayPushUri: string;
|
||||
}
|
||||
|
||||
export type State =
|
||||
@ -49,10 +49,10 @@ export namespace State {
|
||||
}
|
||||
export interface Ready extends BaseInfo {
|
||||
status: "ready";
|
||||
amount: AmountJson;
|
||||
exchange: SelectFieldHandler,
|
||||
subject: TextFieldHandler,
|
||||
amount: AmountJson,
|
||||
error: undefined;
|
||||
accept: ButtonHandler;
|
||||
operationError?: TalerErrorDetail;
|
||||
}
|
||||
}
|
||||
|
||||
@ -63,5 +63,5 @@ const viewMapping: StateViewMap<State> = {
|
||||
};
|
||||
|
||||
|
||||
export const SendPage = compose("SendPage", (p: Props) => useComponentState(p, wxApi), viewMapping)
|
||||
export const TransferPickupPage = compose("TransferPickupPage", (p: Props) => useComponentState(p, wxApi), viewMapping)
|
||||
|
@ -0,0 +1,72 @@
|
||||
/*
|
||||
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 <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
import { Amounts, TalerErrorDetail } from "@gnu-taler/taler-util";
|
||||
import { TalerError } from "@gnu-taler/taler-wallet-core";
|
||||
import { useState } from "preact/hooks";
|
||||
import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js";
|
||||
import * as wxApi from "../../wxApi.js";
|
||||
import { Props, State } from "./index.js";
|
||||
|
||||
export function useComponentState(
|
||||
{ talerPayPushUri }: Props,
|
||||
api: typeof wxApi,
|
||||
): State {
|
||||
const hook = useAsyncAsHook(async () => {
|
||||
return await api.checkPeerPushPayment({
|
||||
talerUri: talerPayPushUri,
|
||||
})
|
||||
}, [])
|
||||
const [operationError, setOperationError] = useState<TalerErrorDetail | undefined>(undefined)
|
||||
|
||||
if (!hook) {
|
||||
return {
|
||||
status: "loading",
|
||||
error: undefined,
|
||||
}
|
||||
}
|
||||
if (hook.hasError) {
|
||||
return {
|
||||
status: "loading-uri",
|
||||
error: hook,
|
||||
};
|
||||
}
|
||||
|
||||
const { amount, peerPushPaymentIncomingId } = hook.response
|
||||
|
||||
async function accept(): Promise<void> {
|
||||
try {
|
||||
const resp = await api.acceptPeerPushPayment({
|
||||
peerPushPaymentIncomingId
|
||||
})
|
||||
} catch (e) {
|
||||
if (e instanceof TalerError) {
|
||||
setOperationError(e.errorDetail)
|
||||
}
|
||||
console.error(e)
|
||||
throw Error("error trying to accept")
|
||||
}
|
||||
}
|
||||
return {
|
||||
status: "ready",
|
||||
amount: Amounts.parseOrThrow(amount),
|
||||
error: undefined,
|
||||
accept: {
|
||||
onClick: accept
|
||||
},
|
||||
operationError
|
||||
}
|
||||
}
|
@ -23,7 +23,14 @@ import { createExample } from "../../test-utils.js";
|
||||
import { ReadyView } from "./views.js";
|
||||
|
||||
export default {
|
||||
title: "wallet/invoice",
|
||||
title: "wallet/transfer pickup",
|
||||
};
|
||||
|
||||
export const Ready = createExample(ReadyView, {});
|
||||
export const Ready = createExample(ReadyView, {
|
||||
amount: {
|
||||
currency: "ARS",
|
||||
value: 1,
|
||||
fraction: 0,
|
||||
},
|
||||
accept: {},
|
||||
});
|
@ -0,0 +1,31 @@
|
||||
/*
|
||||
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 <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Sebastian Javier Marchano (sebasjm)
|
||||
*/
|
||||
|
||||
import { expect } from "chai";
|
||||
|
||||
describe("test description", () => {
|
||||
|
||||
it("should assert", () => {
|
||||
|
||||
expect([]).deep.equals([])
|
||||
});
|
||||
})
|
||||
|
@ -14,16 +14,15 @@
|
||||
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
import { Amounts } from "@gnu-taler/taler-util";
|
||||
import { styled } from "@linaria/react";
|
||||
import { h, VNode } from "preact";
|
||||
import { useState } from "preact/hooks";
|
||||
import { Amount } from "../../components/Amount.js";
|
||||
import { ErrorTalerOperation } from "../../components/ErrorTalerOperation.js";
|
||||
import { LoadingError } from "../../components/LoadingError.js";
|
||||
import { SelectList } from "../../components/SelectList.js";
|
||||
import { Input } from "../../components/styled/index.js";
|
||||
import { LogoHeader } from "../../components/LogoHeader.js";
|
||||
import { Part } from "../../components/Part.js";
|
||||
import { SubTitle, WalletAction } from "../../components/styled/index.js";
|
||||
import { useTranslationContext } from "../../context/translation.js";
|
||||
import { Button } from "../../mui/Button.js";
|
||||
import { TextField } from "../../mui/TextField.js";
|
||||
import { State } from "./index.js";
|
||||
|
||||
export function LoadingUriView({ error }: State.LoadingUriError): VNode {
|
||||
@ -37,22 +36,39 @@ export function LoadingUriView({ error }: State.LoadingUriError): VNode {
|
||||
);
|
||||
}
|
||||
|
||||
const Container = styled.div``;
|
||||
|
||||
export function ReadyView({ amount, exchange, subject }: State.Ready): VNode {
|
||||
export function ReadyView({
|
||||
accept,
|
||||
amount,
|
||||
operationError,
|
||||
}: State.Ready): VNode {
|
||||
const { i18n } = useTranslationContext();
|
||||
return (
|
||||
<Container>
|
||||
<p>Sending {Amounts.stringify(amount)}</p>
|
||||
<TextField
|
||||
label="Subject"
|
||||
variant="filled"
|
||||
required
|
||||
value={subject.value}
|
||||
onChange={subject.onInput}
|
||||
/>
|
||||
<p>to:</p>
|
||||
<Button>Scan QR code</Button>
|
||||
</Container>
|
||||
<WalletAction>
|
||||
<LogoHeader />
|
||||
<SubTitle>
|
||||
<i18n.Translate>Digital cash transfer</i18n.Translate>
|
||||
</SubTitle>
|
||||
{operationError && (
|
||||
<ErrorTalerOperation
|
||||
title={
|
||||
<i18n.Translate>
|
||||
Could not finish the pickup operation
|
||||
</i18n.Translate>
|
||||
}
|
||||
error={operationError}
|
||||
/>
|
||||
)}
|
||||
<section style={{ textAlign: "left" }}>
|
||||
<Part
|
||||
title={<i18n.Translate>Amount</i18n.Translate>}
|
||||
text={<Amount value={amount} />}
|
||||
/>
|
||||
</section>
|
||||
<section>
|
||||
<Button variant="contained" color="success" onClick={accept.onClick}>
|
||||
<i18n.Translate>Pickup</i18n.Translate>
|
||||
</Button>
|
||||
</section>
|
||||
</WalletAction>
|
||||
);
|
||||
}
|
@ -25,5 +25,9 @@ import * as a4 from "./Refund/stories.jsx";
|
||||
import * as a5 from "./Tip/stories.jsx";
|
||||
import * as a6 from "./Withdraw/stories.jsx";
|
||||
import * as a7 from "./TermsOfServiceSection.stories.js";
|
||||
import * as a8 from "./InvoiceCreate/stories.js";
|
||||
import * as a9 from "./InvoicePay/stories.js";
|
||||
import * as a10 from "./TransferCreate/stories.js";
|
||||
import * as a11 from "./TransferPickup/stories.js";
|
||||
|
||||
export default [a1, a3, a4, a5, a6, a7];
|
||||
export default [a1, a3, a4, a5, a6, a7, a8, a9, a10, a11];
|
||||
|
@ -201,11 +201,33 @@ function openWalletURIFromPopup(talerUri: string): void {
|
||||
`static/wallet.html#/cta/refund?talerRefundUri=${talerUri}`,
|
||||
);
|
||||
break;
|
||||
default:
|
||||
case TalerUriType.TalerPayPull:
|
||||
url = chrome.runtime.getURL(
|
||||
`static/wallet.html#/cta/invoice/pay?talerPayPullUri=${talerUri}`,
|
||||
);
|
||||
break;
|
||||
case TalerUriType.TalerPayPush:
|
||||
url = chrome.runtime.getURL(
|
||||
`static/wallet.html#/cta/transfer/pickup?talerPayPushUri=${talerUri}`,
|
||||
);
|
||||
break;
|
||||
case TalerUriType.TalerNotifyReserve:
|
||||
logger.warn(
|
||||
"Response with HTTP 402 has Taler header, but header value is not a taler:// URI.",
|
||||
`Response with HTTP 402 the Taler header but it is deprecated ${talerUri}`,
|
||||
);
|
||||
break;
|
||||
case TalerUriType.Unknown:
|
||||
logger.warn(
|
||||
`Response with HTTP 402 the Taler header but could not classify ${talerUri}`,
|
||||
);
|
||||
return;
|
||||
default: {
|
||||
const error: never = uriType;
|
||||
logger.warn(
|
||||
`Response with HTTP 402 the Taler header "${error}", but header value is not a taler:// URI.`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
chrome.tabs.create({ active: true, url }, () => {
|
||||
|
@ -181,10 +181,11 @@ function getContentForExample(item: ExampleItem | undefined): () => VNode {
|
||||
item.component,
|
||||
item.name,
|
||||
);
|
||||
if (!example)
|
||||
if (!example) {
|
||||
return function ExampleNotFoundMessage() {
|
||||
return <div>example not found</div>;
|
||||
};
|
||||
}
|
||||
return () => example.render(example.render.args);
|
||||
}
|
||||
|
||||
@ -314,7 +315,9 @@ function ErrorReport({
|
||||
children: ComponentChild;
|
||||
selected: ExampleItem | undefined;
|
||||
}): VNode {
|
||||
const [error] = useErrorBoundary();
|
||||
const [error, resetError] = useErrorBoundary();
|
||||
//if there is an error, reset when unloading this component
|
||||
useEffect(() => (error ? resetError : undefined));
|
||||
if (error) {
|
||||
return (
|
||||
<div>
|
||||
|
@ -60,8 +60,10 @@ import {
|
||||
DestinationSelectionSendCash,
|
||||
} from "./DestinationSelection.js";
|
||||
import { ExchangeSelectionPage } from "./ExchangeSelection/index.js";
|
||||
import { InvoicePage } from "./Invoice/index.js";
|
||||
import { SendPage } from "./Send/index.js";
|
||||
import { TransferCreatePage } from "../cta/TransferCreate/index.js";
|
||||
import { InvoiceCreatePage } from "../cta/InvoiceCreate/index.js";
|
||||
import { TransferPickupPage } from "../cta/TransferPickup/index.js";
|
||||
import { InvoicePayPage } from "../cta/InvoicePay/index.js";
|
||||
|
||||
export function Application(): VNode {
|
||||
const [globalNotification, setGlobalNotification] = useState<
|
||||
@ -153,7 +155,7 @@ export function Application(): VNode {
|
||||
redirectTo(Pages.balanceDeposit({ amount }))
|
||||
}
|
||||
goToWalletWalletSend={(amount: string) =>
|
||||
redirectTo(Pages.send({ amount }))
|
||||
redirectTo(Pages.ctaTransferCreate({ amount }))
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
@ -163,11 +165,9 @@ export function Application(): VNode {
|
||||
redirectTo(Pages.ctaWithdrawManual({ amount }))
|
||||
}
|
||||
goToWalletWalletInvoice={(amount?: string) =>
|
||||
redirectTo(Pages.invoice({ amount }))
|
||||
redirectTo(Pages.ctaInvoiceCreate({ amount }))
|
||||
}
|
||||
/>
|
||||
<Route path={Pages.invoice.pattern} component={InvoicePage} />
|
||||
<Route path={Pages.send.pattern} component={SendPage} />
|
||||
|
||||
<Route
|
||||
path={Pages.balanceTransaction.pattern}
|
||||
@ -275,6 +275,20 @@ export function Application(): VNode {
|
||||
component={DepositPageCTA}
|
||||
cancel={() => redirectTo(Pages.balance)}
|
||||
/>
|
||||
<Route
|
||||
path={Pages.ctaInvoiceCreate.pattern}
|
||||
component={InvoiceCreatePage}
|
||||
/>
|
||||
<Route
|
||||
path={Pages.ctaTransferCreate.pattern}
|
||||
component={TransferCreatePage}
|
||||
/>
|
||||
|
||||
<Route path={Pages.ctaInvoicePay} component={InvoicePayPage} />
|
||||
<Route
|
||||
path={Pages.ctaTransferPickup}
|
||||
component={TransferPickupPage}
|
||||
/>
|
||||
|
||||
{/**
|
||||
* NOT FOUND
|
||||
|
@ -25,6 +25,10 @@ import {
|
||||
TransactionCommon,
|
||||
TransactionDeposit,
|
||||
TransactionPayment,
|
||||
TransactionPeerPullCredit,
|
||||
TransactionPeerPullDebit,
|
||||
TransactionPeerPushCredit,
|
||||
TransactionPeerPushDebit,
|
||||
TransactionRefresh,
|
||||
TransactionRefund,
|
||||
TransactionTip,
|
||||
@ -118,6 +122,31 @@ const exampleData = {
|
||||
},
|
||||
refundPending: undefined,
|
||||
} as TransactionRefund,
|
||||
push_credit: {
|
||||
...commonTransaction(),
|
||||
type: TransactionType.PeerPushCredit,
|
||||
|
||||
exchangeBaseUrl: "https://exchange.taler.net",
|
||||
} as TransactionPeerPushCredit,
|
||||
push_debit: {
|
||||
...commonTransaction(),
|
||||
type: TransactionType.PeerPushDebit,
|
||||
talerUri:
|
||||
"taler://pay-push/exchange.taler.ar/HS585JK0QCXHJ8Z8QWZA3EBAY5WY7XNC1RR2MHJXSH2Z4WP0YPJ0",
|
||||
exchangeBaseUrl: "https://exchange.taler.net",
|
||||
} as TransactionPeerPushDebit,
|
||||
pull_credit: {
|
||||
...commonTransaction(),
|
||||
type: TransactionType.PeerPullCredit,
|
||||
talerUri:
|
||||
"taler://pay-push/exchange.taler.ar/HS585JK0QCXHJ8Z8QWZA3EBAY5WY7XNC1RR2MHJXSH2Z4WP0YPJ0",
|
||||
exchangeBaseUrl: "https://exchange.taler.net",
|
||||
} as TransactionPeerPullCredit,
|
||||
pull_debit: {
|
||||
...commonTransaction(),
|
||||
type: TransactionType.PeerPullDebit,
|
||||
exchangeBaseUrl: "https://exchange.taler.net",
|
||||
} as TransactionPeerPullDebit,
|
||||
};
|
||||
|
||||
export const NoBalance = createExample(TestedComponent, {
|
||||
@ -327,3 +356,21 @@ export const FiveOfficialCurrenciesWithHighValue = createExample(
|
||||
],
|
||||
},
|
||||
);
|
||||
|
||||
export const PeerToPeer = createExample(TestedComponent, {
|
||||
transactions: [
|
||||
exampleData.pull_credit,
|
||||
exampleData.pull_debit,
|
||||
exampleData.push_credit,
|
||||
exampleData.push_debit,
|
||||
],
|
||||
balances: [
|
||||
{
|
||||
available: "USD:10",
|
||||
pendingIncoming: "USD:0",
|
||||
pendingOutgoing: "USD:0",
|
||||
hasPendingTransactions: false,
|
||||
requiresUserInput: false,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
@ -1,56 +0,0 @@
|
||||
/*
|
||||
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 <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
import { Amounts } from "@gnu-taler/taler-util";
|
||||
import { styled } from "@linaria/react";
|
||||
import { h, VNode } from "preact";
|
||||
import { LoadingError } from "../../components/LoadingError.js";
|
||||
import { useTranslationContext } from "../../context/translation.js";
|
||||
import { Button } from "../../mui/Button.js";
|
||||
import { TextField } from "../../mui/TextField.js";
|
||||
import { State } from "./index.js";
|
||||
|
||||
export function LoadingUriView({ error }: State.LoadingUriError): VNode {
|
||||
const { i18n } = useTranslationContext();
|
||||
|
||||
return (
|
||||
<LoadingError
|
||||
title={<i18n.Translate>Could not load</i18n.Translate>}
|
||||
error={error}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const Container = styled.div``;
|
||||
|
||||
export function ReadyView({ amount, subject }: State.Ready): VNode {
|
||||
const { i18n } = useTranslationContext();
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<p>Creating an invoice of {Amounts.stringify(amount)}</p>
|
||||
<TextField
|
||||
label="Subject"
|
||||
variant="filled"
|
||||
required
|
||||
value={subject.value}
|
||||
onChange={subject.onInput}
|
||||
/>
|
||||
<p>to:</p>
|
||||
<Button>Scan QR code</Button>
|
||||
</Container>
|
||||
);
|
||||
}
|
@ -25,6 +25,10 @@ import {
|
||||
TransactionCommon,
|
||||
TransactionDeposit,
|
||||
TransactionPayment,
|
||||
TransactionPeerPullCredit,
|
||||
TransactionPeerPullDebit,
|
||||
TransactionPeerPushCredit,
|
||||
TransactionPeerPushDebit,
|
||||
TransactionRefresh,
|
||||
TransactionRefund,
|
||||
TransactionTip,
|
||||
@ -139,6 +143,30 @@ const exampleData = {
|
||||
},
|
||||
refundPending: undefined,
|
||||
} as TransactionRefund,
|
||||
push_credit: {
|
||||
...commonTransaction,
|
||||
type: TransactionType.PeerPushCredit,
|
||||
exchangeBaseUrl: "https://exchange.taler.net",
|
||||
} as TransactionPeerPushCredit,
|
||||
push_debit: {
|
||||
...commonTransaction,
|
||||
type: TransactionType.PeerPushDebit,
|
||||
talerUri:
|
||||
"taler://pay-push/exchange.taler.ar/HS585JK0QCXHJ8Z8QWZA3EBAY5WY7XNC1RR2MHJXSH2Z4WP0YPJ0",
|
||||
exchangeBaseUrl: "https://exchange.taler.net",
|
||||
} as TransactionPeerPushDebit,
|
||||
pull_credit: {
|
||||
...commonTransaction,
|
||||
type: TransactionType.PeerPullCredit,
|
||||
talerUri:
|
||||
"taler://pay-push/exchange.taler.ar/HS585JK0QCXHJ8Z8QWZA3EBAY5WY7XNC1RR2MHJXSH2Z4WP0YPJ0",
|
||||
exchangeBaseUrl: "https://exchange.taler.net",
|
||||
} as TransactionPeerPullCredit,
|
||||
pull_debit: {
|
||||
...commonTransaction,
|
||||
type: TransactionType.PeerPullDebit,
|
||||
exchangeBaseUrl: "https://exchange.taler.net",
|
||||
} as TransactionPeerPullDebit,
|
||||
};
|
||||
|
||||
const transactionError = {
|
||||
@ -498,3 +526,19 @@ export const RefundError = createExample(TestedComponent, {
|
||||
export const RefundPending = createExample(TestedComponent, {
|
||||
transaction: { ...exampleData.refund, pending: true },
|
||||
});
|
||||
|
||||
export const InvoiceCredit = createExample(TestedComponent, {
|
||||
transaction: { ...exampleData.pull_credit },
|
||||
});
|
||||
|
||||
export const InvoiceDebit = createExample(TestedComponent, {
|
||||
transaction: { ...exampleData.pull_debit },
|
||||
});
|
||||
|
||||
export const TransferCredit = createExample(TestedComponent, {
|
||||
transaction: { ...exampleData.push_credit },
|
||||
});
|
||||
|
||||
export const TransferDebit = createExample(TestedComponent, {
|
||||
transaction: { ...exampleData.push_debit },
|
||||
});
|
||||
|
@ -45,6 +45,7 @@ import { ErrorTalerOperation } from "../components/ErrorTalerOperation.js";
|
||||
import { Loading } from "../components/Loading.js";
|
||||
import { LoadingError } from "../components/LoadingError.js";
|
||||
import { Kind, Part, PartCollapsible, PartPayto } from "../components/Part.js";
|
||||
import { QR } from "../components/QR.js";
|
||||
import { ShowFullContractTermPopup } from "../components/ShowFullContractTermPopup.js";
|
||||
import {
|
||||
CenteredDialog,
|
||||
@ -557,6 +558,172 @@ export function TransactionView({
|
||||
);
|
||||
}
|
||||
|
||||
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} />
|
||||
<Button onClick={copy}>copy</Button>
|
||||
<Button onClick={toggle}>hide qr</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<div>{text.substring(0, 64)}...</div>
|
||||
<Button onClick={copy}>copy</Button>
|
||||
<Button onClick={toggle}>show qr</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (transaction.type === TransactionType.PeerPullCredit) {
|
||||
const total = Amounts.parseOrThrow(transaction.amountEffective);
|
||||
return (
|
||||
<TransactionTemplate>
|
||||
<Header
|
||||
timestamp={transaction.timestamp}
|
||||
type={i18n.str`Credit`}
|
||||
total={total}
|
||||
kind="positive"
|
||||
>
|
||||
Invoice
|
||||
</Header>
|
||||
|
||||
<Part
|
||||
title={<i18n.Translate>Exchange</i18n.Translate>}
|
||||
text={transaction.exchangeBaseUrl}
|
||||
kind="neutral"
|
||||
/>
|
||||
<Part
|
||||
title={<i18n.Translate>URI</i18n.Translate>}
|
||||
text={<ShowQrWithCopy text={transaction.talerUri} />}
|
||||
kind="neutral"
|
||||
/>
|
||||
<Part
|
||||
title={<i18n.Translate>Details</i18n.Translate>}
|
||||
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 (
|
||||
<TransactionTemplate>
|
||||
<Header
|
||||
timestamp={transaction.timestamp}
|
||||
type={i18n.str`Debit`}
|
||||
total={total}
|
||||
kind="negative"
|
||||
>
|
||||
Invoice
|
||||
</Header>
|
||||
|
||||
<Part
|
||||
title={<i18n.Translate>Exchange</i18n.Translate>}
|
||||
text={transaction.exchangeBaseUrl}
|
||||
kind="neutral"
|
||||
/>
|
||||
<Part
|
||||
title={<i18n.Translate>Details</i18n.Translate>}
|
||||
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 (
|
||||
<TransactionTemplate>
|
||||
<Header
|
||||
timestamp={transaction.timestamp}
|
||||
type={i18n.str`Debit`}
|
||||
total={total}
|
||||
kind="negative"
|
||||
>
|
||||
Transfer
|
||||
</Header>
|
||||
|
||||
<Part
|
||||
title={<i18n.Translate>Exchange</i18n.Translate>}
|
||||
text={transaction.exchangeBaseUrl}
|
||||
kind="neutral"
|
||||
/>
|
||||
<Part
|
||||
title={<i18n.Translate>URI</i18n.Translate>}
|
||||
text={<QR text={transaction.talerUri} />}
|
||||
kind="neutral"
|
||||
/>
|
||||
<Part
|
||||
title={<i18n.Translate>Details</i18n.Translate>}
|
||||
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 (
|
||||
<TransactionTemplate>
|
||||
<Header
|
||||
timestamp={transaction.timestamp}
|
||||
type={i18n.str`Credit`}
|
||||
total={total}
|
||||
kind="positive"
|
||||
>
|
||||
Transfer
|
||||
</Header>
|
||||
|
||||
<Part
|
||||
title={<i18n.Translate>Exchange</i18n.Translate>}
|
||||
text={transaction.exchangeBaseUrl}
|
||||
kind="neutral"
|
||||
/>
|
||||
<Part
|
||||
title={<i18n.Translate>Details</i18n.Translate>}
|
||||
text={
|
||||
<TransferDetails
|
||||
amount={{
|
||||
effective: Amounts.parseOrThrow(transaction.amountEffective),
|
||||
raw: Amounts.parseOrThrow(transaction.amountRaw),
|
||||
}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</TransactionTemplate>
|
||||
);
|
||||
}
|
||||
return <div />;
|
||||
}
|
||||
|
||||
@ -736,6 +903,88 @@ export interface AmountWithFee {
|
||||
raw: AmountJson;
|
||||
}
|
||||
|
||||
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>
|
||||
<td>Invoice</td>
|
||||
<td>
|
||||
<Amount value={amount.raw} maxFracSize={maxFrac} />
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
{Amounts.isNonZero(fee) && (
|
||||
<tr>
|
||||
<td>Transaction fees</td>
|
||||
<td>
|
||||
<Amount value={fee} negative maxFracSize={maxFrac} />
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
<tr>
|
||||
<td colSpan={2}>
|
||||
<hr />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Total</td>
|
||||
<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>
|
||||
<td>Transfer</td>
|
||||
<td>
|
||||
<Amount value={amount.raw} maxFracSize={maxFrac} />
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
{Amounts.isNonZero(fee) && (
|
||||
<tr>
|
||||
<td>Transaction fees</td>
|
||||
<td>
|
||||
<Amount value={fee} negative maxFracSize={maxFrac} />
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
<tr>
|
||||
<td colSpan={2}>
|
||||
<hr />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Total</td>
|
||||
<td>
|
||||
<Amount value={amount.effective} maxFracSize={maxFrac} />
|
||||
</td>
|
||||
</tr>
|
||||
</PurchaseDetailsTable>
|
||||
);
|
||||
}
|
||||
|
||||
export function WithdrawDetails({ amount }: { amount: AmountWithFee }): VNode {
|
||||
const { i18n } = useTranslationContext();
|
||||
|
||||
|
@ -24,12 +24,18 @@
|
||||
import {
|
||||
AcceptExchangeTosRequest,
|
||||
AcceptManualWithdrawalResult,
|
||||
AcceptPeerPullPaymentRequest,
|
||||
AcceptPeerPushPaymentRequest,
|
||||
AcceptTipRequest,
|
||||
AcceptWithdrawalResponse,
|
||||
AddExchangeRequest,
|
||||
AmountString,
|
||||
ApplyRefundResponse,
|
||||
BalancesResponse,
|
||||
CheckPeerPullPaymentRequest,
|
||||
CheckPeerPullPaymentResponse,
|
||||
CheckPeerPushPaymentRequest,
|
||||
CheckPeerPushPaymentResponse,
|
||||
CoinDumpJson,
|
||||
ConfirmPayResult,
|
||||
CoreApiResponse,
|
||||
@ -41,6 +47,10 @@ import {
|
||||
GetExchangeWithdrawalInfo,
|
||||
GetFeeForDepositRequest,
|
||||
GetWithdrawalDetailsForUriRequest,
|
||||
InitiatePeerPullPaymentRequest,
|
||||
InitiatePeerPullPaymentResponse,
|
||||
InitiatePeerPushPaymentRequest,
|
||||
InitiatePeerPushPaymentResponse,
|
||||
KnownBankAccounts,
|
||||
Logger,
|
||||
NotificationType,
|
||||
@ -473,3 +483,24 @@ export function onUpdateNotification(
|
||||
};
|
||||
return platform.listenToWalletBackground(onNewMessage);
|
||||
}
|
||||
|
||||
export function initiatePeerPushPayment(req: InitiatePeerPushPaymentRequest): Promise<InitiatePeerPushPaymentResponse> {
|
||||
return callBackend("initiatePeerPushPayment", req);
|
||||
}
|
||||
export function checkPeerPushPayment(req: CheckPeerPushPaymentRequest): Promise<CheckPeerPushPaymentResponse> {
|
||||
return callBackend("checkPeerPushPayment", req);
|
||||
}
|
||||
export function acceptPeerPushPayment(req: AcceptPeerPushPaymentRequest): Promise<void> {
|
||||
return callBackend("acceptPeerPushPayment", req);
|
||||
}
|
||||
export function initiatePeerPullPayment(req: InitiatePeerPullPaymentRequest): Promise<InitiatePeerPullPaymentResponse> {
|
||||
return callBackend("initiatePeerPullPayment", req);
|
||||
}
|
||||
export function checkPeerPullPayment(req: CheckPeerPullPaymentRequest): Promise<CheckPeerPullPaymentResponse> {
|
||||
return callBackend("checkPeerPullPayment", req);
|
||||
}
|
||||
export function acceptPeerPullPayment(req: AcceptPeerPullPaymentRequest): Promise<void> {
|
||||
return callBackend("acceptPeerPullPayment", req);
|
||||
}
|
||||
|
||||
|
||||
|
@ -276,15 +276,35 @@ function parseTalerUriAndRedirect(tabId: number, talerUri: string): void {
|
||||
tabId,
|
||||
`/cta/refund?talerRefundUri=${talerUri}`,
|
||||
);
|
||||
case TalerUriType.TalerPayPull:
|
||||
return platform.redirectTabToWalletPage(
|
||||
tabId,
|
||||
`/cta/invoice/pay?talerPayPullUri=${talerUri}`,
|
||||
);
|
||||
case TalerUriType.TalerPayPush:
|
||||
return platform.redirectTabToWalletPage(
|
||||
tabId,
|
||||
`/cta/transfer/pickup?talerPayPushUri=${talerUri}`,
|
||||
);
|
||||
case TalerUriType.TalerNotifyReserve:
|
||||
// FIXME: Is this still useful?
|
||||
// handleNotifyReserve(w);
|
||||
break;
|
||||
default:
|
||||
logger.warn(
|
||||
"Response with HTTP 402 has Taler header, but header value is not a taler:// URI.",
|
||||
`Response with HTTP 402 the Taler header but it is deprecated ${talerUri}`,
|
||||
);
|
||||
break;
|
||||
case TalerUriType.Unknown:
|
||||
logger.warn(
|
||||
`Response with HTTP 402 the Taler header but could not classify ${talerUri}`,
|
||||
);
|
||||
return;
|
||||
default: {
|
||||
const error: never = uriType;
|
||||
logger.warn(
|
||||
`Response with HTTP 402 the Taler header "${error}", but header value is not a taler:// URI.`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user