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:
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