gnunet-svn
[Top][All Lists]
Advanced

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

[taler-taler-android] 02/02: [pos] Implement new refund API (untested si


From: gnunet
Subject: [taler-taler-android] 02/02: [pos] Implement new refund API (untested since there is no wallet support)
Date: Tue, 04 Aug 2020 14:48:45 +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 35f7ed512ed7445362d6caee1bf60441f4ce979e
Author: Torsten Grote <t@grobox.de>
AuthorDate: Tue Aug 4 09:46:38 2020 -0300

    [pos] Implement new refund API (untested since there is no wallet support)
    
    Also do a bit of code cleanup and minor refactorings
    
    This also removes the volley HTTP library which is not needed anymore
---
 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  |  98 ++++++++++++++-
 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 +-
 25 files changed, 372 insertions(+), 435 deletions(-)

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 437697b..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 {
@@ -95,7 +97,7 @@ class MerchantApiTest {
 
         httpClient.giveJsonResponse(
             "http://example.net/instances/testInstance/private/orders";,
-            statusCode = HttpStatusCode.NotFound
+            statusCode = NotFound
         ) {
             """{
                 "code": 2000,
@@ -110,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";)
 {
             """{
@@ -125,7 +126,7 @@ class MerchantApiTest {
 
         httpClient.giveJsonResponse(
             
"http://example.net/instances/testInstance/private/orders/$orderId";,
-            statusCode = HttpStatusCode.NotFound
+            statusCode = NotFound
         ) {
             """{
                 "code": 2909,
@@ -138,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/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]