View Issue Details

IDProjectCategoryView StatusLast Update
0012066Talerwallet (Android App)public2026-11-11 01:25
Reporterbot-cg [agent] Assigned ToResolution deadline
ImplementerReviewer 
Release signed byDeployed by 
VerifierCommunicatorCM_7  
Data protection authority notification requiredNo  
CSIRT notification requiredNo  
Estimated effort (hours)Actual effort (hours)0 
Fast-trackNo
Work packagenone
PrioritynormalSeverityminorReproducibilityhave not tried
Status newResolutionopen 
Summary0012066: Android: payment screen says only "Insufficient balance" -- wallet-core's reason (merchant does not accept the payment service, age restriction, fees, ...) and the available amount are dropped (regression of 0009490, patch attached)
DescriptionWhen a payment cannot be made, the Android payment screen only says "Insufficient balance". wallet-core tells the app why (InsufficientBalanceHint) and how much is available, and Android already has translated strings for every reason, but the payment screen no longer uses them. So a payment the merchant cannot accept (wrong payment service, wire method mismatch, age restriction, coins being refreshed, fees not covered) is reported as "you do not have enough money" next to a balance that is plainly sufficient.

This is a regression of 0009490 (fixed in fe8a23b4 "[wallet] render causeHint in merchant payments"). The rendering was deleted together with the old prompt in 6108bb43 "[wallet] delete leftovers of old payment prompt" (2025-08-02) and was not carried over to PromptPaymentComposable.

What the user sees (same scenarios on all three wallets, origin/master builds of 2026-11-11, wallet funded with KUDOS:20 from the demo backend):

1. Order for KUDOS:50, wallet holds KUDOS:20 (the plain case):
- Android: "Total: 50.00", red "Insufficient balance", disabled Pay. No amount available, no reason (before-android-1.png).
- iOS: title "Payment not possible", "Balance insufficient! You don't have enough KUDOS.", "Amount to pay: KUDOS 50.00", "Amount available: KUDOS 20.00" (before-ios-1.png).
- WebExtension: "The wallet has KUDOS:20 available, which is not enough for KUDOS:50." plus a "Withdraw funds" button (before-web-1.png).

2. Order for KUDOS:1 that the merchant cannot accept from this payment service (wallet-core dev experiment merchant-deposit-insufficient, which makes coin selection report the hint merchant-deposit-insufficient), wallet holds KUDOS 19.50:
- Android: "Insufficient balance" (before-android-2.png), which is false.
- iOS: "Merchant doesn't accept the wire method of the provider, this likely means it is misconfigured" and "Amount available: KUDOS 19.50" (before-ios-2.png).
- WebExtension: "The available funds cannot be deposited by this merchant." (before-web-2.png).

Code (origin/master 4c483527):
- wallet/src/main/java/net/taler/wallet/payment/PromptPaymentComposable.kt:203-209 (v0 contracts) and :585-589 (button of a v1 choice): always R.string.payment_balance_insufficient.
- .../payment/PaymentResponses.kt:117-125: ChoiceSelectionDetail.InsufficientBalance.balanceDetails is decoded; :185-195 InsufficientBalanceHint.stringResId() maps every hint to an existing string.
- .../peer/OutgoingPushComposable.kt:213-214 already does it right for peer payments: res.causeHint?.stringResId() ?: R.string.payment_balance_insufficient.
- PayStatus.InsufficientBalance (PaymentManager.kt:73-78) is no longer emitted anywhere.
References: iOS PaymentScan.swift:207-230 with WalletModel.swift:42-68; web routes/payment-model.ts:223-236,291-299 and ui/error.ts:136-181.

Patch (android-payment-insufficient-reason.patch, against origin/master):
- The v0 heading and the v1 choice button use balanceDetails.causeHint?.stringResId(), falling back to "Insufficient balance" (no new strings for this part).
- Under the reason, "Available: <balanceAvailable>" (one new English string payment_balance_available), like iOS's "Amount available".
Verified: ./gradlew :wallet:assembleFdroidSim builds; after-android-1.png (plain case: "Insufficient balance" / "Available: 19.50") and after-android-2.png (the merchant-deposit case now shows the merchant reason) are the same scenarios with the patched build.

Not in the patch, possible follow-up: a "Withdraw" shortcut on this screen, which only the WebExtension has (iOS lacks it too).

Assisted by LLM
Steps To Reproduce1. Fund the wallet with KUDOS:20 from https://bank.demo.taler.net/.
2. Create an order for KUDOS:50 (any merchant accepting exchange.demo.taler.net) and open its taler://pay/ link: "Insufficient balance" only.
3. Developer mode on, open taler://dev-experiment/merchant-deposit-insufficient, then pay an order for KUDOS:1: still "Insufficient balance" although the balance suffices.
(Automated: wc-stack pay-uri --amount KUDOS:50; wc-android uri <uri>.)
TagsNo tags attached.
Attached Files
before-android-1.png (67,881 bytes)   
before-android-1.png (67,881 bytes)   
before-ios-1.png (228,071 bytes)   
before-ios-1.png (228,071 bytes)   
before-web-1.png (99,646 bytes)   
before-web-1.png (99,646 bytes)   
before-android-2.png (78,492 bytes)   
before-android-2.png (78,492 bytes)   
before-ios-2.png (239,000 bytes)   
before-ios-2.png (239,000 bytes)   
before-web-2.png (95,468 bytes)   
before-web-2.png (95,468 bytes)   
after-android-1.png (75,105 bytes)   
after-android-1.png (75,105 bytes)   
after-android-2.png (94,913 bytes)   
after-android-2.png (94,913 bytes)   
android-payment-insufficient-reason.patch (4,245 bytes)   
diff --git a/wallet/src/main/java/net/taler/wallet/payment/PromptPaymentComposable.kt b/wallet/src/main/java/net/taler/wallet/payment/PromptPaymentComposable.kt
index 3663d1d3..5fa03b37 100644
--- a/wallet/src/main/java/net/taler/wallet/payment/PromptPaymentComposable.kt
+++ b/wallet/src/main/java/net/taler/wallet/payment/PromptPaymentComposable.kt
@@ -17,6 +17,7 @@
 package net.taler.wallet.payment
 
 import android.graphics.Bitmap
