View Issue Details
| ID | Project | Category | View Status | Date Submitted | Last Update |
|---|---|---|---|---|---|
| 0012067 | Taler | wallet (Android App) | public | 2026-11-11 01:26 | 2026-11-11 01:26 |
| 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 | 0012067: Android: a pending transaction stuck on an error only says "pending" -- wallet-core's error and the Retry action are shown in developer mode only (patch attached) | ||||
| Description | When wallet-core cannot complete a transaction (exchange unreachable, exchange answering 5xx, ...) it keeps retrying and reports the last error of the transaction in TransactionCommon.error, and offers the "retry" action in txActions. Outside developer mode the Android wallet shows neither: the transaction just says "This transaction is pending" for ever, with Abort as the only action. The error and the Retry button only exist in developer mode (raw JSON behind an "Error" button). What the user sees (KUDOS p2p "Send" from the demo exchange whose follow-up requests fail): - Android, developer mode off: "This transaction is pending", amounts, purpose, "Abort". Nothing says why it does not progress, no way to retry now (before-android-1.png). wallet-core's getTransactionById for this transaction at that moment returned "error": {"code":7003,"hint":"HTTP request failed: Failed to connect to exchange.demo.taler.net/...:443"} and "txActions": ["retry","abort","suspend"]. - Android, developer mode on: the same screen shows "Retry", "Suspend", "Show JSON" and a red "Error" button with the JSON - the information is there, just hidden from normal users (before-android-devmode-1.png). - iOS (same defect, filed separately and related): "Pending", "This transaction is not yet ready...", "Abort" (before-ios-1.png, before-ios-2.png). - WebExtension (reference): the transaction detail shows the state "Pending", a red card "Wallet reported a problem" with the error and a "Technical details" section (code, hint, JSON, copy button), and the actions "Try now", "Abort", "Suspend" (before-web-1.png, before-web-2.png). Code (origin/master 4c483527): - wallet/src/main/java/net/taler/wallet/transactions/TransitionsComposable.kt:103-108: Resume, Suspend and Retry are rendered only if (devMode) (commit fe7d07be "[wallet] Only show "Retry" action in dev mode", 2024-06). - The error is shown only in developer mode, as JSON (transactions/ErrorTransactionComposable.kt): transactions/TransactionDetailScreen.kt:402-406, 445-451, 497-503; withdraw/TransactionWithdrawalComposable.kt:203-207; deposit/TransactionDepositComposable.kt:143-146; refund/TransactionRefundComposable.kt:105-109; payment/TransactionPaymentComposable.kt:150-153. - Everything needed is already decoded (Transaction.error, txActions) and wired (TransactionManager.retryTransaction, R.string.transactions_retry). Why it matters: a pending transaction that is stuck on an error looks exactly like one that is waiting for the counterparty. The user cannot tell that the exchange (or the network) is the problem and has to wait for wallet-core's back-off, which grows to minutes. The patch (android-F403-tx-error-and-retry.patch, 2 files): - TransitionsComposable.kt (used by every transaction detail screen): when the transaction carries an error and developer mode is off, a banner "The wallet reported a problem" with the error text and "The wallet will retry automatically. You can also retry now."; the Retry action is shown to everybody when there is an error (Suspend/Resume stay developer-only, keeping the intent of fe7d07be for healthy transactions). - strings.xml: two new English strings. The error text is still wallet-core's English hint; a translated message per error code is the subject of a separate report (Android error mapping), which this banner would pick up. Verified: :wallet:assembleFdroidSim and :wallet:testFdroidSimUnitTest pass; after-android-1.png is the same transaction with the patched build (banner + Retry); tapping Retry sends retryTransaction (logcat). Assisted by LLM | ||||
| Steps To Reproduce | 1. Fund the wallet (e.g. KUDOS:10 from https://bank.demo.taler.net/). 2. Send money to another wallet (p2p push), KUDOS:1. 3. Make the exchange unreachable for the device (here: iptables REJECT for exchange.demo.taler.net inside the emulator; a dead network or a broken exchange does the same), wait until the purse long-poll has failed once (~60 s). 4. Open the pending transaction with developer mode off: "This transaction is pending" and "Abort" only. (Alternative with developer mode: taler://dev-experiment/start-tc?fake_500=1 before step 2 makes every exchange request fail with 500; switching developer mode off afterwards removes the experiment again, so the hidden-error screen needs the network variant on Android.) | ||||
| Tags | No tags attached. | ||||
| Attached Files | android-F403-tx-error-and-retry.patch (5,451 bytes)
diff --git a/wallet/src/main/java/net/taler/wallet/transactions/TransitionsComposable.kt b/wallet/src/main/java/net/taler/wallet/transactions/TransitionsComposable.kt
index f654604f..e0045197 100644
--- a/wallet/src/main/java/net/taler/wallet/transactions/TransitionsComposable.kt
+++ b/wallet/src/main/java/net/taler/wallet/transactions/TransitionsComposable.kt
@@ -18,6 +18,7 @@ package net.taler.wallet.transactions
import androidx.compose.foundation.layout.Arrangement.Center
import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.ExperimentalLayoutApi
import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.Row
@@ -27,6 +28,7 @@ import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
+import androidx.compose.material3.CardDefaults
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
@@ -42,11 +44,15 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontFamily
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import kotlinx.serialization.json.Json
import net.taler.lib.android.copyToClipBoard
import net.taler.wallet.R
+import net.taler.wallet.backend.TalerErrorInfo
+import net.taler.wallet.compose.Banner
import net.taler.wallet.transactions.TransactionAction.*
@OptIn(ExperimentalLayoutApi::class)
@@ -99,9 +105,19 @@ fun TransitionsComposable(
})
}
+ // Developer mode has the raw JSON (ErrorTransactionButton); everybody
+ // else gets told that the wallet is stuck on an error and why.
+ val error = t.error
+ if (error != null && !devMode) {
+ TransactionErrorBanner(error)
+ }
+
FlowRow(horizontalArrangement = Center) {
t.txActions.forEach {
- if (it in arrayOf(Resume, Suspend, Retry)) {
+ if (it == Retry && error != null) {
+ // a failing transaction can be retried by anybody
+ TransitionComposable(it, onTransition)
+ } else if (it in arrayOf(Resume, Suspend, Retry)) {
if (devMode) TransitionComposable(it, onTransition)
} else {
TransitionComposable(it, onTransition)
@@ -117,6 +133,42 @@ fun TransitionsComposable(
}
}
+@Composable
+fun TransactionErrorBanner(error: TalerErrorInfo) {
+ Banner(
+ modifier = Modifier.padding(top = 16.dp),
+ colors = CardDefaults.cardColors(
+ containerColor = MaterialTheme.colorScheme.errorContainer,
+ contentColor = MaterialTheme.colorScheme.onErrorContainer,
+ ),
+ ) {
+ Column(Modifier.fillMaxWidth()) {
+ Text(
+ text = stringResource(R.string.transaction_error_title),
+ fontWeight = FontWeight.Bold,
+ textAlign = TextAlign.Center,
+ modifier = Modifier.fillMaxWidth(),
+ )
+ Text(
+ text = error.userFacingMsg.ifEmpty {
+ stringResource(R.string.error_code_only, error.rawCode)
+ },
+ textAlign = TextAlign.Center,
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(top = 8.dp),
+ )
+ Text(
+ text = stringResource(R.string.transaction_error_retry_hint),
+ textAlign = TextAlign.Center,
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(top = 8.dp),
+ )
+ }
+ }
+}
+
@Composable
fun TransitionComposable(t: TransactionAction, onClick: (t: TransactionAction) -> Unit) {
var showConfirmDialog by remember { mutableStateOf(false) }
diff --git a/wallet/src/main/res/values/strings.xml b/wallet/src/main/res/values/strings.xml
index 89750709..8ac36512 100644
--- a/wallet/src/main/res/values/strings.xml
+++ b/wallet/src/main/res/values/strings.xml
@@ -210,6 +210,8 @@ GNU Taler is immune to many types of fraud such as credit card data theft, phish
<string name="transaction_state_failed">This transaction has failed or been abandoned</string>
<string name="transaction_state_finalizing">This transaction is finalizing</string>
<string name="transaction_state_pending">This transaction is pending</string>
+ <string name="transaction_error_title">The wallet reported a problem</string>
+ <string name="transaction_error_retry_hint">The wallet will retry automatically. You can also retry now.</string>
<string name="transaction_state_pending_bank">Waiting for authorization in the bank</string>
<string name="transaction_state_pending_kyc_bank">This transaction would exceed a limit set by the payment service. To increase the limit, follow the instructions linked below.</string>
<string name="transaction_state_pending_kyc_auth">You need to prove having control over the bank account for the deposit. To continue, follow the instructions linked below.</string>
| ||||
| related to | 0012068 | new | iOS: a pending transaction stuck on an error only says "Pending" -- wallet-core's error is never shown and there is no "Retry now" (patch attached) |
| Date Modified | Username | Field | Change |
|---|---|---|---|
| 2026-11-11 01:26 | bot-cg | New Issue | |
| 2026-11-11 01:26 | bot-cg | File Added: before-android-1.png | |
| 2026-11-11 01:26 | bot-cg | File Added: before-android-devmode-1.png | |
| 2026-11-11 01:26 | bot-cg | File Added: before-ios-1.png | |
| 2026-11-11 01:26 | bot-cg | File Added: before-ios-2.png | |
| 2026-11-11 01:26 | bot-cg | File Added: before-web-1.png | |
| 2026-11-11 01:26 | bot-cg | File Added: before-web-2.png | |
| 2026-11-11 01:26 | bot-cg | File Added: after-android-1.png | |
| 2026-11-11 01:26 | bot-cg | File Added: android-F403-tx-error-and-retry.patch |