gnunet-svn
[Top][All Lists]
Advanced

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

[taler-wallet-core] 02/02: fix #6363, breaking with merchant backend tha


From: gnunet
Subject: [taler-wallet-core] 02/02: fix #6363, breaking with merchant backend that accounts implemented
Date: Wed, 26 Apr 2023 19:20:27 +0200

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

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

commit 03d3cce8274fcbf6fb9cb8be03e867136f196c5d
Author: Sebastian <sebasjm@gmail.com>
AuthorDate: Wed Apr 26 14:20:18 2023 -0300

    fix #6363, breaking with merchant backend that accounts implemented
---
 .../src/components/form/FormProvider.tsx           |   6 +
 .../form/InputPaytoForm.stories.tsx}               |  50 ++---
 .../src/components/form/InputPaytoForm.tsx         | 202 ++++++++++++++++++---
 .../{stories.tsx => components/index.stories.ts}   |  32 +---
 .../instance/DefaultInstanceFormFields.tsx         |   2 +-
 .../merchant-backoffice-ui/src/declaration.d.ts    |  55 +++++-
 .../src/paths/admin/create/CreatePage.tsx          |  12 +-
 .../src/paths/instance/update/UpdatePage.tsx       |  25 ++-
 packages/merchant-backoffice-ui/src/stories.tsx    |   3 +-
 9 files changed, 281 insertions(+), 106 deletions(-)

