gnunet-svn
[Top][All Lists]
Advanced

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

[taler-merchant-backoffice] 02/02: fixing issue taken from christian com


From: gnunet
Subject: [taler-merchant-backoffice] 02/02: fixing issue taken from christian comments:
Date: Tue, 30 Nov 2021 20:21:49 +0100

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

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

commit 54723c9d6cec1b46e577222a2f66b37c85ef10ac
Author: Sebastian <sebasjm@gmail.com>
AuthorDate: Tue Nov 30 16:19:38 2021 -0300

    fixing issue taken from christian comments:
    
     * iban validaton client side
     * iban account should be CAP
     * added claim token option when creating an order
     * added wire transfer deadline, was missing
     * default wire transfer delay is now optional
     * refund deadline is now optional
     * fix update instance: do not redirect when there is a server error, stay 
in the same view and display the server error
---
 .../src/components/form/InputPaytoForm.tsx         |  87 +++++++++---
 .../instance/DefaultInstanceFormFields.tsx         |   5 +-
 packages/merchant-backoffice/src/i18n/index.tsx    |   6 +-
 .../paths/instance/orders/create/CreatePage.tsx    |  39 +++++-
 .../src/paths/instance/update/UpdatePage.tsx       |   4 +-
 .../src/paths/instance/update/index.tsx            |  17 ++-
 packages/merchant-backoffice/src/schemas/index.ts  |   8 +-
 .../merchant-backoffice/src/utils/constants.ts     | 147 +++++++++++++++++++++
 8 files changed, 282 insertions(+), 31 deletions(-)

