diff --git a/clients/terminal-android/app/src/debug/AndroidManifest.xml b/clients/terminal-android/app/src/debug/AndroidManifest.xml
new file mode 100644
index 00000000..4ca45d05
--- /dev/null
+++ b/clients/terminal-android/app/src/debug/AndroidManifest.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
diff --git a/clients/terminal-android/app/src/debug/res/xml/network_security_config.xml b/clients/terminal-android/app/src/debug/res/xml/network_security_config.xml
new file mode 100644
index 00000000..92185cb8
--- /dev/null
+++ b/clients/terminal-android/app/src/debug/res/xml/network_security_config.xml
@@ -0,0 +1,17 @@
+
+
+
+
+ 10.0.2.2
+ localhost
+ 127.0.0.1
+
+
diff --git a/clients/terminal-android/app/src/main/java/lu/rewardflow/terminal/ui/terminal/TerminalViewModel.kt b/clients/terminal-android/app/src/main/java/lu/rewardflow/terminal/ui/terminal/TerminalViewModel.kt
index a67d3f03..04d09e49 100644
--- a/clients/terminal-android/app/src/main/java/lu/rewardflow/terminal/ui/terminal/TerminalViewModel.kt
+++ b/clients/terminal-android/app/src/main/java/lu/rewardflow/terminal/ui/terminal/TerminalViewModel.kt
@@ -25,6 +25,7 @@ import lu.rewardflow.terminal.data.model.TransactionItem
import lu.rewardflow.terminal.data.network.NetworkMonitor
import lu.rewardflow.terminal.data.repository.CategoryRepository
import lu.rewardflow.terminal.data.repository.DeviceConfigRepository
+import retrofit2.HttpException
import javax.inject.Inject
/**
@@ -294,13 +295,34 @@ class TerminalViewModel @Inject constructor(
_state.value = _state.value.copy(
actionInProgress = false,
actionResult = ActionResult.Failure(
- result.exceptionOrNull()?.message ?: "Operation failed"
+ readableErrorMessage(result.exceptionOrNull())
),
)
}
}
}
+ /** Extract a human-readable message from a Retrofit/HttpException.
+ *
+ * Retrofit's default `message` is the HTTP status text ("Bad Request"),
+ * which hides the JSON ``{message, error_code, details}`` body the
+ * backend returns. Read the body and surface its message instead so
+ * cashiers see "Staff PIN is required" or "Card not found" rather
+ * than the generic 400. */
+ private fun readableErrorMessage(t: Throwable?): String {
+ if (t is HttpException) {
+ val body = runCatching { t.response()?.errorBody()?.string() }.getOrNull()
+ if (!body.isNullOrBlank()) {
+ // Cheap JSON peek — avoid pulling in a full adapter for one field.
+ val match = "\"message\"\\s*:\\s*\"([^\"]+)\"".toRegex().find(body)
+ if (match != null) return match.groupValues[1]
+ return body.take(200)
+ }
+ return "HTTP ${t.code()}"
+ }
+ return t?.message ?: "Operation failed"
+ }
+
fun refreshCurrentCustomer() {
val current = _state.value.customer ?: return
viewModelScope.launch {