gnunet-svn
[Top][All Lists]
Advanced

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

[taler-wallet-core] 02/02: refactor better QA


From: gnunet
Subject: [taler-wallet-core] 02/02: refactor better QA
Date: Tue, 03 Jan 2023 05:58:45 +0100

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

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

commit a2668c22f0d18386fc988f27299172145d9fa15d
Author: Sebastian <sebasjm@gmail.com>
AuthorDate: Tue Jan 3 01:57:39 2023 -0300

    refactor better QA
    
    removed axios, use fetch
    removed jest, added mocha and chai
    moved the default request handler to runtime dependency (so it can be 
replaced for testing)
    refactored ALL the test to the standard web-utils
    all hooks now use ONE request handler
    moved the tests from test folder to src
---
 packages/merchant-backoffice-ui/package.json       |  11 +-
 .../merchant-backoffice-ui/src/InstanceRoutes.tsx  |   4 +-
 .../src/components/form/InputPaytoForm.tsx         |   2 +-
 .../browserMocks.ts => src/context/api.ts}         |  45 +-
 .../src/context/backend.test.ts                    | 131 ++++
 .../merchant-backoffice-ui/src/context/backend.ts  |   3 +
 .../merchant-backoffice-ui/src/context/fetch.ts    |  54 --
 packages/merchant-backoffice-ui/src/hooks/async.ts |   4 +-
 .../merchant-backoffice-ui/src/hooks/backend.ts    | 461 +++++++-------
 packages/merchant-backoffice-ui/src/hooks/index.ts |  15 +-
 .../src/hooks/instance.test.ts                     | 660 +++++++++++++++++++++
 .../merchant-backoffice-ui/src/hooks/instance.ts   | 113 ++--
 .../merchant-backoffice-ui/src/hooks/order.test.ts | 579 ++++++++++++++++++
 packages/merchant-backoffice-ui/src/hooks/order.ts |  97 +--
 .../src/hooks/product.test.ts                      | 326 ++++++++++
 .../merchant-backoffice-ui/src/hooks/product.ts    |  69 +--
 .../src/hooks/reserve.test.ts                      | 431 ++++++++++++++
 .../merchant-backoffice-ui/src/hooks/reserves.ts   |  90 +--
 .../merchant-backoffice-ui/src/hooks/templates.ts  |  92 +--
 .../merchant-backoffice-ui/src/hooks/testing.tsx   | 120 ++++
 .../src/hooks/transfer.test.ts                     | 277 +++++++++
 .../merchant-backoffice-ui/src/hooks/transfer.ts   |  80 +--
 packages/merchant-backoffice-ui/src/hooks/urls.ts  | 291 +++++++++
 packages/merchant-backoffice-ui/src/manifest.json  |  21 -
 .../src/paths/admin/list/index.tsx                 |   2 +-
 .../src/paths/instance/details/index.tsx           |   2 +-
 .../src/paths/instance/kyc/list/index.tsx          |   2 +-
 .../src/paths/instance/orders/create/index.tsx     |   2 +-
 .../src/paths/instance/orders/details/index.tsx    |   2 +-
 .../src/paths/instance/orders/list/index.tsx       |   2 +-
 .../src/paths/instance/products/list/index.tsx     |   2 +-
 .../src/paths/instance/products/update/index.tsx   |   2 +-
 .../paths/instance/reserves/create/CreatePage.tsx  |   9 +-
 .../src/paths/instance/reserves/details/index.tsx  |   2 +-
 .../src/paths/instance/reserves/list/index.tsx     |   2 +-
 .../src/paths/instance/templates/list/index.tsx    |   2 +-
 .../src/paths/instance/templates/update/index.tsx  |   2 +-
 .../src/paths/instance/transfers/list/index.tsx    |   2 +-
 .../src/paths/instance/update/index.tsx            |   2 +-
 .../{tests/functions => src/utils}/regex.test.ts   |   9 +-
 .../merchant-backoffice-ui/src/utils/request.ts    | 282 +++++++++
 .../src/utils/switchableAxios.ts                   |  73 ---
 .../tests/__mocks__/fileMocks.ts                   |  24 -
 .../tests/__mocks__/fileTransformer.js             |  31 -
 .../tests/__mocks__/setupTests.ts                  |  28 -
 packages/merchant-backoffice-ui/tests/axiosMock.ts | 445 --------------
 .../tests/context/backend.test.tsx                 | 172 ------
 .../merchant-backoffice-ui/tests/declarations.d.ts |  28 -
 .../merchant-backoffice-ui/tests/header.test.tsx   |  63 --
 .../tests/hooks/async.test.ts                      | 158 -----
 .../tests/hooks/listener.test.ts                   |  62 --
 .../tests/hooks/notification.test.ts               |  51 --
 .../tests/hooks/swr/index.tsx                      |  46 --
 .../tests/hooks/swr/instance.test.ts               | 636 --------------------
 .../tests/hooks/swr/order.test.ts                  | 567 ------------------
 .../tests/hooks/swr/product.test.ts                | 338 -----------
 .../tests/hooks/swr/reserve.test.ts                | 470 ---------------
 .../tests/hooks/swr/transfer.test.ts               | 268 ---------
 pnpm-lock.yaml                                     | 107 ++--
 59 files changed, 3569 insertions(+), 4302 deletions(-)

diff --git a/packages/merchant-backoffice-ui/package.json 
b/packages/merchant-backoffice-ui/package.json
index beacd42f6..cffe73e3f 100644
--- a/packages/merchant-backoffice-ui/package.json
+++ b/packages/merchant-backoffice-ui/package.json
@@ -35,7 +35,6 @@
   "dependencies": {
     "@gnu-taler/taler-util": "workspace:*",
     "@gnu-taler/web-util": "workspace:*",
-    "axios": "^0.21.1",
     "date-fns": "2.29.3",
     "history": "4.10.1",
     "jed": "1.1.1",
@@ -48,10 +47,8 @@
   "devDependencies": {
     "@creativebulma/bulma-tooltip": "^1.2.0",
     "@gnu-taler/pogen": "^0.0.5",
-    "@testing-library/preact": "^2.0.1",
-    "@testing-library/preact-hooks": "^1.1.0",
+    "@types/chai": "^4.3.0",
     "@types/history": "^4.7.8",
-    "@types/jest": "^26.0.23",
     "@types/mocha": "^8.2.3",
     "@types/node": "^18.11.17",
     "@typescript-eslint/eslint-plugin": "^4.22.0",
@@ -64,6 +61,7 @@
     "bulma-switch-control": "^1.1.1",
     "bulma-timeline": "^3.0.4",
     "bulma-upload-control": "^1.2.0",
+    "chai": "^4.3.6",
     "dotenv": "^8.2.0",
     "eslint": "^7.25.0",
     "eslint-config-preact": "^1.1.4",
@@ -72,13 +70,12 @@
     "html-webpack-inline-source-plugin": "0.0.10",
     "html-webpack-skip-assets-plugin": "^1.0.1",
     "inline-chunk-html-plugin": "^1.1.1",
-    "jest": "^26.6.3",
-    "jest-preset-preact": "^4.0.2",
     "mocha": "^9.2.0",
     "preact-render-to-string": "^5.2.6",
     "rimraf": "^3.0.2",
     "sass": "1.56.1",
+    "source-map-support": "^0.5.21",
     "typedoc": "^0.20.36",
     "typescript": "4.8.4"
   }
-}
\ No newline at end of file
+}
diff --git a/packages/merchant-backoffice-ui/src/InstanceRoutes.tsx 
b/packages/merchant-backoffice-ui/src/InstanceRoutes.tsx
index 8ac5c698b..1c55572bb 100644
--- a/packages/merchant-backoffice-ui/src/InstanceRoutes.tsx
+++ b/packages/merchant-backoffice-ui/src/InstanceRoutes.tsx
@@ -28,7 +28,7 @@ import { Loading } from "./components/exception/loading.js";
 import { Menu, NotificationCard } from "./components/menu/index.js";
 import { useBackendContext } from "./context/backend.js";
 import { InstanceContextProvider } from "./context/instance.js";
-import { HttpError } from "./hooks/backend.js";
+import { HttpError } from "./utils/request.js";
 import {
   useBackendDefaultToken,
   useBackendInstanceToken,
@@ -484,7 +484,7 @@ export function Redirect({ to }: { to: string }): null {
 function AdminInstanceUpdatePage({
   id,
   ...rest
-}: { id: string } & InstanceUpdatePageProps) {
+}: { id: string } & InstanceUpdatePageProps): VNode {
   const [token, changeToken] = useBackendInstanceToken(id);
   const { updateLoginStatus: changeBackend } = useBackendContext();
   const updateLoginStatus = (url: string, token?: string) => {
diff --git 
a/packages/merchant-backoffice-ui/src/components/form/InputPaytoForm.tsx 
b/packages/merchant-backoffice-ui/src/components/form/InputPaytoForm.tsx
index 7bf39152b..68f79bc35 100644
--- a/packages/merchant-backoffice-ui/src/components/form/InputPaytoForm.tsx
+++ b/packages/merchant-backoffice-ui/src/components/form/InputPaytoForm.tsx
@@ -195,7 +195,7 @@ export function InputPaytoForm<T>({
       if (opt_value) url.searchParams.set(opt_key, opt_value);
     });
   }
-  const paytoURL = !url ? "" : url.toString();
+  const paytoURL = !url ? "" : url.href;
 
   const errors: FormErrors<Entity> = {
     target: value.target === noTargetValue ? i18n.str`required` : undefined,
diff --git a/packages/merchant-backoffice-ui/tests/__mocks__/browserMocks.ts 
b/packages/merchant-backoffice-ui/src/context/api.ts
similarity index 57%
rename from packages/merchant-backoffice-ui/tests/__mocks__/browserMocks.ts
rename to packages/merchant-backoffice-ui/src/context/api.ts
index 98a5153de..81586bd35 100644
--- a/packages/merchant-backoffice-ui/tests/__mocks__/browserMocks.ts
+++ b/packages/merchant-backoffice-ui/src/context/api.ts
@@ -14,29 +14,30 @@
  GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 
- /**
+/**
  *
  * @author Sebastian Javier Marchano (sebasjm)
  */
 
-// Mock Browser API's which are not supported by JSDOM, e.g. ServiceWorker, 
LocalStorage
-/**
- * An example how to mock localStorage is given below 👇
- */
-
-/* 
-// Mocks localStorage
-const localStorageMock = (function() {
-       let store = {};
-
-       return {
-               getItem: (key) => store[key] || null,
-               setItem: (key, value) => store[key] = value.toString(),
-               clear: () => store = {}
-       };
-
-})();
-
-Object.defineProperty(window, 'localStorage', {
-       value: localStorageMock
-}); */
+import { ComponentChildren, createContext, h, VNode } from "preact";
+import { useContext } from "preact/hooks";
+import { defaultRequestHandler } from "../utils/request.js";
+
+interface Type {
+  request: typeof defaultRequestHandler;
+}
+
+const Context = createContext<Type>({
+  request: defaultRequestHandler,
+});
+
+export const useApiContext = (): Type => useContext(Context);
+export const ApiContextProvider = ({
+  children,
+  value,
+}: {
+  value: Type;
+  children: ComponentChildren;
+}): VNode => {
+  return h(Context.Provider, { value, children });
+};
diff --git a/packages/merchant-backoffice-ui/src/context/backend.test.ts 
b/packages/merchant-backoffice-ui/src/context/backend.test.ts
new file mode 100644
index 000000000..c7fb19293
--- /dev/null
+++ b/packages/merchant-backoffice-ui/src/context/backend.test.ts
@@ -0,0 +1,131 @@
+/*
+ This file is part of GNU Taler
+ (C) 2021-2023 Taler Systems S.A.
+
+ GNU Taler is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+ */
+
+/**
+ *
+ * @author Sebastian Javier Marchano (sebasjm)
+ */
+
+import { tests } from "@gnu-taler/web-util/lib/index.browser";
+import { ComponentChildren, h, VNode } from "preact";
+import { MerchantBackend } from "../declaration.js";
+import {
+  useAdminAPI,
+  useInstanceAPI,
+  useManagementAPI,
+} from "../hooks/instance.js";
+import { expect } from "chai";
+import { ApiMockEnvironment } from "../hooks/testing.js";
+import { API_CREATE_INSTANCE, API_UPDATE_CURRENT_INSTANCE_AUTH, 
API_UPDATE_INSTANCE_AUTH_BY_ID } from "../hooks/urls.js";
+
+interface TestingContextProps {
+  children?: ComponentChildren;
+}
+
+describe("backend context api ", () => {
+
+  it("should use new token after updating the instance token in the settings 
as user", async () => {
+    const env = new ApiMockEnvironment();
+
+    const hookBehavior = await tests.hookBehaveLikeThis(
+      () => {
+        const instance = useInstanceAPI();
+        const management = useManagementAPI("default");
+        const admin = useAdminAPI();
+
+        return { instance, management, admin };
+      },
+      {},
+      [
+        ({ instance, management, admin }) => {
+          env.addRequestExpectation(API_UPDATE_INSTANCE_AUTH_BY_ID("default"), 
{
+            request: {
+              method: "token",
+              token: "another_token",
+            },
+            response: {
+              name: "instance_name",
+            } as MerchantBackend.Instances.QueryInstancesResponse,
+          });
+
+          management.setNewToken("another_token")
+        },
+        ({ instance, management, admin }) => {
+          expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: 
"ok" });
+
+          env.addRequestExpectation(API_CREATE_INSTANCE, {
+            auth: "another_token",
+            request: {
+              id: "new_instance_id",
+            } as MerchantBackend.Instances.InstanceConfigurationMessage,
+          });
+
+          admin.createInstance({
+            id: "new_instance_id",
+          } as MerchantBackend.Instances.InstanceConfigurationMessage);
+
+        },
+      ], env.buildTestingContext());
+
+    expect(hookBehavior).deep.eq({ result: "ok" });
+    expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" });
+  });
+
+  it("should use new token after updating the instance token in the settings 
as admin", async () => {
+    const env = new ApiMockEnvironment();
+
+    const hookBehavior = await tests.hookBehaveLikeThis(
+      () => {
+        const instance = useInstanceAPI();
+        const management = useManagementAPI("default");
+        const admin = useAdminAPI();
+
+        return { instance, management, admin };
+      },
+      {},
+      [
+        ({ instance, management, admin }) => {
+          env.addRequestExpectation(API_UPDATE_CURRENT_INSTANCE_AUTH, {
+            request: {
+              method: "token",
+              token: "another_token",
+            },
+            response: {
+              name: "instance_name",
+            } as MerchantBackend.Instances.QueryInstancesResponse,
+          });
+          instance.setNewToken("another_token");
+        },
+        ({ instance, management, admin }) => {
+          expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: 
"ok" });
+
+          env.addRequestExpectation(API_CREATE_INSTANCE, {
+            auth: "another_token",
+            request: {
+              id: "new_instance_id",
+            } as MerchantBackend.Instances.InstanceConfigurationMessage,
+          });
+
+          admin.createInstance({
+            id: "new_instance_id",
+          } as MerchantBackend.Instances.InstanceConfigurationMessage);
+        },
+      ], env.buildTestingContext());
+
+    expect(hookBehavior).deep.eq({ result: "ok" });
+    expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" });
+  });
+});
diff --git a/packages/merchant-backoffice-ui/src/context/backend.ts 
b/packages/merchant-backoffice-ui/src/context/backend.ts
index f8d1bc397..f7f8afea6 100644
--- a/packages/merchant-backoffice-ui/src/context/backend.ts
+++ b/packages/merchant-backoffice-ui/src/context/backend.ts
@@ -31,6 +31,7 @@ interface BackendContextType {
   clearAllTokens: () => void;
   addTokenCleaner: (c: () => void) => void;
   updateLoginStatus: (url: string, token?: string) => void;
+  updateToken: (token?: string) => void;
 }
 
 const BackendContext = createContext<BackendContextType>({
@@ -41,6 +42,7 @@ const BackendContext = createContext<BackendContextType>({
   clearAllTokens: () => null,
   addTokenCleaner: () => null,
   updateLoginStatus: () => null,
+  updateToken: () => null,
 });
 
 function useBackendContextState(
@@ -87,6 +89,7 @@ function useBackendContextState(
     updateLoginStatus,
     resetBackend,
     clearAllTokens,
+    updateToken,
     addTokenCleaner: addTokenCleanerMemo,
   };
 }
diff --git a/packages/merchant-backoffice-ui/src/context/fetch.ts 
b/packages/merchant-backoffice-ui/src/context/fetch.ts
deleted file mode 100644
index 88c9bc30c..000000000
--- a/packages/merchant-backoffice-ui/src/context/fetch.ts
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2021-2023 Taler Systems S.A.
-
- GNU Taler is free software; you can redistribute it and/or modify it under the
- terms of the GNU General Public License as published by the Free Software
- Foundation; either version 3, or (at your option) any later version.
-
- GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along with
- GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
- */
-
-/**
- *
- * @author Sebastian Javier Marchano (sebasjm)
- */
-
-import { h, createContext, VNode, ComponentChildren } from "preact";
-import { useContext } from "preact/hooks";
-import useSWR from "swr";
-import useSWRInfinite from "swr/infinite";
-
-interface Type {
-  useSWR: typeof useSWR;
-  useSWRInfinite: typeof useSWRInfinite;
-}
-
-const Context = createContext<Type>({} as Type);
-
-export const useFetchContext = (): Type => useContext(Context);
-export const FetchContextProvider = ({
-  children,
-}: {
-  children: ComponentChildren;
-}): VNode => {
-  return h(Context.Provider, { value: { useSWR, useSWRInfinite }, children });
-};
-
-export const FetchContextProviderTesting = ({
-  children,
-  data,
-}: {
-  children: ComponentChildren;
-  data: any;
-}): VNode => {
-  return h(Context.Provider, {
-    value: { useSWR: () => data, useSWRInfinite },
-    children,
-  });
-};
diff --git a/packages/merchant-backoffice-ui/src/hooks/async.ts 
b/packages/merchant-backoffice-ui/src/hooks/async.ts
index 6c116e628..f22badc88 100644
--- a/packages/merchant-backoffice-ui/src/hooks/async.ts
+++ b/packages/merchant-backoffice-ui/src/hooks/async.ts
@@ -19,7 +19,6 @@
  * @author Sebastian Javier Marchano (sebasjm)
  */
 import { useState } from "preact/hooks";
-import { cancelPendingRequest } from "./backend.js";
 
 export interface Options {
   slowTolerance: number;
@@ -62,8 +61,7 @@ export function useAsync<T>(
     clearTimeout(handler);
   };
 
-  function cancel() {
-    cancelPendingRequest();
+  function cancel(): void {
     setLoading(false);
     setSlow(false);
   }
diff --git a/packages/merchant-backoffice-ui/src/hooks/backend.ts 
b/packages/merchant-backoffice-ui/src/hooks/backend.ts
index cbfac35de..a0639a4a0 100644
--- a/packages/merchant-backoffice-ui/src/hooks/backend.ts
+++ b/packages/merchant-backoffice-ui/src/hooks/backend.ts
@@ -20,15 +20,16 @@
  */
 
 import { useSWRConfig } from "swr";
-import axios, { AxiosError, AxiosResponse } from "axios";
 import { MerchantBackend } from "../declaration.js";
 import { useBackendContext } from "../context/backend.js";
-import { useEffect, useState } from "preact/hooks";
-import { DEFAULT_REQUEST_TIMEOUT } from "../utils/constants.js";
+import { useCallback, useEffect, useState } from "preact/hooks";
+import { useInstanceContext } from "../context/instance.js";
 import {
-  axiosHandler,
-  removeAxiosCancelToken,
-} from "../utils/switchableAxios.js";
+  HttpResponse,
+  HttpResponseOk,
+  RequestOptions,
+} from "../utils/request.js";
+import { useApiContext } from "../context/api.js";
 
 export function useMatchMutate(): (
   re: RegExp,
@@ -44,9 +45,7 @@ export function useMatchMutate(): (
 
   return function matchRegexMutate(re: RegExp, value?: unknown) {
     const allKeys = Array.from(cache.keys());
-    // console.log(allKeys)
     const keys = allKeys.filter((key) => re.test(key));
-    // console.log(allKeys.length, keys.length)
     const mutations = keys.map((key) => {
       // console.log(key)
       mutate(key, value, true);
@@ -55,268 +54,234 @@ export function useMatchMutate(): (
   };
 }
 
-export type HttpResponse<T> =
-  | HttpResponseOk<T>
-  | HttpResponseLoading<T>
-  | HttpError;
-export type HttpResponsePaginated<T> =
-  | HttpResponseOkPaginated<T>
-  | HttpResponseLoading<T>
-  | HttpError;
-
-export interface RequestInfo {
-  url: string;
-  hasToken: boolean;
-  params: unknown;
-  data: unknown;
-  status: number;
-}
-
-interface HttpResponseLoading<T> {
-  ok?: false;
-  loading: true;
-  clientError?: false;
-  serverError?: false;
-
-  data?: T;
-}
-export interface HttpResponseOk<T> {
-  ok: true;
-  loading?: false;
-  clientError?: false;
-  serverError?: false;
-
-  data: T;
-  info?: RequestInfo;
-}
-
-export type HttpResponseOkPaginated<T> = HttpResponseOk<T> & WithPagination;
-
-export interface WithPagination {
-  loadMore: () => void;
-  loadMorePrev: () => void;
-  isReachingEnd?: boolean;
-  isReachingStart?: boolean;
-}
-
-export type HttpError =
-  | HttpResponseClientError
-  | HttpResponseServerError
-  | HttpResponseUnexpectedError;
-export interface SwrError {
-  info: unknown;
-  status: number;
-  message: string;
-}
-export interface HttpResponseServerError {
-  ok?: false;
-  loading?: false;
-  clientError?: false;
-  serverError: true;
-
-  error?: MerchantBackend.ErrorDetail;
-  status: number;
-  message: string;
-  info?: RequestInfo;
-}
-interface HttpResponseClientError {
-  ok?: false;
-  loading?: false;
-  clientError: true;
-  serverError?: false;
-
-  info?: RequestInfo;
-  isUnauthorized: boolean;
-  isNotfound: boolean;
-  status: number;
-  error?: MerchantBackend.ErrorDetail;
-  message: string;
-}
-
-interface HttpResponseUnexpectedError {
-  ok?: false;
-  loading?: false;
-  clientError?: false;
-  serverError?: false;
-
-  info?: RequestInfo;
-  status?: number;
-  error: unknown;
-  message: string;
-}
-
-type Methods = "get" | "post" | "patch" | "delete" | "put";
-
-interface RequestOptions {
-  method?: Methods;
-  token?: string;
-  data?: unknown;
-  params?: unknown;
-}
-
-function buildRequestOk<T>(
-  res: AxiosResponse<T>,
-  url: string,
-  hasToken: boolean,
-): HttpResponseOk<T> {
-  return {
-    ok: true,
-    data: res.data,
-    info: {
-      params: res.config.params,
-      data: res.config.data,
-      url,
-      hasToken,
-      status: res.status,
-    },
-  };
-}
-
-// function buildResponse<T>(data?: T, error?: MerchantBackend.ErrorDetail, 
isValidating?: boolean): HttpResponse<T> {
-//   if (isValidating) return {loading: true}
-//   if (error) return buildRequestFailed()
-// }
-
-function buildRequestFailed(
-  ex: AxiosError<MerchantBackend.ErrorDetail>,
-  url: string,
-  hasToken: boolean,
-):
-  | HttpResponseClientError
-  | HttpResponseServerError
-  | HttpResponseUnexpectedError {
-  const status = ex.response?.status;
-
-  const info: RequestInfo = {
-    data: ex.request?.data,
-    params: ex.request?.params,
-    url,
-    hasToken,
-    status: status || 0,
-  };
-
-  if (status && status >= 400 && status < 500) {
-    const error: HttpResponseClientError = {
-      clientError: true,
-      isNotfound: status === 404,
-      isUnauthorized: status === 401,
-      status,
-      info,
-      message: ex.response?.data?.hint || ex.message,
-      error: ex.response?.data,
-    };
-    return error;
-  }
-  if (status && status >= 500 && status < 600) {
-    const error: HttpResponseServerError = {
-      serverError: true,
-      status,
-      info,
-      message:
-        `${ex.response?.data?.hint} (code ${ex.response?.data?.code})` ||
-        ex.message,
-      error: ex.response?.data,
-    };
-    return error;
-  }
-
-  const error: HttpResponseUnexpectedError = {
-    info,
-    status,
-    error: ex,
-    message: ex.message,
-  };
-
-  return error;
-}
-
-const CancelToken = axios.CancelToken;
-let source = CancelToken.source();
-
-export function cancelPendingRequest(): void {
-  source.cancel("canceled by the user");
-  source = CancelToken.source();
-}
-
-export function isAxiosError<T>(
-  error: AxiosError | any,
-): error is AxiosError<T> {
-  return error && error.isAxiosError;
-}
-
-export async function request<T>(
-  url: string,
-  options: RequestOptions = {},
-): Promise<HttpResponseOk<T>> {
-  const headers = options.token
-    ? { Authorization: `Bearer ${options.token}` }
-    : undefined;
-
-  try {
-    const res = await axiosHandler({
-      url,
-      responseType: "json",
-      headers,
-      cancelToken: !removeAxiosCancelToken ? source.token : undefined,
-      method: options.method || "get",
-      data: options.data,
-      params: options.params,
-      timeout: DEFAULT_REQUEST_TIMEOUT * 1000,
-    });
-    return buildRequestOk<T>(res, url, !!options.token);
-  } catch (e) {
-    if (isAxiosError<MerchantBackend.ErrorDetail>(e)) {
-      const error = buildRequestFailed(e, url, !!options.token);
-      throw error;
-    }
-    throw e;
-  }
-}
-
-export function multiFetcher<T>(
-  urls: string[],
-  token: string,
-  backend: string,
-): Promise<HttpResponseOk<T>[]> {
-  return Promise.all(urls.map((url) => fetcher<T>(url, token, backend)));
-}
-
-export function fetcher<T>(
-  url: string,
-  token: string,
-  backend: string,
-): Promise<HttpResponseOk<T>> {
-  return request<T>(`${backend}${url}`, { token });
-}
-
 export function useBackendInstancesTestForAdmin(): 
HttpResponse<MerchantBackend.Instances.InstancesResponse> {
-  const { url, token } = useBackendContext();
+  const { request } = useBackendBaseRequest();
 
   type Type = MerchantBackend.Instances.InstancesResponse;
 
   const [result, setResult] = useState<HttpResponse<Type>>({ loading: true });
 
   useEffect(() => {
-    request<Type>(`${url}/management/instances`, { token })
+    request<Type>(`/management/instances`)
       .then((data) => setResult(data))
       .catch((error) => setResult(error));
-  }, [url, token]);
+  }, [request]);
 
   return result;
 }
 
 export function useBackendConfig(): 
HttpResponse<MerchantBackend.VersionResponse> {
-  const { url, token } = useBackendContext();
+  const { request } = useBackendBaseRequest();
 
   type Type = MerchantBackend.VersionResponse;
 
   const [result, setResult] = useState<HttpResponse<Type>>({ loading: true });
 
   useEffect(() => {
-    request<Type>(`${url}/config`, { token })
+    request<Type>(`/config`)
       .then((data) => setResult(data))
       .catch((error) => setResult(error));
-  }, [url, token]);
+  }, [request]);
 
   return result;
 }
+
+interface useBackendInstanceRequestType {
+  request: <T>(
+    path: string,
+    options?: RequestOptions,
+  ) => Promise<HttpResponseOk<T>>;
+  fetcher: <T>(path: string) => Promise<HttpResponseOk<T>>;
+  reserveDetailFetcher: <T>(path: string) => Promise<HttpResponseOk<T>>;
+  tipsDetailFetcher: <T>(path: string) => Promise<HttpResponseOk<T>>;
+  multiFetcher: <T>(url: string[]) => Promise<HttpResponseOk<T>[]>;
+  orderFetcher: <T>(
+    path: string,
+    paid?: YesOrNo,
+    refunded?: YesOrNo,
+    wired?: YesOrNo,
+    searchDate?: Date,
+    delta?: number,
+  ) => Promise<HttpResponseOk<T>>;
+  transferFetcher: <T>(
+    path: string,
+    payto_uri?: string,
+    verified?: string,
+    position?: string,
+    delta?: number,
+  ) => Promise<HttpResponseOk<T>>;
+  templateFetcher: <T>(
+    path: string,
+    position?: string,
+    delta?: number,
+  ) => Promise<HttpResponseOk<T>>;
+}
+interface useBackendBaseRequestType {
+  request: <T>(
+    path: string,
+    options?: RequestOptions,
+  ) => Promise<HttpResponseOk<T>>;
+}
+
+type YesOrNo = "yes" | "no";
+
+/**
+ *
+ * @param root the request is intended to the base URL and no the instance URL
+ * @returns request handler to
+ */
+export function useBackendBaseRequest(): useBackendBaseRequestType {
+  const { url: backend, token } = useBackendContext();
+  const { request: requestHandler } = useApiContext();
+
+  const request = useCallback(
+    function requestImpl<T>(
+      path: string,
+      options: RequestOptions = {},
+    ): Promise<HttpResponseOk<T>> {
+      return requestHandler<T>(backend, path, { token, ...options });
+    },
+    [backend, token],
+  );
+
+  return { request };
+}
+
+export function useBackendInstanceRequest(): useBackendInstanceRequestType {
+  const { url: baseUrl, token: baseToken } = useBackendContext();
+  const { token: instanceToken, id, admin } = useInstanceContext();
+  const { request: requestHandler } = useApiContext();
+
+  const { backend, token } = !admin
+    ? { backend: baseUrl, token: baseToken }
+    : { backend: `${baseUrl}/instances/${id}`, token: instanceToken };
+
+  const request = useCallback(
+    function requestImpl<T>(
+      path: string,
+      options: RequestOptions = {},
+    ): Promise<HttpResponseOk<T>> {
+      return requestHandler<T>(backend, path, { token, ...options });
+    },
+    [backend, token],
+  );
+
+  const multiFetcher = useCallback(
+    function multiFetcherImpl<T>(
+      paths: string[],
+    ): Promise<HttpResponseOk<T>[]> {
+      return Promise.all(
+        paths.map((path) => requestHandler<T>(backend, path, { token })),
+      );
+    },
+    [backend, token],
+  );
+
+  const fetcher = useCallback(
+    function fetcherImpl<T>(path: string): Promise<HttpResponseOk<T>> {
+      return requestHandler<T>(backend, path, { token });
+    },
+    [backend, token],
+  );
+
+  const orderFetcher = useCallback(
+    function orderFetcherImpl<T>(
+      path: string,
+      paid?: YesOrNo,
+      refunded?: YesOrNo,
+      wired?: YesOrNo,
+      searchDate?: Date,
+      delta?: number,
+    ): Promise<HttpResponseOk<T>> {
+      const date_ms =
+        delta && delta < 0 && searchDate
+          ? searchDate.getTime() + 1
+          : searchDate?.getTime();
+      const params: any = {};
+      if (paid !== undefined) params.paid = paid;
+      if (delta !== undefined) params.delta = delta;
+      if (refunded !== undefined) params.refunded = refunded;
+      if (wired !== undefined) params.wired = wired;
+      if (date_ms !== undefined) params.date_ms = date_ms;
+      return requestHandler<T>(backend, path, { params, token });
+    },
+    [backend, token],
+  );
+
+  const reserveDetailFetcher = useCallback(
+    function reserveDetailFetcherImpl<T>(
+      path: string,
+    ): Promise<HttpResponseOk<T>> {
+      return requestHandler<T>(backend, path, {
+        params: {
+          tips: "yes",
+        },
+        token,
+      });
+    },
+    [backend, token],
+  );
+
+  const tipsDetailFetcher = useCallback(
+    function tipsDetailFetcherImpl<T>(
+      path: string,
+    ): Promise<HttpResponseOk<T>> {
+      return requestHandler<T>(backend, path, {
+        params: {
+          pickups: "yes",
+        },
+        token,
+      });
+    },
+    [backend, token],
+  );
+
+  const transferFetcher = useCallback(
+    function transferFetcherImpl<T>(
+      path: string,
+      payto_uri?: string,
+      verified?: string,
+      position?: string,
+      delta?: number,
+    ): Promise<HttpResponseOk<T>> {
+      const params: any = {};
+      if (payto_uri !== undefined) params.payto_uri = payto_uri;
+      if (verified !== undefined) params.verified = verified;
+      if (delta !== undefined) {
+        params.limit = delta;
+      }
+      if (position !== undefined) params.offset = position;
+
+      return requestHandler<T>(backend, path, { params, token });
+    },
+    [backend, token],
+  );
+
+  const templateFetcher = useCallback(
+    function templateFetcherImpl<T>(
+      path: string,
+      position?: string,
+      delta?: number,
+    ): Promise<HttpResponseOk<T>> {
+      const params: any = {};
+      if (delta !== undefined) {
+        params.limit = delta;
+      }
+      if (position !== undefined) params.offset = position;
+
+      return requestHandler<T>(backend, path, { params, token });
+    },
+    [backend, token],
+  );
+
+  return {
+    request,
+    fetcher,
+    multiFetcher,
+    orderFetcher,
+    reserveDetailFetcher,
+    tipsDetailFetcher,
+    transferFetcher,
+    templateFetcher,
+  };
+}
diff --git a/packages/merchant-backoffice-ui/src/hooks/index.ts 
b/packages/merchant-backoffice-ui/src/hooks/index.ts
index 0581d9938..bb210c9ba 100644
--- a/packages/merchant-backoffice-ui/src/hooks/index.ts
+++ b/packages/merchant-backoffice-ui/src/hooks/index.ts
@@ -59,6 +59,7 @@ export function useBackendDefaultToken(
 export function useBackendInstanceToken(
   id: string,
 ): [string | undefined, StateUpdater<string | undefined>] {
+  const [random, setRandom] = useState(0);
   const [token, setToken] = useLocalStorage(`backend-token-${id}`);
   const [defaultToken, defaultSetToken] = useBackendDefaultToken();
 
@@ -66,8 +67,20 @@ export function useBackendInstanceToken(
   if (id === "default") {
     return [defaultToken, defaultSetToken];
   }
+  function updateToken(
+    value:
+      | (string | undefined)
+      | ((s: string | undefined) => string | undefined),
+  ): void {
+    setToken((p) => {
+      const toStore = value instanceof Function ? value(p) : value;
+      // setToken(value)
+      setRandom(new Date().getTime());
+      return toStore;
+    });
+  }
 
-  return [token, setToken];
+  return [token, updateToken];
 }
 
 export function useLang(initial?: string): [string, StateUpdater<string>] {
diff --git a/packages/merchant-backoffice-ui/src/hooks/instance.test.ts 
b/packages/merchant-backoffice-ui/src/hooks/instance.test.ts
new file mode 100644
index 000000000..c7aa63e20
--- /dev/null
+++ b/packages/merchant-backoffice-ui/src/hooks/instance.test.ts
@@ -0,0 +1,660 @@
+/*
+ This file is part of GNU Taler
+ (C) 2021-2023 Taler Systems S.A.
+
+ GNU Taler is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+ */
+
+/**
+ *
+ * @author Sebastian Javier Marchano (sebasjm)
+ */
+
+import { tests } from "@gnu-taler/web-util/lib/index.browser";
+import { expect } from "chai";
+import { MerchantBackend } from "../declaration.js";
+import { useAdminAPI, useBackendInstances, useInstanceAPI, useInstanceDetails, 
useManagementAPI } from "./instance.js";
+import { ApiMockEnvironment } from "./testing.js";
+import {
+  API_CREATE_INSTANCE,
+  API_DELETE_INSTANCE,
+  API_GET_CURRENT_INSTANCE,
+  API_LIST_INSTANCES,
+  API_UPDATE_CURRENT_INSTANCE,
+  API_UPDATE_CURRENT_INSTANCE_AUTH, API_UPDATE_INSTANCE_BY_ID
+} from "./urls.js";
+
+describe("instance api interaction with details", () => {
+
+  it("should evict cache when updating an instance", async () => {
+    const env = new ApiMockEnvironment();
+
+    env.addRequestExpectation(API_GET_CURRENT_INSTANCE, {
+      response: {
+        name: 'instance_name'
+      } as MerchantBackend.Instances.QueryInstancesResponse,
+    });
+
+    const hookBehavior = await tests.hookBehaveLikeThis(
+      () => {
+        const api = useInstanceAPI();
+        const query = useInstanceDetails();
+        return { query, api };
+      },
+      {},
+      [
+        ({ query, api }) => {
+          expect(query.loading).true;
+        },
+        ({ query, api }) => {
+          expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: 
"ok" });
+
+          expect(query.loading).false;
+          expect(query.ok).true
+          if (!query.ok) return;
+          expect(query.data).deep.equals({
+            name: 'instance_name'
+          });
+          env.addRequestExpectation(API_UPDATE_CURRENT_INSTANCE, {
+            request: {
+              name: 'other_name'
+            } as MerchantBackend.Instances.InstanceReconfigurationMessage,
+          });
+          env.addRequestExpectation(API_GET_CURRENT_INSTANCE, {
+            response: {
+              name: 'other_name'
+            } as MerchantBackend.Instances.QueryInstancesResponse,
+          });
+          api.updateInstance({
+            name: 'other_name'
+          } as MerchantBackend.Instances.InstanceReconfigurationMessage);
+        },
+        ({ query, api }) => {
+          expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: 
"ok" });
+          expect(query.loading).false;
+          expect(query.ok).true
+          if (!query.ok) return;
+          expect(query.data).deep.equals({
+            name: 'other_name'
+          });
+        },
+      ], env.buildTestingContext());
+
+    expect(hookBehavior).deep.eq({ result: "ok" });
+    expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" });
+
+  });
+
+  it("should evict cache when setting the instance's token", async () => {
+    const env = new ApiMockEnvironment();
+
+    env.addRequestExpectation(API_GET_CURRENT_INSTANCE, {
+      response: {
+        name: 'instance_name',
+        auth: {
+          method: 'token',
+          token: 'not-secret',
+        }
+      } as MerchantBackend.Instances.QueryInstancesResponse,
+    });
+
+    const hookBehavior = await tests.hookBehaveLikeThis(
+      () => {
+        const api = useInstanceAPI();
+        const query = useInstanceDetails();
+        return { query, api };
+      },
+      {},
+      [
+        ({ query, api }) => {
+          expect(query.loading).true;
+        },
+        ({ query, api }) => {
+          expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: 
"ok" });
+          expect(query.loading).false;
+          expect(query.ok).true
+          if (!query.ok) return;
+          expect(query.data).deep.equals({
+            name: 'instance_name',
+            auth: {
+              method: 'token',
+              token: 'not-secret',
+            }
+          });
+          env.addRequestExpectation(API_UPDATE_CURRENT_INSTANCE_AUTH, {
+            request: {
+              method: 'token',
+              token: 'secret'
+            } as MerchantBackend.Instances.InstanceAuthConfigurationMessage,
+          });
+          env.addRequestExpectation(API_GET_CURRENT_INSTANCE, {
+            response: {
+              name: 'instance_name',
+              auth: {
+                method: 'token',
+                token: 'secret',
+              }
+            } as MerchantBackend.Instances.QueryInstancesResponse,
+          });
+          api.setNewToken('secret')
+        },
+        ({ query, api }) => {
+          expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: 
"ok" });
+          expect(query.loading).false;
+          expect(query.ok).true
+          if (!query.ok) return;
+          expect(query.data).deep.equals({
+            name: 'instance_name',
+            auth: {
+              method: 'token',
+              token: 'secret',
+            }
+          });
+        },
+      ], env.buildTestingContext());
+    expect(hookBehavior).deep.eq({ result: "ok" });
+    expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" });
+
+  });
+
+  it("should evict cache when clearing the instance's token", async () => {
+    const env = new ApiMockEnvironment();
+
+    env.addRequestExpectation(API_GET_CURRENT_INSTANCE, {
+      response: {
+        name: 'instance_name',
+        auth: {
+          method: 'token',
+          token: 'not-secret',
+        }
+      } as MerchantBackend.Instances.QueryInstancesResponse,
+    });
+
+    const hookBehavior = await tests.hookBehaveLikeThis(
+      () => {
+        const api = useInstanceAPI();
+        const query = useInstanceDetails();
+        return { query, api };
+      },
+      {},
+      [
+        ({ query, api }) => {
+          expect(query.loading).true;
+        },
+        ({ query, api }) => {
+          expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: 
"ok" });
+          expect(query.loading).false;
+          expect(query.ok).true
+          if (!query.ok) return;
+          expect(query.data).deep.equals({
+            name: 'instance_name',
+            auth: {
+              method: 'token',
+              token: 'not-secret',
+            }
+          });
+          env.addRequestExpectation(API_UPDATE_CURRENT_INSTANCE_AUTH, {
+            request: {
+              method: 'external',
+            } as MerchantBackend.Instances.InstanceAuthConfigurationMessage,
+          });
+          env.addRequestExpectation(API_GET_CURRENT_INSTANCE, {
+            response: {
+              name: 'instance_name',
+              auth: {
+                method: 'external',
+              }
+            } as MerchantBackend.Instances.QueryInstancesResponse,
+          });
+
+          api.clearToken();
+        },
+        ({ query, api }) => {
+          expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: 
"ok" });
+          expect(query.loading).false;
+          expect(query.ok).true
+          if (!query.ok) return;
+          expect(query.data).deep.equals({
+            name: 'instance_name',
+            auth: {
+              method: 'external',
+            }
+          });
+        },
+      ], env.buildTestingContext());
+    expect(hookBehavior).deep.eq({ result: "ok" });
+    expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" });
+    // const { result, waitForNextUpdate } = renderHook(
+    //   () => {
+    //     const api = useInstanceAPI();
+    //     const query = useInstanceDetails();
+
+    //     return { query, api };
+    //   },
+    //   { wrapper: TestingContext }
+    // );
+
+    // expect(result.current).not.undefined;
+    // if (!result.current) {
+    //   return;
+    // }
+    // expect(result.current.query.loading).true;
+
+    // await waitForNextUpdate({ timeout: 1 });
+
+    // expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" 
});
+
+    // expect(result.current.query.loading).false;
+
+    // expect(result.current?.query.ok).true;
+    // if (!result.current?.query.ok) return;
+
+    // expect(result.current.query.data).equals({
+    //   name: 'instance_name',
+    //   auth: {
+    //     method: 'token',
+    //     token: 'not-secret',
+    //   }
+    // });
+
+
+    // act(async () => {
+    //   await result.current?.api.clearToken();
+    // });
+
+    // expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" 
});
+
+
+    // expect(result.current.query.loading).false;
+
+    // await waitForNextUpdate({ timeout: 1 });
+
+    // expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" 
});
+
+    // expect(result.current.query.loading).false;
+    // expect(result.current.query.ok).true;
+
+    // expect(result.current.query.data).equals({
+    //   name: 'instance_name',
+    //   auth: {
+    //     method: 'external',
+    //   }
+    // });
+  });
+});
+
+describe("instance admin api interaction with listing", () => {
+
+  it("should evict cache when creating a new instance", async () => {
+    const env = new ApiMockEnvironment();
+
+    env.addRequestExpectation(API_LIST_INSTANCES, {
+      response: {
+        instances: [{
+          name: 'instance_name'
+        } as MerchantBackend.Instances.Instance]
+      },
+    });
+
+    const hookBehavior = await tests.hookBehaveLikeThis(
+      () => {
+        const api = useAdminAPI();
+        const query = useBackendInstances();
+        return { query, api };
+      },
+      {},
+      [
+        ({ query, api }) => {
+          expect(query.loading).true;
+        },
+        ({ query, api }) => {
+          expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: 
"ok" });
+          expect(query.loading).false;
+          expect(query.ok).true
+          if (!query.ok) return;
+          expect(query.data).deep.equals({
+            instances: [{
+              name: 'instance_name'
+            }]
+          });
+
+          env.addRequestExpectation(API_CREATE_INSTANCE, {
+            request: {
+              name: 'other_name'
+            } as MerchantBackend.Instances.InstanceConfigurationMessage,
+          });
+          env.addRequestExpectation(API_LIST_INSTANCES, {
+            response: {
+              instances: [{
+                name: 'instance_name'
+              } as MerchantBackend.Instances.Instance,
+              {
+                name: 'other_name'
+              } as MerchantBackend.Instances.Instance]
+            },
+          });
+
+          api.createInstance({
+            name: 'other_name'
+          } as MerchantBackend.Instances.InstanceConfigurationMessage);
+        },
+        ({ query, api }) => {
+          expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: 
"ok" });
+          expect(query.loading).false;
+          expect(query.ok).true
+          if (!query.ok) return;
+          expect(query.data).deep.equals({
+            instances: [{
+              name: 'instance_name'
+            }, {
+              name: 'other_name'
+            }]
+          });
+        },
+      ], env.buildTestingContext());
+    expect(hookBehavior).deep.eq({ result: "ok" });
+    expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" });
+
+  });
+
+  it("should evict cache when deleting an instance", async () => {
+    const env = new ApiMockEnvironment();
+
+    env.addRequestExpectation(API_LIST_INSTANCES, {
+      response: {
+        instances: [{
+          id: 'default',
+          name: 'instance_name'
+        } as MerchantBackend.Instances.Instance,
+        {
+          id: 'the_id',
+          name: 'second_instance'
+        } as MerchantBackend.Instances.Instance]
+      },
+    });
+
+    const hookBehavior = await tests.hookBehaveLikeThis(
+      () => {
+        const api = useAdminAPI();
+        const query = useBackendInstances();
+        return { query, api };
+      },
+      {},
+      [
+        ({ query, api }) => {
+          expect(query.loading).true;
+        },
+        ({ query, api }) => {
+          expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: 
"ok" });
+          expect(query.loading).false;
+          expect(query.ok).true
+          if (!query.ok) return;
+          expect(query.data).deep.equals({
+            instances: [{
+              id: 'default',
+              name: 'instance_name'
+            }, {
+              id: 'the_id',
+              name: 'second_instance'
+            }]
+          });
+
+          env.addRequestExpectation(API_DELETE_INSTANCE('the_id'), {});
+          env.addRequestExpectation(API_LIST_INSTANCES, {
+            response: {
+              instances: [{
+                id: 'default',
+                name: 'instance_name'
+              } as MerchantBackend.Instances.Instance]
+            },
+          });
+
+          api.deleteInstance('the_id');
+        },
+        ({ query, api }) => {
+          expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: 
"ok" });
+          expect(query.loading).false;
+          expect(query.ok).true
+          if (!query.ok) return;
+          expect(query.data).deep.equals({
+            instances: [{
+              id: 'default',
+              name: 'instance_name'
+            }]
+          });
+        },
+      ], env.buildTestingContext());
+    expect(hookBehavior).deep.eq({ result: "ok" });
+    expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" });
+
+    // const { result, waitForNextUpdate } = renderHook(
+    //   () => {
+    //     const api = useAdminAPI();
+    //     const query = useBackendInstances();
+
+    //     return { query, api };
+    //   },
+    //   { wrapper: TestingContext }
+    // );
+
+    // expect(result.current).not.undefined;
+    // if (!result.current) {
+    //   return;
+    // }
+    // expect(result.current.query.loading).true;
+
+    // await waitForNextUpdate({ timeout: 1 });
+
+    // expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" 
});
+
+    // expect(result.current.query.loading).false;
+
+    // expect(result.current?.query.ok).true;
+    // if (!result.current?.query.ok) return;
+
+    // expect(result.current.query.data).equals({
+    //   instances: [{
+    //     id: 'default',
+    //     name: 'instance_name'
+    //   }, {
+    //     id: 'the_id',
+    //     name: 'second_instance'
+    //   }]
+    // });
+
+    // env.addRequestExpectation(API_DELETE_INSTANCE('the_id'), {});
+
+    // act(async () => {
+    //   await result.current?.api.deleteInstance('the_id');
+    // });
+
+    // expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" 
});
+
+    // env.addRequestExpectation(API_LIST_INSTANCES, {
+    //   response: {
+    //     instances: [{
+    //       id: 'default',
+    //       name: 'instance_name'
+    //     } as MerchantBackend.Instances.Instance]
+    //   },
+    // });
+
+    // expect(result.current.query.loading).false;
+
+    // await waitForNextUpdate({ timeout: 1 });
+
+    // expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" 
});
+
+    // expect(result.current.query.loading).false;
+    // expect(result.current.query.ok).true;
+
+    // expect(result.current.query.data).equals({
+    //   instances: [{
+    //     id: 'default',
+    //     name: 'instance_name'
+    //   }]
+    // });
+  });
+
+  it("should evict cache when deleting (purge) an instance", async () => {
+    const env = new ApiMockEnvironment();
+
+    env.addRequestExpectation(API_LIST_INSTANCES, {
+      response: {
+        instances: [{
+          id: 'default',
+          name: 'instance_name'
+        } as MerchantBackend.Instances.Instance,
+        {
+          id: 'the_id',
+          name: 'second_instance'
+        } as MerchantBackend.Instances.Instance]
+      },
+    });
+
+    const hookBehavior = await tests.hookBehaveLikeThis(
+      () => {
+        const api = useAdminAPI();
+        const query = useBackendInstances();
+        return { query, api };
+      },
+      {},
+      [
+        ({ query, api }) => {
+          expect(query.loading).true;
+        },
+        ({ query, api }) => {
+          expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: 
"ok" });
+          expect(query.loading).false;
+          expect(query.ok).true
+          if (!query.ok) return;
+          expect(query.data).deep.equals({
+            instances: [{
+              id: 'default',
+              name: 'instance_name'
+            }, {
+              id: 'the_id',
+              name: 'second_instance'
+            }]
+          });
+
+          env.addRequestExpectation(API_DELETE_INSTANCE('the_id'), {
+            qparam: {
+              purge: 'YES'
+            }
+          });
+          env.addRequestExpectation(API_LIST_INSTANCES, {
+            response: {
+              instances: [{
+                id: 'default',
+                name: 'instance_name'
+              } as MerchantBackend.Instances.Instance]
+            },
+          });
+
+          api.purgeInstance('the_id')
+        },
+        ({ query, api }) => {
+          expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: 
"ok" });
+          expect(query.loading).false;
+          expect(query.ok).true
+          if (!query.ok) return;
+          expect(query.data).deep.equals({
+            instances: [{
+              id: 'default',
+              name: 'instance_name'
+            }]
+          });
+        },
+      ], env.buildTestingContext());
+    expect(hookBehavior).deep.eq({ result: "ok" });
+    expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" });
+
+  });
+});
+
+describe("instance management api interaction with listing", () => {
+
+  it("should evict cache when updating an instance", async () => {
+    const env = new ApiMockEnvironment();
+
+    env.addRequestExpectation(API_LIST_INSTANCES, {
+      response: {
+        instances: [{
+          id: 'managed',
+          name: 'instance_name'
+        } as MerchantBackend.Instances.Instance]
+      },
+    });
+
+    const hookBehavior = await tests.hookBehaveLikeThis(
+      () => {
+        const api = useManagementAPI('managed');
+        const query = useBackendInstances();
+        return { query, api };
+      },
+      {},
+      [
+        ({ query, api }) => {
+          expect(query.loading).true;
+        },
+        ({ query, api }) => {
+          expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: 
"ok" });
+          expect(query.loading).false;
+          expect(query.ok).true
+          if (!query.ok) return;
+          expect(query.data).deep.equals({
+            instances: [{
+              id: 'managed',
+              name: 'instance_name'
+            }]
+          });
+
+          env.addRequestExpectation(API_UPDATE_INSTANCE_BY_ID('managed'), {
+            request: {
+              name: 'other_name'
+            } as MerchantBackend.Instances.InstanceReconfigurationMessage,
+          });
+          env.addRequestExpectation(API_LIST_INSTANCES, {
+            response: {
+              instances: [
+                {
+                  id: 'managed',
+                  name: 'other_name'
+                } as MerchantBackend.Instances.Instance]
+            },
+          });
+
+          api.updateInstance({
+            name: 'other_name'
+          } as MerchantBackend.Instances.InstanceConfigurationMessage);
+        },
+        ({ query, api }) => {
+          expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: 
"ok" });
+          expect(query.loading).false;
+          expect(query.ok).true
+          if (!query.ok) return;
+          expect(query.data).deep.equals({
+            instances: [{
+              id: 'managed',
+              name: 'other_name'
+            }]
+          });
+        },
+      ], env.buildTestingContext());
+    expect(hookBehavior).deep.eq({ result: "ok" });
+    expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" });
+
+  });
+
+});
+
diff --git a/packages/merchant-backoffice-ui/src/hooks/instance.ts 
b/packages/merchant-backoffice-ui/src/hooks/instance.ts
index ab59487de..3c05472d0 100644
--- a/packages/merchant-backoffice-ui/src/hooks/instance.ts
+++ b/packages/merchant-backoffice-ui/src/hooks/instance.ts
@@ -15,14 +15,11 @@
  */
 import useSWR, { useSWRConfig } from "swr";
 import { useBackendContext } from "../context/backend.js";
