From a102d7a5061910a58953ea738681b65f18b54b90 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Thu, 20 Jun 2024 15:11:22 -0300 Subject: fix #8926 --- .../paths/instance/accounts/create/CreatePage.tsx | 10 +- .../paths/instance/accounts/update/UpdatePage.tsx | 296 ++++++++++++++++++--- .../src/paths/instance/accounts/update/index.tsx | 116 ++++---- 3 files changed, 318 insertions(+), 104 deletions(-) (limited to 'packages') diff --git a/packages/merchant-backoffice-ui/src/paths/instance/accounts/create/CreatePage.tsx b/packages/merchant-backoffice-ui/src/paths/instance/accounts/create/CreatePage.tsx index 8684eb90d..61f62e631 100644 --- a/packages/merchant-backoffice-ui/src/paths/instance/accounts/create/CreatePage.tsx +++ b/packages/merchant-backoffice-ui/src/paths/instance/accounts/create/CreatePage.tsx @@ -62,13 +62,13 @@ export function CreatePage({ onCreate, onBack }: Props): VNode { const { i18n } = useTranslationContext(); const [importing, setImporting] = useState(false); + const [state, setState] = useState>({}); + const facadeURL = safeConvertURL(state.credit_facade_url); + const [revenuePayto, setRevenuePayto] = useState( // parsePaytoUri("payto://x-taler-bank/asd.com:1010/asd/pepe"), undefined, ); - const [state, setState] = useState>({}); - const facadeURL = safeConvertURL(state.credit_facade_url); - const [testError, setTestError] = useState( undefined, ); @@ -272,8 +272,8 @@ export function CreatePage({ onCreate, onBack }: Props): VNode { class="button is-info" data-tooltip={i18n.str`Compare info from server with account form`} disabled={!state.credit_facade_url} - onClick={() => { - testAccountInfo(); + onClick={async () => { + const result = await testAccountInfo(); }} > Test diff --git a/packages/merchant-backoffice-ui/src/paths/instance/accounts/update/UpdatePage.tsx b/packages/merchant-backoffice-ui/src/paths/instance/accounts/update/UpdatePage.tsx index 812b2aa50..73fe43026 100644 --- a/packages/merchant-backoffice-ui/src/paths/instance/accounts/update/UpdatePage.tsx +++ b/packages/merchant-backoffice-ui/src/paths/instance/accounts/update/UpdatePage.tsx @@ -19,9 +19,18 @@ * @author Sebastian Javier Marchano (sebasjm) */ -import { TalerMerchantApi } from "@gnu-taler/taler-util"; +import { + HttpStatusCode, + PaytoString, + PaytoUri, + TalerError, + TalerMerchantApi, + TranslatedString, + assertUnreachable, + parsePaytoUri, +} from "@gnu-taler/taler-util"; import { useTranslationContext } from "@gnu-taler/web-util/browser"; -import { Fragment, h, VNode } from "preact"; +import { Fragment, VNode, h } from "preact"; import { useState } from "preact/hooks"; import { AsyncButton } from "../../../../components/exception/AsyncButton.js"; import { @@ -31,33 +40,64 @@ import { import { Input } from "../../../../components/form/Input.js"; import { InputPaytoForm } from "../../../../components/form/InputPaytoForm.js"; import { InputSelector } from "../../../../components/form/InputSelector.js"; -import { undefinedIfEmpty } from "../../../../utils/table.js"; import { WithId } from "../../../../declaration.js"; +import { undefinedIfEmpty } from "../../../../utils/table.js"; +import { testRevenueAPI } from "../create/index.js"; +import { InputToggle } from "../../../../components/form/InputToggle.js"; +import { + CompareAccountsModal, + ImportingAccountModal, +} from "../../../../components/modal/index.js"; type Entity = TalerMerchantApi.BankAccountDetail & WithId; - +type FormType = TalerMerchantApi.AccountPatchDetails & { + verified: boolean; + payto_uri?: PaytoString; +}; const accountAuthType = ["unedit", "none", "basic"]; interface Props { onUpdate: (d: TalerMerchantApi.AccountPatchDetails) => Promise; + onReplace: ( + prev: TalerMerchantApi.BankAccountDetail, + next: TalerMerchantApi.AccountAddDetails, + ) => Promise; onBack?: () => void; account: Entity; } -export function UpdatePage({ account, onUpdate, onBack }: Props): VNode { +export function UpdatePage({ + account, + onUpdate, + onBack, + onReplace, +}: Props): VNode { const { i18n } = useTranslationContext(); - const [state, setState] = - useState>(account); + const [state, setState] = useState>({ + payto_uri: account.payto_uri, + credit_facade_url: account.credit_facade_url, + credit_facade_credentials: { + // @ts-ignore + type: "unedit", + }, + }); + const [importing, setImporting] = useState(false); - // @ts-expect-error "unedit" is fine since is part of the accountAuthType values - if (state.credit_facade_credentials?.type === "unedit") { - // we use this to set creds to undefined but server don't get this type - state.credit_facade_credentials = undefined; - } + const [revenuePayto, setRevenuePayto] = useState( + // parsePaytoUri("payto://x-taler-bank/asd.com:1010/asd/pepe"), + undefined, + ); + const [testError, setTestError] = useState( + undefined, + ); + + const replacingAccountId = state.payto_uri !== account.payto_uri; const facadeURL = safeConvertURL(state.credit_facade_url); - const errors: FormErrors = { + const errors: FormErrors = { + payto_uri: !state.payto_uri ? i18n.str`required` : undefined, + credit_facade_url: !state.credit_facade_url ? undefined : !facadeURL @@ -69,21 +109,29 @@ export function UpdatePage({ account, onUpdate, onBack }: Props): VNode { : facadeURL.hash ? i18n.str`URL should not hash param` : undefined, - credit_facade_credentials: undefinedIfEmpty({ - username: - state.credit_facade_credentials?.type !== "basic" - ? undefined - : !state.credit_facade_credentials.username - ? i18n.str`required` - : undefined, + credit_facade_credentials: !state.credit_facade_credentials + ? undefined + : undefinedIfEmpty({ + type: + replacingAccountId && + // @ts-ignore + state.credit_facade_credentials?.type === "unedit" + ? i18n.str`required` + : undefined, + username: + state.credit_facade_credentials?.type !== "basic" + ? undefined + : !state.credit_facade_credentials.username + ? i18n.str`required` + : undefined, - password: - state.credit_facade_credentials?.type !== "basic" - ? undefined - : !state.credit_facade_credentials.password - ? i18n.str`required` - : undefined, - }), + password: + state.credit_facade_credentials?.type !== "basic" + ? undefined + : !state.credit_facade_credentials.password + ? i18n.str`required` + : undefined, + }), }; const hasErrors = Object.keys(errors).some( @@ -102,21 +150,98 @@ export function UpdatePage({ account, onUpdate, onBack }: Props): VNode { credit_facade_url == undefined || state.credit_facade_credentials === undefined ? undefined - : state.credit_facade_credentials.type === "basic" - ? { - type: "basic", - password: state.credit_facade_credentials.password, - username: state.credit_facade_credentials.username, - } - : { - type: "none", - }; - - return onUpdate({ credit_facade_credentials, credit_facade_url }); + : // @ts-ignore + state.credit_facade_credentials.type === "unedit" + ? undefined + : state.credit_facade_credentials.type === "basic" + ? { + type: "basic", + password: state.credit_facade_credentials.password, + username: state.credit_facade_credentials.username, + } + : { + type: "none", + }; + + if (replacingAccountId) { + console.log("======== REPLACE"); + return onReplace(account, { + payto_uri: state.payto_uri!, + credit_facade_credentials, + credit_facade_url, + }); + } else { + console.log("======== UPDATE"); + return onUpdate({ credit_facade_credentials, credit_facade_url }); + } }; + async function testAccountInfo() { + const revenueAPI = !state.credit_facade_url + ? undefined + : new URL("./", state.credit_facade_url); + + if (revenueAPI) { + const resp = await testRevenueAPI( + revenueAPI, + state.credit_facade_credentials, + ); + if (resp instanceof TalerError) { + setTestError(i18n.str`The request to check the revenue API failed.`); + setState({ + ...state, + verified: undefined, + }); + return; + } else if (resp.type === "fail") { + switch (resp.case) { + case HttpStatusCode.BadRequest: { + setTestError(i18n.str`Server replied with "bad request".`); + setState({ + ...state, + verified: undefined, + }); + return; + } + case HttpStatusCode.Unauthorized: { + setTestError(i18n.str`Unauthorized, check credentials.`); + setState({ + ...state, + verified: false, + }); + return; + } + case HttpStatusCode.NotFound: { + setTestError( + i18n.str`The endpoint doesn't seems to be a Taler Revenue API.`, + ); + setState({ + ...state, + verified: undefined, + }); + return; + } + default: { + assertUnreachable(resp); + } + } + } else { + const found = resp.body; + const match = state.payto_uri === found; + setState({ + ...state, + verified: match, + }); + if (!match) { + setRevenuePayto(parsePaytoUri(resp.body)); + } + setTestError(undefined); + } + } + } + return ( -
+
@@ -124,7 +249,8 @@ export function UpdatePage({ account, onUpdate, onBack }: Props): VNode {
- Account: {account.id.substring(0, 8)}... + Account:{" "} + {account.id.substring(0, 8)}...
@@ -141,14 +267,22 @@ export function UpdatePage({ account, onUpdate, onBack }: Props): VNode { valueHandler={setState} errors={errors} > - + name="payto_uri" label={i18n.str`Account`} - readonly /> +
+

+ + If the bank supports Taler Revenue API then you can add + the endpoint URL below to keep the revenue information in + sync. + +

+
name="credit_facade_url" - label={i18n.str`Account info URL`} + label={i18n.str`Endpoint URL`} help="https://bank.demo.taler.net/accounts/_username_/taler-revenue/" expand tooltip={i18n.str`From where the merchant can download information about incoming wire transfers to this account`} @@ -179,6 +313,34 @@ export function UpdatePage({ account, onUpdate, onBack }: Props): VNode { /> ) : undefined} + + label={i18n.str`Match`} + tooltip={i18n.str`Check where the information match against the server info.`} + name="verified" + readonly + threeState + help={ + testError !== undefined + ? testError + : state.verified === undefined + ? i18n.str`Not verified` + : state.verified + ? i18n.str`Last test was ok` + : i18n.str`Last test failed` + } + side={ + + } + />
@@ -203,7 +365,53 @@ export function UpdatePage({ account, onUpdate, onBack }: Props): VNode {
-
+ {!importing ? undefined : ( + { + setImporting(false); + }} + onConfirm={(ac) => { + const u = new URL(ac.infoURL); + const user = u.username; + const pwd = u.password; + u.password = ""; + u.username = ""; + const credit_facade_url = u.href; + setState({ + payto_uri: ac.accountURI, + credit_facade_credentials: + user || pwd + ? { + type: "basic", + password: pwd, + username: user, + } + : undefined, + credit_facade_url, + }); + setImporting(false); + }} + /> + )} + {!revenuePayto ? undefined : ( + { + setRevenuePayto(undefined); + }} + onConfirm={(d) => { + setState({ + ...state, + payto_uri: d, + }); + setRevenuePayto(undefined); + }} + formPayto={ + !state.payto_uri ? undefined : parsePaytoUri(state.payto_uri) + } + testPayto={revenuePayto} + /> + )} + ); } diff --git a/packages/merchant-backoffice-ui/src/paths/instance/accounts/update/index.tsx b/packages/merchant-backoffice-ui/src/paths/instance/accounts/update/index.tsx index 1ada0c8d7..60dad7257 100644 --- a/packages/merchant-backoffice-ui/src/paths/instance/accounts/update/index.tsx +++ b/packages/merchant-backoffice-ui/src/paths/instance/accounts/update/index.tsx @@ -19,10 +19,13 @@ * @author Sebastian Javier Marchano (sebasjm) */ -import { HttpStatusCode, TalerError, TalerMerchantApi, assertUnreachable } from "@gnu-taler/taler-util"; import { - useTranslationContext -} from "@gnu-taler/web-util/browser"; + HttpStatusCode, + TalerError, + TalerMerchantApi, + assertUnreachable, +} from "@gnu-taler/taler-util"; +import { useTranslationContext } from "@gnu-taler/web-util/browser"; import { Fragment, VNode, h } from "preact"; import { useState } from "preact/hooks"; import { ErrorLoadingMerchant } from "../../../../components/ErrorLoadingMerchant.js"; @@ -33,7 +36,6 @@ import { useBankAccountDetails } from "../../../../hooks/bank.js"; import { Notification } from "../../../../utils/types.js"; import { LoginPage } from "../../../login/index.js"; import { NotFoundPageOrAdminCreate } from "../../../notfound/index.js"; -import { TestRevenueErrorType, testRevenueAPI } from "../create/index.js"; import { UpdatePage } from "./UpdatePage.js"; import { WithId } from "../../../../declaration.js"; @@ -66,7 +68,7 @@ export default function UpdateValidator({ return ; } case HttpStatusCode.Unauthorized: { - return + return ; } default: { assertUnreachable(result); @@ -81,67 +83,71 @@ export default function UpdateValidator({ account={{ ...result.body, id: bid }} onBack={onBack} onUpdate={async (request) => { - const revenueAPI = !request.credit_facade_url - ? undefined - : new URL("./", request.credit_facade_url); - - if (revenueAPI) { - const resp = await testRevenueAPI( - revenueAPI, - request.credit_facade_credentials, + return api.instance + .updateBankAccount(state.token, bid, request) + .then((updated) => { + if (updated.type === "fail") { + setNotif({ + message: i18n.str`could not update account`, + type: "ERROR", + description: updated.detail.hint, + }); + return; + } + onConfirm(); + }) + .catch((error) => { + setNotif({ + message: i18n.str`could not update account`, + type: "ERROR", + description: error.message, + }); + }); + }} + onReplace={async (prev, next) => { + try { + const created = await api.instance.addBankAccount( + state.token, + next, ); - if (resp instanceof TalerError) { + if (created.type === "fail") { setNotif({ - message: i18n.str`Could not create account`, + message: i18n.str`could not create account`, type: "ERROR", - description: i18n.str`The request to check the revenue API failed.`, - details: JSON.stringify(resp.errorDetail, undefined, 2), + description: created.detail.hint, }); return; } - if (resp.type === "fail") { - switch (resp.case) { - case HttpStatusCode.BadRequest: { - setNotif({ - message: i18n.str`Could not create account`, - type: "ERROR", - description: i18n.str`Server replied with "bad request".`, - }); - return; - - } - case HttpStatusCode.Unauthorized: { - setNotif({ - message: i18n.str`Could not create account`, - type: "ERROR", - description: i18n.str`Unauthorized, try with another credentials.`, - }); - return; - - } - case HttpStatusCode.NotFound: { - setNotif({ - message: i18n.str`Could not create account`, - type: "ERROR", - description: i18n.str`The endpoint doesn't seems to be a Taler Revenue API`, - }); - return; - } - default: { - assertUnreachable(resp); - } - } - } + } catch (error: any) { + setNotif({ + message: i18n.str`could not create account`, + type: "ERROR", + description: error.message, + }); + return; } - return api.instance.updateBankAccount(state.token, bid, request) - .then(onConfirm) - .catch((error) => { + try { + const deleted = await api.instance.deleteBankAccount( + state.token, + prev.h_wire, + ); + if (deleted.type === "fail") { setNotif({ - message: i18n.str`could not update account`, + message: i18n.str`could not delete account`, type: "ERROR", - description: error.message, + description: deleted.detail.hint, }); + return; + } + } catch (error: any) { + setNotif({ + message: i18n.str`could not delete account`, + type: "ERROR", + description: error.message, }); + return; + } + onConfirm(); }} /> -- cgit v1.2.3