gnunet-svn
[Top][All Lists]
Advanced

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

[taler-taler-android] branch master updated (b673849 -> 35f7ed5)


From: gnunet
Subject: [taler-taler-android] branch master updated (b673849 -> 35f7ed5)
Date: Tue, 04 Aug 2020 14:48:43 +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 b673849  Try to get fdroid nightly command to pick up our release 
builds
     new d3a035c  [pos] fix tests in merchant-lib
     new 35f7ed5  [pos] Implement new refund API (untested since there is no 
wallet support)

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:
 merchant-lib/src/main/AndroidManifest.xml          |   1 -
 .../merchantlib/{MerchantConfig.kt => Config.kt}   |  15 +++
 .../main/java/net/taler/merchantlib/MerchantApi.kt |  12 ++
 .../merchantlib/{PostOrderRequest.kt => Orders.kt} |   0
 .../merchantlib/{ConfigResponse.kt => Refunds.kt}  |  23 ++--
 .../java/net/taler/merchantlib/MerchantApiTest.kt  | 103 +++++++++++++++-
 merchant-terminal/.gitlab-ci.yml                   |   1 +
 merchant-terminal/build.gradle                     |   3 -
 merchant-terminal/src/main/AndroidManifest.xml     |   1 -
 .../java/net/taler/merchantpos/MainViewModel.kt    |   7 +-
 .../src/main/java/net/taler/merchantpos/Utils.kt   |  15 ---
 ...MerchantConfigFragment.kt => ConfigFragment.kt} |   4 +-
 .../taler/merchantpos/config/MerchantRequest.kt    |  59 ---------
 ...rchantHistoryFragment.kt => HistoryFragment.kt} |   6 +-
 .../net/taler/merchantpos/history/RefundManager.kt | 134 ---------------------
 .../taler/merchantpos/order/CategoriesFragment.kt  |  40 ------
 .../{CategoriesFragment.kt => CategoryAdapter.kt}  |  51 +-------
 .../net/taler/merchantpos/order/OrderAdapter.kt    | 114 ++++++++++++++++++
 .../taler/merchantpos/order/OrderStateFragment.kt  |  92 --------------
 .../taler/merchantpos/payment/PaymentManager.kt    |   4 +-
 .../{history => refund}/RefundFragment.kt          |  23 ++--
 .../net/taler/merchantpos/refund/RefundManager.kt  |  91 ++++++++++++++
 .../{history => refund}/RefundUriFragment.kt       |   2 +-
 .../main/res/layout/fragment_merchant_config.xml   |   2 +-
 .../src/main/res/layout/fragment_refund.xml        |   2 +-
 .../src/main/res/navigation/nav_graph.xml          |   8 +-
 26 files changed, 376 insertions(+), 437 deletions(-)
 rename merchant-lib/src/main/java/net/taler/merchantlib/{MerchantConfig.kt => 
Config.kt} (77%)
 rename merchant-lib/src/main/java/net/taler/merchantlib/{PostOrderRequest.kt 
=> Orders.kt} (100%)
 rename merchant-lib/src/main/java/net/taler/merchantlib/{ConfigResponse.kt => 
Refunds.kt} (65%)
 rename 
merchant-terminal/src/main/java/net/taler/merchantpos/config/{MerchantConfigFragment.kt
 => ConfigFragment.kt} (97%)
 delete mode 100644 
merchant-terminal/src/main/java/net/taler/merchantpos/config/MerchantRequest.kt
 rename 
merchant-terminal/src/main/java/net/taler/merchantpos/history/{MerchantHistoryFragment.kt
 => HistoryFragment.kt} (92%)
 delete mode 100644 
merchant-terminal/src/main/java/net/taler/merchantpos/history/RefundManager.kt
 copy 
merchant-terminal/src/main/java/net/taler/merchantpos/order/{CategoriesFragment.kt
 => CategoryAdapter.kt} (55%)
 create mode 100644 
merchant-terminal/src/main/java/net/taler/merchantpos/order/OrderAdapter.kt
 rename merchant-terminal/src/main/java/net/taler/merchantpos/{history => 
refund}/RefundFragment.kt (83%)
 create mode 100644 
merchant-terminal/src/main/java/net/taler/merchantpos/refund/RefundManager.kt
 rename merchant-terminal/src/main/java/net/taler/merchantpos/{history => 
refund}/RefundUriFragment.kt (98%)

diff --git a/merchant-lib/src/main/AndroidManifest.xml 
b/merchant-lib/src/main/AndroidManifest.xml
index 7318c07..1408b33 100644
--- a/merchant-lib/src/main/AndroidManifest.xml
+++ b/merchant-lib/src/main/AndroidManifest.xml
@@ -17,7 +17,6 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android";
     package="net.taler.merchantlib">
 
-    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
     <uses-permission android:name="android.permission.INTERNET" />
 
 </manifest>
diff --git a/merchant-lib/src/main/java/net/taler/merchantlib/MerchantConfig.kt 
b/merchant-lib/src/main/java/net/taler/merchantlib/Config.kt
similarity index 77%
rename from merchant-lib/src/main/java/net/taler/merchantlib/MerchantConfig.kt
rename to merchant-lib/src/main/java/net/taler/merchantlib/Config.kt
index a8d113e..eb09485 100644
--- a/merchant-lib/src/main/java/net/taler/merchantlib/MerchantConfig.kt
+++ b/merchant-lib/src/main/java/net/taler/merchantlib/Config.kt
@@ -19,6 +19,21 @@ package net.taler.merchantlib
 import kotlinx.serialization.SerialName
 import kotlinx.serialization.Serializable
 
+@Serializable
+data class ConfigResponse(
+    /**
+     * libtool-style representation of the Merchant protocol version, see
+     * 
https://www.gnu.org/software/libtool/manual/html_node/Versioning.html#Versioning
+     * The format is "current:revision:age".
+     */
+    val version: String,
+
+    /**
+    Currency supported by this backend.
+     */
+    val currency: String
+)
+
 @Serializable
 data class MerchantConfig(
     @SerialName("base_url")
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 96892f5..c92d4d2 100644
--- a/merchant-lib/src/main/java/net/taler/merchantlib/MerchantApi.kt
+++ b/merchant-lib/src/main/java/net/taler/merchantlib/MerchantApi.kt
@@ -72,6 +72,18 @@ class MerchantApi(private val httpClient: HttpClient) {
         } as OrderHistory
     }
 
