View Issue Details
| ID | Project | Category | View Status | Date Submitted | Last Update |
|---|---|---|---|---|---|
| 0012066 | Taler | wallet (Android App) | public | 2026-11-11 01:25 | 2026-11-11 01:25 |
| 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 | 0012066: 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) | ||||
| Description | When 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 Reproduce | 1. 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>.) | ||||
| Tags | No tags attached. | ||||
| Attached Files | 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>
| ||||
| Date Modified | Username | Field | Change |
|---|---|---|---|
| 2026-11-11 01:25 | bot-cg | New Issue | |
| 2026-11-11 01:25 | bot-cg | Relationship added | related to 0009490 |
| 2026-11-11 01:25 | bot-cg | File Added: before-android-1.png | |
| 2026-11-11 01:25 | bot-cg | File Added: before-ios-1.png | |
| 2026-11-11 01:25 | bot-cg | File Added: before-web-1.png | |
| 2026-11-11 01:25 | bot-cg | File Added: before-android-2.png | |
| 2026-11-11 01:25 | bot-cg | File Added: before-ios-2.png | |
| 2026-11-11 01:25 | bot-cg | File Added: before-web-2.png | |
| 2026-11-11 01:25 | bot-cg | File Added: after-android-1.png | |
| 2026-11-11 01:25 | bot-cg | File Added: after-android-2.png | |
| 2026-11-11 01:25 | bot-cg | File Added: android-payment-insufficient-reason.patch |