View Issue Details
| ID | Project | Category | View Status | Date Submitted | Last Update |
|---|---|---|---|---|---|
| 0012069 | Taler | wallet (Android App) | public | 2026-11-11 01:27 | 2026-11-11 01:36 |
| 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 | 0012069: Android: errors are shown as "Error" + wallet-core's English hint -- no translated message even for "could not reach the service"; "Unsupported URI" is a hard-coded English literal (patch attached) | ||||
| Description | The Android wallet shows every wallet-core error as the word "Error" followed by wallet-core's English diagnostic text (TalerErrorInfo.userFacingMsg = hint/message). Nothing is mapped from the error code to a translated explanation, not even the most common case, a service that cannot be reached. The WebExtension maps about 25 codes to translated messages plus guidance, and iOS at least recognises network codes ("Network problem" / "Couldn't establish a connection"). What the user sees (developer mode off): 1. wallet-core reports WALLET_NETWORK_ERROR (7003, "HTTP request failed: Unable to resolve host ...") for the balances query: - Android: "Error" / "HTTP request failed: Unable to resolve host "exchange.invalid"" / Export error diagnostics (before-android-1.png). English in every language, and it does not say what to do. - WebExtension: "The wallet could not reach the service." / "Check your connection and try again." / "Technical details" (code, hint, JSON, copy) (before-web-1.png). - iOS: network codes 7002/7003/7005/7032 get the title "Network problem" (or "Couldn't establish a connection" while retrying, before-ios-1.png); other codes are titled "Internal core error" (that half is 0011934). 2. A taler:// link with an unknown action (taler://frobnicate/x), a real, non-injected case: - Android: "Error" / "Unsupported URI: taler://frobnicate/x". The text is a hard-coded English literal in HandleUriScreen.kt, so it cannot be translated at all (before-android-2.png), although R.string.uri_invalid "Not a valid Taler link" exists. - WebExtension: "Enter a valid taler:// wallet link." (before-web-2.png); iOS: "Unknown command: taler://frobnicate/x" (before-ios-2.png). Case 1 was produced with wallet-core's dev experiment api-error (operation getBalances, code 7003) so that all three wallets get the identical error; Android shows the same text for real network failures that end in an error screen. The injection survives switching developer mode off, so the screenshot is the release (non-developer) rendering. Code (origin/master 4c483527): - wallet/src/main/java/net/taler/wallet/backend/WalletResponse.kt:81-105: userFacingMsg = errorResponse.hint or hint + message (wallet-core English). - wallet/src/main/java/net/taler/wallet/compose/ErrorComposable.kt:85-88 (title "Error"), :117-123 (body = userFacingMsg). No code-based mapping anywhere. - wallet/src/main/java/net/taler/wallet/HandleUriScreen.kt:148-150: makeCustomError("Unsupported URI: $u2"). - All the codes are in backend/TalerErrorCode.kt (7002, 7003, 7005, 7013, 7014, 7031, 7032, 7035, 7041, 7049, 7054, 7055, 7058, GENERIC_TIMEOUT). - Reference: packages/wallet-webui/src/ui/error.ts:22-101 (messages and guidance per code) in taler-typescript-core. The patch (android-F401-error-messages.patch): - new compose/ErrorMessages.kt: TalerErrorInfo.errorMessage() maps the network/timeout group, malformed response, order already claimed / already paid / not found, malformed Taler URI, KYC limit exceeded, no suitable exchange and peer payment gone to a string resource plus optional guidance (wording taken from the web wallet so translators can reuse it). - ErrorComposable.kt: for a known code, show the translated message and guidance; wallet-core's hint stays below in small text (useful for support); unknown codes look as before. Developer mode still shows the JSON. - HandleUriScreen.kt: "Unsupported URI" uses R.string.uri_invalid ("Not a valid Taler link: taler://..."). - strings.xml: 11 new English strings. Two other reports touch single screens with the same root cause (payment prompt errors headed "Error creating order"; add-exchange failures) and can use this mapping. Verified: :wallet:assembleFdroidSim and :wallet:testFdroidSimUnitTest pass; after-android-1.png and after-android-2.png are the same two scenarios with the patched build. Assisted by LLM | ||||
| Steps To Reproduce | 1. Developer mode on, open taler://dev-experiment/api-error?operation=getBalances&count=50&detail=eyJjb2RlIjo3MDAzLCJoaW50IjoiSFRUUCByZXF1ZXN0IGZhaWxlZDogVW5hYmxlIHRvIHJlc29sdmUgaG9zdCBcImV4Y2hhbmdlLmludmFsaWRcIiIsInJlcXVlc3RVcmwiOiJodHRwczovL2V4Y2hhbmdlLmludmFsaWQva2V5cyIsInJlcXVlc3RNZXRob2QiOiJHRVQifQ ({"code":7003,"hint":"HTTP request failed: Unable to resolve host \"exchange.invalid\"",...}). 2. Developer mode off (do not restart the app: that drops the experiment), go to Settings and back to Balances: "Error" + English hint. 3. Open taler://frobnicate/x: "Error" / "Unsupported URI: taler://frobnicate/x". | ||||
| Tags | No tags attached. | ||||
| Attached Files | android-F401-error-messages.patch (7,931 bytes)
diff --git a/wallet/src/main/java/net/taler/wallet/HandleUriScreen.kt b/wallet/src/main/java/net/taler/wallet/HandleUriScreen.kt
index 03a3c56a..05e85ce8 100644
--- a/wallet/src/main/java/net/taler/wallet/HandleUriScreen.kt
+++ b/wallet/src/main/java/net/taler/wallet/HandleUriScreen.kt
@@ -14,6 +14,7 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.stringResource
import androidx.core.net.toUri
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Dispatchers
@@ -46,6 +47,7 @@ fun HandleUriScreen(
val networkStatus by model.networkManager.networkStatus.observeAsState()
val payStatus by model.paymentManager.payStatus.observeAsState(PayStatus.None)
val scope = rememberCoroutineScope()
+ val invalidUriMessage = stringResource(R.string.uri_invalid)
fun processTalerUri() {
if (processing) return
@@ -146,7 +148,7 @@ fun HandleUriScreen(
onNavigate(WalletDestination.Contacts(addContactUri = u2), true)
}
else -> {
- errorInfo = TalerErrorInfo.makeCustomError("Unsupported URI: $u2")
+ errorInfo = TalerErrorInfo.makeCustomError("$invalidUriMessage: $u2")
}
}
} catch (_: CancellationException) {
diff --git a/wallet/src/main/java/net/taler/wallet/compose/ErrorComposable.kt b/wallet/src/main/java/net/taler/wallet/compose/ErrorComposable.kt
index 117a6d5a..5c1498d6 100644
--- a/wallet/src/main/java/net/taler/wallet/compose/ErrorComposable.kt
+++ b/wallet/src/main/java/net/taler/wallet/compose/ErrorComposable.kt
@@ -115,7 +115,29 @@ fun ErrorComposable(
)
}
} else {
- Text(
+ val known = remember(error) { error.errorMessage() }
+ if (known != null) {
+ Text(
+ text = stringResource(known.message),
+ style = MaterialTheme.typography.bodyLarge,
+ textAlign = TextAlign.Center,
+ )
+ known.guidance?.let {
+ Text(
+ text = stringResource(it),
+ modifier = Modifier.padding(top = 8.dp),
+ textAlign = TextAlign.Center,
+ )
+ }
+ // wallet-core's English diagnostic, for support
+ if (error.userFacingMsg.isNotEmpty()) Text(
+ text = error.userFacingMsg,
+ modifier = Modifier.padding(top = 16.dp),
+ style = MaterialTheme.typography.bodySmall,
+ color = MaterialTheme.colorScheme.onSurfaceVariant,
+ textAlign = TextAlign.Center,
+ )
+ } else Text(
text = error.userFacingMsg.ifEmpty {
stringResource(R.string.error_code_only, error.rawCode)
},
diff --git a/wallet/src/main/java/net/taler/wallet/compose/ErrorMessages.kt b/wallet/src/main/java/net/taler/wallet/compose/ErrorMessages.kt
new file mode 100644
index 00000000..8905f033
--- /dev/null
+++ b/wallet/src/main/java/net/taler/wallet/compose/ErrorMessages.kt
@@ -0,0 +1,63 @@
+/*
+ * This file is part of GNU Taler
+ * (C) 2026 Taler Systems S.A.
+ *
+ * GNU Taler is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 3, or (at your option) any later version.
+ *
+ * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+
+package net.taler.wallet.compose
+
+import androidx.annotation.StringRes
+import net.taler.wallet.R
+import net.taler.wallet.backend.TalerErrorCode.*
+import net.taler.wallet.backend.TalerErrorInfo
+
+/**
+ * A translated explanation of a wallet-core error, and what the user can do.
+ * wallet-core's own [TalerErrorInfo.hint] is English diagnostic text and stays
+ * available as technical detail.
+ */
+data class ErrorMessage(
+ @param:StringRes val message: Int,
+ @param:StringRes val guidance: Int? = null,
+)
+
+fun TalerErrorInfo.errorMessage(): ErrorMessage? = when (code) {
+ WALLET_NETWORK_ERROR,
+ WALLET_UNEXPECTED_REQUEST_ERROR,
+ WALLET_EXCHANGE_UNAVAILABLE -> ErrorMessage(
+ R.string.error_network,
+ R.string.error_network_guidance,
+ )
+ WALLET_HTTP_REQUEST_GENERIC_TIMEOUT,
+ GENERIC_TIMEOUT -> ErrorMessage(
+ R.string.error_timeout,
+ R.string.error_network_guidance,
+ )
+ WALLET_RECEIVED_MALFORMED_RESPONSE -> ErrorMessage(R.string.error_malformed_response)
+ WALLET_ORDER_ALREADY_CLAIMED -> ErrorMessage(
+ R.string.error_order_claimed,
+ R.string.error_order_new_guidance,
+ )
+ WALLET_ORDER_ALREADY_PAID -> ErrorMessage(R.string.error_order_paid)
+ WALLET_MERCHANT_ORDER_NOT_FOUND -> ErrorMessage(
+ R.string.error_order_not_found,
+ R.string.error_order_new_guidance,
+ )
+ WALLET_TALER_URI_MALFORMED,
+ WALLET_INVALID_TALER_PAY_URI -> ErrorMessage(R.string.uri_invalid)
+ WALLET_KYC_LIMIT_EXCEEDED -> ErrorMessage(R.string.error_kyc_limit)
+ WALLET_NO_SUITABLE_EXCHANGE -> ErrorMessage(R.string.error_no_suitable_exchange)
+ WALLET_PEER_PULL_DEBIT_PURSE_GONE,
+ WALLET_PEER_PUSH_CREDIT_PURSE_GONE -> ErrorMessage(R.string.error_peer_gone)
+ else -> null
+}
diff --git a/wallet/src/main/res/values/strings.xml b/wallet/src/main/res/values/strings.xml
index 89750709..22787d12 100644
--- a/wallet/src/main/res/values/strings.xml
+++ b/wallet/src/main/res/values/strings.xml
@@ -67,6 +67,17 @@ GNU Taler is immune to many types of fraud such as credit card data theft, phish
<string name="error">Error</string>
<string name="error_code_only">Error code %1$d</string>
<string name="error_export">Export error diagnostics</string>
+ <string name="error_network">The wallet could not reach the service.</string>
+ <string name="error_network_guidance">Check your connection and try again.</string>
+ <string name="error_timeout">The service took too long to respond.</string>
+ <string name="error_malformed_response">A service returned a response the wallet could not understand.</string>
+ <string name="error_order_claimed">This order was already claimed by another wallet.</string>
+ <string name="error_order_paid">This order was already paid.</string>
+ <string name="error_order_not_found">The merchant order no longer exists or has expired.</string>
+ <string name="error_order_new_guidance">Return to the merchant and request a new order.</string>
+ <string name="error_kyc_limit">This amount exceeds the limit available without identity verification.</string>
+ <string name="error_no_suitable_exchange">No compatible payment service is available for this operation.</string>
+ <string name="error_peer_gone">This payment is no longer available.</string>
<string name="wallet_init_error_title">Initialization error</string>
<string name="wallet_init_error_message">An error occurred while starting the wallet. This can happen after an app update or if the wallet data is incompatible. Please retry, and if the problem persists, export the logs and report this issue to the developers.</string>
<string name="wallet_export_database">Export database</string>
| ||||
| related to | 0012077 | new | Android: a stalled request shows only "Network is slow... / Retrying..." -- no reason, no time to the next retry, no Cancel or "Retry now" (patch attached) |
| Date Modified | Username | Field | Change |
|---|---|---|---|
| 2026-11-11 01:27 | bot-cg | New Issue | |
| 2026-11-11 01:27 | bot-cg | File Added: before-android-1.png | |
| 2026-11-11 01:27 | bot-cg | File Added: before-android-2.png | |
| 2026-11-11 01:27 | bot-cg | File Added: before-web-1.png | |
| 2026-11-11 01:27 | bot-cg | File Added: before-web-2.png | |
| 2026-11-11 01:27 | bot-cg | File Added: before-ios-1.png | |
| 2026-11-11 01:27 | bot-cg | File Added: before-ios-2.png | |
| 2026-11-11 01:27 | bot-cg | File Added: after-android-1.png | |
| 2026-11-11 01:27 | bot-cg | File Added: after-android-2.png | |
| 2026-11-11 01:27 | bot-cg | File Added: android-F401-error-messages.patch |