gnunet-svn
[Top][All Lists]
Advanced

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

[taler-wallet-core] branch master updated: prepare for 2fa impl


From: gnunet
Subject: [taler-wallet-core] branch master updated: prepare for 2fa impl
Date: Tue, 09 Jan 2024 22:51:57 +0100

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

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

The following commit(s) were added to refs/heads/master by this push:
     new f5771cc7b prepare for 2fa impl
f5771cc7b is described below

commit f5771cc7b99dc938fd606dcbee350b66ec8027c9
Author: Sebastian <sebasjm@gmail.com>
AuthorDate: Tue Jan 9 18:51:49 2024 -0300

    prepare for 2fa impl
---
 .../demobank-ui/src/components/Cashouts/views.tsx  |   6 +-
 packages/demobank-ui/src/context/config.ts         |  21 ++-
 .../demobank-ui/src/pages/AccountPage/state.ts     |   6 +-
 .../demobank-ui/src/pages/OperationState/state.ts  |   6 +-
 .../demobank-ui/src/pages/OperationState/views.tsx |  30 ++--
 .../src/pages/PaytoWireTransferForm.tsx            |  14 +-
 packages/demobank-ui/src/pages/QrCodeSection.tsx   |   7 +-
 .../demobank-ui/src/pages/RegistrationPage.tsx     |  16 +-
 .../demobank-ui/src/pages/WalletWithdrawForm.tsx   |   7 +-
 packages/demobank-ui/src/pages/WireTransfer.tsx    |   6 +-
 .../src/pages/WithdrawalConfirmationQuestion.tsx   |  22 +--
 .../demobank-ui/src/pages/WithdrawalQRCode.tsx     |   5 +-
 .../src/pages/account/ShowAccountDetails.tsx       |  21 +--
 .../src/pages/account/UpdateAccountPassword.tsx    |   9 +-
 .../demobank-ui/src/pages/admin/AccountForm.tsx    |   6 +-
 .../demobank-ui/src/pages/admin/AccountList.tsx    |   4 +-
 .../src/pages/admin/CreateNewAccount.tsx           |  18 +-
 .../demobank-ui/src/pages/admin/RemoveAccount.tsx  |  14 +-
 .../src/pages/business/CreateCashout.tsx           |  44 +++--
 .../src/pages/business/ShowCashoutDetails.tsx      |  29 ++--
 packages/taler-util/src/http-client/bank-core.ts   | 182 +++++++++++----------
 packages/taler-util/src/operation.ts               |  10 +-
 22 files changed, 276 insertions(+), 207 deletions(-)

diff --git a/packages/demobank-ui/src/components/Cashouts/views.tsx 
b/packages/demobank-ui/src/components/Cashouts/views.tsx
index 341c43b48..1f1f02e4e 100644
--- a/packages/demobank-ui/src/components/Cashouts/views.tsx
+++ b/packages/demobank-ui/src/components/Cashouts/views.tsx
@@ -14,7 +14,7 @@
  GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 
-import { Amounts, TalerError, assertUnreachable } from "@gnu-taler/taler-util";
+import { Amounts, HttpStatusCode, TalerError, assertUnreachable } from 
"@gnu-taler/taler-util";
 import { Attention, Loading, useTranslationContext } from 
"@gnu-taler/web-util/browser";
 import { format } from "date-fns";
 import { Fragment, VNode, h } from "preact";
