gnunet-svn
[Top][All Lists]
Advanced

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

[taler-taler-android] 02/02: [pos] migrate order posting and checking to


From: gnunet
Subject: [taler-taler-android] 02/02: [pos] migrate order posting and checking to v1 API and merchant-lib
Date: Wed, 22 Jul 2020 21:53:59 +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 a8c811f6cdf4bf1b787ebaaa9fd220588fd1ffcf
Author: Torsten Grote <t@grobox.de>
AuthorDate: Wed Jul 22 16:53:06 2020 -0300

    [pos] migrate order posting and checking to v1 API and merchant-lib
---
 .../main/java/net/taler/merchantlib/MerchantApi.kt |  48 ++++++++-
 .../java/net/taler/merchantlib/MerchantConfig.kt   |  30 ++++--
 .../java/net/taler/merchantlib/PostOrderRequest.kt |  83 +++++++++++++++
 .../main/java/net/taler/merchantlib/Response.kt    |  63 ++++++++++++
 .../java/net/taler/merchantlib/MerchantApiTest.kt  |  95 +++++++++++++++++
 .../java/net/taler/merchantlib/MockHttpClient.kt   |  33 +++++-
 .../java/net/taler/merchantlib/TestResponse.kt     |  22 ++--
 merchant-terminal/.gitlab-ci.yml                   |   1 +
 .../java/net/taler/merchantpos/MainViewModel.kt    |   2 +-
 .../net/taler/merchantpos/config/MerchantConfig.kt |   5 +-
 .../taler/merchantpos/config/MerchantRequest.kt    |   1 -
 .../main/java/net/taler/merchantpos/order/Order.kt |  21 ++++
 .../net/taler/merchantpos/order/OrderManager.kt    |   2 +-
 .../java/net/taler/merchantpos/payment/Payment.kt  |   2 +-
 .../taler/merchantpos/payment/PaymentManager.kt    | 113 ++++++---------------
 .../merchantpos/payment/ProcessPaymentFragment.kt  |   6 +-
 merchant-terminal/src/main/res/values/strings.xml  |   2 +
 17 files changed, 412 insertions(+), 117 deletions(-)

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 3406f78..335e42d 100644
--- a/merchant-lib/src/main/java/net/taler/merchantlib/MerchantApi.kt
+++ b/merchant-lib/src/main/java/net/taler/merchantlib/MerchantApi.kt
@@ -21,6 +21,16 @@ 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.get
+import io.ktor.client.request.header
+import io.ktor.client.request.post
+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
 
 class MerchantApi(private val httpClient: HttpClient) {
 
@@ -30,10 +40,46 @@ class MerchantApi(private val httpClient: HttpClient) {
         return httpClient.get("$baseUrl/config")
     }
 
+    suspend fun postOrder(
+        merchantConfig: MerchantConfig,
+        contractTerms: ContractTerms
+    ): Response<PostOrderResponse> = response {
+        httpClient.post(merchantConfig.urlFor("private/orders")) {
+            header(Authorization, "ApiKey ${merchantConfig.apiKey}")
+            contentType(Json)
+            body = PostOrderRequest(contractTerms)
+        } as PostOrderResponse
+    }
+
+    suspend fun checkOrder(
+        merchantConfig: MerchantConfig,
+        orderId: String
+    ): Response<CheckPaymentResponse> = response {
+        httpClient.get(merchantConfig.urlFor("private/orders/$orderId")) {
+            header(Authorization, "ApiKey ${merchantConfig.apiKey}")
+        } as CheckPaymentResponse
+    }
+
+    private suspend fun <T> response(request: suspend () -> T): Response<T> {
+        return try {
+            success(request())
+        } catch (e: Throwable) {
+            failure(e)
+        }
+    }
 }
 
 private fun getDefaultHttpClient(): HttpClient = HttpClient(OkHttp) {
     install(JsonFeature) {
-        serializer = KotlinxSerializer()
+        serializer = getSerializer()
     }
 }
