gnunet-svn
[Top][All Lists]
Advanced

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

[taler-taler-android] 02/04: [pos] Enable refunds


From: gnunet
Subject: [taler-taler-android] 02/04: [pos] Enable refunds
Date: Wed, 01 Apr 2020 20:31:09 +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 aa6dad91b20edd0a304423d1edc267cf4e8b5dbe
Author: Torsten Grote <address@hidden>
AuthorDate: Wed Apr 1 15:02:15 2020 -0300

    [pos] Enable refunds
---
 build.gradle                                       |   2 +-
 .../java/net/taler/merchantpos/MainActivity.kt     |   4 +
 .../src/main/java/net/taler/merchantpos/Utils.kt   |  15 ++
 .../net/taler/merchantpos/config/ConfigManager.kt  |   6 +-
 .../taler/merchantpos/config/MerchantRequest.kt    |   3 +-
 .../taler/merchantpos/history/HistoryManager.kt    |  10 +-
 .../taler/merchantpos/history/RefundFragment.kt    |   4 +-
 .../net/taler/merchantpos/history/RefundManager.kt |  24 ++-
 .../taler/merchantpos/history/RefundUriFragment.kt |   6 +
 .../taler/merchantpos/payment/PaymentManager.kt    |  20 +--
 .../main/res/layout/fragment_process_payment.xml   | 155 ++++++++++---------
 .../src/main/res/layout/fragment_refund_uri.xml    | 137 +++++++++--------
 .../src/main/res/layout/list_item_history.xml      |   1 -
 .../src/main/res/navigation/nav_graph.xml          | 165 +++++++++++----------
 merchant-terminal/src/main/res/values/strings.xml  |  10 +-
 .../src/main/java/net/taler/common/TalerUtils.kt   |   7 +
 16 files changed, 316 insertions(+), 253 deletions(-)

diff --git a/build.gradle b/build.gradle
index 57a780b..59e7f30 100644
--- a/build.gradle
+++ b/build.gradle
@@ -7,7 +7,7 @@ buildscript {
         jcenter()
     }
     dependencies {
-        classpath 'com.android.tools.build:gradle:3.6.1'
+        classpath 'com.android.tools.build:gradle:3.6.2'
         classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
         classpath 
"androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version"
     }
diff --git 
a/merchant-terminal/src/main/java/net/taler/merchantpos/MainActivity.kt 
b/merchant-terminal/src/main/java/net/taler/merchantpos/MainActivity.kt
index d6e3747..64f6ceb 100644
--- a/merchant-terminal/src/main/java/net/taler/merchantpos/MainActivity.kt
+++ b/merchant-terminal/src/main/java/net/taler/merchantpos/MainActivity.kt
@@ -47,6 +47,10 @@ class MainActivity : AppCompatActivity(), 
OnNavigationItemSelectedListener {
 
     private var reallyExit = false
 
+    companion object {
+        val TAG = "taler-pos"
+    }
+
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
         setContentView(R.layout.activity_main)
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 578debf..9deb042 100644
--- a/merchant-terminal/src/main/java/net/taler/merchantpos/Utils.kt
+++ b/merchant-terminal/src/main/java/net/taler/merchantpos/Utils.kt
@@ -16,11 +16,15 @@
 
 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)
@@ -32,3 +36,14 @@ 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/ConfigManager.kt 
b/merchant-terminal/src/main/java/net/taler/merchantpos/config/ConfigManager.kt
index edb8059..171cf28 100644
--- 
a/merchant-terminal/src/main/java/net/taler/merchantpos/config/ConfigManager.kt
+++ 
b/merchant-terminal/src/main/java/net/taler/merchantpos/config/ConfigManager.kt
@@ -26,7 +26,6 @@ import androidx.lifecycle.LiveData
 import androidx.lifecycle.MutableLiveData
 import com.android.volley.Request.Method.GET
 import com.android.volley.RequestQueue
-import com.android.volley.Response.ErrorListener
 import com.android.volley.Response.Listener
 import com.android.volley.VolleyError
 import com.android.volley.toolbox.JsonObjectRequest
@@ -35,6 +34,7 @@ import com.fasterxml.jackson.module.kotlin.readValue
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.launch
+import net.taler.merchantpos.LogErrorListener
 import net.taler.merchantpos.R
 import org.json.JSONObject
 
