commit 92180336e4c73fd51b6661374cf01f99874dd7d9
parent d4d3b5c989f4a522a5355dadf70c98d7c4077c14
Author: Sebastian <sebasjm@taler-systems.com>
Date: Tue, 3 Feb 2026 17:00:57 -0300
fix #10775
Diffstat:
7 files changed, 88 insertions(+), 53 deletions(-)
diff --git a/packages/merchant-backoffice-ui/src/hooks/transfer.ts b/packages/merchant-backoffice-ui/src/hooks/transfer.ts
@@ -91,7 +91,7 @@ export function useInstanceIncomingTransfers(
data.body.incoming,
args?.position,
updatePosition,
- (d) => String(d.wtid),
+ (d) => String(d.expected_transfer_serial_id),
PAGINATED_LIST_REQUEST,
);
}
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
@@ -352,11 +352,11 @@ function ClaimedPage({
{order.contract_terms.timestamp.t_s === "never"
? i18n.str`Never`
: format(
- new Date(
- order.contract_terms.timestamp.t_s * 1000,
- ),
- datetimeFormatForPreferences(preferences),
- )}
+ new Date(
+ order.contract_terms.timestamp.t_s * 1000,
+ ),
+ datetimeFormatForPreferences(preferences),
+ )}
</p>
</div>
</div>
@@ -493,8 +493,29 @@ function PaidPage({
AbsoluteTime.fromProtocolTimestamp(b.timestamp),
),
);
+
+ const orderAmounts = getOrderAmountAndWirefee(order);
+ if (orderAmounts === "v1-without-index") {
+ return (
+ <i18n.Translate>
+ The contract terms have a v1 order without choices_index.
+ </i18n.Translate>
+ );
+ }
+ if (orderAmounts === "v1-wrong-index") {
+ return (
+ <i18n.Translate>
+ The contract terms have a v1 order with a bad choices_index.
+ </i18n.Translate>
+ );
+ }
+ const { amount, wireFee } = orderAmounts;
+
+
+ let totalRefunded = Amounts.zeroOfAmount(amount)
sortedOrders.reduce(mergeRefunds, []).forEach((e) => {
if (e.timestamp.t_s === "never") return;
+ totalRefunded = Amounts.add(totalRefunded, e.amount).amount
if (e.pending) {
if (wireDeadlineInThePast) {
events.push({
@@ -524,23 +545,6 @@ function PaidPage({
}
});
- const orderAmounts = getOrderAmountAndWirefee(order);
- if (orderAmounts === "v1-without-index") {
- return (
- <i18n.Translate>
- The contract terms have a v1 order without choices_index.
- </i18n.Translate>
- );
- }
- if (orderAmounts === "v1-wrong-index") {
- return (
- <i18n.Translate>
- The contract terms have a v1 order with a bad choices_index.
- </i18n.Translate>
- );
- }
- const { amount } = orderAmounts;
-
const wireMap: Record<
string,
{
@@ -551,8 +555,10 @@ function PaidPage({
> = {};
let hasUnconfirmedWireTransfer = false;
+ let totalWired = Amounts.zeroOfAmount(amount)
if (order.wire_details.length) {
order.wire_details.forEach((w) => {
+ totalWired = Amounts.add(totalWired, w.amount).amount
if (!wireMap[w.wtid]) {
const info = {
time:
@@ -581,13 +587,26 @@ function PaidPage({
type: info.confirmed ? "wired-confirmed" : "wired-pending",
});
});
- } else {
- 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: i18n.str`wire deadline`,
- type: "wire-deadline",
- });
+ const totalFee = Amounts.add(totalRefunded, wireFee).amount
+ const shouldBeWired = Amounts.sub(amount, totalFee).amount
+ const notAllHasBeenWired = Amounts.cmp(totalWired, shouldBeWired) < 0 // FIXME: should be updated with the protocol see #10971
+ const w_deadline = AbsoluteTime.fromProtocolTimestamp(order.contract_terms.wire_transfer_deadline)
+ if (w_deadline.t_ms !== "never") {
+ if (!AbsoluteTime.isExpired(w_deadline)) {
+ events.push({
+ when: new Date(w_deadline.t_ms),
+ description: i18n.str`wire deadline`,
+ type: "wire-deadline",
+ });
+ } else {
+ if (notAllHasBeenWired) {
+ events.push({
+ when: new Date(w_deadline.t_ms),
+ description: i18n.str`wire deadline`,
+ type: "wired-overdue",
+ });
+ }
+ }
}
}
@@ -603,9 +622,9 @@ function PaidPage({
!order.refunded || wireDeadlineInThePast
? undefined
: stringifyRefundUri({
- merchantBaseUrl: state.backendUrl.href as HostPortPath,
- orderId: order.contract_terms.order_id,
- });
+ merchantBaseUrl: state.backendUrl.href as HostPortPath,
+ orderId: order.contract_terms.order_id,
+ });
const refund_taken = order.refund_details.reduce((prev, cur) => {
if (cur.pending) return prev;
@@ -864,9 +883,9 @@ function UnpaidPage({
{order.creation_time.t_s === "never"
? i18n.str`Never`
: format(
- new Date(order.creation_time.t_s * 1000),
- datetimeFormatForPreferences(preferences),
- )}
+ new Date(order.creation_time.t_s * 1000),
+ datetimeFormatForPreferences(preferences),
+ )}
</p>
</div>
</div>
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
@@ -78,15 +78,6 @@ export function Timeline({ events: e }: Props) {
<i class="mdi mdi-flag" />
</div>
);
- case "wire-deadline":
- return (
- <div
- class="timeline-marker is-icon "
- data-tooltip={i18n.str`This is when the wire transfer is going to be executed by the payment service provider.`}
- >
- <i class="mdi mdi-flag" />
- </div>
- );
case "delivery":
return (
<div class="timeline-marker is-icon ">
@@ -99,11 +90,20 @@ export function Timeline({ events: e }: Props) {
<i class="mdi mdi-flag " />
</div>
);
+ case "wire-deadline":
+ return (
+ <div
+ class="timeline-marker is-icon "
+ data-tooltip={i18n.str`This is when the wire transfer is going to be executed by the payment service provider.`}
+ >
+ <i class="mdi mdi-flag" />
+ </div>
+ );
case "wired-pending":
return (
<div
class="timeline-marker is-icon is-warning"
- data-tooltip={i18n.str`This is when the wire transfer is going to be executed by the payment service provider.`}
+ data-tooltip={i18n.str`This wire transfer has been notified by the payment service provider.`}
>
<i class="mdi mdi-cash" />
</div>
@@ -112,6 +112,15 @@ export function Timeline({ events: e }: Props) {
return (
<div
class="timeline-marker is-icon is-success"
+ data-tooltip={i18n.str`This wire transfer has been notified manually or by the banking system.`}
+ >
+ <i class="mdi mdi-cash" />
+ </div>
+ );
+ case "wired-overdue":
+ return (
+ <div
+ class="timeline-marker is-icon is-danger"
data-tooltip={i18n.str`This wire transfer has been notified by the payment service provider.`}
>
<i class="mdi mdi-cash" />
@@ -186,6 +195,7 @@ export interface Event {
| "wire-deadline"
| "wired-pending"
| "wired-confirmed"
+ | "wired-overdue"
| "delivery"
| "now";
}
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/transfers/list/Table.tsx b/packages/merchant-backoffice-ui/src/paths/instance/transfers/list/Table.tsx
@@ -166,7 +166,7 @@ export function CardTableVerified({
<span class="icon">
<i class="mdi mdi-arrow-left-right" />
</span>
- <i18n.Translate>Verified wire transfers</i18n.Translate>
+ <i18n.Translate>Confirmed wire transfers into bank account</i18n.Translate>
</p>
</header>
<div class="card-content">
@@ -188,13 +188,13 @@ export function CardTableVerified({
<thead>
<tr>
<th>
- <i18n.Translate>ID</i18n.Translate>
+ <i18n.Translate>Date</i18n.Translate>
</th>
<th>
<i18n.Translate>Amount</i18n.Translate>
</th>
<th>
- <i18n.Translate>Executed on</i18n.Translate>
+ <i18n.Translate>ID</i18n.Translate>
</th>
</tr>
</thead>
@@ -202,8 +202,6 @@ export function CardTableVerified({
{transfers.map((i) => {
return (
<tr key={i.id}>
- <td title={i.wtid}>{i.wtid.substring(0, 16)}...</td>
- <td>{i.credit_amount}</td>
<td>
{i.execution_time
? i.execution_time.t_s == "never"
@@ -214,6 +212,8 @@ export function CardTableVerified({
)
: i18n.str`unknown`}
</td>
+ <td>{i.credit_amount}</td>
+ <td title={i.wtid} style={{wordBreak:"break-word"}}>{i.wtid}</td>
</tr>
);
})}
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/transfers/list/index.tsx b/packages/merchant-backoffice-ui/src/paths/instance/transfers/list/index.tsx
@@ -238,9 +238,9 @@ export default function ListTransfer({}: Props): VNode {
}}
onLoadMoreBefore={show.loadFirst}
onLoadMoreAfter={show.loadNext}
- // onShowAll={() => setFilter(undefined)}
onShowUnverified={() => setFilter(false)}
onShowVerified={() => setFilter(true)}
+ // onShowAll={() => setFilter(undefined)}
// isAllTransfers={isAllTransfers}
isVerifiedTransfers={form.verified}
isNonVerifiedTransfers={!form.verified}
diff --git a/packages/merchant-backoffice-ui/src/utils/constants.ts b/packages/merchant-backoffice-ui/src/utils/constants.ts
@@ -35,7 +35,7 @@ export const CROCKFORD_BASE32_REGEX =
export const URL_REGEX =
/^((https?:)(\/\/\/?)([\w]*(?::[\w]*)?@)?([\d\w\.-]+)(?::(\d+))?)\/$/;
-export const PAGINATED_LIST_SIZE = 5;
+export const PAGINATED_LIST_SIZE = 2;
// when doing paginated request, ask for one more
// and use it to know if there are more to request
export const PAGINATED_LIST_REQUEST = PAGINATED_LIST_SIZE + 1;
diff --git a/packages/taler-util/src/types-taler-merchant.ts b/packages/taler-util/src/types-taler-merchant.ts
@@ -3040,6 +3040,11 @@ export interface TransactionWireTransfer {
// Was this transfer confirmed by the merchant via the
// POST /transfers API, or is it merely claimed by the exchange?
confirmed: boolean;
+
+ // Transfer serial ID of this wire transfer, useful as
+ // ``offset`` for the GET ``/private/incoming`` endpoint.
+ // Since **v25**.
+ expected_transfer_serial_id?: Integer;
}
export interface TransactionWireReport {
@@ -4799,6 +4804,7 @@ export const codecForTransactionWireTransfer =
.property("execution_time", codecForTimestamp)
.property("amount", codecForAmountString())
.property("confirmed", codecForBoolean())
+ .property("expected_transfer_serial_id", codecOptional(codecForNumber()))
.build("TalerMerchantApi.TransactionWireTransfer");
export const codecForTransactionWireReport = (): Codec<TransactionWireReport> =>