gnunet-svn
[Top][All Lists]
Advanced

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

[taler-wallet-core] 05/08: work in progress, new api being used. merchan


From: gnunet
Subject: [taler-wallet-core] 05/08: work in progress, new api being used. merchant now should move into using the full API
Date: Tue, 26 Mar 2024 20:58:52 +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 e2bfbced7ab027c901913e83ff7dd82240661990
Author: Sebastian <sebasjm@gmail.com>
AuthorDate: Fri Mar 22 13:56:16 2024 -0300

    work in progress, new api being used. merchant now should move into using 
the full API
---
 packages/merchant-backoffice-ui/src/Routing.tsx    |  16 +-
 .../src/components/form/InputCurrency.tsx          |   4 +-
 .../instance/DefaultInstanceFormFields.tsx         |  14 +-
 .../src/components/menu/SideBar.tsx                |  52 +-
 .../src/components/menu/index.tsx                  |  21 +-
 .../src/components/modal/index.tsx                 |  14 +-
 .../src/components/product/ProductForm.tsx         |  25 +-
 .../src/context/backend.test.ts                    | 163 -------
 .../merchant-backoffice-ui/src/context/backend.ts  |  69 ---
 .../merchant-backoffice-ui/src/context/config.ts   |  29 --
 .../src/{hooks => context}/session.ts              | 151 ++++--
 .../merchant-backoffice-ui/src/declaration.d.ts    |   3 -
 .../merchant-backoffice-ui/src/hooks/backend.ts    | 245 +++++-----
 .../src/hooks/instance.test.ts                     |   3 +-
 .../merchant-backoffice-ui/src/hooks/instance.ts   |  87 ++--
 .../merchant-backoffice-ui/src/hooks/preference.ts |   3 +-
 .../merchant-backoffice-ui/src/hooks/testing.tsx   |  24 +-
 .../src/paths/admin/create/Create.stories.tsx      |  31 +-
 .../src/paths/admin/create/index.tsx               |  48 +-
 .../src/paths/admin/create/stories.tsx             |  33 +-
 .../src/paths/admin/list/TableActive.tsx           |   4 +-
 .../src/paths/instance/details/index.tsx           |   6 +-
 .../src/paths/instance/details/stories.tsx         |  33 +-
 .../paths/instance/orders/create/CreatePage.tsx    | 525 ++++++++++++---------
 .../paths/instance/orders/details/DetailPage.tsx   |  13 +-
 .../src/paths/instance/orders/list/Table.tsx       |  35 +-
 .../instance/otp_devices/create/CreatePage.tsx     |   3 +-
 .../otp_devices/create/CreatedSuccessfully.tsx     |  17 +-
 .../paths/instance/templates/create/CreatePage.tsx | 218 +++++----
 .../src/paths/instance/templates/qr/QrPage.tsx     |  34 +-
 .../paths/instance/templates/update/UpdatePage.tsx |  14 +-
 .../src/paths/instance/token/DetailPage.tsx        |  49 +-
 .../src/paths/instance/token/index.tsx             |  12 +-
 .../paths/instance/transfers/create/CreatePage.tsx |   4 +-
 .../src/paths/instance/update/UpdatePage.tsx       |   8 +-
 .../src/paths/instance/update/index.tsx            |   4 +-
 .../paths/instance/webhooks/create/CreatePage.tsx  |   6 +-
 .../paths/instance/webhooks/update/UpdatePage.tsx  |   1 -
 .../src/paths/login/index.tsx                      |  82 +++-
 .../src/paths/settings/index.tsx                   | 206 ++++----
 .../taler-util/src/http-client/authentication.ts   |  30 ++
 packages/taler-util/src/http-client/merchant.ts    |  14 +-
 packages/taler-util/src/http-client/types.ts       |  17 +
 43 files changed, 1264 insertions(+), 1106 deletions(-)

diff --git a/packages/merchant-backoffice-ui/src/Routing.tsx 
b/packages/merchant-backoffice-ui/src/Routing.tsx
index 50a91c060..4ed5850e7 100644
--- a/packages/merchant-backoffice-ui/src/Routing.tsx
+++ b/packages/merchant-backoffice-ui/src/Routing.tsx
@@ -39,7 +39,10 @@ import { MerchantBackend } from "./declaration.js";
 import { useInstanceBankAccounts } from "./hooks/bank.js";
 import { useInstanceKYCDetails } from "./hooks/instance.js";
 import { usePreference } from "./hooks/preference.js";
-import { DEFAULT_ADMIN_USERNAME, useSessionState } from "./hooks/session.js";
+import {
+  DEFAULT_ADMIN_USERNAME,
+  useSessionContext,
+} from "./context/session.js";
 import InstanceCreatePage from "./paths/admin/create/index.js";
 import InstanceListPage from "./paths/admin/list/index.js";
 import BankAccountCreatePage from "./paths/instance/accounts/create/index.js";
@@ -74,6 +77,7 @@ import { LoginPage } from "./paths/login/index.js";
 import NotFoundPage from "./paths/notfound/index.js";
 import { Settings } from "./paths/settings/index.js";
 import { Notification } from "./utils/types.js";