diff --git 
a/packages/merchant-backoffice/src/components/form/InputPaytoForm.tsx 
b/packages/merchant-backoffice/src/components/form/InputPaytoForm.tsx
index 02436a3..73cf751 100644
--- a/packages/merchant-backoffice/src/components/form/InputPaytoForm.tsx
+++ b/packages/merchant-backoffice/src/components/form/InputPaytoForm.tsx
@@ -20,7 +20,8 @@
  */
 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,12 +34,13 @@ export interface Props<T> extends InputProps<T> {
 
 // https://datatracker.ietf.org/doc/html/rfc8905
 type Entity = {
+  // iban, bitcoin, x-taler-bank. it defined the format
   target: string;
-  path: string;
+  // path1 if the first field to be used
   path1: string;
-  path2: string;
-  host: string;
-  account: string;
+  // path2 if the second field to be used, optional
+  path2?: string;
+  // options of the payto uri
   options: {
     "receiver-name"?: string;
     sender?: string;
@@ -49,6 +51,61 @@ type Entity = {
   };
 };
 
+/**
+ * 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: {} };
@@ -69,16 +126,19 @@ export function InputPaytoForm<T>({
 
   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 url = new URL(`payto://${value.target}${value.path}`);
+  const url = new URL(`payto://${value.target}${payToPath}`);
   const ops = value.options!;
   Object.keys(ops).forEach((opt_key) => {
     const opt_value = ops[opt_key];
@@ -91,11 +151,7 @@ export function InputPaytoForm<T>({
     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
+      ? validateIBAN(value.path1, i18n)
       : undefined,
     path2:
       value.target === "x-taler-bank"
@@ -168,6 +224,7 @@ export function InputPaytoForm<T>({
               name="path1"
               label={i18n`Account`}
               tooltip={i18n`Bank Account Number.`}
+              inputExtra={{ style: { textTransform: "uppercase" } }}
             />
           </Fragment>
         )}
@@ -198,7 +255,7 @@ export function InputPaytoForm<T>({
             />
           </Fragment>
         )}
-        {value.target === "void" && <Fragment></Fragment>}
+        {value.target === "void" && <Fragment />}
         {value.target === "x-taler-bank" && (
           <Fragment>
             <Input<Entity>
diff --git 
a/packages/merchant-backoffice/src/components/instance/DefaultInstanceFormFields.tsx
 
b/packages/merchant-backoffice/src/components/instance/DefaultInstanceFormFields.tsx
index 5a8e1d1..8c59a6d 100644
--- 
a/packages/merchant-backoffice/src/components/instance/DefaultInstanceFormFields.tsx
+++ 
b/packages/merchant-backoffice/src/components/instance/DefaultInstanceFormFields.tsx
@@ -19,7 +19,7 @@
  * @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";
@@ -37,7 +37,7 @@ export function DefaultInstanceFormFields({
 }: {
   readonlyId?: boolean;
   showId: boolean;
-}) {
+}): VNode {
   const i18n = useTranslator();
   const backend = useBackendContext();
   return (
@@ -109,6 +109,7 @@ export function DefaultInstanceFormFields({
         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 f89ab31..9403de1 100644
--- a/packages/merchant-backoffice/src/i18n/index.tsx
+++ b/packages/merchant-backoffice/src/i18n/index.tsx
@@ -25,7 +25,11 @@ 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(
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 4a5af6b..580ead1 100644
--- 
a/packages/merchant-backoffice/src/paths/instance/orders/create/CreatePage.tsx
+++ 
b/packages/merchant-backoffice/src/paths/instance/orders/create/CreatePage.tsx
@@ -41,6 +41,7 @@ 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;
@@ -71,6 +72,7 @@ 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: "",
@@ -98,10 +100,12 @@ 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;
@@ -157,17 +161,29 @@ export function CreatePage({
         : undefined,
     payments: undefinedIfEmpty({
       refund_deadline: !value.payments?.refund_deadline
-        ? i18n`required`
+        ? 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`pay deadline cannot be before refund 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
-        ? i18n`required`
+        ? 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
@@ -211,10 +227,12 @@ export function CreatePage({
                 Math.floor(value.payments.pay_deadline.getTime() / 1000) * 
1000,
             }
           : undefined,
-        wire_transfer_deadline: value.payments.pay_deadline
+        wire_transfer_deadline: value.payments.wire_transfer_deadline
           ? {
               t_ms:
-                Math.floor(value.payments.pay_deadline.getTime() / 1000) * 
1000,
+                Math.floor(
+                  value.payments.wire_transfer_deadline.getTime() / 1000
+                ) * 1000,
             }
           : undefined,
         refund_deadline: value.payments.refund_deadline
@@ -238,6 +256,7 @@ export function CreatePage({
         product_id: p.product.id,
         quantity: p.quantity,
       })),
+      create_token: value.payments.createToken,
     };
 
     onCreate(request);
@@ -454,6 +473,11 @@ export function CreatePage({
                   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`}
@@ -475,6 +499,11 @@ export function CreatePage({
                   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
diff --git 
a/packages/merchant-backoffice/src/paths/instance/update/UpdatePage.tsx 
b/packages/merchant-backoffice/src/paths/instance/update/UpdatePage.tsx
index 863f977..741edc6 100644
--- a/packages/merchant-backoffice/src/paths/instance/update/UpdatePage.tsx
+++ b/packages/merchant-backoffice/src/paths/instance/update/UpdatePage.tsx
@@ -153,9 +153,7 @@ export function UpdatePage({
     (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);
 
diff --git a/packages/merchant-backoffice/src/paths/instance/update/index.tsx 
b/packages/merchant-backoffice/src/paths/instance/update/index.tsx
index 38a652d..bd5f4c7 100644
--- a/packages/merchant-backoffice/src/paths/instance/update/index.tsx
+++ b/packages/merchant-backoffice/src/paths/instance/update/index.tsx
@@ -14,7 +14,9 @@
  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";
@@ -24,6 +26,8 @@ import {
   useManagedInstanceDetails,
   useManagementAPI,
 } from "../../../hooks/instance";
+import { useTranslator } from "../../../i18n";
+import { Notification } from "../../../utils/types";
 import { UpdatePage } from "./UpdatePage";
 
 export interface Props {
@@ -65,6 +69,8 @@ function CommonUpdate(
   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();
@@ -73,6 +79,7 @@ function CommonUpdate(
 
   return (
     <Fragment>
+      <NotificationCard notification={notif} />
       <UpdatePage
         onBack={onBack}
         isLoading={false}
@@ -80,7 +87,15 @@ function CommonUpdate(
         onUpdate={(
           d: MerchantBackend.Instances.InstanceReconfigurationMessage
         ): Promise<void> => {
-          return updateInstance(d).then(onConfirm).catch(onUpdateError);
+          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
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]