gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[taler-taler-android] 02/02: [pos] refactor configuration fetching and v


From: gnunet
Subject: [taler-taler-android] 02/02: [pos] refactor configuration fetching and validation
Date: Thu, 23 Jul 2020 20:44:37 +0200

This is an automated email from the git hooks/post-receive script.

torsten-grote pushed a commit to branch master
in repository taler-android.

commit 8eb241ccce345a35b05a6335d11306465220f66d
Author: Torsten Grote <t@grobox.de>
AuthorDate: Thu Jul 23 15:41:50 2020 -0300

    [pos] refactor configuration fetching and validation
---
 build.gradle                                       |   1 +
 merchant-lib/build.gradle                          |   9 +-
 .../main/java/net/taler/merchantlib/MerchantApi.kt |   9 +-
 .../java/net/taler/merchantlib/MerchantConfig.kt   |   5 +-
 .../main/java/net/taler/merchantlib/Response.kt    |  19 ++-
 .../java/net/taler/merchantlib/MerchantApiTest.kt  |   5 +-
 merchant-terminal/build.gradle                     |  12 +-
 .../java/net/taler/merchantpos/MainViewModel.kt    |   9 +-
 .../net/taler/merchantpos/config/ConfigManager.kt  | 106 +++++++++--------
 .../taler/merchantpos/config/MerchantRequest.kt    |  14 ++-
 .../config/{MerchantConfig.kt => PosConfig.kt}     |  52 ++++-----
 .../taler/merchantpos/history/HistoryManager.kt    |   2 +-
 .../net/taler/merchantpos/order/OrderFragment.kt   |   2 +-
 .../net/taler/merchantpos/order/OrderManager.kt    |  31 ++---
 .../taler/merchantpos/payment/PaymentManager.kt    |   8 +-
 .../taler/merchantpos/order/OrderManagerTest.kt    | 128 +++++++--------------
 taler-kotlin-common/build.gradle                   |   2 +-
 17 files changed, 190 insertions(+), 224 deletions(-)

