redirect after success #7357

This commit is contained in:
Sebastian 2022-09-16 16:03:58 -03:00
parent 6ddb2de842
commit 59d235e8d2
No known key found for this signature in database
GPG Key ID: BE4FF68352439FC1
36 changed files with 289 additions and 621 deletions

View File

@ -21,19 +21,19 @@ import { ButtonHandler } from "../../mui/handlers.js";
import { compose, StateViewMap } from "../../utils/index.js";
import * as wxApi from "../../wxApi.js";
import { useComponentState } from "./state.js";
import { CompletedView, LoadingUriView, ReadyView } from "./views.js";
import { LoadingUriView, ReadyView } from "./views.js";
export interface Props {
talerDepositUri: string | undefined;
amountStr: AmountString | undefined;
cancel: () => Promise<void>;
onSuccess: (tx: string) => Promise<void>;
}
export type State =
| State.Loading
| State.LoadingUriError
| State.Ready
| State.Completed;
| State.Ready;
export namespace State {
export interface Loading {
@ -62,7 +62,6 @@ export namespace State {
const viewMapping: StateViewMap<State> = {
loading: Loading,
"loading-uri": LoadingUriView,
completed: CompletedView,
ready: ReadyView,
};

View File

@ -21,12 +21,9 @@ import * as wxApi from "../../wxApi.js";
import { Props, State } from "./index.js";
export function useComponentState(
{ talerDepositUri, amountStr, cancel }: Props,
{ talerDepositUri, amountStr, cancel, onSuccess }: Props,
api: typeof wxApi,
): State {
const [result, setResult] = useState<CreateDepositGroupResponse | undefined>(
undefined,
);
const info = useAsyncAsHook(async () => {
if (!talerDepositUri) throw Error("ERROR_NO-URI-FOR-DEPOSIT");
@ -51,14 +48,7 @@ export function useComponentState(
const { deposit, uri, amount } = info.response;
async function doDeposit(): Promise<void> {
const resp = await api.createDepositGroup(uri, Amounts.stringify(amount));
setResult(resp);
}
if (result !== undefined) {
return {
status: "completed",
error: undefined,
};
onSuccess(resp.transactionId);
}
return {

View File

@ -35,6 +35,7 @@ describe("Deposit CTA states", () => {
cancel: async () => {
null;
},
onSuccess: async () => { null; },
},
{
prepareRefund: async () => ({}),
@ -75,13 +76,14 @@ describe("Deposit CTA states", () => {
cancel: async () => {
null;
},
onSuccess: async () => { null; },
},
{
prepareDeposit: async () =>
({
effectiveDepositAmount: Amounts.parseOrThrow("EUR:1"),
totalDepositCost: Amounts.parseOrThrow("EUR:1.2"),
} as PrepareDepositResponse as any),
({
effectiveDepositAmount: Amounts.parseOrThrow("EUR:1"),
totalDepositCost: Amounts.parseOrThrow("EUR:1.2"),
} as PrepareDepositResponse as any),
createDepositGroup: async () => ({}),
} as any,
),

View File

@ -40,24 +40,6 @@ export function LoadingUriView({ error }: State.LoadingUriError): VNode {
/>
);
}
export function CompletedView(state: State.Completed): VNode {
const { i18n } = useTranslationContext();
return (
<WalletAction>
<LogoHeader />
<SubTitle>
<i18n.Translate>Digital cash deposit</i18n.Translate>
</SubTitle>
<section>
<p>
<i18n.Translate>deposit completed</i18n.Translate>
</p>
</section>
</WalletAction>
);
}
export function ReadyView(state: State.Ready): VNode {
const { i18n } = useTranslationContext();

View File

@ -17,7 +17,7 @@
import { Loading } from "../../components/Loading.js";
import { HookError } from "../../hooks/useAsyncAsHook.js";
import { compose, StateViewMap } from "../../utils/index.js";
import { LoadingUriView, ReadyView, CreatedView } from "./views.js";
import { LoadingUriView, ReadyView } from "./views.js";
import * as wxApi from "../../wxApi.js";
import { useComponentState } from "./state.js";
import { AmountJson, TalerErrorDetail } from "@gnu-taler/taler-util";
@ -26,12 +26,12 @@ import { ButtonHandler, TextFieldHandler } from "../../mui/handlers.js";
export interface Props {
amount: string;
onClose: () => Promise<void>;
onSuccess: (tx: string) => Promise<void>;
}
export type State =
| State.Loading
| State.LoadingUriError
| State.Created
| State.Ready;
export namespace State {
@ -49,11 +49,6 @@ export namespace State {
error: undefined;
cancel: ButtonHandler;
}
export interface Created extends BaseInfo {
status: "created";
talerUri: string;
copyToClipboard: ButtonHandler;
}
export interface Ready extends BaseInfo {
status: "ready";
create: ButtonHandler;
@ -70,7 +65,6 @@ export namespace State {
const viewMapping: StateViewMap<State> = {
loading: Loading,
"loading-uri": LoadingUriView,
created: CreatedView,
ready: ReadyView,
};

View File

@ -22,13 +22,12 @@ import * as wxApi from "../../wxApi.js";
import { Props, State } from "./index.js";
export function useComponentState(
{ amount: amountStr, onClose }: Props,
{ amount: amountStr, onClose, onSuccess }: 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");
@ -49,22 +48,6 @@ export function useComponentState(
};
}
if (talerUri) {
return {
status: "created",
talerUri,
error: undefined,
cancel: {
onClick: onClose,
},
copyToClipboard: {
onClick: async () => {
navigator.clipboard.writeText(talerUri);
},
},
};
}
const exchanges = hook.response.exchanges.filter(
(e) => e.currency === amount.currency,
);
@ -74,7 +57,7 @@ export function useComponentState(
);
const selected = exchanges[Number(exchangeIdx)];
async function accept(): Promise<string> {
async function accept(): Promise<void> {
try {
const resp = await api.initiatePeerPullPayment({
amount: Amounts.stringify(amount),
@ -83,7 +66,8 @@ export function useComponentState(
summary: subject,
},
});
return resp.talerUri;
onSuccess(resp.transactionId);
} catch (e) {
if (e instanceof TalerError) {
setOperationError(e.errorDetail);
@ -103,10 +87,7 @@ export function useComponentState(
invalid: !subject || Amounts.isZero(amount),
exchangeUrl: selected.exchangeBaseUrl,
create: {
onClick: async () => {
const uri = await accept();
setTalerUri(uri);
},
onClick: accept
},
cancel: {
onClick: onClose,

View File

@ -20,19 +20,12 @@
*/
import { createExample } from "../../test-utils.js";
import { ReadyView, CreatedView } from "./views.js";
import { ReadyView } from "./views.js";
export default {
title: "wallet/invoice create",
};
export const ShowQr = createExample(CreatedView, {
talerUri:
"taler://pay-pull/exchange.taler.ar/HS585JK0QCXHJ8Z8QWZA3EBAY5WY7XNC1RR2MHJXSH2Z4WP0YPJ0",
cancel: {},
copyToClipboard: {},
});
export const Ready = createExample(ReadyView, {
chosenAmount: {
currency: "ARS",

View File

@ -45,38 +45,6 @@ export function LoadingUriView({ error }: State.LoadingUriError): VNode {
);
}
export function CreatedView({
talerUri,
copyToClipboard,
cancel,
}: State.Created): VNode {
const { i18n } = useTranslationContext();
return (
<WalletAction>
<LogoHeader />
<SubTitle>
<i18n.Translate>Digital cash invoice</i18n.Translate>
</SubTitle>
<section>
<p>
<i18n.Translate>Show this QR to pay the invoice</i18n.Translate>
</p>
<QR text={talerUri} />
</section>
<section>
or
<Button onClick={copyToClipboard.onClick}>
<i18n.Translate>Copy the invoice URI</i18n.Translate>
</Button>
</section>
<section>
<Link upperCased onClick={cancel.onClick}>
<i18n.Translate>Close</i18n.Translate>
</Link>
</section>
</WalletAction>
);
}
export function ReadyView({
invalid,

View File

@ -32,6 +32,7 @@ export interface Props {
talerPayPullUri: string;
onClose: () => Promise<void>;
goToWalletManualWithdraw: (amount?: string) => Promise<void>;
onSuccess: (tx: string) => Promise<void>;
}
export type State =

View File

@ -30,7 +30,7 @@ import * as wxApi from "../../wxApi.js";
import { Props, State } from "./index.js";
export function useComponentState(
{ talerPayPullUri, onClose, goToWalletManualWithdraw }: Props,
{ talerPayPullUri, onClose, goToWalletManualWithdraw, onSuccess }: Props,
api: typeof wxApi,
): State {
const hook = useAsyncAsHook(async () => {
@ -149,7 +149,7 @@ export function useComponentState(
const resp = await api.acceptPeerPullPayment({
peerPullPaymentIncomingId,
});
await onClose();
onSuccess(resp.transactionId);
} catch (e) {
if (e instanceof TalerError) {
setOperationError(e.errorDetail);

View File

@ -35,6 +35,7 @@ export interface Props {
talerPayUri?: string;
goToWalletManualWithdraw: (amount?: string) => Promise<void>;
cancel: () => Promise<void>;
onSuccess: (tx: string) => Promise<void>;
}
export type State =
@ -43,7 +44,6 @@ export type State =
| State.Ready
| State.NoEnoughBalance
| State.NoBalanceForCurrency
| State.Completed
| State.Confirmed;
export namespace State {
@ -86,13 +86,6 @@ export namespace State {
balance: AmountJson;
}
export interface Completed extends BaseInfo {
status: "completed";
payStatus: PreparePayResult;
payResult: ConfirmPayResult;
paymentError?: TalerError;
balance: AmountJson;
}
}
const viewMapping: StateViewMap<State> = {
@ -101,7 +94,6 @@ const viewMapping: StateViewMap<State> = {
"no-balance-for-currency": BaseView,
"no-enough-balance": BaseView,
confirmed: BaseView,
completed: BaseView,
ready: BaseView,
};

View File

@ -31,12 +31,9 @@ import * as wxApi from "../../wxApi.js";
import { Props, State } from "./index.js";
export function useComponentState(
{ talerPayUri, cancel, goToWalletManualWithdraw }: Props,
{ talerPayUri, cancel, goToWalletManualWithdraw, onSuccess }: Props,
api: typeof wxApi,
): State {
const [payResult, setPayResult] = useState<ConfirmPayResult | undefined>(
undefined,
);
const [payErrMsg, setPayErrMsg] = useState<TalerError | undefined>(undefined);
const hook = useAsyncAsHook(async () => {
@ -104,17 +101,6 @@ export function useComponentState(
const foundAmount = Amounts.parseOrThrow(foundBalance.available);
if (payResult) {
return {
status: "completed",
balance: foundAmount,
payStatus,
paymentError: payErrMsg,
payResult,
...baseResult,
};
}
if (payStatus.status === PreparePayResultType.InsufficientBalance) {
return {
status: "no-enough-balance",
@ -157,7 +143,7 @@ export function useComponentState(
console.log(`should d to ${fu}`);
}
}
setPayResult(res);
onSuccess(res.transactionId);
} catch (e) {
if (e instanceof TalerError) {
setPayErrMsg(e);

View File

@ -334,80 +334,4 @@ export const AlreadyConfirmedByOther = createExample(BaseView, {
},
});
export const AlreadyPaidWithoutFulfillment = createExample(BaseView, {
status: "completed",
error: undefined,
amount: Amounts.parseOrThrow("USD:10"),
balance: {
currency: "USD",
fraction: 40000000,
value: 11,
},
uri: "taler://pay/merchant-backend.taler/2021.242-01G2X4275RBWG/?c=66BE594PDZR24744J6EQK52XM0",
payResult: {
type: ConfirmPayResultType.Done,
contractTerms: {} as any,
transactionId: "",
},
payStatus: {
status: PreparePayResultType.AlreadyConfirmed,
amountEffective: "USD:10",
amountRaw: "USD:10",
contractTerms: {
merchant: {
name: "the merchant",
logo: merchantIcon,
website: "https://www.themerchant.taler",
email: "contact@merchant.taler",
},
summary: "some beers",
amount: "USD:10",
} as Partial<ContractTerms> as any,
contractTermsHash: "123456",
proposalId: "96YY92RQZGF3V7TJSPN4SF9549QX7BRF88Q5PYFCSBNQ0YK4RPK0",
paid: true,
},
});
export const AlreadyPaidWithFulfillment = createExample(BaseView, {
status: "completed",
error: undefined,
amount: Amounts.parseOrThrow("USD:10"),
balance: {
currency: "USD",
fraction: 40000000,
value: 11,
},
uri: "taler://pay/merchant-backend.taler/2021.242-01G2X4275RBWG/?c=66BE594PDZR24744J6EQK52XM0",
payResult: {
type: ConfirmPayResultType.Done,
contractTerms: {
fulfillment_message: "thanks for buying!",
fulfillment_url: "https://demo.taler.net",
} as Partial<ContractTerms> as any,
transactionId: "",
},
payStatus: {
status: PreparePayResultType.AlreadyConfirmed,
amountEffective: "USD:10",
amountRaw: "USD:10",
contractTerms: {
merchant: {
name: "the merchant",
logo: merchantIcon,
website: "https://www.themerchant.taler",
email: "contact@merchant.taler",
},
fulfillment_url: "https://demo.taler.net",
fulfillment_message:
"congratulations! you are looking at the fulfillment message! ",
summary: "some beers",
amount: "USD:10",
} as Partial<ContractTerms> as any,
contractTermsHash: "123456",
proposalId: "96YY92RQZGF3V7TJSPN4SF9549QX7BRF88Q5PYFCSBNQ0YK4RPK0",
paid: true,
},
});

View File

@ -75,6 +75,7 @@ describe("Payment CTA states", () => {
talerPayUri: undefined,
cancel: nullFunction,
goToWalletManualWithdraw: nullFunction,
onSuccess: async () => { null; },
},
{
onUpdateNotification: nullFunction,
@ -110,18 +111,19 @@ describe("Payment CTA states", () => {
talerPayUri: "taller://pay",
cancel: nullFunction,
goToWalletManualWithdraw: nullFunction,
onSuccess: async () => { null; },
},
{
onUpdateNotification: nullFunction,
preparePay: async () =>
({
amountRaw: "USD:10",
status: PreparePayResultType.InsufficientBalance,
} as Partial<PreparePayResult>),
({
amountRaw: "USD:10",
status: PreparePayResultType.InsufficientBalance,
} as Partial<PreparePayResult>),
getBalance: async () =>
({
balances: [],
} as Partial<BalancesResponse>),
({
balances: [],
} as Partial<BalancesResponse>),
} as Partial<typeof wxApi> as any,
),
);
@ -152,22 +154,23 @@ describe("Payment CTA states", () => {
talerPayUri: "taller://pay",
cancel: nullFunction,
goToWalletManualWithdraw: nullFunction,
onSuccess: async () => { null; },
},
{
onUpdateNotification: nullFunction,
preparePay: async () =>
({
amountRaw: "USD:10",
status: PreparePayResultType.InsufficientBalance,
} as Partial<PreparePayResult>),
({
amountRaw: "USD:10",
status: PreparePayResultType.InsufficientBalance,
} as Partial<PreparePayResult>),
getBalance: async () =>
({
balances: [
{
available: "USD:5",
},
],
} as Partial<BalancesResponse>),
({
balances: [
{
available: "USD:5",
},
],
} as Partial<BalancesResponse>),
} as Partial<typeof wxApi> as any,
),
);
@ -198,23 +201,24 @@ describe("Payment CTA states", () => {
talerPayUri: "taller://pay",
cancel: nullFunction,
goToWalletManualWithdraw: nullFunction,
onSuccess: async () => { null; },
},
{
onUpdateNotification: nullFunction,
preparePay: async () =>
({
amountRaw: "USD:10",
amountEffective: "USD:10",
status: PreparePayResultType.PaymentPossible,
} as Partial<PreparePayResult>),
({
amountRaw: "USD:10",
amountEffective: "USD:10",
status: PreparePayResultType.PaymentPossible,
} as Partial<PreparePayResult>),
getBalance: async () =>
({
balances: [
{
available: "USD:15",
},
],
} as Partial<BalancesResponse>),
({
balances: [
{
available: "USD:15",
},
],
} as Partial<BalancesResponse>),
} as Partial<typeof wxApi> as any,
),
);
@ -247,23 +251,24 @@ describe("Payment CTA states", () => {
talerPayUri: "taller://pay",
cancel: nullFunction,
goToWalletManualWithdraw: nullFunction,
onSuccess: async () => { null; },
},
{
onUpdateNotification: nullFunction,
preparePay: async () =>
({
amountRaw: "USD:9",
amountEffective: "USD:10",
status: PreparePayResultType.PaymentPossible,
} as Partial<PreparePayResult>),
({
amountRaw: "USD:9",
amountEffective: "USD:10",
status: PreparePayResultType.PaymentPossible,
} as Partial<PreparePayResult>),
getBalance: async () =>
({
balances: [
{
available: "USD:15",
},
],
} as Partial<BalancesResponse>),
({
balances: [
{
available: "USD:15",
},
],
} as Partial<BalancesResponse>),
} as Partial<typeof wxApi> as any,
),
);
@ -296,28 +301,29 @@ describe("Payment CTA states", () => {
talerPayUri: "taller://pay",
cancel: nullFunction,
goToWalletManualWithdraw: nullFunction,
onSuccess: async () => { null; },
},
{
onUpdateNotification: nullFunction,
preparePay: async () =>
({
amountRaw: "USD:9",
amountEffective: "USD:10",
status: PreparePayResultType.PaymentPossible,
} as Partial<PreparePayResult>),
({
amountRaw: "USD:9",
amountEffective: "USD:10",
status: PreparePayResultType.PaymentPossible,
} as Partial<PreparePayResult>),
getBalance: async () =>
({
balances: [
{
available: "USD:15",
},
],
} as Partial<BalancesResponse>),
({
balances: [
{
available: "USD:15",
},
],
} as Partial<BalancesResponse>),
confirmPay: async () =>
({
type: ConfirmPayResultType.Done,
contractTerms: {},
} as Partial<ConfirmPayResult>),
({
type: ConfirmPayResultType.Done,
contractTerms: {},
} as Partial<ConfirmPayResult>),
} as Partial<typeof wxApi> as any,
),
);
@ -340,18 +346,18 @@ describe("Payment CTA states", () => {
r.payHandler.onClick();
}
await waitNextUpdate();
// await waitNextUpdate();
{
const r = getLastResultOrThrow();
if (r.status !== "completed") expect.fail();
expect(r.balance).deep.equal(Amounts.parseOrThrow("USD:15"));
expect(r.amount).deep.equal(Amounts.parseOrThrow("USD:9"));
// expect(r.totalFees).deep.equal(Amounts.parseOrThrow("USD:1"));
// if (r.payResult.type !== ConfirmPayResultType.Done) expect.fail();
// expect(r.payResult.contractTerms).not.undefined;
// expect(r.payHandler.onClick).undefined;
}
// {
// const r = getLastResultOrThrow();
// if (r.status !== "completed") expect.fail();
// expect(r.balance).deep.equal(Amounts.parseOrThrow("USD:15"));
// expect(r.amount).deep.equal(Amounts.parseOrThrow("USD:9"));
// // expect(r.totalFees).deep.equal(Amounts.parseOrThrow("USD:1"));
// // if (r.payResult.type !== ConfirmPayResultType.Done) expect.fail();
// // expect(r.payResult.contractTerms).not.undefined;
// // expect(r.payHandler.onClick).undefined;
// }
await assertNoPendingUpdate();
});
@ -364,28 +370,29 @@ describe("Payment CTA states", () => {
talerPayUri: "taller://pay",
cancel: nullFunction,
goToWalletManualWithdraw: nullFunction,
onSuccess: async () => { null; },
},
{
onUpdateNotification: nullFunction,
preparePay: async () =>
({
amountRaw: "USD:9",
amountEffective: "USD:10",
status: PreparePayResultType.PaymentPossible,
} as Partial<PreparePayResult>),
({
amountRaw: "USD:9",
amountEffective: "USD:10",
status: PreparePayResultType.PaymentPossible,
} as Partial<PreparePayResult>),
getBalance: async () =>
({
balances: [
{
available: "USD:15",
},
],
} as Partial<BalancesResponse>),
({
balances: [
{
available: "USD:15",
},
],
} as Partial<BalancesResponse>),
confirmPay: async () =>
({
type: ConfirmPayResultType.Pending,
lastError: { code: 1 },
} as Partial<ConfirmPayResult>),
({
type: ConfirmPayResultType.Pending,
lastError: { code: 1 },
} as Partial<ConfirmPayResult>),
} as Partial<typeof wxApi> as any,
),
);
@ -447,23 +454,24 @@ describe("Payment CTA states", () => {
talerPayUri: "taller://pay",
cancel: nullFunction,
goToWalletManualWithdraw: nullFunction,
onSuccess: async () => { null; },
},
{
onUpdateNotification: subscriptions.saveSubscription,
preparePay: async () =>
({
amountRaw: "USD:9",
amountEffective: "USD:10",
status: PreparePayResultType.PaymentPossible,
} as Partial<PreparePayResult>),
({
amountRaw: "USD:9",
amountEffective: "USD:10",
status: PreparePayResultType.PaymentPossible,
} as Partial<PreparePayResult>),
getBalance: async () =>
({
balances: [
{
available: Amounts.stringify(availableBalance),
},
],
} as Partial<BalancesResponse>),
({
balances: [
{
available: Amounts.stringify(availableBalance),
},
],
} as Partial<BalancesResponse>),
} as Partial<typeof wxApi> as any,
),
);

View File

@ -63,7 +63,6 @@ export function LoadingUriView({ error }: State.LoadingUriError): VNode {
type SupportedStates =
| State.Ready
| State.Confirmed
| State.Completed
| State.NoBalanceForCurrency
| State.NoEnoughBalance;
@ -167,7 +166,6 @@ export function BaseView(state: SupportedStates): VNode {
/>
)}
</section>
{state.status !== "completed" ? (
<ButtonsSection
amount={state.amount}
balance={state.balance}
@ -176,7 +174,6 @@ export function BaseView(state: SupportedStates): VNode {
payHandler={state.status === "ready" ? state.payHandler : undefined}
goToWalletManualWithdraw={state.goToWalletManualWithdraw}
/>
) : undefined}
<section>
<Link upperCased onClick={state.cancel}>
<i18n.Translate>Cancel</i18n.Translate>
@ -285,35 +282,6 @@ function ShowImportantMessage({ state }: { state: SupportedStates }): VNode {
);
}
if (state.status == "completed") {
const { payResult, paymentError } = state;
if (paymentError) {
return <ErrorTalerOperation error={paymentError.errorDetail} />;
}
if (payResult.type === ConfirmPayResultType.Done) {
return (
<SuccessBox>
<h3>
<i18n.Translate>Payment complete</i18n.Translate>
</h3>
<p>
{!payResult.contractTerms.fulfillment_message ? (
payResult.contractTerms.fulfillment_url ? (
<i18n.Translate>
You are going to be redirected to $
{payResult.contractTerms.fulfillment_url}
</i18n.Translate>
) : (
<i18n.Translate>You can close this page.</i18n.Translate>
)
) : (
payResult.contractTerms.fulfillment_message
)}
</p>
</SuccessBox>
);
}
}
return <Fragment />;
}

View File

@ -22,7 +22,6 @@ import { compose, StateViewMap } from "../../utils/index.js";
import * as wxApi from "../../wxApi.js";
import { useComponentState } from "./state.js";
import {
CompletedView,
IgnoredView,
InProgressView,
LoadingUriView,
@ -32,6 +31,7 @@ import {
export interface Props {
talerRefundUri?: string;
cancel: () => Promise<void>;
onSuccess: (tx: string) => Promise<void>;
}
export type State =
@ -39,8 +39,7 @@ export type State =
| State.LoadingUriError
| State.Ready
| State.Ignored
| State.InProgress
| State.Completed;
| State.InProgress;
export namespace State {
export interface Loading {
@ -79,17 +78,12 @@ export namespace State {
status: "in-progress";
error: undefined;
}
export interface Completed extends BaseInfo {
status: "completed";
error: undefined;
}
}
const viewMapping: StateViewMap<State> = {
loading: Loading,
"loading-uri": LoadingUriView,
"in-progress": InProgressView,
completed: CompletedView,
ignored: IgnoredView,
ready: ReadyView,
};

View File

@ -21,7 +21,7 @@ import * as wxApi from "../../wxApi.js";
import { Props, State } from "./index.js";
export function useComponentState(
{ talerRefundUri, cancel }: Props,
{ talerRefundUri, cancel, onSuccess }: Props,
api: typeof wxApi,
): State {
const [ignored, setIgnored] = useState(false);
@ -51,8 +51,9 @@ export function useComponentState(
const { refund, uri } = info.response;
const doAccept = async (): Promise<void> => {
await api.applyRefund(uri);
info.retry();
const res = await api.applyRefund(uri);
onSuccess(res.transactionId);
};
const doIgnore = async (): Promise<void> => {
@ -75,13 +76,6 @@ export function useComponentState(
};
}
if (Amounts.isZero(baseInfo.awaitingAmount)) {
return {
status: "completed",
...baseInfo,
};
}
if (refund.pending) {
return {
status: "in-progress",

View File

@ -23,7 +23,6 @@ import { Amounts } from "@gnu-taler/taler-util";
import beer from "../../../static-dev/beer.png";
import { createExample } from "../../test-utils.js";
import {
CompletedView,
IgnoredView,
InProgressView,
ReadyView,
@ -32,15 +31,6 @@ export default {
title: "cta/refund",
};
export const Complete = createExample(CompletedView, {
status: "completed",
amount: Amounts.parseOrThrow("USD:1"),
granted: Amounts.parseOrThrow("USD:1"),
error: undefined,
merchantName: "the merchant",
products: undefined,
});
export const InProgress = createExample(InProgressView, {
status: "in-progress",
error: undefined,

View File

@ -40,6 +40,7 @@ describe("Refund CTA states", () => {
cancel: async () => {
null;
},
onSuccess: async () => { null; },
},
{
prepareRefund: async () => ({}),
@ -79,25 +80,26 @@ describe("Refund CTA states", () => {
cancel: async () => {
null;
},
onSuccess: async () => { null; },
},
{
prepareRefund: async () =>
({
effectivePaid: "EUR:2",
awaiting: "EUR:2",
gone: "EUR:0",
granted: "EUR:0",
pending: false,
proposalId: "1",
info: {
contractTermsHash: "123",
merchant: {
name: "the merchant name",
},
orderId: "orderId1",
summary: "the summary",
({
effectivePaid: "EUR:2",
awaiting: "EUR:2",
gone: "EUR:0",
granted: "EUR:0",
pending: false,
proposalId: "1",
info: {
contractTermsHash: "123",
merchant: {
name: "the merchant name",
},
} as PrepareRefundResult as any),
orderId: "orderId1",
summary: "the summary",
},
} as PrepareRefundResult as any),
applyRefund: async () => ({}),
onUpdateNotification: async () => ({}),
} as any,
@ -136,25 +138,26 @@ describe("Refund CTA states", () => {
cancel: async () => {
null;
},
onSuccess: async () => { null; },
},
{
prepareRefund: async () =>
({
effectivePaid: "EUR:2",
awaiting: "EUR:2",
gone: "EUR:0",
granted: "EUR:0",
pending: false,
proposalId: "1",
info: {
contractTermsHash: "123",
merchant: {
name: "the merchant name",
},
orderId: "orderId1",
summary: "the summary",
({
effectivePaid: "EUR:2",
awaiting: "EUR:2",
gone: "EUR:0",
granted: "EUR:0",
pending: false,
proposalId: "1",
info: {
contractTermsHash: "123",
merchant: {
name: "the merchant name",
},
} as PrepareRefundResult as any),
orderId: "orderId1",
summary: "the summary",
},
} as PrepareRefundResult as any),
applyRefund: async () => ({}),
onUpdateNotification: async () => ({}),
} as any,
@ -220,25 +223,27 @@ describe("Refund CTA states", () => {
cancel: async () => {
null;
},
onSuccess: async () => { null; },
},
{
prepareRefund: async () =>
({
awaiting: Amounts.stringify(awaiting),
effectivePaid: "EUR:2",
gone: "EUR:0",
granted: Amounts.stringify(granted),
pending,
proposalId: "1",
info: {
contractTermsHash: "123",
merchant: {
name: "the merchant name",
},
orderId: "orderId1",
summary: "the summary",
({
awaiting: Amounts.stringify(awaiting),
effectivePaid: "EUR:2",
gone: "EUR:0",
granted: Amounts.stringify(granted),
pending,
proposalId: "1",
info: {
contractTermsHash: "123",
merchant: {
name: "the merchant name",
},
} as PrepareRefundResult as any),
orderId: "orderId1",
summary: "the summary",
},
} as PrepareRefundResult as any),
applyRefund: async () => ({}),
onUpdateNotification: subscriptions.saveSubscription,
} as any,
@ -286,7 +291,7 @@ describe("Refund CTA states", () => {
{
const state = getLastResultOrThrow();
if (state.status !== "completed") expect.fail("3");
if (state.status !== "ready") expect.fail("3");
if (state.error) expect.fail();
expect(state.merchantName).eq("the merchant name");
expect(state.products).undefined;

View File

@ -92,32 +92,6 @@ export function InProgressView(state: State.InProgress): VNode {
</WalletAction>
);
}
export function CompletedView(state: State.Completed): VNode {
const { i18n } = useTranslationContext();
return (
<WalletAction>
<LogoHeader />
<SubTitle>
<i18n.Translate>Digital cash refund</i18n.Translate>
</SubTitle>
<section>
<p>
<i18n.Translate>this refund is already accepted.</i18n.Translate>
</p>
</section>
<section>
<Part
big
title={<i18n.Translate>Total to refunded</i18n.Translate>}
text={<Amount value={state.granted} />}
kind="negative"
/>
</section>
</WalletAction>
);
}
export function ReadyView(state: State.Ready): VNode {
const { i18n } = useTranslationContext();
return (

View File

@ -32,6 +32,7 @@ import {
export interface Props {
talerTipUri?: string;
onCancel: () => Promise<void>;
onSuccess: (tx: string) => Promise<void>;
}
export type State =

View File

@ -21,7 +21,7 @@ import * as wxApi from "../../wxApi.js";
import { Props, State } from "./index.js";
export function useComponentState(
{ talerTipUri, onCancel }: Props,
{ talerTipUri, onCancel, onSuccess }: Props,
api: typeof wxApi,
): State {
const [tipIgnored, setTipIgnored] = useState(false);
@ -48,8 +48,11 @@ export function useComponentState(
const { tip } = tipInfo.response;
const doAccept = async (): Promise<void> => {
await api.acceptTip({ walletTipId: tip.walletTipId });
const res = await api.acceptTip({ walletTipId: tip.walletTipId });
//FIX: this may not be seen since we are moving to the success also
tipInfo.retry();
onSuccess(res.transactionId)
};
const baseInfo = {

View File

@ -34,6 +34,7 @@ describe("Tip CTA states", () => {
onCancel: async () => {
null;
},
onSuccess: async () => { null; },
},
{
prepareTip: async () => ({}),
@ -74,16 +75,17 @@ describe("Tip CTA states", () => {
onCancel: async () => {
null;
},
onSuccess: async () => { null; },
},
{
prepareTip: async () =>
({
accepted: tipAccepted,
exchangeBaseUrl: "exchange url",
merchantBaseUrl: "merchant url",
tipAmountEffective: "EUR:1",
walletTipId: "tip_id",
} as PrepareTipResult as any),
({
accepted: tipAccepted,
exchangeBaseUrl: "exchange url",
merchantBaseUrl: "merchant url",
tipAmountEffective: "EUR:1",
walletTipId: "tip_id",
} as PrepareTipResult as any),
acceptTip: async () => {
tipAccepted = true;
},
@ -134,15 +136,16 @@ describe("Tip CTA states", () => {
onCancel: async () => {
null;
},
onSuccess: async () => { null; },
},
{
prepareTip: async () =>
({
exchangeBaseUrl: "exchange url",
merchantBaseUrl: "merchant url",
tipAmountEffective: "EUR:1",
walletTipId: "tip_id",
} as PrepareTipResult as any),
({
exchangeBaseUrl: "exchange url",
merchantBaseUrl: "merchant url",
tipAmountEffective: "EUR:1",
walletTipId: "tip_id",
} as PrepareTipResult as any),
acceptTip: async () => ({}),
} as any,
),
@ -188,16 +191,17 @@ describe("Tip CTA states", () => {
onCancel: async () => {
null;
},
onSuccess: async () => { null; },
},
{
prepareTip: async () =>
({
accepted: true,
exchangeBaseUrl: "exchange url",
merchantBaseUrl: "merchant url",
tipAmountEffective: "EUR:1",
walletTipId: "tip_id",
} as PrepareTipResult as any),
({
accepted: true,
exchangeBaseUrl: "exchange url",
merchantBaseUrl: "merchant url",
tipAmountEffective: "EUR:1",
walletTipId: "tip_id",
} as PrepareTipResult as any),
acceptTip: async () => ({}),
} as any,
),

View File

@ -21,17 +21,17 @@ import { ButtonHandler, TextFieldHandler } from "../../mui/handlers.js";
import { compose, StateViewMap } from "../../utils/index.js";
import * as wxApi from "../../wxApi.js";
import { useComponentState } from "./state.js";
import { LoadingUriView, ReadyView, CreatedView } from "./views.js";
import { LoadingUriView, ReadyView } from "./views.js";
export interface Props {
amount: string;
onClose: () => Promise<void>;
onSuccess: (tx: string) => Promise<void>;
}
export type State =
| State.Loading
| State.LoadingUriError
| State.Created
| State.Ready;
export namespace State {
@ -49,11 +49,6 @@ export namespace State {
error: undefined;
cancel: ButtonHandler;
}
export interface Created extends BaseInfo {
status: "created";
talerUri: string;
copyToClipboard: ButtonHandler;
}
export interface Ready extends BaseInfo {
status: "ready";
invalid: boolean;
@ -69,7 +64,6 @@ export namespace State {
const viewMapping: StateViewMap<State> = {
loading: Loading,
"loading-uri": LoadingUriView,
created: CreatedView,
ready: ReadyView,
};

View File

@ -21,34 +21,18 @@ import * as wxApi from "../../wxApi.js";
import { Props, State } from "./index.js";
export function useComponentState(
{ amount: amountStr, onClose }: Props,
{ amount: amountStr, onClose, onSuccess }: 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: "created",
talerUri,
error: undefined,
cancel: {
onClick: onClose,
},
copyToClipboard: {
onClick: async () => {
navigator.clipboard.writeText(talerUri);
},
},
};
}
async function accept(): Promise<string> {
async function accept(): Promise<void> {
try {
const resp = await api.initiatePeerPushPayment({
amount: Amounts.stringify(amount),
@ -56,7 +40,7 @@ export function useComponentState(
summary: subject,
},
});
return resp.talerUri;
onSuccess(resp.transactionId);
} catch (e) {
if (e instanceof TalerError) {
setOperationError(e.errorDetail);
@ -77,10 +61,7 @@ export function useComponentState(
onInput: async (e) => setSubject(e),
},
create: {
onClick: async () => {
const uri = await accept();
setTalerUri(uri);
},
onClick: accept
},
chosenAmount: amount,
toBeReceived: amount,

View File

@ -20,19 +20,12 @@
*/
import { createExample } from "../../test-utils.js";
import { ReadyView, CreatedView } from "./views.js";
import { ReadyView } from "./views.js";
export default {
title: "wallet/transfer create",
};
export const ShowQr = createExample(CreatedView, {
talerUri:
"taler://pay-push/exchange.taler.ar/HS585JK0QCXHJ8Z8QWZA3EBAY5WY7XNC1RR2MHJXSH2Z4WP0YPJ0",
cancel: {},
copyToClipboard: {},
});
export const Ready = createExample(ReadyView, {
chosenAmount: {
currency: "ARS",

View File

@ -38,34 +38,6 @@ export function LoadingUriView({ error }: State.LoadingUriError): VNode {
);
}
export function CreatedView({
talerUri,
copyToClipboard,
cancel,
}: State.Created): VNode {
const { i18n } = useTranslationContext();
return (
<WalletAction>
<LogoHeader />
<SubTitle>
<i18n.Translate>Digital cash transfer</i18n.Translate>
</SubTitle>
<section>
<p>Show this QR to receive the transfer</p>
<QR text={talerUri} />
</section>
<section>
or
<Button onClick={copyToClipboard.onClick}>Copy the transfer URI</Button>
</section>
<section>
<Link upperCased onClick={cancel.onClick}>
<i18n.Translate>Close</i18n.Translate>
</Link>
</section>
</WalletAction>
);
}
export function ReadyView({
subject,

View File

@ -30,6 +30,7 @@ import { LoadingUriView, ReadyView } from "./views.js";
export interface Props {
talerPayPushUri: string;
onClose: () => Promise<void>;
onSuccess: (tx: string) => Promise<void>;
}
export type State = State.Loading | State.LoadingUriError | State.Ready;

View File

@ -27,7 +27,7 @@ import * as wxApi from "../../wxApi.js";
import { Props, State } from "./index.js";
export function useComponentState(
{ talerPayPushUri, onClose }: Props,
{ talerPayPushUri, onClose, onSuccess }: Props,
api: typeof wxApi,
): State {
const hook = useAsyncAsHook(async () => {
@ -68,7 +68,7 @@ export function useComponentState(
const resp = await api.acceptPeerPushPayment({
peerPushPaymentIncomingId,
});
await onClose();
onSuccess(resp.transactionId)
} catch (e) {
if (e instanceof TalerError) {
setOperationError(e.errorDetail);

View File

@ -26,7 +26,6 @@ import {
useComponentStateFromURI,
} from "./state.js";
import {
CompletedView,
LoadingExchangeView,
LoadingInfoView,
LoadingUriView,
@ -36,11 +35,13 @@ import {
export interface PropsFromURI {
talerWithdrawUri: string | undefined;
cancel: () => Promise<void>;
onSuccess: (txid: string) => Promise<void>;
}
export interface PropsFromParams {
amount: string;
cancel: () => Promise<void>;
onSuccess: (txid: string) => Promise<void>;
}
export type State =
@ -48,8 +49,7 @@ export type State =
| State.LoadingUriError
| State.LoadingExchangeError
| State.LoadingInfoError
| State.Success
| State.Completed;
| State.Success;
export namespace State {
export interface Loading {
@ -69,11 +69,6 @@ export namespace State {
error: HookError;
}
export type Completed = {
status: "completed";
error: undefined;
};
export type Success = {
status: "success";
error: undefined;
@ -100,7 +95,6 @@ const viewMapping: StateViewMap<State> = {
"loading-uri": LoadingUriView,
"loading-exchange": LoadingExchangeView,
"loading-info": LoadingInfoView,
completed: CompletedView,
success: SuccessView,
};

View File

@ -23,7 +23,7 @@ import * as wxApi from "../../wxApi.js";
import { PropsFromURI, PropsFromParams, State } from "./index.js";
export function useComponentStateFromParams(
{ amount, cancel }: PropsFromParams,
{ amount, cancel, onSuccess }: PropsFromParams,
api: typeof wxApi,
): State {
const [ageRestricted, setAgeRestricted] = useState(0);
@ -40,8 +40,8 @@ export function useComponentStateFromParams(
// get the first exchange with the currency as the default one
const exchange = exchangeHookDep
? exchangeHookDep.exchanges.find(
(e) => e.currency === chosenAmount.currency,
)
(e) => e.currency === chosenAmount.currency,
)
: undefined;
/**
* For the exchange selected, bring the status of the terms of service
@ -90,7 +90,6 @@ export function useComponentStateFromParams(
undefined,
);
const [doingWithdraw, setDoingWithdraw] = useState<boolean>(false);
const [withdrawCompleted, setWithdrawCompleted] = useState<boolean>(false);
if (!exchangeHook) return { status: "loading", error: undefined };
if (exchangeHook.hasError) {
@ -122,7 +121,7 @@ export function useComponentStateFromParams(
Amounts.stringify(amount),
);
setWithdrawCompleted(true);
onSuccess(response.transactionId);
} catch (e) {
if (e instanceof TalerError) {
setWithdrawError(e);
@ -143,9 +142,6 @@ export function useComponentStateFromParams(
if (!amountHook.response) {
return { status: "loading", error: undefined };
}
if (withdrawCompleted) {
return { status: "completed", error: undefined };
}
const withdrawalFee = Amounts.sub(
amountHook.response.amount.raw,
@ -156,8 +152,8 @@ export function useComponentStateFromParams(
const { state: termsState } = (!terms
? undefined
: terms.hasError
? undefined
: terms.response) || { state: undefined };
? undefined
: terms.response) || { state: undefined };
async function onAccept(accepted: boolean): Promise<void> {
if (!termsState || !exchange) return;
@ -194,10 +190,10 @@ export function useComponentStateFromParams(
//TODO: calculate based on exchange info
const ageRestriction = ageRestrictionEnabled
? {
list: ageRestrictionOptions,
value: String(ageRestricted),
onChange: async (v: string) => setAgeRestricted(parseInt(v, 10)),
}
list: ageRestrictionOptions,
value: String(ageRestricted),
onChange: async (v: string) => setAgeRestricted(parseInt(v, 10)),
}
: undefined;
return {
@ -218,19 +214,19 @@ export function useComponentStateFromParams(
tosProps: !termsState
? undefined
: {
onAccept,
onReview: setReviewing,
reviewed: reviewed,
reviewing: reviewing,
terms: termsState,
},
onAccept,
onReview: setReviewing,
reviewed: reviewed,
reviewing: reviewing,
terms: termsState,
},
mustAcceptFirst,
cancel,
};
}
export function useComponentStateFromURI(
{ talerWithdrawUri, cancel }: PropsFromURI,
{ talerWithdrawUri, cancel, onSuccess }: PropsFromURI,
api: typeof wxApi,
): State {
const [ageRestricted, setAgeRestricted] = useState(0);
@ -303,7 +299,6 @@ export function useComponentStateFromURI(
undefined,
);
const [doingWithdraw, setDoingWithdraw] = useState<boolean>(false);
const [withdrawCompleted, setWithdrawCompleted] = useState<boolean>(false);
if (!uriInfoHook) return { status: "loading", error: undefined };
if (uriInfoHook.hasError) {
@ -343,8 +338,10 @@ export function useComponentStateFromURI(
);
if (res.confirmTransferUrl) {
document.location.href = res.confirmTransferUrl;
} else {
onSuccess(res.transactionId)
}
setWithdrawCompleted(true);
} catch (e) {
if (e instanceof TalerError) {
setWithdrawError(e);
@ -365,9 +362,6 @@ export function useComponentStateFromURI(
if (!amountHook.response) {
return { status: "loading", error: undefined };
}
if (withdrawCompleted) {
return { status: "completed", error: undefined };
}
const withdrawalFee = Amounts.sub(
amountHook.response.amount.raw,
@ -378,8 +372,8 @@ export function useComponentStateFromURI(
const { state: termsState } = (!terms
? undefined
: terms.hasError
? undefined
: terms.response) || { state: undefined };
? undefined
: terms.response) || { state: undefined };
async function onAccept(accepted: boolean): Promise<void> {
if (!termsState || !thisExchange) return;
@ -416,10 +410,10 @@ export function useComponentStateFromURI(
//TODO: calculate based on exchange info
const ageRestriction = ageRestrictionEnabled
? {
list: ageRestrictionOptions,
value: String(ageRestricted),
onChange: async (v: string) => setAgeRestricted(parseInt(v, 10)),
}
list: ageRestrictionOptions,
value: String(ageRestricted),
onChange: async (v: string) => setAgeRestricted(parseInt(v, 10)),
}
: undefined;
return {
@ -441,12 +435,12 @@ export function useComponentStateFromURI(
tosProps: !termsState
? undefined
: {
onAccept,
onReview: setReviewing,
reviewed: reviewed,
reviewing: reviewing,
terms: termsState,
},
onAccept,
onReview: setReviewing,
reviewed: reviewed,
reviewing: reviewing,
terms: termsState,
},
mustAcceptFirst,
cancel,
};

View File

@ -21,7 +21,7 @@
import { createExample } from "../../test-utils.js";
import { TermsState } from "../../utils/index.js";
import { CompletedView, SuccessView } from "./views.js";
import { SuccessView } from "./views.js";
export default {
title: "cta/withdraw",
@ -179,11 +179,6 @@ export const EditExchangeModified = createExample(SuccessView, {
tosProps: normalTosState,
});
export const CompletedWithoutBankURL = createExample(CompletedView, {
status: "completed",
error: undefined,
});
export const WithAgeRestriction = createExample(SuccessView, {
error: undefined,
status: "success",

View File

@ -68,6 +68,7 @@ describe("Withdraw CTA states", () => {
cancel: async () => {
null;
},
onSuccess: async () => { null },
},
{
listExchanges: async () => ({ exchanges }),
@ -108,6 +109,7 @@ describe("Withdraw CTA states", () => {
cancel: async () => {
null;
},
onSuccess: async () => { null },
},
{
listExchanges: async () => ({ exchanges }),
@ -150,6 +152,7 @@ describe("Withdraw CTA states", () => {
cancel: async () => {
null;
},
onSuccess: async () => { null },
},
{
listExchanges: async () => ({ exchanges }),
@ -160,10 +163,10 @@ describe("Withdraw CTA states", () => {
}),
getExchangeWithdrawalInfo:
async (): Promise<ExchangeWithdrawDetails> =>
({
withdrawalAmountRaw: "ARS:2",
withdrawalAmountEffective: "ARS:2",
} as any),
({
withdrawalAmountRaw: "ARS:2",
withdrawalAmountEffective: "ARS:2",
} as any),
getExchangeTos: async (): Promise<GetExchangeTosResult> => ({
contentType: "text",
content: "just accept",
@ -224,6 +227,7 @@ describe("Withdraw CTA states", () => {
cancel: async () => {
null;
},
onSuccess: async () => { null },
},
{
listExchanges: async () => ({ exchanges }),
@ -234,10 +238,10 @@ describe("Withdraw CTA states", () => {
}),
getExchangeWithdrawalInfo:
async (): Promise<ExchangeWithdrawDetails> =>
({
withdrawalAmountRaw: "ARS:2",
withdrawalAmountEffective: "ARS:2",
} as any),
({
withdrawalAmountRaw: "ARS:2",
withdrawalAmountEffective: "ARS:2",
} as any),
getExchangeTos: async (): Promise<GetExchangeTosResult> => ({
contentType: "text",
content: "just accept",

View File

@ -76,29 +76,6 @@ export function LoadingInfoView({ error }: State.LoadingInfoError): VNode {
);
}
export function CompletedView(state: State.Completed): VNode {
const { i18n } = useTranslationContext();
return (
<WalletAction>
<LogoHeader />
<SubTitle>
<i18n.Translate>Digital cash withdrawal</i18n.Translate>
</SubTitle>
<SuccessBox>
<h3>
<i18n.Translate>Withdrawal in process...</i18n.Translate>
</h3>
<p>
<i18n.Translate>
You can close the page now. Check your bank if the transaction need
a confirmation step to be completed
</i18n.Translate>
</p>
</SuccessBox>
</WalletAction>
);
}
export function SuccessView(state: State.Success): VNode {
const { i18n } = useTranslationContext();
return (

View File

@ -249,41 +249,49 @@ export function Application(): VNode {
redirectTo(Pages.ctaWithdrawManual({ amount }))
}
cancel={() => redirectTo(Pages.balance)}
onSuccess={(tid:string) => redirectTo(Pages.balanceTransaction({ tid }))}
/>
<Route
path={Pages.ctaRefund}
component={RefundPage}
cancel={() => redirectTo(Pages.balance)}
onSuccess={(tid:string) => redirectTo(Pages.balanceTransaction({ tid }))}
/>
<Route
path={Pages.ctaTips}
component={TipPage}
onCancel={() => redirectTo(Pages.balance)}
onSuccess={(tid:string) => redirectTo(Pages.balanceTransaction({ tid }))}
/>
<Route
path={Pages.ctaWithdraw}
component={WithdrawPageFromURI}
cancel={() => redirectTo(Pages.balance)}
onSuccess={(tid:string) => redirectTo(Pages.balanceTransaction({ tid }))}
/>
<Route
path={Pages.ctaWithdrawManual.pattern}
component={WithdrawPageFromParams}
cancel={() => redirectTo(Pages.balance)}
onSuccess={(tid:string) => redirectTo(Pages.balanceTransaction({ tid }))}
/>
<Route
path={Pages.ctaDeposit}
component={DepositPageCTA}
cancel={() => redirectTo(Pages.balance)}
onSuccess={(tid:string) => redirectTo(Pages.balanceTransaction({ tid }))}
/>
<Route
path={Pages.ctaInvoiceCreate.pattern}
component={InvoiceCreatePage}
onClose={() => redirectTo(Pages.balance)}
onSuccess={(tid:string) => redirectTo(Pages.balanceTransaction({ tid }))}
/>
<Route
path={Pages.ctaTransferCreate.pattern}
component={TransferCreatePage}
onClose={() => redirectTo(Pages.balance)}
onSuccess={(tid:string) => redirectTo(Pages.balanceTransaction({ tid }))}
/>
<Route
path={Pages.ctaInvoicePay}
@ -292,11 +300,13 @@ export function Application(): VNode {
redirectTo(Pages.ctaWithdrawManual({ amount }))
}
onClose={() => redirectTo(Pages.balance)}
onSuccess={(tid:string) => redirectTo(Pages.balanceTransaction({ tid }))}
/>
<Route
path={Pages.ctaTransferPickup}
component={TransferPickupPage}
onClose={() => redirectTo(Pages.balance)}
onSuccess={(tid:string) => redirectTo(Pages.balanceTransaction({ tid }))}
/>
{/**

View File

@ -634,7 +634,7 @@ export function TransactionView({
text={transaction.exchangeBaseUrl}
kind="neutral"
/>
{transaction.pending && (
{transaction.pending && ( /** pending is not-pay */
<Part
title={<i18n.Translate>URI</i18n.Translate>}
text={<ShowQrWithCopy text={transaction.talerUri} />}
@ -720,13 +720,13 @@ export function TransactionView({
text={transaction.exchangeBaseUrl}
kind="neutral"
/>
{transaction.pending && (
{/* {transaction.pending && ( //pending is not-received
)} */}
<Part
title={<i18n.Translate>URI</i18n.Translate>}
text={<ShowQrWithCopy text={transaction.talerUri} />}
kind="neutral"
/>
)}
/>
<Part
title={<i18n.Translate>Details</i18n.Translate>}
text={