diff --git a/packages/taler-util/src/walletTypes.ts b/packages/taler-util/src/walletTypes.ts index 6e71de6ee..7ec182fa2 100644 --- a/packages/taler-util/src/walletTypes.ts +++ b/packages/taler-util/src/walletTypes.ts @@ -713,6 +713,17 @@ export const codecForGetWithdrawalDetailsForUri = (): Codec => + buildCodecForObject() + .property("exchangeBaseUrl", codecForString()) + .property("amount", codecForAmountJson()) + .build("GetExchangeWithdrawalInfo"); + export interface AbortProposalRequest { proposalId: string; } @@ -791,7 +802,7 @@ export interface MakeSyncSignatureRequest { /** * Planchet for a coin during refresh. */ - export interface RefreshPlanchetInfo { +export interface RefreshPlanchetInfo { /** * Public key for the coin. */ diff --git a/packages/taler-wallet-core/src/operations/withdraw.ts b/packages/taler-wallet-core/src/operations/withdraw.ts index e6b6e8746..620ad88be 100644 --- a/packages/taler-wallet-core/src/operations/withdraw.ts +++ b/packages/taler-wallet-core/src/operations/withdraw.ts @@ -92,7 +92,7 @@ interface DenominationSelectionInfo { * * Sent to the wallet frontend to be rendered and shown to the user. */ -interface ExchangeWithdrawDetails { +export interface ExchangeWithdrawDetails { /** * Exchange that the reserve will be created at. */ diff --git a/packages/taler-wallet-core/src/wallet.ts b/packages/taler-wallet-core/src/wallet.ts index fec7e6155..cbaf03c3b 100644 --- a/packages/taler-wallet-core/src/wallet.ts +++ b/packages/taler-wallet-core/src/wallet.ts @@ -28,6 +28,7 @@ import { codecForDeleteTransactionRequest, codecForRetryTransactionRequest, codecForSetWalletDeviceIdRequest, + codecForGetExchangeWithdrawalInfo, durationFromSpec, durationMin, getDurationRemaining, @@ -693,6 +694,10 @@ async function dispatchRequestInternal( const req = codecForGetWithdrawalDetailsForUri().decode(payload); return await getWithdrawalDetailsForUri(ws, req.talerWithdrawUri); } + case "getExchangeWithdrawalInfo": { + const req = codecForGetExchangeWithdrawalInfo().decode(payload); + return await getExchangeWithdrawalInfo(ws, req.exchangeBaseUrl, req.amount); + } case "acceptManualWithdrawal": { const req = codecForAcceptManualWithdrawalRequet().decode(payload); const res = await acceptManualWithdrawal( diff --git a/packages/taler-wallet-webextension/.storybook/preview.js b/packages/taler-wallet-webextension/.storybook/preview.js index 488663469..0efb96308 100644 --- a/packages/taler-wallet-webextension/.storybook/preview.js +++ b/packages/taler-wallet-webextension/.storybook/preview.js @@ -119,7 +119,6 @@ export const decorators = [ margin: 0; font-size: 100%; padding: 0; - background-color: #f8faf7; font-family: Arial, Helvetica, sans-serif; }`} diff --git a/packages/taler-wallet-webextension/src/components/Part.tsx b/packages/taler-wallet-webextension/src/components/Part.tsx new file mode 100644 index 000000000..87b16de87 --- /dev/null +++ b/packages/taler-wallet-webextension/src/components/Part.tsx @@ -0,0 +1,16 @@ +import { AmountLike } from "@gnu-taler/taler-util"; +import { ExtraLargeText, LargeText, SmallLightText } from "./styled"; + +export type Kind = 'positive' | 'negative' | 'neutral'; +interface Props { + title: string, text: AmountLike, kind: Kind, big?: boolean +} +export function Part({ text, title, kind, big }: Props) { + const Text = big ? ExtraLargeText : LargeText; + return
+ {title} + + {text} + +
+} diff --git a/packages/taler-wallet-webextension/src/components/styled/index.tsx b/packages/taler-wallet-webextension/src/components/styled/index.tsx index de045584c..553726de4 100644 --- a/packages/taler-wallet-webextension/src/components/styled/index.tsx +++ b/packages/taler-wallet-webextension/src/components/styled/index.tsx @@ -12,6 +12,16 @@ export const PaymentStatus = styled.div<{ color: string }>` ` export const WalletAction = styled.section` + max-width: 50%; + + margin: auto; + height: 100%; + + & h1:first-child { + margin-top: 0; + } +` +export const WalletActionOld = styled.section` border: solid 5px black; border-radius: 10px; margin-left: auto; @@ -152,7 +162,7 @@ export const PopupBox = styled.div<{ noPadding?: boolean }>` ` -export const Button = styled.button` +export const Button = styled.button<{ upperCased?: boolean }>` display: inline-block; zoom: 1; line-height: normal; @@ -162,6 +172,7 @@ export const Button = styled.button` cursor: pointer; user-select: none; box-sizing: border-box; + text-transform: ${({ upperCased }) => upperCased ? 'uppercase' : 'none'}; font-family: inherit; font-size: 100%; @@ -242,11 +253,11 @@ export const ButtonBoxPrimary = styled(ButtonBox)` ` export const ButtonSuccess = styled(ButtonVariant)` - background-color: rgb(28, 184, 65); + background-color: #388e3c; ` export const ButtonBoxSuccess = styled(ButtonBox)` - color: rgb(28, 184, 65); - border-color: rgb(28, 184, 65); + color: #388e3c; + border-color: #388e3c; ` export const ButtonWarning = styled(ButtonVariant)` diff --git a/packages/taler-wallet-webextension/src/cta/Pay.tsx b/packages/taler-wallet-webextension/src/cta/Pay.tsx index 24c76ce6c..758bc4b54 100644 --- a/packages/taler-wallet-webextension/src/cta/Pay.tsx +++ b/packages/taler-wallet-webextension/src/cta/Pay.tsx @@ -136,7 +136,9 @@ export function PayPage({ talerPayUri }: Props): JSX.Element { setPayResult(res); } catch (e) { console.error(e); - setPayErrMsg(e.message); + if (e instanceof Error) { + setPayErrMsg(e.message); + } } } diff --git a/packages/taler-wallet-webextension/src/cta/Withdraw.stories.tsx b/packages/taler-wallet-webextension/src/cta/Withdraw.stories.tsx index 747f855fa..a89a18c15 100644 --- a/packages/taler-wallet-webextension/src/cta/Withdraw.stories.tsx +++ b/packages/taler-wallet-webextension/src/cta/Withdraw.stories.tsx @@ -19,6 +19,9 @@ * @author Sebastian Javier Marchano (sebasjm) */ +import { amountFractionalBase, Amounts } from '@gnu-taler/taler-util'; +import { ExchangeRecord } from '@gnu-taler/taler-wallet-core'; +import { ExchangeWithdrawDetails } from '@gnu-taler/taler-wallet-core/src/operations/withdraw'; import { createExample } from '../test-utils'; import { View as TestedComponent } from './Withdraw'; @@ -30,16 +33,29 @@ export default { }, }; -export const CompleteWithExchange = createExample(TestedComponent, { +export const WithdrawWithFee = createExample(TestedComponent, { details: { - amount: 'USD:2', - possibleExchanges: [], - }, - selectedExchange: 'Some exchange' + exchangeInfo: { + baseUrl: 'exchange.demo.taler.net' + } as ExchangeRecord, + withdrawFee: { + currency: 'USD', + fraction: amountFractionalBase*0.5, + value: 0 + }, + } as ExchangeWithdrawDetails, + amount: 'USD:2', }) -export const CompleteWithoutExchange = createExample(TestedComponent, { +export const WithdrawWithoutFee = createExample(TestedComponent, { details: { - amount: 'USD:2', - possibleExchanges: [], - }, + exchangeInfo: { + baseUrl: 'exchange.demo.taler.net' + } as ExchangeRecord, + withdrawFee: { + currency: 'USD', + fraction: 0, + value: 0 + }, + } as ExchangeWithdrawDetails, + amount: 'USD:2', }) diff --git a/packages/taler-wallet-webextension/src/cta/Withdraw.tsx b/packages/taler-wallet-webextension/src/cta/Withdraw.tsx index d5f3c89ae..9719b8f5e 100644 --- a/packages/taler-wallet-webextension/src/cta/Withdraw.tsx +++ b/packages/taler-wallet-webextension/src/cta/Withdraw.tsx @@ -21,98 +21,78 @@ * @author Florian Dold */ -import { i18n } from '@gnu-taler/taler-util' -import { renderAmount } from "../renderHtml"; - -import { useState, useEffect } from "preact/hooks"; -import { - acceptWithdrawal, - onUpdateNotification, - getWithdrawalDetailsForUri, -} from "../wxApi"; -import { h } from 'preact'; -import { WithdrawUriInfoResponse } from "@gnu-taler/taler-util"; +import { AmountLike, Amounts, i18n, WithdrawUriInfoResponse } from '@gnu-taler/taler-util'; +import { ExchangeWithdrawDetails } from '@gnu-taler/taler-wallet-core/src/operations/withdraw'; +import { useEffect, useState } from "preact/hooks"; import { JSX } from "preact/jsx-runtime"; -import { WalletAction } from '../components/styled'; +import { LogoHeader } from '../components/LogoHeader'; +import { Part } from '../components/Part'; +import { ButtonSuccess, WalletAction } from '../components/styled'; +import { + acceptWithdrawal, getExchangeWithdrawalInfo, getWithdrawalDetailsForUri, onUpdateNotification +} from "../wxApi"; + interface Props { talerWithdrawUri?: string; } export interface ViewProps { - details: WithdrawUriInfoResponse; - selectedExchange?: string; + details: ExchangeWithdrawDetails; + amount: string; accept: () => Promise; setCancelled: (b: boolean) => void; setSelecting: (b: boolean) => void; }; -export function View({ details, selectedExchange, accept, setCancelled, setSelecting }: ViewProps) { +function amountToString(text: AmountLike) { + const aj = Amounts.jsonifyAmount(text) + const amount = Amounts.stringifyValue(aj) + return `${amount} ${aj.currency}` +} + + +export function View({ details, amount, accept, setCancelled, setSelecting }: ViewProps) { return ( - -
-

- Taler Wallet -

-
-
+ + +

+ {i18n.str`Digital cash withdrawal`} +

+
-

Digital Cash Withdrawal

-

- You are about to withdraw{" "} - {renderAmount(details.amount)} from your bank account - into your wallet. -

- {selectedExchange ? ( -

- The exchange {selectedExchange} will be used as the - Taler payment service provider. -

- ) : null} - -
- -

- setSelecting(true)} - > - {i18n.str`Chose different exchange provider`} - -
- setCancelled(true)} - > - {i18n.str`Cancel withdraw operation`} - -

-
+ + + {Amounts.isNonZero(details.withdrawFee) && + + } +
-
+ +
+ +
+ + {i18n.str`Accept fees and withdraw`} + +
+
) } export function WithdrawPage({ talerWithdrawUri, ...rest }: Props): JSX.Element { - const [details, setDetails] = useState(undefined); - const [selectedExchange, setSelectedExchange] = useState(undefined); + const [uriInfo, setUriInfo] = useState(undefined); + const [details, setDetails] = useState(undefined); const [cancelled, setCancelled] = useState(false); const [selecting, setSelecting] = useState(false); const [error, setError] = useState(false); const [updateCounter, setUpdateCounter] = useState(1); - const [state, setState] = useState(1) useEffect(() => { return onUpdateNotification(() => { @@ -127,47 +107,59 @@ export function WithdrawPage({ talerWithdrawUri, ...rest }: Props): JSX.Element const fetchData = async (): Promise => { try { const res = await getWithdrawalDetailsForUri({ talerWithdrawUri }); - setDetails(res); - if (res.defaultExchangeBaseUrl) { - setSelectedExchange(res.defaultExchangeBaseUrl); - } + setUriInfo(res); } catch (e) { console.error('error', JSON.stringify(e, undefined, 2)) setError(true) } }; fetchData(); - }, [selectedExchange, selecting, talerWithdrawUri, updateCounter, state]); + }, [selecting, talerWithdrawUri, updateCounter]); + + useEffect(() => { + async function fetchData() { + if (!uriInfo || !uriInfo.defaultExchangeBaseUrl) return + const res = await getExchangeWithdrawalInfo({ + exchangeBaseUrl: uriInfo.defaultExchangeBaseUrl, + amount: Amounts.parseOrThrow(uriInfo.amount) + }) + setDetails(res) + } + fetchData() + }, [uriInfo]) if (!talerWithdrawUri) { return missing withdraw uri; } const accept = async (): Promise => { - if (!selectedExchange) { + if (!details) { throw Error("can't accept, no exchange selected"); } - console.log("accepting exchange", selectedExchange); - const res = await acceptWithdrawal(talerWithdrawUri, selectedExchange); + console.log("accepting exchange", details.exchangeInfo.baseUrl); + const res = await acceptWithdrawal(talerWithdrawUri, details.exchangeInfo.baseUrl); console.log("accept withdrawal response", res); if (res.confirmTransferUrl) { document.location.href = res.confirmTransferUrl; } }; - if (!details) { - return Loading...; - } if (cancelled) { return Withdraw operation has been cancelled.; } if (error) { return This URI is not valid anymore.; } + if (!uriInfo) { + return Loading...; + } + if (!details) { + return Getting withdrawal details.; + } return } diff --git a/packages/taler-wallet-webextension/src/wallet/Transaction.tsx b/packages/taler-wallet-webextension/src/wallet/Transaction.tsx index cc5430d0d..435753725 100644 --- a/packages/taler-wallet-webextension/src/wallet/Transaction.tsx +++ b/packages/taler-wallet-webextension/src/wallet/Transaction.tsx @@ -24,6 +24,7 @@ import { Pages } from "../NavigationBar"; import emptyImg from "../../static/img/empty.png" import { Button, ButtonBox, ButtonBoxDestructive, ButtonDestructive, ButtonPrimary, ExtraLargeText, FontIcon, LargeText, ListOfProducts, PopupBox, Row, RowBorderGray, SmallLightText, WalletBox } from "../components/styled"; import { ErrorMessage } from "../components/ErrorMessage"; +import { Part } from "../components/Part"; export function TransactionPage({ tid }: { tid: string; }): JSX.Element { const [transaction, setTransaction] = useState< @@ -60,7 +61,6 @@ export interface WalletTransactionProps { onBack: () => void, } - export function TransactionView({ transaction, onDelete, onRetry, onBack }: WalletTransactionProps) { function Status() { @@ -90,16 +90,6 @@ export function TransactionView({ transaction, onDelete, onRetry, onBack }: Wall } - type Kind = 'positive' | 'negative' | 'neutral'; - function Part({ text, title, kind, big }: { title: string, text: AmountLike, kind: Kind, big?: boolean }) { - const Text = big ? ExtraLargeText : LargeText; - return
- {title} - - {text} - -
- } function amountToString(text: AmountLike) { const aj = Amounts.jsonifyAmount(text) diff --git a/packages/taler-wallet-webextension/src/wxApi.ts b/packages/taler-wallet-webextension/src/wxApi.ts index 52ce27f2b..acb28ffec 100644 --- a/packages/taler-wallet-webextension/src/wxApi.ts +++ b/packages/taler-wallet-webextension/src/wxApi.ts @@ -38,9 +38,11 @@ import { DeleteTransactionRequest, RetryTransactionRequest, SetWalletDeviceIdRequest, + GetExchangeWithdrawalInfo, } from "@gnu-taler/taler-util"; import { AddBackupProviderRequest, BackupProviderState, OperationFailedError, RemoveBackupProviderRequest } from "@gnu-taler/taler-wallet-core"; import { BackupInfo } from "@gnu-taler/taler-wallet-core"; +import { ExchangeWithdrawDetails } from "@gnu-taler/taler-wallet-core/src/operations/withdraw"; export interface ExtendedPermissionsResponse { newValue: boolean; @@ -281,6 +283,16 @@ export function getWithdrawalDetailsForUri( return callBackend("getWithdrawalDetailsForUri", req); } + +/** + * Get diagnostics information + */ + export function getExchangeWithdrawalInfo( + req: GetExchangeWithdrawalInfo, +): Promise { + return callBackend("getExchangeWithdrawalInfo", req); +} + export function prepareTip(req: PrepareTipRequest): Promise { return callBackend("prepareTip", req); }