View Issue Details
| ID | Project | Category | View Status | Date Submitted | Last Update |
|---|---|---|---|---|---|
| 0012112 | Taler | wallet (Android App) | public | 2026-11-11 02:25 | 2026-11-11 02:27 |
| 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 | 0012112: Android: ToS status decoded from "status" but wallet-core sends "tosStatus"; an exchange without terms shows "NULL" instead of "terms not provided" | ||||
| Description | For an exchange that publishes no terms of service, Android shows a terms page whose only content is the word "NULL", instead of its own message "Terms of service were not provided by payment service". Cause: wallet-core's getExchangeTos answer names the field "tosStatus" (GetExchangeTosResult; for a missing /terms it returns {"tosStatus":"missing-tos","contentType":"text/plain","content":"NULL","currentEtag":"NULL",...}, taler-wallet-core/src/exchanges.ts getExchangeTos), but Android's TosResponse decodes it from "status" (wallet/src/main/java/net/taler/wallet/exchanges/Exchanges.kt:63-64). The JSON decoder ignores unknown keys, so status is always Unknown and the MissingTos branch in ReviewExchangeTosScreen.kt:159-162 can never run. The same field drives nothing else on that screen. What the user sees (exchange.demo.taler.net behind a local proxy whose /terms answers 404, so wallet-core reports missing-tos): - Android: "PSP's terms of service" / "NULL" (before-android-1.png). The menu offers "Review terms of service" because the exchange list (which decodes tosStatus correctly) counts missing-tos as accepted. - iOS: "This payment service has not published any terms of service." (before-ios-1.png). - WebExtension: "Status: Terms unavailable" above a "NULL" body (before-web-1.png). Real exchanges hit this when /terms is missing or temporarily unavailable (404/501), cf. 0011620 and 0010928. Patch (android-tos-status-field.patch): - @SerialName("tosStatus") on TosResponse.status; - the existing missing-terms message (R.string.exchange_tos_missing) is shown inside the screen's scaffold, so title and back navigation stay, and no Accept button is offered when there is nothing to accept (previously the message replaced the whole screen); - unit test wallet/src/test/java/net/taler/wallet/exchanges/TosResponseTest.kt decoding wallet-core's missing-tos and proposed answers. Verified: ./gradlew :wallet:testFdroidSimUnitTest --tests net.taler.wallet.exchanges.TosResponseTest passes (2/2); with the @SerialName line removed both tests fail. :wallet:assembleFdroidSim builds; after-android-1.png shows "Terms of service were not provided by payment service" for the same exchange. Assisted by LLM | ||||
| Steps To Reproduce | Use an exchange whose /terms answers 404 (e.g. a proxy in front of exchange.demo.taler.net), add it, then Settings > Payment services > menu > Review terms of service. | ||||
| Tags | No tags attached. | ||||
| Attached Files | android-tos-status-field.patch (4,488 bytes)
diff --git a/wallet/src/main/java/net/taler/wallet/exchanges/Exchanges.kt b/wallet/src/main/java/net/taler/wallet/exchanges/Exchanges.kt
index d110f044..9e918c4d 100644
--- a/wallet/src/main/java/net/taler/wallet/exchanges/Exchanges.kt
+++ b/wallet/src/main/java/net/taler/wallet/exchanges/Exchanges.kt
@@ -61,6 +61,8 @@ enum class ExchangeTosStatus {
@Serializable
data class TosResponse(
+ // wallet-core's GetExchangeTosResult calls it "tosStatus"
+ @SerialName("tosStatus")
val status: ExchangeTosStatus = ExchangeTosStatus.Unknown,
val content: String,
val currentEtag: String,
diff --git a/wallet/src/main/java/net/taler/wallet/exchanges/ReviewExchangeTosScreen.kt b/wallet/src/main/java/net/taler/wallet/exchanges/ReviewExchangeTosScreen.kt
index 34701d08..444b0a81 100644
--- a/wallet/src/main/java/net/taler/wallet/exchanges/ReviewExchangeTosScreen.kt
+++ b/wallet/src/main/java/net/taler/wallet/exchanges/ReviewExchangeTosScreen.kt
@@ -156,11 +156,7 @@ fun ReviewExchangeTosComposable(
onNavigateBack: () -> Unit,
model: MainViewModel? = null,
) {
- if (tos.status == ExchangeTosStatus.MissingTos) {
- EmptyComposable(message = stringResource(R.string.exchange_tos_missing))
- return
- }
-
+ val missingTos = tos.status == ExchangeTosStatus.MissingTos
var expanded by remember { mutableStateOf(false) }
GlobalScaffold(
@@ -168,7 +164,8 @@ fun ReviewExchangeTosComposable(
title = { Text(stringResource(R.string.nav_exchange_tos)) },
onNavigateBack = onNavigateBack,
bottomBar = {
- if (!readOnly) BottomButtonBox {
+ // nothing to accept when the exchange publishes no terms
+ if (!readOnly && !missingTos) BottomButtonBox {
Button(
modifier = Modifier
.systemBarsPaddingBottom(),
@@ -179,7 +176,10 @@ fun ReviewExchangeTosComposable(
}
},
) { innerPadding ->
- LazyColumn(Modifier.padding(innerPadding)) {
+ if (missingTos) EmptyComposable(
+ modifier = Modifier.padding(innerPadding),
+ message = stringResource(R.string.exchange_tos_missing),
+ ) else LazyColumn(Modifier.padding(innerPadding)) {
if (tos.tosAvailableLanguages.size > 1) item {
ExposedDropdownMenuBox(
expanded = expanded,
diff --git a/wallet/src/test/java/net/taler/wallet/exchanges/TosResponseTest.kt b/wallet/src/test/java/net/taler/wallet/exchanges/TosResponseTest.kt
new file mode 100644
index 00000000..dd9e52b3
--- /dev/null
+++ b/wallet/src/test/java/net/taler/wallet/exchanges/TosResponseTest.kt
@@ -0,0 +1,43 @@
+/*
+ * 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.exchanges
+
+import net.taler.wallet.backend.BackendManager
+import org.junit.Assert.assertEquals
+import org.junit.Test
+
+class TosResponseTest {
+
+ private fun decode(s: String): TosResponse =
+ BackendManager.json.decodeFromString(TosResponse.serializer(), s)
+
+ /** wallet-core's answer for an exchange that publishes no terms (GET /terms -> 404) */
+ @Test
+ fun testMissingTos() {
+ val tos = decode("""{"tosStatus":"missing-tos","contentType":"text/plain","content":"NULL",""" +
+ """"currentEtag":"NULL","tosAvailableLanguages":[]}""")
+ assertEquals(ExchangeTosStatus.MissingTos, tos.status)
+ }
+
+ @Test
+ fun testProposedTos() {
+ val tos = decode("""{"tosStatus":"proposed","contentType":"text/markdown","content":"# Terms",""" +
+ """"currentEtag":"v1","contentLanguage":"en","tosAvailableLanguages":["en"]}""")
+ assertEquals(ExchangeTosStatus.Proposed, tos.status)
+ assertEquals("v1", tos.currentEtag)
+ }
+}
| ||||
| Date Modified | Username | Field | Change |
|---|---|---|---|
| 2026-11-11 02:25 | bot-cg | New Issue | |
| 2026-11-11 02:25 | bot-cg | Relationship added | related to 0011620 |
| 2026-11-11 02:25 | bot-cg | File Added: before-android-1.png | |
| 2026-11-11 02:25 | bot-cg | File Added: before-ios-1.png | |
| 2026-11-11 02:25 | bot-cg | File Added: before-web-1.png | |
| 2026-11-11 02:25 | bot-cg | File Added: after-android-1.png | |
| 2026-11-11 02:25 | bot-cg | File Added: android-tos-status-field.patch |