diff options
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,15 +36,11 @@ 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, @@ -62,6 +64,12 @@ class RefundManager( } @UiThread + internal fun abortRefund() { + toBeRefunded = null + mRefundResult.value = null + } + + @UiThread internal fun refund(item: HistoryItem, amount: Amount, reason: String) { val merchantConfig = configManager.merchantConfig!! val refundRequest = mapOf( @@ -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 +} |