View Issue Details
| ID | Project | Category | View Status | Date Submitted | Last Update |
|---|---|---|---|---|---|
| 0012074 | Taler | wallet (WebExtension) | public | 2026-11-11 01:33 | 2026-11-11 01:33 |
| Reporter | bot-cg [agent] | Assigned To | Resolution deadline | ||
| Implementer | Reviewer | ||||
| Release signed by | Deployed by | ||||
| Verifier | Communicator | CM_7 | |||
| Data protection authority notification required | No | ||||
| CSIRT notification required | No | ||||
| Estimated effort (hours) | Actual effort (hours) | 0 | |||
| Fast-track | No | ||||
| Work package | none | ||||
| Priority | normal | Severity | minor | Reproducibility | have not tried |
| Status | new | Resolution | open | ||
| Summary | 0012074: WebExtension: Deposit / Pay request with too little money says only "The wallet could not prepare …" -- the insufficient-balance wording exists but is used only for merchant payments (patch attached) | ||||
| Description | When a deposit or the payment of a peer request (taler://pay-pull) is refused because the wallet does not have enough money, the web wallet shows only "The wallet could not prepare this deposit." / "The wallet could not prepare this peer payment." The reason (WALLET_DEPOSIT_GROUP_INSUFFICIENT_BALANCE / WALLET_PEER_PULL_PAYMENT_INSUFFICIENT_BALANCE) appears only inside the collapsed "Technical details". wallet-core attaches insufficientBalanceDetails to these errors, and the web UI already turns exactly that structure into translated sentences -- but only for merchant payments (7050). Screenshots (demo backend, wallet holding KUDOS:20): - before-web-1-deposit.png, before-web-2-deposit-details.png: deposit of KUDOS:50 -> "The wallet could not prepare this deposit." - before-web-3-paypull.png: invoice of KUDOS:50 -> "The wallet could not prepare this peer payment." + "Withdraw funds" - before-web-4-deposit-iban.png: deposit to an IBAN, which no exchange of this wallet serves -> the same generic sentence - before-android-1-deposit.png (reference): Android shows "Available for transfer: ク23.00" and "Insufficient balance" under the amount - before-android-2-paypull.png / -3-paypull-behind.png, before-ios-2-paypull.png: for the invoice Android says "Insufficient balance" (behind an error sheet with the code), iOS shows "Internal core error" (0011934); the iOS deposit screen (DepositAmountView.swift:100) says "You don't have enough <currency>." Code (taler-typescript-core origin/master bf2e571a2, packages/wallet-webui/src/): - ui/error.ts:22-79: errorMessage() maps WALLET_PAY_MERCHANT_INSUFFICIENT_BALANCE to insufficientBalanceMessage(); 7026 / 7027 / 7028 fall to the fallback - ui/error.ts:81-101: errorGuidance() likewise only for 7050 - routes/App.tsx:1362-1380, 1416-1436 (deposit review/confirm), 2257-2271 (pay-pull prepare), 2388-2394 (pay-pull confirm) pass these errors through walletErrorHint() wallet-core: deposits.ts:2775-2781 / 2886-2892 (7026 with insufficientBalanceDetails), pay-peer-pull-debit.ts:2087-2092 / 1797-1802 (7028). Patch (attached, web-insufficient-balance-wording.patch, ui/error.ts + test/error.test.ts): - 7026, 7027 and 7028 are handled like 7050: the existing insufficientBalanceMessage() and the existing "Withdraw funds or choose another payment option, then try again." guidance. - A deposit refused with causeHint merchant-deposit-insufficient (wallet-core's verdict when no exchange can wire to that kind of account, e.g. an IBAN on an x-taler-bank exchange) would otherwise read "The available funds cannot be deposited by this merchant." plus withdraw advice; it gets "The exchanges of this wallet cannot deposit to this type of bank account." and no guidance. This is the only new string. - Two unit tests for the mapping. Verification: Chrome extension build; after-web-1-deposit.png and after-web-3-paypull.png: "The wallet has KUDOS:20 available, which is not enough for KUDOS:50." + guidance; after-web-4-deposit-iban.png: "The exchanges of this wallet cannot deposit to this type of bank account." test/error.test.ts 3/3, error-presentation 4/4, i18n-catalog tests pass (compiled with --noEmitOnError false, since master has 29 pre-existing type errors in other test files; none in the touched files). Assisted by LLM | ||||
| Steps To Reproduce | 1. Withdraw KUDOS:20 from https://bank.demo.taler.net/. 2. Deposit KUDOS:50 to an x-taler-bank account (e.g. open payto://x-taler-bank/bank.demo.taler.net/<account>?receiver-name=X) -> "Review deposit". 3. Let another wallet create an invoice for KUDOS:50 (taler://pay-pull/...) and open it. 4. Both show only "The wallet could not prepare …". | ||||
| Tags | No tags attached. | ||||
| Attached Files | web-insufficient-balance-wording.patch (6,149 bytes)
diff --git a/packages/wallet-webui/src/ui/error.ts b/packages/wallet-webui/src/ui/error.ts
index 1450c2531..023c27ac5 100644
--- a/packages/wallet-webui/src/ui/error.ts
+++ b/packages/wallet-webui/src/ui/error.ts
@@ -19,7 +19,28 @@ export function localError(message: string): ErrorPresentation {
return { message };
}
+function insufficientBalanceDetails(
+ detail: TalerErrorDetail,
+): PaymentInsufficientBalanceDetails | undefined {
+ return (
+ detail as {
+ insufficientBalanceDetails?: PaymentInsufficientBalanceDetails;
+ }
+ ).insufficientBalanceDetails;
+}
+
+/** A deposit refused because no exchange can wire to this kind of account. */
+function depositTargetUnsupported(detail: TalerErrorDetail): boolean {
+ return (
+ detail.code === TalerErrorCode.WALLET_DEPOSIT_GROUP_INSUFFICIENT_BALANCE &&
+ insufficientBalanceDetails(detail)?.causeHint ===
+ InsufficientBalanceHint.MerchantDepositInsufficient
+ );
+}
+
function errorMessage(detail: TalerErrorDetail, fallback: string): string {
+ if (depositTargetUnsupported(detail))
+ return /* Translators: A deposit sends digital cash from this wallet to an external account, usually a bank account; it does not add funds to the wallet. */ i18n.str`The exchanges of this wallet cannot deposit to this type of bank account.`;
switch (detail.code) {
case TalerErrorCode.WALLET_ORDER_ALREADY_CLAIMED:
return /* Translators: Claiming a merchant order assigns it to a wallet for payment; it does not mean that payment has completed. */ i18n.str`This order was already claimed by another wallet.`;
@@ -28,13 +49,10 @@ function errorMessage(detail: TalerErrorDetail, fallback: string): string {
case TalerErrorCode.WALLET_MERCHANT_ORDER_NOT_FOUND:
return i18n.str`The merchant order no longer exists or has expired.`;
case TalerErrorCode.WALLET_PAY_MERCHANT_INSUFFICIENT_BALANCE:
- return insufficientBalanceMessage(
- (
- detail as {
- insufficientBalanceDetails?: PaymentInsufficientBalanceDetails;
- }
- ).insufficientBalanceDetails,
- );
+ case TalerErrorCode.WALLET_DEPOSIT_GROUP_INSUFFICIENT_BALANCE:
+ case TalerErrorCode.WALLET_PEER_PUSH_PAYMENT_INSUFFICIENT_BALANCE:
+ case TalerErrorCode.WALLET_PEER_PULL_PAYMENT_INSUFFICIENT_BALANCE:
+ return insufficientBalanceMessage(insufficientBalanceDetails(detail));
case TalerErrorCode.WALLET_NETWORK_ERROR:
return i18n.str`The wallet could not reach the service.`;
case TalerErrorCode.WALLET_HTTP_REQUEST_GENERIC_TIMEOUT:
@@ -79,6 +97,7 @@ function errorMessage(detail: TalerErrorDetail, fallback: string): string {
}
function errorGuidance(detail: TalerErrorDetail): string | undefined {
+ if (depositTargetUnsupported(detail)) return undefined;
switch (detail.code) {
case TalerErrorCode.WALLET_ORDER_ALREADY_CLAIMED:
return i18n.str`Return to the merchant and request a new order.`;
@@ -87,6 +106,9 @@ function errorGuidance(detail: TalerErrorDetail): string | undefined {
case TalerErrorCode.WALLET_MERCHANT_ORDER_NOT_FOUND:
return i18n.str`Return to the merchant and create a new order.`;
case TalerErrorCode.WALLET_PAY_MERCHANT_INSUFFICIENT_BALANCE:
+ case TalerErrorCode.WALLET_DEPOSIT_GROUP_INSUFFICIENT_BALANCE:
+ case TalerErrorCode.WALLET_PEER_PUSH_PAYMENT_INSUFFICIENT_BALANCE:
+ case TalerErrorCode.WALLET_PEER_PULL_PAYMENT_INSUFFICIENT_BALANCE:
return /* Translators: Withdrawing adds digital cash to this wallet, usually funded by a bank transfer; it does not cash out the wallet to a bank account. */ i18n.str`Withdraw funds or choose another payment option, then try again.`;
case TalerErrorCode.WALLET_NETWORK_ERROR:
case TalerErrorCode.WALLET_HTTP_REQUEST_GENERIC_TIMEOUT:
diff --git a/packages/wallet-webui/test/error.test.ts b/packages/wallet-webui/test/error.test.ts
index ec1288885..cc418b5b0 100644
--- a/packages/wallet-webui/test/error.test.ts
+++ b/packages/wallet-webui/test/error.test.ts
@@ -1,6 +1,11 @@
import assert from "node:assert/strict";
import test from "node:test";
-import { errorFromException } from "../src/ui/error.js";
+import {
+ AbsoluteTime,
+ InsufficientBalanceHint,
+ TalerErrorCode,
+} from "@gnu-taler/taler-util";
+import { errorFromException, walletCoreError } from "../src/ui/error.js";
test("generic exceptions do not replace a translated user-facing fallback", () => {
const error = errorFromException(
@@ -9,3 +14,49 @@ test("generic exceptions do not replace a translated user-facing fallback", () =
);
assert.equal(error.message, "Operation failed");
});
+
+test("deposit and peer-payment shortfalls are explained like merchant payments", () => {
+ for (const code of [
+ TalerErrorCode.WALLET_DEPOSIT_GROUP_INSUFFICIENT_BALANCE,
+ TalerErrorCode.WALLET_PEER_PUSH_PAYMENT_INSUFFICIENT_BALANCE,
+ TalerErrorCode.WALLET_PEER_PULL_PAYMENT_INSUFFICIENT_BALANCE,
+ ]) {
+ const error = walletCoreError(
+ {
+ code,
+ when: AbsoluteTime.now(),
+ insufficientBalanceDetails: {
+ amountRequested: "KUDOS:50",
+ balanceAvailable: "KUDOS:20",
+ causeHint: InsufficientBalanceHint.WalletBalanceAvailableInsufficient,
+ },
+ },
+ "Operation failed",
+ );
+ assert.equal(
+ error.message,
+ "The wallet has KUDOS:20 available, which is not enough for KUDOS:50.",
+ );
+ assert.ok(error.guidance);
+ }
+});
+
+test("a deposit to an account type no exchange serves is not called a shortfall", () => {
+ const error = walletCoreError(
+ {
+ code: TalerErrorCode.WALLET_DEPOSIT_GROUP_INSUFFICIENT_BALANCE,
+ when: AbsoluteTime.now(),
+ insufficientBalanceDetails: {
+ amountRequested: "KUDOS:1",
+ balanceAvailable: "KUDOS:20",
+ causeHint: InsufficientBalanceHint.MerchantDepositInsufficient,
+ },
+ },
+ "Operation failed",
+ );
+ assert.equal(
+ error.message,
+ "The exchanges of this wallet cannot deposit to this type of bank account.",
+ );
+ assert.equal(error.guidance, undefined);
+});
| ||||
| Date Modified | Username | Field | Change |
|---|---|---|---|
| 2026-11-11 01:33 | bot-cg | New Issue | |
| 2026-11-11 01:33 | bot-cg | File Added: before-web-1-deposit.png | |
| 2026-11-11 01:33 | bot-cg | File Added: before-web-2-deposit-details.png | |
| 2026-11-11 01:33 | bot-cg | File Added: before-web-3-paypull.png | |
| 2026-11-11 01:33 | bot-cg | File Added: before-web-4-deposit-iban.png | |
| 2026-11-11 01:33 | bot-cg | File Added: before-android-1-deposit.png | |
| 2026-11-11 01:33 | bot-cg | File Added: before-android-2-paypull.png | |
| 2026-11-11 01:33 | bot-cg | File Added: before-android-3-paypull-behind.png | |
| 2026-11-11 01:33 | bot-cg | File Added: before-ios-2-paypull.png | |
| 2026-11-11 01:33 | bot-cg | File Added: after-web-1-deposit.png | |
| 2026-11-11 01:33 | bot-cg | File Added: after-web-3-paypull.png | |
| 2026-11-11 01:33 | bot-cg | File Added: after-web-4-deposit-iban.png | |
| 2026-11-11 01:33 | bot-cg | File Added: web-insufficient-balance-wording.patch |