+
+fun getSerializer() = KotlinxSerializer(
+    Json(
+        JsonConfiguration(
+            encodeDefaults = false,
+            ignoreUnknownKeys = true
+        )
+    )
+)
diff --git 
a/merchant-terminal/src/main/java/net/taler/merchantpos/payment/Payment.kt 
b/merchant-lib/src/main/java/net/taler/merchantlib/MerchantConfig.kt
similarity index 56%
copy from 
merchant-terminal/src/main/java/net/taler/merchantpos/payment/Payment.kt
copy to merchant-lib/src/main/java/net/taler/merchantlib/MerchantConfig.kt
index b7e4a4b..71185b9 100644
--- a/merchant-terminal/src/main/java/net/taler/merchantpos/payment/Payment.kt
+++ b/merchant-lib/src/main/java/net/taler/merchantlib/MerchantConfig.kt
@@ -14,16 +14,24 @@
  * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 
-package net.taler.merchantpos.payment
+package net.taler.merchantlib
 
-import net.taler.merchantpos.order.Order
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
 
-data class Payment(
-    val order: Order,
-    val summary: String,
-    val currency: String,
-    val orderId: String? = null,
-    val talerPayUri: String? = null,
-    val paid: Boolean = false,
-    val error: Boolean = false
-)
+@Serializable
+data class MerchantConfig(
+    @SerialName("base_url")
+    val baseUrl: String,
+    val instance: String,
+    @SerialName("api_key")
+    val apiKey: String
+) {
+    fun urlFor(endpoint: String, params: Map<String, String>? = null): String {
+        val sb = StringBuilder(baseUrl)
+        if (sb.last() != '/') sb.append('/')
+        sb.append("instances/$instance/")
+        sb.append(endpoint)
+        return sb.toString()
+    }
+}
diff --git 
a/merchant-lib/src/main/java/net/taler/merchantlib/PostOrderRequest.kt 
b/merchant-lib/src/main/java/net/taler/merchantlib/PostOrderRequest.kt
new file mode 100644
index 0000000..a6e74d6
--- /dev/null
+++ b/merchant-lib/src/main/java/net/taler/merchantlib/PostOrderRequest.kt
@@ -0,0 +1,83 @@
+/*
+ * This file is part of GNU Taler
+ * (C) 2020 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.merchantlib
+
+import kotlinx.serialization.Decoder
+import kotlinx.serialization.Encoder
+import kotlinx.serialization.KSerializer
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+import kotlinx.serialization.Serializer
+import kotlinx.serialization.json.JsonInput
+import kotlinx.serialization.json.JsonObject
+import net.taler.common.ContractTerms
+
+@Serializable
+data class PostOrderRequest(
+    @SerialName("order")
+    val contractTerms: ContractTerms
+
+)
+
+@Serializable
+data class PostOrderResponse(
+    @SerialName("order_id")
+    val orderId: String
+)
+
+@Serializable
+sealed class CheckPaymentResponse {
+    abstract val paid: Boolean
+
+    @Serializer(forClass = CheckPaymentResponse::class)
+    companion object : KSerializer<CheckPaymentResponse> {
+        override fun deserialize(decoder: Decoder): CheckPaymentResponse {
+            val input = decoder as JsonInput
+            val tree = input.decodeJson() as JsonObject
+            val paid = tree.getPrimitive("paid").boolean
+//            return if (paid) decoder.json.fromJson(Paid.serializer(), tree)
+//            else decoder.json.fromJson(Unpaid.serializer(), tree)
+            // manual parsing due to 
https://github.com/Kotlin/kotlinx.serialization/issues/576
+            return if (paid) Paid(
+                refunded = tree.getPrimitive("refunded").boolean
+            ) else Unpaid(
+                talerPayUri = tree.getPrimitive("taler_pay_uri").content
+            )
+        }
+
+        override fun serialize(encoder: Encoder, value: CheckPaymentResponse) 
= when (value) {
+            is Unpaid -> Unpaid.serializer().serialize(encoder, value)
+            is Paid -> Paid.serializer().serialize(encoder, value)
+        }
+    }
+
+    @Serializable
+    data class Unpaid(
+        override val paid: Boolean = false,
+        @SerialName("taler_pay_uri")
+        val talerPayUri: String,
+        @SerialName("already_paid_order_id")
+        val alreadyPaidOrderId: String? = null
+    ) : CheckPaymentResponse()
+
+    @Serializable
+    data class Paid(
+        override val paid: Boolean = true,
+        val refunded: Boolean
+    ) : CheckPaymentResponse()
+
+}
diff --git a/merchant-lib/src/main/java/net/taler/merchantlib/Response.kt 
b/merchant-lib/src/main/java/net/taler/merchantlib/Response.kt
new file mode 100644
index 0000000..23fa101
--- /dev/null
+++ b/merchant-lib/src/main/java/net/taler/merchantlib/Response.kt
@@ -0,0 +1,63 @@
+/*
+ * This file is part of GNU Taler
+ * (C) 2020 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.merchantlib
+
+import io.ktor.client.call.receive
+import io.ktor.client.features.ClientRequestException
+import kotlinx.serialization.Serializable
+
+class Response<out T> private constructor(
+    private val value: Any?
+) {
+
+    companion object {
+        fun <T> success(value: T): Response<T> =
+            Response(value)
+
+        fun <T> failure(e: Throwable): Response<T> =
+            Response(Failure(e))
+    }
+
+    val isFailure: Boolean get() = value is Failure
+
+    suspend fun handle(onFailure: ((String) -> Any)? = null, onSuccess: ((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}"
+    }
+
+    private class Failure(val exception: Throwable)
+
+    @Serializable
+    private class Error(
+        val code: Int?,
+        val hint: String?
+    )
+
+}
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 6b2199b..de1ca33 100644
--- a/merchant-lib/src/test/java/net/taler/merchantlib/MerchantApiTest.kt
+++ b/merchant-lib/src/test/java/net/taler/merchantlib/MerchantApiTest.kt
@@ -16,15 +16,25 @@
 
 package net.taler.merchantlib
 
+import io.ktor.http.HttpStatusCode
 import kotlinx.coroutines.runBlocking
+import net.taler.common.Amount
+import net.taler.common.ContractProduct
+import net.taler.common.ContractTerms
 import net.taler.merchantlib.MockHttpClient.giveJsonResponse
 import net.taler.merchantlib.MockHttpClient.httpClient
 import org.junit.Assert.assertEquals
+import org.junit.Assert.assertTrue
 import org.junit.Test
 
 class MerchantApiTest {
 
     private val api = MerchantApi(httpClient)
+    private val merchantConfig = MerchantConfig(
+        baseUrl = "http://example.net/";,
+        instance = "testInstance",
+        apiKey = "apiKeyFooBar"
+    )
 
     @Test
     fun testGetConfig() = runBlocking {
@@ -40,4 +50,89 @@ class MerchantApiTest {
         assertEquals(ConfigResponse("0:0:0", "INTKUDOS"), response)
     }
 
+    @Test
+    fun testPostOrder() = runBlocking {
+        val product = ContractProduct(
+            productId = "foo",
+            description = "bar",
+            price = Amount("TEST", 1, 0),
+            quantity = 2
+        )
+        val contractTerms = ContractTerms(
+            summary = "test",
+            amount = Amount("TEST", 2, 1),
+            fulfillmentUrl = "http://example.org";,
+            products = listOf(product)
+        )
+        val contractTermsJson = """
+            {
+                "order": {
+                    "summary": "${contractTerms.summary}",
+                    "amount": "${contractTerms.amount.toJSONString()}",
+                    "fulfillment_url": "${contractTerms.fulfillmentUrl}",
+                    "products": [
+                        {
+                            "product_id": "${product.productId}",
+                            "description": "${product.description}",
+                            "price": "${product.price.toJSONString()}",
+                            "quantity": ${product.quantity}
+                        }
+                    ]
+                }
+            }
+        """.trimIndent()
+        httpClient.giveJsonResponse(
+            "http://example.net/instances/testInstance/private/orders";,
+            contractTermsJson
+        ) {
+            """{"order_id": "test"}"""
+        }
+        api.postOrder(merchantConfig, contractTerms).assertSuccess {
+            assertEquals(PostOrderResponse("test"), it)
+        }
+
+        httpClient.giveJsonResponse(
+            "http://example.net/instances/testInstance/private/orders";,
+            statusCode = HttpStatusCode.NotFound
+        ) {
+            """{
+                "code": 2000,
+                "hint": "merchant instance unknown"
+            }"""
+        }
+        api.postOrder(merchantConfig, contractTerms).assertFailure {
+            assertTrue(it.contains("2000"))
+            assertTrue(it.contains("merchant instance unknown"))
+        }
+    }
+
+    @Test
+    fun testCheckOrder() = runBlocking {
+        val orderId = "orderIdFoo"
+        val unpaidResponse = CheckPaymentResponse.Unpaid(false, 
"http://taler.net/foo";)
+        
httpClient.giveJsonResponse("http://example.net/instances/testInstance/private/orders/$orderId";)
 {
+            """{
+                "paid": ${unpaidResponse.paid},
+                "taler_pay_uri": "${unpaidResponse.talerPayUri}"
+            }""".trimIndent()
+        }
+        api.checkOrder(merchantConfig, orderId).assertSuccess {
+            assertEquals(unpaidResponse, it)
+        }
+
+        httpClient.giveJsonResponse(
+            
"http://example.net/instances/testInstance/private/orders/$orderId";,
+            statusCode = HttpStatusCode.NotFound
+        ) {
+            """{
+                "code": 2909,
+                "hint": "Did not find contract terms for order in DB"
+            }"""
+        }
+        api.checkOrder(merchantConfig, orderId).assertFailure {
+            assertTrue(it.contains("2909"))
+            assertTrue(it.contains("Did not find contract terms for order in 
DB"))
+        }
+    }
+
 }
diff --git a/merchant-lib/src/test/java/net/taler/merchantlib/MockHttpClient.kt 
b/merchant-lib/src/test/java/net/taler/merchantlib/MockHttpClient.kt
index 076b77e..993be15 100644
--- a/merchant-lib/src/test/java/net/taler/merchantlib/MockHttpClient.kt
+++ b/merchant-lib/src/test/java/net/taler/merchantlib/MockHttpClient.kt
@@ -21,31 +21,50 @@ import io.ktor.client.engine.mock.MockEngine
 import io.ktor.client.engine.mock.MockEngineConfig
 import io.ktor.client.engine.mock.respond
 import io.ktor.client.features.json.JsonFeature
-import io.ktor.client.features.json.serializer.KotlinxSerializer
+import io.ktor.client.features.logging.LogLevel
+import io.ktor.client.features.logging.Logger
+import io.ktor.client.features.logging.Logging
+import io.ktor.client.features.logging.SIMPLE
 import io.ktor.http.ContentType.Application.Json
+import io.ktor.http.HttpStatusCode
 import io.ktor.http.Url
+import io.ktor.http.content.TextContent
 import io.ktor.http.fullPath
 import io.ktor.http.headersOf
 import io.ktor.http.hostWithPort
+import org.junit.Assert.assertEquals
 
 object MockHttpClient {
 
     val httpClient = HttpClient(MockEngine) {
         install(JsonFeature) {
-            serializer = KotlinxSerializer()
+            serializer = getSerializer()
+        }
+        install(Logging) {
+            logger = Logger.SIMPLE
+            level = LogLevel.ALL
         }
         engine {
             addHandler { error("No response handler set") }
         }
     }
 
-    fun HttpClient.giveJsonResponse(url: String, jsonProducer: () -> String) {
+    fun HttpClient.giveJsonResponse(
+        url: String,
+        expectedBody: String? = null,
+        statusCode: HttpStatusCode = HttpStatusCode.OK,
+        jsonProducer: () -> String
+    ) {
         val httpConfig = engineConfig as MockEngineConfig
         httpConfig.requestHandlers.removeAt(0)
         httpConfig.requestHandlers.add { request ->
             if (request.url.fullUrl == url) {
                 val headers = headersOf("Content-Type" to 
listOf(Json.toString()))
-                respond(jsonProducer(), headers = headers)
+                if (expectedBody != null) {
+                    val content = request.body as TextContent
+                    assertJsonEquals(expectedBody, content.text)
+                }
+                respond(jsonProducer(), headers = headers, status = statusCode)
             } else {
                 error("Unexpected URL: ${request.url.fullUrl}")
             }
@@ -55,4 +74,10 @@ object MockHttpClient {
     private val Url.hostWithPortIfRequired: String get() = if (port == 
protocol.defaultPort) host else hostWithPort
     private val Url.fullUrl: String get() = 
"${protocol.name}://$hostWithPortIfRequired$fullPath"
 
+    private fun assertJsonEquals(json1: String, json2: String) {
+        val parsed1 = kotlinx.serialization.json.Json.parseJson(json1)
+        val parsed2 = kotlinx.serialization.json.Json.parseJson(json2)
+        assertEquals(parsed1, parsed2)
+    }
+
 }
diff --git 
a/merchant-terminal/src/main/java/net/taler/merchantpos/payment/Payment.kt 
b/merchant-lib/src/test/java/net/taler/merchantlib/TestResponse.kt
similarity index 65%
copy from 
merchant-terminal/src/main/java/net/taler/merchantpos/payment/Payment.kt
copy to merchant-lib/src/test/java/net/taler/merchantlib/TestResponse.kt
index b7e4a4b..0d3d906 100644
--- a/merchant-terminal/src/main/java/net/taler/merchantpos/payment/Payment.kt
+++ b/merchant-lib/src/test/java/net/taler/merchantlib/TestResponse.kt
@@ -14,16 +14,16 @@
  * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 
-package net.taler.merchantpos.payment
+package net.taler.merchantlib
 
-import net.taler.merchantpos.order.Order
+import org.junit.Assert
 
-data class Payment(
-    val order: Order,
-    val summary: String,
-    val currency: String,
-    val orderId: String? = null,
-    val talerPayUri: String? = null,
-    val paid: Boolean = false,
-    val error: Boolean = false
-)
+internal suspend fun <T> Response<T>.assertSuccess(assertions: (T) -> Any) {
+    Assert.assertFalse(isFailure)
+    handle(onSuccess = { assertions(it) })
+}
+
+internal suspend fun <T> Response<T>.assertFailure(assertions: (String) -> 
Any) {
+    Assert.assertTrue(isFailure)
+    handle(onFailure = { assertions(it) })
+}
diff --git a/merchant-terminal/.gitlab-ci.yml b/merchant-terminal/.gitlab-ci.yml
index 034818c..74ac21f 100644
--- a/merchant-terminal/.gitlab-ci.yml
+++ b/merchant-terminal/.gitlab-ci.yml
@@ -3,6 +3,7 @@ merchant_test:
   only:
     changes:
       - merchant-terminal/**/*
