merchant-backoffice

ZZZ: Inactive/Deprecated
Log | Files | Refs | Submodules | README

commit af7f0fa9df6165e3d82246d4077ac2d4da6cb597
parent 1e8fbfc481346f39cc05a7532dcfb92a8240a7d4
Author: ms <ms@taler.net>
Date:   Tue,  5 Apr 2022 10:47:50 +0200

copying pybank's look.  WIP

Diffstat:
Mpackages/bank/src/components/QR.tsx | 2+-
Mpackages/bank/src/pages/home/index.tsx | 172+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------
2 files changed, 134 insertions(+), 40 deletions(-)

diff --git a/packages/bank/src/components/QR.tsx b/packages/bank/src/components/QR.tsx @@ -36,7 +36,7 @@ export function QR({ text }: { text: string }): VNode { width: "100%", display: "flex", flexDirection: "column", - alignItems: "center", + alignItems: "left", }} > <div diff --git a/packages/bank/src/pages/home/index.tsx b/packages/bank/src/pages/home/index.tsx @@ -109,6 +109,9 @@ interface AccountStateType { * Helpers. * ***********/ +function genCaptchaNumbers(): string { + return `${Math.floor(Math.random() * 10)} + ${Math.floor(Math.random() * 10)}`; +} /** * Bring the state to show the public accounts page. */ @@ -124,7 +127,7 @@ function goPublicAccounts(pageStateSetter: StateUpdater<PageStateType>) { function validateAmount(maybeAmount: string): any { const amountRegex = "^[0-9]+(\.[0-9]+)?$"; if (typeof maybeAmount !== "undefined" || maybeAmount !== "") { - console.log("Maybe valid amount", maybeAmount); + console.log("Maybe valid amount: " + maybeAmount); // tolerating comma instead of point. maybeAmount = maybeAmount.replace(",", "."); const re = RegExp(amountRegex) @@ -515,9 +518,8 @@ async function createTransactionCall( * user should receive a QR code of the "taler://withdraw/" type and * supposed to scan it with their phone. * - * TODO: (1) after the scan, the page should refresh itself and inform the - * user about the operation's outcome. (2) use POST helper. - */ + * TODO: (1) after the scan, the page should refresh itself and inform + * the user about the operation's outcome. (2) use POST helper. */ async function createWithdrawalCall( amount: string, backendState: BackendStateTypeOpt, @@ -563,7 +565,7 @@ async function createWithdrawalCall( console.log("Withdrawal operation created!"); let resp = await res.json(); - pageStateSetter((prevState) => ({ + pageStateSetter((prevState: PageStateType) => ({ ...prevState, withdrawalInProgress: true, talerWithdrawUri: resp.taler_withdraw_uri, @@ -710,7 +712,7 @@ function BankFrame(Props: any): VNode { </div> <a href="https://taler.net/"> <img - src={talerLogo} + src={talerLogo} height="100" width="224" style="margin: 2em 2em"> @@ -815,7 +817,7 @@ function PaytoWireTransfer(Props: any): VNode { href="#" onClick={() => { console.log("switch to raw payto form"); - pageStateSetter((prevState) => ({...prevState, isRawPayto: true})); + pageStateSetter((prevState: any) => ({...prevState, isRawPayto: true})); }}>{i18n`Want to try the raw payto://-format?`} </a></p> </div> @@ -861,13 +863,107 @@ function PaytoWireTransfer(Props: any): VNode { } /** - * Let user choose a amount and submit the withdtawal. + * Offer the QR code (and a clickable taler://-link) to + * permit the passing of exchange and reserve details to + * the bank. Poll the backend until such operation is done. + */ +function TalerWithdrawalQRCode(Props: any): VNode { + const [pageState, pageStateSetter] = useContext(PageContext); + const { + withdrawalId, + talerWithdrawUri, + accountLabel, + backendState } = Props; + const i18n = useTranslator(); + console.log(`Showing withdraw URI: ${talerWithdrawUri}`); + // polling the wallet: + const { data, error } = useSWR( + `integration-api/accounts/${accountLabel}/withdrawal-operation/${withdrawalId}` + ); + const captchaNumbers = { + a: Math.floor(Math.random() * 10), + b: Math.floor(Math.random() * 10) + } + var captchaAnswer = ""; + // fall here as long as the wallet did NOT communicate: + if (typeof error !== "undefined") { + console.log("transactions not found error", error); + switch(error.status) { + case 404: { + return (<section id="main" class="content"> + <h1 class="nav">{i18n`Withdraw to a Taler Wallet`}</h1> + <p>{i18n`You can use this QR code to withdraw to your mobile wallet:`}</p> + {QR({text: talerWithdrawUri})} + <p>Click <a href={talerWithdrawUri}>{i18n`this link`}</a> to open your Taler wallet!</p> + <br /><br /> + <button onClick={() => { + pageStateSetter((prevState: PageStateType) => { + const { withdrawalOutcome, withdrawalId, talerWithdrawUri, ...rest } = prevState; + return { ...rest, withdrawalInProgress: false }; + })}}>{i18n`Close`}</button> + </section>); + } + default: { + pageStateSetter((prevState: PageStateType) => ({...prevState, hasError: true, error: error })) + return <p>Could not complete the withdrawal: {error}</p> + } + } + } + // here the reserve public key and exchange payment details are known to the bank, + // ask for a confirmation (used to be the CAPTCHA page): + return (<Fragment> + <h1 class="nav">{i18n`Confirm Withdrawal`}</h1> + <p><Translate> + Please, authorize this operation by answering the following question. + </Translate></p> + <div> + <label>What is <em>{captchaNumbers.a} + {captchaNumbers.b}</em> ?&nbsp;</label> + <input + type="text" + required + onInput={(e): void => { + captchaAnswer = e.currentTarget.value; + }} /> + <input + type="submit" + value="confirm" + onClick={ () => { + if (captchaAnswer == (captchaNumbers.a + captchaNumbers.b).toString()) { + confirmWithdrawalCall( + backendState, + pageState.withdrawalId, + pageStateSetter) + return; + } + pageStateSetter((prevState: PageStateType) => + ({...prevState, hasError: true, error: "Answer is wrong."})) + }} /> + <input + type="submit" + value="abort" + onClick={ () => + abortWithdrawalCall( + backendState, + pageState.withdrawalId, + pageStateSetter + )} /> + </div> + <p><Translate> + A this point, a <b>real</b> bank would ask for an additional + authentication proof (PIN/TAN, one time password, ..), instead + of a simple calculation. + </Translate></p> + </Fragment>); +} + +/** + * Let the user choose an amount and submit the withdtawal. */ function TalerWithdrawal(Props: any): VNode { const {backendState, pageStateSetter} = Props; const currency = useContext(CurrencyContext); const i18n = useTranslator(); - var submitAmount = ""; // without currency. + var submitAmount = "5.00"; // must match the first <select> child. const amountRegex = "^[0-9]+(\.[0-9]+)?$"; var submitButton = <input @@ -893,10 +989,14 @@ function TalerWithdrawal(Props: any): VNode { <div> <h2>{i18n`Withdraw Money into a Taler wallet`}</h2> <div id="reserve-form" - class="pure-form" - name="tform"> + class="pure-form" + name="tform"> {i18n`Amount to withdraw`}:&nbsp; - <select id="reserve-amount" name="withdraw-amount" class="amount" autofocus> + <select id="reserve-amount" + name="withdraw-amount" + class="amount" autofocus + onChange={(e): void => { + submitAmount = e.currentTarget.value; }}> <option value="5.00">5.00</option> <option value="10.00">10.00</option> <option value="15.00">15.00</option> @@ -1092,6 +1192,7 @@ function Account(Props: any): VNode { tryManualTransfer, withdrawalOutcome, transferOutcome, + withdrawalId, talerWithdrawUri } = pageState; const i18n = useTranslator(); const logOut = ( @@ -1201,14 +1302,17 @@ function Account(Props: any): VNode { * the outcome. */ if (talerWithdrawUri) { - console.log(`Showing withdraw URI: ${talerWithdrawUri}`); - return (<Fragment> - <p>Scan the following QR code, and then confirm!</p> - <div>{QR({text: talerWithdrawUri})}</div> - <a href={talerWithdrawUri}></a> - <p>Withdraw address: <pre>{talerWithdrawUri}</pre></p> - {Props.children} - </Fragment>); + console.log("Bank created a new Taler withdrawal"); + return ( + <BankFrame> + {logOut}<br /> + <TalerWithdrawalQRCode + accountLabel={accountLabel} + backendState={backendState} + withdrawalId={withdrawalId} + talerWithdrawUri={talerWithdrawUri} /> + </BankFrame> + ); } const balance = parseAmount(data.balance.amount) if (tryManualTransfer) { @@ -1231,13 +1335,10 @@ function Account(Props: any): VNode { </section> <CurrencyContext.Provider value={balance.currency}> {Props.children} + <TalerWithdrawal + backendState={backendState} + pageStateSetter={pageStateSetter} /> </CurrencyContext.Provider> - { - withdrawalInProgress && !transferOutcome && - <TalerWithdrawal - backendState={backendState} - pageStateSetter={pageStateSetter} /> - } <section id="main"> <article> <h2>{i18n`Latest transactions:`}</h2> @@ -1416,19 +1517,10 @@ export function BankHome(): VNode { backendUrl={backendState.url}> <PageContext.Provider value={[pageState, pageStateSetter]}> <Account accountLabel={backendState.username} backendState={backendState}> - - { /** - * No action is currently being performed (page is 'pristine'): - * offer the Taler withdrawal button. - */ - !pageState.withdrawalInProgress && !pageState.transferOutcome && <TalerWithdrawal - backendState={backendState} - pageStateSetter={pageStateSetter} /> - } - { /** * Wire transfer reached a persisten state: offer to - * return back to the pristine profile page. + * return back to the pristine profile page. FIXME: + * move this into the Account component. */ pageState.transferOutcome && <button onClick={() => { pageStateSetter((prevState) => { @@ -1438,7 +1530,8 @@ export function BankHome(): VNode { { /** * Withdrawal reached a persisten state: offer to - * return back to the pristine profile page. + * return back to the pristine profile page. FIXME: + * move this into the Account component. */ pageState.withdrawalOutcome && <button onClick={() => { pageStateSetter((prevState) => { @@ -1452,7 +1545,8 @@ export function BankHome(): VNode { { /** * The withdrawal QR code is rendered: offer to confirm - * or abort the operation. + * or abort the operation. FIXME: move this into the Account + * component. */ pageState.talerWithdrawUri && <div><button onClick={() => { confirmWithdrawalCall(