gnunet-svn
[Top][All Lists]
Advanced

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

[taler-merchant-backoffice] branch master updated (4f796dd -> 54723c9)


From: gnunet
Subject: [taler-merchant-backoffice] branch master updated (4f796dd -> 54723c9)
Date: Tue, 30 Nov 2021 20:21:47 +0100

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

sebasjm pushed a change to branch master
in repository merchant-backoffice.

    from 4f796dd  storybook
     new b56c499  format
     new 54723c9  fixing issue taken from christian comments:

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


Summary of changes:
 .../src/components/form/InputPaytoForm.tsx         | 341 +++++++++----
 .../instance/DefaultInstanceFormFields.tsx         | 138 +++--
 packages/merchant-backoffice/src/i18n/index.tsx    |  60 ++-
 .../src/paths/admin/create/CreatePage.tsx          | 257 ++++++----
 .../src/paths/admin/create/index.tsx               |  56 +-
 .../paths/instance/orders/create/CreatePage.tsx    | 568 ++++++++++++++-------
 .../src/paths/instance/update/UpdatePage.tsx       | 288 ++++++-----
 .../src/paths/instance/update/index.tsx            | 100 +++-
 packages/merchant-backoffice/src/schemas/index.ts  |   8 +-
 .../merchant-backoffice/src/utils/constants.ts     | 147 ++++++
 10 files changed, 1318 insertions(+), 645 deletions(-)

diff --git 
a/packages/merchant-backoffice/src/components/form/InputPaytoForm.tsx 
b/packages/merchant-backoffice/src/components/form/InputPaytoForm.tsx
index c52dc33..73cf751 100644
--- a/packages/merchant-backoffice/src/components/form/InputPaytoForm.tsx
+++ b/packages/merchant-backoffice/src/components/form/InputPaytoForm.tsx
@@ -15,12 +15,13 @@
  */
 
 /**
-*
-* @author Sebastian Javier Marchano (sebasjm)
-*/
+ *
+ * @author Sebastian Javier Marchano (sebasjm)
+ */
 import { h, VNode, Fragment } from "preact";
 import { useCallback, useState } from "preact/hooks";
-import { Translate, useTranslator } from "../../i18n";
+import { Translate, Translator, useTranslator } from "../../i18n";
+import { COUNTRY_TABLE } from "../../utils/constants";
 import { FormErrors, FormProvider } from "./FormProvider";
 import { Input } from "./Input";
 import { InputGroup } from "./InputGroup";
@@ -33,135 +34,287 @@ export interface Props<T> extends InputProps<T> {
 
 // https://datatracker.ietf.org/doc/html/rfc8905
 type Entity = {
-  target: string,
-  path: string,
-  path1: string,
-  path2: string,
-  host: string,
-  account: string,
+  // iban, bitcoin, x-taler-bank. it defined the format
+  target: string;
+  // path1 if the first field to be used
+  path1: string;
+  // path2 if the second field to be used, optional
+  path2?: string;
+  // options of the payto uri
   options: {
-    'receiver-name'?: string,
-    sender?: string,
-    message?: string,
-    amount?: string,
-    instruction?: string,
-    [name: string]: string | undefined,
-  },
+    "receiver-name"?: string;
+    sender?: string;
+    message?: string;
+    amount?: string;
+    instruction?: string;
+    [name: string]: string | undefined;
+  };
+};
+
+/**
+ * An IBAN is validated by converting it into an integer and performing a
+ * basic mod-97 operation (as described in ISO 7064) on it.
+ * If the IBAN is valid, the remainder equals 1.
+ *
+ * The algorithm of IBAN validation is as follows:
+ * 1.- Check that the total IBAN length is correct as per the country. If not, 
the IBAN is invalid
+ * 2.- Move the four initial characters to the end of the string
+ * 3.- Replace each letter in the string with two digits, thereby expanding 
the string, where A = 10, B = 11, ..., Z = 35
+ * 4.- Interpret the string as a decimal integer and compute the remainder of 
that number on division by 97
+ *
+ * If the remainder is 1, the check digit test is passed and the IBAN might be 
valid.
+ *
+ */
+function validateIBAN(iban: string, i18n: Translator): string | undefined {
+  // Check total length
+  if (iban.length < 4)
+    return i18n`IBAN numbers usually have more that 4 digits`;
+  if (iban.length > 34)
+    return i18n`IBAN numbers usually have less that 34 digits`;
+
+  const A_code = "A".charCodeAt(0);
+  const Z_code = "Z".charCodeAt(0);
+  const IBAN = iban.toUpperCase();
+  // check supported country
+  const code = IBAN.substr(0, 2);
+  const found = code in COUNTRY_TABLE;
+  if (!found) return i18n`IBAN country code not found`;
+
+  // 2.- Move the four initial characters to the end of the string
+  const step2 = IBAN.substr(4) + iban.substr(0, 4);
+  const step3 = Array.from(step2)
+    .map((letter) => {
+      const code = letter.charCodeAt(0);
+      if (code < A_code || code > Z_code) return letter;
+      return `${letter.charCodeAt(0) - "A".charCodeAt(0) + 10}`;
+    })
+    .join("");
+
+  function calculate_iban_checksum(str: string): number {
+    const numberStr = str.substr(0, 5);
+    const rest = str.substr(5);
+    const number = parseInt(numberStr, 10);
+    const result = number % 97;
+    if (rest.length > 0) {
+      return calculate_iban_checksum(`${result}${rest}`);
+    }
+    return result;
+  }
+
+  const checksum = calculate_iban_checksum(step3);
+  if (checksum !== 1) return i18n`IBAN number is not valid, checksum is wrong`;
+  return undefined;
 }
 
 // const targets = ['ach', 'bic', 'iban', 'upi', 'bitcoin', 'ilp', 'void', 
'x-taler-bank']
-const targets = ['iban', 'x-taler-bank']
-const defaultTarget = { target: 'iban', options: {} }
+const targets = ["iban", "x-taler-bank"];
+const defaultTarget = { target: "iban", options: {} };
 
 function undefinedIfEmpty<T>(obj: T): T | undefined {
-  return Object.keys(obj).some(k => (obj as any)[k] !== undefined) ? obj : 
undefined
+  return Object.keys(obj).some((k) => (obj as any)[k] !== undefined)
+    ? obj
+    : undefined;
 }
 
-export function InputPaytoForm<T>({ name, readonly, label, tooltip }: 
Props<keyof T>): VNode {
-  const { value: paytos, onChange, } = useField<T>(name);
+export function InputPaytoForm<T>({
+  name,
+  readonly,
+  label,
+  tooltip,
+}: Props<keyof T>): VNode {
+  const { value: paytos, onChange } = useField<T>(name);
 
-  const [value, valueHandler] = useState<Partial<Entity>>(defaultTarget)
+  const [value, valueHandler] = useState<Partial<Entity>>(defaultTarget);
 
-  if (value.path1) {
+  let payToPath;
+  if (value.target === "iban" && value.path1) {
+    payToPath = `/${value.path1.toUpperCase()}`;
+  } else if (value.path1) {
     if (value.path2) {
-      value.path = `/${value.path1}/${value.path2}`
+      payToPath = `/${value.path1}/${value.path2}`;
     } else {
-      value.path = `/${value.path1}`
+      payToPath = `/${value.path1}`;
     }
   }
-  const i18n = useTranslator()
+  const i18n = useTranslator();
 
-  const url = new URL(`payto://${value.target}${value.path}`)
-  const ops = value.options!
-  Object.keys(ops).forEach(opt_key => {
-    const opt_value = ops[opt_key]
-    if (opt_value) url.searchParams.set(opt_key, opt_value)
-  })
-  const paytoURL = url.toString()
+  const url = new URL(`payto://${value.target}${payToPath}`);
+  const ops = value.options!;
+  Object.keys(ops).forEach((opt_key) => {
+    const opt_value = ops[opt_key];
+    if (opt_value) url.searchParams.set(opt_key, opt_value);
+  });
+  const paytoURL = url.toString();
 
   const errors: FormErrors<Entity> = {
     target: !value.target ? i18n`required` : undefined,
-    path1: !value.path1 ? i18n`required` : (
-      value.target === 'iban' ? (
-        value.path1.length < 4 ? i18n`IBAN numbers usually have more that 4 
digits` : (
-          value.path1.length > 34 ? i18n`IBAN numbers usually have less that 
34 digits` :
-          undefined
-        )
-      ): undefined 
-    ),
-    path2: value.target === 'x-taler-bank' ? (!value.path2 ? i18n`required` : 
undefined) : undefined,
+    path1: !value.path1
+      ? i18n`required`
+      : value.target === "iban"
+      ? validateIBAN(value.path1, i18n)
+      : undefined,
+    path2:
+      value.target === "x-taler-bank"
+        ? !value.path2
+          ? i18n`required`
+          : undefined
+        : undefined,
     options: undefinedIfEmpty({
-      'receiver-name': !value.options?.["receiver-name"] ? i18n`required` : 
undefined,
-    })
-  }
+      "receiver-name": !value.options?.["receiver-name"]
+        ? i18n`required`
+        : undefined,
+    }),
+  };
 
