p2p tx rendering
This commit is contained in:
parent
7dc66c2441
commit
d84424202d
@ -471,7 +471,7 @@ async function getMergeReserveInfo(
|
|||||||
export async function acceptPeerPushPayment(
|
export async function acceptPeerPushPayment(
|
||||||
ws: InternalWalletState,
|
ws: InternalWalletState,
|
||||||
req: AcceptPeerPushPaymentRequest,
|
req: AcceptPeerPushPaymentRequest,
|
||||||
) {
|
): Promise<void> {
|
||||||
const peerInc = await ws.db
|
const peerInc = await ws.db
|
||||||
.mktx((x) => ({ peerPushPaymentIncoming: x.peerPushPaymentIncoming }))
|
.mktx((x) => ({ peerPushPaymentIncoming: x.peerPushPaymentIncoming }))
|
||||||
.runReadOnly(async (tx) => {
|
.runReadOnly(async (tx) => {
|
||||||
|
@ -108,16 +108,20 @@ export const Pages = {
|
|||||||
"/settings/exchange/add/:currency?",
|
"/settings/exchange/add/:currency?",
|
||||||
),
|
),
|
||||||
|
|
||||||
invoice: pageDefinition<{ amount?: string }>("/invoice/:amount?"),
|
|
||||||
send: pageDefinition<{ amount?: string }>("/send/:amount?"),
|
|
||||||
|
|
||||||
cta: pageDefinition<{ action: string }>("/cta/:action"),
|
cta: pageDefinition<{ action: string }>("/cta/:action"),
|
||||||
ctaPay: "/cta/pay",
|
ctaPay: "/cta/pay",
|
||||||
ctaRefund: "/cta/refund",
|
ctaRefund: "/cta/refund",
|
||||||
ctaTips: "/cta/tip",
|
ctaTips: "/cta/tip",
|
||||||
ctaWithdraw: "/cta/withdraw",
|
ctaWithdraw: "/cta/withdraw",
|
||||||
ctaDeposit: "/cta/deposit",
|
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 }>(
|
ctaWithdrawManual: pageDefinition<{ amount?: string }>(
|
||||||
"/cta/manual-withdraw/:amount?",
|
"/cta/manual-withdraw/:amount?",
|
||||||
),
|
),
|
||||||
|
@ -113,8 +113,58 @@ export function TransactionItem(props: { tx: Transaction }): VNode {
|
|||||||
pending={tx.pending}
|
pending={tx.pending}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
default:
|
case TransactionType.PeerPullCredit:
|
||||||
throw Error("unsupported transaction type");
|
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/>
|
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 { Loading } from "../../components/Loading.js";
|
||||||
import { HookError } from "../../hooks/useAsyncAsHook.js";
|
import { HookError } from "../../hooks/useAsyncAsHook.js";
|
||||||
|
import { ButtonHandler } from "../../mui/handlers.js";
|
||||||
import { compose, StateViewMap } from "../../utils/index.js";
|
import { compose, StateViewMap } from "../../utils/index.js";
|
||||||
import { LoadingUriView, ReadyView } from "./views.js";
|
|
||||||
import * as wxApi from "../../wxApi.js";
|
import * as wxApi from "../../wxApi.js";
|
||||||
import { useComponentState } from "./state.js";
|
import { useComponentState } from "./state.js";
|
||||||
import { AmountJson } from "@gnu-taler/taler-util";
|
import { LoadingUriView, ReadyView } from "./views.js";
|
||||||
import { TextFieldHandler } from "../../mui/handlers.js";
|
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
p: string;
|
talerPayPullUri: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type State =
|
export type State =
|
||||||
@ -49,9 +49,10 @@ export namespace State {
|
|||||||
}
|
}
|
||||||
export interface Ready extends BaseInfo {
|
export interface Ready extends BaseInfo {
|
||||||
status: "ready";
|
status: "ready";
|
||||||
amount: AmountJson;
|
amount: AmountJson,
|
||||||
subject: TextFieldHandler;
|
|
||||||
error: undefined;
|
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/>
|
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 { useState } from "preact/hooks";
|
||||||
import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js";
|
import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js";
|
||||||
import * as wxApi from "../../wxApi.js";
|
import * as wxApi from "../../wxApi.js";
|
||||||
import { Props, State } from "./index.js";
|
import { Props, State } from "./index.js";
|
||||||
|
|
||||||
export function useComponentState(
|
export function useComponentState(
|
||||||
{ p }: Props,
|
{ talerPayPullUri }: Props,
|
||||||
api: typeof wxApi,
|
api: typeof wxApi,
|
||||||
): State {
|
): State {
|
||||||
const [subject, setSubject] = useState("");
|
const hook = useAsyncAsHook(async () => {
|
||||||
const amount = Amounts.parseOrThrow("ARS:0")
|
return await api.checkPeerPullPayment({
|
||||||
|
talerUri: talerPayPullUri
|
||||||
const hook = useAsyncAsHook(api.listExchanges);
|
})
|
||||||
const [exchangeIdx, setExchangeIdx] = useState("0")
|
}, [])
|
||||||
|
const [operationError, setOperationError] = useState<TalerErrorDetail | undefined>(undefined)
|
||||||
|
|
||||||
if (!hook) {
|
if (!hook) {
|
||||||
return {
|
return {
|
||||||
@ -43,24 +45,30 @@ export function useComponentState(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const exchanges = hook.response.exchanges;
|
const { amount, peerPullPaymentIncomingId } = hook.response
|
||||||
const exchangeMap = exchanges.reduce((prev, cur, idx) => ({ ...prev, [cur.exchangeBaseUrl]: String(idx) }), {} as Record<string, string>)
|
|
||||||
const selected = exchanges[Number(exchangeIdx)];
|
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 {
|
return {
|
||||||
status: "ready",
|
status: "ready",
|
||||||
exchange: {
|
amount: Amounts.parseOrThrow(amount),
|
||||||
list: exchangeMap,
|
|
||||||
value: exchangeIdx,
|
|
||||||
onChange: async (v) => {
|
|
||||||
setExchangeIdx(v)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
subject: {
|
|
||||||
value: subject,
|
|
||||||
onInput: async (e) => setSubject(e)
|
|
||||||
},
|
|
||||||
amount,
|
|
||||||
error: undefined,
|
error: undefined,
|
||||||
|
accept: {
|
||||||
|
onClick: accept
|
||||||
|
},
|
||||||
|
operationError
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -23,7 +23,14 @@ import { createExample } from "../../test-utils.js";
|
|||||||
import { ReadyView } from "./views.js";
|
import { ReadyView } from "./views.js";
|
||||||
|
|
||||||
export default {
|
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/>
|
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";
|
* @author Sebastian Javier Marchano (sebasjm)
|
||||||
import { Props, State } from "./index.js";
|
*/
|
||||||
|
|
||||||
|
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/>
|
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 { Loading } from "../../components/Loading.js";
|
||||||
import { HookError } from "../../hooks/useAsyncAsHook.js";
|
import { HookError } from "../../hooks/useAsyncAsHook.js";
|
||||||
|
import { ButtonHandler } from "../../mui/handlers.js";
|
||||||
import { compose, StateViewMap } from "../../utils/index.js";
|
import { compose, StateViewMap } from "../../utils/index.js";
|
||||||
import { LoadingUriView, ReadyView } from "./views.js";
|
|
||||||
import * as wxApi from "../../wxApi.js";
|
import * as wxApi from "../../wxApi.js";
|
||||||
import { useComponentState } from "./state.js";
|
import { useComponentState } from "./state.js";
|
||||||
import { AmountJson } from "@gnu-taler/taler-util";
|
import { LoadingUriView, ReadyView } from "./views.js";
|
||||||
import { SelectFieldHandler, TextFieldHandler } from "../../mui/handlers.js";
|
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
p: string;
|
talerPayPushUri: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type State =
|
export type State =
|
||||||
@ -49,10 +49,10 @@ export namespace State {
|
|||||||
}
|
}
|
||||||
export interface Ready extends BaseInfo {
|
export interface Ready extends BaseInfo {
|
||||||
status: "ready";
|
status: "ready";
|
||||||
amount: AmountJson;
|
amount: AmountJson,
|
||||||
exchange: SelectFieldHandler,
|
|
||||||
subject: TextFieldHandler,
|
|
||||||
error: undefined;
|
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";
|
import { ReadyView } from "./views.js";
|
||||||
|
|
||||||
export default {
|
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/>
|
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 { 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 { LoadingError } from "../../components/LoadingError.js";
|
||||||
import { SelectList } from "../../components/SelectList.js";
|
import { LogoHeader } from "../../components/LogoHeader.js";
|
||||||
import { Input } from "../../components/styled/index.js";
|
import { Part } from "../../components/Part.js";
|
||||||
|
import { SubTitle, WalletAction } from "../../components/styled/index.js";
|
||||||
import { useTranslationContext } from "../../context/translation.js";
|
import { useTranslationContext } from "../../context/translation.js";
|
||||||
import { Button } from "../../mui/Button.js";
|
import { Button } from "../../mui/Button.js";
|
||||||
import { TextField } from "../../mui/TextField.js";
|
|
||||||
import { State } from "./index.js";
|
import { State } from "./index.js";
|
||||||
|
|
||||||
export function LoadingUriView({ error }: State.LoadingUriError): VNode {
|
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({
|
||||||
|
accept,
|
||||||
export function ReadyView({ amount, exchange, subject }: State.Ready): VNode {
|
amount,
|
||||||
|
operationError,
|
||||||
|
}: State.Ready): VNode {
|
||||||
const { i18n } = useTranslationContext();
|
const { i18n } = useTranslationContext();
|
||||||
return (
|
return (
|
||||||
<Container>
|
<WalletAction>
|
||||||
<p>Sending {Amounts.stringify(amount)}</p>
|
<LogoHeader />
|
||||||
<TextField
|
<SubTitle>
|
||||||
label="Subject"
|
<i18n.Translate>Digital cash transfer</i18n.Translate>
|
||||||
variant="filled"
|
</SubTitle>
|
||||||
required
|
{operationError && (
|
||||||
value={subject.value}
|
<ErrorTalerOperation
|
||||||
onChange={subject.onInput}
|
title={
|
||||||
/>
|
<i18n.Translate>
|
||||||
<p>to:</p>
|
Could not finish the pickup operation
|
||||||
<Button>Scan QR code</Button>
|
</i18n.Translate>
|
||||||
</Container>
|
}
|
||||||
|
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 a5 from "./Tip/stories.jsx";
|
||||||
import * as a6 from "./Withdraw/stories.jsx";
|
import * as a6 from "./Withdraw/stories.jsx";
|
||||||
import * as a7 from "./TermsOfServiceSection.stories.js";
|
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}`,
|
`static/wallet.html#/cta/refund?talerRefundUri=${talerUri}`,
|
||||||
);
|
);
|
||||||
break;
|
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(
|
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;
|
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 }, () => {
|
chrome.tabs.create({ active: true, url }, () => {
|
||||||
|
@ -181,10 +181,11 @@ function getContentForExample(item: ExampleItem | undefined): () => VNode {
|
|||||||
item.component,
|
item.component,
|
||||||
item.name,
|
item.name,
|
||||||
);
|
);
|
||||||
if (!example)
|
if (!example) {
|
||||||
return function ExampleNotFoundMessage() {
|
return function ExampleNotFoundMessage() {
|
||||||
return <div>example not found</div>;
|
return <div>example not found</div>;
|
||||||
};
|
};
|
||||||
|
}
|
||||||
return () => example.render(example.render.args);
|
return () => example.render(example.render.args);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -314,7 +315,9 @@ function ErrorReport({
|
|||||||
children: ComponentChild;
|
children: ComponentChild;
|
||||||
selected: ExampleItem | undefined;
|
selected: ExampleItem | undefined;
|
||||||
}): VNode {
|
}): VNode {
|
||||||
const [error] = useErrorBoundary();
|
const [error, resetError] = useErrorBoundary();
|
||||||
|
//if there is an error, reset when unloading this component
|
||||||
|
useEffect(() => (error ? resetError : undefined));
|
||||||
if (error) {
|
if (error) {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
@ -60,8 +60,10 @@ import {
|
|||||||
DestinationSelectionSendCash,
|
DestinationSelectionSendCash,
|
||||||
} from "./DestinationSelection.js";
|
} from "./DestinationSelection.js";
|
||||||
import { ExchangeSelectionPage } from "./ExchangeSelection/index.js";
|
import { ExchangeSelectionPage } from "./ExchangeSelection/index.js";
|
||||||
import { InvoicePage } from "./Invoice/index.js";
|
import { TransferCreatePage } from "../cta/TransferCreate/index.js";
|
||||||
import { SendPage } from "./Send/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 {
|
export function Application(): VNode {
|
||||||
const [globalNotification, setGlobalNotification] = useState<
|
const [globalNotification, setGlobalNotification] = useState<
|
||||||
@ -153,7 +155,7 @@ export function Application(): VNode {
|
|||||||
redirectTo(Pages.balanceDeposit({ amount }))
|
redirectTo(Pages.balanceDeposit({ amount }))
|
||||||
}
|
}
|
||||||
goToWalletWalletSend={(amount: string) =>
|
goToWalletWalletSend={(amount: string) =>
|
||||||
redirectTo(Pages.send({ amount }))
|
redirectTo(Pages.ctaTransferCreate({ amount }))
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
@ -163,11 +165,9 @@ export function Application(): VNode {
|
|||||||
redirectTo(Pages.ctaWithdrawManual({ amount }))
|
redirectTo(Pages.ctaWithdrawManual({ amount }))
|
||||||
}
|
}
|
||||||
goToWalletWalletInvoice={(amount?: string) =>
|
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
|
<Route
|
||||||
path={Pages.balanceTransaction.pattern}
|
path={Pages.balanceTransaction.pattern}
|
||||||
@ -275,6 +275,20 @@ export function Application(): VNode {
|
|||||||
component={DepositPageCTA}
|
component={DepositPageCTA}
|
||||||
cancel={() => redirectTo(Pages.balance)}
|
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
|
* NOT FOUND
|
||||||
|
@ -25,6 +25,10 @@ import {
|
|||||||
TransactionCommon,
|
TransactionCommon,
|
||||||
TransactionDeposit,
|
TransactionDeposit,
|
||||||
TransactionPayment,
|
TransactionPayment,
|
||||||
|
TransactionPeerPullCredit,
|
||||||
|
TransactionPeerPullDebit,
|
||||||
|
TransactionPeerPushCredit,
|
||||||
|
TransactionPeerPushDebit,
|
||||||
TransactionRefresh,
|
TransactionRefresh,
|
||||||
TransactionRefund,
|
TransactionRefund,
|
||||||
TransactionTip,
|
TransactionTip,
|
||||||
@ -118,6 +122,31 @@ const exampleData = {
|
|||||||
},
|
},
|
||||||
refundPending: undefined,
|
refundPending: undefined,
|
||||||
} as TransactionRefund,
|
} 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, {
|
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,
|
TransactionCommon,
|
||||||
TransactionDeposit,
|
TransactionDeposit,
|
||||||
TransactionPayment,
|
TransactionPayment,
|
||||||
|
TransactionPeerPullCredit,
|
||||||
|
TransactionPeerPullDebit,
|
||||||
|
TransactionPeerPushCredit,
|
||||||
|
TransactionPeerPushDebit,
|
||||||
TransactionRefresh,
|
TransactionRefresh,
|
||||||
TransactionRefund,
|
TransactionRefund,
|
||||||
TransactionTip,
|
TransactionTip,
|
||||||
@ -139,6 +143,30 @@ const exampleData = {
|
|||||||
},
|
},
|
||||||
refundPending: undefined,
|
refundPending: undefined,
|
||||||
} as TransactionRefund,
|
} 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 = {
|
const transactionError = {
|
||||||
@ -498,3 +526,19 @@ export const RefundError = createExample(TestedComponent, {
|
|||||||
export const RefundPending = createExample(TestedComponent, {
|
export const RefundPending = createExample(TestedComponent, {
|
||||||
transaction: { ...exampleData.refund, pending: true },
|
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 { Loading } from "../components/Loading.js";
|
||||||
import { LoadingError } from "../components/LoadingError.js";
|
import { LoadingError } from "../components/LoadingError.js";
|
||||||
import { Kind, Part, PartCollapsible, PartPayto } from "../components/Part.js";
|
import { Kind, Part, PartCollapsible, PartPayto } from "../components/Part.js";
|
||||||
|
import { QR } from "../components/QR.js";
|
||||||
import { ShowFullContractTermPopup } from "../components/ShowFullContractTermPopup.js";
|
import { ShowFullContractTermPopup } from "../components/ShowFullContractTermPopup.js";
|
||||||
import {
|
import {
|
||||||
CenteredDialog,
|
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 />;
|
return <div />;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -736,6 +903,88 @@ export interface AmountWithFee {
|
|||||||
raw: AmountJson;
|
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 {
|
export function WithdrawDetails({ amount }: { amount: AmountWithFee }): VNode {
|
||||||
const { i18n } = useTranslationContext();
|
const { i18n } = useTranslationContext();
|
||||||
|
|
||||||
|
@ -24,12 +24,18 @@
|
|||||||
import {
|
import {
|
||||||
AcceptExchangeTosRequest,
|
AcceptExchangeTosRequest,
|
||||||
AcceptManualWithdrawalResult,
|
AcceptManualWithdrawalResult,
|
||||||
|
AcceptPeerPullPaymentRequest,
|
||||||
|
AcceptPeerPushPaymentRequest,
|
||||||
AcceptTipRequest,
|
AcceptTipRequest,
|
||||||
AcceptWithdrawalResponse,
|
AcceptWithdrawalResponse,
|
||||||
AddExchangeRequest,
|
AddExchangeRequest,
|
||||||
AmountString,
|
AmountString,
|
||||||
ApplyRefundResponse,
|
ApplyRefundResponse,
|
||||||
BalancesResponse,
|
BalancesResponse,
|
||||||
|
CheckPeerPullPaymentRequest,
|
||||||
|
CheckPeerPullPaymentResponse,
|
||||||
|
CheckPeerPushPaymentRequest,
|
||||||
|
CheckPeerPushPaymentResponse,
|
||||||
CoinDumpJson,
|
CoinDumpJson,
|
||||||
ConfirmPayResult,
|
ConfirmPayResult,
|
||||||
CoreApiResponse,
|
CoreApiResponse,
|
||||||
@ -41,6 +47,10 @@ import {
|
|||||||
GetExchangeWithdrawalInfo,
|
GetExchangeWithdrawalInfo,
|
||||||
GetFeeForDepositRequest,
|
GetFeeForDepositRequest,
|
||||||
GetWithdrawalDetailsForUriRequest,
|
GetWithdrawalDetailsForUriRequest,
|
||||||
|
InitiatePeerPullPaymentRequest,
|
||||||
|
InitiatePeerPullPaymentResponse,
|
||||||
|
InitiatePeerPushPaymentRequest,
|
||||||
|
InitiatePeerPushPaymentResponse,
|
||||||
KnownBankAccounts,
|
KnownBankAccounts,
|
||||||
Logger,
|
Logger,
|
||||||
NotificationType,
|
NotificationType,
|
||||||
@ -473,3 +483,24 @@ export function onUpdateNotification(
|
|||||||
};
|
};
|
||||||
return platform.listenToWalletBackground(onNewMessage);
|
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,
|
tabId,
|
||||||
`/cta/refund?talerRefundUri=${talerUri}`,
|
`/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:
|
case TalerUriType.TalerNotifyReserve:
|
||||||
// FIXME: Is this still useful?
|
// FIXME: Is this still useful?
|
||||||
// handleNotifyReserve(w);
|
// handleNotifyReserve(w);
|
||||||
break;
|
|
||||||
default:
|
|
||||||
logger.warn(
|
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;
|
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