diff options
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) + } +} |