taler-typescript-core

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

commit 92180336e4c73fd51b6661374cf01f99874dd7d9
parent d4d3b5c989f4a522a5355dadf70c98d7c4077c14
Author: Sebastian <sebasjm@taler-systems.com>
Date:   Tue,  3 Feb 2026 17:00:57 -0300

fix #10775

Diffstat:
Mpackages/merchant-backoffice-ui/src/hooks/transfer.ts | 2+-
Mpackages/merchant-backoffice-ui/src/paths/instance/orders/details/DetailPage.tsx | 89++++++++++++++++++++++++++++++++++++++++++++++++-------------------------------
Mpackages/merchant-backoffice-ui/src/paths/instance/orders/details/Timeline.tsx | 30++++++++++++++++++++----------
Mpackages/merchant-backoffice-ui/src/paths/instance/transfers/list/Table.tsx | 10+++++-----
Mpackages/merchant-backoffice-ui/src/paths/instance/transfers/list/index.tsx | 2+-
Mpackages/merchant-backoffice-ui/src/utils/constants.ts | 2+-
Mpackages/taler-util/src/types-taler-merchant.ts | 6++++++
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> =>