gnunet-svn
[Top][All Lists]
Advanced

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

[taler-wallet-core] 01/02: fix listing, add cache invalidation, fix code


From: gnunet
Subject: [taler-wallet-core] 01/02: fix listing, add cache invalidation, fix codec for accounts
Date: Fri, 05 Apr 2024 15:42:48 +0200

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

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

commit 70151490bd79d38f8064b720fc124d1774a18235
Author: Sebastian <sebasjm@gmail.com>
AuthorDate: Fri Apr 5 10:30:19 2024 -0300

    fix listing, add cache invalidation, fix codec for accounts
---
 packages/bank-ui/src/hooks/account.ts              |  11 +-
 packages/bank-ui/src/hooks/regional.ts             |   8 +-
 packages/bank-ui/src/utils.ts                      |   6 +-
 .../merchant-backoffice-ui/src/Application.tsx     | 192 ++++++++++++++++++++-
 packages/merchant-backoffice-ui/src/Routing.tsx    |  59 +------
 packages/merchant-backoffice-ui/src/hooks/bank.ts  |   3 +-
 .../merchant-backoffice-ui/src/hooks/order.test.ts |   2 +-
 packages/merchant-backoffice-ui/src/hooks/order.ts |  11 +-
 packages/merchant-backoffice-ui/src/hooks/otp.ts   |   4 +-
 .../merchant-backoffice-ui/src/hooks/product.ts    |   4 +-
 .../merchant-backoffice-ui/src/hooks/templates.ts  |   6 +-
 .../src/hooks/transfer.test.ts                     |   4 +-
 .../merchant-backoffice-ui/src/hooks/transfer.ts   |   4 +-
 .../merchant-backoffice-ui/src/hooks/webhooks.ts   |   8 +-
 .../src/paths/instance/orders/list/Table.tsx       |  12 +-
 .../paths/instance/otp_devices/list/ListPage.tsx   |   2 -
 .../src/paths/instance/otp_devices/list/Table.tsx  |  18 +-
 .../src/paths/instance/products/list/Table.tsx     |   6 -
 .../src/paths/instance/templates/list/ListPage.tsx |   2 -
 .../src/paths/instance/templates/list/Table.tsx    |  18 +-
 .../paths/instance/templates/update/UpdatePage.tsx |   4 +-
 .../src/paths/instance/templates/update/index.tsx  |   2 +-
 .../src/paths/instance/transfers/list/ListPage.tsx |   2 -
 .../src/paths/instance/transfers/list/Table.tsx    |  20 +--
 .../src/paths/instance/webhooks/list/ListPage.tsx  |   2 -
 .../src/paths/instance/webhooks/list/Table.tsx     |  18 +-
 .../merchant-backoffice-ui/src/utils/constants.ts  |   9 +-
 .../bin/create_merchantAndBankAccount_pdf.sh       |  13 ++
 packages/taler-util/src/http-client/merchant.ts    | 173 ++++++++++++++-----
 packages/taler-util/src/http-client/types.ts       |   4 +-
 packages/taler-util/src/time.ts                    |   6 +
 packages/web-util/src/context/merchant-api.ts      |   5 +-
 32 files changed, 417 insertions(+), 221 deletions(-)

diff --git a/packages/bank-ui/src/hooks/account.ts 
b/packages/bank-ui/src/hooks/account.ts
index e1cd423de..43d43a3f2 100644
--- a/packages/bank-ui/src/hooks/account.ts
+++ b/packages/bank-ui/src/hooks/account.ts
@@ -22,12 +22,12 @@ import {
   WithdrawalOperationStatus,
 } from "@gnu-taler/taler-util";
 import { useEffect, useState } from "preact/hooks";
-import { PAGE_SIZE } from "../utils.js";
 import { useSessionState } from "./session.js";
 
 // FIX default import https://github.com/microsoft/TypeScript/issues/49189
 import _useSWR, { SWRHook, mutate } from "swr";
 import { useBankCoreApiContext } from "@gnu-taler/web-util/browser";
