[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[taler-taler-android] branch master updated (a8c811f -> 8eb241c)
From: |
gnunet |
Subject: |
[taler-taler-android] branch master updated (a8c811f -> 8eb241c) |
Date: |
Thu, 23 Jul 2020 20:44:35 +0200 |
This is an automated email from the git hooks/post-receive script.
torsten-grote pushed a change to branch master
in repository taler-android.
from a8c811f [pos] migrate order posting and checking to v1 API and
merchant-lib
new 08b10a2 [pos] delete unpaid/unclaimed orders when user cancels or
timeout happens
new 8eb241c [pos] refactor configuration fetching and validation
The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
Summary of changes:
build.gradle | 1 +
merchant-lib/build.gradle | 9 +-
.../main/java/net/taler/merchantlib/MerchantApi.kt | 38 +++---
.../java/net/taler/merchantlib/MerchantConfig.kt | 5 +-
.../main/java/net/taler/merchantlib/Response.kt | 27 ++++-
.../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 | 23 +++-
.../taler/merchantpos/order/OrderManagerTest.kt | 128 +++++++--------------
taler-kotlin-common/build.gradle | 2 +-
17 files changed, 230 insertions(+), 236 deletions(-)
rename
merchant-terminal/src/main/java/net/taler/merchantpos/config/{MerchantConfig.kt
=> PosConfig.kt} (65%)
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 335e42d..e995724 100644
--- a/merchant-lib/src/main/java/net/taler/merchantlib/MerchantApi.kt
+++ b/merchant-lib/src/main/java/net/taler/merchantlib/MerchantApi.kt
@@ -16,28 +16,29 @@
package net.taler.merchantlib
+import android.util.Log
import io.ktor.client.HttpClient
import io.ktor.client.engine.okhttp.OkHttp
import io.ktor.client.features.json.JsonFeature
import io.ktor.client.features.json.serializer.KotlinxSerializer
+import io.ktor.client.request.delete
import io.ktor.client.request.get
import io.ktor.client.request.header
import io.ktor.client.request.post
+import io.ktor.client.statement.HttpResponse
+import io.ktor.client.statement.readBytes
import io.ktor.http.ContentType.Application.Json
import io.ktor.http.HttpHeaders.Authorization
import io.ktor.http.contentType
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonConfiguration
import net.taler.common.ContractTerms
-import net.taler.merchantlib.Response.Companion.failure
-import net.taler.merchantlib.Response.Companion.success
+import net.taler.merchantlib.Response.Companion.response
class MerchantApi(private val httpClient: HttpClient) {
- constructor() : this(getDefaultHttpClient())
-
- 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(
@@ -60,16 +61,27 @@ class MerchantApi(private val httpClient: HttpClient) {
} as CheckPaymentResponse
}
- private suspend fun <T> response(request: suspend () -> T): Response<T> {
- return try {
- success(request())
- } catch (e: Throwable) {
- failure(e)
- }
+ suspend fun deleteOrder(
+ merchantConfig: MerchantConfig,
+ orderId: String
+ ): Response<HttpResponse> = response {
+ val resp =
httpClient.delete(merchantConfig.urlFor("private/orders/$orderId")) {
+ header(Authorization, "ApiKey ${merchantConfig.apiKey}")
+ } as HttpResponse
+ // TODO remove when the API call was fixed
+ Log.e("TEST", "status: ${resp.status.value}")
+ Log.e("TEST", String(resp.readBytes()))
+ resp
}
+
}
-private fun getDefaultHttpClient(): HttpClient = HttpClient(OkHttp) {
+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 23fa101..1b49d78 100644
--- a/merchant-lib/src/main/java/net/taler/merchantlib/Response.kt
+++ b/merchant-lib/src/main/java/net/taler/merchantlib/Response.kt
@@ -25,6 +25,14 @@ class Response<out T> private constructor(
) {
companion object {
+ suspend fun <T> response(request: suspend () -> T): Response<T> {
+ return try {
+ success(request())
+ } catch (e: Throwable) {
+ failure(e)
+ }
+ }
+
fun <T> success(value: T): Response<T> =
Response(value)
@@ -42,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 e238284..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
@@ -18,6 +18,7 @@ package net.taler.merchantpos.payment
import android.content.Context
import android.os.CountDownTimer
+import android.util.Log
import androidx.annotation.UiThread
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
@@ -27,6 +28,7 @@ import kotlinx.coroutines.launch
import net.taler.merchantlib.CheckPaymentResponse
import net.taler.merchantlib.MerchantApi
import net.taler.merchantlib.PostOrderResponse
+import net.taler.merchantpos.MainActivity.Companion.TAG
import net.taler.merchantpos.R
import net.taler.merchantpos.config.ConfigManager
import net.taler.merchantpos.order.Order
@@ -54,17 +56,16 @@ class PaymentManager(
}
override fun onFinish() {
- val str = context.getString(R.string.error_timeout)
- payment.value?.copy(error = str)?.let { mPayment.value = it }
+ cancelPayment(context.getString(R.string.error_timeout))
}
}
@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)
}
}
@@ -77,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)
}
}
@@ -97,7 +98,19 @@ class PaymentManager(
cancelPayment(error)
}
+ @UiThread
fun cancelPayment(error: String) {
+ // delete unpaid order
+ val merchantConfig = configManager.merchantConfig!!
+ mPayment.value?.let { payment ->
+ if (!payment.paid) payment.orderId?.let { orderId ->
+ Log.e(TAG, "Deleting cancelled and unpaid order $orderId")
+ scope.launch(Dispatchers.IO) {
+ api.deleteOrder(merchantConfig, orderId)
+ }
+ }
+ }
+
mPayment.value = mPayment.value!!.copy(error = error)
checkTimer.cancel()
}
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.
- [taler-taler-android] branch master updated (a8c811f -> 8eb241c),
gnunet <=