diff --git 
a/packages/merchant-backoffice-ui/src/components/form/FormProvider.tsx 
b/packages/merchant-backoffice-ui/src/components/form/FormProvider.tsx
index 7bcebd706..0d53c4d08 100644
--- a/packages/merchant-backoffice-ui/src/components/form/FormProvider.tsx
+++ b/packages/merchant-backoffice-ui/src/components/form/FormProvider.tsx
@@ -82,6 +82,12 @@ export interface FormType<T> {
 
 const FormContext = createContext<FormType<unknown>>(null!);
 
+/**
+ * FIXME:
+ * USE MEMORY EVENTS INSTEAD OF CONTEXT
+ * @deprecated
+ */
+
 export function useFormContext<T>() {
   return useContext<FormType<T>>(FormContext);
 }
diff --git a/packages/merchant-backoffice-ui/src/stories.tsx 
b/packages/merchant-backoffice-ui/src/components/form/InputPaytoForm.stories.tsx
similarity index 50%
copy from packages/merchant-backoffice-ui/src/stories.tsx
copy to 
packages/merchant-backoffice-ui/src/components/form/InputPaytoForm.stories.tsx
index ccfde4ef2..2c1961639 100644
--- a/packages/merchant-backoffice-ui/src/stories.tsx
+++ 
b/packages/merchant-backoffice-ui/src/components/form/InputPaytoForm.stories.tsx
@@ -18,30 +18,30 @@
  *
  * @author Sebastian Javier Marchano (sebasjm)
  */
-import { strings } from "./i18n/strings.js";
 
-import * as admin from "./paths/admin/index.stories.js";
-import * as instance from "./paths/instance/index.stories.js";
-
-import { renderStories } from "@gnu-taler/web-util/lib/index.browser";
-
-import "./scss/main.scss";
-
-function SortStories(a: any, b: any): number {
-  return (a?.order ?? 0) - (b?.order ?? 0);
-}
-
-function main(): void {
-  renderStories(
-    { admin, instance },
-    {
-      strings,
-    },
+import { h } from "preact";
+import { tests } from "@gnu-taler/web-util/lib/index.browser";
+import { InputPaytoForm } from "./InputPaytoForm.js";
+import { FormProvider } from "./FormProvider.js";
+import { useState } from "preact/hooks";
+
+export default {
+  title: "Components/Form/PayTo",
+  component: InputPaytoForm,
+  argTypes: {
+    onUpdate: { action: "onUpdate" },
+    onBack: { action: "onBack" },
+  },
+};
+
+export const Example = tests.createExample(() => {
+  const initial = {
+    accounts: [],
+  };
+  const [form, updateForm] = useState<Partial<typeof initial>>(initial);
+  return (
+    <FormProvider valueHandler={updateForm} object={form}>
+      <InputPaytoForm name="accounts" label="Accounts:" />
+    </FormProvider>
   );
-}
-
-if (document.readyState === "loading") {
-  document.addEventListener("DOMContentLoaded", main);
-} else {
-  main();
-}
+}, {});
diff --git 
a/packages/merchant-backoffice-ui/src/components/form/InputPaytoForm.tsx 
b/packages/merchant-backoffice-ui/src/components/form/InputPaytoForm.tsx
index 3cd36a6e0..98fe2f91a 100644
--- a/packages/merchant-backoffice-ui/src/components/form/InputPaytoForm.tsx
+++ b/packages/merchant-backoffice-ui/src/components/form/InputPaytoForm.tsx
@@ -28,6 +28,8 @@ import { Input } from "./Input.js";
 import { InputGroup } from "./InputGroup.js";
 import { InputSelector } from "./InputSelector.js";
 import { InputProps, useField } from "./useField.js";
+import { InputWithAddon } from "./InputWithAddon.js";
+import { MerchantBackend } from "../../declaration.js";
 
 export interface Props<T> extends InputProps<T> {
   isValid?: (e: any) => boolean;
@@ -50,6 +52,13 @@ type Entity = {
     instruction?: string;
     [name: string]: string | undefined;
   };
+  auth: {
+    type: "unset" | "basic" | "none";
+    url?: string;
+    username?: string;
+    password?: string;
+    repeat?: string;
+  };
 };
 
 function isEthereumAddress(address: string) {
@@ -162,8 +171,15 @@ const targets = [
   "bitcoin",
   "ethereum",
 ];
+const accountAuthType = ["none", "basic"];
 const noTargetValue = targets[0];
-const defaultTarget = { target: noTargetValue, options: {} };
+const defaultTarget: Partial<Entity> = {
+  target: noTargetValue,
+  options: {},
+  auth: {
+    type: "unset" as const,
+  },
+};
 
 export function InputPaytoForm<T>({
   name,
@@ -187,7 +203,7 @@ export function InputPaytoForm<T>({
   }
   const { i18n } = useTranslationContext();
 
-  const ops = value.options!;
+  const ops = value.options ?? {};
   const url = tryUrl(`payto://${value.target}${payToPath}`);
   if (url) {
     Object.keys(ops).forEach((opt_key) => {
@@ -222,6 +238,24 @@ export function InputPaytoForm<T>({
         ? i18n.str`required`
         : undefined,
     }),
+    auth: !value.auth
+      ? undefined
+      : undefinedIfEmpty({
+          username:
+            value.auth.type === "basic" && !value.auth.username
+              ? i18n.str`required`
+              : undefined,
+          password:
+            value.auth.type === "basic" && !value.auth.password
+              ? i18n.str`required`
+              : undefined,
+          repeat:
+            value.auth.type === "basic" && !value.auth.repeat
+              ? i18n.str`required`
+              : value.auth.repeat !== value.auth.password
+              ? i18n.str`is not the same`
+              : undefined,
+        }),
   };
 
   const hasErrors = Object.keys(errors).some(
@@ -229,10 +263,31 @@ export function InputPaytoForm<T>({
   );
 
   const submit = useCallback((): void => {
+    const accounts: MerchantBackend.Instances.MerchantBankAccount[] = paytos;
     const alreadyExists =
-      paytos.findIndex((x: string) => x === paytoURL) !== -1;
+      accounts.findIndex((x) => x.payto_uri === paytoURL) !== -1;
     if (!alreadyExists) {
-      onChange([paytoURL, ...paytos] as any);
+      const newValue: MerchantBackend.Instances.MerchantBankAccount = {
+        payto_uri: paytoURL,
+      };
+      if (value.auth) {
+        if (value.auth.url) {
+          newValue.credit_facade_url = value.auth.url;
+        }
+        if (value.auth.type === "none") {
+          newValue.credit_facade_credentials = {
+            type: "none",
+          };
+        }
+        if (value.auth.type === "basic") {
+          newValue.credit_facade_credentials = {
+            type: "basic",
+            username: value.auth.username ?? "",
+            password: value.auth.password ?? "",
+          };
+        }
+      }
+      onChange([newValue, ...accounts] as any);
     }
     valueHandler(defaultTarget);
   }, [value]);
@@ -339,37 +394,126 @@ export function InputPaytoForm<T>({
           </Fragment>
         )}
 
+        {/**
+         * Show additional fields apart from the payto
+         */}
         {value.target !== noTargetValue && (
-          <Input
-            name="options.receiver-name"
-            label={i18n.str`Name`}
-            tooltip={i18n.str`Bank account owner's name.`}
-          />
-        )}
+          <Fragment>
+            <Input
+              name="options.receiver-name"
+              label={i18n.str`Name`}
+              tooltip={i18n.str`Bank account owner's name.`}
+            />
+            <InputWithAddon
+              name="auth.url"
+              label={i18n.str`Account info URL`}
+              help="https://bank.com";
+              expand
+              tooltip={i18n.str`From where the merchant can download 
information about incoming wire transfers to this account`}
+            />
+            <InputSelector
+              name="auth.type"
+              label={i18n.str`Auth type`}
+              tooltip={i18n.str`Choose the authentication type for the account 
info URL`}
+              values={accountAuthType}
+              toStr={(str) => {
+                // if (str === "unset") {
+                //   return "Without change";
+                // }
+                if (str === "none") return "Without authentication";
+                return "Username and password";
+              }}
+            />
+            {value.auth?.type === "basic" ? (
+              <Fragment>
+                <Input
+                  name="auth.username"
+                  label={i18n.str`Username`}
+                  tooltip={i18n.str`Username to access the account 
information.`}
+                />
+                <Input
+                  name="auth.password"
+                  inputType="password"
+                  label={i18n.str`Password`}
+                  tooltip={i18n.str`Password to access the account 
information.`}
+                />
+                <Input
+                  name="auth.repeat"
+                  inputType="password"
+                  label={i18n.str`Repeat password`}
+                />
+              </Fragment>
+            ) : undefined}
 
+            {/* <InputWithAddon
+              name="options.credit_credentials"
+              label={i18n.str`Account info`}
+              inputType={showKey ? "text" : "password"}
+              help="From where the merchant can download information about 
incoming wire transfers to this account"
+              expand
+              tooltip={i18n.str`Useful to validate the purchase`}
+              fromStr={(v) => v.toUpperCase()}
+              addonAfter={
+                <span class="icon">
+                  {showKey ? (
+                    <i class="mdi mdi-eye" />
+                  ) : (
+                    <i class="mdi mdi-eye-off" />
+                  )}
+                </span>
+              }
+              side={
+                <span style={{ display: "flex" }}>
+                  <button
+                    data-tooltip={
+                      showKey
+                        ? i18n.str`show secret key`
+                        : i18n.str`hide secret key`
+                    }
+                    class="button is-info mr-3"
+                    onClick={(e) => {
+                      setShowKey(!showKey);
+                    }}
+                  >
+                    {showKey ? (
+                      <i18n.Translate>hide</i18n.Translate>
+                    ) : (
+                      <i18n.Translate>show</i18n.Translate>
+                    )}
+                  </button>
+                </span>
+              }
+            /> */}
+          </Fragment>
+        )}
+        {/**
+         * Show the values in the list
+         */}
         <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%" }}
+            {paytos.map(
+              (v: MerchantBackend.Instances.MerchantBankAccount, i: number) => 
(
+                <div
+                  key={i}
+                  class="tags has-addons mt-3 mb-0 mr-3"
+                  style={{ flexWrap: "nowrap" }}
                 >
-                  {v}
-                </span>
-                <a
-                  class="tag is-medium is-danger is-delete mb-0"
-                  onClick={() => {
-                    onChange(paytos.filter((f: any) => f !== v) as any);
-                  }}
-                />
-              </div>
-            ))}
+                  <span
+                    class="tag is-medium is-info mb-0"
+                    style={{ maxWidth: "90%" }}
+                  >
+                    {v.payto_uri}
+                  </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.str`No accounts yet.`}
             {required && (
               <span class="icon has-text-danger is-right">
diff --git a/packages/merchant-backoffice-ui/src/stories.tsx 
b/packages/merchant-backoffice-ui/src/components/index.stories.ts
similarity index 52%
copy from packages/merchant-backoffice-ui/src/stories.tsx
copy to packages/merchant-backoffice-ui/src/components/index.stories.ts
index ccfde4ef2..c57ddab14 100644
--- a/packages/merchant-backoffice-ui/src/stories.tsx
+++ b/packages/merchant-backoffice-ui/src/components/index.stories.ts
@@ -14,34 +14,4 @@
  GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 
-/**
- *
- * @author Sebastian Javier Marchano (sebasjm)
- */
-import { strings } from "./i18n/strings.js";
-
-import * as admin from "./paths/admin/index.stories.js";
-import * as instance from "./paths/instance/index.stories.js";
-
-import { renderStories } from "@gnu-taler/web-util/lib/index.browser";
-
-import "./scss/main.scss";
-
-function SortStories(a: any, b: any): number {
-  return (a?.order ?? 0) - (b?.order ?? 0);
-}
-
-function main(): void {
-  renderStories(
-    { admin, instance },
-    {
-      strings,
-    },
-  );
-}
-
-if (document.readyState === "loading") {
-  document.addEventListener("DOMContentLoaded", main);
-} else {
-  main();
-}
+export * as payto from "./form/InputPaytoForm.stories.js";
diff --git 
a/packages/merchant-backoffice-ui/src/components/instance/DefaultInstanceFormFields.tsx
 
b/packages/merchant-backoffice-ui/src/components/instance/DefaultInstanceFormFields.tsx
index 3a3bdd6f3..bbdc9708a 100644
--- 
a/packages/merchant-backoffice-ui/src/components/instance/DefaultInstanceFormFields.tsx
+++ 
b/packages/merchant-backoffice-ui/src/components/instance/DefaultInstanceFormFields.tsx
@@ -86,7 +86,7 @@ export function DefaultInstanceFormFields({
       />
 
       <InputPaytoForm<Entity>
-        name="payto_uris"
+        name="accounts"
         label={i18n.str`Bank account`}
         tooltip={i18n.str`URI specifying bank account for crediting revenue.`}
       />
diff --git a/packages/merchant-backoffice-ui/src/declaration.d.ts 
b/packages/merchant-backoffice-ui/src/declaration.d.ts
index b21af32d1..58e14a114 100644
--- a/packages/merchant-backoffice-ui/src/declaration.d.ts
+++ b/packages/merchant-backoffice-ui/src/declaration.d.ts
@@ -262,15 +262,45 @@ export namespace MerchantBackend {
       // header.
       token?: string;
     }
+    type FacadeCredentials = NoFacadeCredentials | BasicAuthFacadeCredentials;
+
+    interface NoFacadeCredentials {
+      type: "none";
+    }
+
+    interface BasicAuthFacadeCredentials {
+      type: "basic";
+
+      // Username to use to authenticate
+      username: string;
+
+      // Password to use to authenticate
+      password: string;
+    }
+
+    interface MerchantBankAccount {
+      // The payto:// URI where the wallet will send coins.
+      payto_uri: string;
+
+      // Optional base URL for a facade where the
+      // merchant backend can see incoming wire
+      // transfers to reconcile its accounting
+      // with that of the exchange. Used by
+      // taler-merchant-wirewatch.
+      credit_facade_url?: string;
+
+      // Credentials for accessing the credit facade.
+      credit_facade_credentials?: FacadeCredentials;
+    }
     //POST /private/instances
     interface InstanceConfigurationMessage {
-      // The URI where the wallet will send coins.  A merchant may have
+      // Bank accounts of the merchant.  A merchant may have
       // multiple accounts, thus this is an array.  Note that by
-      // removing URIs from this list the respective account is set to
+      // removing accounts from this list the respective account is set to
       // inactive and thus unavailable for new contracts, but preserved
       // in the database as existing offers and contracts may still refer
       // to it.
-      payto_uris: string[];
+      accounts: MerchantBankAccount[];
 
       // Name of the merchant instance to create (will become $INSTANCE).
       id: string;
@@ -326,10 +356,11 @@ export namespace MerchantBackend {
 
     // PATCH /private/instances/$INSTANCE
     interface InstanceReconfigurationMessage {
-      // The URI where the wallet will send coins.  A merchant may have
-      // multiple accounts, thus this is an array.  Note that by
-      // removing URIs from this list
-      payto_uris: string[];
+      // Bank accounts of the merchant.  A merchant may have
+      // multiple accounts, thus this is an array.  Note that removing
+      // URIs from this list deactivates the specified accounts
+      // (they will no longer be used for future contracts).
+      accounts: MerchantBankAccount[];
 
       // Merchant name corresponding to this instance.
       name: string;
@@ -491,6 +522,16 @@ export namespace MerchantBackend {
       // salt used to compute h_wire
       salt: HashCode;
 
+      // URL from where the merchant can download information
+      // about incoming wire transfers to this account.
+      credit_facade_url?: string;
+
+      // Credentials to use when accessing the credit facade.
+      // Never returned on a GET (as this may be somewhat
+      // sensitive data). Can be set in POST
+      // or PATCH requests to update (or delete) credentials.
+      credit_facade_credentials?: FacadeCredentials;
+
       // true if this account is active,
       // false if it is historic.
       active: boolean;
diff --git 
a/packages/merchant-backoffice-ui/src/paths/admin/create/CreatePage.tsx 
b/packages/merchant-backoffice-ui/src/paths/admin/create/CreatePage.tsx
index 0ef1f1270..4087908a2 100644
--- a/packages/merchant-backoffice-ui/src/paths/admin/create/CreatePage.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/admin/create/CreatePage.tsx
@@ -47,7 +47,7 @@ interface Props {
 function with_defaults(id?: string): Partial<Entity> {
   return {
     id,
-    payto_uris: [],
+    accounts: [],
     user_type: "business",
     default_pay_delay: { d_us: 2 * 1000 * 60 * 60 * 1000 }, // two hours
     default_wire_fee_amortization: 1,
@@ -75,12 +75,14 @@ export function CreatePage({ onCreate, onBack, forceId }: 
Props): VNode {
       : value.user_type !== "business" && value.user_type !== "individual"
       ? i18n.str`should be business or individual`
       : undefined,
-    payto_uris:
-      !value.payto_uris || !value.payto_uris.length
+    accounts:
+      !value.accounts || !value.accounts.length
         ? i18n.str`required`
         : undefinedIfEmpty(
-            value.payto_uris.map((p) => {
-              return !PAYTO_REGEX.test(p) ? i18n.str`is not valid` : undefined;
+            value.accounts.map((p) => {
+              return !PAYTO_REGEX.test(p.payto_uri)
+                ? i18n.str`is not valid`
+                : undefined;
             }),
           ),
     default_max_deposit_fee: !value.default_max_deposit_fee
diff --git 
a/packages/merchant-backoffice-ui/src/paths/instance/update/UpdatePage.tsx 
b/packages/merchant-backoffice-ui/src/paths/instance/update/UpdatePage.tsx
index 2b57ab429..ecf6e2ae5 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/update/UpdatePage.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/update/UpdatePage.tsx
@@ -53,14 +53,23 @@ interface Props {
 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 { accounts: qAccounts, ...rest } = from;
+  const accounts = qAccounts
+    .filter((a) => a.active)
+    .map(
+      (a) =>
+        ({
+          payto_uri: a.payto_uri,
+          credit_facade_url: a.credit_facade_url,
+          credit_facade_credentials: a.credit_facade_credentials,
+        } as MerchantBackend.Instances.MerchantBankAccount),
+    );
   const defaults = {
     default_wire_fee_amortization: 1,
     default_pay_delay: { d_us: 2 * 1000 * 1000 * 60 * 60 }, //two hours
     default_wire_transfer_delay: { d_us: 2 * 1000 * 1000 * 60 * 60 * 2 }, 
//two hours
   };
-  return { ...defaults, ...rest, payto_uris };
+  return { ...defaults, ...rest, accounts };
 }
 
 function getTokenValuePart(t?: string): string | undefined {
@@ -103,12 +112,14 @@ export function UpdatePage({
       : value.user_type !== "business" && value.user_type !== "individual"
       ? i18n.str`should be business or individual`
       : undefined,
-    payto_uris:
-      !value.payto_uris || !value.payto_uris.length
+    accounts:
+      !value.accounts || !value.accounts.length
         ? i18n.str`required`
         : undefinedIfEmpty(
-            value.payto_uris.map((p) => {
-              return !PAYTO_REGEX.test(p) ? i18n.str`is not valid` : undefined;
+            value.accounts.map((p) => {
+              return !PAYTO_REGEX.test(p.payto_uri)
+                ? i18n.str`is not valid`
+                : undefined;
             }),
           ),
     default_max_deposit_fee: !value.default_max_deposit_fee
diff --git a/packages/merchant-backoffice-ui/src/stories.tsx 
b/packages/merchant-backoffice-ui/src/stories.tsx
index ccfde4ef2..2c61e5586 100644
--- a/packages/merchant-backoffice-ui/src/stories.tsx
+++ b/packages/merchant-backoffice-ui/src/stories.tsx
@@ -22,6 +22,7 @@ import { strings } from "./i18n/strings.js";
 
 import * as admin from "./paths/admin/index.stories.js";
 import * as instance from "./paths/instance/index.stories.js";
+import * as components from "./components/index.stories.js";
 
 import { renderStories } from "@gnu-taler/web-util/lib/index.browser";
 
@@ -33,7 +34,7 @@ function SortStories(a: any, b: any): number {
 
 function main(): void {
   renderStories(
-    { admin, instance },
+    { admin, instance, components },
     {
       strings,
     },

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