diff options
author | Torsten Grote <t@grobox.de> | 2020-08-04 09:46:38 -0300 |
---|---|---|
committer | Torsten Grote <t@grobox.de> | 2020-08-04 09:46:38 -0300 |
commit | 35f7ed512ed7445362d6caee1bf60441f4ce979e (patch) | |
tree | 08fb02c802ff36065dd85fb9dcb95a0adabc03c3 /merchant-terminal/src | |
parent | d3a035c59c508b7b0ef3c06a1b0f1f3c0a077bb8 (diff) | |
download | taler-android-35f7ed512ed7445362d6caee1bf60441f4ce979e.tar.gz taler-android-35f7ed512ed7445362d6caee1bf60441f4ce979e.tar.bz2 taler-android-35f7ed512ed7445362d6caee1bf60441f4ce979e.zip |
[pos] Implement new refund API (untested since there is no wallet support)
Also do a bit of code cleanup and minor refactorings
This also removes the volley HTTP library which is not needed anymore
Diffstat (limited to 'merchant-terminal/src')
18 files changed, 294 insertions, 372 deletions
diff --git a/merchant-terminal/src/main/AndroidManifest.xml b/merchant-terminal/src/main/AndroidManifest.xml index 3d89fee..1518293 100644 --- a/merchant-terminal/src/main/AndroidManifest.xml +++ b/merchant-terminal/src/main/AndroidManifest.xml @@ -19,7 +19,6 @@ package="net.taler.merchantpos"> <uses-permission android:name="android.permission.NFC" /> - <uses-permission android:name="android.permission.INTERNET" /> <uses-feature android:name="android.hardware.nfc" diff --git a/merchant-terminal/src/main/java/net/taler/merchantpos/MainViewModel.kt b/merchant-terminal/src/main/java/net/taler/merchantpos/MainViewModel.kt index 905738b..5f5d534 100644 --- a/merchant-terminal/src/main/java/net/taler/merchantpos/MainViewModel.kt +++ b/merchant-terminal/src/main/java/net/taler/merchantpos/MainViewModel.kt @@ -19,20 +19,18 @@ package net.taler.merchantpos import android.app.Application import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.viewModelScope -import com.android.volley.toolbox.Volley import net.taler.merchantlib.MerchantApi import net.taler.merchantlib.getDefaultHttpClient import net.taler.merchantpos.config.ConfigManager import net.taler.merchantpos.history.HistoryManager -import net.taler.merchantpos.history.RefundManager import net.taler.merchantpos.order.OrderManager import net.taler.merchantpos.payment.PaymentManager +import net.taler.merchantpos.refund.RefundManager class MainViewModel(app: Application) : AndroidViewModel(app) { private val httpClient = getDefaultHttpClient() private val api = MerchantApi(httpClient) - private val queue = Volley.newRequestQueue(app) val orderManager = OrderManager(app) val configManager = ConfigManager(app, viewModelScope, httpClient, api).apply { @@ -40,11 +38,10 @@ class MainViewModel(app: Application) : AndroidViewModel(app) { } val paymentManager = PaymentManager(app, configManager, viewModelScope, api) val historyManager = HistoryManager(configManager, viewModelScope, api) - val refundManager = RefundManager(configManager, queue) + val refundManager = RefundManager(configManager, viewModelScope, api) override fun onCleared() { httpClient.close() - queue.cancelAll { !it.isCanceled } } } diff --git a/merchant-terminal/src/main/java/net/taler/merchantpos/Utils.kt b/merchant-terminal/src/main/java/net/taler/merchantpos/Utils.kt index 9deb042..578debf 100644 --- a/merchant-terminal/src/main/java/net/taler/merchantpos/Utils.kt +++ b/merchant-terminal/src/main/java/net/taler/merchantpos/Utils.kt @@ -16,15 +16,11 @@ package net.taler.merchantpos -import android.util.Log import android.view.View import androidx.annotation.StringRes -import com.android.volley.Response -import com.android.volley.VolleyError import com.google.android.material.snackbar.BaseTransientBottomBar.ANIMATION_MODE_FADE import com.google.android.material.snackbar.BaseTransientBottomBar.Duration import com.google.android.material.snackbar.Snackbar.make -import net.taler.merchantpos.MainActivity.Companion.TAG fun topSnackbar(view: View, text: CharSequence, @Duration duration: Int) { make(view, text, duration) @@ -36,14 +32,3 @@ fun topSnackbar(view: View, text: CharSequence, @Duration duration: Int) { fun topSnackbar(view: View, @StringRes resId: Int, @Duration duration: Int) { topSnackbar(view, view.resources.getText(resId), duration) } - -class LogErrorListener(private val onError: (error: VolleyError) -> Any) : - Response.ErrorListener { - - override fun onErrorResponse(error: VolleyError) { - val body = error.networkResponse.data?.let { String(it) } - Log.e(TAG, "$error $body") - onError.invoke(error) - } - -} diff --git a/merchant-terminal/src/main/java/net/taler/merchantpos/config/MerchantConfigFragment.kt b/merchant-terminal/src/main/java/net/taler/merchantpos/config/ConfigFragment.kt index 77a87fb..daddbff 100644 --- a/merchant-terminal/src/main/java/net/taler/merchantpos/config/MerchantConfigFragment.kt +++ b/merchant-terminal/src/main/java/net/taler/merchantpos/config/ConfigFragment.kt @@ -34,13 +34,13 @@ import kotlinx.android.synthetic.main.fragment_merchant_config.* import net.taler.common.navigate import net.taler.merchantpos.MainViewModel import net.taler.merchantpos.R -import net.taler.merchantpos.config.MerchantConfigFragmentDirections.Companion.actionSettingsToOrder +import net.taler.merchantpos.config.ConfigFragmentDirections.Companion.actionSettingsToOrder import net.taler.merchantpos.topSnackbar /** * Fragment that displays merchant settings. */ -class MerchantConfigFragment : Fragment() { +class ConfigFragment : Fragment() { private val model: MainViewModel by activityViewModels() private val configManager by lazy { model.configManager } diff --git a/merchant-terminal/src/main/java/net/taler/merchantpos/config/MerchantRequest.kt b/merchant-terminal/src/main/java/net/taler/merchantpos/config/MerchantRequest.kt deleted file mode 100644 index 5d41196..0000000 --- a/merchant-terminal/src/main/java/net/taler/merchantpos/config/MerchantRequest.kt +++ /dev/null @@ -1,59 +0,0 @@ -/* - * This file is part of GNU Taler - * (C) 2020 Taler Systems S.A. - * - * GNU Taler is free software; you can redistribute it and/or modify it under the - * terms of the GNU General Public License as published by the Free Software - * Foundation; either version 3, or (at your option) any later version. - * - * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - * A PARTICULAR PURPOSE. See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along with - * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> - */ - -package net.taler.merchantpos.config - -import android.net.Uri -import android.util.ArrayMap -import com.android.volley.Response -import com.android.volley.toolbox.JsonObjectRequest -import net.taler.merchantlib.MerchantConfig -import net.taler.merchantpos.LogErrorListener -import org.json.JSONObject - -class MerchantRequest( - method: Int, - private val merchantConfig: MerchantConfig, - endpoint: String, - params: Map<String, String>?, - jsonRequest: JSONObject?, - listener: Response.Listener<JSONObject>, - errorListener: LogErrorListener -) : - JsonObjectRequest( - method, - merchantConfig.legacyUrl(endpoint, params), - jsonRequest, - listener, - errorListener - ) { - - override fun getHeaders(): MutableMap<String, String> { - val headerMap = ArrayMap<String, String>() - headerMap["Authorization"] = "ApiKey " + merchantConfig.apiKey - return headerMap - } - -} - -private fun MerchantConfig.legacyUrl(endpoint: String, params: Map<String, String>?): String { - val uriBuilder = Uri.parse(baseUrl).buildUpon() - uriBuilder.appendPath(endpoint) - params?.forEach { - uriBuilder.appendQueryParameter(it.key, it.value) - } - return uriBuilder.toString() -} diff --git a/merchant-terminal/src/main/java/net/taler/merchantpos/history/MerchantHistoryFragment.kt b/merchant-terminal/src/main/java/net/taler/merchantpos/history/HistoryFragment.kt index 596b8b0..8cc435a 100644 --- a/merchant-terminal/src/main/java/net/taler/merchantpos/history/MerchantHistoryFragment.kt +++ b/merchant-terminal/src/main/java/net/taler/merchantpos/history/HistoryFragment.kt @@ -35,8 +35,8 @@ import net.taler.common.navigate import net.taler.merchantlib.OrderHistoryEntry import net.taler.merchantpos.MainViewModel import net.taler.merchantpos.R -import net.taler.merchantpos.history.MerchantHistoryFragmentDirections.Companion.actionGlobalMerchantSettings -import net.taler.merchantpos.history.MerchantHistoryFragmentDirections.Companion.actionNavHistoryToRefundFragment +import net.taler.merchantpos.history.HistoryFragmentDirections.Companion.actionGlobalMerchantSettings +import net.taler.merchantpos.history.HistoryFragmentDirections.Companion.actionNavHistoryToRefundFragment internal interface RefundClickListener { fun onRefundClicked(item: OrderHistoryEntry) @@ -45,7 +45,7 @@ internal interface RefundClickListener { /** * Fragment to display the merchant's payment history, received from the backend. */ -class MerchantHistoryFragment : Fragment(), RefundClickListener { +class HistoryFragment : Fragment(), RefundClickListener { companion object { const val TAG = "taler-merchant" diff --git a/merchant-terminal/src/main/java/net/taler/merchantpos/history/RefundManager.kt b/merchant-terminal/src/main/java/net/taler/merchantpos/history/RefundManager.kt deleted file mode 100644 index 7f9b4c5..0000000 --- a/merchant-terminal/src/main/java/net/taler/merchantpos/history/RefundManager.kt +++ /dev/null @@ -1,134 +0,0 @@ -/* - * This file is part of GNU Taler - * (C) 2020 Taler Systems S.A. - * - * GNU Taler is free software; you can redistribute it and/or modify it under the - * terms of the GNU General Public License as published by the Free Software - * Foundation; either version 3, or (at your option) any later version. - * - * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - * A PARTICULAR PURPOSE. See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along with - * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> - */ - -package net.taler.merchantpos.history - -import android.util.Log -import androidx.annotation.UiThread -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData -import com.android.volley.Request.Method.POST -import com.android.volley.RequestQueue -import com.android.volley.Response.Listener -import com.android.volley.VolleyError -import net.taler.common.Amount -import net.taler.merchantlib.OrderHistoryEntry -import net.taler.merchantpos.LogErrorListener -import net.taler.merchantpos.config.ConfigManager -import net.taler.merchantpos.config.MerchantRequest -import org.json.JSONObject - -sealed class RefundResult { - object Error : RefundResult() - object PastDeadline : RefundResult() - object AlreadyRefunded : RefundResult() - class Success( - val refundUri: String, - val item: OrderHistoryEntry, - val amount: Amount, - val reason: String - ) : RefundResult() -} - -class RefundManager( - private val configManager: ConfigManager, - private val queue: RequestQueue -) { - - companion object { - val TAG = RefundManager::class.java.simpleName - } - - var toBeRefunded: OrderHistoryEntry? = null - private set - - private val mRefundResult = MutableLiveData<RefundResult>() - internal val refundResult: LiveData<RefundResult> = mRefundResult - - @UiThread - internal fun startRefund(item: OrderHistoryEntry) { - toBeRefunded = item - mRefundResult.value = null - } - - @UiThread - internal fun abortRefund() { - toBeRefunded = null - mRefundResult.value = null - } - - @UiThread - internal fun refund(item: OrderHistoryEntry, amount: Amount, reason: String) { - val merchantConfig = configManager.merchantConfig!! - val refundRequest = mapOf( - "order_id" to item.orderId, - "refund" to amount.toJSONString(), - "reason" to reason - ) - val body = JSONObject(refundRequest) - Log.d(TAG, body.toString(4)) - val req = MerchantRequest(POST, merchantConfig, "refund", null, body, - Listener { onRefundResponse(it, item, amount, reason) }, - LogErrorListener { onRefundError(it) } - ) - queue.add(req) - } - - @UiThread - private fun onRefundResponse( - json: JSONObject, - item: OrderHistoryEntry, - amount: Amount, - reason: String - ) { - if (!json.has("contract_terms")) { - Log.e(TAG, "Contract terms missing: $json") - onRefundError() - return - } - - val contractTerms = json.getJSONObject("contract_terms") - val refundDeadline = if (contractTerms.has("refund_deadline")) { - contractTerms.getJSONObject("refund_deadline").getLong("t_ms") - } else null - val autoRefund = contractTerms.has("auto_refund") - val refundUri = json.getString("taler_refund_uri") - - Log.e("TEST", "refundDeadline: $refundDeadline") - if (refundDeadline != null) Log.e( - "TEST", - "refundDeadline passed: ${System.currentTimeMillis() > refundDeadline}" - ) - Log.e("TEST", "autoRefund: $autoRefund") - Log.e("TEST", "refundUri: $refundUri") - - mRefundResult.value = RefundResult.Success(refundUri, item, amount, reason) - } - - @UiThread - private fun onRefundError(error: VolleyError? = null) { - val data = error?.networkResponse?.data - if (data != null) { - val json = JSONObject(String(data)) - if (json.has("code") && json.getInt("code") == 2602) { - mRefundResult.value = RefundResult.AlreadyRefunded - return - } - } - mRefundResult.value = RefundResult.Error - } - -} diff --git a/merchant-terminal/src/main/java/net/taler/merchantpos/order/CategoriesFragment.kt b/merchant-terminal/src/main/java/net/taler/merchantpos/order/CategoriesFragment.kt index e935d4f..4f8e5af 100644 --- a/merchant-terminal/src/main/java/net/taler/merchantpos/order/CategoriesFragment.kt +++ b/merchant-terminal/src/main/java/net/taler/merchantpos/order/CategoriesFragment.kt @@ -21,18 +21,14 @@ import android.view.LayoutInflater import android.view.View import android.view.View.INVISIBLE import android.view.ViewGroup -import android.widget.Button import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels import androidx.lifecycle.Observer import androidx.recyclerview.widget.LinearLayoutManager -import androidx.recyclerview.widget.RecyclerView -import androidx.recyclerview.widget.RecyclerView.Adapter import kotlinx.android.synthetic.main.fragment_categories.* import net.taler.merchantpos.MainViewModel import net.taler.merchantpos.R import net.taler.merchantpos.config.Category -import net.taler.merchantpos.order.CategoryAdapter.CategoryViewHolder interface CategorySelectionListener { fun onCategorySelected(category: Category) @@ -69,39 +65,3 @@ class CategoriesFragment : Fragment(), CategorySelectionListener { } } - -private class CategoryAdapter( - private val listener: CategorySelectionListener -) : Adapter<CategoryViewHolder>() { - - private val categories = ArrayList<Category>() - - override fun getItemCount() = categories.size - - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CategoryViewHolder { - val view = - LayoutInflater.from(parent.context).inflate(R.layout.list_item_category, parent, false) - return CategoryViewHolder(view) - } - - override fun onBindViewHolder(holder: CategoryViewHolder, position: Int) { - holder.bind(categories[position]) - } - - fun setItems(items: List<Category>) { - categories.clear() - categories.addAll(items) - notifyDataSetChanged() - } - - private inner class CategoryViewHolder(v: View) : RecyclerView.ViewHolder(v) { - private val button: Button = v.findViewById(R.id.button) - - fun bind(category: Category) { - button.text = category.localizedName - button.isPressed = category.selected - button.setOnClickListener { listener.onCategorySelected(category) } - } - } - -} diff --git a/merchant-terminal/src/main/java/net/taler/merchantpos/order/CategoryAdapter.kt b/merchant-terminal/src/main/java/net/taler/merchantpos/order/CategoryAdapter.kt new file mode 100644 index 0000000..c690ec5 --- /dev/null +++ b/merchant-terminal/src/main/java/net/taler/merchantpos/order/CategoryAdapter.kt @@ -0,0 +1,62 @@ +/* + * This file is part of GNU Taler + * (C) 2020 Taler Systems S.A. + * + * GNU Taler is free software; you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation; either version 3, or (at your option) any later version. + * + * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with + * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +package net.taler.merchantpos.order + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.Button +import androidx.recyclerview.widget.RecyclerView +import androidx.recyclerview.widget.RecyclerView.Adapter +import net.taler.merchantpos.R +import net.taler.merchantpos.config.Category +import net.taler.merchantpos.order.CategoryAdapter.CategoryViewHolder + +internal class CategoryAdapter(private val listener: CategorySelectionListener) : + Adapter<CategoryViewHolder>() { + + private val categories = ArrayList<Category>() + + override fun getItemCount() = categories.size + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CategoryViewHolder { + val view = + LayoutInflater.from(parent.context).inflate(R.layout.list_item_category, parent, false) + return CategoryViewHolder(view) + } + + override fun onBindViewHolder(holder: CategoryViewHolder, position: Int) { + holder.bind(categories[position]) + } + + fun setItems(items: List<Category>) { + categories.clear() + categories.addAll(items) + notifyDataSetChanged() + } + + internal inner class CategoryViewHolder(v: View) : RecyclerView.ViewHolder(v) { + private val button: Button = v.findViewById(R.id.button) + + fun bind(category: Category) { + button.text = category.localizedName + button.isPressed = category.selected + button.setOnClickListener { listener.onCategorySelected(category) } + } + } + +} diff --git a/merchant-terminal/src/main/java/net/taler/merchantpos/order/OrderAdapter.kt b/merchant-terminal/src/main/java/net/taler/merchantpos/order/OrderAdapter.kt new file mode 100644 index 0000000..2180ccb --- /dev/null +++ b/merchant-terminal/src/main/java/net/taler/merchantpos/order/OrderAdapter.kt @@ -0,0 +1,114 @@ +/* + * This file is part of GNU Taler + * (C) 2020 Taler Systems S.A. + * + * GNU Taler is free software; you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation; either version 3, or (at your option) any later version. + * + * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with + * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +package net.taler.merchantpos.order + +import android.view.LayoutInflater +import android.view.MotionEvent +import android.view.View +import android.view.ViewGroup +import android.widget.TextView +import androidx.recyclerview.selection.ItemDetailsLookup +import androidx.recyclerview.selection.ItemKeyProvider +import androidx.recyclerview.selection.SelectionTracker +import androidx.recyclerview.widget.AsyncListDiffer +import androidx.recyclerview.widget.DiffUtil.ItemCallback +import androidx.recyclerview.widget.RecyclerView +import androidx.recyclerview.widget.RecyclerView.Adapter +import net.taler.merchantpos.R +import net.taler.merchantpos.config.ConfigProduct +import net.taler.merchantpos.order.OrderAdapter.OrderViewHolder + +internal class OrderAdapter : Adapter<OrderViewHolder>() { + + lateinit var tracker: SelectionTracker<String> + val keyProvider = OrderKeyProvider() + private val itemCallback = object : ItemCallback<ConfigProduct>() { + override fun areItemsTheSame(oldItem: ConfigProduct, newItem: ConfigProduct): Boolean { + return oldItem == newItem + } + + override fun areContentsTheSame(oldItem: ConfigProduct, newItem: ConfigProduct): Boolean { + return oldItem.quantity == newItem.quantity + } + } + private val differ = AsyncListDiffer(this, itemCallback) + + override fun getItemCount() = differ.currentList.size + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): OrderViewHolder { + val view = + LayoutInflater.from(parent.context).inflate(R.layout.list_item_order, parent, false) + return OrderViewHolder(view) + } + + override fun onBindViewHolder(holder: OrderViewHolder, position: Int) { + val item = getItem(position)!! + holder.bind(item, tracker.isSelected(item.id)) + } + + fun setItems(items: List<ConfigProduct>, commitCallback: () -> Unit) { + // toMutableList() is needed for some reason, otherwise doesn't update adapter + differ.submitList(items.toMutableList(), commitCallback) + } + + fun getItem(position: Int): ConfigProduct? = differ.currentList[position] + + fun getItemByKey(key: String): ConfigProduct? { + return differ.currentList.find { it.id == key } + } + + fun findPosition(product: ConfigProduct): Int { + return differ.currentList.indexOf(product) + } + + internal inner class OrderViewHolder(private val v: View) : RecyclerView.ViewHolder(v) { + private val quantity: TextView = v.findViewById(R.id.quantity) + private val name: TextView = v.findViewById(R.id.name) + private val price: TextView = v.findViewById(R.id.price) + + fun bind(product: ConfigProduct, selected: Boolean) { + v.isActivated = selected + quantity.text = product.quantity.toString() + name.text = product.localizedDescription + price.text = product.totalPrice.amountStr + } + } + + internal inner class OrderKeyProvider : ItemKeyProvider<String>(SCOPE_MAPPED) { + override fun getKey(position: Int) = getItem(position)!!.id + override fun getPosition(key: String): Int { + return differ.currentList.indexOfFirst { it.id == key } + } + } + + internal class OrderLineLookup(private val list: RecyclerView) : ItemDetailsLookup<String>() { + override fun getItemDetails(e: MotionEvent): ItemDetails<String>? { + list.findChildViewUnder(e.x, e.y)?.let { view -> + val holder = list.getChildViewHolder(view) + val adapter = list.adapter as OrderAdapter + val position = holder.adapterPosition + return object : ItemDetails<String>() { + override fun getPosition(): Int = position + override fun getSelectionKey(): String = adapter.keyProvider.getKey(position) + override fun inSelectionHotspot(e: MotionEvent) = true + } + } + return null + } + } + +}
\ No newline at end of file diff --git a/merchant-terminal/src/main/java/net/taler/merchantpos/order/OrderStateFragment.kt b/merchant-terminal/src/main/java/net/taler/merchantpos/order/OrderStateFragment.kt index f792d7a..b60f3a5 100644 --- a/merchant-terminal/src/main/java/net/taler/merchantpos/order/OrderStateFragment.kt +++ b/merchant-terminal/src/main/java/net/taler/merchantpos/order/OrderStateFragment.kt @@ -18,32 +18,21 @@ package net.taler.merchantpos.order import android.os.Bundle import android.view.LayoutInflater -import android.view.MotionEvent import android.view.View import android.view.ViewGroup -import android.widget.TextView import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels import androidx.lifecycle.Observer -import androidx.recyclerview.selection.ItemDetailsLookup -import androidx.recyclerview.selection.ItemKeyProvider import androidx.recyclerview.selection.SelectionPredicates import androidx.recyclerview.selection.SelectionTracker import androidx.recyclerview.selection.StorageStrategy -import androidx.recyclerview.widget.AsyncListDiffer -import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.LinearLayoutManager -import androidx.recyclerview.widget.RecyclerView -import androidx.recyclerview.widget.RecyclerView.Adapter -import androidx.recyclerview.widget.RecyclerView.ViewHolder import kotlinx.android.synthetic.main.fragment_order_state.* import net.taler.common.fadeIn import net.taler.common.fadeOut import net.taler.merchantpos.MainViewModel import net.taler.merchantpos.R -import net.taler.merchantpos.config.ConfigProduct import net.taler.merchantpos.order.OrderAdapter.OrderLineLookup -import net.taler.merchantpos.order.OrderAdapter.OrderViewHolder class OrderStateFragment : Fragment() { @@ -130,84 +119,3 @@ class OrderStateFragment : Fragment() { } } - -private class OrderAdapter : Adapter<OrderViewHolder>() { - - lateinit var tracker: SelectionTracker<String> - val keyProvider = OrderKeyProvider() - private val itemCallback = object : DiffUtil.ItemCallback<ConfigProduct>() { - override fun areItemsTheSame(oldItem: ConfigProduct, newItem: ConfigProduct): Boolean { - return oldItem == newItem - } - - override fun areContentsTheSame(oldItem: ConfigProduct, newItem: ConfigProduct): Boolean { - return oldItem.quantity == newItem.quantity - } - } - private val differ = AsyncListDiffer(this, itemCallback) - - override fun getItemCount() = differ.currentList.size - - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): OrderViewHolder { - val view = - LayoutInflater.from(parent.context).inflate(R.layout.list_item_order, parent, false) - return OrderViewHolder(view) - } - - override fun onBindViewHolder(holder: OrderViewHolder, position: Int) { - val item = getItem(position)!! - holder.bind(item, tracker.isSelected(item.id)) - } - - fun setItems(items: List<ConfigProduct>, commitCallback: () -> Unit) { - // toMutableList() is needed for some reason, otherwise doesn't update adapter - differ.submitList(items.toMutableList(), commitCallback) - } - - fun getItem(position: Int): ConfigProduct? = differ.currentList[position] - - fun getItemByKey(key: String): ConfigProduct? { - return differ.currentList.find { it.id == key } - } - - fun findPosition(product: ConfigProduct): Int { - return differ.currentList.indexOf(product) - } - - private inner class OrderViewHolder(private val v: View) : ViewHolder(v) { - private val quantity: TextView = v.findViewById(R.id.quantity) - private val name: TextView = v.findViewById(R.id.name) - private val price: TextView = v.findViewById(R.id.price) - - fun bind(product: ConfigProduct, selected: Boolean) { - v.isActivated = selected - quantity.text = product.quantity.toString() - name.text = product.localizedDescription - price.text = product.totalPrice.amountStr - } - } - - private inner class OrderKeyProvider : ItemKeyProvider<String>(SCOPE_MAPPED) { - override fun getKey(position: Int) = getItem(position)!!.id - override fun getPosition(key: String): Int { - return differ.currentList.indexOfFirst { it.id == key } - } - } - - internal class OrderLineLookup(private val list: RecyclerView) : ItemDetailsLookup<String>() { - override fun getItemDetails(e: MotionEvent): ItemDetails<String>? { - list.findChildViewUnder(e.x, e.y)?.let { view -> - val holder = list.getChildViewHolder(view) - val adapter = list.adapter as OrderAdapter - val position = holder.adapterPosition - return object : ItemDetails<String>() { - override fun getPosition(): Int = position - override fun getSelectionKey(): String = adapter.keyProvider.getKey(position) - override fun inSelectionHotspot(e: MotionEvent) = true - } - } - return null - } - } - -} diff --git a/merchant-terminal/src/main/java/net/taler/merchantpos/payment/PaymentManager.kt b/merchant-terminal/src/main/java/net/taler/merchantpos/payment/PaymentManager.kt index bc1e35f..6bab0e6 100644 --- a/merchant-terminal/src/main/java/net/taler/merchantpos/payment/PaymentManager.kt +++ b/merchant-terminal/src/main/java/net/taler/merchantpos/payment/PaymentManager.kt @@ -110,8 +110,8 @@ class PaymentManager( // delete unpaid order val merchantConfig = configManager.merchantConfig!! mPayment.value?.let { payment -> - if (!payment.paid) payment.orderId?.let { orderId -> - Log.e(TAG, "Deleting cancelled and unpaid order $orderId") + if (!payment.paid && payment.error != null) payment.orderId?.let { orderId -> + Log.d(TAG, "Deleting cancelled and unpaid order $orderId") scope.launch(Dispatchers.IO) { api.deleteOrder(merchantConfig, orderId) } diff --git a/merchant-terminal/src/main/java/net/taler/merchantpos/history/RefundFragment.kt b/merchant-terminal/src/main/java/net/taler/merchantpos/refund/RefundFragment.kt index 17d78f6..edb2758 100644 --- a/merchant-terminal/src/main/java/net/taler/merchantpos/history/RefundFragment.kt +++ b/merchant-terminal/src/main/java/net/taler/merchantpos/refund/RefundFragment.kt @@ -14,13 +14,12 @@ * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ -package net.taler.merchantpos.history +package net.taler.merchantpos.refund import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import androidx.annotation.StringRes import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels import androidx.lifecycle.Observer @@ -36,11 +35,11 @@ import net.taler.common.navigate import net.taler.merchantlib.OrderHistoryEntry import net.taler.merchantpos.MainViewModel import net.taler.merchantpos.R -import net.taler.merchantpos.history.RefundFragmentDirections.Companion.actionRefundFragmentToRefundUriFragment -import net.taler.merchantpos.history.RefundResult.AlreadyRefunded -import net.taler.merchantpos.history.RefundResult.Error -import net.taler.merchantpos.history.RefundResult.PastDeadline -import net.taler.merchantpos.history.RefundResult.Success +import net.taler.merchantpos.refund.RefundFragmentDirections.Companion.actionRefundFragmentToRefundUriFragment +import net.taler.merchantpos.refund.RefundResult.AlreadyRefunded +import net.taler.merchantpos.refund.RefundResult.Error +import net.taler.merchantpos.refund.RefundResult.PastDeadline +import net.taler.merchantpos.refund.RefundResult.Success class RefundFragment : Fragment() { @@ -88,9 +87,9 @@ class RefundFragment : Fragment() { } private fun onRefundResultChanged(result: RefundResult?): Any = when (result) { - Error -> onError(R.string.refund_error_backend) - PastDeadline -> onError(R.string.refund_error_deadline) - AlreadyRefunded -> onError(R.string.refund_error_already_refunded) + is Error -> onError(result.msg) + PastDeadline -> onError(getString(R.string.refund_error_deadline)) + AlreadyRefunded -> onError(getString(R.string.refund_error_already_refunded)) is Success -> { progressBar.fadeOut() refundButton.fadeIn() @@ -100,8 +99,8 @@ class RefundFragment : Fragment() { } } - private fun onError(@StringRes res: Int) { - Snackbar.make(requireView(), res, LENGTH_LONG).show() + private fun onError(msg: String) { + Snackbar.make(requireView(), msg, LENGTH_LONG).show() progressBar.fadeOut() refundButton.fadeIn() } diff --git a/merchant-terminal/src/main/java/net/taler/merchantpos/refund/RefundManager.kt b/merchant-terminal/src/main/java/net/taler/merchantpos/refund/RefundManager.kt new file mode 100644 index 0000000..ea2d398 --- /dev/null +++ b/merchant-terminal/src/main/java/net/taler/merchantpos/refund/RefundManager.kt @@ -0,0 +1,91 @@ +/* + * This file is part of GNU Taler + * (C) 2020 Taler Systems S.A. + * + * GNU Taler is free software; you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation; either version 3, or (at your option) any later version. + * + * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with + * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +package net.taler.merchantpos.refund + +import androidx.annotation.UiThread +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import net.taler.common.Amount +import net.taler.merchantlib.MerchantApi +import net.taler.merchantlib.OrderHistoryEntry +import net.taler.merchantlib.RefundRequest +import net.taler.merchantpos.config.ConfigManager + +sealed class RefundResult { + class Error(val msg: String) : RefundResult() + object PastDeadline : RefundResult() + object AlreadyRefunded : RefundResult() + class Success( + val refundUri: String, + val item: OrderHistoryEntry, + val amount: Amount, + val reason: String + ) : RefundResult() +} + +class RefundManager( + private val configManager: ConfigManager, + private val scope: CoroutineScope, + private val api: MerchantApi +) { + + var toBeRefunded: OrderHistoryEntry? = null + private set + + private val mRefundResult = MutableLiveData<RefundResult>() + internal val refundResult: LiveData<RefundResult> = mRefundResult + + @UiThread + internal fun startRefund(item: OrderHistoryEntry) { + toBeRefunded = item + mRefundResult.value = null + } + + @UiThread + internal fun abortRefund() { + toBeRefunded = null + mRefundResult.value = null + } + + @UiThread + internal fun refund(item: OrderHistoryEntry, amount: Amount, reason: String) { + val merchantConfig = configManager.merchantConfig!! + val request = RefundRequest(amount, reason) + scope.launch(Dispatchers.IO) { + api.giveRefund(merchantConfig, item.orderId, request).handle(::onRefundError) { + val result = RefundResult.Success( + refundUri = it.talerRefundUri, + item = item, + amount = amount, + reason = reason + ) + mRefundResult.postValue(result) + } + } + } + + @UiThread + private fun onRefundError(msg: String) { + if (msg.contains("2602")) { + mRefundResult.postValue(RefundResult.AlreadyRefunded) + } else mRefundResult.postValue(RefundResult.Error(msg)) + } + +} diff --git a/merchant-terminal/src/main/java/net/taler/merchantpos/history/RefundUriFragment.kt b/merchant-terminal/src/main/java/net/taler/merchantpos/refund/RefundUriFragment.kt index 1ea0959..b8e8997 100644 --- a/merchant-terminal/src/main/java/net/taler/merchantpos/history/RefundUriFragment.kt +++ b/merchant-terminal/src/main/java/net/taler/merchantpos/refund/RefundUriFragment.kt @@ -14,7 +14,7 @@ * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ -package net.taler.merchantpos.history +package net.taler.merchantpos.refund import android.os.Bundle import android.view.LayoutInflater diff --git a/merchant-terminal/src/main/res/layout/fragment_merchant_config.xml b/merchant-terminal/src/main/res/layout/fragment_merchant_config.xml index b19f14c..0061a1c 100644 --- a/merchant-terminal/src/main/res/layout/fragment_merchant_config.xml +++ b/merchant-terminal/src/main/res/layout/fragment_merchant_config.xml @@ -24,7 +24,7 @@ <androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent" android:layout_height="wrap_content" - tools:context=".config.MerchantConfigFragment"> + tools:context=".config.ConfigFragment"> <com.google.android.material.textfield.TextInputLayout android:id="@+id/configUrlView" diff --git a/merchant-terminal/src/main/res/layout/fragment_refund.xml b/merchant-terminal/src/main/res/layout/fragment_refund.xml index 944da55..a13cd5a 100644 --- a/merchant-terminal/src/main/res/layout/fragment_refund.xml +++ b/merchant-terminal/src/main/res/layout/fragment_refund.xml @@ -19,7 +19,7 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" - tools:context=".history.RefundFragment"> + tools:context=".refund.RefundFragment"> <com.google.android.material.textfield.TextInputLayout android:id="@+id/amountView" diff --git a/merchant-terminal/src/main/res/navigation/nav_graph.xml b/merchant-terminal/src/main/res/navigation/nav_graph.xml index 606f2de..2c9ef2c 100644 --- a/merchant-terminal/src/main/res/navigation/nav_graph.xml +++ b/merchant-terminal/src/main/res/navigation/nav_graph.xml @@ -54,7 +54,7 @@ <fragment android:id="@+id/nav_history" - android:name="net.taler.merchantpos.history.MerchantHistoryFragment" + android:name="net.taler.merchantpos.history.HistoryFragment" android:label="@string/history_label" tools:layout="@layout/fragment_merchant_history"> <action @@ -64,7 +64,7 @@ <fragment android:id="@+id/refundFragment" - android:name="net.taler.merchantpos.history.RefundFragment" + android:name="net.taler.merchantpos.refund.RefundFragment" android:label="@string/history_refund" tools:layout="@layout/fragment_refund"> <action @@ -75,13 +75,13 @@ <fragment android:id="@+id/refundUriFragment" - android:name="net.taler.merchantpos.history.RefundUriFragment" + android:name="net.taler.merchantpos.refund.RefundUriFragment" android:label="@string/history_refund" tools:layout="@layout/fragment_refund_uri" /> <fragment android:id="@+id/nav_settings" - android:name="net.taler.merchantpos.config.MerchantConfigFragment" + android:name="net.taler.merchantpos.config.ConfigFragment" android:label="@string/config_label" tools:layout="@layout/fragment_merchant_config"> <action |