diff options
author | Torsten Grote <t@grobox.de> | 2020-08-11 15:24:25 -0300 |
---|---|---|
committer | Torsten Grote <t@grobox.de> | 2020-08-11 16:08:56 -0300 |
commit | d13be7c5c1be2492d38959a29e1b1c33df4938ff (patch) | |
tree | 3e4ae91e98c0388289779b3f8d972df196aa6a49 /wallet | |
parent | f0670e2f3936f0223c02e9ec0d0de52f31a3539f (diff) | |
download | taler-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')
12 files changed, 323 insertions, 115 deletions
diff --git a/wallet/build.gradle b/wallet/build.gradle index ef5ddfa..d0fd97d 100644 --- a/wallet/build.gradle +++ b/wallet/build.gradle @@ -20,6 +20,7 @@ plugins { id "com.android.application" id "kotlin-android" id "kotlin-android-extensions" + id 'kotlinx-serialization' id "de.undercouch.download" } 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) + } +} |