-  const hasErrors = Object.keys(errors).some(k => (errors as any)[k] !== 
undefined)
+  const hasErrors = Object.keys(errors).some(
+    (k) => (errors as any)[k] !== undefined
+  );
 
   const submit = useCallback((): void => {
-    const alreadyExists = paytos.findIndex((x:string) => x === paytoURL) !== 
-1;
+    const alreadyExists =
+      paytos.findIndex((x: string) => x === paytoURL) !== -1;
     if (!alreadyExists) {
-      onChange([paytoURL, ...paytos] as any)
+      onChange([paytoURL, ...paytos] as any);
     }
-    valueHandler(defaultTarget)
-  }, [value])
-
+    valueHandler(defaultTarget);
+  }, [value]);
 
   //FIXME: translating plural singular
   return (
     <InputGroup name="payto" label={label} fixed tooltip={tooltip}>
-      <FormProvider<Entity> name="tax" errors={errors} object={value} 
valueHandler={valueHandler} >
-
-        <InputSelector<Entity> name="target" label={i18n`Target type`} 
tooltip={i18n`Method to use for wire transfer`} values={targets} />
-
-        {value.target === 'ach' && <Fragment>
-          <Input<Entity> name="path1" label={i18n`Routing`} 
tooltip={i18n`Routing number.`} />
-          <Input<Entity> name="path2" label={i18n`Account`} 
tooltip={i18n`Account number.`} />
-        </Fragment>}
-        {value.target === 'bic' && <Fragment>
-          <Input<Entity> name="path1" label={i18n`Code`} 
tooltip={i18n`Business Identifier Code.`} />
-        </Fragment>}
-        {value.target === 'iban' && <Fragment>
-          <Input<Entity> name="path1" label={i18n`Account`} tooltip={i18n`Bank 
Account Number.`} />
-        </Fragment>}
-        {value.target === 'upi' && <Fragment>
-          <Input<Entity> name="path1" label={i18n`Account`} 
tooltip={i18n`Unified Payment Interface.`} />
-        </Fragment>}
-        {value.target === 'bitcoin' && <Fragment>
-          <Input<Entity> name="path1" label={i18n`Address`} 
tooltip={i18n`Bitcoin protocol.`} />
-        </Fragment>}
-        {value.target === 'ilp' && <Fragment>
-          <Input<Entity> name="path1" label={i18n`Address`} 
tooltip={i18n`Interledger protocol.`} />
-        </Fragment>}
-        {value.target === 'void' && <Fragment>
-        </Fragment>}
-        {value.target === 'x-taler-bank' && <Fragment>
-          <Input<Entity> name="path1" label={i18n`Host`} tooltip={i18n`Bank 
host.`} />
-          <Input<Entity> name="path2" label={i18n`Account`} tooltip={i18n`Bank 
account.`} />
-        </Fragment>}
-
-        <Input name="options.receiver-name" label={i18n`Name`} 
tooltip={i18n`Bank account owner's name.`} />
+      <FormProvider<Entity>
+        name="tax"
+        errors={errors}
+        object={value}
+        valueHandler={valueHandler}
+      >
+        <InputSelector<Entity>
+          name="target"
+          label={i18n`Target type`}
+          tooltip={i18n`Method to use for wire transfer`}
+          values={targets}
+        />
+
+        {value.target === "ach" && (
+          <Fragment>
+            <Input<Entity>
+              name="path1"
+              label={i18n`Routing`}
+              tooltip={i18n`Routing number.`}
+            />
+            <Input<Entity>
+              name="path2"
+              label={i18n`Account`}
+              tooltip={i18n`Account number.`}
+            />
+          </Fragment>
+        )}
+        {value.target === "bic" && (
+          <Fragment>
+            <Input<Entity>
+              name="path1"
+              label={i18n`Code`}
+              tooltip={i18n`Business Identifier Code.`}
+            />
+          </Fragment>
+        )}
+        {value.target === "iban" && (
+          <Fragment>
+            <Input<Entity>
+              name="path1"
+              label={i18n`Account`}
+              tooltip={i18n`Bank Account Number.`}
+              inputExtra={{ style: { textTransform: "uppercase" } }}
+            />
+          </Fragment>
+        )}
+        {value.target === "upi" && (
+          <Fragment>
+            <Input<Entity>
+              name="path1"
+              label={i18n`Account`}
+              tooltip={i18n`Unified Payment Interface.`}
+            />
+          </Fragment>
+        )}
+        {value.target === "bitcoin" && (
+          <Fragment>
+            <Input<Entity>
+              name="path1"
+              label={i18n`Address`}
+              tooltip={i18n`Bitcoin protocol.`}
+            />
+          </Fragment>
+        )}
+        {value.target === "ilp" && (
+          <Fragment>
+            <Input<Entity>
+              name="path1"
+              label={i18n`Address`}
+              tooltip={i18n`Interledger protocol.`}
+            />
+          </Fragment>
+        )}
+        {value.target === "void" && <Fragment />}
+        {value.target === "x-taler-bank" && (
+          <Fragment>
+            <Input<Entity>
+              name="path1"
+              label={i18n`Host`}
+              tooltip={i18n`Bank host.`}
+            />
+            <Input<Entity>
+              name="path2"
+              label={i18n`Account`}
+              tooltip={i18n`Bank account.`}
+            />
+          </Fragment>
+        )}
+
+        <Input
+          name="options.receiver-name"
+          label={i18n`Name`}
+          tooltip={i18n`Bank account owner's name.`}
+        />
 
         <div class="field is-horizontal">
           <div class="field-label is-normal" />
-          <div class="field-body" style={{ display: 'block' }}>
-            {paytos.map((v: any, i: number) => <div key={i} class="tags 
has-addons mt-3 mb-0 mr-3" style={{ flexWrap: 'nowrap' }}>
-              <span class="tag is-medium is-info mb-0" style={{ maxWidth: 
'90%' }}>{v}</span>
-              <a class="tag is-medium is-danger is-delete mb-0" onClick={() => 
{
-                onChange(paytos.filter((f: any) => f !== v) as any);
-              }} />
-            </div>
-            )}
+          <div class="field-body" style={{ display: "block" }}>
+            {paytos.map((v: any, i: number) => (
+              <div
+                key={i}
+                class="tags has-addons mt-3 mb-0 mr-3"
+                style={{ flexWrap: "nowrap" }}
+              >
+                <span
+                  class="tag is-medium is-info mb-0"
+                  style={{ maxWidth: "90%" }}
+                >
+                  {v}
+                </span>
+                <a
+                  class="tag is-medium is-danger is-delete mb-0"
+                  onClick={() => {
+                    onChange(paytos.filter((f: any) => f !== v) as any);
+                  }}
+                />
+              </div>
+            ))}
             {!paytos.length && i18n`No accounts yet.`}
           </div>
         </div>
 
         <div class="buttons is-right mt-5">
-          <button class="button is-info"
+          <button
+            class="button is-info"
             data-tooltip={i18n`add tax to the tax list`}
             disabled={hasErrors}
-            onClick={submit}><Translate>Add</Translate></button>
+            onClick={submit}
+          >
+            <Translate>Add</Translate>
+          </button>
         </div>
       </FormProvider>
     </InputGroup>
-  )
+  );
 }
diff --git 
a/packages/merchant-backoffice/src/components/instance/DefaultInstanceFormFields.tsx
 
b/packages/merchant-backoffice/src/components/instance/DefaultInstanceFormFields.tsx
index fae8a35..8c59a6d 100644
--- 
a/packages/merchant-backoffice/src/components/instance/DefaultInstanceFormFields.tsx
+++ 
b/packages/merchant-backoffice/src/components/instance/DefaultInstanceFormFields.tsx
@@ -15,11 +15,11 @@
  */
 
 /**
-*
-* @author Sebastian Javier Marchano (sebasjm)
-*/
+ *
+ * @author Sebastian Javier Marchano (sebasjm)
+ */
 
-import { Fragment, h } from "preact";
+import { Fragment, h, VNode } from "preact";
 import { useBackendContext } from "../../context/backend";
 import { useTranslator } from "../../i18n";
 import { Entity } from "../../paths/admin/create/CreatePage";
@@ -31,56 +31,86 @@ import { InputLocation } from "../form/InputLocation";
 import { InputPaytoForm } from "../form/InputPaytoForm";
 import { InputWithAddon } from "../form/InputWithAddon";
 
