summaryrefslogtreecommitdiff
path: root/packages
diff options
context:
space:
mode:
Diffstat (limited to 'packages')
-rw-r--r--packages/taler-util/src/walletTypes.ts13
-rw-r--r--packages/taler-wallet-core/src/operations/withdraw.ts2
-rw-r--r--packages/taler-wallet-core/src/wallet.ts5
-rw-r--r--packages/taler-wallet-webextension/.storybook/preview.js1
-rw-r--r--packages/taler-wallet-webextension/src/components/Part.tsx16
-rw-r--r--packages/taler-wallet-webextension/src/components/styled/index.tsx19
-rw-r--r--packages/taler-wallet-webextension/src/cta/Pay.tsx4
-rw-r--r--packages/taler-wallet-webextension/src/cta/Withdraw.stories.tsx34
-rw-r--r--packages/taler-wallet-webextension/src/cta/Withdraw.tsx148
-rw-r--r--packages/taler-wallet-webextension/src/wallet/Transaction.tsx12
-rw-r--r--packages/taler-wallet-webextension/src/wxApi.ts12
11 files changed, 160 insertions, 106 deletions
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<GetWithdrawalDetails
.property("talerWithdrawUri", codecForString())
.build("GetWithdrawalDetailsForUriRequest");
+export interface GetExchangeWithdrawalInfo {
+ exchangeBaseUrl: string;
+ amount: AmountJson;
+}
+
+export const codecForGetExchangeWithdrawalInfo = (): Codec<GetExchangeWithdrawalInfo> =>
+ buildCodecForObject<GetExchangeWithdrawalInfo>()
+ .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;
}`}
</style>
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 <div style={{ margin: '1em' }}>
+ <SmallLightText style={{ margin: '.5em' }}>{title}</SmallLightText>
+ <Text style={{ color: kind == 'positive' ? 'green' : (kind == 'negative' ? 'red' : 'black') }}>
+ {text}
+ </Text>
+ </div>
+}
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 { 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 { LogoHeader } from '../components/LogoHeader';
+import { Part } from '../components/Part';
+import { ButtonSuccess, WalletAction } from '../components/styled';
import {
- acceptWithdrawal,
- onUpdateNotification,
- getWithdrawalDetailsForUri,
+ acceptWithdrawal, getExchangeWithdrawalInfo, getWithdrawalDetailsForUri, onUpdateNotification
} from "../wxApi";
-import { h } from 'preact';
-import { WithdrawUriInfoResponse } from "@gnu-taler/taler-util";
-import { JSX } from "preact/jsx-runtime";
-import { WalletAction } from '../components/styled';
+
interface Props {
talerWithdrawUri?: string;
}
export interface ViewProps {
- details: WithdrawUriInfoResponse;
- selectedExchange?: string;
+ details: ExchangeWithdrawDetails;
+ amount: string;
accept: () => Promise<void>;
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 (
- <WalletAction>
- <div style="border-bottom: 3px dashed #aa3939; margin-bottom: 2em;">
- <h1 style="font-family: monospace; font-size: 250%;">
- <span style="color: #aa3939;">❰</span>Taler Wallet<span style="color: #aa3939;">❱</span>
- </h1>
- </div>
- <div class="fade">
+ <WalletAction style={{ textAlign: 'center' }}>
+ <LogoHeader />
+ <h2>
+ {i18n.str`Digital cash withdrawal`}
+ </h2>
+ <section>
+ <div>
+ <Part title="Total to withdraw" text={amountToString(Amounts.sub(Amounts.parseOrThrow(amount), details.withdrawFee).amount)} kind='positive' />
+ <Part title="Chosen amount" text={amountToString(amount)} kind='neutral' />
+ {Amounts.isNonZero(details.withdrawFee) &&
+ <Part title="Exchange fee" text={amountToString(details.withdrawFee)} kind='negative' />
+ }
+ <Part title="Exchange" text={details.exchangeInfo.baseUrl} kind='neutral' big />
+ </div>
+ </section>
+ <section>
+
<div>
- <h1><i18n.Translate>Digital Cash Withdrawal</i18n.Translate></h1>
- <p><i18n.Translate>
- You are about to withdraw{" "}
- <strong>{renderAmount(details.amount)}</strong> from your bank account
- into your wallet.
- </i18n.Translate></p>
- {selectedExchange ? (
- <p><i18n.Translate>
- The exchange <strong>{selectedExchange}</strong> will be used as the
- Taler payment service provider.
- </i18n.Translate></p>
- ) : null}
-
- <div>
- <button
- class="pure-button button-success"
- disabled={!selectedExchange}
- onClick={() => accept()}
- >
- {i18n.str`Accept fees and withdraw`}
- </button>
- <p>
- <span
- role="button"
- tabIndex={0}
- style={{ textDecoration: "underline", cursor: "pointer" }}
- onClick={() => setSelecting(true)}
- >
- {i18n.str`Chose different exchange provider`}
- </span>
- <br />
- <span
- role="button"
- tabIndex={0}
- style={{ textDecoration: "underline", cursor: "pointer" }}
- onClick={() => setCancelled(true)}
- >
- {i18n.str`Cancel withdraw operation`}
- </span>
- </p>
- </div>
+ <ButtonSuccess
+ upperCased
+ disabled={!details.exchangeInfo.baseUrl}
+ onClick={accept}
+ >
+ {i18n.str`Accept fees and withdraw`}
+ </ButtonSuccess>
</div>
- </div>
+ </section>
</WalletAction>
)
}
export function WithdrawPage({ talerWithdrawUri, ...rest }: Props): JSX.Element {
- const [details, setDetails] = useState<WithdrawUriInfoResponse | undefined>(undefined);
- const [selectedExchange, setSelectedExchange] = useState<string | undefined>(undefined);
+ const [uriInfo, setUriInfo] = useState<WithdrawUriInfoResponse | undefined>(undefined);
+ const [details, setDetails] = useState<ExchangeWithdrawDetails | undefined>(undefined);
const [cancelled, setCancelled] = useState(false);
const [selecting, setSelecting] = useState(false);
const [error, setError] = useState<boolean>(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<void> => {
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 <span><i18n.Translate>missing withdraw uri</i18n.Translate></span>;
}
const accept = async (): Promise<void> => {
- 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 <span><i18n.Translate>Loading...</i18n.Translate></span>;
- }
if (cancelled) {
return <span><i18n.Translate>Withdraw operation has been cancelled.</i18n.Translate></span>;
}
if (error) {
return <span><i18n.Translate>This URI is not valid anymore.</i18n.Translate></span>;
}
+ if (!uriInfo) {
+ return <span><i18n.Translate>Loading...</i18n.Translate></span>;
+ }
+ if (!details) {
+ return <span><i18n.Translate>Getting withdrawal details.</i18n.Translate></span>;
+ }
return <View accept={accept}
setCancelled={setCancelled} setSelecting={setSelecting}
- details={details} selectedExchange={selectedExchange}
+ details={details} amount={uriInfo.amount}
/>
}
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
</footer>
</WalletBox>
}
- 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 <div style={{ margin: '1em' }}>
- <SmallLightText style={{ margin: '.5em' }}>{title}</SmallLightText>
- <Text style={{ color: kind == 'positive' ? 'green' : (kind == 'negative' ? 'red' : 'black') }}>
- {text}
- </Text>
- </div>
- }
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<ExchangeWithdrawDetails> {
+ return callBackend("getExchangeWithdrawalInfo", req);
+}
+
export function prepareTip(req: PrepareTipRequest): Promise<PrepareTipResult> {
return callBackend("prepareTip", req);
}