summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTorsten Grote <t@grobox.de>2020-08-03 13:31:26 -0300
committerTorsten Grote <t@grobox.de>2020-08-03 13:31:26 -0300
commit5b1163311192e9adf15ef3d626c72812e638f90c (patch)
tree5d3a7c7c9aa2153507f8328c81e4d0f7d8cf78c9
parent89f8c6a6d590ab3a702f80fbe50c2e9dd67d4bf8 (diff)
downloadtaler-android-5b1163311192e9adf15ef3d626c72812e638f90c.tar.gz
taler-android-5b1163311192e9adf15ef3d626c72812e638f90c.tar.bz2
taler-android-5b1163311192e9adf15ef3d626c72812e638f90c.zip
[pos] improve payment processing
- cancel orders that have been abandoned and will not be paid - show unpaid orders in history (in case one makes it through) - set deadlines when creating orders in case it helps with enabling refunds
-rw-r--r--merchant-lib/src/main/java/net/taler/merchantlib/MerchantApi.kt5
-rw-r--r--merchant-lib/src/main/java/net/taler/merchantlib/OrderHistory.kt3
-rw-r--r--merchant-lib/src/main/java/net/taler/merchantlib/PostOrderRequest.kt6
-rw-r--r--merchant-terminal/src/main/java/net/taler/merchantpos/history/HistoryItemAdapter.kt88
-rw-r--r--merchant-terminal/src/main/java/net/taler/merchantpos/history/HistoryManager.kt6
-rw-r--r--merchant-terminal/src/main/java/net/taler/merchantpos/history/MerchantHistoryFragment.kt60
-rw-r--r--merchant-terminal/src/main/java/net/taler/merchantpos/order/Order.kt7
-rw-r--r--merchant-terminal/src/main/java/net/taler/merchantpos/payment/PaymentManager.kt9
-rw-r--r--merchant-terminal/src/main/java/net/taler/merchantpos/payment/ProcessPaymentFragment.kt5
-rw-r--r--merchant-terminal/src/main/res/values/strings.xml1
-rw-r--r--taler-kotlin-android/src/main/java/net/taler/common/ContractTerms.kt8
11 files changed, 128 insertions, 70 deletions
diff --git a/merchant-lib/src/main/java/net/taler/merchantlib/MerchantApi.kt b/merchant-lib/src/main/java/net/taler/merchantlib/MerchantApi.kt
index db375863..96892f5f 100644
--- a/merchant-lib/src/main/java/net/taler/merchantlib/MerchantApi.kt
+++ b/merchant-lib/src/main/java/net/taler/merchantlib/MerchantApi.kt
@@ -29,7 +29,6 @@ import io.ktor.http.HttpHeaders.Authorization
import io.ktor.http.contentType
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonConfiguration
-import net.taler.common.ContractTerms
import net.taler.merchantlib.Response.Companion.response
class MerchantApi(private val httpClient: HttpClient) {
@@ -40,12 +39,12 @@ class MerchantApi(private val httpClient: HttpClient) {
suspend fun postOrder(
merchantConfig: MerchantConfig,
- contractTerms: ContractTerms
+ orderRequest: PostOrderRequest
): Response<PostOrderResponse> = response {
httpClient.post(merchantConfig.urlFor("private/orders")) {
header(Authorization, "ApiKey ${merchantConfig.apiKey}")
contentType(Json)
- body = PostOrderRequest(contractTerms)
+ body = orderRequest
} as PostOrderResponse
}
diff --git a/merchant-lib/src/main/java/net/taler/merchantlib/OrderHistory.kt b/merchant-lib/src/main/java/net/taler/merchantlib/OrderHistory.kt
index 718bde53..b1ff5b13 100644
--- a/merchant-lib/src/main/java/net/taler/merchantlib/OrderHistory.kt
+++ b/merchant-lib/src/main/java/net/taler/merchantlib/OrderHistory.kt
@@ -41,6 +41,9 @@ data class OrderHistoryEntry(
// the summary of the order
val summary: String,
+ // if the order has been paid
+ val paid: Boolean,
+
// whether some part of the order is refundable
val refundable: Boolean
)
diff --git a/merchant-lib/src/main/java/net/taler/merchantlib/PostOrderRequest.kt b/merchant-lib/src/main/java/net/taler/merchantlib/PostOrderRequest.kt
index 4854a80e..783dd19d 100644
--- a/merchant-lib/src/main/java/net/taler/merchantlib/PostOrderRequest.kt
+++ b/merchant-lib/src/main/java/net/taler/merchantlib/PostOrderRequest.kt
@@ -25,12 +25,14 @@ import kotlinx.serialization.Serializer
import kotlinx.serialization.json.JsonInput
import kotlinx.serialization.json.JsonObject
import net.taler.common.ContractTerms
+import net.taler.common.Duration
@Serializable
data class PostOrderRequest(
@SerialName("order")
- val contractTerms: ContractTerms
-
+ val contractTerms: ContractTerms,
+ @SerialName("refund_delay")
+ val refundDelay: Duration? = null
)
@Serializable
diff --git a/merchant-terminal/src/main/java/net/taler/merchantpos/history/HistoryItemAdapter.kt b/merchant-terminal/src/main/java/net/taler/merchantpos/history/HistoryItemAdapter.kt
new file mode 100644
index 00000000..25e94fb7
--- /dev/null
+++ b/merchant-terminal/src/main/java/net/taler/merchantpos/history/HistoryItemAdapter.kt
@@ -0,0 +1,88 @@
+/*
+ * 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.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.ImageButton
+import android.widget.TextView
+import androidx.recyclerview.widget.RecyclerView
+import androidx.recyclerview.widget.RecyclerView.Adapter
+import net.taler.common.toRelativeTime
+import net.taler.merchantlib.OrderHistoryEntry
+import net.taler.merchantpos.R
+import net.taler.merchantpos.history.HistoryItemAdapter.HistoryItemViewHolder
+import java.util.ArrayList
+
+
+internal class HistoryItemAdapter(private val listener: RefundClickListener) :
+ Adapter<HistoryItemViewHolder>() {
+
+ private val items = ArrayList<OrderHistoryEntry>()
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HistoryItemViewHolder {
+ val v =
+ LayoutInflater.from(parent.context).inflate(R.layout.list_item_history, parent, false)
+ return HistoryItemViewHolder(v)
+ }
+
+ override fun getItemCount() = items.size
+
+ override fun onBindViewHolder(holder: HistoryItemViewHolder, position: Int) {
+ holder.bind(items[position])
+ }
+
+ fun setData(items: List<OrderHistoryEntry>) {
+ this.items.clear()
+ this.items.addAll(items)
+ this.notifyDataSetChanged()
+ }
+
+ internal inner class HistoryItemViewHolder(private val v: View) : RecyclerView.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)
+
+ private val orderIdColor = orderIdView.currentTextColor
+
+ fun bind(item: OrderHistoryEntry) {
+ orderSummaryView.text = item.summary
+ val amount = item.amount
+ orderAmountView.text = amount.toString()
+ orderTimeView.text = item.timestamp.ms.toRelativeTime(v.context)
+ if (item.paid) {
+ orderIdView.text = v.context.getString(R.string.history_ref_no, item.orderId)
+ orderIdView.setTextColor(orderIdColor)
+ } else {
+ orderIdView.text = v.context.getString(R.string.history_unpaid)
+ orderIdView.setTextColor(v.context.getColor(R.color.red))
+ }
+ if (item.refundable) {
+ refundButton.visibility = View.VISIBLE
+ refundButton.setOnClickListener { listener.onRefundClicked(item) }
+ } else {
+ refundButton.visibility = View.GONE
+ }
+ }
+
+ }
+
+}
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 cb770960..aabe4ccd 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
@@ -55,9 +55,9 @@ class HistoryManager(
}
}
- private fun onHistoryError(msg: String) = scope.launch(Dispatchers.Main) {
- mIsLoading.value = false
- mItems.value = HistoryResult.Error(msg)
+ private fun onHistoryError(msg: String) {
+ mIsLoading.postValue(false)
+ mItems.postValue(HistoryResult.Error(msg))
}
}
diff --git a/merchant-terminal/src/main/java/net/taler/merchantpos/history/MerchantHistoryFragment.kt b/merchant-terminal/src/main/java/net/taler/merchantpos/history/MerchantHistoryFragment.kt
index 25805dca..596b8b02 100644
--- a/merchant-terminal/src/main/java/net/taler/merchantpos/history/MerchantHistoryFragment.kt
+++ b/merchant-terminal/src/main/java/net/taler/merchantpos/history/MerchantHistoryFragment.kt
@@ -20,34 +20,25 @@ import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
-import android.view.View.GONE
-import android.view.View.VISIBLE
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.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.DividerItemDecoration.VERTICAL
import androidx.recyclerview.widget.LinearLayoutManager
-import androidx.recyclerview.widget.RecyclerView.Adapter
-import androidx.recyclerview.widget.RecyclerView.ViewHolder
import com.google.android.material.snackbar.BaseTransientBottomBar.LENGTH_LONG
import com.google.android.material.snackbar.Snackbar
import kotlinx.android.synthetic.main.fragment_merchant_history.*
import net.taler.common.exhaustive
import net.taler.common.navigate
-import net.taler.common.toRelativeTime
import net.taler.merchantlib.OrderHistoryEntry
import net.taler.merchantpos.MainViewModel
import net.taler.merchantpos.R
-import net.taler.merchantpos.history.HistoryItemAdapter.HistoryItemViewHolder
import net.taler.merchantpos.history.MerchantHistoryFragmentDirections.Companion.actionGlobalMerchantSettings
import net.taler.merchantpos.history.MerchantHistoryFragmentDirections.Companion.actionNavHistoryToRefundFragment
-import java.util.ArrayList
-private interface RefundClickListener {
+internal interface RefundClickListener {
fun onRefundClicked(item: OrderHistoryEntry)
}
@@ -115,52 +106,3 @@ class MerchantHistoryFragment : Fragment(), RefundClickListener {
}
}
-
-private class HistoryItemAdapter(private val listener: RefundClickListener) :
- Adapter<HistoryItemViewHolder>() {
-
- private val items = ArrayList<OrderHistoryEntry>()
-
- override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HistoryItemViewHolder {
- val v =
- LayoutInflater.from(parent.context).inflate(R.layout.list_item_history, parent, false)
- return HistoryItemViewHolder(v)
- }
-
- override fun getItemCount() = items.size
-
- override fun onBindViewHolder(holder: HistoryItemViewHolder, position: Int) {
- holder.bind(items[position])
- }
-
- fun setData(items: List<OrderHistoryEntry>) {
- this.items.clear()
- this.items.addAll(items)
- this.notifyDataSetChanged()
- }
-
- 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: OrderHistoryEntry) {
- orderSummaryView.text = item.summary
- val amount = item.amount
- orderAmountView.text = amount.toString()
- orderIdView.text = v.context.getString(R.string.history_ref_no, item.orderId)
- orderTimeView.text = item.timestamp.ms.toRelativeTime(v.context)
- if (item.refundable) {
- refundButton.visibility = VISIBLE
- refundButton.setOnClickListener { listener.onRefundClicked(item) }
- } else {
- refundButton.visibility = GONE
- }
- }
-
- }
-
-}
diff --git a/merchant-terminal/src/main/java/net/taler/merchantpos/order/Order.kt b/merchant-terminal/src/main/java/net/taler/merchantpos/order/Order.kt
index bb753627..4053d4b1 100644
--- a/merchant-terminal/src/main/java/net/taler/merchantpos/order/Order.kt
+++ b/merchant-terminal/src/main/java/net/taler/merchantpos/order/Order.kt
@@ -18,10 +18,12 @@ package net.taler.merchantpos.order
import net.taler.common.Amount
import net.taler.common.ContractTerms
+import net.taler.common.Timestamp
import net.taler.common.now
import net.taler.merchantpos.config.Category
import net.taler.merchantpos.config.ConfigProduct
import java.net.URLEncoder
+import java.util.concurrent.TimeUnit.HOURS
private const val FULFILLMENT_PREFIX = "taler://fulfillment-success/"
@@ -115,12 +117,15 @@ data class Order(val id: Int, val currency: String, val availableCategories: Map
}
fun toContractTerms(): ContractTerms {
+ val deadline = Timestamp(now() + HOURS.toMillis(1))
return ContractTerms(
summary = summary,
summaryI18n = summaryI18n,
amount = total,
fulfillmentUrl = fulfillmentUri,
- products = products.map { it.toContractProduct() }
+ products = products.map { it.toContractProduct() },
+ refundDeadline = deadline,
+ wireTransferDeadline = deadline
)
}
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 fc4f6422..bc1e35fc 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
@@ -25,13 +25,16 @@ import androidx.lifecycle.MutableLiveData
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
+import net.taler.common.Duration
import net.taler.merchantlib.CheckPaymentResponse
import net.taler.merchantlib.MerchantApi
+import net.taler.merchantlib.PostOrderRequest
import net.taler.merchantlib.PostOrderResponse
import net.taler.merchantpos.MainActivity.Companion.TAG
import net.taler.merchantpos.R
import net.taler.merchantpos.config.ConfigManager
import net.taler.merchantpos.order.Order
+import java.util.concurrent.TimeUnit.HOURS
import java.util.concurrent.TimeUnit.MINUTES
import java.util.concurrent.TimeUnit.SECONDS
@@ -65,7 +68,11 @@ class PaymentManager(
val merchantConfig = configManager.merchantConfig!!
mPayment.value = Payment(order, order.summary, configManager.currency!!)
scope.launch(Dispatchers.IO) {
- val response = api.postOrder(merchantConfig, order.toContractTerms())
+ val request = PostOrderRequest(
+ contractTerms = order.toContractTerms(),
+ refundDelay = Duration(HOURS.toMillis(1))
+ )
+ val response = api.postOrder(merchantConfig, request)
response.handle(::onNetworkError, ::onOrderCreated)
}
}
diff --git a/merchant-terminal/src/main/java/net/taler/merchantpos/payment/ProcessPaymentFragment.kt b/merchant-terminal/src/main/java/net/taler/merchantpos/payment/ProcessPaymentFragment.kt
index 5278a039..27ef366d 100644
--- a/merchant-terminal/src/main/java/net/taler/merchantpos/payment/ProcessPaymentFragment.kt
+++ b/merchant-terminal/src/main/java/net/taler/merchantpos/payment/ProcessPaymentFragment.kt
@@ -60,6 +60,11 @@ class ProcessPaymentFragment : Fragment() {
}
}
+ override fun onDestroy() {
+ super.onDestroy()
+ paymentManager.cancelPayment(getString(R.string.error_cancelled))
+ }
+
private fun onPaymentStateChanged(payment: Payment) {
if (payment.error != null) {
topSnackbar(requireView(), payment.error, LENGTH_LONG)
diff --git a/merchant-terminal/src/main/res/values/strings.xml b/merchant-terminal/src/main/res/values/strings.xml
index 4c0ba5ad..f7a62daa 100644
--- a/merchant-terminal/src/main/res/values/strings.xml
+++ b/merchant-terminal/src/main/res/values/strings.xml
@@ -47,6 +47,7 @@
<string name="history_label">Payment history</string>
<string name="history_ref_no" translatable="false">@string/payment_order_id</string>
+ <string name="history_unpaid">Unpaid</string>
<string name="history_refund">Refund</string>
<string name="refund_amount">Amount</string>
<string name="refund_reason">Refund reason</string>
diff --git a/taler-kotlin-android/src/main/java/net/taler/common/ContractTerms.kt b/taler-kotlin-android/src/main/java/net/taler/common/ContractTerms.kt
index 013427f8..ab442f21 100644
--- a/taler-kotlin-android/src/main/java/net/taler/common/ContractTerms.kt
+++ b/taler-kotlin-android/src/main/java/net/taler/common/ContractTerms.kt
@@ -35,7 +35,13 @@ data class ContractTerms(
@SerialName("fulfillment_url")
@get:JsonProperty("fulfillment_url")
val fulfillmentUrl: String,
- val products: List<ContractProduct>
+ val products: List<ContractProduct>,
+ @SerialName("wire_transfer_deadline")
+ @get:JsonProperty("wire_transfer_deadline")
+ val wireTransferDeadline: Timestamp? = null,
+ @SerialName("refund_deadline")
+ @get:JsonProperty("refund_deadline")
+ val refundDeadline: Timestamp? = null
)
@JsonInclude(NON_NULL)