-export function DefaultInstanceFormFields({ readonlyId, showId }: { 
readonlyId?: boolean; showId: boolean }) {
+export function DefaultInstanceFormFields({
+  readonlyId,
+  showId,
+}: {
+  readonlyId?: boolean;
+  showId: boolean;
+}): VNode {
   const i18n = useTranslator();
   const backend = useBackendContext();
-  return <Fragment>
-    {showId && <InputWithAddon<Entity> name="id"
-      addonBefore={`${backend.url}/instances/`} readonly={readonlyId}
-      label={i18n`Identifier`}
-      tooltip={i18n`Name of the instance in URLs. The 'default' instance is 
special in that it is used to administer other instances.`} />
-    }
-
-    <Input<Entity> name="name"
-      label={i18n`Business name`}
-      tooltip={i18n`Legal name of the business represented by this instance.`} 
/>
-
-    <InputPaytoForm<Entity> name="payto_uris"
-      label={i18n`Bank account`} 
-      tooltip={i18n`URI specifying bank account for crediting revenue.`} />
-
-    <InputCurrency<Entity> name="default_max_deposit_fee"
-      label={i18n`Default max deposit fee`}
-      tooltip={i18n`Maximum deposit fees this merchant is willing to pay per 
order by default.`} />
-
-    <InputCurrency<Entity> name="default_max_wire_fee"
-      label={i18n`Default max wire fee`}
-      tooltip={i18n`Maximum wire fees this merchant is willing to pay per wire 
transfer by default.`} />
-
-    <Input<Entity> name="default_wire_fee_amortization"
-      label={i18n`Default wire fee amortization`}
-      tooltip={i18n`Number of orders excess wire transfer fees will be divided 
by to compute per order surcharge.`} />
-
-    <InputGroup name="address"
-      label={i18n`Address`}
-      tooltip={i18n`Physical location of the merchant.`}>
-      <InputLocation name="address" />
-    </InputGroup>
-
-    <InputGroup name="jurisdiction"
-      label={i18n`Jurisdiction`}
-      tooltip={i18n`Jurisdiction for legal disputes with the merchant.`}>
-      <InputLocation name="jurisdiction" />
-    </InputGroup>
-
-    <InputDuration<Entity> name="default_pay_delay"
-      label={i18n`Default payment delay`}
-      withForever
-      tooltip={i18n`Time customers have to pay an order before the offer 
expires by default.`} />
-
-    <InputDuration<Entity> name="default_wire_transfer_delay"
-      label={i18n`Default wire transfer delay`}
-      tooltip={i18n`Maximum time an exchange is allowed to delay wiring funds 
to the merchant, enabling it to aggregate smaller payments into larger wire 
transfers and reducing wire fees.`} />
-
-  </Fragment>;
+  return (
+    <Fragment>
+      {showId && (
+        <InputWithAddon<Entity>
+          name="id"
+          addonBefore={`${backend.url}/instances/`}
+          readonly={readonlyId}
+          label={i18n`Identifier`}
+          tooltip={i18n`Name of the instance in URLs. The 'default' instance 
is special in that it is used to administer other instances.`}
+        />
+      )}
+
+      <Input<Entity>
+        name="name"
+        label={i18n`Business name`}
+        tooltip={i18n`Legal name of the business represented by this 
instance.`}
+      />
+
+      <InputPaytoForm<Entity>
+        name="payto_uris"
+        label={i18n`Bank account`}
+        tooltip={i18n`URI specifying bank account for crediting revenue.`}
+      />
+
+      <InputCurrency<Entity>
+        name="default_max_deposit_fee"
+        label={i18n`Default max deposit fee`}
+        tooltip={i18n`Maximum deposit fees this merchant is willing to pay per 
order by default.`}
+      />
+
+      <InputCurrency<Entity>
+        name="default_max_wire_fee"
+        label={i18n`Default max wire fee`}
+        tooltip={i18n`Maximum wire fees this merchant is willing to pay per 
wire transfer by default.`}
+      />
+
+      <Input<Entity>
+        name="default_wire_fee_amortization"
+        label={i18n`Default wire fee amortization`}
+        tooltip={i18n`Number of orders excess wire transfer fees will be 
divided by to compute per order surcharge.`}
+      />
+
+      <InputGroup
+        name="address"
+        label={i18n`Address`}
+        tooltip={i18n`Physical location of the merchant.`}
+      >
+        <InputLocation name="address" />
+      </InputGroup>
+
+      <InputGroup
+        name="jurisdiction"
+        label={i18n`Jurisdiction`}
+        tooltip={i18n`Jurisdiction for legal disputes with the merchant.`}
+      >
+        <InputLocation name="jurisdiction" />
+      </InputGroup>
+
+      <InputDuration<Entity>
+        name="default_pay_delay"
+        label={i18n`Default payment delay`}
+        withForever
+        tooltip={i18n`Time customers have to pay an order before the offer 
expires by default.`}
+      />
+
+      <InputDuration<Entity>
+        name="default_wire_transfer_delay"
+        label={i18n`Default wire transfer delay`}
+        tooltip={i18n`Maximum time an exchange is allowed to delay wiring 
funds to the merchant, enabling it to aggregate smaller payments into larger 
wire transfers and reducing wire fees.`}
+        withForever
+      />
+    </Fragment>
+  );
 }
diff --git a/packages/merchant-backoffice/src/i18n/index.tsx 
b/packages/merchant-backoffice/src/i18n/index.tsx
index 63c8e19..9403de1 100644
--- a/packages/merchant-backoffice/src/i18n/index.tsx
+++ b/packages/merchant-backoffice/src/i18n/index.tsx
@@ -25,25 +25,31 @@ import { ComponentChild, ComponentChildren, h, Fragment, 
VNode } from "preact";
 
 import { useTranslationContext } from "../context/translation";
 
-export function useTranslator() {
+export type Translator = (
+  stringSeq: TemplateStringsArray,
+  ...values: any[]
+) => string;
+export function useTranslator(): Translator {
   const ctx = useTranslationContext();
-  const jed = ctx.handler
-  return function str(stringSeq: TemplateStringsArray, ...values: any[]): 
string {
+  const jed = ctx.handler;
+  return function str(
+    stringSeq: TemplateStringsArray,
+    ...values: any[]
+  ): string {
     const s = toI18nString(stringSeq);
-    if (!s) return s
+    if (!s) return s;
     const tr = jed
       .translate(s)
       .ifPlural(1, s)
       .fetch(...values);
     return tr;
-  }
+  };
 }
 
-
 /**
  * Convert template strings to a msgid
  */
- function toI18nString(stringSeq: ReadonlyArray<string>): string {
+function toI18nString(stringSeq: ReadonlyArray<string>): string {
   let s = "";
   for (let i = 0; i < stringSeq.length; i++) {
     s += stringSeq[i];
@@ -54,7 +60,6 @@ export function useTranslator() {
   return s;
 }
 
-
 interface TranslateSwitchProps {
   target: number;
   children: ComponentChildren;
@@ -88,7 +93,7 @@ interface TranslateProps {
 
 function getTranslatedChildren(
   translation: string,
-  children: ComponentChildren,
+  children: ComponentChildren
 ): ComponentChild[] {
   const tr = translation.split(/%(\d+)\$s/);
   const childArray = children instanceof Array ? children : [children];
@@ -110,7 +115,7 @@ function getTranslatedChildren(
       // Text
       result.push(tr[i]);
     } else {
-      const childIdx = Number.parseInt(tr[i],10) - 1;
+      const childIdx = Number.parseInt(tr[i], 10) - 1;
       result.push(placeholderChildren[childIdx]);
     }
   }
@@ -131,9 +136,9 @@ function getTranslatedChildren(
  */
 export function Translate({ children }: TranslateProps): VNode {
   const s = stringifyChildren(children);
-  const ctx = useTranslationContext()
+  const ctx = useTranslationContext();
   const translation: string = ctx.handler.ngettext(s, s, 1);
-  const result = getTranslatedChildren(translation, children)
+  const result = getTranslatedChildren(translation, children);
   return <Fragment>{result}</Fragment>;
 }
 
@@ -154,14 +159,16 @@ export function TranslateSwitch({ children, target }: 
TranslateSwitchProps) {
   let plural: VNode<TranslationPluralProps> | undefined;
   // const children = this.props.children;
   if (children) {
-    (children instanceof Array ? children : [children]).forEach((child: any) 
=> {
-      if (child.type === TranslatePlural) {
-        plural = child;
+    (children instanceof Array ? children : [children]).forEach(
+      (child: any) => {
+        if (child.type === TranslatePlural) {
+          plural = child;
+        }
+        if (child.type === TranslateSingular) {
+          singular = child;
+        }
       }
-      if (child.type === TranslateSingular) {
-        singular = child;
-      }
-    });
+    );
   }
   if (!singular || !plural) {
     console.error("translation not found");
@@ -182,9 +189,12 @@ interface TranslationPluralProps {
 /**
  * See [[TranslateSwitch]].
  */
-export function TranslatePlural({ children, target }: TranslationPluralProps): 
VNode {
+export function TranslatePlural({
+  children,
+  target,
+}: TranslationPluralProps): VNode {
   const s = stringifyChildren(children);
-  const ctx = useTranslationContext()
+  const ctx = useTranslationContext();
   const translation = ctx.handler.ngettext(s, s, 1);
   const result = getTranslatedChildren(translation, children);
   return <Fragment>{result}</Fragment>;
@@ -193,11 +203,13 @@ export function TranslatePlural({ children, target }: 
TranslationPluralProps): V
 /**
  * See [[TranslateSwitch]].
  */
-export function TranslateSingular({ children, target }: 
TranslationPluralProps): VNode {
+export function TranslateSingular({
+  children,
+  target,
+}: TranslationPluralProps): VNode {
   const s = stringifyChildren(children);
-  const ctx = useTranslationContext()
+  const ctx = useTranslationContext();
   const translation = ctx.handler.ngettext(s, s, target);
   const result = getTranslatedChildren(translation, children);
   return <Fragment>{result}</Fragment>;
-
 }
diff --git a/packages/merchant-backoffice/src/paths/admin/create/CreatePage.tsx 
b/packages/merchant-backoffice/src/paths/admin/create/CreatePage.tsx
index f5fa7c9..f1214c9 100644
--- a/packages/merchant-backoffice/src/paths/admin/create/CreatePage.tsx
+++ b/packages/merchant-backoffice/src/paths/admin/create/CreatePage.tsx
@@ -15,15 +15,18 @@
  */
 
 /**
-*
-* @author Sebastian Javier Marchano (sebasjm)
-*/
+ *
+ * @author Sebastian Javier Marchano (sebasjm)
+ */
 
 import { h, VNode } from "preact";
 import { useState } from "preact/hooks";
-import * as yup from 'yup';
+import * as yup from "yup";
 import { AsyncButton } from "../../../components/exception/AsyncButton";
-import { FormErrors, FormProvider } from 
"../../../components/form/FormProvider";
+import {
+  FormErrors,
+  FormProvider,
+} from "../../../components/form/FormProvider";
 import { SetTokenNewInstanceModal } from "../../../components/modal";
 import { MerchantBackend } from "../../../declaration";
 import { Translate, useTranslator } from "../../../i18n";
@@ -31,10 +34,9 @@ import { DefaultInstanceFormFields } from 
"../../../components/instance/DefaultI
 import { INSTANCE_ID_REGEX, PAYTO_REGEX } from "../../../utils/constants";
 import { Amounts } from "@gnu-taler/taler-util";
 
-export type Entity = MerchantBackend.Instances.InstanceConfigurationMessage & 
{ 
-  auth_token?: string 
-}
-
+export type Entity = MerchantBackend.Instances.InstanceConfigurationMessage & {
+  auth_token?: string;
+};
 
 interface Props {
   onCreate: (d: Entity) => Promise<void>;
@@ -53,137 +55,180 @@ function with_defaults(id?: string): Partial<Entity> {
 }
 
 function undefinedIfEmpty<T>(obj: T): T | undefined {
-  return Object.keys(obj).some(k => (obj as any)[k] !== undefined) ? obj : 
undefined
+  return Object.keys(obj).some((k) => (obj as any)[k] !== undefined)
+    ? obj
+    : undefined;
 }
 
 export function CreatePage({ onCreate, onBack, forceId }: Props): VNode {
-  const [value, valueHandler] = useState(with_defaults(forceId))
+  const [value, valueHandler] = useState(with_defaults(forceId));
   const [isTokenSet, updateIsTokenSet] = useState<boolean>(false);
-  const [isTokenDialogActive, updateIsTokenDialogActive] = 
useState<boolean>(false);
+  const [isTokenDialogActive, updateIsTokenDialogActive] =
+    useState<boolean>(false);
 
-  const i18n = useTranslator()
+  const i18n = useTranslator();
 
   const errors: FormErrors<Entity> = {
-    id: !value.id ? i18n`required` : (!INSTANCE_ID_REGEX.test(value.id) ? 
i18n`is not valid` : undefined),
+    id: !value.id
+      ? i18n`required`
+      : !INSTANCE_ID_REGEX.test(value.id)
+      ? i18n`is not valid`
+      : undefined,
     name: !value.name ? i18n`required` : undefined,
     payto_uris:
-      !value.payto_uris || !value.payto_uris.length ? i18n`required` : (
-        undefinedIfEmpty(value.payto_uris.map(p => {
-          return !PAYTO_REGEX.test(p) ? i18n`is not valid` : undefined
-        }))
-      ),
-    default_max_deposit_fee:
-      !value.default_max_deposit_fee ? i18n`required` : (
-        !Amounts.parse(value.default_max_deposit_fee) ? i18n`invalid format` :
-          undefined
-      ),
-    default_max_wire_fee:
-      !value.default_max_wire_fee ? i18n`required` : (
-        !Amounts.parse(value.default_max_wire_fee) ? i18n`invalid format` :
-          undefined
-      ),
+      !value.payto_uris || !value.payto_uris.length
+        ? i18n`required`
+        : undefinedIfEmpty(
+            value.payto_uris.map((p) => {
+              return !PAYTO_REGEX.test(p) ? i18n`is not valid` : undefined;
+            })
+          ),
+    default_max_deposit_fee: !value.default_max_deposit_fee
+      ? i18n`required`
+      : !Amounts.parse(value.default_max_deposit_fee)
+      ? i18n`invalid format`
+      : undefined,
+    default_max_wire_fee: !value.default_max_wire_fee
+      ? i18n`required`
+      : !Amounts.parse(value.default_max_wire_fee)
+      ? i18n`invalid format`
+      : undefined,
     default_wire_fee_amortization:
-      value.default_wire_fee_amortization === undefined ? i18n`required` : (
-        isNaN(value.default_wire_fee_amortization) ? i18n`is not a number` : (
-          value.default_wire_fee_amortization < 1 ? i18n`must be 1 or greater` 
:
-            undefined
-        )
-      ),
-    default_pay_delay:
-      !value.default_pay_delay ? i18n`required` : undefined,
-    default_wire_transfer_delay:
-      !value.default_wire_transfer_delay ? i18n`required` : undefined,
+      value.default_wire_fee_amortization === undefined
+        ? i18n`required`
+        : isNaN(value.default_wire_fee_amortization)
+        ? i18n`is not a number`
+        : value.default_wire_fee_amortization < 1
+        ? i18n`must be 1 or greater`
+        : undefined,
+    default_pay_delay: !value.default_pay_delay ? i18n`required` : undefined,
+    default_wire_transfer_delay: !value.default_wire_transfer_delay
+      ? i18n`required`
+      : undefined,
     address: undefinedIfEmpty({
       address_lines:
-        value.address?.address_lines && value.address?.address_lines.length > 
7 ? i18n`max 7 lines` :
-          undefined
+        value.address?.address_lines && value.address?.address_lines.length > 7
+          ? i18n`max 7 lines`
+          : undefined,
     }),
     jurisdiction: undefinedIfEmpty({
-      address_lines: value.address?.address_lines && 
value.address?.address_lines.length > 7 ? i18n`max 7 lines` :
-        undefined
+      address_lines:
+        value.address?.address_lines && value.address?.address_lines.length > 7
+          ? i18n`max 7 lines`
+          : undefined,
     }),
   };
 
-  const hasErrors = Object.keys(errors).some(k => (errors as any)[k] !== 
undefined)
+  const hasErrors = Object.keys(errors).some(
+    (k) => (errors as any)[k] !== undefined
+  );
 
   const submit = (): Promise<void> => {
     // use conversion instead of this
     const newToken = value.auth_token;
     value.auth_token = undefined;
-    value.auth = newToken === null || newToken === undefined ? { method: 
"external" } : { method: "token", token: `secret-token:${newToken}` };
-    if (!value.address) value.address = {}
-    if (!value.jurisdiction) value.jurisdiction = {}
+    value.auth =
+      newToken === null || newToken === undefined
+        ? { method: "external" }
+        : { method: "token", token: `secret-token:${newToken}` };
+    if (!value.address) value.address = {};
+    if (!value.jurisdiction) value.jurisdiction = {};
     // remove above use conversion
     // schema.validateSync(value, { abortEarly: false })
     return onCreate(value as Entity);
-  }
+  };
 
   function updateToken(token: string | null) {
-    valueHandler(old => ({ ...old, auth_token: token === null ? undefined : 
token }))
+    valueHandler((old) => ({
+      ...old,
+      auth_token: token === null ? undefined : token,
+    }));
   }
 
-  return <div>
-    <div class="columns">
-      <div class="column" />
-      <div class="column is-four-fifths">
-        {isTokenDialogActive && <SetTokenNewInstanceModal
-          onCancel={() => {
-            updateIsTokenDialogActive(false);
-            updateIsTokenSet(false);
-          }}
-          onClear={() => {
-            updateToken(null);
-            updateIsTokenDialogActive(false);
-            updateIsTokenSet(true);
-          }}
-          onConfirm={(newToken) => {
-            updateToken(newToken); updateIsTokenDialogActive(false);
-            updateIsTokenSet(true);
-          }}
-        />}
-      </div>
-      <div class="column" />
-    </div>
-
-    <section class="hero is-hero-bar">
-      <div class="hero-body">
-        <div class="level">
-          <div class="level-item has-text-centered">
-            <h1 class="title">
-              <button class="button is-danger has-tooltip-bottom"
-                data-tooltip={i18n`change authorization configuration`}
-                onClick={() => updateIsTokenDialogActive(true)} >
-                <div class="icon is-centered"><i class="mdi mdi-lock-reset" 
/></div>
-                <span><Translate>Set access token</Translate></span>
-              </button>
-            </h1>
-          </div>
-        </div>
-      </div></section>
-
-
-    <section class="section is-main-section">
+  return (
+    <div>
       <div class="columns">
         <div class="column" />
         <div class="column is-four-fifths">
-
-          <FormProvider<Entity> errors={errors} object={value} 
valueHandler={valueHandler} >
-
-            <DefaultInstanceFormFields readonlyId={!!forceId} showId={true} />
-
-          </FormProvider>
-
-          <div class="buttons is-right mt-5">
-            {onBack && <button class="button" 
onClick={onBack}><Translate>Cancel</Translate></button>}
-            <AsyncButton onClick={submit} disabled={!isTokenSet || hasErrors} 
data-tooltip={
-              hasErrors ? i18n`Need to complete marked fields and choose 
authorization method` : 'confirm operation'
-            }><Translate>Confirm</Translate></AsyncButton>
-          </div>
-
+          {isTokenDialogActive && (
+            <SetTokenNewInstanceModal
+              onCancel={() => {
+                updateIsTokenDialogActive(false);
+                updateIsTokenSet(false);
+              }}
+              onClear={() => {
+                updateToken(null);
+                updateIsTokenDialogActive(false);
+                updateIsTokenSet(true);
+              }}
+              onConfirm={(newToken) => {
+                updateToken(newToken);
+                updateIsTokenDialogActive(false);
+                updateIsTokenSet(true);
+              }}
+            />
+          )}
         </div>
         <div class="column" />
       </div>
-    </section>
 
-  </div>
+      <section class="hero is-hero-bar">
+        <div class="hero-body">
+          <div class="level">
+            <div class="level-item has-text-centered">
+              <h1 class="title">
+                <button
+                  class="button is-danger has-tooltip-bottom"
+                  data-tooltip={i18n`change authorization configuration`}
+                  onClick={() => updateIsTokenDialogActive(true)}
+                >
+                  <div class="icon is-centered">
+                    <i class="mdi mdi-lock-reset" />
+                  </div>
+                  <span>
+                    <Translate>Set access token</Translate>
+                  </span>
+                </button>
+              </h1>
+            </div>
+          </div>
+        </div>
+      </section>
+
+      <section class="section is-main-section">
+        <div class="columns">
+          <div class="column" />
+          <div class="column is-four-fifths">
+            <FormProvider<Entity>
+              errors={errors}
+              object={value}
+              valueHandler={valueHandler}
+            >
+              <DefaultInstanceFormFields readonlyId={!!forceId} showId={true} 
/>
+            </FormProvider>
+
+            <div class="buttons is-right mt-5">
+              {onBack && (
+                <button class="button" onClick={onBack}>
+                  <Translate>Cancel</Translate>
+                </button>
+              )}
+              <AsyncButton
+                onClick={submit}
+                disabled={!isTokenSet || hasErrors}
+                data-tooltip={
+                  hasErrors
+                    ? i18n`Need to complete marked fields and choose 
authorization method`
+                    : "confirm operation"
+                }
+              >
+                <Translate>Confirm</Translate>
+              </AsyncButton>
+            </div>
+          </div>
+          <div class="column" />
+        </div>
+      </section>
+    </div>
+  );
 }
diff --git a/packages/merchant-backoffice/src/paths/admin/create/index.tsx 
b/packages/merchant-backoffice/src/paths/admin/create/index.tsx
index e240bb6..3f31b3d 100644
--- a/packages/merchant-backoffice/src/paths/admin/create/index.tsx
+++ b/packages/merchant-backoffice/src/paths/admin/create/index.tsx
@@ -14,9 +14,9 @@
  GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 /**
-*
-* @author Sebastian Javier Marchano (sebasjm)
-*/
+ *
+ * @author Sebastian Javier Marchano (sebasjm)
+ */
 import { Fragment, h, VNode } from "preact";
 import { useState } from "preact/hooks";
 import { NotificationCard } from "../../../components/menu";
@@ -36,31 +36,39 @@ export type Entity = 
MerchantBackend.Instances.InstanceConfigurationMessage;
 
 export default function Create({ onBack, onConfirm, forceId }: Props): VNode {
   const { createInstance } = useAdminAPI();
-  const [notif, setNotif] = useState<Notification | undefined>(undefined)
+  const [notif, setNotif] = useState<Notification | undefined>(undefined);
   const [createdOk, setCreatedOk] = useState<Entity | undefined>(undefined);
-  const i18n = useTranslator()
+  const i18n = useTranslator();
 
   if (createdOk) {
-    return <InstanceCreatedSuccessfully entity={createdOk} 
onConfirm={onConfirm} />
+    return (
+      <InstanceCreatedSuccessfully entity={createdOk} onConfirm={onConfirm} />
+    );
   }
 
-  return <Fragment>
-    <NotificationCard notification={notif} />
-
-    <CreatePage
-      onBack={onBack}
-      forceId={forceId}
-      onCreate={(d: MerchantBackend.Instances.InstanceConfigurationMessage) => 
{
-        return createInstance(d).then(() => {
-          setCreatedOk(d)
-        }).catch((error) => {
-          setNotif({
-            message: i18n`Failed to create instance`,
-            type: "ERROR",
-            description: error.message
-          })
-        })
-      }} />
-  </Fragment>
+  return (
+    <Fragment>
+      <NotificationCard notification={notif} />
 
+      <CreatePage
+        onBack={onBack}
+        forceId={forceId}
+        onCreate={(
+          d: MerchantBackend.Instances.InstanceConfigurationMessage
+        ) => {
+          return createInstance(d)
+            .then(() => {
+              setCreatedOk(d);
+            })
+            .catch((error) => {
+              setNotif({
+                message: i18n`Failed to create instance`,
+                type: "ERROR",
+                description: error.message,
+              });
+            });
+        }}
+      />
+    </Fragment>
+  );
 }
diff --git 
a/packages/merchant-backoffice/src/paths/instance/orders/create/CreatePage.tsx 
b/packages/merchant-backoffice/src/paths/instance/orders/create/CreatePage.tsx
index e0e9970..580ead1 100644
--- 
a/packages/merchant-backoffice/src/paths/instance/orders/create/CreatePage.tsx
+++ 
b/packages/merchant-backoffice/src/paths/instance/orders/create/CreatePage.tsx
@@ -15,15 +15,18 @@
  */
 
 /**
-*
-* @author Sebastian Javier Marchano (sebasjm)
-*/
+ *
+ * @author Sebastian Javier Marchano (sebasjm)
+ */
 
 import { add, isAfter, isBefore, isFuture } from "date-fns";
 import { Amounts } from "@gnu-taler/taler-util";
 import { Fragment, h, VNode } from "preact";
 import { useEffect, useState } from "preact/hooks";
-import { FormProvider, FormErrors } from 
"../../../../components/form/FormProvider";
+import {
+  FormProvider,
+  FormErrors,
+} from "../../../../components/form/FormProvider";
 import { Input } from "../../../../components/form/Input";
 import { InputCurrency } from "../../../../components/form/InputCurrency";
 import { InputDate } from "../../../../components/form/InputDate";
@@ -33,17 +36,18 @@ import { ProductList } from 
"../../../../components/product/ProductList";
 import { useConfigContext } from "../../../../context/config";
 import { Duration, MerchantBackend, WithId } from "../../../../declaration";
 import { Translate, useTranslator } from "../../../../i18n";
-import { OrderCreateSchema as schema } from '../../../../schemas/index';
+import { OrderCreateSchema as schema } from "../../../../schemas/index";
 import { rate } from "../../../../utils/amount";
 import { InventoryProductForm } from 
"../../../../components/product/InventoryProductForm";
 import { NonInventoryProductFrom } from 
"../../../../components/product/NonInventoryProductForm";
 import { InputNumber } from "../../../../components/form/InputNumber";
+import { InputBoolean } from "../../../../components/form/InputBoolean";
 
 interface Props {
   onCreate: (d: MerchantBackend.Orders.PostOrderRequest) => void;
   onBack?: () => void;
   instanceConfig: InstanceConfig;
-  instanceInventory: (MerchantBackend.Products.ProductDetail & WithId)[],
+  instanceInventory: (MerchantBackend.Products.ProductDetail & WithId)[];
 }
 interface InstanceConfig {
   default_max_wire_fee: string;
@@ -53,9 +57,10 @@ interface InstanceConfig {
 }
 
 function with_defaults(config: InstanceConfig): Partial<Entity> {
-  const defaultPayDeadline = !config.default_pay_delay || 
config.default_pay_delay.d_ms === "forever" ?
-    undefined :
-    add(new Date(), { seconds: config.default_pay_delay.d_ms / 1000 })
+  const defaultPayDeadline =
+    !config.default_pay_delay || config.default_pay_delay.d_ms === "forever"
+      ? undefined
+      : add(new Date(), { seconds: config.default_pay_delay.d_ms / 1000 });
 
   return {
     inventoryProducts: {},
@@ -67,14 +72,15 @@ function with_defaults(config: InstanceConfig): 
Partial<Entity> {
       wire_fee_amortization: config.default_wire_fee_amortization,
       pay_deadline: defaultPayDeadline,
       refund_deadline: defaultPayDeadline,
+      createToken: true,
     },
     shipping: {},
-    extra: ''
+    extra: "",
   };
 }
 
 interface ProductAndQuantity {
-  product: MerchantBackend.Products.ProductDetail & WithId,
+  product: MerchantBackend.Products.ProductDetail & WithId;
   quantity: number;
 }
 export interface ProductMap {
@@ -94,14 +100,16 @@ interface Shipping {
 interface Payments {
   refund_deadline?: Date;
   pay_deadline?: Date;
+  wire_transfer_deadline?: Date;
   auto_refund_deadline?: Date;
   max_fee?: string;
   max_wire_fee?: string;
   wire_fee_amortization?: number;
+  createToken: boolean;
 }
 interface Entity {
-  inventoryProducts: ProductMap,
-  products: MerchantBackend.Product[],
+  inventoryProducts: ProductMap;
+  products: MerchantBackend.Product[];
   pricing: Partial<Pricing>;
   payments: Partial<Payments>;
   shipping: Partial<Shipping>;
@@ -110,65 +118,100 @@ interface Entity {
 
 const stringIsValidJSON = (value: string) => {
   try {
-    JSON.parse(value.trim())
-    return true
+    JSON.parse(value.trim());
+    return true;
   } catch {
-    return false
+    return false;
   }
-}
+};
 
 function undefinedIfEmpty<T>(obj: T): T | undefined {
-  return Object.keys(obj).some(k => (obj as any)[k] !== undefined) ? obj : 
undefined
+  return Object.keys(obj).some((k) => (obj as any)[k] !== undefined)
+    ? obj
+    : undefined;
 }
 
-export function CreatePage({ onCreate, onBack, instanceConfig, 
instanceInventory }: Props): VNode {
-  const [value, valueHandler] = useState(with_defaults(instanceConfig))
-  const config = useConfigContext()
-  const zero = Amounts.getZero(config.currency)
+export function CreatePage({
+  onCreate,
+  onBack,
+  instanceConfig,
+  instanceInventory,
+}: Props): VNode {
+  const [value, valueHandler] = useState(with_defaults(instanceConfig));
+  const config = useConfigContext();
+  const zero = Amounts.getZero(config.currency);
 
   const inventoryList = Object.values(value.inventoryProducts || {});
   const productList = Object.values(value.products || {});
 
   const i18n = useTranslator();
-  
+
   const errors: FormErrors<Entity> = {
     pricing: undefinedIfEmpty({
       summary: !value.pricing?.summary ? i18n`required` : undefined,
-      order_price: !value.pricing?.order_price ? i18n`required` : (
-        (Amounts.parse(value.pricing.order_price)?.value || 0) <= 0 ?
-          i18n`must be greater than 0` : undefined
-      )
+      order_price: !value.pricing?.order_price
+        ? i18n`required`
+        : (Amounts.parse(value.pricing.order_price)?.value || 0) <= 0
+        ? i18n`must be greater than 0`
+        : undefined,
     }),
-    extra: value.extra && !stringIsValidJSON(value.extra) ? i18n`not a valid 
json` : undefined,
+    extra:
+      value.extra && !stringIsValidJSON(value.extra)
+        ? i18n`not a valid json`
+        : undefined,
     payments: undefinedIfEmpty({
-      refund_deadline: !value.payments?.refund_deadline ? i18n`required` : (
-        !isFuture(value.payments.refund_deadline) ? i18n`should be in the 
future` : (
-          value.payments.pay_deadline && 
isBefore(value.payments.refund_deadline, value.payments.pay_deadline) ?
-            i18n`pay deadline cannot be before refund deadline` : undefined
-        )
-      ),
-      pay_deadline: !value.payments?.pay_deadline ? i18n`required` : (
-        !isFuture(value.payments.pay_deadline) ? i18n`should be in the future` 
: undefined
-      ),
-      auto_refund_deadline: !value.payments?.auto_refund_deadline ? undefined 
: (
-        !isFuture(value.payments.auto_refund_deadline) ? i18n`should be in the 
future` : (
-          !value.payments?.refund_deadline ? i18n`should have a refund 
deadline` : (
-            !isAfter(value.payments.refund_deadline, 
value.payments.auto_refund_deadline) ?
-              i18n`auto refund cannot be after refund deadline` : undefined
+      refund_deadline: !value.payments?.refund_deadline
+        ? undefined
+        : !isFuture(value.payments.refund_deadline)
+        ? i18n`should be in the future`
+        : value.payments.pay_deadline &&
+          isBefore(value.payments.refund_deadline, value.payments.pay_deadline)
+        ? i18n`refund deadline cannot be before pay deadline`
+        : value.payments.wire_transfer_deadline &&
+          isBefore(
+            value.payments.wire_transfer_deadline,
+            value.payments.refund_deadline
           )
-        )
-      ),
+        ? i18n`wire transfer deadline cannot be before refund deadline`
+        : undefined,
+      pay_deadline: !value.payments?.pay_deadline
+        ? undefined
+        : !isFuture(value.payments.pay_deadline)
+        ? i18n`should be in the future`
+        : value.payments.wire_transfer_deadline &&
+          isBefore(
+            value.payments.wire_transfer_deadline,
+            value.payments.pay_deadline
+          )
+        ? i18n`wire transfer deadline cannot be before pay deadline`
+        : undefined,
+      auto_refund_deadline: !value.payments?.auto_refund_deadline
+        ? undefined
+        : !isFuture(value.payments.auto_refund_deadline)
+        ? i18n`should be in the future`
+        : !value.payments?.refund_deadline
+        ? i18n`should have a refund deadline`
+        : !isAfter(
+            value.payments.refund_deadline,
+            value.payments.auto_refund_deadline
+          )
+        ? i18n`auto refund cannot be after refund deadline`
+        : undefined,
     }),
     shipping: undefinedIfEmpty({
-      delivery_date: !value.shipping?.delivery_date ? undefined : (
-        !isFuture(value.shipping.delivery_date) ? i18n`should be in the 
future` : undefined
-      ),
+      delivery_date: !value.shipping?.delivery_date
+        ? undefined
+        : !isFuture(value.shipping.delivery_date)
+        ? i18n`should be in the future`
+        : undefined,
     }),
-  }
-  const hasErrors = Object.keys(errors).some(k => (errors as any)[k] !== 
undefined)
+  };
+  const hasErrors = Object.keys(errors).some(
+    (k) => (errors as any)[k] !== undefined
+  );
 
   const submit = (): void => {
-    const order = schema.cast(value)
+    const order = schema.cast(value);
     if (!value.payments) return;
     if (!value.shipping) return;
 
@@ -178,198 +221,335 @@ export function CreatePage({ onCreate, onBack, 
instanceConfig, instanceInventory
         summary: order.pricing.summary,
         products: productList,
         extra: value.extra,
-        pay_deadline: value.payments.pay_deadline ? { t_ms: 
Math.floor(value.payments.pay_deadline.getTime() / 1000) * 1000 } : undefined,
-        wire_transfer_deadline: value.payments.pay_deadline ? { t_ms: 
Math.floor(value.payments.pay_deadline.getTime() / 1000) * 1000 } : undefined,
-        refund_deadline: value.payments.refund_deadline ? { t_ms: 
Math.floor(value.payments.refund_deadline.getTime() / 1000) * 1000 } : 
undefined,
+        pay_deadline: value.payments.pay_deadline
+          ? {
+              t_ms:
+                Math.floor(value.payments.pay_deadline.getTime() / 1000) * 
1000,
+            }
+          : undefined,
+        wire_transfer_deadline: value.payments.wire_transfer_deadline
+          ? {
+              t_ms:
+                Math.floor(
+                  value.payments.wire_transfer_deadline.getTime() / 1000
+                ) * 1000,
+            }
+          : undefined,
+        refund_deadline: value.payments.refund_deadline
+          ? {
+              t_ms:
+                Math.floor(value.payments.refund_deadline.getTime() / 1000) *
+                1000,
+            }
+          : undefined,
         wire_fee_amortization: value.payments.wire_fee_amortization,
         max_fee: value.payments.max_fee,
         max_wire_fee: value.payments.max_wire_fee,
 
-        delivery_date: value.shipping.delivery_date ? { t_ms: 
value.shipping.delivery_date.getTime() } : undefined,
+        delivery_date: value.shipping.delivery_date
+          ? { t_ms: value.shipping.delivery_date.getTime() }
+          : undefined,
         delivery_location: value.shipping.delivery_location,
         fulfillment_url: value.shipping.fullfilment_url,
       },
-      inventory_products: inventoryList.map(p => ({
+      inventory_products: inventoryList.map((p) => ({
         product_id: p.product.id,
-        quantity: p.quantity
+        quantity: p.quantity,
       })),
-    }
+      create_token: value.payments.createToken,
+    };
 
     onCreate(request);
-  }
+  };
 
-  const addProductToTheInventoryList = (product: 
MerchantBackend.Products.ProductDetail & WithId, quantity: number) => {
-    valueHandler(v => {
-      const inventoryProducts = { ...v.inventoryProducts }
-      inventoryProducts[product.id] = { product, quantity }
-      return ({ ...v, inventoryProducts })
-    })
-  }
+  const addProductToTheInventoryList = (
+    product: MerchantBackend.Products.ProductDetail & WithId,
+    quantity: number
+  ) => {
+    valueHandler((v) => {
+      const inventoryProducts = { ...v.inventoryProducts };
+      inventoryProducts[product.id] = { product, quantity };
+      return { ...v, inventoryProducts };
+    });
+  };
 
   const removeProductFromTheInventoryList = (id: string) => {
-    valueHandler(v => {
-      const inventoryProducts = { ...v.inventoryProducts }
-      delete inventoryProducts[id]
-      return ({ ...v, inventoryProducts })
-    })
-  }
+    valueHandler((v) => {
+      const inventoryProducts = { ...v.inventoryProducts };
+      delete inventoryProducts[id];
+      return { ...v, inventoryProducts };
+    });
+  };
 
   const addNewProduct = async (product: MerchantBackend.Product) => {
-    return valueHandler(v => {
-      const products = v.products ? [...v.products, product] : []
-      return ({ ...v, products })
-    })
-  }
+    return valueHandler((v) => {
+      const products = v.products ? [...v.products, product] : [];
+      return { ...v, products };
+    });
+  };
 
   const removeFromNewProduct = (index: number) => {
-    valueHandler(v => {
-      const products = v.products ? [...v.products] : []
-      products.splice(index, 1)
-      return ({ ...v, products })
-    })
-  }
+    valueHandler((v) => {
+      const products = v.products ? [...v.products] : [];
+      products.splice(index, 1);
+      return { ...v, products };
+    });
+  };
 
-  const [editingProduct, setEditingProduct] = useState<MerchantBackend.Product 
| undefined>(undefined)
+  const [editingProduct, setEditingProduct] = useState<
+    MerchantBackend.Product | undefined
+  >(undefined);
 
   const totalPriceInventory = inventoryList.reduce((prev, cur) => {
-    const p = Amounts.parseOrThrow(cur.product.price)
-    return Amounts.add(prev, Amounts.mult(p, cur.quantity).amount).amount
-  }, zero)
+    const p = Amounts.parseOrThrow(cur.product.price);
+    return Amounts.add(prev, Amounts.mult(p, cur.quantity).amount).amount;
+  }, zero);
 
   const totalPriceProducts = productList.reduce((prev, cur) => {
-    if (!cur.price) return zero
-    const p = Amounts.parseOrThrow(cur.price)
-    return Amounts.add(prev, Amounts.mult(p, cur.quantity).amount).amount
-  }, zero)
+    if (!cur.price) return zero;
+    const p = Amounts.parseOrThrow(cur.price);
+    return Amounts.add(prev, Amounts.mult(p, cur.quantity).amount).amount;
+  }, zero);
 
-  const hasProducts = inventoryList.length > 0 || productList.length > 0
-  const totalPrice = Amounts.add(totalPriceInventory, totalPriceProducts)
+  const hasProducts = inventoryList.length > 0 || productList.length > 0;
+  const totalPrice = Amounts.add(totalPriceInventory, totalPriceProducts);
 
   const totalAsString = Amounts.stringify(totalPrice.amount);
-  const allProducts = productList.concat(inventoryList.map(asProduct))
+  const allProducts = productList.concat(inventoryList.map(asProduct));
 
   useEffect(() => {
-    valueHandler(v => {
-      return ({
-        ...v, pricing: {
+    valueHandler((v) => {
+      return {
+        ...v,
+        pricing: {
           ...v.pricing,
           products_price: hasProducts ? totalAsString : undefined,
           order_price: hasProducts ? totalAsString : undefined,
-        }
-      })
-    })
-  }, [hasProducts, totalAsString])
-
-  const discountOrRise = rate(value.pricing?.order_price || 
`${config.currency}:0`, totalAsString)
-
-  return <div>
-
-    <section class="section is-main-section">
-      <div class="columns">
-        <div class="column" />
-        <div class="column is-four-fifths">
-
-          {/* // FIXME: translating plural singular */}
-          <InputGroup name="inventory_products" label={i18n`Manage products in 
order`} alternative={
-            allProducts.length > 0 && <p>
-              {allProducts.length} products
-              with a total price of {totalAsString}.
-            </p>
-          } tooltip={i18n`Manage list of products in the order.`}>
-
-            <InventoryProductForm
-              currentProducts={value.inventoryProducts || {}}
-              onAddProduct={addProductToTheInventoryList}
-              inventory={instanceInventory}
-            />
-
-            <NonInventoryProductFrom productToEdit={editingProduct} 
onAddProduct={(p) => {
-              setEditingProduct(undefined)
-              return addNewProduct(p)
-            }} />
-
-            {allProducts.length > 0 &&
-              <ProductList list={allProducts}
-                actions={[{
-                  name: i18n`Remove`,
-                  tooltip: i18n`Remove this product from the order.`,
-                  handler: (e, index) => {
-                    if (e.product_id) {
-                      removeProductFromTheInventoryList(e.product_id)
-                    } else {
-                      removeFromNewProduct(index);
-                      setEditingProduct(e);
-                    }
-                  }
-                }]}
+        },
+      };
+    });
+  }, [hasProducts, totalAsString]);
+
+  const discountOrRise = rate(
+    value.pricing?.order_price || `${config.currency}:0`,
+    totalAsString
+  );
+
+  return (
+    <div>
+      <section class="section is-main-section">
+        <div class="columns">
+          <div class="column" />
+          <div class="column is-four-fifths">
+            {/* // FIXME: translating plural singular */}
+            <InputGroup
+              name="inventory_products"
+              label={i18n`Manage products in order`}
+              alternative={
+                allProducts.length > 0 && (
+                  <p>
+                    {allProducts.length} products with a total price of{" "}
+                    {totalAsString}.
+                  </p>
+                )
+              }
+              tooltip={i18n`Manage list of products in the order.`}
+            >
+              <InventoryProductForm
+                currentProducts={value.inventoryProducts || {}}
+                onAddProduct={addProductToTheInventoryList}
+                inventory={instanceInventory}
               />
-            }
-          </InputGroup>
-
-          <FormProvider<Entity> errors={errors} object={value} 
valueHandler={valueHandler as any}>
-            {hasProducts ?
-              <Fragment>
-                <InputCurrency name="pricing.products_price" label={i18n`Total 
price`} readonly tooltip={i18n`total product price added up`} />
-                <InputCurrency name="pricing.order_price"
-                  label={i18n`Total price`}
-                  addonAfter={discountOrRise > 0 && (discountOrRise < 1 ?
-                    `discount of %${Math.round((1 - discountOrRise) * 100)}` :
-                    `rise of %${Math.round((discountOrRise - 1) * 100)}`)
-                  }
-                  tooltip={i18n`Amount to be paid by the customer`}
-                />
-              </Fragment> :
-              <InputCurrency name="pricing.order_price" label={i18n`Order 
price`} tooltip={i18n`final order price`} />
-            }
 
-            <Input name="pricing.summary" inputType="multiline" 
label={i18n`Summary`} tooltip={i18n`Title of the order to be shown to the 
customer`} />
+              <NonInventoryProductFrom
+                productToEdit={editingProduct}
+                onAddProduct={(p) => {
+                  setEditingProduct(undefined);
+                  return addNewProduct(p);
+                }}
+              />
 
-            <InputGroup name="shipping" label={i18n`Shipping and Fulfillment`} 
initialActive >
-              <InputDate name="shipping.delivery_date" label={i18n`Delivery 
date`} tooltip={i18n`Deadline for physical delivery assured by the merchant.`} 
/>
-              {value.shipping?.delivery_date &&
-                <InputGroup name="shipping.delivery_location" 
label={i18n`Location`} tooltip={i18n`address where the products will be 
delivered`} >
-                  <InputLocation name="shipping.delivery_location" />
-                </InputGroup>
-              }
-              <Input name="shipping.fullfilment_url" label={i18n`Fulfillment 
URL`} tooltip={i18n`URL to which the user will be redirected after successful 
payment.`} />
+              {allProducts.length > 0 && (
+                <ProductList
+                  list={allProducts}
+                  actions={[
+                    {
+                      name: i18n`Remove`,
+                      tooltip: i18n`Remove this product from the order.`,
+                      handler: (e, index) => {
+                        if (e.product_id) {
+                          removeProductFromTheInventoryList(e.product_id);
+                        } else {
+                          removeFromNewProduct(index);
+                          setEditingProduct(e);
+                        }
+                      },
+                    },
+                  ]}
+                />
+              )}
             </InputGroup>
 
-            <InputGroup name="payments" label={i18n`Taler payment options`} 
tooltip={i18n`Override default Taler payment settings for this order`}>
-              <InputDate name="payments.pay_deadline" label={i18n`Payment 
deadline`} tooltip={i18n`Deadline for the customer to pay for the offer before 
it expires. Inventory products will be reserved until this deadline.`} />
-              <InputDate name="payments.refund_deadline" label={i18n`Refund 
deadline`} tooltip={i18n`Time until which the order can be refunded by the 
merchant.`} />
-              <InputDate name="payments.auto_refund_deadline" 
label={i18n`Auto-refund deadline`} tooltip={i18n`Time until which the wallet 
will automatically check for refunds without user interaction.`} />
+            <FormProvider<Entity>
+              errors={errors}
+              object={value}
+              valueHandler={valueHandler as any}
+            >
+              {hasProducts ? (
+                <Fragment>
+                  <InputCurrency
+                    name="pricing.products_price"
+                    label={i18n`Total price`}
+                    readonly
+                    tooltip={i18n`total product price added up`}
+                  />
+                  <InputCurrency
+                    name="pricing.order_price"
+                    label={i18n`Total price`}
+                    addonAfter={
+                      discountOrRise > 0 &&
+                      (discountOrRise < 1
+                        ? `discount of %${Math.round(
+                            (1 - discountOrRise) * 100
+                          )}`
+                        : `rise of %${Math.round((discountOrRise - 1) * 100)}`)
+                    }
+                    tooltip={i18n`Amount to be paid by the customer`}
+                  />
+                </Fragment>
+              ) : (
+                <InputCurrency
+                  name="pricing.order_price"
+                  label={i18n`Order price`}
+                  tooltip={i18n`final order price`}
+                />
+              )}
 
-              <InputCurrency name="payments.max_fee" label={i18n`Maximum 
deposit fee`} tooltip={i18n`Maximum deposit fees the merchant is willing to 
cover for this order. Higher deposit fees must be covered in full by the 
consumer.`} />
-              <InputCurrency name="payments.max_wire_fee" label={i18n`Maximum 
wire fee`} tooltip={i18n`Maximum aggregate wire fees the merchant is willing to 
cover for this order. Wire fees exceeding this amount are to be covered by the 
customers.`} />
-              <InputNumber name="payments.wire_fee_amortization" 
label={i18n`Wire fee amortization`} tooltip={i18n`Factor by which wire fees 
exceeding the above threshold are divided to determine the share of excess wire 
fees to be paid explicitly by the consumer.`} />
-            </InputGroup>
+              <Input
+                name="pricing.summary"
+                inputType="multiline"
+                label={i18n`Summary`}
+                tooltip={i18n`Title of the order to be shown to the customer`}
+              />
 
-            <InputGroup name="extra" label={i18n`Additional information`} 
tooltip={i18n`Custom information to be included in the contract for this 
order.`}>
-              <Input name="extra" inputType="multiline" label={`Value`} 
tooltip={i18n`You must enter a value in JavaScript Object Notation (JSON).`} />
-            </InputGroup>
-          </FormProvider>
+              <InputGroup
+                name="shipping"
+                label={i18n`Shipping and Fulfillment`}
+                initialActive
+              >
+                <InputDate
+                  name="shipping.delivery_date"
+                  label={i18n`Delivery date`}
+                  tooltip={i18n`Deadline for physical delivery assured by the 
merchant.`}
+                />
+                {value.shipping?.delivery_date && (
+                  <InputGroup
+                    name="shipping.delivery_location"
+                    label={i18n`Location`}
+                    tooltip={i18n`address where the products will be 
delivered`}
+                  >
+                    <InputLocation name="shipping.delivery_location" />
+                  </InputGroup>
+                )}
+                <Input
+                  name="shipping.fullfilment_url"
+                  label={i18n`Fulfillment URL`}
+                  tooltip={i18n`URL to which the user will be redirected after 
successful payment.`}
+                />
+              </InputGroup>
+
+              <InputGroup
+                name="payments"
+                label={i18n`Taler payment options`}
+                tooltip={i18n`Override default Taler payment settings for this 
order`}
+              >
+                <InputDate
+                  name="payments.pay_deadline"
+                  label={i18n`Payment deadline`}
+                  tooltip={i18n`Deadline for the customer to pay for the offer 
before it expires. Inventory products will be reserved until this deadline.`}
+                />
+                <InputDate
+                  name="payments.refund_deadline"
+                  label={i18n`Refund deadline`}
+                  tooltip={i18n`Time until which the order can be refunded by 
the merchant.`}
+                />
+                <InputDate
+                  name="payments.wire_transfer_deadline"
+                  label={i18n`Wire transfer deadline`}
+                  tooltip={i18n`Deadline for the exchange to make the wire 
transfer.`}
+                />
+                <InputDate
+                  name="payments.auto_refund_deadline"
+                  label={i18n`Auto-refund deadline`}
+                  tooltip={i18n`Time until which the wallet will automatically 
check for refunds without user interaction.`}
+                />
 
-          <div class="buttons is-right mt-5">
-            {onBack && <button class="button" onClick={onBack} 
><Translate>Cancel</Translate></button>}
-            <button class="button is-success" onClick={submit} 
disabled={hasErrors} ><Translate>Confirm</Translate></button>
+                <InputCurrency
+                  name="payments.max_fee"
+                  label={i18n`Maximum deposit fee`}
+                  tooltip={i18n`Maximum deposit fees the merchant is willing 
to cover for this order. Higher deposit fees must be covered in full by the 
consumer.`}
+                />
+                <InputCurrency
+                  name="payments.max_wire_fee"
+                  label={i18n`Maximum wire fee`}
+                  tooltip={i18n`Maximum aggregate wire fees the merchant is 
willing to cover for this order. Wire fees exceeding this amount are to be 
covered by the customers.`}
+                />
+                <InputNumber
+                  name="payments.wire_fee_amortization"
+                  label={i18n`Wire fee amortization`}
+                  tooltip={i18n`Factor by which wire fees exceeding the above 
threshold are divided to determine the share of excess wire fees to be paid 
explicitly by the consumer.`}
+                />
+                <InputBoolean
+                  name="payments.createToken"
+                  label={i18n`Create token`}
+                  tooltip={i18n`Uncheck this option if the merchant backend 
generated an order ID with enough entropy to prevent adversarial claims.`}
+                />
+              </InputGroup>
+
+              <InputGroup
+                name="extra"
+                label={i18n`Additional information`}
+                tooltip={i18n`Custom information to be included in the 
contract for this order.`}
+              >
+                <Input
+                  name="extra"
+                  inputType="multiline"
+                  label={`Value`}
+                  tooltip={i18n`You must enter a value in JavaScript Object 
Notation (JSON).`}
+                />
+              </InputGroup>
+            </FormProvider>
+
+            <div class="buttons is-right mt-5">
+              {onBack && (
+                <button class="button" onClick={onBack}>
+                  <Translate>Cancel</Translate>
+                </button>
+              )}
+              <button
+                class="button is-success"
+                onClick={submit}
+                disabled={hasErrors}
+              >
+                <Translate>Confirm</Translate>
+              </button>
+            </div>
           </div>
-
+          <div class="column" />
         </div>
-        <div class="column" />
-      </div>
-    </section>
-
-  </div>
+      </section>
+    </div>
+  );
 }
 
 function asProduct(p: ProductAndQuantity) {
-  return ({
+  return {
     product_id: p.product.id,
     image: p.product.image,
     price: p.product.price,
     unit: p.product.unit,
     quantity: p.quantity,
     description: p.product.description,
-    taxes: p.product.taxes
-  })
+    taxes: p.product.taxes,
+  };
 }
diff --git 
a/packages/merchant-backoffice/src/paths/instance/update/UpdatePage.tsx 
b/packages/merchant-backoffice/src/paths/instance/update/UpdatePage.tsx
index 0fa96ed..741edc6 100644
--- a/packages/merchant-backoffice/src/paths/instance/update/UpdatePage.tsx
+++ b/packages/merchant-backoffice/src/paths/instance/update/UpdatePage.tsx
@@ -15,191 +15,245 @@
  */
 
 /**
-*
-* @author Sebastian Javier Marchano (sebasjm)
-*/
+ *
+ * @author Sebastian Javier Marchano (sebasjm)
+ */
 
 import { h, VNode } from "preact";
 import { useState } from "preact/hooks";
-import * as yup from 'yup';
+import * as yup from "yup";
 import { AsyncButton } from "../../../components/exception/AsyncButton";
-import { FormProvider, FormErrors } from 
"../../../components/form/FormProvider";
+import {
+  FormProvider,
+  FormErrors,
+} from "../../../components/form/FormProvider";
 import { UpdateTokenModal } from "../../../components/modal";
 import { useInstanceContext } from "../../../context/instance";
 import { MerchantBackend } from "../../../declaration";
 import { Translate, useTranslator } from "../../../i18n";
-import { InstanceUpdateSchema as schema } from '../../../schemas';
+import { InstanceUpdateSchema as schema } from "../../../schemas";
 import { DefaultInstanceFormFields } from 
"../../../components/instance/DefaultInstanceFormFields";
 import { PAYTO_REGEX } from "../../../utils/constants";
 import { Amounts } from "@gnu-taler/taler-util";
 
-
-type Entity = MerchantBackend.Instances.InstanceReconfigurationMessage & { 
-  auth_token?: string 
-}
+type Entity = MerchantBackend.Instances.InstanceReconfigurationMessage & {
+  auth_token?: string;
+};
 
 //MerchantBackend.Instances.InstanceAuthConfigurationMessage
 interface Props {
   onUpdate: (d: Entity) => void;
-  onChangeAuth: (d: 
MerchantBackend.Instances.InstanceAuthConfigurationMessage) => Promise<void>;
+  onChangeAuth: (
+    d: MerchantBackend.Instances.InstanceAuthConfigurationMessage
+  ) => Promise<void>;
   selected: MerchantBackend.Instances.QueryInstancesResponse;
   isLoading: boolean;
   onBack: () => void;
 }
 
-function convert(from: MerchantBackend.Instances.QueryInstancesResponse): 
Entity {
-  const { accounts, ...rest } = from
-  const payto_uris = accounts.filter(a => a.active).map(a => a.payto_uri)
+function convert(
+  from: MerchantBackend.Instances.QueryInstancesResponse
+): Entity {
+  const { accounts, ...rest } = from;
+  const payto_uris = accounts.filter((a) => a.active).map((a) => a.payto_uri);
   const defaults = {
     default_wire_fee_amortization: 1,
     default_pay_delay: { d_ms: 1000 * 60 * 60 }, //one hour
     default_wire_transfer_delay: { d_ms: 1000 * 60 * 60 * 2 }, //two hours
-  }
+  };
   return { ...defaults, ...rest, payto_uris };
 }
 
 function getTokenValuePart(t?: string): string | undefined {
-  if (!t) return t
+  if (!t) return t;
   const match = /secret-token:(.*)/.exec(t);
   if (!match || !match[1]) return undefined;
-  return match[1]
+  return match[1];
 }
 
 function undefinedIfEmpty<T>(obj: T): T | undefined {
-  return Object.keys(obj).some(k => (obj as any)[k] !== undefined) ? obj : 
undefined
+  return Object.keys(obj).some((k) => (obj as any)[k] !== undefined)
+    ? obj
+    : undefined;
 }
 
-export function UpdatePage({ onUpdate, onChangeAuth, selected, onBack }: 
Props): VNode {
-  const { id, token } = useInstanceContext()
-  const currentTokenValue = getTokenValuePart(token)
+export function UpdatePage({
+  onUpdate,
+  onChangeAuth,
+  selected,
+  onBack,
+}: Props): VNode {
+  const { id, token } = useInstanceContext();
+  const currentTokenValue = getTokenValuePart(token);
 
   function updateToken(token: string | undefined | null) {
-    const value = token && token.startsWith('secret-token:') ?
-      token.substring('secret-token:'.length) : token
+    const value =
+      token && token.startsWith("secret-token:")
+        ? token.substring("secret-token:".length)
+        : token;
 
     if (!token) {
-      onChangeAuth({ method: 'external' })
+      onChangeAuth({ method: "external" });
     } else {
-      onChangeAuth({ method: 'token', token: `secret-token:${value}` })
+      onChangeAuth({ method: "token", token: `secret-token:${value}` });
     }
   }
 
-  const [value, valueHandler] = useState<Partial<Entity>>(convert(selected))
+  const [value, valueHandler] = useState<Partial<Entity>>(convert(selected));
 
-  const i18n = useTranslator()
+  const i18n = useTranslator();
 
   const errors: FormErrors<Entity> = {
     name: !value.name ? i18n`required` : undefined,
     payto_uris:
-      !value.payto_uris || !value.payto_uris.length ? i18n`required` : (
-        undefinedIfEmpty(value.payto_uris.map(p => {
-          return !PAYTO_REGEX.test(p) ? i18n`is not valid` : undefined
-        }))
-      ),
-    default_max_deposit_fee:
-      !value.default_max_deposit_fee ? i18n`required` : (
-        !Amounts.parse(value.default_max_deposit_fee) ? i18n`invalid format` :
-          undefined
-      ),
-    default_max_wire_fee:
-      !value.default_max_wire_fee ? i18n`required` : (
-        !Amounts.parse(value.default_max_wire_fee) ? i18n`invalid format` :
-          undefined
-      ),
+      !value.payto_uris || !value.payto_uris.length
+        ? i18n`required`
+        : undefinedIfEmpty(
+            value.payto_uris.map((p) => {
+              return !PAYTO_REGEX.test(p) ? i18n`is not valid` : undefined;
+            })
+          ),
+    default_max_deposit_fee: !value.default_max_deposit_fee
+      ? i18n`required`
+      : !Amounts.parse(value.default_max_deposit_fee)
+      ? i18n`invalid format`
+      : undefined,
+    default_max_wire_fee: !value.default_max_wire_fee
+      ? i18n`required`
+      : !Amounts.parse(value.default_max_wire_fee)
+      ? i18n`invalid format`
+      : undefined,
     default_wire_fee_amortization:
-      value.default_wire_fee_amortization === undefined ? i18n`required` : (
-        isNaN(value.default_wire_fee_amortization) ? i18n`is not a number` : (
-          value.default_wire_fee_amortization < 1 ? i18n`must be 1 or greater` 
:
-            undefined
-        )
-      ),
-    default_pay_delay:
-      !value.default_pay_delay ? i18n`required` : undefined,
-    default_wire_transfer_delay:
-      !value.default_wire_transfer_delay ? i18n`required` : undefined,
+      value.default_wire_fee_amortization === undefined
+        ? i18n`required`
+        : isNaN(value.default_wire_fee_amortization)
+        ? i18n`is not a number`
+        : value.default_wire_fee_amortization < 1
+        ? i18n`must be 1 or greater`
+        : undefined,
+    default_pay_delay: !value.default_pay_delay ? i18n`required` : undefined,
+    default_wire_transfer_delay: !value.default_wire_transfer_delay
+      ? i18n`required`
+      : undefined,
     address: undefinedIfEmpty({
       address_lines:
-        value.address?.address_lines && value.address?.address_lines.length > 
7 ? i18n`max 7 lines` :
-          undefined
+        value.address?.address_lines && value.address?.address_lines.length > 7
+          ? i18n`max 7 lines`
+          : undefined,
     }),
     jurisdiction: undefinedIfEmpty({
-      address_lines: value.address?.address_lines && 
value.address?.address_lines.length > 7 ? i18n`max 7 lines` :
-        undefined
+      address_lines:
+        value.address?.address_lines && value.address?.address_lines.length > 7
+          ? i18n`max 7 lines`
+          : undefined,
     }),
   };
 
-  const hasErrors = Object.keys(errors).some(k => (errors as any)[k] !== 
undefined)
+  const hasErrors = Object.keys(errors).some(
+    (k) => (errors as any)[k] !== undefined
+  );
   const submit = async (): Promise<void> => {
-    await onUpdate(schema.cast(value));
-    await onBack()
-    return Promise.resolve()
-  }
+    await onUpdate(value as Entity);
+  };
   const [active, setActive] = useState(false);
 
-  return <div>
-    <section class="section">
-
-      <section class="hero is-hero-bar">
-        <div class="hero-body">
-
-          <div class="level">
-            <div class="level-left">
-              <div class="level-item">
-                <span class="is-size-4"><Translate>Instance id</Translate>: 
<b>{id}</b></span>
+  return (
+    <div>
+      <section class="section">
+        <section class="hero is-hero-bar">
+          <div class="hero-body">
+            <div class="level">
+              <div class="level-left">
+                <div class="level-item">
+                  <span class="is-size-4">
+                    <Translate>Instance id</Translate>: <b>{id}</b>
+                  </span>
+                </div>
               </div>
-            </div>
-            <div class="level-right">
-              <div class="level-item">
-                <h1 class="title">
-                  <button class="button is-danger" 
-                    data-tooltip={i18n`Change the authorization method use for 
this instance.`}
-                    onClick={(): void => { setActive(!active); }} >
-                    <div class="icon is-left"><i class="mdi mdi-lock-reset" 
/></div>
-                    <span><Translate>Manage access token</Translate></span>
-                  </button>
-                </h1>
+              <div class="level-right">
+                <div class="level-item">
+                  <h1 class="title">
+                    <button
+                      class="button is-danger"
+                      data-tooltip={i18n`Change the authorization method use 
for this instance.`}
+                      onClick={(): void => {
+                        setActive(!active);
+                      }}
+                    >
+                      <div class="icon is-left">
+                        <i class="mdi mdi-lock-reset" />
+                      </div>
+                      <span>
+                        <Translate>Manage access token</Translate>
+                      </span>
+                    </button>
+                  </h1>
+                </div>
               </div>
             </div>
           </div>
-        </div></section>
-
-      <div class="columns">
-        <div class="column" />
-        <div class="column is-four-fifths">
-          {active && <UpdateTokenModal oldToken={currentTokenValue}
-            onCancel={() => { setActive(false); }}
-            onClear={() => { updateToken(null); setActive(false); }}
-            onConfirm={(newToken) => {
-              updateToken(newToken); setActive(false)
-            }}
-          />}
-        </div>
-        <div class="column" />
-      </div>
-      <hr />
-
-      <div class="columns">
-        <div class="column" />
-        <div class="column is-four-fifths">
-          <FormProvider<Entity> errors={errors} object={value} 
valueHandler={valueHandler} >
-
-            <DefaultInstanceFormFields showId={false} />
-
-          </FormProvider>
+        </section>
 
-          <div class="buttons is-right mt-4">
-            <button class="button" onClick={onBack} data-tooltip="cancel 
operation"><Translate>Cancel</Translate></button>
-
-            <AsyncButton onClick={submit} data-tooltip={
-              hasErrors ? i18n`Need to complete marked fields` : 'confirm 
operation'
-            } disabled={hasErrors} 
><Translate>Confirm</Translate></AsyncButton>
+        <div class="columns">
+          <div class="column" />
+          <div class="column is-four-fifths">
+            {active && (
+              <UpdateTokenModal
+                oldToken={currentTokenValue}
+                onCancel={() => {
+                  setActive(false);
+                }}
+                onClear={() => {
+                  updateToken(null);
+                  setActive(false);
+                }}
+                onConfirm={(newToken) => {
+                  updateToken(newToken);
+                  setActive(false);
+                }}
+              />
+            )}
           </div>
+          <div class="column" />
         </div>
-        <div class="column" />
-      </div>
+        <hr />
 
-    </section>
+        <div class="columns">
+          <div class="column" />
+          <div class="column is-four-fifths">
+            <FormProvider<Entity>
+              errors={errors}
+              object={value}
+              valueHandler={valueHandler}
+            >
+              <DefaultInstanceFormFields showId={false} />
+            </FormProvider>
 
-  </div >
+            <div class="buttons is-right mt-4">
+              <button
+                class="button"
+                onClick={onBack}
+                data-tooltip="cancel operation"
+              >
+                <Translate>Cancel</Translate>
+              </button>
 
+              <AsyncButton
+                onClick={submit}
+                data-tooltip={
+                  hasErrors
+                    ? i18n`Need to complete marked fields`
+                    : "confirm operation"
+                }
+                disabled={hasErrors}
+              >
+                <Translate>Confirm</Translate>
+              </AsyncButton>
+            </div>
+          </div>
+          <div class="column" />
+        </div>
+      </section>
+    </div>
+  );
 }
diff --git a/packages/merchant-backoffice/src/paths/instance/update/index.tsx 
b/packages/merchant-backoffice/src/paths/instance/update/index.tsx
index 33dc476..bd5f4c7 100644
--- a/packages/merchant-backoffice/src/paths/instance/update/index.tsx
+++ b/packages/merchant-backoffice/src/paths/instance/update/index.tsx
@@ -14,11 +14,20 @@
  GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 import { Fragment, h, VNode } from "preact";
+import { useState } from "preact/hooks";
 import { Loading } from "../../../components/exception/loading";
+import { NotificationCard } from "../../../components/menu";
 import { useInstanceContext } from "../../../context/instance";
 import { MerchantBackend } from "../../../declaration";
 import { HttpError, HttpResponse } from "../../../hooks/backend";
-import { useInstanceAPI, useInstanceDetails, useManagedInstanceDetails, 
useManagementAPI } from "../../../hooks/instance";
+import {
+  useInstanceAPI,
+  useInstanceDetails,
+  useManagedInstanceDetails,
+  useManagementAPI,
+} from "../../../hooks/instance";
+import { useTranslator } from "../../../i18n";
+import { Notification } from "../../../utils/types";
 import { UpdatePage } from "./UpdatePage";
 
 export interface Props {
@@ -29,41 +38,76 @@ export interface Props {
   onNotFound: () => VNode;
   onLoadError: (e: HttpError) => VNode;
   onUpdateError: (e: HttpError) => void;
-
 }
 
 export default function Update(props: Props): VNode {
   const { updateInstance, clearToken, setNewToken } = useInstanceAPI();
-  const result = useInstanceDetails()
-  return CommonUpdate(props, result, updateInstance, clearToken, setNewToken)
+  const result = useInstanceDetails();
+  return CommonUpdate(props, result, updateInstance, clearToken, setNewToken);
 }
 
-export function AdminUpdate(props:Props & {instanceId:string}): VNode {
-  const { updateInstance, clearToken, setNewToken } = 
useManagementAPI(props.instanceId);
-  const result = useManagedInstanceDetails(props.instanceId)
-  return CommonUpdate(props, result, updateInstance, clearToken, setNewToken)
+export function AdminUpdate(props: Props & { instanceId: string }): VNode {
+  const { updateInstance, clearToken, setNewToken } = useManagementAPI(
+    props.instanceId
+  );
+  const result = useManagedInstanceDetails(props.instanceId);
+  return CommonUpdate(props, result, updateInstance, clearToken, setNewToken);
 }
 
-function CommonUpdate({ onBack, onConfirm, onLoadError, onNotFound, 
onUpdateError, onUnauthorized }: Props, result: 
HttpResponse<MerchantBackend.Instances.QueryInstancesResponse>, updateInstance: 
any, clearToken: any, setNewToken: any): VNode {
-  const { changeToken } = useInstanceContext()
+function CommonUpdate(
+  {
+    onBack,
+    onConfirm,
+    onLoadError,
+    onNotFound,
+    onUpdateError,
+    onUnauthorized,
+  }: Props,
+  result: HttpResponse<MerchantBackend.Instances.QueryInstancesResponse>,
+  updateInstance: any,
+  clearToken: any,
+  setNewToken: any
+): VNode {
+  const { changeToken } = useInstanceContext();
+  const [notif, setNotif] = useState<Notification | undefined>(undefined);
+  const i18n = useTranslator();
 
-  if (result.clientError && result.isUnauthorized) return onUnauthorized()
-  if (result.clientError && result.isNotfound) return onNotFound()
-  if (result.loading) return <Loading />
-  if (!result.ok) return onLoadError(result)
+  if (result.clientError && result.isUnauthorized) return onUnauthorized();
+  if (result.clientError && result.isNotfound) return onNotFound();
+  if (result.loading) return <Loading />;
+  if (!result.ok) return onLoadError(result);
 
-  return <Fragment>
-    <UpdatePage
-      onBack={onBack}
-      isLoading={false}
-      selected={result.data}
-      onUpdate={(d: MerchantBackend.Instances.InstanceReconfigurationMessage): 
Promise<void> => {
-        return updateInstance(d).then(onConfirm).catch(onUpdateError)
-      }} 
-      onChangeAuth={(d: 
MerchantBackend.Instances.InstanceAuthConfigurationMessage): Promise<void> => {
-        const apiCall = d.method === 'external' ? clearToken() : 
setNewToken(d.token!);
-        return apiCall.then(() => 
changeToken(d.token)).then(onConfirm).catch(onUpdateError)
-      }} 
+  return (
+    <Fragment>
+      <NotificationCard notification={notif} />
+      <UpdatePage
+        onBack={onBack}
+        isLoading={false}
+        selected={result.data}
+        onUpdate={(
+          d: MerchantBackend.Instances.InstanceReconfigurationMessage
+        ): Promise<void> => {
+          return updateInstance(d)
+            .then(onConfirm)
+            .catch((error: Error) =>
+              setNotif({
+                message: i18n`Failed to create instance`,
+                type: "ERROR",
+                description: error.message,
+              })
+            );
+        }}
+        onChangeAuth={(
+          d: MerchantBackend.Instances.InstanceAuthConfigurationMessage
+        ): Promise<void> => {
+          const apiCall =
+            d.method === "external" ? clearToken() : setNewToken(d.token!);
+          return apiCall
+            .then(() => changeToken(d.token))
+            .then(onConfirm)
+            .catch(onUpdateError);
+        }}
       />
-  </Fragment>
-}
\ No newline at end of file
+    </Fragment>
+  );
+}
diff --git a/packages/merchant-backoffice/src/schemas/index.ts 
b/packages/merchant-backoffice/src/schemas/index.ts
index 1c58016..9d64a67 100644
--- a/packages/merchant-backoffice/src/schemas/index.ts
+++ b/packages/merchant-backoffice/src/schemas/index.ts
@@ -98,10 +98,10 @@ export const InstanceSchema = yup.object().shape({
     district: yup.string().optional(),
     country_subdivision: yup.string().optional(),
   }).meta({ type: 'group' }),
-  default_pay_delay: yup.object()
-    .shape({ d_ms: yup.number() })
-    .required()
-    .meta({ type: 'duration' }),
+  // default_pay_delay: yup.object()
+  //   .shape({ d_ms: yup.number() })
+  //   .required()
+  //   .meta({ type: 'duration' }),
   // .transform(numberToDuration),
   default_wire_transfer_delay: yup.object()
     .shape({ d_ms: yup.number() })
diff --git a/packages/merchant-backoffice/src/utils/constants.ts 
b/packages/merchant-backoffice/src/utils/constants.ts
index 6f76a71..5356a1a 100644
--- a/packages/merchant-backoffice/src/utils/constants.ts
+++ b/packages/merchant-backoffice/src/utils/constants.ts
@@ -45,3 +45,150 @@ export const DEFAULT_REQUEST_TIMEOUT = 10;
 export const MAX_IMAGE_SIZE = 1024 * 1024;
 
 export const INSTANCE_ID_REGEX = /^[a-zA-Z0-9][a-zA-Z0-9_.@-]+$/
+
+export const COUNTRY_TABLE = {
+  AE: "U.A.E.",
+  AF: "Afghanistan",
+  AL: "Albania",
+  AM: "Armenia",
+  AN: "Netherlands Antilles",
+  AR: "Argentina",
+  AT: "Austria",
+  AU: "Australia",
+  AZ: "Azerbaijan",
+  BA: "Bosnia and Herzegovina",
+  BD: "Bangladesh",
+  BE: "Belgium",
+  BG: "Bulgaria",
+  BH: "Bahrain",
+  BN: "Brunei Darussalam",
+  BO: "Bolivia",
+  BR: "Brazil",
+  BT: "Bhutan",
+  BY: "Belarus",
+  BZ: "Belize",
+  CA: "Canada",
+  CG: "Congo",
+  CH: "Switzerland",
+  CI: "Cote d'Ivoire",
+  CL: "Chile",
+  CM: "Cameroon",
+  CN: "People's Republic of China",
+  CO: "Colombia",
+  CR: "Costa Rica",
+  CS: "Serbia and Montenegro",
+  CZ: "Czech Republic",
+  DE: "Germany",
+  DK: "Denmark",
+  DO: "Dominican Republic",
+  DZ: "Algeria",
+  EC: "Ecuador",
+  EE: "Estonia",
+  EG: "Egypt",
+  ER: "Eritrea",
+  ES: "Spain",
+  ET: "Ethiopia",
+  FI: "Finland",
+  FO: "Faroe Islands",
+  FR: "France",
+  GB: "United Kingdom",
+  GD: "Caribbean",
+  GE: "Georgia",
+  GL: "Greenland",
+  GR: "Greece",
+  GT: "Guatemala",
+  HK: "Hong Kong",
+  // HK: "Hong Kong S.A.R.",
+  HN: "Honduras",
+  HR: "Croatia",
+  HT: "Haiti",
+  HU: "Hungary",
+  ID: "Indonesia",
+  IE: "Ireland",
+  IL: "Israel",
+  IN: "India",
+  IQ: "Iraq",
+  IR: "Iran",
+  IS: "Iceland",
+  IT: "Italy",
+  JM: "Jamaica",
+  JO: "Jordan",
+  JP: "Japan",
+  KE: "Kenya",
+  KG: "Kyrgyzstan",
+  KH: "Cambodia",
+  KR: "South Korea",
+  KW: "Kuwait",
+  KZ: "Kazakhstan",
+  LA: "Laos",
+  LB: "Lebanon",
+  LI: "Liechtenstein",
+  LK: "Sri Lanka",
+  LT: "Lithuania",
+  LU: "Luxembourg",
+  LV: "Latvia",
+  LY: "Libya",
+  MA: "Morocco",
+  MC: "Principality of Monaco",
+  MD: "Moldava",
+  // MD: "Moldova",
+  ME: "Montenegro",
+  MK: "Former Yugoslav Republic of Macedonia",
+  ML: "Mali",
+  MM: "Myanmar",
+  MN: "Mongolia",
+  MO: "Macau S.A.R.",
+  MT: "Malta",
+  MV: "Maldives",
+  MX: "Mexico",
+  MY: "Malaysia",
+  NG: "Nigeria",
+  NI: "Nicaragua",
+  NL: "Netherlands",
+  NO: "Norway",
+  NP: "Nepal",
+  NZ: "New Zealand",
+  OM: "Oman",
+  PA: "Panama",
+  PE: "Peru",
+  PH: "Philippines",
+  PK: "Islamic Republic of Pakistan",
+  PL: "Poland",
+  PR: "Puerto Rico",
+  PT: "Portugal",
+  PY: "Paraguay",
+  QA: "Qatar",
+  RE: "Reunion",
+  RO: "Romania",
+  RS: "Serbia",
+  RU: "Russia",
+  RW: "Rwanda",
+  SA: "Saudi Arabia",
+  SE: "Sweden",
+  SG: "Singapore",
+  SI: "Slovenia",
+  SK: "Slovak",
+  SN: "Senegal",
+  SO: "Somalia",
+  SR: "Suriname",
+  SV: "El Salvador",
+  SY: "Syria",
+  TH: "Thailand",
+  TJ: "Tajikistan",
+  TM: "Turkmenistan",
+  TN: "Tunisia",
+  TR: "Turkey",
+  TT: "Trinidad and Tobago",
+  TW: "Taiwan",
+  TZ: "Tanzania",
+  UA: "Ukraine",
+  US: "United States",
+  UY: "Uruguay",
+  VA: "Vatican",
+  VE: "Venezuela",
+  VN: "Viet Nam",
+  YE: "Yemen",
+  ZA: "South Africa",
+  ZW: "Zimbabwe"
+}
+

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