From 03d3cce8274fcbf6fb9cb8be03e867136f196c5d Mon Sep 17 00:00:00 2001 From: Sebastian Date: Wed, 26 Apr 2023 14:20:18 -0300 Subject: fix #6363, breaking with merchant backend that accounts implemented --- .../src/components/form/FormProvider.tsx | 6 + .../src/components/form/InputPaytoForm.stories.tsx | 47 +++++ .../src/components/form/InputPaytoForm.tsx | 202 ++++++++++++++++++--- .../src/components/index.stories.ts | 17 ++ .../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, 319 insertions(+), 50 deletions(-) create mode 100644 packages/merchant-backoffice-ui/src/components/form/InputPaytoForm.stories.tsx create mode 100644 packages/merchant-backoffice-ui/src/components/index.stories.ts (limited to 'packages') 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 { const FormContext = createContext>(null!); +/** + * FIXME: + * USE MEMORY EVENTS INSTEAD OF CONTEXT + * @deprecated + */ + export function useFormContext() { return useContext>(FormContext); } diff --git a/packages/merchant-backoffice-ui/src/components/form/InputPaytoForm.stories.tsx b/packages/merchant-backoffice-ui/src/components/form/InputPaytoForm.stories.tsx new file mode 100644 index 000000000..2c1961639 --- /dev/null +++ b/packages/merchant-backoffice-ui/src/components/form/InputPaytoForm.stories.tsx @@ -0,0 +1,47 @@ +/* + This file is part of GNU Taler + (C) 2021-2023 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + GNU Taler; see the file COPYING. If not, see + */ + +/** + * + * @author Sebastian Javier Marchano (sebasjm) + */ + +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>(initial); + return ( + + + + ); +}, {}); 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 extends InputProps { 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 = { + target: noTargetValue, + options: {}, + auth: { + type: "unset" as const, + }, +}; export function InputPaytoForm({ name, @@ -187,7 +203,7 @@ export function InputPaytoForm({ } 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({ ? 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({ ); 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({ )} + {/** + * Show additional fields apart from the payto + */} {value.target !== noTargetValue && ( - - )} + + + + { + // if (str === "unset") { + // return "Without change"; + // } + if (str === "none") return "Without authentication"; + return "Username and password"; + }} + /> + {value.auth?.type === "basic" ? ( + + + + + + ) : undefined} + {/* v.toUpperCase()} + addonAfter={ + + {showKey ? ( + + ) : ( + + )} + + } + side={ + + + + } + /> */} + + )} + {/** + * Show the values in the list + */}
- {paytos.map((v: any, i: number) => ( - + ), + )} {!paytos.length && i18n.str`No accounts yet.`} {required && ( diff --git a/packages/merchant-backoffice-ui/src/components/index.stories.ts b/packages/merchant-backoffice-ui/src/components/index.stories.ts new file mode 100644 index 000000000..c57ddab14 --- /dev/null +++ b/packages/merchant-backoffice-ui/src/components/index.stories.ts @@ -0,0 +1,17 @@ +/* + This file is part of GNU Taler + (C) 2021-2023 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + GNU Taler; see the file COPYING. If not, see + */ + +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({ /> - 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 { 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, }, -- cgit v1.2.3