+import androidx.annotation.StringRes
 import androidx.compose.animation.animateContentSize
 import androidx.compose.foundation.BorderStroke
 import androidx.compose.foundation.Image
@@ -94,6 +95,7 @@ import net.taler.wallet.compose.cardPaddings
 import net.taler.wallet.donau.DonauInfo
 import net.taler.wallet.donau.DonauSelector
 import net.taler.wallet.donau.DonauToggle
+import net.taler.wallet.payment.GetChoicesForPaymentResponse.ChoiceSelectionDetail
 import net.taler.wallet.payment.GetChoicesForPaymentResponse.ChoiceSelectionDetail.InsufficientBalance
 import net.taler.wallet.payment.GetChoicesForPaymentResponse.ChoiceSelectionDetail.PaymentPossible
 import net.taler.wallet.payment.TokenAvailabilityHint.MerchantUnexpected
@@ -203,9 +205,18 @@ fun PromptPaymentComposable(
                 } else if (choice.details is InsufficientBalance) {
                     Text(
                         modifier = Modifier.padding(bottom = 3.dp),
-                        text = stringResource(R.string.payment_balance_insufficient),
+                        text = stringResource(choice.details.insufficientResId()),
                         color = MaterialTheme.colorScheme.error,
                     )
+                    choice.details.balanceDetails?.let { details ->
+                        Text(
+                            modifier = Modifier.padding(bottom = 3.dp),
+                            text = stringResource(
+                                R.string.payment_balance_available,
+                                details.balanceAvailable.withSpec(choice.details.amountRaw.spec),
+                            ),
+                        )
+                    }
                 }
             } else null,
             leading = {
@@ -583,9 +594,7 @@ fun PaymentChoice(
                                 R.string.payment_tokens_insufficient,
                             )
                         } else {
-                            stringResource(
-                                R.string.payment_balance_insufficient,
-                            )
+                            stringResource(choice.details.insufficientResId())
                         },
                     )
                 }
@@ -594,6 +603,16 @@ fun PaymentChoice(
     }
 }
 
+/**
+ * The reason wallet-core gave for not being able to pay this choice, e.g. that
+ * the merchant does not accept the payment service, falling back to the plain
+ * "insufficient balance" when there is no more specific reason.
+ */
+@StringRes
+private fun ChoiceSelectionDetail.insufficientResId(): Int =
+    (this as? InsufficientBalance)?.balanceDetails?.causeHint?.stringResId()
+        ?: R.string.payment_balance_insufficient
+
 @Composable
 fun PaymentInput(
     input: ContractInput,
diff --git a/wallet/src/main/res/values/strings.xml b/wallet/src/main/res/values/strings.xml
index 89750709..484e5749 100644
--- a/wallet/src/main/res/values/strings.xml
+++ b/wallet/src/main/res/values/strings.xml
@@ -283,6 +283,7 @@ GNU Taler is immune to many types of fraud such as credit card data theft, phish
     <string name="payment_already_paid">You\'ve already paid for this purchase.</string>
     <string name="payment_amount_total">Total: %1$s</string>
     <string name="payment_automatic_execution">This payment was automatically confirmed</string>
+    <string name="payment_balance_available">Available: %1$s</string>
     <string name="payment_balance_insufficient">Insufficient balance</string>
     <string name="payment_balance_insufficient_hint_age_restricted">Purchase not possible due to age restriction</string>
     <string name="payment_balance_insufficient_hint_exchange_missing_global_fees">Payment service is missing the global fee configuration, this likely means it is misconfigured</string>

Required notifications

Address or linkRemarkAdded byNotifiedActions

Relationships

related to 0009490 closedavalos Android walllet should render insufficient balance details / hints for merchant payments 

Activities

Issue History

Date ModifiedUsernameFieldChange
2026-11-11 01:25bot-cgNew Issue
2026-11-11 01:25bot-cgRelationship addedrelated to 0009490
2026-11-11 01:25bot-cgFile Added: before-android-1.png
2026-11-11 01:25bot-cgFile Added: before-ios-1.png
2026-11-11 01:25bot-cgFile Added: before-web-1.png
2026-11-11 01:25bot-cgFile Added: before-android-2.png
2026-11-11 01:25bot-cgFile Added: before-ios-2.png
2026-11-11 01:25bot-cgFile Added: before-web-2.png
2026-11-11 01:25bot-cgFile Added: after-android-1.png
2026-11-11 01:25bot-cgFile Added: after-android-2.png
2026-11-11 01:25bot-cgFile Added: android-payment-insufficient-reason.patch