From bc35e8924e652c323001f62f6781657545fa378f Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Tue, 28 Jul 2020 17:16:57 -0300 Subject: [pos] adapt history to new v1 API --- .../main/java/net/taler/merchantlib/MerchantApi.kt | 19 ++++--- .../java/net/taler/merchantlib/MerchantConfig.kt | 2 +- .../java/net/taler/merchantlib/OrderHistory.kt | 46 ++++++++++++++++ .../java/net/taler/merchantlib/PostOrderRequest.kt | 6 +-- .../main/java/net/taler/merchantlib/Response.kt | 7 ++- merchant-terminal/build.gradle | 3 -- .../java/net/taler/merchantpos/MainViewModel.kt | 8 +-- .../taler/merchantpos/history/HistoryManager.kt | 62 ++++++---------------- .../merchantpos/history/MerchantHistoryFragment.kt | 34 +++++++----- .../taler/merchantpos/history/RefundFragment.kt | 3 +- .../net/taler/merchantpos/history/RefundManager.kt | 11 ++-- .../main/java/net/taler/common/ContractTerms.kt | 2 + 12 files changed, 113 insertions(+), 90 deletions(-) create mode 100644 merchant-lib/src/main/java/net/taler/merchantlib/OrderHistory.kt 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 e995724..db37586 100644 --- a/merchant-lib/src/main/java/net/taler/merchantlib/MerchantApi.kt +++ b/merchant-lib/src/main/java/net/taler/merchantlib/MerchantApi.kt @@ -16,7 +16,6 @@ package net.taler.merchantlib -import android.util.Log import io.ktor.client.HttpClient import io.ktor.client.engine.okhttp.OkHttp import io.ktor.client.features.json.JsonFeature @@ -25,8 +24,6 @@ import io.ktor.client.request.delete import io.ktor.client.request.get import io.ktor.client.request.header import io.ktor.client.request.post -import io.ktor.client.statement.HttpResponse -import io.ktor.client.statement.readBytes import io.ktor.http.ContentType.Application.Json import io.ktor.http.HttpHeaders.Authorization import io.ktor.http.contentType @@ -64,14 +61,16 @@ class MerchantApi(private val httpClient: HttpClient) { suspend fun deleteOrder( merchantConfig: MerchantConfig, orderId: String - ): Response = response { - val resp = httpClient.delete(merchantConfig.urlFor("private/orders/$orderId")) { + ): Response = response { + httpClient.delete(merchantConfig.urlFor("private/orders/$orderId")) { header(Authorization, "ApiKey ${merchantConfig.apiKey}") - } as HttpResponse - // TODO remove when the API call was fixed - Log.e("TEST", "status: ${resp.status.value}") - Log.e("TEST", String(resp.readBytes())) - resp + } as Unit + } + + suspend fun getOrderHistory(merchantConfig: MerchantConfig): Response = response { + httpClient.get(merchantConfig.urlFor("private/orders")) { + header(Authorization, "ApiKey ${merchantConfig.apiKey}") + } as OrderHistory } } diff --git a/merchant-lib/src/main/java/net/taler/merchantlib/MerchantConfig.kt b/merchant-lib/src/main/java/net/taler/merchantlib/MerchantConfig.kt index a01624e..a8d113e 100644 --- a/merchant-lib/src/main/java/net/taler/merchantlib/MerchantConfig.kt +++ b/merchant-lib/src/main/java/net/taler/merchantlib/MerchantConfig.kt @@ -31,7 +31,7 @@ data class MerchantConfig( fun urlFor(endpoint: String): String { val sb = StringBuilder(baseUrl) if (sb.last() != '/') sb.append('/') - sb.append("instances/$instance/") + instance?.let { sb.append("instances/$it/") } sb.append(endpoint) return sb.toString() } diff --git a/merchant-lib/src/main/java/net/taler/merchantlib/OrderHistory.kt b/merchant-lib/src/main/java/net/taler/merchantlib/OrderHistory.kt new file mode 100644 index 0000000..718bde5 --- /dev/null +++ b/merchant-lib/src/main/java/net/taler/merchantlib/OrderHistory.kt @@ -0,0 +1,46 @@ +/* + * 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 + */ + +package net.taler.merchantlib + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import net.taler.common.Amount +import net.taler.common.Timestamp + +@Serializable +data class OrderHistory( + val orders: List +) + +@Serializable +data class OrderHistoryEntry( + // order ID of the transaction related to this entry. + @SerialName("order_id") + val orderId: String, + + // when the order was created + val timestamp: Timestamp, + + // the amount of money the order is for + val amount: Amount, + + // the summary of the order + val summary: String, + + // 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 a6e74d6..4854a80 100644 --- a/merchant-lib/src/main/java/net/taler/merchantlib/PostOrderRequest.kt +++ b/merchant-lib/src/main/java/net/taler/merchantlib/PostOrderRequest.kt @@ -48,11 +48,11 @@ sealed class CheckPaymentResponse { override fun deserialize(decoder: Decoder): CheckPaymentResponse { val input = decoder as JsonInput val tree = input.decodeJson() as JsonObject - val paid = tree.getPrimitive("paid").boolean -// return if (paid) decoder.json.fromJson(Paid.serializer(), tree) + val orderStatus = tree.getPrimitive("order_status").content +// return if (orderStatus == "paid") decoder.json.fromJson(Paid.serializer(), tree) // else decoder.json.fromJson(Unpaid.serializer(), tree) // manual parsing due to https://github.com/Kotlin/kotlinx.serialization/issues/576 - return if (paid) Paid( + return if (orderStatus == "paid") Paid( refunded = tree.getPrimitive("refunded").boolean ) else Unpaid( talerPayUri = tree.getPrimitive("taler_pay_uri").content diff --git a/merchant-lib/src/main/java/net/taler/merchantlib/Response.kt b/merchant-lib/src/main/java/net/taler/merchantlib/Response.kt index 1b49d78..9aefa5f 100644 --- a/merchant-lib/src/main/java/net/taler/merchantlib/Response.kt +++ b/merchant-lib/src/main/java/net/taler/merchantlib/Response.kt @@ -16,8 +16,11 @@ package net.taler.merchantlib +import android.util.Log import io.ktor.client.call.receive import io.ktor.client.features.ClientRequestException +import io.ktor.client.features.ResponseException +import io.ktor.client.features.ServerResponseException import kotlinx.serialization.Serializable class Response private constructor( @@ -29,6 +32,7 @@ class Response private constructor( return try { success(request()) } catch (e: Throwable) { + Log.e("merchant-lib", "Error", e) failure(e) } } @@ -63,10 +67,11 @@ class Response private constructor( private suspend fun getFailureString(failure: Failure): String = when (failure.exception) { is ClientRequestException -> getExceptionString(failure.exception) + is ServerResponseException -> getExceptionString(failure.exception) else -> failure.exception.toString() } - private suspend fun getExceptionString(e: ClientRequestException): String { + private suspend fun getExceptionString(e: ResponseException): String { return try { val error: Error = e.response.receive() "Error ${error.code}: ${error.hint}" diff --git a/merchant-terminal/build.gradle b/merchant-terminal/build.gradle index 1cec0c5..4499892 100644 --- a/merchant-terminal/build.gradle +++ b/merchant-terminal/build.gradle @@ -73,9 +73,6 @@ dependencies { implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0" - // JSON parsing and serialization - implementation "com.fasterxml.jackson.module:jackson-module-kotlin:2.10.2" - testImplementation 'androidx.test.ext:junit:1.1.1' testImplementation 'org.robolectric:robolectric:4.3.1' } 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 b62c550..905738b 100644 --- a/merchant-terminal/src/main/java/net/taler/merchantpos/MainViewModel.kt +++ b/merchant-terminal/src/main/java/net/taler/merchantpos/MainViewModel.kt @@ -20,9 +20,6 @@ import android.app.Application import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.viewModelScope import com.android.volley.toolbox.Volley -import com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES -import com.fasterxml.jackson.databind.ObjectMapper -import com.fasterxml.jackson.module.kotlin.KotlinModule import net.taler.merchantlib.MerchantApi import net.taler.merchantlib.getDefaultHttpClient import net.taler.merchantpos.config.ConfigManager @@ -35,9 +32,6 @@ class MainViewModel(app: Application) : AndroidViewModel(app) { private val httpClient = getDefaultHttpClient() private val api = MerchantApi(httpClient) - private val mapper = ObjectMapper() - .registerModule(KotlinModule()) - .configure(FAIL_ON_UNKNOWN_PROPERTIES, false) private val queue = Volley.newRequestQueue(app) val orderManager = OrderManager(app) @@ -45,7 +39,7 @@ class MainViewModel(app: Application) : AndroidViewModel(app) { addConfigurationReceiver(orderManager) } val paymentManager = PaymentManager(app, configManager, viewModelScope, api) - val historyManager = HistoryManager(configManager, queue, mapper) + val historyManager = HistoryManager(configManager, viewModelScope, api) val refundManager = RefundManager(configManager, queue) override fun onCleared() { 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 24c7a0c..cb77096 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 @@ -19,40 +19,22 @@ package net.taler.merchantpos.history import androidx.annotation.UiThread 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.Listener -import com.fasterxml.jackson.annotation.JsonIgnore -import com.fasterxml.jackson.annotation.JsonProperty -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 kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import net.taler.merchantlib.MerchantApi +import net.taler.merchantlib.OrderHistoryEntry import net.taler.merchantpos.config.ConfigManager -import net.taler.merchantpos.config.MerchantRequest -import org.json.JSONObject - -data class HistoryItem( - @JsonProperty("order_id") - val orderId: String, - val amount: Amount, - val summary: String, - val timestamp: Timestamp -) { - @get:JsonIgnore - val time = timestamp.ms -} sealed class HistoryResult { - object Error : HistoryResult() - class Success(val items: List) : HistoryResult() + class Error(val msg: String) : HistoryResult() + class Success(val items: List) : HistoryResult() } class HistoryManager( private val configManager: ConfigManager, - private val queue: RequestQueue, - private val mapper: ObjectMapper + private val scope: CoroutineScope, + private val api: MerchantApi ) { private val mIsLoading = MutableLiveData(false) @@ -65,29 +47,17 @@ class HistoryManager( internal fun fetchHistory() { mIsLoading.value = true val merchantConfig = configManager.merchantConfig!! - val params = mapOf("instance" to merchantConfig.instance!!) - val req = MerchantRequest(GET, merchantConfig, "history", params, null, - Listener { onHistoryResponse(it) }, - LogErrorListener { onHistoryError() }) - queue.add(req) - } - - @UiThread - private fun onHistoryResponse(body: JSONObject) { - mIsLoading.value = false - val items = arrayListOf() - val historyJson = body.getJSONArray("history") - for (i in 0 until historyJson.length()) { - val historyItem: HistoryItem = mapper.readValue(historyJson.getString(i)) - items.add(historyItem) + scope.launch(Dispatchers.IO) { + api.getOrderHistory(merchantConfig).handle(::onHistoryError) { + mIsLoading.postValue(false) + mItems.postValue(HistoryResult.Success(it.orders)) + } } - mItems.value = HistoryResult.Success(items) } - @UiThread - private fun onHistoryError() { + private fun onHistoryError(msg: String) = scope.launch(Dispatchers.Main) { mIsLoading.value = false - mItems.value = HistoryResult.Error + mItems.value = 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 6da3dd2..25805dc 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,6 +20,8 @@ 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 @@ -31,21 +33,22 @@ 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_SHORT +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.* +import java.util.ArrayList private interface RefundClickListener { - fun onRefundClicked(item: HistoryItem) + fun onRefundClicked(item: OrderHistoryEntry) } /** @@ -87,7 +90,7 @@ class MerchantHistoryFragment : Fragment(), RefundClickListener { }) historyManager.items.observe(viewLifecycleOwner, Observer { result -> when (result) { - is HistoryResult.Error -> onError() + is HistoryResult.Error -> onError(result.msg) is HistoryResult.Success -> historyListAdapter.setData(result.items) }.exhaustive }) @@ -95,18 +98,18 @@ class MerchantHistoryFragment : Fragment(), RefundClickListener { override fun onStart() { super.onStart() - if (model.configManager.merchantConfig?.instance == null) { + if (model.configManager.merchantConfig?.baseUrl == null) { navigate(actionGlobalMerchantSettings()) } else { historyManager.fetchHistory() } } - private fun onError() { - Snackbar.make(requireView(), R.string.error_network, LENGTH_SHORT).show() + private fun onError(msg: String) { + Snackbar.make(requireView(), msg, LENGTH_LONG).show() } - override fun onRefundClicked(item: HistoryItem) { + override fun onRefundClicked(item: OrderHistoryEntry) { refundManager.startRefund(item) navigate(actionNavHistoryToRefundFragment()) } @@ -116,7 +119,7 @@ class MerchantHistoryFragment : Fragment(), RefundClickListener { private class HistoryItemAdapter(private val listener: RefundClickListener) : Adapter() { - private val items = ArrayList() + private val items = ArrayList() override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HistoryItemViewHolder { val v = @@ -130,7 +133,7 @@ private class HistoryItemAdapter(private val listener: RefundClickListener) : holder.bind(items[position]) } - fun setData(items: List) { + fun setData(items: List) { this.items.clear() this.items.addAll(items) this.notifyDataSetChanged() @@ -144,13 +147,18 @@ private class HistoryItemAdapter(private val listener: RefundClickListener) : private val orderIdView: TextView = v.findViewById(R.id.orderIdView) private val refundButton: ImageButton = v.findViewById(R.id.refundButton) - fun bind(item: HistoryItem) { + 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.time.toRelativeTime(v.context) - refundButton.setOnClickListener { listener.onRefundClicked(item) } + 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/history/RefundFragment.kt b/merchant-terminal/src/main/java/net/taler/merchantpos/history/RefundFragment.kt index eb3d46d..17d78f6 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 @@ -33,6 +33,7 @@ import net.taler.common.AmountParserException import net.taler.common.fadeIn import net.taler.common.fadeOut 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 @@ -65,7 +66,7 @@ class RefundFragment : Fragment() { }) } - private fun onRefundButtonClicked(item: HistoryItem) { + private fun onRefundButtonClicked(item: OrderHistoryEntry) { val inputAmount = try { Amount.fromString(item.amount.currency, amountInputView.text.toString()) } catch (e: AmountParserException) { 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 da642d6..7f9b4c5 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 @@ -25,6 +25,7 @@ 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 @@ -36,7 +37,7 @@ sealed class RefundResult { object AlreadyRefunded : RefundResult() class Success( val refundUri: String, - val item: HistoryItem, + val item: OrderHistoryEntry, val amount: Amount, val reason: String ) : RefundResult() @@ -51,14 +52,14 @@ class RefundManager( val TAG = RefundManager::class.java.simpleName } - var toBeRefunded: HistoryItem? = null + var toBeRefunded: OrderHistoryEntry? = null private set private val mRefundResult = MutableLiveData() internal val refundResult: LiveData = mRefundResult @UiThread - internal fun startRefund(item: HistoryItem) { + internal fun startRefund(item: OrderHistoryEntry) { toBeRefunded = item mRefundResult.value = null } @@ -70,7 +71,7 @@ class RefundManager( } @UiThread - internal fun refund(item: HistoryItem, amount: Amount, reason: String) { + internal fun refund(item: OrderHistoryEntry, amount: Amount, reason: String) { val merchantConfig = configManager.merchantConfig!! val refundRequest = mapOf( "order_id" to item.orderId, @@ -89,7 +90,7 @@ class RefundManager( @UiThread private fun onRefundResponse( json: JSONObject, - item: HistoryItem, + item: OrderHistoryEntry, amount: Amount, reason: String ) { diff --git a/taler-kotlin-common/src/main/java/net/taler/common/ContractTerms.kt b/taler-kotlin-common/src/main/java/net/taler/common/ContractTerms.kt index c07127a..b891ef7 100644 --- a/taler-kotlin-common/src/main/java/net/taler/common/ContractTerms.kt +++ b/taler-kotlin-common/src/main/java/net/taler/common/ContractTerms.kt @@ -82,8 +82,10 @@ data class ContractMerchant( val name: String ) +@Serializable @JsonInclude(NON_EMPTY) class Timestamp( + @SerialName("t_ms") @JsonProperty("t_ms") val ms: Long ) -- cgit v1.2.3