diff options
Diffstat (limited to 'packages/taler-wallet-webextension/src/cta')
-rw-r--r-- | packages/taler-wallet-webextension/src/cta/Pay.stories.tsx | 62 | ||||
-rw-r--r-- | packages/taler-wallet-webextension/src/cta/Pay.tsx | 243 |
2 files changed, 194 insertions, 111 deletions
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}` } |