aboutsummaryrefslogtreecommitdiff
path: root/packages
diff options
context:
space:
mode:
Diffstat (limited to 'packages')
-rw-r--r--packages/taler-util/src/amounts.ts4
-rw-r--r--packages/taler-wallet-webextension/src/components/ErrorMessage.tsx2
-rw-r--r--packages/taler-wallet-webextension/src/components/styled/index.tsx16
-rw-r--r--packages/taler-wallet-webextension/src/cta/Pay.stories.tsx62
-rw-r--r--packages/taler-wallet-webextension/src/cta/Pay.tsx243
-rw-r--r--packages/taler-wallet-webextension/src/hooks/useBalances.tsx1
6 files changed, 211 insertions, 117 deletions
diff --git a/packages/taler-util/src/amounts.ts b/packages/taler-util/src/amounts.ts
index f0434be0e..5a8c7f06f 100644
--- a/packages/taler-util/src/amounts.ts
+++ b/packages/taler-util/src/amounts.ts
@@ -407,7 +407,7 @@ export class Amounts {
return `${a.currency}:${s}`;
}
- static stringifyValue(a: AmountJson): string {
+ static stringifyValue(a: AmountJson, minFractional: number = 0): string {
const av = a.value + Math.floor(a.fraction / amountFractionalBase);
const af = a.fraction % amountFractionalBase;
let s = av.toString();
@@ -416,7 +416,7 @@ export class Amounts {
s = s + ".";
let n = af;
for (let i = 0; i < amountFractionalLength; i++) {
- if (!n) {
+ if (!n && i >= minFractional) {
break;
}
s = s + Math.floor((n / amountFractionalBase) * 10).toString();
diff --git a/packages/taler-wallet-webextension/src/components/ErrorMessage.tsx b/packages/taler-wallet-webextension/src/components/ErrorMessage.tsx
index b0e339c70..cfcef16d5 100644
--- a/packages/taler-wallet-webextension/src/components/ErrorMessage.tsx
+++ b/packages/taler-wallet-webextension/src/components/ErrorMessage.tsx
@@ -22,7 +22,7 @@ export function ErrorMessage({ title, description }: { title?: string|VNode; des
const [showErrorDetail, setShowErrorDetail] = useState(false);
if (!title)
return null;
- return <ErrorBox>
+ return <ErrorBox style={{paddingTop: 0, paddingBottom: 0}}>
<div>
<p>{title}</p>
{ description && <button onClick={() => { setShowErrorDetail(v => !v); }}>
diff --git a/packages/taler-wallet-webextension/src/components/styled/index.tsx b/packages/taler-wallet-webextension/src/components/styled/index.tsx
index 0dbf34b5c..0537621bf 100644
--- a/packages/taler-wallet-webextension/src/components/styled/index.tsx
+++ b/packages/taler-wallet-webextension/src/components/styled/index.tsx
@@ -520,8 +520,7 @@ export const ErrorBox = styled.div`
justify-content: space-between;
flex-direction: column;
/* margin: 0.5em; */
- padding-left: 1em;
- padding-right: 1em;
+ padding: 1em;
/* width: 100%; */
color: #721c24;
background: #f8d7da;
@@ -539,6 +538,19 @@ export const ErrorBox = styled.div`
}
}
`
+
+export const SuccessBox = styled(ErrorBox)`
+ color: #0f5132;
+ background-color: #d1e7dd;
+ border-color: #badbcc;
+`
+
+export const WarningBox = styled(ErrorBox)`
+ color: #664d03;
+ background-color: #fff3cd;
+ border-color: #ffecb5;
+`
+
export const PopupNavigation = styled.div<{ devMode?: boolean }>`
background-color:#0042b2;
height: 35px;
diff --git a/packages/taler-wallet-webextension/src/cta/Pay.stories.tsx b/packages/taler-wallet-webextension/src/cta/Pay.stories.tsx
index 3ca30ccb2..622e7950f 100644
--- a/packages/taler-wallet-webextension/src/cta/Pay.stories.tsx
+++ b/packages/taler-wallet-webextension/src/cta/Pay.stories.tsx
@@ -30,7 +30,7 @@ export default {
},
};
-export const InsufficientBalance = createExample(TestedComponent, {
+export const NoBalance = createExample(TestedComponent, {
payStatus: {
status: PreparePayResultType.InsufficientBalance,
noncePriv: '',
@@ -46,6 +46,27 @@ export const InsufficientBalance = createExample(TestedComponent, {
}
});
+export const NoEnoughBalance = createExample(TestedComponent, {
+ payStatus: {
+ status: PreparePayResultType.InsufficientBalance,
+ noncePriv: '',
+ proposalId: "proposal1234",
+ contractTerms: {
+ merchant: {
+ name: 'someone'
+ },
+ summary: 'some beers',
+ amount: 'USD:10',
+ } as Partial<ContractTerms> as any,
+ amountRaw: 'USD:10',
+ },
+ balance: {
+ currency: 'USD',
+ fraction: 40000000,
+ value: 9
+ }
+});
+
export const PaymentPossible = createExample(TestedComponent, {
uri: 'taler://pay/merchant-backend.taler/2021.242-01G2X4275RBWG/?c=66BE594PDZR24744J6EQK52XM0',
payStatus: {
@@ -66,6 +87,26 @@ export const PaymentPossible = createExample(TestedComponent, {
}
});
+export const PaymentPossibleWithFee = createExample(TestedComponent, {
+ uri: 'taler://pay/merchant-backend.taler/2021.242-01G2X4275RBWG/?c=66BE594PDZR24744J6EQK52XM0',
+ payStatus: {
+ status: PreparePayResultType.PaymentPossible,
+ amountEffective: 'USD:10.20',
+ amountRaw: 'USD:10',
+ noncePriv: '',
+ contractTerms: {
+ nonce: '123213123',
+ merchant: {
+ name: 'someone'
+ },
+ amount: 'USD:10',
+ summary: 'some beers',
+ } as Partial<ContractTerms> as any,
+ contractTermsHash: '123456',
+ proposalId: 'proposal1234'
+ }
+});
+
export const AlreadyConfirmedWithFullfilment = createExample(TestedComponent, {
payStatus: {
status: PreparePayResultType.AlreadyConfirmed,
@@ -102,3 +143,22 @@ export const AlreadyConfirmedWithoutFullfilment = createExample(TestedComponent,
paid: false,
}
});
+
+export const AlreadyPaid = createExample(TestedComponent, {
+ payStatus: {
+ status: PreparePayResultType.AlreadyConfirmed,
+ amountEffective: 'USD:10',
+ amountRaw: 'USD:10',
+ contractTerms: {
+ merchant: {
+ name: 'someone'
+ },
+ fulfillment_message: 'congratulations! you are looking at the fulfillment message! ',
+ summary: 'some beers',
+ amount: 'USD:10',
+ } as Partial<ContractTerms> as any,
+ contractTermsHash: '123456',
+ proposalId: 'proposal1234',
+ paid: true,
+ }
+});
diff --git a/packages/taler-wallet-webextension/src/cta/Pay.tsx b/packages/taler-wallet-webextension/src/cta/Pay.tsx
index c0038f8fd..e7a3415ac 100644
--- a/packages/taler-wallet-webextension/src/cta/Pay.tsx
+++ b/packages/taler-wallet-webextension/src/cta/Pay.tsx
@@ -24,56 +24,45 @@
*/
// import * as i18n from "../i18n";
-import { renderAmount, ProgressButton } from "../renderHtml";
-import * as wxApi from "../wxApi";
-
-import { useState, useEffect } from "preact/hooks";
-
-import { AmountLike, ConfirmPayResultDone, getJsonI18n, i18n } from "@gnu-taler/taler-util";
-import {
- PreparePayResult,
- ConfirmPayResult,
- AmountJson,
- PreparePayResultType,
- Amounts,
- ContractTerms,
- ConfirmPayResultType,
-} from "@gnu-taler/taler-util";
-import { JSX, VNode, h, Fragment } from "preact";
-import { ButtonDestructive, ButtonSuccess, ButtonWarning, LinkSuccess, LinkWarning, WalletAction } from "../components/styled";
+import { AmountJson, AmountLike, Amounts, ConfirmPayResult, ConfirmPayResultDone, ConfirmPayResultType, ContractTerms, getJsonI18n, i18n, PreparePayResult, PreparePayResultType } from "@gnu-taler/taler-util";
+import { Fragment, JSX, VNode } from "preact";
+import { useEffect, useState } from "preact/hooks";
import { LogoHeader } from "../components/LogoHeader";
import { Part } from "../components/Part";
import { QR } from "../components/QR";
+import { ButtonSuccess, LinkSuccess, SuccessBox, WalletAction, WarningBox } from "../components/styled";
+import { useBalances } from "../hooks/useBalances";
+import * as wxApi from "../wxApi";
interface Props {
talerPayUri?: string
}
-export function AlreadyPaid({ payStatus }: { payStatus: PreparePayResult }) {
- const fulfillmentUrl = payStatus.contractTerms.fulfillment_url;
- let message;
- if (fulfillmentUrl) {
- message = (
- <span>
- You have already paid for this article. Click{" "}
- <a href={fulfillmentUrl} target="_bank" rel="external">here</a> to view it again.
- </span>
- );
- } else {
- message = <span>
- You have already paid for this article:{" "}
- <em>
- {payStatus.contractTerms.fulfillment_message ?? "no message given"}
- </em>
- </span>;
- }
- return <section class="main">
- <h1>GNU Taler Wallet</h1>
- <article class="fade">
- {message}
- </article>
- </section>
-}
+// export function AlreadyPaid({ payStatus }: { payStatus: PreparePayResult }) {
+// const fulfillmentUrl = payStatus.contractTerms.fulfillment_url;
+// let message;
+// if (fulfillmentUrl) {
+// message = (
+// <span>
+// You have already paid for this article. Click{" "}
+// <a href={fulfillmentUrl} target="_bank" rel="external">here</a> to view it again.
+// </span>
+// );
+// } else {
+// message = <span>
+// You have already paid for this article:{" "}
+// <em>
+// {payStatus.contractTerms.fulfillment_message ?? "no message given"}
+// </em>
+// </span>;
+// }
+// return <section class="main">
+// <h1>GNU Taler Wallet</h1>
+// <article class="fade">
+// {message}
+// </article>
+// </section>
+// }
const doPayment = async (payStatus: PreparePayResult): Promise<ConfirmPayResultDone> => {
if (payStatus.status !== "payment-possible") {
@@ -98,6 +87,12 @@ export function PayPage({ talerPayUri }: Props): JSX.Element {
const [payResult, setPayResult] = useState<ConfirmPayResult | undefined>(undefined);
const [payErrMsg, setPayErrMsg] = useState<string | undefined>("");
+ const balance = useBalances()
+ const balanceWithoutError = balance?.error ? [] : (balance?.response.balances || [])
+
+ const foundBalance = balanceWithoutError.find(b => payStatus && Amounts.parseOrThrow(b.available).currency === Amounts.parseOrThrow(payStatus?.amountRaw).currency)
+ const foundAmount = foundBalance ? Amounts.parseOrThrow(foundBalance.available) : undefined
+
useEffect(() => {
if (!talerPayUri) return;
const doFetch = async (): Promise<void> => {
@@ -115,24 +110,24 @@ export function PayPage({ talerPayUri }: Props): JSX.Element {
return <span>Loading payment information ...</span>;
}
- if (payResult && payResult.type === ConfirmPayResultType.Done) {
- if (payResult.contractTerms.fulfillment_message) {
- const obj = {
- fulfillment_message: payResult.contractTerms.fulfillment_message,
- fulfillment_message_i18n:
- payResult.contractTerms.fulfillment_message_i18n,
- };
- const msg = getJsonI18n(obj, "fulfillment_message");
- return (
- <div>
- <p>Payment succeeded.</p>
- <p>{msg}</p>
- </div>
- );
- } else {
- return <span>Redirecting ...</span>;
- }
- }
+ // if (payResult && payResult.type === ConfirmPayResultType.Done) {
+ // if (payResult.contractTerms.fulfillment_message) {
+ // const obj = {
+ // fulfillment_message: payResult.contractTerms.fulfillment_message,
+ // fulfillment_message_i18n:
+ // payResult.contractTerms.fulfillment_message_i18n,
+ // };
+ // const msg = getJsonI18n(obj, "fulfillment_message");
+ // return (
+ // <div>
+ // <p>Payment succeeded.</p>
+ // <p>{msg}</p>
+ // </div>
+ // );
+ // } else {
+ // return <span>Redirecting ...</span>;
+ // }
+ // }
const onClick = async () => {
try {
@@ -147,7 +142,7 @@ export function PayPage({ talerPayUri }: Props): JSX.Element {
}
- return <PaymentRequestView uri={talerPayUri} payStatus={payStatus} onClick={onClick} payErrMsg={payErrMsg} />;
+ return <PaymentRequestView uri={talerPayUri} payStatus={payStatus} onClick={onClick} payErrMsg={payErrMsg} balance={foundAmount} />;
}
export interface PaymentRequestViewProps {
@@ -155,8 +150,9 @@ export interface PaymentRequestViewProps {
onClick: () => void;
payErrMsg?: string;
uri: string;
+ balance: AmountJson | undefined;
}
-export function PaymentRequestView({ uri, payStatus, onClick, payErrMsg }: PaymentRequestViewProps) {
+export function PaymentRequestView({ uri, payStatus, onClick, payErrMsg, balance }: PaymentRequestViewProps) {
let totalFees: AmountJson = Amounts.getZero(payStatus.amountRaw);
const contractTerms: ContractTerms = payStatus.contractTerms;
@@ -183,71 +179,98 @@ export function PaymentRequestView({ uri, payStatus, onClick, payErrMsg }: Payme
merchantName = <strong>(pub: {contractTerms.merchant_pub})</strong>;
}
- const [showQR, setShowQR] = useState<boolean>(false)
- const privateUri = payStatus.status !== PreparePayResultType.AlreadyConfirmed ? `${uri}&n=${payStatus.noncePriv}` : uri
+ function Alternative() {
+ const [showQR, setShowQR] = useState<boolean>(false)
+ const privateUri = payStatus.status !== PreparePayResultType.AlreadyConfirmed ? `${uri}&n=${payStatus.noncePriv}` : uri
+ return <section>
+ <LinkSuccess upperCased onClick={() => setShowQR(qr => !qr)}>
+ {!showQR ? i18n.str`Pay with a mobile phone` : i18n.str`Hide QR`}
+ </LinkSuccess>
+ {showQR && <div>
+ <QR text={privateUri} />
+ Scan the QR code or <a href={privateUri}>click here</a>
+ </div>}
+ </section>
+ }
+
+ function ButtonsSection() {
+ if (payErrMsg) {
+ return <section>
+ <div>
+ <p>Payment failed: {payErrMsg}</p>
+ <button class="pure-button button-success" onClick={onClick} >
+ {i18n.str`Retry`}
+ </button>
+ </div>
+ </section>
+ }
+ if (payStatus.status === PreparePayResultType.PaymentPossible) {
+ return <Fragment>
+ <section>
+ <ButtonSuccess upperCased>
+ {i18n.str`Pay`} {amountToString(payStatus.amountEffective)}
+ </ButtonSuccess>
+ </section>
+ <Alternative />
+ </Fragment>
+ }
+ if (payStatus.status === PreparePayResultType.InsufficientBalance) {
+ return <Fragment>
+ <section>
+ {balance ? <WarningBox>
+ Your balance of {amountToString(balance)} is not enough to pay for this purchase
+ </WarningBox> : <WarningBox>
+ Your balance is not enough to pay for this purchase.
+ </WarningBox>}
+ </section>
+ <section>
+ <ButtonSuccess upperCased>
+ {i18n.str`Withdraw digital cash`}
+ </ButtonSuccess>
+ </section>
+ <Alternative />
+ </Fragment>
+ }
+ if (payStatus.status === PreparePayResultType.AlreadyConfirmed) {
+ return <Fragment>
+ <section>
+ {payStatus.paid && contractTerms.fulfillment_message && <Part title="Merchant message" text={contractTerms.fulfillment_message} kind='neutral' />}
+ </section>
+ {!payStatus.paid && <Alternative />}
+ </Fragment>
+ }
+ return <span />
+ }
+
return <WalletAction>
<LogoHeader />
+
<h2>
{i18n.str`Digital cash payment`}
</h2>
+ {payStatus.status === PreparePayResultType.AlreadyConfirmed &&
+ (payStatus.paid ? <SuccessBox> Already paid </SuccessBox> : <WarningBox> Already confirmed </WarningBox>)
+ }
<section>
- {payStatus.status === PreparePayResultType.InsufficientBalance ?
- <Part title="Insufficient balance" text="No enough coins to pay" kind='negative' /> :
- <Part big title="Total amount with fee" text={amountToString(payStatus.amountEffective)} kind='negative' />
+ {payStatus.status !== PreparePayResultType.InsufficientBalance && Amounts.isNonZero(totalFees) &&
+ <Part big title="Total to pay" text={amountToString(payStatus.amountEffective)} kind='negative' />
}
<Part big title="Purchase amount" text={amountToString(payStatus.amountRaw)} kind='neutral' />
- {Amounts.isNonZero(totalFees) && <Part big title="Fee" text={amountToString(totalFees)} kind='negative' />}
+ {Amounts.isNonZero(totalFees) && <Fragment>
+ <Part big title="Fee" text={amountToString(totalFees)} kind='negative' />
+ </Fragment>
+ }
<Part title="Merchant" text={contractTerms.merchant.name} kind='neutral' />
<Part title="Purchase" text={contractTerms.summary} kind='neutral' />
{contractTerms.order_id && <Part title="Receipt" text={`#${contractTerms.order_id}`} kind='neutral' />}
</section>
- {showQR && <section>
- <QR text={privateUri} />
- Scan the QR code or <a href={privateUri}>click here</a>
- </section>}
- <section>
- {payErrMsg ? (
- <div>
- <p>Payment failed: {payErrMsg}</p>
- <button class="pure-button button-success" onClick={onClick} >
- {i18n.str`Retry`}
- </button>
- </div>
- ) : (
- payStatus.status === PreparePayResultType.PaymentPossible ? <Fragment>
- <LinkSuccess upperCased onClick={() => setShowQR(qr => !qr)}>
- {!showQR ? i18n.str`Complete with mobile wallet` : i18n.str`Hide QR`}
- </LinkSuccess>
- <ButtonSuccess upperCased>
- {i18n.str`Confirm payment`}
- </ButtonSuccess>
- </Fragment> : (
- payStatus.status === PreparePayResultType.InsufficientBalance ? <Fragment>
- <LinkSuccess upperCased onClick={() => setShowQR(qr => !qr)}>
- {!showQR ? i18n.str`Pay with other device` : i18n.str`Hide QR`}
- </LinkSuccess>
- <ButtonDestructive upperCased disabled>
- {i18n.str`No enough coins`}
- </ButtonDestructive>
- </Fragment> :
- <Fragment>
- {payStatus.contractTerms.fulfillment_message && <div>
- {payStatus.contractTerms.fulfillment_message}
- </div>}
- <LinkWarning upperCased href={payStatus.contractTerms.fulfillment_url}>
- {i18n.str`Already paid`}
- </LinkWarning>
- </Fragment>
-
- )
- )}
+ <ButtonsSection />
- </section>
</WalletAction>
}
function amountToString(text: AmountLike) {
const aj = Amounts.jsonifyAmount(text)
- const amount = Amounts.stringifyValue(aj)
+ const amount = Amounts.stringifyValue(aj, 2)
return `${amount} ${aj.currency}`
}
diff --git a/packages/taler-wallet-webextension/src/hooks/useBalances.tsx b/packages/taler-wallet-webextension/src/hooks/useBalances.tsx
index f12fca21c..503b7a492 100644
--- a/packages/taler-wallet-webextension/src/hooks/useBalances.tsx
+++ b/packages/taler-wallet-webextension/src/hooks/useBalances.tsx
@@ -32,7 +32,6 @@ export type BalancesHook = BalancesHookOk | BalancesHookError | undefined;
export function useBalances(): BalancesHook {
const [balance, setBalance] = useState<BalancesHook>(undefined);
- console.log('render balance')
useEffect(() => {
async function checkBalance() {
try {