aboutsummaryrefslogtreecommitdiff
path: root/packages
diff options
context:
space:
mode:
authorSebastian <sebasjm@gmail.com>2024-06-20 15:11:22 -0300
committerSebastian <sebasjm@gmail.com>2024-06-20 15:11:40 -0300
commita102d7a5061910a58953ea738681b65f18b54b90 (patch)
tree0d7282bcd32483ca08b2d26e1ba428d217dbbc8c /packages
parent39be2ef1cf5910b4fb543ea6d50345425d897247 (diff)
downloadwallet-core-a102d7a5061910a58953ea738681b65f18b54b90.tar.xz
fix #8926
Diffstat (limited to 'packages')
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/accounts/create/CreatePage.tsx10
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/accounts/update/UpdatePage.tsx296
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/accounts/update/index.tsx116
3 files changed, 318 insertions, 104 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
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<Partial<Entity>>({});
+ const facadeURL = safeConvertURL(state.credit_facade_url);
+
const [revenuePayto, setRevenuePayto] = useState<PaytoUri | undefined>(
// parsePaytoUri("payto://x-taler-bank/asd.com:1010/asd/pepe"),
undefined,
);
- const [state, setState] = useState<Partial<Entity>>({});
- const facadeURL = safeConvertURL(state.credit_facade_url);
-
const [testError, setTestError] = useState<TranslatedString | undefined>(
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();
}}
>
<i18n.Translate>Test</i18n.Translate>
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>
);
}
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 <NotFoundPageOrAdminCreate />;
}
case HttpStatusCode.Unauthorized: {
- return <LoginPage />
+ return <LoginPage />;
}
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();
}}
/>
</Fragment>