gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[taler-wallet-core] branch master updated (9ba6a59d1 -> 6f7512be7)


From: gnunet
Subject: [taler-wallet-core] branch master updated (9ba6a59d1 -> 6f7512be7)
Date: Fri, 24 Nov 2023 18:43:24 +0100

This is an automated email from the git hooks/post-receive script.

sebasjm pushed a change to branch master
in repository wallet-core.

    from 9ba6a59d1 using monitor api
     new 3df64dd45 update to the latest gana
     new 6f7512be7 show cashout details

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 packages/demobank-ui/src/Routing.tsx               |   2 +-
 .../demobank-ui/src/components/Cashouts/views.tsx  |  57 ++-
 packages/demobank-ui/src/hooks/circuit.ts          |  41 +-
 .../demobank-ui/src/pages/AccountPage/views.tsx    |  17 +
 packages/demobank-ui/src/pages/BankFrame.tsx       |   1 -
 packages/demobank-ui/src/pages/PaymentOptions.tsx  |   2 +-
 .../src/pages/PaytoWireTransferForm.tsx            |   2 +-
 .../demobank-ui/src/pages/ProfileNavigation.tsx    |   4 +-
 .../src/pages/account/ShowAccountDetails.tsx       |   3 +-
 .../src/pages/account/UpdateAccountPassword.tsx    |   2 +-
 .../demobank-ui/src/pages/admin/AccountForm.tsx    |  93 +++--
 .../src/pages/business/CreateCashout.tsx           |  52 ++-
 .../src/pages/business/ShowCashoutDetails.tsx      | 431 ++++++++++++---------
 packages/taler-util/src/http-client/bank-core.ts   |   2 +-
 packages/taler-util/src/taler-error-codes.ts       |  80 ++++
 15 files changed, 503 insertions(+), 286 deletions(-)

diff --git a/packages/demobank-ui/src/Routing.tsx 
b/packages/demobank-ui/src/Routing.tsx
index 733d55a0f..8ed66d4cf 100644
--- a/packages/demobank-ui/src/Routing.tsx
+++ b/packages/demobank-ui/src/Routing.tsx
@@ -258,7 +258,7 @@ export function Routing(): VNode {
             <ShowCashoutDetails
               id={cid}
               onCancel={() => {
-                route("/account");
+                route("/my-cashouts");
               }}
             />
           )}