@@ -26,13 +26,13 @@ import { State } from "./index.js";
 export function FailedView({ error }: State.Failed) {
   const { i18n } = useTranslationContext();
   switch (error.case) {
-    case "cashout-not-supported": return <Attention type="danger"
+    case HttpStatusCode.NotImplemented: return <Attention type="danger"
       title={i18n.str`Cashout not supported.`}>
       <div class="mt-2 text-sm text-red-700">
         {error.detail.hint}
       </div>
     </Attention>
-    case "account-not-found": return <Attention type="danger"
+    case HttpStatusCode.NotFound: return <Attention type="danger"
       title={i18n.str`Account not found.`}>
       <div class="mt-2 text-sm text-red-700">
         {error.detail.hint}
diff --git a/packages/demobank-ui/src/context/config.ts 
b/packages/demobank-ui/src/context/config.ts
index 9908c73a2..2d70cf932 100644
--- a/packages/demobank-ui/src/context/config.ts
+++ b/packages/demobank-ui/src/context/config.ts
@@ -14,7 +14,7 @@
  GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 
-import { TalerCorebankApi, TalerCoreBankHttpClient, TalerError } from 
"@gnu-taler/taler-util";
+import { LibtoolVersion, TalerCorebankApi, TalerCoreBankHttpClient, TalerError 
} from "@gnu-taler/taler-util";
 import { BrowserHttpLib, ErrorLoading, useTranslationContext } from 
"@gnu-taler/web-util/browser";
 import { ComponentChildren, createContext, FunctionComponent, h, VNode } from 
"preact";
 import { useContext, useEffect, useState } from "preact/hooks";
@@ -34,8 +34,15 @@ const Context = createContext<Type>(undefined as any);
 
 export const useBankCoreApiContext = (): Type => useContext(Context);
 
+enum VersionHint {
+  /**
+   * when this flag is on, server is running an old version with cashout 
before implementing 2fa API
+   */
+  CASHOUT_BEFORE_2FA,
+}
+
 export type ConfigResult = undefined
-  | { type: "ok", config: TalerCorebankApi.Config }
+  | { type: "ok", config: TalerCorebankApi.Config, hint: VersionHint[] }
   | { type: "incompatible", result: TalerCorebankApi.Config, supported: string 
}
   | { type: "error", error: TalerError }
 
@@ -56,9 +63,15 @@ export const BankCoreApiProvider = ({
     api.getConfig()
       .then((resp) => {
         if (api.isCompatible(resp.body.version)) {
-          setChecked({ type: "ok", config: resp.body });
+          setChecked({ type: "ok", config: resp.body, hint: [] });
         } else {
-          setChecked({ type: "incompatible", result: resp.body, supported: 
api.PROTOCOL_VERSION })
+          //this API supports version 3.0.3
+          const compare = LibtoolVersion.compare("3:0:3", resp.body.version)
+          if (compare?.compatible ?? false) {
+            setChecked({ type: "ok", config: resp.body, hint: 
[VersionHint.CASHOUT_BEFORE_2FA] });
+          } else {
+            setChecked({ type: "incompatible", result: resp.body, supported: 
api.PROTOCOL_VERSION })
+          }
         }
       })
       .catch((error: unknown) => {
diff --git a/packages/demobank-ui/src/pages/AccountPage/state.ts 
b/packages/demobank-ui/src/pages/AccountPage/state.ts
index d28aba7bf..88e8cf747 100644
--- a/packages/demobank-ui/src/pages/AccountPage/state.ts
+++ b/packages/demobank-ui/src/pages/AccountPage/state.ts
@@ -14,7 +14,7 @@
  GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 
-import { Amounts, TalerError, parsePaytoUri } from "@gnu-taler/taler-util";
+import { Amounts, HttpStatusCode, TalerError, parsePaytoUri } from 
"@gnu-taler/taler-util";
 import { useTranslationContext } from "@gnu-taler/web-util/browser";
 import { useAccountDetails } from "../../hooks/access.js";
 import { assertUnreachable } from "../WithdrawalOperationPage.js";
@@ -40,11 +40,11 @@ export function useComponentState({ account, 
goToConfirmOperation }: Props): Sta
 
   if (result.type === "fail") {
     switch (result.case) {
-      case "unauthorized": return {
+      case HttpStatusCode.Unauthorized: return {
         status: "login",
         reason: "forbidden"
       }
-      case "not-found": return {
+      case HttpStatusCode.NotFound: return {
         status: "login",
         reason: "not-found",
       }
diff --git a/packages/demobank-ui/src/pages/OperationState/state.ts 
b/packages/demobank-ui/src/pages/OperationState/state.ts
index defca6f13..477146d1e 100644
--- a/packages/demobank-ui/src/pages/OperationState/state.ts
+++ b/packages/demobank-ui/src/pages/OperationState/state.ts
@@ -14,7 +14,7 @@
  GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 
-import { Amounts, FailCasesByMethod, TalerCoreBankErrorsByMethod, TalerError, 
TalerErrorDetail, TranslatedString, parsePaytoUri, parseWithdrawUri, 
stringifyWithdrawUri } from "@gnu-taler/taler-util";
+import { Amounts, FailCasesByMethod, HttpStatusCode, 
TalerCoreBankErrorsByMethod, TalerError, TalerErrorDetail, TranslatedString, 
parsePaytoUri, parseWithdrawUri, stringifyWithdrawUri } from 
"@gnu-taler/taler-util";
 import { notify, notifyError, notifyInfo, useTranslationContext, utils } from 
"@gnu-taler/web-util/browser";
 import { useEffect, useState } from "preact/hooks";
 import { mutate } from "swr";
@@ -134,8 +134,8 @@ export function useComponentState({ currency, onClose }: 
Props): utils.Recursive
 
     if (result.type === "fail") {
       switch (result.case) {
-        case "invalid-id":
-        case "not-found": {
+        case HttpStatusCode.BadRequest:
+        case HttpStatusCode.NotFound: {
           return {
             status: "aborted",
             error: undefined,
diff --git a/packages/demobank-ui/src/pages/OperationState/views.tsx 
b/packages/demobank-ui/src/pages/OperationState/views.tsx
index b1f09ba2a..a02bb3bbd 100644
--- a/packages/demobank-ui/src/pages/OperationState/views.tsx
+++ b/packages/demobank-ui/src/pages/OperationState/views.tsx
@@ -14,7 +14,7 @@
  GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 
-import { TranslatedString, stringifyWithdrawUri } from "@gnu-taler/taler-util";
+import { HttpStatusCode, TalerErrorCode, TranslatedString, 
stringifyWithdrawUri } from "@gnu-taler/taler-util";
 import { Attention, LocalNotificationBanner, ShowInputErrorLabel, notifyInfo, 
useLocalNotification, useTranslationContext } from 
"@gnu-taler/web-util/browser";
 import { Fragment, VNode, h } from "preact";
 import { useEffect, useMemo, useState } from "preact/hooks";
@@ -70,19 +70,19 @@ export function NeedConfirmationView({ error, onAbort: 
doAbort, onConfirm: doCon
       const resp = await doAbort()
       if (!resp) return;
       switch (resp.case) {
-        case "previously-confirmed": return notify({
+        case HttpStatusCode.Conflict: return notify({
           type: "error",
           title: i18n.str`The reserve operation has been confirmed previously 
and can't be aborted`,
           description: resp.detail.hint as TranslatedString,
           debug: resp.detail,
         })
-        case "invalid-id": return notify({
+        case HttpStatusCode.BadRequest: return notify({
           type: "error",
           title: i18n.str`The operation id is invalid.`,
           description: resp.detail.hint as TranslatedString,
           debug: resp.detail,
         });
-        case "not-found": return notify({
+        case HttpStatusCode.NotFound: return notify({
           type: "error",
           title: i18n.str`The operation was not found.`,
           description: resp.detail.hint as TranslatedString,
@@ -104,31 +104,31 @@ export function NeedConfirmationView({ error, onAbort: 
doAbort, onConfirm: doCon
         return
       }
       switch (hasError.case) {
-        case "previously-aborted": return notify({
+        case TalerErrorCode.BANK_CONFIRM_ABORT_CONFLICT: return notify({
           type: "error",
           title: i18n.str`The withdrawal has been aborted previously and can't 
be confirmed`,
           description: hasError.detail.hint as TranslatedString,
           debug: hasError.detail,
         })
-        case "no-exchange-or-reserve-selected": return notify({
+        case TalerErrorCode.BANK_CONFIRM_INCOMPLETE: return notify({
           type: "error",
           title: i18n.str`The withdraw operation cannot be confirmed because 
no exchange and reserve public key selection happened before`,
           description: hasError.detail.hint as TranslatedString,
           debug: hasError.detail,
         })
-        case "invalid-id": return notify({
+        case HttpStatusCode.BadRequest: return notify({
           type: "error",
           title: i18n.str`The operation id is invalid.`,
           description: hasError.detail.hint as TranslatedString,
           debug: hasError.detail,
         });
-        case "not-found": return notify({
+        case HttpStatusCode.NotFound: return notify({
           type: "error",
           title: i18n.str`The operation was not found.`,
           description: hasError.detail.hint as TranslatedString,
           debug: hasError.detail,
         });
-        case "insufficient-funds": return notify({
+        case TalerErrorCode.BANK_UNALLOWED_DEBIT: return notify({
           type: "error",
           title: i18n.str`Your balance is not enough.`,
           description: hasError.detail.hint as TranslatedString,
@@ -215,19 +215,19 @@ export function NeedConfirmationView({ error, onAbort: 
doAbort, onConfirm: doCon
 export function FailedView({ error }: State.Failed) {
   const { i18n } = useTranslationContext();
   switch (error.case) {
-    case "unauthorized": return <Attention type="danger"
+    case HttpStatusCode.Unauthorized: return <Attention type="danger"
       title={i18n.str`Unauthorized to make the operation, maybe the session 
has expired or the password changed.`}>
       <div class="mt-2 text-sm text-red-700">
         {error.detail.hint}
       </div>
     </Attention>
-    case "insufficient-funds": return <Attention type="danger"
+    case HttpStatusCode.Conflict: return <Attention type="danger"
       title={i18n.str`The operation was rejected due to insufficient funds.`}>
       <div class="mt-2 text-sm text-red-700">
         {error.detail.hint}
       </div>
     </Attention>
-    case "account-not-found": return <Attention type="danger"
+    case HttpStatusCode.NotFound: return <Attention type="danger"
       title={i18n.str`The operation was rejected due to insufficient funds.`}>
       <div class="mt-2 text-sm text-red-700">
         {error.detail.hint}
@@ -322,19 +322,19 @@ export function ReadyView({ uri, onClose: doClose }: 
State.Ready): VNode<{}> {
       const hasError = await doClose()
       if (!hasError) return;
       switch (hasError.case) {
-        case "previously-confirmed": return notify({
+        case HttpStatusCode.Conflict: return notify({
           type: "error",
           title: i18n.str`The reserve operation has been confirmed previously 
and can't be aborted`,
           description: hasError.detail.hint as TranslatedString,
           debug: hasError.detail,
         })
-        case "invalid-id": return notify({
+        case HttpStatusCode.BadRequest: return notify({
           type: "error",
           title: i18n.str`The operation id is invalid.`,
           description: hasError.detail.hint as TranslatedString,
           debug: hasError.detail,
         });
-        case "not-found": return notify({
+        case HttpStatusCode.NotFound: return notify({
           type: "error",
           title: i18n.str`The operation was not found.`,
           description: hasError.detail.hint as TranslatedString,
diff --git a/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx 
b/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx
index c785b24df..7a94f5486 100644
--- a/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx
+++ b/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx
@@ -21,8 +21,10 @@ import {
   Amounts,
   CurrencySpecification,
   FRAC_SEPARATOR,
+  HttpStatusCode,
   Logger,
   PaytoString,
+  TalerErrorCode,
   TranslatedString,
   buildPayto,
   parsePaytoUri,
@@ -151,37 +153,37 @@ export function PaytoWireTransferForm({
       mutate(() => true)
       if (res.type === "fail") {
         switch (res.case) {
-          case "invalid-input": return notify({
+          case HttpStatusCode.BadRequest: return notify({
             type: "error",
             title: i18n.str`The request was invalid or the payto://-URI used 
unacceptable features.`,
             description: res.detail.hint as TranslatedString,
             debug: res.detail,
           })
-          case "unauthorized": return notify({
+          case HttpStatusCode.Unauthorized: return notify({
             type: "error",
             title: i18n.str`Not enough permission to complete the operation.`,
             description: res.detail.hint as TranslatedString,
             debug: res.detail,
           })
-          case "creditor-not-found": return notify({
+          case TalerErrorCode.BANK_UNKNOWN_CREDITOR: return notify({
             type: "error",
             title: i18n.str`The destination account "${puri}" was not found.`,
             description: res.detail.hint as TranslatedString,
             debug: res.detail,
           })
-          case "creditor-same": return notify({
+          case TalerErrorCode.BANK_SAME_ACCOUNT: return notify({
             type: "error",
             title: i18n.str`The origin and the destination of the transfer 
can't be the same.`,
             description: res.detail.hint as TranslatedString,
             debug: res.detail,
           })
-          case "insufficient-funds": return notify({
+          case TalerErrorCode.BANK_UNALLOWED_DEBIT: return notify({
             type: "error",
             title: i18n.str`Your balance is not enough.`,
             description: res.detail.hint as TranslatedString,
             debug: res.detail,
           })
-          case "not-found": return notify({
+          case HttpStatusCode.NotFound: return notify({
             type: "error",
             title: i18n.str`The origin account "${puri}" was not found.`,
             description: res.detail.hint as TranslatedString,
diff --git a/packages/demobank-ui/src/pages/QrCodeSection.tsx 
b/packages/demobank-ui/src/pages/QrCodeSection.tsx
index 60beb012e..64ebf7e83 100644
--- a/packages/demobank-ui/src/pages/QrCodeSection.tsx
+++ b/packages/demobank-ui/src/pages/QrCodeSection.tsx
@@ -15,6 +15,7 @@
  */
 
 import {
+  HttpStatusCode,
   stringifyWithdrawUri,
   TranslatedString,
   WithdrawUriResult
@@ -68,17 +69,17 @@ export function QrCodeSection({
         onAborted();
       } else {
         switch (resp.case) {
-          case "previously-confirmed": return notify({
+          case HttpStatusCode.Conflict: return notify({
             type: "error",
             title: i18n.str`The reserve operation has been confirmed 
previously and can't be aborted`
           })
-          case "invalid-id": return notify({
+          case HttpStatusCode.BadRequest: return notify({
             type: "error",
             title: i18n.str`The operation id is invalid.`,
             description: resp.detail.hint as TranslatedString,
             debug: resp.detail,
           })
-          case "not-found": return notify({
+          case HttpStatusCode.NotFound: return notify({
             type: "error",
             title: i18n.str`The operation was not found.`,
             description: resp.detail.hint as TranslatedString,
diff --git a/packages/demobank-ui/src/pages/RegistrationPage.tsx 
b/packages/demobank-ui/src/pages/RegistrationPage.tsx
index d20c402ac..89bfbcb35 100644
--- a/packages/demobank-ui/src/pages/RegistrationPage.tsx
+++ b/packages/demobank-ui/src/pages/RegistrationPage.tsx
@@ -13,7 +13,7 @@
  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 { AccessToken, Logger, TranslatedString } from "@gnu-taler/taler-util";
+import { AccessToken, HttpStatusCode, Logger, TalerErrorCode, TranslatedString 
} from "@gnu-taler/taler-util";
 import {
   LocalNotificationBanner,
   ShowInputErrorLabel,
@@ -101,43 +101,43 @@ function RegistrationForm({ onComplete, onCancel }: { 
onComplete: () => void, on
       const creationResponse = await api.createAccount("" as AccessToken, { 
name, username, password });
       if (creationResponse.type === "fail") {
         switch (creationResponse.case) {
-          case "invalid-phone-or-email": return notify({
+          case HttpStatusCode.BadRequest: return notify({
             type: "error",
             title: i18n.str`Server replied with invalid phone or email.`,
             description: creationResponse.detail.hint as TranslatedString,
             debug: creationResponse.detail,
           })
-          case "insufficient-funds": return notify({
+          case TalerErrorCode.BANK_UNALLOWED_DEBIT: return notify({
             type: "error",
             title: i18n.str`Registration is disabled because the bank ran out 
of bonus credit.`,
             description: creationResponse.detail.hint as TranslatedString,
             debug: creationResponse.detail,
           })
-          case "unauthorized": return notify({
+          case HttpStatusCode.Unauthorized: return notify({
             type: "error",
             title: i18n.str`No enough permission to create that account.`,
             description: creationResponse.detail.hint as TranslatedString,
             debug: creationResponse.detail,
           })
-          case "payto-already-exists": return notify({
+          case TalerErrorCode.BANK_REGISTER_PAYTO_URI_REUSE: return notify({
             type: "error",
             title: i18n.str`That account id is already taken.`,
             description: creationResponse.detail.hint as TranslatedString,
             debug: creationResponse.detail,
           })
-          case "username-already-exists": return notify({
+          case TalerErrorCode.BANK_REGISTER_USERNAME_REUSE: return notify({
             type: "error",
             title: i18n.str`That username is already taken.`,
             description: creationResponse.detail.hint as TranslatedString,
             debug: creationResponse.detail,
           })
-          case "username-reserved": return notify({
+          case TalerErrorCode.BANK_RESERVED_USERNAME_CONFLICT: return notify({
             type: "error",
             title: i18n.str`That username can't be used because is reserved.`,
             description: creationResponse.detail.hint as TranslatedString,
             debug: creationResponse.detail,
           })
-          case "user-cant-set-debt": return notify({
+          case TalerErrorCode.BANK_NON_ADMIN_PATCH_DEBT_LIMIT: return notify({
             type: "error",
             title: i18n.str`Only admin is allow to set debt limit.`,
             description: creationResponse.detail.hint as TranslatedString,
diff --git a/packages/demobank-ui/src/pages/WalletWithdrawForm.tsx 
b/packages/demobank-ui/src/pages/WalletWithdrawForm.tsx
index 5eef95f1e..bee36e7ad 100644
--- a/packages/demobank-ui/src/pages/WalletWithdrawForm.tsx
+++ b/packages/demobank-ui/src/pages/WalletWithdrawForm.tsx
@@ -17,6 +17,7 @@
 import {
   AmountJson,
   Amounts,
+  HttpStatusCode,
   Logger,
   TranslatedString,
   parseWithdrawUri
@@ -103,7 +104,7 @@ function OldWithdrawalForm({ goToConfirmOperation, limit, 
onCancel, focus }: {
         }
       } else {
         switch (resp.case) {
-          case "insufficient-funds": {
+          case HttpStatusCode.Conflict: {
             notify({
               type: "error",
               title: i18n.str`The operation was rejected due to insufficient 
funds`,
@@ -112,7 +113,7 @@ function OldWithdrawalForm({ goToConfirmOperation, limit, 
onCancel, focus }: {
             })
             break;
           }
-          case "unauthorized": {
+          case HttpStatusCode.Unauthorized: {
             notify({
               type: "error",
               title: i18n.str`The operation was rejected due to insufficient 
funds`,
@@ -121,7 +122,7 @@ function OldWithdrawalForm({ goToConfirmOperation, limit, 
onCancel, focus }: {
             })
             break;
           }
-          case "account-not-found": {
+          case HttpStatusCode.NotFound: {
             notify({
               type: "error",
               title: i18n.str`Account not found`,
diff --git a/packages/demobank-ui/src/pages/WireTransfer.tsx 
b/packages/demobank-ui/src/pages/WireTransfer.tsx
index 7648df482..d6133b504 100644
--- a/packages/demobank-ui/src/pages/WireTransfer.tsx
+++ b/packages/demobank-ui/src/pages/WireTransfer.tsx
@@ -1,4 +1,4 @@
-import { Amounts, TalerError } from "@gnu-taler/taler-util";
+import { Amounts, HttpStatusCode, TalerError } from "@gnu-taler/taler-util";
 import { Loading, notifyInfo, useTranslationContext } from 
"@gnu-taler/web-util/browser";
 import { Fragment, VNode, h } from "preact";
 import { ErrorLoadingWithDebug } from "../components/ErrorLoadingWithDebug.js";
@@ -22,8 +22,8 @@ export function WireTransfer({ toAccount, onRegister, 
onCancel, onSuccess }: { o
   }
   if (result.type === "fail") {
     switch (result.case) {
-      case "unauthorized": return <LoginForm currentUser={account} />
-      case "not-found": return <LoginForm currentUser={account} />
+      case HttpStatusCode.Unauthorized: return <LoginForm 
currentUser={account} />
+      case HttpStatusCode.NotFound: return <LoginForm currentUser={account} />
       default: assertUnreachable(result)
     }
   }
diff --git a/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx 
b/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx
index 43b88ccd8..ed1db854a 100644
--- a/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx
+++ b/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx
@@ -16,11 +16,13 @@
 
 import {
   AmountJson,
+  HttpStatusCode,
   Logger,
   PaytoUri,
   PaytoUriIBAN,
   PaytoUriTalerBank,
   TalerError,
+  TalerErrorCode,
   TranslatedString,
   WithdrawUriResult
 } from "@gnu-taler/taler-util";
@@ -81,8 +83,8 @@ export function WithdrawalConfirmationQuestion({
   }
   if (withdrawalInfo.type === "fail") {
     switch (withdrawalInfo.case) {
-      case "not-found": return <OperationNotFound onClose={onAborted} />
-      case "invalid-id": return <OperationNotFound onClose={onAborted} />
+      case HttpStatusCode.NotFound: return <OperationNotFound 
onClose={onAborted} />
+      case HttpStatusCode.BadRequest: return <OperationNotFound 
onClose={onAborted} />
       default: assertUnreachable(withdrawalInfo)
     }
   }
@@ -121,31 +123,31 @@ export function WithdrawalConfirmationQuestion({
         }
       } else {
         switch (resp.case) {
-          case "previously-aborted": return notify({
+          case TalerErrorCode.BANK_CONFIRM_ABORT_CONFLICT: return notify({
             type: "error",
             title: i18n.str`The withdrawal has been aborted previously and 
can't be confirmed`,
             description: resp.detail.hint as TranslatedString,
             debug: resp.detail,
           });
-          case "no-exchange-or-reserve-selected": return notify({
+          case TalerErrorCode.BANK_CONFIRM_INCOMPLETE: return notify({
             type: "error",
             title: i18n.str`The withdraw operation cannot be confirmed because 
no exchange and reserve public key selection happened before`,
             description: resp.detail.hint as TranslatedString,
             debug: resp.detail,
           });
-          case "invalid-id": return notify({
+          case HttpStatusCode.BadRequest: return notify({
             type: "error",
             title: i18n.str`The operation id is invalid.`,
             description: resp.detail.hint as TranslatedString,
             debug: resp.detail,
           })
-          case "not-found": return notify({
+          case HttpStatusCode.NotFound: return notify({
             type: "error",
             title: i18n.str`The operation was not found.`,
             description: resp.detail.hint as TranslatedString,
             debug: resp.detail,
           })
-          case "insufficient-funds": return notify({
+          case TalerErrorCode.BANK_UNALLOWED_DEBIT: return notify({
             type: "error",
             title: i18n.str`Your balance is not enough for the operation.`,
             description: resp.detail.hint as TranslatedString,
@@ -167,17 +169,17 @@ export function WithdrawalConfirmationQuestion({
         onAborted();
       } else {
         switch (resp.case) {
-          case "previously-confirmed": return notify({
+          case HttpStatusCode.Conflict: return notify({
             type: "error",
             title: i18n.str`The reserve operation has been confirmed 
previously and can't be aborted`
           });
-          case "invalid-id": return notify({
+          case HttpStatusCode.BadRequest: return notify({
             type: "error",
             title: i18n.str`The operation id is invalid.`,
             description: resp.detail.hint as TranslatedString,
             debug: resp.detail,
           })
-          case "not-found": return notify({
+          case HttpStatusCode.NotFound: return notify({
             type: "error",
             title: i18n.str`The operation was not found.`,
             description: resp.detail.hint as TranslatedString,
diff --git a/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx 
b/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx
index c5a558ab8..f05f183d4 100644
--- a/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx
+++ b/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx
@@ -16,6 +16,7 @@
 
 import {
   Amounts,
+  HttpStatusCode,
   Logger,
   TalerError,
   WithdrawUriResult,
@@ -55,8 +56,8 @@ export function WithdrawalQRCode({
   }
   if (result.type === "fail") {
     switch (result.case) {
-      case "invalid-id":
-      case "not-found": return <OperationNotFound onClose={onClose} />
+      case HttpStatusCode.BadRequest:
+      case HttpStatusCode.NotFound: return <OperationNotFound 
onClose={onClose} />
       default: assertUnreachable(result)
     }
   }
diff --git a/packages/demobank-ui/src/pages/account/ShowAccountDetails.tsx 
b/packages/demobank-ui/src/pages/account/ShowAccountDetails.tsx
index 4b66c0d8d..98fb72283 100644
--- a/packages/demobank-ui/src/pages/account/ShowAccountDetails.tsx
+++ b/packages/demobank-ui/src/pages/account/ShowAccountDetails.tsx
@@ -1,4 +1,4 @@
-import { TalerCorebankApi, TalerError, TranslatedString } from 
"@gnu-taler/taler-util";
+import { HttpStatusCode, TalerCorebankApi, TalerError, TalerErrorCode, 
TranslatedString } from "@gnu-taler/taler-util";
 import { Loading, LocalNotificationBanner, notifyInfo, useLocalNotification, 
useTranslationContext } from "@gnu-taler/web-util/browser";
 import { Fragment, VNode, h } from "preact";
 import { useState } from "preact/hooks";
@@ -40,8 +40,8 @@ export function ShowAccountDetails({
   }
   if (result.type === "fail") {
     switch (result.case) {
-      case "not-found": return <LoginForm currentUser={account} />
-      case "unauthorized": return <LoginForm currentUser={account} />
+      case HttpStatusCode.Unauthorized:
+      case HttpStatusCode.NotFound: return <LoginForm currentUser={account} />
       default: assertUnreachable(result)
     }
   }
@@ -52,6 +52,7 @@ export function ShowAccountDetails({
       const resp = await api.updateAccount({
         token: creds.token,
         username: account,
+
       }, submitAccount);
 
       if (resp.type === "ok") {
@@ -59,39 +60,39 @@ export function ShowAccountDetails({
         onUpdateSuccess();
       } else {
         switch (resp.case) {
-          case "unauthorized": return notify({
+          case HttpStatusCode.Unauthorized: return notify({
             type: "error",
             title: i18n.str`The rights to change the account are not 
sufficient`,
             description: resp.detail.hint as TranslatedString,
             debug: resp.detail,
           })
-          case "not-found": return notify({
+          case HttpStatusCode.NotFound: return notify({
             type: "error",
             title: i18n.str`The username was not found`,
             description: resp.detail.hint as TranslatedString,
             debug: resp.detail,
           })
-          case "user-cant-change-name": return notify({
+          case TalerErrorCode.BANK_NON_ADMIN_PATCH_LEGAL_NAME: return notify({
             type: "error",
             title: i18n.str`You can't change the legal name, please contact 
the your account administrator.`,
             description: resp.detail.hint as TranslatedString,
             debug: resp.detail,
           })
-          case "user-cant-change-debt": return notify({
+          case TalerErrorCode.BANK_NON_ADMIN_PATCH_DEBT_LIMIT: return notify({
             type: "error",
             title: i18n.str`You can't change the debt limit, please contact 
the your account administrator.`,
             description: resp.detail.hint as TranslatedString,
             debug: resp.detail,
           })
-          case "user-cant-change-cashout": return notify({
+          case TalerErrorCode.BANK_NON_ADMIN_PATCH_CASHOUT: return notify({
             type: "error",
             title: i18n.str`You can't change the cashout address, please 
contact the your account administrator.`,
             description: resp.detail.hint as TranslatedString,
             debug: resp.detail,
           })
-          case "missing-contact-data": return notify({
+          case TalerErrorCode.BANK_NON_ADMIN_PATCH_CONTACT: return notify({
             type: "error",
-            title: i18n.str`You need contact data to enable 2FA.`,
+            title: i18n.str`You can't change the contact data, please contact 
the your account administrator.`,
             description: resp.detail.hint as TranslatedString,
             debug: resp.detail,
           })
diff --git a/packages/demobank-ui/src/pages/account/UpdateAccountPassword.tsx 
b/packages/demobank-ui/src/pages/account/UpdateAccountPassword.tsx
index ece1f63e7..95c425dc7 100644
--- a/packages/demobank-ui/src/pages/account/UpdateAccountPassword.tsx
+++ b/packages/demobank-ui/src/pages/account/UpdateAccountPassword.tsx
@@ -9,6 +9,7 @@ import { doAutoFocus } from "../PaytoWireTransferForm.js";
 import { ProfileNavigation } from "../ProfileNavigation.js";
 import { assertUnreachable } from "../WithdrawalOperationPage.js";
 import { LocalNotificationBanner } from "@gnu-taler/web-util/browser";
+import { HttpStatusCode, TalerErrorCode } from "@gnu-taler/taler-util";
 
 export function UpdateAccountPassword({
   account: accountName,
@@ -57,19 +58,19 @@ export function UpdateAccountPassword({
         onUpdateSuccess();
       } else {
         switch (resp.case) {
-          case "unauthorized": return notify({
+          case HttpStatusCode.Unauthorized: return notify({
             type: "error",
             title: i18n.str`Not authorized to change the password, maybe the 
session is invalid.`
           })
-          case "not-found": return notify({
+          case HttpStatusCode.NotFound: return notify({
             type: "error",
             title: i18n.str`Account not found`
           })
-          case "user-require-old-password": return notify({
+          case TalerErrorCode.BANK_NON_ADMIN_PATCH_MISSING_OLD_PASSWORD: 
return notify({
             type: "error",
             title: i18n.str`Old password need to be provided in order to 
change new one. If you don't have it contact your account administrator.`
           })
-          case "wrong-old-password": return notify({
+          case TalerErrorCode.BANK_PATCH_BAD_OLD_PASSWORD: return notify({
             type: "error",
             title: i18n.str`Your current password doesn't match, can't change 
to a new password.`
           })
diff --git a/packages/demobank-ui/src/pages/admin/AccountForm.tsx 
b/packages/demobank-ui/src/pages/admin/AccountForm.tsx
index c64431918..859c04396 100644
--- a/packages/demobank-ui/src/pages/admin/AccountForm.tsx
+++ b/packages/demobank-ui/src/pages/admin/AccountForm.tsx
@@ -66,7 +66,7 @@ export function AccountForm<PurposeType extends keyof 
ChangeByPurposeType>({
 
 
   const defaultValue: AccountFormData = {
-    debit_threshold: Amounts.stringifyValue(template?.debit_threshold 
??config.default_debit_threshold),
+    debit_threshold: Amounts.stringifyValue(template?.debit_threshold ?? 
config.default_debit_threshold),
     isExchange: template?.is_taler_exchange,
     isPublic: template?.is_public,
     name: template?.name ?? "",
@@ -418,13 +418,13 @@ export function AccountForm<PurposeType extends keyof 
ChangeByPurposeType>({
                   <i18n.Translate>Is public</i18n.Translate>
                 </span>
               </span>
-              <button type="button" data-enabled={form.isPublic ?? 
defaultValue.isPublic  ? "true" : "false"} class="bg-indigo-600 
data-[enabled=false]:bg-gray-200 relative inline-flex h-6 w-11 flex-shrink-0 
cursor-pointer rounded-full border-2 border-transparent transition-colors 
duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-indigo-600 
focus:ring-offset-2" role="switch" aria-checked="false" 
aria-labelledby="availability-label" aria-describedby="availability-description"
+              <button type="button" data-enabled={form.isPublic ?? 
defaultValue.isPublic ? "true" : "false"} class="bg-indigo-600 
data-[enabled=false]:bg-gray-200 relative inline-flex h-6 w-11 flex-shrink-0 
cursor-pointer rounded-full border-2 border-transparent transition-colors 
duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-indigo-600 
focus:ring-offset-2" role="switch" aria-checked="false" 
aria-labelledby="availability-label" aria-describedby="availability-description"
 
                 onClick={() => {
                   form.isPublic = !form.isPublic
                   updateForm(structuredClone(form))
                 }}>
-                <span aria-hidden="true" data-enabled={form.isPublic ?? 
defaultValue.isPublic  ? "true" : "false"} class="translate-x-5 
data-[enabled=false]:translate-x-0 pointer-events-none inline-block h-5 w-5 
transform rounded-full bg-white shadow ring-0 transition duration-200 
ease-in-out"></span>
+                <span aria-hidden="true" data-enabled={form.isPublic ?? 
defaultValue.isPublic ? "true" : "false"} class="translate-x-5 
data-[enabled=false]:translate-x-0 pointer-events-none inline-block h-5 w-5 
transform rounded-full bg-white shadow ring-0 transition duration-200 
ease-in-out"></span>
               </button>
             </div>
             <p class="mt-2 text-sm text-gray-500" >
diff --git a/packages/demobank-ui/src/pages/admin/AccountList.tsx 
b/packages/demobank-ui/src/pages/admin/AccountList.tsx
index 42d3d7fca..dd4586697 100644
--- a/packages/demobank-ui/src/pages/admin/AccountList.tsx
+++ b/packages/demobank-ui/src/pages/admin/AccountList.tsx
@@ -1,4 +1,4 @@
-import { Amounts, TalerError } from "@gnu-taler/taler-util";
+import { Amounts, HttpStatusCode, TalerError } from "@gnu-taler/taler-util";
 import { Loading, useTranslationContext } from "@gnu-taler/web-util/browser";
 import { Fragment, VNode, h } from "preact";
 import { ErrorLoadingWithDebug } from 
"../../components/ErrorLoadingWithDebug.js";
@@ -29,7 +29,7 @@ export function AccountList({ onRemoveAccount, 
onShowAccountDetails, onUpdateAcc
   }
   if (result.data.type === "fail") {
     switch (result.data.case) {
-      case "unauthorized": return <Fragment />
+      case HttpStatusCode.Unauthorized: return <Fragment />
       default: assertUnreachable(result.data.case)
     }
   }
diff --git a/packages/demobank-ui/src/pages/admin/CreateNewAccount.tsx 
b/packages/demobank-ui/src/pages/admin/CreateNewAccount.tsx
index 3d196973e..d6b7d5b1e 100644
--- a/packages/demobank-ui/src/pages/admin/CreateNewAccount.tsx
+++ b/packages/demobank-ui/src/pages/admin/CreateNewAccount.tsx
@@ -1,4 +1,4 @@
-import { TalerCorebankApi, TranslatedString } from "@gnu-taler/taler-util";
+import { HttpStatusCode, TalerCorebankApi, TalerErrorCode, TranslatedString } 
from "@gnu-taler/taler-util";
 import { Attention, LocalNotificationBanner, notifyInfo, useLocalNotification, 
useTranslationContext } from "@gnu-taler/web-util/browser";
 import { Fragment, VNode, h } from "preact";
 import { useState } from "preact/hooks";
@@ -48,43 +48,43 @@ export function CreateNewAccount({
         onCreateSuccess();
       } else {
         switch (resp.case) {
-          case "invalid-phone-or-email": return notify({
+          case HttpStatusCode.BadRequest: return notify({
             type: "error",
-            title: i18n.str`Server replied with invalid phone or email`,
+            title: i18n.str`Server replied that phone or email is invalid`,
             description: resp.detail.hint as TranslatedString,
             debug: resp.detail,
           })
-          case "unauthorized": return notify({
+          case HttpStatusCode.Unauthorized: return notify({
             type: "error",
             title: i18n.str`The rights to perform the operation are not 
sufficient`,
             description: resp.detail.hint as TranslatedString,
             debug: resp.detail,
           })
-          case "username-already-exists": return notify({
+          case TalerErrorCode.BANK_REGISTER_USERNAME_REUSE: return notify({
             type: "error",
             title: i18n.str`Account username is already taken`,
             description: resp.detail.hint as TranslatedString,
             debug: resp.detail,
           })
-          case "payto-already-exists": return notify({
+          case TalerErrorCode.BANK_REGISTER_PAYTO_URI_REUSE: return notify({
             type: "error",
             title: i18n.str`Account id is already taken`,
             description: resp.detail.hint as TranslatedString,
             debug: resp.detail,
           })
-          case "insufficient-funds": return notify({
+          case TalerErrorCode.BANK_UNALLOWED_DEBIT: return notify({
             type: "error",
             title: i18n.str`Bank ran out of bonus credit.`,
             description: resp.detail.hint as TranslatedString,
             debug: resp.detail,
           })
-          case "username-reserved": return notify({
+          case TalerErrorCode.BANK_RESERVED_USERNAME_CONFLICT: return notify({
             type: "error",
             title: i18n.str`Account username can't be used because is 
reserved`,
             description: resp.detail.hint as TranslatedString,
             debug: resp.detail,
           })
-          case "user-cant-set-debt": return notify({
+          case TalerErrorCode.BANK_NON_ADMIN_PATCH_DEBT_LIMIT: return notify({
             type: "error",
             title: i18n.str`Only admin is allow to set debt limit.`,
             description: resp.detail.hint as TranslatedString,
diff --git a/packages/demobank-ui/src/pages/admin/RemoveAccount.tsx 
b/packages/demobank-ui/src/pages/admin/RemoveAccount.tsx
index 0d294a3a6..d47f48dd9 100644
--- a/packages/demobank-ui/src/pages/admin/RemoveAccount.tsx
+++ b/packages/demobank-ui/src/pages/admin/RemoveAccount.tsx
@@ -1,4 +1,4 @@
-import { Amounts, TalerError, TranslatedString } from "@gnu-taler/taler-util";
+import { Amounts, HttpStatusCode, TalerError, TalerErrorCode, TranslatedString 
} from "@gnu-taler/taler-util";
 import { Attention, Loading, LocalNotificationBanner, ShowInputErrorLabel, 
notifyInfo, useLocalNotification, useTranslationContext } from 
"@gnu-taler/web-util/browser";
 import { Fragment, VNode, h } from "preact";
 import { useState } from "preact/hooks";
@@ -39,8 +39,8 @@ export function RemoveAccount({
   }
   if (result.type === "fail") {
     switch (result.case) {
-      case "unauthorized": return <LoginForm currentUser={account} />
-      case "not-found": return <LoginForm currentUser={account} />
+      case HttpStatusCode.Unauthorized: return <LoginForm 
currentUser={account} />
+      case HttpStatusCode.NotFound: return <LoginForm currentUser={account} />
       default: assertUnreachable(result)
     }
   }
@@ -65,25 +65,25 @@ export function RemoveAccount({
         onUpdateSuccess();
       } else {
         switch (resp.case) {
-          case "unauthorized": return notify({
+          case HttpStatusCode.Unauthorized: return notify({
             type: "error",
             title: i18n.str`No enough permission to delete the account.`,
             description: resp.detail.hint as TranslatedString,
             debug: resp.detail,
           })
-          case "not-found": return notify({
+          case HttpStatusCode.NotFound: return notify({
             type: "error",
             title: i18n.str`The username was not found.`,
             description: resp.detail.hint as TranslatedString,
             debug: resp.detail,
           })
-          case "username-reserved": return notify({
+          case TalerErrorCode.BANK_RESERVED_USERNAME_CONFLICT: return notify({
             type: "error",
             title: i18n.str`Can't delete a reserved username.`,
             description: resp.detail.hint as TranslatedString,
             debug: resp.detail,
           })
-          case "balance-not-zero": return notify({
+          case TalerErrorCode.BANK_ACCOUNT_BALANCE_NOT_ZERO: return notify({
             type: "error",
             title: i18n.str`Can't delete an account with balance different 
than zero.`,
             description: resp.detail.hint as TranslatedString,
diff --git a/packages/demobank-ui/src/pages/business/CreateCashout.tsx 
b/packages/demobank-ui/src/pages/business/CreateCashout.tsx
index 92c80ea38..c3921cbd0 100644
--- a/packages/demobank-ui/src/pages/business/CreateCashout.tsx
+++ b/packages/demobank-ui/src/pages/business/CreateCashout.tsx
@@ -15,7 +15,9 @@
  */
 import {
   Amounts,
+  HttpStatusCode,
   TalerError,
+  TalerErrorCode,
   TranslatedString,
   encodeCrock,
   getRandomBytes,
@@ -99,8 +101,8 @@ export function CreateCashout({
   }
   if (resultAccount.type === "fail") {
     switch (resultAccount.case) {
-      case "unauthorized": return <LoginForm currentUser={accountName} />
-      case "not-found": return <LoginForm currentUser={accountName} />
+      case HttpStatusCode.Unauthorized: return <LoginForm 
currentUser={accountName} />
+      case HttpStatusCode.NotFound: return <LoginForm 
currentUser={accountName} />
       default: assertUnreachable(resultAccount)
     }
   }
@@ -181,8 +183,9 @@ export function CreateCashout({
   async function createCashout() {
     const request_uid = encodeCrock(getRandomBytes(32))
     await handleError(async () => {
-      if (!creds || !form.subject || !form.channel) return;
+      const validChannel = config.supported_tan_channels.length === 0 || 
form.channel
 
+      if (!creds || !form.subject || !validChannel) return;
       const resp = await api.createCashout(creds, {
         request_uid,
         amount_credit: Amounts.stringify(calc.credit),
@@ -194,47 +197,55 @@ export function CreateCashout({
         notifyInfo(i18n.str`Cashout created`)
       } else {
         switch (resp.case) {
-          case "account-not-found": return notify({
+          case HttpStatusCode.NotFound: return notify({
             type: "error",
             title: i18n.str`Account not found`,
             description: resp.detail.hint as TranslatedString,
             debug: resp.detail,
           });
-          case "request-already-used": return notify({
+          case TalerErrorCode.BANK_TRANSFER_REQUEST_UID_REUSED: return notify({
             type: "error",
             title: i18n.str`Duplicated request detected, check if the 
operation succeded or try again.`,
             description: resp.detail.hint as TranslatedString,
             debug: resp.detail,
           });
-          case "incorrect-exchange-rate": return notify({
+          case TalerErrorCode.BANK_BAD_CONVERSION: return notify({
             type: "error",
             title: i18n.str`The exchange rate was incorrectly applied`,
             description: resp.detail.hint as TranslatedString,
             debug: resp.detail,
           });
-          case "no-enough-balance": return notify({
+          case TalerErrorCode.BANK_UNALLOWED_DEBIT: return notify({
             type: "error",
             title: i18n.str`The account does not have sufficient funds`,
             description: resp.detail.hint as TranslatedString,
             debug: resp.detail,
           });
-          case "cashout-not-supported": return notify({
+          case HttpStatusCode.NotImplemented: return notify({
             type: "error",
             title: i18n.str`Cashouts are not supported`,
             description: resp.detail.hint as TranslatedString,
             debug: resp.detail,
           });
-          case "no-cashout-uri": return notify({
+          case TalerErrorCode.BANK_CONFIRM_INCOMPLETE: return notify({
             type: "error",
             title: i18n.str`Missing cashout URI in the profile`,
             description: resp.detail.hint as TranslatedString,
             debug: resp.detail,
           });
+          case TalerErrorCode.BANK_TAN_CHANNEL_SCRIPT_FAILED: return notify({
+            type: "error",
+            title: i18n.str`Sending the confirmation message failed, retry 
later or contact the administrator.`,
+            description: resp.detail.hint as TranslatedString,
+            debug: resp.detail,
+          });
         }
         assertUnreachable(resp)
       }
     })
   }
+  const cashoutDisabled = config.supported_tan_channels.length < 1 || 
!resultAccount.body.cashout_payto_uri
+  console.log("disab", cashoutDisabled)
   const cashoutAccount = !resultAccount.body.cashout_payto_uri ? undefined :
     parsePaytoUri(resultAccount.body.cashout_payto_uri);
   const cashoutAccountName = !cashoutAccount ? undefined : 
cashoutAccount.targetPath
@@ -317,7 +328,7 @@ export function CreateCashout({
                     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}
+                    disabled={cashoutDisabled}
                     data-error={!!errors?.subject && form.subject !== 
undefined}
                     value={form.subject ?? ""}
                     onChange={(e) => {
@@ -360,7 +371,7 @@ export function CreateCashout({
                     left
                     currency={limit.currency}
                     value={trimmedAmountStr}
-                    onChange={!resultAccount.body.cashout_payto_uri ? 
undefined : (value) => {
+                    onChange={cashoutDisabled ? undefined : (value) => {
                       form.amount = value;
                       updateForm(structuredClone(form));
                     }}
@@ -424,7 +435,15 @@ export function CreateCashout({
               )}
 
               {/* channel */}
-              {config.supported_tan_channels.length === 0 ? undefined :
+              {config.supported_tan_channels.length === 0 ?
+                <div class="sm:col-span-5">
+                  <Attention type="warning" title={i18n.str`No cashout channel 
available`}>
+                    <i18n.Translate>
+                      Before doing a cashout the server need to provide an 
second channel to confirm the operation
+                    </i18n.Translate>
+                  </Attention>
+                </div>
+                :
                 <div class="sm:col-span-5">
                   <label
                     class="block text-sm font-medium leading-6 text-gray-900"
@@ -507,7 +526,6 @@ export function CreateCashout({
           </div>
         </form>
       </div>
-
     </div>
   );
 }
diff --git a/packages/demobank-ui/src/pages/business/ShowCashoutDetails.tsx 
b/packages/demobank-ui/src/pages/business/ShowCashoutDetails.tsx
index 3ef835574..5d8db5aee 100644
--- a/packages/demobank-ui/src/pages/business/ShowCashoutDetails.tsx
+++ b/packages/demobank-ui/src/pages/business/ShowCashoutDetails.tsx
@@ -15,7 +15,9 @@
  */
 import {
   Amounts,
+  HttpStatusCode,
   TalerError,
+  TalerErrorCode,
   TranslatedString
 } from "@gnu-taler/taler-util";
 import {
@@ -73,9 +75,9 @@ export function ShowCashoutDetails({
   }
   if (result.type === "fail") {
     switch (result.case) {
-      case "not-found": return <Attention type="warning" title={i18n.str`This 
cashout not found. Maybe already aborted.`}>
+      case HttpStatusCode.NotFound: return <Attention type="warning" 
title={i18n.str`This cashout not found. Maybe already aborted.`}>
       </Attention>
-      case "cashout-not-supported": return <Attention type="warning" 
title={i18n.str`Cashouts are not supported`}>
+      case HttpStatusCode.NotImplemented: return <Attention type="warning" 
title={i18n.str`Cashouts are not supported`}>
       </Attention>
       default: assertUnreachable(result)
     }
@@ -93,6 +95,7 @@ export function ShowCashoutDetails({
   });
   const isPending = String(result.body.status).toUpperCase() === "PENDING";
   const { fiat_currency_specification, regional_currency_specification } = 
info.body
+  // won't implement in retry in old API 3:0:3 since request_uid is missing
   async function doAbortCashout() {
     if (!creds) return;
     await handleError(async () => {
@@ -101,19 +104,19 @@ export function ShowCashoutDetails({
         onCancel();
       } else {
         switch (resp.case) {
-          case "not-found": return notify({
+          case HttpStatusCode.NotFound: 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({
+          case HttpStatusCode.Conflict: 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({
+          case HttpStatusCode.NotImplemented: return notify({
             type: "error",
             title: i18n.str`Cashout operation is not supported.`,
             description: resp.detail.hint as TranslatedString,
@@ -136,49 +139,49 @@ export function ShowCashoutDetails({
         mutate(() => true)//clean cashout state
       } else {
         switch (resp.case) {
-          case "not-found": return notify({
+          case HttpStatusCode.NotFound: 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({
+          case TalerErrorCode.BANK_UNALLOWED_DEBIT: 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({
+          case TalerErrorCode.BANK_BAD_CONVERSION: 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({
+          case TalerErrorCode.BANK_CONFIRM_ABORT_CONFLICT: 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({
+          case TalerErrorCode.BANK_CONFIRM_INCOMPLETE: 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({
+          case HttpStatusCode.TooManyRequests: 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({
+          case HttpStatusCode.NotImplemented: 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({
+          case TalerErrorCode.BANK_TAN_CHALLENGE_FAILED: return notify({
             type: "error",
             title: i18n.str`The code for this cashout is invalid.`,
             description: resp.detail.hint as TranslatedString,
diff --git a/packages/taler-util/src/http-client/bank-core.ts 
b/packages/taler-util/src/http-client/bank-core.ts
index b7e0292bd..dd0948250 100644
--- a/packages/taler-util/src/http-client/bank-core.ts
+++ b/packages/taler-util/src/http-client/bank-core.ts
@@ -20,7 +20,9 @@ import {
   TalerErrorCode,
   codecForChallenge,
   codecForTalerErrorDetail,
-  codecForTanTransmission
+  codecForTanTransmission,
+  opKnownHttpFailure,
+  opKnownTalerFailure
 } from "@gnu-taler/taler-util";
 import {
   HttpRequestLibrary,
@@ -45,7 +47,7 @@ export type TalerCoreBankErrorsByMethod<prop extends keyof 
TalerCoreBankHttpClie
  * Uses libtool's current:revision:age versioning.
  */
 export class TalerCoreBankHttpClient {
-  public readonly PROTOCOL_VERSION = "0:0:0";
+  public readonly PROTOCOL_VERSION = "4:0:0";
 
   httpLib: HttpRequestLibrary;
 
@@ -95,17 +97,17 @@ export class TalerCoreBankHttpClient {
     });
     switch (resp.status) {
       case HttpStatusCode.Ok: return opSuccess(resp, 
codecForRegisterAccountResponse())
-      case HttpStatusCode.BadRequest: return 
opKnownFailure("invalid-phone-or-email", resp);
-      case HttpStatusCode.Unauthorized: return opKnownFailure("unauthorized", 
resp);
+      case HttpStatusCode.BadRequest: return opKnownHttpFailure(resp.status, 
resp);
+      case HttpStatusCode.Unauthorized: return opKnownHttpFailure(resp.status, 
resp);
       case HttpStatusCode.Conflict: {
         const body = await resp.json()
         const details = codecForTalerErrorDetail().decode(body)
         switch (details.code) {
-          case TalerErrorCode.BANK_REGISTER_USERNAME_REUSE: return 
opKnownFailure("username-already-exists", resp);
-          case TalerErrorCode.BANK_REGISTER_PAYTO_URI_REUSE: return 
opKnownFailure("payto-already-exists", resp);
-          case TalerErrorCode.BANK_UNALLOWED_DEBIT: return 
opKnownFailure("insufficient-funds", resp);
-          case TalerErrorCode.BANK_RESERVED_USERNAME_CONFLICT: return 
opKnownFailure("username-reserved", resp);
-          case TalerErrorCode.BANK_NON_ADMIN_PATCH_DEBT_LIMIT: return 
opKnownFailure("user-cant-set-debt", resp);
+          case TalerErrorCode.BANK_REGISTER_USERNAME_REUSE: return 
opKnownTalerFailure(details.code, resp);
+          case TalerErrorCode.BANK_REGISTER_PAYTO_URI_REUSE: return 
opKnownTalerFailure(details.code, resp);
+          case TalerErrorCode.BANK_UNALLOWED_DEBIT: return 
opKnownTalerFailure(details.code, resp);
+          case TalerErrorCode.BANK_RESERVED_USERNAME_CONFLICT: return 
opKnownTalerFailure(details.code, resp);
+          case TalerErrorCode.BANK_NON_ADMIN_PATCH_DEBT_LIMIT: return 
opKnownTalerFailure(details.code, resp);
           default: return opUnknownFailure(resp, body)
         }
       }
@@ -127,14 +129,14 @@ export class TalerCoreBankHttpClient {
     switch (resp.status) {
       case HttpStatusCode.Accepted: return opSuccess(resp, codecForChallenge())
       case HttpStatusCode.NoContent: return opEmptySuccess()
-      case HttpStatusCode.NotFound: return opKnownFailure("not-found", resp);
-      case HttpStatusCode.Unauthorized: return opKnownFailure("unauthorized", 
resp);
+      case HttpStatusCode.NotFound: return opKnownHttpFailure(resp.status, 
resp);
+      case HttpStatusCode.Unauthorized: return opKnownHttpFailure(resp.status, 
resp);
       case HttpStatusCode.Conflict: {
         const body = await resp.json()
         const details = codecForTalerErrorDetail().decode(body)
         switch (details.code) {
-          case TalerErrorCode.BANK_RESERVED_USERNAME_CONFLICT: return 
opKnownFailure("username-reserved", resp);
-          case TalerErrorCode.BANK_ACCOUNT_BALANCE_NOT_ZERO: return 
opKnownFailure("balance-not-zero", resp);
+          case TalerErrorCode.BANK_RESERVED_USERNAME_CONFLICT: return 
opKnownTalerFailure(details.code, resp);
+          case TalerErrorCode.BANK_ACCOUNT_BALANCE_NOT_ZERO: return 
opKnownTalerFailure(details.code, resp);
           default: return opUnknownFailure(resp, body)
         }
       }
@@ -158,16 +160,16 @@ export class TalerCoreBankHttpClient {
     switch (resp.status) {
       case HttpStatusCode.Accepted: return opSuccess(resp, codecForChallenge())
       case HttpStatusCode.NoContent: return opEmptySuccess()
-      case HttpStatusCode.NotFound: return opKnownFailure("not-found", resp);
-      case HttpStatusCode.Unauthorized: return opKnownFailure("unauthorized", 
resp);
+      case HttpStatusCode.NotFound: return opKnownHttpFailure(resp.status, 
resp);
+      case HttpStatusCode.Unauthorized: return opKnownHttpFailure(resp.status, 
resp);
       case HttpStatusCode.Conflict: {
         const body = await resp.json()
         const details = codecForTalerErrorDetail().decode(body)
         switch (details.code) {
-          case TalerErrorCode.BANK_NON_ADMIN_PATCH_LEGAL_NAME: return 
opKnownFailure("user-cant-change-name", resp);
-          case TalerErrorCode.BANK_NON_ADMIN_PATCH_DEBT_LIMIT: return 
opKnownFailure("user-cant-change-debt", resp);
-          case TalerErrorCode.BANK_NON_ADMIN_PATCH_CASHOUT: return 
opKnownFailure("user-cant-change-cashout", resp);
-          case TalerErrorCode.BANK_MISSING_TAN_INFO: return 
opKnownFailure("missing-contact-data", resp);
+          case TalerErrorCode.BANK_NON_ADMIN_PATCH_LEGAL_NAME: return 
opKnownTalerFailure(details.code, resp);
+          case TalerErrorCode.BANK_NON_ADMIN_PATCH_DEBT_LIMIT: return 
opKnownTalerFailure(details.code, resp);
+          case TalerErrorCode.BANK_NON_ADMIN_PATCH_CASHOUT: return 
opKnownTalerFailure(details.code, resp);
+          case TalerErrorCode.BANK_NON_ADMIN_PATCH_CONTACT: return 
opKnownTalerFailure(details.code, resp);
           default: return opUnknownFailure(resp, body)
         }
       }
@@ -191,14 +193,14 @@ export class TalerCoreBankHttpClient {
     switch (resp.status) {
       case HttpStatusCode.Accepted: return opSuccess(resp, codecForChallenge())
       case HttpStatusCode.NoContent: return opEmptySuccess()
-      case HttpStatusCode.NotFound: return opKnownFailure("not-found", resp);
-      case HttpStatusCode.Unauthorized: return opKnownFailure("unauthorized", 
resp);
+      case HttpStatusCode.NotFound: return opKnownHttpFailure(resp.status, 
resp);
+      case HttpStatusCode.Unauthorized: return opKnownHttpFailure(resp.status, 
resp);
       case HttpStatusCode.Conflict: {
         const body = await resp.json()
         const details = codecForTalerErrorDetail().decode(body)
         switch (details.code) {
-          case TalerErrorCode.BANK_NON_ADMIN_PATCH_MISSING_OLD_PASSWORD: 
return opKnownFailure("user-require-old-password", resp);
-          case TalerErrorCode.BANK_PATCH_BAD_OLD_PASSWORD: return 
opKnownFailure("wrong-old-password", resp);
+          case TalerErrorCode.BANK_NON_ADMIN_PATCH_MISSING_OLD_PASSWORD: 
return opKnownTalerFailure(details.code, resp);
+          case TalerErrorCode.BANK_PATCH_BAD_OLD_PASSWORD: return 
opKnownTalerFailure(details.code, resp);
           default: return opUnknownFailure(resp, body)
         }
       }
@@ -246,7 +248,7 @@ export class TalerCoreBankHttpClient {
     switch (resp.status) {
       case HttpStatusCode.Ok: return opSuccess(resp, 
codecForListBankAccountsResponse())
       case HttpStatusCode.NoContent: return opFixedSuccess({ accounts: [] })
-      case HttpStatusCode.Unauthorized: return opKnownFailure("unauthorized", 
resp);
+      case HttpStatusCode.Unauthorized: return opKnownHttpFailure(resp.status, 
resp);
       default: return opUnknownFailure(resp, await resp.text())
     }
   }
@@ -265,8 +267,8 @@ export class TalerCoreBankHttpClient {
     });
     switch (resp.status) {
       case HttpStatusCode.Ok: return opSuccess(resp, codecForAccountData())
-      case HttpStatusCode.Unauthorized: return opKnownFailure("unauthorized", 
resp);
-      case HttpStatusCode.NotFound: return opKnownFailure("not-found", resp);
+      case HttpStatusCode.Unauthorized: return opKnownHttpFailure(resp.status, 
resp);
+      case HttpStatusCode.NotFound: return opKnownHttpFailure(resp.status, 
resp);
       default: return opUnknownFailure(resp, await resp.text())
     }
   }
@@ -291,8 +293,8 @@ export class TalerCoreBankHttpClient {
     switch (resp.status) {
       case HttpStatusCode.Ok: return opSuccess(resp, 
codecForBankAccountTransactionsResponse())
       case HttpStatusCode.NoContent: return opFixedSuccess({ transactions: [] 
})
-      case HttpStatusCode.Unauthorized: return opKnownFailure("unauthorized", 
resp);
-      case HttpStatusCode.NotFound: return opKnownFailure("not-found", resp);
+      case HttpStatusCode.Unauthorized: return opKnownHttpFailure(resp.status, 
resp);
+      case HttpStatusCode.NotFound: return opKnownHttpFailure(resp.status, 
resp);
       default: return opUnknownFailure(resp, await resp.text())
     }
   }
@@ -311,8 +313,8 @@ export class TalerCoreBankHttpClient {
     });
     switch (resp.status) {
       case HttpStatusCode.Ok: return opSuccess(resp, 
codecForBankAccountTransactionInfo())
-      case HttpStatusCode.NotFound: return opKnownFailure("not-found", resp);
-      case HttpStatusCode.Unauthorized: return opKnownFailure("unauthorized", 
resp);
+      case HttpStatusCode.NotFound: return opKnownHttpFailure(resp.status, 
resp);
+      case HttpStatusCode.Unauthorized: return opKnownHttpFailure(resp.status, 
resp);
       default: return opUnknownFailure(resp, await resp.text())
     }
   }
@@ -333,16 +335,16 @@ export class TalerCoreBankHttpClient {
     switch (resp.status) {
       case HttpStatusCode.Accepted: return opSuccess(resp, codecForChallenge())
       case HttpStatusCode.Ok: return opSuccess(resp, 
codecForCreateTransactionResponse())
-      case HttpStatusCode.BadRequest: return opKnownFailure("invalid-input", 
resp);
-      case HttpStatusCode.Unauthorized: return opKnownFailure("unauthorized", 
resp);
-      case HttpStatusCode.NotFound: return opKnownFailure("not-found", resp);
+      case HttpStatusCode.BadRequest: return opKnownHttpFailure(resp.status, 
resp);
+      case HttpStatusCode.Unauthorized: return opKnownHttpFailure(resp.status, 
resp);
+      case HttpStatusCode.NotFound: return opKnownHttpFailure(resp.status, 
resp);
       case HttpStatusCode.Conflict: {
         const body = await resp.json()
         const details = codecForTalerErrorDetail().decode(body)
         switch (details.code) {
-          case TalerErrorCode.BANK_SAME_ACCOUNT: return 
opKnownFailure("creditor-same", resp);
-          case TalerErrorCode.BANK_UNKNOWN_CREDITOR: return 
opKnownFailure("creditor-not-found", resp);
-          case TalerErrorCode.BANK_UNALLOWED_DEBIT: return 
opKnownFailure("insufficient-funds", resp);
+          case TalerErrorCode.BANK_SAME_ACCOUNT: return 
opKnownTalerFailure(details.code, resp);
+          case TalerErrorCode.BANK_UNKNOWN_CREDITOR: return 
opKnownTalerFailure(details.code, resp);
+          case TalerErrorCode.BANK_UNALLOWED_DEBIT: return 
opKnownTalerFailure(details.code, resp);
           default: return opUnknownFailure(resp, body)
         }
       }
@@ -369,9 +371,9 @@ export class TalerCoreBankHttpClient {
     });
     switch (resp.status) {
       case HttpStatusCode.Ok: return opSuccess(resp, 
codecForBankAccountCreateWithdrawalResponse())
-      case HttpStatusCode.NotFound: return opKnownFailure("account-not-found", 
resp);
-      case HttpStatusCode.Conflict: return 
opKnownFailure("insufficient-funds", resp);
-      case HttpStatusCode.Unauthorized: return opKnownFailure("unauthorized", 
resp);
+      case HttpStatusCode.NotFound: return opKnownHttpFailure(resp.status, 
resp);
+      case HttpStatusCode.Conflict: return opKnownHttpFailure(resp.status, 
resp);
+      case HttpStatusCode.Unauthorized: return opKnownHttpFailure(resp.status, 
resp);
       default: return opUnknownFailure(resp, await resp.text())
     }
   }
@@ -391,9 +393,9 @@ export class TalerCoreBankHttpClient {
     switch (resp.status) {
       case HttpStatusCode.NoContent: return opEmptySuccess()
       //FIXME: missing in docs
-      case HttpStatusCode.BadRequest: return opKnownFailure("invalid-id", resp)
-      case HttpStatusCode.NotFound: return opKnownFailure("not-found", resp)
-      case HttpStatusCode.Conflict: return 
opKnownFailure("previously-confirmed", resp);
+      case HttpStatusCode.BadRequest: return opKnownHttpFailure(resp.status, 
resp)
+      case HttpStatusCode.NotFound: return opKnownHttpFailure(resp.status, 
resp)
+      case HttpStatusCode.Conflict: return opKnownHttpFailure(resp.status, 
resp);
       default: return opUnknownFailure(resp, await resp.text())
     }
   }
@@ -414,15 +416,15 @@ export class TalerCoreBankHttpClient {
       case HttpStatusCode.Accepted: return opSuccess(resp, codecForChallenge())
       case HttpStatusCode.NoContent: return opEmptySuccess()
       //FIXME: missing in docs
-      case HttpStatusCode.BadRequest: return opKnownFailure("invalid-id", resp)
-      case HttpStatusCode.NotFound: return opKnownFailure("not-found", resp)
+      case HttpStatusCode.BadRequest: return opKnownHttpFailure(resp.status, 
resp)
+      case HttpStatusCode.NotFound: return opKnownHttpFailure(resp.status, 
resp)
       case HttpStatusCode.Conflict: {
         const body = await resp.json()
         const details = codecForTalerErrorDetail().decode(body)
         switch (details.code) {
-          case TalerErrorCode.BANK_CONFIRM_ABORT_CONFLICT: return 
opKnownFailure("previously-aborted", resp);
-          case TalerErrorCode.BANK_CONFIRM_INCOMPLETE: return 
opKnownFailure("no-exchange-or-reserve-selected", resp);
-          case TalerErrorCode.BANK_UNALLOWED_DEBIT: return 
opKnownFailure("insufficient-funds", resp);
+          case TalerErrorCode.BANK_CONFIRM_ABORT_CONFLICT: return 
opKnownTalerFailure(details.code, resp);
+          case TalerErrorCode.BANK_CONFIRM_INCOMPLETE: return 
opKnownTalerFailure(details.code, resp);
+          case TalerErrorCode.BANK_UNALLOWED_DEBIT: return 
opKnownTalerFailure(details.code, resp);
           default: return opUnknownFailure(resp, body)
         }
       }
@@ -449,8 +451,8 @@ export class TalerCoreBankHttpClient {
     switch (resp.status) {
       case HttpStatusCode.Ok: return opSuccess(resp, 
codecForWithdrawalPublicInfo())
       //FIXME: missing in docs
-      case HttpStatusCode.BadRequest: return opKnownFailure("invalid-id", resp)
-      case HttpStatusCode.NotFound: return opKnownFailure("not-found", resp)
+      case HttpStatusCode.BadRequest: return opKnownHttpFailure(resp.status, 
resp)
+      case HttpStatusCode.NotFound: return opKnownHttpFailure(resp.status, 
resp)
       default: return opUnknownFailure(resp, await resp.text())
     }
   }
@@ -475,19 +477,27 @@ export class TalerCoreBankHttpClient {
     switch (resp.status) {
       case HttpStatusCode.Accepted: return opSuccess(resp, codecForChallenge())
       case HttpStatusCode.Ok: return opSuccess(resp, codecForCashoutPending())
-      case HttpStatusCode.NotFound: return opKnownFailure("account-not-found", 
resp)
+      case HttpStatusCode.NotFound: return opKnownHttpFailure(resp.status, 
resp)
       case HttpStatusCode.Conflict: {
         const body = await resp.json()
         const details = codecForTalerErrorDetail().decode(body)
         switch (details.code) {
-          case TalerErrorCode.BANK_TRANSFER_REQUEST_UID_REUSED: return 
opKnownFailure("request-already-used", resp);
-          case TalerErrorCode.BANK_BAD_CONVERSION: return 
opKnownFailure("incorrect-exchange-rate", resp);
-          case TalerErrorCode.BANK_UNALLOWED_DEBIT: return 
opKnownFailure("no-enough-balance", resp);
-          case TalerErrorCode.BANK_CONFIRM_INCOMPLETE: return 
opKnownFailure("no-cashout-uri", resp);
+          case TalerErrorCode.BANK_TRANSFER_REQUEST_UID_REUSED: return 
opKnownTalerFailure(details.code, resp);
+          case TalerErrorCode.BANK_BAD_CONVERSION: return 
opKnownTalerFailure(details.code, resp);
+          case TalerErrorCode.BANK_UNALLOWED_DEBIT: return 
opKnownTalerFailure(details.code, resp);
+          case TalerErrorCode.BANK_CONFIRM_INCOMPLETE: return 
opKnownTalerFailure(details.code, resp);
           default: return opUnknownFailure(resp, body)
         }
       }
-      case HttpStatusCode.NotImplemented: return 
opKnownFailure("cashout-not-supported", resp);
+      case HttpStatusCode.BadGateway: {
+        const body = await resp.json()
+        const details = codecForTalerErrorDetail().decode(body)
+        switch (details.code) {
+          case TalerErrorCode.BANK_TAN_CHANNEL_SCRIPT_FAILED: return 
opKnownTalerFailure(details.code, resp);
+          default: return opUnknownFailure(resp, body)
+        }
+      }
+      case HttpStatusCode.NotImplemented: return 
opKnownHttpFailure(resp.status, resp);
       default: return opUnknownFailure(resp, await resp.text())
     }
   }
@@ -506,9 +516,9 @@ export class TalerCoreBankHttpClient {
     });
     switch (resp.status) {
       case HttpStatusCode.NoContent: return opEmptySuccess()
-      case HttpStatusCode.NotFound: return opKnownFailure("not-found", resp);
-      case HttpStatusCode.Conflict: return opKnownFailure("already-confirmed", 
resp);
-      case HttpStatusCode.NotImplemented: return 
opKnownFailure("cashout-not-supported", resp);
+      case HttpStatusCode.NotFound: return opKnownHttpFailure(resp.status, 
resp);
+      case HttpStatusCode.Conflict: return opKnownHttpFailure(resp.status, 
resp);
+      case HttpStatusCode.NotImplemented: return 
opKnownHttpFailure(resp.status, resp);
       default: return opUnknownFailure(resp, await resp.text())
     }
   }
@@ -528,21 +538,21 @@ export class TalerCoreBankHttpClient {
     });
     switch (resp.status) {
       case HttpStatusCode.NoContent: return opEmptySuccess()
-      case HttpStatusCode.NotFound: return opKnownFailure("not-found", resp);
+      case HttpStatusCode.NotFound: return opKnownHttpFailure(resp.status, 
resp);
       case HttpStatusCode.Conflict: {
         const body = await resp.json()
         const details = codecForTalerErrorDetail().decode(body)
         switch (details.code) {
-          case TalerErrorCode.BANK_CONFIRM_ABORT_CONFLICT: return 
opKnownFailure("already-aborted", resp);
-          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("invalid-code", resp);
+          case TalerErrorCode.BANK_CONFIRM_ABORT_CONFLICT: return 
opKnownTalerFailure(details.code, resp);
+          case TalerErrorCode.BANK_CONFIRM_INCOMPLETE: return 
opKnownTalerFailure(details.code, resp);
+          case TalerErrorCode.BANK_UNALLOWED_DEBIT: return 
opKnownTalerFailure(details.code, resp);
+          case TalerErrorCode.BANK_BAD_CONVERSION: return 
opKnownTalerFailure(details.code, resp);
+          case TalerErrorCode.BANK_TAN_CHALLENGE_FAILED: return 
opKnownTalerFailure(details.code, resp);
           default: return opUnknownFailure(resp, body)
         }
       }
-      case HttpStatusCode.TooManyRequests: return 
opKnownFailure("too-many-attempts", resp);
-      case HttpStatusCode.NotImplemented: return 
opKnownFailure("cashout-not-supported", resp);
+      case HttpStatusCode.TooManyRequests: return 
opKnownHttpFailure(resp.status, resp);
+      case HttpStatusCode.NotImplemented: return 
opKnownHttpFailure(resp.status, resp);
       default: return opUnknownFailure(resp, await resp.text())
     }
   }
@@ -561,8 +571,8 @@ export class TalerCoreBankHttpClient {
     });
     switch (resp.status) {
       case HttpStatusCode.Ok: return opSuccess(resp, 
codecForCashoutStatusResponse())
-      case HttpStatusCode.NotFound: return opKnownFailure("not-found", resp);
-      case HttpStatusCode.NotImplemented: return 
opKnownFailure("cashout-not-supported", resp);
+      case HttpStatusCode.NotFound: return opKnownHttpFailure(resp.status, 
resp);
+      case HttpStatusCode.NotImplemented: return 
opKnownHttpFailure(resp.status, resp);
       default: return opUnknownFailure(resp, await resp.text())
     }
   }
@@ -583,8 +593,8 @@ export class TalerCoreBankHttpClient {
     switch (resp.status) {
       case HttpStatusCode.Ok: return opSuccess(resp, codecForCashouts())
       case HttpStatusCode.NoContent: return opFixedSuccess({ cashouts: [] });
-      case HttpStatusCode.NotFound: return opKnownFailure("account-not-found", 
resp);;
-      case HttpStatusCode.NotImplemented: return 
opKnownFailure("cashout-not-supported", resp);
+      case HttpStatusCode.NotFound: return opKnownHttpFailure(resp.status, 
resp);;
+      case HttpStatusCode.NotImplemented: return 
opKnownHttpFailure(resp.status, resp);
       default: return opUnknownFailure(resp, await resp.text())
     }
   }
@@ -605,7 +615,7 @@ export class TalerCoreBankHttpClient {
     switch (resp.status) {
       case HttpStatusCode.Ok: return opSuccess(resp, codecForGlobalCashouts())
       case HttpStatusCode.NoContent: return opFixedSuccess({ cashouts: [] });
-      case HttpStatusCode.NotImplemented: return 
opKnownFailure("cashout-not-supported", resp);
+      case HttpStatusCode.NotImplemented: return 
opKnownHttpFailure(resp.status, resp);
       default: return opUnknownFailure(resp, await resp.text())
     }
   }
@@ -634,11 +644,11 @@ export class TalerCoreBankHttpClient {
     });
     switch (resp.status) {
       case HttpStatusCode.Ok: return opSuccess(resp, codecForMonitorResponse())
-      case HttpStatusCode.BadRequest: return opKnownFailure("invalid-input", 
resp);
-      case HttpStatusCode.Unauthorized: return opKnownFailure("unauthorized", 
resp);
+      case HttpStatusCode.BadRequest: return opKnownHttpFailure(resp.status, 
resp);
+      case HttpStatusCode.Unauthorized: return opKnownHttpFailure(resp.status, 
resp);
       //FIXME remove when server is updated
       //FIXME: should be 404 ?
-      case HttpStatusCode.ServiceUnavailable: return 
opKnownFailure("monitor-not-supported", resp);
+      case HttpStatusCode.ServiceUnavailable: return 
opKnownHttpFailure(resp.status, resp);
       default: return opUnknownFailure(resp, await resp.text())
     }
   }
@@ -656,13 +666,13 @@ export class TalerCoreBankHttpClient {
     });
     switch (resp.status) {
       case HttpStatusCode.Ok: return opSuccess(resp, codecForTanTransmission())
-      case HttpStatusCode.Unauthorized: return opKnownFailure("unauthorized", 
resp);
-      case HttpStatusCode.NotFound: return opKnownFailure("invalid-challenge", 
resp);
+      case HttpStatusCode.Unauthorized: return opKnownHttpFailure(resp.status, 
resp);
+      case HttpStatusCode.NotFound: return opKnownHttpFailure(resp.status, 
resp);
       case HttpStatusCode.BadGateway: {
         const body = await resp.json()
         const details = codecForTalerErrorDetail().decode(body)
         switch (details.code) {
-          case TalerErrorCode.BANK_TAN_CHANNEL_SCRIPT_FAILED: return 
opKnownFailure("tan-failed", resp);
+          case TalerErrorCode.BANK_TAN_CHANNEL_SCRIPT_FAILED: return 
opKnownTalerFailure(details.code, resp);
           default: return opUnknownFailure(resp, body)
         }
       }
@@ -679,11 +689,19 @@ export class TalerCoreBankHttpClient {
       },
     });
     switch (resp.status) {
-      case HttpStatusCode.Ok: return opEmptySuccess()
-      case HttpStatusCode.Unauthorized: return opKnownFailure("unauthorized", 
resp);
-      case HttpStatusCode.NotFound: return opKnownFailure("invalid-challenge", 
resp);
-      case HttpStatusCode.Conflict: return opKnownFailure("wrong-code", resp);
-      case HttpStatusCode.TooManyRequests: return 
opKnownFailure("too-many-errors", resp);
+      case HttpStatusCode.NoContent: return opEmptySuccess()
+      case HttpStatusCode.Unauthorized: return opKnownHttpFailure(resp.status, 
resp);
+      case HttpStatusCode.NotFound: return opKnownHttpFailure(resp.status, 
resp);
+      case HttpStatusCode.Conflict: {
+        const body = await resp.json()
+        const details = codecForTalerErrorDetail().decode(body)
+        switch (details.code) {
+          case TalerErrorCode.BANK_TAN_CHALLENGE_EXPIRED: return 
opKnownTalerFailure(details.code, resp);
+          case TalerErrorCode.BANK_TAN_CHALLENGE_FAILED: return 
opKnownTalerFailure(details.code, resp);
+          default: return opUnknownFailure(resp, body)
+        }
+      }
+      case HttpStatusCode.TooManyRequests: return 
opKnownHttpFailure(resp.status, resp);
       default: return opUnknownFailure(resp, await resp.text())
     }
   }
diff --git a/packages/taler-util/src/operation.ts 
b/packages/taler-util/src/operation.ts
index 06bfe26bd..8b264d905 100644
--- a/packages/taler-util/src/operation.ts
+++ b/packages/taler-util/src/operation.ts
@@ -1,5 +1,5 @@
 import { HttpResponse, readSuccessResponseJsonOrThrow, readTalerErrorResponse 
} from "./http-common.js";
-import { Codec, TalerError, TalerErrorCode, TalerErrorDetail } from 
"./index.js";
+import { Codec, HttpStatusCode, TalerError, TalerErrorCode, TalerErrorDetail } 
from "./index.js";
 
 export type OperationResult<Body, ErrorEnum> =
   | OperationOk<Body>
@@ -31,6 +31,14 @@ export function opFixedSuccess<T>(body: T): OperationOk<T> {
 export function opEmptySuccess(): OperationOk<void> {
   return { type: "ok" as const, body: void 0 }
 }
+export async function opKnownHttpFailure<T extends HttpStatusCode>(s: T, resp: 
HttpResponse): Promise<OperationFail<T>> {
+  const detail = await readTalerErrorResponse(resp)
+  return { type: "fail", case: s, detail }
+}
+export async function opKnownTalerFailure<T extends TalerErrorCode>(s: T, 
resp: HttpResponse): Promise<OperationFail<T>> {
+  const detail = await readTalerErrorResponse(resp)
+  return { type: "fail", case: s, detail }
+}
 export async function opKnownFailure<T extends string>(s: T, resp: 
HttpResponse): Promise<OperationFail<T>> {
   const detail = await readTalerErrorResponse(resp)
   return { type: "fail", case: s, detail }

-- 
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]