diff options
author | Sebastian <sebasjm@gmail.com> | 2021-10-11 15:59:55 -0300 |
---|---|---|
committer | Sebastian <sebasjm@gmail.com> | 2021-10-11 15:59:55 -0300 |
commit | be8e3f4b1d090a536967f132a7fd4742bbcd5343 (patch) | |
tree | d184ba3cf40510009e4121c6daa5653e0be475b0 /packages/taler-wallet-webextension/src/cta | |
parent | 78fb5f79a8690ee490c96b271dadd37f4c9442d6 (diff) | |
download | wallet-core-be8e3f4b1d090a536967f132a7fd4742bbcd5343.tar.xz |
fixing withdrawal process
Diffstat (limited to 'packages/taler-wallet-webextension/src/cta')
3 files changed, 247 insertions, 103 deletions
diff --git a/packages/taler-wallet-webextension/src/cta/Pay.tsx b/packages/taler-wallet-webextension/src/cta/Pay.tsx index 8e02cf6bb..675b14ff9 100644 --- a/packages/taler-wallet-webextension/src/cta/Pay.tsx +++ b/packages/taler-wallet-webextension/src/cta/Pay.tsx @@ -88,7 +88,7 @@ export function PayPage({ talerPayUri }: Props): JSX.Element { const [payErrMsg, setPayErrMsg] = useState<string | undefined>(undefined); const balance = useBalances() - const balanceWithoutError = balance?.error ? [] : (balance?.response.balances || []) + 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 @@ -143,17 +143,21 @@ export function PayPage({ talerPayUri }: Props): JSX.Element { } - return <PaymentRequestView uri={talerPayUri} payStatus={payStatus} onClick={onClick} payErrMsg={payErrMsg} balance={foundAmount} />; + return <PaymentRequestView uri={talerPayUri} + payStatus={payStatus} payResult={payResult} + onClick={onClick} payErrMsg={payErrMsg} + balance={foundAmount} />; } export interface PaymentRequestViewProps { payStatus: PreparePayResult; + payResult?: ConfirmPayResult; onClick: () => void; payErrMsg?: string; uri: string; balance: AmountJson | undefined; } -export function PaymentRequestView({ uri, payStatus, 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; @@ -195,6 +199,16 @@ export function PaymentRequestView({ uri, payStatus, onClick, payErrMsg, balance } function ButtonsSection() { + if (payResult) { + if (payResult.type === ConfirmPayResultType.Pending) { + return <section> + <div> + <p>Processing...</p> + </div> + </section> + } + return null + } if (payErrMsg) { return <section> <div> @@ -208,7 +222,7 @@ export function PaymentRequestView({ uri, payStatus, onClick, payErrMsg, balance if (payStatus.status === PreparePayResultType.PaymentPossible) { return <Fragment> <section> - <ButtonSuccess upperCased> + <ButtonSuccess upperCased onClick={onClick}> {i18n.str`Pay`} {amountToString(payStatus.amountEffective)} </ButtonSuccess> </section> @@ -252,6 +266,15 @@ export function PaymentRequestView({ uri, payStatus, onClick, payErrMsg, balance {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' /> diff --git a/packages/taler-wallet-webextension/src/cta/Withdraw.stories.tsx b/packages/taler-wallet-webextension/src/cta/Withdraw.stories.tsx index 94fdea8fb..69073f500 100644 --- a/packages/taler-wallet-webextension/src/cta/Withdraw.stories.tsx +++ b/packages/taler-wallet-webextension/src/cta/Withdraw.stories.tsx @@ -31,6 +31,7 @@ export default { title: 'cta/withdraw', component: TestedComponent, argTypes: { + onSwitchExchange: { action: 'onRetry' }, }, }; @@ -381,6 +382,15 @@ const termsXml = `<?xml version="1.0" encoding="utf-8"?> `; export const WithdrawNewTermsXML = createExample(TestedComponent, { + knownExchanges: [{ + currency: 'USD', + exchangeBaseUrl: 'exchange.demo.taler.net', + paytoUris: ['asd'], + },{ + currency: 'USD', + exchangeBaseUrl: 'exchange.test.taler.net', + paytoUris: ['asd'], + }], details: { exchangeInfo: { baseUrl: 'exchange.demo.taler.net' @@ -391,9 +401,15 @@ export const WithdrawNewTermsXML = createExample(TestedComponent, { value: 0 }, } as ExchangeWithdrawDetails, - amount: 'USD:2', + amount: { + currency: 'USD', + value: 2, + fraction: 10000000 + }, + + onSwitchExchange: async () => { }, terms: { - value : { + value: { type: 'xml', document: new DOMParser().parseFromString(termsXml, "text/xml"), }, @@ -402,6 +418,15 @@ export const WithdrawNewTermsXML = createExample(TestedComponent, { }) export const WithdrawNewTermsReviewingXML = createExample(TestedComponent, { + knownExchanges: [{ + currency: 'USD', + exchangeBaseUrl: 'exchange.demo.taler.net', + paytoUris: ['asd'], + },{ + currency: 'USD', + exchangeBaseUrl: 'exchange.test.taler.net', + paytoUris: ['asd'], + }], details: { exchangeInfo: { baseUrl: 'exchange.demo.taler.net' @@ -412,9 +437,15 @@ export const WithdrawNewTermsReviewingXML = createExample(TestedComponent, { value: 0 }, } as ExchangeWithdrawDetails, - amount: 'USD:2', + amount: { + currency: 'USD', + value: 2, + fraction: 10000000 + }, + + onSwitchExchange: async () => { }, terms: { - value : { + value: { type: 'xml', document: new DOMParser().parseFromString(termsXml, "text/xml"), }, @@ -424,6 +455,15 @@ export const WithdrawNewTermsReviewingXML = createExample(TestedComponent, { }) export const WithdrawNewTermsAcceptedXML = createExample(TestedComponent, { + knownExchanges: [{ + currency: 'USD', + exchangeBaseUrl: 'exchange.demo.taler.net', + paytoUris: ['asd'], + },{ + currency: 'USD', + exchangeBaseUrl: 'exchange.test.taler.net', + paytoUris: ['asd'], + }], details: { exchangeInfo: { baseUrl: 'exchange.demo.taler.net' @@ -434,9 +474,14 @@ export const WithdrawNewTermsAcceptedXML = createExample(TestedComponent, { value: 0 }, } as ExchangeWithdrawDetails, - amount: 'USD:2', + amount: { + currency: 'USD', + value: 2, + fraction: 10000000 + }, + onSwitchExchange: async () => { }, terms: { - value : { + value: { type: 'xml', document: new DOMParser().parseFromString(termsXml, "text/xml"), }, @@ -446,6 +491,15 @@ export const WithdrawNewTermsAcceptedXML = createExample(TestedComponent, { }) export const WithdrawNewTermsShowAfterAcceptedXML = createExample(TestedComponent, { + knownExchanges: [{ + currency: 'USD', + exchangeBaseUrl: 'exchange.demo.taler.net', + paytoUris: ['asd'], + },{ + currency: 'USD', + exchangeBaseUrl: 'exchange.test.taler.net', + paytoUris: ['asd'], + }], details: { exchangeInfo: { baseUrl: 'exchange.demo.taler.net' @@ -456,9 +510,15 @@ export const WithdrawNewTermsShowAfterAcceptedXML = createExample(TestedComponen value: 0 }, } as ExchangeWithdrawDetails, - amount: 'USD:2', + amount: { + currency: 'USD', + value: 2, + fraction: 10000000 + }, + + onSwitchExchange: async () => { }, terms: { - value : { + value: { type: 'xml', document: new DOMParser().parseFromString(termsXml, "text/xml"), }, @@ -469,6 +529,15 @@ export const WithdrawNewTermsShowAfterAcceptedXML = createExample(TestedComponen }) export const WithdrawChangedTermsXML = createExample(TestedComponent, { + knownExchanges: [{ + currency: 'USD', + exchangeBaseUrl: 'exchange.demo.taler.net', + paytoUris: ['asd'], + },{ + currency: 'USD', + exchangeBaseUrl: 'exchange.test.taler.net', + paytoUris: ['asd'], + }], details: { exchangeInfo: { baseUrl: 'exchange.demo.taler.net' @@ -479,9 +548,15 @@ export const WithdrawChangedTermsXML = createExample(TestedComponent, { value: 0 }, } as ExchangeWithdrawDetails, - amount: 'USD:2', + amount: { + currency: 'USD', + value: 2, + fraction: 10000000 + }, + + onSwitchExchange: async () => { }, terms: { - value : { + value: { type: 'xml', document: new DOMParser().parseFromString(termsXml, "text/xml"), }, @@ -490,6 +565,15 @@ export const WithdrawChangedTermsXML = createExample(TestedComponent, { }) export const WithdrawNotFoundTermsXML = createExample(TestedComponent, { + knownExchanges: [{ + currency: 'USD', + exchangeBaseUrl: 'exchange.demo.taler.net', + paytoUris: ['asd'], + },{ + currency: 'USD', + exchangeBaseUrl: 'exchange.test.taler.net', + paytoUris: ['asd'], + }], details: { exchangeInfo: { baseUrl: 'exchange.demo.taler.net' @@ -500,13 +584,28 @@ export const WithdrawNotFoundTermsXML = createExample(TestedComponent, { value: 0 }, } as ExchangeWithdrawDetails, - amount: 'USD:2', + amount: { + currency: 'USD', + value: 2, + fraction: 10000000 + }, + + onSwitchExchange: async () => { }, terms: { status: 'notfound' }, }) export const WithdrawAcceptedTermsXML = createExample(TestedComponent, { + knownExchanges: [{ + currency: 'USD', + exchangeBaseUrl: 'exchange.demo.taler.net', + paytoUris: ['asd'], + },{ + currency: 'USD', + exchangeBaseUrl: 'exchange.test.taler.net', + paytoUris: ['asd'], + }], details: { exchangeInfo: { baseUrl: 'exchange.demo.taler.net' @@ -517,7 +616,13 @@ export const WithdrawAcceptedTermsXML = createExample(TestedComponent, { value: 0 }, } as ExchangeWithdrawDetails, - amount: 'USD:2', + amount: { + currency: 'USD', + value: 2, + fraction: 10000000 + }, + + onSwitchExchange: async () => { }, terms: { status: 'accepted' }, @@ -525,6 +630,15 @@ export const WithdrawAcceptedTermsXML = createExample(TestedComponent, { export const WithdrawAcceptedTermsWithoutFee = createExample(TestedComponent, { + knownExchanges: [{ + currency: 'USD', + exchangeBaseUrl: 'exchange.demo.taler.net', + paytoUris: ['asd'], + },{ + currency: 'USD', + exchangeBaseUrl: 'exchange.test.taler.net', + paytoUris: ['asd'], + }], details: { exchangeInfo: { baseUrl: 'exchange.demo.taler.net' @@ -535,9 +649,15 @@ export const WithdrawAcceptedTermsWithoutFee = createExample(TestedComponent, { value: 0 }, } as ExchangeWithdrawDetails, - amount: 'USD:2', + amount: { + currency: 'USD', + value: 2, + fraction: 10000000 + }, + + onSwitchExchange: async () => { }, terms: { - value : { + value: { type: 'xml', document: new DOMParser().parseFromString(termsXml, "text/xml"), }, diff --git a/packages/taler-wallet-webextension/src/cta/Withdraw.tsx b/packages/taler-wallet-webextension/src/cta/Withdraw.tsx index 46451e72c..52295f1af 100644 --- a/packages/taler-wallet-webextension/src/cta/Withdraw.tsx +++ b/packages/taler-wallet-webextension/src/cta/Withdraw.tsx @@ -21,18 +21,21 @@ * @author Florian Dold */ -import { AmountLike, Amounts, i18n, WithdrawUriInfoResponse } from '@gnu-taler/taler-util'; +import { AmountJson, Amounts, ExchangeListItem, 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 { useState } from "preact/hooks"; +import { Fragment } from 'preact/jsx-runtime'; import { CheckboxOutlined } from '../components/CheckboxOutlined'; import { ExchangeXmlTos } from '../components/ExchangeToS'; import { LogoHeader } from '../components/LogoHeader'; import { Part } from '../components/Part'; -import { ButtonDestructive, ButtonSuccess, ButtonWarning, LinkSuccess, LinkWarning, TermsOfService, WalletAction } from '../components/styled'; +import { SelectList } from '../components/SelectList'; +import { ButtonSuccess, ButtonWarning, LinkSuccess, LinkWarning, TermsOfService, WalletAction } from '../components/styled'; +import { useAsyncAsHook } from '../hooks/useAsyncAsHook'; import { - acceptWithdrawal, getExchangeWithdrawalInfo, getWithdrawalDetailsForUri, onUpdateNotification, setExchangeTosAccepted + acceptWithdrawal, getExchangeWithdrawalInfo, getWithdrawalDetailsForUri, setExchangeTosAccepted, listExchanges } from "../wxApi"; -import { h } from 'preact'; +import { wxMain } from '../wxBackend.js'; interface Props { talerWithdrawUri?: string; @@ -40,7 +43,8 @@ interface Props { export interface ViewProps { details: ExchangeWithdrawDetails; - amount: string; + amount: AmountJson; + onSwitchExchange: (ex: string) => void; onWithdraw: () => Promise<void>; onReview: (b: boolean) => void; onAccept: (b: boolean) => void; @@ -50,7 +54,8 @@ export interface ViewProps { terms: { value?: TermsDocument; status: TermsStatus; - } + }, + knownExchanges: ExchangeListItem[] }; @@ -68,15 +73,18 @@ interface TermsDocumentHtml { href: string, } -function amountToString(text: AmountLike) { +function amountToString(text: AmountJson) { const aj = Amounts.jsonifyAmount(text) const amount = Amounts.stringifyValue(aj) return `${amount} ${aj.currency}` } -export function View({ details, amount, onWithdraw, terms, reviewing, onReview, onAccept, accepted, confirmed }: ViewProps) { +export function View({ details, knownExchanges, amount, onWithdraw, onSwitchExchange, terms, reviewing, onReview, onAccept, accepted, confirmed }: ViewProps) { const needsReview = terms.status === 'changed' || terms.status === 'new' + const [switchingExchange, setSwitchingExchange] = useState<string | undefined>(undefined) + const exchanges = knownExchanges.reduce((prev, ex) => ({ ...prev, [ex.exchangeBaseUrl]: ex.exchangeBaseUrl }), {}) + return ( <WalletAction> <LogoHeader /> @@ -84,7 +92,7 @@ export function View({ details, amount, onWithdraw, terms, reviewing, onReview, {i18n.str`Digital cash withdrawal`} </h2> <section> - <Part title="Total to withdraw" text={amountToString(Amounts.sub(Amounts.parseOrThrow(amount), details.withdrawFee).amount)} kind='positive' /> + <Part title="Total to withdraw" text={amountToString(Amounts.sub(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' /> @@ -93,11 +101,21 @@ export function View({ details, amount, onWithdraw, terms, reviewing, onReview, </section> {!reviewing && <section> - <LinkSuccess - upperCased - > - {i18n.str`Edit exchange`} - </LinkSuccess> + {switchingExchange !== undefined ? <Fragment> + <div> + <SelectList label="Known exchanges" list={exchanges} name="" onChange={onSwitchExchange} /> + </div> + <p> + This is the list of known exchanges + </p> + <LinkSuccess upperCased onClick={() => onSwitchExchange(switchingExchange)}> + {i18n.str`Confirm exchange selection`} + </LinkSuccess> + </Fragment> + : <LinkSuccess upperCased onClick={() => setSwitchingExchange("")}> + {i18n.str`Switch exchange`} + </LinkSuccess>} + </section> } {!reviewing && accepted && @@ -140,6 +158,9 @@ export function View({ details, amount, onWithdraw, terms, reviewing, onReview, </section> } + {/** + * Main action section + */} <section> {terms.status === 'new' && !accepted && !reviewing && <ButtonSuccess @@ -178,80 +199,55 @@ export function View({ details, amount, onWithdraw, terms, reviewing, onReview, ) } -export function WithdrawPage({ talerWithdrawUri, ...rest }: Props): JSX.Element { - 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); +export function WithdrawPageWithParsedURI({ uri, uriInfo }: { uri: string, uriInfo: WithdrawUriInfoResponse }) { + const [customExchange, setCustomExchange] = useState<string | undefined>(undefined) + const [errorAccepting, setErrorAccepting] = useState<string | undefined>(undefined) + const [reviewing, setReviewing] = useState<boolean>(false) const [accepted, setAccepted] = useState<boolean>(false) const [confirmed, setConfirmed] = useState<boolean>(false) - useEffect(() => { - return onUpdateNotification(() => { - console.log('updating...') - setUpdateCounter(updateCounter + 1); - }); - }, []); - - useEffect(() => { - console.log('on effect yes', talerWithdrawUri) - if (!talerWithdrawUri) return - const fetchData = async (): Promise<void> => { - try { - const res = await getWithdrawalDetailsForUri({ talerWithdrawUri }); - setUriInfo(res); - } catch (e) { - console.error('error', JSON.stringify(e, undefined, 2)) - setError(true) - } - }; - fetchData(); - }, [selecting, talerWithdrawUri, updateCounter]); + const knownExchangesHook = useAsyncAsHook(() => listExchanges()) - useEffect(() => { - async function fetchData() { - if (!uriInfo || !uriInfo.defaultExchangeBaseUrl) return - 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]) + const knownExchanges = !knownExchangesHook || knownExchangesHook.hasError ? [] : knownExchangesHook.response.exchanges + const withdrawAmount = Amounts.parseOrThrow(uriInfo.amount) + const thisCurrencyExchanges = knownExchanges.filter(ex => ex.currency === withdrawAmount.currency) - if (!talerWithdrawUri) { - return <span><i18n.Translate>missing withdraw uri</i18n.Translate></span>; + const exchange = customExchange || uriInfo.defaultExchangeBaseUrl || thisCurrencyExchanges[0]?.exchangeBaseUrl + const detailsHook = useAsyncAsHook(async () => { + if (!exchange) throw Error('no default exchange') + return getExchangeWithdrawalInfo({ + exchangeBaseUrl: exchange, + amount: withdrawAmount, + tosAcceptedFormat: ['text/json', 'text/xml', 'text/pdf'] + }) + }) + + if (!detailsHook) { + return <span><i18n.Translate>Getting withdrawal details.</i18n.Translate></span>; + } + if (detailsHook.hasError) { + return <span><i18n.Translate>Problems getting details: {detailsHook.message}</i18n.Translate></span>; } + const details = detailsHook.response + const onAccept = async (): Promise<void> => { - if (!details) { - throw Error("can't accept, no exchange selected"); - } try { - await setExchangeTosAccepted(details.exchangeDetails.exchangeBaseUrl, details.tosRequested?.tosEtag) + await setExchangeTosAccepted(details.exchangeInfo.baseUrl, details.tosRequested?.tosEtag) setAccepted(true) } catch (e) { - setError(true) + if (e instanceof Error) { + setErrorAccepting(e.message) + } } } const onWithdraw = async (): Promise<void> => { - if (!details) { - throw Error("can't accept, no exchange selected"); - } setConfirmed(true) - console.log("accepting exchange", details.exchangeInfo.baseUrl); + console.log("accepting exchange", details.exchangeDetails.exchangeBaseUrl); try { - const res = await acceptWithdrawal(talerWithdrawUri, details.exchangeInfo.baseUrl); + const res = await acceptWithdrawal(uri, details.exchangeInfo.baseUrl); console.log("accept withdrawal response", res); if (res.confirmTransferUrl) { document.location.href = res.confirmTransferUrl; @@ -261,19 +257,6 @@ export function WithdrawPage({ talerWithdrawUri, ...rest }: Props): JSX.Element } }; - 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>; - } - let termsContent: TermsDocument | undefined = undefined; if (details.tosRequested) { if (details.tosRequested.tosContentType === 'text/xml') { @@ -295,14 +278,32 @@ export function WithdrawPage({ talerWithdrawUri, ...rest }: Props): JSX.Element return <View onWithdraw={onWithdraw} // setCancelled={setCancelled} setSelecting={setSelecting} - details={details} amount={uriInfo.amount} + details={details} amount={withdrawAmount} terms={{ status, value: termsContent }} + onSwitchExchange={setCustomExchange} + knownExchanges={knownExchanges} confirmed={confirmed} accepted={accepted} onAccept={onAccept} reviewing={reviewing} onReview={setReviewing} // terms={[]} /> } +export function WithdrawPage({ talerWithdrawUri }: Props): JSX.Element { + const uriInfoHook = useAsyncAsHook(() => !talerWithdrawUri ? Promise.reject(undefined) : + getWithdrawalDetailsForUri({ talerWithdrawUri }) + ) + + if (!talerWithdrawUri) { + return <span><i18n.Translate>missing withdraw uri</i18n.Translate></span>; + } + if (!uriInfoHook) { + return <span><i18n.Translate>Loading...</i18n.Translate></span>; + } + if (uriInfoHook.hasError) { + return <span><i18n.Translate>This URI is not valid anymore: {uriInfoHook.message}</i18n.Translate></span>; + } + return <WithdrawPageWithParsedURI uri={talerWithdrawUri} uriInfo={uriInfoHook.response} /> +} |