commit 1d633f06390c0de2b9cdb7d7a44fa0ae4f42e5fd
parent 53e9842f4590fe4b991dedd058a9014428b5d4f8
Author: Sebastian <sebasjm@gmail.com>
Date: Fri, 31 Oct 2025 11:34:10 -0300
bank account
Diffstat:
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>
);