+      - merchant-lib/**/*
       - taler-kotlin-common/**/*
       - build.gradle
   script: ./gradlew :merchant-terminal:check :merchant-terminal:assembleRelease
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 2dd2c24..ce05980 100644
--- a/merchant-terminal/src/main/java/net/taler/merchantpos/MainViewModel.kt
+++ b/merchant-terminal/src/main/java/net/taler/merchantpos/MainViewModel.kt
@@ -42,7 +42,7 @@ class MainViewModel(app: Application) : AndroidViewModel(app) 
{
     val configManager = ConfigManager(app, viewModelScope, api, mapper, 
queue).apply {
         addConfigurationReceiver(orderManager)
     }
-    val paymentManager = PaymentManager(configManager, queue, mapper)
+    val paymentManager = PaymentManager(app, configManager, viewModelScope, 
api)
     val historyManager = HistoryManager(configManager, queue, mapper)
     val refundManager = RefundManager(configManager, queue)
 
diff --git 
a/merchant-terminal/src/main/java/net/taler/merchantpos/config/MerchantConfig.kt
 
b/merchant-terminal/src/main/java/net/taler/merchantpos/config/MerchantConfig.kt
index 0e707d3..0c7e3b7 100644
--- 
a/merchant-terminal/src/main/java/net/taler/merchantpos/config/MerchantConfig.kt
+++ 
b/merchant-terminal/src/main/java/net/taler/merchantpos/config/MerchantConfig.kt
@@ -23,7 +23,7 @@ import net.taler.common.Amount
 import net.taler.common.ContractProduct
 import net.taler.common.Product
 import net.taler.common.TalerUtils
-import java.util.*
+import java.util.UUID
 
 data class Config(
     val configUrl: String,
@@ -50,6 +50,9 @@ data class MerchantConfig(
         }
         return uriBuilder.toString()
     }
+    fun convert() = net.taler.merchantlib.MerchantConfig(
+        baseUrl, instance, apiKey
+    )
 }
 
 data class Category(
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 6c9c741..9cfae94 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,7 +16,6 @@
 
 package net.taler.merchantpos.config
 
-
 import android.util.ArrayMap
 import com.android.volley.Response
 import com.android.volley.toolbox.JsonObjectRequest
diff --git 
a/merchant-terminal/src/main/java/net/taler/merchantpos/order/Order.kt 
b/merchant-terminal/src/main/java/net/taler/merchantpos/order/Order.kt
index ff6e6b7..bb75362 100644
--- a/merchant-terminal/src/main/java/net/taler/merchantpos/order/Order.kt
+++ b/merchant-terminal/src/main/java/net/taler/merchantpos/order/Order.kt
@@ -17,8 +17,13 @@
 package net.taler.merchantpos.order
 
 import net.taler.common.Amount
+import net.taler.common.ContractTerms
+import net.taler.common.now
 import net.taler.merchantpos.config.Category
 import net.taler.merchantpos.config.ConfigProduct
+import java.net.URLEncoder
+
+private const val FULFILLMENT_PREFIX = "taler://fulfillment-success/"
 
 data class Order(val id: Int, val currency: String, val availableCategories: 
Map<Int, Category>) {
     val products = ArrayList<ConfigProduct>()
@@ -103,4 +108,20 @@ data class Order(val id: Int, val currency: String, val 
availableCategories: Map
             }.toMap()
         }
 
+    private val fulfillmentUri: String
+        get() {
+            val fulfillmentId = "${now()}-${hashCode()}"
+            return "$FULFILLMENT_PREFIX${URLEncoder.encode(summary, 
"UTF-8")}#$fulfillmentId"
+        }
+
+    fun toContractTerms(): ContractTerms {
+        return ContractTerms(
+            summary = summary,
+            summaryI18n = summaryI18n,
+            amount = total,
+            fulfillmentUrl = fulfillmentUri,
+            products = products.map { it.toContractProduct() }
+        )
+    }
+
 }
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 ff2be48..46ea238 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
@@ -113,7 +113,7 @@ class OrderManager(
 
     @UiThread
     internal fun getOrder(orderId: Int): LiveOrder {
-        return orders[orderId] ?: throw IllegalArgumentException()
+        return orders[orderId] ?: throw IllegalArgumentException("Order not 
found: $orderId")
     }
 
     @UiThread
diff --git 
a/merchant-terminal/src/main/java/net/taler/merchantpos/payment/Payment.kt 
b/merchant-terminal/src/main/java/net/taler/merchantpos/payment/Payment.kt
index b7e4a4b..9200ced 100644
--- a/merchant-terminal/src/main/java/net/taler/merchantpos/payment/Payment.kt
+++ b/merchant-terminal/src/main/java/net/taler/merchantpos/payment/Payment.kt
@@ -25,5 +25,5 @@ data class Payment(
     val orderId: String? = null,
     val talerPayUri: String? = null,
     val paid: Boolean = false,
-    val error: Boolean = false
+    val error: String? = null
 )
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 9138740..e238284 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
@@ -16,42 +16,33 @@
 
 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
-import com.android.volley.Request.Method.GET
-import com.android.volley.Request.Method.POST
-import com.android.volley.RequestQueue
-import com.android.volley.Response.Listener
-import com.fasterxml.jackson.databind.ObjectMapper
-import net.taler.common.Timestamp
-import net.taler.common.now
-import net.taler.merchantpos.LogErrorListener
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import net.taler.merchantlib.CheckPaymentResponse
+import net.taler.merchantlib.MerchantApi
+import net.taler.merchantlib.PostOrderResponse
+import net.taler.merchantpos.R
 import net.taler.merchantpos.config.ConfigManager
-import net.taler.merchantpos.config.MerchantRequest
 import net.taler.merchantpos.order.Order
-import org.json.JSONArray
-import org.json.JSONObject
-import java.net.URLEncoder
 import java.util.concurrent.TimeUnit.MINUTES
 import java.util.concurrent.TimeUnit.SECONDS
 
 private val TIMEOUT = MINUTES.toMillis(2)
 private val CHECK_INTERVAL = SECONDS.toMillis(1)
-private const val FULFILLMENT_PREFIX = "taler://fulfillment-success/"
 
 class PaymentManager(
+    private val context: Context,
     private val configManager: ConfigManager,
-    private val queue: RequestQueue,
-    private val mapper: ObjectMapper
+    private val scope: CoroutineScope,
+    private val api: MerchantApi
 ) {
 
-    companion object {
-        val TAG = PaymentManager::class.java.simpleName
-    }
-
     private val mPayment = MutableLiveData<Payment>()
     val payment: LiveData<Payment> = mPayment
 
@@ -63,93 +54,51 @@ class PaymentManager(
         }
 
         override fun onFinish() {
-            payment.value?.copy(error = true)?.let { mPayment.value = it }
+            val str = context.getString(R.string.error_timeout)
+            payment.value?.copy(error = str)?.let { mPayment.value = it }
         }
     }
 
     @UiThread
     fun createPayment(order: Order) {
         val merchantConfig = configManager.merchantConfig!!
-
-        val currency = merchantConfig.currency!!
-        val summary = order.summary
-        val summaryI18n = order.summaryI18n
-        val now = now()
-        val deadline = Timestamp(now + MINUTES.toMillis(120))
-
-        mPayment.value = Payment(order, summary, currency)
-
-        val fulfillmentId = "${now}-${order.hashCode()}"
-        val fulfillmentUrl =
-            "${FULFILLMENT_PREFIX}${URLEncoder.encode(summary, 
"UTF-8")}#$fulfillmentId"
-        val body = JSONObject().apply {
-            put("order", JSONObject().apply {
-                put("amount", order.total.toJSONString())
-                put("summary", summary)
-                if (summaryI18n != null) put("summary_i18n", order.summaryI18n)
-                // fulfillment_url needs to be unique per order
-                put("fulfillment_url", fulfillmentUrl)
-                put("instance", "default")
-                put("wire_transfer_deadline", 
JSONObject(mapper.writeValueAsString(deadline)))
-                put("refund_deadline", 
JSONObject(mapper.writeValueAsString(deadline)))
-                put("products", order.getProductsJson())
-            })
+        mPayment.value = Payment(order, order.summary, 
merchantConfig.currency!!)
+        scope.launch(Dispatchers.IO) {
+            val response = api.postOrder(merchantConfig.convert(), 
order.toContractTerms())
+            response.handle(::onNetworkError, ::onOrderCreated)
         }
-
-        Log.d(TAG, body.toString(4))
-
-        val req = MerchantRequest(POST, merchantConfig, "order", null, body,
-            Listener { onOrderCreated(it) },
-            LogErrorListener { onNetworkError() }
-        )
-        queue.add(req)
-    }
-
-    private fun Order.getProductsJson(): JSONArray {
-        val contractProducts = products.map { it.toContractProduct() }
-        val productsStr = mapper.writeValueAsString(contractProducts)
-        return JSONArray(productsStr)
     }
 
-    private fun onOrderCreated(orderResponse: JSONObject) {
-        val orderId = orderResponse.getString("order_id")
-        mPayment.value = mPayment.value!!.copy(orderId = orderId)
+    private fun onOrderCreated(orderResponse: PostOrderResponse) = 
scope.launch(Dispatchers.Main) {
+        mPayment.value = mPayment.value!!.copy(orderId = orderResponse.orderId)
         checkTimer.start()
     }
 
     private fun checkPayment(orderId: String) {
         val merchantConfig = configManager.merchantConfig!!
-        val params = mapOf(
-            "order_id" to orderId,
-            "instance" to merchantConfig.instance
-        )
-
-        val req = MerchantRequest(GET, merchantConfig, "check-payment", 
params, null,
-            Listener { onPaymentChecked(it) },
-            LogErrorListener { onNetworkError() })
-        queue.add(req)
+        scope.launch(Dispatchers.IO) {
+            val response = api.checkOrder(merchantConfig.convert(), orderId)
+            response.handle(::onNetworkError, ::onPaymentChecked)
+        }
     }
 
-    /**
-     * Called when the /check-payment response gave a result.
-     */
-    private fun onPaymentChecked(checkPaymentResponse: JSONObject) {
+    private fun onPaymentChecked(response: CheckPaymentResponse) = 
scope.launch(Dispatchers.Main) {
         val currentValue = requireNotNull(mPayment.value)
-        if (checkPaymentResponse.getBoolean("paid")) {
+        if (response.paid) {
             mPayment.value = currentValue.copy(paid = true)
             checkTimer.cancel()
         } else if (currentValue.talerPayUri == null) {
-            val talerPayUri = checkPaymentResponse.getString("taler_pay_uri")
-            mPayment.value = currentValue.copy(talerPayUri = talerPayUri)
+            response as CheckPaymentResponse.Unpaid
+            mPayment.value = currentValue.copy(talerPayUri = 
response.talerPayUri)
         }
     }
 
-    private fun onNetworkError() {
-        cancelPayment()
+    private fun onNetworkError(error: String) = scope.launch(Dispatchers.Main) 
{
+        cancelPayment(error)
     }
 
-    fun cancelPayment() {
-        mPayment.value = mPayment.value!!.copy(error = true)
+    fun cancelPayment(error: String) {
+        mPayment.value = mPayment.value!!.copy(error = error)
         checkTimer.cancel()
     }
 
diff --git 
a/merchant-terminal/src/main/java/net/taler/merchantpos/payment/ProcessPaymentFragment.kt
 
b/merchant-terminal/src/main/java/net/taler/merchantpos/payment/ProcessPaymentFragment.kt
index 9060fd3..5278a03 100644
--- 
a/merchant-terminal/src/main/java/net/taler/merchantpos/payment/ProcessPaymentFragment.kt
+++ 
b/merchant-terminal/src/main/java/net/taler/merchantpos/payment/ProcessPaymentFragment.kt
@@ -61,8 +61,8 @@ class ProcessPaymentFragment : Fragment() {
     }
 
     private fun onPaymentStateChanged(payment: Payment) {
-        if (payment.error) {
-            topSnackbar(requireView(), R.string.error_network, LENGTH_LONG)
+        if (payment.error != null) {
+            topSnackbar(requireView(), payment.error, LENGTH_LONG)
             findNavController().navigateUp()
             return
         }
@@ -86,7 +86,7 @@ class ProcessPaymentFragment : Fragment() {
     }
 
     private fun onPaymentCancel() {
-        paymentManager.cancelPayment()
+        paymentManager.cancelPayment(getString(R.string.error_cancelled))
         findNavController().navigateUp()
         topSnackbar(requireView(), R.string.payment_canceled, LENGTH_LONG)
     }
diff --git a/merchant-terminal/src/main/res/values/strings.xml 
b/merchant-terminal/src/main/res/values/strings.xml
index b3dcd8d..4c0ba5a 100644
--- a/merchant-terminal/src/main/res/values/strings.xml
+++ b/merchant-terminal/src/main/res/values/strings.xml
@@ -64,6 +64,8 @@
     <string name="refund_order_ref">Purchase reference: %1$s\n\n%2$s</string>
 
     <string name="error_network">Network error</string>
+    <string name="error_timeout">No payment found, please try again!</string>
+    <string name="error_cancelled">Payment cancelled</string>
 
     <string name="toast_back_to_exit">Click «back» again to exit</string>
 

-- 
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]