diff options
author | Sebastian <sebasjm@gmail.com> | 2021-09-17 15:48:33 -0300 |
---|---|---|
committer | Sebastian <sebasjm@gmail.com> | 2021-09-17 15:49:07 -0300 |
commit | 315b167bee240e625beea731df6472a971b46cb2 (patch) | |
tree | 098557be6106622844ad5d59dce7e0c64bb22bcc | |
parent | 490620ad04a677fa220cbe77dc0bea29b6e80c12 (diff) |
issue #5860
-rw-r--r-- | packages/taler-util/src/taleruri.ts | 3 | ||||
-rw-r--r-- | packages/taler-util/src/walletTypes.ts | 6 | ||||
-rw-r--r-- | packages/taler-wallet-core/src/crypto/workers/cryptoApi.ts | 6 | ||||
-rw-r--r-- | packages/taler-wallet-core/src/crypto/workers/cryptoImplementation.ts | 11 | ||||
-rw-r--r-- | packages/taler-wallet-core/src/operations/pay.ts | 20 | ||||
-rw-r--r-- | packages/taler-wallet-webextension/package.json | 1 | ||||
-rw-r--r-- | packages/taler-wallet-webextension/src/components/QR.tsx | 37 | ||||
-rw-r--r-- | packages/taler-wallet-webextension/src/components/styled/index.tsx | 8 | ||||
-rw-r--r-- | packages/taler-wallet-webextension/src/cta/Pay.stories.tsx | 7 | ||||
-rw-r--r-- | packages/taler-wallet-webextension/src/cta/Pay.tsx | 136 | ||||
-rw-r--r-- | packages/taler-wallet-webextension/src/cta/Withdraw.tsx | 133 | ||||
-rw-r--r-- | pnpm-lock.yaml | 6 |
12 files changed, 233 insertions, 141 deletions
diff --git a/packages/taler-util/src/taleruri.ts b/packages/taler-util/src/taleruri.ts index 6c0dc7b86..09c70682a 100644 --- a/packages/taler-util/src/taleruri.ts +++ b/packages/taler-util/src/taleruri.ts @@ -22,6 +22,7 @@ export interface PayUriResult { orderId: string; sessionId: string; claimToken: string | undefined; + noncePriv: string | undefined; } export interface WithdrawUriResult { @@ -147,6 +148,7 @@ export function parsePayUri(s: string): PayUriResult | undefined { const c = pi?.rest.split("?"); const q = new URLSearchParams(c[1] ?? ""); const claimToken = q.get("c") ?? undefined; + const noncePriv = q.get("n") ?? undefined; const parts = c[0].split("/"); if (parts.length < 3) { return undefined; @@ -163,6 +165,7 @@ export function parsePayUri(s: string): PayUriResult | undefined { orderId, sessionId: sessionId, claimToken, + noncePriv, }; } diff --git a/packages/taler-util/src/walletTypes.ts b/packages/taler-util/src/walletTypes.ts index 79403ac69..2b35423bc 100644 --- a/packages/taler-util/src/walletTypes.ts +++ b/packages/taler-util/src/walletTypes.ts @@ -325,6 +325,7 @@ export const codecForPreparePayResultPaymentPossible = (): Codec<PreparePayResul .property("contractTerms", codecForContractTerms()) .property("proposalId", codecForString()) .property("contractTermsHash", codecForString()) + .property("noncePriv", codecForString()) .property( "status", codecForConstString(PreparePayResultType.PaymentPossible), @@ -336,6 +337,7 @@ export const codecForPreparePayResultInsufficientBalance = (): Codec<PreparePayR .property("amountRaw", codecForAmountString()) .property("contractTerms", codecForAny()) .property("proposalId", codecForString()) + .property("noncePriv", codecForString()) .property( "status", codecForConstString(PreparePayResultType.InsufficientBalance), @@ -354,6 +356,7 @@ export const codecForPreparePayResultAlreadyConfirmed = (): Codec<PreparePayResu .property("contractTerms", codecForAny()) .property("contractTermsHash", codecForString()) .property("proposalId", codecForString()) + .property("noncePriv", codecForString()) .build("PreparePayResultAlreadyConfirmed"); export const codecForPreparePayResult = (): Codec<PreparePayResult> => @@ -385,6 +388,7 @@ export interface PreparePayResultPaymentPossible { contractTermsHash: string; amountRaw: string; amountEffective: string; + noncePriv: string; } export interface PreparePayResultInsufficientBalance { @@ -392,6 +396,7 @@ export interface PreparePayResultInsufficientBalance { proposalId: string; contractTerms: ContractTerms; amountRaw: string; + noncePriv: string; } export interface PreparePayResultAlreadyConfirmed { @@ -402,6 +407,7 @@ export interface PreparePayResultAlreadyConfirmed { amountEffective: string; contractTermsHash: string; proposalId: string; + noncePriv: string; } export interface BankWithdrawDetails { diff --git a/packages/taler-wallet-core/src/crypto/workers/cryptoApi.ts b/packages/taler-wallet-core/src/crypto/workers/cryptoApi.ts index da92e83c6..6bace01a3 100644 --- a/packages/taler-wallet-core/src/crypto/workers/cryptoApi.ts +++ b/packages/taler-wallet-core/src/crypto/workers/cryptoApi.ts @@ -213,7 +213,7 @@ export class CryptoApi { ws.w = null; } } catch (e) { - logger.error(e); + logger.error(e as string); } if (ws.currentWorkItem !== null) { ws.currentWorkItem.reject(e); @@ -379,6 +379,10 @@ export class CryptoApi { return this.doRpc<{ priv: string; pub: string }>("createEddsaKeypair", 1); } + eddsaGetPublic(key: string): Promise<{ priv: string; pub: string }> { + return this.doRpc<{ priv: string; pub: string }>("eddsaGetPublic", 1, key); + } + rsaUnblind(sig: string, bk: string, pk: string): Promise<string> { return this.doRpc<string>("rsaUnblind", 4, sig, bk, pk); } diff --git a/packages/taler-wallet-core/src/crypto/workers/cryptoImplementation.ts b/packages/taler-wallet-core/src/crypto/workers/cryptoImplementation.ts index e1580a7d1..7112964db 100644 --- a/packages/taler-wallet-core/src/crypto/workers/cryptoImplementation.ts +++ b/packages/taler-wallet-core/src/crypto/workers/cryptoImplementation.ts @@ -62,6 +62,7 @@ import { setupRefreshTransferPub, setupTipPlanchet, setupWithdrawPlanchet, + eddsaGetPublic, } from "../talerCrypto.js"; import { randomBytes } from "../primitives/nacl-fast.js"; import { kdf } from "../primitives/kdf.js"; @@ -141,7 +142,7 @@ function timestampRoundedToBuffer(ts: Timestamp): Uint8Array { class SignaturePurposeBuilder { private chunks: Uint8Array[] = []; - constructor(private purposeNum: number) {} + constructor(private purposeNum: number) { } put(bytes: Uint8Array): SignaturePurposeBuilder { this.chunks.push(Uint8Array.from(bytes)); @@ -170,7 +171,6 @@ class SignaturePurposeBuilder { function buildSigPS(purposeNum: number): SignaturePurposeBuilder { return new SignaturePurposeBuilder(purposeNum); } - export class CryptoImplementation { static enableTracing = false; @@ -361,6 +361,13 @@ export class CryptoImplementation { }; } + eddsaGetPublic(key: string): { priv: string; pub: string } { + return { + priv: key, + pub: encodeCrock(eddsaGetPublic(decodeCrock(key))) + } + } + /** * Unblind a blindly signed value. */ diff --git a/packages/taler-wallet-core/src/operations/pay.ts b/packages/taler-wallet-core/src/operations/pay.ts index 9a7b0d069..970aa46ff 100644 --- a/packages/taler-wallet-core/src/operations/pay.ts +++ b/packages/taler-wallet-core/src/operations/pay.ts @@ -875,7 +875,9 @@ async function startDownloadProposal( orderId: string, sessionId: string | undefined, claimToken: string | undefined, + noncePriv: string | undefined, ): Promise<string> { + const oldProposal = await ws.db .mktx((x) => ({ proposals: x.proposals })) .runReadOnly(async (tx) => { @@ -884,12 +886,20 @@ async function startDownloadProposal( orderId, ]); }); - if (oldProposal) { + + /** + * If we have already claimed this proposal with the same sessionId + * nonce and claim token, reuse it. + */ + if (oldProposal && + oldProposal.downloadSessionId === sessionId && + oldProposal.noncePriv === noncePriv && + oldProposal.claimToken === claimToken) { await processDownloadProposal(ws, oldProposal.proposalId); return oldProposal.proposalId; } - const { priv, pub } = await ws.cryptoApi.createEddsaKeypair(); + const { priv, pub } = await (noncePriv ? ws.cryptoApi.eddsaGetPublic(noncePriv) : ws.cryptoApi.createEddsaKeypair()); const proposalId = encodeCrock(getRandomBytes(32)); const proposalRecord: ProposalRecord = { @@ -1405,6 +1415,7 @@ export async function checkPaymentByProposalId( status: PreparePayResultType.InsufficientBalance, contractTerms: d.contractTermsRaw, proposalId: proposal.proposalId, + noncePriv: proposal.noncePriv, amountRaw: Amounts.stringify(d.contractData.amount), }; } @@ -1417,6 +1428,7 @@ export async function checkPaymentByProposalId( status: PreparePayResultType.PaymentPossible, contractTerms: d.contractTermsRaw, proposalId: proposal.proposalId, + noncePriv: proposal.noncePriv, amountEffective: Amounts.stringify(totalCost), amountRaw: Amounts.stringify(res.paymentAmount), contractTermsHash: d.contractData.contractTermsHash, @@ -1453,6 +1465,7 @@ export async function checkPaymentByProposalId( amountRaw: Amounts.stringify(purchase.download.contractData.amount), amountEffective: Amounts.stringify(purchase.totalPayCost), proposalId, + noncePriv: proposal.noncePriv, }; } else if (!purchase.timestampFirstSuccessfulPay) { return { @@ -1463,6 +1476,7 @@ export async function checkPaymentByProposalId( amountRaw: Amounts.stringify(purchase.download.contractData.amount), amountEffective: Amounts.stringify(purchase.totalPayCost), proposalId, + noncePriv: proposal.noncePriv, }; } else { const paid = !purchase.paymentSubmitPending; @@ -1475,6 +1489,7 @@ export async function checkPaymentByProposalId( amountEffective: Amounts.stringify(purchase.totalPayCost), ...(paid ? { nextUrl: purchase.download.contractData.orderId } : {}), proposalId, + noncePriv: proposal.noncePriv, }; } } @@ -1507,6 +1522,7 @@ export async function preparePayForUri( uriResult.orderId, uriResult.sessionId, uriResult.claimToken, + uriResult.noncePriv, ); return checkPaymentByProposalId(ws, proposalId, uriResult.sessionId); diff --git a/packages/taler-wallet-webextension/package.json b/packages/taler-wallet-webextension/package.json index 028a5c660..4023e4ebd 100644 --- a/packages/taler-wallet-webextension/package.json +++ b/packages/taler-wallet-webextension/package.json @@ -22,6 +22,7 @@ "history": "4.10.1", "preact": "^10.5.13", "preact-router": "^3.2.1", + "qrcode-generator": "^1.4.4", "tslib": "^2.1.0" }, "devDependencies": { diff --git a/packages/taler-wallet-webextension/src/components/QR.tsx b/packages/taler-wallet-webextension/src/components/QR.tsx new file mode 100644 index 000000000..8e3f69295 --- /dev/null +++ b/packages/taler-wallet-webextension/src/components/QR.tsx @@ -0,0 +1,37 @@ +/* + This file is part of GNU Taler + (C) 2021 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + + import { h, VNode } from "preact"; + import { useEffect, useRef } from "preact/hooks"; + import qrcode from "qrcode-generator"; + + export function QR({ text }: { text: string; }):VNode { + const divRef = useRef<HTMLDivElement>(null); + useEffect(() => { + if (!divRef.current) return + const qr = qrcode(0, 'L'); + qr.addData(text); + qr.make(); + divRef.current.innerHTML = qr.createSvgTag({ + scalable: true, + }); + }); + + return <div style={{ width: '100%', display: 'flex', flexDirection: 'column', alignItems: 'center' }}> + <div style={{ width: '50%', minWidth: 200, maxWidth: 300 }} ref={divRef} /> + </div>; + } +
\ No newline at end of file diff --git a/packages/taler-wallet-webextension/src/components/styled/index.tsx b/packages/taler-wallet-webextension/src/components/styled/index.tsx index f7945569e..a46f38ee9 100644 --- a/packages/taler-wallet-webextension/src/components/styled/index.tsx +++ b/packages/taler-wallet-webextension/src/components/styled/index.tsx @@ -29,10 +29,11 @@ export const PaymentStatus = styled.div<{ color: string }>` export const WalletAction = styled.section` display: flex; + text-align: center; flex-direction: column; justify-content: space-between; align-items: center; - max-width: 50%; + /* max-width: 50%; */ margin: auto; height: 100%; @@ -42,6 +43,10 @@ export const WalletAction = styled.section` } section { margin-bottom: 2em; + & button { + margin-right: 8px; + margin-left: 8px; + } } ` export const WalletActionOld = styled.section` @@ -628,6 +633,7 @@ export const TermsOfService = styled.div` display: flex; flex-direction: column; text-align: left; + max-width: 500px; & > header { text-align: center; diff --git a/packages/taler-wallet-webextension/src/cta/Pay.stories.tsx b/packages/taler-wallet-webextension/src/cta/Pay.stories.tsx index 38e3d0f35..9a997687f 100644 --- a/packages/taler-wallet-webextension/src/cta/Pay.stories.tsx +++ b/packages/taler-wallet-webextension/src/cta/Pay.stories.tsx @@ -33,6 +33,7 @@ export default { export const InsufficientBalance = createExample(TestedComponent, { payStatus: { status: PreparePayResultType.InsufficientBalance, + noncePriv: '', proposalId: "proposal1234", contractTerms: { merchant: { @@ -45,15 +46,19 @@ export const InsufficientBalance = createExample(TestedComponent, { }); export const PaymentPossible = createExample(TestedComponent, { + uri: 'taler://pay/merchant-backend.taler/2021.242-01G2X4275RBWG/?c=66BE594PDZR24744J6EQK52XM0', payStatus: { status: PreparePayResultType.PaymentPossible, amountEffective: 'USD:10', 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' @@ -65,6 +70,7 @@ export const AlreadyConfirmedWithFullfilment = createExample(TestedComponent, { status: PreparePayResultType.AlreadyConfirmed, amountEffective: 'USD:10', amountRaw: 'USD:10', + noncePriv: '', contractTerms: { merchant: { name: 'someone' @@ -82,6 +88,7 @@ export const AlreadyConfirmedWithoutFullfilment = createExample(TestedComponent, payStatus: { status: PreparePayResultType.AlreadyConfirmed, amountEffective: 'USD:10', + noncePriv: '', amountRaw: 'USD:10', contractTerms: { merchant: { diff --git a/packages/taler-wallet-webextension/src/cta/Pay.tsx b/packages/taler-wallet-webextension/src/cta/Pay.tsx index 758bc4b54..e85cd60a1 100644 --- a/packages/taler-wallet-webextension/src/cta/Pay.tsx +++ b/packages/taler-wallet-webextension/src/cta/Pay.tsx @@ -29,7 +29,7 @@ import * as wxApi from "../wxApi"; import { useState, useEffect } from "preact/hooks"; -import { ConfirmPayResultDone, getJsonI18n, i18n } from "@gnu-taler/taler-util"; +import { AmountLike, ConfirmPayResultDone, getJsonI18n, i18n } from "@gnu-taler/taler-util"; import { PreparePayResult, ConfirmPayResult, @@ -39,7 +39,11 @@ import { ContractTerms, ConfirmPayResultType, } from "@gnu-taler/taler-util"; -import { JSX, VNode, h } from "preact"; +import { JSX, VNode, h, Fragment } from "preact"; +import { ButtonSuccess, LinkSuccess, WalletAction } from "../components/styled"; +import { LogoHeader } from "../components/LogoHeader"; +import { Part } from "../components/Part"; +import { QR } from "../components/QR"; interface Props { talerPayUri?: string @@ -143,17 +147,17 @@ export function PayPage({ talerPayUri }: Props): JSX.Element { } - return <PaymentRequestView payStatus={payStatus} onClick={onClick} payErrMsg={payErrMsg} />; + return <PaymentRequestView uri={talerPayUri} payStatus={payStatus} onClick={onClick} payErrMsg={payErrMsg} />; } export interface PaymentRequestViewProps { payStatus: PreparePayResult; onClick: () => void; payErrMsg?: string; - + uri: string; } -export function PaymentRequestView({ payStatus, onClick, payErrMsg }: PaymentRequestViewProps) { - let totalFees: AmountJson | undefined = undefined; +export function PaymentRequestView({ uri, payStatus, onClick, payErrMsg }: PaymentRequestViewProps) { + let totalFees: AmountJson = Amounts.getZero(payStatus.amountRaw); let insufficientBalance = false; const [loading, setLoading] = useState(false); const contractTerms: ContractTerms = payStatus.contractTerms; @@ -174,6 +178,7 @@ export function PaymentRequestView({ payStatus, onClick, payErrMsg }: PaymentReq if (payStatus.status == PreparePayResultType.InsufficientBalance) { insufficientBalance = true; + return <div>no te alcanza</div> } if (payStatus.status === PreparePayResultType.PaymentPossible) { @@ -191,65 +196,62 @@ export function PaymentRequestView({ payStatus, onClick, payErrMsg }: PaymentReq merchantName = <strong>(pub: {contractTerms.merchant_pub})</strong>; } - const amount = ( - <strong>{renderAmount(Amounts.parseOrThrow(contractTerms.amount))}</strong> - ); - - return <section class="main"> - <h1>GNU Taler Wallet</h1> - <article class="fade"> - <div> - <p> - <i18n.Translate> - The merchant <span>{merchantName}</span> offers you to purchase: - </i18n.Translate> - <div style={{ textAlign: "center" }}> - <strong>{contractTerms.summary}</strong> - </div> - {totalFees ? ( - <i18n.Translate> - The total price is <span>{amount} </span> - (plus <span>{renderAmount(totalFees)}</span> fees). - </i18n.Translate> - ) : ( - <i18n.Translate> - The total price is <span>{amount}</span>. - </i18n.Translate> - )} - </p> - - {insufficientBalance ? ( - <div> - <p style={{ color: "red", fontWeight: "bold" }}> - Unable to pay: Your balance is insufficient. - </p> - </div> - ) : null} - - {payErrMsg ? ( - <div> - <p>Payment failed: {payErrMsg}</p> - <button - class="pure-button button-success" - onClick={onClick} - > - {i18n.str`Retry`} - </button> - </div> - ) : ( - <div> - <ProgressButton - isLoading={loading} - disabled={insufficientBalance} - onClick={onClick} - > - {i18n.str`Confirm payment`} - </ProgressButton> - </div> - )} - </div> - </article> - </section> - + const [showQR, setShowQR] = useState<boolean>(false) + const privateUri = `${uri}&n=${payStatus.noncePriv}` + return <WalletAction> + <LogoHeader /> + <h2> + {i18n.str`Digital cash payment`} + </h2> + <section> + <Part big title="Total paid" 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' />} + <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} /> + <a href={privateUri}>or click here to pay with a installed wallet</a> + </section>} + <section> + {payErrMsg ? ( + <div> + <p>Payment failed: {payErrMsg}</p> + <button + class="pure-button button-success" + onClick={onClick} + > + {i18n.str`Retry`} + </button> + </div> + ) : ( + <Fragment> + + <LinkSuccess + upperCased + // disabled={!details.exchangeInfo.baseUrl} + onClick={() => setShowQR(qr => !qr)} + > + {!showQR ? i18n.str`Complete with mobile wallet` : i18n.str`Hide QR`} + </LinkSuccess> + <ButtonSuccess + upperCased + // disabled={!details.exchangeInfo.baseUrl} + // onClick={() => onReview(true)} + > + {i18n.str`Confirm payment`} + </ButtonSuccess> + </Fragment> + )} + + </section> + </WalletAction> +} -}
\ No newline at end of file +function amountToString(text: AmountLike) { + const aj = Amounts.jsonifyAmount(text) + const amount = Amounts.stringifyValue(aj) + return `${amount} ${aj.currency}` +} diff --git a/packages/taler-wallet-webextension/src/cta/Withdraw.tsx b/packages/taler-wallet-webextension/src/cta/Withdraw.tsx index ac25bcd15..304313a9e 100644 --- a/packages/taler-wallet-webextension/src/cta/Withdraw.tsx +++ b/packages/taler-wallet-webextension/src/cta/Withdraw.tsx @@ -80,20 +80,18 @@ export function View({ details, amount, onWithdraw, terms, reviewing, onReview, const needsReview = terms.status === 'changed' || terms.status === 'new' return ( - <WalletAction style={{ textAlign: 'center' }}> + <WalletAction> <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> + <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 /> </section> {!reviewing && <section> @@ -132,63 +130,50 @@ export function View({ details, amount, onWithdraw, terms, reviewing, onReview, } {(reviewing || accepted) && <section> - <div> - <CheckboxOutlined - name="terms" - enabled={accepted} - label={i18n.str`I accept the exchange terms of service`} - onToggle={() => { - onAccept(!accepted) - onReview(false) - }} - /> - </div> + <CheckboxOutlined + name="terms" + enabled={accepted} + label={i18n.str`I accept the exchange terms of service`} + onToggle={() => { + onAccept(!accepted) + onReview(false) + }} + /> </section> } <section> {terms.status === 'new' && !accepted && - <div> - <ButtonSuccess - upperCased - disabled={!details.exchangeInfo.baseUrl} - onClick={() => onReview(true)} - > - {i18n.str`Review exchange terms of service`} - </ButtonSuccess> - </div> + <ButtonSuccess + upperCased + disabled={!details.exchangeInfo.baseUrl} + onClick={() => onReview(true)} + > + {i18n.str`Review exchange terms of service`} + </ButtonSuccess> } {terms.status === 'changed' && !accepted && - <div> - <ButtonWarning - upperCased - disabled={!details.exchangeInfo.baseUrl} - onClick={() => onReview(true)} - > - {i18n.str`Review new version of terms of service`} - </ButtonWarning> - </div> + <ButtonWarning + upperCased + disabled={!details.exchangeInfo.baseUrl} + onClick={() => onReview(true)} + > + {i18n.str`Review new version of terms of service`} + </ButtonWarning> } {(terms.status === 'accepted' || (needsReview && accepted)) && - <div> - <ButtonSuccess - upperCased - disabled={!details.exchangeInfo.baseUrl || confirmed} - onClick={onWithdraw} - > - {i18n.str`Confirm withdrawal`} - </ButtonSuccess> - </div> + <ButtonSuccess + upperCased + disabled={!details.exchangeInfo.baseUrl || confirmed} + onClick={onWithdraw} + > + {i18n.str`Confirm withdrawal`} + </ButtonSuccess> } {terms.status === 'notfound' && - <div> - <ButtonDestructive - upperCased - disabled={true} - > - {i18n.str`Exchange doesn't have terms of service`} - </ButtonDestructive> - </div> + <ButtonDestructive upperCased disabled> + {i18n.str`Exchange doesn't have terms of service`} + </ButtonDestructive> } </section> </WalletAction> @@ -231,12 +216,16 @@ export function WithdrawPage({ talerWithdrawUri, ...rest }: Props): JSX.Element useEffect(() => { async function fetchData() { if (!uriInfo || !uriInfo.defaultExchangeBaseUrl) return - const res = await getExchangeWithdrawalInfo({ - exchangeBaseUrl: uriInfo.defaultExchangeBaseUrl, - amount: Amounts.parseOrThrow(uriInfo.amount), - tosAcceptedFormat: ['text/json', 'text/xml', 'text/pdf'] - }) - setDetails(res) + try { + const res = await getExchangeWithdrawalInfo({ + exchangeBaseUrl: uriInfo.defaultExchangeBaseUrl, + amount: Amounts.parseOrThrow(uriInfo.amount), + tosAcceptedFormat: ['text/json', 'text/xml', 'text/pdf'] + }) + setDetails(res) + } catch (e) { + setError(true) + } } fetchData() }, [uriInfo]) @@ -249,8 +238,12 @@ export function WithdrawPage({ talerWithdrawUri, ...rest }: Props): JSX.Element if (!details) { throw Error("can't accept, no exchange selected"); } - await setExchangeTosAccepted(details.exchangeDetails.exchangeBaseUrl, details.tosRequested?.tosEtag) - setAccepted(true) + try { + await setExchangeTosAccepted(details.exchangeDetails.exchangeBaseUrl, details.tosRequested?.tosEtag) + setAccepted(true) + } catch (e) { + setError(true) + } } const onWithdraw = async (): Promise<void> => { @@ -259,10 +252,14 @@ export function WithdrawPage({ talerWithdrawUri, ...rest }: Props): JSX.Element } setConfirmed(true) 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; + try { + const res = await acceptWithdrawal(talerWithdrawUri, details.exchangeInfo.baseUrl); + console.log("accept withdrawal response", res); + if (res.confirmTransferUrl) { + document.location.href = res.confirmTransferUrl; + } + } catch (e) { + setConfirmed(false) } }; @@ -288,7 +285,7 @@ export function WithdrawPage({ talerWithdrawUri, ...rest }: Props): JSX.Element } catch (e) { console.log(e) debugger; - } + } } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2eb62d7f1..bea05decd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -248,6 +248,7 @@ importers: preact-cli: ^3.0.5 preact-render-to-string: ^5.1.19 preact-router: ^3.2.1 + qrcode-generator: ^1.4.4 rimraf: ^3.0.2 rollup: ^2.37.1 rollup-plugin-css-only: ^3.1.0 @@ -264,6 +265,7 @@ importers: history: 4.10.1 preact: 10.5.14 preact-router: 3.2.1_preact@10.5.14 + qrcode-generator: 1.4.4 tslib: 2.3.1 devDependencies: '@babel/core': 7.13.16 @@ -16698,6 +16700,10 @@ packages: engines: {node: '>=0.6.0', teleport: '>=0.2.0'} dev: true + /qrcode-generator/1.4.4: + resolution: {integrity: sha512-HM7yY8O2ilqhmULxGMpcHSF1EhJJ9yBj8gvDEuZ6M+KGJ0YY2hKpnXvRD+hZPLrDVck3ExIGhmPtSdcjC+guuw==} + dev: false + /qs/6.10.1: resolution: {integrity: sha512-M528Hph6wsSVOBiYUnGf+K/7w0hNshs/duGsNXPUCLH5XAqjEtiPGwNONLV0tBH8NoGb0mvD5JubnUTrujKDTg==} engines: {node: '>=0.6'} |