diff options
Diffstat (limited to 'packages/taler-wallet-webextension/src/cta/Pay.tsx')
-rw-r--r-- | packages/taler-wallet-webextension/src/cta/Pay.tsx | 351 |
1 files changed, 225 insertions, 126 deletions
diff --git a/packages/taler-wallet-webextension/src/cta/Pay.tsx b/packages/taler-wallet-webextension/src/cta/Pay.tsx index 675b14ff9..1023013d2 100644 --- a/packages/taler-wallet-webextension/src/cta/Pay.tsx +++ b/packages/taler-wallet-webextension/src/cta/Pay.tsx @@ -24,18 +24,36 @@ */ // import * as i18n from "../i18n"; -import { AmountJson, AmountLike, Amounts, ConfirmPayResult, ConfirmPayResultDone, ConfirmPayResultType, ContractTerms, getJsonI18n, i18n, PreparePayResult, PreparePayResultType } from "@gnu-taler/taler-util"; -import { Fragment, JSX, VNode } from "preact"; +import { + AmountJson, + AmountLike, + Amounts, + ConfirmPayResult, + ConfirmPayResultDone, + ConfirmPayResultType, + ContractTerms, + i18n, + PreparePayResult, + PreparePayResultType, +} from "@gnu-taler/taler-util"; +import { h, 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, ErrorBox, LinkSuccess, SuccessBox, WalletAction, WarningBox } from "../components/styled"; +import { + ButtonSuccess, + ErrorBox, + LinkSuccess, + SuccessBox, + WalletAction, + WarningBox, +} from "../components/styled"; import { useBalances } from "../hooks/useBalances"; import * as wxApi from "../wxApi"; interface Props { - talerPayUri?: string + talerPayUri?: string; } // export function AlreadyPaid({ payStatus }: { payStatus: PreparePayResult }) { @@ -64,7 +82,9 @@ interface Props { // </section> // } -const doPayment = async (payStatus: PreparePayResult): Promise<ConfirmPayResultDone> => { +const doPayment = async ( + payStatus: PreparePayResult, +): Promise<ConfirmPayResultDone> => { if (payStatus.status !== "payment-possible") { throw Error(`invalid state: ${payStatus.status}`); } @@ -80,18 +100,29 @@ const doPayment = async (payStatus: PreparePayResult): Promise<ConfirmPayResultD return res; }; - - export function PayPage({ talerPayUri }: Props): JSX.Element { - const [payStatus, setPayStatus] = useState<PreparePayResult | undefined>(undefined); - const [payResult, setPayResult] = useState<ConfirmPayResult | undefined>(undefined); + const [payStatus, setPayStatus] = useState<PreparePayResult | undefined>( + undefined, + ); + const [payResult, setPayResult] = useState<ConfirmPayResult | undefined>( + undefined, + ); const [payErrMsg, setPayErrMsg] = useState<string | undefined>(undefined); - const balance = useBalances() - const balanceWithoutError = balance?.hasError ? [] : (balance?.response.balances || []) + const balance = useBalances(); + const balanceWithoutError = balance?.hasError + ? [] + : 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 + 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; @@ -101,7 +132,7 @@ export function PayPage({ talerPayUri }: Props): JSX.Element { setPayStatus(p); } catch (e) { if (e instanceof Error) { - setPayErrMsg(e.message) + setPayErrMsg(e.message); } } }; @@ -109,30 +140,28 @@ export function PayPage({ talerPayUri }: Props): JSX.Element { }, [talerPayUri]); if (!talerPayUri) { - return <span>missing pay uri</span> + return <span>missing pay uri</span>; } if (!payStatus) { if (payErrMsg) { - return <WalletAction> - <LogoHeader /> - <h2> - {i18n.str`Digital cash payment`} - </h2> - <section> - <p>Could not get the payment information for this order</p> - <ErrorBox> - {payErrMsg} - </ErrorBox> - </section> - </WalletAction> + return ( + <WalletAction> + <LogoHeader /> + <h2>{i18n.str`Digital cash payment`}</h2> + <section> + <p>Could not get the payment information for this order</p> + <ErrorBox>{payErrMsg}</ErrorBox> + </section> + </WalletAction> + ); } return <span>Loading payment information ...</span>; } const onClick = async () => { try { - const res = await doPayment(payStatus) + const res = await doPayment(payStatus); setPayResult(res); } catch (e) { console.error(e); @@ -140,13 +169,18 @@ export function PayPage({ talerPayUri }: Props): JSX.Element { setPayErrMsg(e.message); } } + }; - } - - return <PaymentRequestView uri={talerPayUri} - payStatus={payStatus} payResult={payResult} - onClick={onClick} payErrMsg={payErrMsg} - balance={foundAmount} />; + return ( + <PaymentRequestView + uri={talerPayUri} + payStatus={payStatus} + payResult={payResult} + onClick={onClick} + payErrMsg={payErrMsg} + balance={foundAmount} + /> + ); } export interface PaymentRequestViewProps { @@ -157,7 +191,14 @@ export interface PaymentRequestViewProps { uri: string; balance: AmountJson | undefined; } -export function PaymentRequestView({ uri, payStatus, payResult, onClick, payErrMsg, balance }: PaymentRequestViewProps) { +export function PaymentRequestView({ + uri, + payStatus, + payResult, + onClick, + payErrMsg, + balance, +}: PaymentRequestViewProps) { let totalFees: AmountJson = Amounts.getZero(payStatus.amountRaw); const contractTerms: ContractTerms = payStatus.contractTerms; @@ -185,116 +226,174 @@ export function PaymentRequestView({ uri, payStatus, payResult, onClick, payErrM } 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> + 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 (payResult) { if (payResult.type === ConfirmPayResultType.Pending) { - return <section> - <div> - <p>Processing...</p> - </div> - </section> + return ( + <section> + <div> + <p>Processing...</p> + </div> + </section> + ); } - return null + return null; } 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> + return ( <section> - <ButtonSuccess upperCased onClick={onClick}> - {i18n.str`Pay`} {amountToString(payStatus.amountEffective)} - </ButtonSuccess> + <div> + <p>Payment failed: {payErrMsg}</p> + <button class="pure-button button-success" onClick={onClick}> + {i18n.str`Retry`} + </button> + </div> </section> - <Alternative /> - </Fragment> + ); + } + if (payStatus.status === PreparePayResultType.PaymentPossible) { + return ( + <Fragment> + <section> + <ButtonSuccess upperCased onClick={onClick}> + {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> + 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 ( + <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 <span />; } - return <WalletAction> - <LogoHeader /> - - <h2> - {i18n.str`Digital cash payment`} - </h2> - {payStatus.status === PreparePayResultType.AlreadyConfirmed && - (payStatus.paid ? <SuccessBox> Already paid </SuccessBox> : <WarningBox> Already claimed </WarningBox>) - } - {payResult && payResult.type === ConfirmPayResultType.Done && ( - <SuccessBox> - <h3>Payment complete</h3> - <p>{!payResult.contractTerms.fulfillment_message ? - "You will now be sent back to the merchant you came from." : - payResult.contractTerms.fulfillment_message - }</p> - </SuccessBox> - )} - <section> - {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) && <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> - <ButtonsSection /> + return ( + <WalletAction> + <LogoHeader /> - </WalletAction> + <h2>{i18n.str`Digital cash payment`}</h2> + {payStatus.status === PreparePayResultType.AlreadyConfirmed && + (payStatus.paid ? ( + <SuccessBox> Already paid </SuccessBox> + ) : ( + <WarningBox> Already claimed </WarningBox> + ))} + {payResult && payResult.type === ConfirmPayResultType.Done && ( + <SuccessBox> + <h3>Payment complete</h3> + <p> + {!payResult.contractTerms.fulfillment_message + ? "You will now be sent back to the merchant you came from." + : payResult.contractTerms.fulfillment_message} + </p> + </SuccessBox> + )} + <section> + {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) && ( + <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> + <ButtonsSection /> + </WalletAction> + ); } function amountToString(text: AmountLike) { - const aj = Amounts.jsonifyAmount(text) - const amount = Amounts.stringifyValue(aj, 2) - return `${amount} ${aj.currency}` + const aj = Amounts.jsonifyAmount(text); + const amount = Amounts.stringifyValue(aj, 2); + return `${amount} ${aj.currency}`; } |