taler-typescript-core

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

commit acf564b9e4996043b8ce9cd8dcdca1fb291d211a
parent 91d40b9d3193d99ec72476b5ab12f4ec8cc91c83
Author: Florian Dold <dold@taler.net>
Date:   Thu, 30 Jul 2026 14:18:19 +0200

util: add typed details for the new wallet error codes

Also adds Paytos.parseOrThrow, so the callers that only checked whether a
payto URI parsed can raise a coded error and keep the reason for the
rejection instead of discarding it.

Diffstat:
Mpackages/taler-util/src/errors.ts | 84+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Mpackages/taler-util/src/payto.ts | 26+++++++++++++++++++++++++-
2 files changed, 107 insertions(+), 3 deletions(-)

diff --git a/packages/taler-util/src/errors.ts b/packages/taler-util/src/errors.ts @@ -56,8 +56,12 @@ export interface DetailsMap { claimUrl: string; }; [TalerErrorCode.WALLET_PURCHASE_NOT_FOUND]: { - merchantBaseUrl: string; - orderId: string; + /** + * Optional: call sites that only know the wallet's proposal ID cannot + * name the order the purchase would have belonged to. + */ + merchantBaseUrl?: string; + orderId?: string; }; [TalerErrorCode.WALLET_ORDER_ALREADY_PAID]: { orderId: string; @@ -72,9 +76,21 @@ export interface DetailsMap { baseUrlForDownload: string; baseUrlFromContractTerms: string; }; + /** + * @deprecated superseded by {@link TalerErrorCode.WALLET_TALER_URI_MALFORMED}, + * which covers every taler:// URI action instead of just pay. + */ [TalerErrorCode.WALLET_INVALID_TALER_PAY_URI]: { talerPayUri: string; }; + [TalerErrorCode.WALLET_TALER_URI_MALFORMED]: { + talerUri?: string; + /** + * The taler:// action that the request expected, if the URI parsed + * but denotes something else. + */ + expectedAction?: string; + }; [TalerErrorCode.WALLET_UNEXPECTED_REQUEST_ERROR]: { requestUrl: string; requestMethod: string; @@ -197,6 +213,70 @@ export interface DetailsMap { client: string; server: string; }; + [TalerErrorCode.WALLET_CORE_API_BAD_REQUEST]: { + /** + * Name of the request parameter that is malformed or missing, + * if the problem can be attributed to a single parameter. + */ + parameter?: string; + }; + [TalerErrorCode.WALLET_TRANSACTION_NOT_FOUND]: { + transactionId: string; + }; + [TalerErrorCode.WALLET_TRANSACTION_ACTION_UNSUPPORTED]: { + transactionId: string; + action: string; + }; + [TalerErrorCode.WALLET_KYC_LIMIT_EXCEEDED]: { + /** + * Exchange that imposes the limit. Absent when the operation spans + * several exchanges, as a deposit can. + */ + exchangeBaseUrl?: string; + /** + * Amount that was requested and that exceeds the limit. + */ + requestedAmount: string; + }; + [TalerErrorCode.WALLET_NO_SUITABLE_EXCHANGE]: { + currency?: string; + /** + * Wire method that no known exchange supports, if the + * operation was restricted to one. + */ + wireMethod?: string; + }; + [TalerErrorCode.WALLET_BANK_ACCOUNT_NOT_FOUND]: { + bankAccountId: string; + }; + [TalerErrorCode.WALLET_PEER_CONTRACT_NOT_FOUND]: empty; + [TalerErrorCode.WALLET_PEER_PUSH_CREDIT_PURSE_GONE]: empty; + [TalerErrorCode.WALLET_PEER_PULL_DEBIT_ALREADY_PAID]: empty; + [TalerErrorCode.WALLET_TOKENS_IN_USE]: { + tokenFamilyHash: string; + }; + [TalerErrorCode.WALLET_DB_BACKEND_UNSUPPORTED]: { + /** + * Name of the database backend that does not support the operation. + */ + backend: string; + }; + [TalerErrorCode.WALLET_MAILBOX_UNAVAILABLE]: { + mailboxBaseUrl?: string; + }; + [TalerErrorCode.WALLET_ALIAS_REGISTRATION_FAILED]: { + directoryBaseUrl?: string; + }; + [TalerErrorCode.WALLET_CONTRACT_TERMS_UNSUPPORTED]: empty; + [TalerErrorCode.WALLET_EXCHANGE_SIGNATURE_INVALID]: { + exchangeBaseUrl?: string; + }; + [TalerErrorCode.WALLET_EXCHANGE_ENTRY_OUTDATED]: { + exchangeBaseUrl: string; + }; + [TalerErrorCode.WALLET_PEER_PULL_PAYMENT_INSUFFICIENT_BALANCE]: { + insufficientBalanceDetails: PaymentInsufficientBalanceDetails; + }; } type ErrBody<Y> = Y extends keyof DetailsMap ? DetailsMap[Y] : empty; diff --git a/packages/taler-util/src/payto.ts b/packages/taler-util/src/payto.ts @@ -18,7 +18,8 @@ import { BitcoinBech32 } from "./bech32.js"; import { BitcoinSewgit } from "./segwit_addr.js"; import { generateFakeSegwitAddress } from "./bitcoin.js"; import { Codec, Context, DecodingError, renderContext } from "./codec.js"; -import { assertUnreachable } from "./errors.js"; +import { assertUnreachable, TalerError } from "./errors.js"; +import { TalerErrorCode } from "./taler-error-codes.js"; import { IbanString, parseIban, ParseIbanError } from "./iban.js"; import { AmountString } from "./index.node.js"; import { Result, ResultError, ResultOk } from "./result.js"; @@ -541,6 +542,29 @@ export namespace Paytos { return Result.unpack(fromString(p)); } + /** + * Parse a payto:// URI that came from outside (a client request, a server + * response, the database), and raise a coded error if it does not parse. + * + * Callers that already know the URI is well-formed should use + * {@link asString}; this one keeps the reason for the rejection in the hint + * so that a UI can tell the user what is actually wrong with the account. + */ + export function parseOrThrow( + s: string, + opts: ParsePaytoOptions = {}, + ): Paytos.URI { + const res = fromString(s, opts); + if (Result.isError(res)) { + throw TalerError.fromDetail( + TalerErrorCode.GENERIC_PAYTO_URI_MALFORMED, + {}, + `payto URI is malformed (${PaytoParseError[res.error]})`, + ); + } + return res.value; + } + export interface ParsePaytoOptions { /** * do not check path component format