redirect after success #7357
This commit is contained in:
parent
6ddb2de842
commit
59d235e8d2
@ -21,19 +21,19 @@ import { ButtonHandler } from "../../mui/handlers.js";
|
|||||||
import { compose, StateViewMap } from "../../utils/index.js";
|
import { compose, StateViewMap } from "../../utils/index.js";
|
||||||
import * as wxApi from "../../wxApi.js";
|
import * as wxApi from "../../wxApi.js";
|
||||||
import { useComponentState } from "./state.js";
|
import { useComponentState } from "./state.js";
|
||||||
import { CompletedView, LoadingUriView, ReadyView } from "./views.js";
|
import { LoadingUriView, ReadyView } from "./views.js";
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
talerDepositUri: string | undefined;
|
talerDepositUri: string | undefined;
|
||||||
amountStr: AmountString | undefined;
|
amountStr: AmountString | undefined;
|
||||||
cancel: () => Promise<void>;
|
cancel: () => Promise<void>;
|
||||||
|
onSuccess: (tx: string) => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type State =
|
export type State =
|
||||||
| State.Loading
|
| State.Loading
|
||||||
| State.LoadingUriError
|
| State.LoadingUriError
|
||||||
| State.Ready
|
| State.Ready;
|
||||||
| State.Completed;
|
|
||||||
|
|
||||||
export namespace State {
|
export namespace State {
|
||||||
export interface Loading {
|
export interface Loading {
|
||||||
@ -62,7 +62,6 @@ export namespace State {
|
|||||||
const viewMapping: StateViewMap<State> = {
|
const viewMapping: StateViewMap<State> = {
|
||||||
loading: Loading,
|
loading: Loading,
|
||||||
"loading-uri": LoadingUriView,
|
"loading-uri": LoadingUriView,
|
||||||
completed: CompletedView,
|
|
||||||
ready: ReadyView,
|
ready: ReadyView,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -21,12 +21,9 @@ import * as wxApi from "../../wxApi.js";
|
|||||||
import { Props, State } from "./index.js";
|
import { Props, State } from "./index.js";
|
||||||
|
|
||||||
export function useComponentState(
|
export function useComponentState(
|
||||||
{ talerDepositUri, amountStr, cancel }: Props,
|
{ talerDepositUri, amountStr, cancel, onSuccess }: Props,
|
||||||
api: typeof wxApi,
|
api: typeof wxApi,
|
||||||
): State {
|
): State {
|
||||||
const [result, setResult] = useState<CreateDepositGroupResponse | undefined>(
|
|
||||||
undefined,
|
|
||||||
);
|
|
||||||
|
|
||||||
const info = useAsyncAsHook(async () => {
|
const info = useAsyncAsHook(async () => {
|
||||||
if (!talerDepositUri) throw Error("ERROR_NO-URI-FOR-DEPOSIT");
|
if (!talerDepositUri) throw Error("ERROR_NO-URI-FOR-DEPOSIT");
|
||||||
@ -51,14 +48,7 @@ export function useComponentState(
|
|||||||
const { deposit, uri, amount } = info.response;
|
const { deposit, uri, amount } = info.response;
|
||||||
async function doDeposit(): Promise<void> {
|
async function doDeposit(): Promise<void> {
|
||||||
const resp = await api.createDepositGroup(uri, Amounts.stringify(amount));
|
const resp = await api.createDepositGroup(uri, Amounts.stringify(amount));
|
||||||
setResult(resp);
|
onSuccess(resp.transactionId);
|
||||||
}
|
|
||||||
|
|
||||||
if (result !== undefined) {
|
|
||||||
return {
|
|
||||||
status: "completed",
|
|
||||||
error: undefined,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -35,6 +35,7 @@ describe("Deposit CTA states", () => {
|
|||||||
cancel: async () => {
|
cancel: async () => {
|
||||||
null;
|
null;
|
||||||
},
|
},
|
||||||
|
onSuccess: async () => { null; },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
prepareRefund: async () => ({}),
|
prepareRefund: async () => ({}),
|
||||||
@ -75,13 +76,14 @@ describe("Deposit CTA states", () => {
|
|||||||
cancel: async () => {
|
cancel: async () => {
|
||||||
null;
|
null;
|
||||||
},
|
},
|
||||||
|
onSuccess: async () => { null; },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
prepareDeposit: async () =>
|
prepareDeposit: async () =>
|
||||||
({
|
({
|
||||||
effectiveDepositAmount: Amounts.parseOrThrow("EUR:1"),
|
effectiveDepositAmount: Amounts.parseOrThrow("EUR:1"),
|
||||||
totalDepositCost: Amounts.parseOrThrow("EUR:1.2"),
|
totalDepositCost: Amounts.parseOrThrow("EUR:1.2"),
|
||||||
} as PrepareDepositResponse as any),
|
} as PrepareDepositResponse as any),
|
||||||
createDepositGroup: async () => ({}),
|
createDepositGroup: async () => ({}),
|
||||||
} as any,
|
} as any,
|
||||||
),
|
),
|
||||||
|
@ -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 {
|
export function ReadyView(state: State.Ready): VNode {
|
||||||
const { i18n } = useTranslationContext();
|
const { i18n } = useTranslationContext();
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
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 { compose, StateViewMap } from "../../utils/index.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 * as wxApi from "../../wxApi.js";
|
||||||
import { useComponentState } from "./state.js";
|
import { useComponentState } from "./state.js";
|
||||||
import { AmountJson, TalerErrorDetail } from "@gnu-taler/taler-util";
|
import { AmountJson, TalerErrorDetail } from "@gnu-taler/taler-util";
|
||||||
@ -26,12 +26,12 @@ import { ButtonHandler, TextFieldHandler } from "../../mui/handlers.js";
|
|||||||
export interface Props {
|
export interface Props {
|
||||||
amount: string;
|
amount: string;
|
||||||
onClose: () => Promise<void>;
|
onClose: () => Promise<void>;
|
||||||
|
onSuccess: (tx: string) => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type State =
|
export type State =
|
||||||
| State.Loading
|
| State.Loading
|
||||||
| State.LoadingUriError
|
| State.LoadingUriError
|
||||||
| State.Created
|
|
||||||
| State.Ready;
|
| State.Ready;
|
||||||
|
|
||||||
export namespace State {
|
export namespace State {
|
||||||
@ -49,11 +49,6 @@ export namespace State {
|
|||||||
error: undefined;
|
error: undefined;
|
||||||
cancel: ButtonHandler;
|
cancel: ButtonHandler;
|
||||||
}
|
}
|
||||||
export interface Created extends BaseInfo {
|
|
||||||
status: "created";
|
|
||||||
talerUri: string;
|
|
||||||
copyToClipboard: ButtonHandler;
|
|
||||||
}
|
|
||||||
export interface Ready extends BaseInfo {
|
export interface Ready extends BaseInfo {
|
||||||
status: "ready";
|
status: "ready";
|
||||||
create: ButtonHandler;
|
create: ButtonHandler;
|
||||||
@ -70,7 +65,6 @@ export namespace State {
|
|||||||
const viewMapping: StateViewMap<State> = {
|
const viewMapping: StateViewMap<State> = {
|
||||||
loading: Loading,
|
loading: Loading,
|
||||||
"loading-uri": LoadingUriView,
|
"loading-uri": LoadingUriView,
|
||||||
created: CreatedView,
|
|
||||||
ready: ReadyView,
|
ready: ReadyView,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -22,13 +22,12 @@ import * as wxApi from "../../wxApi.js";
|
|||||||
import { Props, State } from "./index.js";
|
import { Props, State } from "./index.js";
|
||||||
|
|
||||||
export function useComponentState(
|
export function useComponentState(
|
||||||
{ amount: amountStr, onClose }: Props,
|
{ amount: amountStr, onClose, onSuccess }: Props,
|
||||||
api: typeof wxApi,
|
api: typeof wxApi,
|
||||||
): State {
|
): State {
|
||||||
const amount = Amounts.parseOrThrow(amountStr);
|
const amount = Amounts.parseOrThrow(amountStr);
|
||||||
|
|
||||||
const [subject, setSubject] = useState("");
|
const [subject, setSubject] = useState("");
|
||||||
const [talerUri, setTalerUri] = useState("");
|
|
||||||
|
|
||||||
const hook = useAsyncAsHook(api.listExchanges);
|
const hook = useAsyncAsHook(api.listExchanges);
|
||||||
const [exchangeIdx, setExchangeIdx] = useState("0");
|
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(
|
const exchanges = hook.response.exchanges.filter(
|
||||||
(e) => e.currency === amount.currency,
|
(e) => e.currency === amount.currency,
|
||||||
);
|
);
|
||||||
@ -74,7 +57,7 @@ export function useComponentState(
|
|||||||
);
|
);
|
||||||
const selected = exchanges[Number(exchangeIdx)];
|
const selected = exchanges[Number(exchangeIdx)];
|
||||||
|
|
||||||
async function accept(): Promise<string> {
|
async function accept(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const resp = await api.initiatePeerPullPayment({
|
const resp = await api.initiatePeerPullPayment({
|
||||||
amount: Amounts.stringify(amount),
|
amount: Amounts.stringify(amount),
|
||||||
@ -83,7 +66,8 @@ export function useComponentState(
|
|||||||
summary: subject,
|
summary: subject,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
return resp.talerUri;
|
|
||||||
|
onSuccess(resp.transactionId);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e instanceof TalerError) {
|
if (e instanceof TalerError) {
|
||||||
setOperationError(e.errorDetail);
|
setOperationError(e.errorDetail);
|
||||||
@ -103,10 +87,7 @@ export function useComponentState(
|
|||||||
invalid: !subject || Amounts.isZero(amount),
|
invalid: !subject || Amounts.isZero(amount),
|
||||||
exchangeUrl: selected.exchangeBaseUrl,
|
exchangeUrl: selected.exchangeBaseUrl,
|
||||||
create: {
|
create: {
|
||||||
onClick: async () => {
|
onClick: accept
|
||||||
const uri = await accept();
|
|
||||||
setTalerUri(uri);
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
cancel: {
|
cancel: {
|
||||||
onClick: onClose,
|
onClick: onClose,
|
||||||
|
@ -20,19 +20,12 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { createExample } from "../../test-utils.js";
|
import { createExample } from "../../test-utils.js";
|
||||||
import { ReadyView, CreatedView } from "./views.js";
|
import { ReadyView } from "./views.js";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: "wallet/invoice create",
|
title: "wallet/invoice create",
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ShowQr = createExample(CreatedView, {
|
|
||||||
talerUri:
|
|
||||||
"taler://pay-pull/exchange.taler.ar/HS585JK0QCXHJ8Z8QWZA3EBAY5WY7XNC1RR2MHJXSH2Z4WP0YPJ0",
|
|
||||||
cancel: {},
|
|
||||||
copyToClipboard: {},
|
|
||||||
});
|
|
||||||
|
|
||||||
export const Ready = createExample(ReadyView, {
|
export const Ready = createExample(ReadyView, {
|
||||||
chosenAmount: {
|
chosenAmount: {
|
||||||
currency: "ARS",
|
currency: "ARS",
|
||||||
|
@ -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({
|
export function ReadyView({
|
||||||
invalid,
|
invalid,
|
||||||
|
@ -32,6 +32,7 @@ export interface Props {
|
|||||||
talerPayPullUri: string;
|
talerPayPullUri: string;
|
||||||
onClose: () => Promise<void>;
|
onClose: () => Promise<void>;
|
||||||
goToWalletManualWithdraw: (amount?: string) => Promise<void>;
|
goToWalletManualWithdraw: (amount?: string) => Promise<void>;
|
||||||
|
onSuccess: (tx: string) => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type State =
|
export type State =
|
||||||
|
@ -30,7 +30,7 @@ import * as wxApi from "../../wxApi.js";
|
|||||||
import { Props, State } from "./index.js";
|
import { Props, State } from "./index.js";
|
||||||
|
|
||||||
export function useComponentState(
|
export function useComponentState(
|
||||||
{ talerPayPullUri, onClose, goToWalletManualWithdraw }: Props,
|
{ talerPayPullUri, onClose, goToWalletManualWithdraw, onSuccess }: Props,
|
||||||
api: typeof wxApi,
|
api: typeof wxApi,
|
||||||
): State {
|
): State {
|
||||||
const hook = useAsyncAsHook(async () => {
|
const hook = useAsyncAsHook(async () => {
|
||||||
@ -149,7 +149,7 @@ export function useComponentState(
|
|||||||
const resp = await api.acceptPeerPullPayment({
|
const resp = await api.acceptPeerPullPayment({
|
||||||
peerPullPaymentIncomingId,
|
peerPullPaymentIncomingId,
|
||||||
});
|
});
|
||||||
await onClose();
|
onSuccess(resp.transactionId);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e instanceof TalerError) {
|
if (e instanceof TalerError) {
|
||||||
setOperationError(e.errorDetail);
|
setOperationError(e.errorDetail);
|
||||||
|
@ -35,6 +35,7 @@ export interface Props {
|
|||||||
talerPayUri?: string;
|
talerPayUri?: string;
|
||||||
goToWalletManualWithdraw: (amount?: string) => Promise<void>;
|
goToWalletManualWithdraw: (amount?: string) => Promise<void>;
|
||||||
cancel: () => Promise<void>;
|
cancel: () => Promise<void>;
|
||||||
|
onSuccess: (tx: string) => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type State =
|
export type State =
|
||||||
@ -43,7 +44,6 @@ export type State =
|
|||||||
| State.Ready
|
| State.Ready
|
||||||
| State.NoEnoughBalance
|
| State.NoEnoughBalance
|
||||||
| State.NoBalanceForCurrency
|
| State.NoBalanceForCurrency
|
||||||
| State.Completed
|
|
||||||
| State.Confirmed;
|
| State.Confirmed;
|
||||||
|
|
||||||
export namespace State {
|
export namespace State {
|
||||||
@ -86,13 +86,6 @@ export namespace State {
|
|||||||
balance: AmountJson;
|
balance: AmountJson;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Completed extends BaseInfo {
|
|
||||||
status: "completed";
|
|
||||||
payStatus: PreparePayResult;
|
|
||||||
payResult: ConfirmPayResult;
|
|
||||||
paymentError?: TalerError;
|
|
||||||
balance: AmountJson;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const viewMapping: StateViewMap<State> = {
|
const viewMapping: StateViewMap<State> = {
|
||||||
@ -101,7 +94,6 @@ const viewMapping: StateViewMap<State> = {
|
|||||||
"no-balance-for-currency": BaseView,
|
"no-balance-for-currency": BaseView,
|
||||||
"no-enough-balance": BaseView,
|
"no-enough-balance": BaseView,
|
||||||
confirmed: BaseView,
|
confirmed: BaseView,
|
||||||
completed: BaseView,
|
|
||||||
ready: BaseView,
|
ready: BaseView,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -31,12 +31,9 @@ import * as wxApi from "../../wxApi.js";
|
|||||||
import { Props, State } from "./index.js";
|
import { Props, State } from "./index.js";
|
||||||
|
|
||||||
export function useComponentState(
|
export function useComponentState(
|
||||||
{ talerPayUri, cancel, goToWalletManualWithdraw }: Props,
|
{ talerPayUri, cancel, goToWalletManualWithdraw, onSuccess }: Props,
|
||||||
api: typeof wxApi,
|
api: typeof wxApi,
|
||||||
): State {
|
): State {
|
||||||
const [payResult, setPayResult] = useState<ConfirmPayResult | undefined>(
|
|
||||||
undefined,
|
|
||||||
);
|
|
||||||
const [payErrMsg, setPayErrMsg] = useState<TalerError | undefined>(undefined);
|
const [payErrMsg, setPayErrMsg] = useState<TalerError | undefined>(undefined);
|
||||||
|
|
||||||
const hook = useAsyncAsHook(async () => {
|
const hook = useAsyncAsHook(async () => {
|
||||||
@ -104,17 +101,6 @@ export function useComponentState(
|
|||||||
|
|
||||||
const foundAmount = Amounts.parseOrThrow(foundBalance.available);
|
const foundAmount = Amounts.parseOrThrow(foundBalance.available);
|
||||||
|
|
||||||
if (payResult) {
|
|
||||||
return {
|
|
||||||
status: "completed",
|
|
||||||
balance: foundAmount,
|
|
||||||
payStatus,
|
|
||||||
paymentError: payErrMsg,
|
|
||||||
payResult,
|
|
||||||
...baseResult,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (payStatus.status === PreparePayResultType.InsufficientBalance) {
|
if (payStatus.status === PreparePayResultType.InsufficientBalance) {
|
||||||
return {
|
return {
|
||||||
status: "no-enough-balance",
|
status: "no-enough-balance",
|
||||||
@ -157,7 +143,7 @@ export function useComponentState(
|
|||||||
console.log(`should d to ${fu}`);
|
console.log(`should d to ${fu}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setPayResult(res);
|
onSuccess(res.transactionId);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e instanceof TalerError) {
|
if (e instanceof TalerError) {
|
||||||
setPayErrMsg(e);
|
setPayErrMsg(e);
|
||||||
|
@ -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,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
@ -75,6 +75,7 @@ describe("Payment CTA states", () => {
|
|||||||
talerPayUri: undefined,
|
talerPayUri: undefined,
|
||||||
cancel: nullFunction,
|
cancel: nullFunction,
|
||||||
goToWalletManualWithdraw: nullFunction,
|
goToWalletManualWithdraw: nullFunction,
|
||||||
|
onSuccess: async () => { null; },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
onUpdateNotification: nullFunction,
|
onUpdateNotification: nullFunction,
|
||||||
@ -110,18 +111,19 @@ describe("Payment CTA states", () => {
|
|||||||
talerPayUri: "taller://pay",
|
talerPayUri: "taller://pay",
|
||||||
cancel: nullFunction,
|
cancel: nullFunction,
|
||||||
goToWalletManualWithdraw: nullFunction,
|
goToWalletManualWithdraw: nullFunction,
|
||||||
|
onSuccess: async () => { null; },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
onUpdateNotification: nullFunction,
|
onUpdateNotification: nullFunction,
|
||||||
preparePay: async () =>
|
preparePay: async () =>
|
||||||
({
|
({
|
||||||
amountRaw: "USD:10",
|
amountRaw: "USD:10",
|
||||||
status: PreparePayResultType.InsufficientBalance,
|
status: PreparePayResultType.InsufficientBalance,
|
||||||
} as Partial<PreparePayResult>),
|
} as Partial<PreparePayResult>),
|
||||||
getBalance: async () =>
|
getBalance: async () =>
|
||||||
({
|
({
|
||||||
balances: [],
|
balances: [],
|
||||||
} as Partial<BalancesResponse>),
|
} as Partial<BalancesResponse>),
|
||||||
} as Partial<typeof wxApi> as any,
|
} as Partial<typeof wxApi> as any,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -152,22 +154,23 @@ describe("Payment CTA states", () => {
|
|||||||
talerPayUri: "taller://pay",
|
talerPayUri: "taller://pay",
|
||||||
cancel: nullFunction,
|
cancel: nullFunction,
|
||||||
goToWalletManualWithdraw: nullFunction,
|
goToWalletManualWithdraw: nullFunction,
|
||||||
|
onSuccess: async () => { null; },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
onUpdateNotification: nullFunction,
|
onUpdateNotification: nullFunction,
|
||||||
preparePay: async () =>
|
preparePay: async () =>
|
||||||
({
|
({
|
||||||
amountRaw: "USD:10",
|
amountRaw: "USD:10",
|
||||||
status: PreparePayResultType.InsufficientBalance,
|
status: PreparePayResultType.InsufficientBalance,
|
||||||
} as Partial<PreparePayResult>),
|
} as Partial<PreparePayResult>),
|
||||||
getBalance: async () =>
|
getBalance: async () =>
|
||||||
({
|
({
|
||||||
balances: [
|
balances: [
|
||||||
{
|
{
|
||||||
available: "USD:5",
|
available: "USD:5",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
} as Partial<BalancesResponse>),
|
} as Partial<BalancesResponse>),
|
||||||
} as Partial<typeof wxApi> as any,
|
} as Partial<typeof wxApi> as any,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -198,23 +201,24 @@ describe("Payment CTA states", () => {
|
|||||||
talerPayUri: "taller://pay",
|
talerPayUri: "taller://pay",
|
||||||
cancel: nullFunction,
|
cancel: nullFunction,
|
||||||
goToWalletManualWithdraw: nullFunction,
|
goToWalletManualWithdraw: nullFunction,
|
||||||
|
onSuccess: async () => { null; },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
onUpdateNotification: nullFunction,
|
onUpdateNotification: nullFunction,
|
||||||
preparePay: async () =>
|
preparePay: async () =>
|
||||||
({
|
({
|
||||||
amountRaw: "USD:10",
|
amountRaw: "USD:10",
|
||||||
amountEffective: "USD:10",
|
amountEffective: "USD:10",
|
||||||
status: PreparePayResultType.PaymentPossible,
|
status: PreparePayResultType.PaymentPossible,
|
||||||
} as Partial<PreparePayResult>),
|
} as Partial<PreparePayResult>),
|
||||||
getBalance: async () =>
|
getBalance: async () =>
|
||||||
({
|
({
|
||||||
balances: [
|
balances: [
|
||||||
{
|
{
|
||||||
available: "USD:15",
|
available: "USD:15",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
} as Partial<BalancesResponse>),
|
} as Partial<BalancesResponse>),
|
||||||
} as Partial<typeof wxApi> as any,
|
} as Partial<typeof wxApi> as any,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -247,23 +251,24 @@ describe("Payment CTA states", () => {
|
|||||||
talerPayUri: "taller://pay",
|
talerPayUri: "taller://pay",
|
||||||
cancel: nullFunction,
|
cancel: nullFunction,
|
||||||
goToWalletManualWithdraw: nullFunction,
|
goToWalletManualWithdraw: nullFunction,
|
||||||
|
onSuccess: async () => { null; },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
onUpdateNotification: nullFunction,
|
onUpdateNotification: nullFunction,
|
||||||
preparePay: async () =>
|
preparePay: async () =>
|
||||||
({
|
({
|
||||||
amountRaw: "USD:9",
|
amountRaw: "USD:9",
|
||||||
amountEffective: "USD:10",
|
amountEffective: "USD:10",
|
||||||
status: PreparePayResultType.PaymentPossible,
|
status: PreparePayResultType.PaymentPossible,
|
||||||
} as Partial<PreparePayResult>),
|
} as Partial<PreparePayResult>),
|
||||||
getBalance: async () =>
|
getBalance: async () =>
|
||||||
({
|
({
|
||||||
balances: [
|
balances: [
|
||||||
{
|
{
|
||||||
available: "USD:15",
|
available: "USD:15",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
} as Partial<BalancesResponse>),
|
} as Partial<BalancesResponse>),
|
||||||
} as Partial<typeof wxApi> as any,
|
} as Partial<typeof wxApi> as any,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -296,28 +301,29 @@ describe("Payment CTA states", () => {
|
|||||||
talerPayUri: "taller://pay",
|
talerPayUri: "taller://pay",
|
||||||
cancel: nullFunction,
|
cancel: nullFunction,
|
||||||
goToWalletManualWithdraw: nullFunction,
|
goToWalletManualWithdraw: nullFunction,
|
||||||
|
onSuccess: async () => { null; },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
onUpdateNotification: nullFunction,
|
onUpdateNotification: nullFunction,
|
||||||
preparePay: async () =>
|
preparePay: async () =>
|
||||||
({
|
({
|
||||||
amountRaw: "USD:9",
|
amountRaw: "USD:9",
|
||||||
amountEffective: "USD:10",
|
amountEffective: "USD:10",
|
||||||
status: PreparePayResultType.PaymentPossible,
|
status: PreparePayResultType.PaymentPossible,
|
||||||
} as Partial<PreparePayResult>),
|
} as Partial<PreparePayResult>),
|
||||||
getBalance: async () =>
|
getBalance: async () =>
|
||||||
({
|
({
|
||||||
balances: [
|
balances: [
|
||||||
{
|
{
|
||||||
available: "USD:15",
|
available: "USD:15",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
} as Partial<BalancesResponse>),
|
} as Partial<BalancesResponse>),
|
||||||
confirmPay: async () =>
|
confirmPay: async () =>
|
||||||
({
|
({
|
||||||
type: ConfirmPayResultType.Done,
|
type: ConfirmPayResultType.Done,
|
||||||
contractTerms: {},
|
contractTerms: {},
|
||||||
} as Partial<ConfirmPayResult>),
|
} as Partial<ConfirmPayResult>),
|
||||||
} as Partial<typeof wxApi> as any,
|
} as Partial<typeof wxApi> as any,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -340,18 +346,18 @@ describe("Payment CTA states", () => {
|
|||||||
r.payHandler.onClick();
|
r.payHandler.onClick();
|
||||||
}
|
}
|
||||||
|
|
||||||
await waitNextUpdate();
|
// await waitNextUpdate();
|
||||||
|
|
||||||
{
|
// {
|
||||||
const r = getLastResultOrThrow();
|
// const r = getLastResultOrThrow();
|
||||||
if (r.status !== "completed") expect.fail();
|
// if (r.status !== "completed") expect.fail();
|
||||||
expect(r.balance).deep.equal(Amounts.parseOrThrow("USD:15"));
|
// expect(r.balance).deep.equal(Amounts.parseOrThrow("USD:15"));
|
||||||
expect(r.amount).deep.equal(Amounts.parseOrThrow("USD:9"));
|
// expect(r.amount).deep.equal(Amounts.parseOrThrow("USD:9"));
|
||||||
// expect(r.totalFees).deep.equal(Amounts.parseOrThrow("USD:1"));
|
// // expect(r.totalFees).deep.equal(Amounts.parseOrThrow("USD:1"));
|
||||||
// if (r.payResult.type !== ConfirmPayResultType.Done) expect.fail();
|
// // if (r.payResult.type !== ConfirmPayResultType.Done) expect.fail();
|
||||||
// expect(r.payResult.contractTerms).not.undefined;
|
// // expect(r.payResult.contractTerms).not.undefined;
|
||||||
// expect(r.payHandler.onClick).undefined;
|
// // expect(r.payHandler.onClick).undefined;
|
||||||
}
|
// }
|
||||||
|
|
||||||
await assertNoPendingUpdate();
|
await assertNoPendingUpdate();
|
||||||
});
|
});
|
||||||
@ -364,28 +370,29 @@ describe("Payment CTA states", () => {
|
|||||||
talerPayUri: "taller://pay",
|
talerPayUri: "taller://pay",
|
||||||
cancel: nullFunction,
|
cancel: nullFunction,
|
||||||
goToWalletManualWithdraw: nullFunction,
|
goToWalletManualWithdraw: nullFunction,
|
||||||
|
onSuccess: async () => { null; },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
onUpdateNotification: nullFunction,
|
onUpdateNotification: nullFunction,
|
||||||
preparePay: async () =>
|
preparePay: async () =>
|
||||||
({
|
({
|
||||||
amountRaw: "USD:9",
|
amountRaw: "USD:9",
|
||||||
amountEffective: "USD:10",
|
amountEffective: "USD:10",
|
||||||
status: PreparePayResultType.PaymentPossible,
|
status: PreparePayResultType.PaymentPossible,
|
||||||
} as Partial<PreparePayResult>),
|
} as Partial<PreparePayResult>),
|
||||||
getBalance: async () =>
|
getBalance: async () =>
|
||||||
({
|
({
|
||||||
balances: [
|
balances: [
|
||||||
{
|
{
|
||||||
available: "USD:15",
|
available: "USD:15",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
} as Partial<BalancesResponse>),
|
} as Partial<BalancesResponse>),
|
||||||
confirmPay: async () =>
|
confirmPay: async () =>
|
||||||
({
|
({
|
||||||
type: ConfirmPayResultType.Pending,
|
type: ConfirmPayResultType.Pending,
|
||||||
lastError: { code: 1 },
|
lastError: { code: 1 },
|
||||||
} as Partial<ConfirmPayResult>),
|
} as Partial<ConfirmPayResult>),
|
||||||
} as Partial<typeof wxApi> as any,
|
} as Partial<typeof wxApi> as any,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -447,23 +454,24 @@ describe("Payment CTA states", () => {
|
|||||||
talerPayUri: "taller://pay",
|
talerPayUri: "taller://pay",
|
||||||
cancel: nullFunction,
|
cancel: nullFunction,
|
||||||
goToWalletManualWithdraw: nullFunction,
|
goToWalletManualWithdraw: nullFunction,
|
||||||
|
onSuccess: async () => { null; },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
onUpdateNotification: subscriptions.saveSubscription,
|
onUpdateNotification: subscriptions.saveSubscription,
|
||||||
preparePay: async () =>
|
preparePay: async () =>
|
||||||
({
|
({
|
||||||
amountRaw: "USD:9",
|
amountRaw: "USD:9",
|
||||||
amountEffective: "USD:10",
|
amountEffective: "USD:10",
|
||||||
status: PreparePayResultType.PaymentPossible,
|
status: PreparePayResultType.PaymentPossible,
|
||||||
} as Partial<PreparePayResult>),
|
} as Partial<PreparePayResult>),
|
||||||
getBalance: async () =>
|
getBalance: async () =>
|
||||||
({
|
({
|
||||||
balances: [
|
balances: [
|
||||||
{
|
{
|
||||||
available: Amounts.stringify(availableBalance),
|
available: Amounts.stringify(availableBalance),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
} as Partial<BalancesResponse>),
|
} as Partial<BalancesResponse>),
|
||||||
} as Partial<typeof wxApi> as any,
|
} as Partial<typeof wxApi> as any,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -63,7 +63,6 @@ export function LoadingUriView({ error }: State.LoadingUriError): VNode {
|
|||||||
type SupportedStates =
|
type SupportedStates =
|
||||||
| State.Ready
|
| State.Ready
|
||||||
| State.Confirmed
|
| State.Confirmed
|
||||||
| State.Completed
|
|
||||||
| State.NoBalanceForCurrency
|
| State.NoBalanceForCurrency
|
||||||
| State.NoEnoughBalance;
|
| State.NoEnoughBalance;
|
||||||
|
|
||||||
@ -167,7 +166,6 @@ export function BaseView(state: SupportedStates): VNode {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</section>
|
</section>
|
||||||
{state.status !== "completed" ? (
|
|
||||||
<ButtonsSection
|
<ButtonsSection
|
||||||
amount={state.amount}
|
amount={state.amount}
|
||||||
balance={state.balance}
|
balance={state.balance}
|
||||||
@ -176,7 +174,6 @@ export function BaseView(state: SupportedStates): VNode {
|
|||||||
payHandler={state.status === "ready" ? state.payHandler : undefined}
|
payHandler={state.status === "ready" ? state.payHandler : undefined}
|
||||||
goToWalletManualWithdraw={state.goToWalletManualWithdraw}
|
goToWalletManualWithdraw={state.goToWalletManualWithdraw}
|
||||||
/>
|
/>
|
||||||
) : undefined}
|
|
||||||
<section>
|
<section>
|
||||||
<Link upperCased onClick={state.cancel}>
|
<Link upperCased onClick={state.cancel}>
|
||||||
<i18n.Translate>Cancel</i18n.Translate>
|
<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 />;
|
return <Fragment />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,7 +22,6 @@ import { compose, StateViewMap } from "../../utils/index.js";
|
|||||||
import * as wxApi from "../../wxApi.js";
|
import * as wxApi from "../../wxApi.js";
|
||||||
import { useComponentState } from "./state.js";
|
import { useComponentState } from "./state.js";
|
||||||
import {
|
import {
|
||||||
CompletedView,
|
|
||||||
IgnoredView,
|
IgnoredView,
|
||||||
InProgressView,
|
InProgressView,
|
||||||
LoadingUriView,
|
LoadingUriView,
|
||||||
@ -32,6 +31,7 @@ import {
|
|||||||
export interface Props {
|
export interface Props {
|
||||||
talerRefundUri?: string;
|
talerRefundUri?: string;
|
||||||
cancel: () => Promise<void>;
|
cancel: () => Promise<void>;
|
||||||
|
onSuccess: (tx: string) => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type State =
|
export type State =
|
||||||
@ -39,8 +39,7 @@ export type State =
|
|||||||
| State.LoadingUriError
|
| State.LoadingUriError
|
||||||
| State.Ready
|
| State.Ready
|
||||||
| State.Ignored
|
| State.Ignored
|
||||||
| State.InProgress
|
| State.InProgress;
|
||||||
| State.Completed;
|
|
||||||
|
|
||||||
export namespace State {
|
export namespace State {
|
||||||
export interface Loading {
|
export interface Loading {
|
||||||
@ -79,17 +78,12 @@ export namespace State {
|
|||||||
status: "in-progress";
|
status: "in-progress";
|
||||||
error: undefined;
|
error: undefined;
|
||||||
}
|
}
|
||||||
export interface Completed extends BaseInfo {
|
|
||||||
status: "completed";
|
|
||||||
error: undefined;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const viewMapping: StateViewMap<State> = {
|
const viewMapping: StateViewMap<State> = {
|
||||||
loading: Loading,
|
loading: Loading,
|
||||||
"loading-uri": LoadingUriView,
|
"loading-uri": LoadingUriView,
|
||||||
"in-progress": InProgressView,
|
"in-progress": InProgressView,
|
||||||
completed: CompletedView,
|
|
||||||
ignored: IgnoredView,
|
ignored: IgnoredView,
|
||||||
ready: ReadyView,
|
ready: ReadyView,
|
||||||
};
|
};
|
||||||
|
@ -21,7 +21,7 @@ import * as wxApi from "../../wxApi.js";
|
|||||||
import { Props, State } from "./index.js";
|
import { Props, State } from "./index.js";
|
||||||
|
|
||||||
export function useComponentState(
|
export function useComponentState(
|
||||||
{ talerRefundUri, cancel }: Props,
|
{ talerRefundUri, cancel, onSuccess }: Props,
|
||||||
api: typeof wxApi,
|
api: typeof wxApi,
|
||||||
): State {
|
): State {
|
||||||
const [ignored, setIgnored] = useState(false);
|
const [ignored, setIgnored] = useState(false);
|
||||||
@ -51,8 +51,9 @@ export function useComponentState(
|
|||||||
const { refund, uri } = info.response;
|
const { refund, uri } = info.response;
|
||||||
|
|
||||||
const doAccept = async (): Promise<void> => {
|
const doAccept = async (): Promise<void> => {
|
||||||
await api.applyRefund(uri);
|
const res = await api.applyRefund(uri);
|
||||||
info.retry();
|
|
||||||
|
onSuccess(res.transactionId);
|
||||||
};
|
};
|
||||||
|
|
||||||
const doIgnore = async (): Promise<void> => {
|
const doIgnore = async (): Promise<void> => {
|
||||||
@ -75,13 +76,6 @@ export function useComponentState(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Amounts.isZero(baseInfo.awaitingAmount)) {
|
|
||||||
return {
|
|
||||||
status: "completed",
|
|
||||||
...baseInfo,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (refund.pending) {
|
if (refund.pending) {
|
||||||
return {
|
return {
|
||||||
status: "in-progress",
|
status: "in-progress",
|
||||||
|
@ -23,7 +23,6 @@ import { Amounts } from "@gnu-taler/taler-util";
|
|||||||
import beer from "../../../static-dev/beer.png";
|
import beer from "../../../static-dev/beer.png";
|
||||||
import { createExample } from "../../test-utils.js";
|
import { createExample } from "../../test-utils.js";
|
||||||
import {
|
import {
|
||||||
CompletedView,
|
|
||||||
IgnoredView,
|
IgnoredView,
|
||||||
InProgressView,
|
InProgressView,
|
||||||
ReadyView,
|
ReadyView,
|
||||||
@ -32,15 +31,6 @@ export default {
|
|||||||
title: "cta/refund",
|
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, {
|
export const InProgress = createExample(InProgressView, {
|
||||||
status: "in-progress",
|
status: "in-progress",
|
||||||
error: undefined,
|
error: undefined,
|
||||||
|
@ -40,6 +40,7 @@ describe("Refund CTA states", () => {
|
|||||||
cancel: async () => {
|
cancel: async () => {
|
||||||
null;
|
null;
|
||||||
},
|
},
|
||||||
|
onSuccess: async () => { null; },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
prepareRefund: async () => ({}),
|
prepareRefund: async () => ({}),
|
||||||
@ -79,25 +80,26 @@ describe("Refund CTA states", () => {
|
|||||||
cancel: async () => {
|
cancel: async () => {
|
||||||
null;
|
null;
|
||||||
},
|
},
|
||||||
|
onSuccess: async () => { null; },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
prepareRefund: async () =>
|
prepareRefund: async () =>
|
||||||
({
|
({
|
||||||
effectivePaid: "EUR:2",
|
effectivePaid: "EUR:2",
|
||||||
awaiting: "EUR:2",
|
awaiting: "EUR:2",
|
||||||
gone: "EUR:0",
|
gone: "EUR:0",
|
||||||
granted: "EUR:0",
|
granted: "EUR:0",
|
||||||
pending: false,
|
pending: false,
|
||||||
proposalId: "1",
|
proposalId: "1",
|
||||||
info: {
|
info: {
|
||||||
contractTermsHash: "123",
|
contractTermsHash: "123",
|
||||||
merchant: {
|
merchant: {
|
||||||
name: "the merchant name",
|
name: "the merchant name",
|
||||||
},
|
|
||||||
orderId: "orderId1",
|
|
||||||
summary: "the summary",
|
|
||||||
},
|
},
|
||||||
} as PrepareRefundResult as any),
|
orderId: "orderId1",
|
||||||
|
summary: "the summary",
|
||||||
|
},
|
||||||
|
} as PrepareRefundResult as any),
|
||||||
applyRefund: async () => ({}),
|
applyRefund: async () => ({}),
|
||||||
onUpdateNotification: async () => ({}),
|
onUpdateNotification: async () => ({}),
|
||||||
} as any,
|
} as any,
|
||||||
@ -136,25 +138,26 @@ describe("Refund CTA states", () => {
|
|||||||
cancel: async () => {
|
cancel: async () => {
|
||||||
null;
|
null;
|
||||||
},
|
},
|
||||||
|
onSuccess: async () => { null; },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
prepareRefund: async () =>
|
prepareRefund: async () =>
|
||||||
({
|
({
|
||||||
effectivePaid: "EUR:2",
|
effectivePaid: "EUR:2",
|
||||||
awaiting: "EUR:2",
|
awaiting: "EUR:2",
|
||||||
gone: "EUR:0",
|
gone: "EUR:0",
|
||||||
granted: "EUR:0",
|
granted: "EUR:0",
|
||||||
pending: false,
|
pending: false,
|
||||||
proposalId: "1",
|
proposalId: "1",
|
||||||
info: {
|
info: {
|
||||||
contractTermsHash: "123",
|
contractTermsHash: "123",
|
||||||
merchant: {
|
merchant: {
|
||||||
name: "the merchant name",
|
name: "the merchant name",
|
||||||
},
|
|
||||||
orderId: "orderId1",
|
|
||||||
summary: "the summary",
|
|
||||||
},
|
},
|
||||||
} as PrepareRefundResult as any),
|
orderId: "orderId1",
|
||||||
|
summary: "the summary",
|
||||||
|
},
|
||||||
|
} as PrepareRefundResult as any),
|
||||||
applyRefund: async () => ({}),
|
applyRefund: async () => ({}),
|
||||||
onUpdateNotification: async () => ({}),
|
onUpdateNotification: async () => ({}),
|
||||||
} as any,
|
} as any,
|
||||||
@ -220,25 +223,27 @@ describe("Refund CTA states", () => {
|
|||||||
cancel: async () => {
|
cancel: async () => {
|
||||||
null;
|
null;
|
||||||
},
|
},
|
||||||
|
onSuccess: async () => { null; },
|
||||||
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
prepareRefund: async () =>
|
prepareRefund: async () =>
|
||||||
({
|
({
|
||||||
awaiting: Amounts.stringify(awaiting),
|
awaiting: Amounts.stringify(awaiting),
|
||||||
effectivePaid: "EUR:2",
|
effectivePaid: "EUR:2",
|
||||||
gone: "EUR:0",
|
gone: "EUR:0",
|
||||||
granted: Amounts.stringify(granted),
|
granted: Amounts.stringify(granted),
|
||||||
pending,
|
pending,
|
||||||
proposalId: "1",
|
proposalId: "1",
|
||||||
info: {
|
info: {
|
||||||
contractTermsHash: "123",
|
contractTermsHash: "123",
|
||||||
merchant: {
|
merchant: {
|
||||||
name: "the merchant name",
|
name: "the merchant name",
|
||||||
},
|
|
||||||
orderId: "orderId1",
|
|
||||||
summary: "the summary",
|
|
||||||
},
|
},
|
||||||
} as PrepareRefundResult as any),
|
orderId: "orderId1",
|
||||||
|
summary: "the summary",
|
||||||
|
},
|
||||||
|
} as PrepareRefundResult as any),
|
||||||
applyRefund: async () => ({}),
|
applyRefund: async () => ({}),
|
||||||
onUpdateNotification: subscriptions.saveSubscription,
|
onUpdateNotification: subscriptions.saveSubscription,
|
||||||
} as any,
|
} as any,
|
||||||
@ -286,7 +291,7 @@ describe("Refund CTA states", () => {
|
|||||||
{
|
{
|
||||||
const state = getLastResultOrThrow();
|
const state = getLastResultOrThrow();
|
||||||
|
|
||||||
if (state.status !== "completed") expect.fail("3");
|
if (state.status !== "ready") expect.fail("3");
|
||||||
if (state.error) expect.fail();
|
if (state.error) expect.fail();
|
||||||
expect(state.merchantName).eq("the merchant name");
|
expect(state.merchantName).eq("the merchant name");
|
||||||
expect(state.products).undefined;
|
expect(state.products).undefined;
|
||||||
|
@ -92,32 +92,6 @@ export function InProgressView(state: State.InProgress): VNode {
|
|||||||
</WalletAction>
|
</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 {
|
export function ReadyView(state: State.Ready): VNode {
|
||||||
const { i18n } = useTranslationContext();
|
const { i18n } = useTranslationContext();
|
||||||
return (
|
return (
|
||||||
|
@ -32,6 +32,7 @@ import {
|
|||||||
export interface Props {
|
export interface Props {
|
||||||
talerTipUri?: string;
|
talerTipUri?: string;
|
||||||
onCancel: () => Promise<void>;
|
onCancel: () => Promise<void>;
|
||||||
|
onSuccess: (tx: string) => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type State =
|
export type State =
|
||||||
|
@ -21,7 +21,7 @@ import * as wxApi from "../../wxApi.js";
|
|||||||
import { Props, State } from "./index.js";
|
import { Props, State } from "./index.js";
|
||||||
|
|
||||||
export function useComponentState(
|
export function useComponentState(
|
||||||
{ talerTipUri, onCancel }: Props,
|
{ talerTipUri, onCancel, onSuccess }: Props,
|
||||||
api: typeof wxApi,
|
api: typeof wxApi,
|
||||||
): State {
|
): State {
|
||||||
const [tipIgnored, setTipIgnored] = useState(false);
|
const [tipIgnored, setTipIgnored] = useState(false);
|
||||||
@ -48,8 +48,11 @@ export function useComponentState(
|
|||||||
const { tip } = tipInfo.response;
|
const { tip } = tipInfo.response;
|
||||||
|
|
||||||
const doAccept = async (): Promise<void> => {
|
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();
|
tipInfo.retry();
|
||||||
|
onSuccess(res.transactionId)
|
||||||
};
|
};
|
||||||
|
|
||||||
const baseInfo = {
|
const baseInfo = {
|
||||||
|
@ -34,6 +34,7 @@ describe("Tip CTA states", () => {
|
|||||||
onCancel: async () => {
|
onCancel: async () => {
|
||||||
null;
|
null;
|
||||||
},
|
},
|
||||||
|
onSuccess: async () => { null; },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
prepareTip: async () => ({}),
|
prepareTip: async () => ({}),
|
||||||
@ -74,16 +75,17 @@ describe("Tip CTA states", () => {
|
|||||||
onCancel: async () => {
|
onCancel: async () => {
|
||||||
null;
|
null;
|
||||||
},
|
},
|
||||||
|
onSuccess: async () => { null; },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
prepareTip: async () =>
|
prepareTip: async () =>
|
||||||
({
|
({
|
||||||
accepted: tipAccepted,
|
accepted: tipAccepted,
|
||||||
exchangeBaseUrl: "exchange url",
|
exchangeBaseUrl: "exchange url",
|
||||||
merchantBaseUrl: "merchant url",
|
merchantBaseUrl: "merchant url",
|
||||||
tipAmountEffective: "EUR:1",
|
tipAmountEffective: "EUR:1",
|
||||||
walletTipId: "tip_id",
|
walletTipId: "tip_id",
|
||||||
} as PrepareTipResult as any),
|
} as PrepareTipResult as any),
|
||||||
acceptTip: async () => {
|
acceptTip: async () => {
|
||||||
tipAccepted = true;
|
tipAccepted = true;
|
||||||
},
|
},
|
||||||
@ -134,15 +136,16 @@ describe("Tip CTA states", () => {
|
|||||||
onCancel: async () => {
|
onCancel: async () => {
|
||||||
null;
|
null;
|
||||||
},
|
},
|
||||||
|
onSuccess: async () => { null; },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
prepareTip: async () =>
|
prepareTip: async () =>
|
||||||
({
|
({
|
||||||
exchangeBaseUrl: "exchange url",
|
exchangeBaseUrl: "exchange url",
|
||||||
merchantBaseUrl: "merchant url",
|
merchantBaseUrl: "merchant url",
|
||||||
tipAmountEffective: "EUR:1",
|
tipAmountEffective: "EUR:1",
|
||||||
walletTipId: "tip_id",
|
walletTipId: "tip_id",
|
||||||
} as PrepareTipResult as any),
|
} as PrepareTipResult as any),
|
||||||
acceptTip: async () => ({}),
|
acceptTip: async () => ({}),
|
||||||
} as any,
|
} as any,
|
||||||
),
|
),
|
||||||
@ -188,16 +191,17 @@ describe("Tip CTA states", () => {
|
|||||||
onCancel: async () => {
|
onCancel: async () => {
|
||||||
null;
|
null;
|
||||||
},
|
},
|
||||||
|
onSuccess: async () => { null; },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
prepareTip: async () =>
|
prepareTip: async () =>
|
||||||
({
|
({
|
||||||
accepted: true,
|
accepted: true,
|
||||||
exchangeBaseUrl: "exchange url",
|
exchangeBaseUrl: "exchange url",
|
||||||
merchantBaseUrl: "merchant url",
|
merchantBaseUrl: "merchant url",
|
||||||
tipAmountEffective: "EUR:1",
|
tipAmountEffective: "EUR:1",
|
||||||
walletTipId: "tip_id",
|
walletTipId: "tip_id",
|
||||||
} as PrepareTipResult as any),
|
} as PrepareTipResult as any),
|
||||||
acceptTip: async () => ({}),
|
acceptTip: async () => ({}),
|
||||||
} as any,
|
} as any,
|
||||||
),
|
),
|
||||||
|
@ -21,17 +21,17 @@ import { ButtonHandler, TextFieldHandler } from "../../mui/handlers.js";
|
|||||||
import { compose, StateViewMap } from "../../utils/index.js";
|
import { compose, StateViewMap } from "../../utils/index.js";
|
||||||
import * as wxApi from "../../wxApi.js";
|
import * as wxApi from "../../wxApi.js";
|
||||||
import { useComponentState } from "./state.js";
|
import { useComponentState } from "./state.js";
|
||||||
import { LoadingUriView, ReadyView, CreatedView } from "./views.js";
|
import { LoadingUriView, ReadyView } from "./views.js";
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
amount: string;
|
amount: string;
|
||||||
onClose: () => Promise<void>;
|
onClose: () => Promise<void>;
|
||||||
|
onSuccess: (tx: string) => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type State =
|
export type State =
|
||||||
| State.Loading
|
| State.Loading
|
||||||
| State.LoadingUriError
|
| State.LoadingUriError
|
||||||
| State.Created
|
|
||||||
| State.Ready;
|
| State.Ready;
|
||||||
|
|
||||||
export namespace State {
|
export namespace State {
|
||||||
@ -49,11 +49,6 @@ export namespace State {
|
|||||||
error: undefined;
|
error: undefined;
|
||||||
cancel: ButtonHandler;
|
cancel: ButtonHandler;
|
||||||
}
|
}
|
||||||
export interface Created extends BaseInfo {
|
|
||||||
status: "created";
|
|
||||||
talerUri: string;
|
|
||||||
copyToClipboard: ButtonHandler;
|
|
||||||
}
|
|
||||||
export interface Ready extends BaseInfo {
|
export interface Ready extends BaseInfo {
|
||||||
status: "ready";
|
status: "ready";
|
||||||
invalid: boolean;
|
invalid: boolean;
|
||||||
@ -69,7 +64,6 @@ export namespace State {
|
|||||||
const viewMapping: StateViewMap<State> = {
|
const viewMapping: StateViewMap<State> = {
|
||||||
loading: Loading,
|
loading: Loading,
|
||||||
"loading-uri": LoadingUriView,
|
"loading-uri": LoadingUriView,
|
||||||
created: CreatedView,
|
|
||||||
ready: ReadyView,
|
ready: ReadyView,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -21,34 +21,18 @@ import * as wxApi from "../../wxApi.js";
|
|||||||
import { Props, State } from "./index.js";
|
import { Props, State } from "./index.js";
|
||||||
|
|
||||||
export function useComponentState(
|
export function useComponentState(
|
||||||
{ amount: amountStr, onClose }: Props,
|
{ amount: amountStr, onClose, onSuccess }: Props,
|
||||||
api: typeof wxApi,
|
api: typeof wxApi,
|
||||||
): State {
|
): State {
|
||||||
const amount = Amounts.parseOrThrow(amountStr);
|
const amount = Amounts.parseOrThrow(amountStr);
|
||||||
|
|
||||||
const [subject, setSubject] = useState("");
|
const [subject, setSubject] = useState("");
|
||||||
const [talerUri, setTalerUri] = useState("");
|
|
||||||
const [operationError, setOperationError] = useState<
|
const [operationError, setOperationError] = useState<
|
||||||
TalerErrorDetail | undefined
|
TalerErrorDetail | undefined
|
||||||
>(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 {
|
try {
|
||||||
const resp = await api.initiatePeerPushPayment({
|
const resp = await api.initiatePeerPushPayment({
|
||||||
amount: Amounts.stringify(amount),
|
amount: Amounts.stringify(amount),
|
||||||
@ -56,7 +40,7 @@ export function useComponentState(
|
|||||||
summary: subject,
|
summary: subject,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
return resp.talerUri;
|
onSuccess(resp.transactionId);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e instanceof TalerError) {
|
if (e instanceof TalerError) {
|
||||||
setOperationError(e.errorDetail);
|
setOperationError(e.errorDetail);
|
||||||
@ -77,10 +61,7 @@ export function useComponentState(
|
|||||||
onInput: async (e) => setSubject(e),
|
onInput: async (e) => setSubject(e),
|
||||||
},
|
},
|
||||||
create: {
|
create: {
|
||||||
onClick: async () => {
|
onClick: accept
|
||||||
const uri = await accept();
|
|
||||||
setTalerUri(uri);
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
chosenAmount: amount,
|
chosenAmount: amount,
|
||||||
toBeReceived: amount,
|
toBeReceived: amount,
|
||||||
|
@ -20,19 +20,12 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { createExample } from "../../test-utils.js";
|
import { createExample } from "../../test-utils.js";
|
||||||
import { ReadyView, CreatedView } from "./views.js";
|
import { ReadyView } from "./views.js";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: "wallet/transfer create",
|
title: "wallet/transfer create",
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ShowQr = createExample(CreatedView, {
|
|
||||||
talerUri:
|
|
||||||
"taler://pay-push/exchange.taler.ar/HS585JK0QCXHJ8Z8QWZA3EBAY5WY7XNC1RR2MHJXSH2Z4WP0YPJ0",
|
|
||||||
cancel: {},
|
|
||||||
copyToClipboard: {},
|
|
||||||
});
|
|
||||||
|
|
||||||
export const Ready = createExample(ReadyView, {
|
export const Ready = createExample(ReadyView, {
|
||||||
chosenAmount: {
|
chosenAmount: {
|
||||||
currency: "ARS",
|
currency: "ARS",
|
||||||
|
@ -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({
|
export function ReadyView({
|
||||||
subject,
|
subject,
|
||||||
|
@ -30,6 +30,7 @@ import { LoadingUriView, ReadyView } from "./views.js";
|
|||||||
export interface Props {
|
export interface Props {
|
||||||
talerPayPushUri: string;
|
talerPayPushUri: string;
|
||||||
onClose: () => Promise<void>;
|
onClose: () => Promise<void>;
|
||||||
|
onSuccess: (tx: string) => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type State = State.Loading | State.LoadingUriError | State.Ready;
|
export type State = State.Loading | State.LoadingUriError | State.Ready;
|
||||||
|
@ -27,7 +27,7 @@ import * as wxApi from "../../wxApi.js";
|
|||||||
import { Props, State } from "./index.js";
|
import { Props, State } from "./index.js";
|
||||||
|
|
||||||
export function useComponentState(
|
export function useComponentState(
|
||||||
{ talerPayPushUri, onClose }: Props,
|
{ talerPayPushUri, onClose, onSuccess }: Props,
|
||||||
api: typeof wxApi,
|
api: typeof wxApi,
|
||||||
): State {
|
): State {
|
||||||
const hook = useAsyncAsHook(async () => {
|
const hook = useAsyncAsHook(async () => {
|
||||||
@ -68,7 +68,7 @@ export function useComponentState(
|
|||||||
const resp = await api.acceptPeerPushPayment({
|
const resp = await api.acceptPeerPushPayment({
|
||||||
peerPushPaymentIncomingId,
|
peerPushPaymentIncomingId,
|
||||||
});
|
});
|
||||||
await onClose();
|
onSuccess(resp.transactionId)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e instanceof TalerError) {
|
if (e instanceof TalerError) {
|
||||||
setOperationError(e.errorDetail);
|
setOperationError(e.errorDetail);
|
||||||
|
@ -26,7 +26,6 @@ import {
|
|||||||
useComponentStateFromURI,
|
useComponentStateFromURI,
|
||||||
} from "./state.js";
|
} from "./state.js";
|
||||||
import {
|
import {
|
||||||
CompletedView,
|
|
||||||
LoadingExchangeView,
|
LoadingExchangeView,
|
||||||
LoadingInfoView,
|
LoadingInfoView,
|
||||||
LoadingUriView,
|
LoadingUriView,
|
||||||
@ -36,11 +35,13 @@ import {
|
|||||||
export interface PropsFromURI {
|
export interface PropsFromURI {
|
||||||
talerWithdrawUri: string | undefined;
|
talerWithdrawUri: string | undefined;
|
||||||
cancel: () => Promise<void>;
|
cancel: () => Promise<void>;
|
||||||
|
onSuccess: (txid: string) => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PropsFromParams {
|
export interface PropsFromParams {
|
||||||
amount: string;
|
amount: string;
|
||||||
cancel: () => Promise<void>;
|
cancel: () => Promise<void>;
|
||||||
|
onSuccess: (txid: string) => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type State =
|
export type State =
|
||||||
@ -48,8 +49,7 @@ export type State =
|
|||||||
| State.LoadingUriError
|
| State.LoadingUriError
|
||||||
| State.LoadingExchangeError
|
| State.LoadingExchangeError
|
||||||
| State.LoadingInfoError
|
| State.LoadingInfoError
|
||||||
| State.Success
|
| State.Success;
|
||||||
| State.Completed;
|
|
||||||
|
|
||||||
export namespace State {
|
export namespace State {
|
||||||
export interface Loading {
|
export interface Loading {
|
||||||
@ -69,11 +69,6 @@ export namespace State {
|
|||||||
error: HookError;
|
error: HookError;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Completed = {
|
|
||||||
status: "completed";
|
|
||||||
error: undefined;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type Success = {
|
export type Success = {
|
||||||
status: "success";
|
status: "success";
|
||||||
error: undefined;
|
error: undefined;
|
||||||
@ -100,7 +95,6 @@ const viewMapping: StateViewMap<State> = {
|
|||||||
"loading-uri": LoadingUriView,
|
"loading-uri": LoadingUriView,
|
||||||
"loading-exchange": LoadingExchangeView,
|
"loading-exchange": LoadingExchangeView,
|
||||||
"loading-info": LoadingInfoView,
|
"loading-info": LoadingInfoView,
|
||||||
completed: CompletedView,
|
|
||||||
success: SuccessView,
|
success: SuccessView,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -23,7 +23,7 @@ import * as wxApi from "../../wxApi.js";
|
|||||||
import { PropsFromURI, PropsFromParams, State } from "./index.js";
|
import { PropsFromURI, PropsFromParams, State } from "./index.js";
|
||||||
|
|
||||||
export function useComponentStateFromParams(
|
export function useComponentStateFromParams(
|
||||||
{ amount, cancel }: PropsFromParams,
|
{ amount, cancel, onSuccess }: PropsFromParams,
|
||||||
api: typeof wxApi,
|
api: typeof wxApi,
|
||||||
): State {
|
): State {
|
||||||
const [ageRestricted, setAgeRestricted] = useState(0);
|
const [ageRestricted, setAgeRestricted] = useState(0);
|
||||||
@ -40,8 +40,8 @@ export function useComponentStateFromParams(
|
|||||||
// get the first exchange with the currency as the default one
|
// get the first exchange with the currency as the default one
|
||||||
const exchange = exchangeHookDep
|
const exchange = exchangeHookDep
|
||||||
? exchangeHookDep.exchanges.find(
|
? exchangeHookDep.exchanges.find(
|
||||||
(e) => e.currency === chosenAmount.currency,
|
(e) => e.currency === chosenAmount.currency,
|
||||||
)
|
)
|
||||||
: undefined;
|
: undefined;
|
||||||
/**
|
/**
|
||||||
* For the exchange selected, bring the status of the terms of service
|
* For the exchange selected, bring the status of the terms of service
|
||||||
@ -90,7 +90,6 @@ export function useComponentStateFromParams(
|
|||||||
undefined,
|
undefined,
|
||||||
);
|
);
|
||||||
const [doingWithdraw, setDoingWithdraw] = useState<boolean>(false);
|
const [doingWithdraw, setDoingWithdraw] = useState<boolean>(false);
|
||||||
const [withdrawCompleted, setWithdrawCompleted] = useState<boolean>(false);
|
|
||||||
|
|
||||||
if (!exchangeHook) return { status: "loading", error: undefined };
|
if (!exchangeHook) return { status: "loading", error: undefined };
|
||||||
if (exchangeHook.hasError) {
|
if (exchangeHook.hasError) {
|
||||||
@ -122,7 +121,7 @@ export function useComponentStateFromParams(
|
|||||||
Amounts.stringify(amount),
|
Amounts.stringify(amount),
|
||||||
);
|
);
|
||||||
|
|
||||||
setWithdrawCompleted(true);
|
onSuccess(response.transactionId);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e instanceof TalerError) {
|
if (e instanceof TalerError) {
|
||||||
setWithdrawError(e);
|
setWithdrawError(e);
|
||||||
@ -143,9 +142,6 @@ export function useComponentStateFromParams(
|
|||||||
if (!amountHook.response) {
|
if (!amountHook.response) {
|
||||||
return { status: "loading", error: undefined };
|
return { status: "loading", error: undefined };
|
||||||
}
|
}
|
||||||
if (withdrawCompleted) {
|
|
||||||
return { status: "completed", error: undefined };
|
|
||||||
}
|
|
||||||
|
|
||||||
const withdrawalFee = Amounts.sub(
|
const withdrawalFee = Amounts.sub(
|
||||||
amountHook.response.amount.raw,
|
amountHook.response.amount.raw,
|
||||||
@ -156,8 +152,8 @@ export function useComponentStateFromParams(
|
|||||||
const { state: termsState } = (!terms
|
const { state: termsState } = (!terms
|
||||||
? undefined
|
? undefined
|
||||||
: terms.hasError
|
: terms.hasError
|
||||||
? undefined
|
? undefined
|
||||||
: terms.response) || { state: undefined };
|
: terms.response) || { state: undefined };
|
||||||
|
|
||||||
async function onAccept(accepted: boolean): Promise<void> {
|
async function onAccept(accepted: boolean): Promise<void> {
|
||||||
if (!termsState || !exchange) return;
|
if (!termsState || !exchange) return;
|
||||||
@ -194,10 +190,10 @@ export function useComponentStateFromParams(
|
|||||||
//TODO: calculate based on exchange info
|
//TODO: calculate based on exchange info
|
||||||
const ageRestriction = ageRestrictionEnabled
|
const ageRestriction = ageRestrictionEnabled
|
||||||
? {
|
? {
|
||||||
list: ageRestrictionOptions,
|
list: ageRestrictionOptions,
|
||||||
value: String(ageRestricted),
|
value: String(ageRestricted),
|
||||||
onChange: async (v: string) => setAgeRestricted(parseInt(v, 10)),
|
onChange: async (v: string) => setAgeRestricted(parseInt(v, 10)),
|
||||||
}
|
}
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -218,19 +214,19 @@ export function useComponentStateFromParams(
|
|||||||
tosProps: !termsState
|
tosProps: !termsState
|
||||||
? undefined
|
? undefined
|
||||||
: {
|
: {
|
||||||
onAccept,
|
onAccept,
|
||||||
onReview: setReviewing,
|
onReview: setReviewing,
|
||||||
reviewed: reviewed,
|
reviewed: reviewed,
|
||||||
reviewing: reviewing,
|
reviewing: reviewing,
|
||||||
terms: termsState,
|
terms: termsState,
|
||||||
},
|
},
|
||||||
mustAcceptFirst,
|
mustAcceptFirst,
|
||||||
cancel,
|
cancel,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useComponentStateFromURI(
|
export function useComponentStateFromURI(
|
||||||
{ talerWithdrawUri, cancel }: PropsFromURI,
|
{ talerWithdrawUri, cancel, onSuccess }: PropsFromURI,
|
||||||
api: typeof wxApi,
|
api: typeof wxApi,
|
||||||
): State {
|
): State {
|
||||||
const [ageRestricted, setAgeRestricted] = useState(0);
|
const [ageRestricted, setAgeRestricted] = useState(0);
|
||||||
@ -303,7 +299,6 @@ export function useComponentStateFromURI(
|
|||||||
undefined,
|
undefined,
|
||||||
);
|
);
|
||||||
const [doingWithdraw, setDoingWithdraw] = useState<boolean>(false);
|
const [doingWithdraw, setDoingWithdraw] = useState<boolean>(false);
|
||||||
const [withdrawCompleted, setWithdrawCompleted] = useState<boolean>(false);
|
|
||||||
|
|
||||||
if (!uriInfoHook) return { status: "loading", error: undefined };
|
if (!uriInfoHook) return { status: "loading", error: undefined };
|
||||||
if (uriInfoHook.hasError) {
|
if (uriInfoHook.hasError) {
|
||||||
@ -343,8 +338,10 @@ export function useComponentStateFromURI(
|
|||||||
);
|
);
|
||||||
if (res.confirmTransferUrl) {
|
if (res.confirmTransferUrl) {
|
||||||
document.location.href = res.confirmTransferUrl;
|
document.location.href = res.confirmTransferUrl;
|
||||||
|
} else {
|
||||||
|
onSuccess(res.transactionId)
|
||||||
}
|
}
|
||||||
setWithdrawCompleted(true);
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e instanceof TalerError) {
|
if (e instanceof TalerError) {
|
||||||
setWithdrawError(e);
|
setWithdrawError(e);
|
||||||
@ -365,9 +362,6 @@ export function useComponentStateFromURI(
|
|||||||
if (!amountHook.response) {
|
if (!amountHook.response) {
|
||||||
return { status: "loading", error: undefined };
|
return { status: "loading", error: undefined };
|
||||||
}
|
}
|
||||||
if (withdrawCompleted) {
|
|
||||||
return { status: "completed", error: undefined };
|
|
||||||
}
|
|
||||||
|
|
||||||
const withdrawalFee = Amounts.sub(
|
const withdrawalFee = Amounts.sub(
|
||||||
amountHook.response.amount.raw,
|
amountHook.response.amount.raw,
|
||||||
@ -378,8 +372,8 @@ export function useComponentStateFromURI(
|
|||||||
const { state: termsState } = (!terms
|
const { state: termsState } = (!terms
|
||||||
? undefined
|
? undefined
|
||||||
: terms.hasError
|
: terms.hasError
|
||||||
? undefined
|
? undefined
|
||||||
: terms.response) || { state: undefined };
|
: terms.response) || { state: undefined };
|
||||||
|
|
||||||
async function onAccept(accepted: boolean): Promise<void> {
|
async function onAccept(accepted: boolean): Promise<void> {
|
||||||
if (!termsState || !thisExchange) return;
|
if (!termsState || !thisExchange) return;
|
||||||
@ -416,10 +410,10 @@ export function useComponentStateFromURI(
|
|||||||
//TODO: calculate based on exchange info
|
//TODO: calculate based on exchange info
|
||||||
const ageRestriction = ageRestrictionEnabled
|
const ageRestriction = ageRestrictionEnabled
|
||||||
? {
|
? {
|
||||||
list: ageRestrictionOptions,
|
list: ageRestrictionOptions,
|
||||||
value: String(ageRestricted),
|
value: String(ageRestricted),
|
||||||
onChange: async (v: string) => setAgeRestricted(parseInt(v, 10)),
|
onChange: async (v: string) => setAgeRestricted(parseInt(v, 10)),
|
||||||
}
|
}
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -441,12 +435,12 @@ export function useComponentStateFromURI(
|
|||||||
tosProps: !termsState
|
tosProps: !termsState
|
||||||
? undefined
|
? undefined
|
||||||
: {
|
: {
|
||||||
onAccept,
|
onAccept,
|
||||||
onReview: setReviewing,
|
onReview: setReviewing,
|
||||||
reviewed: reviewed,
|
reviewed: reviewed,
|
||||||
reviewing: reviewing,
|
reviewing: reviewing,
|
||||||
terms: termsState,
|
terms: termsState,
|
||||||
},
|
},
|
||||||
mustAcceptFirst,
|
mustAcceptFirst,
|
||||||
cancel,
|
cancel,
|
||||||
};
|
};
|
||||||
|
@ -21,7 +21,7 @@
|
|||||||
|
|
||||||
import { createExample } from "../../test-utils.js";
|
import { createExample } from "../../test-utils.js";
|
||||||
import { TermsState } from "../../utils/index.js";
|
import { TermsState } from "../../utils/index.js";
|
||||||
import { CompletedView, SuccessView } from "./views.js";
|
import { SuccessView } from "./views.js";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: "cta/withdraw",
|
title: "cta/withdraw",
|
||||||
@ -179,11 +179,6 @@ export const EditExchangeModified = createExample(SuccessView, {
|
|||||||
tosProps: normalTosState,
|
tosProps: normalTosState,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const CompletedWithoutBankURL = createExample(CompletedView, {
|
|
||||||
status: "completed",
|
|
||||||
error: undefined,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const WithAgeRestriction = createExample(SuccessView, {
|
export const WithAgeRestriction = createExample(SuccessView, {
|
||||||
error: undefined,
|
error: undefined,
|
||||||
status: "success",
|
status: "success",
|
||||||
|
@ -68,6 +68,7 @@ describe("Withdraw CTA states", () => {
|
|||||||
cancel: async () => {
|
cancel: async () => {
|
||||||
null;
|
null;
|
||||||
},
|
},
|
||||||
|
onSuccess: async () => { null },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
listExchanges: async () => ({ exchanges }),
|
listExchanges: async () => ({ exchanges }),
|
||||||
@ -108,6 +109,7 @@ describe("Withdraw CTA states", () => {
|
|||||||
cancel: async () => {
|
cancel: async () => {
|
||||||
null;
|
null;
|
||||||
},
|
},
|
||||||
|
onSuccess: async () => { null },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
listExchanges: async () => ({ exchanges }),
|
listExchanges: async () => ({ exchanges }),
|
||||||
@ -150,6 +152,7 @@ describe("Withdraw CTA states", () => {
|
|||||||
cancel: async () => {
|
cancel: async () => {
|
||||||
null;
|
null;
|
||||||
},
|
},
|
||||||
|
onSuccess: async () => { null },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
listExchanges: async () => ({ exchanges }),
|
listExchanges: async () => ({ exchanges }),
|
||||||
@ -160,10 +163,10 @@ describe("Withdraw CTA states", () => {
|
|||||||
}),
|
}),
|
||||||
getExchangeWithdrawalInfo:
|
getExchangeWithdrawalInfo:
|
||||||
async (): Promise<ExchangeWithdrawDetails> =>
|
async (): Promise<ExchangeWithdrawDetails> =>
|
||||||
({
|
({
|
||||||
withdrawalAmountRaw: "ARS:2",
|
withdrawalAmountRaw: "ARS:2",
|
||||||
withdrawalAmountEffective: "ARS:2",
|
withdrawalAmountEffective: "ARS:2",
|
||||||
} as any),
|
} as any),
|
||||||
getExchangeTos: async (): Promise<GetExchangeTosResult> => ({
|
getExchangeTos: async (): Promise<GetExchangeTosResult> => ({
|
||||||
contentType: "text",
|
contentType: "text",
|
||||||
content: "just accept",
|
content: "just accept",
|
||||||
@ -224,6 +227,7 @@ describe("Withdraw CTA states", () => {
|
|||||||
cancel: async () => {
|
cancel: async () => {
|
||||||
null;
|
null;
|
||||||
},
|
},
|
||||||
|
onSuccess: async () => { null },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
listExchanges: async () => ({ exchanges }),
|
listExchanges: async () => ({ exchanges }),
|
||||||
@ -234,10 +238,10 @@ describe("Withdraw CTA states", () => {
|
|||||||
}),
|
}),
|
||||||
getExchangeWithdrawalInfo:
|
getExchangeWithdrawalInfo:
|
||||||
async (): Promise<ExchangeWithdrawDetails> =>
|
async (): Promise<ExchangeWithdrawDetails> =>
|
||||||
({
|
({
|
||||||
withdrawalAmountRaw: "ARS:2",
|
withdrawalAmountRaw: "ARS:2",
|
||||||
withdrawalAmountEffective: "ARS:2",
|
withdrawalAmountEffective: "ARS:2",
|
||||||
} as any),
|
} as any),
|
||||||
getExchangeTos: async (): Promise<GetExchangeTosResult> => ({
|
getExchangeTos: async (): Promise<GetExchangeTosResult> => ({
|
||||||
contentType: "text",
|
contentType: "text",
|
||||||
content: "just accept",
|
content: "just accept",
|
||||||
|
@ -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 {
|
export function SuccessView(state: State.Success): VNode {
|
||||||
const { i18n } = useTranslationContext();
|
const { i18n } = useTranslationContext();
|
||||||
return (
|
return (
|
||||||
|
@ -249,41 +249,49 @@ export function Application(): VNode {
|
|||||||
redirectTo(Pages.ctaWithdrawManual({ amount }))
|
redirectTo(Pages.ctaWithdrawManual({ amount }))
|
||||||
}
|
}
|
||||||
cancel={() => redirectTo(Pages.balance)}
|
cancel={() => redirectTo(Pages.balance)}
|
||||||
|
onSuccess={(tid:string) => redirectTo(Pages.balanceTransaction({ tid }))}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path={Pages.ctaRefund}
|
path={Pages.ctaRefund}
|
||||||
component={RefundPage}
|
component={RefundPage}
|
||||||
cancel={() => redirectTo(Pages.balance)}
|
cancel={() => redirectTo(Pages.balance)}
|
||||||
|
onSuccess={(tid:string) => redirectTo(Pages.balanceTransaction({ tid }))}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path={Pages.ctaTips}
|
path={Pages.ctaTips}
|
||||||
component={TipPage}
|
component={TipPage}
|
||||||
onCancel={() => redirectTo(Pages.balance)}
|
onCancel={() => redirectTo(Pages.balance)}
|
||||||
|
onSuccess={(tid:string) => redirectTo(Pages.balanceTransaction({ tid }))}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path={Pages.ctaWithdraw}
|
path={Pages.ctaWithdraw}
|
||||||
component={WithdrawPageFromURI}
|
component={WithdrawPageFromURI}
|
||||||
cancel={() => redirectTo(Pages.balance)}
|
cancel={() => redirectTo(Pages.balance)}
|
||||||
|
onSuccess={(tid:string) => redirectTo(Pages.balanceTransaction({ tid }))}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path={Pages.ctaWithdrawManual.pattern}
|
path={Pages.ctaWithdrawManual.pattern}
|
||||||
component={WithdrawPageFromParams}
|
component={WithdrawPageFromParams}
|
||||||
cancel={() => redirectTo(Pages.balance)}
|
cancel={() => redirectTo(Pages.balance)}
|
||||||
|
onSuccess={(tid:string) => redirectTo(Pages.balanceTransaction({ tid }))}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path={Pages.ctaDeposit}
|
path={Pages.ctaDeposit}
|
||||||
component={DepositPageCTA}
|
component={DepositPageCTA}
|
||||||
cancel={() => redirectTo(Pages.balance)}
|
cancel={() => redirectTo(Pages.balance)}
|
||||||
|
onSuccess={(tid:string) => redirectTo(Pages.balanceTransaction({ tid }))}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path={Pages.ctaInvoiceCreate.pattern}
|
path={Pages.ctaInvoiceCreate.pattern}
|
||||||
component={InvoiceCreatePage}
|
component={InvoiceCreatePage}
|
||||||
onClose={() => redirectTo(Pages.balance)}
|
onClose={() => redirectTo(Pages.balance)}
|
||||||
|
onSuccess={(tid:string) => redirectTo(Pages.balanceTransaction({ tid }))}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path={Pages.ctaTransferCreate.pattern}
|
path={Pages.ctaTransferCreate.pattern}
|
||||||
component={TransferCreatePage}
|
component={TransferCreatePage}
|
||||||
onClose={() => redirectTo(Pages.balance)}
|
onClose={() => redirectTo(Pages.balance)}
|
||||||
|
onSuccess={(tid:string) => redirectTo(Pages.balanceTransaction({ tid }))}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path={Pages.ctaInvoicePay}
|
path={Pages.ctaInvoicePay}
|
||||||
@ -292,11 +300,13 @@ export function Application(): VNode {
|
|||||||
redirectTo(Pages.ctaWithdrawManual({ amount }))
|
redirectTo(Pages.ctaWithdrawManual({ amount }))
|
||||||
}
|
}
|
||||||
onClose={() => redirectTo(Pages.balance)}
|
onClose={() => redirectTo(Pages.balance)}
|
||||||
|
onSuccess={(tid:string) => redirectTo(Pages.balanceTransaction({ tid }))}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path={Pages.ctaTransferPickup}
|
path={Pages.ctaTransferPickup}
|
||||||
component={TransferPickupPage}
|
component={TransferPickupPage}
|
||||||
onClose={() => redirectTo(Pages.balance)}
|
onClose={() => redirectTo(Pages.balance)}
|
||||||
|
onSuccess={(tid:string) => redirectTo(Pages.balanceTransaction({ tid }))}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/**
|
{/**
|
||||||
|
@ -634,7 +634,7 @@ export function TransactionView({
|
|||||||
text={transaction.exchangeBaseUrl}
|
text={transaction.exchangeBaseUrl}
|
||||||
kind="neutral"
|
kind="neutral"
|
||||||
/>
|
/>
|
||||||
{transaction.pending && (
|
{transaction.pending && ( /** pending is not-pay */
|
||||||
<Part
|
<Part
|
||||||
title={<i18n.Translate>URI</i18n.Translate>}
|
title={<i18n.Translate>URI</i18n.Translate>}
|
||||||
text={<ShowQrWithCopy text={transaction.talerUri} />}
|
text={<ShowQrWithCopy text={transaction.talerUri} />}
|
||||||
@ -720,13 +720,13 @@ export function TransactionView({
|
|||||||
text={transaction.exchangeBaseUrl}
|
text={transaction.exchangeBaseUrl}
|
||||||
kind="neutral"
|
kind="neutral"
|
||||||
/>
|
/>
|
||||||
{transaction.pending && (
|
{/* {transaction.pending && ( //pending is not-received
|
||||||
|
)} */}
|
||||||
<Part
|
<Part
|
||||||
title={<i18n.Translate>URI</i18n.Translate>}
|
title={<i18n.Translate>URI</i18n.Translate>}
|
||||||
text={<ShowQrWithCopy text={transaction.talerUri} />}
|
text={<ShowQrWithCopy text={transaction.talerUri} />}
|
||||||
kind="neutral"
|
kind="neutral"
|
||||||
/>
|
/>
|
||||||
)}
|
|
||||||
<Part
|
<Part
|
||||||
title={<i18n.Translate>Details</i18n.Translate>}
|
title={<i18n.Translate>Details</i18n.Translate>}
|
||||||
text={
|
text={
|
||||||
|
Loading…
Reference in New Issue
Block a user