aboutsummaryrefslogtreecommitdiff
path: root/wallet/src
diff options
context:
space:
mode:
authorTorsten Grote <t@grobox.de>2020-08-11 15:24:25 -0300
committerTorsten Grote <t@grobox.de>2020-08-11 16:08:56 -0300
commitd13be7c5c1be2492d38959a29e1b1c33df4938ff (patch)
tree3e4ae91e98c0388289779b3f8d972df196aa6a49 /wallet/src
parentf0670e2f3936f0223c02e9ec0d0de52f31a3539f (diff)
downloadtaler-android-d13be7c5c1be2492d38959a29e1b1c33df4938ff.tar.gz
taler-android-d13be7c5c1be2492d38959a29e1b1c33df4938ff.tar.bz2
taler-android-d13be7c5c1be2492d38959a29e1b1c33df4938ff.zip
[wallet] start to move deserialization into the backend API
and off the UI thread for less sluggish, i.e. more responsive UI
Diffstat (limited to 'wallet/src')
-rw-r--r--wallet/src/main/java/net/taler/wallet/MainViewModel.kt63
-rw-r--r--wallet/src/main/java/net/taler/wallet/backend/WalletBackendApi.kt75
-rw-r--r--wallet/src/main/java/net/taler/wallet/backend/WalletResponse.kt82
-rw-r--r--wallet/src/main/java/net/taler/wallet/balances/BalanceAdapter.kt2
-rw-r--r--wallet/src/main/java/net/taler/wallet/balances/BalanceResponse.kt24
-rw-r--r--wallet/src/main/java/net/taler/wallet/exchanges/ExchangeAdapter.kt2
-rw-r--r--wallet/src/main/java/net/taler/wallet/transactions/TransactionManager.kt2
-rw-r--r--wallet/src/main/java/net/taler/wallet/transactions/Transactions.kt2
-rw-r--r--wallet/src/main/java/net/taler/wallet/withdraw/TosSection.kt7
-rw-r--r--wallet/src/main/java/net/taler/wallet/withdraw/WithdrawManager.kt122
-rw-r--r--wallet/src/test/java/net/taler/wallet/backend/WalletResponseTest.kt56
11 files changed, 322 insertions, 115 deletions
diff --git a/wallet/src/main/java/net/taler/wallet/MainViewModel.kt b/wallet/src/main/java/net/taler/wallet/MainViewModel.kt
index 2c5e318..330704e 100644
--- a/wallet/src/main/java/net/taler/wallet/MainViewModel.kt
+++ b/wallet/src/main/java/net/taler/wallet/MainViewModel.kt
@@ -27,7 +27,8 @@ import androidx.lifecycle.viewModelScope
import com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.kotlin.KotlinModule
-import com.fasterxml.jackson.module.kotlin.readValue
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.launch
import net.taler.common.Amount
import net.taler.common.AmountMixin
import net.taler.common.Event
@@ -37,6 +38,7 @@ import net.taler.common.assertUiThread
import net.taler.common.toEvent
import net.taler.wallet.backend.WalletBackendApi
import net.taler.wallet.balances.BalanceItem
+import net.taler.wallet.balances.BalanceResponse
import net.taler.wallet.exchanges.ExchangeManager
import net.taler.wallet.payment.PaymentManager
import net.taler.wallet.pending.PendingOperationsManager
@@ -68,15 +70,19 @@ class MainViewModel(val app: Application) : AndroidViewModel(app) {
var merchantVersion: String? = null
private set
- private val walletBackendApi = WalletBackendApi(app, {
- // nothing to do when we connect, balance will be requested by BalanceFragment in onStart()
- }) { payload ->
+ private val mapper = ObjectMapper()
+ .registerModule(KotlinModule())
+ .configure(FAIL_ON_UNKNOWN_PROPERTIES, false)
+ .addMixIn(Amount::class.java, AmountMixin::class.java)
+ .addMixIn(Timestamp::class.java, TimestampMixin::class.java)
+
+ private val api = WalletBackendApi(app) { payload ->
if (payload.optString("operation") == "init") {
val result = payload.getJSONObject("result")
val versions = result.getJSONObject("supported_protocol_versions")
exchangeVersion = versions.getString("exchange")
merchantVersion = versions.getString("merchant")
- } else if (payload.getString("type") != "waiting-for-retry") { // ignore ping
+ } else if (payload.getString("type") != "waiting-for-retry") { // ignore ping
Log.i(TAG, "Received notification from wallet-core: ${payload.toString(2)}")
loadBalances()
if (payload.optString("type") in transactionNotifications) {
@@ -92,20 +98,12 @@ class MainViewModel(val app: Application) : AndroidViewModel(app) {
}
}
- private val mapper = ObjectMapper()
- .registerModule(KotlinModule())
- .configure(FAIL_ON_UNKNOWN_PROPERTIES, false)
- .addMixIn(Amount::class.java, AmountMixin::class.java)
- .addMixIn(Timestamp::class.java, TimestampMixin::class.java)
-
- val withdrawManager = WithdrawManager(walletBackendApi, mapper)
- val paymentManager = PaymentManager(walletBackendApi, mapper)
- val pendingOperationsManager: PendingOperationsManager =
- PendingOperationsManager(walletBackendApi)
- val transactionManager: TransactionManager =
- TransactionManager(walletBackendApi, viewModelScope, mapper)
- val refundManager = RefundManager(walletBackendApi)
- val exchangeManager: ExchangeManager = ExchangeManager(walletBackendApi, mapper)
+ val withdrawManager = WithdrawManager(api, viewModelScope)
+ val paymentManager = PaymentManager(api, mapper)
+ val pendingOperationsManager: PendingOperationsManager = PendingOperationsManager(api)
+ val transactionManager: TransactionManager = TransactionManager(api, viewModelScope, mapper)
+ val refundManager = RefundManager(api)
+ val exchangeManager: ExchangeManager = ExchangeManager(api, mapper)
private val mTransactionsEvent = MutableLiveData<Event<String>>()
val transactionsEvent: LiveData<Event<String>> = mTransactionsEvent
@@ -118,20 +116,21 @@ class MainViewModel(val app: Application) : AndroidViewModel(app) {
val lastBackup: LiveData<Long> = mLastBackup
override fun onCleared() {
- walletBackendApi.destroy()
+ api.destroy()
super.onCleared()
}
@UiThread
- fun loadBalances() {
+ fun loadBalances(): Job = viewModelScope.launch {
showProgressBar.value = true
- walletBackendApi.sendRequest("getBalances") { isError, result ->
- if (isError) {
- Log.e(TAG, "Error retrieving balances: ${result.toString(2)}")
- return@sendRequest
- }
- mBalances.value = mapper.readValue(result.getString("balances"))
- showProgressBar.value = false
+ val response = api.request("getBalances", BalanceResponse.serializer())
+ showProgressBar.value = false
+ response.onError {
+ // TODO expose in UI
+ Log.e(TAG, "Error retrieving balances: $it")
+ }
+ response.onSuccess {
+ mBalances.value = it.balances
}
}
@@ -145,22 +144,22 @@ class MainViewModel(val app: Application) : AndroidViewModel(app) {
@UiThread
fun dangerouslyReset() {
- walletBackendApi.sendRequest("reset")
+ api.sendRequest("reset")
withdrawManager.testWithdrawalInProgress.value = false
mBalances.value = emptyList()
}
fun startTunnel() {
- walletBackendApi.sendRequest("startTunnel")
+ api.sendRequest("startTunnel")
}
fun stopTunnel() {
- walletBackendApi.sendRequest("stopTunnel")
+ api.sendRequest("stopTunnel")
}
fun tunnelResponse(resp: String) {
val respJson = JSONObject(resp)
- walletBackendApi.sendRequest("tunnelResponse", respJson)
+ api.sendRequest("tunnelResponse", respJson)
}
}
diff --git a/wallet/src/main/java/net/taler/wallet/backend/WalletBackendApi.kt b/wallet/src/main/java/net/taler/wallet/backend/WalletBackendApi.kt
index 51b3419..ea8f26f 100644
--- a/wallet/src/main/java/net/taler/wallet/backend/WalletBackendApi.kt
+++ b/wallet/src/main/java/net/taler/wallet/backend/WalletBackendApi.kt
@@ -14,7 +14,6 @@
* GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
-
package net.taler.wallet.backend
import android.app.Application
@@ -27,21 +26,35 @@ import android.os.IBinder
import android.os.Message
import android.os.Messenger
import android.util.Log
-import android.util.SparseArray
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.withContext
+import kotlinx.serialization.KSerializer
+import kotlinx.serialization.json.Json
+import kotlinx.serialization.json.JsonConfiguration
+import net.taler.wallet.backend.WalletBackendService.Companion.MSG_COMMAND
+import net.taler.wallet.backend.WalletBackendService.Companion.MSG_NOTIFY
+import net.taler.wallet.backend.WalletBackendService.Companion.MSG_REPLY
+import net.taler.wallet.backend.WalletBackendService.Companion.MSG_SUBSCRIBE_NOTIFY
import org.json.JSONObject
import java.lang.ref.WeakReference
import java.util.LinkedList
+import java.util.concurrent.ConcurrentHashMap
+import java.util.concurrent.atomic.AtomicInteger
+import kotlin.coroutines.resume
+import kotlin.coroutines.suspendCoroutine
class WalletBackendApi(
private val app: Application,
- private val onConnected: (() -> Unit),
private val notificationHandler: ((payload: JSONObject) -> Unit)
) {
-
+ private val json = Json(
+ JsonConfiguration.Stable.copy(ignoreUnknownKeys = true)
+ )
private var walletBackendMessenger: Messenger? = null
private val queuedMessages = LinkedList<Message>()
- private val handlers = SparseArray<(isError: Boolean, message: JSONObject) -> Unit>()
- private var nextRequestID = 1
+ private val handlers = ConcurrentHashMap<Int, (isError: Boolean, message: JSONObject) -> Unit>()
+ private var nextRequestID = AtomicInteger(0)
+ private val incomingMessenger = Messenger(IncomingHandler(this))
private val walletBackendConn = object : ServiceConnection {
override fun onServiceDisconnected(p0: ComponentName?) {
@@ -54,10 +67,15 @@ class WalletBackendApi(
val bm = Messenger(binder)
walletBackendMessenger = bm
pumpQueue(bm)
- val msg = Message.obtain(null, WalletBackendService.MSG_SUBSCRIBE_NOTIFY)
+ val msg = Message.obtain(null, MSG_SUBSCRIBE_NOTIFY)
msg.replyTo = incomingMessenger
bm.send(msg)
- onConnected.invoke()
+ }
+ }
+
+ init {
+ Intent(app, WalletBackendService::class.java).also { intent ->
+ app.bindService(intent, walletBackendConn, Context.BIND_AUTO_CREATE)
}
}
@@ -66,11 +84,11 @@ class WalletBackendApi(
override fun handleMessage(msg: Message) {
val api = weakApi.get() ?: return
when (msg.what) {
- WalletBackendService.MSG_REPLY -> {
+ MSG_REPLY -> {
val requestID = msg.data.getInt("requestID", 0)
val operation = msg.data.getString("operation", "??")
Log.i(TAG, "got reply for operation $operation ($requestID)")
- val h = api.handlers.get(requestID)
+ val h = api.handlers.remove(requestID)
if (h == null) {
Log.e(TAG, "request ID not associated with a handler")
return
@@ -84,7 +102,7 @@ class WalletBackendApi(
val json = JSONObject(response)
h(isError, json)
}
- WalletBackendService.MSG_NOTIFY -> {
+ MSG_NOTIFY -> {
val payloadStr = msg.data.getString("payload")
if (payloadStr == null) {
Log.e(TAG, "Notification had no payload: $msg")
@@ -97,14 +115,6 @@ class WalletBackendApi(
}
}
- private val incomingMessenger = Messenger(IncomingHandler(this))
-
- init {
- Intent(app, WalletBackendService::class.java).also { intent ->
- app.bindService(intent, walletBackendConn, Context.BIND_AUTO_CREATE)
- }
- }
-
private fun pumpQueue(bm: Messenger) {
while (true) {
val msg = queuedMessages.pollFirst() ?: return
@@ -112,16 +122,15 @@ class WalletBackendApi(
}
}
-
fun sendRequest(
operation: String,
args: JSONObject? = null,
onResponse: (isError: Boolean, message: JSONObject) -> Unit = { _, _ -> }
) {
- val requestID = nextRequestID++
+ val requestID = nextRequestID.incrementAndGet()
Log.i(TAG, "sending request for operation $operation ($requestID)")
- val msg = Message.obtain(null, WalletBackendService.MSG_COMMAND)
- handlers.put(requestID, onResponse)
+ val msg = Message.obtain(null, MSG_COMMAND)
+ handlers[requestID] = onResponse
msg.replyTo = incomingMessenger
val data = msg.data
data.putString("operation", operation)
@@ -137,6 +146,26 @@ class WalletBackendApi(
}
}
+ suspend fun <T> request(
+ operation: String,
+ serializer: KSerializer<T>? = null,
+ args: (JSONObject.() -> JSONObject)? = null
+ ): WalletResponse<T> = withContext(Dispatchers.Default) {
+ suspendCoroutine<WalletResponse<T>> { cont ->
+ sendRequest(operation, args?.invoke(JSONObject())) { isError, message ->
+ val response = if (isError) {
+ val error = json.parse(WalletErrorInfo.serializer(), message.toString())
+ WalletResponse.Error<T>(error)
+ } else {
+ @Suppress("UNCHECKED_CAST") // if serializer is null, T must be Unit
+ val t: T = serializer?.let { json.parse(serializer, message.toString()) } ?: Unit as T
+ WalletResponse.Success(t)
+ }
+ cont.resume(response)
+ }
+ }
+ }
+
fun destroy() {
// FIXME: implement this!
}
diff --git a/wallet/src/main/java/net/taler/wallet/backend/WalletResponse.kt b/wallet/src/main/java/net/taler/wallet/backend/WalletResponse.kt
new file mode 100644
index 0000000..05a53f3
--- /dev/null
+++ b/wallet/src/main/java/net/taler/wallet/backend/WalletResponse.kt
@@ -0,0 +1,82 @@
+/*
+ * 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.wallet.backend
+
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+import org.json.JSONObject
+
+@Serializable
+sealed class WalletResponse<T> {
+ @Serializable
+ @SerialName("response")
+ data class Success<T>(
+ val result: T
+ ) : WalletResponse<T>()
+
+ @Serializable
+ @SerialName("error")
+ data class Error<T>(
+ val error: WalletErrorInfo
+ ) : WalletResponse<T>()
+
+ fun onSuccess(block: (result: T) -> Unit): WalletResponse<T> {
+ if (this is Success) block(this.result)
+ return this
+ }
+
+ fun onError(block: (result: WalletErrorInfo) -> Unit): WalletResponse<T> {
+ if (this is Error) block(this.error)
+ return this
+ }
+}
+
+@Serializable
+data class WalletErrorInfo(
+ // Numeric error code defined defined in the
+ // GANA gnu-taler-error-codes registry.
+ val talerErrorCode: Int,
+
+ // English description of the error code.
+ val talerErrorHint: String,
+
+ // English diagnostic message that can give details
+ // for the instance of the error.
+ val message: String,
+
+ // Error details, type depends
+ // on talerErrorCode
+ val details: String?
+) {
+ val userFacingMsg: String
+ get() {
+ return StringBuilder().apply {
+ append(talerErrorCode)
+ append(" ")
+ append(message)
+ details?.let { it ->
+ val details = JSONObject(it)
+ details.optJSONObject("errorResponse")?.let { errorResponse ->
+ append("\n\n")
+ append(errorResponse.optString("code"))
+ append(" ")
+ append(errorResponse.optString("hint"))
+ }
+ }
+ }.toString()
+ }
+}
diff --git a/wallet/src/main/java/net/taler/wallet/balances/BalanceAdapter.kt b/wallet/src/main/java/net/taler/wallet/balances/BalanceAdapter.kt
index c090e75..24ee1a1 100644
--- a/wallet/src/main/java/net/taler/wallet/balances/BalanceAdapter.kt
+++ b/wallet/src/main/java/net/taler/wallet/balances/BalanceAdapter.kt
@@ -24,10 +24,12 @@ import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.Adapter
+import kotlinx.serialization.Serializable
import net.taler.common.Amount
import net.taler.wallet.R
import net.taler.wallet.balances.BalanceAdapter.BalanceViewHolder
+@Serializable
data class BalanceItem(
val available: Amount,
val pendingIncoming: Amount,
diff --git a/wallet/src/main/java/net/taler/wallet/balances/BalanceResponse.kt b/wallet/src/main/java/net/taler/wallet/balances/BalanceResponse.kt
new file mode 100644
index 0000000..d1a111f
--- /dev/null
+++ b/wallet/src/main/java/net/taler/wallet/balances/BalanceResponse.kt
@@ -0,0 +1,24 @@
+/*
+ * 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.wallet.balances
+
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class BalanceResponse(
+ val balances: List<BalanceItem>
+)
diff --git a/wallet/src/main/java/net/taler/wallet/exchanges/ExchangeAdapter.kt b/wallet/src/main/java/net/taler/wallet/exchanges/ExchangeAdapter.kt
index 189f444..17ac50f 100644
--- a/wallet/src/main/java/net/taler/wallet/exchanges/ExchangeAdapter.kt
+++ b/wallet/src/main/java/net/taler/wallet/exchanges/ExchangeAdapter.kt
@@ -24,10 +24,12 @@ import android.widget.TextView
import androidx.appcompat.widget.PopupMenu
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.Adapter
+import kotlinx.serialization.Serializable
import net.taler.wallet.R
import net.taler.wallet.cleanExchange
import net.taler.wallet.exchanges.ExchangeAdapter.ExchangeItemViewHolder
+@Serializable
data class ExchangeItem(
val exchangeBaseUrl: String,
val currency: String,
diff --git a/wallet/src/main/java/net/taler/wallet/transactions/TransactionManager.kt b/wallet/src/main/java/net/taler/wallet/transactions/TransactionManager.kt
index 8ec3914..b9f86b3 100644
--- a/wallet/src/main/java/net/taler/wallet/transactions/TransactionManager.kt
+++ b/wallet/src/main/java/net/taler/wallet/transactions/TransactionManager.kt
@@ -87,7 +87,7 @@ class TransactionManager(
@WorkerThread
private fun onTransactionsLoaded(
liveData: MutableLiveData<TransactionsResult>,
- currency: String?, // only non-null if we should update all transactions cache
+ currency: String?, // only non-null if we should update all transactions cache
result: JSONObject
) {
val transactionsArray = result.getString("transactions")
diff --git a/wallet/src/main/java/net/taler/wallet/transactions/Transactions.kt b/wallet/src/main/java/net/taler/wallet/transactions/Transactions.kt
index 5363834..721522c 100644
--- a/wallet/src/main/java/net/taler/wallet/transactions/Transactions.kt
+++ b/wallet/src/main/java/net/taler/wallet/transactions/Transactions.kt
@@ -36,6 +36,8 @@ import net.taler.wallet.cleanExchange
import net.taler.wallet.transactions.WithdrawalDetails.ManualTransfer
import net.taler.wallet.transactions.WithdrawalDetails.TalerBankIntegrationApi
+data class Transactions(val transactions: List<Transaction>)
+
@JsonTypeInfo(use = NAME, include = PROPERTY, property = "type")
@JsonSubTypes(
Type(value = TransactionWithdrawal::class, name = "withdrawal"),
diff --git a/wallet/src/main/java/net/taler/wallet/withdraw/TosSection.kt b/wallet/src/main/java/net/taler/wallet/withdraw/TosSection.kt
index b27de42..b198478 100644
--- a/wallet/src/main/java/net/taler/wallet/withdraw/TosSection.kt
+++ b/wallet/src/main/java/net/taler/wallet/withdraw/TosSection.kt
@@ -17,6 +17,7 @@
package net.taler.wallet.withdraw
import io.noties.markwon.Markwon
+import kotlinx.serialization.Serializable
import org.commonmark.node.Code
import org.commonmark.node.Document
import org.commonmark.node.Heading
@@ -73,3 +74,9 @@ private fun getNodeText(rootNode: Node): String {
}
return text
}
+
+@Serializable
+data class TosResponse(
+ val tos: String,
+ val currentEtag: String
+)
diff --git a/wallet/src/main/java/net/taler/wallet/withdraw/WithdrawManager.kt b/wallet/src/main/java/net/taler/wallet/withdraw/WithdrawManager.kt
index 6fb9390..1066550 100644
--- a/wallet/src/main/java/net/taler/wallet/withdraw/WithdrawManager.kt
+++ b/wallet/src/main/java/net/taler/wallet/withdraw/WithdrawManager.kt
@@ -19,16 +19,16 @@ package net.taler.wallet.withdraw
import android.util.Log
import androidx.annotation.UiThread
import androidx.lifecycle.MutableLiveData
-import com.fasterxml.jackson.databind.ObjectMapper
-import com.fasterxml.jackson.module.kotlin.readValue
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+import kotlinx.serialization.Serializable
import net.taler.common.Amount
import net.taler.wallet.TAG
import net.taler.wallet.backend.WalletBackendApi
+import net.taler.wallet.backend.WalletErrorInfo
import net.taler.wallet.exchanges.ExchangeFees
import net.taler.wallet.exchanges.ExchangeItem
-import net.taler.wallet.getErrorString
import net.taler.wallet.withdraw.WithdrawStatus.ReceivedDetails
-import org.json.JSONObject
sealed class WithdrawStatus {
data class Loading(val talerWithdrawUri: String? = null) : WithdrawStatus()
@@ -53,12 +53,14 @@ sealed class WithdrawStatus {
data class Error(val message: String?) : WithdrawStatus()
}
+@Serializable
data class WithdrawalDetailsForUri(
val amount: Amount,
val defaultExchangeBaseUrl: String?,
val possibleExchanges: List<ExchangeItem>
)
+@Serializable
data class WithdrawalDetails(
val tosAccepted: Boolean,
val amountRaw: Amount,
@@ -66,8 +68,8 @@ data class WithdrawalDetails(
)
class WithdrawManager(
- private val walletBackendApi: WalletBackendApi,
- private val mapper: ObjectMapper
+ private val api: WalletBackendApi,
+ private val scope: CoroutineScope
) {
val withdrawStatus = MutableLiveData<WithdrawStatus>()
@@ -78,22 +80,21 @@ class WithdrawManager(
fun withdrawTestkudos() {
testWithdrawalInProgress.value = true
- walletBackendApi.sendRequest("withdrawTestkudos") { _, _ ->
+ api.sendRequest("withdrawTestkudos") { _, _ ->
testWithdrawalInProgress.postValue(false)
}
}
- fun getWithdrawalDetails(uri: String) {
+ fun getWithdrawalDetails(uri: String) = scope.launch {
withdrawStatus.value = WithdrawStatus.Loading(uri)
- val args = JSONObject().apply {
- put("talerWithdrawUri", uri)
- }
- walletBackendApi.sendRequest("getWithdrawalDetailsForUri", args) { isError, result ->
- if (isError) {
- handleError("getWithdrawalDetailsForUri", result)
- return@sendRequest
+ val response =
+ api.request("getWithdrawalDetailsForUri", WithdrawalDetailsForUri.serializer()) {
+ put("talerWithdrawUri", uri)
}
- val details: WithdrawalDetailsForUri = mapper.readValue(result.toString())
+ response.onError { error ->
+ handleError("getWithdrawalDetailsForUri", error)
+ }
+ response.onSuccess { details ->
if (details.defaultExchangeBaseUrl == null) {
// TODO go to exchange selection screen instead
val chosenExchange = details.possibleExchanges[0].exchangeBaseUrl
@@ -104,45 +105,51 @@ class WithdrawManager(
}
}
- fun getWithdrawalDetails(exchangeBaseUrl: String, amount: Amount, uri: String? = null) {
+ fun getWithdrawalDetails(
+ exchangeBaseUrl: String,
+ amount: Amount,
+ uri: String? = null
+ ) = scope.launch {
withdrawStatus.value = WithdrawStatus.Loading(uri)
- val args = JSONObject().apply {
- put("exchangeBaseUrl", exchangeBaseUrl)
- put("amount", amount.toJSONString())
- }
- walletBackendApi.sendRequest("getWithdrawalDetailsForAmount", args) { isError, result ->
- if (isError) {
- handleError("getWithdrawalDetailsForAmount", result)
- return@sendRequest
+ val response =
+ api.request("getWithdrawalDetailsForAmount", WithdrawalDetails.serializer()) {
+ put("exchangeBaseUrl", exchangeBaseUrl)
+ put("amount", amount.toJSONString())
}
- val details: WithdrawalDetails = mapper.readValue(result.toString())
- if (details.tosAccepted)
+ response.onError { error ->
+ handleError("getWithdrawalDetailsForAmount", error)
+ }
+ response.onSuccess { details ->
+ if (details.tosAccepted) {
withdrawStatus.value = ReceivedDetails(
talerWithdrawUri = uri,
exchangeBaseUrl = exchangeBaseUrl,
amountRaw = details.amountRaw,
amountEffective = details.amountEffective
)
- else getExchangeTos(exchangeBaseUrl, details, uri)
+ } else getExchangeTos(exchangeBaseUrl, details, uri)
}
}
- private fun getExchangeTos(exchangeBaseUrl: String, details: WithdrawalDetails, uri: String?) {
- val args = JSONObject().apply {
+ private fun getExchangeTos(
+ exchangeBaseUrl: String,
+ details: WithdrawalDetails,
+ uri: String?
+ ) = scope.launch {
+ val response = api.request("getExchangeTos", TosResponse.serializer()) {
put("exchangeBaseUrl", exchangeBaseUrl)
}
- walletBackendApi.sendRequest("getExchangeTos", args) { isError, result ->
- if (isError) {
- handleError("getExchangeTos", result)
- return@sendRequest
- }
+ response.onError {
+ handleError("getExchangeTos", it)
+ }
+ response.onSuccess {
withdrawStatus.value = WithdrawStatus.TosReviewRequired(
talerWithdrawUri = uri,
exchangeBaseUrl = exchangeBaseUrl,
amountRaw = details.amountRaw,
amountEffective = details.amountEffective,
- tosText = result.getString("tos"),
- tosEtag = result.getString("currentEtag")
+ tosText = it.tos,
+ tosEtag = it.currentEtag
)
}
}
@@ -150,17 +157,14 @@ class WithdrawManager(
/**
* Accept the currently displayed terms of service.
*/
- fun acceptCurrentTermsOfService() {
+ fun acceptCurrentTermsOfService() = scope.launch {
val s = withdrawStatus.value as WithdrawStatus.TosReviewRequired
- val args = JSONObject().apply {
+ api.request<Unit>("setExchangeTosAccepted") {
put("exchangeBaseUrl", s.exchangeBaseUrl)
put("etag", s.tosEtag)
- }
- walletBackendApi.sendRequest("setExchangeTosAccepted", args) { isError, result ->
- if (isError) {
- handleError("setExchangeTosAccepted", result)
- return@sendRequest
- }
+ }.onError {
+ handleError("setExchangeTosAccepted", it)
+ }.onSuccess {
withdrawStatus.value = ReceivedDetails(
talerWithdrawUri = s.talerWithdrawUri,
exchangeBaseUrl = s.exchangeBaseUrl,
@@ -171,33 +175,33 @@ class WithdrawManager(
}
@UiThread
- fun acceptWithdrawal() {
+ fun acceptWithdrawal() = scope.launch {
val status = withdrawStatus.value as ReceivedDetails
+ val operation = if (status.talerWithdrawUri == null) {
+ "acceptManualWithdrawal"
+ } else {
+ "acceptBankIntegratedWithdrawal"
+ }
+ withdrawStatus.value = WithdrawStatus.Withdrawing
- val operation = if (status.talerWithdrawUri == null)
- "acceptManualWithdrawal" else "acceptBankIntegratedWithdrawal"
- val args = JSONObject().apply {
+ api.request<Unit>(operation) {
put("exchangeBaseUrl", status.exchangeBaseUrl)
if (status.talerWithdrawUri == null) {
put("amount", status.amountRaw)
} else {
put("talerWithdrawUri", status.talerWithdrawUri)
}
- }
- withdrawStatus.value = WithdrawStatus.Withdrawing
- walletBackendApi.sendRequest(operation, args) { isError, result ->
- if (isError) {
- handleError(operation, result)
- return@sendRequest
- }
+ }.onError {
+ handleError(operation, it)
+ }.onSuccess {
withdrawStatus.value = WithdrawStatus.Success(status.amountRaw.currency)
}
}
@UiThread
- private fun handleError(operation: String, result: JSONObject) {
- Log.e(TAG, "Error $operation ${result.toString(2)}")
- withdrawStatus.value = WithdrawStatus.Error(getErrorString(result))
+ private fun handleError(operation: String, error: WalletErrorInfo) {
+ Log.e(TAG, "Error $operation $error")
+ withdrawStatus.value = WithdrawStatus.Error(error.userFacingMsg)
}
}
diff --git a/wallet/src/test/java/net/taler/wallet/backend/WalletResponseTest.kt b/wallet/src/test/java/net/taler/wallet/backend/WalletResponseTest.kt
new file mode 100644
index 0000000..b7d7c68
--- /dev/null
+++ b/wallet/src/test/java/net/taler/wallet/backend/WalletResponseTest.kt
@@ -0,0 +1,56 @@
+/*
+ * 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.wallet.backend
+
+import junit.framework.Assert.assertEquals
+import kotlinx.serialization.UnstableDefault
+import kotlinx.serialization.json.Json
+import kotlinx.serialization.json.JsonConfiguration
+import net.taler.wallet.balances.BalanceResponse
+import org.junit.Test
+
+@UnstableDefault
+class WalletResponseTest {
+
+ private val json = Json(JsonConfiguration(ignoreUnknownKeys = true))
+
+ @Test
+ fun testBalanceResponse() {
+ val serializer = WalletResponse.Success.serializer(BalanceResponse.serializer())
+ val response = json.parse(
+ serializer, """
+ {
+ "type": "response",
+ "operation": "getBalances",
+ "id": 2,
+ "result": {
+ "balances": [
+ {
+ "available": "TESTKUDOS:15.8",
+ "pendingIncoming": "TESTKUDOS:0",
+ "pendingOutgoing": "TESTKUDOS:0",
+ "hasPendingTransactions": false,
+ "requiresUserInput": false
+ }
+ ]
+ }
+ }
+ """.trimIndent()
+ )
+ assertEquals(1, response.result.balances.size)
+ }
+}