summaryrefslogtreecommitdiff
path: root/packages/merchant-backoffice-ui/src/paths/instance/orders/details
diff options
context:
space:
mode:
Diffstat (limited to 'packages/merchant-backoffice-ui/src/paths/instance/orders/details')
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/orders/details/Detail.stories.tsx37
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/orders/details/DetailPage.tsx216
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/orders/details/Timeline.tsx6
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/orders/details/index.tsx87
4 files changed, 183 insertions, 163 deletions
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/orders/details/Detail.stories.tsx b/packages/merchant-backoffice-ui/src/paths/instance/orders/details/Detail.stories.tsx
index 6e73a01a5..7d4877db9 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/orders/details/Detail.stories.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/orders/details/Detail.stories.tsx
@@ -1,6 +1,6 @@
/*
This file is part of GNU Taler
- (C) 2021-2023 Taler Systems S.A.
+ (C) 2021-2024 Taler Systems S.A.
GNU Taler is free software; you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
@@ -19,9 +19,9 @@
* @author Sebastian Javier Marchano (sebasjm)
*/
+import { AmountString, TalerMerchantApi } from "@gnu-taler/taler-util";
import { addDays } from "date-fns";
-import { h, VNode, FunctionalComponent } from "preact";
-import { MerchantBackend } from "../../../../declaration.js";
+import { FunctionalComponent, h } from "preact";
import { DetailPage as TestedComponent } from "./DetailPage.js";
export default {
@@ -42,14 +42,13 @@ function createExample<Props>(
return r;
}
-const defaultContractTerm = {
- amount: "TESTKUDOS:10",
+const defaultContractTerm: TalerMerchantApi.ContractTerms = {
+ amount: "TESTKUDOS:10" as AmountString,
timestamp: {
t_s: new Date().getTime() / 1000,
},
- auditors: [],
exchanges: [],
- max_fee: "TESTKUDOS:1",
+ max_fee: "TESTKUDOS:1" as AmountString,
merchant: {} as any,
merchant_base_url: "http://merchant.url/",
order_id: "2021.165-03GDFC26Y1NNG",
@@ -66,7 +65,7 @@ const defaultContractTerm = {
},
wire_method: "x-taler-bank",
h_wire: "asd",
-} as MerchantBackend.ContractTerms;
+};
// contract_terms: defaultContracTerm,
export const Claimed = createExample(TestedComponent, {
@@ -83,16 +82,16 @@ export const PaidNotRefundable = createExample(TestedComponent, {
order_status: "paid",
contract_terms: defaultContractTerm,
refunded: false,
- deposit_total: "TESTKUDOS:10",
- exchange_ec: 0,
+ deposit_total: "TESTKUDOS:10" as AmountString,
+ exchange_code: 0,
order_status_url: "http://merchant.backend/status",
- exchange_hc: 0,
- refund_amount: "TESTKUDOS:0",
+ exchange_http_status: 0,
+ refund_amount: "TESTKUDOS:0" as AmountString,
refund_details: [],
refund_pending: false,
wire_details: [],
- wire_reports: [],
wired: false,
+ wire_reports: [],
},
});
@@ -107,15 +106,15 @@ export const PaidRefundable = createExample(TestedComponent, {
},
},
refunded: false,
- deposit_total: "TESTKUDOS:10",
- exchange_ec: 0,
+ deposit_total: "TESTKUDOS:10" as AmountString,
+ exchange_code: 0,
order_status_url: "http://merchant.backend/status",
- exchange_hc: 0,
- refund_amount: "TESTKUDOS:0",
+ exchange_http_status: 0,
+ refund_amount: "TESTKUDOS:0" as AmountString,
refund_details: [],
+ wire_reports: [],
refund_pending: false,
wire_details: [],
- wire_reports: [],
wired: false,
},
});
@@ -130,6 +129,6 @@ export const Unpaid = createExample(TestedComponent, {
},
summary: "text summary",
taler_pay_uri: "pay uri",
- total_amount: "TESTKUDOS:10",
+ total_amount: "TESTKUDOS:10" as AmountString,
},
});
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/orders/details/DetailPage.tsx b/packages/merchant-backoffice-ui/src/paths/instance/orders/details/DetailPage.tsx
index 5ff76e37a..498ea83e3 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/orders/details/DetailPage.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/orders/details/DetailPage.tsx
@@ -1,6 +1,6 @@
/*
This file is part of GNU Taler
- (C) 2021-2023 Taler Systems S.A.
+ (C) 2021-2024 Taler Systems S.A.
GNU Taler is free software; you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
@@ -19,8 +19,15 @@
* @author Sebastian Javier Marchano (sebasjm)
*/
-import { AmountJson, Amounts, stringifyRefundUri } from "@gnu-taler/taler-util";
-import { useTranslationContext } from "@gnu-taler/web-util/browser";
+import {
+ AmountJson,
+ Amounts,
+ TalerMerchantApi,
+ stringifyRefundUri,
+} from "@gnu-taler/taler-util";
+import {
+ useTranslationContext
+} from "@gnu-taler/web-util/browser";
import { format, formatDistance } from "date-fns";
import { Fragment, VNode, h } from "preact";
import { useState } from "preact/hooks";
@@ -33,28 +40,30 @@ import { InputGroup } from "../../../../components/form/InputGroup.js";
import { InputLocation } from "../../../../components/form/InputLocation.js";
import { TextField } from "../../../../components/form/TextField.js";
import { ProductList } from "../../../../components/product/ProductList.js";
-import { useBackendContext } from "../../../../context/backend.js";
-import { MerchantBackend } from "../../../../declaration.js";
-import { datetimeFormatForSettings, useSettings } from "../../../../hooks/useSettings.js";
+import { useSessionContext } from "../../../../context/session.js";
+import {
+ datetimeFormatForSettings,
+ usePreference,
+} from "../../../../hooks/preference.js";
import { mergeRefunds } from "../../../../utils/amount.js";
import { RefundModal } from "../list/Table.js";
import { Event, Timeline } from "./Timeline.js";
-type Entity = MerchantBackend.Orders.MerchantOrderStatusResponse;
-type CT = MerchantBackend.ContractTerms;
+type Entity = TalerMerchantApi.MerchantOrderStatusResponse;
+type CT = TalerMerchantApi.ContractTerms;
interface Props {
onBack: () => void;
selected: Entity;
id: string;
- onRefund: (id: string, value: MerchantBackend.Orders.RefundRequest) => void;
+ onRefund: (id: string, value: TalerMerchantApi.RefundRequest) => void;
}
-type Paid = MerchantBackend.Orders.CheckPaymentPaidResponse & {
+type Paid = TalerMerchantApi.CheckPaymentPaidResponse & {
refund_taken: string;
};
-type Unpaid = MerchantBackend.Orders.CheckPaymentUnpaidResponse;
-type Claimed = MerchantBackend.Orders.CheckPaymentClaimedResponse;
+type Unpaid = TalerMerchantApi.CheckPaymentUnpaidResponse;
+type Claimed = TalerMerchantApi.CheckPaymentClaimedResponse;
function ContractTerms({ value }: { value: CT }) {
const { i18n } = useTranslationContext();
@@ -149,8 +158,12 @@ function ClaimedPage({
order,
}: {
id: string;
- order: MerchantBackend.Orders.CheckPaymentClaimedResponse;
+ order: TalerMerchantApi.CheckPaymentClaimedResponse;
}) {
+ const now = new Date();
+ const refundable =
+ order.contract_terms.refund_deadline.t_s !== "never" &&
+ now.getTime() < order.contract_terms.refund_deadline.t_s * 1000;
const events: Event[] = [];
if (order.contract_terms.timestamp.t_s !== "never") {
events.push({
@@ -166,20 +179,20 @@ function ClaimedPage({
type: "deadline",
});
}
- if (order.contract_terms.refund_deadline.t_s !== "never") {
+ if (order.contract_terms.refund_deadline.t_s !== "never" && refundable) {
events.push({
when: new Date(order.contract_terms.refund_deadline.t_s * 1000),
description: "refund deadline",
type: "deadline",
});
}
- if (order.contract_terms.wire_transfer_deadline.t_s !== "never") {
- events.push({
- when: new Date(order.contract_terms.wire_transfer_deadline.t_s * 1000),
- description: "wire deadline",
- type: "deadline",
- });
- }
+ // if (order.contract_terms.wire_transfer_deadline.t_s !== "never") {
+ // events.push({
+ // when: new Date(order.contract_terms.wire_transfer_deadline.t_s * 1000),
+ // description: "wire deadline",
+ // type: "deadline",
+ // });
+ // }
if (
order.contract_terms.delivery_date &&
order.contract_terms.delivery_date.t_s !== "never"
@@ -193,7 +206,7 @@ function ClaimedPage({
const [value, valueHandler] = useState<Partial<Claimed>>(order);
const { i18n } = useTranslationContext();
- const [settings] = useSettings()
+ const [settings] = usePreference();
return (
<div>
@@ -237,10 +250,14 @@ function ClaimedPage({
<b>
<i18n.Translate>claimed at</i18n.Translate>:
</b>{" "}
- {format(
- new Date(order.contract_terms.timestamp.t_s * 1000),
- datetimeFormatForSettings(settings)
- )}
+ {order.contract_terms.timestamp.t_s === "never"
+ ? "never"
+ : format(
+ new Date(
+ order.contract_terms.timestamp.t_s * 1000,
+ ),
+ datetimeFormatForSettings(settings),
+ )}
</p>
</div>
</div>
@@ -311,25 +328,16 @@ function PaidPage({
onRefund,
}: {
id: string;
- order: MerchantBackend.Orders.CheckPaymentPaidResponse;
+ order: TalerMerchantApi.CheckPaymentPaidResponse;
onRefund: (id: string) => void;
}) {
+ const now = new Date();
+ const refundable =
+ order.contract_terms.refund_deadline.t_s !== "never" &&
+ now.getTime() < order.contract_terms.refund_deadline.t_s * 1000;
+
const events: Event[] = [];
- if (order.contract_terms.timestamp.t_s !== "never") {
- events.push({
- when: new Date(order.contract_terms.timestamp.t_s * 1000),
- description: "order created",
- type: "start",
- });
- }
- if (order.contract_terms.pay_deadline.t_s !== "never") {
- events.push({
- when: new Date(order.contract_terms.pay_deadline.t_s * 1000),
- description: "pay deadline",
- type: "deadline",
- });
- }
- if (order.contract_terms.refund_deadline.t_s !== "never") {
+ if (order.contract_terms.refund_deadline.t_s !== "never" && refundable) {
events.push({
when: new Date(order.contract_terms.refund_deadline.t_s * 1000),
description: "refund deadline",
@@ -363,66 +371,71 @@ function PaidPage({
});
}
});
- if (order.wire_details && order.wire_details.length) {
- if (order.wire_details.length > 1) {
- let last: MerchantBackend.Orders.TransactionWireTransfer | null = null;
- let first: MerchantBackend.Orders.TransactionWireTransfer | null = null;
- let total: AmountJson | null = null;
-
- order.wire_details.forEach((w) => {
- if (last === null || last.execution_time.t_s < w.execution_time.t_s) {
- last = w;
- }
- if (first === null || first.execution_time.t_s > w.execution_time.t_s) {
- first = w;
- }
- total =
- total === null
- ? Amounts.parseOrThrow(w.amount)
- : Amounts.add(total, Amounts.parseOrThrow(w.amount)).amount;
- });
- const last_time = last!.execution_time.t_s;
- if (last_time !== "never") {
- events.push({
- when: new Date(last_time * 1000),
- description: `wired ${Amounts.stringify(total!)}`,
- type: "wired-range",
+ const ra = !order.refunded ? undefined : Amounts.parse(order.refund_amount);
+ const am = Amounts.parseOrThrow(order.contract_terms.amount);
+ if (ra && Amounts.cmp(ra, am) === 1) {
+ if (order.wire_details && order.wire_details.length) {
+ if (order.wire_details.length > 1) {
+ let last: TalerMerchantApi.TransactionWireTransfer | null = null;
+ let first: TalerMerchantApi.TransactionWireTransfer | null = null;
+ let total: AmountJson | null = null;
+
+ order.wire_details.forEach((w) => {
+ if (last === null || last.execution_time.t_s < w.execution_time.t_s) {
+ last = w;
+ }
+ if (
+ first === null ||
+ first.execution_time.t_s > w.execution_time.t_s
+ ) {
+ first = w;
+ }
+ total =
+ total === null
+ ? Amounts.parseOrThrow(w.amount)
+ : Amounts.add(total, Amounts.parseOrThrow(w.amount)).amount;
});
- }
- const first_time = first!.execution_time.t_s;
- if (first_time !== "never") {
- events.push({
- when: new Date(first_time * 1000),
- description: `wire transfer started...`,
- type: "wired-range",
- });
- }
- } else {
- order.wire_details.forEach((e) => {
- if (e.execution_time.t_s !== "never") {
+ const last_time = last!.execution_time.t_s;
+ if (last_time !== "never") {
events.push({
- when: new Date(e.execution_time.t_s * 1000),
- description: `wired ${e.amount}`,
- type: "wired",
+ when: new Date(last_time * 1000),
+ description: `wired ${Amounts.stringify(total!)}`,
+ type: "wired-range",
});
}
- });
+ const first_time = first!.execution_time.t_s;
+ if (first_time !== "never") {
+ events.push({
+ when: new Date(first_time * 1000),
+ description: `wire transfer started...`,
+ type: "wired-range",
+ });
+ }
+ } else {
+ order.wire_details.forEach((e) => {
+ if (e.execution_time.t_s !== "never") {
+ events.push({
+ when: new Date(e.execution_time.t_s * 1000),
+ description: `wired ${e.amount}`,
+ type: "wired",
+ });
+ }
+ });
+ }
}
}
- const now = new Date()
const nextEvent = events.find((e) => {
- return e.when.getTime() > now.getTime()
- })
+ return e.when.getTime() > now.getTime();
+ });
const [value, valueHandler] = useState<Partial<Paid>>(order);
- const { url: backendURL } = useBackendContext()
+ const { state } = useSessionContext();
+
const refundurl = stringifyRefundUri({
- merchantBaseUrl: backendURL,
- orderId: order.contract_terms.order_id
- })
- const refundable =
- new Date().getTime() < order.contract_terms.refund_deadline.t_s * 1000;
+ merchantBaseUrl: state.backendUrl.href,
+ orderId: order.contract_terms.order_id,
+ });
const { i18n } = useTranslationContext();
const amount = Amounts.parseOrThrow(order.contract_terms.amount);
@@ -503,15 +516,16 @@ function PaidPage({
textOverflow: "ellipsis",
}}
>
- {nextEvent &&
+ {nextEvent && (
<p>
- <i18n.Translate>Next event in </i18n.Translate> {formatDistance(
+ <i18n.Translate>Next event in </i18n.Translate>{" "}
+ {formatDistance(
nextEvent.when,
new Date(),
// "yyyy/MM/dd HH:mm:ss",
)}
</p>
- }
+ )}
</div>
</div>
</div>
@@ -607,11 +621,11 @@ function UnpaidPage({
order,
}: {
id: string;
- order: MerchantBackend.Orders.CheckPaymentUnpaidResponse;
+ order: TalerMerchantApi.CheckPaymentUnpaidResponse;
}) {
const [value, valueHandler] = useState<Partial<Unpaid>>(order);
const { i18n } = useTranslationContext();
- const [settings] = useSettings()
+ const [settings] = usePreference();
return (
<div>
<section class="hero is-hero-bar">
@@ -659,9 +673,9 @@ function UnpaidPage({
{order.creation_time.t_s === "never"
? "never"
: format(
- new Date(order.creation_time.t_s * 1000),
- datetimeFormatForSettings(settings)
- )}
+ new Date(order.creation_time.t_s * 1000),
+ datetimeFormatForSettings(settings),
+ )}
</p>
</div>
</div>
@@ -764,7 +778,3 @@ export function DetailPage({ id, selected, onRefund, onBack }: Props): VNode {
</Fragment>
);
}
-
-async function copyToClipboard(text: string) {
- return navigator.clipboard.writeText(text);
-}
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/orders/details/Timeline.tsx b/packages/merchant-backoffice-ui/src/paths/instance/orders/details/Timeline.tsx
index 8c863f386..2d62e2252 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/orders/details/Timeline.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/orders/details/Timeline.tsx
@@ -1,6 +1,6 @@
/*
This file is part of GNU Taler
- (C) 2021-2023 Taler Systems S.A.
+ (C) 2021-2024 Taler Systems S.A.
GNU Taler is free software; you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
@@ -16,7 +16,7 @@
import { format } from "date-fns";
import { h } from "preact";
import { useEffect, useState } from "preact/hooks";
-import { datetimeFormatForSettings, useSettings } from "../../../../hooks/useSettings.js";
+import { datetimeFormatForSettings, usePreference } from "../../../../hooks/preference.js";
interface Props {
events: Event[];
@@ -31,7 +31,7 @@ export function Timeline({ events: e }: Props) {
});
events.sort((a, b) => a.when.getTime() - b.when.getTime());
- const [settings] = useSettings();
+ const [settings] = usePreference();
const [state, setState] = useState(events);
useEffect(() => {
const handle = setTimeout(() => {
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/orders/details/index.tsx b/packages/merchant-backoffice-ui/src/paths/instance/orders/details/index.tsx
index 1517a3c42..b28e59b29 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/orders/details/index.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/orders/details/index.tsx
@@ -1,6 +1,6 @@
/*
This file is part of GNU Taler
- (C) 2021-2023 Taler Systems S.A.
+ (C) 2021-2024 Taler Systems S.A.
GNU Taler is free software; you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
@@ -14,55 +14,62 @@
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
import {
- useTranslationContext,
- HttpError,
- ErrorType,
+ HttpStatusCode,
+ TalerError,
+ assertUnreachable,
+} 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 { ErrorLoadingMerchant } from "../../../../components/ErrorLoadingMerchant.js";
import { Loading } from "../../../../components/exception/loading.js";
import { NotificationCard } from "../../../../components/menu/index.js";
-import { MerchantBackend } from "../../../../declaration.js";
-import { useOrderAPI, useOrderDetails } from "../../../../hooks/order.js";
+import { useSessionContext } from "../../../../context/session.js";
+import { useOrderDetails } from "../../../../hooks/order.js";
import { Notification } from "../../../../utils/types.js";
+import { LoginPage } from "../../../login/index.js";
+import { NotFoundPageOrAdminCreate } from "../../../notfound/index.js";
import { DetailPage } from "./DetailPage.js";
-import { HttpStatusCode } from "@gnu-taler/taler-util";
export interface Props {
oid: string;
-
onBack: () => void;
- onUnauthorized: () => VNode;
- onNotFound: () => VNode;
- onLoadError: (error: HttpError<MerchantBackend.ErrorDetail>) => VNode;
}
-export default function Update({
- oid,
- onBack,
- onLoadError,
- onNotFound,
- onUnauthorized,
-}: Props): VNode {
- const { refundOrder } = useOrderAPI();
+export default function Update({ oid, onBack }: Props): VNode {
const result = useOrderDetails(oid);
const [notif, setNotif] = useState<Notification | undefined>(undefined);
+ const { lib: api } = useSessionContext();
+ const { state } = useSessionContext();
const { i18n } = useTranslationContext();
- if (result.loading) return <Loading />;
- if (!result.ok) {
- if (
- result.type === ErrorType.CLIENT &&
- result.status === HttpStatusCode.Unauthorized
- )
- return onUnauthorized();
- if (
- result.type === ErrorType.CLIENT &&
- result.status === HttpStatusCode.NotFound
- )
- return onNotFound();
- return onLoadError(result);
+ if (!result) return <Loading />;
+ if (result instanceof TalerError) {
+ return <ErrorLoadingMerchant error={result} />;
+ }
+ if (result.type === "fail") {
+ switch (result.case) {
+ case HttpStatusCode.NotFound: {
+ return <NotFoundPageOrAdminCreate />;
+ }
+ case HttpStatusCode.BadGateway: {
+ return <div>Failed to obtain a response from the exchange</div>;
+ }
+ case HttpStatusCode.GatewayTimeout: {
+ return (
+ <div>The merchant's interaction with the exchange took too long</div>
+ );
+ }
+ case HttpStatusCode.Unauthorized: {
+ return <LoginPage />
+ }
+ default: {
+ assertUnreachable(result);
+ }
+ }
}
return (
@@ -72,8 +79,12 @@ export default function Update({
<DetailPage
onBack={onBack}
id={oid}
- onRefund={(id, value) =>
- refundOrder(id, value)
+ onRefund={(id, value) => {
+ if (state.status !== "loggedIn") {
+ return;
+ }
+ api.instance
+ .addRefund(state.token, id, value)
.then(() =>
setNotif({
message: i18n.str`refund created successfully`,
@@ -86,9 +97,9 @@ export default function Update({
type: "ERROR",
description: error.message,
}),
- )
- }
- selected={result.data}
+ );
+ }}
+ selected={result.body}
/>
</Fragment>
);