summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTorsten Grote <t@grobox.de>2020-03-16 15:53:58 -0300
committerTorsten Grote <t@grobox.de>2020-03-17 11:32:46 -0300
commit7eebd0754d16eccadeb3d1cb53c1cebffda65d07 (patch)
treec402456c20863daddec352421d8ad38f10a7709f
parentbca790d381880b3077df0e37cf29b7a2f9e2266c (diff)
downloadmerchant-terminal-android-master.tar.gz
merchant-terminal-android-master.tar.bz2
merchant-terminal-android-master.zip
Add refund button to history items and allow to refund ordersHEADmaster
(still hidden in UI because API incomplete/broken)
-rw-r--r--app/src/main/java/net/taler/merchantpos/MainViewModel.kt2
-rw-r--r--app/src/main/java/net/taler/merchantpos/Utils.kt4
-rw-r--r--app/src/main/java/net/taler/merchantpos/history/HistoryManager.kt6
-rw-r--r--app/src/main/java/net/taler/merchantpos/history/MerchantHistoryFragment.kt26
-rw-r--r--app/src/main/java/net/taler/merchantpos/history/RefundFragment.kt99
-rw-r--r--app/src/main/java/net/taler/merchantpos/history/RefundManager.kt111
-rw-r--r--app/src/main/java/net/taler/merchantpos/history/RefundUriFragment.kt65
-rw-r--r--app/src/main/res/drawable/ic_cash_refund.xml9
-rw-r--r--app/src/main/res/layout/fragment_refund.xml122
-rw-r--r--app/src/main/res/layout/fragment_refund_uri.xml93
-rw-r--r--app/src/main/res/layout/list_item_history.xml21
-rw-r--r--app/src/main/res/navigation/nav_graph.xml25
-rw-r--r--app/src/main/res/values/strings.xml12
13 files changed, 580 insertions, 15 deletions
diff --git a/app/src/main/java/net/taler/merchantpos/MainViewModel.kt b/app/src/main/java/net/taler/merchantpos/MainViewModel.kt
index 560ca59..3fe472d 100644
--- a/app/src/main/java/net/taler/merchantpos/MainViewModel.kt
+++ b/app/src/main/java/net/taler/merchantpos/MainViewModel.kt
@@ -25,6 +25,7 @@ import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.kotlin.KotlinModule
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
@@ -41,6 +42,7 @@ class MainViewModel(app: Application) : AndroidViewModel(app) {
}
val paymentManager = PaymentManager(configManager, queue, mapper)
val historyManager = HistoryManager(configManager, queue, mapper)
+ val refundManager = RefundManager(configManager, queue)
override fun onCleared() {
queue.cancelAll { !it.isCanceled }
diff --git a/app/src/main/java/net/taler/merchantpos/Utils.kt b/app/src/main/java/net/taler/merchantpos/Utils.kt
index 507d142..a0c30d6 100644
--- a/app/src/main/java/net/taler/merchantpos/Utils.kt
+++ b/app/src/main/java/net/taler/merchantpos/Utils.kt
@@ -30,11 +30,13 @@ import android.view.View
import android.view.View.INVISIBLE
import android.view.View.VISIBLE
import androidx.annotation.StringRes
+import androidx.fragment.app.Fragment
import androidx.lifecycle.LiveData
import androidx.lifecycle.MediatorLiveData
import androidx.lifecycle.Observer
import androidx.navigation.NavController
import androidx.navigation.NavDirections
+import androidx.navigation.fragment.findNavController
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
@@ -107,6 +109,8 @@ fun topSnackbar(view: View, @StringRes resId: Int, @Duration duration: Int) {
fun NavDirections.navigate(nav: NavController) = nav.navigate(this)
+fun Fragment.navigate(directions: NavDirections) = findNavController().navigate(directions)
+
fun Long.toRelativeTime(context: Context): CharSequence {
val now = System.currentTimeMillis()
return if (now - this > DAY_IN_MILLIS * 2) {
diff --git a/app/src/main/java/net/taler/merchantpos/history/HistoryManager.kt b/app/src/main/java/net/taler/merchantpos/history/HistoryManager.kt
index 1459876..594e7cc 100644
--- a/app/src/main/java/net/taler/merchantpos/history/HistoryManager.kt
+++ b/app/src/main/java/net/taler/merchantpos/history/HistoryManager.kt
@@ -16,10 +16,12 @@
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.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
@@ -79,7 +81,7 @@ class HistoryManager(
val params = mapOf("instance" to merchantConfig.instance)
val req = MerchantRequest(GET, merchantConfig, "history", params, null,
Listener { onHistoryResponse(it) },
- ErrorListener { onNetworkError() })
+ ErrorListener { onHistoryError() })
queue.add(req)
}
@@ -96,7 +98,7 @@ class HistoryManager(
}
@UiThread
- private fun onNetworkError() {
+ private fun onHistoryError() {
mIsLoading.value = false
mItems.value = HistoryResult.Error
}
diff --git a/app/src/main/java/net/taler/merchantpos/history/MerchantHistoryFragment.kt b/app/src/main/java/net/taler/merchantpos/history/MerchantHistoryFragment.kt
index 5299f28..0c53f71 100644
--- a/app/src/main/java/net/taler/merchantpos/history/MerchantHistoryFragment.kt
+++ b/app/src/main/java/net/taler/merchantpos/history/MerchantHistoryFragment.kt
@@ -22,11 +22,11 @@ import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
+import android.widget.ImageButton
import android.widget.TextView
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.Observer
-import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.DividerItemDecoration.VERTICAL
import androidx.recyclerview.widget.LinearLayoutManager
@@ -40,14 +40,19 @@ import net.taler.merchantpos.R
import net.taler.merchantpos.exhaustive
import net.taler.merchantpos.history.HistoryItemAdapter.HistoryItemViewHolder
import net.taler.merchantpos.history.MerchantHistoryFragmentDirections.Companion.actionGlobalMerchantSettings
+import net.taler.merchantpos.history.MerchantHistoryFragmentDirections.Companion.actionNavHistoryToRefundFragment
import net.taler.merchantpos.navigate
import net.taler.merchantpos.toRelativeTime
import java.util.*
+private interface RefundClickListener {
+ fun onRefundClicked(item: HistoryItem)
+}
+
/**
* Fragment to display the merchant's payment history, received from the backend.
*/
-class MerchantHistoryFragment : Fragment() {
+class MerchantHistoryFragment : Fragment(), RefundClickListener {
companion object {
const val TAG = "taler-merchant"
@@ -55,8 +60,9 @@ class MerchantHistoryFragment : Fragment() {
private val model: MainViewModel by activityViewModels()
private val historyManager by lazy { model.historyManager }
+ private val refundManager by lazy { model.refundManager }
- private val historyListAdapter = HistoryItemAdapter()
+ private val historyListAdapter = HistoryItemAdapter(this)
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
@@ -91,7 +97,7 @@ class MerchantHistoryFragment : Fragment() {
override fun onStart() {
super.onStart()
if (model.configManager.merchantConfig?.instance == null) {
- actionGlobalMerchantSettings().navigate(findNavController())
+ navigate(actionGlobalMerchantSettings())
} else {
historyManager.fetchHistory()
}
@@ -101,9 +107,15 @@ class MerchantHistoryFragment : Fragment() {
Snackbar.make(view!!, R.string.error_network, LENGTH_SHORT).show()
}
+ override fun onRefundClicked(item: HistoryItem) {
+ refundManager.startRefund(item)
+ navigate(actionNavHistoryToRefundFragment())
+ }
+
}
-internal class HistoryItemAdapter : Adapter<HistoryItemViewHolder>() {
+private class HistoryItemAdapter(private val listener: RefundClickListener) :
+ Adapter<HistoryItemViewHolder>() {
private val items = ArrayList<HistoryItem>()
@@ -125,12 +137,13 @@ internal class HistoryItemAdapter : Adapter<HistoryItemViewHolder>() {
this.notifyDataSetChanged()
}
- internal class HistoryItemViewHolder(private val v: View) : ViewHolder(v) {
+ private inner class HistoryItemViewHolder(private val v: View) : ViewHolder(v) {
private val orderSummaryView: TextView = v.findViewById(R.id.orderSummaryView)
private val orderAmountView: TextView = v.findViewById(R.id.orderAmountView)
private val orderTimeView: TextView = v.findViewById(R.id.orderTimeView)
private val orderIdView: TextView = v.findViewById(R.id.orderIdView)
+ private val refundButton: ImageButton = v.findViewById(R.id.refundButton)
fun bind(item: HistoryItem) {
orderSummaryView.text = item.summary
@@ -139,6 +152,7 @@ internal class HistoryItemAdapter : Adapter<HistoryItemViewHolder>() {
orderAmountView.text = "${amount.amount} ${amount.currency}"
orderIdView.text = v.context.getString(R.string.history_ref_no, item.orderId)
orderTimeView.text = item.time.toRelativeTime(v.context)
+ refundButton.setOnClickListener { listener.onRefundClicked(item) }
}
}
diff --git a/app/src/main/java/net/taler/merchantpos/history/RefundFragment.kt b/app/src/main/java/net/taler/merchantpos/history/RefundFragment.kt
new file mode 100644
index 0000000..1797cea
--- /dev/null
+++ b/app/src/main/java/net/taler/merchantpos/history/RefundFragment.kt
@@ -0,0 +1,99 @@
+/*
+ * 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.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
+import androidx.navigation.fragment.findNavController
+import com.google.android.material.snackbar.BaseTransientBottomBar.LENGTH_LONG
+import com.google.android.material.snackbar.Snackbar
+import kotlinx.android.synthetic.main.fragment_refund.*
+import net.taler.merchantpos.MainViewModel
+import net.taler.merchantpos.R
+import net.taler.merchantpos.fadeIn
+import net.taler.merchantpos.fadeOut
+import net.taler.merchantpos.history.RefundFragmentDirections.Companion.actionRefundFragmentToRefundUriFragment
+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.navigate
+
+class RefundFragment : Fragment() {
+
+ private val model: MainViewModel by activityViewModels()
+ private val refundManager by lazy { model.refundManager }
+
+ override fun onCreateView(
+ inflater: LayoutInflater, container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+ return inflater.inflate(R.layout.fragment_refund, container, false)
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ val item = refundManager.toBeRefunded ?: throw IllegalStateException()
+ amountInputView.setText(item.amount.amount)
+ currencyView.text = item.amount.currency
+ abortButton.setOnClickListener { findNavController().navigateUp() }
+ refundButton.setOnClickListener { onRefundButtonClicked(item) }
+
+ refundManager.refundResult.observe(viewLifecycleOwner, Observer { result ->
+ onRefundResultChanged(result)
+ })
+ }
+
+ private fun onRefundButtonClicked(item: HistoryItem) {
+ val inputAmount = amountInputView.text.toString().toDouble()
+ if (inputAmount > item.amount.amount.toDouble()) {
+ amountView.error = getString(R.string.refund_error_max_amount, item.amount.amount)
+ return
+ }
+ if (inputAmount <= 0.0) {
+ amountView.error = getString(R.string.refund_error_zero)
+ return
+ }
+ amountView.error = null
+ refundButton.fadeOut()
+ progressBar.fadeIn()
+ refundManager.refund(item, inputAmount, reasonInputView.text.toString())
+ }
+
+ private fun onRefundResultChanged(result: RefundResult?): Any = when (result) {
+ Error -> onError(R.string.refund_error_backend)
+ PastDeadline -> onError(R.string.refund_error_deadline)
+ is Success -> {
+ progressBar.fadeOut()
+ refundButton.fadeIn()
+ navigate(actionRefundFragmentToRefundUriFragment())
+ }
+ null -> { // no-op
+ }
+ }
+
+ private fun onError(@StringRes res: Int) {
+ Snackbar.make(view!!, res, LENGTH_LONG).show()
+ progressBar.fadeOut()
+ refundButton.fadeIn()
+ }
+
+}
diff --git a/app/src/main/java/net/taler/merchantpos/history/RefundManager.kt b/app/src/main/java/net/taler/merchantpos/history/RefundManager.kt
new file mode 100644
index 0000000..270b3b8
--- /dev/null
+++ b/app/src/main/java/net/taler/merchantpos/history/RefundManager.kt
@@ -0,0 +1,111 @@
+/*
+ * 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.ErrorListener
+import com.android.volley.Response.Listener
+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()
+ class Success(
+ val refundUri: String,
+ val item: HistoryItem,
+ val amount: Double,
+ val reason: String
+ ) : RefundResult()
+}
+
+class RefundManager(
+ private val configManager: ConfigManager,
+ private val queue: RequestQueue
+) {
+
+ var toBeRefunded: HistoryItem? = null
+ private set
+
+ private val mRefundResult = MutableLiveData<RefundResult>()
+ internal val refundResult: LiveData<RefundResult> = mRefundResult
+
+ @UiThread
+ internal fun startRefund(item: HistoryItem) {
+ toBeRefunded = item
+ mRefundResult.value = null
+ }
+
+ @UiThread
+ internal fun refund(item: HistoryItem, amount: Double, reason: String) {
+ val merchantConfig = configManager.merchantConfig!!
+ val refundRequest = mapOf(
+ "order_id" to item.orderId,
+ "refund" to "${item.amount.currency}:$amount",
+ "reason" to reason
+ )
+ val body = JSONObject(refundRequest)
+ val req = MerchantRequest(POST, merchantConfig, "refund", null, body,
+ Listener { onRefundResponse(it, item, amount, reason) },
+ ErrorListener { onRefundError() }
+ )
+ queue.add(req)
+ }
+
+ @UiThread
+ private fun onRefundResponse(
+ json: JSONObject,
+ item: HistoryItem,
+ amount: Double,
+ reason: String
+ ) {
+ if (!json.has("contract_terms")) {
+ Log.e("TEST", "json: $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() {
+ mRefundResult.value = RefundResult.Error
+ }
+
+}
diff --git a/app/src/main/java/net/taler/merchantpos/history/RefundUriFragment.kt b/app/src/main/java/net/taler/merchantpos/history/RefundUriFragment.kt
new file mode 100644
index 0000000..f2bd569
--- /dev/null
+++ b/app/src/main/java/net/taler/merchantpos/history/RefundUriFragment.kt
@@ -0,0 +1,65 @@
+/*
+ * 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.annotation.SuppressLint
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.activityViewModels
+import androidx.navigation.fragment.findNavController
+import kotlinx.android.synthetic.main.fragment_refund_uri.*
+import net.taler.merchantpos.MainViewModel
+import net.taler.merchantpos.NfcManager.Companion.hasNfc
+import net.taler.merchantpos.QrCodeManager.makeQrCode
+import net.taler.merchantpos.R
+
+class RefundUriFragment : Fragment() {
+
+ private val model: MainViewModel by activityViewModels()
+ private val refundManager by lazy { model.refundManager }
+
+ override fun onCreateView(
+ inflater: LayoutInflater, container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+ return inflater.inflate(R.layout.fragment_refund_uri, container, false)
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ val result = refundManager.refundResult.value
+ if (result !is RefundResult.Success) throw IllegalStateException()
+
+ refundQrcodeView.setImageBitmap(makeQrCode(result.refundUri))
+
+ val introRes =
+ if (hasNfc(requireContext())) R.string.refund_intro_nfc else R.string.refund_intro
+ refundIntroView.setText(introRes)
+
+ @SuppressLint("SetTextI18n")
+ refundAmountView.text = "${result.amount} ${result.item.amount.currency}"
+
+ refundRefView.text =
+ getString(R.string.refund_order_ref, result.item.orderId, result.reason)
+
+ cancelRefundButton.setOnClickListener { findNavController().navigateUp() }
+ }
+
+}
diff --git a/app/src/main/res/drawable/ic_cash_refund.xml b/app/src/main/res/drawable/ic_cash_refund.xml
new file mode 100644
index 0000000..7359ca3
--- /dev/null
+++ b/app/src/main/res/drawable/ic_cash_refund.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:fillColor="#000000"
+ android:pathData="M3,11H21V23H3V11M12,15A2,2 0 0,1 14,17A2,2 0 0,1 12,19A2,2 0 0,1 10,17A2,2 0 0,1 12,15M7,13A2,2 0 0,1 5,15V19A2,2 0 0,1 7,21H17A2,2 0 0,1 19,19V15A2,2 0 0,1 17,13H7M17,5V10H15.5V6.5H9.88L12.3,8.93L11.24,10L7,5.75L11.24,1.5L12.3,2.57L9.88,5H17Z" />
+</vector>
diff --git a/app/src/main/res/layout/fragment_refund.xml b/app/src/main/res/layout/fragment_refund.xml
new file mode 100644
index 0000000..5a78cdd
--- /dev/null
+++ b/app/src/main/res/layout/fragment_refund.xml
@@ -0,0 +1,122 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ 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/>
+ -->
+
+<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=".history.RefundFragment">
+
+ <com.google.android.material.textfield.TextInputLayout
+ android:id="@+id/amountView"
+ style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.Dense"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_margin="16dp"
+ android:hint="@string/refund_amount"
+ app:boxBackgroundMode="outline"
+ app:endIconMode="clear_text"
+ app:endIconTint="?attr/colorControlNormal"
+ app:layout_constraintBottom_toTopOf="@+id/reasonView"
+ app:layout_constraintEnd_toStartOf="@+id/currencyView"
+ app:layout_constraintHorizontal_chainStyle="packed"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintVertical_chainStyle="spread">
+
+ <com.google.android.material.textfield.TextInputEditText
+ android:id="@+id/amountInputView"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:ems="6"
+ android:inputType="numberDecimal"
+ tools:text="23.42" />
+
+ </com.google.android.material.textfield.TextInputLayout>
+
+ <TextView
+ android:id="@+id/currencyView"
+ android:layout_width="wrap_content"
+ android:layout_height="0dp"
+ android:layout_marginStart="8dp"
+ android:gravity="start|center_vertical"
+ app:layout_constraintBottom_toBottomOf="@+id/amountView"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toEndOf="@+id/amountView"
+ app:layout_constraintTop_toTopOf="@+id/amountView"
+ tools:text="TESTKUDOS" />
+
+ <com.google.android.material.textfield.TextInputLayout
+ android:id="@+id/reasonView"
+ style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.Dense"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_margin="16dp"
+ android:hint="@string/refund_reason"
+ app:endIconMode="clear_text"
+ app:layout_constraintBottom_toTopOf="@+id/abortButton"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintHorizontal_bias="0.5"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@+id/amountView">
+
+ <com.google.android.material.textfield.TextInputEditText
+ android:id="@+id/reasonInputView"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:inputType="textAutoComplete|textAutoCorrect|textMultiLine" />
+
+ </com.google.android.material.textfield.TextInputLayout>
+
+ <Button
+ android:id="@+id/abortButton"
+ 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_toStartOf="@+id/refundButton"
+ app:layout_constraintHorizontal_bias="0.76"
+ app:layout_constraintHorizontal_chainStyle="spread_inside"
+ app:layout_constraintStart_toStartOf="parent" />
+
+ <Button
+ android:id="@+id/refundButton"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_margin="16dp"
+ android:backgroundTint="@color/green"
+ android:text="@string/refund_confirm"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintHorizontal_bias="0.5"
+ app:layout_constraintStart_toEndOf="@+id/abortButton" />
+
+ <ProgressBar
+ android:id="@+id/progressBar"
+ style="?android:attr/progressBarStyle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:visibility="invisible"
+ app:layout_constraintBottom_toBottomOf="@+id/refundButton"
+ app:layout_constraintEnd_toEndOf="@+id/refundButton"
+ app:layout_constraintStart_toStartOf="@+id/refundButton"
+ app:layout_constraintTop_toTopOf="@+id/refundButton"
+ tools:visibility="visible" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/app/src/main/res/layout/fragment_refund_uri.xml b/app/src/main/res/layout/fragment_refund_uri.xml
new file mode 100644
index 0000000..8447d28
--- /dev/null
+++ b/app/src/main/res/layout/fragment_refund_uri.xml
@@ -0,0 +1,93 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ 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/>
+ -->
+
+<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">
+
+ <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" />
+
+ <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" />
+
+ <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" />
+
+ <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" />
+
+ <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" />
+
+ <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" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/app/src/main/res/layout/list_item_history.xml b/app/src/main/res/layout/list_item_history.xml
index 06ffff2..fe485ba 100644
--- a/app/src/main/res/layout/list_item_history.xml
+++ b/app/src/main/res/layout/list_item_history.xml
@@ -40,12 +40,12 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
+ android:layout_marginEnd="16dp"
android:textColor="?android:attr/textColorPrimary"
android:textSize="20sp"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="@+id/orderSummaryView"
- app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintHorizontal_bias="0.0"
+ app:layout_constraintEnd_toStartOf="@+id/refundButton"
app:layout_constraintStart_toEndOf="@+id/orderSummaryView"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.0"
@@ -72,13 +72,26 @@
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
+ android:layout_marginEnd="16dp"
android:textSize="20sp"
app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintHorizontal_bias="0.5"
+ app:layout_constraintEnd_toStartOf="@+id/refundButton"
app:layout_constraintStart_toEndOf="@+id/orderIdView"
app:layout_constraintTop_toBottomOf="@+id/orderAmountView"
app:layout_constraintVertical_bias="1.0"
tools:text="3 hrs. ago" />
+ <ImageButton
+ android:id="@+id/refundButton"
+ android:layout_width="48dp"
+ android:layout_height="48dp"
+ 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"
+ app:srcCompat="@drawable/ic_cash_refund" />
+
</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/app/src/main/res/navigation/nav_graph.xml b/app/src/main/res/navigation/nav_graph.xml
index 0d7d870..2e337f2 100644
--- a/app/src/main/res/navigation/nav_graph.xml
+++ b/app/src/main/res/navigation/nav_graph.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.
~
@@ -57,7 +56,27 @@
android:id="@+id/nav_history"
android:name="net.taler.merchantpos.history.MerchantHistoryFragment"
android:label="@string/history_label"
- tools:layout="@layout/fragment_merchant_history" />
+ tools:layout="@layout/fragment_merchant_history">
+ <action
+ 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">
+ <action
+ android:id="@+id/action_refundFragment_to_refundUriFragment"
+ app:destination="@id/refundUriFragment" />
+ </fragment>
+
+ <fragment
+ 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"
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index ac84c5d..77c7e03 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -48,6 +48,18 @@
<string name="history_label">Payment History</string>
<string name="history_received_at">Received at</string>
<string name="history_ref_no">Ref. No: %s</string>
+ <string name="history_refund">Refund Order</string>
+ <string name="refund_amount">Amount</string>
+ <string name="refund_reason">Refund reason</string>
+ <string name="refund_abort">Abort</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_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_order_ref">Order Reference: %1$s\n\n%2$s</string>
<string name="error_network">Network Error</string>