taler-typescript-core

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

commit e09adaa044f648e5d0e5b7a74756b5944f2dd6e2
parent 1d03bc4793dc2d78694a88eee9067339bac5ade5
Author: Florian Dold <dold@taler.net>
Date:   Wed, 26 Aug 2026 01:49:23 +0200

wallet-webui: preserve terminal payment error details

Diffstat:
Mpackages/wallet-webui/src/routes/App.tsx | 17+++++++++--------
Mpackages/wallet-webui/src/routes/transaction-model.ts | 14+++++++++-----
Mpackages/wallet-webui/test/transaction-model.test.ts | 33+++++++++++++++++++++++++++++++++
3 files changed, 51 insertions(+), 13 deletions(-)

diff --git a/packages/wallet-webui/src/routes/App.tsx b/packages/wallet-webui/src/routes/App.tsx @@ -123,6 +123,7 @@ import { } from "./withdrawal-model.js"; import { transactionDetailView, + transactionErrorPresentation, transactionHistoryView, transactionStateView, type TransactionUiAction, @@ -3749,6 +3750,10 @@ function PaymentRoute() { return <PaymentDialog transaction={transaction} actionId={actionId} />; const done = transaction.txState.major === TransactionMajorState.Done; const pending = transaction.txState.major === TransactionMajorState.Pending; + const terminalError = transactionErrorPresentation( + transaction, + i18n.str`Payment is ${transactionStateView(transaction).label}.`, + ); return ( <PaymentScreen state={done ? `done` : pending ? "pending" : "error"} @@ -3764,14 +3769,10 @@ function PaymentRoute() { error={ done || pending ? undefined - : transaction.error - ? walletCoreError( - transaction.error, - i18n.str`Payment is ${transactionStateView(transaction).label}.`, - ) - : localError( - i18n.str`Payment is ${transactionStateView(transaction).label}.`, - ) + : (terminalError ?? + localError( + i18n.str`Payment is ${transactionStateView(transaction).label}.`, + )) } onSelectChoice={() => {}} onConfirm={() => {}} diff --git a/packages/wallet-webui/src/routes/transaction-model.ts b/packages/wallet-webui/src/routes/transaction-model.ts @@ -344,13 +344,18 @@ function timestampView( }; } -function errorPresentation( +/** Preserve the structured reason for any wallet-core transaction failure. */ +export function transactionErrorPresentation( transaction: Transaction, + fallback?: string, ): ErrorPresentation | undefined { const detail = transaction.error ?? transaction.failReason ?? transaction.abortReason; if (!detail) return undefined; - return walletCoreError(detail, i18n.str`Wallet error ${detail.code}`); + return walletCoreError( + detail, + fallback ?? i18n.str`Wallet error ${detail.code}`, + ); } function fieldsFor(transaction: Transaction): TransactionDetailField[] { @@ -779,6 +784,7 @@ export function transactionDetailView( transaction.txState.minor === TransactionMinorState.Ready ? transaction.talerUri : undefined; + const error = transactionErrorPresentation(transaction); return { ...base, ...(transaction.localTransactionId @@ -788,9 +794,7 @@ export function transactionDetailView( fields: fieldsFor(transaction), ...(technicalFields.length ? { technicalFields } : {}), actions, - ...(errorPresentation(transaction) - ? { error: errorPresentation(transaction) } - : {}), + ...(error ? { error } : {}), ...(externalAction ? { externalAction } : {}), ...(continuationAction ? { continuationAction } : {}), ...(peerShareUri ? { peerShareUri } : {}), diff --git a/packages/wallet-webui/test/transaction-model.test.ts b/packages/wallet-webui/test/transaction-model.test.ts @@ -16,6 +16,7 @@ import { } from "@gnu-taler/taler-util"; import { transactionDetailView, + transactionErrorPresentation, transactionHistoryView, } from "../src/routes/transaction-model.js"; @@ -232,6 +233,38 @@ test("transaction details preserve complete wallet-core errors", () => { assert.strictEqual(view.error?.detail, detail); }); +test("transaction errors preserve every wallet-core terminal reason", () => { + const active = { + code: TalerErrorCode.WALLET_NETWORK_ERROR, + hint: "active retry error", + }; + const failed = { + code: TalerErrorCode.WALLET_ORDER_ALREADY_CLAIMED, + hint: "terminal failure", + nestedFutureField: { kept: true }, + }; + const aborted = { + code: TalerErrorCode.WALLET_ORDER_ALREADY_PAID, + hint: "terminal abort", + }; + + assert.strictEqual( + transactionErrorPresentation(payment({ failReason: failed }))?.detail, + failed, + ); + assert.strictEqual( + transactionErrorPresentation(payment({ abortReason: aborted }))?.detail, + aborted, + ); + assert.strictEqual( + transactionErrorPresentation( + payment({ error: active, failReason: failed, abortReason: aborted }), + )?.detail, + active, + ); + assert.equal(transactionErrorPresentation(payment()), undefined); +}); + test("payment details include the Penpot order summary when contract terms are requested", () => { const view = transactionDetailView( payment({