+import { PAGINATED_LIST_REQUEST } from "../utils.js";
 const useSWR = _useSWR as unknown as SWRHook;
 
 export interface InstanceTemplateFilter {
@@ -184,7 +184,7 @@ export function usePublicAccounts(
     return await api.getPublicAccounts(
       { account },
       {
-        limit: PAGE_SIZE,
+        limit: PAGINATED_LIST_REQUEST,
         offset: txid ? String(txid) : undefined,
         order: "asc",
       },
@@ -232,11 +232,12 @@ export function buildPaginatedResult<DataType, OffsetId>(
   setOffset: (o: OffsetId | undefined) => void,
   getId: (r: DataType) => OffsetId,
 ): PaginatedResult<DataType[]> {
-  const isLastPage = data.length <= PAGE_SIZE;
+  const isLastPage = data.length < PAGINATED_LIST_REQUEST;
   const isFirstPage = offset === undefined;
 
   const result = structuredClone(data);
-  if (result.length == PAGE_SIZE + 1) {
+  if (result.length == PAGINATED_LIST_REQUEST) {
+    //do now show the last element, used to know if this is the last page
     result.pop();
   }
   return {
@@ -280,7 +281,7 @@ export function useTransactions(account: string, initial?: 
number) {
     return await api.getTransactions(
       { username, token },
       {
-        limit: PAGE_SIZE + 1,
+        limit: PAGINATED_LIST_REQUEST,
         offset: txid ? String(txid) : undefined,
         order: "dec",
       },
diff --git a/packages/bank-ui/src/hooks/regional.ts 
b/packages/bank-ui/src/hooks/regional.ts
index 909bcfcec..e0c861a0f 100644
--- a/packages/bank-ui/src/hooks/regional.ts
+++ b/packages/bank-ui/src/hooks/regional.ts
@@ -14,7 +14,6 @@
  GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 
-import { PAGE_SIZE } from "../utils.js";
 import { useSessionState } from "./session.js";
 
 import {
@@ -36,6 +35,7 @@ import { useBankCoreApiContext } from 
"@gnu-taler/web-util/browser";
 import { useState } from "preact/hooks";
 import _useSWR, { SWRHook, mutate } from "swr";
 import { buildPaginatedResult } from "./account.js";
+import { PAGINATED_LIST_REQUEST } from "../utils.js";
 
 // FIX default import https://github.com/microsoft/TypeScript/issues/49189
 const useSWR = _useSWR as unknown as SWRHook;
@@ -232,14 +232,14 @@ export function useBusinessAccounts() {
 
   const [offset, setOffset] = useState<number | undefined>();
 
-  function fetcher([token, offset]: [AccessToken, number]) {
+  function fetcher([token, aid]: [AccessToken, number]) {
     // FIXME: add account name filter
     return api.getAccounts(
       token,
       {},
       {
-        limit: PAGE_SIZE + 1,
-        offset: String(offset),
+        limit: PAGINATED_LIST_REQUEST,
+        offset: aid ? String(aid) : undefined,
         order: "asc",
       },
     );
diff --git a/packages/bank-ui/src/utils.ts b/packages/bank-ui/src/utils.ts
index 305f13803..2cc502416 100644
--- a/packages/bank-ui/src/utils.ts
+++ b/packages/bank-ui/src/utils.ts
@@ -120,7 +120,11 @@ export enum CashoutStatus {
   PENDING = "pending",
 }
 
-export const PAGE_SIZE = 5;
+
+export const PAGINATED_LIST_SIZE = 5;
+// when doing paginated request, ask for one more
+// and use it to know if there are more to request
+export const PAGINATED_LIST_REQUEST = PAGINATED_LIST_SIZE + 1;
 
 type Translator = ReturnType<typeof useTranslationContext>["i18n"];
 
diff --git a/packages/merchant-backoffice-ui/src/Application.tsx 
b/packages/merchant-backoffice-ui/src/Application.tsx
index 497f49c0e..1a4bd6708 100644
--- a/packages/merchant-backoffice-ui/src/Application.tsx
+++ b/packages/merchant-backoffice-ui/src/Application.tsx
@@ -19,7 +19,7 @@
  * @author Sebastian Javier Marchano (sebasjm)
  */
 
-import { TalerMerchantApi, assertUnreachable, canonicalizeBaseUrl } from 
"@gnu-taler/taler-util";
+import { CacheEvictor, TalerMerchantApi, TalerMerchantInstanceCacheEviction, 
TalerMerchantManagementCacheEviction, assertUnreachable, canonicalizeBaseUrl } 
from "@gnu-taler/taler-util";
 import {
   BrowserHashNavigationProvider,
   ConfigResultFail,
@@ -33,13 +33,20 @@ import { useEffect, useState } from "preact/hooks";
 import { SWRConfig } from "swr";
 import { Routing } from "./Routing.js";
 import { Loading } from "./components/exception/loading.js";
+import { NotificationCard } from "./components/menu/index.js";
 import { SettingsProvider } from "./context/settings.js";
+import { revalidateBankAccountDetails, revalidateInstanceBankAccounts } from 
"./hooks/bank.js";
+import { revalidateBackendInstances, revalidateInstanceDetails, 
revalidateManagedInstanceDetails } from "./hooks/instance.js";
+import { revalidateInstanceOtpDevices, revalidateOtpDeviceDetails } from 
"./hooks/otp.js";
+import { revalidateInstanceProducts, revalidateProductDetails } from 
"./hooks/product.js";
+import { revalidateInstanceTemplates, revalidateTemplateDetails } from 
"./hooks/templates.js";
+import { revalidateInstanceTransfers } from "./hooks/transfer.js";
+import { revalidateInstanceWebhooks, revalidateWebhookDetails } from 
"./hooks/webhooks.js";
 import { strings } from "./i18n/strings.js";
 import { MerchantUiSettings, buildDefaultBackendBaseURL, fetchSettings } from 
"./settings.js";
-import { NotificationCard } from "./components/menu/index.js";
+import { revalidateInstanceOrders, revalidateOrderDetails } from 
"./hooks/order.js";
 const WITH_LOCAL_STORAGE_CACHE = false;
 
-
 export function Application(): VNode {
   const [settings, setSettings] = useState<MerchantUiSettings>();
   useEffect(() => {
@@ -57,7 +64,9 @@ export function Application(): VNode {
           de: strings["de"].completeness,
         }}
       >
-        <MerchantApiProvider baseUrl={new URL("/", baseUrl)} 
frameOnError={OnConfigError}>
+        <MerchantApiProvider baseUrl={new URL("/", baseUrl)} 
frameOnError={OnConfigError} evictors={{
+          management: swrCacheEvictor
+        }}>
           <SWRConfig
             value={{
               provider: WITH_LOCAL_STORAGE_CACHE
@@ -169,3 +178,178 @@ function OnConfigError({ state }: { state: 
ConfigResultFail<TalerMerchantApi.Ver
     default: assertUnreachable(state)
   }
 }
+
+const swrCacheEvictor= new class implements 
CacheEvictor<TalerMerchantManagementCacheEviction | 
TalerMerchantInstanceCacheEviction> {
+  async notifySuccess(op: TalerMerchantManagementCacheEviction | 
TalerMerchantInstanceCacheEviction) {
+    switch(op) {
+      case TalerMerchantManagementCacheEviction.CREATE_INSTANCE: {
+        await Promise.all([
+          revalidateBackendInstances()
+        ])
+        return
+      }
+      case TalerMerchantManagementCacheEviction.UPDATE_INSTANCE: {
+        await Promise.all([
+          revalidateManagedInstanceDetails()
+        ])
+        return
+      }
+      case TalerMerchantManagementCacheEviction.DELETE_INSTANCE:{
+        await Promise.all([
+          revalidateBackendInstances()
+        ])
+        return
+      }
+      case TalerMerchantInstanceCacheEviction.UPDATE_CURRENT_INSTANCE:{
+        await Promise.all([
+          revalidateInstanceDetails()
+        ])
+        return
+      }
+      case TalerMerchantInstanceCacheEviction.DELETE_CURRENT_INSTANCE:{
+        await Promise.all([
+          revalidateInstanceDetails()
+        ])
+        return
+      }
+      case TalerMerchantInstanceCacheEviction.CREATE_BANK_ACCOUNT:{
+        await Promise.all([
+          revalidateInstanceBankAccounts()
+        ])
+        return
+      }
+      case TalerMerchantInstanceCacheEviction.UPDATE_BANK_ACCOUNT:{
+        await Promise.all([
+          revalidateBankAccountDetails()
+        ])
+        return
+      }
+      case TalerMerchantInstanceCacheEviction.DELETE_BANK_ACCOUNT:{
+        await Promise.all([
+          revalidateInstanceBankAccounts()
+        ])
+        return
+      }
+      case TalerMerchantInstanceCacheEviction.CREATE_PRODUCT:{
+        await Promise.all([
+          revalidateInstanceProducts()
+        ])
+        return
+      }
+      case TalerMerchantInstanceCacheEviction.UPDATE_PRODUCT:{
+        await Promise.all([
+          revalidateProductDetails()
+        ])
+        return
+      }
+      case TalerMerchantInstanceCacheEviction.DELETE_PRODUCT:{
+        await Promise.all([
+          revalidateInstanceProducts()
+        ])
+        return
+      }
+      case TalerMerchantInstanceCacheEviction.CREATE_TRANSFER:{
+        await Promise.all([
+          revalidateInstanceTransfers()
+        ])
+        return
+      }
+      case TalerMerchantInstanceCacheEviction.DELETE_TRANSFER:{
+        await Promise.all([
+          revalidateInstanceTransfers()
+        ])
+        return
+      }
+      case TalerMerchantInstanceCacheEviction.CREATE_DEVICE:{
+        await Promise.all([
+          revalidateInstanceOtpDevices()
+        ])
+        return
+      }
+      case TalerMerchantInstanceCacheEviction.UPDATE_DEVICE:{
+        await Promise.all([
+          revalidateOtpDeviceDetails()
+        ])
+        return
+      }
+      case TalerMerchantInstanceCacheEviction.DELETE_DEVICE:{
+        await Promise.all([
+          revalidateInstanceOtpDevices()
+        ])
+        return
+      }
+      case TalerMerchantInstanceCacheEviction.CREATE_TEMPLATE:{
+        await Promise.all([
+          revalidateInstanceTemplates()
+        ])
+        return
+      }
+      case TalerMerchantInstanceCacheEviction.UPDATE_TEMPLATE:{
+        await Promise.all([
+          revalidateTemplateDetails()
+        ])
+        return
+      }
+      case TalerMerchantInstanceCacheEviction.DELETE_TEMPLATE:{
+        await Promise.all([
+          revalidateInstanceTemplates()
+        ])
+        return
+      }
+      case TalerMerchantInstanceCacheEviction.CREATE_WEBHOOK:{
+        await Promise.all([
+          revalidateInstanceWebhooks()
+        ])
+        return
+      }
+      case TalerMerchantInstanceCacheEviction.UPDATE_WEBHOOK:{
+        await Promise.all([
+          revalidateWebhookDetails()
+        ])
+        return
+      }
+      case TalerMerchantInstanceCacheEviction.DELETE_WEBHOOK:{
+        await Promise.all([
+          revalidateInstanceWebhooks()
+        ])
+        return
+      }
+      case TalerMerchantInstanceCacheEviction.CREATE_ORDER:{
+        await Promise.all([
+          revalidateInstanceOrders()
+        ])
+        return
+      }
+      case TalerMerchantInstanceCacheEviction.UPDATE_ORDER: {
+        await Promise.all([
+          revalidateOrderDetails()
+        ])
+        return
+      }
+      case TalerMerchantInstanceCacheEviction.DELETE_ORDER: {
+        await Promise.all([
+          revalidateInstanceOrders()
+        ])
+        return
+      }
+      case TalerMerchantInstanceCacheEviction.LAST:
+      // case TalerMerchantInstanceCacheEviction.CREATE_TOKENFAMILY:{
+      //   await Promise.all([
+      //     reva
+      //   ])
+      //   return
+      // }
+      // case TalerMerchantInstanceCacheEviction.UPDATE_TOKENFAMILY:{
+      //   await Promise.all([
+      //   ])
+      //   return
+      // }
+      // case TalerMerchantInstanceCacheEviction.DELETE_TOKENFAMILY:{
+      //   await Promise.all([
+      //   ])
+      //   return
+      // }
+    }
+  }
+
+}
diff --git a/packages/merchant-backoffice-ui/src/Routing.tsx 
b/packages/merchant-backoffice-ui/src/Routing.tsx
index 874c2b0f2..7398aaeec 100644
--- a/packages/merchant-backoffice-ui/src/Routing.tsx
+++ b/packages/merchant-backoffice-ui/src/Routing.tsx
@@ -312,8 +312,6 @@ export function Routing(_p: Props): VNode {
             onUpdate={(id: string): void => {
               route(`/instance/${id}/update`);
             }}
-            // onUnauthorized={LoginPageAccessDenied}
-            // onLoadError={ServerErrorRedirectTo(InstancePaths.error)}
           />
         )}
         {state.isAdmin && (
@@ -322,7 +320,7 @@ export function Routing(_p: Props): VNode {
             component={InstanceCreatePage}
             onBack={() => route(AdminPaths.list_instances)}
             onConfirm={() => {
-              route(InstancePaths.order_list);
+              route(AdminPaths.list_instances);
             }}
           />
         )}
@@ -334,9 +332,6 @@ export function Routing(_p: Props): VNode {
             onConfirm={() => {
               route(AdminPaths.list_instances);
             }}
-            // onUpdateError={ServerErrorRedirectTo(AdminPaths.list_instances)}
-            // onLoadError={ServerErrorRedirectTo(AdminPaths.list_instances)}
-            // onNotFound={NotFoundPage}
           />
         )}
         {/**
@@ -351,10 +346,6 @@ export function Routing(_p: Props): VNode {
           onConfirm={() => {
             route(`/`);
           }}
-          // onUpdateError={noop}
-          // onNotFound={IfAdminCreateDefaultOr(NotFoundPage)}
-          // onUnauthorized={LoginPageAccessDenied}
-          // onLoadError={ServerErrorRedirectTo(InstancePaths.error)}
         />
         {/**
          * Update instance page
@@ -368,9 +359,6 @@ export function Routing(_p: Props): VNode {
           onCancel={() => {
             route(InstancePaths.order_list);
           }}
-          // onNotFound={IfAdminCreateDefaultOr(NotFoundPage)}
-          // onUnauthorized={LoginPageAccessDenied}
-          // onLoadError={ServerErrorRedirectTo(InstancePaths.error)}
         />
         {/**
          * Inventory pages
@@ -384,9 +372,6 @@ export function Routing(_p: Props): VNode {
           onSelect={(id: string) => {
             route(InstancePaths.inventory_update.replace(":pid", id));
           }}
-          // onUnauthorized={LoginPageAccessDenied}
-          // onLoadError={ServerErrorRedirectTo(InstancePaths.settings)}
-          // onNotFound={IfAdminCreateDefaultOr(NotFoundPage)}
         />
         <Route
           path={InstancePaths.inventory_update}
@@ -397,9 +382,6 @@ export function Routing(_p: Props): VNode {
           onBack={() => {
             route(InstancePaths.inventory_list);
           }}
-          // onUnauthorized={LoginPageAccessDenied}
-          // onLoadError={ServerErrorRedirectTo(InstancePaths.inventory_list)}
-          // onNotFound={IfAdminCreateDefaultOr(NotFoundPage)}
         />
         <Route
           path={InstancePaths.inventory_new}
@@ -423,9 +405,6 @@ export function Routing(_p: Props): VNode {
           onSelect={(id: string) => {
             route(InstancePaths.bank_update.replace(":bid", id));
           }}
-          // onUnauthorized={LoginPageAccessDenied}
-          // onLoadError={ServerErrorRedirectTo(InstancePaths.settings)}
-          // onNotFound={IfAdminCreateDefaultOr(NotFoundPage)}
         />
         <Route
           path={InstancePaths.bank_update}
@@ -436,9 +415,6 @@ export function Routing(_p: Props): VNode {
           onBack={() => {
             route(InstancePaths.bank_list);
           }}
-          // onUnauthorized={LoginPageAccessDenied}
-          // onLoadError={ServerErrorRedirectTo(InstancePaths.inventory_list)}
-          // onNotFound={IfAdminCreateDefaultOr(NotFoundPage)}
         />
         <Route
           path={InstancePaths.bank_new}
@@ -462,16 +438,10 @@ export function Routing(_p: Props): VNode {
           onSelect={(id: string) => {
             route(InstancePaths.order_details.replace(":oid", id));
           }}
-          // onUnauthorized={LoginPageAccessDenied}
-          // onLoadError={ServerErrorRedirectTo(InstancePaths.settings)}
-          // onNotFound={IfAdminCreateDefaultOr(NotFoundPage)}
         />
         <Route
           path={InstancePaths.order_details}
           component={OrderDetailsPage}
-          // onUnauthorized={LoginPageAccessDenied}
-          // onLoadError={ServerErrorRedirectTo(InstancePaths.order_list)}
-          // onNotFound={IfAdminCreateDefaultOr(NotFoundPage)}
           onBack={() => {
             route(InstancePaths.order_list);
           }}
@@ -492,9 +462,6 @@ export function Routing(_p: Props): VNode {
         <Route
           path={InstancePaths.transfers_list}
           component={TransferListPage}
-          // onUnauthorized={LoginPageAccessDenied}
-          // onNotFound={IfAdminCreateDefaultOr(NotFoundPage)}
-          // onLoadError={ServerErrorRedirectTo(InstancePaths.settings)}
           onCreate={() => {
             route(InstancePaths.transfers_new);
           }}
@@ -515,9 +482,6 @@ export function Routing(_p: Props): VNode {
         <Route
           path={InstancePaths.webhooks_list}
           component={WebhookListPage}
-          // onUnauthorized={LoginPageAccessDenied}
-          // onNotFound={IfAdminCreateDefaultOr(NotFoundPage)}
-          // onLoadError={ServerErrorRedirectTo(InstancePaths.settings)}
           onCreate={() => {
             route(InstancePaths.webhooks_new);
           }}
@@ -531,9 +495,6 @@ export function Routing(_p: Props): VNode {
           onConfirm={() => {
             route(InstancePaths.webhooks_list);
           }}
-          // onUnauthorized={LoginPageAccessDenied}
-          // onLoadError={ServerErrorRedirectTo(InstancePaths.webhooks_list)}
-          // onNotFound={IfAdminCreateDefaultOr(NotFoundPage)}
           onBack={() => {
             route(InstancePaths.webhooks_list);
           }}
@@ -554,9 +515,6 @@ export function Routing(_p: Props): VNode {
         <Route
           path={InstancePaths.otp_devices_list}
           component={ValidatorListPage}
-          // onUnauthorized={LoginPageAccessDenied}
-          // onNotFound={IfAdminCreateDefaultOr(NotFoundPage)}
-          // onLoadError={ServerErrorRedirectTo(InstancePaths.settings)}
           onCreate={() => {
             route(InstancePaths.otp_devices_new);
           }}
@@ -570,9 +528,6 @@ export function Routing(_p: Props): VNode {
           onConfirm={() => {
             route(InstancePaths.otp_devices_list);
           }}
-          // onUnauthorized={LoginPageAccessDenied}
-          // 
onLoadError={ServerErrorRedirectTo(InstancePaths.otp_devices_list)}
-          // onNotFound={IfAdminCreateDefaultOr(NotFoundPage)}
           onBack={() => {
             route(InstancePaths.otp_devices_list);
           }}
@@ -593,9 +548,6 @@ export function Routing(_p: Props): VNode {
         <Route
           path={InstancePaths.templates_list}
           component={TemplateListPage}
-          // onUnauthorized={LoginPageAccessDenied}
-          // onNotFound={IfAdminCreateDefaultOr(NotFoundPage)}
-          // onLoadError={ServerErrorRedirectTo(InstancePaths.settings)}
           onCreate={() => {
             route(InstancePaths.templates_new);
           }}
@@ -615,9 +567,6 @@ export function Routing(_p: Props): VNode {
           onConfirm={() => {
             route(InstancePaths.templates_list);
           }}
-          // onUnauthorized={LoginPageAccessDenied}
-          // onLoadError={ServerErrorRedirectTo(InstancePaths.templates_list)}
-          // onNotFound={IfAdminCreateDefaultOr(NotFoundPage)}
           onBack={() => {
             route(InstancePaths.templates_list);
           }}
@@ -638,9 +587,6 @@ export function Routing(_p: Props): VNode {
           onOrderCreated={(id: string) => {
             route(InstancePaths.order_details.replace(":oid", id));
           }}
-          // onUnauthorized={LoginPageAccessDenied}
-          // onLoadError={ServerErrorRedirectTo(InstancePaths.templates_list)}
-          // onNotFound={IfAdminCreateDefaultOr(NotFoundPage)}
           onBack={() => {
             route(InstancePaths.templates_list);
           }}
@@ -648,9 +594,6 @@ export function Routing(_p: Props): VNode {
         <Route
           path={InstancePaths.templates_qr}
           component={TemplateQrPage}
-          // onUnauthorized={LoginPageAccessDenied}
-          // onLoadError={ServerErrorRedirectTo(InstancePaths.templates_list)}
-          // onNotFound={IfAdminCreateDefaultOr(NotFoundPage)}
           onBack={() => {
             route(InstancePaths.templates_list);
           }}
diff --git a/packages/merchant-backoffice-ui/src/hooks/bank.ts 
b/packages/merchant-backoffice-ui/src/hooks/bank.ts
index 513314f17..6555ec085 100644
--- a/packages/merchant-backoffice-ui/src/hooks/bank.ts
+++ b/packages/merchant-backoffice-ui/src/hooks/bank.ts
@@ -22,6 +22,7 @@ import { useState } from "preact/hooks";
 import { AccessToken, TalerHttpError, TalerMerchantManagementResultByMethod } 
from "@gnu-taler/taler-util";
 import _useSWR, { SWRHook, mutate } from "swr";
 import { useSessionContext } from "../context/session.js";
+import { PAGINATED_LIST_REQUEST } from "../utils/constants.js";
 import { buildPaginatedResult } from "./webhooks.js";
 const useSWR = _useSWR as unknown as SWRHook;
 
@@ -43,7 +44,7 @@ export function useInstanceBankAccounts() {
 
   async function fetcher([token, bid]: [AccessToken, string]) {
     return await instance.listBankAccounts(token, {
-      limit: 5,
+      limit: PAGINATED_LIST_REQUEST,
       offset: bid,
       order: "dec",
     });
diff --git a/packages/merchant-backoffice-ui/src/hooks/order.test.ts 
b/packages/merchant-backoffice-ui/src/hooks/order.test.ts
index 1aa2fcf0a..9c1eaccbb 100644
--- a/packages/merchant-backoffice-ui/src/hooks/order.test.ts
+++ b/packages/merchant-backoffice-ui/src/hooks/order.test.ts
@@ -464,7 +464,7 @@ describe("order listing pagination", () => {
     expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" });
   });
 
-  it("should load more if result brings more that PAGE_SIZE", async () => {
+  it("should load more if result brings more that PAGINATED_LIST_REQUEST", 
async () => {
     const env = new ApiMockEnvironment();
 
     const ordersFrom0to20 = Array.from({ length: 20 }).map((e, i) => ({
diff --git a/packages/merchant-backoffice-ui/src/hooks/order.ts 
b/packages/merchant-backoffice-ui/src/hooks/order.ts
index b1805f6e3..79f970ec2 100644
--- a/packages/merchant-backoffice-ui/src/hooks/order.ts
+++ b/packages/merchant-backoffice-ui/src/hooks/order.ts
@@ -16,7 +16,7 @@
 import {
   useMerchantApiContext
 } from "@gnu-taler/web-util/browser";
-import { PAGE_SIZE } from "../utils/constants.js";
+import { PAGINATED_LIST_REQUEST } from "../utils/constants.js";
 
 // FIX default import https://github.com/microsoft/TypeScript/issues/49189
 import { AbsoluteTime, AccessToken, TalerHttpError, 
TalerMerchantManagementResultByMethod } from "@gnu-taler/taler-util";
@@ -60,6 +60,13 @@ export interface InstanceOrderFilter {
   position?: string;
 }
 
+export function revalidateInstanceOrders() {
+  return mutate(
+    (key) => Array.isArray(key) && key[key.length - 1] === "listOrders",
+    undefined,
+    { revalidate: true },
+  );
+}
 export function useInstanceOrders(
   args?: InstanceOrderFilter,
   updatePosition: (d: string | undefined) => void = () => { },
@@ -71,7 +78,7 @@ export function useInstanceOrders(
 
   async function fetcher([token, o, p, r, w, d]: [AccessToken, string, 
boolean, boolean, boolean, AbsoluteTime]) {
     return await instance.listOrders(token, {
-      limit: PAGE_SIZE,
+      limit: PAGINATED_LIST_REQUEST,
       offset: o,
       order: "dec",
       paid: p,
diff --git a/packages/merchant-backoffice-ui/src/hooks/otp.ts 
b/packages/merchant-backoffice-ui/src/hooks/otp.ts
index 898a27a69..9820c5f11 100644
--- a/packages/merchant-backoffice-ui/src/hooks/otp.ts
+++ b/packages/merchant-backoffice-ui/src/hooks/otp.ts
@@ -17,7 +17,7 @@ import {
   useMerchantApiContext
 } from "@gnu-taler/web-util/browser";
 import { useState } from "preact/hooks";
-import { PAGE_SIZE } from "../utils/constants.js";
+import { PAGINATED_LIST_REQUEST } from "../utils/constants.js";
 
 // FIX default import https://github.com/microsoft/TypeScript/issues/49189
 import { AccessToken, TalerHttpError, TalerMerchantManagementResultByMethod } 
from "@gnu-taler/taler-util";
@@ -41,7 +41,7 @@ export function useInstanceOtpDevices() {
 
   async function fetcher([token, bid]: [AccessToken, string]) {
     return await instance.listOtpDevices(token, {
-      limit: PAGE_SIZE,
+      limit: PAGINATED_LIST_REQUEST,
       offset: bid,
       order: "dec",
     });
diff --git a/packages/merchant-backoffice-ui/src/hooks/product.ts 
b/packages/merchant-backoffice-ui/src/hooks/product.ts
index cfbd4a653..7f3504c64 100644
--- a/packages/merchant-backoffice-ui/src/hooks/product.ts
+++ b/packages/merchant-backoffice-ui/src/hooks/product.ts
@@ -22,7 +22,7 @@ import { AccessToken, OperationOk, TalerHttpError, 
TalerMerchantApi, TalerMercha
 import { useState } from "preact/hooks";
 import _useSWR, { SWRHook, mutate } from "swr";
 import { useSessionContext } from "../context/session.js";
-import { PAGE_SIZE } from "../utils/constants.js";
+import { PAGINATED_LIST_REQUEST } from "../utils/constants.js";
 import { buildPaginatedResult } from "./webhooks.js";
 const useSWR = _useSWR as unknown as SWRHook;
 
@@ -46,7 +46,7 @@ export function useInstanceProducts() {
 
   async function fetcher([token, bid]: [AccessToken, number]) {
     const list = await instance.listProducts(token, {
-      limit: PAGE_SIZE,
+      limit: PAGINATED_LIST_REQUEST,
       offset: bid === undefined ? undefined: String(bid),
       order: "dec",
     });
diff --git a/packages/merchant-backoffice-ui/src/hooks/templates.ts 
b/packages/merchant-backoffice-ui/src/hooks/templates.ts
index dbea93fdf..e0065e284 100644
--- a/packages/merchant-backoffice-ui/src/hooks/templates.ts
+++ b/packages/merchant-backoffice-ui/src/hooks/templates.ts
@@ -17,7 +17,7 @@ import {
   useMerchantApiContext
 } from "@gnu-taler/web-util/browser";
 import { useState } from "preact/hooks";
-import { PAGE_SIZE } from "../utils/constants.js";
+import { PAGINATED_LIST_REQUEST } from "../utils/constants.js";
 
 // FIX default import https://github.com/microsoft/TypeScript/issues/49189
 import { AccessToken, TalerHttpError, TalerMerchantManagementResultByMethod } 
from "@gnu-taler/taler-util";
@@ -45,7 +45,7 @@ export function useInstanceTemplates() {
 
   async function fetcher([token, bid]: [AccessToken, string]) {
     return await instance.listTemplates(token, {
-      limit: PAGE_SIZE,
+      limit: PAGINATED_LIST_REQUEST,
       offset: bid,
       order: "dec",
     });
@@ -64,7 +64,7 @@ export function useInstanceTemplates() {
 
 }
 
-export function revalidateProductDetails() {
+export function revalidateTemplateDetails() {
   return mutate(
     (key) => Array.isArray(key) && key[key.length - 1] === 
"getTemplateDetails",
     undefined,
diff --git a/packages/merchant-backoffice-ui/src/hooks/transfer.test.ts 
b/packages/merchant-backoffice-ui/src/hooks/transfer.test.ts
index d0865d236..7daaf5049 100644
--- a/packages/merchant-backoffice-ui/src/hooks/transfer.test.ts
+++ b/packages/merchant-backoffice-ui/src/hooks/transfer.test.ts
@@ -163,7 +163,7 @@ describe("transfer listing pagination", () => {
     expect(hookBehavior).deep.eq({ result: "ok" });
   });
 
-  it("should load more if result brings more that PAGE_SIZE", async () => {
+  it("should load more if result brings more that PAGINATED_LIST_REQUEST", 
async () => {
     const env = new ApiMockEnvironment();
 
     const transfersFrom0to20 = Array.from({ length: 20 }).map((e, i) => ({
@@ -172,7 +172,7 @@ describe("transfer listing pagination", () => {
     const transfersFrom20to40 = Array.from({ length: 20 }).map((e, i) => ({
       wtid: String(i + 20),
     }));
-    const transfersFrom20to0 = [...transfersFrom0to20].reverse();
+    // const transfersFrom20to0 = [...transfersFrom0to20].reverse();
 
     env.addRequestExpectation(API_LIST_TRANSFERS, {
       qparam: { limit: 20, payto_uri: "payto://", offset: "1" },
diff --git a/packages/merchant-backoffice-ui/src/hooks/transfer.ts 
b/packages/merchant-backoffice-ui/src/hooks/transfer.ts
index 44068f52d..6c2fc1d75 100644
--- a/packages/merchant-backoffice-ui/src/hooks/transfer.ts
+++ b/packages/merchant-backoffice-ui/src/hooks/transfer.ts
@@ -16,7 +16,7 @@
 import {
   useMerchantApiContext
 } from "@gnu-taler/web-util/browser";
-import { PAGE_SIZE } from "../utils/constants.js";
+import { PAGINATED_LIST_REQUEST } from "../utils/constants.js";
 
 // FIX default import https://github.com/microsoft/TypeScript/issues/49189
 import { AccessToken, TalerHttpError, TalerMerchantManagementResultByMethod } 
from "@gnu-taler/taler-util";
@@ -51,7 +51,7 @@ export function useInstanceTransfers(
     return await instance.listWireTransfers(token, {
       paytoURI: p,
       verified: v,
-      limit: PAGE_SIZE,
+      limit: PAGINATED_LIST_REQUEST,
       offset: o,
       order: "dec",
     });
diff --git a/packages/merchant-backoffice-ui/src/hooks/webhooks.ts 
b/packages/merchant-backoffice-ui/src/hooks/webhooks.ts
index c69db6e80..c5d0382e2 100644
--- a/packages/merchant-backoffice-ui/src/hooks/webhooks.ts
+++ b/packages/merchant-backoffice-ui/src/hooks/webhooks.ts
@@ -17,7 +17,7 @@ import {
   useMerchantApiContext
 } from "@gnu-taler/web-util/browser";
 import { useState } from "preact/hooks";
-import { PAGE_SIZE } from "../utils/constants.js";
+import { PAGINATED_LIST_REQUEST } from "../utils/constants.js";
 
 // FIX default import https://github.com/microsoft/TypeScript/issues/49189
 import { AccessToken, OperationOk, TalerHttpError, 
TalerMerchantManagementResultByMethod } from "@gnu-taler/taler-util";
@@ -43,7 +43,7 @@ export function useInstanceWebhooks() {
 
   async function fetcher([token, bid]: [AccessToken, string]) {
     return await instance.listWebhooks(token, {
-      limit: 5,
+      limit: PAGINATED_LIST_REQUEST,
       offset: bid,
       order: "dec",
     });
@@ -71,11 +71,11 @@ type PaginatedResult<T> = OperationOk<T> & {
 //TODO: consider sending this to web-util
 export function buildPaginatedResult<R, OffId>(data: R[], offset: OffId | 
undefined, setOffset: (o: OffId | undefined) => void, getId: (r: R) => OffId): 
PaginatedResult<R[]> {
 
-  const isLastPage = data.length <= PAGE_SIZE;
+  const isLastPage = data.length < PAGINATED_LIST_REQUEST;
   const isFirstPage = offset === undefined;
 
   const result = structuredClone(data);
-  if (result.length == PAGE_SIZE + 1) {
+  if (result.length == PAGINATED_LIST_REQUEST) {
     result.pop();
   }
   return {
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 c3df81b87..919b608c3 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
@@ -146,7 +146,7 @@ function Table({
     <div class="table-container">
       {hasMoreBefore && (
         <button class="button is-fullwidth" onClick={onLoadMoreBefore}>
-          <i18n.Translate>load newer orders</i18n.Translate>
+          <i18n.Translate>load first page</i18n.Translate>
         </button>
       )}
       <table class="table is-striped is-hoverable is-fullwidth">
@@ -219,8 +219,10 @@ function Table({
         </tbody>
       </table>
       {hasMoreAfter && (
-        <button class="button is-fullwidth" onClick={onLoadMoreAfter}>
-          <i18n.Translate>load older orders</i18n.Translate>
+        <button class="button is-fullwidth" 
+        data-tooltip={i18n.str`load more orders after the last one`}
+        onClick={onLoadMoreAfter}>
+          <i18n.Translate>load next page</i18n.Translate>
         </button>
       )}
     </div>
@@ -301,7 +303,7 @@ export function RefundModal({
           : undefined,
   };
   const hasErrors = Object.keys(errors).some(
-    (k) => (errors as any)[k] !== undefined,
+    (k) => (errors as Record<string, unknown>)[k] !== undefined,
   );
 
   const validateAndConfirm = () => {
@@ -380,7 +382,7 @@ export function RefundModal({
         <FormProvider<State>
           errors={errors}
           object={form}
-          valueHandler={(d) => setValue(d as any)}
+          valueHandler={(d) => setValue(d)}
         >
           <InputCurrency<State>
             name="refund"
diff --git 
a/packages/merchant-backoffice-ui/src/paths/instance/otp_devices/list/ListPage.tsx
 
b/packages/merchant-backoffice-ui/src/paths/instance/otp_devices/list/ListPage.tsx
index 9022cc35b..8ca0a9c58 100644
--- 
a/packages/merchant-backoffice-ui/src/paths/instance/otp_devices/list/ListPage.tsx
+++ 
b/packages/merchant-backoffice-ui/src/paths/instance/otp_devices/list/ListPage.tsx
@@ -52,9 +52,7 @@ export function ListPage({
         onDelete={onDelete}
         onSelect={onSelect}
         onLoadMoreBefore={onLoadMoreBefore}
-        hasMoreBefore={!onLoadMoreBefore}
         onLoadMoreAfter={onLoadMoreAfter}
-        hasMoreAfter={!onLoadMoreAfter}
       />
     </section>
   );
diff --git 
a/packages/merchant-backoffice-ui/src/paths/instance/otp_devices/list/Table.tsx 
b/packages/merchant-backoffice-ui/src/paths/instance/otp_devices/list/Table.tsx
index 7b1ccd4fc..afe3c98e2 100644
--- 
a/packages/merchant-backoffice-ui/src/paths/instance/otp_devices/list/Table.tsx
+++ 
b/packages/merchant-backoffice-ui/src/paths/instance/otp_devices/list/Table.tsx
@@ -32,8 +32,6 @@ interface Props {
   onSelect: (e: Entity) => void;
   onCreate: () => void;
   onLoadMoreBefore?: () => void;
-  hasMoreBefore?: boolean;
-  hasMoreAfter?: boolean;
   onLoadMoreAfter?: () => void;
 }
 
@@ -44,8 +42,6 @@ export function CardTable({
   onSelect,
   onLoadMoreAfter,
   onLoadMoreBefore,
-  hasMoreAfter,
-  hasMoreBefore,
 }: Props): VNode {
   const [rowSelection, rowSelectionHandler] = useState<string[]>([]);
 
@@ -85,8 +81,6 @@ export function CardTable({
                 rowSelectionHandler={rowSelectionHandler}
                 onLoadMoreAfter={onLoadMoreAfter}
                 onLoadMoreBefore={onLoadMoreBefore}
-                hasMoreAfter={hasMoreAfter}
-                hasMoreBefore={hasMoreBefore}
               />
             ) : (
               <EmptyTable />
@@ -104,8 +98,6 @@ interface TableProps {
   onSelect: (e: Entity) => void;
   rowSelectionHandler: StateUpdater<string[]>;
   onLoadMoreBefore?: () => void;
-  hasMoreBefore?: boolean;
-  hasMoreAfter?: boolean;
   onLoadMoreAfter?: () => void;
 }
 
@@ -115,19 +107,17 @@ function Table({
   onDelete,
   onSelect,
   onLoadMoreBefore,
-  hasMoreAfter,
-  hasMoreBefore,
 }: TableProps): VNode {
   const { i18n } = useTranslationContext();
   return (
     <div class="table-container">
-      {hasMoreBefore && (
+      {onLoadMoreBefore && (
         <button
           class="button is-fullwidth"
           data-tooltip={i18n.str`load more devices before the first one`}
           onClick={onLoadMoreBefore}
         >
-          <i18n.Translate>load newer devices</i18n.Translate>
+          <i18n.Translate>load first page</i18n.Translate>
         </button>
       )}
       <table class="table is-fullwidth is-striped is-hoverable is-fullwidth">
@@ -174,13 +164,13 @@ function Table({
           })}
         </tbody>
       </table>
-      {hasMoreAfter && (
+      {onLoadMoreAfter && (
         <button
           class="button is-fullwidth"
           data-tooltip={i18n.str`load more devices after the last one`}
           onClick={onLoadMoreAfter}
         >
-          <i18n.Translate>load older devices</i18n.Translate>
+          <i18n.Translate>load next page</i18n.Translate>
         </button>
       )}
     </div>
diff --git 
a/packages/merchant-backoffice-ui/src/paths/instance/products/list/Table.tsx 
b/packages/merchant-backoffice-ui/src/paths/instance/products/list/Table.tsx
index 265146c01..292974e89 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/products/list/Table.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/products/list/Table.tsx
@@ -341,12 +341,6 @@ function FastProductWithInfiniteStockUpdateForm({
 
       <div class="buttons is-expanded">
 
-        <div class="buttons mt-5">
-
-          <button class="button mt-5" onClick={onCancel}>
-            <i18n.Translate>Clone</i18n.Translate>
-          </button>
-        </div>
         <div class="buttons is-right mt-5">
           <button class="button" onClick={onCancel}>
             <i18n.Translate>Cancel</i18n.Translate>
diff --git 
a/packages/merchant-backoffice-ui/src/paths/instance/templates/list/ListPage.tsx
 
b/packages/merchant-backoffice-ui/src/paths/instance/templates/list/ListPage.tsx
index 84ff9e0f2..66d8a2f7e 100644
--- 
a/packages/merchant-backoffice-ui/src/paths/instance/templates/list/ListPage.tsx
+++ 
b/packages/merchant-backoffice-ui/src/paths/instance/templates/list/ListPage.tsx
@@ -57,9 +57,7 @@ export function ListPage({
         onSelect={onSelect}
         onNewOrder={onNewOrder}
         onLoadMoreBefore={onLoadMoreBefore}
-        hasMoreBefore={!onLoadMoreBefore}
         onLoadMoreAfter={onLoadMoreAfter}
-        hasMoreAfter={!onLoadMoreAfter}
       />
   );
 }
diff --git 
a/packages/merchant-backoffice-ui/src/paths/instance/templates/list/Table.tsx 
b/packages/merchant-backoffice-ui/src/paths/instance/templates/list/Table.tsx
index 11caca970..082e622e3 100644
--- 
a/packages/merchant-backoffice-ui/src/paths/instance/templates/list/Table.tsx
+++ 
b/packages/merchant-backoffice-ui/src/paths/instance/templates/list/Table.tsx
@@ -34,8 +34,6 @@ interface Props {
   onQR: (e: Entity) => void;
   onCreate: () => void;
   onLoadMoreBefore?: () => void;
-  hasMoreBefore?: boolean;
-  hasMoreAfter?: boolean;
   onLoadMoreAfter?: () => void;
 }
 
@@ -48,8 +46,6 @@ export function CardTable({
   onNewOrder,
   onLoadMoreAfter,
   onLoadMoreBefore,
-  hasMoreAfter,
-  hasMoreBefore,
 }: Props): VNode {
   const [rowSelection, rowSelectionHandler] = useState<string[]>([]);
 
@@ -91,8 +87,6 @@ export function CardTable({
                 rowSelectionHandler={rowSelectionHandler}
                 onLoadMoreAfter={onLoadMoreAfter}
                 onLoadMoreBefore={onLoadMoreBefore}
-                hasMoreAfter={hasMoreAfter}
-                hasMoreBefore={hasMoreBefore}
               />
             ) : (
               <EmptyTable />
@@ -112,8 +106,6 @@ interface TableProps {
   onSelect: (e: Entity) => void;
   rowSelectionHandler: StateUpdater<string[]>;
   onLoadMoreBefore?: () => void;
-  hasMoreBefore?: boolean;
-  hasMoreAfter?: boolean;
   onLoadMoreAfter?: () => void;
 }
 
@@ -125,19 +117,17 @@ function Table({
   onQR,
   onSelect,
   onLoadMoreBefore,
-  hasMoreAfter,
-  hasMoreBefore,
 }: TableProps): VNode {
   const { i18n } = useTranslationContext();
   return (
     <div class="table-container">
-      {hasMoreBefore && (
+      {onLoadMoreBefore && (
         <button
           class="button is-fullwidth"
           data-tooltip={i18n.str`load more templates before the first one`}
           onClick={onLoadMoreBefore}
         >
-          <i18n.Translate>load newer templates</i18n.Translate>
+          <i18n.Translate>load first page</i18n.Translate>
         </button>
       )}
       <table class="table is-fullwidth is-striped is-hoverable is-fullwidth">
@@ -198,13 +188,13 @@ function Table({
           })}
         </tbody>
       </table>
-      {hasMoreAfter && (
+      {onLoadMoreAfter && (
         <button
           class="button is-fullwidth"
           data-tooltip={i18n.str`load more templates after the last one`}
           onClick={onLoadMoreAfter}
         >
-          <i18n.Translate>load older templates</i18n.Translate>
+          <i18n.Translate>load next page</i18n.Translate>
         </button>
       )}
     </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 e1493a870..b99549825 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
@@ -62,7 +62,7 @@ type Entity = {
 interface Props {
   onUpdate: (d: TalerMerchantApi.TemplatePatchDetails) => Promise<void>;
   onBack?: () => void;
-  template: TalerMerchantApi.TemplateDetails;
+  template: TalerMerchantApi.TemplateDetails & WithId;
 }
 
 export function UpdatePage({ template, onUpdate, onBack }: Props): VNode {
@@ -187,7 +187,7 @@ export function UpdatePage({ template, onUpdate, onBack }: 
Props): VNode {
               <div class="level-left">
                 <div class="level-item">
                   <span class="is-size-4">
-                    {new 
URL(`templates/${template.otp_id}`,backendUrl.href).href}
+                    {new URL(`templates/${template.id}`,backendUrl.href).href}
                   </span>
                 </div>
               </div>
diff --git 
a/packages/merchant-backoffice-ui/src/paths/instance/templates/update/index.tsx 
b/packages/merchant-backoffice-ui/src/paths/instance/templates/update/index.tsx
index 2c0c358e2..9e5099947 100644
--- 
a/packages/merchant-backoffice-ui/src/paths/instance/templates/update/index.tsx
+++ 
b/packages/merchant-backoffice-ui/src/paths/instance/templates/update/index.tsx
@@ -79,7 +79,7 @@ export default function UpdateTemplate({
     <Fragment>
       <NotificationCard notification={notif} />
       <UpdatePage
-        template={result.body}
+        template={{...result.body, id: tid}}
         onBack={onBack}
         onUpdate={(data) => {
           return lib.instance.updateTemplate(state.token, tid, data)
diff --git 
a/packages/merchant-backoffice-ui/src/paths/instance/transfers/list/ListPage.tsx
 
b/packages/merchant-backoffice-ui/src/paths/instance/transfers/list/ListPage.tsx
index 7b54dc5ed..ff2bc6c23 100644
--- 
a/packages/merchant-backoffice-ui/src/paths/instance/transfers/list/ListPage.tsx
+++ 
b/packages/merchant-backoffice-ui/src/paths/instance/transfers/list/ListPage.tsx
@@ -125,9 +125,7 @@ export function ListPage({
         onCreate={onCreate}
         onDelete={onDelete}
         onLoadMoreBefore={onLoadMoreBefore}
-        hasMoreBefore={!onLoadMoreBefore}
         onLoadMoreAfter={onLoadMoreAfter}
-        hasMoreAfter={!onLoadMoreAfter}
       />
     </section>
   );
diff --git 
a/packages/merchant-backoffice-ui/src/paths/instance/transfers/list/Table.tsx 
b/packages/merchant-backoffice-ui/src/paths/instance/transfers/list/Table.tsx
index cf7ebe922..b9235c669 100644
--- 
a/packages/merchant-backoffice-ui/src/paths/instance/transfers/list/Table.tsx
+++ 
b/packages/merchant-backoffice-ui/src/paths/instance/transfers/list/Table.tsx
@@ -34,8 +34,6 @@ interface Props {
   onCreate: () => void;
   accounts: string[];
   onLoadMoreBefore?: () => void;
-  hasMoreBefore?: boolean;
-  hasMoreAfter?: boolean;
   onLoadMoreAfter?: () => void;
 }
 
@@ -45,8 +43,6 @@ export function CardTable({
   onDelete,
   onLoadMoreAfter,
   onLoadMoreBefore,
-  hasMoreAfter,
-  hasMoreBefore,
 }: Props): VNode {
   const [rowSelection, rowSelectionHandler] = useState<string[]>([]);
 
@@ -85,8 +81,6 @@ export function CardTable({
                 rowSelectionHandler={rowSelectionHandler}
                 onLoadMoreAfter={onLoadMoreAfter}
                 onLoadMoreBefore={onLoadMoreBefore}
-                hasMoreAfter={hasMoreAfter}
-                hasMoreBefore={hasMoreBefore}
               />
             ) : (
               <EmptyTable />
@@ -103,8 +97,6 @@ interface TableProps {
   onDelete: (id: Entity) => void;
   rowSelectionHandler: StateUpdater<string[]>;
   onLoadMoreBefore?: () => void;
-  hasMoreBefore?: boolean;
-  hasMoreAfter?: boolean;
   onLoadMoreAfter?: () => void;
 }
 
@@ -113,20 +105,18 @@ function Table({
   onLoadMoreAfter,
   onDelete,
   onLoadMoreBefore,
-  hasMoreAfter,
-  hasMoreBefore,
 }: TableProps): VNode {
   const { i18n } = useTranslationContext();
   const [settings] = usePreference();
   return (
     <div class="table-container">
-      {hasMoreBefore && (
+      {onLoadMoreBefore && (
         <button
           class="button is-fullwidth"
           data-tooltip={i18n.str`load more transfers before the first one`}
           onClick={onLoadMoreBefore}
         >
-          <i18n.Translate>load newer transfers</i18n.Translate>
+          <i18n.Translate>load first page</i18n.Translate>
         </button>
       )}
       <table class="table is-fullwidth is-striped is-hoverable is-fullwidth">
@@ -192,13 +182,13 @@ function Table({
           })}
         </tbody>
       </table>
-      {hasMoreAfter && (
+      {onLoadMoreAfter && (
         <button
           class="button is-fullwidth"
-          data-tooltip={i18n.str`load more transfer after the last one`}
+          data-tooltip={i18n.str`load more transfers after the last one`}
           onClick={onLoadMoreAfter}
         >
-          <i18n.Translate>load older transfers</i18n.Translate>
+          <i18n.Translate>load next page</i18n.Translate>
         </button>
       )}
     </div>
diff --git 
a/packages/merchant-backoffice-ui/src/paths/instance/webhooks/list/ListPage.tsx 
b/packages/merchant-backoffice-ui/src/paths/instance/webhooks/list/ListPage.tsx
index 98bd61d8f..3f1feb8e9 100644
--- 
a/packages/merchant-backoffice-ui/src/paths/instance/webhooks/list/ListPage.tsx
+++ 
b/packages/merchant-backoffice-ui/src/paths/instance/webhooks/list/ListPage.tsx
@@ -55,9 +55,7 @@ export function ListPage({
         onDelete={onDelete}
         onSelect={onSelect}
         onLoadMoreBefore={onLoadMoreBefore}
-        hasMoreBefore={!onLoadMoreBefore}
         onLoadMoreAfter={onLoadMoreAfter}
-        hasMoreAfter={!onLoadMoreAfter}
       />
     </section>
   );
diff --git 
a/packages/merchant-backoffice-ui/src/paths/instance/webhooks/list/Table.tsx 
b/packages/merchant-backoffice-ui/src/paths/instance/webhooks/list/Table.tsx
index 2cafc7f9a..919285e78 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/webhooks/list/Table.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/webhooks/list/Table.tsx
@@ -32,8 +32,6 @@ interface Props {
   onSelect: (e: Entity) => void;
   onCreate: () => void;
   onLoadMoreBefore?: () => void;
-  hasMoreBefore?: boolean;
-  hasMoreAfter?: boolean;
   onLoadMoreAfter?: () => void;
 }
 
@@ -44,8 +42,6 @@ export function CardTable({
   onSelect,
   onLoadMoreAfter,
   onLoadMoreBefore,
-  hasMoreAfter,
-  hasMoreBefore,
 }: Props): VNode {
   const [rowSelection, rowSelectionHandler] = useState<string[]>([]);
 
@@ -85,8 +81,6 @@ export function CardTable({
                 rowSelectionHandler={rowSelectionHandler}
                 onLoadMoreAfter={onLoadMoreAfter}
                 onLoadMoreBefore={onLoadMoreBefore}
-                hasMoreAfter={hasMoreAfter}
-                hasMoreBefore={hasMoreBefore}
               />
             ) : (
               <EmptyTable />
@@ -104,8 +98,6 @@ interface TableProps {
   onSelect: (e: Entity) => void;
   rowSelectionHandler: StateUpdater<string[]>;
   onLoadMoreBefore?: () => void;
-  hasMoreBefore?: boolean;
-  hasMoreAfter?: boolean;
   onLoadMoreAfter?: () => void;
 }
 
@@ -115,19 +107,17 @@ function Table({
   onDelete,
   onSelect,
   onLoadMoreBefore,
-  hasMoreAfter,
-  hasMoreBefore,
 }: TableProps): VNode {
   const { i18n } = useTranslationContext();
   return (
     <div class="table-container">
-      {hasMoreBefore && (
+      {onLoadMoreBefore && (
         <button
           class="button is-fullwidth"
           data-tooltip={i18n.str`load more webhooks before the first one`}
           onClick={onLoadMoreBefore}
         >
-          <i18n.Translate>load newer webhooks</i18n.Translate>
+          <i18n.Translate>load first page</i18n.Translate>
         </button>
       )}
       <table class="table is-fullwidth is-striped is-hoverable is-fullwidth">
@@ -181,13 +171,13 @@ function Table({
           })}
         </tbody>
       </table>
-      {hasMoreAfter && (
+      {onLoadMoreAfter && (
         <button
           class="button is-fullwidth"
           data-tooltip={i18n.str`load more webhooks after the last one`}
           onClick={onLoadMoreAfter}
         >
-          <i18n.Translate>load older webhooks</i18n.Translate>
+          <i18n.Translate>load next page</i18n.Translate>
         </button>
       )}
     </div>
diff --git a/packages/merchant-backoffice-ui/src/utils/constants.ts 
b/packages/merchant-backoffice-ui/src/utils/constants.ts
index 9e7a69ed0..6b4d8eade 100644
--- a/packages/merchant-backoffice-ui/src/utils/constants.ts
+++ b/packages/merchant-backoffice-ui/src/utils/constants.ts
@@ -35,11 +35,10 @@ export const CROCKFORD_BASE32_REGEX =
 export const URL_REGEX =
   /^((https?:)(\/\/\/?)([\w]*(?::[\w]*)?@)?([\d\w\.-]+)(?::(\d+))?)\/$/;
 
-// how much rows we add every time user hit load more
-export const PAGE_SIZE = 20;
-// how bigger can be the result set
-// after this threshold, load more with move the cursor
-export const MAX_RESULT_SIZE = PAGE_SIZE * 2 - 1;
+export const PAGINATED_LIST_SIZE = 5;
+// when doing paginated request, ask for one more
+// and use it to know if there are more to request
+export const PAGINATED_LIST_REQUEST = PAGINATED_LIST_SIZE + 1;
 
 // how much we will wait for all request, in seconds
 export const DEFAULT_REQUEST_TIMEOUT = 10;
diff --git a/packages/taler-harness/bin/create_merchantAndBankAccount_pdf.sh 
b/packages/taler-harness/bin/create_merchantAndBankAccount_pdf.sh
index cd87c18c9..008791eff 100755
--- a/packages/taler-harness/bin/create_merchantAndBankAccount_pdf.sh
+++ b/packages/taler-harness/bin/create_merchantAndBankAccount_pdf.sh
@@ -1,7 +1,20 @@
 #!/bin/bash
+# This file is in the public domain.
+
 THIS_FILE=$(realpath "$0")
 DIR=$(dirname "$THIS_FILE")
 
+# this script requires from debian packages
+# * jq 
+# * sponge (from moreutils) 
+# * wkhtmltopdf
+# * qrencode
+# * sponge: 
+#
+# and from python pip packages
+#
+# * chevron
+
 DATA=$(mktemp)
 set -e
 
diff --git a/packages/taler-util/src/http-client/merchant.ts 
b/packages/taler-util/src/http-client/merchant.ts
index cfe3155d1..23d7c76df 100644
--- a/packages/taler-util/src/http-client/merchant.ts
+++ b/packages/taler-util/src/http-client/merchant.ts
@@ -81,9 +81,36 @@ export type TalerMerchantInstanceErrorsByMethod<
 
 export enum TalerMerchantInstanceCacheEviction {
   CREATE_ORDER,
+  UPDATE_ORDER,
+  DELETE_ORDER,
+  UPDATE_CURRENT_INSTANCE,
+  DELETE_CURRENT_INSTANCE,
+  CREATE_BANK_ACCOUNT,
+  UPDATE_BANK_ACCOUNT,
+  DELETE_BANK_ACCOUNT,
+  CREATE_PRODUCT,
+  UPDATE_PRODUCT,
+  DELETE_PRODUCT,
+  CREATE_TRANSFER,
+  DELETE_TRANSFER,
+  CREATE_DEVICE,
+  UPDATE_DEVICE,
+  DELETE_DEVICE,
+  CREATE_TEMPLATE,
+  UPDATE_TEMPLATE,
+  DELETE_TEMPLATE,
+  CREATE_WEBHOOK,
+  UPDATE_WEBHOOK,
+  DELETE_WEBHOOK,
+  CREATE_TOKENFAMILY,
+  UPDATE_TOKENFAMILY,
+  DELETE_TOKENFAMILY,
+  LAST,
 }
 export enum TalerMerchantManagementCacheEviction {
-  CREATE_INSTANCE,
+  CREATE_INSTANCE = TalerMerchantInstanceCacheEviction.LAST + 1,
+  UPDATE_INSTANCE,
+  DELETE_INSTANCE,
 }
 /**
  * Protocol version spoken with the core bank.
@@ -150,8 +177,10 @@ export class TalerMerchantInstanceHttpClient {
     });
 
     switch (resp.status) {
-      case HttpStatusCode.Ok:
+      case HttpStatusCode.Ok: {
+        
this.cacheEvictor.notifySuccess(TalerMerchantInstanceCacheEviction.UPDATE_ORDER);
         return opSuccessFromHttp(resp, codecForClaimResponse());
+      }
       case HttpStatusCode.Conflict:
         return opKnownHttpFailure(resp.status, resp);
       case HttpStatusCode.NotFound:
@@ -173,8 +202,10 @@ export class TalerMerchantInstanceHttpClient {
     });
 
     switch (resp.status) {
-      case HttpStatusCode.Ok:
+      case HttpStatusCode.Ok: {
+        
this.cacheEvictor.notifySuccess(TalerMerchantInstanceCacheEviction.UPDATE_ORDER);
         return opSuccessFromHttp(resp, codecForPaymentResponse());
+      }
       case HttpStatusCode.BadRequest:
         return opKnownHttpFailure(resp.status, resp);
       case HttpStatusCode.PaymentRequired:
@@ -274,8 +305,10 @@ export class TalerMerchantInstanceHttpClient {
     });
 
     switch (resp.status) {
-      case HttpStatusCode.Ok:
+      case HttpStatusCode.Ok: {
+        
this.cacheEvictor.notifySuccess(TalerMerchantInstanceCacheEviction.UPDATE_ORDER);
         return opSuccessFromHttp(resp, codecForPaidRefundStatusResponse());
+      }
       case HttpStatusCode.BadRequest:
         return opKnownHttpFailure(resp.status, resp);
       case HttpStatusCode.Forbidden:
@@ -302,8 +335,10 @@ export class TalerMerchantInstanceHttpClient {
     });
 
     switch (resp.status) {
-      case HttpStatusCode.Ok:
+      case HttpStatusCode.Ok: {
+        
this.cacheEvictor.notifySuccess(TalerMerchantInstanceCacheEviction.UPDATE_ORDER);
         return opSuccessFromHttp(resp, codecForAbortResponse());
+      }
       case HttpStatusCode.BadRequest:
         return opKnownHttpFailure(resp.status, resp);
       case HttpStatusCode.Forbidden:
@@ -330,8 +365,10 @@ export class TalerMerchantInstanceHttpClient {
     });
 
     switch (resp.status) {
-      case HttpStatusCode.Ok:
+      case HttpStatusCode.Ok: {
+        
this.cacheEvictor.notifySuccess(TalerMerchantInstanceCacheEviction.UPDATE_ORDER);
         return opSuccessFromHttp(resp, codecForWalletRefundResponse());
+      }
       case HttpStatusCode.BadRequest:
         return opKnownHttpFailure(resp.status, resp);
       case HttpStatusCode.Forbidden:
@@ -399,8 +436,10 @@ export class TalerMerchantInstanceHttpClient {
       headers,
     });
     switch (resp.status) {
-      case HttpStatusCode.NoContent:
+      case HttpStatusCode.NoContent: {
+        
this.cacheEvictor.notifySuccess(TalerMerchantInstanceCacheEviction.UPDATE_CURRENT_INSTANCE);
         return opEmptySuccess(resp);
+      }
       case HttpStatusCode.Unauthorized: // FIXME: missing in docs
         return opKnownHttpFailure(resp.status, resp);
       case HttpStatusCode.NotFound:
@@ -461,8 +500,10 @@ export class TalerMerchantInstanceHttpClient {
     });
 
     switch (resp.status) {
-      case HttpStatusCode.NoContent:
+      case HttpStatusCode.NoContent: {
+        
this.cacheEvictor.notifySuccess(TalerMerchantInstanceCacheEviction.DELETE_CURRENT_INSTANCE);
         return opEmptySuccess(resp);
+      }
       case HttpStatusCode.Unauthorized:
         return opKnownHttpFailure(resp.status, resp);
       case HttpStatusCode.NotFound:
@@ -550,8 +591,10 @@ export class TalerMerchantInstanceHttpClient {
     });
 
     switch (resp.status) {
-      case HttpStatusCode.Ok:
+      case HttpStatusCode.Ok: {
+        
this.cacheEvictor.notifySuccess(TalerMerchantInstanceCacheEviction.CREATE_BANK_ACCOUNT);
         return opSuccessFromHttp(resp, codecForAccountAddResponse());
+      }
       case HttpStatusCode.Unauthorized: // FIXME: missing in docs
         return opKnownHttpFailure(resp.status, resp);
       case HttpStatusCode.NotFound:
@@ -583,8 +626,10 @@ export class TalerMerchantInstanceHttpClient {
       headers,
     });
     switch (resp.status) {
-      case HttpStatusCode.NoContent:
+      case HttpStatusCode.NoContent: {
+        
this.cacheEvictor.notifySuccess(TalerMerchantInstanceCacheEviction.UPDATE_BANK_ACCOUNT);
         return opEmptySuccess(resp);
+      }
       case HttpStatusCode.Unauthorized: // FIXME: missing in docs
         return opKnownHttpFailure(resp.status, resp);
       case HttpStatusCode.NotFound:
@@ -669,8 +714,10 @@ export class TalerMerchantInstanceHttpClient {
     });
 
     switch (resp.status) {
-      case HttpStatusCode.NoContent:
+      case HttpStatusCode.NoContent: {
+        
this.cacheEvictor.notifySuccess(TalerMerchantInstanceCacheEviction.DELETE_BANK_ACCOUNT);
         return opEmptySuccess(resp);
+      }
       case HttpStatusCode.Unauthorized: // FIXME: missing in docs
         return opKnownHttpFailure(resp.status, resp);
       case HttpStatusCode.NotFound:
@@ -704,8 +751,10 @@ export class TalerMerchantInstanceHttpClient {
     });
 
     switch (resp.status) {
-      case HttpStatusCode.NoContent:
+      case HttpStatusCode.NoContent: {
+        
this.cacheEvictor.notifySuccess(TalerMerchantInstanceCacheEviction.CREATE_PRODUCT);
         return opEmptySuccess(resp);
+      }
       case HttpStatusCode.Unauthorized: // FIXME: missing in docs
         return opKnownHttpFailure(resp.status, resp);
       case HttpStatusCode.NotFound: // FIXME: missing in docs
@@ -738,8 +787,10 @@ export class TalerMerchantInstanceHttpClient {
     });
 
     switch (resp.status) {
-      case HttpStatusCode.NoContent:
+      case HttpStatusCode.NoContent: {
+        
this.cacheEvictor.notifySuccess(TalerMerchantInstanceCacheEviction.UPDATE_PRODUCT);
         return opEmptySuccess(resp);
+      }
       case HttpStatusCode.Unauthorized: // FIXME: missing in docs
         return opKnownHttpFailure(resp.status, resp);
       case HttpStatusCode.NotFound:
@@ -831,8 +882,10 @@ export class TalerMerchantInstanceHttpClient {
     });
 
     switch (resp.status) {
-      case HttpStatusCode.NoContent:
+      case HttpStatusCode.NoContent: {
+        
this.cacheEvictor.notifySuccess(TalerMerchantInstanceCacheEviction.UPDATE_PRODUCT);
         return opEmptySuccess(resp);
+      }
       case HttpStatusCode.Unauthorized: // FIXME: missing in docs
         return opKnownHttpFailure(resp.status, resp);
       case HttpStatusCode.NotFound:
@@ -860,8 +913,10 @@ export class TalerMerchantInstanceHttpClient {
     });
 
     switch (resp.status) {
-      case HttpStatusCode.NoContent:
+      case HttpStatusCode.NoContent: {
+        
this.cacheEvictor.notifySuccess(TalerMerchantInstanceCacheEviction.DELETE_PRODUCT);
         return opEmptySuccess(resp);
+      }
       case HttpStatusCode.Unauthorized: // FIXME: missing in docs
         return opKnownHttpFailure(resp.status, resp);
       case HttpStatusCode.NotFound:
@@ -1049,8 +1104,10 @@ export class TalerMerchantInstanceHttpClient {
     });
 
     switch (resp.status) {
-      case HttpStatusCode.Ok:
+      case HttpStatusCode.Ok: {
+        
this.cacheEvictor.notifySuccess(TalerMerchantInstanceCacheEviction.UPDATE_ORDER);
         return opEmptySuccess(resp);
+      }
       case HttpStatusCode.NoContent:
         return opEmptySuccess(resp);
       case HttpStatusCode.Unauthorized: // FIXME: missing in docs
@@ -1082,8 +1139,10 @@ export class TalerMerchantInstanceHttpClient {
     });
 
     switch (resp.status) {
-      case HttpStatusCode.NoContent:
+      case HttpStatusCode.NoContent: {
+        
this.cacheEvictor.notifySuccess(TalerMerchantInstanceCacheEviction.DELETE_ORDER);
         return opEmptySuccess(resp);
+      }
       case HttpStatusCode.Unauthorized: // FIXME: missing in docs
         return opKnownHttpFailure(resp.status, resp);
       case HttpStatusCode.NotFound:
@@ -1120,8 +1179,10 @@ export class TalerMerchantInstanceHttpClient {
     });
 
     switch (resp.status) {
-      case HttpStatusCode.Ok:
+      case HttpStatusCode.Ok: {
+        
this.cacheEvictor.notifySuccess(TalerMerchantInstanceCacheEviction.UPDATE_ORDER);
         return opSuccessFromHttp(resp, codecForMerchantRefundResponse());
+      }
       case HttpStatusCode.Forbidden:
         return opKnownHttpFailure(resp.status, resp);
       case HttpStatusCode.Unauthorized: // FIXME: missing in docs
@@ -1161,8 +1222,10 @@ export class TalerMerchantInstanceHttpClient {
     });
 
     switch (resp.status) {
-      case HttpStatusCode.NoContent:
+      case HttpStatusCode.NoContent: {
+        
this.cacheEvictor.notifySuccess(TalerMerchantInstanceCacheEviction.CREATE_TRANSFER);
         return opEmptySuccess(resp);
+      }
       case HttpStatusCode.Unauthorized: // FIXME: missing in docs
         return opKnownHttpFailure(resp.status, resp);
       case HttpStatusCode.NotFound:
@@ -1234,8 +1297,10 @@ export class TalerMerchantInstanceHttpClient {
     });
 
     switch (resp.status) {
-      case HttpStatusCode.NoContent:
+      case HttpStatusCode.NoContent: {
+        
this.cacheEvictor.notifySuccess(TalerMerchantInstanceCacheEviction.DELETE_TRANSFER);
         return opEmptySuccess(resp);
+      }
       case HttpStatusCode.Unauthorized: // FIXME: missing in docs
         return opKnownHttpFailure(resp.status, resp);
       case HttpStatusCode.NotFound:
@@ -1271,8 +1336,10 @@ export class TalerMerchantInstanceHttpClient {
     });
 
     switch (resp.status) {
-      case HttpStatusCode.NoContent:
+      case HttpStatusCode.NoContent: {
+        
this.cacheEvictor.notifySuccess(TalerMerchantInstanceCacheEviction.CREATE_DEVICE);
         return opEmptySuccess(resp);
+      }
       case HttpStatusCode.Unauthorized: // FIXME: missing in docs
         return opKnownHttpFailure(resp.status, resp);
       case HttpStatusCode.NotFound:
@@ -1302,8 +1369,10 @@ export class TalerMerchantInstanceHttpClient {
       headers,
     });
     switch (resp.status) {
-      case HttpStatusCode.NoContent:
+      case HttpStatusCode.NoContent: {
+        
this.cacheEvictor.notifySuccess(TalerMerchantInstanceCacheEviction.UPDATE_DEVICE);
         return opEmptySuccess(resp);
+      }
       case HttpStatusCode.Unauthorized: // FIXME: missing in docs
         return opKnownHttpFailure(resp.status, resp);
       case HttpStatusCode.NotFound:
@@ -1396,8 +1465,10 @@ export class TalerMerchantInstanceHttpClient {
       headers,
     });
     switch (resp.status) {
-      case HttpStatusCode.NoContent:
+      case HttpStatusCode.NoContent: {
+        
this.cacheEvictor.notifySuccess(TalerMerchantInstanceCacheEviction.DELETE_DEVICE);
         return opEmptySuccess(resp);
+      }
       case HttpStatusCode.Unauthorized: // FIXME: missing in docs
         return opKnownHttpFailure(resp.status, resp);
       case HttpStatusCode.NotFound:
@@ -1430,8 +1501,10 @@ export class TalerMerchantInstanceHttpClient {
       headers,
     });
     switch (resp.status) {
-      case HttpStatusCode.NoContent:
+      case HttpStatusCode.NoContent: {
+        
this.cacheEvictor.notifySuccess(TalerMerchantInstanceCacheEviction.CREATE_TEMPLATE);
         return opEmptySuccess(resp);
+      }
       case HttpStatusCode.Unauthorized: // FIXME: missing in docs
         return opKnownHttpFailure(resp.status, resp);
       case HttpStatusCode.NotFound:
@@ -1461,8 +1534,10 @@ export class TalerMerchantInstanceHttpClient {
       headers,
     });
     switch (resp.status) {
-      case HttpStatusCode.NoContent:
+      case HttpStatusCode.NoContent: {
+        
this.cacheEvictor.notifySuccess(TalerMerchantInstanceCacheEviction.UPDATE_TEMPLATE);
         return opEmptySuccess(resp);
+      }
       case HttpStatusCode.Unauthorized: // FIXME: missing in docs
         return opKnownHttpFailure(resp.status, resp);
       case HttpStatusCode.NotFound:
@@ -1544,8 +1619,10 @@ export class TalerMerchantInstanceHttpClient {
       headers,
     });
     switch (resp.status) {
-      case HttpStatusCode.NoContent:
+      case HttpStatusCode.NoContent: {
+        
this.cacheEvictor.notifySuccess(TalerMerchantInstanceCacheEviction.DELETE_TEMPLATE);
         return opEmptySuccess(resp);
+      }
       case HttpStatusCode.Unauthorized: // FIXME: missing in docs
         return opKnownHttpFailure(resp.status, resp);
       case HttpStatusCode.NotFound:
@@ -1615,8 +1692,10 @@ export class TalerMerchantInstanceHttpClient {
     });
 
     switch (resp.status) {
-      case HttpStatusCode.NoContent:
+      case HttpStatusCode.NoContent: {
+        
this.cacheEvictor.notifySuccess(TalerMerchantInstanceCacheEviction.CREATE_WEBHOOK);
         return opEmptySuccess(resp);
+      }
       case HttpStatusCode.Unauthorized: // FIXME: missing in docs
         return opKnownHttpFailure(resp.status, resp);
       case HttpStatusCode.NotFound:
@@ -1647,8 +1726,10 @@ export class TalerMerchantInstanceHttpClient {
     });
 
     switch (resp.status) {
-      case HttpStatusCode.NoContent:
+      case HttpStatusCode.NoContent: {
+        
this.cacheEvictor.notifySuccess(TalerMerchantInstanceCacheEviction.UPDATE_WEBHOOK);
         return opEmptySuccess(resp);
+      }
       case HttpStatusCode.Unauthorized: // FIXME: missing in docs
         return opKnownHttpFailure(resp.status, resp);
       case HttpStatusCode.NotFound:
@@ -1731,8 +1812,10 @@ export class TalerMerchantInstanceHttpClient {
       headers,
     });
     switch (resp.status) {
-      case HttpStatusCode.NoContent:
+      case HttpStatusCode.NoContent: {
+        
this.cacheEvictor.notifySuccess(TalerMerchantInstanceCacheEviction.DELETE_WEBHOOK);
         return opEmptySuccess(resp);
+      }
       case HttpStatusCode.Unauthorized: // FIXME: missing in docs
         return opKnownHttpFailure(resp.status, resp);
       case HttpStatusCode.NotFound:
@@ -1766,8 +1849,10 @@ export class TalerMerchantInstanceHttpClient {
     });
 
     switch (resp.status) {
-      case HttpStatusCode.NoContent:
+      case HttpStatusCode.NoContent: {
+        
this.cacheEvictor.notifySuccess(TalerMerchantInstanceCacheEviction.CREATE_TOKENFAMILY);
         return opEmptySuccess(resp);
+      }
       case HttpStatusCode.Unauthorized: // FIXME: missing in docs
         return opKnownHttpFailure(resp.status, resp);
       case HttpStatusCode.NotFound:
@@ -1797,8 +1882,10 @@ export class TalerMerchantInstanceHttpClient {
       headers,
     });
     switch (resp.status) {
-      case HttpStatusCode.Ok:
+      case HttpStatusCode.Ok: {
+        
this.cacheEvictor.notifySuccess(TalerMerchantInstanceCacheEviction.UPDATE_TOKENFAMILY);
         return opSuccessFromHttp(resp, codecForTokenFamilyDetails());
+      }
       case HttpStatusCode.Unauthorized: // FIXME: missing in docs
         return opKnownHttpFailure(resp.status, resp);
       case HttpStatusCode.NotFound:
@@ -1883,8 +1970,10 @@ export class TalerMerchantInstanceHttpClient {
       headers,
     });
     switch (resp.status) {
-      case HttpStatusCode.NoContent:
+      case HttpStatusCode.NoContent: {
+        
this.cacheEvictor.notifySuccess(TalerMerchantInstanceCacheEviction.DELETE_TOKENFAMILY);
         return opEmptySuccess(resp);
+      }
       case HttpStatusCode.Unauthorized: // FIXME: missing in docs
         return opKnownHttpFailure(resp.status, resp);
       case HttpStatusCode.NotFound:
@@ -1913,15 +2002,15 @@ export type TalerMerchantManagementErrorsByMethod<
 > = FailCasesByMethod<TalerMerchantManagementHttpClient, prop>;
 
 export class TalerMerchantManagementHttpClient extends 
TalerMerchantInstanceHttpClient {
-  readonly cacheManagementEvictor: 
CacheEvictor<TalerMerchantManagementCacheEviction>;
+  readonly cacheManagementEvictor: 
CacheEvictor<TalerMerchantInstanceCacheEviction | 
TalerMerchantManagementCacheEviction>;
   constructor(
     readonly baseUrl: string,
     httpClient?: HttpRequestLibrary,
-    cacheManagementEvictor?: 
CacheEvictor<TalerMerchantManagementCacheEviction>,
-    cacheEvictor?: CacheEvictor<TalerMerchantInstanceCacheEviction>,
+    // cacheManagementEvictor?: 
CacheEvictor<TalerMerchantManagementCacheEviction>,
+    cacheEvictor?: CacheEvictor<TalerMerchantInstanceCacheEviction | 
TalerMerchantManagementCacheEviction>,
   ) {
     super(baseUrl, httpClient, cacheEvictor);
-    this.cacheManagementEvictor = cacheManagementEvictor ?? nullEvictor;
+    this.cacheManagementEvictor = cacheEvictor ?? nullEvictor;
   }
 
   getSubInstanceAPI(instanceId: string) {
@@ -1953,9 +2042,7 @@ export class TalerMerchantManagementHttpClient extends 
TalerMerchantInstanceHttp
 
     switch (resp.status) {
       case HttpStatusCode.NoContent: {
-        this.cacheManagementEvictor.notifySuccess(
-          TalerMerchantManagementCacheEviction.CREATE_INSTANCE,
-        );
+        
this.cacheManagementEvictor.notifySuccess(TalerMerchantManagementCacheEviction.CREATE_INSTANCE);
         return opEmptySuccess(resp);
       }
       case HttpStatusCode.Unauthorized: // FIXME: missing in docs
@@ -2022,8 +2109,10 @@ export class TalerMerchantManagementHttpClient extends 
TalerMerchantInstanceHttp
       headers,
     });
     switch (resp.status) {
-      case HttpStatusCode.NoContent:
+      case HttpStatusCode.NoContent: {
+        
this.cacheManagementEvictor.notifySuccess(TalerMerchantManagementCacheEviction.UPDATE_INSTANCE);
         return opEmptySuccess(resp);
+      }
       case HttpStatusCode.Unauthorized: // FIXME: missing in docs
         return opKnownHttpFailure(resp.status, resp);
       case HttpStatusCode.NotFound:
@@ -2112,8 +2201,10 @@ export class TalerMerchantManagementHttpClient extends 
TalerMerchantInstanceHttp
       headers,
     });
     switch (resp.status) {
-      case HttpStatusCode.NoContent:
+      case HttpStatusCode.NoContent: {
+        
this.cacheManagementEvictor.notifySuccess(TalerMerchantManagementCacheEviction.DELETE_INSTANCE);
         return opEmptySuccess(resp);
+      }
       case HttpStatusCode.Unauthorized: // FIXME: missing in docs
         return opKnownHttpFailure(resp.status, resp);
       case HttpStatusCode.NotFound:
diff --git a/packages/taler-util/src/http-client/types.ts 
b/packages/taler-util/src/http-client/types.ts
index dd2161deb..ea7ba341b 100644
--- a/packages/taler-util/src/http-client/types.ts
+++ b/packages/taler-util/src/http-client/types.ts
@@ -777,7 +777,7 @@ export const codecForTransferDetails =
       .property("payto_uri", codecForPaytoString())
       .property("exchange_url", codecForURL())
       .property("transfer_serial_id", codecForNumber())
-      .property("execution_time", codecForTimestamp)
+      .property("execution_time", codecOptional(codecForTimestamp))
       .property("verified", codecOptional(codecForBoolean()))
       .property("confirmed", codecOptional(codecForBoolean()))
       .build("TalerMerchantApi.TransferDetails");
@@ -3781,6 +3781,8 @@ export namespace TalerMerchantApi {
     // List of accounts that are known for the instance.
     accounts: BankAccountSummaryEntry[];
   }
+
+  // TODO: missing in docs
   export interface BankAccountSummaryEntry {
     // payto:// URI of the account.
     payto_uri: PaytoString;
diff --git a/packages/taler-util/src/time.ts b/packages/taler-util/src/time.ts
index 2e24856ee..95b4911a0 100644
--- a/packages/taler-util/src/time.ts
+++ b/packages/taler-util/src/time.ts
@@ -604,6 +604,9 @@ export function durationAdd(d1: Duration, d2: Duration): 
Duration {
 
 export const codecForAbsoluteTime: Codec<AbsoluteTime> = {
   decode(x: any, c?: Context): AbsoluteTime {
+    if (x === undefined) {
+      throw Error(`got undefined and expected absolute time at 
${renderContext(c)}`);
+    }
     const t_ms = x.t_ms;
     if (typeof t_ms === "string") {
       if (t_ms === "never") {
@@ -619,6 +622,9 @@ export const codecForAbsoluteTime: Codec<AbsoluteTime> = {
 export const codecForTimestamp: Codec<TalerProtocolTimestamp> = {
   decode(x: any, c?: Context): TalerProtocolTimestamp {
     // Compatibility, should be removed soon.
+    if (x === undefined) {
+      throw Error(`got undefined and expected timestamp at 
${renderContext(c)}`);
+    }
     const t_ms = x.t_ms;
     if (typeof t_ms === "string") {
       if (t_ms === "never") {
diff --git a/packages/web-util/src/context/merchant-api.ts 
b/packages/web-util/src/context/merchant-api.ts
index 26d9c9e85..604119e84 100644
--- a/packages/web-util/src/context/merchant-api.ts
+++ b/packages/web-util/src/context/merchant-api.ts
@@ -69,10 +69,7 @@ enum VersionHint {
 }
 
 type Evictors = {
-  management?: CacheEvictor<TalerMerchantManagementCacheEviction>;
-  instance?: (
-    instanceId: string,
-  ) => CacheEvictor<TalerMerchantInstanceCacheEviction>;
+  management?: CacheEvictor<TalerMerchantManagementCacheEviction | 
TalerMerchantInstanceCacheEviction>;
 };
 
 type ConfigResult<T> =

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