+import { createHashHistory } from "history";
 
 export enum InstancePaths {
   error = "/error",
@@ -138,9 +142,10 @@ export const publicPages = {
   go: urlPattern(/\/home/, () => "#/home"),
 };
 
+const history = createHashHistory();
 export function Routing(_p: Props): VNode {
   const { i18n } = useTranslationContext();
-  const { state } = useSessionState();
+  const { state } = useSessionContext();
 
   type GlobalNotifState =
     | (Notification & { to: string | undefined })
@@ -152,8 +157,10 @@ export function Routing(_p: Props): VNode {
 
   const instance = useInstanceBankAccounts();
   const accounts = !instance.ok ? undefined : instance.data.accounts;
-  const shouldWarnAboutMissingBankAccounts = !state.isAdmin && accounts !== 
undefined  && accounts.length < 1
-  const shouldLogin = state.status === "loggedOut" || state.status === 
"expired";
+  const shouldWarnAboutMissingBankAccounts =
+    !state.isAdmin && accounts !== undefined && accounts.length < 1;
+  const shouldLogin =
+    state.status === "loggedOut" || state.status === "expired";
 
   function ServerErrorRedirectTo(to: InstancePaths | AdminPaths) {
     return function ServerErrorRedirectToImpl(
@@ -275,6 +282,7 @@ export function Routing(_p: Props): VNode {
       )}
 
       <Router
+        history={history}
         onChange={(e) => {
           const movingOutFromNotification =
             globalNotification && e.url !== globalNotification.to;
diff --git 
a/packages/merchant-backoffice-ui/src/components/form/InputCurrency.tsx 
b/packages/merchant-backoffice-ui/src/components/form/InputCurrency.tsx
index c1359e641..f60508504 100644
--- a/packages/merchant-backoffice-ui/src/components/form/InputCurrency.tsx
+++ b/packages/merchant-backoffice-ui/src/components/form/InputCurrency.tsx
@@ -18,8 +18,8 @@
  *
  * @author Sebastian Javier Marchano (sebasjm)
  */
+import { useMerchantApiContext } from "@gnu-taler/web-util/browser";
 import { ComponentChildren, h, VNode } from "preact";
-import { useConfigContext } from "../../context/config.js";
 import { Amount } from "../../declaration.js";
 import { InputWithAddon } from "./InputWithAddon.js";
 import { InputProps } from "./useField.js";
@@ -43,7 +43,7 @@ export function InputCurrency<T>({
   children,
   side,
 }: Props<keyof T>): VNode {
-  const config = useConfigContext();
+  const { config } = useMerchantApiContext();
   return (
     <InputWithAddon<T>
       name={name}
diff --git 
a/packages/merchant-backoffice-ui/src/components/instance/DefaultInstanceFormFields.tsx
 
b/packages/merchant-backoffice-ui/src/components/instance/DefaultInstanceFormFields.tsx
index e36549e76..cb4442897 100644
--- 
a/packages/merchant-backoffice-ui/src/components/instance/DefaultInstanceFormFields.tsx
+++ 
b/packages/merchant-backoffice-ui/src/components/instance/DefaultInstanceFormFields.tsx
@@ -19,9 +19,11 @@
  * @author Sebastian Javier Marchano (sebasjm)
  */
 
-import { useTranslationContext } from "@gnu-taler/web-util/browser";
-import { Fragment, h, VNode } from "preact";
-import { useBackendContext } from "../../context/backend.js";
+import {
+  useTranslationContext
+} from "@gnu-taler/web-util/browser";
+import { Fragment, VNode, h } from "preact";
+import { useSessionContext } from "../../context/session.js";
 import { Entity } from "../../paths/admin/create/CreatePage.js";
 import { Input } from "../form/Input.js";
 import { InputDuration } from "../form/InputDuration.js";
@@ -40,13 +42,15 @@ export function DefaultInstanceFormFields({
   showId: boolean;
 }): VNode {
   const { i18n } = useTranslationContext();
-  const { url: backendURL } = useBackendContext()
+  const {
+    state: { backendUrl },
+  } = useSessionContext();
   return (
     <Fragment>
       {showId && (
         <InputWithAddon<Entity>
           name="id"
-          addonBefore={`${backendURL}/instances/`}
+          addonBefore={new URL("instances/", backendUrl).href}
           readonly={readonlyId}
           label={i18n.str`Identifier`}
           tooltip={i18n.str`Name of the instance in URLs. The 'default' 
instance is special in that it is used to administer other instances.`}
diff --git a/packages/merchant-backoffice-ui/src/components/menu/SideBar.tsx 
b/packages/merchant-backoffice-ui/src/components/menu/SideBar.tsx
index a9b9618bb..adc47b216 100644
--- a/packages/merchant-backoffice-ui/src/components/menu/SideBar.tsx
+++ b/packages/merchant-backoffice-ui/src/components/menu/SideBar.tsx
@@ -19,12 +19,14 @@
  * @author Sebastian Javier Marchano (sebasjm)
  */
 
-import { useMerchantApiContext, useTranslationContext } from 
"@gnu-taler/web-util/browser";
-import { Fragment, h, VNode } from "preact";
-import { useConfigContext } from "../../context/config.js";
+import {
+  useMerchantApiContext,
+  useTranslationContext,
+} from "@gnu-taler/web-util/browser";
+import { Fragment, VNode, h } from "preact";
+import { useSessionContext } from "../../context/session.js";
 import { useInstanceKYCDetails } from "../../hooks/instance.js";
 import { LangSelector } from "./LangSelector.js";
-import { useSessionState } from "../../hooks/session.js";
 
 // const GIT_HASH = typeof __GIT_HASH__ !== "undefined" ? __GIT_HASH__ : 
undefined;
 const VERSION = typeof __VERSION__ !== "undefined" ? __VERSION__ : undefined;
@@ -33,20 +35,21 @@ interface Props {
   mobile?: boolean;
 }
 
-export function Sidebar({
-  mobile,
-}: Props): VNode {
-  const config = useConfigContext();
-  // const { url: backendURL } = useBackendContext()
+export function Sidebar({ mobile }: Props): VNode {
   const { i18n } = useTranslationContext();
   const kycStatus = useInstanceKYCDetails();
   const needKYC = kycStatus.ok && kycStatus.data.type === "redirect";
-  const { state, logOut } = useSessionState();
-  const { url } = useMerchantApiContext();
-  const isLoggedIn = state.status === "loggedIn" || state.status === 
"impersonate"
-  const hasToken = isLoggedIn && state.token !== undefined
+  const { state, logOut } = useSessionContext();
+  const isLoggedIn = state.status === "loggedIn";
+  const hasToken = isLoggedIn && state.token !== undefined;
+  const backendURL = state.backendUrl;
+  const { config } = useMerchantApiContext();
+
   return (
-    <aside class="aside is-placed-left is-expanded" style={{ overflowY: 
"scroll" }}>
+    <aside
+      class="aside is-placed-left is-expanded"
+      style={{ overflowY: "scroll" }}
+    >
       {mobile && (
         <div
           class="footer"
@@ -187,9 +190,10 @@ export function Sidebar({
         </p>
         <ul class="menu-list">
           <li>
-            <a class="has-icon is-state-info is-hoverable"
+            <a
+              class="has-icon is-state-info is-hoverable"
               onClick={(e): void => {
-                e.preventDefault()
+                e.preventDefault();
               }}
             >
               <span class="icon">
@@ -206,7 +210,7 @@ export function Sidebar({
                 <i class="mdi mdi-web" />
               </span>
               <span class="menu-item-label">
-                {url.hostname}
+                {new URL(backendURL).hostname}
               </span>
             </div>
           </li>
@@ -215,12 +219,10 @@ export function Sidebar({
               <span style={{ width: "3rem" }} class="icon">
                 ID
               </span>
-              <span class="menu-item-label">
-                {state.instance}
-              </span>
+              <span class="menu-item-label">{state.instance}</span>
             </div>
           </li>
-          {state.isAdmin && state.status !== "impersonate" && (
+          {state.isAdmin && (
             <Fragment>
               <p class="menu-label">
                 <i18n.Translate>Instances</i18n.Translate>
@@ -247,12 +249,12 @@ export function Sidebar({
               </li>
             </Fragment>
           )}
-          {hasToken ?
+          {hasToken ? (
             <li>
               <a
                 class="has-icon is-state-info is-hoverable"
                 onClick={(e): void => {
-                  logOut()
+                  logOut();
                   e.preventDefault();
                 }}
               >
@@ -263,8 +265,8 @@ export function Sidebar({
                   <i18n.Translate>Log out</i18n.Translate>
                 </span>
               </a>
-            </li> : undefined
-          }
+            </li>
+          ) : undefined}
         </ul>
       </div>
     </aside>
diff --git a/packages/merchant-backoffice-ui/src/components/menu/index.tsx 
b/packages/merchant-backoffice-ui/src/components/menu/index.tsx
index fa2de563e..aa955db4e 100644
--- a/packages/merchant-backoffice-ui/src/components/menu/index.tsx
+++ b/packages/merchant-backoffice-ui/src/components/menu/index.tsx
@@ -21,7 +21,7 @@ import { InstancePaths } from "../../Routing.js";
 import { Notification } from "../../utils/types.js";
 import { NavigationBar } from "./NavigationBar.js";
 import { Sidebar } from "./SideBar.js";
-import { useSessionState } from "../../hooks/session.js";
+import { useSessionContext } from "../../context/session.js";
 import { useNavigationContext } from "@gnu-taler/web-util/browser";
 
 function getInstanceTitle(path: string, id: string): string {
@@ -97,15 +97,14 @@ function WithTitle({
 export function Menu(_p: MenuProps): VNode {
   const [mobileOpen, setMobileOpen] = useState(false);
 
-  const { state, logIn } = useSessionState();
+  const { state, deImpersonate } = useSessionContext();
   const { path } = useNavigationContext();
 
   const titleWithSubtitle = !state.isAdmin
     ? getInstanceTitle(path, state.instance)
     : getAdminTitle(path, state.instance);
 
-  const isLoggedIn =
-    state.status === "loggedIn" || state.status === "impersonate";
+  const isLoggedIn =state.status === "loggedIn";
 
   return (
     <WithTitle title={titleWithSubtitle}>
@@ -119,10 +118,10 @@ export function Menu(_p: MenuProps): VNode {
         />
 
         {isLoggedIn && (
-          <Sidebar mobile={mobileOpen} mimic={state.status === "impersonate"} 
/>
+          <Sidebar mobile={mobileOpen} />
         )}
 
-        {state.status === "impersonate" && (
+        {state.status !== "loggedOut" && state.impersonate !== undefined && (
           <nav
             class="level"
             style={{
@@ -139,10 +138,7 @@ export function Menu(_p: MenuProps): VNode {
                 <a
                   href="#/instances"
                   onClick={(e) => {
-                    logIn({
-                      instance: state.originalInstance,
-                      token: state.originalToken,
-                    });
+                    deImpersonate();
                     e.preventDefault();
                   }}
                 >
@@ -227,14 +223,13 @@ export function NotConnectedAppMenu({
 
 export function NotYetReadyAppMenu({ title }: NotYetReadyAppMenuProps): VNode {
   const [mobileOpen, setMobileOpen] = useState(false);
-  const { state } = useSessionState();
+  const { state } = useSessionContext();
 
   useEffect(() => {
     document.title = `Taler Backoffice: ${title}`;
   }, [title]);
   
-  const isLoggedIn =
-    state.status === "loggedIn" || state.status === "impersonate";
+  const isLoggedIn = state.status === "loggedIn";
 
   return (
     <div
diff --git a/packages/merchant-backoffice-ui/src/components/modal/index.tsx 
b/packages/merchant-backoffice-ui/src/components/modal/index.tsx
index c684ba7a3..1335d0f77 100644
--- a/packages/merchant-backoffice-ui/src/components/modal/index.tsx
+++ b/packages/merchant-backoffice-ui/src/components/modal/index.tsx
@@ -22,11 +22,11 @@
 import { useTranslationContext } from "@gnu-taler/web-util/browser";
 import { ComponentChildren, Fragment, h, VNode } from "preact";
 import { useState } from "preact/hooks";
-import { useInstanceContext } from "../../context/instance.js";
 import { DEFAULT_REQUEST_TIMEOUT } from "../../utils/constants.js";
 import { Spinner } from "../exception/loading.js";
 import { FormProvider } from "../form/FormProvider.js";
 import { Input } from "../form/Input.js";
+import { useSessionContext } from "../../context/session.js";
 
 interface Props {
   active?: boolean;
@@ -298,8 +298,8 @@ export function UpdateTokenModal({
     new_token: !form.new_token
       ? i18n.str`cannot be empty`
       : form.new_token === form.old_token
-      ? i18n.str`cannot be the same as the old token`
-      : undefined,
+        ? i18n.str`cannot be the same as the old token`
+        : undefined,
     repeat_token:
       form.new_token !== form.repeat_token
         ? i18n.str`is not the same`
@@ -310,9 +310,9 @@ export function UpdateTokenModal({
     (k) => (errors as any)[k] !== undefined,
   );
 
-  const instance = useInstanceContext();
+  const { state } = useSessionContext();
 
-  const text = i18n.str`You are updating the access token from instance with 
id ${instance.id}`;
+  const text = i18n.str`You are updating the access token from instance with 
id ${state.instance}`;
 
   return (
     <ClearConfirmModal
@@ -374,8 +374,8 @@ export function SetTokenNewInstanceModal({
     new_token: !form.new_token
       ? i18n.str`cannot be empty`
       : form.new_token === form.old_token
-      ? i18n.str`cannot be the same as the old access token`
-      : undefined,
+        ? i18n.str`cannot be the same as the old access token`
+        : undefined,
     repeat_token:
       form.new_token !== form.repeat_token
         ? i18n.str`is not the same`
diff --git 
a/packages/merchant-backoffice-ui/src/components/product/ProductForm.tsx 
b/packages/merchant-backoffice-ui/src/components/product/ProductForm.tsx
index 47e3431e2..11344cde3 100644
--- a/packages/merchant-backoffice-ui/src/components/product/ProductForm.tsx
+++ b/packages/merchant-backoffice-ui/src/components/product/ProductForm.tsx
@@ -19,11 +19,13 @@
  * @author Sebastian Javier Marchano (sebasjm)
  */
 
-import { useTranslationContext } from "@gnu-taler/web-util/browser";
+import {
+  useMerchantApiContext,
+  useTranslationContext,
+} from "@gnu-taler/web-util/browser";
 import { h } from "preact";
 import { useCallback, useEffect, useState } from "preact/hooks";
 import * as yup from "yup";
-import { useBackendContext } from "../../context/backend.js";
 import { MerchantBackend } from "../../declaration.js";
 import {
   ProductCreateSchema as createSchema,
@@ -37,6 +39,7 @@ import { InputNumber } from "../form/InputNumber.js";
 import { InputStock, Stock } from "../form/InputStock.js";
 import { InputTaxes } from "../form/InputTaxes.js";
 import { InputWithAddon } from "../form/InputWithAddon.js";
+import { useSessionContext } from "../../context/session.js";
 
 type Entity = MerchantBackend.Products.ProductDetail & { product_id: string };
 
@@ -58,12 +61,12 @@ export function ProductForm({ onSubscribe, initial, 
alreadyExist }: Props) {
       !initial || initial.total_stock === -1
         ? undefined
         : {
-          current: initial.total_stock || 0,
-          lost: initial.total_lost || 0,
-          sold: initial.total_sold || 0,
-          address: initial.address,
-          nextRestock: initial.next_restock,
-        },
+            current: initial.total_stock || 0,
+            lost: initial.total_lost || 0,
+            sold: initial.total_sold || 0,
+            address: initial.address,
+            nextRestock: initial.next_restock,
+          },
   });
   let errors: FormErrors<Entity> = {};
 
@@ -114,7 +117,9 @@ export function ProductForm({ onSubscribe, initial, 
alreadyExist }: Props) {
     onSubscribe(hasErrors ? undefined : submit);
   }, [submit, hasErrors]);
 
-  const { url: backendURL } = useBackendContext()
+  const {
+    state: { backendUrl },
+  } = useSessionContext();
   const { i18n } = useTranslationContext();
 
   return (
@@ -128,7 +133,7 @@ export function ProductForm({ onSubscribe, initial, 
alreadyExist }: Props) {
         {alreadyExist ? undefined : (
           <InputWithAddon<Entity>
             name="product_id"
-            addonBefore={`${backendURL}/product/`}
+            addonBefore={new URL("product/", backendUrl).href}
             label={i18n.str`ID`}
             tooltip={i18n.str`product identification to use in URLs (for 
internal use only)`}
           />
diff --git a/packages/merchant-backoffice-ui/src/context/backend.test.ts 
b/packages/merchant-backoffice-ui/src/context/backend.test.ts
deleted file mode 100644
index 74530e750..000000000
--- a/packages/merchant-backoffice-ui/src/context/backend.test.ts
+++ /dev/null
@@ -1,163 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2021-2024 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 tests from "@gnu-taler/web-util/testing";
-import { ComponentChildren, h, VNode } from "preact";
-import { AccessToken, 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_NEW_LOGIN,
-  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,
-          });
-          env.addRequestExpectation(API_NEW_LOGIN, {
-            auth: "another_token",
-            request: {
-              scope: "write",
-              duration: {
-                "d_us": "forever",
-              },
-              refreshable: true,
-            },
-            
-          });
-
-          management.setNewAccessToken(undefined,"another_token" as 
AccessToken);
-        },
-        ({ 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,
-          });
-          env.addRequestExpectation(API_NEW_LOGIN, {
-            auth: "another_token",
-            request: {
-              scope: "write",
-              duration: {
-                "d_us": "forever",
-              },
-              refreshable: true,
-            },            
-          });
-          instance.setNewAccessToken(undefined, "another_token" as 
AccessToken);
-        },
-        ({ 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
deleted file mode 100644
index f78236216..000000000
--- a/packages/merchant-backoffice-ui/src/context/backend.ts
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2021-2024 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 { useMemoryStorage } from "@gnu-taler/web-util/browser";
-import { createContext, h, VNode } from "preact";
-import { useContext } from "preact/hooks";
-import { LoginToken } from "../declaration.js";
-import { useBackendDefaultToken, useBackendURL } from "../hooks/index.js";
-
-interface BackendContextType {
-  url: string,
-  alreadyTriedLogin: boolean;
-  token?: LoginToken;
-  updateToken: (token: LoginToken | undefined) => void;
-}
-
-const BackendContext = createContext<BackendContextType>({
-  url: "",
-  alreadyTriedLogin: false,
-  token: undefined,
-  updateToken: () => null,
-});
-
-function useBackendContextState(
-  defaultUrl?: string,
-): BackendContextType {
-  const [url] = useBackendURL(defaultUrl);
-  const [token, updateToken] = useBackendDefaultToken();
-
-  return {
-    url,
-    token,
-    alreadyTriedLogin: token !== undefined,
-    updateToken,
-  };
-}
-
-const BackendContextProvider = ({
-  children,
-  defaultUrl,
-}: {
-  children: any;
-  defaultUrl?: string;
-}): VNode => {
-  const value = useBackendContextState(defaultUrl);
-
-  return h(BackendContext.Provider, { value, children });
-};
-
-const useBackendContext = (): BackendContextType =>
-  useContext(BackendContext);
diff --git a/packages/merchant-backoffice-ui/src/context/config.ts 
b/packages/merchant-backoffice-ui/src/context/config.ts
deleted file mode 100644
index 8c562b3c1..000000000
--- a/packages/merchant-backoffice-ui/src/context/config.ts
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2021-2024 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 { createContext } from "preact";
-import { useContext } from "preact/hooks";
-import { MerchantBackend } from "../declaration.js";
-
-const Context = createContext<MerchantBackend.VersionResponse>(null!);
-
-export const ConfigContextProvider = Context.Provider;
-export const useConfigContext = (): MerchantBackend.VersionResponse => 
useContext(Context);
diff --git a/packages/merchant-backoffice-ui/src/hooks/session.ts 
b/packages/merchant-backoffice-ui/src/context/session.ts
similarity index 57%
rename from packages/merchant-backoffice-ui/src/hooks/session.ts
rename to packages/merchant-backoffice-ui/src/context/session.ts
index 8bf075e94..83f3f113a 100644
--- a/packages/merchant-backoffice-ui/src/hooks/session.ts
+++ b/packages/merchant-backoffice-ui/src/context/session.ts
@@ -21,40 +21,45 @@ import {
   buildCodecForUnion,
   codecForBoolean,
   codecForConstString,
-  codecForConstTrue,
   codecForString,
   codecOptional,
 } from "@gnu-taler/taler-util";
-import { buildStorageKey, useLocalStorage, useMerchantApiContext } from 
"@gnu-taler/web-util/browser";
+import {
+  buildStorageKey,
+  useLocalStorage,
+  useMerchantApiContext,
+} from "@gnu-taler/web-util/browser";
 import { mutate } from "swr";
 
 /**
  * Has the information to reach and
  * authenticate at the bank's backend.
  */
-export type SessionState = LoggedIn | LoggedOut | Expired | Impersonate;
+export type SessionState = LoggedIn | LoggedOut | Expired;
 
 interface LoggedIn {
   status: "loggedIn";
-  instance: string;
+  backendUrl: string;
   isAdmin: boolean;
+  instance: string;
   token: AccessToken | undefined;
+  impersonate: Impersonate | undefined;
+}
+interface Impersonate {
+  originalInstance: string;
+  originalToken: AccessToken | undefined;
+  originalBackendUrl: string;
 }
 interface Expired {
   status: "expired";
-  instance: string;
+  backendUrl: string;
   isAdmin: boolean;
-}
-interface Impersonate {
-  status: "impersonate";
   instance: string;
-  isAdmin: true;
-  token: AccessToken | undefined;
-  originalInstance: string;
-  originalToken: AccessToken | undefined;
+  impersonate: Impersonate | undefined;
 }
 interface LoggedOut {
   status: "loggedOut";
+  backendUrl: string;
   instance: string;
   isAdmin: boolean;
 }
@@ -62,7 +67,9 @@ interface LoggedOut {
 export const codecForSessionStateLoggedIn = (): Codec<LoggedIn> =>
   buildCodecForObject<LoggedIn>()
     .property("status", codecForConstString("loggedIn"))
+    .property("backendUrl", codecForString())
     .property("instance", codecForString())
+    .property("impersonate", codecOptional(codecForImpresonate()))
     .property("token", codecOptional(codecForString() as Codec<AccessToken>))
     .property("isAdmin", codecForBoolean())
     .build("SessionState.LoggedIn");
@@ -70,54 +77,87 @@ export const codecForSessionStateLoggedIn = (): 
Codec<LoggedIn> =>
 export const codecForSessionStateExpired = (): Codec<Expired> =>
   buildCodecForObject<Expired>()
     .property("status", codecForConstString("expired"))
+    .property("backendUrl", codecForString())
     .property("instance", codecForString())
+    .property("impersonate", codecOptional(codecForImpresonate()))
     .property("isAdmin", codecForBoolean())
     .build("SessionState.Expired");
 
 export const codecForSessionStateLoggedOut = (): Codec<LoggedOut> =>
   buildCodecForObject<LoggedOut>()
     .property("status", codecForConstString("loggedOut"))
+    .property("backendUrl", codecForString())
     .property("instance", codecForString())
     .property("isAdmin", codecForBoolean())
     .build("SessionState.LoggedOut");
 
-export const codecForSessionStateImpresonate = (): Codec<Impersonate> =>
+export const codecForImpresonate = (): Codec<Impersonate> =>
   buildCodecForObject<Impersonate>()
-    .property("status", codecForConstString("impersonate"))
-    .property("instance", codecForString())
-    .property("isAdmin", codecForConstTrue())
-    .property("token", codecOptional(codecForString() as Codec<AccessToken>))
     .property("originalInstance", codecForString())
-    .property("originalToken", codecOptional(codecForString() as 
Codec<AccessToken>))
+    .property(
+      "originalToken",
+      codecOptional(codecForString() as Codec<AccessToken>),
+    )
+    .property("originalBackendUrl", codecForString())
     .build("SessionState.Impersonate");
 
 export const codecForSessionState = (): Codec<SessionState> =>
   buildCodecForUnion<SessionState>()
     .discriminateOn("status")
     .alternative("loggedIn", codecForSessionStateLoggedIn())
-    .alternative("impersonate", codecForSessionStateImpresonate())
     .alternative("loggedOut", codecForSessionStateLoggedOut())
     .alternative("expired", codecForSessionStateExpired())
     .build("SessionState");
 
-export const defaultState = (instance: string): SessionState => ({
-  status: "loggedIn",
-  instance,
-  isAdmin: instance === DEFAULT_ADMIN_USERNAME,
-  token: undefined,
-});
+function inferInstanceName(url: URL) {
+  const match = INSTANCE_ID_LOOKUP.exec(url.href);
+  return !match || !match[1] ? DEFAULT_ADMIN_USERNAME : match[1];
+}
+
+export const defaultState = (url: URL): SessionState => {
+  const instance = inferInstanceName(url);
+  return {
+    status: "loggedIn",
+    instance,
+    backendUrl: url.href,
+    isAdmin: instance === DEFAULT_ADMIN_USERNAME,
+    token: undefined,
+    impersonate: undefined,
+  };
+};
 
 export interface SessionStateHandler {
   state: SessionState;
+  /**
+   * from every state to logout state
+   */
   logOut(): void;
+  /**
+   * from impersonate to loggedIn
+   */
+  deImpersonate(): void;
+  /**
+   * from non-loggedOut state to expired
+   */
   expired(): void;
-  logIn(info: { instance: string; token?: AccessToken }): void;
+  /**
+   * from any to loggedIn
+   * @param info
+   */
+  logIn(info: { token?: AccessToken }): void;
+  /**
+   * from loggedIn to impersonate
+   * @param info
+   */
   impersonate(info: { instance: string; token?: AccessToken }): void;
 }
 
-const SESSION_STATE_KEY = buildStorageKey("merchant-session", 
codecForSessionState());
+const SESSION_STATE_KEY = buildStorageKey(
+  "merchant-session",
+  codecForSessionState(),
+);
 
-export const DEFAULT_ADMIN_USERNAME = "default"
+export const DEFAULT_ADMIN_USERNAME = "default";
 
 export const INSTANCE_ID_LOOKUP = /\/instances\/([^/]*)\/?$/;
 
@@ -126,21 +166,43 @@ export const INSTANCE_ID_LOOKUP = 
/\/instances\/([^/]*)\/?$/;
  * login credentials and backend's
  * base URL.
  */
-export function useSessionState(): SessionStateHandler {
+export function useSessionContext(): SessionStateHandler {
   const { url } = useMerchantApiContext();
 
-  const match = INSTANCE_ID_LOOKUP.exec(url.href);
-  const instanceName = !match || !match[1] ? DEFAULT_ADMIN_USERNAME : match[1];
-
   const { value: state, update } = useLocalStorage(
     SESSION_STATE_KEY,
-    defaultState(instanceName),
+    defaultState(url),
   );
 
   return {
     state,
     logOut() {
-      update(defaultState(instanceName));
+      const instance = inferInstanceName(url);
+      const nextState: SessionState = {
+        status: "loggedOut",
+        backendUrl: url.href,
+        instance,
+        isAdmin: instance === DEFAULT_ADMIN_USERNAME,
+      };
+      update(nextState);
+    },
+    deImpersonate() {
+      if (state.status === "loggedOut" || state.status === "expired") {
+        // can't impersonate if not loggedin
+        return;
+      }
+      if (state.impersonate === undefined) {
+        return;
+      }
+      const nextState: SessionState = {
+        status: "loggedIn",
+        backendUrl: state.impersonate.originalBackendUrl,
+        isAdmin: state.impersonate.originalInstance === DEFAULT_ADMIN_USERNAME,
+        instance: state.impersonate.originalInstance,
+        token: state.impersonate.originalToken,
+        impersonate: undefined,
+      };
+      update(nextState);
     },
     impersonate(info) {
       if (state.status === "loggedOut" || state.status === "expired") {
@@ -148,31 +210,36 @@ export function useSessionState(): SessionStateHandler {
         return;
       }
       const nextState: SessionState = {
-        status: "impersonate",
-        originalToken: state.token,
-        originalInstance: state.instance,
-        isAdmin: true,
+        status: "loggedIn",
+        backendUrl: new URL(`instances/${info.instance}`, state.backendUrl)
+          .href,
+        isAdmin: info.instance === DEFAULT_ADMIN_USERNAME,
         instance: info.instance,
         token: info.token,
+        impersonate: {
+          originalBackendUrl: state.backendUrl,
+          originalToken: state.token,
+          originalInstance: state.instance,
+        },
       };
       update(nextState);
     },
     expired() {
       if (state.status === "loggedOut") return;
+
       const nextState: SessionState = {
+        ...state,
         status: "expired",
-        instance: state.instance,
-        isAdmin: state.instance === DEFAULT_ADMIN_USERNAME,
       };
       update(nextState);
     },
     logIn(info) {
       // admin is defined by the username
       const nextState: SessionState = {
+        impersonate: undefined,
+        ...state,
         status: "loggedIn",
-        instance: info.instance,
         token: info.token,
-        isAdmin: state.instance === DEFAULT_ADMIN_USERNAME,
       };
       update(nextState);
       cleanAllCache();
diff --git a/packages/merchant-backoffice-ui/src/declaration.d.ts 
b/packages/merchant-backoffice-ui/src/declaration.d.ts
index ff526282a..e39257a79 100644
--- a/packages/merchant-backoffice-ui/src/declaration.d.ts
+++ b/packages/merchant-backoffice-ui/src/declaration.d.ts
@@ -117,9 +117,6 @@ interface LoginToken {
 // token used to get loginToken
 // must forget after used
 declare const __ac_token: unique symbol;
-type AccessToken = string & {
-  [__ac_token]: true;
-};
 
 export namespace ExchangeBackend {
   interface WireResponse {
diff --git a/packages/merchant-backoffice-ui/src/hooks/backend.ts 
b/packages/merchant-backoffice-ui/src/hooks/backend.ts
index 4305a9309..37dfd8fd6 100644
--- a/packages/merchant-backoffice-ui/src/hooks/backend.ts
+++ b/packages/merchant-backoffice-ui/src/hooks/backend.ts
@@ -19,8 +19,13 @@
  * @author Sebastian Javier Marchano (sebasjm)
  */
 
-import { AbsoluteTime, HttpStatusCode } from "@gnu-taler/taler-util";
 import {
+  AbsoluteTime,
+  AccessToken,
+  HttpStatusCode,
+} from "@gnu-taler/taler-util";
+import {
+  EmptyObject,
   ErrorType,
   HttpError,
   HttpResponse,
@@ -31,10 +36,8 @@ import {
 } from "@gnu-taler/web-util/browser";
 import { useCallback, useEffect, useState } from "preact/hooks";
 import { useSWRConfig } from "swr";
-import { useBackendContext } from "../context/backend.js";
-import { useInstanceContext } from "../context/instance.js";
-import { AccessToken, LoginToken, MerchantBackend, Timestamp } from 
"../declaration.js";
-
+import { useSessionContext } from "../context/session.js";
+import { LoginToken, MerchantBackend, Timestamp } from "../declaration.js";
 
 export function useMatchMutate(): (
   re?: RegExp,
@@ -49,18 +52,22 @@ export function useMatchMutate(): (
   }
 
   return function matchRegexMutate(re?: RegExp) {
-    return mutate((key) => {
-      // evict if no key or regex === all
-      if (!key || !re) return true
-      // match string
-      if (typeof key === 'string' && re.test(key)) return true
-      // record or object have the path at [0]
-      if (typeof key === 'object' && re.test(key[0])) return true
-      //key didn't match regex
-      return false
-    }, undefined, {
-      revalidate: true,
-    });
+    return mutate(
+      (key) => {
+        // evict if no key or regex === all
+        if (!key || !re) return true;
+        // match string
+        if (typeof key === "string" && re.test(key)) return true;
+        // record or object have the path at [0]
+        if (typeof key === "object" && re.test(key[0])) return true;
+        //key didn't match regex
+        return false;
+      },
+      undefined,
+      {
+        revalidate: true,
+      },
+    );
   };
 }
 
@@ -97,30 +104,36 @@ export function useBackendConfig(): HttpResponse<
   const { request } = useBackendBaseRequest();
 
   type Type = MerchantBackend.VersionResponse;
-  type State = { data: HttpResponse<Type, 
RequestError<MerchantBackend.ErrorDetail>>, timer: number }
-  const [result, setResult] = useState<State>({ data: { loading: true }, 
timer: 0 });
+  type State = {
+    data: HttpResponse<Type, RequestError<MerchantBackend.ErrorDetail>>;
+    timer: number;
+  };
+  const [result, setResult] = useState<State>({
+    data: { loading: true },
+    timer: 0,
+  });
 
   useEffect(() => {
     if (result.timer) {
-      clearTimeout(result.timer)
+      clearTimeout(result.timer);
     }
     function tryConfig(): void {
       request<Type>(`/config`)
         .then((data) => {
           const timer: any = setTimeout(() => {
-            tryConfig()
-          }, CHECK_CONFIG_INTERVAL_OK)
-          setResult({ data, timer })
+            tryConfig();
+          }, CHECK_CONFIG_INTERVAL_OK);
+          setResult({ data, timer });
         })
         .catch((error) => {
           const timer: any = setTimeout(() => {
-            tryConfig()
-          }, CHECK_CONFIG_INTERVAL_FAIL)
-          const data = error.cause
-          setResult({ data, timer })
+            tryConfig();
+          }, CHECK_CONFIG_INTERVAL_FAIL);
+          const data = error.cause;
+          setResult({ data, timer });
         });
     }
-    tryConfig()
+    tryConfig();
   }, [request]);
 
   return result.data;
@@ -134,29 +147,29 @@ interface useBackendInstanceRequestType {
   fetcher: <T>(endpoint: string) => Promise<HttpResponseOk<T>>;
   multiFetcher: <T>(params: [url: string[]]) => Promise<HttpResponseOk<T>[]>;
   orderFetcher: <T>(
-    params: [endpoint: string,
+    params: [
+      endpoint: string,
       paid?: YesOrNo,
       refunded?: YesOrNo,
       wired?: YesOrNo,
       searchDate?: Date,
-      delta?: number,]
+      delta?: number,
+    ],
   ) => Promise<HttpResponseOk<T>>;
   transferFetcher: <T>(
-    params: [endpoint: string,
+    params: [
+      endpoint: string,
       payto_uri?: string,
       verified?: string,
       position?: string,
-      delta?: number,]
+      delta?: number,
+    ],
   ) => Promise<HttpResponseOk<T>>;
   templateFetcher: <T>(
-    params: [endpoint: string,
-      position?: string,
-      delta?: number]
+    params: [endpoint: string, position?: string, delta?: number],
   ) => Promise<HttpResponseOk<T>>;
   webhookFetcher: <T>(
-    params: [endpoint: string,
-      position?: string,
-      delta?: number]
+    params: [endpoint: string, position?: string, delta?: number],
   ) => Promise<HttpResponseOk<T>>;
 }
 interface useBackendBaseRequestType {
@@ -167,14 +180,16 @@ interface useBackendBaseRequestType {
 }
 
 type YesOrNo = "yes" | "no";
-type LoginResult = {
-  valid: true;
-  token: string;
-  expiration: Timestamp;
-} | {
-  valid: false;
-  cause: HttpError<{}>;
-}
+type LoginResult =
+  | {
+      valid: true;
+      token: string;
+      expiration: Timestamp;
+    }
+  | {
+      valid: false;
+      cause: HttpError<EmptyObject>;
+    };
 
 export function useCredentialsChecker() {
   const { request } = useApiContext();
@@ -187,24 +202,34 @@ export function useCredentialsChecker() {
     const data: MerchantBackend.Instances.LoginTokenRequest = {
       scope: "write",
       duration: {
-        d_us: "forever"
+        d_us: "forever",
       },
       refreshable: true,
-    }
+    };
     try {
-      const response = await 
request<MerchantBackend.Instances.LoginTokenSuccessResponse>(baseUrl, 
`/private/token`, {
-        method: "POST",
-        token,
-        data
-      });
-      return { valid: true, token: response.data.token, expiration: 
response.data.expiration };
+      const response =
+        await request<MerchantBackend.Instances.LoginTokenSuccessResponse>(
+          baseUrl,
+          `/private/token`,
+          {
+            method: "POST",
+            token,
+            data,
+          },
+        );
+      return {
+        valid: true,
+        token: response.data.token,
+        expiration: response.data.expiration,
+      };
     } catch (error) {
       if (error instanceof RequestError) {
         return { valid: false, cause: error.cause };
       }
 
       return {
-        valid: false, cause: {
+        valid: false,
+        cause: {
           type: ErrorType.UNEXPECTED,
           loading: false,
           info: {
@@ -212,23 +237,28 @@ export function useCredentialsChecker() {
             status: 0,
             options: {},
             url: `/private/token`,
-            payload: {}
+            payload: {},
           },
           exception: error,
-          message: (error instanceof Error ? error.message : "unpexepected 
error")
-        }
+          message:
+            error instanceof Error ? error.message : "unpexepected error",
+        },
       };
     }
-  };
+  }
 
   async function refreshLoginToken(
     baseUrl: string,
-    token: LoginToken
+    token: LoginToken,
   ): Promise<LoginResult> {
-
-    if 
(AbsoluteTime.isExpired(AbsoluteTime.fromProtocolTimestamp(token.expiration))) {
+    if (
+      AbsoluteTime.isExpired(
+        AbsoluteTime.fromProtocolTimestamp(token.expiration),
+      )
+    ) {
       return {
-        valid: false, cause: {
+        valid: false,
+        cause: {
           type: ErrorType.CLIENT,
           status: HttpStatusCode.Unauthorized,
           message: "login token expired, login again.",
@@ -237,16 +267,16 @@ export function useCredentialsChecker() {
             status: 401,
             options: {},
             url: `/private/token`,
-            payload: {}
+            payload: {},
           },
-          payload: {}
+          payload: {},
         },
-      }
+      };
     }
 
-    return requestNewLoginToken(baseUrl, token.token as AccessToken)
+    return requestNewLoginToken(baseUrl, token.token as AccessToken);
   }
-  return { requestNewLoginToken, refreshLoginToken }
+  return { requestNewLoginToken, refreshLoginToken };
 }
 
 /**
@@ -255,37 +285,36 @@ export function useCredentialsChecker() {
  * @returns request handler to
  */
 export function useBackendBaseRequest(): useBackendBaseRequestType {
-  const { url: backend, token: loginToken } = useBackendContext();
   const { request: requestHandler } = useApiContext();
-  const token = loginToken?.token;
+  const { state } = useSessionContext();
+  const token = state.status === "loggedIn" ? state.token : undefined;
+  const baseUrl = state.backendUrl;
 
   const request = useCallback(
     function requestImpl<T>(
       endpoint: string,
       options: RequestOptions = {},
     ): Promise<HttpResponseOk<T>> {
-      return requestHandler<T>(backend, endpoint, { ...options, token 
}).then(res => {
-        return res
-      }).catch(err => {
-        throw err
-      });
+      return requestHandler<T>(baseUrl, endpoint, { ...options, token })
+        .then((res) => {
+          return res;
+        })
+        .catch((err) => {
+          throw err;
+        });
     },
-    [backend, token],
+    [baseUrl, token],
   );
 
   return { request };
 }
 
 export function useBackendInstanceRequest(): useBackendInstanceRequestType {
-  const { url: rootBackendUrl, token: rootToken } = useBackendContext();
-  const { token: instanceToken, admin } = useInstanceContext();
   const { request: requestHandler } = useApiContext();
 
-  const { baseUrl, token: loginToken } = !admin
-    ? { baseUrl: rootBackendUrl, token: rootToken }
-    : { baseUrl: rootBackendUrl, token: instanceToken };
-
-  const token = loginToken?.token;
+  const { state } = useSessionContext();
+  const token = state.status === "loggedIn" ? state.token : undefined;
+  const baseUrl = state.backendUrl;
 
   const request = useCallback(
     function requestImpl<T>(
@@ -301,7 +330,7 @@ export function useBackendInstanceRequest(): 
useBackendInstanceRequestType {
     function multiFetcherImpl<T>(
       args: [endpoints: string[]],
     ): Promise<HttpResponseOk<T>[]> {
-      const [endpoints] = args
+      const [endpoints] = args;
       return Promise.all(
         endpoints.map((endpoint) =>
           requestHandler<T>(baseUrl, endpoint, { token }),
@@ -320,18 +349,22 @@ export function useBackendInstanceRequest(): 
useBackendInstanceRequestType {
 
   const orderFetcher = useCallback(
     function orderFetcherImpl<T>(
-      args: [endpoint: string,
+      args: [
+        endpoint: string,
         paid?: YesOrNo,
         refunded?: YesOrNo,
         wired?: YesOrNo,
         searchDate?: Date,
-        delta?: number,]
+        delta?: number,
+      ],
     ): Promise<HttpResponseOk<T>> {
-      const [endpoint, paid, refunded, wired, searchDate, delta] = args
+      const [endpoint, paid, refunded, wired, searchDate, delta] = args;
       const date_s =
         delta && delta < 0 && searchDate
           ? Math.floor(searchDate.getTime() / 1000) + 1
-          : searchDate !== undefined ? Math.floor(searchDate.getTime() / 1000) 
: undefined;
+          : searchDate !== undefined
+            ? Math.floor(searchDate.getTime() / 1000)
+            : undefined;
       const params: any = {};
       if (paid !== undefined) params.paid = paid;
       if (delta !== undefined) params.delta = delta;
@@ -339,12 +372,12 @@ export function useBackendInstanceRequest(): 
useBackendInstanceRequestType {
       if (wired !== undefined) params.wired = wired;
       if (date_s !== undefined) params.date_s = date_s;
       if (delta === 0) {
-        //in this case we can already assume the response 
+        //in this case we can already assume the response
         //and avoid network
         return Promise.resolve({
           ok: true,
           data: { orders: [] } as T,
-        })
+        });
       }
       return requestHandler<T>(baseUrl, endpoint, { params, token });
     },
@@ -353,23 +386,25 @@ export function useBackendInstanceRequest(): 
useBackendInstanceRequestType {
 
   const transferFetcher = useCallback(
     function transferFetcherImpl<T>(
-      args: [endpoint: string,
+      args: [
+        endpoint: string,
         payto_uri?: string,
         verified?: string,
         position?: string,
-        delta?: number,]
+        delta?: number,
+      ],
     ): Promise<HttpResponseOk<T>> {
-      const [endpoint, payto_uri, verified, position, delta] = args
+      const [endpoint, payto_uri, verified, position, delta] = args;
       const params: any = {};
       if (payto_uri !== undefined) params.payto_uri = payto_uri;
       if (verified !== undefined) params.verified = verified;
       if (delta === 0) {
-        //in this case we can already assume the response 
+        //in this case we can already assume the response
         //and avoid network
         return Promise.resolve({
           ok: true,
           data: { transfers: [] } as T,
-        })
+        });
       }
       if (delta !== undefined) {
         params.limit = delta;
@@ -383,19 +418,17 @@ export function useBackendInstanceRequest(): 
useBackendInstanceRequestType {
 
   const templateFetcher = useCallback(
     function templateFetcherImpl<T>(
-      args: [endpoint: string,
-        position?: string,
-        delta?: number,]
+      args: [endpoint: string, position?: string, delta?: number],
     ): Promise<HttpResponseOk<T>> {
-      const [endpoint, position, delta] = args
+      const [endpoint, position, delta] = args;
       const params: any = {};
       if (delta === 0) {
-        //in this case we can already assume the response 
+        //in this case we can already assume the response
         //and avoid network
         return Promise.resolve({
           ok: true,
           data: { templates: [] } as T,
-        })
+        });
       }
       if (delta !== undefined) {
         params.limit = delta;
@@ -409,19 +442,17 @@ export function useBackendInstanceRequest(): 
useBackendInstanceRequestType {
 
   const webhookFetcher = useCallback(
     function webhookFetcherImpl<T>(
-      args: [endpoint: string,
-        position?: string,
-        delta?: number,]
+      args: [endpoint: string, position?: string, delta?: number],
     ): Promise<HttpResponseOk<T>> {
-      const [endpoint, position, delta] = args
+      const [endpoint, position, delta] = args;
       const params: any = {};
       if (delta === 0) {
-        //in this case we can already assume the response 
+        //in this case we can already assume the response
         //and avoid network
         return Promise.resolve({
           ok: true,
           data: { webhooks: [] } as T,
-        })
+        });
       }
       if (delta !== undefined) {
         params.limit = delta;
diff --git a/packages/merchant-backoffice-ui/src/hooks/instance.test.ts 
b/packages/merchant-backoffice-ui/src/hooks/instance.test.ts
index 4f6cabc9e..a1bb3d5d4 100644
--- a/packages/merchant-backoffice-ui/src/hooks/instance.test.ts
+++ b/packages/merchant-backoffice-ui/src/hooks/instance.test.ts
@@ -21,7 +21,7 @@
 
 import * as tests from "@gnu-taler/web-util/testing";
 import { expect } from "chai";
-import { AccessToken, MerchantBackend } from "../declaration.js";
+import { MerchantBackend } from "../declaration.js";
 import {
   useAdminAPI,
   useBackendInstances,
@@ -40,6 +40,7 @@ import {
   API_UPDATE_CURRENT_INSTANCE_AUTH,
   API_UPDATE_INSTANCE_BY_ID,
 } from "./urls.js";
+import { AccessToken } from "@gnu-taler/taler-util";
 
 describe("instance api interaction with details", () => {
   it("should evict cache when updating an instance", async () => {
diff --git a/packages/merchant-backoffice-ui/src/hooks/instance.ts 
b/packages/merchant-backoffice-ui/src/hooks/instance.ts
index 352f54982..dfe97fd61 100644
--- a/packages/merchant-backoffice-ui/src/hooks/instance.ts
+++ b/packages/merchant-backoffice-ui/src/hooks/instance.ts
@@ -17,9 +17,9 @@ import {
   HttpResponse,
   HttpResponseOk,
   RequestError,
+  useMerchantApiContext,
 } from "@gnu-taler/web-util/browser";
-import { useBackendContext } from "../context/backend.js";
-import { AccessToken, MerchantBackend } from "../declaration.js";
+import { MerchantBackend } from "../declaration.js";
 import {
   useBackendBaseRequest,
   useBackendInstanceRequest,
@@ -29,6 +29,8 @@ import {
 
 // FIX default import https://github.com/microsoft/TypeScript/issues/49189
 import _useSWR, { SWRHook, useSWRConfig } from "swr";
+import { useSessionContext } from "../context/session.js";
+import { AccessToken } from "@gnu-taler/taler-util";
 const useSWR = _useSWR as unknown as SWRHook;
 
 interface InstanceAPI {
@@ -37,7 +39,10 @@ interface InstanceAPI {
   ) => Promise<void>;
   deleteInstance: () => Promise<void>;
   clearAccessToken: (currentToken: AccessToken | undefined) => Promise<void>;
-  setNewAccessToken: (currentToken: AccessToken | undefined, token: 
AccessToken) => Promise<void>;
+  setNewAccessToken: (
+    currentToken: AccessToken | undefined,
+    token: AccessToken,
+  ) => Promise<void>;
 }
 
 export function useAdminAPI(): AdminAPI {
@@ -87,10 +92,13 @@ export interface AdminAPI {
 
 export function useManagementAPI(instanceId: string): InstanceAPI {
   const mutateAll = useMatchMutate();
-  const { url: backendURL } = useBackendContext()
-  const { updateToken } = useBackendContext();
+  const {
+    state: { backendUrl },
+    logIn,
+    logOut,
+  } = useSessionContext();
   const { request } = useBackendBaseRequest();
-  const { requestNewLoginToken } = useCredentialsChecker()
+  const { requestNewLoginToken } = useCredentialsChecker();
 
   const updateInstance = async (
     instance: MerchantBackend.Instances.InstanceReconfigurationMessage,
@@ -111,7 +119,9 @@ export function useManagementAPI(instanceId: string): 
InstanceAPI {
     mutateAll(/\/management\/instances/);
   };
 
-  const clearAccessToken = async (currentToken: AccessToken | undefined): 
Promise<void> => {
+  const clearAccessToken = async (
+    currentToken: AccessToken | undefined,
+  ): Promise<void> => {
     await request(`/management/instances/${instanceId}/auth`, {
       method: "POST",
       token: currentToken,
@@ -121,36 +131,46 @@ export function useManagementAPI(instanceId: string): 
InstanceAPI {
     mutateAll(/\/management\/instances/);
   };
 
-  const setNewAccessToken = async (currentToken: AccessToken | undefined, 
newToken: AccessToken): Promise<void> => {
+  const setNewAccessToken = async (
+    currentToken: AccessToken | undefined,
+    newToken: AccessToken,
+  ): Promise<void> => {
     await request(`/management/instances/${instanceId}/auth`, {
       method: "POST",
       token: currentToken,
       data: { method: "token", token: newToken },
     });
 
-    const resp = await requestNewLoginToken(backendURL, newToken)
+    const resp = await requestNewLoginToken(backendUrl, newToken);
     if (resp.valid) {
-      const { token, expiration } = resp
-      updateToken({ token, expiration });
+      logIn({ token: resp.token as AccessToken });
     } else {
-      updateToken(undefined)
+      logOut();
     }
 
     mutateAll(/\/management\/instances/);
   };
 
-  return { updateInstance, deleteInstance, setNewAccessToken, clearAccessToken 
};
+  return {
+    updateInstance,
+    deleteInstance,
+    setNewAccessToken,
+    clearAccessToken,
+  };
 }
 
 export function useInstanceAPI(): InstanceAPI {
   const { mutate } = useSWRConfig();
-  const { url: backendURL, updateToken } = useBackendContext()
-
   const {
-    token: adminToken,
-  } = useBackendContext();
+    state: { backendUrl },
+  } = useSessionContext();
+
   const { request } = useBackendInstanceRequest();
-  const { requestNewLoginToken } = useCredentialsChecker()
+  const { requestNewLoginToken } = useCredentialsChecker();
+  const { state, logIn, logOut } = useSessionContext();
+
+  const adminToken =
+    state.status === "loggedIn" && state.isAdmin ? state.token : undefined;
 
   const updateInstance = async (
     instance: MerchantBackend.Instances.InstanceReconfigurationMessage,
@@ -160,7 +180,9 @@ export function useInstanceAPI(): InstanceAPI {
       data: instance,
     });
 
-    if (adminToken) mutate(["/private/instances", adminToken, backendURL], 
null);
+    if (adminToken) {
+      mutate(["/private/instances", adminToken, backendUrl], null);
+    }
     mutate([`/private/`], null);
   };
 
@@ -170,11 +192,15 @@ export function useInstanceAPI(): InstanceAPI {
       // token: adminToken,
     });
 
-    if (adminToken) mutate(["/private/instances", adminToken, backendURL], 
null);
+    if (adminToken) {
+      mutate(["/private/instances", adminToken, backendUrl], null);
+    }
     mutate([`/private/`], null);
   };
 
-  const clearAccessToken = async (currentToken: AccessToken | undefined): 
Promise<void> => {
+  const clearAccessToken = async (
+    currentToken: AccessToken | undefined,
+  ): Promise<void> => {
     await request(`/private/auth`, {
       method: "POST",
       token: currentToken,
@@ -184,25 +210,32 @@ export function useInstanceAPI(): InstanceAPI {
     mutate([`/private/`], null);
   };
 
-  const setNewAccessToken = async (currentToken: AccessToken | undefined, 
newToken: AccessToken): Promise<void> => {
+  const setNewAccessToken = async (
+    currentToken: AccessToken | undefined,
+    newToken: AccessToken,
+  ): Promise<void> => {
     await request(`/private/auth`, {
       method: "POST",
       token: currentToken,
       data: { method: "token", token: newToken },
     });
 
-    const resp = await requestNewLoginToken(backendURL, newToken)
+    const resp = await requestNewLoginToken(backendUrl, newToken);
     if (resp.valid) {
-      const { token, expiration } = resp
-      updateToken({ token, expiration });
+      logIn({ token: resp.token as AccessToken });
     } else {
-      updateToken(undefined)
+      logOut();
     }
 
     mutate([`/private/`], null);
   };
 
-  return { updateInstance, deleteInstance, setNewAccessToken, clearAccessToken 
};
+  return {
+    updateInstance,
+    deleteInstance,
+    setNewAccessToken,
+    clearAccessToken,
+  };
 }
 
 export function useInstanceDetails(): HttpResponse<
diff --git a/packages/merchant-backoffice-ui/src/hooks/preference.ts 
b/packages/merchant-backoffice-ui/src/hooks/preference.ts
index 4570ff679..5a50eb378 100644
--- a/packages/merchant-backoffice-ui/src/hooks/preference.ts
+++ b/packages/merchant-backoffice-ui/src/hooks/preference.ts
@@ -59,6 +59,7 @@ const PREFERENCES_KEY = buildStorageKey(
 export function usePreference(): [
   Readonly<Preferences>,
   <T extends keyof Preferences>(key: T, value: Preferences[T]) => void,
+  (s: Preferences) => void,
 ] {
   const { value, update } = useLocalStorage(PREFERENCES_KEY, defaultSettings);
   function updateField<T extends keyof Preferences>(k: T, v: Preferences[T]) {
@@ -66,7 +67,7 @@ export function usePreference(): [
     update(newValue);
   }
 
-  return [value, updateField];
+  return [value, updateField, update];
 }
 
 export function dateFormatForSettings(s: Preferences): string {
diff --git a/packages/merchant-backoffice-ui/src/hooks/testing.tsx 
b/packages/merchant-backoffice-ui/src/hooks/testing.tsx
index d9a70e794..bebf7716b 100644
--- a/packages/merchant-backoffice-ui/src/hooks/testing.tsx
+++ b/packages/merchant-backoffice-ui/src/hooks/testing.tsx
@@ -24,8 +24,6 @@ import { ComponentChildren, FunctionalComponent, h, VNode } 
from "preact";
 import { HttpRequestLibrary, HttpRequestOptions, HttpResponse } from 
"@gnu-taler/taler-util/http";
 import { SWRConfig } from "swr";
 import { ApiContextProvider } from "@gnu-taler/web-util/browser";
-import { BackendContextProvider } from "../context/backend.js";
-import { InstanceContextProvider } from "../context/instance.js";
 import { HttpResponseOk, RequestOptions } from "@gnu-taler/web-util/browser";
 import { TalerBankIntegrationHttpClient, TalerCoreBankHttpClient, 
TalerRevenueHttpClient, TalerWireGatewayHttpClient } from 
"@gnu-taler/taler-util";
 
@@ -149,15 +147,15 @@ export class ApiMockEnvironment extends MockEnvironment {
       const bankWire = new 
TalerWireGatewayHttpClient(bankCore.getWireGatewayAPI("b").href, "b", 
mockHttpClient)
 
       return (
-        <BackendContextProvider defaultUrl="http://backend";>
-          <InstanceContextProvider
-            value={{
-              token: undefined,
-              id: "default",
-              admin: true,
-              changeToken: () => null,
-            }}
-          >
+        // <BackendContextProvider defaultUrl="http://backend";>
+        //   <InstanceContextProvider
+        //     value={{
+        //       token: undefined,
+        //       id: "default",
+        //       admin: true,
+        //       changeToken: () => null,
+        //     }}
+        //   >
             <ApiContextProvider value={{ request, bankCore, bankIntegration, 
bankRevenue, bankWire }}>
               <SC
                 value={{
@@ -172,8 +170,8 @@ export class ApiMockEnvironment extends MockEnvironment {
                 {children}
               </SC>
             </ApiContextProvider>
-          </InstanceContextProvider>
-        </BackendContextProvider>
+        //   </InstanceContextProvider>
+        // </BackendContextProvider>
       );
     };
   }
diff --git 
a/packages/merchant-backoffice-ui/src/paths/admin/create/Create.stories.tsx 
b/packages/merchant-backoffice-ui/src/paths/admin/create/Create.stories.tsx
index ec54dc150..39fdb6bdc 100644
--- a/packages/merchant-backoffice-ui/src/paths/admin/create/Create.stories.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/admin/create/Create.stories.tsx
@@ -20,8 +20,8 @@
  */
 
 import { h, VNode, FunctionalComponent } from "preact";
-import { ConfigContextProvider } from "../../../context/config.js";
 import { CreatePage as TestedComponent } from "./CreatePage.js";
+import { MerchantApiProviderTesting } from "@gnu-taler/web-util/browser";
 
 export default {
   title: "Pages/Instance/Create",
@@ -37,19 +37,32 @@ function createExample<Props>(
   props: Partial<Props>,
 ) {
   const r = (args: any) => (
-    <ConfigContextProvider
+    <MerchantApiProviderTesting
       value={{
-        currency: "ARS",
-        version: "1",
-        currencies: {
-          currency: "TESTKUDOS"
+        cancelRequest: () => {},
+        config: {
+          currency: "ARS",
+          version: "1",
+          currencies: {
+            "ASD": {
+              name: "testkudos",
+              alt_unit_names: {},
+              num_fractional_input_digits: 1,
+              num_fractional_normal_digits: 1,
+              num_fractional_trailing_zero_digits: 1,
+            }
+          },
+          exchanges: [],
+          name: "taler-merchant"
         },
-        exchanges: [],
-        name: "taler-merchant"
+        hints: [],
+        lib: {} as any,
+        onActivity: (() => {}) as any,
+        url: new URL("asdasd"),
       }}
     >
       <Component {...args} />
-    </ConfigContextProvider>
+    </MerchantApiProviderTesting>
   );
   r.args = props;
   return r;
diff --git a/packages/merchant-backoffice-ui/src/paths/admin/create/index.tsx 
b/packages/merchant-backoffice-ui/src/paths/admin/create/index.tsx
index cbda65bfe..440cd2b07 100644
--- a/packages/merchant-backoffice-ui/src/paths/admin/create/index.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/admin/create/index.tsx
@@ -17,16 +17,18 @@
  *
  * @author Sebastian Javier Marchano (sebasjm)
  */
-import { useTranslationContext } from "@gnu-taler/web-util/browser";
-import { Fragment, h, VNode } from "preact";
+import {
+  useMerchantApiContext,
+  useTranslationContext,
+} from "@gnu-taler/web-util/browser";
+import { Fragment, VNode, h } from "preact";
 import { useState } from "preact/hooks";
 import { NotificationCard } from "../../../components/menu/index.js";
-import { AccessToken, MerchantBackend } from "../../../declaration.js";
-import { useAdminAPI, useInstanceAPI } from "../../../hooks/instance.js";
+import { MerchantBackend } from "../../../declaration.js";
+import { useAdminAPI } from "../../../hooks/instance.js";
+import { useSessionContext } from "../../../context/session.js";
 import { Notification } from "../../../utils/types.js";
 import { CreatePage } from "./CreatePage.js";
-import { useCredentialsChecker } from "../../../hooks/backend.js";
-import { useBackendContext } from "../../../context/backend.js";
 
 interface Props {
   onBack?: () => void;
@@ -39,8 +41,8 @@ export default function Create({ onBack, onConfirm, forceId 
}: Props): VNode {
   const { createInstance } = useAdminAPI();
   const [notif, setNotif] = useState<Notification | undefined>(undefined);
   const { i18n } = useTranslationContext();
-  const { requestNewLoginToken } = useCredentialsChecker()
-  const { url: backendURL, updateToken } = useBackendContext()
+  const { lib } = useMerchantApiContext();
+  const { state, logIn } = useSessionContext();
 
   return (
     <Fragment>
@@ -53,15 +55,29 @@ export default function Create({ onBack, onConfirm, forceId 
}: Props): VNode {
           d: MerchantBackend.Instances.InstanceConfigurationMessage,
         ) => {
           try {
-            await createInstance(d)
+            await createInstance(d);
             if (d.auth.token) {
-              const resp = await requestNewLoginToken(backendURL, d.auth.token 
as AccessToken)
-              if (resp.valid) {
-                const { token, expiration } = resp
-                updateToken({ token, expiration });
-              } else {
-                updateToken(undefined)
+              const result = await lib.authenticate.createAccessToken(
+                d.auth.token,
+                {
+                  scope: "write",
+                  duration: {
+                    d_us: "forever",
+                  },
+                  refreshable: true,
+                },
+              );
+              if (result.type === "ok") {
+                const { access_token } = result.body;
+                logIn({ token: access_token });
               }
+              // const resp = await requestNewLoginToken(backendURL.href, 
d.auth.token as AccessToken)
+              // if (resp.valid) {
+              //   const { token, expiration } = resp
+              //   updateToken({ token, expiration });
+              // } else {
+              //   updateToken(undefined)
+              // }
             }
             onConfirm();
           } catch (ex) {
@@ -72,7 +88,7 @@ export default function Create({ onBack, onConfirm, forceId 
}: Props): VNode {
                 description: ex.message,
               });
             } else {
-              console.error(ex)
+              console.error(ex);
             }
           }
         }}
diff --git a/packages/merchant-backoffice-ui/src/paths/admin/create/stories.tsx 
b/packages/merchant-backoffice-ui/src/paths/admin/create/stories.tsx
index 9a947c9d5..8166dc739 100644
--- a/packages/merchant-backoffice-ui/src/paths/admin/create/stories.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/admin/create/stories.tsx
@@ -19,9 +19,9 @@
  * @author Sebastian Javier Marchano (sebasjm)
  */
 
-import { h, VNode, FunctionalComponent } from "preact";
-import { ConfigContextProvider } from "../../../context/config.js";
+import { FunctionalComponent, h } from "preact";
 import { CreatePage as TestedComponent } from "./CreatePage.js";
+import { MerchantApiProviderTesting } from "@gnu-taler/web-util/browser";
 
 export default {
   title: "Pages/Instance/Create",
@@ -37,19 +37,32 @@ function createExample<Props>(
   props: Partial<Props>,
 ) {
   const component = (args: any) => (
-    <ConfigContextProvider
+    <MerchantApiProviderTesting
       value={{
-        currency: "TESTKUDOS",
-        version: "1",
-        currencies: {
-          currency: "TESTKUDOS"
+        cancelRequest: () => {},
+        config: {
+          currency: "ARS",
+          version: "1",
+          currencies: {
+            "ASD": {
+              name: "testkudos",
+              alt_unit_names: {},
+              num_fractional_input_digits: 1,
+              num_fractional_normal_digits: 1,
+              num_fractional_trailing_zero_digits: 1,
+            }
+          },
+          exchanges: [],
+          name: "taler-merchant"
         },
-        exchanges: [],
-        name: "taler-merchant"
+        hints: [],
+        lib: {} as any,
+        onActivity: (() => {}) as any,
+        url: new URL("asdasd"),
       }}
     >
       <Internal {...(props as any)} />
-    </ConfigContextProvider>
+    </MerchantApiProviderTesting>
   );
   return { component, props };
 }
diff --git 
a/packages/merchant-backoffice-ui/src/paths/admin/list/TableActive.tsx 
b/packages/merchant-backoffice-ui/src/paths/admin/list/TableActive.tsx
index 711a5a4f0..bc18bb352 100644
--- a/packages/merchant-backoffice-ui/src/paths/admin/list/TableActive.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/admin/list/TableActive.tsx
@@ -23,7 +23,7 @@ import { useTranslationContext } from 
"@gnu-taler/web-util/browser";
 import { h, VNode } from "preact";
 import { StateUpdater, useEffect, useState } from "preact/hooks";
 import { MerchantBackend } from "../../../declaration.js";
-import { useSessionState } from "../../../hooks/session.js";
+import { useSessionContext } from "../../../context/session.js";
 
 interface Props {
   instances: MerchantBackend.Instances.Instance[];
@@ -149,7 +149,7 @@ function Table({
   onPurge,
 }: TableProps): VNode {
   const { i18n } = useTranslationContext();
-  const { impersonate } = useSessionState()
+  const { impersonate } = useSessionContext()
   return (
     <div class="table-container">
       <table class="table is-fullwidth is-striped is-hoverable is-fullwidth">
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 13dd3a2f6..2a37ee588 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/details/index.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/details/index.tsx
@@ -18,11 +18,11 @@ import { Fragment, h, VNode } from "preact";
 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 { MerchantBackend } from "../../../declaration.js";
 import { useInstanceAPI, useInstanceDetails } from 
"../../../hooks/instance.js";
 import { DetailPage } from "./DetailPage.js";
 import { HttpStatusCode } from "@gnu-taler/taler-util";
+import { useSessionContext } from "../../../context/session.js";
 
 interface Props {
   onUnauthorized: () => VNode;
@@ -39,7 +39,7 @@ export default function Detail({
   onDelete,
   onNotFound,
 }: Props): VNode {
-  const { id } = useInstanceContext();
+  const { state } = useSessionContext();
   const result = useInstanceDetails();
   const [deleting, setDeleting] = useState<boolean>(false);
 
@@ -69,7 +69,7 @@ export default function Detail({
       />
       {deleting && (
         <DeleteModal
-          element={{ name: result.data.name, id }}
+          element={{ name: result.data.name, id: state.instance }}
           onCancel={() => setDeleting(false)}
           onConfirm={async (): Promise<void> => {
             try {
diff --git 
a/packages/merchant-backoffice-ui/src/paths/instance/details/stories.tsx 
b/packages/merchant-backoffice-ui/src/paths/instance/details/stories.tsx
index aabe67e00..94e19bb6e 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/details/stories.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/details/stories.tsx
@@ -19,8 +19,8 @@
  * @author Sebastian Javier Marchano (sebasjm)
  */
 
-import { h, VNode, FunctionalComponent } from "preact";
-import { ConfigContextProvider } from "../../../context/config.js";
+import { MerchantApiProviderTesting } from "@gnu-taler/web-util/browser";
+import { FunctionalComponent, h } from "preact";
 import { DetailPage as TestedComponent } from "./DetailPage.js";
 
 export default {
@@ -37,19 +37,32 @@ function createExample<Props>(
   props: Partial<Props>,
 ) {
   const component = (args: any) => (
-    <ConfigContextProvider
+    <MerchantApiProviderTesting
       value={{
-        currency: "TESTKUDOS",
-        version: "1",
-        currencies: {
-          currency: "TESTKUDOS"
+        cancelRequest: () => {},
+        config: {
+          currency: "ARS",
+          version: "1",
+          currencies: {
+            "ASD": {
+              name: "testkudos",
+              alt_unit_names: {},
+              num_fractional_input_digits: 1,
+              num_fractional_normal_digits: 1,
+              num_fractional_trailing_zero_digits: 1,
+            }
+          },
+          exchanges: [],
+          name: "taler-merchant"
         },
-        exchanges: [],
-        name: "taler-merchant"
+        hints: [],
+        lib: {} as any,
+        onActivity: (() => {}) as any,
+        url: new URL("asdasd"),
       }}
     >
       <Internal {...(props as any)} />
-    </ConfigContextProvider>
+    </MerchantApiProviderTesting>
   );
   return { component, props };
 }
diff --git 
a/packages/merchant-backoffice-ui/src/paths/instance/orders/create/CreatePage.tsx
 
b/packages/merchant-backoffice-ui/src/paths/instance/orders/create/CreatePage.tsx
index 5633d93ab..fca123773 100644
--- 
a/packages/merchant-backoffice-ui/src/paths/instance/orders/create/CreatePage.tsx
+++ 
b/packages/merchant-backoffice-ui/src/paths/instance/orders/create/CreatePage.tsx
@@ -19,10 +19,18 @@
  * @author Sebastian Javier Marchano (sebasjm)
  */
 
-import { AbsoluteTime, Amounts, Duration, TalerProtocolDuration } from 
"@gnu-taler/taler-util";
-import { useTranslationContext } from "@gnu-taler/web-util/browser";
+import {
+  AbsoluteTime,
+  Amounts,
+  Duration,
+  TalerProtocolDuration,
+} from "@gnu-taler/taler-util";
+import {
+  useMerchantApiContext,
+  useTranslationContext,
+} from "@gnu-taler/web-util/browser";
 import { format, isFuture } from "date-fns";
-import { ComponentChildren, Fragment, VNode, h } from "preact";
+import { Fragment, VNode, h } from "preact";
 import { useEffect, useState } from "preact/hooks";
 import {
   FormErrors,
@@ -39,10 +47,8 @@ import { InputToggle } from 
"../../../../components/form/InputToggle.js";
 import { InventoryProductForm } from 
"../../../../components/product/InventoryProductForm.js";
 import { NonInventoryProductFrom } from 
"../../../../components/product/NonInventoryProductForm.js";
 import { ProductList } from "../../../../components/product/ProductList.js";
-import { useConfigContext } from "../../../../context/config.js";
 import { MerchantBackend, WithId } from "../../../../declaration.js";
 import { usePreference } from "../../../../hooks/preference.js";
-import { OrderCreateSchema as schema } from "../../../../schemas/index.js";
 import { rate } from "../../../../utils/amount.js";
 import { undefinedIfEmpty } from "../../../../utils/table.js";
 
@@ -58,9 +64,16 @@ interface InstanceConfig {
   default_wire_transfer_delay: TalerProtocolDuration;
 }
 
-function with_defaults(config: InstanceConfig, currency: string): 
Partial<Entity> {
-  const defaultPayDeadline = 
Duration.fromTalerProtocolDuration(config.default_pay_delay);
-  const defaultWireDeadline = 
Duration.fromTalerProtocolDuration(config.default_wire_transfer_delay);
+function with_defaults(
+  config: InstanceConfig,
+  _currency: string,
+): Partial<Entity> {
+  const defaultPayDeadline = Duration.fromTalerProtocolDuration(
+    config.default_pay_delay,
+  );
+  const defaultWireDeadline = Duration.fromTalerProtocolDuration(
+    config.default_wire_transfer_delay,
+  );
 
   return {
     inventoryProducts: {},
@@ -69,9 +82,9 @@ function with_defaults(config: InstanceConfig, currency: 
string): Partial<Entity
     payments: {
       max_fee: undefined,
       createToken: true,
-      pay_deadline: (defaultPayDeadline),
-      refund_deadline: (defaultPayDeadline),
-      wire_transfer_deadline: (defaultWireDeadline),
+      pay_deadline: defaultPayDeadline,
+      refund_deadline: defaultPayDeadline,
+      wire_transfer_deadline: defaultWireDeadline,
     },
     shipping: {},
     extra: {},
@@ -114,26 +127,17 @@ interface Entity {
   extra: Record<string, string>;
 }
 
-const stringIsValidJSON = (value: string) => {
-  try {
-    JSON.parse(value.trim());
-    return true;
-  } catch {
-    return false;
-  }
-};
-
 export function CreatePage({
   onCreate,
   onBack,
   instanceConfig,
   instanceInventory,
 }: Props): VNode {
-  const config = useConfigContext();
-  const instance_default = with_defaults(instanceConfig, config.currency)
+  const { config } = useMerchantApiContext();
+  const instance_default = with_defaults(instanceConfig, config.currency);
   const [value, valueHandler] = useState(instance_default);
   const zero = Amounts.zeroOfCurrency(config.currency);
-  const [settings, updateSettings] = usePreference()
+  const [settings, updateSettings] = usePreference();
   const inventoryList = Object.values(value.inventoryProducts || {});
   const productList = Object.values(value.products || {});
 
@@ -158,22 +162,25 @@ export function CreatePage({
       refund_deadline: !value.payments?.refund_deadline
         ? undefined
         : value.payments.pay_deadline &&
-          Duration.cmp(value.payments.refund_deadline, 
value.payments.pay_deadline) === -1
-          ? i18n.str`refund deadline cannot be before pay deadline`
-          : value.payments.wire_transfer_deadline &&
             Duration.cmp(
-              value.payments.wire_transfer_deadline,
               value.payments.refund_deadline,
+              value.payments.pay_deadline,
             ) === -1
+          ? i18n.str`refund deadline cannot be before pay deadline`
+          : value.payments.wire_transfer_deadline &&
+              Duration.cmp(
+                value.payments.wire_transfer_deadline,
+                value.payments.refund_deadline,
+              ) === -1
             ? i18n.str`wire transfer deadline cannot be before refund deadline`
             : undefined,
       pay_deadline: !value.payments?.pay_deadline
         ? i18n.str`required`
         : value.payments.wire_transfer_deadline &&
-          Duration.cmp(
-            value.payments.wire_transfer_deadline,
-            value.payments.pay_deadline,
-          ) === -1
+            Duration.cmp(
+              value.payments.wire_transfer_deadline,
+              value.payments.pay_deadline,
+            ) === -1
           ? i18n.str`wire transfer deadline cannot be before pay deadline`
           : undefined,
       wire_transfer_deadline: !value.payments?.wire_transfer_deadline
@@ -184,12 +191,11 @@ export function CreatePage({
         : !value.payments?.refund_deadline
           ? i18n.str`should have a refund deadline`
           : Duration.cmp(
-            value.payments.refund_deadline,
-            value.payments.auto_refund_deadline,
-          ) == -1
+                value.payments.refund_deadline,
+                value.payments.auto_refund_deadline,
+              ) == -1
             ? i18n.str`auto refund cannot be after refund deadline`
             : undefined,
-
     }),
     shipping: undefinedIfEmpty({
       delivery_date: !value.shipping?.delivery_date
@@ -214,18 +220,34 @@ export function CreatePage({
         summary: order.pricing.summary,
         products: productList,
         extra: undefinedIfEmpty(value.extra),
-        pay_deadline: !value.payments.pay_deadline ?
-          i18n.str`required` :
-          
AbsoluteTime.toProtocolTimestamp(AbsoluteTime.addDuration(AbsoluteTime.now(), 
value.payments.pay_deadline))
-        ,// : undefined,
+        pay_deadline: !value.payments.pay_deadline
+          ? i18n.str`required`
+          : AbsoluteTime.toProtocolTimestamp(
+              AbsoluteTime.addDuration(
+                AbsoluteTime.now(),
+                value.payments.pay_deadline,
+              ),
+            ), // : undefined,
         wire_transfer_deadline: value.payments.wire_transfer_deadline
-          ? 
AbsoluteTime.toProtocolTimestamp(AbsoluteTime.addDuration(AbsoluteTime.now(), 
value.payments.wire_transfer_deadline))
+          ? AbsoluteTime.toProtocolTimestamp(
+              AbsoluteTime.addDuration(
+                AbsoluteTime.now(),
+                value.payments.wire_transfer_deadline,
+              ),
+            )
           : undefined,
         refund_deadline: value.payments.refund_deadline
-          ? 
AbsoluteTime.toProtocolTimestamp(AbsoluteTime.addDuration(AbsoluteTime.now(), 
value.payments.refund_deadline))
+          ? AbsoluteTime.toProtocolTimestamp(
+              AbsoluteTime.addDuration(
+                AbsoluteTime.now(),
+                value.payments.refund_deadline,
+              ),
+            )
           : undefined,
         auto_refund: value.payments.auto_refund_deadline
-          ? 
Duration.toTalerProtocolDuration(value.payments.auto_refund_deadline)
+          ? Duration.toTalerProtocolDuration(
+              value.payments.auto_refund_deadline,
+            )
           : undefined,
         max_fee: value.payments.max_fee as string,
 
@@ -301,7 +323,7 @@ export function CreatePage({
   const totalAsString = Amounts.stringify(totalPrice.amount);
   const allProducts = productList.concat(inventoryList.map(asProduct));
 
-  const [newField, setNewField] = useState("")
+  const [newField, setNewField] = useState("");
 
   useEffect(() => {
     valueHandler((v) => {
@@ -328,37 +350,43 @@ export function CreatePage({
   );
 
   // if there is no default pay deadline
-  const noDefault_payDeadline = !instance_default.payments || 
!instance_default.payments.pay_deadline
+  const noDefault_payDeadline =
+    !instance_default.payments || !instance_default.payments.pay_deadline;
   // and there is no default wire deadline
-  const noDefault_wireDeadline = !instance_default.payments || 
!instance_default.payments.wire_transfer_deadline
+  const noDefault_wireDeadline =
+    !instance_default.payments ||
+    !instance_default.payments.wire_transfer_deadline;
   // user required to set the taler options
-  const requiresSomeTalerOptions = noDefault_payDeadline || 
noDefault_wireDeadline
-
+  const requiresSomeTalerOptions =
+    noDefault_payDeadline || noDefault_wireDeadline;
 
   return (
     <div>
-
       <section class="section is-main-section">
         <div class="tabs is-toggle is-fullwidth is-small">
           <ul>
-            <li class={!settings.advanceOrderMode ? "is-active" : ""} 
onClick={() => {
-              updateSettings({
-                ...settings,
-                advanceOrderMode: false
-              })
-            }}>
-              <a >
-                <span><i18n.Translate>Simple</i18n.Translate></span>
+            <li
+              class={!settings.advanceOrderMode ? "is-active" : ""}
+              onClick={() => {
+                updateSettings("advanceOrderMode", false);
+              }}
+            >
+              <a>
+                <span>
+                  <i18n.Translate>Simple</i18n.Translate>
+                </span>
               </a>
             </li>
-            <li class={settings.advanceOrderMode ? "is-active" : ""} 
onClick={() => {
-              updateSettings({
-                ...settings,
-                advanceOrderMode: true
-              })
-            }}>
-              <a >
-                <span><i18n.Translate>Advanced</i18n.Translate></span>
+            <li
+              class={settings.advanceOrderMode ? "is-active" : ""}
+              onClick={() => {
+                updateSettings("advanceOrderMode", true);
+              }}
+            >
+              <a>
+                <span>
+                  <i18n.Translate>Advanced</i18n.Translate>
+                </span>
               </a>
             </li>
           </ul>
@@ -386,7 +414,7 @@ export function CreatePage({
                 inventory={instanceInventory}
               />
 
-              {settings.advanceOrderMode &&
+              {settings.advanceOrderMode && (
                 <NonInventoryProductFrom
                   productToEdit={editingProduct}
                   onAddProduct={(p) => {
@@ -394,7 +422,7 @@ export function CreatePage({
                     return addNewProduct(p);
                   }}
                 />
-              }
+              )}
 
               {allProducts.length > 0 && (
                 <ProductList
@@ -437,8 +465,8 @@ export function CreatePage({
                       discountOrRise > 0 &&
                       (discountOrRise < 1
                         ? `discount of %${Math.round(
-                          (1 - discountOrRise) * 100,
-                        )}`
+                            (1 - discountOrRise) * 100,
+                          )}`
                         : `rise of %${Math.round((discountOrRise - 1) * 100)}`)
                     }
                     tooltip={i18n.str`Amount to be paid by the customer`}
@@ -459,7 +487,7 @@ export function CreatePage({
                 tooltip={i18n.str`Title of the order to be shown to the 
customer`}
               />
 
-              {settings.advanceOrderMode &&
+              {settings.advanceOrderMode && (
                 <InputGroup
                   name="shipping"
                   label={i18n.str`Shipping and Fulfillment`}
@@ -485,146 +513,201 @@ export function CreatePage({
                     tooltip={i18n.str`URL to which the user will be redirected 
after successful payment.`}
                   />
                 </InputGroup>
-              }
+              )}
 
-              {(settings.advanceOrderMode || requiresSomeTalerOptions) &&
+              {(settings.advanceOrderMode || requiresSomeTalerOptions) && (
                 <InputGroup
                   name="payments"
                   label={i18n.str`Taler payment options`}
                   tooltip={i18n.str`Override default Taler payment settings 
for this order`}
                 >
-                  {(settings.advanceOrderMode || noDefault_payDeadline) && 
<InputDuration
-                    name="payments.pay_deadline"
-                    label={i18n.str`Payment time`}
-                    help={<DeadlineHelp 
duration={value.payments?.pay_deadline} />}
-                    withForever
-                    withoutClear
-                    tooltip={i18n.str`Time for the customer to pay for the 
offer before it expires. Inventory products will be reserved until this 
deadline. Time start to run after the order is created.`}
-                    side={
-                      <span>
-                        <button class="button" onClick={() => {
-                          const c = {
-                            ...value,
-                            payments: {
-                              ...(value.payments ?? {}),
-                              pay_deadline: 
instance_default.payments?.pay_deadline
-                            }
-                          }
-                          valueHandler(c)
-                        }}>
-                          <i18n.Translate>default</i18n.Translate>
-                        </button>
-                      </span>
-                    }
-                  />}
-                  {settings.advanceOrderMode && <InputDuration
-                    name="payments.refund_deadline"
-                    label={i18n.str`Refund time`}
-                    help={<DeadlineHelp 
duration={value.payments?.refund_deadline} />}
-                    withForever
-                    withoutClear
-                    tooltip={i18n.str`Time while the order can be refunded by 
the merchant. Time starts after the order is created.`}
-                    side={
-                      <span>
-                        <button class="button" onClick={() => {
-                          valueHandler({
-                            ...value,
-                            payments: {
-                              ...(value.payments ?? {}),
-                              refund_deadline: 
instance_default.payments?.refund_deadline
-                            }
-                          })
-                        }}>
-                          <i18n.Translate>default</i18n.Translate>
-                        </button>
-                      </span>
-                    }
-                  />}
-                  {(settings.advanceOrderMode || noDefault_wireDeadline) && 
<InputDuration
-                    name="payments.wire_transfer_deadline"
-                    label={i18n.str`Wire transfer time`}
-                    help={<DeadlineHelp 
duration={value.payments?.wire_transfer_deadline} />}
-                    withoutClear
-                    withForever
-                    tooltip={i18n.str`Time for the exchange to make the wire 
transfer. Time starts after the order is created.`}
-                    side={
-                      <span>
-                        <button class="button" onClick={() => {
-                          valueHandler({
-                            ...value,
-                            payments: {
-                              ...(value.payments ?? {}),
-                              wire_transfer_deadline: 
instance_default.payments?.wire_transfer_deadline
-                            }
-                          })
-                        }}>
-                          <i18n.Translate>default</i18n.Translate>
-                        </button>
-                      </span>
-                    }
-                  />}
-                  {settings.advanceOrderMode && <InputDuration
-                    name="payments.auto_refund_deadline"
-                    label={i18n.str`Auto-refund time`}
-                    help={<DeadlineHelp 
duration={value.payments?.auto_refund_deadline} />}
-                    tooltip={i18n.str`Time until which the wallet will 
automatically check for refunds without user interaction.`}
-                    withForever
-                  />}
-
-                  {settings.advanceOrderMode && <InputCurrency
-                    name="payments.max_fee"
-                    label={i18n.str`Maximum fee`}
-                    tooltip={i18n.str`Maximum fees the merchant is willing to 
cover for this order. Higher deposit fees must be covered in full by the 
consumer.`}
-                  />}
-                  {settings.advanceOrderMode && <InputToggle
-                    name="payments.createToken"
-                    label={i18n.str`Create token`}
-                    tooltip={i18n.str`If the order ID is easy to guess the 
token will prevent user to steal orders from others.`}
-                  />}
-                  {settings.advanceOrderMode && <InputNumber
-                    name="payments.minimum_age"
-                    label={i18n.str`Minimum age required`}
-                    tooltip={i18n.str`Any value greater than 0 will limit the 
coins able be used to pay this contract. If empty the age restriction will be 
defined by the products`}
-                    help={
-                      minAgeByProducts > 0
-                        ? i18n.str`Min age defined by the producs is 
${minAgeByProducts}`
-                        : i18n.str`No product with age restriction in this 
order`
-                    }
-                  />}
+                  {(settings.advanceOrderMode || noDefault_payDeadline) && (
+                    <InputDuration
+                      name="payments.pay_deadline"
+                      label={i18n.str`Payment time`}
+                      help={
+                        <DeadlineHelp duration={value.payments?.pay_deadline} 
/>
+                      }
+                      withForever
+                      withoutClear
+                      tooltip={i18n.str`Time for the customer to pay for the 
offer before it expires. Inventory products will be reserved until this 
deadline. Time start to run after the order is created.`}
+                      side={
+                        <span>
+                          <button
+                            class="button"
+                            onClick={() => {
+                              const c = {
+                                ...value,
+                                payments: {
+                                  ...(value.payments ?? {}),
+                                  pay_deadline:
+                                    instance_default.payments?.pay_deadline,
+                                },
+                              };
+                              valueHandler(c);
+                            }}
+                          >
+                            <i18n.Translate>default</i18n.Translate>
+                          </button>
+                        </span>
+                      }
+                    />
+                  )}
+                  {settings.advanceOrderMode && (
+                    <InputDuration
+                      name="payments.refund_deadline"
+                      label={i18n.str`Refund time`}
+                      help={
+                        <DeadlineHelp
+                          duration={value.payments?.refund_deadline}
+                        />
+                      }
+                      withForever
+                      withoutClear
+                      tooltip={i18n.str`Time while the order can be refunded 
by the merchant. Time starts after the order is created.`}
+                      side={
+                        <span>
+                          <button
+                            class="button"
+                            onClick={() => {
+                              valueHandler({
+                                ...value,
+                                payments: {
+                                  ...(value.payments ?? {}),
+                                  refund_deadline:
+                                    instance_default.payments?.refund_deadline,
+                                },
+                              });
+                            }}
+                          >
+                            <i18n.Translate>default</i18n.Translate>
+                          </button>
+                        </span>
+                      }
+                    />
+                  )}
+                  {(settings.advanceOrderMode || noDefault_wireDeadline) && (
+                    <InputDuration
+                      name="payments.wire_transfer_deadline"
+                      label={i18n.str`Wire transfer time`}
+                      help={
+                        <DeadlineHelp
+                          duration={value.payments?.wire_transfer_deadline}
+                        />
+                      }
+                      withoutClear
+                      withForever
+                      tooltip={i18n.str`Time for the exchange to make the wire 
transfer. Time starts after the order is created.`}
+                      side={
+                        <span>
+                          <button
+                            class="button"
+                            onClick={() => {
+                              valueHandler({
+                                ...value,
+                                payments: {
+                                  ...(value.payments ?? {}),
+                                  wire_transfer_deadline:
+                                    instance_default.payments
+                                      ?.wire_transfer_deadline,
+                                },
+                              });
+                            }}
+                          >
+                            <i18n.Translate>default</i18n.Translate>
+                          </button>
+                        </span>
+                      }
+                    />
+                  )}
+                  {settings.advanceOrderMode && (
+                    <InputDuration
+                      name="payments.auto_refund_deadline"
+                      label={i18n.str`Auto-refund time`}
+                      help={
+                        <DeadlineHelp
+                          duration={value.payments?.auto_refund_deadline}
+                        />
+                      }
+                      tooltip={i18n.str`Time until which the wallet will 
automatically check for refunds without user interaction.`}
+                      withForever
+                    />
+                  )}
+
+                  {settings.advanceOrderMode && (
+                    <InputCurrency
+                      name="payments.max_fee"
+                      label={i18n.str`Maximum fee`}
+                      tooltip={i18n.str`Maximum fees the merchant is willing 
to cover for this order. Higher deposit fees must be covered in full by the 
consumer.`}
+                    />
+                  )}
+                  {settings.advanceOrderMode && (
+                    <InputToggle
+                      name="payments.createToken"
+                      label={i18n.str`Create token`}
+                      tooltip={i18n.str`If the order ID is easy to guess the 
token will prevent user to steal orders from others.`}
+                    />
+                  )}
+                  {settings.advanceOrderMode && (
+                    <InputNumber
+                      name="payments.minimum_age"
+                      label={i18n.str`Minimum age required`}
+                      tooltip={i18n.str`Any value greater than 0 will limit 
the coins able be used to pay this contract. If empty the age restriction will 
be defined by the products`}
+                      help={
+                        minAgeByProducts > 0
+                          ? i18n.str`Min age defined by the producs is 
${minAgeByProducts}`
+                          : i18n.str`No product with age restriction in this 
order`
+                      }
+                    />
+                  )}
                 </InputGroup>
-              }
+              )}
 
-              {settings.advanceOrderMode &&
+              {settings.advanceOrderMode && (
                 <InputGroup
                   name="extra"
                   label={i18n.str`Additional information`}
                   tooltip={i18n.str`Custom information to be included in the 
contract for this order.`}
                 >
-                  {Object.keys(value.extra ?? {}).map((key) => {
-
-                    return <Input
-                      name={`extra.${key}`}
-                      inputType="multiline"
-                      label={key}
-                      tooltip={i18n.str`You must enter a value in JavaScript 
Object Notation (JSON).`}
-                      side={
-                        <button class="button" onClick={(e) => {
-                          if (value.extra && value.extra[key] !== undefined) {
-                            console.log(value.extra)
-                            delete value.extra[key]
-                          }
-                          valueHandler({
-                            ...value,
-                          })
-                        }}>remove</button>
-                      }
-                    />
+                  {Object.keys(value.extra ?? {}).map((key, idx) => {
+                    return (
+                      <Input
+                        name={`extra.${key}`}
+                        key={String(idx)}
+                        inputType="multiline"
+                        label={key}
+                        tooltip={i18n.str`You must enter a value in JavaScript 
Object Notation (JSON).`}
+                        side={
+                          <button
+                            class="button"
+                            onClick={(e) => {
+                              if (
+                                value.extra &&
+                                value.extra[key] !== undefined
+                              ) {
+                                console.log(value.extra);
+                                delete value.extra[key];
+                              }
+                              valueHandler({
+                                ...value,
+                              });
+                              e.preventDefault();
+                            }}
+                          >
+                            remove
+                          </button>
+                        }
+                      />
+                    );
                   })}
                   <div class="field is-horizontal">
                     <div class="field-label is-normal">
                       <label class="label">
                         <i18n.Translate>Custom field name</i18n.Translate>
-                        <span class="icon has-tooltip-right" 
data-tooltip={"new extra field"}>
+                        <span
+                          class="icon has-tooltip-right"
+                          data-tooltip={"new extra field"}
+                        >
                           <i class="mdi mdi-information" />
                         </span>
                       </label>
@@ -632,23 +715,33 @@ export function CreatePage({
                     <div class="field-body is-flex-grow-3">
                       <div class="field">
                         <p class="control">
-                          <input class="input " value={newField} onChange={(e) 
=> setNewField(e.currentTarget.value)} />
+                          <input
+                            class="input "
+                            value={newField}
+                            onChange={(e) => 
setNewField(e.currentTarget.value)}
+                          />
                         </p>
                       </div>
                     </div>
-                    <button class="button" onClick={(e) => {
-                      setNewField("")
-                      valueHandler({
-                        ...value,
-                        extra: {
-                          ...(value.extra ?? {}),
-                          [newField]: ""
-                        }
-                      })
-                    }}>add</button>
+                    <button
+                      class="button"
+                      onClick={(e) => {
+                        setNewField("");
+                        valueHandler({
+                          ...value,
+                          extra: {
+                            ...(value.extra ?? {}),
+                            [newField]: "",
+                          },
+                        });
+                        e.preventDefault();
+                      }}
+                    >
+                      add
+                    </button>
                   </div>
                 </InputGroup>
-              }
+              )}
             </FormProvider>
 
             <div class="buttons is-right mt-5">
@@ -686,20 +779,24 @@ function asProduct(p: ProductAndQuantity): 
MerchantBackend.Product {
   };
 }
 
-
 function DeadlineHelp({ duration }: { duration?: Duration }): VNode {
   const { i18n } = useTranslationContext();
-  const [now, setNow] = useState(AbsoluteTime.now())
+  const [now, setNow] = useState(AbsoluteTime.now());
   useEffect(() => {
     const iid = setInterval(() => {
-      setNow(AbsoluteTime.now())
-    }, 60 * 1000)
+      setNow(AbsoluteTime.now());
+    }, 60 * 1000);
     return () => {
-      clearInterval(iid)
-    }
-  })
-  if (!duration) return <i18n.Translate>Disabled</i18n.Translate>
-  const when = AbsoluteTime.addDuration(now, duration)
-  if (when.t_ms === "never") return <i18n.Translate>No 
deadline</i18n.Translate>
-  return <i18n.Translate>Deadline at {format(when.t_ms, "dd/MM/yy 
HH:mm")}</i18n.Translate>
+      clearInterval(iid);
+    };
+  });
+  if (!duration) return <i18n.Translate>Disabled</i18n.Translate>;
+  const when = AbsoluteTime.addDuration(now, duration);
+  if (when.t_ms === "never")
+    return <i18n.Translate>No deadline</i18n.Translate>;
+  return (
+    <i18n.Translate>
+      Deadline at {format(when.t_ms, "dd/MM/yy HH:mm")}
+    </i18n.Translate>
+  );
 }
diff --git 
a/packages/merchant-backoffice-ui/src/paths/instance/orders/details/DetailPage.tsx
 
b/packages/merchant-backoffice-ui/src/paths/instance/orders/details/DetailPage.tsx
index 1efaaf6e0..69e9df52e 100644
--- 
a/packages/merchant-backoffice-ui/src/paths/instance/orders/details/DetailPage.tsx
+++ 
b/packages/merchant-backoffice-ui/src/paths/instance/orders/details/DetailPage.tsx
@@ -20,7 +20,7 @@
  */
 
 import { AmountJson, Amounts, stringifyRefundUri } from 
"@gnu-taler/taler-util";
-import { useMerchantApiContext, useTranslationContext } from 
"@gnu-taler/web-util/browser";
+import { useTranslationContext } from "@gnu-taler/web-util/browser";
 import { format, formatDistance } from "date-fns";
 import { Fragment, VNode, h } from "preact";
 import { useState } from "preact/hooks";
@@ -33,6 +33,7 @@ import { InputGroup } from 
"../../../../components/form/InputGroup.js";
 import { InputLocation } from "../../../../components/form/InputLocation.js";
 import { TextField } from "../../../../components/form/TextField.js";
 import { ProductList } from "../../../../components/product/ProductList.js";
+import { useSessionContext } from "../../../../context/session.js";
 import { MerchantBackend } from "../../../../declaration.js";
 import { datetimeFormatForSettings, usePreference } from 
"../../../../hooks/preference.js";
 import { mergeRefunds } from "../../../../utils/amount.js";
@@ -415,10 +416,12 @@ function PaidPage({
   })
 
   const [value, valueHandler] = useState<Partial<Paid>>(order);
-  const { url: backendURL } = useMerchantApiContext();
+  const {
+    state: { backendUrl },
+  } = useSessionContext();
 
   const refundurl = stringifyRefundUri({
-    merchantBaseUrl: backendURL.href,
+    merchantBaseUrl: backendUrl,
     orderId: order.contract_terms.order_id
   })
   const refundable =
@@ -764,7 +767,3 @@ export function DetailPage({ id, selected, onRefund, onBack 
}: Props): VNode {
     </Fragment>
   );
 }
-
-async function copyToClipboard(text: string) {
-  return navigator.clipboard.writeText(text);
-}
diff --git 
a/packages/merchant-backoffice-ui/src/paths/instance/orders/list/Table.tsx 
b/packages/merchant-backoffice-ui/src/paths/instance/orders/list/Table.tsx
index 87e84945c..cebc4afe6 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/orders/list/Table.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/orders/list/Table.tsx
@@ -20,7 +20,10 @@
  */
 
 import { Amounts } from "@gnu-taler/taler-util";
-import { useTranslationContext } from "@gnu-taler/web-util/browser";
+import {
+  useMerchantApiContext,
+  useTranslationContext,
+} from "@gnu-taler/web-util/browser";
 import { format } from "date-fns";
 import { h, VNode } from "preact";
 import { StateUpdater, useState } from "preact/hooks";
@@ -33,10 +36,12 @@ import { InputCurrency } from 
"../../../../components/form/InputCurrency.js";
 import { InputGroup } from "../../../../components/form/InputGroup.js";
 import { InputSelector } from "../../../../components/form/InputSelector.js";
 import { ConfirmModal } from "../../../../components/modal/index.js";
-import { useConfigContext } from "../../../../context/config.js";
 import { MerchantBackend, WithId } from "../../../../declaration.js";
 import { mergeRefunds } from "../../../../utils/amount.js";
-import { datetimeFormatForSettings, usePreference } from 
"../../../../hooks/preference.js";
+import {
+  datetimeFormatForSettings,
+  usePreference,
+} from "../../../../hooks/preference.js";
 
 type Entity = MerchantBackend.Orders.OrderHistoryEntry & WithId;
 interface Props {
@@ -141,10 +146,7 @@ function Table({
   return (
     <div class="table-container">
       {hasMoreBefore && (
-        <button
-          class="button is-fullwidth"
-          onClick={onLoadMoreBefore}
-        >
+        <button class="button is-fullwidth" onClick={onLoadMoreBefore}>
           <i18n.Translate>load newer orders</i18n.Translate>
         </button>
       )}
@@ -174,9 +176,9 @@ function Table({
                   {i.timestamp.t_s === "never"
                     ? "never"
                     : format(
-                      new Date(i.timestamp.t_s * 1000),
-                      datetimeFormatForSettings(settings),
-                    )}
+                        new Date(i.timestamp.t_s * 1000),
+                        datetimeFormatForSettings(settings),
+                      )}
                 </td>
                 <td
                   onClick={(): void => onSelect(i)}
@@ -218,10 +220,7 @@ function Table({
         </tbody>
       </table>
       {hasMoreAfter && (
-        <button
-          class="button is-fullwidth"
-          onClick={onLoadMoreAfter}
-        >
+        <button class="button is-fullwidth" onClick={onLoadMoreAfter}>
           <i18n.Translate>load older orders</i18n.Translate>
         </button>
       )}
@@ -268,7 +267,7 @@ export function RefundModal({
     order.order_status === "paid" ? order.refund_details : []
   ).reduce(mergeRefunds, []);
 
-  const config = useConfigContext();
+  const { config } = useMerchantApiContext();
   const totalRefunded = refunds
     .map((r) => r.amount)
     .reduce(
@@ -362,9 +361,9 @@ export function RefundModal({
                           {r.timestamp.t_s === "never"
                             ? "never"
                             : format(
-                              new Date(r.timestamp.t_s * 1000),
-                              datetimeFormatForSettings(settings),
-                            )}
+                                new Date(r.timestamp.t_s * 1000),
+                                datetimeFormatForSettings(settings),
+                              )}
                         </td>
                         <td>{r.amount}</td>
                         <td>{r.reason}</td>
diff --git 
a/packages/merchant-backoffice-ui/src/paths/instance/otp_devices/create/CreatePage.tsx
 
b/packages/merchant-backoffice-ui/src/paths/instance/otp_devices/create/CreatePage.tsx
index 83345de3e..930a0d82c 100644
--- 
a/packages/merchant-backoffice-ui/src/paths/instance/otp_devices/create/CreatePage.tsx
+++ 
b/packages/merchant-backoffice-ui/src/paths/instance/otp_devices/create/CreatePage.tsx
@@ -34,7 +34,6 @@ import {
 import { Input } from "../../../../components/form/Input.js";
 import { InputSelector } from "../../../../components/form/InputSelector.js";
 import { InputWithAddon } from "../../../../components/form/InputWithAddon.js";
-import { useBackendContext } from "../../../../context/backend.js";
 import { MerchantBackend } from "../../../../declaration.js";
 
 type Entity = MerchantBackend.OTP.OtpDeviceAddDetails;
@@ -49,7 +48,6 @@ const algorithmsNames = ["off", "30s 8d TOTP-SHA1", "30s 8d 
eTOTP-SHA1"];
 
 export function CreatePage({ onCreate, onBack }: Props): VNode {
   const { i18n } = useTranslationContext();
-  const backend = useBackendContext();
 
   const [state, setState] = useState<Partial<Entity>>({});
 
@@ -145,6 +143,7 @@ export function CreatePage({ onCreate, onBack }: Props): 
VNode {
                             ...s,
                             otp_key: randomRfc3548Base32Key(),
                           }));
+                          e.preventDefault();
                         }}
                       >
                         <i18n.Translate>random</i18n.Translate>
diff --git 
a/packages/merchant-backoffice-ui/src/paths/instance/otp_devices/create/CreatedSuccessfully.tsx
 
b/packages/merchant-backoffice-ui/src/paths/instance/otp_devices/create/CreatedSuccessfully.tsx
index c6591cdbe..60abc3ca6 100644
--- 
a/packages/merchant-backoffice-ui/src/paths/instance/otp_devices/create/CreatedSuccessfully.tsx
+++ 
b/packages/merchant-backoffice-ui/src/paths/instance/otp_devices/create/CreatedSuccessfully.tsx
@@ -15,12 +15,11 @@
  */
 
 import { useTranslationContext } from "@gnu-taler/web-util/browser";
-import { Fragment, VNode, h } from "preact";
+import { VNode, h } from "preact";
 import { QR } from "../../../../components/exception/QR.js";
 import { CreatedSuccessfully as Template } from 
"../../../../components/notifications/CreatedSuccessfully.js";
-import { useInstanceContext } from "../../../../context/instance.js";
+import { useSessionContext } from "../../../../context/session.js";
 import { MerchantBackend } from "../../../../declaration.js";
-import { useBackendContext } from "../../../../context/backend.js";
 
 type Entity = MerchantBackend.OTP.OtpDeviceAddDetails;
 
@@ -34,11 +33,13 @@ export function CreatedSuccessfully({
   onConfirm,
 }: Props): VNode {
   const { i18n } = useTranslationContext();
-  const { url: backendURL } = useBackendContext()
-  const { id: instanceId } = useInstanceContext();
-  const issuer = new URL(backendURL).hostname;
-  const qrText = 
`otpauth://totp/${instanceId}/${entity.otp_device_id}?issuer=${issuer}&algorithm=SHA1&digits=8&period=30&secret=${entity.otp_key}`;
-  const qrTextSafe = 
`otpauth://totp/${instanceId}/${entity.otp_device_id}?issuer=${issuer}&algorithm=SHA1&digits=8&period=30&secret=${entity.otp_key.substring(0,
 6)}...`;
+  const {
+    state: { backendUrl },
+  } = useSessionContext();
+  const { state } = useSessionContext();
+  const issuer = backendUrl;
+  const qrText = 
`otpauth://totp/${state.instance}/${entity.otp_device_id}?issuer=${issuer}&algorithm=SHA1&digits=8&period=30&secret=${entity.otp_key}`;
+  const qrTextSafe = 
`otpauth://totp/${state.instance}/${entity.otp_device_id}?issuer=${issuer}&algorithm=SHA1&digits=8&period=30&secret=${entity.otp_key.substring(0,
 6)}...`;
 
   return (
     <Template onConfirm={onConfirm} >
diff --git 
a/packages/merchant-backoffice-ui/src/paths/instance/templates/create/CreatePage.tsx
 
b/packages/merchant-backoffice-ui/src/paths/instance/templates/create/CreatePage.tsx
index d27f6a022..b07582252 100644
--- 
a/packages/merchant-backoffice-ui/src/paths/instance/templates/create/CreatePage.tsx
+++ 
b/packages/merchant-backoffice-ui/src/paths/instance/templates/create/CreatePage.tsx
@@ -23,11 +23,12 @@ import {
   AmountString,
   Amounts,
   Duration,
-  MerchantTemplateContractDetails,
   assertUnreachable,
 } from "@gnu-taler/taler-util";
-import { useTranslationContext } from "@gnu-taler/web-util/browser";
-import { Fragment, VNode, h } from "preact";
+import {
+  useTranslationContext
+} from "@gnu-taler/web-util/browser";
+import { VNode, h } from "preact";
 import { useState } from "preact/hooks";
 import { AsyncButton } from "../../../../components/exception/AsyncButton.js";
 import {
@@ -39,12 +40,11 @@ import { InputCurrency } from 
"../../../../components/form/InputCurrency.js";
 import { InputDuration } from "../../../../components/form/InputDuration.js";
 import { InputNumber } from "../../../../components/form/InputNumber.js";
 import { InputSearchOnList } from 
"../../../../components/form/InputSearchOnList.js";
+import { InputTab } from "../../../../components/form/InputTab.js";
 import { InputWithAddon } from "../../../../components/form/InputWithAddon.js";
-import { useBackendContext } from "../../../../context/backend.js";
+import { useSessionContext } from "../../../../context/session.js";
 import { MerchantBackend } from "../../../../declaration.js";
 import { useInstanceOtpDevices } from "../../../../hooks/otp.js";
-import { undefinedIfEmpty } from "../../../../utils/table.js";
-import { InputTab } from "../../../../components/form/InputTab.js";
 
 enum Steps {
   BOTH_FIXED,
@@ -55,14 +55,14 @@ enum Steps {
 
 // type Entity = MerchantBackend.Template.TemplateAddDetails & { type: Steps };
 type Entity = {
-  id?: string,
-  description?: string,
-  otpId?: string,
-  summary?: string,
-  amount?: AmountString,
-  minimum_age?: number,
-  pay_duration?: Duration,
-  type: Steps,
+  id?: string;
+  description?: string;
+  otpId?: string;
+  summary?: string;
+  amount?: AmountString;
+  minimum_age?: number;
+  pay_duration?: Duration;
+  type: Steps;
 };
 
 interface Props {
@@ -72,8 +72,10 @@ interface Props {
 
 export function CreatePage({ onCreate, onBack }: Props): VNode {
   const { i18n } = useTranslationContext();
-  const { url: backendURL } = useBackendContext()
-  const devices = useInstanceOtpDevices()
+  const {
+    state: { backendUrl },
+  } = useSessionContext();
+  const devices = useInstanceOtpDevices();
 
   const [state, setState] = useState<Partial<Entity>>({
     minimum_age: 0,
@@ -83,9 +85,7 @@ export function CreatePage({ onCreate, onBack }: Props): 
VNode {
     type: Steps.NON_FIXED,
   });
 
-  const parsedPrice = !state.amount
-    ? undefined
-    : Amounts.parse(state.amount);
+  const parsedPrice = !state.amount ? undefined : Amounts.parse(state.amount);
 
   const errors: FormErrors<Entity> = {
     id: !state.id
@@ -93,10 +93,10 @@ export function CreatePage({ onCreate, onBack }: Props): 
VNode {
       : !/[a-zA-Z0-9]*/.test(state.id)
         ? i18n.str`no valid. only characters and numbers`
         : undefined,
-    description: !state.description
-      ? i18n.str`should not be empty`
-      : undefined,
-    amount: !(state.type === Steps.FIXED_PRICE || state.type === 
Steps.BOTH_FIXED)
+    description: !state.description ? i18n.str`should not be empty` : 
undefined,
+    amount: !(
+      state.type === Steps.FIXED_PRICE || state.type === Steps.BOTH_FIXED
+    )
       ? undefined
       : !state.amount
         ? i18n.str`required`
@@ -105,7 +105,9 @@ export function CreatePage({ onCreate, onBack }: Props): 
VNode {
           : Amounts.isZero(parsedPrice)
             ? i18n.str`must be greater than 0`
             : undefined,
-    summary: !(state.type === Steps.FIXED_SUMMARY || state.type === 
Steps.BOTH_FIXED)
+    summary: !(
+      state.type === Steps.FIXED_SUMMARY || state.type === Steps.BOTH_FIXED
+    )
       ? undefined
       : !state.summary
         ? i18n.str`required`
@@ -130,55 +132,60 @@ export function CreatePage({ onCreate, onBack }: Props): 
VNode {
   const submitForm = () => {
     if (hasErrors || state.type === undefined) return Promise.reject();
     switch (state.type) {
-      case Steps.FIXED_PRICE: return onCreate({
-        template_id: state.id!,
-        template_description: state.description!,
-        template_contract: {
-          minimum_age: state.minimum_age!,
-          pay_duration: Duration.toTalerProtocolDuration(state.pay_duration!),
-          amount: state.amount!,
-          // summary: state.summary,
-        },
-        otp_id: state.otpId!
-      })
-      case Steps.FIXED_SUMMARY: return onCreate({
-        template_id: state.id!,
-        template_description: state.description!,
-        template_contract: {
-          minimum_age: state.minimum_age!,
-          pay_duration: Duration.toTalerProtocolDuration(state.pay_duration!),
-          // amount: state.amount!,
-          summary: state.summary,
-        },
-        otp_id: state.otpId!,
-      })
-      case Steps.NON_FIXED: return onCreate({
-        template_id: state.id!,
-        template_description: state.description!,
-        template_contract: {
-          minimum_age: state.minimum_age!,
-          pay_duration: Duration.toTalerProtocolDuration(state.pay_duration!),
-          // amount: state.amount!,
-          // summary: state.summary,
-        },
-        otp_id: state.otpId!,
-      })
-      case Steps.BOTH_FIXED: return onCreate({
-        template_id: state.id!,
-        template_description: state.description!,
-        template_contract: {
-          minimum_age: state.minimum_age!,
-          pay_duration: Duration.toTalerProtocolDuration(state.pay_duration!),
-          amount: state.amount!,
-          summary: state.summary,
-        },
-        otp_id: state.otpId!,
-      })
-      default: assertUnreachable(state.type)
+      case Steps.FIXED_PRICE:
+        return onCreate({
+          template_id: state.id!,
+          template_description: state.description!,
+          template_contract: {
+            minimum_age: state.minimum_age!,
+            pay_duration: 
Duration.toTalerProtocolDuration(state.pay_duration!),
+            amount: state.amount!,
+            // summary: state.summary,
+          },
+          otp_id: state.otpId!,
+        });
+      case Steps.FIXED_SUMMARY:
+        return onCreate({
+          template_id: state.id!,
+          template_description: state.description!,
+          template_contract: {
+            minimum_age: state.minimum_age!,
+            pay_duration: 
Duration.toTalerProtocolDuration(state.pay_duration!),
+            // amount: state.amount!,
+            summary: state.summary,
+          },
+          otp_id: state.otpId!,
+        });
+      case Steps.NON_FIXED:
+        return onCreate({
+          template_id: state.id!,
+          template_description: state.description!,
+          template_contract: {
+            minimum_age: state.minimum_age!,
+            pay_duration: 
Duration.toTalerProtocolDuration(state.pay_duration!),
+            // amount: state.amount!,
+            // summary: state.summary,
+          },
+          otp_id: state.otpId!,
+        });
+      case Steps.BOTH_FIXED:
+        return onCreate({
+          template_id: state.id!,
+          template_description: state.description!,
+          template_contract: {
+            minimum_age: state.minimum_age!,
+            pay_duration: 
Duration.toTalerProtocolDuration(state.pay_duration!),
+            amount: state.amount!,
+            summary: state.summary,
+          },
+          otp_id: state.otpId!,
+        });
+      default:
+        assertUnreachable(state.type);
       // return onCreate(state);
-    };
-  }
-  const deviceList = !devices.ok ? [] : devices.data.otp_devices
+    }
+  };
+  const deviceList = !devices.ok ? [] : devices.data.otp_devices;
 
   return (
     <div>
@@ -193,7 +200,7 @@ export function CreatePage({ onCreate, onBack }: Props): 
VNode {
             >
               <InputWithAddon<Entity>
                 name="id"
-                help={`${backendURL}/templates/${state.id ?? ""}`}
+                help={new URL(`templates/${state.id ?? ""}`, backendUrl).href}
                 label={i18n.str`Identifier`}
                 tooltip={i18n.str`Name of the template in URLs.`}
               />
@@ -207,12 +214,16 @@ export function CreatePage({ onCreate, onBack }: Props): 
VNode {
                 name="type"
                 label={i18n.str`Type`}
                 help={(() => {
-                  if (state.type === undefined) return ""
+                  if (state.type === undefined) return "";
                   switch (state.type) {
-                    case Steps.NON_FIXED: return i18n.str`User will be able to 
input price and summary before payment.`
-                    case Steps.FIXED_PRICE: return i18n.str`User will be able 
to add a summary before payment.`
-                    case Steps.FIXED_SUMMARY: return i18n.str`User will be 
able to set the price before payment.`
-                    case Steps.BOTH_FIXED: return i18n.str`User will not be 
able to change the price or the summary.`
+                    case Steps.NON_FIXED:
+                      return i18n.str`User will be able to input price and 
summary before payment.`;
+                    case Steps.FIXED_PRICE:
+                      return i18n.str`User will be able to add a summary 
before payment.`;
+                    case Steps.FIXED_SUMMARY:
+                      return i18n.str`User will be able to set the price 
before payment.`;
+                    case Steps.BOTH_FIXED:
+                      return i18n.str`User will not be able to change the 
price or the summary.`;
                   }
                 })()}
                 tooltip={i18n.str`Define what the user be allowed to modify`}
@@ -224,28 +235,34 @@ export function CreatePage({ onCreate, onBack }: Props): 
VNode {
                 ]}
                 toStr={(v: Steps): string => {
                   switch (v) {
-                    case Steps.NON_FIXED: return i18n.str`Simple`
-                    case Steps.FIXED_PRICE: return i18n.str`With price`
-                    case Steps.FIXED_SUMMARY: return i18n.str`With summary`
-                    case Steps.BOTH_FIXED: return i18n.str`With price and 
summary`
+                    case Steps.NON_FIXED:
+                      return i18n.str`Simple`;
+                    case Steps.FIXED_PRICE:
+                      return i18n.str`With price`;
+                    case Steps.FIXED_SUMMARY:
+                      return i18n.str`With summary`;
+                    case Steps.BOTH_FIXED:
+                      return i18n.str`With price and summary`;
                   }
                 }}
               />
-              {state.type === Steps.BOTH_FIXED || state.type === 
Steps.FIXED_SUMMARY ?
+              {state.type === Steps.BOTH_FIXED ||
+              state.type === Steps.FIXED_SUMMARY ? (
                 <Input<Entity>
                   name="summary"
                   inputType="multiline"
                   label={i18n.str`Fixed summary`}
                   tooltip={i18n.str`If specified, this template will create 
order with the same summary`}
                 />
-                : undefined}
-              {state.type === Steps.BOTH_FIXED || state.type === 
Steps.FIXED_PRICE ?
+              ) : undefined}
+              {state.type === Steps.BOTH_FIXED ||
+              state.type === Steps.FIXED_PRICE ? (
                 <InputCurrency<Entity>
                   name="amount"
                   label={i18n.str`Fixed price`}
                   tooltip={i18n.str`If specified, this template will create 
order with the same price`}
                 />
-                : undefined}
+              ) : undefined}
               <InputNumber<Entity>
                 name="minimum_age"
                 label={i18n.str`Minimum age`}
@@ -262,28 +279,29 @@ export function CreatePage({ onCreate, onBack }: Props): 
VNode {
                 name="otpId"
                 label={i18n.str`OTP device`}
                 readonly
-                side={<button
-                  class="button is-danger"
-                  data-tooltip={i18n.str`without otp device`}
-                  onClick={(): void => {
-                    setState((v) => ({ ...v, otpId: undefined }));
-                  }}
-                >
-                  <span>
-                    <i18n.Translate>remove</i18n.Translate>
-                  </span>
-                </button>}
+                side={
+                  <button
+                    class="button is-danger"
+                    data-tooltip={i18n.str`without otp device`}
+                    onClick={(): void => {
+                      setState((v) => ({ ...v, otpId: undefined }));
+                    }}
+                  >
+                    <span>
+                      <i18n.Translate>remove</i18n.Translate>
+                    </span>
+                  </button>
+                }
                 tooltip={i18n.str`Use to verify transaction in offline mode.`}
               />
               <InputSearchOnList
                 label={i18n.str`Search device`}
                 onChange={(p) => setState((v) => ({ ...v, otpId: p?.id }))}
-                list={deviceList.map(e => ({
+                list={deviceList.map((e) => ({
                   description: e.device_description,
-                  id: e.otp_device_id
+                  id: e.otp_device_id,
                 }))}
               />
-
             </FormProvider>
 
             <div class="buttons is-right mt-5">
diff --git 
a/packages/merchant-backoffice-ui/src/paths/instance/templates/qr/QrPage.tsx 
b/packages/merchant-backoffice-ui/src/paths/instance/templates/qr/QrPage.tsx
index 809151565..1aa5bc317 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/templates/qr/QrPage.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/templates/qr/QrPage.tsx
@@ -20,7 +20,10 @@
  */
 
 import { stringifyPayTemplateUri } from "@gnu-taler/taler-util";
-import { useTranslationContext } from "@gnu-taler/web-util/browser";
+import {
+  useMerchantApiContext,
+  useTranslationContext,
+} from "@gnu-taler/web-util/browser";
 import { VNode, h } from "preact";
 import { useState } from "preact/hooks";
 import { QR } from "../../../../components/exception/QR.js";
@@ -30,9 +33,7 @@ import {
 } from "../../../../components/form/FormProvider.js";
 import { Input } from "../../../../components/form/Input.js";
 import { InputCurrency } from "../../../../components/form/InputCurrency.js";
-import { useBackendContext } from "../../../../context/backend.js";
-import { useConfigContext } from "../../../../context/config.js";
-import { useInstanceContext } from "../../../../context/instance.js";
+import { useSessionContext } from "../../../../context/session.js";
 import { MerchantBackend } from "../../../../declaration.js";
 
 type Entity = MerchantBackend.Template.UsingTemplateDetails;
@@ -45,9 +46,10 @@ interface Props {
 
 export function QrPage({ contract, id: templateId, onBack }: Props): VNode {
   const { i18n } = useTranslationContext();
-  const { url: backendURL } = useBackendContext()
-  const { id: instanceId } = useInstanceContext();
-  const config = useConfigContext();
+  const {
+    state: { backendUrl },
+  } = useSessionContext();
+  const { config } = useMerchantApiContext();
 
   const [state, setState] = useState<Partial<Entity>>({
     amount: contract.amount,
@@ -59,30 +61,26 @@ export function QrPage({ contract, id: templateId, onBack 
}: Props): VNode {
   const fixedAmount = !!contract.amount;
   const fixedSummary = !!contract.summary;
 
-  const templateParams: Record<string, string> = {}
+  const templateParams: Record<string, string> = {};
   if (!fixedAmount) {
     if (state.amount) {
-      templateParams.amount = state.amount
+      templateParams.amount = state.amount;
     } else {
-      templateParams.amount = config.currency
+      templateParams.amount = config.currency;
     }
   }
 
   if (!fixedSummary) {
-    templateParams.summary = state.summary ?? ""
+    templateParams.summary = state.summary ?? "";
   }
 
-  const merchantBaseUrl = new URL(backendURL).href;
+  const merchantBaseUrl = backendUrl;
 
   const payTemplateUri = stringifyPayTemplateUri({
     merchantBaseUrl,
     templateId,
-    templateParams
-  })
-
-  const issuer = encodeURIComponent(
-    `${new URL(backendURL).host}/${instanceId}`,
-  );
+    templateParams,
+  });
 
   return (
     <div>
diff --git 
a/packages/merchant-backoffice-ui/src/paths/instance/templates/update/UpdatePage.tsx
 
b/packages/merchant-backoffice-ui/src/paths/instance/templates/update/UpdatePage.tsx
index cdf2ebab4..ae11ad991 100644
--- 
a/packages/merchant-backoffice-ui/src/paths/instance/templates/update/UpdatePage.tsx
+++ 
b/packages/merchant-backoffice-ui/src/paths/instance/templates/update/UpdatePage.tsx
@@ -26,7 +26,7 @@ import {
   assertUnreachable
 } from "@gnu-taler/taler-util";
 import { useTranslationContext } from "@gnu-taler/web-util/browser";
-import { Fragment, VNode, h } from "preact";
+import { VNode, h } from "preact";
 import { useState } from "preact/hooks";
 import { AsyncButton } from "../../../../components/exception/AsyncButton.js";
 import {
@@ -37,11 +37,10 @@ import { Input } from 
"../../../../components/form/Input.js";
 import { InputCurrency } from "../../../../components/form/InputCurrency.js";
 import { InputDuration } from "../../../../components/form/InputDuration.js";
 import { InputNumber } from "../../../../components/form/InputNumber.js";
+import { InputSearchOnList } from 
"../../../../components/form/InputSearchOnList.js";
 import { InputTab } from "../../../../components/form/InputTab.js";
-import { InputWithAddon } from "../../../../components/form/InputWithAddon.js";
-import { useBackendContext } from "../../../../context/backend.js";
+import { useSessionContext } from "../../../../context/session.js";
 import { MerchantBackend } from "../../../../declaration.js";
-import { InputSearchOnList } from 
"../../../../components/form/InputSearchOnList.js";
 import { useInstanceOtpDevices } from "../../../../hooks/otp.js";
 
 enum Steps {
@@ -68,7 +67,10 @@ interface Props {
 
 export function UpdatePage({ template, onUpdate, onBack }: Props): VNode {
   const { i18n } = useTranslationContext();
-  const { url: backendURL } = useBackendContext()
+  const {
+    state: { backendUrl },
+  } = useSessionContext();
+
 
   const intialStep =
     template.template_contract.amount === undefined && 
template.template_contract.summary === undefined
@@ -187,7 +189,7 @@ export function UpdatePage({ template, onUpdate, onBack }: 
Props): VNode {
               <div class="level-left">
                 <div class="level-item">
                   <span class="is-size-4">
-                    {backendURL}/templates/{template.otp_id}
+                    {new URL(`templates/${template.otp_id}`,backendUrl).href}
                   </span>
                 </div>
               </div>
diff --git 
a/packages/merchant-backoffice-ui/src/paths/instance/token/DetailPage.tsx 
b/packages/merchant-backoffice-ui/src/paths/instance/token/DetailPage.tsx
index 1e9186624..f2b1db29b 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/token/DetailPage.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/token/DetailPage.tsx
@@ -25,19 +25,23 @@ import { useState } from "preact/hooks";
 import { AsyncButton } from "../../../components/exception/AsyncButton.js";
 import { FormProvider } from "../../../components/form/FormProvider.js";
 import { Input } from "../../../components/form/Input.js";
-import { useInstanceContext } from "../../../context/instance.js";
-import { AccessToken } from "../../../declaration.js";
 import { NotificationCard } from "../../../components/menu/index.js";
+import { useSessionContext } from "../../../context/session.js";
+import { AccessToken } from "@gnu-taler/taler-util";
 
 interface Props {
-  instanceId: string;
   hasToken: boolean | undefined;
   onClearToken: (c: AccessToken | undefined) => void;
   onNewToken: (c: AccessToken | undefined, s: AccessToken) => void;
   onBack?: () => void;
 }
 
-export function DetailPage({ instanceId, hasToken, onBack, onNewToken, 
onClearToken }: Props): VNode {
+export function DetailPage({
+  hasToken,
+  onBack,
+  onNewToken,
+  onClearToken,
+}: Props): VNode {
   type State = { old_token: string; new_token: string; repeat_token: string };
   const [form, setValue] = useState<Partial<State>>({
     old_token: "",
@@ -47,9 +51,10 @@ export function DetailPage({ instanceId, hasToken, onBack, 
onNewToken, onClearTo
   const { i18n } = useTranslationContext();
 
   const errors = {
-    old_token: hasToken && !form.old_token
-      ? i18n.str`you need your access token to perform the operation`
-      : undefined,
+    old_token:
+      hasToken && !form.old_token
+        ? i18n.str`you need your access token to perform the operation`
+        : undefined,
     new_token: !form.new_token
       ? i18n.str`cannot be empty`
       : form.new_token === form.old_token
@@ -65,15 +70,17 @@ export function DetailPage({ instanceId, hasToken, onBack, 
onNewToken, onClearTo
     (k) => (errors as any)[k] !== undefined,
   );
 
-  const instance = useInstanceContext();
+  const { state } = useSessionContext();
 
-  const text = i18n.str`You are updating the access token from instance with 
id "${instance.id}"`;
+  const text = i18n.str`You are updating the access token from instance with 
id "${state.instance}"`;
 
   async function submitForm() {
     if (hasErrors) return;
-    const oldToken = hasToken ? `secret-token:${form.old_token}` as 
AccessToken : undefined;
+    const oldToken = hasToken
+      ? (`secret-token:${form.old_token}` as AccessToken)
+      : undefined;
     const newToken = `secret-token:${form.new_token}` as AccessToken;
-    onNewToken(oldToken, newToken)
+    onNewToken(oldToken, newToken);
   }
 
   return (
@@ -84,9 +91,7 @@ export function DetailPage({ instanceId, hasToken, onBack, 
onNewToken, onClearTo
             <div class="level">
               <div class="level-left">
                 <div class="level-item">
-                  <span class="is-size-4">
-                    {text}
-                  </span>
+                  <span class="is-size-4">{text}</span>
                 </div>
               </div>
             </div>
@@ -94,7 +99,7 @@ export function DetailPage({ instanceId, hasToken, onBack, 
onNewToken, onClearTo
         </section>
         <hr />
 
-        {!hasToken &&
+        {!hasToken && (
           <NotificationCard
             notification={{
               message: i18n.str`This instance doesn't have authentication 
token.`,
@@ -102,7 +107,7 @@ export function DetailPage({ instanceId, hasToken, onBack, 
onNewToken, onClearTo
               type: "WARN",
             }}
           />
-        }
+        )}
 
         <div class="columns">
           <div class="column" />
@@ -119,7 +124,8 @@ export function DetailPage({ instanceId, hasToken, onBack, 
onNewToken, onClearTo
                     />
                     <p>
                       <i18n.Translate>
-                        Clearing the access token will mean public access to 
the instance.
+                        Clearing the access token will mean public access to 
the
+                        instance.
                       </i18n.Translate>
                     </p>
                     <div class="buttons is-right mt-5">
@@ -127,10 +133,11 @@ export function DetailPage({ instanceId, hasToken, 
onBack, onNewToken, onClearTo
                         class="button"
                         onClick={() => {
                           if (hasToken) {
-                            const oldToken = `secret-token:${form.old_token}` 
as AccessToken;
-                            onClearToken(oldToken)
+                            const oldToken =
+                              `secret-token:${form.old_token}` as AccessToken;
+                            onClearToken(oldToken);
                           } else {
-                            onClearToken(undefined)
+                            onClearToken(undefined);
                           }
                         }}
                       >
@@ -140,7 +147,6 @@ export function DetailPage({ instanceId, hasToken, onBack, 
onNewToken, onClearTo
                   </Fragment>
                 )}
 
-
                 <Input<State>
                   name="new_token"
                   label={i18n.str`New access token`}
@@ -176,7 +182,6 @@ export function DetailPage({ instanceId, hasToken, onBack, 
onNewToken, onClearTo
           </div>
           <div class="column" />
         </div>
-
       </section>
     </div>
   );
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/token/index.tsx 
b/packages/merchant-backoffice-ui/src/paths/instance/token/index.tsx
index 13642ec22..d7bf7a6d5 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/token/index.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/token/index.tsx
@@ -16,15 +16,13 @@
 import { HttpStatusCode } from "@gnu-taler/taler-util";
 import { ErrorType, HttpError, useTranslationContext } from 
"@gnu-taler/web-util/browser";
 import { Fragment, VNode, h } from "preact";
-import { Loading } from "../../../components/exception/loading.js";
-import { AccessToken, MerchantBackend } from "../../../declaration.js";
-import { useInstanceAPI, useInstanceDetails } from 
"../../../hooks/instance.js";
-import { DetailPage } from "./DetailPage.js";
-import { useInstanceContext } from "../../../context/instance.js";
 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 { useInstanceAPI, useInstanceDetails } from 
"../../../hooks/instance.js";
 import { Notification } from "../../../utils/types.js";
-import { useBackendContext } from "../../../context/backend.js";
+import { DetailPage } from "./DetailPage.js";
 
 interface Props {
   onUnauthorized: () => VNode;
@@ -45,7 +43,6 @@ export default function Token({
 
   const [notif, setNotif] = useState<Notification | undefined>(undefined);
   const { clearAccessToken, setNewAccessToken } = useInstanceAPI();
-  const { id } = useInstanceContext();
   const result = useInstanceDetails()
 
   if (result.loading) return <Loading />;
@@ -69,7 +66,6 @@ export default function Token({
     <Fragment>
       <NotificationCard notification={notif} />
       <DetailPage
-        instanceId={id}
         onBack={onCancel}
         hasToken={hasToken}
         onClearToken={async (currentToken): Promise<void> => {
diff --git 
a/packages/merchant-backoffice-ui/src/paths/instance/transfers/create/CreatePage.tsx
 
b/packages/merchant-backoffice-ui/src/paths/instance/transfers/create/CreatePage.tsx
index eb25045a0..576c21cd2 100644
--- 
a/packages/merchant-backoffice-ui/src/paths/instance/transfers/create/CreatePage.tsx
+++ 
b/packages/merchant-backoffice-ui/src/paths/instance/transfers/create/CreatePage.tsx
@@ -20,7 +20,7 @@
  */
 
 import { useTranslationContext } from "@gnu-taler/web-util/browser";
-import { h, VNode } from "preact";
+import { VNode, h } from "preact";
 import { useState } from "preact/hooks";
 import { AsyncButton } from "../../../../components/exception/AsyncButton.js";
 import {
@@ -30,7 +30,6 @@ import {
 import { Input } from "../../../../components/form/Input.js";
 import { InputCurrency } from "../../../../components/form/InputCurrency.js";
 import { InputSelector } from "../../../../components/form/InputSelector.js";
-import { useConfigContext } from "../../../../context/config.js";
 import { MerchantBackend } from "../../../../declaration.js";
 import {
   CROCKFORD_BASE32_REGEX,
@@ -47,7 +46,6 @@ interface Props {
 
 export function CreatePage({ accounts, onCreate, onBack }: Props): VNode {
   const { i18n } = useTranslationContext();
-  const { currency } = useConfigContext();
 
   const [state, setState] = useState<Partial<Entity>>({
     wtid: "",
diff --git 
a/packages/merchant-backoffice-ui/src/paths/instance/update/UpdatePage.tsx 
b/packages/merchant-backoffice-ui/src/paths/instance/update/UpdatePage.tsx
index ff0d55d2d..f0f0bfac9 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/update/UpdatePage.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/update/UpdatePage.tsx
@@ -19,6 +19,7 @@
  * @author Sebastian Javier Marchano (sebasjm)
  */
 
+import { Duration } from "@gnu-taler/taler-util";
 import { useTranslationContext } from "@gnu-taler/web-util/browser";
 import { h, VNode } from "preact";
 import { useState } from "preact/hooks";
@@ -28,10 +29,9 @@ import {
   FormProvider,
 } from "../../../components/form/FormProvider.js";
 import { DefaultInstanceFormFields } from 
"../../../components/instance/DefaultInstanceFormFields.js";
-import { useInstanceContext } from "../../../context/instance.js";
 import { MerchantBackend } from "../../../declaration.js";
 import { undefinedIfEmpty } from "../../../utils/table.js";
-import { Duration } from "@gnu-taler/taler-util";
+import { useSessionContext } from "../../../context/session.js";
 
 export type Entity = 
Omit<Omit<MerchantBackend.Instances.InstanceReconfigurationMessage, 
"default_pay_delay">, "default_wire_transfer_delay"> & {
   default_pay_delay: Duration,
@@ -64,7 +64,7 @@ export function UpdatePage({
   selected,
   onBack,
 }: Props): VNode {
-  const { id } = useInstanceContext();
+  const { state } = useSessionContext();
 
   const [value, valueHandler] = useState<Partial<Entity>>(convert(selected));
 
@@ -125,7 +125,7 @@ export function UpdatePage({
               <div class="level-left">
                 <div class="level-item">
                   <span class="is-size-4">
-                    <i18n.Translate>Instance id</i18n.Translate>: <b>{id}</b>
+                    <i18n.Translate>Instance id</i18n.Translate>: 
<b>{state.instance}</b>
                   </span>
                 </div>
               </div>
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 be3793ac3..de1371974 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/update/index.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/update/index.tsx
@@ -24,8 +24,7 @@ import { Fragment, VNode, h } from "preact";
 import { useState } from "preact/hooks";
 import { Loading } from "../../../components/exception/loading.js";
 import { NotificationCard } from "../../../components/menu/index.js";
-import { useInstanceContext } from "../../../context/instance.js";
-import { AccessToken, MerchantBackend } from "../../../declaration.js";
+import { MerchantBackend } from "../../../declaration.js";
 import {
   useInstanceAPI,
   useInstanceDetails,
@@ -65,7 +64,6 @@ function CommonUpdate(
     onConfirm,
     onLoadError,
     onNotFound,
-    onUpdateError,
     onUnauthorized,
   }: Props,
   result: HttpResponse<
diff --git 
a/packages/merchant-backoffice-ui/src/paths/instance/webhooks/create/CreatePage.tsx
 
b/packages/merchant-backoffice-ui/src/paths/instance/webhooks/create/CreatePage.tsx
index b89e5e6bf..83604711e 100644
--- 
a/packages/merchant-backoffice-ui/src/paths/instance/webhooks/create/CreatePage.tsx
+++ 
b/packages/merchant-backoffice-ui/src/paths/instance/webhooks/create/CreatePage.tsx
@@ -28,12 +28,8 @@ import {
   FormProvider,
 } from "../../../../components/form/FormProvider.js";
 import { Input } from "../../../../components/form/Input.js";
-import { InputCurrency } from "../../../../components/form/InputCurrency.js";
-import { InputDuration } from "../../../../components/form/InputDuration.js";
-import { InputNumber } from "../../../../components/form/InputNumber.js";
-import { useBackendContext } from "../../../../context/backend.js";
-import { MerchantBackend } from "../../../../declaration.js";
 import { InputSelector } from "../../../../components/form/InputSelector.js";
+import { MerchantBackend } from "../../../../declaration.js";
 
 type Entity = MerchantBackend.Webhooks.WebhookAddDetails;
 
diff --git 
a/packages/merchant-backoffice-ui/src/paths/instance/webhooks/update/UpdatePage.tsx
 
b/packages/merchant-backoffice-ui/src/paths/instance/webhooks/update/UpdatePage.tsx
index 304ac90f3..be21629d5 100644
--- 
a/packages/merchant-backoffice-ui/src/paths/instance/webhooks/update/UpdatePage.tsx
+++ 
b/packages/merchant-backoffice-ui/src/paths/instance/webhooks/update/UpdatePage.tsx
@@ -28,7 +28,6 @@ import {
   FormProvider,
 } from "../../../../components/form/FormProvider.js";
 import { Input } from "../../../../components/form/Input.js";
-import { useBackendContext } from "../../../../context/backend.js";
 import { MerchantBackend, WithId } from "../../../../declaration.js";
 
 type Entity = MerchantBackend.Webhooks.WebhookPatchDetails & WithId;
diff --git a/packages/merchant-backoffice-ui/src/paths/login/index.tsx 
b/packages/merchant-backoffice-ui/src/paths/login/index.tsx
index d94b7e506..1c0b915bd 100644
--- a/packages/merchant-backoffice-ui/src/paths/login/index.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/login/index.tsx
@@ -19,7 +19,11 @@
  * @author Sebastian Javier Marchano (sebasjm)
  */
 
-import { HttpStatusCode } from "@gnu-taler/taler-util";
+import {
+  AccessToken,
+  HttpStatusCode,
+  TalerAuthentication,
+} from "@gnu-taler/taler-util";
 import {
   useMerchantApiContext,
   useTranslationContext,
@@ -27,40 +31,68 @@ import {
 import { ComponentChildren, Fragment, VNode, h } from "preact";
 import { useState } from "preact/hooks";
 import { NotificationCard } from "../../components/menu/index.js";
-import { AccessToken } from "../../declaration.js";
-import { DEFAULT_ADMIN_USERNAME, useSessionState } from 
"../../hooks/session.js";
+import {
+  DEFAULT_ADMIN_USERNAME,
+  useSessionContext,
+} from "../../context/session.js";
 import { Notification } from "../../utils/types.js";
 
-interface Props {
-}
+interface Props {}
 
-function normalizeToken(r: string): AccessToken {
-  return `secret-token:${r}` as AccessToken;
-}
+const tokenRequest = {
+  scope: "write",
+  duration: {
+    d_us: "forever" as const,
+  },
+  refreshable: true,
+};
 
 export function LoginPage(_p: Props): VNode {
   const [token, setToken] = useState("");
   const [notif, setNotif] = useState<Notification | undefined>(undefined);
-  const { state, logIn } = useSessionState();
+  const { state, logIn } = useSessionContext();
   const { lib } = useMerchantApiContext();
 
   const { i18n } = useTranslationContext();
 
+  async function doImpersonateImpl(instanceId: string) {
+    const result = await lib
+      .impersonate(instanceId)
+      .createAccessTokenMerchant(token, tokenRequest);
+    if (result.type === "ok") {
+      const { token } = result.body;
+      logIn({ token });
+      return;
+    } else {
+      switch (result.case) {
+        case HttpStatusCode.Unauthorized: {
+          setNotif({
+            message: "Your password is incorrect",
+            type: "ERROR",
+          });
+          return;
+        }
+        case HttpStatusCode.NotFound: {
+          setNotif({
+            message: "Your instance not found",
+            type: "ERROR",
+          });
+          return;
+        }
+      }
+    }
+  }
   async function doLoginImpl() {
-    const secretToken = normalizeToken(token);
-    const result = await lib.authenticate.createAccessToken(secretToken, {
-      scope: "write",
-      duration: {
-        d_us: "forever"
-      },
-      refreshable: true,
-    });
+    const result = await lib.authenticate.createAccessTokenMerchant(
+      token,
+      tokenRequest,
+    );
     if (result.type === "ok") {
-      const { access_token } = result.body;
-      logIn({ instance: state.instance, token: access_token });
+      const { token } = result.body;
+      logIn({ token });
       return;
     } else {
-      switch(result.case) {
+      switch (result.case) {
         case HttpStatusCode.Unauthorized: {
           setNotif({
             message: "Your password is incorrect",
@@ -79,8 +111,8 @@ export function LoginPage(_p: Props): VNode {
     }
   }
 
-  if (state.isAdmin && state.instance !== DEFAULT_ADMIN_USERNAME) {
-    //admin trying to access another instance
+  if (state.status === "loggedIn" && state.impersonate !== undefined) {
+    //the user is loggedin but trying to do an impersonation
     return (
       <div class="columns is-centered" style={{ margin: "auto" }}>
         <div class="column is-two-thirds ">
@@ -115,7 +147,9 @@ export function LoginPage(_p: Props): VNode {
                         placeholder={"current access token"}
                         name="token"
                         onKeyPress={(e) =>
-                          e.keyCode === 13 ? doLoginImpl() : null
+                          e.keyCode === 13
+                            ? doImpersonateImpl(state.instance)
+                            : null
                         }
                         value={token}
                         onInput={(e): void => setToken(e?.currentTarget.value)}
@@ -133,7 +167,7 @@ export function LoginPage(_p: Props): VNode {
                 borderTop: 0,
               }}
             >
-              <AsyncButton onClick={doLoginImpl}>
+              <AsyncButton onClick={() => doImpersonateImpl(state.instance)}>
                 <i18n.Translate>Confirm</i18n.Translate>
               </AsyncButton>
             </footer>
diff --git a/packages/merchant-backoffice-ui/src/paths/settings/index.tsx 
b/packages/merchant-backoffice-ui/src/paths/settings/index.tsx
index 4efda43be..6290f48e6 100644
--- a/packages/merchant-backoffice-ui/src/paths/settings/index.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/settings/index.tsx
@@ -1,10 +1,30 @@
+/*
+ This file is part of GNU Taler
+ (C) 2021-2024 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 { useLang, useTranslationContext } from "@gnu-taler/web-util/browser";
 import { VNode, h } from "preact";
-import { FormErrors, FormProvider } from 
"../../components/form/FormProvider.js";
+import {
+  FormErrors,
+  FormProvider,
+} from "../../components/form/FormProvider.js";
 import { InputSelector } from "../../components/form/InputSelector.js";
 import { InputToggle } from "../../components/form/InputToggle.js";
 import { LangSelector } from "../../components/menu/LangSelector.js";
-import { Settings, usePreference } from "../../hooks/preference.js";
+import { Preferences, usePreference } from "../../hooks/preference.js";
+import { AbsoluteTime } from "@gnu-taler/taler-util";
 
 function getBrowserLang(): string | undefined {
   if (typeof window === "undefined") return undefined;
@@ -14,99 +34,107 @@ function getBrowserLang(): string | undefined {
 }
 
 export function Settings({ onClose }: { onClose?: () => void }): VNode {
-  const { i18n } = useTranslationContext()
-  const borwserLang = getBrowserLang()
-  const { update } = useLang(undefined, {})
+  const { i18n } = useTranslationContext();
+  const borwserLang = getBrowserLang();
+  const { update } = useLang(undefined, {});
 
-  const [value, updateValue] = usePreference()
-  const errors: FormErrors<Settings> = {
-  }
+  const [value, , updateValue] = usePreference();
+  const errors: FormErrors<Preferences> = {};
 
-  function valueHandler(s: (d: Partial<Settings>) => Partial<Settings>): void {
-    const next = s(value)
-    const v: Settings = {
+  function valueHandler(s: (d: Partial<Preferences>) => Partial<Preferences>): 
void {
+    const next = s(value);
+    const v: Preferences = {
       advanceOrderMode: next.advanceOrderMode ?? false,
-      dateFormat: next.dateFormat ?? "ymd"
-    }
-    updateValue(v)
+      hideKycUntil: next.hideKycUntil ?? AbsoluteTime.never(),
+      dateFormat: next.dateFormat ?? "ymd",
+    };
+    updateValue(v);
   }
 
-  return <div>
-    <section class="section is-main-section">
-      <div class="columns">
-        <div class="column" />
-        <div class="column is-four-fifths">
-          <div>
-
-            <FormProvider<Settings>
-              name="settings"
-              errors={errors}
-              object={value}
-              valueHandler={valueHandler}
-            >
-              <div class="field is-horizontal">
-                <div class="field-label is-normal">
-                  <label class="label">
-                    <i18n.Translate>Language</i18n.Translate>
-                    <span class="icon has-tooltip-right" data-tooltip={"Force 
language setting instance of taking the browser"}>
-                      <i class="mdi mdi-information" />
-                    </span>
-                  </label>
-                </div>
-                <div class="field field-body has-addons is-flex-grow-3">
-                  <LangSelector />
-                  &nbsp;
-                  {borwserLang !== undefined && <button
-                    data-tooltip={i18n.str`generate random secret key`}
-                    class="button is-info mr-2"
-                    onClick={(e) => {
-                      update(borwserLang.substring(0, 2))
-                    }}
-                  >
-                    <i18n.Translate>Set default</i18n.Translate>
-                  </button>}
+  return (
+    <div>
+      <section class="section is-main-section">
+        <div class="columns">
+          <div class="column" />
+          <div class="column is-four-fifths">
+            <div>
+              <FormProvider<Preferences>
+                name="settings"
+                errors={errors}
+                object={value}
+                valueHandler={valueHandler}
+              >
+                <div class="field is-horizontal">
+                  <div class="field-label is-normal">
+                    <label class="label">
+                      <i18n.Translate>Language</i18n.Translate>
+                      <span
+                        class="icon has-tooltip-right"
+                        data-tooltip={
+                          "Force language setting instance of taking the 
browser"
+                        }
+                      >
+                        <i class="mdi mdi-information" />
+                      </span>
+                    </label>
+                  </div>
+                  <div class="field field-body has-addons is-flex-grow-3">
+                    <LangSelector />
+                    &nbsp;
+                    {borwserLang !== undefined && (
+                      <button
+                        data-tooltip={i18n.str`generate random secret key`}
+                        class="button is-info mr-2"
+                        onClick={(e) => {
+                          update(borwserLang.substring(0, 2));
+                          e.preventDefault()
+                        }}
+                      >
+                        <i18n.Translate>Set default</i18n.Translate>
+                      </button>
+                    )}
+                  </div>
                 </div>
-              </div>
-              <InputToggle<Settings>
-                label={i18n.str`Advance order creation`}
-                tooltip={i18n.str`Shows more options in the order creation 
form`}
-                name="advanceOrderMode"
-              />
-              <InputSelector<Settings>
-                name="dateFormat"
-                label={i18n.str`Date format`}
-                expand={true}
-                help={
-                  value.dateFormat === "dmy" ? "31/12/2001" : value.dateFormat 
=== "mdy" ? "12/31/2001" : value.dateFormat === "ymd" ? "2001/12/31" : ""
-                }
-                toStr={(e) => {
-                  if (e === "ymd") return "year month day"
-                  if (e === "mdy") return "month day year"
-                  if (e === "dmy") return "day month year"
-                  return "choose one"
-                }}
-                values={[
-                  "ymd",
-                  "mdy",
-                  "dmy",
-                ]}
-                tooltip={i18n.str`how the date is going to be displayed`}
-              />
-            </FormProvider>
+                <InputToggle<Preferences>
+                  label={i18n.str`Advance order creation`}
+                  tooltip={i18n.str`Shows more options in the order creation 
form`}
+                  name="advanceOrderMode"
+                />
+                <InputSelector<Preferences>
+                  name="dateFormat"
+                  label={i18n.str`Date format`}
+                  expand={true}
+                  help={
+                    value.dateFormat === "dmy"
+                      ? "31/12/2001"
+                      : value.dateFormat === "mdy"
+                        ? "12/31/2001"
+                        : value.dateFormat === "ymd"
+                          ? "2001/12/31"
+                          : ""
+                  }
+                  toStr={(e) => {
+                    if (e === "ymd") return "year month day";
+                    if (e === "mdy") return "month day year";
+                    if (e === "dmy") return "day month year";
+                    return "choose one";
+                  }}
+                  values={["ymd", "mdy", "dmy"]}
+                  tooltip={i18n.str`how the date is going to be displayed`}
+                />
+              </FormProvider>
+            </div>
           </div>
+          <div class="column" />
         </div>
-        <div class="column" />
-      </div>
-    </section >
-    {onClose &&
-      <section class="section is-main-section">
-        <button
-          class="button"
-          onClick={onClose}
-        >
-          <i18n.Translate>Close</i18n.Translate>
-        </button>
       </section>
-    }
-  </div >
-}
\ No newline at end of file
+      {onClose && (
+        <section class="section is-main-section">
+          <button class="button" onClick={onClose}>
+            <i18n.Translate>Close</i18n.Translate>
+          </button>
+        </section>
+      )}
+    </div>
+  );
+}
diff --git a/packages/taler-util/src/http-client/authentication.ts 
b/packages/taler-util/src/http-client/authentication.ts
index e8ef6a274..00ef21a06 100644
--- a/packages/taler-util/src/http-client/authentication.ts
+++ b/packages/taler-util/src/http-client/authentication.ts
@@ -34,6 +34,7 @@ import {
   AccessToken,
   TalerAuthentication,
   codecForTokenSuccessResponse,
+  codecForTokenSuccessResponseMerchant,
 } from "./types.js";
 import { makeBearerTokenAuthHeader } from "./utils.js";
 
@@ -85,6 +86,35 @@ export class TalerAuthenticationHttpClient {
     }
   }
 
+  /**
+   *
+   * @returns
+   */
+  async createAccessTokenMerchant(
+    password: string,
+    body: TalerAuthentication.TokenRequest,
+  ) {
+    const url = new URL(`token`, this.baseUrl);
+    const resp = await this.httpLib.fetch(url.href, {
+      method: "POST",
+      headers: {
+        Authorization: makeBearerTokenAuthHeader(password as AccessToken),
+      },
+      body,
+    });
+    switch (resp.status) {
+      case HttpStatusCode.Ok:
+        return opSuccessFromHttp(resp, codecForTokenSuccessResponseMerchant());
+      //FIXME: missing in docs
+      case HttpStatusCode.Unauthorized:
+        return opKnownHttpFailure(resp.status, resp);
+      case HttpStatusCode.NotFound:
+        return opKnownHttpFailure(resp.status, resp);
+      default:
+        return opUnknownFailure(resp, await resp.text());
+    }
+  }
+
   async deleteAccessToken(token: AccessToken) {
     const url = new URL(`token`, this.baseUrl);
     const resp = await this.httpLib.fetch(url.href, {
diff --git a/packages/taler-util/src/http-client/merchant.ts 
b/packages/taler-util/src/http-client/merchant.ts
index 7407cce66..688e80c29 100644
--- a/packages/taler-util/src/http-client/merchant.ts
+++ b/packages/taler-util/src/http-client/merchant.ts
@@ -15,6 +15,7 @@
  */
 
 import {
+  AccessToken,
   HttpStatusCode,
   LibtoolVersion,
   PaginationParams,
@@ -64,6 +65,7 @@ import { opSuccessFromHttp, opUnknownFailure } from 
"../operation.js";
 import {
   CacheEvictor,
   addMerchantPaginationParams,
+  makeBearerTokenAuthHeader,
   nullEvictor,
 } from "./utils.js";
 
@@ -126,12 +128,15 @@ export class TalerMerchantInstanceHttpClient {
   /**
    * 
https://docs.taler.net/core/api-merchant.html#post-[-instances-$INSTANCE]-orders-$ORDER_ID-claim
    */
-  async claimOrder(orderId: string, body: TalerMerchantApi.ClaimRequest) {
+  async claimOrder(token: AccessToken, orderId: string, body: 
TalerMerchantApi.ClaimRequest) {
     const url = new URL(`orders/${orderId}/claim`, this.baseUrl);
 
     const resp = await this.httpLib.fetch(url.href, {
       method: "POST",
       body,
+      headers: {
+        Authorization: makeBearerTokenAuthHeader(token),
+      }
     });
 
     switch (resp.status) {
@@ -516,11 +521,14 @@ export class TalerMerchantInstanceHttpClient {
   /**
    * 
https://docs.taler.net/core/api-merchant.html#get-[-instances-$INSTANCE]-private-accounts
    */
-  async listAccounts() {
+  async listAccounts(token: AccessToken) {
     const url = new URL(`private/accounts`, this.baseUrl);
 
     const resp = await this.httpLib.fetch(url.href, {
       method: "GET",
+      headers: {
+        Authorization: makeBearerTokenAuthHeader(token),
+      }
     });
 
     switch (resp.status) {
@@ -1496,7 +1504,7 @@ export class TalerMerchantManagementHttpClient extends 
TalerMerchantInstanceHttp
   }
 
   getSubInstanceAPI(instanceId: string) {
-    return new URL(`instances/${instanceId}`, this.baseUrl);
+    return new URL(`instances/${instanceId}/`, this.baseUrl);
   }
 
   //
diff --git a/packages/taler-util/src/http-client/types.ts 
b/packages/taler-util/src/http-client/types.ts
index 05897614a..7f97f9ff1 100644
--- a/packages/taler-util/src/http-client/types.ts
+++ b/packages/taler-util/src/http-client/types.ts
@@ -221,6 +221,16 @@ export namespace TalerAuthentication {
     // Opque access token.
     access_token: AccessToken;
   }
+  export interface TokenSuccessResponseMerchant {
+    // Expiration determined by the server.
+    // Can be based on the token_duration
+    // from the request, but ultimately the
+    // server decides the expiration.
+    expiration: Timestamp;
+
+    // Opque access token.
+    token: AccessToken;
+  }
 }
 
 // DD51 https://docs.taler.net/design-documents/051-fractional-digits.html
@@ -254,6 +264,13 @@ export const codecForTokenSuccessResponse =
       .property("expiration", codecForTimestamp)
       .build("TalerAuthentication.TokenSuccessResponse");
 
+export const codecForTokenSuccessResponseMerchant =
+  (): Codec<TalerAuthentication.TokenSuccessResponseMerchant> =>
+    buildCodecForObject<TalerAuthentication.TokenSuccessResponseMerchant>()
+      .property("token", codecForAccessToken())
+      .property("expiration", codecForTimestamp)
+      .build("TalerAuthentication.TokenSuccessResponseMerchant");
+
 export const codecForCurrencySpecificiation =
   (): Codec<CurrencySpecification> =>
     buildCodecForObject<CurrencySpecification>()

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