diff --git a/build.gradle b/build.gradle
index dc530bf..76f687e 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,5 +1,6 @@
 buildscript {
     ext.kotlin_version = '1.3.72'
+    ext.ktor_version = "1.3.2"
     ext.nav_version = "2.3.0"
     ext.lifecycle_version = "2.2.0"
     // check https://android-rebuilds.beuc.net/ for availability of free build 
tools
diff --git a/merchant-lib/build.gradle b/merchant-lib/build.gradle
index 93e2d4d..128f4c1 100644
--- a/merchant-lib/build.gradle
+++ b/merchant-lib/build.gradle
@@ -45,14 +45,13 @@ android {
 }
 
 dependencies {
-    implementation project(":taler-kotlin-common")
+    api project(":taler-kotlin-common")
 
     implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
 
-    def ktor_version = "1.3.2"
-    implementation "io.ktor:ktor-client:$ktor_version"
-    implementation "io.ktor:ktor-client-okhttp:$ktor_version"
-    implementation "io.ktor:ktor-client-serialization-jvm:$ktor_version"
+    api "io.ktor:ktor-client:$ktor_version"
+    api "io.ktor:ktor-client-okhttp:$ktor_version"
+    api "io.ktor:ktor-client-serialization-jvm:$ktor_version"
 
     testImplementation 'junit:junit:4.13'
     testImplementation "io.ktor:ktor-client-mock-jvm:$ktor_version"
diff --git a/merchant-lib/src/main/java/net/taler/merchantlib/MerchantApi.kt 
b/merchant-lib/src/main/java/net/taler/merchantlib/MerchantApi.kt
index 06388f4..e995724 100644
--- a/merchant-lib/src/main/java/net/taler/merchantlib/MerchantApi.kt
+++ b/merchant-lib/src/main/java/net/taler/merchantlib/MerchantApi.kt
@@ -37,8 +37,8 @@ import net.taler.merchantlib.Response.Companion.response
 
 class MerchantApi(private val httpClient: HttpClient) {
 
-    suspend fun getConfig(baseUrl: String): ConfigResponse {
-        return httpClient.get("$baseUrl/config")
+    suspend fun getConfig(baseUrl: String): Response<ConfigResponse> = 
response {
+        httpClient.get("$baseUrl/config") as ConfigResponse
     }
 
     suspend fun postOrder(
@@ -77,6 +77,11 @@ class MerchantApi(private val httpClient: HttpClient) {
 }
 
 fun getDefaultHttpClient(): HttpClient = HttpClient(OkHttp) {
+    engine {
+        config {
+            retryOnConnectionFailure(true)
+        }
+    }
     install(JsonFeature) {
         serializer = getSerializer()
     }
diff --git a/merchant-lib/src/main/java/net/taler/merchantlib/MerchantConfig.kt 
b/merchant-lib/src/main/java/net/taler/merchantlib/MerchantConfig.kt
index 71185b9..a01624e 100644
--- a/merchant-lib/src/main/java/net/taler/merchantlib/MerchantConfig.kt
+++ b/merchant-lib/src/main/java/net/taler/merchantlib/MerchantConfig.kt
@@ -23,11 +23,12 @@ import kotlinx.serialization.Serializable
 data class MerchantConfig(
     @SerialName("base_url")
     val baseUrl: String,
-    val instance: String,
+    // TODO remove instance when it is part of baseURL
+    val instance: String? = null,
     @SerialName("api_key")
     val apiKey: String
 ) {
-    fun urlFor(endpoint: String, params: Map<String, String>? = null): String {
+    fun urlFor(endpoint: String): String {
         val sb = StringBuilder(baseUrl)
         if (sb.last() != '/') sb.append('/')
         sb.append("instances/$instance/")
diff --git a/merchant-lib/src/main/java/net/taler/merchantlib/Response.kt 
b/merchant-lib/src/main/java/net/taler/merchantlib/Response.kt
index eb1ef27..1b49d78 100644
--- a/merchant-lib/src/main/java/net/taler/merchantlib/Response.kt
+++ b/merchant-lib/src/main/java/net/taler/merchantlib/Response.kt
@@ -50,14 +50,29 @@ class Response<out T> private constructor(
         }
     }
 
+    suspend fun handleSuspend(
+        onFailure: ((String) -> Any)? = null,
+        onSuccess: (suspend (T) -> Any)? = null
+    ) {
+        if (value is Failure) onFailure?.let { it(getFailureString(value)) }
+        else onSuccess?.let {
+            @Suppress("UNCHECKED_CAST")
+            it(value as T)
+        }
+    }
+
     private suspend fun getFailureString(failure: Failure): String = when 
(failure.exception) {
         is ClientRequestException -> getExceptionString(failure.exception)
         else -> failure.exception.toString()
     }
 
     private suspend fun getExceptionString(e: ClientRequestException): String {
-        val error: Error = e.response.receive()
-        return "Error ${error.code}: ${error.hint}"
+        return try {
+            val error: Error = e.response.receive()
+            "Error ${error.code}: ${error.hint}"
+        } catch (ex: Exception) {
+            "Status code: ${e.response.status.value}"
+        }
     }
 
     private class Failure(val exception: Throwable)
diff --git 
a/merchant-lib/src/test/java/net/taler/merchantlib/MerchantApiTest.kt 
b/merchant-lib/src/test/java/net/taler/merchantlib/MerchantApiTest.kt
index de1ca33..ea5a12a 100644
--- a/merchant-lib/src/test/java/net/taler/merchantlib/MerchantApiTest.kt
+++ b/merchant-lib/src/test/java/net/taler/merchantlib/MerchantApiTest.kt
@@ -46,8 +46,9 @@ class MerchantApiTest {
             }
             """.trimIndent()
         }
-        val response = api.getConfig("https://backend.int.taler.net";)
-        assertEquals(ConfigResponse("0:0:0", "INTKUDOS"), response)
+        api.getConfig("https://backend.int.taler.net";).assertSuccess {
+            assertEquals(ConfigResponse("0:0:0", "INTKUDOS"), it)
+        }
     }
 
     @Test
diff --git a/merchant-terminal/build.gradle b/merchant-terminal/build.gradle
index 2ba1a66..1cec0c5 100644
--- a/merchant-terminal/build.gradle
+++ b/merchant-terminal/build.gradle
@@ -1,7 +1,10 @@
-apply plugin: 'com.android.application'
-apply plugin: 'kotlin-android'
-apply plugin: 'kotlin-android-extensions'
-apply plugin: "androidx.navigation.safeargs.kotlin"
+plugins {
+    id 'com.android.application'
+    id 'kotlin-android'
+    id 'kotlin-android-extensions'
+    id 'kotlinx-serialization'
+    id 'androidx.navigation.safeargs.kotlin'
+}
 
 android {
     compileSdkVersion 29
@@ -54,7 +57,6 @@ android {
 }
 
 dependencies {
-    implementation project(":taler-kotlin-common")
     implementation project(":merchant-lib")
 
     implementation 'com.google.android.material:material:1.1.0'
diff --git 
a/merchant-terminal/src/main/java/net/taler/merchantpos/MainViewModel.kt 
b/merchant-terminal/src/main/java/net/taler/merchantpos/MainViewModel.kt
index ce05980..b62c550 100644
--- a/merchant-terminal/src/main/java/net/taler/merchantpos/MainViewModel.kt
+++ b/merchant-terminal/src/main/java/net/taler/merchantpos/MainViewModel.kt
@@ -24,6 +24,7 @@ import 
com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PRO
 import com.fasterxml.jackson.databind.ObjectMapper
 import com.fasterxml.jackson.module.kotlin.KotlinModule
 import net.taler.merchantlib.MerchantApi
+import net.taler.merchantlib.getDefaultHttpClient
 import net.taler.merchantpos.config.ConfigManager
 import net.taler.merchantpos.history.HistoryManager
 import net.taler.merchantpos.history.RefundManager
@@ -32,14 +33,15 @@ import net.taler.merchantpos.payment.PaymentManager
 
 class MainViewModel(app: Application) : AndroidViewModel(app) {
 
-    private val api = MerchantApi()
+    private val httpClient = getDefaultHttpClient()
+    private val api = MerchantApi(httpClient)
     private val mapper = ObjectMapper()
         .registerModule(KotlinModule())
         .configure(FAIL_ON_UNKNOWN_PROPERTIES, false)
     private val queue = Volley.newRequestQueue(app)
 
-    val orderManager = OrderManager(app, mapper)
-    val configManager = ConfigManager(app, viewModelScope, api, mapper, 
queue).apply {
+    val orderManager = OrderManager(app)
+    val configManager = ConfigManager(app, viewModelScope, httpClient, 
api).apply {
         addConfigurationReceiver(orderManager)
     }
     val paymentManager = PaymentManager(app, configManager, viewModelScope, 
api)
@@ -47,6 +49,7 @@ class MainViewModel(app: Application) : AndroidViewModel(app) 
{
     val refundManager = RefundManager(configManager, queue)
 
     override fun onCleared() {
+        httpClient.close()
         queue.cancelAll { !it.isCanceled }
     }
 
diff --git 
a/merchant-terminal/src/main/java/net/taler/merchantpos/config/ConfigManager.kt 
b/merchant-terminal/src/main/java/net/taler/merchantpos/config/ConfigManager.kt
index 3f45e32..c0b01a2 100644
--- 
a/merchant-terminal/src/main/java/net/taler/merchantpos/config/ConfigManager.kt
+++ 
b/merchant-terminal/src/main/java/net/taler/merchantpos/config/ConfigManager.kt
@@ -22,15 +22,14 @@ import android.util.Base64.NO_WRAP
 import android.util.Base64.encodeToString
 import android.util.Log
 import androidx.annotation.UiThread
+import androidx.annotation.WorkerThread
 import androidx.lifecycle.LiveData
 import androidx.lifecycle.MutableLiveData
-import com.android.volley.Request.Method.GET
-import com.android.volley.RequestQueue
-import com.android.volley.Response.Listener
-import com.android.volley.VolleyError
-import com.android.volley.toolbox.JsonObjectRequest
-import com.fasterxml.jackson.databind.ObjectMapper
-import com.fasterxml.jackson.module.kotlin.readValue
+import io.ktor.client.HttpClient
+import io.ktor.client.features.ClientRequestException
+import io.ktor.client.request.get
+import io.ktor.client.request.header
+import io.ktor.http.HttpHeaders.Authorization
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.launch
@@ -38,9 +37,8 @@ import net.taler.common.Version
 import net.taler.common.getIncompatibleStringOrNull
 import net.taler.merchantlib.ConfigResponse
 import net.taler.merchantlib.MerchantApi
-import net.taler.merchantpos.LogErrorListener
+import net.taler.merchantlib.MerchantConfig
 import net.taler.merchantpos.R
-import org.json.JSONObject
 
 private const val SETTINGS_NAME = "taler-merchant-terminal"
 
@@ -60,15 +58,14 @@ interface ConfigurationReceiver {
     /**
      * Returns null if the configuration was valid, or a error string for user 
display otherwise.
      */
-    suspend fun onConfigurationReceived(json: JSONObject, currency: String): 
String?
+    suspend fun onConfigurationReceived(posConfig: PosConfig, currency: 
String): String?
 }
 
 class ConfigManager(
     private val context: Context,
     private val scope: CoroutineScope,
-    private val api: MerchantApi,
-    private val mapper: ObjectMapper,
-    private val queue: RequestQueue
+    private val httpClient: HttpClient,
+    private val api: MerchantApi
 ) {
 
     private val prefs = context.getSharedPreferences(SETTINGS_NAME, 
MODE_PRIVATE)
@@ -79,8 +76,12 @@ class ConfigManager(
         username = prefs.getString(SETTINGS_USERNAME, CONFIG_USERNAME_DEMO)!!,
         password = prefs.getString(SETTINGS_PASSWORD, CONFIG_PASSWORD_DEMO)!!
     )
+    @Volatile
     var merchantConfig: MerchantConfig? = null
         private set
+    @Volatile
+    var currency: String? = null
+        private set
 
     private val mConfigUpdateResult = MutableLiveData<ConfigUpdateResult>()
     val configUpdateResult: LiveData<ConfigUpdateResult> = mConfigUpdateResult
@@ -96,74 +97,76 @@ class ConfigManager(
             if (savePassword) config else config.copy(password = "")
         } else null
 
-        val stringRequest = object : JsonObjectRequest(GET, config.configUrl, 
null,
-            Listener { onConfigReceived(it, configToSave) },
-            LogErrorListener { onNetworkError(it) }
-        ) {
-            // send basic auth header
-            override fun getHeaders(): MutableMap<String, String> {
-                val credentials = "${config.username}:${config.password}"
-                val auth = ("Basic ${encodeToString(credentials.toByteArray(), 
NO_WRAP)}")
-                return mutableMapOf("Authorization" to auth)
-            }
-        }
-        queue.add(stringRequest)
-    }
-
-    @UiThread
-    private fun onConfigReceived(json: JSONObject, config: Config?) {
-        val merchantConfig: MerchantConfig = try {
-            mapper.readValue(json.getString("config"))
-        } catch (e: Exception) {
-            Log.e(TAG, "Error parsing merchant config", e)
-            val msg = context.getString(R.string.config_error_malformed)
-            mConfigUpdateResult.value = ConfigUpdateResult.Error(msg)
-            return
-        }
-
         scope.launch(Dispatchers.IO) {
-            val configResponse = api.getConfig(merchantConfig.baseUrl)
-            onMerchantConfigReceived(config, json, merchantConfig, 
configResponse)
+            try {
+                // get PoS configuration
+                val posConfig: PosConfig = httpClient.get(config.configUrl) {
+                    val credentials = "${config.username}:${config.password}"
+                    val auth = ("Basic 
${encodeToString(credentials.toByteArray(), NO_WRAP)}")
+                    header(Authorization, auth)
+                }
+                val merchantConfig = posConfig.merchantConfig
+                // get config from merchant backend API
+                
api.getConfig(merchantConfig.baseUrl).handleSuspend(::onNetworkError) {
+                    onMerchantConfigReceived(configToSave, posConfig, 
merchantConfig, it)
+                }
+            } catch (e: Exception) {
+                Log.e(TAG, "Error retrieving merchant config", e)
+                val msg = if (e is ClientRequestException) {
+                    context.getString(
+                        if (e.response.status.value == 401) 
R.string.config_auth_error
+                        else R.string.config_error_network
+                    )
+                } else {
+                    context.getString(R.string.config_error_malformed)
+                }
+                onNetworkError(msg)
+            }
         }
     }
 
-    private fun onMerchantConfigReceived(
+    @WorkerThread
+    private suspend fun onMerchantConfigReceived(
         newConfig: Config?,
-        configJson: JSONObject,
+        posConfig: PosConfig,
         merchantConfig: MerchantConfig,
         configResponse: ConfigResponse
-    ) = scope.launch(Dispatchers.Default) {
-        val versionIncompatible = VERSION.getIncompatibleStringOrNull(context, 
configResponse.version)
+    ) {
+        val versionIncompatible =
+            VERSION.getIncompatibleStringOrNull(context, 
configResponse.version)
         if (versionIncompatible != null) {
             
mConfigUpdateResult.postValue(ConfigUpdateResult.Error(versionIncompatible))
-            return@launch
+            return
         }
         for (receiver in configurationReceivers) {
             val result = try {
-                receiver.onConfigurationReceived(configJson, 
configResponse.currency)
+                receiver.onConfigurationReceived(posConfig, 
configResponse.currency)
             } catch (e: Exception) {
                 Log.e(TAG, "Error handling configuration by 
${receiver::class.java.simpleName}", e)
                 context.getString(R.string.config_error_unknown)
             }
             if (result != null) {  // error
                 mConfigUpdateResult.postValue(ConfigUpdateResult.Error(result))
-                return@launch
+                return
             }
         }
         newConfig?.let {
             config = it
             saveConfig(it)
         }
-        this@ConfigManager.merchantConfig = merchantConfig.copy(currency = 
configResponse.currency)
+        this.merchantConfig = merchantConfig
+        this.currency = configResponse.currency
         
mConfigUpdateResult.postValue(ConfigUpdateResult.Success(configResponse.currency))
     }
 
+    @UiThread
     fun forgetPassword() {
         config = config.copy(password = "")
         saveConfig(config)
         merchantConfig = null
     }
 
+    @UiThread
     private fun saveConfig(config: Config) {
         prefs.edit()
             .putString(SETTINGS_CONFIG_URL, config.configUrl)
@@ -172,12 +175,7 @@ class ConfigManager(
             .apply()
     }
 
-    @UiThread
-    private fun onNetworkError(it: VolleyError?) {
-        val msg = context.getString(
-            if (it?.networkResponse?.statusCode == 401) 
R.string.config_auth_error
-            else R.string.config_error_network
-        )
+    private fun onNetworkError(msg: String) = scope.launch(Dispatchers.Main) {
         mConfigUpdateResult.value = ConfigUpdateResult.Error(msg)
     }
 
diff --git 
a/merchant-terminal/src/main/java/net/taler/merchantpos/config/MerchantRequest.kt
 
b/merchant-terminal/src/main/java/net/taler/merchantpos/config/MerchantRequest.kt
index 9cfae94..5d41196 100644
--- 
a/merchant-terminal/src/main/java/net/taler/merchantpos/config/MerchantRequest.kt
+++ 
b/merchant-terminal/src/main/java/net/taler/merchantpos/config/MerchantRequest.kt
@@ -16,9 +16,11 @@
 
 package net.taler.merchantpos.config
 
+import android.net.Uri
 import android.util.ArrayMap
 import com.android.volley.Response
 import com.android.volley.toolbox.JsonObjectRequest
+import net.taler.merchantlib.MerchantConfig
 import net.taler.merchantpos.LogErrorListener
 import org.json.JSONObject
 
@@ -33,7 +35,7 @@ class MerchantRequest(
 ) :
     JsonObjectRequest(
         method,
-        merchantConfig.urlFor(endpoint, params),
+        merchantConfig.legacyUrl(endpoint, params),
         jsonRequest,
         listener,
         errorListener
@@ -44,4 +46,14 @@ class MerchantRequest(
         headerMap["Authorization"] = "ApiKey " + merchantConfig.apiKey
         return headerMap
     }
+
+}
+
+private fun MerchantConfig.legacyUrl(endpoint: String, params: Map<String, 
String>?): String {
+    val uriBuilder = Uri.parse(baseUrl).buildUpon()
+    uriBuilder.appendPath(endpoint)
+    params?.forEach {
+        uriBuilder.appendQueryParameter(it.key, it.value)
+    }
+    return uriBuilder.toString()
 }
diff --git 
a/merchant-terminal/src/main/java/net/taler/merchantpos/config/MerchantConfig.kt
 b/merchant-terminal/src/main/java/net/taler/merchantpos/config/PosConfig.kt
similarity index 65%
rename from 
merchant-terminal/src/main/java/net/taler/merchantpos/config/MerchantConfig.kt
rename to 
merchant-terminal/src/main/java/net/taler/merchantpos/config/PosConfig.kt
index 0c7e3b7..2d8c040 100644
--- 
a/merchant-terminal/src/main/java/net/taler/merchantpos/config/MerchantConfig.kt
+++ b/merchant-terminal/src/main/java/net/taler/merchantpos/config/PosConfig.kt
@@ -16,9 +16,8 @@
 
 package net.taler.merchantpos.config
 
-import android.net.Uri
-import com.fasterxml.jackson.annotation.JsonIgnore
-import com.fasterxml.jackson.annotation.JsonProperty
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
 import net.taler.common.Amount
 import net.taler.common.ContractProduct
 import net.taler.common.Product
@@ -34,51 +33,40 @@ data class Config(
     fun hasPassword() = !password.isBlank()
 }
 
-data class MerchantConfig(
-    @JsonProperty("base_url")
-    val baseUrl: String,
-    val instance: String,
-    @JsonProperty("api_key")
-    val apiKey: String,
-    val currency: String?
-) {
-    fun urlFor(endpoint: String, params: Map<String, String>?): String {
-        val uriBuilder = Uri.parse(baseUrl).buildUpon()
-        uriBuilder.appendPath(endpoint)
-        params?.forEach {
-            uriBuilder.appendQueryParameter(it.key, it.value)
-        }
-        return uriBuilder.toString()
-    }
-    fun convert() = net.taler.merchantlib.MerchantConfig(
-        baseUrl, instance, apiKey
-    )
-}
+@Serializable
+data class PosConfig(
+    @SerialName("config")
+    val merchantConfig: net.taler.merchantlib.MerchantConfig,
+    val categories: List<Category>,
+    val products: List<ConfigProduct>
+)
 
+@Serializable
 data class Category(
     val id: Int,
     val name: String,
-    @JsonProperty("name_i18n")
-    val nameI18n: Map<String, String>?
+    @SerialName("name_i18n")
+    val nameI18n: Map<String, String>? = null
 ) {
     var selected: Boolean = false
     val localizedName: String get() = TalerUtils.getLocalizedString(nameI18n, 
name)
 }
 
+@Serializable
 data class ConfigProduct(
-    @JsonIgnore
     val id: String = UUID.randomUUID().toString(),
-    override val productId: String?,
+    @SerialName("product_id")
+    override val productId: String? = null,
     override val description: String,
-    override val descriptionI18n: Map<String, String>?,
+    @SerialName("description_i18n")
+    override val descriptionI18n: Map<String, String>? = null,
     override val price: Amount,
-    override val location: String?,
-    override val image: String?,
+    @SerialName("delivery_location")
+    override val location: String? = null,
+    override val image: String? = null,
     val categories: List<Int>,
-    @JsonIgnore
     val quantity: Int = 0
 ) : Product() {
-    @get:JsonIgnore
     val totalPrice by lazy { price * quantity }
 
     fun toContractProduct() = ContractProduct(
diff --git 
a/merchant-terminal/src/main/java/net/taler/merchantpos/history/HistoryManager.kt
 
b/merchant-terminal/src/main/java/net/taler/merchantpos/history/HistoryManager.kt
index 6b95e16..24c7a0c 100644
--- 
a/merchant-terminal/src/main/java/net/taler/merchantpos/history/HistoryManager.kt
+++ 
b/merchant-terminal/src/main/java/net/taler/merchantpos/history/HistoryManager.kt
@@ -65,7 +65,7 @@ class HistoryManager(
     internal fun fetchHistory() {
         mIsLoading.value = true
         val merchantConfig = configManager.merchantConfig!!
-        val params = mapOf("instance" to merchantConfig.instance)
+        val params = mapOf("instance" to merchantConfig.instance!!)
         val req = MerchantRequest(GET, merchantConfig, "history", params, null,
             Listener { onHistoryResponse(it) },
             LogErrorListener { onHistoryError() })
diff --git 
a/merchant-terminal/src/main/java/net/taler/merchantpos/order/OrderFragment.kt 
b/merchant-terminal/src/main/java/net/taler/merchantpos/order/OrderFragment.kt
index ad6cd87..7291a23 100644
--- 
a/merchant-terminal/src/main/java/net/taler/merchantpos/order/OrderFragment.kt
+++ 
b/merchant-terminal/src/main/java/net/taler/merchantpos/order/OrderFragment.kt
@@ -65,7 +65,7 @@ class OrderFragment : Fragment() {
         super.onStart()
         if (!viewModel.configManager.config.isValid()) {
             navigate(actionOrderToMerchantSettings())
-        } else if (viewModel.configManager.merchantConfig?.currency == null) {
+        } else if (viewModel.configManager.currency == null) {
             navigate(actionGlobalConfigFetcher())
         }
     }
diff --git 
a/merchant-terminal/src/main/java/net/taler/merchantpos/order/OrderManager.kt 
b/merchant-terminal/src/main/java/net/taler/merchantpos/order/OrderManager.kt
index 46ea238..56cdc8a 100644
--- 
a/merchant-terminal/src/main/java/net/taler/merchantpos/order/OrderManager.kt
+++ 
b/merchant-terminal/src/main/java/net/taler/merchantpos/order/OrderManager.kt
@@ -22,19 +22,14 @@ import androidx.annotation.UiThread
 import androidx.lifecycle.LiveData
 import androidx.lifecycle.MutableLiveData
 import androidx.lifecycle.Transformations.map
-import com.fasterxml.jackson.core.type.TypeReference
-import com.fasterxml.jackson.databind.ObjectMapper
 import net.taler.merchantpos.R
 import net.taler.merchantpos.config.Category
 import net.taler.merchantpos.config.ConfigProduct
 import net.taler.merchantpos.config.ConfigurationReceiver
+import net.taler.merchantpos.config.PosConfig
 import net.taler.merchantpos.order.RestartState.ENABLED
-import org.json.JSONObject
 
-class OrderManager(
-    private val context: Context,
-    private val mapper: ObjectMapper
-) : ConfigurationReceiver {
+class OrderManager(private val context: Context) : ConfigurationReceiver {
 
     companion object {
         val TAG = OrderManager::class.java.simpleName
@@ -55,26 +50,18 @@ class OrderManager(
     private val mCategories = MutableLiveData<List<Category>>()
     internal val categories: LiveData<List<Category>> = mCategories
 
-    override suspend fun onConfigurationReceived(json: JSONObject, currency: 
String): String? {
+    override suspend fun onConfigurationReceived(posConfig: PosConfig, 
currency: String): String? {
         // parse categories
-        val categoriesStr = json.getJSONArray("categories").toString()
-        val categoriesType = object : TypeReference<List<Category>>() {}
-        val categories: List<Category> = mapper.readValue(categoriesStr, 
categoriesType)
-        if (categories.isEmpty()) {
+        if (posConfig.categories.isEmpty()) {
             Log.e(TAG, "No valid category found.")
             return context.getString(R.string.config_error_category)
         }
         // pre-select the first category
-        categories[0].selected = true
-
-        // parse products (live data gets updated in setCurrentCategory())
-        val productsStr = json.getJSONArray("products").toString()
-        val productsType = object : TypeReference<List<ConfigProduct>>() {}
-        val products: List<ConfigProduct> = mapper.readValue(productsStr, 
productsType)
+        posConfig.categories[0].selected = true
 
         // group products by categories
         productsByCategory.clear()
-        products.forEach { product ->
+        posConfig.products.forEach { product ->
             val productCurrency = product.price.currency
             if (productCurrency != currency) {
                 Log.e(TAG, "Product $product has currency $productCurrency, 
$currency expected")
@@ -83,7 +70,7 @@ class OrderManager(
                 )
             }
             product.categories.forEach { categoryId ->
-                val category = categories.find { it.id == categoryId }
+                val category = posConfig.categories.find { it.id == categoryId 
}
                 if (category == null) {
                     Log.e(TAG, "Product $product has unknown category 
$categoryId")
                     return context.getString(
@@ -99,8 +86,8 @@ class OrderManager(
         }
         return if (productsByCategory.size > 0) {
             this.currency = currency
-            mCategories.postValue(categories)
-            mProducts.postValue(productsByCategory[categories[0]])
+            mCategories.postValue(posConfig.categories)
+            mProducts.postValue(productsByCategory[posConfig.categories[0]])
             // Initialize first empty order, note this won't work when 
updating config mid-flight
             if (orders.isEmpty()) {
                 val id = orderCounter++
diff --git 
a/merchant-terminal/src/main/java/net/taler/merchantpos/payment/PaymentManager.kt
 
b/merchant-terminal/src/main/java/net/taler/merchantpos/payment/PaymentManager.kt
index ea16cb4..fc4f642 100644
--- 
a/merchant-terminal/src/main/java/net/taler/merchantpos/payment/PaymentManager.kt
+++ 
b/merchant-terminal/src/main/java/net/taler/merchantpos/payment/PaymentManager.kt
@@ -63,9 +63,9 @@ class PaymentManager(
     @UiThread
     fun createPayment(order: Order) {
         val merchantConfig = configManager.merchantConfig!!
-        mPayment.value = Payment(order, order.summary, 
merchantConfig.currency!!)
+        mPayment.value = Payment(order, order.summary, 
configManager.currency!!)
         scope.launch(Dispatchers.IO) {
-            val response = api.postOrder(merchantConfig.convert(), 
order.toContractTerms())
+            val response = api.postOrder(merchantConfig, 
order.toContractTerms())
             response.handle(::onNetworkError, ::onOrderCreated)
         }
     }
@@ -78,7 +78,7 @@ class PaymentManager(
     private fun checkPayment(orderId: String) {
         val merchantConfig = configManager.merchantConfig!!
         scope.launch(Dispatchers.IO) {
-            val response = api.checkOrder(merchantConfig.convert(), orderId)
+            val response = api.checkOrder(merchantConfig, orderId)
             response.handle(::onNetworkError, ::onPaymentChecked)
         }
     }
@@ -106,7 +106,7 @@ class PaymentManager(
             if (!payment.paid) payment.orderId?.let { orderId ->
                 Log.e(TAG, "Deleting cancelled and unpaid order $orderId")
                 scope.launch(Dispatchers.IO) {
-                    api.deleteOrder(merchantConfig.convert(), orderId)
+                    api.deleteOrder(merchantConfig, orderId)
                 }
             }
         }
diff --git 
a/merchant-terminal/src/test/java/net/taler/merchantpos/order/OrderManagerTest.kt
 
b/merchant-terminal/src/test/java/net/taler/merchantpos/order/OrderManagerTest.kt
index d06428d..bb8dcb7 100644
--- 
a/merchant-terminal/src/test/java/net/taler/merchantpos/order/OrderManagerTest.kt
+++ 
b/merchant-terminal/src/test/java/net/taler/merchantpos/order/OrderManagerTest.kt
@@ -19,12 +19,13 @@ package net.taler.merchantpos.order
 import android.app.Application
 import androidx.test.core.app.ApplicationProvider.getApplicationContext
 import androidx.test.ext.junit.runners.AndroidJUnit4
-import 
com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES
-import com.fasterxml.jackson.databind.ObjectMapper
-import com.fasterxml.jackson.module.kotlin.KotlinModule
 import kotlinx.coroutines.runBlocking
+import net.taler.common.Amount
+import net.taler.merchantlib.MerchantConfig
 import net.taler.merchantpos.R
-import org.json.JSONObject
+import net.taler.merchantpos.config.Category
+import net.taler.merchantpos.config.ConfigProduct
+import net.taler.merchantpos.config.PosConfig
 import org.junit.Assert.assertEquals
 import org.junit.Assert.assertNull
 import org.junit.Test
@@ -35,118 +36,71 @@ import org.robolectric.annotation.Config
 @RunWith(AndroidJUnit4::class)
 class OrderManagerTest {
 
-    private val mapper = ObjectMapper()
-        .registerModule(KotlinModule())
-        .configure(FAIL_ON_UNKNOWN_PROPERTIES, false)
-
     private val app: Application = getApplicationContext()
-    private val orderManager = OrderManager(app, mapper)
+    private val orderManager = OrderManager(app)
+    private val posConfig = PosConfig(
+        merchantConfig = MerchantConfig(
+            baseUrl = "http://example.org";,
+            apiKey = "sandbox"
+        ),
+        categories = listOf(
+            Category(1, "one"),
+            Category(2, "two")
+        ),
+        products = listOf(
+            ConfigProduct(
+                description = "foo",
+                price = Amount("KUDOS", 1, 0),
+                categories = listOf(1)
+            ),
+            ConfigProduct(
+                description = "bar",
+                price = Amount("KUDOS", 1, 50000),
+                categories = listOf(2)
+            )
+        )
+    )
 
     @Test
     fun `config test missing categories`() = runBlocking {
-        val json = JSONObject(
-            """
-            { "categories": [] }
-        """.trimIndent()
-        )
-        val result = orderManager.onConfigurationReceived(json, "KUDOS")
+        val config = posConfig.copy(categories = emptyList())
+        val result = orderManager.onConfigurationReceived(config, "KUDOS")
         assertEquals(app.getString(R.string.config_error_category), result)
     }
 
     @Test
     fun `config test currency mismatch`() = runBlocking {
-        val json = JSONObject(
-            """{
-            "categories": [
-                {
-                    "id": 1,
-                    "name": "Snacks"
-                }
-            ],
-            "products": [
-                {
-                    "product_id": "631361561",
-                    "description": "Chips",
-                    "price": "WRONGCUR:1.00",
-                    "categories": [ 1 ],
-                    "delivery_location": "cafeteria"
-                }
-            ]
-        }""".trimIndent()
-        )
-        val result = orderManager.onConfigurationReceived(json, "KUDOS")
+        val products = listOf(posConfig.products[0].copy(price = 
Amount("WRONGCUR", 1, 0)))
+        val config = posConfig.copy(products = products)
+        val result = orderManager.onConfigurationReceived(config, "KUDOS")
         val expectedStr = app.getString(
-            R.string.config_error_currency, "Chips", "WRONGCUR", "KUDOS"
+            R.string.config_error_currency, "foo", "WRONGCUR", "KUDOS"
         )
         assertEquals(expectedStr, result)
     }
 
     @Test
     fun `config test unknown category ID`() = runBlocking {
-        val json = JSONObject(
-            """{
-            "categories": [
-                {
-                    "id": 1,
-                    "name": "Snacks"
-                }
-            ],
-            "products": [
-                {
-                    "product_id": "631361561",
-                    "description": "Chips",
-                    "price": "KUDOS:1.00",
-                    "categories": [ 2 ]
-                }
-            ]
-        }""".trimIndent()
-        )
-        val result = orderManager.onConfigurationReceived(json, "KUDOS")
+        val products = listOf(posConfig.products[0].copy(categories = 
listOf(42)))
+        val config = posConfig.copy(products = products)
+        val result = orderManager.onConfigurationReceived(config, "KUDOS")
         val expectedStr = app.getString(
-            R.string.config_error_product_category_id, "Chips", 2
+            R.string.config_error_product_category_id, "foo", 42
         )
         assertEquals(expectedStr, result)
     }
 
     @Test
     fun `config test no products`() = runBlocking {
-        val json = JSONObject(
-            """{
-            "categories": [
-                {
-                    "id": 1,
-                    "name": "Snacks"
-                }
-            ],
-            "products": []
-        }""".trimIndent()
-        )
-        val result = orderManager.onConfigurationReceived(json, "KUDOS")
+        val config = posConfig.copy(products = emptyList())
+        val result = orderManager.onConfigurationReceived(config, "KUDOS")
         val expectedStr = app.getString(R.string.config_error_product_zero)
         assertEquals(expectedStr, result)
     }
 
     @Test
     fun `config test valid config gets accepted`() = runBlocking {
-        val json = JSONObject(
-            """{
-            "categories": [
-                {
-                    "id": 1,
-                    "name": "Snacks"
-                }
-            ],
-            "products": [
-                {
-                    "product_id": "631361561",
-                    "description": "Chips",
-                    "price": "KUDOS:1.00",
-                    "categories": [ 1 ]
-                }
-            ]
-        }""".trimIndent()
-        )
-        val result = orderManager.onConfigurationReceived(json, "KUDOS")
+        val result = orderManager.onConfigurationReceived(posConfig, "KUDOS")
         assertNull(result)
     }
 
diff --git a/taler-kotlin-common/build.gradle b/taler-kotlin-common/build.gradle
index df4b65f..dd083b7 100644
--- a/taler-kotlin-common/build.gradle
+++ b/taler-kotlin-common/build.gradle
@@ -62,7 +62,7 @@ dependencies {
     implementation 'com.google.zxing:core:3.4.0'  // needs minSdkVersion 24+
 
     // JSON parsing and serialization
-    implementation "org.jetbrains.kotlinx:kotlinx-serialization-runtime:0.20.0"
+    api "org.jetbrains.kotlinx:kotlinx-serialization-runtime:0.20.0"
     implementation "com.fasterxml.jackson.module:jackson-module-kotlin:2.10.2"
 
     lintChecks 'com.github.thirdegg:lint-rules:0.0.4-alpha'

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]