taler-typescript-core

Wallet core logic and WebUIs for various components
Log | Files | Refs | Submodules | README | LICENSE

commit 1d633f06390c0de2b9cdb7d7a44fa0ae4f42e5fd
parent 53e9842f4590fe4b991dedd058a9014428b5d4f8
Author: Sebastian <sebasjm@gmail.com>
Date:   Fri, 31 Oct 2025 11:34:10 -0300

bank account

Diffstat:
Mpackages/merchant-backoffice-ui/src/paths/instance/accounts/create/CreatePage.tsx | 43++++++++++++++++++++++++-------------------
Mpackages/merchant-backoffice-ui/src/paths/instance/accounts/create/index.tsx | 16++++++----------
Mpackages/merchant-backoffice-ui/src/paths/instance/accounts/list/Table.tsx | 145+++++++++++++++++++++++++++++++++++++++++++++++++++----------------------------
Mpackages/merchant-backoffice-ui/src/paths/instance/accounts/list/index.tsx | 34++--------------------------------
Mpackages/merchant-backoffice-ui/src/paths/instance/accounts/update/UpdatePage.tsx | 258+++++++++++++++++++++++++++++++++++++------------------------------------------
Mpackages/merchant-backoffice-ui/src/paths/instance/accounts/update/index.tsx | 93+------------------------------------------------------------------------------
6 files changed, 246 insertions(+), 343 deletions(-)

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 @@ -23,6 +23,7 @@ import { AccessToken, HttpStatusCode, opEmptySuccess, + PaytoParseError, Paytos, TalerMerchantApi, } from "@gnu-taler/taler-util"; @@ -92,17 +93,17 @@ export function CreatePage({ onCreated, onBack }: Props): VNode { !state.credit_facade_credentials || !state.credit_facade_url ? undefined : { - username: - state.credit_facade_credentials.type === "basic" && + username: + state.credit_facade_credentials.type === "basic" && !state.credit_facade_credentials.username - ? i18n.str`Required` - : undefined, - password: - state.credit_facade_credentials.type === "basic" && + ? i18n.str`Required` + : undefined, + password: + state.credit_facade_credentials.type === "basic" && !state.credit_facade_credentials.password - ? i18n.str`Required` - : undefined, - }, + ? i18n.str`Required` + : undefined, + }, ) as any, credit_facade_url: !state.credit_facade_url ? undefined @@ -129,13 +130,13 @@ export function CreatePage({ onCreated, onBack }: Props): VNode { ? undefined : state.credit_facade_credentials?.type === "basic" ? { - type: "basic", - password: state.credit_facade_credentials.password, - username: state.credit_facade_credentials.username, - } + type: "basic", + password: state.credit_facade_credentials.password, + username: state.credit_facade_credentials.username, + } : { - type: "none", - }; + type: "none", + }; const { state: session, lib } = useSessionContext(); const request: TalerMerchantApi.AccountAddDetails = { @@ -184,14 +185,13 @@ export function CreatePage({ onCreated, onBack }: Props): VNode { ); test.onSuccess = (success) => { - const match = state.payto_uri === success; + const match = state.payto_uri === Paytos.toFullString(success); setState({ ...state, verified: match, }); if (!match) { - const parsed = Paytos.fromString(success); - if (parsed.type === "ok") setRevenuePayto(parsed.body); + setRevenuePayto(success); } }; @@ -205,6 +205,11 @@ export function CreatePage({ onCreated, onBack }: Props): VNode { return i18n.str`Unauthorized, check credentials.`; case HttpStatusCode.NotFound: return i18n.str`The endpoint does not seem to be a Taler Revenue API.`; + case PaytoParseError.UNSUPPORTED: + case PaytoParseError.COMPONENTS_LENGTH: + case PaytoParseError.INVALID_TARGET_PATH: + case PaytoParseError.WRONG_PREFIX: + case PaytoParseError.INCOMPLETE: return i18n.str`Unsupported type of account` } }; @@ -341,7 +346,7 @@ export function CreatePage({ onCreated, onBack }: Props): VNode { }); setRevenuePayto(undefined); return opEmptySuccess() - },[])} + }, [])} formPayto={safeParsed} testPayto={revenuePayto} /> diff --git a/packages/merchant-backoffice-ui/src/paths/instance/accounts/create/index.tsx b/packages/merchant-backoffice-ui/src/paths/instance/accounts/create/index.tsx @@ -66,20 +66,13 @@ export default function CreateValidator({ onConfirm, onBack }: Props): VNode { } export enum TestRevenueErrorType { - ANOTHER_ACCOUNT, CANT_VALIDATE, } export async function testRevenueAPI( revenueAPI: URL, creds: FacadeCredentials | undefined, -): Promise< - | OperationOk<Paytos.FullPaytoString> - | OperationFail<TestRevenueErrorType.CANT_VALIDATE> - | OperationFail<HttpStatusCode.NotFound> - | OperationFail<HttpStatusCode.Unauthorized> - | OperationFail<HttpStatusCode.BadRequest> -> { +) { const httpLib: HttpRequestLibrary = new BrowserFetchHttpLib(); const api = new TalerRevenueHttpClient(revenueAPI.href, httpLib); const auth: BasicOrTokenAuth | undefined = @@ -114,12 +107,15 @@ export async function testRevenueAPI( if (!resp.body.credit_account) { return { - type: "fail", + type: "fail" as const, case: TestRevenueErrorType.CANT_VALIDATE, detail: undefined, }; } - return opFixedSuccess(resp.body.credit_account as Paytos.FullPaytoString); + const str = resp.body.credit_account as Paytos.FullPaytoString + const uri = Paytos.fromString(str) + if (uri.type === "fail") return uri + return opFixedSuccess(uri.body); } catch (err) { // FIXME: should we return some other error code here? throw err; diff --git a/packages/merchant-backoffice-ui/src/paths/instance/accounts/list/Table.tsx b/packages/merchant-backoffice-ui/src/paths/instance/accounts/list/Table.tsx @@ -20,6 +20,7 @@ */ import { + HttpStatusCode, parsePaytoUri, Paytos, PaytoType, @@ -27,15 +28,15 @@ import { succeedOrThrow, TalerMerchantApi, } from "@gnu-taler/taler-util"; -import { useTranslationContext } from "@gnu-taler/web-util/browser"; +import { ButtonBetter, ButtonBetterBulma, LocalNotificationBannerBulma, SafeHandlerTemplate, useLocalNotificationBetter, useTranslationContext } from "@gnu-taler/web-util/browser"; import { Fragment, h, VNode } from "preact"; import { StateUpdater, useState } from "preact/hooks"; +import { useSessionContext } from "../../../../context/session.js"; type Entity = TalerMerchantApi.BankAccountEntry; interface Props { accounts: Entity[]; - onDelete: (e: Entity) => void; onSelect: (e: Entity) => void; onCreate: () => void; } @@ -43,59 +44,99 @@ interface Props { export function CardTable({ accounts, onCreate, - onDelete, onSelect, }: Props): VNode { const [rowSelection, rowSelectionHandler] = useState<string[]>([]); const { i18n } = useTranslationContext(); + const { state: session, lib } = useSessionContext(); + const [notification, safeFunctionHandler] = useLocalNotificationBetter(); + const remove = safeFunctionHandler(lib.instance.deleteBankAccount).lambda((id: string) => !session.token ? undefined! : [session.token, id]) + remove.onFail = (fail) => { + switch (fail.case) { + case HttpStatusCode.Unauthorized: return i18n.str`Unauthorized` + case HttpStatusCode.NotFound: return i18n.str`Not found` + } + } + remove.onSuccess = () => i18n.str`The bank account has been deleted.` + // async (e: TalerMerchantApi.BankAccountEntry) => { + // return lib.instance + // .deleteBankAccount(state.token, e.h_wire) + // .then((resp) => { + // if (resp.type === "ok") { + // setNotif({ + // message: i18n.str`The bank account details have been successfully deleted.`, + // type: "SUCCESS", + // }); + // } else { + // setNotif({ + // message: i18n.str`The bank account details could not be deleted.`, + // type: "ERROR", + // description: resp.detail?.hint, + // }); + // } + // }) + // .catch((error) => + // setNotif({ + // message: i18n.str`The bank account details could not be deleted.`, + // type: "ERROR", + // description: + // error instanceof Error ? error.message : String(error), + // }), + // ); + // } return ( - <div class="card has-table"> - <header class="card-header"> - <p class="card-header-title"> - <span class="icon"> - <i class="mdi mdi-bank" /> - </span> - <i18n.Translate>Bank accounts</i18n.Translate> - </p> - <div class="card-header-icon" aria-label="more options"> - <span - class="has-tooltip-left" - data-tooltip={i18n.str`Add new account`} - > - <button class="button is-info" type="button" onClick={onCreate}> - <span class="icon is-small"> - <i class="mdi mdi-plus mdi-36px" /> - </span> - </button> - </span> - </div> - </header> - <div class="card-content"> - <div class="b-table has-pagination"> - <div class="table-wrapper has-mobile-cards"> - {accounts.length > 0 ? ( - <Table - accounts={accounts} - onDelete={onDelete} - onSelect={onSelect} - rowSelection={rowSelection} - rowSelectionHandler={rowSelectionHandler} - /> - ) : ( - <EmptyTable /> - )} + <Fragment> + <LocalNotificationBannerBulma notification={notification} /> + + <div class="card has-table"> + <header class="card-header"> + <p class="card-header-title"> + <span class="icon"> + <i class="mdi mdi-bank" /> + </span> + <i18n.Translate>Bank accounts</i18n.Translate> + </p> + <div class="card-header-icon" aria-label="more options"> + <span + class="has-tooltip-left" + data-tooltip={i18n.str`Add new account`} + > + <button class="button is-info" type="button" onClick={onCreate}> + <span class="icon is-small"> + <i class="mdi mdi-plus mdi-36px" /> + </span> + </button> + </span> + </div> + </header> + <div class="card-content"> + <div class="b-table has-pagination"> + <div class="table-wrapper has-mobile-cards"> + {accounts.length > 0 ? ( + <Table + accounts={accounts} + onDelete={remove} + onSelect={onSelect} + rowSelection={rowSelection} + rowSelectionHandler={rowSelectionHandler} + /> + ) : ( + <EmptyTable /> + )} + </div> </div> </div> </div> - </div> + </Fragment> + ); } interface TableProps { rowSelection: string[]; accounts: Entity[]; - onDelete: (e: Entity) => void; + onDelete: SafeHandlerTemplate<[id:string], unknown>; onSelect: (e: Entity) => void; rowSelectionHandler: StateUpdater<string[]>; } @@ -183,13 +224,13 @@ function Table({ accounts, onDelete, onSelect }: TableProps): VNode { </td> <td class="is-actions-cell right-sticky"> <div class="buttons is-right"> - <button + <ButtonBetterBulma class="button is-danger is-small has-tooltip-left" data-tooltip={i18n.str`Delete selected accounts from the database`} - onClick={() => onDelete(acc)} + onClick={onDelete.withArgs(acc.h_wire)} > <i18n.Translate>Delete</i18n.Translate> - </button> + </ButtonBetterBulma> </div> </td> </tr> @@ -221,13 +262,13 @@ function Table({ accounts, onDelete, onSelect }: TableProps): VNode { </td> <td class="is-actions-cell right-sticky"> <div class="buttons is-right"> - <button + <ButtonBetterBulma class="button is-danger is-small has-tooltip-left" data-tooltip={i18n.str`Delete selected accounts from the database`} - onClick={() => onDelete(acc)} + onClick={onDelete.withArgs(acc.h_wire)} > <i18n.Translate>Delete</i18n.Translate> - </button> + </ButtonBetterBulma> </div> </td> </tr> @@ -259,13 +300,13 @@ function Table({ accounts, onDelete, onSelect }: TableProps): VNode { </td> <td class="is-actions-cell right-sticky"> <div class="buttons is-right"> - <button + <ButtonBetterBulma class="button is-danger is-small has-tooltip-left" data-tooltip={i18n.str`Delete selected accounts from the database`} - onClick={() => onDelete(acc)} + onClick={onDelete.withArgs(acc.h_wire)} > <i18n.Translate>Delete</i18n.Translate> - </button> + </ButtonBetterBulma> </div> </td> </tr> @@ -297,13 +338,13 @@ function Table({ accounts, onDelete, onSelect }: TableProps): VNode { </td> <td class="is-actions-cell right-sticky"> <div class="buttons is-right"> - <button + <ButtonBetterBulma class="button is-danger is-small has-tooltip-left" data-tooltip={i18n.str`Delete selected accounts from the database`} - onClick={() => onDelete(acc)} + onClick={onDelete.withArgs(acc.h_wire)} > <i18n.Translate>Delete</i18n.Translate> - </button> + </ButtonBetterBulma> </div> </td> </tr> diff --git a/packages/merchant-backoffice-ui/src/paths/instance/accounts/list/index.tsx b/packages/merchant-backoffice-ui/src/paths/instance/accounts/list/index.tsx @@ -22,18 +22,15 @@ import { HttpStatusCode, TalerError, - TalerMerchantApi, - assertUnreachable, + assertUnreachable } from "@gnu-taler/taler-util"; -import { LocalNotificationBannerBulma, useTranslationContext } from "@gnu-taler/web-util/browser"; +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"; import { Loading } from "../../../../components/exception/loading.js"; import { NotificationCard } from "../../../../components/menu/index.js"; import { useSessionContext } from "../../../../context/session.js"; import { useInstanceBankAccounts } from "../../../../hooks/bank.js"; -import { Notification } from "../../../../utils/types.js"; import { LoginPage } from "../../../login/index.js"; import { NotFoundPageOrAdminCreate } from "../../../notfound/index.js"; import { CardTable } from "./Table.js"; @@ -69,7 +66,6 @@ export default function ListOtpDevices({ onCreate, onSelect }: Props): VNode { return ( <Fragment> - <LocalNotificationBannerBulma notification={notification} /> {result.body.accounts.length < 1 && ( <NotificationCard notification={{ @@ -89,32 +85,6 @@ export default function ListOtpDevices({ onCreate, onSelect }: Props): VNode { onSelect={(e) => { onSelect(e.h_wire); }} - onDelete={async (e: TalerMerchantApi.BankAccountEntry) => { - return lib.instance - .deleteBankAccount(state.token, e.h_wire) - .then((resp) => { - if (resp.type === "ok") { - setNotif({ - message: i18n.str`The bank account details have been successfully deleted.`, - type: "SUCCESS", - }); - } else { - setNotif({ - message: i18n.str`The bank account details could not be deleted.`, - type: "ERROR", - description: resp.detail?.hint, - }); - } - }) - .catch((error) => - setNotif({ - message: i18n.str`The bank account details could not be deleted.`, - type: "ERROR", - description: - error instanceof Error ? error.message : String(error), - }), - ); - }} /> </section> </Fragment> 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 @@ -20,7 +20,10 @@ */ import { + AccessToken, + BankAccountDetail, HttpStatusCode, + PaytoParseError, PaytoString, PaytoUri, Paytos, @@ -28,10 +31,11 @@ import { TalerMerchantApi, TranslatedString, assertUnreachable, + opEmptySuccess, parsePaytoUri, succeedOrThrow, } from "@gnu-taler/taler-util"; -import { useTranslationContext } from "@gnu-taler/web-util/browser"; +import { ButtonBetterBulma, LocalNotificationBanner, useChallengeHandler, useLocalNotificationBetter, useTranslationContext } from "@gnu-taler/web-util/browser"; import { Fragment, VNode, h } from "preact"; import { useState } from "preact/hooks"; import { @@ -47,6 +51,8 @@ import { WithId } from "../../../../declaration.js"; import { usePreference } from "../../../../hooks/preference.js"; import { undefinedIfEmpty } from "../../../../utils/table.js"; import { TestRevenueErrorType, testRevenueAPI } from "../create/index.js"; +import { useSessionContext } from "../../../../context/session.js"; +import { SolveMFAChallenges } from "../../../../components/SolveMFA.js"; type Entity = TalerMerchantApi.BankAccountDetail & WithId; type FormType = TalerMerchantApi.AccountPatchDetails & { @@ -54,20 +60,15 @@ type FormType = TalerMerchantApi.AccountPatchDetails & { payto_uri?: PaytoString; }; interface Props { - onUpdate: (d: TalerMerchantApi.AccountPatchDetails) => Promise<void>; - onReplace: ( - prev: TalerMerchantApi.BankAccountDetail, - next: TalerMerchantApi.AccountAddDetails, - ) => Promise<void>; + onUpdated: () => void; onBack?: () => void; account: Entity; } export function UpdatePage({ account, - onUpdate, + onUpdated, onBack, - onReplace, }: Props): VNode { const { i18n } = useTranslationContext(); @@ -85,13 +86,14 @@ export function UpdatePage({ }, }); - const [revenuePayto, setRevenuePayto] = useState<PaytoUri | undefined>( + const [revenuePayto, setRevenuePayto] = useState<Paytos.URI | undefined>( // parsePaytoUri("payto://x-taler-bank/asd.com:1010/asd/pepe"), undefined, ); - const [testError, setTestError] = useState<TranslatedString | undefined>( - undefined, - ); + const parsed = !state.payto_uri + ? undefined + : Paytos.fromString(state.payto_uri); + const safeParsed = parsed?.type === "fail" ? undefined : parsed?.body; const replacingAccountId = state.payto_uri !== account.payto_uri; @@ -146,123 +148,117 @@ export function UpdatePage({ const hasErrors = errors !== undefined; - const submitForm = () => { - if (hasErrors) return Promise.reject(); - const credit_facade_url = !state.credit_facade_url - ? undefined - : facadeURL?.href; + const credit_facade_url = !state.credit_facade_url + ? undefined + : facadeURL?.href; - const credit_facade_credentials: - | TalerMerchantApi.FacadeCredentials - | undefined = - credit_facade_url == undefined || - state.credit_facade_credentials === undefined + const credit_facade_credentials: + | TalerMerchantApi.FacadeCredentials + | undefined = + credit_facade_url == undefined || + state.credit_facade_credentials === undefined + ? undefined + : // @ts-expect-error unedit is not in facade creds + state.credit_facade_credentials.type === "unedit" ? undefined - : // @ts-expect-error unedit is not in facade creds - state.credit_facade_credentials.type === "unedit" - ? undefined - : state.credit_facade_credentials.type === "basic" + : state.credit_facade_credentials.type === "basic" + ? { + type: "basic", + password: state.credit_facade_credentials.password, + username: state.credit_facade_credentials.username, + } + : state.credit_facade_credentials.type === "bearer" ? { - type: "basic", - password: state.credit_facade_credentials.password, - username: state.credit_facade_credentials.username, + type: "bearer", + token: state.credit_facade_credentials.token, } - : state.credit_facade_credentials.type === "bearer" - ? { - type: "bearer", - token: state.credit_facade_credentials.token, - } - : { - type: "none", - }; + : { + type: "none", + }; + const { state: session, lib } = useSessionContext(); + const [notification, safeFunctionHandler] = useLocalNotificationBetter(); - if (replacingAccountId) { - return onReplace(account, { - payto_uri: state.payto_uri!, - credit_facade_credentials, - credit_facade_url, - }); + + async function changeBankAccount(token: AccessToken, newPayto: string | undefined, id: string, data: TalerMerchantApi.AccountPatchDetails, challengeIds: string[]) { + if (newPayto) { + const details: TalerMerchantApi.AccountAddDetails = { + ...data, + payto_uri: newPayto + } + const created = await lib.instance.addBankAccount(token, details, { challengeIds }) + if (created.type === "fail") return created + const deleted = await lib.instance.deleteBankAccount(token, id) + if (deleted.type === "fail") return deleted } else { - return onUpdate({ credit_facade_credentials, credit_facade_url }); + const resp = await lib.instance.updateBankAccount(token, id, data) + if (resp.type === "fail") return resp } - }; + return opEmptySuccess() + } - async function testAccountInfo() { - const revenueAPI = !state.credit_facade_url - ? undefined - : new URL("./", state.credit_facade_url); + const mfa = useChallengeHandler(); + const update = safeFunctionHandler(changeBankAccount, + !session.token ? undefined : [session.token, replacingAccountId ? state.payto_uri! : undefined, account.h_wire, { + credit_facade_credentials, credit_facade_url + }, []]) + update.onSuccess = onUpdated + update.onFail = (fail) => { + switch (fail.case) { + case HttpStatusCode.Accepted: + mfa.onChallengeRequired(fail.body) + return i18n.str`Second factor authentication required.` + case HttpStatusCode.Unauthorized: + return i18n.str`Unauthorized` + case HttpStatusCode.NotFound: + return i18n.str`Not found` + case HttpStatusCode.Conflict: + return i18n.str`Conflict` + } + } + const repeat = update.lambda((ids: string[]) => [update.args![0], update.args![1], update.args![2], update.args![3], ids]) - 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 does not seem to be a Taler Revenue API.`, - ); - setState({ - ...state, - verified: undefined, - }); - return; - } - case TestRevenueErrorType.CANT_VALIDATE: { - setTestError( - i18n.str`The request was made correctly, but the bank's server did not respond with the appropriate value for 'credit_account', so we cannot confirm that it is the same bank account.`, - ); - 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); - } + const revenueAPI = !state.credit_facade_url + ? undefined + : new URL("./", state.credit_facade_url); + + const test = safeFunctionHandler(testRevenueAPI, !revenueAPI || !state.credit_facade_url ? undefined : [revenueAPI, state.credit_facade_credentials]) + test.onSuccess = (success) => { + const match = state.payto_uri === Paytos.toFullString(success); + setState({ + ...state, + verified: match, + }); + if (!match) { + setRevenuePayto(success); + } + } + test.onFail = (fail) => { + switch (fail.case) { + case HttpStatusCode.BadRequest: return i18n.str`Server replied with "bad request".` + case HttpStatusCode.Unauthorized: return i18n.str`Unauthorized, check credentials.` + case HttpStatusCode.NotFound: return i18n.str`The endpoint does not seem to be a Taler Revenue API.` + case TestRevenueErrorType.CANT_VALIDATE: return i18n.str`The request was made correctly, but the bank's server did not respond with the appropriate value for 'credit_account', so we cannot confirm that it is the same bank account.` + case PaytoParseError.UNSUPPORTED: + case PaytoParseError.COMPONENTS_LENGTH: + case PaytoParseError.INVALID_TARGET_PATH: + case PaytoParseError.WRONG_PREFIX: + case PaytoParseError.INCOMPLETE: return i18n.str`Unsupported type of account` } } + if (mfa.pendingChallenge) { + return ( + <SolveMFAChallenges + currentChallenge={mfa.pendingChallenge} + onCompleted={repeat} + onCancel={mfa.doCancelChallenge} + /> + ); + } + return ( <Fragment> + <LocalNotificationBanner notification={notification} /> <section class="section"> <section class="hero is-hero-bar"> <div class="hero-body"> @@ -356,26 +352,14 @@ export function UpdatePage({ 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 + <ButtonBetterBulma class="button is-info" data-tooltip={i18n.str`Compare info from server with account form`} - disabled={!state.credit_facade_url} - onClick={async () => { - await testAccountInfo(); - }} + onClick={test} > <i18n.Translate>Test</i18n.Translate> - </button> + </ButtonBetterBulma> } /> </Fragment> @@ -389,13 +373,12 @@ export function UpdatePage({ </button> )} <ButtonBetterBulma - disabled={hasErrors} data-tooltip={ hasErrors ? i18n.str`Please complete the marked fields` : i18n.str`Confirm operation` } - onClick={submitForm} + onClick={update} > <i18n.Translate>Confirm</i18n.Translate> </ButtonBetterBulma> @@ -409,16 +392,15 @@ export function UpdatePage({ onCancel={() => { setRevenuePayto(undefined); }} - onConfirm={(d) => { + confirm={safeFunctionHandler(async () => { setState({ ...state, - payto_uri: d, + payto_uri: Paytos.toFullString(revenuePayto), }); setRevenuePayto(undefined); - }} - formPayto={ - !state.payto_uri ? undefined : parsePaytoUri(state.payto_uri) - } + return opEmptySuccess() + }, [])} + formPayto={safeParsed} 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 @@ -63,8 +63,6 @@ export default function UpdateValidator({ const { state, lib } = useSessionContext(); const result = useBankAccountDetails(bid); - const [notification, safeFunctionHandler] = useLocalNotificationBetter(); - const mfa = useChallengeHandler(); const { i18n } = useTranslationContext(); @@ -87,102 +85,13 @@ export default function UpdateValidator({ } } - async function doUpdateImpl(request: TalerMerchantApi.AccountPatchDetails) { - return lib.instance - .updateBankAccount(state.token, bid, request) - .then((resp) => { - if (resp.type === "fail") { - setNotif({ - message: i18n.str`Could not update account`, - type: "ERROR", - description: resp.detail?.hint, - }); - return; - } - onConfirm(); - }) - .catch((error) => { - setNotif({ - message: i18n.str`Could not update account`, - type: "ERROR", - description: error instanceof Error ? error.message : String(error), - }); - }); - } - - const [doReplace, repeatReplace] = mfa.withMfaHandler( - ({ challengeIds, onChallengeRequired }) => - async function doReplaceImpl( - prev: BankAccountDetail, - next: AccountAddDetails, - ) { - try { - const resp = await lib.instance.addBankAccount(state.token, next, { - challengeIds, - }); - if (resp.type === "fail") { - if (resp.case === HttpStatusCode.Accepted) { - onChallengeRequired(resp.body); - return; - } - setNotif({ - message: i18n.str`Could not create account`, - type: "ERROR", - description: resp.detail?.hint, - }); - return; - } - } catch (error) { - setNotif({ - message: i18n.str`Could not create account`, - type: "ERROR", - description: error instanceof Error ? error.message : String(error), - }); - return; - } - try { - const resp = await lib.instance.deleteBankAccount( - state.token, - prev.h_wire, - ); - if (resp.type === "fail") { - setNotif({ - message: i18n.str`Could not delete account`, - type: "ERROR", - description: resp.detail?.hint, - }); - return; - } - } catch (error) { - setNotif({ - message: i18n.str`Could not delete account`, - type: "ERROR", - description: error instanceof Error ? error.message : String(error), - }); - return; - } - onConfirm(); - }, - ); - - if (mfa.pendingChallenge) { - return ( - <SolveMFAChallenges - currentChallenge={mfa.pendingChallenge} - onCompleted={repeatReplace} - onCancel={mfa.doCancelChallenge} - /> - ); - } return ( <Fragment> - <LocalNotificationBannerBulma notification={notification} /> <UpdatePage account={{ ...result.body, id: bid }} onBack={onBack} - onUpdate={doUpdateImpl} - onReplace={doReplace} + onUpdated={onConfirm} /> </Fragment> );