diff options
Diffstat (limited to 'packages/merchant-backoffice-ui/src/paths/instance/accounts/update/UpdatePage.tsx')
-rw-r--r-- | packages/merchant-backoffice-ui/src/paths/instance/accounts/update/UpdatePage.tsx | 296 |
1 files changed, 252 insertions, 44 deletions
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<void>; + onReplace: ( + prev: TalerMerchantApi.BankAccountDetail, + next: TalerMerchantApi.AccountAddDetails, + ) => Promise<void>; 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<Partial<TalerMerchantApi.AccountPatchDetails>>(account); + const [state, setState] = useState<Partial<FormType>>({ + 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<PaytoUri | undefined>( + // parsePaytoUri("payto://x-taler-bank/asd.com:1010/asd/pepe"), + undefined, + ); + const [testError, setTestError] = useState<TranslatedString | undefined>( + undefined, + ); + + const replacingAccountId = state.payto_uri !== account.payto_uri; const facadeURL = safeConvertURL(state.credit_facade_url); - const errors: FormErrors<TalerMerchantApi.AccountPatchDetails> = { + const errors: FormErrors<FormType> = { + 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 ( - <div> + <Fragment> <section class="section"> <section class="hero is-hero-bar"> <div class="hero-body"> @@ -124,7 +249,8 @@ export function UpdatePage({ account, onUpdate, onBack }: Props): VNode { <div class="level-left"> <div class="level-item"> <span class="is-size-4"> - Account: <b>{account.id.substring(0, 8)}...</b> + <i18n.Translate>Account:</i18n.Translate>{" "} + <b>{account.id.substring(0, 8)}...</b> </span> </div> </div> @@ -141,14 +267,22 @@ export function UpdatePage({ account, onUpdate, onBack }: Props): VNode { valueHandler={setState} errors={errors} > - <InputPaytoForm<Entity> + <InputPaytoForm<FormType> name="payto_uri" label={i18n.str`Account`} - readonly /> + <div class="message-body" style={{ marginBottom: 10 }}> + <p> + <i18n.Translate> + If the bank supports Taler Revenue API then you can add + the endpoint URL below to keep the revenue information in + sync. + </i18n.Translate> + </p> + </div> <Input<Entity> 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 { /> </Fragment> ) : undefined} + <InputToggle<FormType> + 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={ + <button + class="button is-info" + data-tooltip={i18n.str`Compare info from server with account form`} + disabled={!state.credit_facade_url} + onClick={async () => { + const result = await testAccountInfo(); + }} + > + <i18n.Translate>Test</i18n.Translate> + </button> + } + /> </FormProvider> <div class="buttons is-right mt-5"> @@ -203,7 +365,53 @@ export function UpdatePage({ account, onUpdate, onBack }: Props): VNode { </div> </section> </section> - </div> + {!importing ? undefined : ( + <ImportingAccountModal + onCancel={() => { + 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 : ( + <CompareAccountsModal + onCancel={() => { + setRevenuePayto(undefined); + }} + onConfirm={(d) => { + setState({ + ...state, + payto_uri: d, + }); + setRevenuePayto(undefined); + }} + formPayto={ + !state.payto_uri ? undefined : parsePaytoUri(state.payto_uri) + } + testPayto={revenuePayto} + /> + )} + </Fragment> ); } |