deposit from payto
This commit is contained in:
parent
939729004a
commit
dc842eab6b
@ -1070,6 +1070,23 @@ export const codecForGetFeeForDeposit = (): Codec<GetFeeForDepositRequest> =>
|
||||
.property("depositPaytoUri", codecForString())
|
||||
.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 =
|
||||
(): Codec<CreateDepositGroupRequest> =>
|
||||
buildCodecForObject<CreateDepositGroupRequest>()
|
||||
|
@ -35,6 +35,8 @@ import {
|
||||
Logger,
|
||||
NotificationType,
|
||||
parsePaytoUri,
|
||||
PrepareDepositRequest,
|
||||
PrepareDepositResponse,
|
||||
TalerErrorDetail,
|
||||
TalerProtocolTimestamp,
|
||||
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(
|
||||
ws: InternalWalletState,
|
||||
req: CreateDepositGroupRequest,
|
||||
|
@ -46,6 +46,7 @@ import {
|
||||
codecForImportDbRequest,
|
||||
codecForIntegrationTestArgs,
|
||||
codecForListKnownBankAccounts,
|
||||
codecForPrepareDepositRequest,
|
||||
codecForPreparePayRequest, codecForPrepareRefundRequest, codecForPrepareTipRequest,
|
||||
codecForRetryTransactionRequest,
|
||||
codecForSetCoinSuspendedRequest,
|
||||
@ -114,6 +115,7 @@ import { getBalances } from "./operations/balance.js";
|
||||
import {
|
||||
createDepositGroup,
|
||||
getFeeForDeposit,
|
||||
prepareDepositGroup,
|
||||
processDepositGroup,
|
||||
trackDepositGroup
|
||||
} from "./operations/deposits.js";
|
||||
@ -944,6 +946,10 @@ async function dispatchRequestInternal(
|
||||
const req = codecForGetFeeForDeposit().decode(payload);
|
||||
return await getFeeForDeposit(ws, req);
|
||||
}
|
||||
case "prepareDeposit": {
|
||||
const req = codecForPrepareDepositRequest().decode(payload);
|
||||
return await prepareDepositGroup(ws, req);
|
||||
}
|
||||
case "createDepositGroup": {
|
||||
const req = codecForCreateDepositGroupRequest().decode(payload);
|
||||
return await createDepositGroup(ws, req);
|
||||
|
@ -63,6 +63,7 @@ export enum Pages {
|
||||
cta_refund = "/cta/refund",
|
||||
cta_tips = "/cta/tip",
|
||||
cta_withdraw = "/cta/withdraw",
|
||||
cta_deposit = "/cta/deposit",
|
||||
}
|
||||
|
||||
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
|
||||
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";
|
||||
|
||||
export type Kind = "positive" | "negative" | "neutral";
|
||||
@ -39,3 +40,43 @@ export function Part({ text, title, kind, big }: Props): VNode {
|
||||
</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)
|
||||
*/
|
||||
|
||||
import { ContractTerms, PreparePayResultType } from "@gnu-taler/taler-util";
|
||||
import { Amounts } from "@gnu-taler/taler-util";
|
||||
import { createExample } from "../test-utils.js";
|
||||
import { View as TestedComponent } from "./Deposit.js";
|
||||
|
||||
@ -29,6 +29,13 @@ export default {
|
||||
argTypes: {},
|
||||
};
|
||||
|
||||
export const Simple = createExample(TestedComponent, {
|
||||
state: { status: "ready" },
|
||||
export const Ready = createExample(TestedComponent, {
|
||||
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.
|
||||
*/
|
||||
|
||||
import {
|
||||
AmountJson,
|
||||
Amounts,
|
||||
AmountString,
|
||||
CreateDepositGroupResponse,
|
||||
} from "@gnu-taler/taler-util";
|
||||
import { Fragment, h, VNode } from "preact";
|
||||
import { useState } from "preact/hooks";
|
||||
import { Amount } from "../components/Amount.js";
|
||||
import { Loading } from "../components/Loading.js";
|
||||
import { LoadingError } from "../components/LoadingError.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 { 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 {
|
||||
talerDepositUri?: string;
|
||||
amount: AmountString;
|
||||
goBack: () => void;
|
||||
}
|
||||
|
||||
type State = Loading | Ready;
|
||||
type State = Loading | Ready | Completed;
|
||||
interface Loading {
|
||||
status: "loading";
|
||||
hook: HookError | undefined;
|
||||
}
|
||||
interface 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 {
|
||||
status: "loading",
|
||||
status: "ready",
|
||||
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 state = useComponentState(talerDepositUri);
|
||||
if (state.status === "loading") {
|
||||
if (!state.hook) return <Loading />;
|
||||
const state = useComponentState(talerDepositUri, amount, wxApi);
|
||||
|
||||
if (!talerDepositUri) {
|
||||
return (
|
||||
<LoadingError
|
||||
title={<i18n.Translate>Could not load pay status</i18n.Translate>}
|
||||
error={state.hook}
|
||||
/>
|
||||
<span>
|
||||
<i18n.Translate>missing taler deposit uri</i18n.Translate>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
return <View state={state} />;
|
||||
}
|
||||
|
||||
@ -75,13 +147,71 @@ export interface ViewProps {
|
||||
export function View({ state }: ViewProps): VNode {
|
||||
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 (
|
||||
<WalletAction>
|
||||
<LogoHeader />
|
||||
|
||||
<SubTitle>
|
||||
<i18n.Translate>Digital cash refund</i18n.Translate>
|
||||
<i18n.Translate>Digital cash deposit</i18n.Translate>
|
||||
</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>
|
||||
);
|
||||
}
|
||||
|
@ -38,6 +38,7 @@ import { PayPage } from "../cta/Pay.js";
|
||||
import { RefundPage } from "../cta/Refund.js";
|
||||
import { TipPage } from "../cta/Tip.js";
|
||||
import { WithdrawPage } from "../cta/Withdraw.js";
|
||||
import { DepositPage as DepositPageCTA } from "../cta/Deposit.js";
|
||||
import { Pages, WalletNavBar } from "../NavigationBar.js";
|
||||
import { DeveloperPage } from "./DeveloperPage.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_tips} component={TipPage} />
|
||||
<Route path={Pages.cta_withdraw} component={WithdrawPage} />
|
||||
<Route path={Pages.cta_deposit} component={DepositPageCTA} />
|
||||
|
||||
{/**
|
||||
* NOT FOUND
|
||||
|
@ -19,6 +19,7 @@ import {
|
||||
Amounts,
|
||||
NotificationType,
|
||||
parsePaytoUri,
|
||||
parsePayUri,
|
||||
Transaction,
|
||||
TransactionType,
|
||||
WithdrawalType,
|
||||
@ -32,13 +33,14 @@ import { BankDetailsByPaytoType } from "../components/BankDetailsByPaytoType.js"
|
||||
import { ErrorTalerOperation } from "../components/ErrorTalerOperation.js";
|
||||
import { Loading } from "../components/Loading.js";
|
||||
import { LoadingError } from "../components/LoadingError.js";
|
||||
import { Part } from "../components/Part.js";
|
||||
import { Part, PartPayto } from "../components/Part.js";
|
||||
import {
|
||||
Button,
|
||||
ButtonDestructive,
|
||||
ButtonPrimary,
|
||||
CenteredDialog,
|
||||
InfoBox,
|
||||
LargeText,
|
||||
ListOfProducts,
|
||||
Overlay,
|
||||
RowBorderGray,
|
||||
@ -428,6 +430,7 @@ export function TransactionView({
|
||||
Amounts.parseOrThrow(transaction.amountEffective),
|
||||
Amounts.parseOrThrow(transaction.amountRaw),
|
||||
).amount;
|
||||
const payto = parsePaytoUri(transaction.targetPaytoUri);
|
||||
return (
|
||||
<TransactionTemplate>
|
||||
<SubTitle>
|
||||
@ -456,6 +459,7 @@ export function TransactionView({
|
||||
text={<Amount value={fee} />}
|
||||
kind="negative"
|
||||
/>
|
||||
{payto && <PartPayto big payto={payto} kind="neutral" />}
|
||||
</TransactionTemplate>
|
||||
);
|
||||
}
|
||||
|
@ -43,6 +43,8 @@ import {
|
||||
GetWithdrawalDetailsForUriRequest,
|
||||
KnownBankAccounts,
|
||||
NotificationType,
|
||||
PrepareDepositRequest,
|
||||
PrepareDepositResponse,
|
||||
PreparePayResult,
|
||||
PrepareRefundRequest,
|
||||
PrepareRefundResult,
|
||||
@ -160,6 +162,16 @@ export function getFeeForDeposit(
|
||||
} as GetFeeForDepositRequest);
|
||||
}
|
||||
|
||||
export function prepareDeposit(
|
||||
depositPaytoUri: string,
|
||||
amount: AmountString,
|
||||
): Promise<PrepareDepositResponse> {
|
||||
return callBackend("prepareDeposit", {
|
||||
depositPaytoUri,
|
||||
amount,
|
||||
} as PrepareDepositRequest);
|
||||
}
|
||||
|
||||
export function createDepositGroup(
|
||||
depositPaytoUri: string,
|
||||
amount: AmountString,
|
||||
|
Loading…
Reference in New Issue
Block a user