@@ -91,7 +91,7 @@ class ConfigManager(
 
         val stringRequest = object : JsonObjectRequest(GET, config.configUrl, 
null,
             Listener { onConfigReceived(it, configToSave) },
-            ErrorListener { onNetworkError(it) }
+            LogErrorListener { onNetworkError(it) }
         ) {
             // send basic auth header
             override fun getHeaders(): MutableMap<String, String> {
@@ -117,7 +117,7 @@ class ConfigManager(
         val params = mapOf("instance" to merchantConfig.instance)
         val req = MerchantRequest(GET, merchantConfig, "config", params, null,
             Listener { onMerchantConfigReceived(config, json, merchantConfig, 
it) },
-            ErrorListener { onNetworkError(it) }
+            LogErrorListener { onNetworkError(it) }
         )
         queue.add(req)
     }
diff --git 
a/merchant-terminal/src/main/java/net/taler/merchantpos/config/MerchantRequest.kt
 
b/merchant-terminal/src/main/java/net/taler/merchantpos/config/MerchantRequest.kt
index 8d95378..862dd33 100644
--- 
a/merchant-terminal/src/main/java/net/taler/merchantpos/config/MerchantRequest.kt
+++ 
b/merchant-terminal/src/main/java/net/taler/merchantpos/config/MerchantRequest.kt
@@ -20,6 +20,7 @@ package net.taler.merchantpos.config
 import android.util.ArrayMap
 import com.android.volley.Response
 import com.android.volley.toolbox.JsonObjectRequest
+import net.taler.merchantpos.LogErrorListener
 import org.json.JSONObject
 
 class MerchantRequest(
@@ -29,7 +30,7 @@ class MerchantRequest(
     params: Map<String, String>?,
     jsonRequest: JSONObject?,
     listener: Response.Listener<JSONObject>,
-    errorListener: Response.ErrorListener
+    errorListener: LogErrorListener
 ) :
     JsonObjectRequest(method, merchantConfig.urlFor(endpoint, params), 
jsonRequest, listener, errorListener) {
 
diff --git 
a/merchant-terminal/src/main/java/net/taler/merchantpos/history/HistoryManager.kt
 
b/merchant-terminal/src/main/java/net/taler/merchantpos/history/HistoryManager.kt
index 3aaf3a4..6b95e16 100644
--- 
a/merchant-terminal/src/main/java/net/taler/merchantpos/history/HistoryManager.kt
+++ 
b/merchant-terminal/src/main/java/net/taler/merchantpos/history/HistoryManager.kt
@@ -21,7 +21,6 @@ import androidx.lifecycle.LiveData
 import androidx.lifecycle.MutableLiveData
 import com.android.volley.Request.Method.GET
 import com.android.volley.RequestQueue
-import com.android.volley.Response.ErrorListener
 import com.android.volley.Response.Listener
 import com.fasterxml.jackson.annotation.JsonIgnore
 import com.fasterxml.jackson.annotation.JsonProperty
@@ -29,6 +28,7 @@ import com.fasterxml.jackson.databind.ObjectMapper
 import com.fasterxml.jackson.module.kotlin.readValue
 import net.taler.common.Amount
 import net.taler.common.Timestamp
+import net.taler.merchantpos.LogErrorListener
 import net.taler.merchantpos.config.ConfigManager
 import net.taler.merchantpos.config.MerchantRequest
 import org.json.JSONObject
@@ -36,14 +36,10 @@ import org.json.JSONObject
 data class HistoryItem(
     @JsonProperty("order_id")
     val orderId: String,
-    @JsonProperty("amount")
-    val amountStr: String,
+    val amount: Amount,
     val summary: String,
     val timestamp: Timestamp
 ) {
-    @get:JsonIgnore
-    val amount: Amount by lazy { Amount.fromJSONString(amountStr) }
-
     @get:JsonIgnore
     val time = timestamp.ms
 }
@@ -72,7 +68,7 @@ class HistoryManager(
         val params = mapOf("instance" to merchantConfig.instance)
         val req = MerchantRequest(GET, merchantConfig, "history", params, null,
             Listener { onHistoryResponse(it) },
-            ErrorListener { onHistoryError() })
+            LogErrorListener { onHistoryError() })
         queue.add(req)
     }
 
diff --git 
a/merchant-terminal/src/main/java/net/taler/merchantpos/history/RefundFragment.kt
 
b/merchant-terminal/src/main/java/net/taler/merchantpos/history/RefundFragment.kt
index 7652ca4..2b85add 100644
--- 
a/merchant-terminal/src/main/java/net/taler/merchantpos/history/RefundFragment.kt
+++ 
b/merchant-terminal/src/main/java/net/taler/merchantpos/history/RefundFragment.kt
@@ -36,6 +36,7 @@ import net.taler.common.navigate
 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
@@ -72,7 +73,7 @@ class RefundFragment : Fragment() {
             return
         }
         if (inputAmount > item.amount) {
-            amountView.error = getString(R.string.refund_error_max_amount, 
item.amountStr)
+            amountView.error = getString(R.string.refund_error_max_amount, 
item.amount.amountStr)
             return
         }
         if (inputAmount.isZero()) {
@@ -88,6 +89,7 @@ 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 Success -> {
             progressBar.fadeOut()
             refundButton.fadeIn()
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
index 910116e..da642d6 100644
--- 
a/merchant-terminal/src/main/java/net/taler/merchantpos/history/RefundManager.kt
+++ 
b/merchant-terminal/src/main/java/net/taler/merchantpos/history/RefundManager.kt
@@ -22,9 +22,10 @@ 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.ErrorListener
 import com.android.volley.Response.Listener
+import com.android.volley.VolleyError
 import net.taler.common.Amount
+import net.taler.merchantpos.LogErrorListener
 import net.taler.merchantpos.config.ConfigManager
 import net.taler.merchantpos.config.MerchantRequest
 import org.json.JSONObject
@@ -32,6 +33,7 @@ import org.json.JSONObject
 sealed class RefundResult {
     object Error : RefundResult()
     object PastDeadline : RefundResult()
+    object AlreadyRefunded : RefundResult()
     class Success(
         val refundUri: String,
         val item: HistoryItem,
@@ -61,6 +63,12 @@ class RefundManager(
         mRefundResult.value = null
     }
 
+    @UiThread
+    internal fun abortRefund() {
+        toBeRefunded = null
+        mRefundResult.value = null
+    }
+
     @UiThread
     internal fun refund(item: HistoryItem, amount: Amount, reason: String) {
         val merchantConfig = configManager.merchantConfig!!
@@ -73,7 +81,7 @@ class RefundManager(
         Log.d(TAG, body.toString(4))
         val req = MerchantRequest(POST, merchantConfig, "refund", null, body,
             Listener { onRefundResponse(it, item, amount, reason) },
-            ErrorListener { onRefundError() }
+            LogErrorListener { onRefundError(it) }
         )
         queue.add(req)
     }
@@ -86,7 +94,7 @@ class RefundManager(
         reason: String
     ) {
         if (!json.has("contract_terms")) {
-            Log.e("TEST", "json: $json")
+            Log.e(TAG, "Contract terms missing: $json")
             onRefundError()
             return
         }
@@ -110,7 +118,15 @@ class RefundManager(
     }
 
     @UiThread
-    private fun onRefundError() {
+    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/history/RefundUriFragment.kt
 
b/merchant-terminal/src/main/java/net/taler/merchantpos/history/RefundUriFragment.kt
index 1bc4002..1ea0959 100644
--- 
a/merchant-terminal/src/main/java/net/taler/merchantpos/history/RefundUriFragment.kt
+++ 
b/merchant-terminal/src/main/java/net/taler/merchantpos/history/RefundUriFragment.kt
@@ -58,6 +58,12 @@ class RefundUriFragment : Fragment() {
             getString(R.string.refund_order_ref, result.item.orderId, 
result.reason)
 
         cancelRefundButton.setOnClickListener { 
findNavController().navigateUp() }
+        completeButton.setOnClickListener { findNavController().navigateUp() }
+    }
+
+    override fun onDestroy() {
+        super.onDestroy()
+        refundManager.abortRefund()
     }
 
 }
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 054d7cd..9138740 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
@@ -24,10 +24,11 @@ import androidx.lifecycle.MutableLiveData
 import com.android.volley.Request.Method.GET
 import com.android.volley.Request.Method.POST
 import com.android.volley.RequestQueue
-import com.android.volley.Response.ErrorListener
 import com.android.volley.Response.Listener
-import com.android.volley.VolleyError
 import com.fasterxml.jackson.databind.ObjectMapper
+import net.taler.common.Timestamp
+import net.taler.common.now
+import net.taler.merchantpos.LogErrorListener
 import net.taler.merchantpos.config.ConfigManager
 import net.taler.merchantpos.config.MerchantRequest
 import net.taler.merchantpos.order.Order
@@ -73,11 +74,12 @@ class PaymentManager(
         val currency = merchantConfig.currency!!
         val summary = order.summary
         val summaryI18n = order.summaryI18n
-//        val refundDeadline = Timestamp(System.currentTimeMillis() + 
HOURS.toMillis(2))
+        val now = now()
+        val deadline = Timestamp(now + MINUTES.toMillis(120))
 
         mPayment.value = Payment(order, summary, currency)
 
-        val fulfillmentId = "${System.currentTimeMillis()}-${order.hashCode()}"
+        val fulfillmentId = "${now}-${order.hashCode()}"
         val fulfillmentUrl =
             "${FULFILLMENT_PREFIX}${URLEncoder.encode(summary, 
"UTF-8")}#$fulfillmentId"
         val body = JSONObject().apply {
@@ -88,7 +90,8 @@ class PaymentManager(
                 // fulfillment_url needs to be unique per order
                 put("fulfillment_url", fulfillmentUrl)
                 put("instance", "default")
-//                put("refund_deadline", 
JSONObject(mapper.writeValueAsString(refundDeadline)))
+                put("wire_transfer_deadline", 
JSONObject(mapper.writeValueAsString(deadline)))
+                put("refund_deadline", 
JSONObject(mapper.writeValueAsString(deadline)))
                 put("products", order.getProductsJson())
             })
         }
@@ -97,7 +100,7 @@ class PaymentManager(
 
         val req = MerchantRequest(POST, merchantConfig, "order", null, body,
             Listener { onOrderCreated(it) },
-            ErrorListener { onNetworkError(it) }
+            LogErrorListener { onNetworkError() }
         )
         queue.add(req)
     }
@@ -123,7 +126,7 @@ class PaymentManager(
 
         val req = MerchantRequest(GET, merchantConfig, "check-payment", 
params, null,
             Listener { onPaymentChecked(it) },
-            ErrorListener { onNetworkError(it) })
+            LogErrorListener { onNetworkError() })
         queue.add(req)
     }
 
@@ -141,8 +144,7 @@ class PaymentManager(
         }
     }
 
-    private fun onNetworkError(volleyError: VolleyError) {
-        Log.e(PaymentManager::class.java.simpleName, volleyError.toString())
+    private fun onNetworkError() {
         cancelPayment()
     }
 
diff --git a/merchant-terminal/src/main/res/layout/fragment_process_payment.xml 
b/merchant-terminal/src/main/res/layout/fragment_process_payment.xml
index 6cd8ea1..cb69aa2 100644
--- a/merchant-terminal/src/main/res/layout/fragment_process_payment.xml
+++ b/merchant-terminal/src/main/res/layout/fragment_process_payment.xml
@@ -1,5 +1,4 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
+<?xml version="1.0" encoding="utf-8"?><!--
   ~ This file is part of GNU Taler
   ~ (C) 2020 Taler Systems S.A.
   ~
@@ -16,95 +15,95 @@
   -->
 
 <androidx.constraintlayout.widget.ConstraintLayout 
xmlns:android="http://schemas.android.com/apk/res/android";
-        xmlns:app="http://schemas.android.com/apk/res-auto";
-        xmlns:tools="http://schemas.android.com/tools";
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        tools:context=".payment.ProcessPaymentFragment">
+    xmlns:app="http://schemas.android.com/apk/res-auto";
+    xmlns:tools="http://schemas.android.com/tools";
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    tools:context=".payment.ProcessPaymentFragment">
 
     <ImageView
-            android:id="@+id/qrcodeView"
-            android:layout_width="0dp"
-            android:layout_height="0dp"
-            android:layout_margin="32dp"
-            android:visibility="invisible"
-            app:layout_constraintBottom_toBottomOf="parent"
-            app:layout_constraintEnd_toStartOf="@+id/guideline"
-            app:layout_constraintStart_toStartOf="parent"
-            app:layout_constraintTop_toTopOf="parent"
-            tools:ignore="ContentDescription"
-            tools:src="@tools:sample/avatars"
-            tools:visibility="visible" />
+        android:id="@+id/qrcodeView"
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        android:layout_margin="32dp"
+        android:visibility="invisible"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toStartOf="@+id/guideline"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        tools:ignore="ContentDescription"
+        tools:src="@tools:sample/avatars"
+        tools:visibility="visible" />
 
     <ProgressBar
-            android:id="@+id/progressBar"
-            style="?android:attr/progressBarStyleLarge"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            app:layout_constraintBottom_toBottomOf="@+id/qrcodeView"
-            app:layout_constraintEnd_toEndOf="@+id/qrcodeView"
-            app:layout_constraintStart_toStartOf="@+id/qrcodeView"
-            app:layout_constraintTop_toTopOf="@+id/qrcodeView" />
+        android:id="@+id/progressBar"
+        style="?android:attr/progressBarStyleLarge"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        app:layout_constraintBottom_toBottomOf="@+id/qrcodeView"
+        app:layout_constraintEnd_toEndOf="@+id/qrcodeView"
+        app:layout_constraintStart_toStartOf="@+id/qrcodeView"
+        app:layout_constraintTop_toTopOf="@+id/qrcodeView" />
 
     <androidx.constraintlayout.widget.Guideline
-            android:id="@+id/guideline"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:orientation="vertical"
-            app:layout_constraintGuide_percent="0.54" />
+        android:id="@+id/guideline"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:orientation="vertical"
+        app:layout_constraintGuide_percent="0.54" />
 
     <TextView
-            android:id="@+id/payIntroView"
-            android:layout_width="0dp"
-            android:layout_height="wrap_content"
-            android:layout_margin="16dp"
-            android:text="@string/payment_intro_nfc"
-            android:textAlignment="center"
-            android:textSize="24sp"
-            android:visibility="invisible"
-            app:layout_constraintBottom_toTopOf="@+id/amountView"
-            app:layout_constraintEnd_toEndOf="parent"
-            app:layout_constraintStart_toStartOf="@+id/guideline"
-            app:layout_constraintTop_toTopOf="parent"
-            app:layout_constraintVertical_chainStyle="spread"
-            tools:visibility="visible" />
+        android:id="@+id/payIntroView"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_margin="16dp"
+        android:text="@string/payment_intro_nfc"
+        android:textAlignment="center"
+        android:textSize="22sp"
+        android:visibility="invisible"
+        app:layout_constraintBottom_toTopOf="@+id/amountView"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="@+id/guideline"
+        app:layout_constraintTop_toTopOf="parent"
+        app:layout_constraintVertical_chainStyle="spread"
+        tools:visibility="visible" />
 
     <TextView
-            android:id="@+id/amountView"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_margin="16dp"
-            android:textAppearance="@style/TextAppearance.AppCompat.Headline"
-            app:layout_constraintBottom_toTopOf="@+id/orderRefView"
-            app:layout_constraintEnd_toEndOf="parent"
-            app:layout_constraintStart_toStartOf="@+id/guideline"
-            app:layout_constraintTop_toBottomOf="@+id/payIntroView"
-            tools:text="10.49 TESTKUDOS" />
+        android:id="@+id/amountView"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_margin="16dp"
+        android:textAppearance="@style/TextAppearance.AppCompat.Headline"
+        app:layout_constraintBottom_toTopOf="@+id/orderRefView"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="@+id/guideline"
+        app:layout_constraintTop_toBottomOf="@+id/payIntroView"
+        tools:text="10.49 TESTKUDOS" />
 
     <TextView
-            android:id="@+id/orderRefView"
-            android:layout_width="0dp"
-            android:layout_height="wrap_content"
-            android:layout_margin="16dp"
-            android:textAlignment="center"
-            android:visibility="invisible"
-            app:layout_constraintBottom_toTopOf="@id/cancelPaymentButton"
-            app:layout_constraintEnd_toEndOf="parent"
-            app:layout_constraintStart_toStartOf="@+id/guideline"
-            app:layout_constraintTop_toBottomOf="@+id/amountView"
-            tools:text="@string/payment_order_ref"
-            tools:visibility="visible" />
+        android:id="@+id/orderRefView"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_margin="16dp"
+        android:textAlignment="center"
+        android:visibility="invisible"
+        app:layout_constraintBottom_toTopOf="@id/cancelPaymentButton"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="@+id/guideline"
+        app:layout_constraintTop_toBottomOf="@+id/amountView"
+        tools:text="@string/payment_order_ref"
+        tools:visibility="visible" />
 
     <Button
-            android:id="@+id/cancelPaymentButton"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_margin="16dp"
-            android:backgroundTint="@color/red"
-            android:text="@string/payment_cancel"
-            app:layout_constraintBottom_toBottomOf="parent"
-            app:layout_constraintEnd_toEndOf="parent"
-            app:layout_constraintHorizontal_bias="0.0"
-            app:layout_constraintStart_toStartOf="@+id/guideline" />
+        android:id="@+id/cancelPaymentButton"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_margin="16dp"
+        android:backgroundTint="@color/red"
+        android:text="@string/payment_cancel"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintHorizontal_bias="0.0"
+        app:layout_constraintStart_toStartOf="@+id/guideline" />
 
 </androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/merchant-terminal/src/main/res/layout/fragment_refund_uri.xml 
b/merchant-terminal/src/main/res/layout/fragment_refund_uri.xml
index 8447d28..c82a324 100644
--- a/merchant-terminal/src/main/res/layout/fragment_refund_uri.xml
+++ b/merchant-terminal/src/main/res/layout/fragment_refund_uri.xml
@@ -15,79 +15,92 @@
   -->
 
 <androidx.constraintlayout.widget.ConstraintLayout 
xmlns:android="http://schemas.android.com/apk/res/android";
-        xmlns:app="http://schemas.android.com/apk/res-auto";
-        xmlns:tools="http://schemas.android.com/tools";
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        tools:context=".payment.ProcessPaymentFragment">
+    xmlns:app="http://schemas.android.com/apk/res-auto";
+    xmlns:tools="http://schemas.android.com/tools";
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    tools:context=".payment.ProcessPaymentFragment">
 
     <ImageView
-            android:id="@+id/refundQrcodeView"
-            android:layout_width="0dp"
-            android:layout_height="0dp"
-            android:layout_margin="32dp"
-            app:layout_constraintBottom_toBottomOf="parent"
-            app:layout_constraintEnd_toStartOf="@+id/guideline"
-            app:layout_constraintStart_toStartOf="parent"
-            app:layout_constraintTop_toTopOf="parent"
-            tools:ignore="ContentDescription"
-            tools:src="@tools:sample/avatars" />
+        android:id="@+id/refundQrcodeView"
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        android:layout_margin="32dp"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toStartOf="@+id/guideline"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        tools:ignore="ContentDescription"
+        tools:src="@tools:sample/avatars" />
 
     <androidx.constraintlayout.widget.Guideline
-            android:id="@+id/guideline"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:orientation="vertical"
-            app:layout_constraintGuide_percent="0.54" />
+        android:id="@+id/guideline"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:orientation="vertical"
+        app:layout_constraintGuide_percent="0.54" />
 
     <TextView
-            android:id="@+id/refundIntroView"
-            android:layout_width="0dp"
-            android:layout_height="wrap_content"
-            android:layout_margin="16dp"
-            android:text="@string/refund_intro_nfc"
-            android:textAlignment="center"
-            android:textSize="24sp"
-            app:layout_constraintBottom_toTopOf="@+id/refundAmountView"
-            app:layout_constraintEnd_toEndOf="parent"
-            app:layout_constraintStart_toStartOf="@+id/guideline"
-            app:layout_constraintTop_toTopOf="parent"
-            app:layout_constraintVertical_chainStyle="spread" />
+        android:id="@+id/refundIntroView"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_margin="16dp"
+        android:text="@string/refund_intro_nfc"
+        android:textAlignment="center"
+        android:textSize="22sp"
+        app:layout_constraintBottom_toTopOf="@+id/refundAmountView"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="@+id/guideline"
+        app:layout_constraintTop_toTopOf="parent"
+        app:layout_constraintVertical_chainStyle="spread" />
 
     <TextView
-            android:id="@+id/refundAmountView"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_margin="16dp"
-            android:textAppearance="@style/TextAppearance.AppCompat.Headline"
-            app:layout_constraintBottom_toTopOf="@+id/refundRefView"
-            app:layout_constraintEnd_toEndOf="parent"
-            app:layout_constraintStart_toStartOf="@+id/guideline"
-            app:layout_constraintTop_toBottomOf="@+id/refundIntroView"
-            tools:text="10.49 TESTKUDOS" />
+        android:id="@+id/refundAmountView"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_margin="16dp"
+        android:textAppearance="@style/TextAppearance.AppCompat.Headline"
+        app:layout_constraintBottom_toTopOf="@+id/refundRefView"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="@+id/guideline"
+        app:layout_constraintTop_toBottomOf="@+id/refundIntroView"
+        tools:text="10.49 TESTKUDOS" />
 
     <TextView
-            android:id="@+id/refundRefView"
-            android:layout_width="0dp"
-            android:layout_height="wrap_content"
-            android:layout_margin="16dp"
-            android:textAlignment="center"
-            app:layout_constraintBottom_toTopOf="@id/cancelRefundButton"
-            app:layout_constraintEnd_toEndOf="parent"
-            app:layout_constraintStart_toStartOf="@+id/guideline"
-            app:layout_constraintTop_toBottomOf="@+id/refundAmountView"
-            tools:text="@string/refund_order_ref" />
+        android:id="@+id/refundRefView"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_margin="16dp"
+        android:textAlignment="center"
+        app:layout_constraintBottom_toTopOf="@id/cancelRefundButton"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="@+id/guideline"
+        app:layout_constraintTop_toBottomOf="@+id/refundAmountView"
+        tools:text="@string/refund_order_ref" />
 
     <Button
-            android:id="@+id/cancelRefundButton"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_margin="16dp"
-            android:backgroundTint="@color/red"
-            android:text="@string/refund_abort"
-            app:layout_constraintBottom_toBottomOf="parent"
-            app:layout_constraintEnd_toEndOf="parent"
-            app:layout_constraintHorizontal_bias="0.0"
-            app:layout_constraintStart_toStartOf="@+id/guideline" />
+        android:id="@+id/cancelRefundButton"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_margin="16dp"
+        android:backgroundTint="@color/red"
+        android:text="@string/refund_abort"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintEnd_toStartOf="@+id/completeButton"
+        app:layout_constraintHorizontal_chainStyle="spread_inside"
+        app:layout_constraintStart_toEndOf="@+id/refundQrcodeView"
+        app:layout_constraintStart_toStartOf="@+id/guideline" />
+
+    <Button
+        android:id="@+id/completeButton"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_margin="16dp"
+        android:backgroundTint="@color/green"
+        android:text="@string/refund_complete"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toEndOf="@+id/cancelRefundButton" />
 
 </androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/merchant-terminal/src/main/res/layout/list_item_history.xml 
b/merchant-terminal/src/main/res/layout/list_item_history.xml
index fe485ba..57f85ef 100644
--- a/merchant-terminal/src/main/res/layout/list_item_history.xml
+++ b/merchant-terminal/src/main/res/layout/list_item_history.xml
@@ -88,7 +88,6 @@
             android:backgroundTint="?colorPrimary"
             android:contentDescription="@string/history_refund"
             android:tint="?attr/colorOnPrimary"
-            android:visibility="gone"
             app:layout_constraintBottom_toBottomOf="parent"
             app:layout_constraintEnd_toEndOf="parent"
             app:layout_constraintTop_toTopOf="parent"
diff --git a/merchant-terminal/src/main/res/navigation/nav_graph.xml 
b/merchant-terminal/src/main/res/navigation/nav_graph.xml
index 2e337f2..606f2de 100644
--- a/merchant-terminal/src/main/res/navigation/nav_graph.xml
+++ b/merchant-terminal/src/main/res/navigation/nav_graph.xml
@@ -15,123 +15,124 @@
   -->
 
 <navigation xmlns:android="http://schemas.android.com/apk/res/android";
-        xmlns:app="http://schemas.android.com/apk/res-auto";
-        xmlns:tools="http://schemas.android.com/tools";
-        android:id="@+id/nav_graph"
-        app:startDestination="@+id/nav_order"
-        tools:ignore="UnusedNavigation">
+    xmlns:app="http://schemas.android.com/apk/res-auto";
+    xmlns:tools="http://schemas.android.com/tools";
+    android:id="@+id/nav_graph"
+    app:startDestination="@+id/nav_order"
+    tools:ignore="UnusedNavigation">
 
     <fragment
-            android:id="@+id/nav_order"
-            android:name="net.taler.merchantpos.order.OrderFragment"
-            android:label=""
-            tools:layout="@layout/fragment_order">
+        android:id="@+id/nav_order"
+        android:name="net.taler.merchantpos.order.OrderFragment"
+        android:label=""
+        tools:layout="@layout/fragment_order">
         <action
-                android:id="@+id/action_order_to_merchantSettings"
-                app:destination="@+id/nav_settings"
-                app:launchSingleTop="true"
-                app:popUpTo="@+id/nav_graph"
-                app:popUpToInclusive="true" />
+            android:id="@+id/action_order_to_merchantSettings"
+            app:destination="@+id/nav_settings"
+            app:launchSingleTop="true"
+            app:popUpTo="@+id/nav_graph"
+            app:popUpToInclusive="true" />
         <action
-                android:id="@+id/action_order_self"
-                app:destination="@+id/nav_order"
-                app:popUpTo="@+id/nav_graph" />
+            android:id="@+id/action_order_self"
+            app:destination="@+id/nav_order"
+            app:popUpTo="@+id/nav_graph" />
         <action
-                android:id="@+id/action_order_to_processPayment"
-                app:destination="@+id/processPayment" />
+            android:id="@+id/action_order_to_processPayment"
+            app:destination="@+id/processPayment" />
     </fragment>
 
     <fragment
-            android:id="@+id/processPayment"
-            android:name="net.taler.merchantpos.payment.ProcessPaymentFragment"
-            android:label="@string/payment_process_label"
-            tools:layout="@layout/fragment_process_payment">
+        android:id="@+id/processPayment"
+        android:name="net.taler.merchantpos.payment.ProcessPaymentFragment"
+        android:label="@string/payment_process_label"
+        tools:layout="@layout/fragment_process_payment">
         <action
-                android:id="@+id/action_processPayment_to_paymentSuccess"
-                app:destination="@+id/paymentSuccess"
-                app:popUpTo="@id/nav_order" />
+            android:id="@+id/action_processPayment_to_paymentSuccess"
+            app:destination="@+id/paymentSuccess"
+            app:popUpTo="@id/nav_order" />
     </fragment>
 
     <fragment
-            android:id="@+id/nav_history"
-            
android:name="net.taler.merchantpos.history.MerchantHistoryFragment"
-            android:label="@string/history_label"
-            tools:layout="@layout/fragment_merchant_history">
+        android:id="@+id/nav_history"
+        android:name="net.taler.merchantpos.history.MerchantHistoryFragment"
+        android:label="@string/history_label"
+        tools:layout="@layout/fragment_merchant_history">
         <action
-                android:id="@+id/action_nav_history_to_refundFragment"
-                app:destination="@id/refundFragment" />
+            android:id="@+id/action_nav_history_to_refundFragment"
+            app:destination="@id/refundFragment" />
     </fragment>
 
     <fragment
-            android:id="@+id/refundFragment"
-            android:name="net.taler.merchantpos.history.RefundFragment"
-            android:label="@string/history_refund"
-            tools:layout="@layout/fragment_refund">
+        android:id="@+id/refundFragment"
+        android:name="net.taler.merchantpos.history.RefundFragment"
+        android:label="@string/history_refund"
+        tools:layout="@layout/fragment_refund">
         <action
-                android:id="@+id/action_refundFragment_to_refundUriFragment"
-                app:destination="@id/refundUriFragment" />
+            android:id="@+id/action_refundFragment_to_refundUriFragment"
+            app:destination="@id/refundUriFragment"
+            app:popUpTo="@id/nav_history" />
     </fragment>
 
     <fragment
-            android:id="@+id/refundUriFragment"
-            android:name="net.taler.merchantpos.history.RefundUriFragment"
-            android:label="@string/history_refund"
-            tools:layout="@layout/fragment_refund_uri" />
+        android:id="@+id/refundUriFragment"
+        android:name="net.taler.merchantpos.history.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:label="@string/config_label"
-            tools:layout="@layout/fragment_merchant_config">
+        android:id="@+id/nav_settings"
+        android:name="net.taler.merchantpos.config.MerchantConfigFragment"
+        android:label="@string/config_label"
+        tools:layout="@layout/fragment_merchant_config">
         <action
-                android:id="@+id/action_settings_to_order"
-                app:destination="@+id/nav_order"
-                app:launchSingleTop="true"
-                app:popUpTo="@+id/nav_graph"
-                app:popUpToInclusive="true" />
+            android:id="@+id/action_settings_to_order"
+            app:destination="@+id/nav_order"
+            app:launchSingleTop="true"
+            app:popUpTo="@+id/nav_graph"
+            app:popUpToInclusive="true" />
     </fragment>
 
     <fragment
-            android:id="@+id/configFetcher"
-            android:name="net.taler.merchantpos.config.ConfigFetcherFragment"
-            android:label="@string/config_fetching_label"
-            tools:layout="@layout/fragment_config_fetcher">
+        android:id="@+id/configFetcher"
+        android:name="net.taler.merchantpos.config.ConfigFetcherFragment"
+        android:label="@string/config_fetching_label"
+        tools:layout="@layout/fragment_config_fetcher">
         <action
-                android:id="@+id/action_configFetcher_to_merchantSettings"
-                app:destination="@+id/nav_settings"
-                app:launchSingleTop="true"
-                app:popUpTo="@+id/nav_graph"
-                app:popUpToInclusive="true" />
+            android:id="@+id/action_configFetcher_to_merchantSettings"
+            app:destination="@+id/nav_settings"
+            app:launchSingleTop="true"
+            app:popUpTo="@+id/nav_graph"
+            app:popUpToInclusive="true" />
         <action
-                android:id="@+id/action_configFetcher_to_order"
-                app:destination="@+id/nav_order"
-                app:launchSingleTop="true"
-                app:popUpTo="@+id/nav_graph"
-                app:popUpToInclusive="true" />
+            android:id="@+id/action_configFetcher_to_order"
+            app:destination="@+id/nav_order"
+            app:launchSingleTop="true"
+            app:popUpTo="@+id/nav_graph"
+            app:popUpToInclusive="true" />
     </fragment>
 
     <fragment
-            android:id="@+id/paymentSuccess"
-            android:name="net.taler.merchantpos.payment.PaymentSuccessFragment"
-            android:label="@string/payment_received"
-            tools:layout="@layout/fragment_payment_success" />
+        android:id="@+id/paymentSuccess"
+        android:name="net.taler.merchantpos.payment.PaymentSuccessFragment"
+        android:label="@string/payment_received"
+        tools:layout="@layout/fragment_payment_success" />
 
     <action
-            android:id="@+id/action_global_order"
-            app:destination="@+id/nav_order"
-            app:launchSingleTop="true"
-            app:popUpTo="@+id/nav_graph" />
+        android:id="@+id/action_global_order"
+        app:destination="@+id/nav_order"
+        app:launchSingleTop="true"
+        app:popUpTo="@+id/nav_graph" />
     <action
-            android:id="@+id/action_global_merchantHistory"
-            app:destination="@+id/nav_history"
-            app:launchSingleTop="true" />
+        android:id="@+id/action_global_merchantHistory"
+        app:destination="@+id/nav_history"
+        app:launchSingleTop="true" />
     <action
-            android:id="@+id/action_global_merchantSettings"
-            app:destination="@+id/nav_settings"
-            app:launchSingleTop="true" />
+        android:id="@+id/action_global_merchantSettings"
+        app:destination="@+id/nav_settings"
+        app:launchSingleTop="true" />
     <action
-            android:id="@+id/action_global_configFetcher"
-            app:destination="@+id/configFetcher"
-            app:launchSingleTop="true" />
+        android:id="@+id/action_global_configFetcher"
+        app:destination="@+id/configFetcher"
+        app:launchSingleTop="true" />
 
 </navigation>
diff --git a/merchant-terminal/src/main/res/values/strings.xml 
b/merchant-terminal/src/main/res/values/strings.xml
index 756ef38..712a2fc 100644
--- a/merchant-terminal/src/main/res/values/strings.xml
+++ b/merchant-terminal/src/main/res/values/strings.xml
@@ -36,8 +36,8 @@
     <string name="config_fetching_label">Fetching Configuration</string>
     <string name="config_docs">Please refer to <a 
href="https://docs.taler.net/taler-merchant-pos-terminal.html#apis-and-data-formats";>the
 documentation</a> for the configuration format.</string>
 
-    <string name="payment_intro_nfc">Please scan QR Code or use NFC to 
pay</string>
-    <string name="payment_intro">Please scan QR Code to pay</string>
+    <string name="payment_intro_nfc">Please let customer scan QR Code or use 
NFC to pay</string>
+    <string name="payment_intro">Please let customer scan QR Code to 
pay</string>
     <string name="payment_cancel">Cancel Payment</string>
     <string name="payment_received">Payment received</string>
     <string name="payment_back_button">Continue</string>
@@ -51,14 +51,16 @@
     <string name="refund_amount">Amount</string>
     <string name="refund_reason">Refund reason</string>
     <string name="refund_abort">Abort</string>
+    <string name="refund_complete">Received</string>
     <string name="refund_confirm">Give Refund</string>
     <string name="refund_error_max_amount">Greater than order amount of 
%s</string>
     <string name="refund_error_invalid_amount">Invalid amount</string>
     <string name="refund_error_zero">Needs to be positive amount</string>
     <string name="refund_error_backend">Error processing refund</string>
     <string name="refund_error_deadline">Refund deadline has passed</string>
-    <string name="refund_intro_nfc">Please scan QR Code or use NFC to give 
refund</string>
-    <string name="refund_intro">Please scan QR Code to give refund</string>
+    <string name="refund_error_already_refunded">Already refunded</string>
+    <string name="refund_intro_nfc">Please let customer scan QR Code or use 
NFC to give refund</string>
+    <string name="refund_intro">Please let customer scan QR Code to give 
refund</string>
     <string name="refund_order_ref">Order Reference: %1$s\n\n%2$s</string>
 
     <string name="error_network">Network Error</string>
diff --git a/taler-kotlin-common/src/main/java/net/taler/common/TalerUtils.kt 
b/taler-kotlin-common/src/main/java/net/taler/common/TalerUtils.kt
index cb1622e..444caa4 100644
--- a/taler-kotlin-common/src/main/java/net/taler/common/TalerUtils.kt
+++ b/taler-kotlin-common/src/main/java/net/taler/common/TalerUtils.kt
@@ -49,3 +49,10 @@ object TalerUtils {
     }
 
 }
+
+/**
+ * Returns the current time in milliseconds epoch rounded to nearest seconds.
+ */
+fun now(): Long {
+    return ((System.currentTimeMillis() + 500) / 1000) * 1000
+}

-- 
To stop receiving notification emails like this one, please contact
address@hidden.



reply via email to

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