+    suspend fun giveRefund(
+        merchantConfig: MerchantConfig,
+        orderId: String,
+        request: RefundRequest
+    ): Response<RefundResponse> = response {
+        
httpClient.post(merchantConfig.urlFor("private/orders/$orderId/refund")) {
+            header(Authorization, "ApiKey ${merchantConfig.apiKey}")
+            contentType(Json)
+            body = request
+        } as RefundResponse
+    }
+
 }
 
 fun getDefaultHttpClient(): HttpClient = HttpClient(OkHttp) {
diff --git 
a/merchant-lib/src/main/java/net/taler/merchantlib/PostOrderRequest.kt 
b/merchant-lib/src/main/java/net/taler/merchantlib/Orders.kt
similarity index 100%
rename from merchant-lib/src/main/java/net/taler/merchantlib/PostOrderRequest.kt
rename to merchant-lib/src/main/java/net/taler/merchantlib/Orders.kt
diff --git a/merchant-lib/src/main/java/net/taler/merchantlib/ConfigResponse.kt 
b/merchant-lib/src/main/java/net/taler/merchantlib/Refunds.kt
similarity index 65%
rename from merchant-lib/src/main/java/net/taler/merchantlib/ConfigResponse.kt
rename to merchant-lib/src/main/java/net/taler/merchantlib/Refunds.kt
index 49164e6..61f0ab7 100644
--- a/merchant-lib/src/main/java/net/taler/merchantlib/ConfigResponse.kt
+++ b/merchant-lib/src/main/java/net/taler/merchantlib/Refunds.kt
@@ -16,19 +16,28 @@
 
 package net.taler.merchantlib
 
+import kotlinx.serialization.SerialName
 import kotlinx.serialization.Serializable
+import net.taler.common.Amount
 
 @Serializable
-data class ConfigResponse(
+data class RefundRequest(
     /**
-     * libtool-style representation of the Merchant protocol version, see
-     * 
https://www.gnu.org/software/libtool/manual/html_node/Versioning.html#Versioning
-     * The format is "current:revision:age".
+     * Amount to be refunded
      */
-    val version: String,
+    val refund: Amount,
 
     /**
-    Currency supported by this backend.
+     * Human-readable refund justification
      */
-    val currency: String
+    val reason: String
+)
+
+@Serializable
+data class RefundResponse(
+    /**
+     * URL (handled by the backend) that the wallet should access to trigger 
refund processing.
+     */
+    @SerialName("taler_refund_uri")
+    val talerRefundUri: 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 deed81e..f9f5e87 100644
--- a/merchant-lib/src/test/java/net/taler/merchantlib/MerchantApiTest.kt
+++ b/merchant-lib/src/test/java/net/taler/merchantlib/MerchantApiTest.kt
@@ -16,11 +16,12 @@
 
 package net.taler.merchantlib
 
-import io.ktor.http.HttpStatusCode
+import io.ktor.http.HttpStatusCode.Companion.NotFound
 import kotlinx.coroutines.runBlocking
 import net.taler.common.Amount
 import net.taler.common.ContractProduct
 import net.taler.common.ContractTerms
+import net.taler.common.Timestamp
 import net.taler.merchantlib.MockHttpClient.giveJsonResponse
 import net.taler.merchantlib.MockHttpClient.httpClient
 import org.junit.Assert.assertEquals
@@ -35,6 +36,7 @@ class MerchantApiTest {
         instance = "testInstance",
         apiKey = "apiKeyFooBar"
     )
+    private val orderId = "orderIdFoo"
 
     @Test
     fun testGetConfig() = runBlocking {
@@ -65,6 +67,7 @@ class MerchantApiTest {
             fulfillmentUrl = "http://example.org";,
             products = listOf(product)
         )
+        val request = PostOrderRequest(contractTerms)
         val contractTermsJson = """
             {
                 "order": {
@@ -88,20 +91,20 @@ class MerchantApiTest {
         ) {
             """{"order_id": "test"}"""
         }
-        api.postOrder(merchantConfig, contractTerms).assertSuccess {
+        api.postOrder(merchantConfig, request).assertSuccess {
             assertEquals(PostOrderResponse("test"), it)
         }
 
         httpClient.giveJsonResponse(
             "http://example.net/instances/testInstance/private/orders";,
-            statusCode = HttpStatusCode.NotFound
+            statusCode = NotFound
         ) {
             """{
                 "code": 2000,
                 "hint": "merchant instance unknown"
             }"""
         }
-        api.postOrder(merchantConfig, contractTerms).assertFailure {
+        api.postOrder(merchantConfig, request).assertFailure {
             assertTrue(it.contains("2000"))
             assertTrue(it.contains("merchant instance unknown"))
         }
@@ -109,7 +112,6 @@ class MerchantApiTest {
 
     @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";)
 {
             """{
@@ -124,7 +126,7 @@ class MerchantApiTest {
 
         httpClient.giveJsonResponse(
             
"http://example.net/instances/testInstance/private/orders/$orderId";,
-            statusCode = HttpStatusCode.NotFound
+            statusCode = NotFound
         ) {
             """{
                 "code": 2909,
@@ -137,4 +139,93 @@ class MerchantApiTest {
         }
     }
 
+    @Test
+    fun testDeleteOrder() = runBlocking {
+        
httpClient.giveJsonResponse("http://example.net/instances/testInstance/private/orders/$orderId";)
 {
+            "{}"
+        }
+        api.deleteOrder(merchantConfig, orderId).assertSuccess {}
+
+        httpClient.giveJsonResponse(
+            
"http://example.net/instances/testInstance/private/orders/$orderId";,
+            statusCode = NotFound
+        ) {
+            """{
+                "code": 2511,
+                "hint": "Order unknown"
+                }
+            """.trimIndent()
+        }
+        api.deleteOrder(merchantConfig, orderId).assertFailure {
+            assertTrue(it.contains("2511"))
+            assertTrue(it.contains("Order unknown"))
+        }
+    }
+
+    @Test
+    fun testGetOrderHistory() = runBlocking {
+        
httpClient.giveJsonResponse("http://example.net/instances/testInstance/private/orders";)
 {
+            """{  "orders": [
+                    {
+                      "order_id": "2020.217-0281FGXCS25P2",
+                      "row_id": 183,
+                      "timestamp": {
+                        "t_ms": 1596542338000
+                      },
+                      "amount": "TESTKUDOS:1",
+                      "summary": "Chips",
+                      "refundable": true,
+                      "paid": true
+                    },
+                    {
+                      "order_id": "2020.216-01G2ZPXSP6BYT",
+                      "row_id": 154,
+                      "timestamp": {
+                        "t_ms": 1596468174000
+                      },
+                      "amount": "TESTKUDOS:0.8",
+                      "summary": "Peanuts",
+                      "refundable": false,
+                      "paid": false
+                    }
+                ]
+            }""".trimIndent()
+        }
+        api.getOrderHistory(merchantConfig).assertSuccess {
+            assertEquals(2, it.orders.size)
+
+            val order1 = it.orders[0]
+            assertEquals(Amount("TESTKUDOS", 1, 0), order1.amount)
+            assertEquals("2020.217-0281FGXCS25P2", order1.orderId)
+            assertEquals(true, order1.paid)
+            assertEquals(true, order1.refundable)
+            assertEquals("Chips", order1.summary)
+            assertEquals(Timestamp(1596542338000), order1.timestamp)
+
+            val order2 = it.orders[1]
+            assertEquals(Amount("TESTKUDOS", 0, 80000000), order2.amount)
+            assertEquals("2020.216-01G2ZPXSP6BYT", order2.orderId)
+            assertEquals(false, order2.paid)
+            assertEquals(false, order2.refundable)
+            assertEquals("Peanuts", order2.summary)
+            assertEquals(Timestamp(1596468174000), order2.timestamp)
+        }
+    }
+
+    @Test
+    fun testGiveRefund() = runBlocking {
+        
httpClient.giveJsonResponse("http://example.net/instances/testInstance/private/orders/$orderId/refund";)
 {
+            """{
+                "taler_refund_uri": "taler://refund/foo/bar"
+            }""".trimIndent()
+        }
+        val request = RefundRequest(
+            refund = Amount("TESTKUDOS", 5, 0),
+            reason = "Give me my money back now!!!"
+        )
+        api.giveRefund(merchantConfig, orderId, request).assertSuccess {
+            assertEquals("taler://refund/foo/bar", it.talerRefundUri)
+        }
+    }
+
 }
diff --git a/merchant-terminal/.gitlab-ci.yml b/merchant-terminal/.gitlab-ci.yml
index 3e13fcc..023b7bf 100644
--- a/merchant-terminal/.gitlab-ci.yml
+++ b/merchant-terminal/.gitlab-ci.yml
@@ -21,6 +21,7 @@ merchant_deploy_nightly:
       - master
     changes:
       - merchant-terminal/**/*
+      - merchant-lib/**/*
   needs: ["merchant_test"]
   script:
     # Ensure that key exists
diff --git a/merchant-terminal/build.gradle b/merchant-terminal/build.gradle
index 4499892..1bdc138 100644
--- a/merchant-terminal/build.gradle
+++ b/merchant-terminal/build.gradle
@@ -68,9 +68,6 @@ dependencies {
     implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
     implementation "androidx.navigation:navigation-ui-ktx:$nav_version"
 
-    // HTTP Requests
-    implementation 'com.android.volley:volley:1.1.1'
-
     implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"
 
     testImplementation 'androidx.test.ext:junit:1.1.1'
diff --git a/merchant-terminal/src/main/AndroidManifest.xml 
b/merchant-terminal/src/main/AndroidManifest.xml
index 3d89fee..1518293 100644
--- a/merchant-terminal/src/main/AndroidManifest.xml
+++ b/merchant-terminal/src/main/AndroidManifest.xml
@@ -19,7 +19,6 @@
     package="net.taler.merchantpos">
 
     <uses-permission android:name="android.permission.NFC" />
-    <uses-permission android:name="android.permission.INTERNET" />
 
     <uses-feature
         android:name="android.hardware.nfc"
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 905738b..5f5d534 100644
--- a/merchant-terminal/src/main/java/net/taler/merchantpos/MainViewModel.kt
+++ b/merchant-terminal/src/main/java/net/taler/merchantpos/MainViewModel.kt
@@ -19,20 +19,18 @@ package net.taler.merchantpos
 import android.app.Application
 import androidx.lifecycle.AndroidViewModel
 import androidx.lifecycle.viewModelScope
-import com.android.volley.toolbox.Volley
 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
 import net.taler.merchantpos.order.OrderManager
 import net.taler.merchantpos.payment.PaymentManager
+import net.taler.merchantpos.refund.RefundManager
 
 class MainViewModel(app: Application) : AndroidViewModel(app) {
 
     private val httpClient = getDefaultHttpClient()
     private val api = MerchantApi(httpClient)
-    private val queue = Volley.newRequestQueue(app)
 
     val orderManager = OrderManager(app)
     val configManager = ConfigManager(app, viewModelScope, httpClient, 
api).apply {
@@ -40,11 +38,10 @@ class MainViewModel(app: Application) : 
AndroidViewModel(app) {
     }
     val paymentManager = PaymentManager(app, configManager, viewModelScope, 
api)
     val historyManager = HistoryManager(configManager, viewModelScope, api)
-    val refundManager = RefundManager(configManager, queue)
+    val refundManager = RefundManager(configManager, viewModelScope, api)
 
     override fun onCleared() {
         httpClient.close()
-        queue.cancelAll { !it.isCanceled }
     }
 
 }
diff --git a/merchant-terminal/src/main/java/net/taler/merchantpos/Utils.kt 
b/merchant-terminal/src/main/java/net/taler/merchantpos/Utils.kt
index 9deb042..578debf 100644
--- a/merchant-terminal/src/main/java/net/taler/merchantpos/Utils.kt
+++ b/merchant-terminal/src/main/java/net/taler/merchantpos/Utils.kt
@@ -16,15 +16,11 @@
 
 package net.taler.merchantpos
 
-import android.util.Log
 import android.view.View
 import androidx.annotation.StringRes
-import com.android.volley.Response
-import com.android.volley.VolleyError
 import 
com.google.android.material.snackbar.BaseTransientBottomBar.ANIMATION_MODE_FADE
 import com.google.android.material.snackbar.BaseTransientBottomBar.Duration
 import com.google.android.material.snackbar.Snackbar.make
-import net.taler.merchantpos.MainActivity.Companion.TAG
 
 fun topSnackbar(view: View, text: CharSequence, @Duration duration: Int) {
     make(view, text, duration)
@@ -36,14 +32,3 @@ fun topSnackbar(view: View, text: CharSequence, @Duration 
duration: Int) {
 fun topSnackbar(view: View, @StringRes resId: Int, @Duration duration: Int) {
     topSnackbar(view, view.resources.getText(resId), duration)
 }
-
-class LogErrorListener(private val onError: (error: VolleyError) -> Any) :
-    Response.ErrorListener {
-
-    override fun onErrorResponse(error: VolleyError) {
-        val body = error.networkResponse.data?.let { String(it) }
-        Log.e(TAG, "$error $body")
-        onError.invoke(error)
-    }
-
-}
diff --git 
a/merchant-terminal/src/main/java/net/taler/merchantpos/config/MerchantConfigFragment.kt
 
b/merchant-terminal/src/main/java/net/taler/merchantpos/config/ConfigFragment.kt
similarity index 97%
rename from 
merchant-terminal/src/main/java/net/taler/merchantpos/config/MerchantConfigFragment.kt
rename to 
merchant-terminal/src/main/java/net/taler/merchantpos/config/ConfigFragment.kt
index 77a87fb..daddbff 100644
--- 
a/merchant-terminal/src/main/java/net/taler/merchantpos/config/MerchantConfigFragment.kt
+++ 
b/merchant-terminal/src/main/java/net/taler/merchantpos/config/ConfigFragment.kt
@@ -34,13 +34,13 @@ import 
kotlinx.android.synthetic.main.fragment_merchant_config.*
 import net.taler.common.navigate
 import net.taler.merchantpos.MainViewModel
 import net.taler.merchantpos.R
-import 
net.taler.merchantpos.config.MerchantConfigFragmentDirections.Companion.actionSettingsToOrder
+import 
net.taler.merchantpos.config.ConfigFragmentDirections.Companion.actionSettingsToOrder
 import net.taler.merchantpos.topSnackbar
 
 /**
  * Fragment that displays merchant settings.
  */
-class MerchantConfigFragment : Fragment() {
+class ConfigFragment : Fragment() {
 
     private val model: MainViewModel by activityViewModels()
     private val configManager by lazy { model.configManager }
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
deleted file mode 100644
index 5d41196..0000000
--- 
a/merchant-terminal/src/main/java/net/taler/merchantpos/config/MerchantRequest.kt
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * 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.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
-
-class MerchantRequest(
-    method: Int,
-    private val merchantConfig: MerchantConfig,
-    endpoint: String,
-    params: Map<String, String>?,
-    jsonRequest: JSONObject?,
-    listener: Response.Listener<JSONObject>,
-    errorListener: LogErrorListener
-) :
-    JsonObjectRequest(
-        method,
-        merchantConfig.legacyUrl(endpoint, params),
-        jsonRequest,
-        listener,
-        errorListener
-    ) {
-
-    override fun getHeaders(): MutableMap<String, String> {
-        val headerMap = ArrayMap<String, String>()
-        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/history/MerchantHistoryFragment.kt
 
b/merchant-terminal/src/main/java/net/taler/merchantpos/history/HistoryFragment.kt
similarity index 92%
rename from 
merchant-terminal/src/main/java/net/taler/merchantpos/history/MerchantHistoryFragment.kt
rename to 
merchant-terminal/src/main/java/net/taler/merchantpos/history/HistoryFragment.kt
index 596b8b0..8cc435a 100644
--- 
a/merchant-terminal/src/main/java/net/taler/merchantpos/history/MerchantHistoryFragment.kt
+++ 
b/merchant-terminal/src/main/java/net/taler/merchantpos/history/HistoryFragment.kt
@@ -35,8 +35,8 @@ import net.taler.common.navigate
 import net.taler.merchantlib.OrderHistoryEntry
 import net.taler.merchantpos.MainViewModel
 import net.taler.merchantpos.R
-import 
net.taler.merchantpos.history.MerchantHistoryFragmentDirections.Companion.actionGlobalMerchantSettings
-import 
net.taler.merchantpos.history.MerchantHistoryFragmentDirections.Companion.actionNavHistoryToRefundFragment
+import 
net.taler.merchantpos.history.HistoryFragmentDirections.Companion.actionGlobalMerchantSettings
+import 
net.taler.merchantpos.history.HistoryFragmentDirections.Companion.actionNavHistoryToRefundFragment
 
 internal interface RefundClickListener {
     fun onRefundClicked(item: OrderHistoryEntry)
@@ -45,7 +45,7 @@ internal interface RefundClickListener {
 /**
  * Fragment to display the merchant's payment history, received from the 
backend.
  */
-class MerchantHistoryFragment : Fragment(), RefundClickListener {
+class HistoryFragment : Fragment(), RefundClickListener {
 
     companion object {
         const val TAG = "taler-merchant"
diff --git 
a/merchant-terminal/src/main/java/net/taler/merchantpos/history/RefundManager.kt
 
b/merchant-terminal/src/main/java/net/taler/merchantpos/history/RefundManager.kt
deleted file mode 100644
index 7f9b4c5..0000000
--- 
a/merchant-terminal/src/main/java/net/taler/merchantpos/history/RefundManager.kt
+++ /dev/null
@@ -1,134 +0,0 @@
-/*
- * 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.merchantpos.history
-
-import android.util.Log
-import androidx.annotation.UiThread
-import androidx.lifecycle.LiveData
-import androidx.lifecycle.MutableLiveData
-import com.android.volley.Request.Method.POST
-import com.android.volley.RequestQueue
-import com.android.volley.Response.Listener
-import com.android.volley.VolleyError
-import net.taler.common.Amount
-import net.taler.merchantlib.OrderHistoryEntry
-import net.taler.merchantpos.LogErrorListener
-import net.taler.merchantpos.config.ConfigManager
-import net.taler.merchantpos.config.MerchantRequest
-import org.json.JSONObject
-
-sealed class RefundResult {
-    object Error : RefundResult()
-    object PastDeadline : RefundResult()
-    object AlreadyRefunded : RefundResult()
-    class Success(
-        val refundUri: String,
-        val item: OrderHistoryEntry,
-        val amount: Amount,
-        val reason: String
-    ) : RefundResult()
-}
-
-class RefundManager(
-    private val configManager: ConfigManager,
-    private val queue: RequestQueue
-) {
-
-    companion object {
-        val TAG = RefundManager::class.java.simpleName
-    }
-
-    var toBeRefunded: OrderHistoryEntry? = null
-        private set
-
-    private val mRefundResult = MutableLiveData<RefundResult>()
-    internal val refundResult: LiveData<RefundResult> = mRefundResult
-
-    @UiThread
-    internal fun startRefund(item: OrderHistoryEntry) {
-        toBeRefunded = item
-        mRefundResult.value = null
-    }
-
-    @UiThread
-    internal fun abortRefund() {
-        toBeRefunded = null
-        mRefundResult.value = null
-    }
-
-    @UiThread
-    internal fun refund(item: OrderHistoryEntry, amount: Amount, reason: 
String) {
-        val merchantConfig = configManager.merchantConfig!!
-        val refundRequest = mapOf(
-            "order_id" to item.orderId,
-            "refund" to amount.toJSONString(),
-            "reason" to reason
-        )
-        val body = JSONObject(refundRequest)
-        Log.d(TAG, body.toString(4))
-        val req = MerchantRequest(POST, merchantConfig, "refund", null, body,
-            Listener { onRefundResponse(it, item, amount, reason) },
-            LogErrorListener { onRefundError(it) }
-        )
-        queue.add(req)
-    }
-
-    @UiThread
-    private fun onRefundResponse(
-        json: JSONObject,
-        item: OrderHistoryEntry,
-        amount: Amount,
-        reason: String
-    ) {
-        if (!json.has("contract_terms")) {
-            Log.e(TAG, "Contract terms missing: $json")
-            onRefundError()
-            return
-        }
-
-        val contractTerms = json.getJSONObject("contract_terms")
-        val refundDeadline = if (contractTerms.has("refund_deadline")) {
-            contractTerms.getJSONObject("refund_deadline").getLong("t_ms")
-        } else null
-        val autoRefund = contractTerms.has("auto_refund")
-        val refundUri = json.getString("taler_refund_uri")
-
-        Log.e("TEST", "refundDeadline: $refundDeadline")
-        if (refundDeadline != null) Log.e(
-            "TEST",
-            "refundDeadline passed: ${System.currentTimeMillis() > 
refundDeadline}"
-        )
-        Log.e("TEST", "autoRefund: $autoRefund")
-        Log.e("TEST", "refundUri: $refundUri")
-
-        mRefundResult.value = RefundResult.Success(refundUri, item, amount, 
reason)
-    }
-
-    @UiThread
-    private fun onRefundError(error: VolleyError? = null) {
-        val data = error?.networkResponse?.data
-        if (data != null) {
-            val json = JSONObject(String(data))
-            if (json.has("code") && json.getInt("code") == 2602) {
-                mRefundResult.value = RefundResult.AlreadyRefunded
-                return
-            }
-        }
-        mRefundResult.value = RefundResult.Error
-    }
-
-}
diff --git 
a/merchant-terminal/src/main/java/net/taler/merchantpos/order/CategoriesFragment.kt
 
b/merchant-terminal/src/main/java/net/taler/merchantpos/order/CategoriesFragment.kt
index e935d4f..4f8e5af 100644
--- 
a/merchant-terminal/src/main/java/net/taler/merchantpos/order/CategoriesFragment.kt
+++ 
b/merchant-terminal/src/main/java/net/taler/merchantpos/order/CategoriesFragment.kt
@@ -21,18 +21,14 @@ import android.view.LayoutInflater
 import android.view.View
 import android.view.View.INVISIBLE
 import android.view.ViewGroup
-import android.widget.Button
 import androidx.fragment.app.Fragment
 import androidx.fragment.app.activityViewModels
 import androidx.lifecycle.Observer
 import androidx.recyclerview.widget.LinearLayoutManager
-import androidx.recyclerview.widget.RecyclerView
-import androidx.recyclerview.widget.RecyclerView.Adapter
 import kotlinx.android.synthetic.main.fragment_categories.*
 import net.taler.merchantpos.MainViewModel
 import net.taler.merchantpos.R
 import net.taler.merchantpos.config.Category
-import net.taler.merchantpos.order.CategoryAdapter.CategoryViewHolder
 
 interface CategorySelectionListener {
     fun onCategorySelected(category: Category)
@@ -69,39 +65,3 @@ class CategoriesFragment : Fragment(), 
CategorySelectionListener {
     }
 
 }
-
-private class CategoryAdapter(
-    private val listener: CategorySelectionListener
-) : Adapter<CategoryViewHolder>() {
-
-    private val categories = ArrayList<Category>()
-
-    override fun getItemCount() = categories.size
-
-    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): 
CategoryViewHolder {
-        val view =
-            
LayoutInflater.from(parent.context).inflate(R.layout.list_item_category, 
parent, false)
-        return CategoryViewHolder(view)
-    }
-
-    override fun onBindViewHolder(holder: CategoryViewHolder, position: Int) {
-        holder.bind(categories[position])
-    }
-
-    fun setItems(items: List<Category>) {
-        categories.clear()
-        categories.addAll(items)
-        notifyDataSetChanged()
-    }
-
-    private inner class CategoryViewHolder(v: View) : 
RecyclerView.ViewHolder(v) {
-        private val button: Button = v.findViewById(R.id.button)
-
-        fun bind(category: Category) {
-            button.text = category.localizedName
-            button.isPressed = category.selected
-            button.setOnClickListener { listener.onCategorySelected(category) }
-        }
-    }
-
-}
diff --git 
a/merchant-terminal/src/main/java/net/taler/merchantpos/order/CategoriesFragment.kt
 
b/merchant-terminal/src/main/java/net/taler/merchantpos/order/CategoryAdapter.kt
similarity index 55%
copy from 
merchant-terminal/src/main/java/net/taler/merchantpos/order/CategoriesFragment.kt
copy to 
merchant-terminal/src/main/java/net/taler/merchantpos/order/CategoryAdapter.kt
index e935d4f..c690ec5 100644
--- 
a/merchant-terminal/src/main/java/net/taler/merchantpos/order/CategoriesFragment.kt
+++ 
b/merchant-terminal/src/main/java/net/taler/merchantpos/order/CategoryAdapter.kt
@@ -16,63 +16,18 @@
 
 package net.taler.merchantpos.order
 
-import android.os.Bundle
 import android.view.LayoutInflater
 import android.view.View
-import android.view.View.INVISIBLE
 import android.view.ViewGroup
 import android.widget.Button
-import androidx.fragment.app.Fragment
-import androidx.fragment.app.activityViewModels
-import androidx.lifecycle.Observer
-import androidx.recyclerview.widget.LinearLayoutManager
 import androidx.recyclerview.widget.RecyclerView
 import androidx.recyclerview.widget.RecyclerView.Adapter
-import kotlinx.android.synthetic.main.fragment_categories.*
-import net.taler.merchantpos.MainViewModel
 import net.taler.merchantpos.R
 import net.taler.merchantpos.config.Category
 import net.taler.merchantpos.order.CategoryAdapter.CategoryViewHolder
 
-interface CategorySelectionListener {
-    fun onCategorySelected(category: Category)
-}
-
-class CategoriesFragment : Fragment(), CategorySelectionListener {
-
-    private val viewModel: MainViewModel by activityViewModels()
-    private val orderManager by lazy { viewModel.orderManager }
-    private val adapter = CategoryAdapter(this)
-
-    override fun onCreateView(
-        inflater: LayoutInflater,
-        container: ViewGroup?,
-        savedInstanceState: Bundle?
-    ): View? {
-        return inflater.inflate(R.layout.fragment_categories, container, false)
-    }
-
-    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
-        categoriesList.apply {
-            adapter = this@CategoriesFragment.adapter
-            layoutManager = LinearLayoutManager(requireContext())
-        }
-
-        orderManager.categories.observe(viewLifecycleOwner, Observer { 
categories ->
-            adapter.setItems(categories)
-            progressBar.visibility = INVISIBLE
-        })
-    }
-
-    override fun onCategorySelected(category: Category) {
-        orderManager.setCurrentCategory(category)
-    }
-
-}
-
-private class CategoryAdapter(
-    private val listener: CategorySelectionListener
-) : Adapter<CategoryViewHolder>() {
+internal class CategoryAdapter(private val listener: 
CategorySelectionListener) :
+    Adapter<CategoryViewHolder>() {
 
     private val categories = ArrayList<Category>()
 
@@ -94,7 +49,7 @@ private class CategoryAdapter(
         notifyDataSetChanged()
     }
 
-    private inner class CategoryViewHolder(v: View) : 
RecyclerView.ViewHolder(v) {
+    internal inner class CategoryViewHolder(v: View) : 
RecyclerView.ViewHolder(v) {
         private val button: Button = v.findViewById(R.id.button)
 
         fun bind(category: Category) {
diff --git 
a/merchant-terminal/src/main/java/net/taler/merchantpos/order/OrderAdapter.kt 
b/merchant-terminal/src/main/java/net/taler/merchantpos/order/OrderAdapter.kt
new file mode 100644
index 0000000..2180ccb
--- /dev/null
+++ 
b/merchant-terminal/src/main/java/net/taler/merchantpos/order/OrderAdapter.kt
@@ -0,0 +1,114 @@
+/*
+ * 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.merchantpos.order
+
+import android.view.LayoutInflater
+import android.view.MotionEvent
+import android.view.View
+import android.view.ViewGroup
+import android.widget.TextView
+import androidx.recyclerview.selection.ItemDetailsLookup
+import androidx.recyclerview.selection.ItemKeyProvider
+import androidx.recyclerview.selection.SelectionTracker
+import androidx.recyclerview.widget.AsyncListDiffer
+import androidx.recyclerview.widget.DiffUtil.ItemCallback
+import androidx.recyclerview.widget.RecyclerView
+import androidx.recyclerview.widget.RecyclerView.Adapter
+import net.taler.merchantpos.R
+import net.taler.merchantpos.config.ConfigProduct
+import net.taler.merchantpos.order.OrderAdapter.OrderViewHolder
+
+internal class OrderAdapter : Adapter<OrderViewHolder>() {
+
+    lateinit var tracker: SelectionTracker<String>
+    val keyProvider = OrderKeyProvider()
+    private val itemCallback = object : ItemCallback<ConfigProduct>() {
+        override fun areItemsTheSame(oldItem: ConfigProduct, newItem: 
ConfigProduct): Boolean {
+            return oldItem == newItem
+        }
+
+        override fun areContentsTheSame(oldItem: ConfigProduct, newItem: 
ConfigProduct): Boolean {
+            return oldItem.quantity == newItem.quantity
+        }
+    }
+    private val differ = AsyncListDiffer(this, itemCallback)
+
+    override fun getItemCount() = differ.currentList.size
+
+    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): 
OrderViewHolder {
+        val view =
+            
LayoutInflater.from(parent.context).inflate(R.layout.list_item_order, parent, 
false)
+        return OrderViewHolder(view)
+    }
+
+    override fun onBindViewHolder(holder: OrderViewHolder, position: Int) {
+        val item = getItem(position)!!
+        holder.bind(item, tracker.isSelected(item.id))
+    }
+
+    fun setItems(items: List<ConfigProduct>, commitCallback: () -> Unit) {
+        // toMutableList() is needed for some reason, otherwise doesn't update 
adapter
+        differ.submitList(items.toMutableList(), commitCallback)
+    }
+
+    fun getItem(position: Int): ConfigProduct? = differ.currentList[position]
+
+    fun getItemByKey(key: String): ConfigProduct? {
+        return differ.currentList.find { it.id == key }
+    }
+
+    fun findPosition(product: ConfigProduct): Int {
+        return differ.currentList.indexOf(product)
+    }
+
+    internal inner class OrderViewHolder(private val v: View) : 
RecyclerView.ViewHolder(v) {
+        private val quantity: TextView = v.findViewById(R.id.quantity)
+        private val name: TextView = v.findViewById(R.id.name)
+        private val price: TextView = v.findViewById(R.id.price)
+
+        fun bind(product: ConfigProduct, selected: Boolean) {
+            v.isActivated = selected
+            quantity.text = product.quantity.toString()
+            name.text = product.localizedDescription
+            price.text = product.totalPrice.amountStr
+        }
+    }
+
+    internal inner class OrderKeyProvider : 
ItemKeyProvider<String>(SCOPE_MAPPED) {
+        override fun getKey(position: Int) = getItem(position)!!.id
+        override fun getPosition(key: String): Int {
+            return differ.currentList.indexOfFirst { it.id == key }
+        }
+    }
+
+    internal class OrderLineLookup(private val list: RecyclerView) : 
ItemDetailsLookup<String>() {
+        override fun getItemDetails(e: MotionEvent): ItemDetails<String>? {
+            list.findChildViewUnder(e.x, e.y)?.let { view ->
+                val holder = list.getChildViewHolder(view)
+                val adapter = list.adapter as OrderAdapter
+                val position = holder.adapterPosition
+                return object : ItemDetails<String>() {
+                    override fun getPosition(): Int = position
+                    override fun getSelectionKey(): String = 
adapter.keyProvider.getKey(position)
+                    override fun inSelectionHotspot(e: MotionEvent) = true
+                }
+            }
+            return null
+        }
+    }
+
+}
\ No newline at end of file
diff --git 
a/merchant-terminal/src/main/java/net/taler/merchantpos/order/OrderStateFragment.kt
 
b/merchant-terminal/src/main/java/net/taler/merchantpos/order/OrderStateFragment.kt
index f792d7a..b60f3a5 100644
--- 
a/merchant-terminal/src/main/java/net/taler/merchantpos/order/OrderStateFragment.kt
+++ 
b/merchant-terminal/src/main/java/net/taler/merchantpos/order/OrderStateFragment.kt
@@ -18,32 +18,21 @@ package net.taler.merchantpos.order
 
 import android.os.Bundle
 import android.view.LayoutInflater
-import android.view.MotionEvent
 import android.view.View
 import android.view.ViewGroup
-import android.widget.TextView
 import androidx.fragment.app.Fragment
 import androidx.fragment.app.activityViewModels
 import androidx.lifecycle.Observer
-import androidx.recyclerview.selection.ItemDetailsLookup
-import androidx.recyclerview.selection.ItemKeyProvider
 import androidx.recyclerview.selection.SelectionPredicates
 import androidx.recyclerview.selection.SelectionTracker
 import androidx.recyclerview.selection.StorageStrategy
-import androidx.recyclerview.widget.AsyncListDiffer
-import androidx.recyclerview.widget.DiffUtil
 import androidx.recyclerview.widget.LinearLayoutManager
-import androidx.recyclerview.widget.RecyclerView
-import androidx.recyclerview.widget.RecyclerView.Adapter
-import androidx.recyclerview.widget.RecyclerView.ViewHolder
 import kotlinx.android.synthetic.main.fragment_order_state.*
 import net.taler.common.fadeIn
 import net.taler.common.fadeOut
 import net.taler.merchantpos.MainViewModel
 import net.taler.merchantpos.R
-import net.taler.merchantpos.config.ConfigProduct
 import net.taler.merchantpos.order.OrderAdapter.OrderLineLookup
-import net.taler.merchantpos.order.OrderAdapter.OrderViewHolder
 
 class OrderStateFragment : Fragment() {
 
@@ -130,84 +119,3 @@ class OrderStateFragment : Fragment() {
     }
 
 }
-
-private class OrderAdapter : Adapter<OrderViewHolder>() {
-
-    lateinit var tracker: SelectionTracker<String>
-    val keyProvider = OrderKeyProvider()
-    private val itemCallback = object : DiffUtil.ItemCallback<ConfigProduct>() 
{
-        override fun areItemsTheSame(oldItem: ConfigProduct, newItem: 
ConfigProduct): Boolean {
-            return oldItem == newItem
-        }
-
-        override fun areContentsTheSame(oldItem: ConfigProduct, newItem: 
ConfigProduct): Boolean {
-            return oldItem.quantity == newItem.quantity
-        }
-    }
-    private val differ = AsyncListDiffer(this, itemCallback)
-
-    override fun getItemCount() = differ.currentList.size
-
-    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): 
OrderViewHolder {
-        val view =
-            
LayoutInflater.from(parent.context).inflate(R.layout.list_item_order, parent, 
false)
-        return OrderViewHolder(view)
-    }
-
-    override fun onBindViewHolder(holder: OrderViewHolder, position: Int) {
-        val item = getItem(position)!!
-        holder.bind(item, tracker.isSelected(item.id))
-    }
-
-    fun setItems(items: List<ConfigProduct>, commitCallback: () -> Unit) {
-        // toMutableList() is needed for some reason, otherwise doesn't update 
adapter
-        differ.submitList(items.toMutableList(), commitCallback)
-    }
-
-    fun getItem(position: Int): ConfigProduct? = differ.currentList[position]
-
-    fun getItemByKey(key: String): ConfigProduct? {
-        return differ.currentList.find { it.id == key }
-    }
-
-    fun findPosition(product: ConfigProduct): Int {
-        return differ.currentList.indexOf(product)
-    }
-
-    private inner class OrderViewHolder(private val v: View) : ViewHolder(v) {
-        private val quantity: TextView = v.findViewById(R.id.quantity)
-        private val name: TextView = v.findViewById(R.id.name)
-        private val price: TextView = v.findViewById(R.id.price)
-
-        fun bind(product: ConfigProduct, selected: Boolean) {
-            v.isActivated = selected
-            quantity.text = product.quantity.toString()
-            name.text = product.localizedDescription
-            price.text = product.totalPrice.amountStr
-        }
-    }
-
-    private inner class OrderKeyProvider : 
ItemKeyProvider<String>(SCOPE_MAPPED) {
-        override fun getKey(position: Int) = getItem(position)!!.id
-        override fun getPosition(key: String): Int {
-            return differ.currentList.indexOfFirst { it.id == key }
-        }
-    }
-
-    internal class OrderLineLookup(private val list: RecyclerView) : 
ItemDetailsLookup<String>() {
-        override fun getItemDetails(e: MotionEvent): ItemDetails<String>? {
-            list.findChildViewUnder(e.x, e.y)?.let { view ->
-                val holder = list.getChildViewHolder(view)
-                val adapter = list.adapter as OrderAdapter
-                val position = holder.adapterPosition
-                return object : ItemDetails<String>() {
-                    override fun getPosition(): Int = position
-                    override fun getSelectionKey(): String = 
adapter.keyProvider.getKey(position)
-                    override fun inSelectionHotspot(e: MotionEvent) = true
-                }
-            }
-            return 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 bc1e35f..6bab0e6 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
@@ -110,8 +110,8 @@ class PaymentManager(
         // 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")
+            if (!payment.paid && payment.error != null) payment.orderId?.let { 
orderId ->
+                Log.d(TAG, "Deleting cancelled and unpaid order $orderId")
                 scope.launch(Dispatchers.IO) {
                     api.deleteOrder(merchantConfig, orderId)
                 }
diff --git 
a/merchant-terminal/src/main/java/net/taler/merchantpos/history/RefundFragment.kt
 
b/merchant-terminal/src/main/java/net/taler/merchantpos/refund/RefundFragment.kt
similarity index 83%
rename from 
merchant-terminal/src/main/java/net/taler/merchantpos/history/RefundFragment.kt
rename to 
merchant-terminal/src/main/java/net/taler/merchantpos/refund/RefundFragment.kt
index 17d78f6..edb2758 100644
--- 
a/merchant-terminal/src/main/java/net/taler/merchantpos/history/RefundFragment.kt
+++ 
b/merchant-terminal/src/main/java/net/taler/merchantpos/refund/RefundFragment.kt
@@ -14,13 +14,12 @@
  * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 
-package net.taler.merchantpos.history
+package net.taler.merchantpos.refund
 
 import android.os.Bundle
 import android.view.LayoutInflater
 import android.view.View
 import android.view.ViewGroup
-import androidx.annotation.StringRes
 import androidx.fragment.app.Fragment
 import androidx.fragment.app.activityViewModels
 import androidx.lifecycle.Observer
@@ -36,11 +35,11 @@ import net.taler.common.navigate
 import net.taler.merchantlib.OrderHistoryEntry
 import net.taler.merchantpos.MainViewModel
 import net.taler.merchantpos.R
-import 
net.taler.merchantpos.history.RefundFragmentDirections.Companion.actionRefundFragmentToRefundUriFragment
-import net.taler.merchantpos.history.RefundResult.AlreadyRefunded
-import net.taler.merchantpos.history.RefundResult.Error
-import net.taler.merchantpos.history.RefundResult.PastDeadline
-import net.taler.merchantpos.history.RefundResult.Success
+import 
net.taler.merchantpos.refund.RefundFragmentDirections.Companion.actionRefundFragmentToRefundUriFragment
+import net.taler.merchantpos.refund.RefundResult.AlreadyRefunded
+import net.taler.merchantpos.refund.RefundResult.Error
+import net.taler.merchantpos.refund.RefundResult.PastDeadline
+import net.taler.merchantpos.refund.RefundResult.Success
 
 class RefundFragment : Fragment() {
 
@@ -88,9 +87,9 @@ class RefundFragment : Fragment() {
     }
 
     private fun onRefundResultChanged(result: RefundResult?): Any = when 
(result) {
-        Error -> onError(R.string.refund_error_backend)
-        PastDeadline -> onError(R.string.refund_error_deadline)
-        AlreadyRefunded -> onError(R.string.refund_error_already_refunded)
+        is Error -> onError(result.msg)
+        PastDeadline -> onError(getString(R.string.refund_error_deadline))
+        AlreadyRefunded -> 
onError(getString(R.string.refund_error_already_refunded))
         is Success -> {
             progressBar.fadeOut()
             refundButton.fadeIn()
@@ -100,8 +99,8 @@ class RefundFragment : Fragment() {
         }
     }
 
-    private fun onError(@StringRes res: Int) {
-        Snackbar.make(requireView(), res, LENGTH_LONG).show()
+    private fun onError(msg: String) {
+        Snackbar.make(requireView(), msg, LENGTH_LONG).show()
         progressBar.fadeOut()
         refundButton.fadeIn()
     }
diff --git 
a/merchant-terminal/src/main/java/net/taler/merchantpos/refund/RefundManager.kt 
b/merchant-terminal/src/main/java/net/taler/merchantpos/refund/RefundManager.kt
new file mode 100644
index 0000000..ea2d398
--- /dev/null
+++ 
b/merchant-terminal/src/main/java/net/taler/merchantpos/refund/RefundManager.kt
@@ -0,0 +1,91 @@
+/*
+ * 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.merchantpos.refund
+
+import androidx.annotation.UiThread
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import net.taler.common.Amount
+import net.taler.merchantlib.MerchantApi
+import net.taler.merchantlib.OrderHistoryEntry
+import net.taler.merchantlib.RefundRequest
+import net.taler.merchantpos.config.ConfigManager
+
+sealed class RefundResult {
+    class Error(val msg: String) : RefundResult()
+    object PastDeadline : RefundResult()
+    object AlreadyRefunded : RefundResult()
+    class Success(
+        val refundUri: String,
+        val item: OrderHistoryEntry,
+        val amount: Amount,
+        val reason: String
+    ) : RefundResult()
+}
+
+class RefundManager(
+    private val configManager: ConfigManager,
+    private val scope: CoroutineScope,
+    private val api: MerchantApi
+) {
+
+    var toBeRefunded: OrderHistoryEntry? = null
+        private set
+
+    private val mRefundResult = MutableLiveData<RefundResult>()
+    internal val refundResult: LiveData<RefundResult> = mRefundResult
+
+    @UiThread
+    internal fun startRefund(item: OrderHistoryEntry) {
+        toBeRefunded = item
+        mRefundResult.value = null
+    }
+
+    @UiThread
+    internal fun abortRefund() {
+        toBeRefunded = null
+        mRefundResult.value = null
+    }
+
+    @UiThread
+    internal fun refund(item: OrderHistoryEntry, amount: Amount, reason: 
String) {
+        val merchantConfig = configManager.merchantConfig!!
+        val request = RefundRequest(amount, reason)
+        scope.launch(Dispatchers.IO) {
+            api.giveRefund(merchantConfig, item.orderId, 
request).handle(::onRefundError) {
+                val result = RefundResult.Success(
+                    refundUri = it.talerRefundUri,
+                    item = item,
+                    amount = amount,
+                    reason = reason
+                )
+                mRefundResult.postValue(result)
+            }
+        }
+    }
+
+    @UiThread
+    private fun onRefundError(msg: String) {
+        if (msg.contains("2602")) {
+            mRefundResult.postValue(RefundResult.AlreadyRefunded)
+        } else mRefundResult.postValue(RefundResult.Error(msg))
+    }
+
+}
diff --git 
a/merchant-terminal/src/main/java/net/taler/merchantpos/history/RefundUriFragment.kt
 
b/merchant-terminal/src/main/java/net/taler/merchantpos/refund/RefundUriFragment.kt
similarity index 98%
rename from 
merchant-terminal/src/main/java/net/taler/merchantpos/history/RefundUriFragment.kt
rename to 
merchant-terminal/src/main/java/net/taler/merchantpos/refund/RefundUriFragment.kt
index 1ea0959..b8e8997 100644
--- 
a/merchant-terminal/src/main/java/net/taler/merchantpos/history/RefundUriFragment.kt
+++ 
b/merchant-terminal/src/main/java/net/taler/merchantpos/refund/RefundUriFragment.kt
@@ -14,7 +14,7 @@
  * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 
-package net.taler.merchantpos.history
+package net.taler.merchantpos.refund
 
 import android.os.Bundle
 import android.view.LayoutInflater
diff --git a/merchant-terminal/src/main/res/layout/fragment_merchant_config.xml 
b/merchant-terminal/src/main/res/layout/fragment_merchant_config.xml
index b19f14c..0061a1c 100644
--- a/merchant-terminal/src/main/res/layout/fragment_merchant_config.xml
+++ b/merchant-terminal/src/main/res/layout/fragment_merchant_config.xml
@@ -24,7 +24,7 @@
     <androidx.constraintlayout.widget.ConstraintLayout
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        tools:context=".config.MerchantConfigFragment">
+        tools:context=".config.ConfigFragment">
 
         <com.google.android.material.textfield.TextInputLayout
             android:id="@+id/configUrlView"
diff --git a/merchant-terminal/src/main/res/layout/fragment_refund.xml 
b/merchant-terminal/src/main/res/layout/fragment_refund.xml
index 944da55..a13cd5a 100644
--- a/merchant-terminal/src/main/res/layout/fragment_refund.xml
+++ b/merchant-terminal/src/main/res/layout/fragment_refund.xml
@@ -19,7 +19,7 @@
     xmlns:tools="http://schemas.android.com/tools";
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    tools:context=".history.RefundFragment">
+    tools:context=".refund.RefundFragment">
 
     <com.google.android.material.textfield.TextInputLayout
         android:id="@+id/amountView"
diff --git a/merchant-terminal/src/main/res/navigation/nav_graph.xml 
b/merchant-terminal/src/main/res/navigation/nav_graph.xml
index 606f2de..2c9ef2c 100644
--- a/merchant-terminal/src/main/res/navigation/nav_graph.xml
+++ b/merchant-terminal/src/main/res/navigation/nav_graph.xml
@@ -54,7 +54,7 @@
 
     <fragment
         android:id="@+id/nav_history"
-        android:name="net.taler.merchantpos.history.MerchantHistoryFragment"
+        android:name="net.taler.merchantpos.history.HistoryFragment"
         android:label="@string/history_label"
         tools:layout="@layout/fragment_merchant_history">
         <action
@@ -64,7 +64,7 @@
 
     <fragment
         android:id="@+id/refundFragment"
-        android:name="net.taler.merchantpos.history.RefundFragment"
+        android:name="net.taler.merchantpos.refund.RefundFragment"
         android:label="@string/history_refund"
         tools:layout="@layout/fragment_refund">
         <action
@@ -75,13 +75,13 @@
 
     <fragment
         android:id="@+id/refundUriFragment"
-        android:name="net.taler.merchantpos.history.RefundUriFragment"
+        android:name="net.taler.merchantpos.refund.RefundUriFragment"
         android:label="@string/history_refund"
         tools:layout="@layout/fragment_refund_uri" />
 
     <fragment
         android:id="@+id/nav_settings"
-        android:name="net.taler.merchantpos.config.MerchantConfigFragment"
+        android:name="net.taler.merchantpos.config.ConfigFragment"
         android:label="@string/config_label"
         tools:layout="@layout/fragment_merchant_config">
         <action

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