diff --git a/packages/demobank-ui/src/components/Cashouts/views.tsx 
b/packages/demobank-ui/src/components/Cashouts/views.tsx
index 651a7a034..59bb4a16b 100644
--- a/packages/demobank-ui/src/components/Cashouts/views.tsx
+++ b/packages/demobank-ui/src/components/Cashouts/views.tsx
@@ -93,7 +93,7 @@ export function ReadyView({ cashouts, onSelected }: 
State.Ready): VNode {
             {Object.entries(txByDate).map(([date, txs], idx) => {
               return <Fragment key={idx}>
                 <tr class="border-t border-gray-200">
-                  <th colSpan={4} scope="colgroup" class="bg-gray-50 py-2 pl-4 
pr-3 text-left text-sm font-semibold text-gray-900 sm:pl-3">
+                  <th colSpan={6} scope="colgroup" class="bg-gray-50 py-2 pl-4 
pr-3 text-left text-sm font-semibold text-gray-900 sm:pl-3">
                     {date}
                   </th>
                 </tr>
@@ -102,9 +102,12 @@ export function ReadyView({ cashouts, onSelected }: 
State.Ready): VNode {
                   const confirmationTime = item.confirmation_time
                     ? item.confirmation_time.t_s === "never" ? i18n.str`never` 
: format(item.confirmation_time.t_s, "dd/MM/yyyy HH:mm:ss")
                     : "-"
-                  return (<tr key={idx} class="border-b border-gray-200 
last:border-none">
+                  return (<tr key={idx} class="border-b border-gray-200 
hover:bg-gray-200 last:border-none">
 
-                    <td class="relative py-2 pl-2 pr-2 text-sm ">
+                    <td onClick={(e) => {
+                        e.preventDefault();
+                        onSelected(item.id);
+                      }} class="relative py-2 pl-2 pr-2 text-sm ">
                       <div class="font-medium 
text-gray-900">{creationTime}</div>
                       {/* <dl class="font-normal sm:hidden">
                         <dt class="sr-only 
sm:hidden"><i18n.Translate>Amount</i18n.Translate></dt>
@@ -128,18 +131,28 @@ export function ReadyView({ cashouts, onSelected }: 
State.Ready): VNode {
                         </dd>
                       </dl> */}
                     </td>
-                    <td class="hidden sm:table-cell px-3 py-3.5 text-sm 
text-gray-500">{confirmationTime}</td>
-                    <td class="hidden sm:table-cell px-3 py-3.5 text-sm 
text-red-600"><RenderAmount value={Amounts.parseOrThrow(item.amount_debit)}  
spec={resp.body.regional_currency_specification} /></td>
-                    <td class="hidden sm:table-cell px-3 py-3.5 text-sm 
text-green-600"><RenderAmount value={Amounts.parseOrThrow(item.amount_credit)} 
spec={resp.body.fiat_currency_specification} /></td>
+                    <td onClick={(e) => {
+                        e.preventDefault();
+                        onSelected(item.id);
+                      }}class="hidden sm:table-cell px-3 py-3.5 text-sm 
text-gray-500 cursor-pointer">{confirmationTime}</td>
+                    <td onClick={(e) => {
+                        e.preventDefault();
+                        onSelected(item.id);
+                      }}class="hidden sm:table-cell px-3 py-3.5 text-sm 
text-red-600 cursor-pointer"><RenderAmount 
value={Amounts.parseOrThrow(item.amount_debit)} 
spec={resp.body.regional_currency_specification} /></td>
+                    <td onClick={(e) => {
+                        e.preventDefault();
+                        onSelected(item.id);
+                      }}class="hidden sm:table-cell px-3 py-3.5 text-sm 
text-green-600 cursor-pointer"><RenderAmount 
value={Amounts.parseOrThrow(item.amount_credit)} 
spec={resp.body.fiat_currency_specification} /></td>
 
-                    <td class="hidden sm:table-cell px-3 py-3.5 text-sm 
text-gray-500">{item.status}</td>
-                    <td class="hidden sm:table-cell px-3 py-3.5 text-sm 
text-gray-500 break-all min-w-md">
-                      <a href="#" onClick={(e) => {
+                    <td  onClick={(e) => {
                         e.preventDefault();
                         onSelected(item.id);
-                      }}>
-                        {item.subject}
-                      </a>
+                      }}class="hidden sm:table-cell px-3 py-3.5 text-sm 
text-gray-500 cursor-pointer">{item.status}</td>
+                    <td onClick={(e) => {
+                        e.preventDefault();
+                        onSelected(item.id);
+                      }} class="hidden sm:table-cell px-3 py-3.5 text-sm 
text-gray-500 break-all min-w-md">
+                      {item.subject}
                     </td>
                   </tr>)
                 })}
@@ -150,27 +163,9 @@ export function ReadyView({ cashouts, onSelected }: 
State.Ready): VNode {
 
         </table>
 
-        {/* <nav class="flex items-center justify-between border-t 
border-gray-200 bg-white px-4 py-3 sm:px-6 rounded-lg" aria-label="Pagination">
-          <div class="flex flex-1 justify-between sm:justify-end">
-            <button
-              class="relative disabled:bg-gray-100 disabled:text-gray-500 
inline-flex items-center rounded-md bg-white px-3 py-2 text-sm font-semibold 
text-gray-900 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 
focus-visible:outline-offset-0"
-              disabled={!onPrev}
-              onClick={onPrev}
-            >
-              <i18n.Translate>First page</i18n.Translate>
-            </button>
-            <button
-              class="relative disabled:bg-gray-100 disabled:text-gray-500 ml-3 
inline-flex items-center rounded-md bg-white px-3 py-2 text-sm font-semibold 
text-gray-900 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 
focus-visible:outline-offset-0"
-              disabled={!onNext}
-              onClick={onNext}
-            >
-              <i18n.Translate>Next</i18n.Translate>
-            </button>
-          </div>
-        </nav> */}
+
       </div>
     </div>
   );
-  // }
 
 }
diff --git a/packages/demobank-ui/src/hooks/circuit.ts 
b/packages/demobank-ui/src/hooks/circuit.ts
index b483a5420..bb9f5801e 100644
--- a/packages/demobank-ui/src/hooks/circuit.ts
+++ b/packages/demobank-ui/src/hooks/circuit.ts
@@ -18,7 +18,7 @@ import { useState } from "preact/hooks";
 import { MAX_RESULT_SIZE, PAGE_SIZE } from "../utils.js";
 import { useBackendState } from "./backend.js";
 
-import { AccessToken, AmountJson, AmountString, Amounts, OperationOk, 
TalerBankConversionResultByMethod, TalerCoreBankErrorsByMethod, 
TalerCoreBankResultByMethod, TalerCorebankApi, TalerError, TalerHttpError } 
from "@gnu-taler/taler-util";
+import { AccessToken, AmountJson, AmountString, Amounts, OperationOk, 
TalerBankConversionResultByMethod, TalerCoreBankErrorsByMethod, 
TalerCoreBankResultByMethod, TalerCorebankApi, TalerError, TalerHttpError, 
opFixedSuccess } from "@gnu-taler/taler-util";
 import _useSWR, { SWRHook } from "swr";
 import { useBankCoreApiContext } from "../context/config.js";
 import { assertUnreachable } from "../pages/WithdrawalOperationPage.js";
@@ -174,6 +174,43 @@ type CashoutWithId = 
TalerCorebankApi.CashoutStatusResponse & { id: number }
 function notUndefined(c: CashoutWithId | undefined): c is CashoutWithId {
   return c !== undefined
 }
+export function useOnePendingCashouts(account: string) {
+  const { state: credentials } = useBackendState();
+  const { api, config } = useBankCoreApiContext();
+  const token = credentials.status !== "loggedIn" ? undefined : 
credentials.token
+
+  async function fetcher([username, token]: [string, AccessToken]) {
+    const list = await api.getAccountCashouts({ username, token })
+    if (list.type !== "ok") {
+      return list;
+    }
+    const pendingCashout = list.body.cashouts.find(c => c.status === "pending")
+    if (!pendingCashout) return opFixedSuccess(undefined)
+    const cashoutInfo = await api.getCashoutById({ username, token }, 
pendingCashout?.cashout_id)
+    if (cashoutInfo.type !== "ok") {
+      return cashoutInfo;
+    }
+    return opFixedSuccess({ ...cashoutInfo.body, id: pendingCashout.cashout_id 
})
+  }
+
+  const { data, error } = useSWR<OperationOk<CashoutWithId | undefined> | 
TalerCoreBankErrorsByMethod<"getAccountCashouts"> | 
TalerCoreBankErrorsByMethod<"getCashoutById">, TalerHttpError>(
+    !config.allow_conversion ? undefined : [account, token, 
"getAccountCashouts"], fetcher, {
+    refreshInterval: 0,
+    refreshWhenHidden: false,
+    revalidateOnFocus: false,
+    revalidateOnReconnect: false,
+    refreshWhenOffline: false,
+    errorRetryCount: 0,
+    errorRetryInterval: 1,
+    shouldRetryOnError: false,
+    keepPreviousData: true,
+  });
+
+  if (data) return data;
+  if (error) return error;
+  return undefined;
+}
+
 export function useCashouts(account: string) {
   const { state: credentials } = useBackendState();
   const { api, config } = useBankCoreApiContext();
@@ -182,13 +219,11 @@ export function useCashouts(account: string) {
   async function fetcher([username, token]: [string, AccessToken]) {
     const list = await api.getAccountCashouts({ username, token })
     if (list.type !== "ok") {
-      console.error(list)
       return list;
     }
     const all: Array<CashoutWithId | undefined> = await 
Promise.all(list.body.cashouts.map(c => {
       return api.getCashoutById({ username, token }, c.cashout_id).then(r => {
         if (r.type === "fail") {
-          console.error("failed", r)
           return undefined
         }
         return { ...r.body, id: c.cashout_id }
diff --git a/packages/demobank-ui/src/pages/AccountPage/views.tsx 
b/packages/demobank-ui/src/pages/AccountPage/views.tsx
index cfee684fa..d760543c6 100644
--- a/packages/demobank-ui/src/pages/AccountPage/views.tsx
+++ b/packages/demobank-ui/src/pages/AccountPage/views.tsx
@@ -21,6 +21,8 @@ import { Transactions } from 
"../../components/Transactions/index.js";
 import { usePreferences } from "../../hooks/preferences.js";
 import { PaymentOptions } from "../PaymentOptions.js";
 import { State } from "./index.js";
+import { useCashouts, useOnePendingCashouts } from "../../hooks/circuit.js";
+import { TalerError } from "@gnu-taler/taler-util";
 
 export function InvalidIbanView({ error }: State.InvalidIban) {
   return (
@@ -57,8 +59,23 @@ export function ReadyView({ account, limit, 
goToConfirmOperation }: State.Ready)
 
   return <Fragment>
     <ShowDemoInfo />
+    <PendingCashouts account={account}/>
     <PaymentOptions limit={limit} goToConfirmOperation={goToConfirmOperation} 
/>
     <Transactions account={account} />
   </Fragment>;
 }
 
+
+function PendingCashouts({account}: {account: string}):VNode {
+  const { i18n } = useTranslationContext();
+  const result = useOnePendingCashouts(account)
+  if (!result || result instanceof TalerError || result.type !== "ok" || 
!result.body) {
+    return <Fragment />
+  }
+
+  return <Attention title={i18n.str`You have pending cashout operation to 
complete`} >
+    <i18n.Translate>
+      Cashout with subject "{result.body.subject}", look for the code and 
complete the operation <a target="_blank" rel="noreferrer noopener" 
class="font-semibold text-blue-700 hover:text-blue-600" 
href={`#/cashout/${result.body.id}`}>here</a>.
+    </i18n.Translate>
+  </Attention>
+}
\ No newline at end of file
diff --git a/packages/demobank-ui/src/pages/BankFrame.tsx 
b/packages/demobank-ui/src/pages/BankFrame.tsx
index 34c39e9d3..24012cd2b 100644
--- a/packages/demobank-ui/src/pages/BankFrame.tsx
+++ b/packages/demobank-ui/src/pages/BankFrame.tsx
@@ -46,7 +46,6 @@ export function BankFrame({
   useEffect(() => {
     if (error) {
       const desc = (error instanceof Error ? error.stack : String(error)) as 
TranslatedString
-      console.log(error)
       if (error instanceof Error) {
         notifyException(i18n.str`Internal error, please report.`, error)
       } else {
diff --git a/packages/demobank-ui/src/pages/PaymentOptions.tsx 
b/packages/demobank-ui/src/pages/PaymentOptions.tsx
index 76d20867e..bbe33eb57 100644
--- a/packages/demobank-ui/src/pages/PaymentOptions.tsx
+++ b/packages/demobank-ui/src/pages/PaymentOptions.tsx
@@ -33,7 +33,7 @@ export function PaymentOptions({ limit, goToConfirmOperation 
}: { limit: AmountJ
   const [tab, setTab] = useState<"charge-wallet" | "wire-transfer" | 
undefined>();
 
   return (
-    <div class="mt-2">
+    <div class="mt-4">
 
       <fieldset>
         <legend class="px-4 text-base font-semibold leading-6 text-gray-900">
diff --git a/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx 
b/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx
index e035c7fed..33bf18abc 100644
--- a/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx
+++ b/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx
@@ -447,7 +447,7 @@ export function InputAmount(
         <input
           type="number"
           data-left={left}
-          class="text-right rounded-md rounded-l-none 
data-[left=true]:text-left w-full py-1.5 pl-3 text-gray-900  
placeholder:text-gray-400  sm:text-sm sm:leading-6"
+          class="disabled:bg-gray-200 text-right rounded-md rounded-l-none 
data-[left=true]:text-left w-full py-1.5 pl-3 text-gray-900  
placeholder:text-gray-400  sm:text-sm sm:leading-6"
           placeholder="0.00" aria-describedby="price-currency"
           ref={ref}
           name={name}
diff --git a/packages/demobank-ui/src/pages/ProfileNavigation.tsx 
b/packages/demobank-ui/src/pages/ProfileNavigation.tsx
index 1a4b4b865..61a55fe16 100644
--- a/packages/demobank-ui/src/pages/ProfileNavigation.tsx
+++ b/packages/demobank-ui/src/pages/ProfileNavigation.tsx
@@ -3,7 +3,7 @@ import { Fragment, VNode, h } from "preact";
 import { useBankCoreApiContext } from "../context/config.js";
 import { assertUnreachable } from "./WithdrawalOperationPage.js";
 
-export function ProfileNavigation({ current }: { current: "details" | 
"credentials" | "cashouts" }): VNode {
+export function ProfileNavigation({ current, noCashout }: { noCashout?: 
boolean, current: "details" | "credentials" | "cashouts" }): VNode {
   const { i18n } = useTranslationContext()
   const { config } = useBankCoreApiContext()
   return <div>
@@ -44,7 +44,7 @@ export function ProfileNavigation({ current }: { current: 
"details" | "credentia
           <span><i18n.Translate>Credentials</i18n.Translate></span>
           <span aria-hidden="true" data-selected={current == "credentials"} 
class="bg-transparent data-[selected=true]:bg-indigo-500 absolute inset-x-0 
bottom-0 h-0.5"></span>
         </a>
-        {config.allow_conversion ?
+        {config.allow_conversion && !noCashout ?
           <a href="#/my-cashouts" data-selected={current == "cashouts"} 
class="rounded-r-lg text-gray-500 hover:text-gray-700 
data-[selected=true]:text-gray-900 group relative min-w-0 flex-1 
overflow-hidden bg-white py-4 px-4 text-center text-sm font-medium 
hover:bg-gray-50 focus:z-10">
             <span>Cashouts</span>
             <span aria-hidden="true" data-selected={current == "cashouts"} 
class="bg-transparent data-[selected=true]:bg-indigo-500 absolute inset-x-0 
bottom-0 h-0.5"></span>
diff --git a/packages/demobank-ui/src/pages/account/ShowAccountDetails.tsx 
b/packages/demobank-ui/src/pages/account/ShowAccountDetails.tsx
index 3ef3f568c..4332284e8 100644
--- a/packages/demobank-ui/src/pages/account/ShowAccountDetails.tsx
+++ b/packages/demobank-ui/src/pages/account/ShowAccountDetails.tsx
@@ -99,7 +99,7 @@ export function ShowAccountDetails({
     <Fragment>
       <LocalNotificationBanner notification={notification} />
       {accountIsTheCurrentUser ?
-        <ProfileNavigation current="details" />
+        <ProfileNavigation current="details" noCashout={credentials.status === 
"loggedIn" ? credentials.isUserAdministrator : undefined} />
         :
         <h1 class="text-base font-semibold leading-6 text-gray-900">
           <i18n.Translate>Account "{account}"</i18n.Translate>
@@ -128,6 +128,7 @@ export function ShowAccountDetails({
 
         <AccountForm
           focus={update}
+          noCashout={credentials.status === "loggedIn" ? 
credentials.isUserAdministrator : undefined}
           username={account}
           template={result.body}
           purpose={update ? "update" : "show"}
diff --git a/packages/demobank-ui/src/pages/account/UpdateAccountPassword.tsx 
b/packages/demobank-ui/src/pages/account/UpdateAccountPassword.tsx
index d7f5155c9..3c00ad1b8 100644
--- a/packages/demobank-ui/src/pages/account/UpdateAccountPassword.tsx
+++ b/packages/demobank-ui/src/pages/account/UpdateAccountPassword.tsx
@@ -81,7 +81,7 @@ export function UpdateAccountPassword({
     <Fragment>
       <LocalNotificationBanner notification={notification} />
       {accountIsTheCurrentUser ?
-        <ProfileNavigation current="credentials" /> :
+        <ProfileNavigation current="credentials" noCashout={credentials.status 
=== "loggedIn" ? credentials.isUserAdministrator : undefined} /> :
         <h1 class="text-base font-semibold leading-6 text-gray-900">
           <i18n.Translate>Account "{accountName}"</i18n.Translate>
         </h1>
diff --git a/packages/demobank-ui/src/pages/admin/AccountForm.tsx 
b/packages/demobank-ui/src/pages/admin/AccountForm.tsx
index b38d40012..526deeeab 100644
--- a/packages/demobank-ui/src/pages/admin/AccountForm.tsx
+++ b/packages/demobank-ui/src/pages/admin/AccountForm.tsx
@@ -27,11 +27,13 @@ export function AccountForm({
   purpose,
   onChange,
   focus,
+  noCashout,
   children,
 }: {
   focus?: boolean,
   children: ComponentChildren,
   username?: string,
+  noCashout?: boolean,
   template: TalerCorebankApi.AccountData | undefined;
   onChange: (a: AccountFormData | undefined) => void;
   purpose: "create" | "update" | "show";
@@ -44,14 +46,14 @@ export function AccountForm({
   const { i18n } = useTranslationContext();
 
   function updateForm(newForm: typeof initial): void {
-
+    console.log(newForm)
     const parsed = !newForm.cashout_payto_uri
       ? undefined
       : buildPayto("iban", newForm.cashout_payto_uri, undefined);;
 
     const errors = undefinedIfEmpty<RecursivePartial<typeof initial>>({
       cashout_payto_uri: (!newForm.cashout_payto_uri
-        ? i18n.str`required`
+        ? undefined
         : !parsed
           ? i18n.str`does not follow the pattern`
           : !parsed.isKnown || parsed.targetType !== "iban"
@@ -81,10 +83,10 @@ export function AccountForm({
     if (errors) {
       onChange(undefined)
     } else {
-      const cashout = buildPayto("iban", newForm.cashout_payto_uri!, undefined)
+      const cashout = !newForm.cashout_payto_uri? undefined 
:buildPayto("iban", newForm.cashout_payto_uri, undefined)
       const account: AccountFormData = {
         ...newForm as any,
-        cashout_payto_uri: stringifyPaytoUri(cashout)
+        cashout_payto_uri: !cashout ? undefined : stringifyPaytoUri(cashout)
       }
       onChange(account);
     }
@@ -194,6 +196,9 @@ export function AccountForm({
                 onChange={(e) => {
                   if (form.contact_data) {
                     form.contact_data.email = e.currentTarget.value;
+                    if (!form.contact_data.email) {
+                      form.contact_data.email = undefined
+                    }
                     updateForm(structuredClone(form));
                   }
                 }}
@@ -220,12 +225,15 @@ export function AccountForm({
                 class="block w-full disabled:bg-gray-100 rounded-md border-0 
py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 
data-[error=true]:ring-red-500 placeholder:text-gray-400 focus:ring-2 
focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
                 name="phone"
                 id="phone"
-                disabled={purpose !== "create"}
+                disabled={purpose === "show"}
                 value={form.contact_data?.phone ?? ""}
                 data-error={!!errors?.contact_data?.phone && 
form.contact_data?.phone !== undefined}
                 onChange={(e) => {
                   if (form.contact_data) {
                     form.contact_data.phone = e.currentTarget.value;
+                    if (!form.contact_data.email) {
+                      form.contact_data.email = undefined
+                    }
                     updateForm(structuredClone(form));
                   }
                 }}
@@ -240,44 +248,45 @@ export function AccountForm({
           </div>
 
 
-          <div class="sm:col-span-5">
-            <label
-              class="block text-sm font-medium leading-6 text-gray-900"
-              for="cashout"
-            >
-              {i18n.str`Cashout IBAN`}
-              {purpose !== "show" && <b style={{ color: "red" }}> *</b>}
-            </label>
-            <div class="mt-2">
-              <input
-                type="text"
-                ref={focus && purpose === "update" ? doAutoFocus : undefined}
-                data-error={!!errors?.cashout_payto_uri && 
form.cashout_payto_uri !== undefined}
-                class="block w-full disabled:bg-gray-100 rounded-md border-0 
py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 
data-[error=true]:ring-red-500 placeholder:text-gray-400 focus:ring-2 
focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
-                name="cashout"
-                id="cashout"
-                disabled={purpose === "show"}
-                value={form.cashout_payto_uri ?? ""}
-                onChange={(e) => {
-                  form.cashout_payto_uri = e.currentTarget.value as 
PaytoString;
-                  updateForm(structuredClone(form));
-                }}
-                autocomplete="off"
-              />
-              <ShowInputErrorLabel
-                message={errors?.cashout_payto_uri}
-                isDirty={form.cashout_payto_uri !== undefined}
-              />
+          {!noCashout &&
+            <div class="sm:col-span-5">
+              <label
+                class="block text-sm font-medium leading-6 text-gray-900"
+                for="cashout"
+              >
+                {i18n.str`Cashout IBAN`}
+                {purpose !== "show" && <b style={{ color: "red" }}> *</b>}
+              </label>
+              <div class="mt-2">
+                <input
+                  type="text"
+                  ref={focus && purpose === "update" ? doAutoFocus : undefined}
+                  data-error={!!errors?.cashout_payto_uri && 
form.cashout_payto_uri !== undefined}
+                  class="block w-full disabled:bg-gray-100 rounded-md border-0 
py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 
data-[error=true]:ring-red-500 placeholder:text-gray-400 focus:ring-2 
focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
+                  name="cashout"
+                  id="cashout"
+                  disabled={purpose === "show"}
+                  value={form.cashout_payto_uri ?? ""}
+                  onChange={(e) => {
+                    form.cashout_payto_uri = e.currentTarget.value as 
PaytoString;
+                    if (!form.cashout_payto_uri) {
+                      form.cashout_payto_uri= undefined
+                    }
+                    updateForm(structuredClone(form));
+                  }}
+                  autocomplete="off"
+                />
+                <ShowInputErrorLabel
+                  message={errors?.cashout_payto_uri}
+                  isDirty={form.cashout_payto_uri !== undefined}
+                />
+              </div>
+              <p class="mt-2 text-sm text-gray-500" >
+                <i18n.Translate>account number where the money is going to be 
sent when doing cashouts</i18n.Translate>
+              </p>
             </div>
-            <p class="mt-2 text-sm text-gray-500" >
-              <i18n.Translate>account number where the money is going to be 
sent when doing cashouts</i18n.Translate>
-            </p>
-          </div>
-          <div class="sm:col-span-5">
-            <pre>
-              {JSON.stringify(errors, undefined, 2)}
-            </pre>
-          </div>
+          }
+
         </div>
       </div>
       {children}
diff --git a/packages/demobank-ui/src/pages/business/CreateCashout.tsx 
b/packages/demobank-ui/src/pages/business/CreateCashout.tsx
index 2f77f3960..3d3f30250 100644
--- a/packages/demobank-ui/src/pages/business/CreateCashout.tsx
+++ b/packages/demobank-ui/src/pages/business/CreateCashout.tsx
@@ -18,7 +18,8 @@ import {
   TalerError,
   TranslatedString,
   encodeCrock,
-  getRandomBytes
+  getRandomBytes,
+  parsePaytoUri
 } from "@gnu-taler/taler-util";
 import {
   Attention,
@@ -83,7 +84,7 @@ export function CreateCashout({
   const creds = credentials.status !== "loggedIn" ? undefined : credentials
 
   const { api, config } = useBankCoreApiContext()
-  const [form, setForm] = useState<Partial<FormType>>({ isDebit: true, amount: 
"2" });
+  const [form, setForm] = useState<Partial<FormType>>({ isDebit: true, });
   const [notification, notify, handleError] = useLocalNotification()
   const info = useConversionInfo();
 
@@ -171,7 +172,7 @@ export function CreateCashout({
         : Amounts.cmp(limit, calc.debit) === -1
           ? i18n.str`balance is not enough`
           : Amounts.cmp(calc.credit, sellFee) === -1
-            ? i18n.str`the total amount to transfer does not cover the fees`
+            ? i18n.str`need to be higher due to fees`
             : Amounts.isZero(calc.credit)
               ? i18n.str`the total transfer at destination will be zero`
               : undefined,
@@ -242,7 +243,9 @@ export function CreateCashout({
       }
     })
   }
-
+  const cashoutAccount = !resultAccount.body.cashout_payto_uri ? undefined :
+    parsePaytoUri(resultAccount.body.cashout_payto_uri);
+  const cashoutAccountName = !cashoutAccount ? undefined : 
cashoutAccount.targetPath
   return (
     <div>
       <LocalNotificationBanner notification={notification} />
@@ -275,6 +278,24 @@ export function CreateCashout({
                 <RenderAmount value={sellFee} 
spec={fiat_currency_specification} />
               </dd>
             </div>
+            {cashoutAccountName ?
+              <div class="flex items-center justify-between border-t-2 afu 
pt-4">
+                <dt class="flex items-center text-sm text-gray-600">
+                  <span><i18n.Translate>To account</i18n.Translate></span>
+                </dt>
+                <dd class="text-sm text-gray-900">
+                  {cashoutAccountName }
+                </dd>
+              </div> :
+              <div class="flex items-center justify-between border-t-2 afu 
pt-4">
+                <Attention type="warning" title={i18n.str`No cashout account`}>
+                  <i18n.Translate>
+                    Before doing a cashout you need to complete your profile
+                  </i18n.Translate>
+                </Attention>
+              </div>
+            }
+
           </dl>
 
         </section>
@@ -301,9 +322,10 @@ export function CreateCashout({
                   <input
                     ref={focus ? doAutoFocus : undefined}
                     type="text"
-                    class="block w-full rounded-md border-0 py-1.5 
text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 
data-[error=true]:ring-red-500 placeholder:text-gray-400 focus:ring-2 
focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
+                    class="block w-full rounded-md disabled:bg-gray-200 
border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 
data-[error=true]:ring-red-500 placeholder:text-gray-400 focus:ring-2 
focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
                     name="subject"
                     id="subject"
+                    disabled={!resultAccount.body.cashout_payto_uri}
                     data-error={!!errors?.subject && form.subject !== 
undefined}
                     value={form.subject ?? ""}
                     onChange={(e) => {
@@ -346,7 +368,7 @@ export function CreateCashout({
                     left
                     currency={limit.currency}
                     value={trimmedAmountStr}
-                    onChange={(value) => {
+                    onChange={!resultAccount.body.cashout_payto_uri ? 
undefined : (value) => {
                       form.amount = value;
                       updateForm(structuredClone(form));
                     }}
@@ -411,7 +433,7 @@ export function CreateCashout({
 
               {/* channel */}
               <div class="sm:col-span-5">
-              <label
+                <label
                   class="block text-sm font-medium leading-6 text-gray-900"
                   for="channel"
                 >
@@ -421,16 +443,19 @@ export function CreateCashout({
                 <div class="mt-2 max-w-xl text-sm text-gray-500">
                   <div class="px-4 mt-4 grid grid-cols-1 gap-y-6">
 
-                    <label onClick={()=>{
+                    <label onClick={() => {
+                      if (!resultAccount.body.contact_data?.email) return;
                       form.channel = TanChannel.EMAIL
                       updateForm(structuredClone(form))
-                    }} data-selected={form.channel === TanChannel.EMAIL} 
class="relative flex cursor-pointer rounded-lg border bg-white p-4 shadow-sm 
focus:outline-none border-gray-300 data-[selected=true]:ring-2 
data-[selected=true]:ring-indigo-600">
+                    }} data-disabled={!resultAccount.body.contact_data?.email} 
data-selected={form.channel === TanChannel.EMAIL}
+                      class="relative flex 
data-[disabled=false]:cursor-pointer rounded-lg border bg-white 
data-[disabled=true]:bg-gray-200 p-4 shadow-sm focus:outline-none 
border-gray-300 data-[selected=true]:ring-2 
data-[selected=true]:ring-indigo-600">
                       <input type="radio" name="channel" value="Newsletter" 
class="sr-only" />
                       <span class="flex flex-1">
                         <span class="flex flex-col">
                           <span id="project-type-0-label" class="block text-sm 
font-medium text-gray-900 ">
                             <i18n.Translate>Email</i18n.Translate>
                           </span>
+                          {!resultAccount.body.contact_data?.email && 
i18n.str`add a email in your profile to enable this option`}
                         </span>
                       </span>
                       <svg data-selected={form.channel === TanChannel.EMAIL} 
class="h-5 w-5 text-indigo-600 data-[selected=false]:hidden" viewBox="0 0 20 
20" fill="currentColor" aria-hidden="true">
@@ -438,16 +463,19 @@ export function CreateCashout({
                       </svg>
                     </label>
 
-                    <label onClick={()=>{
+                    <label onClick={() => {
+                      if (!resultAccount.body.contact_data?.phone) return;
                       form.channel = TanChannel.SMS
                       updateForm(structuredClone(form))
-                    }} data-selected={form.channel === TanChannel.SMS} 
class="relative flex cursor-pointer rounded-lg border bg-white  p-4 shadow-sm 
focus:outline-none border-gray-300 data-[selected=true]:ring-2 
data-[selected=true]:ring-indigo-600">
-                      <input type="radio" name="channel" value="Existing 
Customers" class="sr-only"  />
+                    }} data-disabled={!resultAccount.body.contact_data?.phone} 
data-selected={form.channel === TanChannel.SMS}
+                      class="relative flex 
data-[disabled=false]:cursor-pointer rounded-lg border 
data-[disabled=true]:bg-gray-200 p-4 shadow-sm focus:outline-none 
border-gray-300 data-[selected=true]:ring-2 
data-[selected=true]:ring-indigo-600">
+                      <input type="radio" name="channel" value="Existing 
Customers" class="sr-only" />
                       <span class="flex flex-1">
                         <span class="flex flex-col">
                           <span id="project-type-1-label" class="block text-sm 
font-medium text-gray-900">
                             <i18n.Translate>SMS</i18n.Translate>
                           </span>
+                          {!resultAccount.body.contact_data?.phone && 
i18n.str`add a phone number in your profile to enable this option`}
                         </span>
                       </span>
                       <svg data-selected={form.channel === TanChannel.SMS} 
class="h-5 w-5 text-indigo-600 data-[selected=false]:hidden" viewBox="0 0 20 
20" fill="currentColor" aria-hidden="true">
diff --git a/packages/demobank-ui/src/pages/business/ShowCashoutDetails.tsx 
b/packages/demobank-ui/src/pages/business/ShowCashoutDetails.tsx
index 52ff713e2..6fd9eb18c 100644
--- a/packages/demobank-ui/src/pages/business/ShowCashoutDetails.tsx
+++ b/packages/demobank-ui/src/pages/business/ShowCashoutDetails.tsx
@@ -14,6 +14,7 @@
  GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 import {
+  Amounts,
   TalerError,
   TranslatedString
 } from "@gnu-taler/taler-util";
@@ -32,7 +33,7 @@ import { ShowInputErrorLabel } from 
"@gnu-taler/web-util/browser";
 import { useBankCoreApiContext } from "../../context/config.js";
 import { useBackendState } from "../../hooks/backend.js";
 import {
-  useCashoutDetails
+  useCashoutDetails, useConversionInfo
 } from "../../hooks/circuit.js";
 import {
   undefinedIfEmpty,
@@ -40,6 +41,7 @@ import {
 } from "../../utils.js";
 import { assertUnreachable } from "../WithdrawalOperationPage.js";
 import { LocalNotificationBanner } from "@gnu-taler/web-util/browser";
+import { RenderAmount } from "../PaytoWireTransferForm.js";
 
 interface Props {
   id: string;
@@ -58,7 +60,12 @@ export function ShowCashoutDetails({
   const result = useCashoutDetails(Number.isNaN(cid) ? undefined : cid);
   const [code, setCode] = useState<string | undefined>(undefined);
   const [notification, notify, handleError] = useLocalNotification()
+  const info = useConversionInfo();
 
+  if (Number.isNaN(cid)) {
+    //TODO: better error message
+    return <div>cashout id should be a number</div>
+  }
   if (!result) {
     return <Loading />
   }
@@ -74,206 +81,252 @@ export function ShowCashoutDetails({
       default: assertUnreachable(result)
     }
   }
-  if (Number.isNaN(cid)) {
-    //TODO: better error message
-    return <div>cashout id should be a number</div>
+  if (!info) {
+    return <Loading />
+  }
+
+  if (info instanceof TalerError) {
+    return <ErrorLoading error={info} />
   }
+
   const errors = undefinedIfEmpty({
     code: !code ? i18n.str`required` : undefined,
   });
   const isPending = String(result.body.status).toUpperCase() === "PENDING";
+  const { fiat_currency_specification, regional_currency_specification } = 
info.body
+  async function doAbortCashout() {
+    if (!creds) return;
+    await handleError(async () => {
+      const resp = await api.abortCashoutById(creds, cid);
+      if (resp.type === "ok") {
+        onCancel();
+      } else {
+        switch (resp.case) {
+          case "not-found": return notify({
+            type: "error",
+            title: i18n.str`Cashout not found. It may be also mean that it was 
already aborted.`,
+            description: resp.detail.hint as TranslatedString,
+            debug: resp.detail,
+          })
+          case "already-confirmed": return notify({
+            type: "error",
+            title: i18n.str`Cashout was already confimed.`,
+            description: resp.detail.hint as TranslatedString,
+            debug: resp.detail,
+          })
+          case "cashout-not-supported": return notify({
+            type: "error",
+            title: i18n.str`Cashout operation is not supported.`,
+            description: resp.detail.hint as TranslatedString,
+            debug: resp.detail,
+          })
+          default: {
+            assertUnreachable(resp)
+          }
+        }
+      }
+    })
+  }
+  async function doConfirmCashout() {
+    if (!creds || !code) return;
+    await handleError(async () => {
+      const resp = await api.confirmCashoutById(creds, cid, {
+        tan: code,
+      });
+      if (resp.type === "ok") {
+        mutate(() => true)//clean cashout state
+      } else {
+        switch (resp.case) {
+          case "not-found": return notify({
+            type: "error",
+            title: i18n.str`Cashout not found. It may be also mean that it was 
already aborted.`,
+            description: resp.detail.hint as TranslatedString,
+            debug: resp.detail,
+          })
+          case "no-enough-balance": return notify({
+            type: "error",
+            title: i18n.str`The account does not have sufficient funds`,
+            description: resp.detail.hint as TranslatedString,
+            debug: resp.detail,
+          });
+          case "incorrect-exchange-rate": return notify({
+            type: "error",
+            title: i18n.str`The exchange rate was incorrectly applied`,
+            description: resp.detail.hint as TranslatedString,
+            debug: resp.detail,
+          });
+          case "already-aborted": return notify({
+            type: "error",
+            title: i18n.str`The cashout operation is already aborted.`,
+            description: resp.detail.hint as TranslatedString,
+            debug: resp.detail,
+          });
+          case "no-cashout-payto": return notify({
+            type: "error",
+            title: i18n.str`Missing destination account.`,
+            description: resp.detail.hint as TranslatedString,
+            debug: resp.detail,
+          })
+          case "too-many-attempts": return notify({
+            type: "error",
+            title: i18n.str`Too many failed attempts.`,
+            description: resp.detail.hint as TranslatedString,
+            debug: resp.detail,
+          })
+          case "cashout-not-supported": return notify({
+            type: "error",
+            title: i18n.str`Cashout operation is not supported.`,
+            description: resp.detail.hint as TranslatedString,
+            debug: resp.detail,
+          })
+          case "invalid-code": return notify({
+            type: "error",
+            title: i18n.str`The code for this cashout is invalid.`,
+            description: resp.detail.hint as TranslatedString,
+            debug: resp.detail,
+          })
+          default: assertUnreachable(resp)
+        }
+      }
+    })
+  }
+
   return (
     <div>
       <LocalNotificationBanner notification={notification} />
-      <h1>Cashout details {id}</h1>
-      <form class="pure-form">
-        <fieldset>
-          <label>
-            <i18n.Translate>Subject</i18n.Translate>
-          </label>
-          <input readOnly value={result.body.subject} />
-        </fieldset>
-        <fieldset>
-          <label>
-            <i18n.Translate>Created</i18n.Translate>
-          </label>
-          <input readOnly value={result.body.creation_time.t_s === "never" ? 
i18n.str`never` : format(result.body.creation_time.t_s, "dd/MM/yyyy HH:mm:ss")} 
/>
-        </fieldset>
-        <fieldset>
-          <label>
-            <i18n.Translate>Confirmed</i18n.Translate>
-          </label>
-          <input readOnly value={result.body.confirmation_time === undefined ? 
"-" :
-            (result.body.confirmation_time.t_s === "never" ?
-              i18n.str`never` :
-              format(result.body.confirmation_time.t_s, "dd/MM/yyyy HH:mm:ss"))
-          } />
-        </fieldset>
-        <fieldset>
-          <label>
-            <i18n.Translate>Debited</i18n.Translate>
-          </label>
-          <input readOnly value={result.body.amount_debit} />
-        </fieldset>
-        <fieldset>
-          <label>
-            <i18n.Translate>Credit</i18n.Translate>
-          </label>
-          <input readOnly value={result.body.amount_credit} />
-        </fieldset>
-        <fieldset>
-          <label>
-            <i18n.Translate>Status</i18n.Translate>
-          </label>
-          <input readOnly value={result.body.status} />
-        </fieldset>
-        {/* <fieldset>
-          <label>
-            <i18n.Translate>Destination</i18n.Translate>
-          </label>
-          <input readOnly value={result.body.credit_payto_uri} />
-        </fieldset> */}
-        {isPending ? (
-          <fieldset>
-            <label>
-              <i18n.Translate>Code</i18n.Translate>
-            </label>
-            <input
-              value={code ?? ""}
-              onChange={(e) => {
-                setCode(e.currentTarget.value);
+      <div class="grid grid-cols-1 gap-x-8 gap-y-8 pt-10 md:grid-cols-3 
bg-gray-100 my-4 px-4 pb-4 rounded-lg">
+
+        <section class="rounded-sm px-4">
+          <h2 id="summary-heading" class="font-medium 
text-lg"><i18n.Translate>Cashout detail</i18n.Translate></h2>
+          <dl class="mt-8 space-y-4">
+            <div class="justify-between items-center flex">
+              <dt class="text-sm 
text-gray-600"><i18n.Translate>Subject</i18n.Translate></dt>
+              <dd class="text-sm ">{result.body.subject}</dd>
+            </div>
+
+
+            <div class="flex items-center justify-between border-t-2 afu pt-4">
+              <dt class="flex items-center text-sm text-gray-600">
+                <span><i18n.Translate>Status</i18n.Translate></span>
+              </dt>
+              <dd data-status={result.body.status} class="text-sm uppercase 
data-[status=pending]:text-yellow-600 data-[status=aborted]:text-red-600 
data-[status=confirmed]:text-green-600" >
+                {result.body.status}
+              </dd>
+            </div>
+          </dl>
+        </section>
+        <div class="bg-white shadow-sm ring-1 ring-gray-900/5 sm:rounded-xl 
md:col-span-2">
+          <div class="px-4 py-6 sm:p-8">
+            <div class="grid max-w-2xl grid-cols-1 gap-x-6 gap-y-8 
sm:grid-cols-6">
+              <div class="sm:col-span-5">
+                <dl class="space-y-4">
+
+                  {result.body.creation_time.t_s !== "never" ?
+                    <div class="justify-between items-center flex ">
+                      <dt class=" 
text-gray-600"><i18n.Translate>Created</i18n.Translate></dt>
+                      <dd class="text-sm ">
+                        {format(result.body.creation_time.t_s * 1000, 
"dd/MM/yyyy HH:mm:ss")}
+                      </dd>
+                    </div>
+                    : undefined}
+
+                  <div class="flex justify-between items-center border-t-2 afu 
pt-4">
+                    <dt 
class="text-gray-600"><i18n.Translate>Debited</i18n.Translate></dt>
+                    <dd class=" font-medium">
+                      <RenderAmount 
value={Amounts.parseOrThrow(result.body.amount_debit)} negative withColor 
spec={regional_currency_specification} />
+                    </dd>
+                  </div>
+
+                  <div class="flex items-center justify-between border-t-2 afu 
pt-4">
+                    <dt class="flex items-center text-gray-600">
+                      <span><i18n.Translate>Credited</i18n.Translate></span>
+
+                    </dt>
+                    <dd class="text-sm ">
+                      <RenderAmount 
value={Amounts.parseOrThrow(result.body.amount_credit)} withColor 
spec={fiat_currency_specification} />
+                    </dd>
+                  </div>
+
+                  {result.body.confirmation_time && 
result.body.confirmation_time.t_s !== "never" ?
+                    <div class="flex justify-between items-center border-t-2 
afu pt-4">
+                      <dt class="  
font-medium"><i18n.Translate>Confirmed</i18n.Translate></dt>
+                      <dd class="  font-medium">
+                        {format(result.body.confirmation_time.t_s * 1000, 
"dd/MM/yyyy HH:mm:ss")}
+                      </dd>
+                    </div>
+                    : undefined}
+                </dl>
+              </div>
+            </div>
+          </div>
+
+        </div>
+
+        {!isPending ? undefined :
+          <Fragment>
+
+            <div />
+            <form
+              class="bg-white shadow-sm ring-1 ring-gray-900/5"
+              autoCapitalize="none"
+              autoCorrect="off"
+              onSubmit={e => {
+                e.preventDefault()
               }}
-            />
-            <ShowInputErrorLabel
-              message={errors?.code}
-              isDirty={code !== undefined}
-            />
-          </fieldset>
-        ) : undefined}
-      </form>
+            >
+              <div class="px-4 py-6 sm:p-8">
+                <label for="withdraw-amount">
+                  Enter the confirmation code
+                </label>
+                <div class="mt-2">
+                  <div class="relative rounded-md shadow-sm">
+                    <input
+                      type="text"
+                      // class="block w-full rounded-md border-0 py-1.5 pl-16 
text-gray-900 ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 
focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
+                      aria-describedby="answer"
+                      autoFocus
+                      class="block w-full rounded-md border-0 py-1.5 
text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 
placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 
sm:text-sm sm:leading-6"
+                      value={code ?? ""}
+                      required
+
+                      name="answer"
+                      id="answer"
+                      autocomplete="off"
+                      onChange={(e): void => {
+                        setCode(e.currentTarget.value)
+                      }}
+                    />
+                  </div>
+                  <ShowInputErrorLabel message={errors?.code} isDirty={code 
!== undefined} />
+                </div>
+              </div>
+              <div class="flex items-center justify-between gap-x-6 border-t 
border-gray-900/10 px-4 py-4 sm:px-8">
+                <button type="button"
+                  class="inline-flex items-center rounded-md bg-red-600 px-3 
py-2 text-sm font-semibold text-white shadow-sm hover:bg-red-500 
focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 
focus-visible:outline-red-500"
+                  onClick={doAbortCashout}
+                >
+                  <i18n.Translate>Abort</i18n.Translate></button>
+                <button type="submit"
+                  class="disabled:opacity-50 disabled:cursor-default 
cursor-pointer rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold 
text-white shadow-sm hover:bg-indigo-500 focus-visible:outline 
focus-visible:outline-2 focus-visible:outline-offset-2 
focus-visible:outline-indigo-600"
+                  disabled={!!errors}
+                  onClick={(e) => {
+                    doConfirmCashout()
+                  }}
+                >
+                  <i18n.Translate>Confirm</i18n.Translate>
+                </button>
+              </div>
+
+            </form>
+          </Fragment>}
+      </div>
+
       <br />
       <div style={{ display: "flex", justifyContent: "space-between" }}>
-        <button
-          class="pure-button pure-button-secondary btn-cancel"
-          onClick={(e) => {
-            e.preventDefault();
-            onCancel();
-          }}
+        <button type="button" class="text-sm font-semibold leading-6 
text-gray-900"
+          onClick={onCancel}
         >
-          {i18n.str`Back`}
-        </button>
-        {isPending ? (
-          <div>
-            <button
-              type="submit"
-              class="pure-button pure-button-primary button-error"
-              onClick={async (e) => {
-                e.preventDefault();
-                if (!creds) return;
-                await handleError(async () => {
-                  const resp = await api.abortCashoutById(creds, cid);
-                  if (resp.type === "ok") {
-                    onCancel();
-                  } else {
-                    switch (resp.case) {
-                      case "not-found": return notify({
-                        type: "error",
-                        title: i18n.str`Cashout not found. It may be also mean 
that it was already aborted.`,
-                        description: resp.detail.hint as TranslatedString,
-                        debug: resp.detail,
-                      })
-                      case "already-confirmed": return notify({
-                        type: "error",
-                        title: i18n.str`Cashout was already confimed.`,
-                        description: resp.detail.hint as TranslatedString,
-                        debug: resp.detail,
-                      })
-                      case "cashout-not-supported": return notify({
-                        type: "error",
-                        title: i18n.str`Cashout operation is not supported.`,
-                        description: resp.detail.hint as TranslatedString,
-                        debug: resp.detail,
-                      })
-                      default: {
-                        assertUnreachable(resp)
-                      }
-                    }
-                  }
-                })
-              }}
-            >
-              {i18n.str`Abort`}
-            </button>
-            &nbsp;
-            <button
-              type="submit"
-              disabled={!code}
-              class="pure-button pure-button-primary "
-              onClick={async (e) => {
-                e.preventDefault();
-                if (!creds || !code) return;
-                await handleError(async () => {
-                  const resp = await api.confirmCashoutById(creds, cid, {
-                    tan: code,
-                  });
-                  if (resp.type === "ok") {
-                    mutate(() => true)//clean cashout state
-                  } else {
-                    switch (resp.case) {
-                      case "not-found": return notify({
-                        type: "error",
-                        title: i18n.str`Cashout not found. It may be also mean 
that it was already aborted.`,
-                        description: resp.detail.hint as TranslatedString,
-                        debug: resp.detail,
-                      })
-                      case "no-enough-balance": return notify({
-                        type: "error",
-                        title: i18n.str`The account does not have sufficient 
funds`,
-                        description: resp.detail.hint as TranslatedString,
-                        debug: resp.detail,
-                      });
-                      case "incorrect-exchange-rate": return notify({
-                        type: "error",
-                        title: i18n.str`The exchange rate was incorrectly 
applied`,
-                        description: resp.detail.hint as TranslatedString,
-                        debug: resp.detail,
-                      });
-                      case "already-aborted": return notify({
-                        type: "error",
-                        title: i18n.str`The cashout operation is already 
aborted.`,
-                        description: resp.detail.hint as TranslatedString,
-                        debug: resp.detail,
-                      });
-                      case "no-cashout-payto": return notify({
-                        type: "error",
-                        title: i18n.str`Missing destination account.`,
-                        description: resp.detail.hint as TranslatedString,
-                        debug: resp.detail,
-                      })
-                      case "too-many-attempts": return notify({
-                        type: "error",
-                        title: i18n.str`Too many failed attempts.`,
-                        description: resp.detail.hint as TranslatedString,
-                        debug: resp.detail,
-                      })
-                      case "cashout-not-supported": return notify({
-                        type: "error",
-                        title: i18n.str`Cashout operation is not supported.`,
-                        description: resp.detail.hint as TranslatedString,
-                        debug: resp.detail,
-                      })
-                      default: assertUnreachable(resp)
-                    }
-                  }
-                })
-              }}
-            >
-              {i18n.str`Confirm`}
-            </button>
-          </div>
-        ) : (
-          <div />
-        )}
+          <i18n.Translate>Cancel</i18n.Translate></button>
       </div>
     </div>
   );
diff --git a/packages/taler-util/src/http-client/bank-core.ts 
b/packages/taler-util/src/http-client/bank-core.ts
index 273fb97c6..9d1a18e04 100644
--- a/packages/taler-util/src/http-client/bank-core.ts
+++ b/packages/taler-util/src/http-client/bank-core.ts
@@ -508,7 +508,7 @@ export class TalerCoreBankHttpClient {
           case TalerErrorCode.BANK_CONFIRM_INCOMPLETE: return 
opKnownFailure("no-cashout-payto", resp);
           case TalerErrorCode.BANK_UNALLOWED_DEBIT: return 
opKnownFailure("no-enough-balance", resp);
           case TalerErrorCode.BANK_BAD_CONVERSION: return 
opKnownFailure("incorrect-exchange-rate", resp);
-          // case TalerErrorCode.BANK_TAN_CHALLENGE_FAILED: return 
opKnownFailure("no-enough-balance", resp);
+          case TalerErrorCode.BANK_TAN_CHALLENGE_FAILED: return 
opKnownFailure("invalid-code", resp);
           default: return opUnknownFailure(resp, body)
         }
       }
diff --git a/packages/taler-util/src/taler-error-codes.ts 
b/packages/taler-util/src/taler-error-codes.ts
index 979fb332e..694adff8f 100644
--- a/packages/taler-util/src/taler-error-codes.ts
+++ b/packages/taler-util/src/taler-error-codes.ts
@@ -664,6 +664,14 @@ export enum TalerErrorCode {
   EXCHANGE_GENERIC_AML_FROZEN = 1036,
 
 
+  /**
+   * The exchange failed to start a KYC attribute conversion helper process. 
It is likely configured incorrectly.
+   * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR 
(500).
+   * (A value of 0 indicates that the error is generated client-side).
+   */
+  EXCHANGE_GENERIC_KYC_CONVERTER_FAILED = 1037,
+
+
   /**
    * The exchange did not find information about the specified transaction in 
the database.
    * Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404).
@@ -3392,6 +3400,78 @@ export enum TalerErrorCode {
   BANK_CONFIRM_INCOMPLETE = 5130,
 
 
+  /**
+   * The request rate is too high. The server is refusing requests to guard 
against brute-force attacks.
+   * Returned with an HTTP status code of #MHD_HTTP_TOO_MANY_REQUESTS (429).
+   * (A value of 0 indicates that the error is generated client-side).
+   */
+  BANK_TAN_RATE_LIMITED = 5131,
+
+
+  /**
+   * This TAN channel is not supported.
+   * Returned with an HTTP status code of #MHD_HTTP_NOT_IMPLEMENTED (501).
+   * (A value of 0 indicates that the error is generated client-side).
+   */
+  BANK_TAN_CHANNEL_NOT_SUPPORTED = 5132,
+
+
+  /**
+   * Failed to send TAN using the helper script. Either script is not found, 
or script timeout, or script terminated with a non-successful result.
+   * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR 
(500).
+   * (A value of 0 indicates that the error is generated client-side).
+   */
+  BANK_TAN_CHANNEL_SCRIPT_FAILED = 5133,
+
+
+  /**
+   * The client's response to the challenge was invalid.
+   * Returned with an HTTP status code of #MHD_HTTP_FORBIDDEN (403).
+   * (A value of 0 indicates that the error is generated client-side).
+   */
+  BANK_TAN_CHALLENGE_FAILED = 5134,
+
+
+  /**
+   * A non-admin user has tried to change their legal name.
+   * Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
+   * (A value of 0 indicates that the error is generated client-side).
+   */
+  BANK_NON_ADMIN_PATCH_LEGAL_NAME = 5135,
+
+
+  /**
+   * A non-admin user has tried to change their debt limit.
+   * Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
+   * (A value of 0 indicates that the error is generated client-side).
+   */
+  BANK_NON_ADMIN_PATCH_DEBT_LIMIT = 5136,
+
+
+  /**
+   * A non-admin user has tried to change their password whihout providing the 
current one.
+   * Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
+   * (A value of 0 indicates that the error is generated client-side).
+   */
+  BANK_NON_ADMIN_PATCH_MISSING_OLD_PASSWORD = 5137,
+
+
+  /**
+   * Provided old password does not match current password.
+   * Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
+   * (A value of 0 indicates that the error is generated client-side).
+   */
+  BANK_PATCH_BAD_OLD_PASSWORD = 5138,
+
+
+  /**
+   * An admin user has tried to become an exchange.
+   * Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
+   * (A value of 0 indicates that the error is generated client-side).
+   */
+  BANK_PATCH_ADMIN_EXCHANGE = 5139,
+
+
   /**
    * The sync service failed find the account in its database.
    * Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404).

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]