deposit from payto
This commit is contained in:
parent
939729004a
commit
dc842eab6b
@ -1070,6 +1070,23 @@ export const codecForGetFeeForDeposit = (): Codec<GetFeeForDepositRequest> =>
|
|||||||
.property("depositPaytoUri", codecForString())
|
.property("depositPaytoUri", codecForString())
|
||||||
.build("GetFeeForDepositRequest");
|
.build("GetFeeForDepositRequest");
|
||||||
|
|
||||||
|
export interface PrepareDepositRequest {
|
||||||
|
depositPaytoUri: string;
|
||||||
|
amount: AmountString;
|
||||||
|
|
||||||
|
}
|
||||||
|
export const codecForPrepareDepositRequest =
|
||||||
|
(): Codec<PrepareDepositRequest> =>
|
||||||
|
buildCodecForObject<PrepareDepositRequest>()
|
||||||
|
.property("amount", codecForAmountString())
|
||||||
|
.property("depositPaytoUri", codecForString())
|
||||||
|
.build("PrepareDepositRequest");
|
||||||
|
|
||||||
|
export interface PrepareDepositResponse {
|
||||||
|
totalDepositCost: AmountJson;
|
||||||
|
effectiveDepositAmount: AmountJson;
|
||||||
|
}
|
||||||
|
|
||||||
export const codecForCreateDepositGroupRequest =
|
export const codecForCreateDepositGroupRequest =
|
||||||
(): Codec<CreateDepositGroupRequest> =>
|
(): Codec<CreateDepositGroupRequest> =>
|
||||||
buildCodecForObject<CreateDepositGroupRequest>()
|
buildCodecForObject<CreateDepositGroupRequest>()
|
||||||
|
@ -35,6 +35,8 @@ import {
|
|||||||
Logger,
|
Logger,
|
||||||
NotificationType,
|
NotificationType,
|
||||||
parsePaytoUri,
|
parsePaytoUri,
|
||||||
|
PrepareDepositRequest,
|
||||||
|
PrepareDepositResponse,
|
||||||
TalerErrorDetail,
|
TalerErrorDetail,
|
||||||
TalerProtocolTimestamp,
|
TalerProtocolTimestamp,
|
||||||
TrackDepositGroupRequest,
|
TrackDepositGroupRequest,
|
||||||
@ -367,6 +369,108 @@ export async function getFeeForDeposit(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function prepareDepositGroup(
|
||||||
|
ws: InternalWalletState,
|
||||||
|
req: PrepareDepositRequest,
|
||||||
|
): Promise<PrepareDepositResponse> {
|
||||||
|
const p = parsePaytoUri(req.depositPaytoUri);
|
||||||
|
if (!p) {
|
||||||
|
throw Error("invalid payto URI");
|
||||||
|
}
|
||||||
|
const amount = Amounts.parseOrThrow(req.amount);
|
||||||
|
|
||||||
|
|
||||||
|
const exchangeInfos: { url: string; master_pub: string }[] = [];
|
||||||
|
|
||||||
|
await ws.db
|
||||||
|
.mktx((x) => ({
|
||||||
|
exchanges: x.exchanges,
|
||||||
|
exchangeDetails: x.exchangeDetails,
|
||||||
|
}))
|
||||||
|
.runReadOnly(async (tx) => {
|
||||||
|
const allExchanges = await tx.exchanges.iter().toArray();
|
||||||
|
for (const e of allExchanges) {
|
||||||
|
const details = await getExchangeDetails(tx, e.baseUrl);
|
||||||
|
if (!details || amount.currency !== details.currency) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
exchangeInfos.push({
|
||||||
|
master_pub: details.masterPublicKey,
|
||||||
|
url: e.baseUrl,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const now = AbsoluteTime.now();
|
||||||
|
const nowRounded = AbsoluteTime.toTimestamp(now);
|
||||||
|
const contractTerms: ContractTerms = {
|
||||||
|
auditors: [],
|
||||||
|
exchanges: exchangeInfos,
|
||||||
|
amount: req.amount,
|
||||||
|
max_fee: Amounts.stringify(amount),
|
||||||
|
max_wire_fee: Amounts.stringify(amount),
|
||||||
|
wire_method: p.targetType,
|
||||||
|
timestamp: nowRounded,
|
||||||
|
merchant_base_url: "",
|
||||||
|
summary: "",
|
||||||
|
nonce: "",
|
||||||
|
wire_transfer_deadline: nowRounded,
|
||||||
|
order_id: "",
|
||||||
|
h_wire: "",
|
||||||
|
pay_deadline: AbsoluteTime.toTimestamp(
|
||||||
|
AbsoluteTime.addDuration(now, durationFromSpec({ hours: 1 })),
|
||||||
|
),
|
||||||
|
merchant: {
|
||||||
|
name: "(wallet)",
|
||||||
|
},
|
||||||
|
merchant_pub: "",
|
||||||
|
refund_deadline: TalerProtocolTimestamp.zero(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const { h: contractTermsHash } = await ws.cryptoApi.hashString({
|
||||||
|
str: canonicalJson(contractTerms),
|
||||||
|
});
|
||||||
|
|
||||||
|
const contractData = extractContractData(
|
||||||
|
contractTerms,
|
||||||
|
contractTermsHash,
|
||||||
|
"",
|
||||||
|
);
|
||||||
|
|
||||||
|
const candidates = await getCandidatePayCoins(ws, {
|
||||||
|
allowedAuditors: contractData.allowedAuditors,
|
||||||
|
allowedExchanges: contractData.allowedExchanges,
|
||||||
|
amount: contractData.amount,
|
||||||
|
maxDepositFee: contractData.maxDepositFee,
|
||||||
|
maxWireFee: contractData.maxWireFee,
|
||||||
|
timestamp: contractData.timestamp,
|
||||||
|
wireFeeAmortization: contractData.wireFeeAmortization,
|
||||||
|
wireMethod: contractData.wireMethod,
|
||||||
|
});
|
||||||
|
|
||||||
|
const payCoinSel = selectPayCoins({
|
||||||
|
candidates,
|
||||||
|
contractTermsAmount: contractData.amount,
|
||||||
|
depositFeeLimit: contractData.maxDepositFee,
|
||||||
|
wireFeeAmortization: contractData.wireFeeAmortization ?? 1,
|
||||||
|
wireFeeLimit: contractData.maxWireFee,
|
||||||
|
prevPayCoins: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!payCoinSel) {
|
||||||
|
throw Error("insufficient funds");
|
||||||
|
}
|
||||||
|
|
||||||
|
const totalDepositCost = await getTotalPaymentCost(ws, payCoinSel);
|
||||||
|
|
||||||
|
const effectiveDepositAmount = await getEffectiveDepositAmount(
|
||||||
|
ws,
|
||||||
|
p.targetType,
|
||||||
|
payCoinSel,
|
||||||
|
);
|
||||||
|
|
||||||
|
return { totalDepositCost, effectiveDepositAmount }
|
||||||
|
}
|
||||||
export async function createDepositGroup(
|
export async function createDepositGroup(
|
||||||
ws: InternalWalletState,
|
ws: InternalWalletState,
|
||||||
req: CreateDepositGroupRequest,
|
req: CreateDepositGroupRequest,
|
||||||
|
@ -46,6 +46,7 @@ import {
|
|||||||
codecForImportDbRequest,
|
codecForImportDbRequest,
|
||||||
codecForIntegrationTestArgs,
|
codecForIntegrationTestArgs,
|
||||||
codecForListKnownBankAccounts,
|
codecForListKnownBankAccounts,
|
||||||
|
codecForPrepareDepositRequest,
|
||||||
codecForPreparePayRequest, codecForPrepareRefundRequest, codecForPrepareTipRequest,
|
codecForPreparePayRequest, codecForPrepareRefundRequest, codecForPrepareTipRequest,
|
||||||
codecForRetryTransactionRequest,
|
codecForRetryTransactionRequest,
|
||||||
codecForSetCoinSuspendedRequest,
|
codecForSetCoinSuspendedRequest,
|
||||||
@ -114,6 +115,7 @@ import { getBalances } from "./operations/balance.js";
|
|||||||
import {
|
import {
|
||||||
createDepositGroup,
|
createDepositGroup,
|
||||||
getFeeForDeposit,
|
getFeeForDeposit,
|
||||||
|
prepareDepositGroup,
|
||||||
processDepositGroup,
|
processDepositGroup,
|
||||||
trackDepositGroup
|
trackDepositGroup
|
||||||
} from "./operations/deposits.js";
|
} from "./operations/deposits.js";
|
||||||
@ -944,6 +946,10 @@ async function dispatchRequestInternal(
|
|||||||
const req = codecForGetFeeForDeposit().decode(payload);
|
const req = codecForGetFeeForDeposit().decode(payload);
|
||||||
return await getFeeForDeposit(ws, req);
|
return await getFeeForDeposit(ws, req);
|
||||||
}
|
}
|
||||||
|
case "prepareDeposit": {
|
||||||
|
const req = codecForPrepareDepositRequest().decode(payload);
|
||||||
|
return await prepareDepositGroup(ws, req);
|
||||||
|
}
|
||||||
case "createDepositGroup": {
|
case "createDepositGroup": {
|
||||||
const req = codecForCreateDepositGroupRequest().decode(payload);
|
const req = codecForCreateDepositGroupRequest().decode(payload);
|
||||||
return await createDepositGroup(ws, req);
|
return await createDepositGroup(ws, req);
|
||||||
|
@ -63,6 +63,7 @@ export enum Pages {
|
|||||||
cta_refund = "/cta/refund",
|
cta_refund = "/cta/refund",
|
||||||
cta_tips = "/cta/tip",
|
cta_tips = "/cta/tip",
|
||||||
cta_withdraw = "/cta/withdraw",
|
cta_withdraw = "/cta/withdraw",
|
||||||
|
cta_deposit = "/cta/deposit",
|
||||||
}
|
}
|
||||||
|
|
||||||
export function PopupNavBar({ path = "" }: { path?: string }): VNode {
|
export function PopupNavBar({ path = "" }: { path?: string }): VNode {
|
||||||
|
@ -13,7 +13,8 @@
|
|||||||
You should have received a copy of the GNU General Public License along with
|
You should have received a copy of the GNU General Public License along with
|
||||||
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||||
*/
|
*/
|
||||||
import { h, VNode } from "preact";
|
import { PaytoUri, stringifyPaytoUri } from "@gnu-taler/taler-util";
|
||||||
|
import { Fragment, h, VNode } from "preact";
|
||||||
import { ExtraLargeText, LargeText, SmallLightText } from "./styled/index.js";
|
import { ExtraLargeText, LargeText, SmallLightText } from "./styled/index.js";
|
||||||
|
|
||||||
export type Kind = "positive" | "negative" | "neutral";
|
export type Kind = "positive" | "negative" | "neutral";
|
||||||
@ -39,3 +40,43 @@ export function Part({ text, title, kind, big }: Props): VNode {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface PropsPayto {
|
||||||
|
payto: PaytoUri;
|
||||||
|
kind: Kind;
|
||||||
|
big?: boolean;
|
||||||
|
}
|
||||||
|
export function PartPayto({ payto, kind, big }: PropsPayto): VNode {
|
||||||
|
const Text = big ? ExtraLargeText : LargeText;
|
||||||
|
let text: string | undefined = undefined;
|
||||||
|
let title = "";
|
||||||
|
if (payto.isKnown) {
|
||||||
|
if (payto.targetType === "x-taler-bank") {
|
||||||
|
text = payto.account;
|
||||||
|
title = "Bank account";
|
||||||
|
} else if (payto.targetType === "bitcoin") {
|
||||||
|
text = payto.targetPath;
|
||||||
|
title = "Bitcoin addr";
|
||||||
|
} else if (payto.targetType === "iban") {
|
||||||
|
text = payto.targetPath;
|
||||||
|
title = "IBAN";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!text) {
|
||||||
|
text = stringifyPaytoUri(payto);
|
||||||
|
title = "Payto URI";
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div style={{ margin: "1em" }}>
|
||||||
|
<SmallLightText style={{ margin: ".5em" }}>{title}</SmallLightText>
|
||||||
|
<Text
|
||||||
|
style={{
|
||||||
|
color:
|
||||||
|
kind == "positive" ? "green" : kind == "negative" ? "red" : "black",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{text}
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
* @author Sebastian Javier Marchano (sebasjm)
|
* @author Sebastian Javier Marchano (sebasjm)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { ContractTerms, PreparePayResultType } from "@gnu-taler/taler-util";
|
import { Amounts } from "@gnu-taler/taler-util";
|
||||||
import { createExample } from "../test-utils.js";
|
import { createExample } from "../test-utils.js";
|
||||||
import { View as TestedComponent } from "./Deposit.js";
|
import { View as TestedComponent } from "./Deposit.js";
|
||||||
|
|
||||||
@ -29,6 +29,13 @@ export default {
|
|||||||
argTypes: {},
|
argTypes: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Simple = createExample(TestedComponent, {
|
export const Ready = createExample(TestedComponent, {
|
||||||
state: { status: "ready" },
|
state: {
|
||||||
|
status: "ready",
|
||||||
|
confirm: {},
|
||||||
|
cost: Amounts.parseOrThrow("EUR:1.2"),
|
||||||
|
effective: Amounts.parseOrThrow("EUR:1"),
|
||||||
|
fee: Amounts.parseOrThrow("EUR:0.2"),
|
||||||
|
hook: undefined,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
92
packages/taler-wallet-webextension/src/cta/Deposit.test.ts
Normal file
92
packages/taler-wallet-webextension/src/cta/Deposit.test.ts
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
/*
|
||||||
|
This file is part of GNU Taler
|
||||||
|
(C) 2021 Taler Systems S.A.
|
||||||
|
|
||||||
|
GNU Taler is free software; you can redistribute it and/or modify it under the
|
||||||
|
terms of the GNU General Public License as published by the Free Software
|
||||||
|
Foundation; either version 3, or (at your option) any later version.
|
||||||
|
|
||||||
|
GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||||
|
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License along with
|
||||||
|
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author Sebastian Javier Marchano (sebasjm)
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Amounts, PrepareDepositResponse } from "@gnu-taler/taler-util";
|
||||||
|
import { expect } from "chai";
|
||||||
|
import { mountHook } from "../test-utils.js";
|
||||||
|
import { useComponentState } from "./Deposit.jsx";
|
||||||
|
|
||||||
|
describe("Deposit CTA states", () => {
|
||||||
|
it("should tell the user that the URI is missing", async () => {
|
||||||
|
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = mountHook(() =>
|
||||||
|
useComponentState(undefined, undefined, {
|
||||||
|
prepareRefund: async () => ({}),
|
||||||
|
applyRefund: async () => ({}),
|
||||||
|
onUpdateNotification: async () => ({})
|
||||||
|
} as any),
|
||||||
|
);
|
||||||
|
|
||||||
|
{
|
||||||
|
const { status, hook } = getLastResultOrThrow()
|
||||||
|
expect(status).equals('loading')
|
||||||
|
expect(hook).undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
await waitNextUpdate()
|
||||||
|
|
||||||
|
{
|
||||||
|
const { status, hook } = getLastResultOrThrow()
|
||||||
|
|
||||||
|
expect(status).equals('loading')
|
||||||
|
if (!hook) expect.fail();
|
||||||
|
if (!hook.hasError) expect.fail();
|
||||||
|
if (hook.operational) expect.fail();
|
||||||
|
expect(hook.message).eq("ERROR_NO-URI-FOR-DEPOSIT");
|
||||||
|
}
|
||||||
|
|
||||||
|
await assertNoPendingUpdate()
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should be ready after loading", async () => {
|
||||||
|
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = mountHook(() =>
|
||||||
|
useComponentState("payto://refund/asdasdas", "EUR:1", {
|
||||||
|
prepareDeposit: async () => ({
|
||||||
|
effectiveDepositAmount: Amounts.parseOrThrow("EUR:1"),
|
||||||
|
totalDepositCost: Amounts.parseOrThrow("EUR:1.2")
|
||||||
|
} as PrepareDepositResponse as any),
|
||||||
|
createDepositGroup: async () => ({}),
|
||||||
|
} as any),
|
||||||
|
);
|
||||||
|
|
||||||
|
{
|
||||||
|
const { status, hook } = getLastResultOrThrow()
|
||||||
|
expect(status).equals('loading')
|
||||||
|
expect(hook).undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
await waitNextUpdate()
|
||||||
|
|
||||||
|
{
|
||||||
|
const state = getLastResultOrThrow()
|
||||||
|
|
||||||
|
if (state.status !== 'ready') expect.fail();
|
||||||
|
if (state.hook) expect.fail();
|
||||||
|
expect(state.confirm.onClick).not.undefined;
|
||||||
|
expect(state.cost).deep.eq(Amounts.parseOrThrow("EUR:1.2"));
|
||||||
|
expect(state.fee).deep.eq(Amounts.parseOrThrow("EUR:0.2"));
|
||||||
|
expect(state.effective).deep.eq(Amounts.parseOrThrow("EUR:1"));
|
||||||
|
}
|
||||||
|
|
||||||
|
await assertNoPendingUpdate()
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
@ -24,48 +24,120 @@
|
|||||||
* Imports.
|
* Imports.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import {
|
||||||
|
AmountJson,
|
||||||
|
Amounts,
|
||||||
|
AmountString,
|
||||||
|
CreateDepositGroupResponse,
|
||||||
|
} from "@gnu-taler/taler-util";
|
||||||
import { Fragment, h, VNode } from "preact";
|
import { Fragment, h, VNode } from "preact";
|
||||||
|
import { useState } from "preact/hooks";
|
||||||
|
import { Amount } from "../components/Amount.js";
|
||||||
import { Loading } from "../components/Loading.js";
|
import { Loading } from "../components/Loading.js";
|
||||||
import { LoadingError } from "../components/LoadingError.js";
|
import { LoadingError } from "../components/LoadingError.js";
|
||||||
import { LogoHeader } from "../components/LogoHeader.js";
|
import { LogoHeader } from "../components/LogoHeader.js";
|
||||||
import { SubTitle, WalletAction } from "../components/styled/index.js";
|
import { Part } from "../components/Part.js";
|
||||||
|
import {
|
||||||
|
ButtonSuccess,
|
||||||
|
SubTitle,
|
||||||
|
WalletAction,
|
||||||
|
} from "../components/styled/index.js";
|
||||||
import { useTranslationContext } from "../context/translation.js";
|
import { useTranslationContext } from "../context/translation.js";
|
||||||
import { HookError } from "../hooks/useAsyncAsHook.js";
|
import { HookError, useAsyncAsHook } from "../hooks/useAsyncAsHook.js";
|
||||||
|
import { ButtonHandler } from "../mui/handlers.js";
|
||||||
|
import * as wxApi from "../wxApi.js";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
talerDepositUri?: string;
|
talerDepositUri?: string;
|
||||||
|
amount: AmountString;
|
||||||
goBack: () => void;
|
goBack: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
type State = Loading | Ready;
|
type State = Loading | Ready | Completed;
|
||||||
interface Loading {
|
interface Loading {
|
||||||
status: "loading";
|
status: "loading";
|
||||||
hook: HookError | undefined;
|
hook: HookError | undefined;
|
||||||
}
|
}
|
||||||
interface Ready {
|
interface Ready {
|
||||||
status: "ready";
|
status: "ready";
|
||||||
|
hook: undefined;
|
||||||
|
fee: AmountJson;
|
||||||
|
cost: AmountJson;
|
||||||
|
effective: AmountJson;
|
||||||
|
confirm: ButtonHandler;
|
||||||
|
}
|
||||||
|
interface Completed {
|
||||||
|
status: "completed";
|
||||||
|
hook: undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
function useComponentState(uri: string | undefined): State {
|
export function useComponentState(
|
||||||
|
talerDepositUri: string | undefined,
|
||||||
|
amountStr: AmountString | undefined,
|
||||||
|
api: typeof wxApi,
|
||||||
|
): State {
|
||||||
|
const [result, setResult] = useState<CreateDepositGroupResponse | undefined>(
|
||||||
|
undefined,
|
||||||
|
);
|
||||||
|
|
||||||
|
const info = useAsyncAsHook(async () => {
|
||||||
|
if (!talerDepositUri) throw Error("ERROR_NO-URI-FOR-DEPOSIT");
|
||||||
|
if (!amountStr) throw Error("ERROR_NO-AMOUNT-FOR-DEPOSIT");
|
||||||
|
const amount = Amounts.parse(amountStr);
|
||||||
|
if (!amount) throw Error("ERROR_INVALID-AMOUNT-FOR-DEPOSIT");
|
||||||
|
const deposit = await api.prepareDeposit(
|
||||||
|
talerDepositUri,
|
||||||
|
Amounts.stringify(amount),
|
||||||
|
);
|
||||||
|
return { deposit, uri: talerDepositUri, amount };
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!info || info.hasError) {
|
||||||
|
return {
|
||||||
|
status: "loading",
|
||||||
|
hook: info,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
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",
|
||||||
|
hook: undefined,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
status: "loading",
|
status: "ready",
|
||||||
hook: undefined,
|
hook: undefined,
|
||||||
|
confirm: {
|
||||||
|
onClick: doDeposit,
|
||||||
|
},
|
||||||
|
fee: Amounts.sub(deposit.totalDepositCost, deposit.effectiveDepositAmount)
|
||||||
|
.amount,
|
||||||
|
cost: deposit.totalDepositCost,
|
||||||
|
effective: deposit.effectiveDepositAmount,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function DepositPage({ talerDepositUri, goBack }: Props): VNode {
|
export function DepositPage({ talerDepositUri, amount, goBack }: Props): VNode {
|
||||||
const { i18n } = useTranslationContext();
|
const { i18n } = useTranslationContext();
|
||||||
|
|
||||||
const state = useComponentState(talerDepositUri);
|
const state = useComponentState(talerDepositUri, amount, wxApi);
|
||||||
if (state.status === "loading") {
|
|
||||||
if (!state.hook) return <Loading />;
|
if (!talerDepositUri) {
|
||||||
return (
|
return (
|
||||||
<LoadingError
|
<span>
|
||||||
title={<i18n.Translate>Could not load pay status</i18n.Translate>}
|
<i18n.Translate>missing taler deposit uri</i18n.Translate>
|
||||||
error={state.hook}
|
</span>
|
||||||
/>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return <View state={state} />;
|
return <View state={state} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -75,13 +147,71 @@ export interface ViewProps {
|
|||||||
export function View({ state }: ViewProps): VNode {
|
export function View({ state }: ViewProps): VNode {
|
||||||
const { i18n } = useTranslationContext();
|
const { i18n } = useTranslationContext();
|
||||||
|
|
||||||
|
if (state.status === "loading") {
|
||||||
|
if (!state.hook) return <Loading />;
|
||||||
|
return (
|
||||||
|
<LoadingError
|
||||||
|
title={<i18n.Translate>Could not load deposit status</i18n.Translate>}
|
||||||
|
error={state.hook}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.status === "completed") {
|
||||||
|
return (
|
||||||
|
<WalletAction>
|
||||||
|
<LogoHeader />
|
||||||
|
|
||||||
|
<SubTitle>
|
||||||
|
<i18n.Translate>Digital cash deposit</i18n.Translate>
|
||||||
|
</SubTitle>
|
||||||
|
<section>
|
||||||
|
<p>
|
||||||
|
<i18n.Translate>deposit completed</i18n.Translate>
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
</WalletAction>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<WalletAction>
|
<WalletAction>
|
||||||
<LogoHeader />
|
<LogoHeader />
|
||||||
|
|
||||||
<SubTitle>
|
<SubTitle>
|
||||||
<i18n.Translate>Digital cash refund</i18n.Translate>
|
<i18n.Translate>Digital cash deposit</i18n.Translate>
|
||||||
</SubTitle>
|
</SubTitle>
|
||||||
|
<section>
|
||||||
|
{Amounts.isNonZero(state.cost) && (
|
||||||
|
<Part
|
||||||
|
big
|
||||||
|
title={<i18n.Translate>Cost</i18n.Translate>}
|
||||||
|
text={<Amount value={state.cost} />}
|
||||||
|
kind="negative"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{Amounts.isNonZero(state.fee) && (
|
||||||
|
<Part
|
||||||
|
big
|
||||||
|
title={<i18n.Translate>Fee</i18n.Translate>}
|
||||||
|
text={<Amount value={state.fee} />}
|
||||||
|
kind="negative"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<Part
|
||||||
|
big
|
||||||
|
title={<i18n.Translate>To be received</i18n.Translate>}
|
||||||
|
text={<Amount value={state.effective} />}
|
||||||
|
kind="positive"
|
||||||
|
/>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<ButtonSuccess upperCased onClick={state.confirm.onClick}>
|
||||||
|
<i18n.Translate>
|
||||||
|
Deposit {<Amount value={state.effective} />}
|
||||||
|
</i18n.Translate>
|
||||||
|
</ButtonSuccess>
|
||||||
|
</section>
|
||||||
</WalletAction>
|
</WalletAction>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -38,6 +38,7 @@ import { PayPage } from "../cta/Pay.js";
|
|||||||
import { RefundPage } from "../cta/Refund.js";
|
import { RefundPage } from "../cta/Refund.js";
|
||||||
import { TipPage } from "../cta/Tip.js";
|
import { TipPage } from "../cta/Tip.js";
|
||||||
import { WithdrawPage } from "../cta/Withdraw.js";
|
import { WithdrawPage } from "../cta/Withdraw.js";
|
||||||
|
import { DepositPage as DepositPageCTA } from "../cta/Deposit.js";
|
||||||
import { Pages, WalletNavBar } from "../NavigationBar.js";
|
import { Pages, WalletNavBar } from "../NavigationBar.js";
|
||||||
import { DeveloperPage } from "./DeveloperPage.js";
|
import { DeveloperPage } from "./DeveloperPage.js";
|
||||||
import { BackupPage } from "./BackupPage.js";
|
import { BackupPage } from "./BackupPage.js";
|
||||||
@ -232,6 +233,7 @@ export function Application(): VNode {
|
|||||||
<Route path={Pages.cta_refund} component={RefundPage} />
|
<Route path={Pages.cta_refund} component={RefundPage} />
|
||||||
<Route path={Pages.cta_tips} component={TipPage} />
|
<Route path={Pages.cta_tips} component={TipPage} />
|
||||||
<Route path={Pages.cta_withdraw} component={WithdrawPage} />
|
<Route path={Pages.cta_withdraw} component={WithdrawPage} />
|
||||||
|
<Route path={Pages.cta_deposit} component={DepositPageCTA} />
|
||||||
|
|
||||||
{/**
|
{/**
|
||||||
* NOT FOUND
|
* NOT FOUND
|
||||||
|
@ -19,6 +19,7 @@ import {
|
|||||||
Amounts,
|
Amounts,
|
||||||
NotificationType,
|
NotificationType,
|
||||||
parsePaytoUri,
|
parsePaytoUri,
|
||||||
|
parsePayUri,
|
||||||
Transaction,
|
Transaction,
|
||||||
TransactionType,
|
TransactionType,
|
||||||
WithdrawalType,
|
WithdrawalType,
|
||||||
@ -32,13 +33,14 @@ import { BankDetailsByPaytoType } from "../components/BankDetailsByPaytoType.js"
|
|||||||
import { ErrorTalerOperation } from "../components/ErrorTalerOperation.js";
|
import { ErrorTalerOperation } from "../components/ErrorTalerOperation.js";
|
||||||
import { Loading } from "../components/Loading.js";
|
import { Loading } from "../components/Loading.js";
|
||||||
import { LoadingError } from "../components/LoadingError.js";
|
import { LoadingError } from "../components/LoadingError.js";
|
||||||
import { Part } from "../components/Part.js";
|
import { Part, PartPayto } from "../components/Part.js";
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
ButtonDestructive,
|
ButtonDestructive,
|
||||||
ButtonPrimary,
|
ButtonPrimary,
|
||||||
CenteredDialog,
|
CenteredDialog,
|
||||||
InfoBox,
|
InfoBox,
|
||||||
|
LargeText,
|
||||||
ListOfProducts,
|
ListOfProducts,
|
||||||
Overlay,
|
Overlay,
|
||||||
RowBorderGray,
|
RowBorderGray,
|
||||||
@ -428,6 +430,7 @@ export function TransactionView({
|
|||||||
Amounts.parseOrThrow(transaction.amountEffective),
|
Amounts.parseOrThrow(transaction.amountEffective),
|
||||||
Amounts.parseOrThrow(transaction.amountRaw),
|
Amounts.parseOrThrow(transaction.amountRaw),
|
||||||
).amount;
|
).amount;
|
||||||
|
const payto = parsePaytoUri(transaction.targetPaytoUri);
|
||||||
return (
|
return (
|
||||||
<TransactionTemplate>
|
<TransactionTemplate>
|
||||||
<SubTitle>
|
<SubTitle>
|
||||||
@ -456,6 +459,7 @@ export function TransactionView({
|
|||||||
text={<Amount value={fee} />}
|
text={<Amount value={fee} />}
|
||||||
kind="negative"
|
kind="negative"
|
||||||
/>
|
/>
|
||||||
|
{payto && <PartPayto big payto={payto} kind="neutral" />}
|
||||||
</TransactionTemplate>
|
</TransactionTemplate>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -43,6 +43,8 @@ import {
|
|||||||
GetWithdrawalDetailsForUriRequest,
|
GetWithdrawalDetailsForUriRequest,
|
||||||
KnownBankAccounts,
|
KnownBankAccounts,
|
||||||
NotificationType,
|
NotificationType,
|
||||||
|
PrepareDepositRequest,
|
||||||
|
PrepareDepositResponse,
|
||||||
PreparePayResult,
|
PreparePayResult,
|
||||||
PrepareRefundRequest,
|
PrepareRefundRequest,
|
||||||
PrepareRefundResult,
|
PrepareRefundResult,
|
||||||
@ -160,6 +162,16 @@ export function getFeeForDeposit(
|
|||||||
} as GetFeeForDepositRequest);
|
} as GetFeeForDepositRequest);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function prepareDeposit(
|
||||||
|
depositPaytoUri: string,
|
||||||
|
amount: AmountString,
|
||||||
|
): Promise<PrepareDepositResponse> {
|
||||||
|
return callBackend("prepareDeposit", {
|
||||||
|
depositPaytoUri,
|
||||||
|
amount,
|
||||||
|
} as PrepareDepositRequest);
|
||||||
|
}
|
||||||
|
|
||||||
export function createDepositGroup(
|
export function createDepositGroup(
|
||||||
depositPaytoUri: string,
|
depositPaytoUri: string,
|
||||||
amount: AmountString,
|
amount: AmountString,
|
||||||
|
Loading…
Reference in New Issue
Block a user