-import { useInstanceContext } from "../context/instance.js";
 import { MerchantBackend } from "../declaration.js";
+import { HttpError, HttpResponse, HttpResponseOk } from "../utils/request.js";
 import {
-  fetcher,
-  HttpError,
-  HttpResponse,
-  HttpResponseOk,
-  request,
+  useBackendBaseRequest,
+  useBackendInstanceRequest,
   useMatchMutate,
 } from "./backend.js";
 
@@ -36,15 +33,14 @@ interface InstanceAPI {
 }
 
 export function useAdminAPI(): AdminAPI {
-  const { url, token } = useBackendContext();
+  const { request } = useBackendBaseRequest();
   const mutateAll = useMatchMutate();
 
   const createInstance = async (
     instance: MerchantBackend.Instances.InstanceConfigurationMessage,
   ): Promise<void> => {
-    await request(`${url}/management/instances`, {
-      method: "post",
-      token,
+    await request(`/management/instances`, {
+      method: "POST",
       data: instance,
     });
 
@@ -52,18 +48,16 @@ export function useAdminAPI(): AdminAPI {
   };
 
   const deleteInstance = async (id: string): Promise<void> => {
-    await request(`${url}/management/instances/${id}`, {
-      method: "delete",
-      token,
+    await request(`/management/instances/${id}`, {
+      method: "DELETE",
     });
 
     mutateAll(/\/management\/instances/);
   };
 
   const purgeInstance = async (id: string): Promise<void> => {
-    await request(`${url}/management/instances/${id}`, {
-      method: "delete",
-      token,
+    await request(`/management/instances/${id}`, {
+      method: "DELETE",
       params: {
         purge: "YES",
       },
@@ -85,14 +79,14 @@ export interface AdminAPI {
 
 export function useManagementAPI(instanceId: string): InstanceAPI {
   const mutateAll = useMatchMutate();
-  const { url, token, updateLoginStatus } = useBackendContext();
+  const { updateToken } = useBackendContext();
+  const { request } = useBackendBaseRequest();
 
   const updateInstance = async (
     instance: MerchantBackend.Instances.InstanceReconfigurationMessage,
   ): Promise<void> => {
-    await request(`${url}/management/instances/${instanceId}`, {
-      method: "patch",
-      token,
+    await request(`/management/instances/${instanceId}`, {
+      method: "PATCH",
       data: instance,
     });
 
@@ -100,18 +94,16 @@ export function useManagementAPI(instanceId: string): 
InstanceAPI {
   };
 
   const deleteInstance = async (): Promise<void> => {
-    await request(`${url}/management/instances/${instanceId}`, {
-      method: "delete",
-      token,
+    await request(`/management/instances/${instanceId}`, {
+      method: "DELETE",
     });
 
     mutateAll(/\/management\/instances/);
   };
 
   const clearToken = async (): Promise<void> => {
-    await request(`${url}/management/instances/${instanceId}/auth`, {
-      method: "post",
-      token,
+    await request(`/management/instances/${instanceId}/auth`, {
+      method: "POST",
       data: { method: "external" },
     });
 
@@ -119,13 +111,12 @@ export function useManagementAPI(instanceId: string): 
InstanceAPI {
   };
 
   const setNewToken = async (newToken: string): Promise<void> => {
-    await request(`${url}/management/instances/${instanceId}/auth`, {
-      method: "post",
-      token,
+    await request(`/management/instances/${instanceId}/auth`, {
+      method: "POST",
       data: { method: "token", token: newToken },
     });
 
-    updateLoginStatus(url, newToken);
+    updateToken(newToken);
     mutateAll(/\/management\/instances/);
   };
 
@@ -139,71 +130,59 @@ export function useInstanceAPI(): InstanceAPI {
     token: adminToken,
     updateLoginStatus,
   } = useBackendContext();
-  const { token: instanceToken, id, admin } = useInstanceContext();
-
-  const { url, token } = !admin
-    ? { url: baseUrl, token: adminToken }
-    : { url: `${baseUrl}/instances/${id}`, token: instanceToken };
+  const { request } = useBackendInstanceRequest();
 
   const updateInstance = async (
     instance: MerchantBackend.Instances.InstanceReconfigurationMessage,
   ): Promise<void> => {
-    await request(`${url}/private/`, {
-      method: "patch",
-      token,
+    await request(`/private/`, {
+      method: "PATCH",
       data: instance,
     });
 
     if (adminToken) mutate(["/private/instances", adminToken, baseUrl], null);
-    mutate([`/private/`, token, url], null);
+    mutate([`/private/`], null);
   };
 
   const deleteInstance = async (): Promise<void> => {
-    await request(`${url}/private/`, {
-      method: "delete",
-      token: adminToken,
+    await request(`/private/`, {
+      method: "DELETE",
+      // token: adminToken,
     });
 
     if (adminToken) mutate(["/private/instances", adminToken, baseUrl], null);
-    mutate([`/private/`, token, url], null);
+    mutate([`/private/`], null);
   };
 
   const clearToken = async (): Promise<void> => {
-    await request(`${url}/private/auth`, {
-      method: "post",
-      token,
+    await request(`/private/auth`, {
+      method: "POST",
       data: { method: "external" },
     });
 
-    mutate([`/private/`, token, url], null);
+    mutate([`/private/`], null);
   };
 
   const setNewToken = async (newToken: string): Promise<void> => {
-    await request(`${url}/private/auth`, {
-      method: "post",
-      token,
+    await request(`/private/auth`, {
+      method: "POST",
       data: { method: "token", token: newToken },
     });
 
     updateLoginStatus(baseUrl, newToken);
-    mutate([`/private/`, token, url], null);
+    mutate([`/private/`], null);
   };
 
   return { updateInstance, deleteInstance, setNewToken, clearToken };
 }
 
 export function useInstanceDetails(): 
HttpResponse<MerchantBackend.Instances.QueryInstancesResponse> {
-  const { url: baseUrl, token: baseToken } = useBackendContext();
-  const { token: instanceToken, id, admin } = useInstanceContext();
-
-  const { url, token } = !admin
-    ? { url: baseUrl, token: baseToken }
-    : { url: `${baseUrl}/instances/${id}`, token: instanceToken };
+  const { fetcher } = useBackendInstanceRequest();
 
   const { data, error, isValidating } = useSWR<
     HttpResponseOk<MerchantBackend.Instances.QueryInstancesResponse>,
     HttpError
-  >([`/private/`, token, url], fetcher, {
+  >([`/private/`], fetcher, {
     refreshInterval: 0,
     refreshWhenHidden: false,
     revalidateOnFocus: false,
@@ -225,17 +204,12 @@ type KYCStatus =
   | { type: "redirect"; status: MerchantBackend.Instances.AccountKycRedirects 
};
 
 export function useInstanceKYCDetails(): HttpResponse<KYCStatus> {
-  const { url: baseUrl, token: baseToken } = useBackendContext();
-  const { token: instanceToken, id, admin } = useInstanceContext();
-
-  const { url, token } = !admin
-    ? { url: baseUrl, token: baseToken }
-    : { url: `${baseUrl}/instances/${id}`, token: instanceToken };
+  const { fetcher } = useBackendInstanceRequest();
 
   const { data, error } = useSWR<
     HttpResponseOk<MerchantBackend.Instances.AccountKycRedirects>,
     HttpError
-  >([`/private/kyc`, token, url], fetcher, {
+  >([`/private/kyc`], fetcher, {
     refreshInterval: 5000,
     refreshWhenHidden: false,
     revalidateOnFocus: false,
@@ -258,12 +232,12 @@ export function useInstanceKYCDetails(): 
HttpResponse<KYCStatus> {
 export function useManagedInstanceDetails(
   instanceId: string,
 ): HttpResponse<MerchantBackend.Instances.QueryInstancesResponse> {
-  const { url, token } = useBackendContext();
+  const { request } = useBackendBaseRequest();
 
   const { data, error, isValidating } = useSWR<
     HttpResponseOk<MerchantBackend.Instances.QueryInstancesResponse>,
     HttpError
-  >([`/management/instances/${instanceId}`, token, url], fetcher, {
+  >([`/management/instances/${instanceId}`], request, {
     refreshInterval: 0,
     refreshWhenHidden: false,
     revalidateOnFocus: false,
@@ -281,13 +255,12 @@ export function useManagedInstanceDetails(
 }
 
 export function useBackendInstances(): 
HttpResponse<MerchantBackend.Instances.InstancesResponse> {
-  const { url } = useBackendContext();
-  const { token } = useInstanceContext();
+  const { request } = useBackendBaseRequest();
 
   const { data, error, isValidating } = useSWR<
     HttpResponseOk<MerchantBackend.Instances.InstancesResponse>,
     HttpError
-  >(["/management/instances", token, url], fetcher);
+  >(["/management/instances"], request);
 
   if (isValidating) return { loading: true, data: data?.data };
   if (data) return data;
diff --git a/packages/merchant-backoffice-ui/src/hooks/order.test.ts 
b/packages/merchant-backoffice-ui/src/hooks/order.test.ts
new file mode 100644
index 000000000..be4d1d804
--- /dev/null
+++ b/packages/merchant-backoffice-ui/src/hooks/order.test.ts
@@ -0,0 +1,579 @@
+/*
+ This file is part of GNU Taler
+ (C) 2021-2023 Taler Systems S.A.
+
+ GNU Taler is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+ */
+
+/**
+ *
+ * @author Sebastian Javier Marchano (sebasjm)
+ */
+
+import { tests } from "@gnu-taler/web-util/lib/index.browser";
+import { expect } from "chai";
+import { MerchantBackend } from "../declaration.js";
+import { useInstanceOrders, useOrderAPI, useOrderDetails } from "./order.js";
+import { ApiMockEnvironment } from "./testing.js";
+import {
+  API_CREATE_ORDER,
+  API_DELETE_ORDER,
+  API_FORGET_ORDER_BY_ID,
+  API_GET_ORDER_BY_ID,
+  API_LIST_ORDERS, API_REFUND_ORDER_BY_ID
+} from "./urls.js";
+
+describe("order api interaction with listing", () => {
+
+  it("should evict cache when creating an order", async () => {
+    const env = new ApiMockEnvironment();
+
+    env.addRequestExpectation(API_LIST_ORDERS, {
+      qparam: { delta: 0, paid: "yes" },
+      response: {
+        orders: [{ order_id: "1" } as 
MerchantBackend.Orders.OrderHistoryEntry],
+      },
+    });
+
+    env.addRequestExpectation(API_LIST_ORDERS, {
+      qparam: { delta: -20, paid: "yes" },
+      response: {
+        orders: [{ order_id: "2" } as 
MerchantBackend.Orders.OrderHistoryEntry],
+      },
+    });
+
+
+    const newDate = (d: Date) => {
+      //console.log("new date", d);
+    };
+
+    const hookBehavior = await tests.hookBehaveLikeThis(
+      () => {
+        const query = useInstanceOrders({ paid: "yes" }, newDate);
+        const api = useOrderAPI();
+        return { query, api };
+      },
+      {},
+      [
+        ({ query, api }) => {
+          expect(query.loading).true;
+        },
+        ({ query, api }) => {
+          expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: 
"ok" });
+          expect(query.loading).undefined;
+          expect(query.ok).true
+          if (!query.ok) return;
+          expect(query.data).deep.equals({
+            orders: [{ order_id: "1" }, { order_id: "2" }],
+          });
+
+          env.addRequestExpectation(API_CREATE_ORDER, {
+            request: {
+              order: { amount: "ARS:12", summary: "pay me" },
+            },
+            response: { order_id: "3" },
+          });
+
+          env.addRequestExpectation(API_LIST_ORDERS, {
+            qparam: { delta: 0, paid: "yes" },
+            response: {
+              orders: [{ order_id: "1" } as any],
+            },
+          });
+
+          env.addRequestExpectation(API_LIST_ORDERS, {
+            qparam: { delta: -20, paid: "yes" },
+            response: {
+              orders: [{ order_id: "2" } as any, { order_id: "3" } as any],
+            },
+          });
+
+          api.createOrder({
+            order: { amount: "ARS:12", summary: "pay me" },
+          } as any);
+        },
+        ({ query, api }) => {
+          expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: 
"ok" });
+
+          expect(query.loading).undefined;
+          expect(query.ok).true
+          if (!query.ok) return;
+          expect(query.data).deep.equals({
+            orders: [{ order_id: "1" }, { order_id: "2" }, { order_id: "3" }],
+          });
+        },
+      ], env.buildTestingContext());
+    expect(hookBehavior).deep.eq({ result: "ok" });
+    expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" });
+
+  });
+
+  it("should evict cache when doing a refund", async () => {
+    const env = new ApiMockEnvironment();
+
+    env.addRequestExpectation(API_LIST_ORDERS, {
+      qparam: { delta: 0, paid: "yes" },
+      response: {
+        orders: [{ order_id: "1", amount: 'EUR:12', refundable: true } as 
MerchantBackend.Orders.OrderHistoryEntry],
+      },
+    });
+
+    env.addRequestExpectation(API_LIST_ORDERS, {
+      qparam: { delta: -20, paid: "yes" },
+      response: { orders: [], },
+    });
+
+
+    const newDate = (d: Date) => {
+      //console.log("new date", d);
+    };
+
+    const hookBehavior = await tests.hookBehaveLikeThis(
+      () => {
+        const query = useInstanceOrders({ paid: "yes" }, newDate);
+        const api = useOrderAPI();
+        return { query, api };
+      },
+      {},
+      [
+        ({ query, api }) => {
+          expect(query.loading).true;
+        },
+        ({ query, api }) => {
+          expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: 
"ok" });
+
+          expect(query.loading).undefined;
+          expect(query.ok).true
+          if (!query.ok) return;
+          expect(query.data).deep.equals({
+            orders: [{
+              order_id: "1",
+              amount: 'EUR:12',
+              refundable: true,
+            }],
+          });
+          env.addRequestExpectation(API_REFUND_ORDER_BY_ID('1'), {
+            request: {
+              reason: 'double pay',
+              refund: 'EUR:1'
+            },
+          });
+
+          env.addRequestExpectation(API_LIST_ORDERS, {
+            qparam: { delta: 0, paid: "yes" },
+            response: {
+              orders: [{ order_id: "1", amount: 'EUR:12', refundable: false } 
as any],
+            },
+          });
+
+          env.addRequestExpectation(API_LIST_ORDERS, {
+            qparam: { delta: -20, paid: "yes" },
+            response: { orders: [], },
+          });
+
+          api.refundOrder('1', {
+            reason: 'double pay',
+            refund: 'EUR:1'
+          });
+        },
+        ({ query, api }) => {
+          expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: 
"ok" });
+
+          expect(query.loading).undefined;
+          expect(query.ok).true
+          if (!query.ok) return;
+          expect(query.data).deep.equals({
+            orders: [{
+              order_id: "1",
+              amount: 'EUR:12',
+              refundable: false,
+            }],
+          });
+        },
+      ], env.buildTestingContext());
+
+    expect(hookBehavior).deep.eq({ result: "ok" });
+    expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" });
+
+  });
+
+  it("should evict cache when deleting an order", async () => {
+    const env = new ApiMockEnvironment();
+
+    env.addRequestExpectation(API_LIST_ORDERS, {
+      qparam: { delta: 0, paid: "yes" },
+      response: {
+        orders: [{ order_id: "1" } as 
MerchantBackend.Orders.OrderHistoryEntry],
+      },
+    });
+
+    env.addRequestExpectation(API_LIST_ORDERS, {
+      qparam: { delta: -20, paid: "yes" },
+      response: {
+        orders: [{ order_id: "2" } as 
MerchantBackend.Orders.OrderHistoryEntry],
+      },
+    });
+
+
+    const newDate = (d: Date) => {
+      //console.log("new date", d);
+    };
+
+    const hookBehavior = await tests.hookBehaveLikeThis(
+      () => {
+        const query = useInstanceOrders({ paid: "yes" }, newDate);
+        const api = useOrderAPI();
+        return { query, api };
+      },
+      {},
+      [
+        ({ query, api }) => {
+          expect(query.loading).true;
+        },
+        ({ query, api }) => {
+          expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: 
"ok" });
+          expect(query.loading).undefined;
+          expect(query.ok).true
+          if (!query.ok) return;
+          expect(query.data).deep.equals({
+            orders: [{ order_id: "1" }, { order_id: "2" }],
+          });
+
+          env.addRequestExpectation(API_DELETE_ORDER('1'), {});
+
+          env.addRequestExpectation(API_LIST_ORDERS, {
+            qparam: { delta: 0, paid: "yes" },
+            response: {
+              orders: [],
+            },
+          });
+
+          env.addRequestExpectation(API_LIST_ORDERS, {
+            qparam: { delta: -20, paid: "yes" },
+            response: {
+              orders: [{ order_id: "2" } as any],
+            },
+          });
+
+          api.deleteOrder('1');
+        },
+        ({ query, api }) => {
+          expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: 
"ok" });
+          expect(query.loading).undefined;
+          expect(query.ok).true
+          if (!query.ok) return;
+          expect(query.data).deep.equals({
+            orders: [{ order_id: "2" }],
+          });
+        },
+      ], env.buildTestingContext());
+    expect(hookBehavior).deep.eq({ result: "ok" });
+    expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" });
+
+  });
+
+});
+
+describe("order api interaction with details", () => {
+
+  it("should evict cache when doing a refund", async () => {
+    const env = new ApiMockEnvironment();
+
+    env.addRequestExpectation(API_GET_ORDER_BY_ID('1'), {
+      // qparam: { delta: 0, paid: "yes" },
+      response: {
+        summary: 'description',
+        refund_amount: 'EUR:0',
+      } as unknown as MerchantBackend.Orders.CheckPaymentPaidResponse,
+    });
+
+    const newDate = (d: Date) => {
+      //console.log("new date", d);
+    };
+
+    const hookBehavior = await tests.hookBehaveLikeThis(
+      () => {
+        const query = useOrderDetails('1')
+        const api = useOrderAPI();
+        return { query, api };
+      },
+      {},
+      [
+        ({ query, api }) => {
+          expect(query.loading).true;
+        },
+        ({ query, api }) => {
+          expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: 
"ok" });
+          expect(query.loading).false;
+          expect(query.ok).true
+          if (!query.ok) return;
+          expect(query.data).deep.equals({
+            summary: 'description',
+            refund_amount: 'EUR:0',
+          });
+          env.addRequestExpectation(API_REFUND_ORDER_BY_ID('1'), {
+            request: {
+              reason: 'double pay',
+              refund: 'EUR:1'
+            },
+          });
+
+          env.addRequestExpectation(API_GET_ORDER_BY_ID('1'), {
+            response: {
+              summary: 'description',
+              refund_amount: 'EUR:1',
+            } as unknown as MerchantBackend.Orders.CheckPaymentPaidResponse,
+          });
+
+          api.refundOrder('1', {
+            reason: 'double pay',
+            refund: 'EUR:1'
+          })
+        },
+        ({ query, api }) => {
+          expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: 
"ok" });
+          expect(query.loading).false;
+          expect(query.ok).true
+          if (!query.ok) return;
+          expect(query.data).deep.equals({
+            summary: 'description',
+            refund_amount: 'EUR:1',
+          });
+        },
+      ], env.buildTestingContext());
+
+    expect(hookBehavior).deep.eq({ result: "ok" });
+    expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" });
+
+  })
+
+  it("should evict cache when doing a forget", async () => {
+    const env = new ApiMockEnvironment();
+
+    env.addRequestExpectation(API_GET_ORDER_BY_ID('1'), {
+      // qparam: { delta: 0, paid: "yes" },
+      response: {
+        summary: 'description',
+        refund_amount: 'EUR:0',
+      } as unknown as MerchantBackend.Orders.CheckPaymentPaidResponse,
+    });
+
+    const newDate = (d: Date) => {
+      //console.log("new date", d);
+    };
+
+    const hookBehavior = await tests.hookBehaveLikeThis(
+      () => {
+        const query = useOrderDetails('1')
+        const api = useOrderAPI();
+        return { query, api };
+      },
+      {},
+      [
+        ({ query, api }) => {
+          expect(query.loading).true;
+        },
+        ({ query, api }) => {
+          expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: 
"ok" });
+          expect(query.loading).false;
+          expect(query.ok).true
+          if (!query.ok) return;
+          expect(query.data).deep.equals({
+            summary: 'description',
+            refund_amount: 'EUR:0',
+          });
+          env.addRequestExpectation(API_FORGET_ORDER_BY_ID('1'), {
+            request: {
+              fields: ['$.summary']
+            },
+          });
+
+          env.addRequestExpectation(API_GET_ORDER_BY_ID('1'), {
+            response: {
+              summary: undefined,
+            } as unknown as MerchantBackend.Orders.CheckPaymentPaidResponse,
+          });
+
+          api.forgetOrder('1', {
+            fields: ['$.summary']
+          })
+        },
+        ({ query, api }) => {
+          expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: 
"ok" });
+          expect(query.loading).false;
+          expect(query.ok).true
+          if (!query.ok) return;
+          expect(query.data).deep.equals({
+            summary: undefined,
+          });
+        },
+      ], env.buildTestingContext());
+    expect(hookBehavior).deep.eq({ result: "ok" });
+    expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" });
+
+  })
+})
+
+describe("order listing pagination", () => {
+
+  it("should not load more if has reach the end", async () => {
+    const env = new ApiMockEnvironment();
+    env.addRequestExpectation(API_LIST_ORDERS, {
+      qparam: { delta: 20, wired: "yes", date_ms: 12 },
+      response: {
+        orders: [{ order_id: "1" } as any],
+      },
+    });
+
+    env.addRequestExpectation(API_LIST_ORDERS, {
+      qparam: { delta: -20, wired: "yes", date_ms: 13 },
+      response: {
+        orders: [{ order_id: "2" } as any],
+      },
+    });
+
+
+    const newDate = (d: Date) => {
+      //console.log("new date", d);
+    };
+
+    const hookBehavior = await tests.hookBehaveLikeThis(
+      () => {
+        const date = new Date(12);
+        const query = useInstanceOrders({ wired: "yes", date }, newDate)
+        const api = useOrderAPI();
+        return { query, api };
+      },
+      {},
+      [
+        ({ query, api }) => {
+          expect(query.loading).true;
+        },
+        ({ query, api }) => {
+          expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: 
"ok" });
+          expect(query.loading).undefined;
+          expect(query.ok).true
+          if (!query.ok) return;
+          expect(query.data).deep.equals({
+            orders: [{ order_id: "1" }, { order_id: "2" }],
+          });
+          expect(query.isReachingEnd).true
+          expect(query.isReachingStart).true
+
+          // should not trigger new state update or query
+          query.loadMore()
+          query.loadMorePrev();
+        },
+      ], env.buildTestingContext());
+
+    expect(hookBehavior).deep.eq({ result: "ok" });
+    expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" });
+
+  });
+
+  it("should load more if result brings more that PAGE_SIZE", async () => {
+    const env = new ApiMockEnvironment();
+
+    const ordersFrom0to20 = Array.from({ length: 20 }).map((e, i) => ({ 
order_id: String(i) }))
+    const ordersFrom20to40 = Array.from({ length: 20 }).map((e, i) => ({ 
order_id: String(i + 20) }))
+    const ordersFrom20to0 = [...ordersFrom0to20].reverse()
+
+    env.addRequestExpectation(API_LIST_ORDERS, {
+      qparam: { delta: 20, wired: "yes", date_ms: 12 },
+      response: {
+        orders: ordersFrom0to20,
+      },
+    });
+
+    env.addRequestExpectation(API_LIST_ORDERS, {
+      qparam: { delta: -20, wired: "yes", date_ms: 13 },
+      response: {
+        orders: ordersFrom20to40,
+      },
+    });
+
+    const newDate = (d: Date) => {
+      //console.log("new date", d);
+    };
+
+    const hookBehavior = await tests.hookBehaveLikeThis(
+      () => {
+        const date = new Date(12);
+        const query = useInstanceOrders({ wired: "yes", date }, newDate)
+        const api = useOrderAPI();
+        return { query, api };
+      },
+      {},
+      [
+        ({ query, api }) => {
+          expect(query.loading).true;
+        },
+        ({ query, api }) => {
+          expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: 
"ok" });
+          expect(query.loading).undefined;
+          expect(query.ok).true
+          if (!query.ok) return;
+          expect(query.data).deep.equals({
+            orders: [...ordersFrom20to0, ...ordersFrom20to40],
+          });
+          expect(query.isReachingEnd).false
+          expect(query.isReachingStart).false
+
+          env.addRequestExpectation(API_LIST_ORDERS, {
+            qparam: { delta: -40, wired: "yes", date_ms: 13 },
+            response: {
+              orders: [...ordersFrom20to40, { order_id: '41' }],
+            },
+          });
+
+          query.loadMore()
+        },
+        ({ query, api }) => {
+          expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: 
"ok" });
+          expect(query.loading).true;
+        },
+        ({ query, api }) => {
+          expect(query.loading).undefined;
+          expect(query.ok).true
+          if (!query.ok) return;
+          expect(query.data).deep.equals({
+            orders: [...ordersFrom20to0, ...ordersFrom20to40, { order_id: '41' 
}],
+          });
+
+          env.addRequestExpectation(API_LIST_ORDERS, {
+            qparam: { delta: 40, wired: "yes", date_ms: 12 },
+            response: {
+              orders: [...ordersFrom0to20, { order_id: '-1' }],
+            },
+          });
+
+          query.loadMorePrev()
+        },
+        ({ query, api }) => {
+          expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: 
"ok" });
+          expect(query.loading).true;
+        },
+        ({ query, api }) => {
+          expect(query.loading).undefined;
+          expect(query.ok).true
+          if (!query.ok) return;
+          expect(query.data).deep.equals({
+            orders: [{ order_id: '-1' }, ...ordersFrom20to0, 
...ordersFrom20to40, { order_id: '41' }],
+          });
+        },
+      ], env.buildTestingContext());
+    expect(hookBehavior).deep.eq({ result: "ok" });
+    expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" });
+  });
+
+
+});
diff --git a/packages/merchant-backoffice-ui/src/hooks/order.ts 
b/packages/merchant-backoffice-ui/src/hooks/order.ts
index d1e26b671..0bea6b963 100644
--- a/packages/merchant-backoffice-ui/src/hooks/order.ts
+++ b/packages/merchant-backoffice-ui/src/hooks/order.ts
@@ -14,20 +14,16 @@
  GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 import { useEffect, useState } from "preact/hooks";
-import useSWR, { useSWRConfig } from "swr";
-import { useBackendContext } from "../context/backend.js";
-import { useInstanceContext } from "../context/instance.js";
+import useSWR from "swr";
 import { MerchantBackend } from "../declaration.js";
 import { MAX_RESULT_SIZE, PAGE_SIZE } from "../utils/constants.js";
 import {
-  fetcher,
   HttpError,
   HttpResponse,
   HttpResponseOk,
   HttpResponsePaginated,
-  request,
-  useMatchMutate,
-} from "./backend.js";
+} from "../utils/request.js";
+import { useBackendInstanceRequest, useMatchMutate } from "./backend.js";
 
 export interface OrderAPI {
   //FIXME: add OutOfStockResponse on 410
@@ -48,52 +44,17 @@ export interface OrderAPI {
 
 type YesOrNo = "yes" | "no";
 
-export function orderFetcher<T>(
-  url: string,
-  token: string,
-  backend: string,
-  paid?: YesOrNo,
-  refunded?: YesOrNo,
-  wired?: YesOrNo,
-  searchDate?: Date,
-  delta?: number,
-): Promise<HttpResponseOk<T>> {
-  const date_ms =
-    delta && delta < 0 && searchDate
-      ? searchDate.getTime() + 1
-      : searchDate?.getTime();
-  const params: any = {};
-  if (paid !== undefined) params.paid = paid;
-  if (delta !== undefined) params.delta = delta;
-  if (refunded !== undefined) params.refunded = refunded;
-  if (wired !== undefined) params.wired = wired;
-  if (date_ms !== undefined) params.date_ms = date_ms;
-  return request<T>(`${backend}${url}`, { token, params });
-}
-
 export function useOrderAPI(): OrderAPI {
   const mutateAll = useMatchMutate();
-  const { url: baseUrl, token: adminToken } = useBackendContext();
-  const { token: instanceToken, id, admin } = useInstanceContext();
-
-  const { url, token } = !admin
-    ? {
-        url: baseUrl,
-        token: adminToken,
-      }
-    : {
-        url: `${baseUrl}/instances/${id}`,
-        token: instanceToken,
-      };
+  const { request } = useBackendInstanceRequest();
 
   const createOrder = async (
     data: MerchantBackend.Orders.PostOrderRequest,
   ): Promise<HttpResponseOk<MerchantBackend.Orders.PostOrderResponse>> => {
     const res = await request<MerchantBackend.Orders.PostOrderResponse>(
-      `${url}/private/orders`,
+      `/private/orders`,
       {
-        method: "post",
-        token,
+        method: "POST",
         data,
       },
     );
@@ -107,10 +68,9 @@ export function useOrderAPI(): OrderAPI {
   ): Promise<HttpResponseOk<MerchantBackend.Orders.MerchantRefundResponse>> => 
{
     mutateAll(/@"\/private\/orders"@/);
     const res = request<MerchantBackend.Orders.MerchantRefundResponse>(
-      `${url}/private/orders/${orderId}/refund`,
+      `/private/orders/${orderId}/refund`,
       {
-        method: "post",
-        token,
+        method: "POST",
         data,
       },
     );
@@ -125,9 +85,8 @@ export function useOrderAPI(): OrderAPI {
     data: MerchantBackend.Orders.ForgetRequest,
   ): Promise<HttpResponseOk<void>> => {
     mutateAll(/@"\/private\/orders"@/);
-    const res = request<void>(`${url}/private/orders/${orderId}/forget`, {
-      method: "patch",
-      token,
+    const res = request<void>(`/private/orders/${orderId}/forget`, {
+      method: "PATCH",
       data,
     });
     // we may be forgetting some fields that are pare of the listing, so we 
must evict everything
@@ -138,9 +97,8 @@ export function useOrderAPI(): OrderAPI {
     orderId: string,
   ): Promise<HttpResponseOk<void>> => {
     mutateAll(/@"\/private\/orders"@/);
-    const res = request<void>(`${url}/private/orders/${orderId}`, {
-      method: "delete",
-      token,
+    const res = request<void>(`/private/orders/${orderId}`, {
+      method: "DELETE",
     });
     await mutateAll(/.*private\/orders.*/);
     return res;
@@ -150,10 +108,9 @@ export function useOrderAPI(): OrderAPI {
     orderId: string,
   ): Promise<HttpResponseOk<string>> => {
     return request<MerchantBackend.Orders.MerchantOrderStatusResponse>(
-      `${url}/private/orders/${orderId}`,
+      `/private/orders/${orderId}`,
       {
-        method: "get",
-        token,
+        method: "GET",
       },
     ).then((res) => {
       const url =
@@ -172,17 +129,12 @@ export function useOrderAPI(): OrderAPI {
 export function useOrderDetails(
   oderId: string,
 ): HttpResponse<MerchantBackend.Orders.MerchantOrderStatusResponse> {
-  const { url: baseUrl, token: baseToken } = useBackendContext();
-  const { token: instanceToken, id, admin } = useInstanceContext();
-
-  const { url, token } = !admin
-    ? { url: baseUrl, token: baseToken }
-    : { url: `${baseUrl}/instances/${id}`, token: instanceToken };
+  const { fetcher } = useBackendInstanceRequest();
 
   const { data, error, isValidating } = useSWR<
     HttpResponseOk<MerchantBackend.Orders.MerchantOrderStatusResponse>,
     HttpError
-  >([`/private/orders/${oderId}`, token, url], fetcher, {
+  >([`/private/orders/${oderId}`], fetcher, {
     refreshInterval: 0,
     refreshWhenHidden: false,
     revalidateOnFocus: false,
@@ -207,12 +159,7 @@ export function useInstanceOrders(
   args?: InstanceOrderFilter,
   updateFilter?: (d: Date) => void,
 ): HttpResponsePaginated<MerchantBackend.Orders.OrderHistory> {
-  const { url: baseUrl, token: baseToken } = useBackendContext();
-  const { token: instanceToken, id, admin } = useInstanceContext();
-
-  const { url, token } = !admin
-    ? { url: baseUrl, token: baseToken }
-    : { url: `${baseUrl}/instances/${id}`, token: instanceToken };
+  const { orderFetcher } = useBackendInstanceRequest();
 
   const [pageBefore, setPageBefore] = useState(1);
   const [pageAfter, setPageAfter] = useState(1);
@@ -233,8 +180,6 @@ export function useInstanceOrders(
   } = useSWR<HttpResponseOk<MerchantBackend.Orders.OrderHistory>, HttpError>(
     [
       `/private/orders`,
-      token,
-      url,
       args?.paid,
       args?.refunded,
       args?.wired,
@@ -250,8 +195,6 @@ export function useInstanceOrders(
   } = useSWR<HttpResponseOk<MerchantBackend.Orders.OrderHistory>, HttpError>(
     [
       `/private/orders`,
-      token,
-      url,
       args?.paid,
       args?.refunded,
       args?.wired,
@@ -314,9 +257,9 @@ export function useInstanceOrders(
     !beforeData || !afterData
       ? []
       : (beforeData || lastBefore).data.orders
-          .slice()
-          .reverse()
-          .concat((afterData || lastAfter).data.orders);
+        .slice()
+        .reverse()
+        .concat((afterData || lastAfter).data.orders);
   if (loadingAfter || loadingBefore) return { loading: true, data: { orders } 
};
   if (beforeData && afterData) {
     return { ok: true, data: { orders }, ...pagination };
diff --git a/packages/merchant-backoffice-ui/src/hooks/product.test.ts 
b/packages/merchant-backoffice-ui/src/hooks/product.test.ts
new file mode 100644
index 000000000..a182b28f4
--- /dev/null
+++ b/packages/merchant-backoffice-ui/src/hooks/product.test.ts
@@ -0,0 +1,326 @@
+/*
+ This file is part of GNU Taler
+ (C) 2021-2023 Taler Systems S.A.
+
+ GNU Taler is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+ */
+
+/**
+ *
+ * @author Sebastian Javier Marchano (sebasjm)
+ */
+
+import { tests } from "@gnu-taler/web-util/lib/index.browser";
+import { expect } from "chai";
+import { MerchantBackend } from "../declaration.js";
+import { useInstanceProducts, useProductAPI, useProductDetails } from 
"./product.js";
+import { ApiMockEnvironment } from "./testing.js";
+import {
+  API_CREATE_PRODUCT,
+  API_DELETE_PRODUCT, API_GET_PRODUCT_BY_ID,
+  API_LIST_PRODUCTS,
+  API_UPDATE_PRODUCT_BY_ID
+} from "./urls.js";
+
+describe("product api interaction with listing", () => {
+  it("should evict cache when creating a product", async () => {
+    const env = new ApiMockEnvironment();
+
+    env.addRequestExpectation(API_LIST_PRODUCTS, {
+      response: {
+        products: [{ product_id: "1234" }],
+      },
+    });
+    env.addRequestExpectation(API_GET_PRODUCT_BY_ID("1234"), {
+      response: { price: "ARS:12" } as MerchantBackend.Products.ProductDetail,
+    });
+
+    const hookBehavior = await tests.hookBehaveLikeThis(
+      () => {
+        const query = useInstanceProducts();
+        const api = useProductAPI();
+        return { query, api };
+      },
+      {},
+      [
+        ({ query, api }) => {
+          expect(query.loading).true;
+        },
+        ({ query, api }) => {
+          expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: 
"ok" });
+          expect(query.loading).true;
+        },
+        ({ query, api }) => {
+          expect(query.loading).undefined;
+          expect(query.ok).true
+          if (!query.ok) return;
+          expect(query.data).deep.equals([
+            { id: "1234", price: "ARS:12" },
+          ]);
+
+          env.addRequestExpectation(API_CREATE_PRODUCT, {
+            request: { price: "ARS:23" } as 
MerchantBackend.Products.ProductAddDetail,
+          });
+
+          env.addRequestExpectation(API_LIST_PRODUCTS, {
+            response: {
+              products: [{ product_id: "1234" }, { product_id: "2345" }],
+            },
+          });
+          env.addRequestExpectation(API_GET_PRODUCT_BY_ID("1234"), {
+            response: { price: "ARS:12" } as 
MerchantBackend.Products.ProductDetail,
+          });
+          env.addRequestExpectation(API_GET_PRODUCT_BY_ID("1234"), {
+            response: { price: "ARS:12" } as 
MerchantBackend.Products.ProductDetail,
+          });
+          env.addRequestExpectation(API_GET_PRODUCT_BY_ID("2345"), {
+            response: { price: "ARS:23" } as 
MerchantBackend.Products.ProductDetail,
+          });
+
+          api.createProduct({
+            price: "ARS:23",
+          } as any)
+
+        },
+        ({ query, api }) => {
+          expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: 
"ok" });
+          expect(query.loading).true;
+        },
+        ({ query, api }) => {
+          expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: 
"ok" });
+          expect(query.loading).undefined;
+          expect(query.ok).true
+          if (!query.ok) return;
+          expect(query.data).deep.equals([
+            {
+              id: "1234",
+              price: "ARS:12",
+            },
+            {
+              id: "2345",
+              price: "ARS:23",
+            },
+          ]);
+        },
+      ], env.buildTestingContext());
+
+    expect(hookBehavior).deep.eq({ result: "ok" });
+    expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" });
+
+  });
+
+  it("should evict cache when updating a product", async () => {
+    const env = new ApiMockEnvironment();
+
+    env.addRequestExpectation(API_LIST_PRODUCTS, {
+      response: {
+        products: [{ product_id: "1234" }],
+      },
+    });
+    env.addRequestExpectation(API_GET_PRODUCT_BY_ID("1234"), {
+      response: { price: "ARS:12" } as MerchantBackend.Products.ProductDetail,
+    });
+
+    const hookBehavior = await tests.hookBehaveLikeThis(
+      () => {
+        const query = useInstanceProducts();
+        const api = useProductAPI();
+        return { query, api };
+      },
+      {},
+      [
+        ({ query, api }) => {
+          expect(query.loading).true;
+        },
+        ({ query, api }) => {
+          expect(query.loading).true;
+        },
+        ({ query, api }) => {
+          expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: 
"ok" });
+          expect(query.loading).undefined;
+          expect(query.ok).true
+          if (!query.ok) return;
+          expect(query.data).deep.equals([
+            { id: "1234", price: "ARS:12" },
+          ]);
+
+          env.addRequestExpectation(API_UPDATE_PRODUCT_BY_ID("1234"), {
+            request: { price: "ARS:13" } as 
MerchantBackend.Products.ProductPatchDetail,
+          });
+
+          env.addRequestExpectation(API_LIST_PRODUCTS, {
+            response: {
+              products: [{ product_id: "1234" }],
+            },
+          });
+          env.addRequestExpectation(API_GET_PRODUCT_BY_ID("1234"), {
+            response: { price: "ARS:13" } as 
MerchantBackend.Products.ProductDetail,
+          });
+
+          api.updateProduct("1234", {
+            price: "ARS:13",
+          } as any)
+
+        },
+        ({ query, api }) => {
+          expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: 
"ok" });
+          expect(query.loading).undefined;
+          expect(query.ok).true
+          if (!query.ok) return;
+          expect(query.data).deep.equals([
+            {
+              id: "1234",
+              price: "ARS:13",
+            },
+          ]);
+        },
+      ], env.buildTestingContext());
+
+    expect(hookBehavior).deep.eq({ result: "ok" });
+    expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" });
+
+  });
+
+  it("should evict cache when deleting a product", async () => {
+    const env = new ApiMockEnvironment();
+
+    env.addRequestExpectation(API_LIST_PRODUCTS, {
+      response: {
+        products: [{ product_id: "1234" }, { product_id: "2345" }],
+      },
+    });
+    env.addRequestExpectation(API_GET_PRODUCT_BY_ID("1234"), {
+      response: { price: "ARS:12" } as MerchantBackend.Products.ProductDetail,
+    });
+    env.addRequestExpectation(API_GET_PRODUCT_BY_ID("2345"), {
+      response: { price: "ARS:23" } as MerchantBackend.Products.ProductDetail,
+    });
+
+    const hookBehavior = await tests.hookBehaveLikeThis(
+      () => {
+        const query = useInstanceProducts();
+        const api = useProductAPI();
+        return { query, api };
+      },
+      {},
+      [
+        ({ query, api }) => {
+          expect(query.loading).true;
+        },
+        ({ query, api }) => {
+          expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: 
"ok" });
+          expect(query.loading).true;
+        },
+        ({ query, api }) => {
+          expect(query.loading).undefined;
+          expect(query.ok).true
+          if (!query.ok) return;
+          expect(query.data).deep.equals([
+            { id: "1234", price: "ARS:12" },
+            { id: "2345", price: "ARS:23" },
+          ]);
+
+          env.addRequestExpectation(API_DELETE_PRODUCT("2345"), {});
+
+          env.addRequestExpectation(API_LIST_PRODUCTS, {
+            response: {
+              products: [{ product_id: "1234" }],
+            },
+          });
+
+          env.addRequestExpectation(API_GET_PRODUCT_BY_ID("1234"), {
+            response: { price: "ARS:12" } as 
MerchantBackend.Products.ProductDetail,
+          });
+          api.deleteProduct("2345");
+        },
+        ({ query, api }) => {
+          expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: 
"ok" });
+          expect(query.loading).true;
+        },
+        ({ query, api }) => {
+          expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: 
"ok" });
+          expect(query.loading).undefined;
+          expect(query.ok).true
+          if (!query.ok) return;
+          expect(query.data).deep.equals([
+            { id: "1234", price: "ARS:12" },
+          ]);
+        },
+      ], env.buildTestingContext());
+
+    expect(hookBehavior).deep.eq({ result: "ok" });
+    expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" });
+  });
+
+});
+
+describe("product api interaction with details", () => {
+  it("should evict cache when updating a product", async () => {
+    const env = new ApiMockEnvironment();
+
+    env.addRequestExpectation(API_GET_PRODUCT_BY_ID("12"), {
+      response: {
+        description: "this is a description",
+      } as MerchantBackend.Products.ProductDetail,
+    });
+
+    const hookBehavior = await tests.hookBehaveLikeThis(
+      () => {
+        const query = useProductDetails("12");
+        const api = useProductAPI();
+        return { query, api };
+      },
+      {},
+      [
+        ({ query, api }) => {
+          expect(query.loading).true;
+        },
+        ({ query, api }) => {
+          expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: 
"ok" });
+          expect(query.loading).false;
+          expect(query.ok).true
+          if (!query.ok) return;
+          expect(query.data).deep.equals({
+            description: "this is a description",
+          });
+
+          env.addRequestExpectation(API_UPDATE_PRODUCT_BY_ID("12"), {
+            request: { description: "other description" } as 
MerchantBackend.Products.ProductPatchDetail,
+          });
+
+          env.addRequestExpectation(API_GET_PRODUCT_BY_ID("12"), {
+            response: {
+              description: "other description",
+            } as MerchantBackend.Products.ProductDetail,
+          });
+
+          api.updateProduct("12", {
+            description: "other description",
+          } as any);
+
+        },
+        ({ query, api }) => {
+          expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: 
"ok" });
+          expect(query.loading).false;
+          expect(query.ok).true
+          if (!query.ok) return;
+          expect(query.data).deep.equals({
+            description: "other description",
+          });
+        },
+      ], env.buildTestingContext());
+
+    expect(hookBehavior).deep.eq({ result: "ok" });
+    expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" });
+
+  })
+})
\ No newline at end of file
diff --git a/packages/merchant-backoffice-ui/src/hooks/product.ts 
b/packages/merchant-backoffice-ui/src/hooks/product.ts
index fb7889834..af8ad74f3 100644
--- a/packages/merchant-backoffice-ui/src/hooks/product.ts
+++ b/packages/merchant-backoffice-ui/src/hooks/product.ts
@@ -14,18 +14,9 @@
  GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 import useSWR, { useSWRConfig } from "swr";
-import { useBackendContext } from "../context/backend.js";
-import { useInstanceContext } from "../context/instance.js";
 import { MerchantBackend, WithId } from "../declaration.js";
-import {
-  fetcher,
-  HttpError,
-  HttpResponse,
-  HttpResponseOk,
-  multiFetcher,
-  request,
-  useMatchMutate,
-} from "./backend.js";
+import { HttpError, HttpResponse, HttpResponseOk } from "../utils/request.js";
+import { useBackendInstanceRequest, useMatchMutate } from "./backend.js";
 
 export interface ProductAPI {
   createProduct: (
@@ -45,19 +36,14 @@ export interface ProductAPI {
 export function useProductAPI(): ProductAPI {
   const mutateAll = useMatchMutate();
   const { mutate } = useSWRConfig();
-  const { url: baseUrl, token: adminToken } = useBackendContext();
-  const { token: instanceToken, id, admin } = useInstanceContext();
 
-  const { url, token } = !admin
-    ? { url: baseUrl, token: adminToken }
-    : { url: `${baseUrl}/instances/${id}`, token: instanceToken };
+  const { request } = useBackendInstanceRequest();
 
   const createProduct = async (
     data: MerchantBackend.Products.ProductAddDetail,
   ): Promise<void> => {
-    const res = await request(`${url}/private/products`, {
-      method: "post",
-      token,
+    const res = await request(`/private/products`, {
+      method: "POST",
       data,
     });
 
@@ -68,9 +54,8 @@ export function useProductAPI(): ProductAPI {
     productId: string,
     data: MerchantBackend.Products.ProductPatchDetail,
   ): Promise<void> => {
-    const r = await request(`${url}/private/products/${productId}`, {
-      method: "patch",
-      token,
+    const r = await request(`/private/products/${productId}`, {
+      method: "PATCH",
       data,
     });
 
@@ -78,20 +63,18 @@ export function useProductAPI(): ProductAPI {
   };
 
   const deleteProduct = async (productId: string): Promise<void> => {
-    await request(`${url}/private/products/${productId}`, {
-      method: "delete",
-      token,
+    await request(`/private/products/${productId}`, {
+      method: "DELETE",
     });
-    await mutate([`/private/products`, token, url]);
+    await mutate([`/private/products`]);
   };
 
   const lockProduct = async (
     productId: string,
     data: MerchantBackend.Products.LockRequest,
   ): Promise<void> => {
-    await request(`${url}/private/products/${productId}/lock`, {
-      method: "post",
-      token,
+    await request(`/private/products/${productId}/lock`, {
+      method: "POST",
       data,
     });
 
@@ -104,17 +87,12 @@ export function useProductAPI(): ProductAPI {
 export function useInstanceProducts(): HttpResponse<
   (MerchantBackend.Products.ProductDetail & WithId)[]
 > {
-  const { url: baseUrl, token: baseToken } = useBackendContext();
-  const { token: instanceToken, id, admin } = useInstanceContext();
-
-  const { url, token } = !admin
-    ? { url: baseUrl, token: baseToken }
-    : { url: `${baseUrl}/instances/${id}`, token: instanceToken };
+  const { fetcher, multiFetcher } = useBackendInstanceRequest();
 
   const { data: list, error: listError } = useSWR<
     HttpResponseOk<MerchantBackend.Products.InventorySummaryResponse>,
     HttpError
-  >([`/private/products`, token, url], fetcher, {
+  >([`/private/products`], fetcher, {
     refreshInterval: 0,
     refreshWhenHidden: false,
     revalidateOnFocus: false,
@@ -128,7 +106,7 @@ export function useInstanceProducts(): HttpResponse<
   const { data: products, error: productError } = useSWR<
     HttpResponseOk<MerchantBackend.Products.ProductDetail>[],
     HttpError
-  >([paths, token, url], multiFetcher, {
+  >([paths], multiFetcher, {
     refreshInterval: 0,
     refreshWhenHidden: false,
     revalidateOnFocus: false,
@@ -144,7 +122,7 @@ export function useInstanceProducts(): HttpResponse<
       //take the id from the queried url
       return {
         ...d.data,
-        id: d.info?.url.replace(/.*\/private\/products\//, "") || "",
+        id: d.info?.url.href.replace(/.*\/private\/products\//, "") || "",
       };
     });
     return { ok: true, data: dataWithId };
@@ -155,23 +133,12 @@ export function useInstanceProducts(): HttpResponse<
 export function useProductDetails(
   productId: string,
 ): HttpResponse<MerchantBackend.Products.ProductDetail> {
-  const { url: baseUrl, token: baseToken } = useBackendContext();
-  const { token: instanceToken, id, admin } = useInstanceContext();
-
-  const { url, token } = !admin
-    ? {
-        url: baseUrl,
-        token: baseToken,
-      }
-    : {
-        url: `${baseUrl}/instances/${id}`,
-        token: instanceToken,
-      };
+  const { fetcher } = useBackendInstanceRequest();
 
   const { data, error, isValidating } = useSWR<
     HttpResponseOk<MerchantBackend.Products.ProductDetail>,
     HttpError
-  >([`/private/products/${productId}`, token, url], fetcher, {
+  >([`/private/products/${productId}`], fetcher, {
     refreshInterval: 0,
     refreshWhenHidden: false,
     revalidateOnFocus: false,
diff --git a/packages/merchant-backoffice-ui/src/hooks/reserve.test.ts 
b/packages/merchant-backoffice-ui/src/hooks/reserve.test.ts
new file mode 100644
index 000000000..da0e054e5
--- /dev/null
+++ b/packages/merchant-backoffice-ui/src/hooks/reserve.test.ts
@@ -0,0 +1,431 @@
+/*
+ This file is part of GNU Taler
+ (C) 2021-2023 Taler Systems S.A.
+
+ GNU Taler is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+ */
+
+/**
+ *
+ * @author Sebastian Javier Marchano (sebasjm)
+ */
+
+import { expect } from "chai";
+import { MerchantBackend } from "../declaration.js";
+import {
+  useInstanceReserves,
+  useReserveDetails,
+  useReservesAPI,
+  useTipDetails
+} from "./reserves.js";
+import { ApiMockEnvironment } from "./testing.js";
+import {
+  API_AUTHORIZE_TIP,
+  API_AUTHORIZE_TIP_FOR_RESERVE,
+  API_CREATE_RESERVE,
+  API_DELETE_RESERVE,
+  API_GET_RESERVE_BY_ID,
+  API_GET_TIP_BY_ID,
+  API_LIST_RESERVES
+} from "./urls.js";
+import { tests } from "@gnu-taler/web-util/lib/index.browser";
+
+describe("reserve api interaction with listing", () => {
+  it("should evict cache when creating a reserve", async () => {
+    const env = new ApiMockEnvironment();
+
+    env.addRequestExpectation(API_LIST_RESERVES, {
+      response: {
+        reserves: [
+          {
+            reserve_pub: "11",
+          } as MerchantBackend.Tips.ReserveStatusEntry,
+        ],
+      },
+    });
+
+    const hookBehavior = await tests.hookBehaveLikeThis(
+      () => {
+        const api = useReservesAPI();
+        const query = useInstanceReserves();
+        return { query, api };
+      },
+      {},
+      [
+        ({ query, api }) => {
+          expect(query.loading).true;
+        },
+        ({ query, api }) => {
+          expect(query.loading).false;
+          expect(query.ok).true
+          if (!query.ok) return;
+          expect(query.data).deep.equals({
+            reserves: [{ reserve_pub: "11" }],
+          });
+
+          env.addRequestExpectation(API_CREATE_RESERVE, {
+            request: {
+              initial_balance: "ARS:3333",
+              exchange_url: "http://url";,
+              wire_method: "iban",
+            },
+            response: {
+              reserve_pub: "22",
+              payto_uri: "payto",
+            },
+          });
+
+          env.addRequestExpectation(API_LIST_RESERVES, {
+            response: {
+              reserves: [
+                {
+                  reserve_pub: "11",
+                } as MerchantBackend.Tips.ReserveStatusEntry,
+                {
+                  reserve_pub: "22",
+                } as MerchantBackend.Tips.ReserveStatusEntry,
+              ],
+            },
+          });
+
+          api.createReserve({
+            initial_balance: "ARS:3333",
+            exchange_url: "http://url";,
+            wire_method: "iban",
+          })
+        },
+        ({ query, api }) => {
+          expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: 
"ok" });
+          expect(query.loading).false;
+          expect(query.ok).true;
+          if (!query.ok) return;
+
+          expect(query.data).deep.equals({
+            reserves: [
+              {
+                reserve_pub: "11",
+              } as MerchantBackend.Tips.ReserveStatusEntry,
+              {
+                reserve_pub: "22",
+              } as MerchantBackend.Tips.ReserveStatusEntry,
+            ],
+          });
+        },
+      ], env.buildTestingContext());
+
+    expect(hookBehavior).deep.eq({ result: "ok" });
+    expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" });
+
+  });
+
+  it("should evict cache when deleting a reserve", async () => {
+    const env = new ApiMockEnvironment();
+
+    env.addRequestExpectation(API_LIST_RESERVES, {
+      response: {
+        reserves: [
+          {
+            reserve_pub: "11",
+          } as MerchantBackend.Tips.ReserveStatusEntry,
+          {
+            reserve_pub: "22",
+          } as MerchantBackend.Tips.ReserveStatusEntry,
+          {
+            reserve_pub: "33",
+          } as MerchantBackend.Tips.ReserveStatusEntry,
+        ],
+      },
+    });
+
+    const hookBehavior = await tests.hookBehaveLikeThis(
+      () => {
+        const api = useReservesAPI();
+        const query = useInstanceReserves();
+        return { query, api };
+      },
+      {},
+      [
+        ({ query, api }) => {
+          expect(query.loading).true;
+        },
+        ({ query, api }) => {
+          expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: 
"ok" })
+
+          expect(query.loading).false;
+          expect(query.ok).true
+          if (!query.ok) return;
+          expect(query.data).deep.equals({
+            reserves: [
+              { reserve_pub: "11" },
+              { reserve_pub: "22" },
+              { reserve_pub: "33" },
+            ],
+          });
+
+          env.addRequestExpectation(API_DELETE_RESERVE("11"), {});
+          env.addRequestExpectation(API_LIST_RESERVES, {
+            response: {
+              reserves: [
+                {
+                  reserve_pub: "22",
+                } as MerchantBackend.Tips.ReserveStatusEntry,
+                {
+                  reserve_pub: "33",
+                } as MerchantBackend.Tips.ReserveStatusEntry,
+              ],
+            },
+          });
+
+          api.deleteReserve("11")
+        },
+        ({ query, api }) => {
+          expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: 
"ok" })
+          expect(query.loading).false;
+          expect(query.ok).true
+          if (!query.ok) return;
+          expect(query.data).deep.equals({
+            reserves: [
+              { reserve_pub: "22" },
+              { reserve_pub: "33" },
+            ],
+          });
+        },
+      ], env.buildTestingContext());
+
+    expect(hookBehavior).deep.eq({ result: "ok" });
+    expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" });
+
+  });
+});
+
+describe("reserve api interaction with details", () => {
+  it("should evict cache when adding a tip for a specific reserve", async () 
=> {
+    const env = new ApiMockEnvironment();
+
+    env.addRequestExpectation(API_GET_RESERVE_BY_ID("11"), {
+      response: {
+        payto_uri: "payto://here",
+        tips: [{ reason: "why?", tip_id: "id1", total_amount: "USD:10" }],
+      } as MerchantBackend.Tips.ReserveDetail,
+      qparam: {
+        tips: "yes"
+      }
+    });
+
+    const hookBehavior = await tests.hookBehaveLikeThis(
+      () => {
+        const api = useReservesAPI();
+        const query = useReserveDetails("11");
+        return { query, api };
+      },
+      {},
+      [
+        ({ query, api }) => {
+          expect(query.loading).true;
+        },
+        ({ query, api }) => {
+          expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: 
"ok" });
+          expect(query.loading).false;
+          expect(query.ok).true
+          if (!query.ok) return;
+          expect(query.data).deep.equals({
+            payto_uri: "payto://here",
+            tips: [{ reason: "why?", tip_id: "id1", total_amount: "USD:10" }],
+          });
+
+          env.addRequestExpectation(API_AUTHORIZE_TIP_FOR_RESERVE("11"), {
+            request: {
+              amount: "USD:12",
+              justification: "not",
+              next_url: "http://taler.net";,
+            },
+            response: {
+              tip_id: "id2",
+              taler_tip_uri: "uri",
+              tip_expiration: { t_s: 1 },
+              tip_status_url: "url",
+            }
+          },);
+
+          env.addRequestExpectation(API_GET_RESERVE_BY_ID("11"), {
+            response: {
+              payto_uri: "payto://here",
+              tips: [
+                { reason: "why?", tip_id: "id1", total_amount: "USD:10" },
+                { reason: "not", tip_id: "id2", total_amount: "USD:12" },
+              ],
+            } as MerchantBackend.Tips.ReserveDetail,
+            qparam: {
+              tips: "yes"
+            }
+          });
+
+          api.authorizeTipReserve("11", {
+            amount: "USD:12",
+            justification: "not",
+            next_url: "http://taler.net";,
+          })
+        },
+        ({ query, api }) => {
+          expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: 
"ok" });
+          expect(query.loading).false;
+
+          expect(query.loading).false;
+          expect(query.ok).true;
+          if (!query.ok) return;
+
+          expect(query.data).deep.equals({
+            payto_uri: "payto://here",
+            tips: [
+              { reason: "why?", tip_id: "id1", total_amount: "USD:10" },
+              { reason: "not", tip_id: "id2", total_amount: "USD:12" },
+            ],
+          });
+        },
+      ], env.buildTestingContext());
+
+    expect(hookBehavior).deep.eq({ result: "ok" });
+    expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" });
+
+  });
+
+  it("should evict cache when adding a tip for a random reserve", async () => {
+    const env = new ApiMockEnvironment();
+
+    env.addRequestExpectation(API_GET_RESERVE_BY_ID("11"), {
+      response: {
+        payto_uri: "payto://here",
+        tips: [{ reason: "why?", tip_id: "id1", total_amount: "USD:10" }],
+      } as MerchantBackend.Tips.ReserveDetail,
+      qparam: {
+        tips: "yes"
+      }
+    });
+
+    const hookBehavior = await tests.hookBehaveLikeThis(
+      () => {
+        const api = useReservesAPI();
+        const query = useReserveDetails("11");
+        return { query, api };
+      },
+      {},
+      [
+        ({ query, api }) => {
+          expect(query.loading).true;
+        },
+        ({ query, api }) => {
+          expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: 
"ok" });
+          expect(query.loading).false;
+          expect(query.ok).true
+          if (!query.ok) return;
+          expect(query.data).deep.equals({
+            payto_uri: "payto://here",
+            tips: [{ reason: "why?", tip_id: "id1", total_amount: "USD:10" }],
+          });
+
+          env.addRequestExpectation(API_AUTHORIZE_TIP, {
+            request: {
+              amount: "USD:12",
+              justification: "not",
+              next_url: "http://taler.net";,
+            },
+            response: {
+              tip_id: "id2",
+              taler_tip_uri: "uri",
+              tip_expiration: { t_s: 1 },
+              tip_status_url: "url",
+            },
+          });
+
+          env.addRequestExpectation(API_GET_RESERVE_BY_ID("11"), {
+            response: {
+              payto_uri: "payto://here",
+              tips: [
+                { reason: "why?", tip_id: "id1", total_amount: "USD:10" },
+                { reason: "not", tip_id: "id2", total_amount: "USD:12" },
+              ],
+            } as MerchantBackend.Tips.ReserveDetail,
+            qparam: {
+              tips: "yes"
+            }
+          });
+
+          api.authorizeTip({
+            amount: "USD:12",
+            justification: "not",
+            next_url: "http://taler.net";,
+          });
+
+        },
+        ({ query, api }) => {
+          expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: 
"ok" });
+          expect(query.loading).false;
+          expect(query.ok).true;
+          if (!query.ok) return;
+
+          expect(query.data).deep.equals({
+            payto_uri: "payto://here",
+            tips: [
+              { reason: "why?", tip_id: "id1", total_amount: "USD:10" },
+              { reason: "not", tip_id: "id2", total_amount: "USD:12" },
+            ],
+          });
+        },
+      ], env.buildTestingContext());
+
+    expect(hookBehavior).deep.eq({ result: "ok" });
+    expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" });
+  });
+});
+
+describe("reserve api interaction with tip details", () => {
+
+  it("should list tips", async () => {
+    const env = new ApiMockEnvironment();
+
+    env.addRequestExpectation(API_GET_TIP_BY_ID("11"), {
+      response: {
+        total_picked_up: "USD:12",
+        reason: "not",
+      } as MerchantBackend.Tips.TipDetails,
+      qparam: {
+        pickups: "yes"
+      }
+    });
+
+    const hookBehavior = await tests.hookBehaveLikeThis(
+      () => {
+        const query = useTipDetails("11");
+        return { query };
+      },
+      {},
+      [
+        ({ query }) => {
+          expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: 
"ok" });
+          expect(query.loading).true;
+        },
+        ({ query }) => {
+          expect(query.loading).false;
+          expect(query.ok).true
+          if (!query.ok) return;
+          expect(query.data).deep.equals({
+            total_picked_up: "USD:12",
+            reason: "not",
+          });
+        },
+      ], env.buildTestingContext());
+
+    expect(hookBehavior).deep.eq({ result: "ok" });
+    expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" });
+
+  });
+});
diff --git a/packages/merchant-backoffice-ui/src/hooks/reserves.ts 
b/packages/merchant-backoffice-ui/src/hooks/reserves.ts
index f6d77f113..dc127af13 100644
--- a/packages/merchant-backoffice-ui/src/hooks/reserves.ts
+++ b/packages/merchant-backoffice-ui/src/hooks/reserves.ts
@@ -14,27 +14,14 @@
  GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 import useSWR, { useSWRConfig } from "swr";
-import { useBackendContext } from "../context/backend.js";
-import { useInstanceContext } from "../context/instance.js";
 import { MerchantBackend } from "../declaration.js";
-import {
-  fetcher,
-  HttpError,
-  HttpResponse,
-  HttpResponseOk,
-  request,
-  useMatchMutate,
-} from "./backend.js";
+import { HttpError, HttpResponse, HttpResponseOk } from "../utils/request.js";
+import { useBackendInstanceRequest, useMatchMutate } from "./backend.js";
 
 export function useReservesAPI(): ReserveMutateAPI {
   const mutateAll = useMatchMutate();
   const { mutate } = useSWRConfig();
-  const { url: baseUrl, token: adminToken } = useBackendContext();
-  const { token: instanceToken, id, admin } = useInstanceContext();
-
-  const { url, token } = !admin
-    ? { url: baseUrl, token: adminToken }
-    : { url: `${baseUrl}/instances/${id}`, token: instanceToken };
+  const { request } = useBackendInstanceRequest();
 
   const createReserve = async (
     data: MerchantBackend.Tips.ReserveCreateRequest,
@@ -42,10 +29,9 @@ export function useReservesAPI(): ReserveMutateAPI {
     HttpResponseOk<MerchantBackend.Tips.ReserveCreateConfirmation>
   > => {
     const res = await request<MerchantBackend.Tips.ReserveCreateConfirmation>(
-      `${url}/private/reserves`,
+      `/private/reserves`,
       {
-        method: "post",
-        token,
+        method: "POST",
         data,
       },
     );
@@ -61,16 +47,15 @@ export function useReservesAPI(): ReserveMutateAPI {
     data: MerchantBackend.Tips.TipCreateRequest,
   ): Promise<HttpResponseOk<MerchantBackend.Tips.TipCreateConfirmation>> => {
     const res = await request<MerchantBackend.Tips.TipCreateConfirmation>(
-      `${url}/private/reserves/${pub}/authorize-tip`,
+      `/private/reserves/${pub}/authorize-tip`,
       {
-        method: "post",
-        token,
+        method: "POST",
         data,
       },
     );
 
     //evict reserve details query
-    await mutate([`/private/reserves/${pub}`, token, url]);
+    await mutate([`/private/reserves/${pub}`]);
 
     return res;
   };
@@ -79,10 +64,9 @@ export function useReservesAPI(): ReserveMutateAPI {
     data: MerchantBackend.Tips.TipCreateRequest,
   ): Promise<HttpResponseOk<MerchantBackend.Tips.TipCreateConfirmation>> => {
     const res = await request<MerchantBackend.Tips.TipCreateConfirmation>(
-      `${url}/private/tips`,
+      `/private/tips`,
       {
-        method: "post",
-        token,
+        method: "POST",
         data,
       },
     );
@@ -94,9 +78,8 @@ export function useReservesAPI(): ReserveMutateAPI {
   };
 
   const deleteReserve = async (pub: string): Promise<HttpResponse<void>> => {
-    const res = await request<void>(`${url}/private/reserves/${pub}`, {
-      method: "delete",
-      token,
+    const res = await request<void>(`/private/reserves/${pub}`, {
+      method: "DELETE",
     });
 
     //evict reserve list query
@@ -123,17 +106,12 @@ export interface ReserveMutateAPI {
 }
 
 export function useInstanceReserves(): 
HttpResponse<MerchantBackend.Tips.TippingReserveStatus> {
-  const { url: baseUrl, token: baseToken } = useBackendContext();
-  const { token: instanceToken, id, admin } = useInstanceContext();
-
-  const { url, token } = !admin
-    ? { url: baseUrl, token: baseToken }
-    : { url: `${baseUrl}/instances/${id}`, token: instanceToken };
+  const { fetcher } = useBackendInstanceRequest();
 
   const { data, error, isValidating } = useSWR<
     HttpResponseOk<MerchantBackend.Tips.TippingReserveStatus>,
     HttpError
-  >([`/private/reserves`, token, url], fetcher);
+  >([`/private/reserves`], fetcher);
 
   if (isValidating) return { loading: true, data: data?.data };
   if (data) return data;
@@ -144,15 +122,12 @@ export function useInstanceReserves(): 
HttpResponse<MerchantBackend.Tips.Tipping
 export function useReserveDetails(
   reserveId: string,
 ): HttpResponse<MerchantBackend.Tips.ReserveDetail> {
-  const { url: baseUrl } = useBackendContext();
-  const { token, id: instanceId, admin } = useInstanceContext();
-
-  const url = !admin ? baseUrl : `${baseUrl}/instances/${instanceId}`;
+  const { reserveDetailFetcher } = useBackendInstanceRequest();
 
   const { data, error, isValidating } = useSWR<
     HttpResponseOk<MerchantBackend.Tips.ReserveDetail>,
     HttpError
-  >([`/private/reserves/${reserveId}`, token, url], reserveDetailFetcher, {
+  >([`/private/reserves/${reserveId}`], reserveDetailFetcher, {
     refreshInterval: 0,
     refreshWhenHidden: false,
     revalidateOnFocus: false,
@@ -169,15 +144,12 @@ export function useReserveDetails(
 export function useTipDetails(
   tipId: string,
 ): HttpResponse<MerchantBackend.Tips.TipDetails> {
-  const { url: baseUrl } = useBackendContext();
-  const { token, id: instanceId, admin } = useInstanceContext();
-
-  const url = !admin ? baseUrl : `${baseUrl}/instances/${instanceId}`;
+  const { tipsDetailFetcher } = useBackendInstanceRequest();
 
   const { data, error, isValidating } = useSWR<
     HttpResponseOk<MerchantBackend.Tips.TipDetails>,
     HttpError
-  >([`/private/tips/${tipId}`, token, url], tipsDetailFetcher, {
+  >([`/private/tips/${tipId}`], tipsDetailFetcher, {
     refreshInterval: 0,
     refreshWhenHidden: false,
     revalidateOnFocus: false,
@@ -190,29 +162,3 @@ export function useTipDetails(
   if (error) return error;
   return { loading: true };
 }
-
-function reserveDetailFetcher<T>(
-  url: string,
-  token: string,
-  backend: string,
-): Promise<HttpResponseOk<T>> {
-  return request<T>(`${backend}${url}`, {
-    token,
-    params: {
-      tips: "yes",
-    },
-  });
-}
-
-function tipsDetailFetcher<T>(
-  url: string,
-  token: string,
-  backend: string,
-): Promise<HttpResponseOk<T>> {
-  return request<T>(`${backend}${url}`, {
-    token,
-    params: {
-      pickups: "yes",
-    },
-  });
-}
diff --git a/packages/merchant-backoffice-ui/src/hooks/templates.ts 
b/packages/merchant-backoffice-ui/src/hooks/templates.ts
index 3e69d78d0..55c3875b5 100644
--- a/packages/merchant-backoffice-ui/src/hooks/templates.ts
+++ b/packages/merchant-backoffice-ui/src/hooks/templates.ts
@@ -14,57 +14,26 @@
  GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 import { MerchantBackend } from "../declaration.js";
-import { useBackendContext } from "../context/backend.js";
+import { useMatchMutate, useBackendInstanceRequest } from "./backend.js";
+import useSWR from "swr";
+import { MAX_RESULT_SIZE, PAGE_SIZE } from "../utils/constants.js";
+import { useEffect, useState } from "preact/hooks";
 import {
-  request,
-  HttpResponse,
   HttpError,
+  HttpResponse,
   HttpResponseOk,
   HttpResponsePaginated,
-  useMatchMutate,
-} from "./backend.js";
-import useSWR from "swr";
-import { useInstanceContext } from "../context/instance.js";
-import { MAX_RESULT_SIZE, PAGE_SIZE } from "../utils/constants.js";
-import { useEffect, useState } from "preact/hooks";
-
-async function templateFetcher<T>(
-  url: string,
-  token: string,
-  backend: string,
-  position?: string,
-  delta?: number,
-): Promise<HttpResponseOk<T>> {
-  const params: any = {};
-  if (delta !== undefined) {
-    params.limit = delta;
-  }
-  if (position !== undefined) params.offset = position;
-
-  return request<T>(`${backend}${url}`, { token, params });
-}
+} from "../utils/request.js";
 
 export function useTemplateAPI(): TemplateAPI {
   const mutateAll = useMatchMutate();
-  const { url: baseUrl, token: adminToken } = useBackendContext();
-  const { token: instanceToken, id, admin } = useInstanceContext();
-
-  const { url, token } = !admin
-    ? {
-        url: baseUrl,
-        token: adminToken,
-      }
-    : {
-        url: `${baseUrl}/instances/${id}`,
-        token: instanceToken,
-      };
+  const { request } = useBackendInstanceRequest();
 
   const createTemplate = async (
     data: MerchantBackend.Template.TemplateAddDetails,
   ): Promise<HttpResponseOk<void>> => {
-    const res = await request<void>(`${url}/private/templates`, {
-      method: "post",
-      token,
+    const res = await request<void>(`/private/templates`, {
+      method: "POST",
       data,
     });
     await mutateAll(/.*private\/templates.*/);
@@ -75,9 +44,8 @@ export function useTemplateAPI(): TemplateAPI {
     templateId: string,
     data: MerchantBackend.Template.TemplatePatchDetails,
   ): Promise<HttpResponseOk<void>> => {
-    const res = await request<void>(`${url}/private/templates/${templateId}`, {
-      method: "patch",
-      token,
+    const res = await request<void>(`/private/templates/${templateId}`, {
+      method: "PATCH",
       data,
     });
     await mutateAll(/.*private\/templates.*/);
@@ -87,9 +55,8 @@ export function useTemplateAPI(): TemplateAPI {
   const deleteTemplate = async (
     templateId: string,
   ): Promise<HttpResponseOk<void>> => {
-    const res = await request<void>(`${url}/private/templates/${templateId}`, {
-      method: "delete",
-      token,
+    const res = await request<void>(`/private/templates/${templateId}`, {
+      method: "DELETE",
     });
     await mutateAll(/.*private\/templates.*/);
     return res;
@@ -102,10 +69,9 @@ export function useTemplateAPI(): TemplateAPI {
     HttpResponseOk<MerchantBackend.Template.UsingTemplateResponse>
   > => {
     const res = await request<MerchantBackend.Template.UsingTemplateResponse>(
-      `${url}/private/templates/${templateId}`,
+      `/private/templates/${templateId}`,
       {
-        method: "post",
-        token,
+        method: "POST",
         data,
       },
     );
@@ -140,12 +106,7 @@ export function useInstanceTemplates(
   args?: InstanceTemplateFilter,
   updatePosition?: (id: string) => void,
 ): HttpResponsePaginated<MerchantBackend.Template.TemplateSummaryResponse> {
-  const { url: baseUrl, token: baseToken } = useBackendContext();
-  const { token: instanceToken, id, admin } = useInstanceContext();
-
-  const { url, token } = !admin
-    ? { url: baseUrl, token: baseToken }
-    : { url: `${baseUrl}/instances/${id}`, token: instanceToken };
+  const { templateFetcher } = useBackendInstanceRequest();
 
   // const [pageBefore, setPageBefore] = useState(1);
   const [pageAfter, setPageAfter] = useState(1);
@@ -180,10 +141,7 @@ export function useInstanceTemplates(
   } = useSWR<
     HttpResponseOk<MerchantBackend.Template.TemplateSummaryResponse>,
     HttpError
-  >(
-    [`/private/templates`, token, url, args?.position, -totalAfter],
-    templateFetcher,
-  );
+  >([`/private/templates`, args?.position, -totalAfter], templateFetcher);
 
   //this will save last result
   // const [lastBefore, setLastBefore] = useState<
@@ -216,10 +174,9 @@ export function useInstanceTemplates(
       if (afterData.data.templates.length < MAX_RESULT_SIZE) {
         setPageAfter(pageAfter + 1);
       } else {
-        const from = `${
-          afterData.data.templates[afterData.data.templates.length - 1]
-            .template_id
-        }`;
+        const from = 
`${afterData.data.templates[afterData.data.templates.length - 1]
+          .template_id
+          }`;
         if (from && updatePosition) updatePosition(from);
       }
     },
@@ -255,17 +212,12 @@ export function useInstanceTemplates(
 export function useTemplateDetails(
   templateId: string,
 ): HttpResponse<MerchantBackend.Template.TemplateDetails> {
-  const { url: baseUrl, token: baseToken } = useBackendContext();
-  const { token: instanceToken, id, admin } = useInstanceContext();
-
-  const { url, token } = !admin
-    ? { url: baseUrl, token: baseToken }
-    : { url: `${baseUrl}/instances/${id}`, token: instanceToken };
+  const { templateFetcher } = useBackendInstanceRequest();
 
   const { data, error, isValidating } = useSWR<
     HttpResponseOk<MerchantBackend.Template.TemplateDetails>,
     HttpError
-  >([`/private/templates/${templateId}`, token, url], templateFetcher, {
+  >([`/private/templates/${templateId}`], templateFetcher, {
     refreshInterval: 0,
     refreshWhenHidden: false,
     revalidateOnFocus: false,
diff --git a/packages/merchant-backoffice-ui/src/hooks/testing.tsx 
b/packages/merchant-backoffice-ui/src/hooks/testing.tsx
new file mode 100644
index 000000000..8c5a5a36b
--- /dev/null
+++ b/packages/merchant-backoffice-ui/src/hooks/testing.tsx
@@ -0,0 +1,120 @@
+/*
+ This file is part of GNU Taler
+ (C) 2021-2023 Taler Systems S.A.
+
+ GNU Taler is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+ */
+
+/**
+ *
+ * @author Sebastian Javier Marchano (sebasjm)
+ */
+
+import { MockEnvironment } from "@gnu-taler/web-util/lib/tests/mock";
+import { ComponentChildren, FunctionalComponent, h, VNode } from "preact";
+import { SWRConfig } from "swr";
+import { ApiContextProvider } from "../context/api.js";
+import { BackendContextProvider } from "../context/backend.js";
+import { InstanceContextProvider } from "../context/instance.js";
+import { HttpResponseOk, RequestOptions } from "../utils/request.js";
+
+export class ApiMockEnvironment extends MockEnvironment {
+  constructor(debug = false) {
+    super(debug);
+  }
+
+  mockApiIfNeeded(): void {
+    null; // do nothing
+  }
+
+  public buildTestingContext(): FunctionalComponent<{
+    children: ComponentChildren;
+  }> {
+    const __SAVE_REQUEST_AND_GET_MOCKED_RESPONSE =
+      this.saveRequestAndGetMockedResponse.bind(this);
+
+    return function TestingContext({
+      children,
+    }: {
+      children: ComponentChildren;
+    }): VNode {
+      async function request<T>(
+        base: string,
+        path: string,
+        options: RequestOptions = {},
+      ): Promise<HttpResponseOk<T>> {
+        const _url = new URL(`${base}${path}`);
+        // Object.entries(options.params ?? {}).forEach(([key, value]) => {
+        //   _url.searchParams.set(key, String(value));
+        // });
+
+        const mocked = __SAVE_REQUEST_AND_GET_MOCKED_RESPONSE(
+          {
+            method: options.method ?? "GET",
+            url: _url.href,
+          },
+          {
+            qparam: options.params,
+            auth: options.token,
+            request: options.data,
+          },
+        );
+
+        return {
+          ok: true,
+          data: (!mocked ? undefined : mocked.payload) as T,
+          loading: false,
+          clientError: false,
+          serverError: false,
+          info: {
+            hasToken: !!options.token,
+            status: !mocked ? 200 : mocked.status,
+            url: _url,
+            payload: options.data,
+          },
+        };
+      }
+      const SC: any = SWRConfig;
+
+      return (
+        <BackendContextProvider
+          defaultUrl="http://backend";
+          initialToken={undefined}
+        >
+          <InstanceContextProvider
+            value={{
+              token: undefined,
+              id: "default",
+              admin: true,
+              changeToken: () => null,
+            }}
+          >
+            <ApiContextProvider value={{ request }}>
+              <SC
+                value={{
+                  loadingTimeout: 0,
+                  dedupingInterval: 0,
+                  shouldRetryOnError: false,
+                  errorRetryInterval: 0,
+                  errorRetryCount: 0,
+                  provider: () => new Map(),
+                }}
+              >
+                {children}
+              </SC>
+            </ApiContextProvider>
+          </InstanceContextProvider>
+        </BackendContextProvider>
+      );
+    };
+  }
+}
diff --git a/packages/merchant-backoffice-ui/src/hooks/transfer.test.ts 
b/packages/merchant-backoffice-ui/src/hooks/transfer.test.ts
new file mode 100644
index 000000000..a553ed362
--- /dev/null
+++ b/packages/merchant-backoffice-ui/src/hooks/transfer.test.ts
@@ -0,0 +1,277 @@
+/*
+ This file is part of GNU Taler
+ (C) 2021-2023 Taler Systems S.A.
+
+ GNU Taler is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+ */
+
+/**
+ *
+ * @author Sebastian Javier Marchano (sebasjm)
+ */
+
+import { tests } from "@gnu-taler/web-util/lib/index.browser";
+import { expect } from "chai";
+import { MerchantBackend } from "../declaration.js";
+import { API_INFORM_TRANSFERS, API_LIST_TRANSFERS } from "./urls.js";
+import { ApiMockEnvironment } from "./testing.js";
+import { useInstanceTransfers, useTransferAPI } from "./transfer.js";
+
+describe("transfer api interaction with listing", () => {
+  it("should evict cache when informing a transfer", async () => {
+    const env = new ApiMockEnvironment();
+
+    env.addRequestExpectation(API_LIST_TRANSFERS, {
+      qparam: { limit: 0 },
+      response: {
+        transfers: [{ wtid: "2" } as 
MerchantBackend.Transfers.TransferDetails],
+      },
+    });
+    // FIXME: is this query really needed? if the hook is rendered without
+    // position argument then then backend is returning the newest and no need
+    // to this second query
+    env.addRequestExpectation(API_LIST_TRANSFERS, {
+      qparam: { limit: -20 },
+      response: {
+        transfers: [],
+      },
+    });
+
+    const moveCursor = (d: string) => {
+      console.log("new position", d);
+    };
+
+    const hookBehavior = await tests.hookBehaveLikeThis(
+      () => {
+        const query = useInstanceTransfers({}, moveCursor);
+        const api = useTransferAPI();
+        return { query, api };
+      },
+      {},
+      [
+        ({ query, api }) => {
+          expect(query.loading).true;
+        },
+
+        ({ query, api }) => {
+          expect(env.assertJustExpectedRequestWereMade()).deep.eq({
+            result: "ok",
+          });
+          expect(query.loading).undefined;
+          expect(query.ok).true;
+          if (!query.ok) return;
+          expect(query.data).deep.equals({
+            transfers: [{ wtid: "2" }],
+          });
+
+          env.addRequestExpectation(API_INFORM_TRANSFERS, {
+            request: {
+              wtid: "3",
+              credit_amount: "EUR:1",
+              exchange_url: "exchange.url",
+              payto_uri: "payto://",
+            },
+            response: { total: "" } as any,
+          });
+
+          env.addRequestExpectation(API_LIST_TRANSFERS, {
+            qparam: { limit: 0 },
+            response: {
+              transfers: [{ wtid: "2" } as any, { wtid: "3" } as any],
+            },
+          });
+
+          env.addRequestExpectation(API_LIST_TRANSFERS, {
+            qparam: { limit: -20 },
+            response: {
+              transfers: [],
+            },
+          });
+
+          api.informTransfer({
+            wtid: "3",
+            credit_amount: "EUR:1",
+            exchange_url: "exchange.url",
+            payto_uri: "payto://",
+          });
+        },
+        ({ query, api }) => {
+          expect(env.assertJustExpectedRequestWereMade()).deep.eq({
+            result: "ok",
+          });
+          expect(query.loading).undefined;
+          expect(query.ok).true;
+          if (!query.ok) return;
+
+          expect(query.data).deep.equals({
+            transfers: [{ wtid: "3" }, { wtid: "2" }],
+          });
+        },
+      ],
+      env.buildTestingContext(),
+    );
+
+    expect(hookBehavior).deep.eq({ result: "ok" });
+    expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" });
+  });
+});
+
+describe("transfer listing pagination", () => {
+  it("should not load more if has reach the end", async () => {
+    const env = new ApiMockEnvironment();
+
+    env.addRequestExpectation(API_LIST_TRANSFERS, {
+      qparam: { limit: 0, payto_uri: "payto://" },
+      response: {
+        transfers: [{ wtid: "2" } as any],
+      },
+    });
+
+    env.addRequestExpectation(API_LIST_TRANSFERS, {
+      qparam: { limit: -20, payto_uri: "payto://" },
+      response: {
+        transfers: [{ wtid: "1" } as any],
+      },
+    });
+
+    const moveCursor = (d: string) => {
+      console.log("new position", d);
+    };
+    const hookBehavior = await tests.hookBehaveLikeThis(
+      () => {
+        return useInstanceTransfers({ payto_uri: "payto://" }, moveCursor);
+      },
+      {},
+      [
+        (query) => {
+          expect(env.assertJustExpectedRequestWereMade()).deep.eq({
+            result: "ok",
+          });
+          expect(query.loading).true;
+        },
+        (query) => {
+          expect(query.loading).undefined;
+          expect(query.ok).true;
+          if (!query.ok) return;
+          expect(query.data).deep.equals({
+            transfers: [{ wtid: "2" }, { wtid: "1" }],
+          });
+          expect(query.isReachingEnd).true;
+          expect(query.isReachingStart).true;
+
+          //check that this button won't trigger more updates since
+          //has reach end and start
+          query.loadMore();
+          query.loadMorePrev();
+        },
+      ],
+      env.buildTestingContext(),
+    );
+
+    expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" });
+    expect(hookBehavior).deep.eq({ result: "ok" });
+  });
+
+  it("should load more if result brings more that PAGE_SIZE", async () => {
+    const env = new ApiMockEnvironment();
+
+    const transfersFrom0to20 = Array.from({ length: 20 }).map((e, i) => ({
+      wtid: String(i),
+    }));
+    const transfersFrom20to40 = Array.from({ length: 20 }).map((e, i) => ({
+      wtid: String(i + 20),
+    }));
+    const transfersFrom20to0 = [...transfersFrom0to20].reverse();
+
+    env.addRequestExpectation(API_LIST_TRANSFERS, {
+      qparam: { limit: 20, payto_uri: "payto://", offset: "1" },
+      response: {
+        transfers: transfersFrom0to20,
+      },
+    });
+
+    env.addRequestExpectation(API_LIST_TRANSFERS, {
+      qparam: { limit: -20, payto_uri: "payto://", offset: "1" },
+      response: {
+        transfers: transfersFrom20to40,
+      },
+    });
+
+    const moveCursor = (d: string) => {
+      console.log("new position", d);
+    };
+
+    const hookBehavior = await tests.hookBehaveLikeThis(
+      () => {
+        return useInstanceTransfers(
+          { payto_uri: "payto://", position: "1" },
+          moveCursor,
+        );
+      },
+      {},
+      [
+        (result) => {
+          expect(env.assertJustExpectedRequestWereMade()).deep.eq({
+            result: "ok",
+          });
+          expect(result.loading).true;
+        },
+        (result) => {
+          expect(result.loading).undefined;
+          expect(result.ok).true;
+          if (!result.ok) return;
+          expect(result.data).deep.equals({
+            transfers: [...transfersFrom20to0, ...transfersFrom20to40],
+          });
+          expect(result.isReachingEnd).false;
+          expect(result.isReachingStart).false;
+
+          //query more
+          env.addRequestExpectation(API_LIST_TRANSFERS, {
+            qparam: { limit: -40, payto_uri: "payto://", offset: "1" },
+            response: {
+              transfers: [...transfersFrom20to40, { wtid: "41" }],
+            },
+          });
+          result.loadMore();
+        },
+        (result) => {
+          expect(env.assertJustExpectedRequestWereMade()).deep.eq({
+            result: "ok",
+          });
+          expect(result.loading).true;
+        },
+        (result) => {
+          expect(env.assertJustExpectedRequestWereMade()).deep.eq({
+            result: "ok",
+          });
+          expect(result.loading).undefined;
+          expect(result.ok).true;
+          if (!result.ok) return;
+          expect(result.data).deep.equals({
+            transfers: [
+              ...transfersFrom20to0,
+              ...transfersFrom20to40,
+              { wtid: "41" },
+            ],
+          });
+          expect(result.isReachingEnd).true;
+          expect(result.isReachingStart).false;
+        },
+      ],
+      env.buildTestingContext(),
+    );
+
+    expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" });
+    expect(hookBehavior).deep.eq({ result: "ok" });
+  });
+});
diff --git a/packages/merchant-backoffice-ui/src/hooks/transfer.ts 
b/packages/merchant-backoffice-ui/src/hooks/transfer.ts
index d1ac2c285..c827772e4 100644
--- a/packages/merchant-backoffice-ui/src/hooks/transfer.ts
+++ b/packages/merchant-backoffice-ui/src/hooks/transfer.ts
@@ -13,55 +13,21 @@
  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 { useEffect, useState } from "preact/hooks";
+import useSWR from "swr";
 import { MerchantBackend } from "../declaration.js";
-import { useBackendContext } from "../context/backend.js";
+import { MAX_RESULT_SIZE, PAGE_SIZE } from "../utils/constants.js";
 import {
-  request,
-  HttpResponse,
   HttpError,
+  HttpResponse,
   HttpResponseOk,
   HttpResponsePaginated,
-  useMatchMutate,
-} from "./backend.js";
-import useSWR from "swr";
-import { useInstanceContext } from "../context/instance.js";
-import { MAX_RESULT_SIZE, PAGE_SIZE } from "../utils/constants.js";
-import { useEffect, useState } from "preact/hooks";
-
-async function transferFetcher<T>(
-  url: string,
-  token: string,
-  backend: string,
-  payto_uri?: string,
-  verified?: string,
-  position?: string,
-  delta?: number,
-): Promise<HttpResponseOk<T>> {
-  const params: any = {};
-  if (payto_uri !== undefined) params.payto_uri = payto_uri;
-  if (verified !== undefined) params.verified = verified;
-  if (delta !== undefined) {
-    params.limit = delta;
-  }
-  if (position !== undefined) params.offset = position;
-
-  return request<T>(`${backend}${url}`, { token, params });
-}
+} from "../utils/request.js";
+import { useBackendInstanceRequest, useMatchMutate } from "./backend.js";
 
 export function useTransferAPI(): TransferAPI {
   const mutateAll = useMatchMutate();
-  const { url: baseUrl, token: adminToken } = useBackendContext();
-  const { token: instanceToken, id, admin } = useInstanceContext();
-
-  const { url, token } = !admin
-    ? {
-        url: baseUrl,
-        token: adminToken,
-      }
-    : {
-        url: `${baseUrl}/instances/${id}`,
-        token: instanceToken,
-      };
+  const { request } = useBackendInstanceRequest();
 
   const informTransfer = async (
     data: MerchantBackend.Transfers.TransferInformation,
@@ -70,10 +36,9 @@ export function useTransferAPI(): TransferAPI {
   > => {
     const res =
       await request<MerchantBackend.Transfers.MerchantTrackTransferResponse>(
-        `${url}/private/transfers`,
+        `/private/transfers`,
         {
-          method: "post",
-          token,
+          method: "POST",
           data,
         },
       );
@@ -103,12 +68,7 @@ export function useInstanceTransfers(
   args?: InstanceTransferFilter,
   updatePosition?: (id: string) => void,
 ): HttpResponsePaginated<MerchantBackend.Transfers.TransferList> {
-  const { url: baseUrl, token: baseToken } = useBackendContext();
-  const { token: instanceToken, id, admin } = useInstanceContext();
-
-  const { url, token } = !admin
-    ? { url: baseUrl, token: baseToken }
-    : { url: `${baseUrl}/instances/${id}`, token: instanceToken };
+  const { transferFetcher } = useBackendInstanceRequest();
 
   const [pageBefore, setPageBefore] = useState(1);
   const [pageAfter, setPageAfter] = useState(1);
@@ -129,8 +89,6 @@ export function useInstanceTransfers(
   } = useSWR<HttpResponseOk<MerchantBackend.Transfers.TransferList>, 
HttpError>(
     [
       `/private/transfers`,
-      token,
-      url,
       args?.payto_uri,
       args?.verified,
       args?.position,
@@ -145,8 +103,6 @@ export function useInstanceTransfers(
   } = useSWR<HttpResponseOk<MerchantBackend.Transfers.TransferList>, 
HttpError>(
     [
       `/private/transfers`,
-      token,
-      url,
       args?.payto_uri,
       args?.verified,
       args?.position,
@@ -185,10 +141,9 @@ export function useInstanceTransfers(
       if (afterData.data.transfers.length < MAX_RESULT_SIZE) {
         setPageAfter(pageAfter + 1);
       } else {
-        const from = `${
-          afterData.data.transfers[afterData.data.transfers.length - 1]
+        const from = 
`${afterData.data.transfers[afterData.data.transfers.length - 1]
             .transfer_serial_id
-        }`;
+          }`;
         if (from && updatePosition) updatePosition(from);
       }
     },
@@ -197,10 +152,9 @@ export function useInstanceTransfers(
       if (beforeData.data.transfers.length < MAX_RESULT_SIZE) {
         setPageBefore(pageBefore + 1);
       } else if (beforeData) {
-        const from = `${
-          beforeData.data.transfers[beforeData.data.transfers.length - 1]
+        const from = 
`${beforeData.data.transfers[beforeData.data.transfers.length - 1]
             .transfer_serial_id
-        }`;
+          }`;
         if (from && updatePosition) updatePosition(from);
       }
     },
@@ -210,9 +164,9 @@ export function useInstanceTransfers(
     !beforeData || !afterData
       ? []
       : (beforeData || lastBefore).data.transfers
-          .slice()
-          .reverse()
-          .concat((afterData || lastAfter).data.transfers);
+        .slice()
+        .reverse()
+        .concat((afterData || lastAfter).data.transfers);
   if (loadingAfter || loadingBefore)
     return { loading: true, data: { transfers } };
   if (beforeData && afterData) {
diff --git a/packages/merchant-backoffice-ui/src/hooks/urls.ts 
b/packages/merchant-backoffice-ui/src/hooks/urls.ts
new file mode 100644
index 000000000..05494c0c9
--- /dev/null
+++ b/packages/merchant-backoffice-ui/src/hooks/urls.ts
@@ -0,0 +1,291 @@
+/*
+ This file is part of GNU Taler
+ (C) 2021-2023 Taler Systems S.A.
+
+ GNU Taler is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+ */
+
+/**
+ *
+ * @author Sebastian Javier Marchano (sebasjm)
+ */
+import { Query } from "@gnu-taler/web-util/lib/tests/mock";
+import { MerchantBackend } from "../declaration.js";
+
+////////////////////
+// ORDER
+////////////////////
+
+export const API_CREATE_ORDER: Query<
+  MerchantBackend.Orders.PostOrderRequest,
+  MerchantBackend.Orders.PostOrderResponse
+> = {
+  method: "POST",
+  url: "http://backend/instances/default/private/orders";,
+};
+
+export const API_GET_ORDER_BY_ID = (
+  id: string,
+): Query<unknown, MerchantBackend.Orders.MerchantOrderStatusResponse> => ({
+  method: "GET",
+  url: `http://backend/instances/default/private/orders/${id}`,
+});
+
+export const API_LIST_ORDERS: Query<
+  unknown,
+  MerchantBackend.Orders.OrderHistory
+> = {
+  method: "GET",
+  url: "http://backend/instances/default/private/orders";,
+};
+
+export const API_REFUND_ORDER_BY_ID = (
+  id: string,
+): Query<
+  MerchantBackend.Orders.RefundRequest,
+  MerchantBackend.Orders.MerchantRefundResponse
+> => ({
+  method: "POST",
+  url: `http://backend/instances/default/private/orders/${id}/refund`,
+});
+
+export const API_FORGET_ORDER_BY_ID = (
+  id: string,
+): Query<MerchantBackend.Orders.ForgetRequest, unknown> => ({
+  method: "PATCH",
+  url: `http://backend/instances/default/private/orders/${id}/forget`,
+});
+
+export const API_DELETE_ORDER = (
+  id: string,
+): Query<MerchantBackend.Orders.ForgetRequest, unknown> => ({
+  method: "DELETE",
+  url: `http://backend/instances/default/private/orders/${id}`,
+});
+
+////////////////////
+// TRANSFER
+////////////////////
+
+export const API_LIST_TRANSFERS: Query<
+  unknown,
+  MerchantBackend.Transfers.TransferList
+> = {
+  method: "GET",
+  url: "http://backend/instances/default/private/transfers";,
+};
+
+export const API_INFORM_TRANSFERS: Query<
+  MerchantBackend.Transfers.TransferInformation,
+  MerchantBackend.Transfers.MerchantTrackTransferResponse
+> = {
+  method: "POST",
+  url: "http://backend/instances/default/private/transfers";,
+};
+
+////////////////////
+// PRODUCT
+////////////////////
+
+export const API_CREATE_PRODUCT: Query<
+  MerchantBackend.Products.ProductAddDetail,
+  unknown
+> = {
+  method: "POST",
+  url: "http://backend/instances/default/private/products";,
+};
+
+export const API_LIST_PRODUCTS: Query<
+  unknown,
+  MerchantBackend.Products.InventorySummaryResponse
+> = {
+  method: "GET",
+  url: "http://backend/instances/default/private/products";,
+};
+
+export const API_GET_PRODUCT_BY_ID = (
+  id: string,
+): Query<unknown, MerchantBackend.Products.ProductDetail> => ({
+  method: "GET",
+  url: `http://backend/instances/default/private/products/${id}`,
+});
+
+export const API_UPDATE_PRODUCT_BY_ID = (
+  id: string,
+): Query<
+  MerchantBackend.Products.ProductPatchDetail,
+  MerchantBackend.Products.InventorySummaryResponse
+> => ({
+  method: "PATCH",
+  url: `http://backend/instances/default/private/products/${id}`,
+});
+
+export const API_DELETE_PRODUCT = (id: string): Query<unknown, unknown> => ({
+  method: "DELETE",
+  url: `http://backend/instances/default/private/products/${id}`,
+});
+
+////////////////////
+// RESERVES
+////////////////////
+
+export const API_CREATE_RESERVE: Query<
+  MerchantBackend.Tips.ReserveCreateRequest,
+  MerchantBackend.Tips.ReserveCreateConfirmation
+> = {
+  method: "POST",
+  url: "http://backend/instances/default/private/reserves";,
+};
+export const API_LIST_RESERVES: Query<
+  unknown,
+  MerchantBackend.Tips.TippingReserveStatus
+> = {
+  method: "GET",
+  url: "http://backend/instances/default/private/reserves";,
+};
+
+export const API_GET_RESERVE_BY_ID = (
+  pub: string,
+): Query<unknown, MerchantBackend.Tips.ReserveDetail> => ({
+  method: "GET",
+  url: `http://backend/instances/default/private/reserves/${pub}`,
+});
+
+export const API_GET_TIP_BY_ID = (
+  pub: string,
+): Query<unknown, MerchantBackend.Tips.TipDetails> => ({
+  method: "GET",
+  url: `http://backend/instances/default/private/tips/${pub}`,
+});
+
+export const API_AUTHORIZE_TIP_FOR_RESERVE = (
+  pub: string,
+): Query<
+  MerchantBackend.Tips.TipCreateRequest,
+  MerchantBackend.Tips.TipCreateConfirmation
+> => ({
+  method: "POST",
+  url: 
`http://backend/instances/default/private/reserves/${pub}/authorize-tip`,
+});
+
+export const API_AUTHORIZE_TIP: Query<
+  MerchantBackend.Tips.TipCreateRequest,
+  MerchantBackend.Tips.TipCreateConfirmation
+> = {
+  method: "POST",
+  url: `http://backend/instances/default/private/tips`,
+};
+
+export const API_DELETE_RESERVE = (id: string): Query<unknown, unknown> => ({
+  method: "DELETE",
+  url: `http://backend/instances/default/private/reserves/${id}`,
+});
+
+////////////////////
+// INSTANCE ADMIN
+////////////////////
+
+export const API_CREATE_INSTANCE: Query<
+  MerchantBackend.Instances.InstanceConfigurationMessage,
+  unknown
+> = {
+  method: "POST",
+  url: "http://backend/management/instances";,
+};
+
+export const API_GET_INSTANCE_BY_ID = (
+  id: string,
+): Query<unknown, MerchantBackend.Instances.QueryInstancesResponse> => ({
+  method: "GET",
+  url: `http://backend/management/instances/${id}`,
+});
+
+export const API_GET_INSTANCE_KYC_BY_ID = (
+  id: string,
+): Query<unknown, MerchantBackend.Instances.AccountKycRedirects> => ({
+  method: "GET",
+  url: `http://backend/management/instances/${id}/kyc`,
+});
+
+export const API_LIST_INSTANCES: Query<
+  unknown,
+  MerchantBackend.Instances.InstancesResponse
+> = {
+  method: "GET",
+  url: "http://backend/management/instances";,
+};
+
+export const API_UPDATE_INSTANCE_BY_ID = (
+  id: string,
+): Query<
+  MerchantBackend.Instances.InstanceReconfigurationMessage,
+  unknown
+> => ({
+  method: "PATCH",
+  url: `http://backend/management/instances/${id}`,
+});
+
+export const API_UPDATE_INSTANCE_AUTH_BY_ID = (
+  id: string,
+): Query<
+  MerchantBackend.Instances.InstanceAuthConfigurationMessage,
+  unknown
+> => ({
+  method: "POST",
+  url: `http://backend/management/instances/${id}/auth`,
+});
+
+export const API_DELETE_INSTANCE = (id: string): Query<unknown, unknown> => ({
+  method: "DELETE",
+  url: `http://backend/management/instances/${id}`,
+});
+
+////////////////////
+// INSTANCE
+////////////////////
+
+export const API_GET_CURRENT_INSTANCE: Query<
+  unknown,
+  MerchantBackend.Instances.QueryInstancesResponse
+> = {
+  method: "GET",
+  url: `http://backend/instances/default/private/`,
+};
+
+export const API_GET_CURRENT_INSTANCE_KYC: Query<
+  unknown,
+  MerchantBackend.Instances.AccountKycRedirects
+> = {
+  method: "GET",
+  url: `http://backend/instances/default/private/kyc`,
+};
+
+export const API_UPDATE_CURRENT_INSTANCE: Query<
+  MerchantBackend.Instances.InstanceReconfigurationMessage,
+  unknown
+> = {
+  method: "PATCH",
+  url: `http://backend/instances/default/private/`,
+};
+
+export const API_UPDATE_CURRENT_INSTANCE_AUTH: Query<
+  MerchantBackend.Instances.InstanceAuthConfigurationMessage,
+  unknown
+> = {
+  method: "POST",
+  url: `http://backend/instances/default/private/auth`,
+};
+
+export const API_DELETE_CURRENT_INSTANCE: Query<unknown, unknown> = {
+  method: "DELETE",
+  url: `http://backend/instances/default/private`,
+};
diff --git a/packages/merchant-backoffice-ui/src/manifest.json 
b/packages/merchant-backoffice-ui/src/manifest.json
deleted file mode 100644
index 2c3de2339..000000000
--- a/packages/merchant-backoffice-ui/src/manifest.json
+++ /dev/null
@@ -1,21 +0,0 @@
-{
-  "name": "backoffice-preact",
-  "short_name": "backoffice-preact",
-  "start_url": "/",
-  "display": "standalone",
-  "orientation": "portrait",
-  "background_color": "#fff",
-  "theme_color": "#673ab8",
-  "icons": [
-    {
-      "src": "/assets/icons/android-chrome-192x192.png",
-      "type": "image/png",
-      "sizes": "192x192"
-    },
-    {
-      "src": "/assets/icons/android-chrome-512x512.png",
-      "type": "image/png",
-      "sizes": "512x512"
-    }
-  ]
-}
diff --git a/packages/merchant-backoffice-ui/src/paths/admin/list/index.tsx 
b/packages/merchant-backoffice-ui/src/paths/admin/list/index.tsx
index 9a81b72d4..bac7a39eb 100644
--- a/packages/merchant-backoffice-ui/src/paths/admin/list/index.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/admin/list/index.tsx
@@ -26,7 +26,7 @@ import { Loading } from 
"../../../components/exception/loading.js";
 import { NotificationCard } from "../../../components/menu/index.js";
 import { DeleteModal, PurgeModal } from "../../../components/modal/index.js";
 import { MerchantBackend } from "../../../declaration.js";
-import { HttpError } from "../../../hooks/backend.js";
+import { HttpError } from "../../../utils/request.js";
 import { useAdminAPI, useBackendInstances } from "../../../hooks/instance.js";
 import { Notification } from "../../../utils/types.js";
 import { View } from "./View.js";
diff --git 
a/packages/merchant-backoffice-ui/src/paths/instance/details/index.tsx 
b/packages/merchant-backoffice-ui/src/paths/instance/details/index.tsx
index 49b64262b..56d5c0755 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/details/index.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/details/index.tsx
@@ -18,7 +18,7 @@ import { useState } from "preact/hooks";
 import { Loading } from "../../../components/exception/loading.js";
 import { DeleteModal } from "../../../components/modal/index.js";
 import { useInstanceContext } from "../../../context/instance.js";
-import { HttpError } from "../../../hooks/backend.js";
+import { HttpError } from "../../../utils/request.js";
 import { useInstanceAPI, useInstanceDetails } from 
"../../../hooks/instance.js";
 import { DetailPage } from "./DetailPage.js";
 
diff --git 
a/packages/merchant-backoffice-ui/src/paths/instance/kyc/list/index.tsx 
b/packages/merchant-backoffice-ui/src/paths/instance/kyc/list/index.tsx
index 295d6a749..83af002b3 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/kyc/list/index.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/kyc/list/index.tsx
@@ -21,7 +21,7 @@
 
 import { h, VNode } from "preact";
 import { Loading } from "../../../../components/exception/loading.js";
-import { HttpError } from "../../../../hooks/backend.js";
+import { HttpError } from "../../../../utils/request.js";
 import { useInstanceKYCDetails } from "../../../../hooks/instance.js";
 import { ListPage } from "./ListPage.js";
 
diff --git 
a/packages/merchant-backoffice-ui/src/paths/instance/orders/create/index.tsx 
b/packages/merchant-backoffice-ui/src/paths/instance/orders/create/index.tsx
index 95232da92..5c6293a81 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/orders/create/index.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/orders/create/index.tsx
@@ -24,7 +24,7 @@ import { useState } from "preact/hooks";
 import { Loading } from "../../../../components/exception/loading.js";
 import { NotificationCard } from "../../../../components/menu/index.js";
 import { MerchantBackend } from "../../../../declaration.js";
-import { HttpError } from "../../../../hooks/backend.js";
+import { HttpError } from "../../../../utils/request.js";
 import { useInstanceDetails } from "../../../../hooks/instance.js";
 import { useOrderAPI } from "../../../../hooks/order.js";
 import { useInstanceProducts } from "../../../../hooks/product.js";
diff --git 
a/packages/merchant-backoffice-ui/src/paths/instance/orders/details/index.tsx 
b/packages/merchant-backoffice-ui/src/paths/instance/orders/details/index.tsx
index bb0240982..19aaddf50 100644
--- 
a/packages/merchant-backoffice-ui/src/paths/instance/orders/details/index.tsx
+++ 
b/packages/merchant-backoffice-ui/src/paths/instance/orders/details/index.tsx
@@ -18,7 +18,7 @@ import { Fragment, h, VNode } from "preact";
 import { useState } from "preact/hooks";
 import { Loading } from "../../../../components/exception/loading.js";
 import { NotificationCard } from "../../../../components/menu/index.js";
-import { HttpError } from "../../../../hooks/backend.js";
+import { HttpError } from "../../../../utils/request.js";
 import { useOrderAPI, useOrderDetails } from "../../../../hooks/order.js";
 import { Notification } from "../../../../utils/types.js";
 import { DetailPage } from "./DetailPage.js";
diff --git 
a/packages/merchant-backoffice-ui/src/paths/instance/orders/list/index.tsx 
b/packages/merchant-backoffice-ui/src/paths/instance/orders/list/index.tsx
index e29c57a7c..3744ce8c5 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/orders/list/index.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/orders/list/index.tsx
@@ -25,7 +25,7 @@ import { useState } from "preact/hooks";
 import { Loading } from "../../../../components/exception/loading.js";
 import { NotificationCard } from "../../../../components/menu/index.js";
 import { MerchantBackend } from "../../../../declaration.js";
-import { HttpError } from "../../../../hooks/backend.js";
+import { HttpError } from "../../../../utils/request.js";
 import {
   InstanceOrderFilter,
   useInstanceOrders,
diff --git 
a/packages/merchant-backoffice-ui/src/paths/instance/products/list/index.tsx 
b/packages/merchant-backoffice-ui/src/paths/instance/products/list/index.tsx
index 41a07a7aa..25332acee 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/products/list/index.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/products/list/index.tsx
@@ -25,7 +25,7 @@ import { useState } from "preact/hooks";
 import { Loading } from "../../../../components/exception/loading.js";
 import { NotificationCard } from "../../../../components/menu/index.js";
 import { MerchantBackend, WithId } from "../../../../declaration.js";
-import { HttpError } from "../../../../hooks/backend.js";
+import { HttpError } from "../../../../utils/request.js";
 import {
   useInstanceProducts,
   useProductAPI,
diff --git 
a/packages/merchant-backoffice-ui/src/paths/instance/products/update/index.tsx 
b/packages/merchant-backoffice-ui/src/paths/instance/products/update/index.tsx
index e141dc52c..5b19a7aa3 100644
--- 
a/packages/merchant-backoffice-ui/src/paths/instance/products/update/index.tsx
+++ 
b/packages/merchant-backoffice-ui/src/paths/instance/products/update/index.tsx
@@ -25,7 +25,7 @@ import { useState } from "preact/hooks";
 import { Loading } from "../../../../components/exception/loading.js";
 import { NotificationCard } from "../../../../components/menu/index.js";
 import { MerchantBackend } from "../../../../declaration.js";
-import { HttpError } from "../../../../hooks/backend.js";
+import { HttpError } from "../../../../utils/request.js";
 import { useProductAPI, useProductDetails } from 
"../../../../hooks/product.js";
 import { Notification } from "../../../../utils/types.js";
 import { UpdatePage } from "./UpdatePage.js";
diff --git 
a/packages/merchant-backoffice-ui/src/paths/instance/reserves/create/CreatePage.tsx
 
b/packages/merchant-backoffice-ui/src/paths/instance/reserves/create/CreatePage.tsx
index de2319636..ad0cca74a 100644
--- 
a/packages/merchant-backoffice-ui/src/paths/instance/reserves/create/CreatePage.tsx
+++ 
b/packages/merchant-backoffice-ui/src/paths/instance/reserves/create/CreatePage.tsx
@@ -31,7 +31,7 @@ import { Input } from "../../../../components/form/Input.js";
 import { InputCurrency } from "../../../../components/form/InputCurrency.js";
 import { InputSelector } from "../../../../components/form/InputSelector.js";
 import { ExchangeBackend, MerchantBackend } from "../../../../declaration.js";
-import { request } from "../../../../hooks/backend.js";
+// import { request } from "../../../../utils/request.js";
 import {
   PAYTO_WIRE_METHOD_LOOKUP,
   URL_REGEX,
@@ -124,11 +124,10 @@ function ViewStep({
             <AsyncButton
               class="has-tooltip-left"
               onClick={() => {
-                return request<ExchangeBackend.WireResponse>(
-                  `${reserve.exchange_url}wire`,
-                )
+                return fetch(`${reserve.exchange_url}wire`)
+                  .then((r) => r.json())
                   .then((r) => {
-                    const wireMethods = r.data.accounts.map((a) => {
+                    const wireMethods = r.data.accounts.map((a: any) => {
                       const match = PAYTO_WIRE_METHOD_LOOKUP.exec(a.payto_uri);
                       return (match && match[1]) || "";
                     });
diff --git 
a/packages/merchant-backoffice-ui/src/paths/instance/reserves/details/index.tsx 
b/packages/merchant-backoffice-ui/src/paths/instance/reserves/details/index.tsx
index b13b075fd..57ee566d1 100644
--- 
a/packages/merchant-backoffice-ui/src/paths/instance/reserves/details/index.tsx
+++ 
b/packages/merchant-backoffice-ui/src/paths/instance/reserves/details/index.tsx
@@ -21,7 +21,7 @@
 
 import { Fragment, h, VNode } from "preact";
 import { Loading } from "../../../../components/exception/loading.js";
-import { HttpError } from "../../../../hooks/backend.js";
+import { HttpError } from "../../../../utils/request.js";
 import { useReserveDetails } from "../../../../hooks/reserves.js";
 import { DetailPage } from "./DetailPage.js";
 
diff --git 
a/packages/merchant-backoffice-ui/src/paths/instance/reserves/list/index.tsx 
b/packages/merchant-backoffice-ui/src/paths/instance/reserves/list/index.tsx
index 9c3255ee8..597bde167 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/reserves/list/index.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/reserves/list/index.tsx
@@ -25,7 +25,7 @@ import { useState } from "preact/hooks";
 import { Loading } from "../../../../components/exception/loading.js";
 import { NotificationCard } from "../../../../components/menu/index.js";
 import { MerchantBackend } from "../../../../declaration.js";
-import { HttpError } from "../../../../hooks/backend.js";
+import { HttpError } from "../../../../utils/request.js";
 import {
   useInstanceReserves,
   useReservesAPI,
diff --git 
a/packages/merchant-backoffice-ui/src/paths/instance/templates/list/index.tsx 
b/packages/merchant-backoffice-ui/src/paths/instance/templates/list/index.tsx
index dcac23983..e1a2d019e 100644
--- 
a/packages/merchant-backoffice-ui/src/paths/instance/templates/list/index.tsx
+++ 
b/packages/merchant-backoffice-ui/src/paths/instance/templates/list/index.tsx
@@ -25,7 +25,7 @@ import { useState } from "preact/hooks";
 import { Loading } from "../../../../components/exception/loading.js";
 import { NotificationCard } from "../../../../components/menu/index.js";
 import { MerchantBackend } from "../../../../declaration.js";
-import { HttpError } from "../../../../hooks/backend.js";
+import { HttpError } from "../../../../utils/request.js";
 import {
   useInstanceTemplates,
   useTemplateAPI,
diff --git 
a/packages/merchant-backoffice-ui/src/paths/instance/templates/update/index.tsx 
b/packages/merchant-backoffice-ui/src/paths/instance/templates/update/index.tsx
index 4a4cc4274..684ffd429 100644
--- 
a/packages/merchant-backoffice-ui/src/paths/instance/templates/update/index.tsx
+++ 
b/packages/merchant-backoffice-ui/src/paths/instance/templates/update/index.tsx
@@ -25,7 +25,7 @@ import { useState } from "preact/hooks";
 import { Loading } from "../../../../components/exception/loading.js";
 import { NotificationCard } from "../../../../components/menu/index.js";
 import { MerchantBackend, WithId } from "../../../../declaration.js";
-import { HttpError } from "../../../../hooks/backend.js";
+import { HttpError } from "../../../../utils/request.js";
 import {
   useTemplateAPI,
   useTemplateDetails,
diff --git 
a/packages/merchant-backoffice-ui/src/paths/instance/transfers/list/index.tsx 
b/packages/merchant-backoffice-ui/src/paths/instance/transfers/list/index.tsx
index 242380fbc..59b56a613 100644
--- 
a/packages/merchant-backoffice-ui/src/paths/instance/transfers/list/index.tsx
+++ 
b/packages/merchant-backoffice-ui/src/paths/instance/transfers/list/index.tsx
@@ -23,7 +23,7 @@ import { h, VNode } from "preact";
 import { useState } from "preact/hooks";
 import { Loading } from "../../../../components/exception/loading.js";
 import { MerchantBackend } from "../../../../declaration.js";
-import { HttpError } from "../../../../hooks/backend.js";
+import { HttpError } from "../../../../utils/request.js";
 import { useInstanceDetails } from "../../../../hooks/instance.js";
 import { useInstanceTransfers } from "../../../../hooks/transfer.js";
 import { ListPage } from "./ListPage.js";
diff --git 
a/packages/merchant-backoffice-ui/src/paths/instance/update/index.tsx 
b/packages/merchant-backoffice-ui/src/paths/instance/update/index.tsx
index 668fe9a8d..02beb36f2 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/update/index.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/update/index.tsx
@@ -20,13 +20,13 @@ import { Loading } from 
"../../../components/exception/loading.js";
 import { NotificationCard } from "../../../components/menu/index.js";
 import { useInstanceContext } from "../../../context/instance.js";
 import { MerchantBackend } from "../../../declaration.js";
-import { HttpError, HttpResponse } from "../../../hooks/backend.js";
 import {
   useInstanceAPI,
   useInstanceDetails,
   useManagedInstanceDetails,
   useManagementAPI,
 } from "../../../hooks/instance.js";
+import { HttpError, HttpResponse } from "../../../utils/request.js";
 import { Notification } from "../../../utils/types.js";
 import { UpdatePage } from "./UpdatePage.js";
 
diff --git a/packages/merchant-backoffice-ui/tests/functions/regex.test.ts 
b/packages/merchant-backoffice-ui/src/utils/regex.test.ts
similarity index 89%
rename from packages/merchant-backoffice-ui/tests/functions/regex.test.ts
rename to packages/merchant-backoffice-ui/src/utils/regex.test.ts
index d866a13a0..41f0156f5 100644
--- a/packages/merchant-backoffice-ui/tests/functions/regex.test.ts
+++ b/packages/merchant-backoffice-ui/src/utils/regex.test.ts
@@ -19,6 +19,7 @@
 * @author Sebastian Javier Marchano (sebasjm)
 */
 
+import { expect } from "chai";
 import { AMOUNT_REGEX, PAYTO_REGEX } from "../../src/utils/constants.js";
 
 describe('payto uri format', () => {
@@ -31,7 +32,7 @@ describe('payto uri format', () => {
   ]
 
   it('should be valid', () => {
-    valids.forEach(v => expect(v).toMatch(PAYTO_REGEX))
+    valids.forEach(v => expect(v).match(PAYTO_REGEX))
   });
 
   const invalids = [
@@ -48,7 +49,7 @@ describe('payto uri format', () => {
   ]
 
   it('should not be valid', () => {
-    invalids.forEach(v => expect(v).not.toMatch(PAYTO_REGEX))
+    invalids.forEach(v => expect(v).not.match(PAYTO_REGEX))
   });
 })
 
@@ -64,7 +65,7 @@ describe('amount format', () => {
   ]
 
   it('should be valid', () => {
-    valids.forEach(v => expect(v).toMatch(AMOUNT_REGEX))
+    valids.forEach(v => expect(v).match(AMOUNT_REGEX))
   });
 
   const invalids = [
@@ -81,7 +82,7 @@ describe('amount format', () => {
   ]
 
   it('should not be valid', () => {
-    invalids.forEach(v => expect(v).not.toMatch(AMOUNT_REGEX))
+    invalids.forEach(v => expect(v).not.match(AMOUNT_REGEX))
   });
 
 })
\ No newline at end of file
diff --git a/packages/merchant-backoffice-ui/src/utils/request.ts 
b/packages/merchant-backoffice-ui/src/utils/request.ts
new file mode 100644
index 000000000..32b31a557
--- /dev/null
+++ b/packages/merchant-backoffice-ui/src/utils/request.ts
@@ -0,0 +1,282 @@
+/*
+ This file is part of GNU Taler
+ (C) 2021-2023 Taler Systems S.A.
+
+ GNU Taler is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+ */
+// import axios, { AxiosError, AxiosResponse } from "axios";
+import { MerchantBackend } from "../declaration.js";
+
+export async function defaultRequestHandler<T>(
+  base: string,
+  path: string,
+  options: RequestOptions = {},
+): Promise<HttpResponseOk<T>> {
+  const requestHeaders = options.token
+    ? { Authorization: `Bearer ${options.token}` }
+    : undefined;
+
+  const requestMethod = options?.method ?? "GET";
+  const requestBody = options?.data;
+  const requestTimeout = 2 * 1000;
+  const requestParams = options.params ?? {};
+
+  const _url = new URL(`${base}${path}`);
+
+  Object.entries(requestParams).forEach(([key, value]) => {
+    _url.searchParams.set(key, String(value));
+  });
+
+  let payload: BodyInit | undefined = undefined;
+  if (requestBody != null) {
+    if (typeof requestBody === "string") {
+      payload = requestBody;
+    } else if (requestBody instanceof ArrayBuffer) {
+      payload = requestBody;
+    } else if (ArrayBuffer.isView(requestBody)) {
+      payload = requestBody;
+    } else if (typeof requestBody === "object") {
+      payload = JSON.stringify(requestBody);
+    } else {
+      throw Error("unsupported request body type");
+    }
+  }
+
+  const controller = new AbortController();
+  const timeoutId = setTimeout(() => {
+    controller.abort("HTTP_REQUEST_TIMEOUT");
+  }, requestTimeout);
+
+  const response = await fetch(_url.href, {
+    headers: {
+      ...requestHeaders,
+      "Content-Type": "text/plain",
+    },
+    method: requestMethod,
+    credentials: "omit",
+    mode: "cors",
+    body: payload,
+    signal: controller.signal,
+  });
+
+  if (timeoutId) {
+    clearTimeout(timeoutId);
+  }
+  const headerMap = new Headers();
+  response.headers.forEach((value, key) => {
+    headerMap.set(key, value);
+  });
+
+  if (response.ok) {
+    const result = await buildRequestOk<T>(
+      response,
+      _url,
+      payload,
+      !!options.token,
+    );
+    return result;
+  } else {
+    const error = await buildRequestFailed(
+      response,
+      _url,
+      payload,
+      !!options.token,
+    );
+    throw error;
+  }
+}
+
+export type HttpResponse<T> =
+  | HttpResponseOk<T>
+  | HttpResponseLoading<T>
+  | HttpError;
+export type HttpResponsePaginated<T> =
+  | HttpResponseOkPaginated<T>
+  | HttpResponseLoading<T>
+  | HttpError;
+
+export interface RequestInfo {
+  url: URL;
+  hasToken: boolean;
+  payload: any;
+  status: number;
+}
+
+interface HttpResponseLoading<T> {
+  ok?: false;
+  loading: true;
+  clientError?: false;
+  serverError?: false;
+
+  data?: T;
+}
+export interface HttpResponseOk<T> {
+  ok: true;
+  loading?: false;
+  clientError?: false;
+  serverError?: false;
+
+  data: T;
+  info?: RequestInfo;
+}
+
+export type HttpResponseOkPaginated<T> = HttpResponseOk<T> & WithPagination;
+
+export interface WithPagination {
+  loadMore: () => void;
+  loadMorePrev: () => void;
+  isReachingEnd?: boolean;
+  isReachingStart?: boolean;
+}
+
+export type HttpError =
+  | HttpResponseClientError
+  | HttpResponseServerError
+  | HttpResponseUnexpectedError;
+export interface SwrError {
+  info: unknown;
+  status: number;
+  message: string;
+}
+export interface HttpResponseServerError {
+  ok?: false;
+  loading?: false;
+  clientError?: false;
+  serverError: true;
+
+  error?: MerchantBackend.ErrorDetail;
+  status: number;
+  message: string;
+  info?: RequestInfo;
+}
+interface HttpResponseClientError {
+  ok?: false;
+  loading?: false;
+  clientError: true;
+  serverError?: false;
+
+  info?: RequestInfo;
+  isUnauthorized: boolean;
+  isNotfound: boolean;
+  status: number;
+  error?: MerchantBackend.ErrorDetail;
+  message: string;
+}
+
+interface HttpResponseUnexpectedError {
+  ok?: false;
+  loading?: false;
+  clientError?: false;
+  serverError?: false;
+
+  info?: RequestInfo;
+  status?: number;
+  error: unknown;
+  message: string;
+}
+
+type Methods = "GET" | "POST" | "PATCH" | "DELETE" | "PUT";
+
+export interface RequestOptions {
+  method?: Methods;
+  token?: string;
+  data?: any;
+  params?: unknown;
+}
+
+async function buildRequestOk<T>(
+  response: Response,
+  url: URL,
+  payload: any,
+  hasToken: boolean,
+): Promise<HttpResponseOk<T>> {
+  const dataTxt = await response.text();
+  const data = dataTxt ? JSON.parse(dataTxt) : undefined
+  return {
+    ok: true,
+    data,
+    info: {
+      payload,
+      url,
+      hasToken,
+      status: response.status,
+    },
+  };
+}
+
+async function buildRequestFailed(
+  response: Response,
+  url: URL,
+  payload: any,
+  hasToken: boolean,
+): Promise<
+  | HttpResponseClientError
+  | HttpResponseServerError
+  | HttpResponseUnexpectedError
+> {
+  const status = response?.status;
+
+  const info: RequestInfo = {
+    payload,
+    url,
+    hasToken,
+    status: status || 0,
+  };
+
+  try {
+    const dataTxt = await response.text();
+    const data = dataTxt ? JSON.parse(dataTxt) : undefined
+    if (status && status >= 400 && status < 500) {
+      const error: HttpResponseClientError = {
+        clientError: true,
+        isNotfound: status === 404,
+        isUnauthorized: status === 401,
+        status,
+        info,
+        message: data?.hint,
+        error: data,
+      };
+      return error;
+    }
+    if (status && status >= 500 && status < 600) {
+      const error: HttpResponseServerError = {
+        serverError: true,
+        status,
+        info,
+        message: `${data?.hint} (code ${data?.code})`,
+        error: data,
+      };
+      return error;
+    }
+    return {
+      info,
+      status,
+      error: {},
+      message: "NOT DEFINED",
+    };
+  } catch (ex) {
+    const error: HttpResponseUnexpectedError = {
+      info,
+      status,
+      error: ex,
+      message: "NOT DEFINED",
+    };
+
+    throw error;
+  }
+}
+
+// export function isAxiosError<T>(
+//   error: AxiosError | any,
+// ): error is AxiosError<T> {
+//   return error && error.isAxiosError;
+// }
diff --git a/packages/merchant-backoffice-ui/src/utils/switchableAxios.ts 
b/packages/merchant-backoffice-ui/src/utils/switchableAxios.ts
deleted file mode 100644
index 20ce7043e..000000000
--- a/packages/merchant-backoffice-ui/src/utils/switchableAxios.ts
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2021-2023 Taler Systems S.A.
-
- GNU Taler is free software; you can redistribute it and/or modify it under the
- terms of the GNU General Public License as published by the Free Software
- Foundation; either version 3, or (at your option) any later version.
-
- GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along with
- GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
- */
-
-import axios, { AxiosPromise, AxiosRequestConfig } from "axios";
-
-/**
- *
- * @author Sebastian Javier Marchano (sebasjm)
- */
-
-export let removeAxiosCancelToken = false;
-
-export let axiosHandler = function doAxiosRequest(
-  config: AxiosRequestConfig,
-): AxiosPromise<any> {
-  return axios(config);
-};
-
-/**
- * Set this backend library to testing mode.
- * Instead of calling the axios library the @handler will be called
- *
- * @param handler callback that will mock axios
- */
-export function setAxiosRequestAsTestingEnvironment(
-  handler: AxiosHandler,
-): void {
-  removeAxiosCancelToken = true;
-  axiosHandler = function defaultTestingHandler(config) {
-    const currentHanlder = listOfHandlersToUseOnce.shift();
-    if (!currentHanlder) {
-      return handler(config);
-    }
-
-    return currentHanlder(config);
-  };
-}
-
-type AxiosHandler = (config: AxiosRequestConfig) => AxiosPromise<any>;
-type AxiosArguments = { args: AxiosRequestConfig | undefined };
-
-const listOfHandlersToUseOnce = new Array<AxiosHandler>();
-
-/**
- *
- * @param handler mock function
- * @returns savedArgs
- */
-export function mockAxiosOnce(handler: AxiosHandler): {
-  args: AxiosRequestConfig | undefined;
-} {
-  const savedArgs: AxiosArguments = { args: undefined };
-  listOfHandlersToUseOnce.push(
-    (config: AxiosRequestConfig): AxiosPromise<any> => {
-      savedArgs.args = config;
-      return handler(config);
-    },
-  );
-  return savedArgs;
-}
diff --git a/packages/merchant-backoffice-ui/tests/__mocks__/fileMocks.ts 
b/packages/merchant-backoffice-ui/tests/__mocks__/fileMocks.ts
deleted file mode 100644
index 982832ea8..000000000
--- a/packages/merchant-backoffice-ui/tests/__mocks__/fileMocks.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2021-2023 Taler Systems S.A.
-
- GNU Taler is free software; you can redistribute it and/or modify it under the
- terms of the GNU General Public License as published by the Free Software
- Foundation; either version 3, or (at your option) any later version.
-
- GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along with
- GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
- */
-
- /**
- *
- * @author Sebastian Javier Marchano (sebasjm)
- */
-
-// This fixed an error related to the CSS and loading gif breaking my Jest test
-// See 
https://facebook.github.io/jest/docs/en/webpack.html#handling-static-assets
-export default 'test-file-stub';
diff --git a/packages/merchant-backoffice-ui/tests/__mocks__/fileTransformer.js 
b/packages/merchant-backoffice-ui/tests/__mocks__/fileTransformer.js
deleted file mode 100644
index b76da9168..000000000
--- a/packages/merchant-backoffice-ui/tests/__mocks__/fileTransformer.js
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2021-2023 Taler Systems S.A.
-
- GNU Taler is free software; you can redistribute it and/or modify it under the
- terms of the GNU General Public License as published by the Free Software
- Foundation; either version 3, or (at your option) any later version.
-
- GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along with
- GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
- */
-
-/**
-*
-* @author Sebastian Javier Marchano (sebasjm)
-*/
-// fileTransformer.js
-
-// eslint-disable-next-line @typescript-eslint/no-var-requires
-const path = require('path');
-
-module.exports = {
-    process(src, filename, config, options) {
-        return 'module.exports = ' + JSON.stringify(path.basename(filename)) + 
';';
-    },
-};
-
diff --git a/packages/merchant-backoffice-ui/tests/__mocks__/setupTests.ts 
b/packages/merchant-backoffice-ui/tests/__mocks__/setupTests.ts
deleted file mode 100644
index fe2d72d5c..000000000
--- a/packages/merchant-backoffice-ui/tests/__mocks__/setupTests.ts
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2021-2023 Taler Systems S.A.
-
- GNU Taler is free software; you can redistribute it and/or modify it under the
- terms of the GNU General Public License as published by the Free Software
- Foundation; either version 3, or (at your option) any later version.
-
- GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along with
- GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
- */
-
-/**
- *
- * @author Sebastian Javier Marchano (sebasjm)
- */
-
-import "regenerator-runtime/runtime";
-// import { configure } from 'enzyme';
-// import Adapter from 'enzyme-adapter-preact-pure';
-
-// configure({
-//     adapter: new Adapter()
-// });
diff --git a/packages/merchant-backoffice-ui/tests/axiosMock.ts 
b/packages/merchant-backoffice-ui/tests/axiosMock.ts
deleted file mode 100644
index ca8d5096d..000000000
--- a/packages/merchant-backoffice-ui/tests/axiosMock.ts
+++ /dev/null
@@ -1,445 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2021-2023 Taler Systems S.A.
-
- GNU Taler is free software; you can redistribute it and/or modify it under the
- terms of the GNU General Public License as published by the Free Software
- Foundation; either version 3, or (at your option) any later version.
-
- GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along with
- GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
- */
-
-/**
-*
-* @author Sebastian Javier Marchano (sebasjm)
-*/
-import * as axios from 'axios';
-import { MerchantBackend } from "../src/declaration.js";
-import { mockAxiosOnce, setAxiosRequestAsTestingEnvironment } from 
"../src/utils/switchableAxios.js";
-// import { mockAxiosOnce, setAxiosRequestAsTestingEnvironment } from 
"../src/hooks/backend.js";
-
-export type Query<Req, Res> = (GetQuery | PostQuery | DeleteQuery | 
PatchQuery) & RequestResponse<Req, Res>
-
-interface RequestResponse<Req, Res> {
-  code?: number,
-}
-interface GetQuery { get: string }
-interface PostQuery { post: string }
-interface DeleteQuery { delete: string }
-interface PatchQuery { patch: string }
-
-
-const JEST_DEBUG_LOG = process.env['JEST_DEBUG_LOG'] !== undefined
-
-type ExpectationValues = { query: Query<any, any>; params?: { auth?: string, 
request?: any, qparam?: any, response?: any } }
-
-type TestValues = [axios.AxiosRequestConfig | undefined, ExpectationValues | 
undefined]
-
-const defaultCallback = (actualQuery?: axios.AxiosRequestConfig): 
axios.AxiosPromise<any> => {
-  if (JEST_DEBUG_LOG) {
-    console.log('UNEXPECTED QUERY', actualQuery)
-  }
-  throw Error('Default Axios mock callback is called, this mean that the test 
did a tried to use axios but there was no expectation in place, try using 
JEST_DEBUG_LOG env')
-}
-
-setAxiosRequestAsTestingEnvironment(
-  defaultCallback
-);
-
-export class AxiosMockEnvironment {
-  expectations: Array<{
-    query: Query<any, any>,
-    auth?: string,
-    params?: { request?: any, qparam?: any, response?: any },
-    result: { args: axios.AxiosRequestConfig | undefined }
-  } | undefined> = []
-  // axiosMock: jest.MockedFunction<axios.AxiosStatic>
-
-  addRequestExpectation<RequestType, ResponseType>(expectedQuery: 
Query<RequestType, ResponseType>, params: { auth?: string, request?: 
RequestType, qparam?: any, response?: ResponseType }): void {
-    const result = mockAxiosOnce(function (actualQuery?: 
axios.AxiosRequestConfig): axios.AxiosPromise {
-
-      if (JEST_DEBUG_LOG) {
-        console.log('query to the backend is made', actualQuery)
-      }
-      if (!expectedQuery) {
-        return Promise.reject("a query was made but it was not expected")
-      }
-      if (JEST_DEBUG_LOG) {
-        console.log('expected query:', params?.request)
-        console.log('expected qparams:', params?.qparam)
-        console.log('sending response:', params?.response)
-      }
-
-      const responseCode = expectedQuery.code || 200
-
-      //This response is what buildRequestOk is expecting in file 
hook/backend.ts
-      if (responseCode >= 200 && responseCode < 300) {
-        return Promise.resolve({
-          data: params?.response, config: {
-            data: params?.response,
-            params: actualQuery?.params || {},
-          }, request: { params: actualQuery?.params || {} }
-        } as any);
-      }
-      //This response is what buildRequestFailed is expecting in file 
hook/backend.ts
-      return Promise.reject({
-        response: {
-          status: responseCode
-        },
-        request: {
-          data: params?.response,
-          params: actualQuery?.params || {},
-        }
-      })
-
-    } as any)
-
-    this.expectations.push(expectedQuery ? { query: expectedQuery, params, 
result } : undefined)
-  }
-
-  getLastTestValues(): TestValues {
-    const expectedQuery = this.expectations.shift()
-
-    return [
-      expectedQuery?.result.args, expectedQuery
-    ]
-  }
-
-}
-
-export function assertJustExpectedRequestWereMade(env: AxiosMockEnvironment): 
void {
-  let size = env.expectations.length
-  while (size-- > 0) {
-    assertNextRequest(env)
-  }
-  assertNoMoreRequestWereMade(env)
-}
-
-export function assertNoMoreRequestWereMade(env: AxiosMockEnvironment): void {
-  const [actualQuery, expectedQuery] = env.getLastTestValues()
-
-  expect(actualQuery).toBeUndefined();
-  expect(expectedQuery).toBeUndefined();
-}
-
-export function assertNextRequest(env: AxiosMockEnvironment): void {
-  const [actualQuery, expectedQuery] = env.getLastTestValues()
-
-  if (!actualQuery) {
-    //expected one query but the tested component didn't execute one
-    expect(actualQuery).toBe(expectedQuery);
-    return
-  }
-
-  if (!expectedQuery) {
-    const errorMessage = 'a query was made to the backend but the test 
explicitly expected no query';
-    if (JEST_DEBUG_LOG) {
-      console.log(errorMessage, actualQuery)
-    }
-    throw Error(errorMessage)
-  }
-  if ('get' in expectedQuery.query) {
-    expect(actualQuery.method).toBe('get');
-    expect(actualQuery.url).toBe(expectedQuery.query.get);
-  }
-  if ('post' in expectedQuery.query) {
-    expect(actualQuery.method).toBe('post');
-    expect(actualQuery.url).toBe(expectedQuery.query.post);
-  }
-  if ('delete' in expectedQuery.query) {
-    expect(actualQuery.method).toBe('delete');
-    expect(actualQuery.url).toBe(expectedQuery.query.delete);
-  }
-  if ('patch' in expectedQuery.query) {
-    expect(actualQuery.method).toBe('patch');
-    expect(actualQuery.url).toBe(expectedQuery.query.patch);
-  }
-
-  if (expectedQuery.params?.request) {
-    expect(actualQuery.data).toMatchObject(expectedQuery.params.request)
-  }
-  if (expectedQuery.params?.qparam) {
-    expect(actualQuery.params).toMatchObject(expectedQuery.params.qparam)
-  }
-
-  if (expectedQuery.params?.auth) {
-    expect(actualQuery.headers.Authorization).toBe(expectedQuery.params?.auth)
-  }
-
-}
-
-////////////////////
-// ORDER
-////////////////////
-
-export const API_CREATE_ORDER: Query<
-  MerchantBackend.Orders.PostOrderRequest,
-  MerchantBackend.Orders.PostOrderResponse
-> = {
-  post: "http://backend/instances/default/private/orders";,
-};
-
-export const API_GET_ORDER_BY_ID = (
-  id: string
-): Query<
-  unknown,
-  MerchantBackend.Orders.MerchantOrderStatusResponse
-> => ({
-  get: `http://backend/instances/default/private/orders/${id}`,
-});
-
-export const API_LIST_ORDERS: Query<
-  unknown,
-  MerchantBackend.Orders.OrderHistory
-> = {
-  get: "http://backend/instances/default/private/orders";,
-};
-
-export const API_REFUND_ORDER_BY_ID = (
-  id: string
-): Query<
-  MerchantBackend.Orders.RefundRequest,
-  MerchantBackend.Orders.MerchantRefundResponse
-> => ({
-  post: `http://backend/instances/default/private/orders/${id}/refund`,
-});
-
-export const API_FORGET_ORDER_BY_ID = (
-  id: string
-): Query<
-  MerchantBackend.Orders.ForgetRequest,
-  unknown
-> => ({
-  patch: `http://backend/instances/default/private/orders/${id}/forget`,
-});
-
-export const API_DELETE_ORDER = (
-  id: string
-): Query<
-  MerchantBackend.Orders.ForgetRequest,
-  unknown
-> => ({
-  delete: `http://backend/instances/default/private/orders/${id}`,
-});
-
-////////////////////
-// TRANSFER
-////////////////////
-
-export const API_LIST_TRANSFERS: Query<
-  unknown,
-  MerchantBackend.Transfers.TransferList
-> = {
-  get: "http://backend/instances/default/private/transfers";,
-};
-
-export const API_INFORM_TRANSFERS: Query<
-  MerchantBackend.Transfers.TransferInformation,
-  MerchantBackend.Transfers.MerchantTrackTransferResponse
-> = {
-  post: "http://backend/instances/default/private/transfers";,
-};
-
-////////////////////
-// PRODUCT
-////////////////////
-
-export const API_CREATE_PRODUCT: Query<
-  MerchantBackend.Products.ProductAddDetail,
-  unknown
-> = {
-  post: "http://backend/instances/default/private/products";,
-};
-
-export const API_LIST_PRODUCTS: Query<
-  unknown,
-  MerchantBackend.Products.InventorySummaryResponse
-> = {
-  get: "http://backend/instances/default/private/products";,
-};
-
-export const API_GET_PRODUCT_BY_ID = (
-  id: string
-): Query<unknown, MerchantBackend.Products.ProductDetail> => ({
-  get: `http://backend/instances/default/private/products/${id}`,
-});
-
-export const API_UPDATE_PRODUCT_BY_ID = (
-  id: string
-): Query<
-  MerchantBackend.Products.ProductPatchDetail,
-  MerchantBackend.Products.InventorySummaryResponse
-> => ({
-  patch: `http://backend/instances/default/private/products/${id}`,
-});
-
-export const API_DELETE_PRODUCT = (
-  id: string
-): Query<
-  unknown, unknown
-> => ({
-  delete: `http://backend/instances/default/private/products/${id}`,
-});
-
-////////////////////
-// RESERVES
-////////////////////
-
-export const API_CREATE_RESERVE: Query<
-  MerchantBackend.Tips.ReserveCreateRequest,
-  MerchantBackend.Tips.ReserveCreateConfirmation
-> = {
-  post: "http://backend/instances/default/private/reserves";,
-};
-export const API_LIST_RESERVES: Query<
-  unknown,
-  MerchantBackend.Tips.TippingReserveStatus
-> = {
-  get: "http://backend/instances/default/private/reserves";,
-};
-
-export const API_GET_RESERVE_BY_ID = (
-  pub: string
-): Query<unknown, MerchantBackend.Tips.ReserveDetail> => ({
-  get: `http://backend/instances/default/private/reserves/${pub}`,
-});
-
-export const API_GET_TIP_BY_ID = (
-  pub: string
-): Query<
-  unknown,
-  MerchantBackend.Tips.TipDetails
-> => ({
-  get: `http://backend/instances/default/private/tips/${pub}`,
-});
-
-export const API_AUTHORIZE_TIP_FOR_RESERVE = (
-  pub: string
-): Query<
-  MerchantBackend.Tips.TipCreateRequest,
-  MerchantBackend.Tips.TipCreateConfirmation
-> => ({
-  post: 
`http://backend/instances/default/private/reserves/${pub}/authorize-tip`,
-});
-
-export const API_AUTHORIZE_TIP: Query<
-  MerchantBackend.Tips.TipCreateRequest,
-  MerchantBackend.Tips.TipCreateConfirmation
-> = ({
-  post: `http://backend/instances/default/private/tips`,
-});
-
-
-export const API_DELETE_RESERVE = (
-  id: string
-): Query<unknown, unknown> => ({
-  delete: `http://backend/instances/default/private/reserves/${id}`,
-});
-
-
-////////////////////
-// INSTANCE ADMIN
-////////////////////
-
-export const API_CREATE_INSTANCE: Query<
-  MerchantBackend.Instances.InstanceConfigurationMessage,
-  unknown
-> = {
-  post: "http://backend/management/instances";,
-};
-
-export const API_GET_INSTANCE_BY_ID = (
-  id: string
-): Query<
-  unknown,
-  MerchantBackend.Instances.QueryInstancesResponse
-> => ({
-  get: `http://backend/management/instances/${id}`,
-});
-
-export const API_GET_INSTANCE_KYC_BY_ID = (
-  id: string
-): Query<
-  unknown,
-  MerchantBackend.Instances.AccountKycRedirects
-> => ({
-  get: `http://backend/management/instances/${id}/kyc`,
-});
-
-export const API_LIST_INSTANCES: Query<
-  unknown,
-  MerchantBackend.Instances.InstancesResponse
-> = {
-  get: "http://backend/management/instances";,
-};
-
-export const API_UPDATE_INSTANCE_BY_ID = (
-  id: string
-): Query<
-  MerchantBackend.Instances.InstanceReconfigurationMessage,
-  unknown
-> => ({
-  patch: `http://backend/management/instances/${id}`,
-});
-
-export const API_UPDATE_INSTANCE_AUTH_BY_ID = (
-  id: string
-): Query<
-  MerchantBackend.Instances.InstanceAuthConfigurationMessage,
-  unknown
-> => ({
-  post: `http://backend/management/instances/${id}/auth`,
-});
-
-export const API_DELETE_INSTANCE = (
-  id: string
-): Query<unknown, unknown> => ({
-  delete: `http://backend/management/instances/${id}`,
-});
-
-////////////////////
-// INSTANCE 
-////////////////////
-
-export const API_GET_CURRENT_INSTANCE: Query<
-  unknown,
-  MerchantBackend.Instances.QueryInstancesResponse
-> = ({
-  get: `http://backend/instances/default/private/`,
-});
-
-export const API_GET_CURRENT_INSTANCE_KYC: Query<
-  unknown,
-  MerchantBackend.Instances.AccountKycRedirects
-> =
-  ({
-    get: `http://backend/instances/default/private/kyc`,
-  });
-
-export const API_UPDATE_CURRENT_INSTANCE: Query<
-  MerchantBackend.Instances.InstanceReconfigurationMessage,
-  unknown
-> = {
-  patch: `http://backend/instances/default/private/`,
-};
-
-export const API_UPDATE_CURRENT_INSTANCE_AUTH: Query<
-  MerchantBackend.Instances.InstanceAuthConfigurationMessage,
-  unknown
-> = {
-  post: `http://backend/instances/default/private/auth`,
-};
-
-export const API_DELETE_CURRENT_INSTANCE: Query<
-  unknown,
-  unknown
-> = ({
-  delete: `http://backend/instances/default/private`,
-});
-
-
diff --git a/packages/merchant-backoffice-ui/tests/context/backend.test.tsx 
b/packages/merchant-backoffice-ui/tests/context/backend.test.tsx
deleted file mode 100644
index 671c19d0b..000000000
--- a/packages/merchant-backoffice-ui/tests/context/backend.test.tsx
+++ /dev/null
@@ -1,172 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2021-2023 Taler Systems S.A.
-
- GNU Taler is free software; you can redistribute it and/or modify it under the
- terms of the GNU General Public License as published by the Free Software
- Foundation; either version 3, or (at your option) any later version.
-
- GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along with
- GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
- */
-
-/**
- *
- * @author Sebastian Javier Marchano (sebasjm)
- */
-
-import { renderHook } from "@testing-library/preact-hooks";
-import { ComponentChildren, h, VNode } from "preact";
-import { act } from "preact/test-utils";
-import { BackendContextProvider } from "../../src/context/backend.js";
-import { InstanceContextProvider } from "../../src/context/instance.js";
-import { MerchantBackend } from "../../src/declaration.js";
-import {
-  useAdminAPI,
-  useInstanceAPI,
-  useManagementAPI,
-} from "../../src/hooks/instance.js";
-import {
-  API_CREATE_INSTANCE,
-  API_GET_CURRENT_INSTANCE,
-  API_UPDATE_CURRENT_INSTANCE_AUTH,
-  API_UPDATE_INSTANCE_AUTH_BY_ID,
-  assertJustExpectedRequestWereMade,
-  AxiosMockEnvironment,
-} from "../axiosMock.js";
-
-interface TestingContextProps {
-  children?: ComponentChildren;
-}
-
-function TestingContext({ children }: TestingContextProps): VNode {
-  return (
-    <BackendContextProvider defaultUrl="http://backend"; initialToken="token">
-      {children}
-    </BackendContextProvider>
-  );
-}
-function AdminTestingContext({ children }: TestingContextProps): VNode {
-  return (
-    <BackendContextProvider defaultUrl="http://backend"; initialToken="token">
-      <InstanceContextProvider
-        value={{
-          token: "token",
-          id: "default",
-          admin: true,
-          changeToken: () => null,
-        }}
-      >
-        {children}
-      </InstanceContextProvider>
-    </BackendContextProvider>
-  );
-}
-
-describe("backend context api ", () => {
-  it("should use new token after updating the instance token in the settings 
as user", async () => {
-    const env = new AxiosMockEnvironment();
-
-    const { result, waitForNextUpdate } = renderHook(
-      () => {
-        const instance = useInstanceAPI();
-        const management = useManagementAPI("default");
-        const admin = useAdminAPI();
-
-        return { instance, management, admin };
-      },
-      { wrapper: TestingContext }
-    );
-
-    if (!result.current) {
-      expect(result.current).toBeDefined();
-      return;
-    }
-
-    env.addRequestExpectation(API_UPDATE_INSTANCE_AUTH_BY_ID("default"), {
-      request: {
-        method: "token",
-        token: "another_token",
-      },
-      response: {
-        name: "instance_name",
-      } as MerchantBackend.Instances.QueryInstancesResponse,
-    });
-
-    await act(async () => {
-      await result.current?.management.setNewToken("another_token");
-    });
-
-    // await waitForNextUpdate({ timeout: 1 });
-
-    assertJustExpectedRequestWereMade(env);
-
-    env.addRequestExpectation(API_CREATE_INSTANCE, {
-      auth: "Bearer another_token",
-      request: {
-        id: "new_instance_id",
-      } as MerchantBackend.Instances.InstanceConfigurationMessage,
-    });
-
-    result.current.admin.createInstance({
-      id: "new_instance_id",
-    } as MerchantBackend.Instances.InstanceConfigurationMessage);
-
-    assertJustExpectedRequestWereMade(env);
-  });
-
-  it("should use new token after updating the instance token in the settings 
as admin", async () => {
-    const env = new AxiosMockEnvironment();
-
-    const { result, waitForNextUpdate } = renderHook(
-      () => {
-        const instance = useInstanceAPI();
-        const management = useManagementAPI("default");
-        const admin = useAdminAPI();
-
-        return { instance, management, admin };
-      },
-      { wrapper: AdminTestingContext }
-    );
-
-    if (!result.current) {
-      expect(result.current).toBeDefined();
-      return;
-    }
-
-    env.addRequestExpectation(API_UPDATE_CURRENT_INSTANCE_AUTH, {
-      request: {
-        method: "token",
-        token: "another_token",
-      },
-      response: {
-        name: "instance_name",
-      } as MerchantBackend.Instances.QueryInstancesResponse,
-    });
-
-    await act(async () => {
-      await result.current?.instance.setNewToken("another_token");
-    });
-
-    // await waitForNextUpdate({ timeout: 1 });
-
-    assertJustExpectedRequestWereMade(env);
-
-    env.addRequestExpectation(API_CREATE_INSTANCE, {
-      auth: "Bearer another_token",
-      request: {
-        id: "new_instance_id",
-      } as MerchantBackend.Instances.InstanceConfigurationMessage,
-    });
-
-    result.current.admin.createInstance({
-      id: "new_instance_id",
-    } as MerchantBackend.Instances.InstanceConfigurationMessage);
-
-    assertJustExpectedRequestWereMade(env);
-  });
-});
diff --git a/packages/merchant-backoffice-ui/tests/declarations.d.ts 
b/packages/merchant-backoffice-ui/tests/declarations.d.ts
deleted file mode 100644
index 677aa9f24..000000000
--- a/packages/merchant-backoffice-ui/tests/declarations.d.ts
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2021-2023 Taler Systems S.A.
-
- GNU Taler is free software; you can redistribute it and/or modify it under the
- terms of the GNU General Public License as published by the Free Software
- Foundation; either version 3, or (at your option) any later version.
-
- GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along with
- GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
- */
-
-/**
- *
- * @author Sebastian Javier Marchano (sebasjm)
- */
-
-declare global {
-  namespace jest {
-    interface Matchers<R> {
-      toBeWithinRange(a: number, b: number): R;
-    }
-  }
-}
diff --git a/packages/merchant-backoffice-ui/tests/header.test.tsx 
b/packages/merchant-backoffice-ui/tests/header.test.tsx
deleted file mode 100644
index 1cf2b7e6c..000000000
--- a/packages/merchant-backoffice-ui/tests/header.test.tsx
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2021-2023 Taler Systems S.A.
-
- GNU Taler is free software; you can redistribute it and/or modify it under the
- terms of the GNU General Public License as published by the Free Software
- Foundation; either version 3, or (at your option) any later version.
-
- GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along with
- GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
- */
-
-/**
- *
- * @author Sebastian Javier Marchano (sebasjm)
- */
-
-import { h } from "preact";
-import { ProductList } from "../src/components/product/ProductList.js";
-// See: https://github.com/preactjs/enzyme-adapter-preact-pure
-// import { shallow } from 'enzyme';
-import { render } from "@testing-library/preact";
-import * as backend from "../src/context/config.js";
-// import * as i18n from "../src/context/translation.js";
-
-// import * as jedLib from "jed";
-// const handler = new jedLib.Jed("en");
-
-describe("Initial Test of the Sidebar", () => {
-  beforeEach(() => {
-    jest
-      .spyOn(backend, "useConfigContext")
-      .mockImplementation(() => ({ version: "", currency: "" }));
-    // jest.spyOn(i18n, "useTranslationContext").mockImplementation(() => ({
-    //   changeLanguage: () => null,
-    //   handler,
-    //   lang: "en",
-    // }));
-  });
-  test("Product list renders a table", () => {
-    const context = render(
-      <ProductList
-        list={[
-          {
-            description: "description of the product",
-            image: "asdasda",
-            price: "USD:10",
-            quantity: 1,
-            taxes: [{ name: "VAT", tax: "EUR:1" }],
-            unit: "book",
-          },
-        ]}
-      />,
-    );
-
-    expect(context.findAllByText("description of the product")).toBeDefined();
-    // expect(context.find('table tr td img').map(img => 
img.prop('src'))).toEqual('');
-  });
-});
diff --git a/packages/merchant-backoffice-ui/tests/hooks/async.test.ts 
b/packages/merchant-backoffice-ui/tests/hooks/async.test.ts
deleted file mode 100644
index 18cfc5c55..000000000
--- a/packages/merchant-backoffice-ui/tests/hooks/async.test.ts
+++ /dev/null
@@ -1,158 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2021-2023 Taler Systems S.A.
-
- GNU Taler is free software; you can redistribute it and/or modify it under the
- terms of the GNU General Public License as published by the Free Software
- Foundation; either version 3, or (at your option) any later version.
-
- GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along with
- GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
- */
-
-import { renderHook } from "@testing-library/preact-hooks"
-import { useAsync } from "../../src/hooks/async.js"
-
-/**
-*
-* @author Sebastian Javier Marchano (sebasjm)
-*/
-test("async function is called", async () => {
-  jest.useFakeTimers()
-
-  const timeout = 500
-
-  const asyncFunction = jest.fn(() => new Promise((res) => {
-    setTimeout(() => {
-      res({ the_answer: 'yes' })
-    }, timeout);
-  }))
-
-  const { result, waitForNextUpdate } = renderHook(() => {
-    return useAsync(asyncFunction)
-  })
-
-  expect(result.current?.isLoading).toBeFalsy()
-
-  result.current?.request()
-  expect(asyncFunction).toBeCalled()
-  await waitForNextUpdate({ timeout: 1 })
-  expect(result.current?.isLoading).toBeTruthy()
-
-  jest.advanceTimersByTime(timeout + 1)
-  await waitForNextUpdate({ timeout: 1 })
-  expect(result.current?.isLoading).toBeFalsy()
-  expect(result.current?.data).toMatchObject({ the_answer: 'yes' })
-  expect(result.current?.error).toBeUndefined()
-  expect(result.current?.isSlow).toBeFalsy()
-})
-
-test("async function return error if rejected", async () => {
-  jest.useFakeTimers()
-
-  const timeout = 500
-
-  const asyncFunction = jest.fn(() => new Promise((_, rej) => {
-    setTimeout(() => {
-      rej({ the_error: 'yes' })
-    }, timeout);
-  }))
-
-  const { result, waitForNextUpdate } = renderHook(() => {
-    return useAsync(asyncFunction)
-  })
-
-  expect(result.current?.isLoading).toBeFalsy()
-
-  result.current?.request()
-  expect(asyncFunction).toBeCalled()
-  await waitForNextUpdate({ timeout: 1 })
-  expect(result.current?.isLoading).toBeTruthy()
-
-  jest.advanceTimersByTime(timeout + 1)
-  await waitForNextUpdate({ timeout: 1 })
-  expect(result.current?.isLoading).toBeFalsy()
-  expect(result.current?.error).toMatchObject({ the_error: 'yes' })
-  expect(result.current?.data).toBeUndefined()
-  expect(result.current?.isSlow).toBeFalsy()
-})
-
-test("async function is slow", async () => {
-  jest.useFakeTimers()
-
-  const timeout = 2200
-
-  const asyncFunction = jest.fn(() => new Promise((res) => {
-    setTimeout(() => {
-      res({ the_answer: 'yes' })
-    }, timeout);
-  }))
-
-  const { result, waitForNextUpdate } = renderHook(() => {
-    return useAsync(asyncFunction)
-  })
-
-  expect(result.current?.isLoading).toBeFalsy()
-
-  result.current?.request()
-  expect(asyncFunction).toBeCalled()
-  await waitForNextUpdate({ timeout: 1 })
-  expect(result.current?.isLoading).toBeTruthy()
-
-  jest.advanceTimersByTime(timeout / 2)
-  await waitForNextUpdate({ timeout: 1 })
-  expect(result.current?.isLoading).toBeTruthy()
-  expect(result.current?.isSlow).toBeTruthy()
-  expect(result.current?.data).toBeUndefined()
-  expect(result.current?.error).toBeUndefined()
-
-  jest.advanceTimersByTime(timeout / 2)
-  await waitForNextUpdate({ timeout: 1 })
-  expect(result.current?.isLoading).toBeFalsy()
-  expect(result.current?.data).toMatchObject({ the_answer: 'yes' })
-  expect(result.current?.error).toBeUndefined()
-  expect(result.current?.isSlow).toBeFalsy()
-
-})
-
-test("async function is cancellable", async () => {
-  jest.useFakeTimers()
-
-  const timeout = 2200
-
-  const asyncFunction = jest.fn(() => new Promise((res) => {
-    setTimeout(() => {
-      res({ the_answer: 'yes' })
-    }, timeout);
-  }))
-
-  const { result, waitForNextUpdate } = renderHook(() => {
-    return useAsync(asyncFunction)
-  })
-
-  expect(result.current?.isLoading).toBeFalsy()
-
-  result.current?.request()
-  expect(asyncFunction).toBeCalled()
-  await waitForNextUpdate({ timeout: 1 })
-  expect(result.current?.isLoading).toBeTruthy()
-
-  jest.advanceTimersByTime(timeout / 2)
-  await waitForNextUpdate({ timeout: 1 })
-  expect(result.current?.isLoading).toBeTruthy()
-  expect(result.current?.isSlow).toBeTruthy()
-  expect(result.current?.data).toBeUndefined()
-  expect(result.current?.error).toBeUndefined()
-
-  result.current?.cancel()
-  await waitForNextUpdate({ timeout: 1 })
-  expect(result.current?.isLoading).toBeFalsy()
-  expect(result.current?.data).toBeUndefined()
-  expect(result.current?.error).toBeUndefined()
-  expect(result.current?.isSlow).toBeFalsy()
-
-})
diff --git a/packages/merchant-backoffice-ui/tests/hooks/listener.test.ts 
b/packages/merchant-backoffice-ui/tests/hooks/listener.test.ts
deleted file mode 100644
index 8afd5f8d1..000000000
--- a/packages/merchant-backoffice-ui/tests/hooks/listener.test.ts
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2021-2023 Taler Systems S.A.
-
- GNU Taler is free software; you can redistribute it and/or modify it under the
- terms of the GNU General Public License as published by the Free Software
- Foundation; either version 3, or (at your option) any later version.
-
- GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along with
- GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
- */
-
-/**
-*
-* @author Sebastian Javier Marchano (sebasjm)
-*/
-
-import { renderHook, act } from '@testing-library/preact-hooks';
-import { useListener } from "../../src/hooks/listener.js";
-
-// jest.useFakeTimers()
-
-test('listener', async () => {
-
-
-  function createSomeString() {
-    return "hello"
-  }
-  async function addWorldToTheEnd(resultFromComponentB: string) {
-    return `${resultFromComponentB} world`
-  }
-  const expectedResult = "hello world"
-
-  const { result } = renderHook(() => useListener(addWorldToTheEnd))
-
-  expect(result.current).toBeDefined()
-  if (!result.current) {
-    return;
-  }
-
-  {
-    const [activator, subscriber] = result.current
-    expect(activator).toBeUndefined()
-
-    act(() => {
-      subscriber(createSomeString)
-    })
-
-  }
-
-  const [activator] = result.current
-  expect(activator).toBeDefined()
-  if (!activator) return;
-
-  const response = await activator()
-  expect(response).toBe(expectedResult)
-
-});
diff --git a/packages/merchant-backoffice-ui/tests/hooks/notification.test.ts 
b/packages/merchant-backoffice-ui/tests/hooks/notification.test.ts
deleted file mode 100644
index 801aa0e2e..000000000
--- a/packages/merchant-backoffice-ui/tests/hooks/notification.test.ts
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2021-2023 Taler Systems S.A.
-
- GNU Taler is free software; you can redistribute it and/or modify it under the
- terms of the GNU General Public License as published by the Free Software
- Foundation; either version 3, or (at your option) any later version.
-
- GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along with
- GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
- */
-
- /**
- *
- * @author Sebastian Javier Marchano (sebasjm)
- */
-
-import { renderHook, act} from '@testing-library/preact-hooks';
-import { useNotifications } from "../../src/hooks/notifications.js";
-
-jest.useFakeTimers()
-
-test('notification should disappear after timeout', () => {
-  jest.spyOn(global, 'setTimeout');
-
-  const timeout = 1000
-  const { result, rerender } = renderHook(() => useNotifications(undefined, 
timeout));
-
-  expect(result.current?.notifications.length).toBe(0);
-
-  act(() => {
-    result.current?.pushNotification({
-      message: 'some_id',
-      type: 'INFO'
-    });
-  });
-  expect(result.current?.notifications.length).toBe(1);
-
-  jest.advanceTimersByTime(timeout/2);
-  rerender()
-  expect(result.current?.notifications.length).toBe(1);
-
-  jest.advanceTimersByTime(timeout);
-  rerender()
-  expect(result.current?.notifications.length).toBe(0);
-
-});
diff --git a/packages/merchant-backoffice-ui/tests/hooks/swr/index.tsx 
b/packages/merchant-backoffice-ui/tests/hooks/swr/index.tsx
deleted file mode 100644
index 2608523e6..000000000
--- a/packages/merchant-backoffice-ui/tests/hooks/swr/index.tsx
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2021-2023 Taler Systems S.A.
-
- GNU Taler is free software; you can redistribute it and/or modify it under the
- terms of the GNU General Public License as published by the Free Software
- Foundation; either version 3, or (at your option) any later version.
-
- GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along with
- GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
- */
-
-/**
- *
- * @author Sebastian Javier Marchano (sebasjm)
- */
-
-import { ComponentChildren, h, VNode } from "preact";
-import { SWRConfig } from "swr";
-import { BackendContextProvider } from "../../../src/context/backend.js";
-import { InstanceContextProvider } from "../../../src/context/instance.js";
-
-interface TestingContextProps {
-  children?: ComponentChildren;
-}
-export function TestingContext({ children }: TestingContextProps): VNode {
-  const SC: any = SWRConfig
-  return (
-    <BackendContextProvider defaultUrl="http://backend"; initialToken="token">
-      <InstanceContextProvider
-        value={{
-          token: "token",
-          id: "default",
-          admin: true,
-          changeToken: () => null,
-        }}
-      >
-        <SC value={{ provider: () => new Map() }}>{children}</SC>
-      </InstanceContextProvider>
-    </BackendContextProvider>
-  );
-}
diff --git a/packages/merchant-backoffice-ui/tests/hooks/swr/instance.test.ts 
b/packages/merchant-backoffice-ui/tests/hooks/swr/instance.test.ts
deleted file mode 100644
index 36a2f7241..000000000
--- a/packages/merchant-backoffice-ui/tests/hooks/swr/instance.test.ts
+++ /dev/null
@@ -1,636 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2021-2023 Taler Systems S.A.
-
- GNU Taler is free software; you can redistribute it and/or modify it under the
- terms of the GNU General Public License as published by the Free Software
- Foundation; either version 3, or (at your option) any later version.
-
- GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along with
- GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
- */
-
-/**
- *
- * @author Sebastian Javier Marchano (sebasjm)
- */
-
-import { renderHook } from "@testing-library/preact-hooks";
-import { act } from "preact/test-utils";
-import { MerchantBackend } from "../../../src/declaration.js";
-import { useAdminAPI, useBackendInstances, useInstanceAPI, useInstanceDetails, 
useManagementAPI } from "../../../src/hooks/instance.js";
-import {
-  API_CREATE_INSTANCE,
-  API_DELETE_INSTANCE,
-  API_GET_CURRENT_INSTANCE,
-  API_LIST_INSTANCES,
-  API_UPDATE_CURRENT_INSTANCE,
-  API_UPDATE_CURRENT_INSTANCE_AUTH,
-  API_UPDATE_INSTANCE_AUTH_BY_ID,
-  API_UPDATE_INSTANCE_BY_ID,
-  assertJustExpectedRequestWereMade,
-  AxiosMockEnvironment
-} from "../../axiosMock.js";
-import { TestingContext } from "./index.js";
-
-describe("instance api interaction with details", () => {
-
-  it("should evict cache when updating an instance", async () => {
-
-    const env = new AxiosMockEnvironment();
-
-    env.addRequestExpectation(API_GET_CURRENT_INSTANCE, {
-      response: {
-        name: 'instance_name'
-      } as MerchantBackend.Instances.QueryInstancesResponse,
-    });
-
-    const { result, waitForNextUpdate } = renderHook(
-      () => {
-        const api = useInstanceAPI();
-        const query = useInstanceDetails();
-
-        return { query, api };
-      },
-      { wrapper: TestingContext }
-    );
-
-    expect(result.current).toBeDefined();
-    if (!result.current) {
-      return;
-    }
-    expect(result.current.query.loading).toBeTruthy();
-
-    await waitForNextUpdate({ timeout: 1 });
-
-    assertJustExpectedRequestWereMade(env);
-
-    expect(result.current.query.loading).toBeFalsy();
-
-    expect(result.current?.query.ok).toBeTruthy();
-    if (!result.current?.query.ok) return;
-
-    expect(result.current.query.data).toEqual({
-      name: 'instance_name'
-    });
-
-    env.addRequestExpectation(API_UPDATE_CURRENT_INSTANCE, {
-      request: {
-        name: 'other_name'
-      } as MerchantBackend.Instances.InstanceReconfigurationMessage,
-    });
-
-    act(async () => {
-      await result.current?.api.updateInstance({
-        name: 'other_name'
-      } as MerchantBackend.Instances.InstanceReconfigurationMessage);
-    });
-
-    assertJustExpectedRequestWereMade(env);
-
-    env.addRequestExpectation(API_GET_CURRENT_INSTANCE, {
-      response: {
-        name: 'other_name'
-      } as MerchantBackend.Instances.QueryInstancesResponse,
-    });
-
-    expect(result.current.query.loading).toBeFalsy();
-
-    await waitForNextUpdate({ timeout: 1 });
-
-    assertJustExpectedRequestWereMade(env);
-
-    expect(result.current.query.loading).toBeFalsy();
-    expect(result.current.query.ok).toBeTruthy();
-
-    expect(result.current.query.data).toEqual({
-      name: 'other_name'
-    });
-  });
-
-  it("should evict cache when setting the instance's token", async () => {
-    const env = new AxiosMockEnvironment();
-
-    env.addRequestExpectation(API_GET_CURRENT_INSTANCE, {
-      response: {
-        name: 'instance_name',
-        auth: {
-          method: 'token',
-          token: 'not-secret',
-        }
-      } as MerchantBackend.Instances.QueryInstancesResponse,
-    });
-
-    const { result, waitForNextUpdate } = renderHook(
-      () => {
-        const api = useInstanceAPI();
-        const query = useInstanceDetails();
-
-        return { query, api };
-      },
-      { wrapper: TestingContext }
-    );
-
-    expect(result.current).toBeDefined();
-    if (!result.current) {
-      return;
-    }
-    expect(result.current.query.loading).toBeTruthy();
-
-    await waitForNextUpdate({ timeout: 1 });
-
-    assertJustExpectedRequestWereMade(env);
-
-    expect(result.current.query.loading).toBeFalsy();
-
-    expect(result.current?.query.ok).toBeTruthy();
-    if (!result.current?.query.ok) return;
-
-    expect(result.current.query.data).toEqual({
-      name: 'instance_name',
-      auth: {
-        method: 'token',
-        token: 'not-secret',
-      }
-    });
-
-    env.addRequestExpectation(API_UPDATE_CURRENT_INSTANCE_AUTH, {
-      request: {
-        method: 'token',
-        token: 'secret'
-      } as MerchantBackend.Instances.InstanceAuthConfigurationMessage,
-    });
-
-    act(async () => {
-      await result.current?.api.setNewToken('secret');
-    });
-
-    assertJustExpectedRequestWereMade(env);
-
-    env.addRequestExpectation(API_GET_CURRENT_INSTANCE, {
-      response: {
-        name: 'instance_name',
-        auth: {
-          method: 'token',
-          token: 'secret',
-        }
-      } as MerchantBackend.Instances.QueryInstancesResponse,
-    });
-
-    expect(result.current.query.loading).toBeFalsy();
-
-    await waitForNextUpdate({ timeout: 1 });
-
-    assertJustExpectedRequestWereMade(env);
-
-    expect(result.current.query.loading).toBeFalsy();
-    expect(result.current.query.ok).toBeTruthy();
-
-    expect(result.current.query.data).toEqual({
-      name: 'instance_name',
-      auth: {
-        method: 'token',
-        token: 'secret',
-      }
-    });
-  });
-
-  it("should evict cache when clearing the instance's token", async () => {
-    const env = new AxiosMockEnvironment();
-
-    env.addRequestExpectation(API_GET_CURRENT_INSTANCE, {
-      response: {
-        name: 'instance_name',
-        auth: {
-          method: 'token',
-          token: 'not-secret',
-        }
-      } as MerchantBackend.Instances.QueryInstancesResponse,
-    });
-
-    const { result, waitForNextUpdate } = renderHook(
-      () => {
-        const api = useInstanceAPI();
-        const query = useInstanceDetails();
-
-        return { query, api };
-      },
-      { wrapper: TestingContext }
-    );
-
-    expect(result.current).toBeDefined();
-    if (!result.current) {
-      return;
-    }
-    expect(result.current.query.loading).toBeTruthy();
-
-    await waitForNextUpdate({ timeout: 1 });
-
-    assertJustExpectedRequestWereMade(env);
-
-    expect(result.current.query.loading).toBeFalsy();
-
-    expect(result.current?.query.ok).toBeTruthy();
-    if (!result.current?.query.ok) return;
-
-    expect(result.current.query.data).toEqual({
-      name: 'instance_name',
-      auth: {
-        method: 'token',
-        token: 'not-secret',
-      }
-    });
-
-    env.addRequestExpectation(API_UPDATE_CURRENT_INSTANCE_AUTH, {
-      request: {
-        method: 'external',
-      } as MerchantBackend.Instances.InstanceAuthConfigurationMessage,
-    });
-
-    act(async () => {
-      await result.current?.api.clearToken();
-    });
-
-    assertJustExpectedRequestWereMade(env);
-
-    env.addRequestExpectation(API_GET_CURRENT_INSTANCE, {
-      response: {
-        name: 'instance_name',
-        auth: {
-          method: 'external',
-        }
-      } as MerchantBackend.Instances.QueryInstancesResponse,
-    });
-
-    expect(result.current.query.loading).toBeFalsy();
-
-    await waitForNextUpdate({ timeout: 1 });
-
-    assertJustExpectedRequestWereMade(env);
-
-    expect(result.current.query.loading).toBeFalsy();
-    expect(result.current.query.ok).toBeTruthy();
-
-    expect(result.current.query.data).toEqual({
-      name: 'instance_name',
-      auth: {
-        method: 'external',
-      }
-    });
-  });
-});
-
-describe("instance admin api interaction with listing", () => {
-
-  it("should evict cache when creating a new instance", async () => {
-    const env = new AxiosMockEnvironment();
-
-    env.addRequestExpectation(API_LIST_INSTANCES, {
-      response: {
-        instances: [{
-          name: 'instance_name'
-        } as MerchantBackend.Instances.Instance]
-      },
-    });
-
-    const { result, waitForNextUpdate } = renderHook(
-      () => {
-        const api = useAdminAPI();
-        const query = useBackendInstances();
-
-        return { query, api };
-      },
-      { wrapper: TestingContext }
-    );
-
-    expect(result.current).toBeDefined();
-    if (!result.current) {
-      return;
-    }
-    expect(result.current.query.loading).toBeTruthy();
-
-    await waitForNextUpdate({ timeout: 1 });
-
-    assertJustExpectedRequestWereMade(env);
-
-    expect(result.current.query.loading).toBeFalsy();
-
-    expect(result.current?.query.ok).toBeTruthy();
-    if (!result.current?.query.ok) return;
-
-    expect(result.current.query.data).toEqual({
-      instances: [{
-        name: 'instance_name'
-      }]
-    });
-
-    env.addRequestExpectation(API_CREATE_INSTANCE, {
-      request: {
-        name: 'other_name'
-      } as MerchantBackend.Instances.InstanceConfigurationMessage,
-    });
-
-    act(async () => {
-      await result.current?.api.createInstance({
-        name: 'other_name'
-      } as MerchantBackend.Instances.InstanceConfigurationMessage);
-    });
-
-    assertJustExpectedRequestWereMade(env);
-
-    env.addRequestExpectation(API_LIST_INSTANCES, {
-      response: {
-        instances: [{
-          name: 'instance_name'
-        } as MerchantBackend.Instances.Instance,
-        {
-          name: 'other_name'
-        } as MerchantBackend.Instances.Instance]
-      },
-    });
-
-    expect(result.current.query.loading).toBeFalsy();
-
-    await waitForNextUpdate({ timeout: 1 });
-
-    assertJustExpectedRequestWereMade(env);
-
-    expect(result.current.query.loading).toBeFalsy();
-    expect(result.current.query.ok).toBeTruthy();
-
-    expect(result.current.query.data).toEqual({
-      instances: [{
-        name: 'instance_name'
-      }, {
-        name: 'other_name'
-      }]
-    });
-  });
-
-  it("should evict cache when deleting an instance", async () => {
-    const env = new AxiosMockEnvironment();
-
-    env.addRequestExpectation(API_LIST_INSTANCES, {
-      response: {
-        instances: [{
-          id: 'default',
-          name: 'instance_name'
-        } as MerchantBackend.Instances.Instance,
-        {
-          id: 'the_id',
-          name: 'second_instance'
-        } as MerchantBackend.Instances.Instance]
-      },
-    });
-
-    const { result, waitForNextUpdate } = renderHook(
-      () => {
-        const api = useAdminAPI();
-        const query = useBackendInstances();
-
-        return { query, api };
-      },
-      { wrapper: TestingContext }
-    );
-
-    expect(result.current).toBeDefined();
-    if (!result.current) {
-      return;
-    }
-    expect(result.current.query.loading).toBeTruthy();
-
-    await waitForNextUpdate({ timeout: 1 });
-
-    assertJustExpectedRequestWereMade(env);
-
-    expect(result.current.query.loading).toBeFalsy();
-
-    expect(result.current?.query.ok).toBeTruthy();
-    if (!result.current?.query.ok) return;
-
-    expect(result.current.query.data).toEqual({
-      instances: [{
-        id: 'default',
-        name: 'instance_name'
-      }, {
-        id: 'the_id',
-        name: 'second_instance'
-      }]
-    });
-
-    env.addRequestExpectation(API_DELETE_INSTANCE('the_id'), {});
-
-    act(async () => {
-      await result.current?.api.deleteInstance('the_id');
-    });
-
-    assertJustExpectedRequestWereMade(env);
-
-    env.addRequestExpectation(API_LIST_INSTANCES, {
-      response: {
-        instances: [{
-          id: 'default',
-          name: 'instance_name'
-        } as MerchantBackend.Instances.Instance]
-      },
-    });
-
-    expect(result.current.query.loading).toBeFalsy();
-
-    await waitForNextUpdate({ timeout: 1 });
-
-    assertJustExpectedRequestWereMade(env);
-
-    expect(result.current.query.loading).toBeFalsy();
-    expect(result.current.query.ok).toBeTruthy();
-
-    expect(result.current.query.data).toEqual({
-      instances: [{
-        id: 'default',
-        name: 'instance_name'
-      }]
-    });
-  });
-  it("should evict cache when deleting (purge) an instance", async () => {
-    const env = new AxiosMockEnvironment();
-
-    env.addRequestExpectation(API_LIST_INSTANCES, {
-      response: {
-        instances: [{
-          id: 'default',
-          name: 'instance_name'
-        } as MerchantBackend.Instances.Instance,
-        {
-          id: 'the_id',
-          name: 'second_instance'
-        } as MerchantBackend.Instances.Instance]
-      },
-    });
-
-    const { result, waitForNextUpdate } = renderHook(
-      () => {
-        const api = useAdminAPI();
-        const query = useBackendInstances();
-
-        return { query, api };
-      },
-      { wrapper: TestingContext }
-    );
-
-    expect(result.current).toBeDefined();
-    if (!result.current) {
-      return;
-    }
-    expect(result.current.query.loading).toBeTruthy();
-
-    await waitForNextUpdate({ timeout: 1 });
-
-    assertJustExpectedRequestWereMade(env);
-
-    expect(result.current.query.loading).toBeFalsy();
-
-    expect(result.current?.query.ok).toBeTruthy();
-    if (!result.current?.query.ok) return;
-
-    expect(result.current.query.data).toEqual({
-      instances: [{
-        id: 'default',
-        name: 'instance_name'
-      }, {
-        id: 'the_id',
-        name: 'second_instance'
-      }]
-    });
-
-    env.addRequestExpectation(API_DELETE_INSTANCE('the_id'), {
-      qparam: {
-        purge: 'YES'
-      }
-    });
-
-    act(async () => {
-      await result.current?.api.purgeInstance('the_id');
-    });
-
-    assertJustExpectedRequestWereMade(env);
-
-    env.addRequestExpectation(API_LIST_INSTANCES, {
-      response: {
-        instances: [{
-          id: 'default',
-          name: 'instance_name'
-        } as MerchantBackend.Instances.Instance]
-      },
-    });
-
-    expect(result.current.query.loading).toBeFalsy();
-
-    await waitForNextUpdate({ timeout: 1 });
-
-    assertJustExpectedRequestWereMade(env);
-
-    expect(result.current.query.loading).toBeFalsy();
-    expect(result.current.query.ok).toBeTruthy();
-
-    expect(result.current.query.data).toEqual({
-      instances: [{
-        id: 'default',
-        name: 'instance_name'
-      }]
-    });
-  });
-});
-
-describe("instance management api interaction with listing", () => {
-
-  it("should evict cache when updating an instance", async () => {
-    const env = new AxiosMockEnvironment();
-
-    env.addRequestExpectation(API_LIST_INSTANCES, {
-      response: {
-        instances: [{
-          id: 'managed',
-          name: 'instance_name'
-        } as MerchantBackend.Instances.Instance]
-      },
-    });
-
-    const { result, waitForNextUpdate } = renderHook(
-      () => {
-        const api = useManagementAPI('managed');
-        const query = useBackendInstances();
-
-        return { query, api };
-      },
-      { wrapper: TestingContext }
-    );
-
-    expect(result.current).toBeDefined();
-    if (!result.current) {
-      return;
-    }
-    expect(result.current.query.loading).toBeTruthy();
-
-    await waitForNextUpdate({ timeout: 1 });
-
-    assertJustExpectedRequestWereMade(env);
-
-    expect(result.current.query.loading).toBeFalsy();
-
-    expect(result.current?.query.ok).toBeTruthy();
-    if (!result.current?.query.ok) return;
-
-    expect(result.current.query.data).toEqual({
-      instances: [{
-        id: 'managed',
-        name: 'instance_name'
-      }]
-    });
-
-    env.addRequestExpectation(API_UPDATE_INSTANCE_BY_ID('managed'), {
-      request: {
-        name: 'other_name'
-      } as MerchantBackend.Instances.InstanceReconfigurationMessage,
-    });
-
-    act(async () => {
-      await result.current?.api.updateInstance({
-        name: 'other_name'
-      } as MerchantBackend.Instances.InstanceConfigurationMessage);
-    });
-
-    assertJustExpectedRequestWereMade(env);
-
-    env.addRequestExpectation(API_LIST_INSTANCES, {
-      response: {
-        instances: [
-          {
-            id: 'managed',
-            name: 'other_name'
-          } as MerchantBackend.Instances.Instance]
-      },
-    });
-
-    expect(result.current.query.loading).toBeFalsy();
-
-    await waitForNextUpdate({ timeout: 1 });
-
-    assertJustExpectedRequestWereMade(env);
-
-    expect(result.current.query.loading).toBeFalsy();
-    expect(result.current.query.ok).toBeTruthy();
-
-    expect(result.current.query.data).toEqual({
-      instances: [{
-        id: 'managed',
-        name: 'other_name'
-      }]
-    });
-  });
-
-});
-
diff --git a/packages/merchant-backoffice-ui/tests/hooks/swr/order.test.ts 
b/packages/merchant-backoffice-ui/tests/hooks/swr/order.test.ts
deleted file mode 100644
index dc6104e43..000000000
--- a/packages/merchant-backoffice-ui/tests/hooks/swr/order.test.ts
+++ /dev/null
@@ -1,567 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2021-2023 Taler Systems S.A.
-
- GNU Taler is free software; you can redistribute it and/or modify it under the
- terms of the GNU General Public License as published by the Free Software
- Foundation; either version 3, or (at your option) any later version.
-
- GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along with
- GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
- */
-
-/**
- *
- * @author Sebastian Javier Marchano (sebasjm)
- */
-
-import { renderHook } from "@testing-library/preact-hooks";
-import { act } from "preact/test-utils";
-import { TestingContext } from ".";
-import { MerchantBackend } from "../../../src/declaration.js";
-import { useInstanceOrders, useOrderAPI, useOrderDetails } from 
"../../../src/hooks/order.js";
-import {
-  API_CREATE_ORDER,
-  API_DELETE_ORDER,
-  API_FORGET_ORDER_BY_ID,
-  API_GET_ORDER_BY_ID,
-  API_LIST_ORDERS, API_REFUND_ORDER_BY_ID, assertJustExpectedRequestWereMade, 
assertNextRequest, assertNoMoreRequestWereMade, AxiosMockEnvironment
-} from "../../axiosMock.js";
-
-describe("order api interaction with listing", () => {
-
-  it("should evict cache when creating an order", async () => {
-    const env = new AxiosMockEnvironment();
-
-    env.addRequestExpectation(API_LIST_ORDERS, {
-      qparam: { delta: 0, paid: "yes" },
-      response: {
-        orders: [{ order_id: "1" } as 
MerchantBackend.Orders.OrderHistoryEntry],
-      },
-    });
-
-    env.addRequestExpectation(API_LIST_ORDERS, {
-      qparam: { delta: -20, paid: "yes" },
-      response: {
-        orders: [{ order_id: "2" } as 
MerchantBackend.Orders.OrderHistoryEntry],
-      },
-    });
-
-
-    const { result, waitForNextUpdate } = renderHook(() => {
-      const newDate = (d: Date) => {
-        console.log("new date", d);
-      };
-      const query = useInstanceOrders({ paid: "yes" }, newDate);
-      const api = useOrderAPI();
-
-      return { query, api };
-    }, { wrapper: TestingContext });
-
-    expect(result.current).toBeDefined();
-    if (!result.current) {
-      return;
-    }
-
-    expect(result.current.query.loading).toBeTruthy();
-    await waitForNextUpdate({ timeout: 1 });
-    assertJustExpectedRequestWereMade(env);
-
-    expect(result.current.query.loading).toBeFalsy();
-    expect(result.current?.query.ok).toBeTruthy();
-    if (!result.current?.query.ok) return;
-
-    expect(result.current.query.data).toEqual({
-      orders: [{ order_id: "1" }, { order_id: "2" }],
-    });
-
-    env.addRequestExpectation(API_CREATE_ORDER, {
-      request: {
-        order: { amount: "ARS:12", summary: "pay me" },
-      },
-      response: { order_id: "3" },
-    });
-
-    env.addRequestExpectation(API_LIST_ORDERS, {
-      qparam: { delta: 0, paid: "yes" },
-      response: {
-        orders: [{ order_id: "1" } as any],
-      },
-    });
-
-    env.addRequestExpectation(API_LIST_ORDERS, {
-      qparam: { delta: -20, paid: "yes" },
-      response: {
-        orders: [{ order_id: "2" } as any, { order_id: "3" } as any],
-      },
-    });
-
-    act(async () => {
-      await result.current?.api.createOrder({
-        order: { amount: "ARS:12", summary: "pay me" },
-      } as any);
-    });
-
-    await waitForNextUpdate({ timeout: 1 });
-    assertJustExpectedRequestWereMade(env);
-
-    expect(result.current.query.loading).toBeFalsy();
-    expect(result.current?.query.ok).toBeTruthy();
-    if (!result.current?.query.ok) return;
-
-    expect(result.current.query.data).toEqual({
-      orders: [{ order_id: "1" }, { order_id: "2" }, { order_id: "3" }],
-    });
-  });
-  it("should evict cache when doing a refund", async () => {
-    const env = new AxiosMockEnvironment();
-
-    env.addRequestExpectation(API_LIST_ORDERS, {
-      qparam: { delta: 0, paid: "yes" },
-      response: {
-        orders: [{ order_id: "1", amount: 'EUR:12', refundable: true } as 
MerchantBackend.Orders.OrderHistoryEntry],
-      },
-    });
-
-    env.addRequestExpectation(API_LIST_ORDERS, {
-      qparam: { delta: -20, paid: "yes" },
-      response: { orders: [], },
-    });
-
-
-    const { result, waitForNextUpdate } = renderHook(() => {
-      const newDate = (d: Date) => {
-        console.log("new date", d);
-      };
-      const query = useInstanceOrders({ paid: "yes" }, newDate);
-      const api = useOrderAPI();
-
-      return { query, api };
-    }, { wrapper: TestingContext });
-
-    expect(result.current).toBeDefined();
-    if (!result.current) {
-      return;
-    }
-
-    expect(result.current.query.loading).toBeTruthy();
-    await waitForNextUpdate({ timeout: 1 });
-    assertJustExpectedRequestWereMade(env);
-
-
-    expect(result.current.query.loading).toBeFalsy();
-    expect(result.current?.query.ok).toBeTruthy();
-    if (!result.current?.query.ok) return;
-
-    expect(result.current.query.data).toEqual({
-      orders: [{
-        order_id: "1",
-        amount: 'EUR:12',
-        refundable: true,
-      }],
-    });
-
-    env.addRequestExpectation(API_REFUND_ORDER_BY_ID('1'), {
-      request: {
-        reason: 'double pay',
-        refund: 'EUR:1'
-      },
-    });
-
-    env.addRequestExpectation(API_LIST_ORDERS, {
-      qparam: { delta: 0, paid: "yes" },
-      response: {
-        orders: [{ order_id: "1", amount: 'EUR:12', refundable: false } as 
any],
-      },
-    });
-
-    env.addRequestExpectation(API_LIST_ORDERS, {
-      qparam: { delta: -20, paid: "yes" },
-      response: { orders: [], },
-    });
-
-    act(async () => {
-      await result.current?.api.refundOrder('1', {
-        reason: 'double pay',
-        refund: 'EUR:1'
-      });
-    });
-
-    await waitForNextUpdate({ timeout: 1 });
-    assertJustExpectedRequestWereMade(env);
-
-    expect(result.current.query.loading).toBeFalsy();
-    expect(result.current?.query.ok).toBeTruthy();
-    if (!result.current?.query.ok) return;
-
-    expect(result.current.query.data).toEqual({
-      orders: [{
-        order_id: "1",
-        amount: 'EUR:12',
-        refundable: false,
-      }],
-    });
-  });
-  it("should evict cache when deleting an order", async () => {
-    const env = new AxiosMockEnvironment();
-
-    env.addRequestExpectation(API_LIST_ORDERS, {
-      qparam: { delta: 0, paid: "yes" },
-      response: {
-        orders: [{ order_id: "1" } as 
MerchantBackend.Orders.OrderHistoryEntry],
-      },
-    });
-
-    env.addRequestExpectation(API_LIST_ORDERS, {
-      qparam: { delta: -20, paid: "yes" },
-      response: {
-        orders: [{ order_id: "2" } as 
MerchantBackend.Orders.OrderHistoryEntry],
-      },
-    });
-
-
-    const { result, waitForNextUpdate } = renderHook(() => {
-      const newDate = (d: Date) => {
-        console.log("new date", d);
-      };
-      const query = useInstanceOrders({ paid: "yes" }, newDate);
-      const api = useOrderAPI();
-
-      return { query, api };
-    }, { wrapper: TestingContext });
-
-    expect(result.current).toBeDefined();
-    if (!result.current) {
-      return;
-    }
-
-    expect(result.current.query.loading).toBeTruthy();
-    await waitForNextUpdate({ timeout: 1 });
-    assertJustExpectedRequestWereMade(env);
-
-    expect(result.current.query.loading).toBeFalsy();
-    expect(result.current?.query.ok).toBeTruthy();
-    if (!result.current?.query.ok) return;
-
-    expect(result.current.query.data).toEqual({
-      orders: [{ order_id: "1" }, { order_id: "2" }],
-    });
-
-    env.addRequestExpectation(API_DELETE_ORDER('1'), {});
-
-    env.addRequestExpectation(API_LIST_ORDERS, {
-      qparam: { delta: 0, paid: "yes" },
-      response: {
-        orders: [],
-      },
-    });
-
-    env.addRequestExpectation(API_LIST_ORDERS, {
-      qparam: { delta: -20, paid: "yes" },
-      response: {
-        orders: [{ order_id: "2" } as any],
-      },
-    });
-
-    act(async () => {
-      await result.current?.api.deleteOrder('1');
-    });
-
-    await waitForNextUpdate({ timeout: 1 });
-    assertJustExpectedRequestWereMade(env);
-
-    expect(result.current.query.loading).toBeFalsy();
-    expect(result.current?.query.ok).toBeTruthy();
-    if (!result.current?.query.ok) return;
-
-    expect(result.current.query.data).toEqual({
-      orders: [{ order_id: "2" }],
-    });
-  });
-
-});
-
-describe("order api interaction with details", () => {
-
-  it("should evict cache when doing a refund", async () => {
-    const env = new AxiosMockEnvironment();
-
-    env.addRequestExpectation(API_GET_ORDER_BY_ID('1'), {
-      // qparam: { delta: 0, paid: "yes" },
-      response: {
-        summary: 'description',
-        refund_amount: 'EUR:0',
-      } as unknown as MerchantBackend.Orders.CheckPaymentPaidResponse,
-    });
-
-    const { result, waitForNextUpdate } = renderHook(() => {
-      const query = useOrderDetails('1')
-      const api = useOrderAPI();
-
-      return { query, api };
-    }, { wrapper: TestingContext });
-
-    expect(result.current).toBeDefined();
-    if (!result.current) {
-      return;
-    }
-
-    expect(result.current.query.loading).toBeTruthy();
-    await waitForNextUpdate({ timeout: 1 });
-    assertJustExpectedRequestWereMade(env);
-
-    expect(result.current.query.loading).toBeFalsy();
-    expect(result.current?.query.ok).toBeTruthy();
-    if (!result.current?.query.ok) return;
-
-    expect(result.current.query.data).toEqual({
-      summary: 'description',
-      refund_amount: 'EUR:0',
-    });
-
-    env.addRequestExpectation(API_REFUND_ORDER_BY_ID('1'), {
-      request: {
-        reason: 'double pay',
-        refund: 'EUR:1'
-      },
-    });
-
-    env.addRequestExpectation(API_GET_ORDER_BY_ID('1'), {
-      response: {
-        summary: 'description',
-        refund_amount: 'EUR:1',
-      } as unknown as MerchantBackend.Orders.CheckPaymentPaidResponse,
-    });
-
-    act(async () => {
-      await result.current?.api.refundOrder('1', {
-        reason: 'double pay',
-        refund: 'EUR:1'
-      });
-    });
-
-    await waitForNextUpdate({ timeout: 1 });
-    assertJustExpectedRequestWereMade(env);
-
-    expect(result.current.query.loading).toBeFalsy();
-    expect(result.current?.query.ok).toBeTruthy();
-    if (!result.current?.query.ok) return;
-
-    expect(result.current.query.data).toEqual({
-      summary: 'description',
-      refund_amount: 'EUR:1',
-    });
-  })
-  it("should evict cache when doing a forget", async () => {
-    const env = new AxiosMockEnvironment();
-
-    env.addRequestExpectation(API_GET_ORDER_BY_ID('1'), {
-      // qparam: { delta: 0, paid: "yes" },
-      response: {
-        summary: 'description',
-        refund_amount: 'EUR:0',
-      } as unknown as MerchantBackend.Orders.CheckPaymentPaidResponse,
-    });
-
-    const { result, waitForNextUpdate } = renderHook(() => {
-      const query = useOrderDetails('1')
-      const api = useOrderAPI();
-
-      return { query, api };
-    }, { wrapper: TestingContext });
-
-    expect(result.current).toBeDefined();
-    if (!result.current) {
-      return;
-    }
-
-    expect(result.current.query.loading).toBeTruthy();
-    await waitForNextUpdate({ timeout: 1 });
-    assertJustExpectedRequestWereMade(env);
-
-    expect(result.current.query.loading).toBeFalsy();
-    expect(result.current?.query.ok).toBeTruthy();
-    if (!result.current?.query.ok) return;
-
-    expect(result.current.query.data).toEqual({
-      summary: 'description',
-      refund_amount: 'EUR:0',
-    });
-
-    env.addRequestExpectation(API_FORGET_ORDER_BY_ID('1'), {
-      request: {
-        fields: ['$.summary']
-      },
-    });
-
-    env.addRequestExpectation(API_GET_ORDER_BY_ID('1'), {
-      response: {
-        summary: undefined,
-      } as unknown as MerchantBackend.Orders.CheckPaymentPaidResponse,
-    });
-
-    act(async () => {
-      await result.current?.api.forgetOrder('1', {
-        fields: ['$.summary']
-      });
-    });
-
-    await waitForNextUpdate({ timeout: 1 });
-    assertJustExpectedRequestWereMade(env);
-
-    expect(result.current.query.loading).toBeFalsy();
-    expect(result.current?.query.ok).toBeTruthy();
-    if (!result.current?.query.ok) return;
-
-    expect(result.current.query.data).toEqual({
-      summary: undefined,
-    });
-  })
-})
-
-describe("order listing pagination", () => {
-
-  it("should not load more if has reach the end", async () => {
-    const env = new AxiosMockEnvironment();
-    env.addRequestExpectation(API_LIST_ORDERS, {
-      qparam: { delta: 20, wired: "yes", date_ms: 12 },
-      response: {
-        orders: [{ order_id: "1" } as any],
-      },
-    });
-
-    env.addRequestExpectation(API_LIST_ORDERS, {
-      qparam: { delta: -20, wired: "yes", date_ms: 13 },
-      response: {
-        orders: [{ order_id: "2" } as any],
-      },
-    });
-
-
-    const { result, waitForNextUpdate } = renderHook(() => {
-      const newDate = (d: Date) => {
-        console.log("new date", d);
-      };
-      const date = new Date(12);
-      const query = useInstanceOrders({ wired: "yes", date }, newDate)
-      return { query }
-    }, { wrapper: TestingContext });
-
-    assertJustExpectedRequestWereMade(env);
-
-    await waitForNextUpdate();
-
-    expect(result.current?.query.ok).toBeTruthy();
-    if (!result.current?.query.ok) return;
-
-    expect(result.current.query.data).toEqual({
-      orders: [{ order_id: "1" }, { order_id: "2" }],
-    });
-
-    expect(result.current.query.isReachingEnd).toBeTruthy()
-    expect(result.current.query.isReachingStart).toBeTruthy()
-
-    await act(() => {
-      if (!result.current?.query.ok) throw Error("not ok");
-      result.current.query.loadMore();
-    });
-    assertNoMoreRequestWereMade(env);
-
-    await act(() => {
-      if (!result.current?.query.ok) throw Error("not ok");
-      result.current.query.loadMorePrev();
-    });
-    assertNoMoreRequestWereMade(env);
-
-    expect(result.current.query.data).toEqual({
-      orders: [
-        { order_id: "1" },
-        { order_id: "2" },
-      ],
-    });
-  });
-
-  it("should load more if result brings more that PAGE_SIZE", async () => {
-    const env = new AxiosMockEnvironment();
-
-    const ordersFrom0to20 = Array.from({ length: 20 }).map((e, i) => ({ 
order_id: String(i) }))
-    const ordersFrom20to40 = Array.from({ length: 20 }).map((e, i) => ({ 
order_id: String(i + 20) }))
-    const ordersFrom20to0 = [...ordersFrom0to20].reverse()
-
-    env.addRequestExpectation(API_LIST_ORDERS, {
-      qparam: { delta: 20, wired: "yes", date_ms: 12 },
-      response: {
-        orders: ordersFrom0to20,
-      },
-    });
-
-    env.addRequestExpectation(API_LIST_ORDERS, {
-      qparam: { delta: -20, wired: "yes", date_ms: 13 },
-      response: {
-        orders: ordersFrom20to40,
-      },
-    });
-
-    const { result, waitForNextUpdate } = renderHook(() => {
-      const newDate = (d: Date) => {
-        console.log("new date", d);
-      };
-      const date = new Date(12);
-      const query = useInstanceOrders({ wired: "yes", date }, newDate)
-      return { query }
-    }, { wrapper: TestingContext });
-
-    assertJustExpectedRequestWereMade(env);
-
-    await waitForNextUpdate({ timeout: 1 });
-
-    expect(result.current?.query.ok).toBeTruthy();
-    if (!result.current?.query.ok) return;
-
-    expect(result.current.query.data).toEqual({
-      orders: [...ordersFrom20to0, ...ordersFrom20to40],
-    });
-
-    expect(result.current.query.isReachingEnd).toBeFalsy()
-    expect(result.current.query.isReachingStart).toBeFalsy()
-
-    env.addRequestExpectation(API_LIST_ORDERS, {
-      qparam: { delta: -40, wired: "yes", date_ms: 13 },
-      response: {
-        orders: [...ordersFrom20to40, { order_id: '41' }],
-      },
-    });
-
-    await act(() => {
-      if (!result.current?.query.ok) throw Error("not ok");
-      result.current.query.loadMore();
-    });
-    await waitForNextUpdate({ timeout: 1 });
-
-    assertJustExpectedRequestWereMade(env);
-
-    env.addRequestExpectation(API_LIST_ORDERS, {
-      qparam: { delta: 40, wired: "yes", date_ms: 12 },
-      response: {
-        orders: [...ordersFrom0to20, { order_id: '-1' }],
-      },
-    });
-
-    await act(() => {
-      if (!result.current?.query.ok) throw Error("not ok");
-      result.current.query.loadMorePrev();
-    });
-    await waitForNextUpdate({ timeout: 1 });
-    assertJustExpectedRequestWereMade(env);
-
-    expect(result.current.query.data).toEqual({
-      orders: [{ order_id: '-1' }, ...ordersFrom20to0, ...ordersFrom20to40, { 
order_id: '41' }],
-    });
-  });
-
-
-});
diff --git a/packages/merchant-backoffice-ui/tests/hooks/swr/product.test.ts 
b/packages/merchant-backoffice-ui/tests/hooks/swr/product.test.ts
deleted file mode 100644
index 6e9247839..000000000
--- a/packages/merchant-backoffice-ui/tests/hooks/swr/product.test.ts
+++ /dev/null
@@ -1,338 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2021-2023 Taler Systems S.A.
-
- GNU Taler is free software; you can redistribute it and/or modify it under the
- terms of the GNU General Public License as published by the Free Software
- Foundation; either version 3, or (at your option) any later version.
-
- GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along with
- GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
- */
-
-/**
- *
- * @author Sebastian Javier Marchano (sebasjm)
- */
-
-import { renderHook } from "@testing-library/preact-hooks";
-import { act } from "preact/test-utils";
-import { TestingContext } from ".";
-import { MerchantBackend } from "../../../src/declaration.js";
-import { useInstanceProducts, useProductAPI, useProductDetails } from 
"../../../src/hooks/product.js";
-import {
-  API_CREATE_PRODUCT,
-  API_DELETE_PRODUCT, API_GET_PRODUCT_BY_ID,
-  API_LIST_PRODUCTS,
-  API_UPDATE_PRODUCT_BY_ID,
-  assertJustExpectedRequestWereMade,
-  assertNextRequest,
-  AxiosMockEnvironment
-} from "../../axiosMock.js";
-
-describe("product api interaction with listing", () => {
-  it("should evict cache when creating a product", async () => {
-    const env = new AxiosMockEnvironment();
-
-    env.addRequestExpectation(API_LIST_PRODUCTS, {
-      response: {
-        products: [{ product_id: "1234" }],
-      },
-    });
-    env.addRequestExpectation(API_GET_PRODUCT_BY_ID("1234"), {
-      response: { price: "ARS:12" } as MerchantBackend.Products.ProductDetail,
-    });
-
-    const { result, waitForNextUpdate } = renderHook(
-      () => {
-        const query = useInstanceProducts();
-        const api = useProductAPI();
-        return { api, query };
-      },
-      { wrapper: TestingContext }
-    ); // get products -> loading
-
-    expect(result.current).toBeDefined();
-    if (!result.current) {
-      return;
-    }
-    expect(result.current.query.loading).toBeTruthy();
-    await waitForNextUpdate({ timeout: 1 });
-
-    await waitForNextUpdate({ timeout: 1 });
-    assertJustExpectedRequestWereMade(env);
-    expect(result.current.query.loading).toBeFalsy();
-    expect(result.current.query.ok).toBeTruthy();
-    if (!result.current?.query.ok) return;
-
-    expect(result.current.query.data).toEqual([
-      { id: "1234", price: "ARS:12" },
-    ]);
-
-    env.addRequestExpectation(API_CREATE_PRODUCT, {
-      request: { price: "ARS:23" } as 
MerchantBackend.Products.ProductAddDetail,
-    });
-
-    env.addRequestExpectation(API_LIST_PRODUCTS, {
-      response: {
-        products: [{ product_id: "1234" }, { product_id: "2345" }],
-      },
-    });
-    env.addRequestExpectation(API_GET_PRODUCT_BY_ID("1234"), {
-      response: { price: "ARS:12" } as MerchantBackend.Products.ProductDetail,
-    });
-    env.addRequestExpectation(API_GET_PRODUCT_BY_ID("1234"), {
-      response: { price: "ARS:12" } as MerchantBackend.Products.ProductDetail,
-    });
-    env.addRequestExpectation(API_GET_PRODUCT_BY_ID("2345"), {
-      response: { price: "ARS:23" } as MerchantBackend.Products.ProductDetail,
-    });
-
-    act(async () => {
-      await result.current?.api.createProduct({
-        price: "ARS:23",
-      } as any);
-    });
-
-    assertNextRequest(env);
-    await waitForNextUpdate({ timeout: 1 });
-    await waitForNextUpdate({ timeout: 1 });
-
-    assertJustExpectedRequestWereMade(env);
-
-    expect(result.current.query.loading).toBeFalsy();
-    expect(result.current?.query.ok).toBeTruthy();
-    if (!result.current?.query.ok) return;
-
-    expect(result.current.query.data).toEqual([
-      {
-        id: "1234",
-        price: "ARS:12",
-      },
-      {
-        id: "2345",
-        price: "ARS:23",
-      },
-    ]);
-  });
-
-  it("should evict cache when updating a product", async () => {
-    const env = new AxiosMockEnvironment();
-
-    env.addRequestExpectation(API_LIST_PRODUCTS, {
-      response: {
-        products: [{ product_id: "1234" }],
-      },
-    });
-    env.addRequestExpectation(API_GET_PRODUCT_BY_ID("1234"), {
-      response: { price: "ARS:12" } as MerchantBackend.Products.ProductDetail,
-    });
-
-    const { result, waitForNextUpdate } = renderHook(
-      () => {
-        const query = useInstanceProducts();
-        const api = useProductAPI();
-        return { api, query };
-      },
-      { wrapper: TestingContext }
-    ); // get products -> loading
-
-    expect(result.current).toBeDefined();
-    if (!result.current) {
-      return;
-    }
-    expect(result.current.query.loading).toBeTruthy();
-    await waitForNextUpdate({ timeout: 1 });
-
-    await waitForNextUpdate({ timeout: 1 });
-    assertJustExpectedRequestWereMade(env);
-    expect(result.current.query.loading).toBeFalsy();
-    expect(result.current.query.ok).toBeTruthy();
-    if (!result.current?.query.ok) return;
-
-    expect(result.current.query.data).toEqual([
-      { id: "1234", price: "ARS:12" },
-    ]);
-
-    env.addRequestExpectation(API_UPDATE_PRODUCT_BY_ID("1234"), {
-      request: { price: "ARS:13" } as 
MerchantBackend.Products.ProductPatchDetail,
-    });
-
-    env.addRequestExpectation(API_LIST_PRODUCTS, {
-      response: {
-        products: [{ product_id: "1234" }],
-      },
-    });
-    env.addRequestExpectation(API_GET_PRODUCT_BY_ID("1234"), {
-      response: { price: "ARS:13" } as MerchantBackend.Products.ProductDetail,
-    });
-
-    act(async () => {
-      await result.current?.api.updateProduct("1234", {
-        price: "ARS:13",
-      } as any);
-    });
-
-    assertNextRequest(env);
-    await waitForNextUpdate({ timeout: 1 });
-
-    assertJustExpectedRequestWereMade(env);
-
-    expect(result.current.query.loading).toBeFalsy();
-    expect(result.current?.query.ok).toBeTruthy();
-    if (!result.current?.query.ok) return;
-
-    expect(result.current.query.data).toEqual([
-      {
-        id: "1234",
-        price: "ARS:13",
-      },
-    ]);
-  });
-
-  it("should evict cache when deleting a product", async () => {
-    const env = new AxiosMockEnvironment();
-
-    env.addRequestExpectation(API_LIST_PRODUCTS, {
-      response: {
-        products: [{ product_id: "1234" }, { product_id: "2345" }],
-      },
-    });
-    env.addRequestExpectation(API_GET_PRODUCT_BY_ID("1234"), {
-      response: { price: "ARS:12" } as MerchantBackend.Products.ProductDetail,
-    });
-    env.addRequestExpectation(API_GET_PRODUCT_BY_ID("2345"), {
-      response: { price: "ARS:23" } as MerchantBackend.Products.ProductDetail,
-    });
-
-    const { result, waitForNextUpdate } = renderHook(
-      () => {
-        const query = useInstanceProducts();
-        const api = useProductAPI();
-        return { api, query };
-      },
-      { wrapper: TestingContext }
-    ); // get products -> loading
-
-    expect(result.current).toBeDefined();
-    if (!result.current) {
-      return;
-    }
-    expect(result.current.query.loading).toBeTruthy();
-    await waitForNextUpdate({ timeout: 1 });
-
-    await waitForNextUpdate({ timeout: 1 });
-    // await waitForNextUpdate({ timeout: 1 });
-    assertJustExpectedRequestWereMade(env);
-
-    expect(result.current.query.loading).toBeFalsy();
-    expect(result.current.query.ok).toBeTruthy();
-    if (!result.current?.query.ok) return;
-
-    expect(result.current.query.data).toEqual([
-      { id: "1234", price: "ARS:12" },
-      { id: "2345", price: "ARS:23" },
-    ]);
-
-    env.addRequestExpectation(API_DELETE_PRODUCT("2345"), {});
-
-    env.addRequestExpectation(API_LIST_PRODUCTS, {
-      response: {
-        products: [{ product_id: "1234" }],
-      },
-    });
-    env.addRequestExpectation(API_GET_PRODUCT_BY_ID("1234"), {
-      response: { price: "ARS:13" } as MerchantBackend.Products.ProductDetail,
-    });
-
-    act(async () => {
-      await result.current?.api.deleteProduct("2345");
-    });
-
-    assertNextRequest(env);
-    await waitForNextUpdate({ timeout: 1 });
-    await waitForNextUpdate({ timeout: 1 });
-
-    assertJustExpectedRequestWereMade(env);
-
-    expect(result.current.query.loading).toBeFalsy();
-    expect(result.current?.query.ok).toBeTruthy();
-    if (!result.current?.query.ok) return;
-
-    expect(result.current.query.data).toEqual([
-      {
-        id: "1234",
-        price: "ARS:13",
-      },
-    ]);
-  });
-
-});
-
-describe("product api interaction with details", () => {
-  it("should evict cache when updating a product", async () => {
-    const env = new AxiosMockEnvironment();
-
-    env.addRequestExpectation(API_GET_PRODUCT_BY_ID("12"), {
-      response: {
-        description: "this is a description",
-      } as MerchantBackend.Products.ProductDetail,
-    });
-
-    const { result, waitForNextUpdate } = renderHook(() => {
-      const query = useProductDetails("12");
-      const api = useProductAPI();
-      return { query, api };
-    }, { wrapper: TestingContext });
-
-    expect(result.current).toBeDefined();
-    if (!result.current) {
-      return;
-    }
-    expect(result.current.query.loading).toBeTruthy();
-    await waitForNextUpdate();
-
-    assertJustExpectedRequestWereMade(env);
-
-    expect(result.current.query.loading).toBeFalsy();
-    expect(result.current?.query.ok).toBeTruthy();
-    if (!result.current?.query.ok) return;
-
-    expect(result.current.query.data).toEqual({
-      description: "this is a description",
-    });
-
-    env.addRequestExpectation(API_UPDATE_PRODUCT_BY_ID("12"), {
-      request: { description: "other description" } as 
MerchantBackend.Products.ProductPatchDetail,
-    });
-
-    env.addRequestExpectation(API_GET_PRODUCT_BY_ID("12"), {
-      response: {
-        description: "other description",
-      } as MerchantBackend.Products.ProductDetail,
-    });
-
-    act(async () => {
-      return await result.current?.api.updateProduct("12", {
-        description: "other description",
-      } as any);
-    });
-
-    assertNextRequest(env);
-    await waitForNextUpdate();
-
-    assertJustExpectedRequestWereMade(env);
-
-    expect(result.current.query.loading).toBeFalsy();
-    expect(result.current?.query.ok).toBeTruthy();
-    if (!result.current?.query.ok) return;
-
-    expect(result.current.query.data).toEqual({
-      description: "other description",
-    });
-  })
-})
\ No newline at end of file
diff --git a/packages/merchant-backoffice-ui/tests/hooks/swr/reserve.test.ts 
b/packages/merchant-backoffice-ui/tests/hooks/swr/reserve.test.ts
deleted file mode 100644
index 8ebbee353..000000000
--- a/packages/merchant-backoffice-ui/tests/hooks/swr/reserve.test.ts
+++ /dev/null
@@ -1,470 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2021-2023 Taler Systems S.A.
-
- GNU Taler is free software; you can redistribute it and/or modify it under the
- terms of the GNU General Public License as published by the Free Software
- Foundation; either version 3, or (at your option) any later version.
-
- GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along with
- GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
- */
-
-/**
- *
- * @author Sebastian Javier Marchano (sebasjm)
- */
-
-import { renderHook } from "@testing-library/preact-hooks";
-import { act } from "preact/test-utils";
-import { MerchantBackend } from "../../../src/declaration.js";
-import {
-  useInstanceReserves,
-  useReserveDetails,
-  useReservesAPI,
-  useTipDetails,
-} from "../../../src/hooks/reserves.js";
-import {
-  API_AUTHORIZE_TIP,
-  API_AUTHORIZE_TIP_FOR_RESERVE,
-  API_CREATE_RESERVE,
-  API_DELETE_RESERVE,
-  API_GET_RESERVE_BY_ID,
-  API_GET_TIP_BY_ID,
-  API_LIST_RESERVES,
-  assertJustExpectedRequestWereMade,
-  AxiosMockEnvironment,
-} from "../../axiosMock.js";
-import { TestingContext } from "./index.js";
-
-describe("reserve api interaction with listing", () => {
-  it("should evict cache when creating a reserve", async () => {
-    const env = new AxiosMockEnvironment();
-
-    env.addRequestExpectation(API_LIST_RESERVES, {
-      response: {
-        reserves: [
-          {
-            reserve_pub: "11",
-          } as MerchantBackend.Tips.ReserveStatusEntry,
-        ],
-      },
-    });
-
-    const { result, waitForNextUpdate } = renderHook(
-      () => {
-        const api = useReservesAPI();
-        const query = useInstanceReserves();
-
-        return { query, api };
-      },
-      { wrapper: TestingContext }
-    );
-
-    expect(result.current).toBeDefined();
-    if (!result.current) {
-      return;
-    }
-    expect(result.current.query.loading).toBeTruthy();
-
-    await waitForNextUpdate({ timeout: 1 });
-
-    assertJustExpectedRequestWereMade(env);
-
-    expect(result.current.query.loading).toBeFalsy();
-    expect(result.current?.query.ok).toBeTruthy();
-    if (!result.current?.query.ok) return;
-
-    expect(result.current.query.data).toEqual({
-      reserves: [{ reserve_pub: "11" }],
-    });
-
-    env.addRequestExpectation(API_CREATE_RESERVE, {
-      request: {
-        initial_balance: "ARS:3333",
-        exchange_url: "http://url";,
-        wire_method: "iban",
-      },
-      response: {
-        reserve_pub: "22",
-        payto_uri: "payto",
-      },
-    });
-
-    act(async () => {
-      await result.current?.api.createReserve({
-        initial_balance: "ARS:3333",
-        exchange_url: "http://url";,
-        wire_method: "iban",
-      });
-      return;
-    });
-
-    assertJustExpectedRequestWereMade(env);
-
-    env.addRequestExpectation(API_LIST_RESERVES, {
-      response: {
-        reserves: [
-          {
-            reserve_pub: "11",
-          } as MerchantBackend.Tips.ReserveStatusEntry,
-          {
-            reserve_pub: "22",
-          } as MerchantBackend.Tips.ReserveStatusEntry,
-        ],
-      },
-    });
-
-    expect(result.current.query.loading).toBeFalsy();
-
-    await waitForNextUpdate({ timeout: 1 });
-
-    assertJustExpectedRequestWereMade(env);
-
-    expect(result.current.query.loading).toBeFalsy();
-    expect(result.current.query.ok).toBeTruthy();
-
-    expect(result.current.query.data).toEqual({
-      reserves: [
-        {
-          reserve_pub: "11",
-        } as MerchantBackend.Tips.ReserveStatusEntry,
-        {
-          reserve_pub: "22",
-        } as MerchantBackend.Tips.ReserveStatusEntry,
-      ],
-    });
-  });
-
-  it("should evict cache when deleting a reserve", async () => {
-    const env = new AxiosMockEnvironment();
-
-    env.addRequestExpectation(API_LIST_RESERVES, {
-      response: {
-        reserves: [
-          {
-            reserve_pub: "11",
-          } as MerchantBackend.Tips.ReserveStatusEntry,
-          {
-            reserve_pub: "22",
-          } as MerchantBackend.Tips.ReserveStatusEntry,
-          {
-            reserve_pub: "33",
-          } as MerchantBackend.Tips.ReserveStatusEntry,
-        ],
-      },
-    });
-
-    const { result, waitForNextUpdate } = renderHook(
-      () => {
-        const api = useReservesAPI();
-        const query = useInstanceReserves();
-
-        return { query, api };
-      },
-      {
-        wrapper: TestingContext,
-      }
-    );
-
-    expect(result.current).toBeDefined();
-    if (!result.current) {
-      return;
-    }
-    expect(result.current.query.loading).toBeTruthy();
-
-    await waitForNextUpdate({ timeout: 1 });
-
-    assertJustExpectedRequestWereMade(env);
-
-    expect(result.current.query.loading).toBeFalsy();
-    expect(result.current?.query.ok).toBeTruthy();
-    if (!result.current?.query.ok) return;
-
-    expect(result.current.query.data).toEqual({
-      reserves: [
-        { reserve_pub: "11" },
-        { reserve_pub: "22" },
-        { reserve_pub: "33" },
-      ],
-    });
-
-    env.addRequestExpectation(API_DELETE_RESERVE("11"), {});
-
-    act(async () => {
-      await result.current?.api.deleteReserve("11");
-      return;
-    });
-
-    assertJustExpectedRequestWereMade(env);
-
-    env.addRequestExpectation(API_LIST_RESERVES, {
-      response: {
-        reserves: [
-          {
-            reserve_pub: "22",
-          } as MerchantBackend.Tips.ReserveStatusEntry,
-          {
-            reserve_pub: "33",
-          } as MerchantBackend.Tips.ReserveStatusEntry,
-        ],
-      },
-    });
-
-    expect(result.current.query.loading).toBeFalsy();
-
-    await waitForNextUpdate({ timeout: 1 });
-
-    assertJustExpectedRequestWereMade(env);
-
-    expect(result.current.query.loading).toBeFalsy();
-    expect(result.current.query.ok).toBeTruthy();
-
-    expect(result.current.query.data).toEqual({
-      reserves: [
-        {
-          reserve_pub: "22",
-        } as MerchantBackend.Tips.ReserveStatusEntry,
-        {
-          reserve_pub: "33",
-        } as MerchantBackend.Tips.ReserveStatusEntry,
-      ],
-    });
-  });
-});
-
-describe("reserve api interaction with details", () => {
-  it("should evict cache when adding a tip for a specific reserve", async () 
=> {
-    const env = new AxiosMockEnvironment();
-
-    env.addRequestExpectation(API_GET_RESERVE_BY_ID("11"), {
-      response: {
-        payto_uri: "payto://here",
-        tips: [{ reason: "why?", tip_id: "id1", total_amount: "USD:10" }],
-      } as MerchantBackend.Tips.ReserveDetail,
-    });
-
-    const { result, waitForNextUpdate } = renderHook(
-      () => {
-        const api = useReservesAPI();
-        const query = useReserveDetails("11");
-
-        return { query, api };
-      },
-      {
-        wrapper: TestingContext,
-      }
-    );
-
-    expect(result.current).toBeDefined();
-    if (!result.current) {
-      return;
-    }
-    expect(result.current.query.loading).toBeTruthy();
-
-    await waitForNextUpdate({ timeout: 1 });
-
-    assertJustExpectedRequestWereMade(env);
-
-    expect(result.current.query.loading).toBeFalsy();
-    expect(result.current?.query.ok).toBeTruthy();
-    if (!result.current?.query.ok) return;
-
-    expect(result.current.query.data).toEqual({
-      payto_uri: "payto://here",
-      tips: [{ reason: "why?", tip_id: "id1", total_amount: "USD:10" }],
-    });
-
-    env.addRequestExpectation(API_AUTHORIZE_TIP_FOR_RESERVE("11"), {
-      request: {
-        amount: "USD:12",
-        justification: "not",
-        next_url: "http://taler.net";,
-      },
-      response: {
-        tip_id: "id2",
-        taler_tip_uri: "uri",
-        tip_expiration: { t_s: 1 },
-        tip_status_url: "url",
-      },
-    });
-
-    act(async () => {
-      await result.current?.api.authorizeTipReserve("11", {
-        amount: "USD:12",
-        justification: "not",
-        next_url: "http://taler.net";,
-      });
-    });
-
-    assertJustExpectedRequestWereMade(env);
-
-    expect(result.current.query.loading).toBeFalsy();
-
-    env.addRequestExpectation(API_GET_RESERVE_BY_ID("11"), {
-      response: {
-        payto_uri: "payto://here",
-        tips: [
-          { reason: "why?", tip_id: "id1", total_amount: "USD:10" },
-          { reason: "not", tip_id: "id2", total_amount: "USD:12" },
-        ],
-      } as MerchantBackend.Tips.ReserveDetail,
-    });
-
-    await waitForNextUpdate({ timeout: 1 });
-
-    assertJustExpectedRequestWereMade(env);
-
-    expect(result.current.query.loading).toBeFalsy();
-    expect(result.current.query.ok).toBeTruthy();
-
-    expect(result.current.query.data).toEqual({
-      payto_uri: "payto://here",
-      tips: [
-        { reason: "why?", tip_id: "id1", total_amount: "USD:10" },
-        { reason: "not", tip_id: "id2", total_amount: "USD:12" },
-      ],
-    });
-  });
-
-  it("should evict cache when adding a tip for a random reserve", async () => {
-    const env = new AxiosMockEnvironment();
-
-    env.addRequestExpectation(API_GET_RESERVE_BY_ID("11"), {
-      response: {
-        payto_uri: "payto://here",
-        tips: [{ reason: "why?", tip_id: "id1", total_amount: "USD:10" }],
-      } as MerchantBackend.Tips.ReserveDetail,
-    });
-
-    const { result, waitForNextUpdate } = renderHook(
-      () => {
-        const api = useReservesAPI();
-        const query = useReserveDetails("11");
-
-        return { query, api };
-      },
-      {
-        wrapper: TestingContext,
-      }
-    );
-
-    expect(result.current).toBeDefined();
-    if (!result.current) {
-      return;
-    }
-    expect(result.current.query.loading).toBeTruthy();
-
-    await waitForNextUpdate({ timeout: 1 });
-
-    assertJustExpectedRequestWereMade(env);
-
-    expect(result.current.query.loading).toBeFalsy();
-    expect(result.current?.query.ok).toBeTruthy();
-    if (!result.current?.query.ok) return;
-
-    expect(result.current.query.data).toEqual({
-      payto_uri: "payto://here",
-      tips: [{ reason: "why?", tip_id: "id1", total_amount: "USD:10" }],
-    });
-
-    env.addRequestExpectation(API_AUTHORIZE_TIP, {
-      request: {
-        amount: "USD:12",
-        justification: "not",
-        next_url: "http://taler.net";,
-      },
-      response: {
-        tip_id: "id2",
-        taler_tip_uri: "uri",
-        tip_expiration: { t_s: 1 },
-        tip_status_url: "url",
-      },
-    });
-
-    act(async () => {
-      await result.current?.api.authorizeTip({
-        amount: "USD:12",
-        justification: "not",
-        next_url: "http://taler.net";,
-      });
-    });
-
-    assertJustExpectedRequestWereMade(env);
-
-    expect(result.current.query.loading).toBeFalsy();
-
-    env.addRequestExpectation(API_GET_RESERVE_BY_ID("11"), {
-      response: {
-        payto_uri: "payto://here",
-        tips: [
-          { reason: "why?", tip_id: "id1", total_amount: "USD:10" },
-          { reason: "not", tip_id: "id2", total_amount: "USD:12" },
-        ],
-      } as MerchantBackend.Tips.ReserveDetail,
-    });
-
-    await waitForNextUpdate({ timeout: 1 });
-
-    assertJustExpectedRequestWereMade(env);
-
-    expect(result.current.query.loading).toBeFalsy();
-    expect(result.current.query.ok).toBeTruthy();
-
-    expect(result.current.query.data).toEqual({
-      payto_uri: "payto://here",
-      tips: [
-        { reason: "why?", tip_id: "id1", total_amount: "USD:10" },
-        { reason: "not", tip_id: "id2", total_amount: "USD:12" },
-      ],
-    });
-  });
-});
-
-describe("reserve api interaction with tip details", () => {
-  it("should list tips", async () => {
-    const env = new AxiosMockEnvironment();
-
-    env.addRequestExpectation(API_GET_TIP_BY_ID("11"), {
-      response: {
-        total_picked_up: "USD:12",
-        reason: "not",
-      } as MerchantBackend.Tips.TipDetails,
-    });
-
-    const { result, waitForNextUpdate } = renderHook(
-      () => {
-        // const api = useReservesAPI();
-        const query = useTipDetails("11");
-
-        return { query };
-      },
-      {
-        wrapper: TestingContext,
-      }
-    );
-
-    expect(result.current).toBeDefined();
-    if (!result.current) {
-      return;
-    }
-    expect(result.current.query.loading).toBeTruthy();
-
-    await waitForNextUpdate({ timeout: 1 });
-
-    assertJustExpectedRequestWereMade(env);
-
-    expect(result.current.query.loading).toBeFalsy();
-    expect(result.current?.query.ok).toBeTruthy();
-    if (!result.current?.query.ok) return;
-
-    expect(result.current.query.data).toEqual({
-      total_picked_up: "USD:12",
-      reason: "not",
-    });
-  });
-});
diff --git a/packages/merchant-backoffice-ui/tests/hooks/swr/transfer.test.ts 
b/packages/merchant-backoffice-ui/tests/hooks/swr/transfer.test.ts
deleted file mode 100644
index 0b1f4a968..000000000
--- a/packages/merchant-backoffice-ui/tests/hooks/swr/transfer.test.ts
+++ /dev/null
@@ -1,268 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2021-2023 Taler Systems S.A.
-
- GNU Taler is free software; you can redistribute it and/or modify it under the
- terms of the GNU General Public License as published by the Free Software
- Foundation; either version 3, or (at your option) any later version.
-
- GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along with
- GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
- */
-
-/**
- *
- * @author Sebastian Javier Marchano (sebasjm)
- */
-
-import { act, renderHook } from "@testing-library/preact-hooks";
-import { TestingContext } from "./index.js";
-import { useInstanceTransfers, useTransferAPI } from 
"../../../src/hooks/transfer.js";
-import {
-  API_INFORM_TRANSFERS,
-  API_LIST_TRANSFERS,
-  assertJustExpectedRequestWereMade,
-  assertNoMoreRequestWereMade,
-  AxiosMockEnvironment,
-} from "../../axiosMock.js";
-import { MerchantBackend } from "../../../src/declaration.js";
-
-describe("transfer api interaction with listing", () => {
-
-  it("should evict cache when informing a transfer", async () => {
-    const env = new AxiosMockEnvironment();
-
-    env.addRequestExpectation(API_LIST_TRANSFERS, {
-      qparam: { limit: 0 },
-      response: {
-        transfers: [{ wtid: "2" } as 
MerchantBackend.Transfers.TransferDetails],
-      },
-    });
-    // FIXME: is this query really needed? if the hook is rendered without
-    // position argument then then backend is returning the newest and no need
-    // to this second query 
-    env.addRequestExpectation(API_LIST_TRANSFERS, {
-      qparam: { limit: -20 },
-      response: {
-        transfers: [],
-      },
-    });
-
-    const { result, waitForNextUpdate } = renderHook(() => {
-      const moveCursor = (d: string) => {
-        console.log("new position", d);
-      };
-      const query = useInstanceTransfers({}, moveCursor);
-      const api = useTransferAPI();
-
-      return { query, api };
-    }, { wrapper: TestingContext });
-
-    expect(result.current).toBeDefined();
-    if (!result.current) {
-      return;
-    }
-
-    expect(result.current.query.loading).toBeTruthy();
-    await waitForNextUpdate({ timeout: 1 });
-    assertJustExpectedRequestWereMade(env);
-
-    expect(result.current.query.loading).toBeFalsy();
-    expect(result.current.query.ok).toBeTruthy();
-    if (!result.current.query.ok) return;
-
-    expect(result.current.query.data).toEqual({
-      transfers: [{ wtid: "2" }],
-    });
-
-    env.addRequestExpectation(API_INFORM_TRANSFERS, {
-      request: {
-        wtid: '3',
-        credit_amount: 'EUR:1',
-        exchange_url: 'exchange.url',
-        payto_uri: 'payto://'
-      },
-      response: { total: '' } as any,
-    });
-
-    env.addRequestExpectation(API_LIST_TRANSFERS, {
-      qparam: { limit: 0 },
-      response: {
-        transfers: [{ wtid: "2" } as any, { wtid: "3" } as any],
-      },
-    });
-
-    env.addRequestExpectation(API_LIST_TRANSFERS, {
-      qparam: { limit: -20 },
-      response: {
-        transfers: [],
-      },
-    });
-
-    act(async () => {
-      await result.current?.api.informTransfer({
-        wtid: '3',
-        credit_amount: 'EUR:1',
-        exchange_url: 'exchange.url',
-        payto_uri: 'payto://'
-      });
-    });
-
-    await waitForNextUpdate({ timeout: 1 });
-    assertJustExpectedRequestWereMade(env);
-
-    expect(result.current.query.loading).toBeFalsy();
-    expect(result.current.query.ok).toBeTruthy();
-    if (!result.current?.query.ok) return;
-
-    expect(result.current.query.data).toEqual({
-      transfers: [{ wtid: "3" }, { wtid: "2" }],
-    });
-  });
-
-});
-
-describe("transfer listing pagination", () => {
-
-  it("should not load more if has reach the end", async () => {
-    const env = new AxiosMockEnvironment();
-    env.addRequestExpectation(API_LIST_TRANSFERS, {
-      qparam: { limit: 0, payto_uri: 'payto://' },
-      response: {
-        transfers: [{ wtid: "2" } as any],
-      },
-    });
-
-    env.addRequestExpectation(API_LIST_TRANSFERS, {
-      qparam: { limit: -20, payto_uri: 'payto://' },
-      response: {
-        transfers: [{ wtid: "1" } as any],
-      },
-    });
-
-
-    const { result, waitForNextUpdate } = renderHook(() => {
-      const moveCursor = (d: string) => {
-        console.log("new position", d);
-      };
-      const query = useInstanceTransfers({ payto_uri: 'payto://' }, moveCursor)
-      return { query }
-    }, { wrapper: TestingContext });
-
-    assertJustExpectedRequestWereMade(env);
-
-    await waitForNextUpdate();
-
-    expect(result.current?.query.ok).toBeTruthy();
-    if (!result.current?.query.ok) return;
-
-    expect(result.current.query.data).toEqual({
-      transfers: [{ wtid: "2" }, { wtid: "1" }],
-    });
-
-    expect(result.current.query.isReachingEnd).toBeTruthy()
-    expect(result.current.query.isReachingStart).toBeTruthy()
-
-    await act(() => {
-      if (!result.current?.query.ok) throw Error("not ok");
-      result.current.query.loadMore();
-    });
-    assertNoMoreRequestWereMade(env);
-
-    await act(() => {
-      if (!result.current?.query.ok) throw Error("not ok");
-      result.current.query.loadMorePrev();
-    });
-    assertNoMoreRequestWereMade(env);
-
-    expect(result.current.query.data).toEqual({
-      transfers: [
-        { wtid: "2" },
-        { wtid: "1" },
-      ],
-    });
-  });
-
-  it("should load more if result brings more that PAGE_SIZE", async () => {
-    const env = new AxiosMockEnvironment();
-
-    const transfersFrom0to20 = Array.from({ length: 20 }).map((e, i) => ({ 
wtid: String(i) }))
-    const transfersFrom20to40 = Array.from({ length: 20 }).map((e, i) => ({ 
wtid: String(i + 20) }))
-    const transfersFrom20to0 = [...transfersFrom0to20].reverse()
-
-    env.addRequestExpectation(API_LIST_TRANSFERS, {
-      qparam: { limit: 20, payto_uri: 'payto://' },
-      response: {
-        transfers: transfersFrom0to20,
-      },
-    });
-
-    env.addRequestExpectation(API_LIST_TRANSFERS, {
-      qparam: { limit: -20, payto_uri: 'payto://' },
-      response: {
-        transfers: transfersFrom20to40,
-      },
-    });
-
-    const { result, waitForNextUpdate } = renderHook(() => {
-      const moveCursor = (d: string) => {
-        console.log("new position", d);
-      };
-      const query = useInstanceTransfers({ payto_uri: 'payto://', position: 
'1' }, moveCursor)
-      return { query }
-    }, { wrapper: TestingContext });
-
-    assertJustExpectedRequestWereMade(env);
-
-    await waitForNextUpdate({ timeout: 1 });
-
-    expect(result.current?.query.ok).toBeTruthy();
-    if (!result.current?.query.ok) return;
-
-    expect(result.current.query.data).toEqual({
-      transfers: [...transfersFrom20to0, ...transfersFrom20to40],
-    });
-
-    expect(result.current.query.isReachingEnd).toBeFalsy()
-    expect(result.current.query.isReachingStart).toBeFalsy()
-
-    env.addRequestExpectation(API_LIST_TRANSFERS, {
-      qparam: { limit: -40, payto_uri: 'payto://', offset: "1" },
-      response: {
-        transfers: [...transfersFrom20to40, { wtid: '41' }],
-      },
-    });
-
-    await act(() => {
-      if (!result.current?.query.ok) throw Error("not ok");
-      result.current.query.loadMore();
-    });
-    await waitForNextUpdate({ timeout: 1 });
-
-    assertJustExpectedRequestWereMade(env);
-
-    env.addRequestExpectation(API_LIST_TRANSFERS, {
-      qparam: { limit: 40, payto_uri: 'payto://', offset: "1" },
-      response: {
-        transfers: [...transfersFrom0to20, { wtid: '-1' }],
-      },
-    });
-
-    await act(() => {
-      if (!result.current?.query.ok) throw Error("not ok");
-      result.current.query.loadMorePrev();
-    });
-    await waitForNextUpdate({ timeout: 1 });
-    assertJustExpectedRequestWereMade(env);
-
-    expect(result.current.query.data).toEqual({
-      transfers: [{ wtid: '-1' }, ...transfersFrom20to0, 
...transfersFrom20to40, { wtid: '41' }],
-    });
-  });
-
-
-});
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index c88b131c0..6ada7a842 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -343,15 +343,12 @@ importers:
       '@gnu-taler/pogen': ^0.0.5
       '@gnu-taler/taler-util': workspace:*
       '@gnu-taler/web-util': workspace:*
-      '@testing-library/preact': ^2.0.1
-      '@testing-library/preact-hooks': ^1.1.0
+      '@types/chai': ^4.3.0
       '@types/history': ^4.7.8
-      '@types/jest': ^26.0.23
       '@types/mocha': ^8.2.3
       '@types/node': ^18.11.17
       '@typescript-eslint/eslint-plugin': ^4.22.0
       '@typescript-eslint/parser': ^4.22.0
-      axios: ^0.21.1
       base64-inline-loader: ^1.1.1
       bulma: ^0.9.2
       bulma-checkbox: ^1.1.1
@@ -360,6 +357,7 @@ importers:
       bulma-switch-control: ^1.1.1
       bulma-timeline: ^3.0.4
       bulma-upload-control: ^1.2.0
+      chai: ^4.3.6
       date-fns: 2.29.3
       dotenv: ^8.2.0
       eslint: ^7.25.0
@@ -371,8 +369,6 @@ importers:
       html-webpack-skip-assets-plugin: ^1.0.1
       inline-chunk-html-plugin: ^1.1.1
       jed: 1.1.1
-      jest: ^26.6.3
-      jest-preset-preact: ^4.0.2
       mocha: ^9.2.0
       preact: 10.11.3
       preact-render-to-string: ^5.2.6
@@ -380,6 +376,7 @@ importers:
       qrcode-generator: 1.4.4
       rimraf: ^3.0.2
       sass: 1.56.1
+      source-map-support: ^0.5.21
       swr: 1.3.0
       typedoc: ^0.20.36
       typescript: 4.8.4
@@ -387,7 +384,6 @@ importers:
     dependencies:
       '@gnu-taler/taler-util': link:../taler-util
       '@gnu-taler/web-util': link:../web-util
-      axios: 0.21.4
       date-fns: 2.29.3
       history: 4.10.1
       jed: 1.1.1
@@ -399,10 +395,8 @@ importers:
     devDependencies:
       '@creativebulma/bulma-tooltip': 1.2.0
       '@gnu-taler/pogen': link:../pogen
-      '@testing-library/preact': 2.0.1_preact@10.11.3
-      '@testing-library/preact-hooks': 1.1.0_eng4adldpgibddgycwaukopxga
+      '@types/chai': 4.3.3
       '@types/history': 4.7.11
-      '@types/jest': 26.0.24
       '@types/mocha': 8.2.3
       '@types/node': 18.11.17
       '@typescript-eslint/eslint-plugin': 4.33.0_k4l66av2tbo6kxzw52jzgbfzii
@@ -415,20 +409,20 @@ importers:
       bulma-switch-control: 1.2.2
       bulma-timeline: 3.0.5
       bulma-upload-control: 1.2.0
+      chai: 4.3.6
       dotenv: 8.6.0
       eslint: 7.32.0
-      eslint-config-preact: 1.3.0_nxlzr75jbqkso2fds5zjovs2ii
+      eslint-config-preact: 1.3.0_pycg7frr72nxxf2dj537ozbyqq
       eslint-plugin-header: 3.1.1_eslint@7.32.0
       html-webpack-inline-chunk-plugin: 1.1.1
       html-webpack-inline-source-plugin: 0.0.10
       html-webpack-skip-assets-plugin: 1.0.3
       inline-chunk-html-plugin: 1.1.1
-      jest: 26.6.3
-      jest-preset-preact: 4.0.5_w5bq6jgm3cbfmbu2zwqko4iate
       mocha: 9.2.2
       preact-render-to-string: 5.2.6_preact@10.11.3
       rimraf: 3.0.2
       sass: 1.56.1
+      source-map-support: 0.5.21
       typedoc: 0.20.37_typescript@4.8.4
       typescript: 4.8.4
 
@@ -5814,16 +5808,6 @@ packages:
       preact: 10.11.2
     dev: true
 
-  /@testing-library/preact-hooks/1.1.0_eng4adldpgibddgycwaukopxga:
-    resolution: {integrity: 
sha512-+JIor+NsOHkK3oIrwMDGKGHXTN0JJi462dBJlj4FNbGaDPTlctE6eu2ranWQirh7/FJMkWfzQCP+tk7jmY8ZrQ==}
-    peerDependencies:
-      '@testing-library/preact': ^2.0.0
-      preact: ^10.4.8
-    dependencies:
-      '@testing-library/preact': 2.0.1_preact@10.11.3
-      preact: 10.11.3
-    dev: true
-
   /@testing-library/preact/2.0.1_preact@10.11.2:
     resolution: {integrity: 
sha512-79kwVOY+3caoLgaPbiPzikjgY0Aya7Fc7TvGtR1upCnz2wrtmPDnN2t9vO7I7vDP2zoA+feSwOH5Q0BFErhaaQ==}
     engines: {node: '>= 10'}
@@ -5834,16 +5818,6 @@ packages:
       preact: 10.11.2
     dev: true
 
-  /@testing-library/preact/2.0.1_preact@10.11.3:
-    resolution: {integrity: 
sha512-79kwVOY+3caoLgaPbiPzikjgY0Aya7Fc7TvGtR1upCnz2wrtmPDnN2t9vO7I7vDP2zoA+feSwOH5Q0BFErhaaQ==}
-    engines: {node: '>= 10'}
-    peerDependencies:
-      preact: '>=10 || ^10.0.0-alpha.0 || ^10.0.0-beta.0'
-    dependencies:
-      '@testing-library/dom': 7.31.2
-      preact: 10.11.3
-    dev: true
-
   /@tootallnate/once/1.1.2:
     resolution: {integrity: 
sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==}
     engines: {node: '>= 6'}
@@ -11268,6 +11242,28 @@ packages:
       - typescript
     dev: true
 
+  /eslint-config-preact/1.3.0_pycg7frr72nxxf2dj537ozbyqq:
+    resolution: {integrity: 
sha512-yHYXg5qNzEJd3D/30AmsIW0W8MuY858KpApXp7xxBF08IYUljSKCOqMx+dVucXHQnAm7+11wOnMkgVHIBAechw==}
+    peerDependencies:
+      eslint: 6.x || 7.x || 8.x
+    dependencies:
+      '@babel/core': 7.18.9
+      '@babel/eslint-parser': 7.19.1_o5peei4wpze5egwf42u76kwdva
+      '@babel/plugin-syntax-class-properties': 7.12.13_@babel+core@7.18.9
+      '@babel/plugin-syntax-decorators': 7.19.0_@babel+core@7.18.9
+      '@babel/plugin-syntax-jsx': 7.18.6_@babel+core@7.18.9
+      eslint: 7.32.0
+      eslint-plugin-compat: 4.0.2_eslint@7.32.0
+      eslint-plugin-jest: 25.7.0_pycg7frr72nxxf2dj537ozbyqq
+      eslint-plugin-react: 7.31.10_eslint@7.32.0
+      eslint-plugin-react-hooks: 4.6.0_eslint@7.32.0
+    transitivePeerDependencies:
+      - '@typescript-eslint/eslint-plugin'
+      - jest
+      - supports-color
+      - typescript
+    dev: true
+
   /eslint-config-preact/1.3.0_qqbgcrpnpybc6dh47gt272vyy4:
     resolution: {integrity: 
sha512-yHYXg5qNzEJd3D/30AmsIW0W8MuY858KpApXp7xxBF08IYUljSKCOqMx+dVucXHQnAm7+11wOnMkgVHIBAechw==}
     peerDependencies:
@@ -11430,6 +11426,27 @@ packages:
       - typescript
     dev: true
 
+  /eslint-plugin-jest/25.7.0_pycg7frr72nxxf2dj537ozbyqq:
+    resolution: {integrity: 
sha512-PWLUEXeeF7C9QGKqvdSbzLOiLTx+bno7/HC9eefePfEb257QFHg7ye3dh80AZVkaa/RQsBB1Q/ORQvg2X7F0NQ==}
+    engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0}
+    peerDependencies:
+      '@typescript-eslint/eslint-plugin': ^4.0.0 || ^5.0.0
+      eslint: ^6.0.0 || ^7.0.0 || ^8.0.0
+      jest: '*'
+    peerDependenciesMeta:
+      '@typescript-eslint/eslint-plugin':
+        optional: true
+      jest:
+        optional: true
+    dependencies:
+      '@typescript-eslint/eslint-plugin': 4.33.0_k4l66av2tbo6kxzw52jzgbfzii
+      '@typescript-eslint/experimental-utils': 
5.41.0_3rubbgt5ekhqrcgx4uwls3neim
+      eslint: 7.32.0
+    transitivePeerDependencies:
+      - supports-color
+      - typescript
+    dev: true
+
   /eslint-plugin-jest/25.7.0_qqbgcrpnpybc6dh47gt272vyy4:
     resolution: {integrity: 
sha512-PWLUEXeeF7C9QGKqvdSbzLOiLTx+bno7/HC9eefePfEb257QFHg7ye3dh80AZVkaa/RQsBB1Q/ORQvg2X7F0NQ==}
     engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0}
@@ -14582,30 +14599,6 @@ packages:
       - supports-color
     dev: true
 
-  /jest-preset-preact/4.0.5_w5bq6jgm3cbfmbu2zwqko4iate:
-    resolution: {integrity: 
sha512-MnU7mfpnwopJkdx0WoEyRmrNDIvRN+w6sOur0zEhaRYYMo0gJM7UdZHWTV8k6uo0+ypY+m0kQW6kMukUx4v8JQ==}
-    peerDependencies:
-      jest: 26.x || 27.x
-      preact: 10.x
-      preact-render-to-string: 5.x
-    dependencies:
-      '@babel/core': 7.18.9
-      '@babel/plugin-proposal-class-properties': 7.18.6_@babel+core@7.18.9
-      '@babel/plugin-transform-react-jsx': 7.19.0_@babel+core@7.18.9
-      '@babel/preset-env': 7.18.9_@babel+core@7.18.9
-      '@babel/preset-typescript': 7.18.6_@babel+core@7.18.9
-      babel-jest: 27.5.1_@babel+core@7.18.9
-      identity-obj-proxy: 3.0.0
-      isomorphic-unfetch: 3.1.0
-      jest: 26.6.3
-      jest-watch-typeahead: 0.6.5_jest@26.6.3
-      preact: 10.11.3
-      preact-render-to-string: 5.2.6_preact@10.11.3
-    transitivePeerDependencies:
-      - encoding
-      - supports-color
-    dev: true
-
   /jest-regex-util/26.0.0:
     resolution: {integrity: 
sha512-Gv3ZIs/nA48/Zvjrl34bf+oD76JHiGDUxNOVgUjh3j890sblXryjY4rss71fPtD/njchl6PSE2hIhvyWa1eT0A==}
     engines: {node: '>= 10.14.2'}

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