gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[taler-taler-android] branch master updated (8eb241c -> 3ceaeb3)


From: gnunet
Subject: [taler-taler-android] branch master updated (8eb241c -> 3ceaeb3)
Date: Thu, 30 Jul 2020 22:12:20 +0200

This is an automated email from the git hooks/post-receive script.

torsten-grote pushed a change to branch master
in repository taler-android.

    from 8eb241c  [pos] refactor configuration fetching and validation
     new bc35e89  [pos] adapt history to new v1 API
     new e19ba09  [wallet] update to new wallet-core with v8 exchange API
     new 8815105  Split out common code into multiplatform Kotlin library
     new 3ceaeb3  [wallet] upgrade payment flow to new API

The 4 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .gitignore                                         |   1 +
 .gitlab-ci.yml                                     |   1 +
 .idea/gradle.xml                                   |   1 +
 anastasis-ui/build.gradle                          |   2 +-
 build.gradle                                       |   4 -
 cashier/.gitlab-ci.yml                             |   1 +
 cashier/build.gradle                               |   2 +-
 .../main/java/net/taler/cashier/MainViewModel.kt   |  16 +-
 merchant-lib/.gitlab-ci.yml                        |   1 +
 merchant-lib/build.gradle                          |   2 +-
 .../main/java/net/taler/merchantlib/MerchantApi.kt |  19 +-
 .../java/net/taler/merchantlib/MerchantConfig.kt   |   2 +-
 .../{MerchantConfig.kt => OrderHistory.kt}         |  40 ++--
 .../java/net/taler/merchantlib/PostOrderRequest.kt |   6 +-
 .../main/java/net/taler/merchantlib/Response.kt    |   6 +-
 .../java/net/taler/merchantlib/MerchantApiTest.kt  |   1 +
 merchant-terminal/.gitlab-ci.yml                   |   1 +
 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 +-
 settings.gradle                                    |   3 +
 {cashier => taler-kotlin-android}/.gitignore       |   0
 taler-kotlin-android/.gitlab-ci.yml                |  12 ++
 .../build.gradle                                   |   6 +
 .../consumer-rules.pro                             |   0
 .../proguard-rules.pro                             |   0
 .../src/main/AndroidManifest.xml                   |   0
 .../src/main/java/net/taler/common/AmountMixin.kt  |  51 +++++
 .../src/main/java/net/taler/common/AndroidUtils.kt |   0
 .../main/java/net/taler/common/ByteArrayUtils.kt   |   0
 .../main/java/net/taler/common/CombinedLiveData.kt |   0
 .../main/java/net/taler/common/ContractTerms.kt    |   6 +-
 .../src/main/java/net/taler/common/Event.kt        |   0
 .../src/main/java/net/taler/common/NfcManager.kt   |   0
 .../main/java/net/taler/common/QrCodeManager.kt    |   0
 .../src/main/java/net/taler/common/SignedAmount.kt |   0
 .../src/main/java/net/taler/common/TalerUtils.kt   |   3 +-
 .../main/res/drawable/selectable_background.xml    |   0
 .../src/main/res/values-night/colors.xml           |   0
 .../src/main/res/values/colors.xml                 |   0
 .../src/main/res/values/strings.xml                |   0
 .../java/net/taler/common/ContractTermsTest.kt     |  74 +++++++
 taler-kotlin-common/.gitlab-ci.yml                 |   6 +-
 taler-kotlin-common/build.gradle                   | 130 ++++++------
 .../kotlin}/net/taler/common/Amount.kt             |  70 +------
 .../src/commonMain/kotlin/net/taler/common/Time.kt |  81 ++++++++
 .../kotlin}/net/taler/common/Version.kt            |   0
 .../kotlin}/net/taler/common/AmountTest.kt         | 147 ++++---------
 .../kotlin/net/taler/common/TestUtils.kt           |  19 +-
 .../kotlin}/net/taler/common/VersionTest.kt        |   6 +-
 .../src/jsMain/kotlin/net/taler/common/Time.kt     |  16 +-
 .../src/jvmMain/kotlin/net/taler/common/Time.kt    |  16 +-
 .../src/nativeMain/kotlin/net/taler/common/Time.kt |  16 +-
 wallet/.gitlab-ci.yml                              |   1 +
 wallet/build.gradle                                |  10 +-
 .../src/main/java/net/taler/wallet/MainActivity.kt |   5 +-
 .../src/main/java/net/taler/wallet/MainFragment.kt |   2 +-
 .../main/java/net/taler/wallet/MainViewModel.kt    |  42 ++--
 .../net/taler/wallet/backend/WalletBackendApi.kt   |   4 +-
 .../taler/wallet/backend/WalletBackendService.kt   |   9 +-
 .../net/taler/wallet/balances/BalanceAdapter.kt    |  11 +-
 .../net/taler/wallet/balances/BalancesFragment.kt  |   2 +-
 .../main/java/net/taler/wallet/crypto/Encoding.kt  | 134 ------------
 .../net/taler/wallet/exchanges/ExchangeFees.kt     |  38 ++--
 .../net/taler/wallet/exchanges/ExchangeManager.kt  |   3 +-
 .../SelectExchangeFragment.kt                      |  12 +-
 .../net/taler/wallet/history/DevHistoryAdapter.kt  | 110 ----------
 .../net/taler/wallet/history/DevHistoryFragment.kt |  87 --------
 .../net/taler/wallet/history/DevHistoryManager.kt  |  75 -------
 .../java/net/taler/wallet/history/HistoryEvent.kt  | 199 ------------------
 .../net/taler/wallet/history/JsonDialogFragment.kt |  57 ------
 .../net/taler/wallet/payment/PaymentManager.kt     | 108 +++++-----
 .../net/taler/wallet/payment/PaymentResponses.kt   |  61 ++++++
 .../taler/wallet/payment/PromptPaymentFragment.kt  |   7 +-
 .../wallet/pending/PendingOperationsManager.kt     |   4 +-
 .../wallet/transactions/TransactionManager.kt      |   8 -
 .../wallet/transactions/TransactionsFragment.kt    |   2 +-
 .../java/net/taler/wallet/withdraw/ExchangeFees.kt |  95 ---------
 .../wallet/withdraw/ManualWithdrawFragment.kt      |   2 +-
 .../wallet/withdraw/PromptWithdrawFragment.kt      |  26 ++-
 .../wallet/withdraw/ReviewExchangeTosFragment.kt   |   2 +-
 .../net/taler/wallet/withdraw/WithdrawManager.kt   | 227 ++++++++++-----------
 wallet/src/main/res/drawable/ic_directions.xml     |  25 ---
 .../src/main/res/drawable/ic_edit.xml              |   4 +-
 wallet/src/main/res/drawable/ic_history.xml        |   9 -
 .../res/drawable/transaction_payment_aborted.xml   |  25 ---
 .../main/res/drawable/transaction_tip_declined.xml |  25 ---
 wallet/src/main/res/layout/fragment_json.xml       |  40 ----
 .../main/res/layout/fragment_prompt_withdraw.xml   |   6 +-
 .../src/main/res/layout/fragment_transactions.xml  |   2 +-
 wallet/src/main/res/layout/list_item_history.xml   |  75 -------
 wallet/src/main/res/layout/payment_bottom_bar.xml  |   1 -
 wallet/src/main/res/menu/activity_main_drawer.xml  |   4 -
 wallet/src/main/res/navigation/nav_graph.xml       |  12 +-
 wallet/src/main/res/values/strings.xml             |   1 -
 wallet/src/main/res/xml/settings_backup.xml        |   2 +-
 .../net/taler/wallet/crypto/Base32CrockfordTest.kt |  36 ----
 100 files changed, 807 insertions(+), 1692 deletions(-)
 copy merchant-lib/src/main/java/net/taler/merchantlib/{MerchantConfig.kt => 
OrderHistory.kt} (59%)
 copy {cashier => taler-kotlin-android}/.gitignore (100%)
 create mode 100644 taler-kotlin-android/.gitlab-ci.yml
 copy {taler-kotlin-common => taler-kotlin-android}/build.gradle (95%)
 rename {taler-kotlin-common => taler-kotlin-android}/consumer-rules.pro (100%)
 rename {taler-kotlin-common => taler-kotlin-android}/proguard-rules.pro (100%)
 rename {taler-kotlin-common => 
taler-kotlin-android}/src/main/AndroidManifest.xml (100%)
 create mode 100644 
taler-kotlin-android/src/main/java/net/taler/common/AmountMixin.kt
 rename {taler-kotlin-common => 
taler-kotlin-android}/src/main/java/net/taler/common/AndroidUtils.kt (100%)
 rename {taler-kotlin-common => 
taler-kotlin-android}/src/main/java/net/taler/common/ByteArrayUtils.kt (100%)
 rename {taler-kotlin-common => 
taler-kotlin-android}/src/main/java/net/taler/common/CombinedLiveData.kt (100%)
 rename {taler-kotlin-common => 
taler-kotlin-android}/src/main/java/net/taler/common/ContractTerms.kt (95%)
 rename {taler-kotlin-common => 
taler-kotlin-android}/src/main/java/net/taler/common/Event.kt (100%)
 rename {taler-kotlin-common => 
taler-kotlin-android}/src/main/java/net/taler/common/NfcManager.kt (100%)
 rename {taler-kotlin-common => 
taler-kotlin-android}/src/main/java/net/taler/common/QrCodeManager.kt (100%)
 rename {taler-kotlin-common => 
taler-kotlin-android}/src/main/java/net/taler/common/SignedAmount.kt (100%)
 rename {taler-kotlin-common => 
taler-kotlin-android}/src/main/java/net/taler/common/TalerUtils.kt (97%)
 rename {taler-kotlin-common => 
taler-kotlin-android}/src/main/res/drawable/selectable_background.xml (100%)
 rename {taler-kotlin-common => 
taler-kotlin-android}/src/main/res/values-night/colors.xml (100%)
 rename {taler-kotlin-common => 
taler-kotlin-android}/src/main/res/values/colors.xml (100%)
 rename {taler-kotlin-common => 
taler-kotlin-android}/src/main/res/values/strings.xml (100%)
 create mode 100644 
taler-kotlin-android/src/test/java/net/taler/common/ContractTermsTest.kt
 rename taler-kotlin-common/src/{main/java => 
commonMain/kotlin}/net/taler/common/Amount.kt (70%)
 create mode 100644 
taler-kotlin-common/src/commonMain/kotlin/net/taler/common/Time.kt
 rename taler-kotlin-common/src/{main/java => 
commonMain/kotlin}/net/taler/common/Version.kt (100%)
 rename taler-kotlin-common/src/{test/java => 
commonTest/kotlin}/net/taler/common/AmountTest.kt (65%)
 copy merchant-terminal/src/main/java/net/taler/merchantpos/payment/Payment.kt 
=> taler-kotlin-common/src/commonTest/kotlin/net/taler/common/TestUtils.kt (67%)
 rename taler-kotlin-common/src/{test/java => 
commonTest/kotlin}/net/taler/common/VersionTest.kt (95%)
 copy merchant-terminal/src/main/java/net/taler/merchantpos/payment/Payment.kt 
=> taler-kotlin-common/src/jsMain/kotlin/net/taler/common/Time.kt (70%)
 copy merchant-terminal/src/main/java/net/taler/merchantpos/payment/Payment.kt 
=> taler-kotlin-common/src/jvmMain/kotlin/net/taler/common/Time.kt (70%)
 copy merchant-terminal/src/main/java/net/taler/merchantpos/payment/Payment.kt 
=> taler-kotlin-common/src/nativeMain/kotlin/net/taler/common/Time.kt (70%)
 delete mode 100644 wallet/src/main/java/net/taler/wallet/crypto/Encoding.kt
 copy merchant-lib/src/main/java/net/taler/merchantlib/ConfigResponse.kt => 
wallet/src/main/java/net/taler/wallet/exchanges/ExchangeFees.kt (54%)
 rename wallet/src/main/java/net/taler/wallet/{withdraw => 
exchanges}/SelectExchangeFragment.kt (94%)
 delete mode 100644 
wallet/src/main/java/net/taler/wallet/history/DevHistoryAdapter.kt
 delete mode 100644 
wallet/src/main/java/net/taler/wallet/history/DevHistoryFragment.kt
 delete mode 100644 
wallet/src/main/java/net/taler/wallet/history/DevHistoryManager.kt
 delete mode 100644 
wallet/src/main/java/net/taler/wallet/history/HistoryEvent.kt
 delete mode 100644 
wallet/src/main/java/net/taler/wallet/history/JsonDialogFragment.kt
 create mode 100644 
wallet/src/main/java/net/taler/wallet/payment/PaymentResponses.kt
 delete mode 100644 
wallet/src/main/java/net/taler/wallet/withdraw/ExchangeFees.kt
 delete mode 100644 wallet/src/main/res/drawable/ic_directions.xml
 copy anastasis-ui/src/main/res/drawable/ic_baseline_check.xml => 
wallet/src/main/res/drawable/ic_edit.xml (51%)
 delete mode 100644 wallet/src/main/res/drawable/ic_history.xml
 delete mode 100644 wallet/src/main/res/drawable/transaction_payment_aborted.xml
 delete mode 100644 wallet/src/main/res/drawable/transaction_tip_declined.xml
 delete mode 100644 wallet/src/main/res/layout/fragment_json.xml
 delete mode 100644 wallet/src/main/res/layout/list_item_history.xml
 delete mode 100644 
wallet/src/test/java/net/taler/wallet/crypto/Base32CrockfordTest.kt

diff --git a/.gitignore b/.gitignore
index 7e4952a..caf9ce6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,7 @@
 *.iml
 .gradle
 /local.properties
+/.idea/artifacts
 /.idea/caches
 /.idea/libraries
 /.idea/misc.xml
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 48f1aec..6dc4426 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -14,6 +14,7 @@ include:
   - local: 'merchant-lib/.gitlab-ci.yml'
   - local: 'merchant-terminal/.gitlab-ci.yml'
   - local: 'taler-kotlin-common/.gitlab-ci.yml'
+  - local: 'taler-kotlin-android/.gitlab-ci.yml'
   - local: 'wallet/.gitlab-ci.yml'
 
 after_script:
diff --git a/.idea/gradle.xml b/.idea/gradle.xml
index 581abbf..01ed15f 100644
--- a/.idea/gradle.xml
+++ b/.idea/gradle.xml
@@ -14,6 +14,7 @@
             <option value="$PROJECT_DIR$/cashier" />
             <option value="$PROJECT_DIR$/merchant-lib" />
             <option value="$PROJECT_DIR$/merchant-terminal" />
+            <option value="$PROJECT_DIR$/taler-kotlin-android" />
             <option value="$PROJECT_DIR$/taler-kotlin-common" />
             <option value="$PROJECT_DIR$/wallet" />
           </set>
diff --git a/anastasis-ui/build.gradle b/anastasis-ui/build.gradle
index 0391c7c..ff0eec5 100644
--- a/anastasis-ui/build.gradle
+++ b/anastasis-ui/build.gradle
@@ -51,7 +51,7 @@ android {
 }
 
 dependencies {
-    implementation project(":taler-kotlin-common")
+    implementation project(":taler-kotlin-android")
 
     implementation 'com.google.android.material:material:1.2.0-beta01'
     implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
diff --git a/build.gradle b/build.gradle
index 76f687e..442d232 100644
--- a/build.gradle
+++ b/build.gradle
@@ -24,7 +24,3 @@ allprojects {
         maven { url 'https://jitpack.io' }
     }
 }
-
-task clean(type: Delete) {
-    delete rootProject.buildDir
-}
diff --git a/cashier/.gitlab-ci.yml b/cashier/.gitlab-ci.yml
index 6a7baed..6b73dee 100644
--- a/cashier/.gitlab-ci.yml
+++ b/cashier/.gitlab-ci.yml
@@ -6,6 +6,7 @@ cashier_test:
     changes:
       - cashier/**/*
       - taler-kotlin-common/**/*
+      - taler-kotlin-android/**/*
       - build.gradle
   script: ./gradlew :cashier:check :cashier:assembleRelease
   artifacts:
diff --git a/cashier/build.gradle b/cashier/build.gradle
index 0d06c60..641a039 100644
--- a/cashier/build.gradle
+++ b/cashier/build.gradle
@@ -54,7 +54,7 @@ android {
 }
 
 dependencies {
-    implementation project(":taler-kotlin-common")
+    implementation project(":taler-kotlin-android")
     implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
     implementation 'androidx.security:security-crypto:1.0.0-rc02'
     implementation 'com.google.android.material:material:1.1.0'
diff --git a/cashier/src/main/java/net/taler/cashier/MainViewModel.kt 
b/cashier/src/main/java/net/taler/cashier/MainViewModel.kt
index c8d9a3b..a4fd35e 100644
--- a/cashier/src/main/java/net/taler/cashier/MainViewModel.kt
+++ b/cashier/src/main/java/net/taler/cashier/MainViewModel.kt
@@ -40,6 +40,7 @@ import net.taler.common.isOnline
 
 private val TAG = MainViewModel::class.java.simpleName
 
+private const val VERSION_BANK = "0:0:0"
 private const val PREF_NAME = "net.taler.cashier.prefs"
 private const val PREF_KEY_BANK_URL = "bankUrl"
 private const val PREF_KEY_USERNAME = "username"
@@ -86,20 +87,21 @@ class MainViewModel(private val app: Application) : 
AndroidViewModel(app) {
     fun checkAndSaveConfig(config: Config) {
         mConfigResult.value = null
         viewModelScope.launch(Dispatchers.IO) {
-            val url = "${config.bankUrl}/accounts/${config.username}/balance"
+            val url = "${config.bankUrl}/config"
             Log.d(TAG, "Checking config: $url")
             val result = when (val response = makeJsonGetRequest(url, config)) 
{
                 is HttpJsonResult.Success -> {
-                    val balance = response.json.getString("balance")
+                    val version = response.json.getString("version")
+                    // TODO check if version is compatible
+                    val currency = response.json.getString("currency")
                     try {
-                        val amount = SignedAmount.fromJSONString(balance)
-                        mCurrency.postValue(amount.amount.currency)
-                        prefs.edit().putString(PREF_KEY_CURRENCY, 
amount.amount.currency).apply()
+                        mCurrency.postValue(currency)
+                        prefs.edit().putString(PREF_KEY_CURRENCY, 
currency).apply()
                         // save config
                         saveConfig(config)
                         ConfigResult.Success
-                    } catch (e: AmountParserException) {
-                        ConfigResult.Error(false, "Invalid Amount: $balance")
+                    } catch (e: Exception) {
+                        ConfigResult.Error(false, "Invalid Config: 
${response.json}")
                     }
                 }
                 is HttpJsonResult.Error -> {
diff --git a/merchant-lib/.gitlab-ci.yml b/merchant-lib/.gitlab-ci.yml
index 62a7516..8f7c7c2 100644
--- a/merchant-lib/.gitlab-ci.yml
+++ b/merchant-lib/.gitlab-ci.yml
@@ -2,6 +2,7 @@ merchant_lib_test:
   stage: test
   only:
     changes:
+      - taler-kotlin-common/**/*
       - merchant-lib/**/*
       - build.gradle
   script: ./gradlew :merchant-lib:check
diff --git a/merchant-lib/build.gradle b/merchant-lib/build.gradle
index 128f4c1..33e8379 100644
--- a/merchant-lib/build.gradle
+++ b/merchant-lib/build.gradle
@@ -45,7 +45,7 @@ android {
 }
 
 dependencies {
-    api project(":taler-kotlin-common")
+    api project(":taler-kotlin-android")
 
     implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
 
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<HttpResponse> = response {
-        val resp = 
httpClient.delete(merchantConfig.urlFor("private/orders/$orderId")) {
+    ): Response<Unit> = 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<OrderHistory> = 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/MerchantConfig.kt 
b/merchant-lib/src/main/java/net/taler/merchantlib/OrderHistory.kt
similarity index 59%
copy from merchant-lib/src/main/java/net/taler/merchantlib/MerchantConfig.kt
copy to merchant-lib/src/main/java/net/taler/merchantlib/OrderHistory.kt
index a01624e..718bde5 100644
--- a/merchant-lib/src/main/java/net/taler/merchantlib/MerchantConfig.kt
+++ b/merchant-lib/src/main/java/net/taler/merchantlib/OrderHistory.kt
@@ -18,21 +18,29 @@ 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 MerchantConfig(
-    @SerialName("base_url")
-    val baseUrl: String,
-    // TODO remove instance when it is part of baseURL
-    val instance: String? = null,
-    @SerialName("api_key")
-    val apiKey: String
-) {
-    fun urlFor(endpoint: String): String {
-        val sb = StringBuilder(baseUrl)
-        if (sb.last() != '/') sb.append('/')
-        sb.append("instances/$instance/")
-        sb.append(endpoint)
-        return sb.toString()
-    }
-}
+data class OrderHistory(
+    val orders: List<OrderHistoryEntry>
+)
+
+@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..65a12a9 100644
--- a/merchant-lib/src/main/java/net/taler/merchantlib/Response.kt
+++ b/merchant-lib/src/main/java/net/taler/merchantlib/Response.kt
@@ -18,6 +18,8 @@ package net.taler.merchantlib
 
 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<out T> private constructor(
@@ -29,6 +31,7 @@ class Response<out T> private constructor(
             return try {
                 success(request())
             } catch (e: Throwable) {
+                println(e)
                 failure(e)
             }
         }
@@ -63,10 +66,11 @@ class Response<out T> 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-lib/src/test/java/net/taler/merchantlib/MerchantApiTest.kt 
b/merchant-lib/src/test/java/net/taler/merchantlib/MerchantApiTest.kt
index ea5a12a..deed81e 100644
--- a/merchant-lib/src/test/java/net/taler/merchantlib/MerchantApiTest.kt
+++ b/merchant-lib/src/test/java/net/taler/merchantlib/MerchantApiTest.kt
@@ -113,6 +113,7 @@ class MerchantApiTest {
         val unpaidResponse = CheckPaymentResponse.Unpaid(false, 
"http://taler.net/foo";)
         
httpClient.giveJsonResponse("http://example.net/instances/testInstance/private/orders/$orderId";)
 {
             """{
+                "order_status": "unpaid",
                 "paid": ${unpaidResponse.paid},
                 "taler_pay_uri": "${unpaidResponse.talerPayUri}"
             }""".trimIndent()
diff --git a/merchant-terminal/.gitlab-ci.yml b/merchant-terminal/.gitlab-ci.yml
index 74ac21f..d159902 100644
--- a/merchant-terminal/.gitlab-ci.yml
+++ b/merchant-terminal/.gitlab-ci.yml
@@ -5,6 +5,7 @@ merchant_test:
       - merchant-terminal/**/*
       - merchant-lib/**/*
       - taler-kotlin-common/**/*
+      - taler-kotlin-android/**/*
       - build.gradle
   script: ./gradlew :merchant-terminal:check :merchant-terminal:assembleRelease
   artifacts:
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<HistoryItem>) : HistoryResult()
+    class Error(val msg: String) : HistoryResult()
+    class Success(val items: List<OrderHistoryEntry>) : 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<HistoryItem>()
-        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<HistoryItemViewHolder>() {
 
-    private val items = ArrayList<HistoryItem>()
+    private val items = ArrayList<OrderHistoryEntry>()
 
     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<HistoryItem>) {
+    fun setData(items: List<OrderHistoryEntry>) {
         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<RefundResult>()
     internal val refundResult: LiveData<RefundResult> = 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/settings.gradle b/settings.gradle
index 14d898d..6175852 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -1,4 +1,7 @@
 include ':cashier', ':merchant-terminal', ':wallet'
 include ':taler-kotlin-common'
+include ':taler-kotlin-android'
 include ':merchant-lib'
 include ':anastasis-ui'
+
+enableFeaturePreview('GRADLE_METADATA')
diff --git a/cashier/.gitignore b/taler-kotlin-android/.gitignore
similarity index 100%
copy from cashier/.gitignore
copy to taler-kotlin-android/.gitignore
diff --git a/taler-kotlin-android/.gitlab-ci.yml 
b/taler-kotlin-android/.gitlab-ci.yml
new file mode 100644
index 0000000..bb5af21
--- /dev/null
+++ b/taler-kotlin-android/.gitlab-ci.yml
@@ -0,0 +1,12 @@
+taler_kotlin_android_test:
+  stage: test
+  only:
+    changes:
+      - taler-kotlin-android/**/*
+      - taler-kotlin-common/**/*
+      - build.gradle
+  script: ./gradlew :taler-kotlin-android:check
+  artifacts:
+    paths:
+      - taler-kotlin-android/build/reports/lint-results.html
+    expire_in: 1 week
diff --git a/taler-kotlin-common/build.gradle 
b/taler-kotlin-android/build.gradle
similarity index 95%
copy from taler-kotlin-common/build.gradle
copy to taler-kotlin-android/build.gradle
index dd083b7..d6d6003 100644
--- a/taler-kotlin-common/build.gradle
+++ b/taler-kotlin-android/build.gradle
@@ -43,9 +43,15 @@ android {
         }
     }
 
+    packagingOptions {
+        exclude("META-INF/*.kotlin_module")
+    }
+
 }
 
 dependencies {
+    api project(":taler-kotlin-common")
+
     implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
     implementation 'androidx.appcompat:appcompat:1.1.0'
     implementation 'androidx.core:core-ktx:1.3.0'
diff --git a/taler-kotlin-common/consumer-rules.pro 
b/taler-kotlin-android/consumer-rules.pro
similarity index 100%
rename from taler-kotlin-common/consumer-rules.pro
rename to taler-kotlin-android/consumer-rules.pro
diff --git a/taler-kotlin-common/proguard-rules.pro 
b/taler-kotlin-android/proguard-rules.pro
similarity index 100%
rename from taler-kotlin-common/proguard-rules.pro
rename to taler-kotlin-android/proguard-rules.pro
diff --git a/taler-kotlin-common/src/main/AndroidManifest.xml 
b/taler-kotlin-android/src/main/AndroidManifest.xml
similarity index 100%
rename from taler-kotlin-common/src/main/AndroidManifest.xml
rename to taler-kotlin-android/src/main/AndroidManifest.xml
diff --git a/taler-kotlin-android/src/main/java/net/taler/common/AmountMixin.kt 
b/taler-kotlin-android/src/main/java/net/taler/common/AmountMixin.kt
new file mode 100644
index 0000000..f9b1330
--- /dev/null
+++ b/taler-kotlin-android/src/main/java/net/taler/common/AmountMixin.kt
@@ -0,0 +1,51 @@
+/*
+ * 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.common
+
+import com.fasterxml.jackson.core.JsonGenerator
+import com.fasterxml.jackson.core.JsonParser
+import com.fasterxml.jackson.databind.DeserializationContext
+import com.fasterxml.jackson.databind.JsonMappingException
+import com.fasterxml.jackson.databind.SerializerProvider
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize
+import com.fasterxml.jackson.databind.annotation.JsonSerialize
+import com.fasterxml.jackson.databind.deser.std.StdDeserializer
+import com.fasterxml.jackson.databind.ser.std.StdSerializer
+
+/**
+ * Used to support Jackson serialization along with KotlinX.
+ */
+@JsonSerialize(using = AmountSerializer::class)
+@JsonDeserialize(using = AmountDeserializer::class)
+abstract class AmountMixin
+
+class AmountSerializer : StdSerializer<Amount>(Amount::class.java) {
+    override fun serialize(value: Amount, gen: JsonGenerator, provider: 
SerializerProvider) {
+        gen.writeString(value.toJSONString())
+    }
+}
+
+class AmountDeserializer : StdDeserializer<Amount>(Amount::class.java) {
+    override fun deserialize(p: JsonParser, ctxt: DeserializationContext): 
Amount {
+        val node = p.codec.readValue(p, String::class.java)
+        try {
+            return Amount.fromJSONString(node)
+        } catch (e: AmountParserException) {
+            throw JsonMappingException(p, "Error parsing Amount", e)
+        }
+    }
+}
diff --git a/taler-kotlin-common/src/main/java/net/taler/common/AndroidUtils.kt 
b/taler-kotlin-android/src/main/java/net/taler/common/AndroidUtils.kt
similarity index 100%
rename from taler-kotlin-common/src/main/java/net/taler/common/AndroidUtils.kt
rename to taler-kotlin-android/src/main/java/net/taler/common/AndroidUtils.kt
diff --git 
a/taler-kotlin-common/src/main/java/net/taler/common/ByteArrayUtils.kt 
b/taler-kotlin-android/src/main/java/net/taler/common/ByteArrayUtils.kt
similarity index 100%
rename from taler-kotlin-common/src/main/java/net/taler/common/ByteArrayUtils.kt
rename to taler-kotlin-android/src/main/java/net/taler/common/ByteArrayUtils.kt
diff --git 
a/taler-kotlin-common/src/main/java/net/taler/common/CombinedLiveData.kt 
b/taler-kotlin-android/src/main/java/net/taler/common/CombinedLiveData.kt
similarity index 100%
rename from 
taler-kotlin-common/src/main/java/net/taler/common/CombinedLiveData.kt
rename to 
taler-kotlin-android/src/main/java/net/taler/common/CombinedLiveData.kt
diff --git 
a/taler-kotlin-common/src/main/java/net/taler/common/ContractTerms.kt 
b/taler-kotlin-android/src/main/java/net/taler/common/ContractTerms.kt
similarity index 95%
rename from taler-kotlin-common/src/main/java/net/taler/common/ContractTerms.kt
rename to taler-kotlin-android/src/main/java/net/taler/common/ContractTerms.kt
index c07127a..0d5fe5b 100644
--- a/taler-kotlin-common/src/main/java/net/taler/common/ContractTerms.kt
+++ b/taler-kotlin-android/src/main/java/net/taler/common/ContractTerms.kt
@@ -18,7 +18,6 @@ package net.taler.common
 
 import androidx.annotation.RequiresApi
 import com.fasterxml.jackson.annotation.JsonIgnore
-import com.fasterxml.jackson.annotation.JsonIgnoreProperties
 import com.fasterxml.jackson.annotation.JsonInclude
 import com.fasterxml.jackson.annotation.JsonInclude.Include.NON_EMPTY
 import com.fasterxml.jackson.annotation.JsonInclude.Include.NON_NULL
@@ -28,13 +27,14 @@ import kotlinx.serialization.Serializable
 import net.taler.common.TalerUtils.getLocalizedString
 
 @Serializable
-@JsonIgnoreProperties(ignoreUnknown = true)
 data class ContractTerms(
     val summary: String,
     @SerialName("summary_i18n")
+    @get:JsonProperty("summary_i18n")
     val summaryI18n: Map<String, String>? = null,
     val amount: Amount,
     @SerialName("fulfillment_url")
+    @get:JsonProperty("fulfillment_url")
     val fulfillmentUrl: String,
     val products: List<ContractProduct>
 )
@@ -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
 )
diff --git a/taler-kotlin-common/src/main/java/net/taler/common/Event.kt 
b/taler-kotlin-android/src/main/java/net/taler/common/Event.kt
similarity index 100%
rename from taler-kotlin-common/src/main/java/net/taler/common/Event.kt
rename to taler-kotlin-android/src/main/java/net/taler/common/Event.kt
diff --git a/taler-kotlin-common/src/main/java/net/taler/common/NfcManager.kt 
b/taler-kotlin-android/src/main/java/net/taler/common/NfcManager.kt
similarity index 100%
rename from taler-kotlin-common/src/main/java/net/taler/common/NfcManager.kt
rename to taler-kotlin-android/src/main/java/net/taler/common/NfcManager.kt
diff --git 
a/taler-kotlin-common/src/main/java/net/taler/common/QrCodeManager.kt 
b/taler-kotlin-android/src/main/java/net/taler/common/QrCodeManager.kt
similarity index 100%
rename from taler-kotlin-common/src/main/java/net/taler/common/QrCodeManager.kt
rename to taler-kotlin-android/src/main/java/net/taler/common/QrCodeManager.kt
diff --git a/taler-kotlin-common/src/main/java/net/taler/common/SignedAmount.kt 
b/taler-kotlin-android/src/main/java/net/taler/common/SignedAmount.kt
similarity index 100%
rename from taler-kotlin-common/src/main/java/net/taler/common/SignedAmount.kt
rename to taler-kotlin-android/src/main/java/net/taler/common/SignedAmount.kt
diff --git a/taler-kotlin-common/src/main/java/net/taler/common/TalerUtils.kt 
b/taler-kotlin-android/src/main/java/net/taler/common/TalerUtils.kt
similarity index 97%
rename from taler-kotlin-common/src/main/java/net/taler/common/TalerUtils.kt
rename to taler-kotlin-android/src/main/java/net/taler/common/TalerUtils.kt
index 444caa4..bb2e78a 100644
--- a/taler-kotlin-common/src/main/java/net/taler/common/TalerUtils.kt
+++ b/taler-kotlin-android/src/main/java/net/taler/common/TalerUtils.kt
@@ -18,8 +18,7 @@ package net.taler.common
 
 import androidx.annotation.RequiresApi
 import androidx.core.os.LocaleListCompat
-import java.util.*
-import kotlin.collections.ArrayList
+import java.util.Locale
 
 object TalerUtils {
 
diff --git 
a/taler-kotlin-common/src/main/res/drawable/selectable_background.xml 
b/taler-kotlin-android/src/main/res/drawable/selectable_background.xml
similarity index 100%
rename from taler-kotlin-common/src/main/res/drawable/selectable_background.xml
rename to taler-kotlin-android/src/main/res/drawable/selectable_background.xml
diff --git a/taler-kotlin-common/src/main/res/values-night/colors.xml 
b/taler-kotlin-android/src/main/res/values-night/colors.xml
similarity index 100%
rename from taler-kotlin-common/src/main/res/values-night/colors.xml
rename to taler-kotlin-android/src/main/res/values-night/colors.xml
diff --git a/taler-kotlin-common/src/main/res/values/colors.xml 
b/taler-kotlin-android/src/main/res/values/colors.xml
similarity index 100%
rename from taler-kotlin-common/src/main/res/values/colors.xml
rename to taler-kotlin-android/src/main/res/values/colors.xml
diff --git a/taler-kotlin-common/src/main/res/values/strings.xml 
b/taler-kotlin-android/src/main/res/values/strings.xml
similarity index 100%
rename from taler-kotlin-common/src/main/res/values/strings.xml
rename to taler-kotlin-android/src/main/res/values/strings.xml
diff --git 
a/taler-kotlin-android/src/test/java/net/taler/common/ContractTermsTest.kt 
b/taler-kotlin-android/src/test/java/net/taler/common/ContractTermsTest.kt
new file mode 100644
index 0000000..79a7598
--- /dev/null
+++ b/taler-kotlin-android/src/test/java/net/taler/common/ContractTermsTest.kt
@@ -0,0 +1,74 @@
+/*
+ * 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.common
+
+import com.fasterxml.jackson.databind.DeserializationFeature
+import com.fasterxml.jackson.databind.ObjectMapper
+import com.fasterxml.jackson.module.kotlin.KotlinModule
+import com.fasterxml.jackson.module.kotlin.readValue
+import org.junit.Assert.assertEquals
+import org.junit.Test
+
+class ContractTermsTest {
+
+    private val mapper = ObjectMapper()
+        .registerModule(KotlinModule())
+        .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
+        .addMixIn(Amount::class.java, AmountMixin::class.java)
+
+    @Test
+    fun test() {
+        val json = """
+            {
+              "amount":"TESTKUDOS:0.5",
+              "extra":{
+                "article_name":"1._The_Free_Software_Definition"
+              },
+              
"fulfillment_url":"https://shop.test.taler.net/essay/1._The_Free_Software_Definition";,
+              "summary":"Essay: 1. The Free Software Definition",
+              "refund_deadline":{"t_ms":1596128414000},
+              "wire_transfer_deadline":{"t_ms":1596128564000},
+              "products":[],
+              
"h_wire":"KV40K023N8EC1F5100TYNS23C4XN68Y1Z3PTJSWFGTMCNYD54KT4S791V2VQ91SZANN86VDAA369M4VEZ0KR6DN71EVRRZA71K681M0",
+              "wire_method":"x-taler-bank",
+              "order_id":"2020.212-01M9VKEAPF76C",
+              "timestamp":{"t_ms":1596128114000},
+              "pay_deadline":{"t_ms":"never"},
+              "max_wire_fee":"TESTKUDOS:1",
+              "max_fee":"TESTKUDOS:1",
+              "wire_fee_amortization":3,
+              
"merchant_base_url":"https://backend.test.taler.net/instances/blog/";,
+              "merchant":{"name":"Blog","instance":"blog"},
+              "exchanges":[
+                {
+                    "url":"https://exchange.test.taler.net/";,
+                    
"master_pub":"DY95EXAHQ2BKM2WK9YHZHYG1R7PPMMJPY14FNGP662DAKE35AKQG"
+                },
+                {
+                    "url":"https://exchange.test.taler.net/";,
+                    
"master_pub":"DY95EXAHQ2BKM2WK9YHZHYG1R7PPMMJPY14FNGP662DAKE35AKQG"}
+                ],
+                "auditors":[],
+                
"merchant_pub":"8DR9NKSZY1CXFRE47NEYXM0K85C4ZGAYH7Y7VZ22GPNF0BRFNYNG",
+                "nonce":"FK8ZKJRV6VX6YFAG4CDSC6W0DWD084Q09DP81ANF30GRFQYM2KPG"
+              }
+        """.trimIndent()
+        val contractTerms: ContractTerms = mapper.readValue(json)
+        assertEquals("Essay: 1. The Free Software Definition", 
contractTerms.summary)
+    }
+
+}
diff --git a/taler-kotlin-common/.gitlab-ci.yml 
b/taler-kotlin-common/.gitlab-ci.yml
index 49d3e98..c241e31 100644
--- a/taler-kotlin-common/.gitlab-ci.yml
+++ b/taler-kotlin-common/.gitlab-ci.yml
@@ -4,8 +4,4 @@ taler_kotlin_common_test:
     changes:
       - taler-kotlin-common/**/*
       - build.gradle
-  script: ./gradlew :taler-kotlin-common:check
-  artifacts:
-    paths:
-      - taler-kotlin-common/build/reports/lint-results.html
-    expire_in: 1 week
+  script: ./gradlew :taler-kotlin-common:jvmTest
diff --git a/taler-kotlin-common/build.gradle b/taler-kotlin-common/build.gradle
index dd083b7..129881d 100644
--- a/taler-kotlin-common/build.gradle
+++ b/taler-kotlin-common/build.gradle
@@ -1,72 +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/>
- */
-
 plugins {
-    id 'com.android.library'
-    id 'kotlin-android'
-    id 'kotlin-android-extensions'
+    id 'org.jetbrains.kotlin.multiplatform'
     id 'kotlinx-serialization'
 }
 
-android {
-    compileSdkVersion 29
-    //noinspection GradleDependency
-    buildToolsVersion "$build_tools_version"
+group 'net.taler'
+version '0.0.1'
 
-    defaultConfig {
-        minSdkVersion 24
-        targetSdkVersion 29
-        versionCode 1
-        versionName "0.1"
+apply plugin: 'maven-publish'
 
-        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
-        consumerProguardFiles 'consumer-rules.pro'
+kotlin {
+    jvm()
+    // This is for iPhone simulator
+    // Switch here to iosArm64 (or iosArm32) to build library for iPhone device
+    iosX64("ios") {
+        binaries {
+            framework()
+        }
     }
-
-    buildTypes {
-        release {
-            minifyEnabled false
-            proguardFiles 
getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+    linuxX64("linux")
+    js {
+        browser {
+        }
+        nodejs {
+        }
+    }
+    sourceSets {
+        def serialization_version = "0.20.0"
+        commonMain {
+            dependencies {
+                implementation kotlin('stdlib-common')
+                implementation 
"org.jetbrains.kotlinx:kotlinx-serialization-runtime-common:$serialization_version"
+            }
+        }
+        commonTest {
+            dependencies {
+                implementation kotlin('test-common')
+                implementation kotlin('test-annotations-common')
+            }
+        }
+        jvmMain {
+            dependencies {
+                implementation kotlin('stdlib-jdk8')
+                implementation 
"org.jetbrains.kotlinx:kotlinx-serialization-runtime:$serialization_version"
+            }
+        }
+        jvmTest {
+            dependencies {
+                implementation kotlin('test')
+                implementation kotlin('test-junit')
+            }
+        }
+        jsMain {
+            dependencies {
+                implementation kotlin('stdlib-js')
+                implementation 
"org.jetbrains.kotlinx:kotlinx-serialization-runtime-js:$serialization_version"
+            }
+        }
+        jsTest {
+            dependencies {
+                implementation kotlin('test-js')
+            }
+        }
+        nativeMain {
+            dependsOn commonMain
+            dependencies {
+                implementation 
"org.jetbrains.kotlinx:kotlinx-serialization-runtime-native:$serialization_version"
+            }
+        }
+        nativeTest {
+            dependsOn commonTest
+        }
+        configure([targets.linux, targets.ios]) {
+            compilations.main.source(sourceSets.nativeMain)
+            compilations.test.source(sourceSets.nativeTest)
         }
     }
-
 }
 
-dependencies {
-    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
-    implementation 'androidx.appcompat:appcompat:1.1.0'
-    implementation 'androidx.core:core-ktx:1.3.0'
-
-    // Navigation
-    implementation "androidx.navigation:navigation-ui-ktx:$nav_version"
-    implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
-
-    // ViewModel and LiveData
-    def lifecycle_version = "2.2.0"
-    implementation 
"androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
-
-    // QR codes
-    implementation 'com.google.zxing:core:3.4.0'  // needs minSdkVersion 24+
-
-    // JSON parsing and serialization
-    api "org.jetbrains.kotlinx:kotlinx-serialization-runtime:0.20.0"
-    implementation "com.fasterxml.jackson.module:jackson-module-kotlin:2.10.2"
-
-    lintChecks 'com.github.thirdegg:lint-rules:0.0.4-alpha'
-
-    testImplementation 'junit:junit:4.13'
-    testImplementation 'org.json:json:20190722'
+configurations {
+    compileClasspath
 }
diff --git a/taler-kotlin-common/src/main/java/net/taler/common/Amount.kt 
b/taler-kotlin-common/src/commonMain/kotlin/net/taler/common/Amount.kt
similarity index 70%
rename from taler-kotlin-common/src/main/java/net/taler/common/Amount.kt
rename to taler-kotlin-common/src/commonMain/kotlin/net/taler/common/Amount.kt
index 992f93b..84d10c5 100644
--- a/taler-kotlin-common/src/main/java/net/taler/common/Amount.kt
+++ b/taler-kotlin-common/src/commonMain/kotlin/net/taler/common/Amount.kt
@@ -16,23 +16,12 @@
 
 package net.taler.common
 
-import android.annotation.SuppressLint
-import com.fasterxml.jackson.core.JsonGenerator
-import com.fasterxml.jackson.core.JsonParser
-import com.fasterxml.jackson.databind.DeserializationContext
-import com.fasterxml.jackson.databind.JsonMappingException
-import com.fasterxml.jackson.databind.SerializerProvider
-import com.fasterxml.jackson.databind.annotation.JsonDeserialize
-import com.fasterxml.jackson.databind.annotation.JsonSerialize
-import com.fasterxml.jackson.databind.deser.std.StdDeserializer
-import com.fasterxml.jackson.databind.ser.std.StdSerializer
 import kotlinx.serialization.Decoder
 import kotlinx.serialization.Encoder
 import kotlinx.serialization.KSerializer
 import kotlinx.serialization.Serializable
 import kotlinx.serialization.Serializer
-import org.json.JSONObject
-import java.lang.Math.floorDiv
+import kotlin.math.floor
 import kotlin.math.pow
 import kotlin.math.roundToInt
 
@@ -40,8 +29,6 @@ class AmountParserException(msg: String? = null, cause: 
Throwable? = null) : Exc
 class AmountOverflowException(msg: String? = null, cause: Throwable? = null) : 
Exception(msg, cause)
 
 @Serializable(with = KotlinXAmountSerializer::class)
-@JsonSerialize(using = AmountSerializer::class)
-@JsonDeserialize(using = AmountDeserializer::class)
 data class Amount(
     /**
      * name of the currency using either a three-character ISO 4217 currency 
code,
@@ -70,29 +57,21 @@ data class Amount(
 
         private const val FRACTIONAL_BASE: Int = 100000000 // 1e8
 
-        @Suppress("unused")
-        private val REGEX = 
Regex("""^[-_*A-Za-z0-9]{1,12}:([0-9]+)\.?([0-9]+)?$""")
         private val REGEX_CURRENCY = Regex("""^[-_*A-Za-z0-9]{1,12}$""")
-        private val MAX_VALUE = 2.0.pow(52)
+        val MAX_VALUE = 2.0.pow(52).toLong()
         private const val MAX_FRACTION_LENGTH = 8
-        private const val MAX_FRACTION = 99_999_999
+        const val MAX_FRACTION = 99_999_999
 
-        @Throws(AmountParserException::class)
-        @SuppressLint("CheckedExceptions")
         fun zero(currency: String): Amount {
             return Amount(checkCurrency(currency), 0, 0)
         }
 
-        @Throws(AmountParserException::class)
-        @SuppressLint("CheckedExceptions")
         fun fromJSONString(str: String): Amount {
             val split = str.split(":")
             if (split.size != 2) throw AmountParserException("Invalid Amount 
Format")
             return fromString(split[0], split[1])
         }
 
-        @Throws(AmountParserException::class)
-        @SuppressLint("CheckedExceptions")
         fun fromString(currency: String, str: String): Amount {
             // value
             val valueSplit = str.split(".")
@@ -110,31 +89,23 @@ data class Amount(
             return Amount(checkCurrency(currency), value, fraction)
         }
 
-        @Throws(AmountParserException::class)
-        @SuppressLint("CheckedExceptions")
-        fun fromJsonObject(json: JSONObject): Amount {
-            val currency = checkCurrency(json.optString("currency"))
-            val value = checkValue(json.optString("value").toLongOrNull())
-            val fraction = 
checkFraction(json.optString("fraction").toIntOrNull())
-            return Amount(currency, value, fraction)
-        }
+        fun min(currency: String): Amount = Amount(currency, 0, 1)
+        fun max(currency: String): Amount = Amount(currency, MAX_VALUE, 
MAX_FRACTION)
+
 
-        @Throws(AmountParserException::class)
-        private fun checkCurrency(currency: String): String {
+        internal fun checkCurrency(currency: String): String {
             if (!REGEX_CURRENCY.matches(currency))
                 throw AmountParserException("Invalid currency: $currency")
             return currency
         }
 
-        @Throws(AmountParserException::class)
-        private fun checkValue(value: Long?): Long {
+        internal fun checkValue(value: Long?): Long {
             if (value == null || value > MAX_VALUE)
                 throw AmountParserException("Value $value greater than 
$MAX_VALUE")
             return value
         }
 
-        @Throws(AmountParserException::class)
-        private fun checkFraction(fraction: Int?): Int {
+        internal fun checkFraction(fraction: Int?): Int {
             if (fraction == null || fraction > MAX_FRACTION)
                 throw AmountParserException("Fraction $fraction greater than 
$MAX_FRACTION")
             return fraction
@@ -153,25 +124,23 @@ data class Amount(
             "$value.$fractionStr"
         }
 
-    @Throws(AmountOverflowException::class)
     operator fun plus(other: Amount): Amount {
         check(currency == other.currency) { "Can only subtract from same 
currency" }
-        val resultValue = value + other.value + floorDiv(fraction + 
other.fraction, FRACTIONAL_BASE)
+        val resultValue = value + other.value + floor((fraction + 
other.fraction).toDouble() / FRACTIONAL_BASE).toLong()
         if (resultValue > MAX_VALUE)
             throw AmountOverflowException()
         val resultFraction = (fraction + other.fraction) % FRACTIONAL_BASE
         return Amount(currency, resultValue, resultFraction)
     }
 
-    @Throws(AmountOverflowException::class)
     operator fun times(factor: Int): Amount {
+        // TODO consider replacing with a faster implementation
         if (factor == 0) return zero(currency)
         var result = this
         for (i in 1 until factor) result += this
         return result
     }
 
-    @Throws(AmountOverflowException::class)
     operator fun minus(other: Amount): Amount {
         check(currency == other.currency) { "Can only subtract from same 
currency" }
         var resultValue = value
@@ -227,20 +196,3 @@ object KotlinXAmountSerializer: KSerializer<Amount> {
         return Amount.fromJSONString(decoder.decodeString())
     }
 }
-
-class AmountSerializer : StdSerializer<Amount>(Amount::class.java) {
-    override fun serialize(value: Amount, gen: JsonGenerator, provider: 
SerializerProvider) {
-        gen.writeString(value.toJSONString())
-    }
-}
-
-class AmountDeserializer : StdDeserializer<Amount>(Amount::class.java) {
-    override fun deserialize(p: JsonParser, ctxt: DeserializationContext): 
Amount {
-        val node = p.codec.readValue(p, String::class.java)
-        try {
-            return Amount.fromJSONString(node)
-        } catch (e: AmountParserException) {
-            throw JsonMappingException(p, "Error parsing Amount", e)
-        }
-    }
-}
diff --git a/taler-kotlin-common/src/commonMain/kotlin/net/taler/common/Time.kt 
b/taler-kotlin-common/src/commonMain/kotlin/net/taler/common/Time.kt
new file mode 100644
index 0000000..962e004
--- /dev/null
+++ b/taler-kotlin-common/src/commonMain/kotlin/net/taler/common/Time.kt
@@ -0,0 +1,81 @@
+/*
+ * 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.common
+
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+import net.taler.common.Duration.Companion.FOREVER
+import kotlin.math.max
+
+expect fun nowMillis(): Long
+
+@Serializable
+data class Timestamp(
+    @SerialName("t_ms")
+    val ms: Long
+) : Comparable<Timestamp> {
+
+    companion object {
+        const val NEVER: Long = -1  // TODO or UINT64_MAX?
+        fun now(): Timestamp = Timestamp(nowMillis())
+    }
+
+    /**
+     * Returns a copy of this [Timestamp] rounded to seconds.
+     */
+    fun truncateSeconds(): Timestamp {
+        if (ms == NEVER) return Timestamp(ms)
+        return Timestamp((ms / 1000L) * 1000L)
+    }
+
+    operator fun minus(other: Timestamp): Duration = when {
+        ms == NEVER -> Duration(FOREVER)
+        other.ms == NEVER -> throw Error("Invalid argument for timestamp 
comparision")
+        ms < other.ms -> Duration(0)
+        else -> Duration(ms - other.ms)
+    }
+
+    operator fun minus(other: Duration): Timestamp = when {
+        ms == NEVER -> this
+        other.ms == FOREVER -> Timestamp(0)
+        else -> Timestamp(max(0, ms - other.ms))
+    }
+
+    override fun compareTo(other: Timestamp): Int {
+        return if (ms == NEVER) {
+            if (other.ms == NEVER) 0
+            else 1
+        } else {
+            if (other.ms == NEVER) -1
+            else ms.compareTo(other.ms)
+        }
+    }
+
+}
+
+@Serializable
+data class Duration(
+    /**
+     * Duration in milliseconds.
+     */
+    @SerialName("d_ms")
+    val ms: Long
+) {
+    companion object {
+        const val FOREVER: Long = -1  // TODO or UINT64_MAX?
+    }
+}
diff --git a/taler-kotlin-common/src/main/java/net/taler/common/Version.kt 
b/taler-kotlin-common/src/commonMain/kotlin/net/taler/common/Version.kt
similarity index 100%
rename from taler-kotlin-common/src/main/java/net/taler/common/Version.kt
rename to taler-kotlin-common/src/commonMain/kotlin/net/taler/common/Version.kt
diff --git a/taler-kotlin-common/src/test/java/net/taler/common/AmountTest.kt 
b/taler-kotlin-common/src/commonTest/kotlin/net/taler/common/AmountTest.kt
similarity index 65%
rename from taler-kotlin-common/src/test/java/net/taler/common/AmountTest.kt
rename to 
taler-kotlin-common/src/commonTest/kotlin/net/taler/common/AmountTest.kt
index 97d9667..e184307 100644
--- a/taler-kotlin-common/src/test/java/net/taler/common/AmountTest.kt
+++ b/taler-kotlin-common/src/commonTest/kotlin/net/taler/common/AmountTest.kt
@@ -16,20 +16,26 @@
 
 package net.taler.common
 
-import com.fasterxml.jackson.databind.ObjectMapper
-import com.fasterxml.jackson.module.kotlin.KotlinModule
-import com.fasterxml.jackson.module.kotlin.readValue
-import org.json.JSONObject
-import org.junit.Assert.assertEquals
-import org.junit.Assert.assertFalse
-import org.junit.Assert.assertTrue
-import org.junit.Assert.fail
-import org.junit.Test
+import kotlin.random.Random
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertFalse
+import kotlin.test.assertTrue
+import kotlin.test.fail
 
 class AmountTest {
 
+    companion object {
+        fun getRandomAmount() = getRandomAmount(getRandomString(1, 
Random.nextInt(1, 12)))
+        fun getRandomAmount(currency: String): Amount {
+            val value = Random.nextLong(0, Amount.MAX_VALUE)
+            val fraction = Random.nextInt(0, Amount.MAX_FRACTION)
+            return Amount(currency, value, fraction)
+        }
+    }
+
     @Test
-    fun `test fromJSONString() works`() {
+    fun testFromJSONString() {
         var str = "TESTKUDOS:23.42"
         var amount = Amount.fromJSONString(str)
         assertEquals(str, amount.toJSONString())
@@ -56,7 +62,7 @@ class AmountTest {
     }
 
     @Test
-    fun `test fromJSONString() accepts max values, rejects above`() {
+    fun testFromJSONStringAcceptsMaxValuesRejectsAbove() {
         val maxValue = 4503599627370496
         val str = "TESTKUDOS123:$maxValue.99999999"
         val amount = Amount.fromJSONString(str)
@@ -82,35 +88,7 @@ class AmountTest {
     }
 
     @Test
-    fun `test JSON deserialization()`() {
-        val mapper = ObjectMapper().registerModule(KotlinModule())
-        var str = "TESTKUDOS:23.42"
-        var amount: Amount = mapper.readValue("\"$str\"")
-        assertEquals(str, amount.toJSONString())
-        assertEquals("TESTKUDOS", amount.currency)
-        assertEquals(23, amount.value)
-        assertEquals((0.42 * 1e8).toInt(), amount.fraction)
-        assertEquals("23.42 TESTKUDOS", amount.toString())
-
-        str = "EUR:500000000.00000001"
-        amount = mapper.readValue("\"$str\"")
-        assertEquals(str, amount.toJSONString())
-        assertEquals("EUR", amount.currency)
-        assertEquals(500000000, amount.value)
-        assertEquals(1, amount.fraction)
-        assertEquals("500000000.00000001 EUR", amount.toString())
-
-        str = "EUR:1500000000.00000003"
-        amount = mapper.readValue("\"$str\"")
-        assertEquals(str, amount.toJSONString())
-        assertEquals("EUR", amount.currency)
-        assertEquals(1500000000, amount.value)
-        assertEquals(3, amount.fraction)
-        assertEquals("1500000000.00000003 EUR", amount.toString())
-    }
-
-    @Test
-    fun `test fromJSONString() rejections`() {
+    fun testFromJSONStringRejections() {
         assertThrows<AmountParserException> {
             Amount.fromJSONString("TESTKUDOS:0,5")
         }
@@ -132,71 +110,7 @@ class AmountTest {
     }
 
     @Test
-    fun `test fromJsonObject() works`() {
-        val map = mapOf(
-            "currency" to "TESTKUDOS",
-            "value" to "23",
-            "fraction" to "42000000"
-        )
-
-        val amount = Amount.fromJsonObject(JSONObject(map))
-        assertEquals("TESTKUDOS:23.42", amount.toJSONString())
-        assertEquals("TESTKUDOS", amount.currency)
-        assertEquals(23, amount.value)
-        assertEquals(42000000, amount.fraction)
-        assertEquals("23.42 TESTKUDOS", amount.toString())
-    }
-
-    @Test
-    fun `test fromJsonObject() accepts max values, rejects above`() {
-        val maxValue = 4503599627370496
-        val maxFraction = 99999999
-        var map = mapOf(
-            "currency" to "TESTKUDOS123",
-            "value" to "$maxValue",
-            "fraction" to "$maxFraction"
-        )
-
-        val amount = Amount.fromJsonObject(JSONObject(map))
-        assertEquals("TESTKUDOS123:$maxValue.$maxFraction", 
amount.toJSONString())
-        assertEquals("TESTKUDOS123", amount.currency)
-        assertEquals(maxValue, amount.value)
-        assertEquals(maxFraction, amount.fraction)
-        assertEquals("$maxValue.$maxFraction TESTKUDOS123", amount.toString())
-
-        // longer currency not accepted
-        assertThrows<AmountParserException>("longer currency was accepted") {
-            map = mapOf(
-                "currency" to "TESTKUDOS1234",
-                "value" to "$maxValue",
-                "fraction" to "$maxFraction"
-            )
-            Amount.fromJsonObject(JSONObject(map))
-        }
-
-        // max value + 1 not accepted
-        assertThrows<AmountParserException>("max value + 1 was accepted") {
-            map = mapOf(
-                "currency" to "TESTKUDOS123",
-                "value" to "${maxValue + 1}",
-                "fraction" to "$maxFraction"
-            )
-            Amount.fromJsonObject(JSONObject(map))
-        }
-
-        // max fraction + 1 not accepted
-        assertThrows<AmountParserException>("max fraction + 1 was accepted") {
-            map = mapOf(
-                "currency" to "TESTKUDOS123",
-                "value" to "$maxValue",
-                "fraction" to "${maxFraction + 1}"
-            )
-            Amount.fromJsonObject(JSONObject(map))
-        }
-    }
-
-    @Test
-    fun `test addition`() {
+    fun testAddition() {
         assertEquals(
             Amount.fromJSONString("EUR:2"),
             Amount.fromJSONString("EUR:1") + Amount.fromJSONString("EUR:1")
@@ -218,7 +132,7 @@ class AmountTest {
     }
 
     @Test
-    fun `test times`() {
+    fun testTimes() {
         assertEquals(
             Amount.fromJSONString("EUR:2"),
             Amount.fromJSONString("EUR:2") * 1
@@ -231,6 +145,12 @@ class AmountTest {
             Amount.fromJSONString("EUR:4.5"),
             Amount.fromJSONString("EUR:1.5") * 3
         )
+        assertEquals(Amount.fromJSONString("EUR:0"), 
Amount.fromJSONString("EUR:1.11") * 0)
+        assertEquals(Amount.fromJSONString("EUR:1.11"), 
Amount.fromJSONString("EUR:1.11") * 1)
+        assertEquals(Amount.fromJSONString("EUR:2.22"), 
Amount.fromJSONString("EUR:1.11") * 2)
+        assertEquals(Amount.fromJSONString("EUR:3.33"), 
Amount.fromJSONString("EUR:1.11") * 3)
+        assertEquals(Amount.fromJSONString("EUR:4.44"), 
Amount.fromJSONString("EUR:1.11") * 4)
+        assertEquals(Amount.fromJSONString("EUR:5.55"), 
Amount.fromJSONString("EUR:1.11") * 5)
         assertEquals(
             Amount.fromJSONString("EUR:1500000000.00000003"),
             Amount.fromJSONString("EUR:500000000.00000001") * 3
@@ -241,7 +161,7 @@ class AmountTest {
     }
 
     @Test
-    fun `test subtraction`() {
+    fun testSubtraction() {
         assertEquals(
             Amount.fromJSONString("EUR:0"),
             Amount.fromJSONString("EUR:1") - Amount.fromJSONString("EUR:1")
@@ -263,7 +183,7 @@ class AmountTest {
     }
 
     @Test
-    fun `test isZero()`() {
+    fun testIsZero() {
         assertTrue(Amount.zero("EUR").isZero())
         assertTrue(Amount.fromJSONString("EUR:0").isZero())
         assertTrue(Amount.fromJSONString("EUR:0.0").isZero())
@@ -276,14 +196,17 @@ class AmountTest {
     }
 
     @Test
-    fun `test comparision`() {
+    fun testComparision() {
         assertTrue(Amount.fromJSONString("EUR:0") <= 
Amount.fromJSONString("EUR:0"))
         assertTrue(Amount.fromJSONString("EUR:0") <= 
Amount.fromJSONString("EUR:0.00000001"))
         assertTrue(Amount.fromJSONString("EUR:0") < 
Amount.fromJSONString("EUR:0.00000001"))
         assertTrue(Amount.fromJSONString("EUR:0") < 
Amount.fromJSONString("EUR:1"))
-        assertTrue(Amount.fromJSONString("EUR:0") == 
Amount.fromJSONString("EUR:0"))
-        assertTrue(Amount.fromJSONString("EUR:42") == 
Amount.fromJSONString("EUR:42"))
-        assertTrue(Amount.fromJSONString("EUR:42.00000001") == 
Amount.fromJSONString("EUR:42.00000001"))
+        assertEquals(Amount.fromJSONString("EUR:0"), 
Amount.fromJSONString("EUR:0"))
+        assertEquals(Amount.fromJSONString("EUR:42"), 
Amount.fromJSONString("EUR:42"))
+        assertEquals(
+            Amount.fromJSONString("EUR:42.00000001"),
+            Amount.fromJSONString("EUR:42.00000001")
+        )
         assertTrue(Amount.fromJSONString("EUR:42.00000001") >= 
Amount.fromJSONString("EUR:42.00000001"))
         assertTrue(Amount.fromJSONString("EUR:42.00000002") >= 
Amount.fromJSONString("EUR:42.00000001"))
         assertTrue(Amount.fromJSONString("EUR:42.00000002") > 
Amount.fromJSONString("EUR:42.00000001"))
diff --git 
a/merchant-terminal/src/main/java/net/taler/merchantpos/payment/Payment.kt 
b/taler-kotlin-common/src/commonTest/kotlin/net/taler/common/TestUtils.kt
similarity index 67%
copy from 
merchant-terminal/src/main/java/net/taler/merchantpos/payment/Payment.kt
copy to taler-kotlin-common/src/commonTest/kotlin/net/taler/common/TestUtils.kt
index 9200ced..e3a6c17 100644
--- a/merchant-terminal/src/main/java/net/taler/merchantpos/payment/Payment.kt
+++ b/taler-kotlin-common/src/commonTest/kotlin/net/taler/common/TestUtils.kt
@@ -14,16 +14,13 @@
  * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 
-package net.taler.merchantpos.payment
+package net.taler.common
 
-import net.taler.merchantpos.order.Order
+import kotlin.random.Random
 
-data class Payment(
-    val order: Order,
-    val summary: String,
-    val currency: String,
-    val orderId: String? = null,
-    val talerPayUri: String? = null,
-    val paid: Boolean = false,
-    val error: String? = null
-)
+private val charPool: List<Char> = ('a'..'z') + ('A'..'Z') + ('0'..'9')
+fun getRandomString(minLength: Int = 1, maxLength: Int = Random.nextInt(0, 
1337)) =
+    (minLength..maxLength)
+        .map { Random.nextInt(0, charPool.size) }
+        .map(charPool::get)
+        .joinToString("")
diff --git a/taler-kotlin-common/src/test/java/net/taler/common/VersionTest.kt 
b/taler-kotlin-common/src/commonTest/kotlin/net/taler/common/VersionTest.kt
similarity index 95%
rename from taler-kotlin-common/src/test/java/net/taler/common/VersionTest.kt
rename to 
taler-kotlin-common/src/commonTest/kotlin/net/taler/common/VersionTest.kt
index 70f30eb..f4f17ea 100644
--- a/taler-kotlin-common/src/test/java/net/taler/common/VersionTest.kt
+++ b/taler-kotlin-common/src/commonTest/kotlin/net/taler/common/VersionTest.kt
@@ -16,9 +16,9 @@
 
 package net.taler.common
 
-import org.junit.Assert.assertEquals
-import org.junit.Assert.assertNull
-import org.junit.Test
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertNull
 
 class VersionTest {
 
diff --git 
a/merchant-terminal/src/main/java/net/taler/merchantpos/payment/Payment.kt 
b/taler-kotlin-common/src/jsMain/kotlin/net/taler/common/Time.kt
similarity index 70%
copy from 
merchant-terminal/src/main/java/net/taler/merchantpos/payment/Payment.kt
copy to taler-kotlin-common/src/jsMain/kotlin/net/taler/common/Time.kt
index 9200ced..b114022 100644
--- a/merchant-terminal/src/main/java/net/taler/merchantpos/payment/Payment.kt
+++ b/taler-kotlin-common/src/jsMain/kotlin/net/taler/common/Time.kt
@@ -14,16 +14,10 @@
  * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 
-package net.taler.merchantpos.payment
+package net.taler.common
 
-import net.taler.merchantpos.order.Order
+import kotlin.js.Date
 
-data class Payment(
-    val order: Order,
-    val summary: String,
-    val currency: String,
-    val orderId: String? = null,
-    val talerPayUri: String? = null,
-    val paid: Boolean = false,
-    val error: String? = null
-)
+actual fun nowMillis(): Long {
+    return Date().getMilliseconds().toLong()
+}
diff --git 
a/merchant-terminal/src/main/java/net/taler/merchantpos/payment/Payment.kt 
b/taler-kotlin-common/src/jvmMain/kotlin/net/taler/common/Time.kt
similarity index 70%
copy from 
merchant-terminal/src/main/java/net/taler/merchantpos/payment/Payment.kt
copy to taler-kotlin-common/src/jvmMain/kotlin/net/taler/common/Time.kt
index 9200ced..6cd9040 100644
--- a/merchant-terminal/src/main/java/net/taler/merchantpos/payment/Payment.kt
+++ b/taler-kotlin-common/src/jvmMain/kotlin/net/taler/common/Time.kt
@@ -14,16 +14,8 @@
  * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 
-package net.taler.merchantpos.payment
+package net.taler.common
 
-import net.taler.merchantpos.order.Order
-
-data class Payment(
-    val order: Order,
-    val summary: String,
-    val currency: String,
-    val orderId: String? = null,
-    val talerPayUri: String? = null,
-    val paid: Boolean = false,
-    val error: String? = null
-)
+actual fun nowMillis(): Long {
+    return System.currentTimeMillis()
+}
diff --git 
a/merchant-terminal/src/main/java/net/taler/merchantpos/payment/Payment.kt 
b/taler-kotlin-common/src/nativeMain/kotlin/net/taler/common/Time.kt
similarity index 70%
copy from 
merchant-terminal/src/main/java/net/taler/merchantpos/payment/Payment.kt
copy to taler-kotlin-common/src/nativeMain/kotlin/net/taler/common/Time.kt
index 9200ced..8a4091a 100644
--- a/merchant-terminal/src/main/java/net/taler/merchantpos/payment/Payment.kt
+++ b/taler-kotlin-common/src/nativeMain/kotlin/net/taler/common/Time.kt
@@ -14,16 +14,10 @@
  * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 
-package net.taler.merchantpos.payment
+package net.taler.common
 
-import net.taler.merchantpos.order.Order
+import kotlin.system.getTimeMillis
 
-data class Payment(
-    val order: Order,
-    val summary: String,
-    val currency: String,
-    val orderId: String? = null,
-    val talerPayUri: String? = null,
-    val paid: Boolean = false,
-    val error: String? = null
-)
+actual fun nowMillis(): Long {
+    return getTimeMillis()
+}
diff --git a/wallet/.gitlab-ci.yml b/wallet/.gitlab-ci.yml
index 56768f7..c417aa9 100644
--- a/wallet/.gitlab-ci.yml
+++ b/wallet/.gitlab-ci.yml
@@ -4,6 +4,7 @@ wallet_test:
     changes:
       - wallet/**/*
       - taler-kotlin-common/**/*
+      - taler-kotlin-android/**/*
       - build.gradle
   script: ./gradlew :wallet:check :wallet:assembleRelease
   artifacts:
diff --git a/wallet/build.gradle b/wallet/build.gradle
index 5b28c6c..1761018 100644
--- a/wallet/build.gradle
+++ b/wallet/build.gradle
@@ -23,7 +23,7 @@ plugins {
     id "de.undercouch.download"
 }
 
-def walletCoreVersion = "v0.7.1-dev.10"
+def walletCoreVersion = "v0.7.1-dev.16"
 
 static def versionCodeEpoch() {
     return (new Date().getTime() / 1000).toInteger()
@@ -47,7 +47,7 @@ android {
         minSdkVersion 24
         targetSdkVersion 29
         versionCode 6
-        versionName "0.7.1.dev.10"
+        versionName "0.7.1.dev.16"
         testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
         buildConfigField "String", "WALLET_CORE_VERSION", 
"\"$walletCoreVersion\""
     }
@@ -83,6 +83,10 @@ android {
         jvmTarget = "1.8"
     }
 
+    packagingOptions {
+        exclude("META-INF/*.kotlin_module")
+    }
+
     lintOptions {
         abortOnError true
         ignoreWarnings false
@@ -93,7 +97,7 @@ android {
 }
 
 dependencies {
-    implementation project(":taler-kotlin-common")
+    implementation project(":taler-kotlin-android")
     implementation project(":anastasis-ui")
     implementation 'net.taler:akono:0.1'
 
diff --git a/wallet/src/main/java/net/taler/wallet/MainActivity.kt 
b/wallet/src/main/java/net/taler/wallet/MainActivity.kt
index fdb8cf8..c7c31ca 100644
--- a/wallet/src/main/java/net/taler/wallet/MainActivity.kt
+++ b/wallet/src/main/java/net/taler/wallet/MainActivity.kt
@@ -81,7 +81,7 @@ class MainActivity : AppCompatActivity(), 
OnNavigationItemSelectedListener,
 
         setSupportActionBar(toolbar)
         val appBarConfiguration = AppBarConfiguration(
-            setOf(R.id.nav_main, R.id.nav_settings, 
R.id.nav_pending_operations, R.id.nav_history),
+            setOf(R.id.nav_main, R.id.nav_settings, 
R.id.nav_pending_operations),
             drawer_layout
         )
         toolbar.setupWithNavController(nav, appBarConfiguration)
@@ -122,7 +122,6 @@ class MainActivity : AppCompatActivity(), 
OnNavigationItemSelectedListener,
             R.id.nav_home -> nav.navigate(R.id.nav_main)
             R.id.nav_settings -> nav.navigate(R.id.nav_settings)
             R.id.nav_pending_operations -> 
nav.navigate(R.id.nav_pending_operations)
-            R.id.nav_history -> nav.navigate(R.id.nav_history)
         }
         drawer_layout.closeDrawer(START)
         return true
@@ -160,7 +159,7 @@ class MainActivity : AppCompatActivity(), 
OnNavigationItemSelectedListener,
                 Log.v(TAG, "navigating!")
                 // there's more than one entry point, so use global action
                 nav.navigate(R.id.action_global_promptWithdraw)
-                model.withdrawManager.getWithdrawalInfo(url)
+                model.withdrawManager.getWithdrawalDetails(url)
             }
             url.toLowerCase(ROOT).startsWith("taler://refund/") -> {
                 model.showProgressBar.value = true
diff --git a/wallet/src/main/java/net/taler/wallet/MainFragment.kt 
b/wallet/src/main/java/net/taler/wallet/MainFragment.kt
index a735987..d5bd3fc 100644
--- a/wallet/src/main/java/net/taler/wallet/MainFragment.kt
+++ b/wallet/src/main/java/net/taler/wallet/MainFragment.kt
@@ -49,7 +49,7 @@ class MainFragment : Fragment() {
 
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
         model.balances.observe(viewLifecycleOwner, Observer {
-            onBalancesChanged(it.values.toList())
+            onBalancesChanged(it)
         })
         model.transactionsEvent.observe(viewLifecycleOwner, EventObserver { 
currency ->
             // we only need to navigate to a dedicated list, when in 
multi-currency mode
diff --git a/wallet/src/main/java/net/taler/wallet/MainViewModel.kt 
b/wallet/src/main/java/net/taler/wallet/MainViewModel.kt
index 46f5021..ffa2dea 100644
--- a/wallet/src/main/java/net/taler/wallet/MainViewModel.kt
+++ b/wallet/src/main/java/net/taler/wallet/MainViewModel.kt
@@ -24,17 +24,19 @@ import androidx.lifecycle.LiveData
 import androidx.lifecycle.MutableLiveData
 import androidx.lifecycle.distinctUntilChanged
 import androidx.lifecycle.viewModelScope
+import com.fasterxml.jackson.databind.DeserializationFeature
 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 net.taler.common.Amount
+import net.taler.common.AmountMixin
 import net.taler.common.Event
 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.exchanges.ExchangeManager
-import net.taler.wallet.history.DevHistoryManager
 import net.taler.wallet.payment.PaymentManager
 import net.taler.wallet.pending.PendingOperationsManager
 import net.taler.wallet.refund.RefundManager
@@ -55,8 +57,8 @@ private val transactionNotifications = listOf(
 
 class MainViewModel(val app: Application) : AndroidViewModel(app) {
 
-    private val mBalances = MutableLiveData<Map<String, BalanceItem>>()
-    val balances: LiveData<Map<String, BalanceItem>> = 
mBalances.distinctUntilChanged()
+    private val mBalances = MutableLiveData<List<BalanceItem>>()
+    val balances: LiveData<List<BalanceItem>> = 
mBalances.distinctUntilChanged()
 
     val devMode = MutableLiveData(BuildConfig.DEBUG)
     val showProgressBar = MutableLiveData<Boolean>()
@@ -85,7 +87,6 @@ class MainViewModel(val app: Application) : 
AndroidViewModel(app) {
             // refresh pending ops and history with each notification
             if (devMode.value == true) {
                 pendingOperationsManager.getPending()
-                historyManager.loadHistory()
             }
         }
     }
@@ -93,13 +94,13 @@ 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)
+        .enable(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT)
 
-    val withdrawManager = WithdrawManager(walletBackendApi)
+    val withdrawManager = WithdrawManager(walletBackendApi, mapper)
     val paymentManager = PaymentManager(walletBackendApi, mapper)
     val pendingOperationsManager: PendingOperationsManager =
         PendingOperationsManager(walletBackendApi)
-    val historyManager: DevHistoryManager =
-        DevHistoryManager(walletBackendApi, viewModelScope, mapper)
     val transactionManager: TransactionManager =
         TransactionManager(walletBackendApi, viewModelScope, mapper)
     val refundManager = RefundManager(walletBackendApi)
@@ -123,26 +124,13 @@ class MainViewModel(val app: Application) : 
AndroidViewModel(app) {
     @UiThread
     fun loadBalances() {
         showProgressBar.value = true
-        walletBackendApi.sendRequest("getBalances", null) { isError, result ->
+        walletBackendApi.sendRequest("getBalances") { isError, result ->
             if (isError) {
                 Log.e(TAG, "Error retrieving balances: ${result.toString(2)}")
                 return@sendRequest
             }
-            val balanceMap = HashMap<String, BalanceItem>()
-            val byCurrency = result.getJSONObject("byCurrency")
-            val currencyList = byCurrency.keys().asSequence().toList().sorted()
-            for (currency in currencyList) {
-                val jsonAmount = byCurrency.getJSONObject(currency)
-                    .getJSONObject("available")
-                val amount = Amount.fromJsonObject(jsonAmount)
-                val jsonAmountIncoming = byCurrency.getJSONObject(currency)
-                    .getJSONObject("pendingIncoming")
-                val amountIncoming = Amount.fromJsonObject(jsonAmountIncoming)
-                val hasPending = transactionManager.hasPending(currency)
-                balanceMap[currency] = BalanceItem(amount, amountIncoming, 
hasPending)
-            }
-            mBalances.postValue(balanceMap)
-            showProgressBar.postValue(false)
+            mBalances.value = mapper.readValue(result.getString("balances"))
+            showProgressBar.value = false
         }
     }
 
@@ -156,17 +144,17 @@ class MainViewModel(val app: Application) : 
AndroidViewModel(app) {
 
     @UiThread
     fun dangerouslyReset() {
-        walletBackendApi.sendRequest("reset", null)
+        walletBackendApi.sendRequest("reset")
         withdrawManager.testWithdrawalInProgress.value = false
-        mBalances.value = emptyMap()
+        mBalances.value = emptyList()
     }
 
     fun startTunnel() {
-        walletBackendApi.sendRequest("startTunnel", null)
+        walletBackendApi.sendRequest("startTunnel")
     }
 
     fun stopTunnel() {
-        walletBackendApi.sendRequest("stopTunnel", null)
+        walletBackendApi.sendRequest("stopTunnel")
     }
 
     fun tunnelResponse(resp: String) {
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 3ffcd7b..51b3419 100644
--- a/wallet/src/main/java/net/taler/wallet/backend/WalletBackendApi.kt
+++ b/wallet/src/main/java/net/taler/wallet/backend/WalletBackendApi.kt
@@ -30,7 +30,7 @@ import android.util.Log
 import android.util.SparseArray
 import org.json.JSONObject
 import java.lang.ref.WeakReference
-import java.util.*
+import java.util.LinkedList
 
 class WalletBackendApi(
     private val app: Application,
@@ -115,7 +115,7 @@ class WalletBackendApi(
 
     fun sendRequest(
         operation: String,
-        args: JSONObject?,
+        args: JSONObject? = null,
         onResponse: (isError: Boolean, message: JSONObject) -> Unit = { _, _ 
-> }
     ) {
         val requestID = nextRequestID++
diff --git 
a/wallet/src/main/java/net/taler/wallet/backend/WalletBackendService.kt 
b/wallet/src/main/java/net/taler/wallet/backend/WalletBackendService.kt
index c810054..f39a3e7 100644
--- a/wallet/src/main/java/net/taler/wallet/backend/WalletBackendService.kt
+++ b/wallet/src/main/java/net/taler/wallet/backend/WalletBackendService.kt
@@ -30,7 +30,7 @@ import net.taler.wallet.BuildConfig.WALLET_CORE_VERSION
 import net.taler.wallet.HostCardEmulatorService
 import org.json.JSONObject
 import java.lang.ref.WeakReference
-import java.util.*
+import java.util.LinkedList
 import java.util.concurrent.ConcurrentHashMap
 import kotlin.system.exitProcess
 
@@ -56,9 +56,10 @@ class WalletBackendService : Service() {
     private val subscribers = LinkedList<Messenger>()
 
     override fun onCreate() {
-        val talerWalletAndroidCode = 
assets.open("taler-wallet-android-$WALLET_CORE_VERSION.js").use {
-            it.readBytes().toString(Charsets.UTF_8)
-        }
+        val talerWalletAndroidCode =
+            assets.open("taler-wallet-android-$WALLET_CORE_VERSION.js").use {
+                it.readBytes().toString(Charsets.UTF_8)
+            }
 
 
         Log.i(TAG, "onCreate in wallet backend service")
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 be50364..c090e75 100644
--- a/wallet/src/main/java/net/taler/wallet/balances/BalanceAdapter.kt
+++ b/wallet/src/main/java/net/taler/wallet/balances/BalanceAdapter.kt
@@ -28,7 +28,14 @@ import net.taler.common.Amount
 import net.taler.wallet.R
 import net.taler.wallet.balances.BalanceAdapter.BalanceViewHolder
 
-data class BalanceItem(val available: Amount, val pendingIncoming: Amount, val 
hasPending: Boolean)
+data class BalanceItem(
+    val available: Amount,
+    val pendingIncoming: Amount,
+    val pendingOutgoing: Amount
+) {
+    val currency: String get() = available.currency
+    val hasPending: Boolean get() = !pendingIncoming.isZero() || 
!pendingOutgoing.isZero()
+}
 
 class BalanceAdapter(private val listener: BalanceClickListener) : 
Adapter<BalanceViewHolder>() {
 
@@ -65,7 +72,7 @@ class BalanceAdapter(private val listener: 
BalanceClickListener) : Adapter<Balan
 
         fun bind(item: BalanceItem) {
             v.setOnClickListener { 
listener.onBalanceClick(item.available.currency) }
-            currencyView.text = item.available.currency
+            currencyView.text = item.currency
             amountView.text = item.available.amountStr
 
             val amountIncoming = item.pendingIncoming
diff --git a/wallet/src/main/java/net/taler/wallet/balances/BalancesFragment.kt 
b/wallet/src/main/java/net/taler/wallet/balances/BalancesFragment.kt
index 22dd992..2b4d032 100644
--- a/wallet/src/main/java/net/taler/wallet/balances/BalancesFragment.kt
+++ b/wallet/src/main/java/net/taler/wallet/balances/BalancesFragment.kt
@@ -60,7 +60,7 @@ class BalancesFragment : Fragment(),
         }
 
         model.balances.observe(viewLifecycleOwner, Observer {
-            onBalancesChanged(it.values.toList())
+            onBalancesChanged(it)
         })
     }
 
diff --git a/wallet/src/main/java/net/taler/wallet/crypto/Encoding.kt 
b/wallet/src/main/java/net/taler/wallet/crypto/Encoding.kt
deleted file mode 100644
index 25a59be..0000000
--- a/wallet/src/main/java/net/taler/wallet/crypto/Encoding.kt
+++ /dev/null
@@ -1,134 +0,0 @@
-/*
- * This file is part of GNU Taler
- * (C) 2020 Taler Systems S.A.
- *
- * GNU Taler is free software; you can redistribute it and/or modify it under 
the
- * terms of the GNU General Public License as published by the Free Software
- * Foundation; either version 3, or (at your option) any later version.
- *
- * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
- * A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
- */
-
-package net.taler.wallet.crypto
-
-import java.io.ByteArrayOutputStream
-
-class EncodingException : Exception("Invalid encoding")
-
-
-object Base32Crockford {
-
-    private fun ByteArray.getIntAt(index: Int): Int {
-        val x = this[index].toInt()
-        return if (x >= 0) x else (x + 256)
-    }
-
-    private var encTable = "0123456789ABCDEFGHJKMNPQRSTVWXYZ"
-
-    fun encode(data: ByteArray): String {
-        val sb = StringBuilder()
-        val size = data.size
-        var bitBuf = 0
-        var numBits = 0
-        var pos = 0
-        while (pos < size || numBits > 0) {
-            if (pos < size && numBits < 5) {
-                val d = data.getIntAt(pos++)
-                bitBuf = (bitBuf shl 8) or d
-                numBits += 8
-            }
-            if (numBits < 5) {
-                // zero-padding
-                bitBuf = bitBuf shl (5 - numBits)
-                numBits = 5
-            }
-            val v = bitBuf.ushr(numBits - 5) and 31
-            sb.append(encTable[v])
-            numBits -= 5
-        }
-        return sb.toString()
-    }
-
-    fun decode(encoded: String, out: ByteArrayOutputStream) {
-        val size = encoded.length
-        var bitpos = 0
-        var bitbuf = 0
-        var readPosition = 0
-
-        while (readPosition < size || bitpos > 0) {
-            //println("at position $readPosition with bitpos $bitpos")
-            if (readPosition < size) {
-                val v = getValue(encoded[readPosition++])
-                bitbuf = (bitbuf shl 5) or v
-                bitpos += 5
-            }
-            while (bitpos >= 8) {
-                val d = (bitbuf ushr (bitpos - 8)) and 0xFF
-                out.write(d)
-                bitpos -= 8
-            }
-            if (readPosition == size && bitpos > 0) {
-                bitbuf = (bitbuf shl (8 - bitpos)) and 0xFF
-                bitpos = if (bitbuf == 0) 0 else 8
-            }
-        }
-    }
-
-    fun decode(encoded: String): ByteArray {
-        val out = ByteArrayOutputStream()
-        decode(encoded, out)
-        return out.toByteArray()
-    }
-
-    private fun getValue(chr: Char): Int {
-        var a = chr
-        when (a) {
-            'O', 'o' -> a = '0'
-            'i', 'I', 'l', 'L' -> a = '1'
-            'u', 'U' -> a = 'V'
-        }
-        if (a in '0'..'9')
-            return a - '0'
-        if (a in 'a'..'z')
-            a = Character.toUpperCase(a)
-        var dec = 0
-        if (a in 'A'..'Z') {
-            if ('I' < a) dec++
-            if ('L' < a) dec++
-            if ('O' < a) dec++
-            if ('U' < a) dec++
-            return a - 'A' + 10 - dec
-        }
-        throw EncodingException()
-    }
-
-    /**
-     * Compute the length of the resulting string when encoding data of the 
given size
-     * in bytes.
-     *
-     * @param dataSize size of the data to encode in bytes
-     * @return size of the string that would result from encoding
-     */
-    @Suppress("unused")
-    fun calculateEncodedStringLength(dataSize: Int): Int {
-        return (dataSize * 8 + 4) / 5
-    }
-
-    /**
-     * Compute the length of the resulting data in bytes when decoding a 
(valid) string of the
-     * given size.
-     *
-     * @param stringSize size of the string to decode
-     * @return size of the resulting data in bytes
-     */
-    @Suppress("unused")
-    fun calculateDecodedDataLength(stringSize: Int): Int {
-        return stringSize * 5 / 8
-    }
-}
-
diff --git a/merchant-lib/src/main/java/net/taler/merchantlib/ConfigResponse.kt 
b/wallet/src/main/java/net/taler/wallet/exchanges/ExchangeFees.kt
similarity index 54%
copy from merchant-lib/src/main/java/net/taler/merchantlib/ConfigResponse.kt
copy to wallet/src/main/java/net/taler/wallet/exchanges/ExchangeFees.kt
index 49164e6..a026283 100644
--- a/merchant-lib/src/main/java/net/taler/merchantlib/ConfigResponse.kt
+++ b/wallet/src/main/java/net/taler/wallet/exchanges/ExchangeFees.kt
@@ -14,21 +14,31 @@
  * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 
-package net.taler.merchantlib
+package net.taler.wallet.exchanges
 
-import kotlinx.serialization.Serializable
+import net.taler.common.Amount
+import net.taler.common.Timestamp
 
-@Serializable
-data class ConfigResponse(
-    /**
-     * libtool-style representation of the Merchant protocol version, see
-     * 
https://www.gnu.org/software/libtool/manual/html_node/Versioning.html#Versioning
-     * The format is "current:revision:age".
-     */
-    val version: String,
+data class CoinFee(
+    val coin: Amount,
+    val quantity: Int,
+    val feeDeposit: Amount,
+    val feeRefresh: Amount,
+    val feeRefund: Amount,
+    val feeWithdraw: Amount
+)
+
+data class WireFee(
+    val start: Timestamp,
+    val end: Timestamp,
+    val wireFee: Amount,
+    val closingFee: Amount
+)
 
-    /**
-    Currency supported by this backend.
-     */
-    val currency: String
+data class ExchangeFees(
+    val withdrawFee: Amount,
+    val overhead: Amount,
+    val earliestDepositExpiration: Timestamp,
+    val coinFees: List<CoinFee>,
+    val wireFees: List<WireFee>
 )
diff --git a/wallet/src/main/java/net/taler/wallet/exchanges/ExchangeManager.kt 
b/wallet/src/main/java/net/taler/wallet/exchanges/ExchangeManager.kt
index 41c8f2c..9d31b5f 100644
--- a/wallet/src/main/java/net/taler/wallet/exchanges/ExchangeManager.kt
+++ b/wallet/src/main/java/net/taler/wallet/exchanges/ExchangeManager.kt
@@ -21,7 +21,6 @@ import androidx.lifecycle.LiveData
 import androidx.lifecycle.MutableLiveData
 import com.fasterxml.jackson.databind.ObjectMapper
 import com.fasterxml.jackson.module.kotlin.readValue
-import net.taler.common.Amount
 import net.taler.common.Event
 import net.taler.common.toEvent
 import net.taler.wallet.TAG
@@ -46,7 +45,7 @@ class ExchangeManager(
 
     private fun list(): LiveData<List<ExchangeItem>> {
         mProgress.value = true
-        walletBackendApi.sendRequest("listExchanges", JSONObject()) { isError, 
result ->
+        walletBackendApi.sendRequest("listExchanges") { isError, result ->
             if (isError) {
                 throw AssertionError("Wallet core failed to return exchanges!")
             } else {
diff --git 
a/wallet/src/main/java/net/taler/wallet/withdraw/SelectExchangeFragment.kt 
b/wallet/src/main/java/net/taler/wallet/exchanges/SelectExchangeFragment.kt
similarity index 94%
rename from 
wallet/src/main/java/net/taler/wallet/withdraw/SelectExchangeFragment.kt
rename to 
wallet/src/main/java/net/taler/wallet/exchanges/SelectExchangeFragment.kt
index 2ade9f2..ef4894d 100644
--- a/wallet/src/main/java/net/taler/wallet/withdraw/SelectExchangeFragment.kt
+++ b/wallet/src/main/java/net/taler/wallet/exchanges/SelectExchangeFragment.kt
@@ -14,7 +14,7 @@
  * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 
-package net.taler.wallet.withdraw
+package net.taler.wallet.exchanges
 
 import android.os.Bundle
 import android.view.LayoutInflater
@@ -33,8 +33,8 @@ import net.taler.common.toRelativeTime
 import net.taler.common.toShortDate
 import net.taler.wallet.MainViewModel
 import net.taler.wallet.R
-import net.taler.wallet.withdraw.CoinFeeAdapter.CoinFeeViewHolder
-import net.taler.wallet.withdraw.WireFeeAdapter.WireFeeViewHolder
+import net.taler.wallet.exchanges.CoinFeeAdapter.CoinFeeViewHolder
+import net.taler.wallet.exchanges.WireFeeAdapter.WireFeeViewHolder
 
 class SelectExchangeFragment : Fragment() {
 
@@ -59,8 +59,10 @@ class SelectExchangeFragment : Fragment() {
             overheadView.visibility = GONE
         } else overheadView.setAmount(fees.overhead)
         expirationView.text = 
fees.earliestDepositExpiration.ms.toRelativeTime(requireContext())
-        coinFeesList.adapter = CoinFeeAdapter(fees.coinFees)
-        wireFeesList.adapter = WireFeeAdapter(fees.wireFees)
+        coinFeesList.adapter =
+            CoinFeeAdapter(fees.coinFees)
+        wireFeesList.adapter =
+            WireFeeAdapter(fees.wireFees)
     }
 
     private fun TextView.setAmount(amount: Amount) {
diff --git a/wallet/src/main/java/net/taler/wallet/history/DevHistoryAdapter.kt 
b/wallet/src/main/java/net/taler/wallet/history/DevHistoryAdapter.kt
deleted file mode 100644
index a2684e1..0000000
--- a/wallet/src/main/java/net/taler/wallet/history/DevHistoryAdapter.kt
+++ /dev/null
@@ -1,110 +0,0 @@
-/*
- * This file is part of GNU Taler
- * (C) 2020 Taler Systems S.A.
- *
- * GNU Taler is free software; you can redistribute it and/or modify it under 
the
- * terms of the GNU General Public License as published by the Free Software
- * Foundation; either version 3, or (at your option) any later version.
- *
- * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
- * A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
- */
-
-package net.taler.wallet.history
-
-import android.content.Context
-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.ImageView
-import android.widget.TextView
-import androidx.recyclerview.widget.RecyclerView.Adapter
-import androidx.recyclerview.widget.RecyclerView.ViewHolder
-import net.taler.common.exhaustive
-import net.taler.common.toRelativeTime
-import net.taler.wallet.R
-import net.taler.wallet.history.DevHistoryAdapter.HistoryViewHolder
-import net.taler.wallet.transactions.AmountType
-
-internal class DevHistoryAdapter(
-    private val listener: OnEventClickListener
-) : Adapter<HistoryViewHolder>() {
-
-    private var history: List<HistoryEvent> = ArrayList()
-
-    init {
-        setHasStableIds(false)
-    }
-
-    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): 
HistoryViewHolder {
-        val view = LayoutInflater.from(parent.context)
-            .inflate(R.layout.list_item_history, parent, false)
-        return HistoryViewHolder(view)
-    }
-
-    override fun getItemCount(): Int = history.size
-
-    override fun onBindViewHolder(holder: HistoryViewHolder, position: Int) {
-        val transaction = history[position]
-        holder.bind(transaction)
-    }
-
-    fun update(updatedHistory: List<HistoryEvent>) {
-        this.history = updatedHistory
-        this.notifyDataSetChanged()
-    }
-
-    internal open inner class HistoryViewHolder(private val v: View) : 
ViewHolder(v) {
-
-        protected val context: Context = v.context
-
-        private val icon: ImageView = v.findViewById(R.id.icon)
-        protected val title: TextView = v.findViewById(R.id.title)
-        private val time: TextView = v.findViewById(R.id.time)
-        private val amount: TextView = v.findViewById(R.id.amount)
-
-        private val amountColor = amount.currentTextColor
-
-        open fun bind(historyEvent: HistoryEvent) {
-            v.setOnClickListener { listener.onTransactionClicked(historyEvent) 
}
-            icon.setImageResource(historyEvent.icon)
-            title.text = historyEvent.title
-            time.text = historyEvent.timestamp.ms.toRelativeTime(context)
-            bindAmount(historyEvent.displayAmount)
-        }
-
-        private fun bindAmount(displayAmount: DisplayAmount?) {
-            if (displayAmount == null) {
-                amount.visibility = GONE
-            } else {
-                amount.visibility = VISIBLE
-                when (displayAmount.type) {
-                    AmountType.Positive -> {
-                        amount.text = context.getString(
-                            R.string.amount_positive, 
displayAmount.amount.amountStr
-                        )
-                        amount.setTextColor(context.getColor(R.color.green))
-                    }
-                    AmountType.Negative -> {
-                        amount.text = context.getString(
-                            R.string.amount_negative, 
displayAmount.amount.amountStr
-                        )
-                        amount.setTextColor(context.getColor(R.color.red))
-                    }
-                    AmountType.Neutral -> {
-                        amount.text = displayAmount.amount.amountStr
-                        amount.setTextColor(amountColor)
-                    }
-                }.exhaustive
-            }
-        }
-
-    }
-
-}
diff --git 
a/wallet/src/main/java/net/taler/wallet/history/DevHistoryFragment.kt 
b/wallet/src/main/java/net/taler/wallet/history/DevHistoryFragment.kt
deleted file mode 100644
index c3c07a3..0000000
--- a/wallet/src/main/java/net/taler/wallet/history/DevHistoryFragment.kt
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * This file is part of GNU Taler
- * (C) 2020 Taler Systems S.A.
- *
- * GNU Taler is free software; you can redistribute it and/or modify it under 
the
- * terms of the GNU General Public License as published by the Free Software
- * Foundation; either version 3, or (at your option) any later version.
- *
- * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
- * A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
- */
-
-package net.taler.wallet.history
-
-import android.os.Bundle
-import android.view.LayoutInflater
-import android.view.View
-import android.view.View.INVISIBLE
-import android.view.View.VISIBLE
-import android.view.ViewGroup
-import androidx.fragment.app.Fragment
-import androidx.fragment.app.activityViewModels
-import androidx.lifecycle.Observer
-import androidx.recyclerview.widget.DividerItemDecoration
-import androidx.recyclerview.widget.LinearLayoutManager.VERTICAL
-import kotlinx.android.synthetic.main.fragment_transactions.*
-import net.taler.common.fadeIn
-import net.taler.common.fadeOut
-import net.taler.wallet.MainViewModel
-import net.taler.wallet.R
-
-internal interface OnEventClickListener {
-    fun onTransactionClicked(historyEvent: HistoryEvent)
-}
-
-class DevHistoryFragment : Fragment(),
-    OnEventClickListener {
-
-    private val model: MainViewModel by activityViewModels()
-    private val historyManager by lazy { model.historyManager }
-    private val historyAdapter by lazy { DevHistoryAdapter(this) }
-
-    override fun onCreateView(
-        inflater: LayoutInflater, container: ViewGroup?,
-        savedInstanceState: Bundle?
-    ): View? {
-        return inflater.inflate(R.layout.fragment_transactions, container, 
false)
-    }
-
-    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
-        if (savedInstanceState == null) historyManager.loadHistory()
-
-        list.apply {
-            adapter = historyAdapter
-            addItemDecoration(DividerItemDecoration(context, VERTICAL))
-        }
-        historyManager.progress.observe(viewLifecycleOwner, Observer { show ->
-            progressBar.visibility = if (show) VISIBLE else INVISIBLE
-        })
-        historyManager.history.observe(viewLifecycleOwner, Observer { result ->
-            onHistoryResult(result)
-        })
-    }
-
-    override fun onTransactionClicked(historyEvent: HistoryEvent) {
-        JsonDialogFragment.new(historyEvent.json.toString(2))
-            .show(parentFragmentManager, null)
-    }
-
-    private fun onHistoryResult(result: HistoryResult) = when (result) {
-        HistoryResult.Error -> {
-            list.fadeOut()
-            emptyState.text = getString(R.string.transactions_error)
-            emptyState.fadeIn()
-        }
-        is HistoryResult.Success -> {
-            emptyState.visibility = if (result.history.isEmpty()) VISIBLE else 
INVISIBLE
-            historyAdapter.update(result.history)
-            list.fadeIn()
-        }
-    }
-
-}
diff --git a/wallet/src/main/java/net/taler/wallet/history/DevHistoryManager.kt 
b/wallet/src/main/java/net/taler/wallet/history/DevHistoryManager.kt
deleted file mode 100644
index 9052d6e..0000000
--- a/wallet/src/main/java/net/taler/wallet/history/DevHistoryManager.kt
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * This file is part of GNU Taler
- * (C) 2020 Taler Systems S.A.
- *
- * GNU Taler is free software; you can redistribute it and/or modify it under 
the
- * terms of the GNU General Public License as published by the Free Software
- * Foundation; either version 3, or (at your option) any later version.
- *
- * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
- * A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
- */
-
-package net.taler.wallet.history
-
-import androidx.annotation.UiThread
-import androidx.lifecycle.LiveData
-import androidx.lifecycle.MutableLiveData
-import com.fasterxml.jackson.databind.ObjectMapper
-import com.fasterxml.jackson.module.kotlin.readValue
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.launch
-import net.taler.wallet.backend.WalletBackendApi
-import org.json.JSONObject
-import java.util.*
-
-sealed class HistoryResult {
-    object Error : HistoryResult()
-    class Success(val history: List<HistoryEvent>) : HistoryResult()
-}
-
-class DevHistoryManager(
-    private val walletBackendApi: WalletBackendApi,
-    private val scope: CoroutineScope,
-    private val mapper: ObjectMapper
-) {
-
-    private val mProgress = MutableLiveData<Boolean>()
-    val progress: LiveData<Boolean> = mProgress
-
-    private val mHistory = MutableLiveData<HistoryResult>()
-    val history: LiveData<HistoryResult> = mHistory
-
-    @UiThread
-    internal fun loadHistory() {
-        mProgress.value = true
-        walletBackendApi.sendRequest("getHistory", null) { isError, result ->
-            scope.launch(Dispatchers.Default) {
-                onEventsLoaded(isError, result)
-            }
-        }
-    }
-
-    private fun onEventsLoaded(isError: Boolean, result: JSONObject) {
-        if (isError) {
-            mHistory.postValue(HistoryResult.Error)
-            return
-        }
-        val history = LinkedList<HistoryEvent>()
-        val json = result.getJSONArray("history")
-        for (i in 0 until json.length()) {
-            val event: HistoryEvent = mapper.readValue(json.getString(i))
-            event.json = json.getJSONObject(i)
-            history.add(event)
-        }
-        history.reverse()  // show latest first
-        mProgress.postValue(false)
-        mHistory.postValue(HistoryResult.Success(history))
-    }
-
-}
diff --git a/wallet/src/main/java/net/taler/wallet/history/HistoryEvent.kt 
b/wallet/src/main/java/net/taler/wallet/history/HistoryEvent.kt
deleted file mode 100644
index 3cbe7d7..0000000
--- a/wallet/src/main/java/net/taler/wallet/history/HistoryEvent.kt
+++ /dev/null
@@ -1,199 +0,0 @@
-/*
- * This file is part of GNU Taler
- * (C) 2020 Taler Systems S.A.
- *
- * GNU Taler is free software; you can redistribute it and/or modify it under 
the
- * terms of the GNU General Public License as published by the Free Software
- * Foundation; either version 3, or (at your option) any later version.
- *
- * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
- * A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
- */
-
-package net.taler.wallet.history
-
-import androidx.annotation.DrawableRes
-import com.fasterxml.jackson.annotation.JsonSubTypes
-import com.fasterxml.jackson.annotation.JsonSubTypes.Type
-import com.fasterxml.jackson.annotation.JsonTypeInfo
-import com.fasterxml.jackson.annotation.JsonTypeInfo.As.PROPERTY
-import com.fasterxml.jackson.annotation.JsonTypeInfo.Id.NAME
-import com.fasterxml.jackson.annotation.JsonTypeName
-import net.taler.common.Amount
-import net.taler.common.Timestamp
-import net.taler.wallet.R
-import net.taler.wallet.transactions.AmountType
-import org.json.JSONObject
-
-class DisplayAmount(
-    val amount: Amount,
-    val type: AmountType
-)
-
-@JsonTypeInfo(
-    use = NAME,
-    include = PROPERTY,
-    property = "type",
-    defaultImpl = UnknownHistoryEvent::class
-)
-/** missing:
-AuditorComplaintSent = "auditor-complained-sent",
-AuditorComplaintProcessed = "auditor-complaint-processed",
-AuditorTrustAdded = "auditor-trust-added",
-AuditorTrustRemoved = "auditor-trust-removed",
-ExchangeTermsAccepted = "exchange-terms-accepted",
-ExchangePolicyChanged = "exchange-policy-changed",
-ExchangeTrustAdded = "exchange-trust-added",
-ExchangeTrustRemoved = "exchange-trust-removed",
-FundsDepositedToSelf = "funds-deposited-to-self",
-FundsRecouped = "funds-recouped",
-ReserveCreated = "reserve-created",
- */
-@JsonSubTypes(
-    Type(value = ExchangeAddedEvent::class, name = "exchange-added"),
-    Type(value = ExchangeUpdatedEvent::class, name = "exchange-updated"),
-    Type(value = ReserveBalanceUpdatedHistoryEvent::class, name = 
"reserve-balance-updated"),
-    Type(value = WithdrawHistoryEvent::class, name = "withdrawn"),
-    Type(value = OrderAcceptedHistoryEvent::class, name = "order-accepted"),
-    Type(value = OrderRefusedHistoryEvent::class, name = "order-refused"),
-    Type(value = OrderRedirectedHistoryEvent::class, name = 
"order-redirected"),
-    Type(value = PaymentHistoryEvent::class, name = "payment-sent"),
-    Type(value = PaymentAbortedHistoryEvent::class, name = "payment-aborted"),
-    Type(value = TipAcceptedHistoryEvent::class, name = "tip-accepted"),
-    Type(value = TipDeclinedHistoryEvent::class, name = "tip-declined"),
-    Type(value = RefundHistoryEvent::class, name = "refund"),
-    Type(value = RefreshHistoryEvent::class, name = "refreshed")
-)
-abstract class HistoryEvent(
-    val timestamp: Timestamp,
-    val eventId: String,
-    @get:DrawableRes
-    open val icon: Int = R.drawable.ic_account_balance
-) {
-    val title: String get() = this::class.java.simpleName
-    open val displayAmount: DisplayAmount? = null
-    lateinit var json: JSONObject
-}
-
-class UnknownHistoryEvent(timestamp: Timestamp, eventId: String) : 
HistoryEvent(timestamp, eventId)
-
-@JsonTypeName("exchange-added")
-class ExchangeAddedEvent(
-    timestamp: Timestamp,
-    eventId: String
-) : HistoryEvent(timestamp, eventId)
-
-@JsonTypeName("exchange-updated")
-class ExchangeUpdatedEvent(
-    timestamp: Timestamp,
-    eventId: String
-) : HistoryEvent(timestamp, eventId)
-
-@JsonTypeName("reserve-balance-updated")
-class ReserveBalanceUpdatedHistoryEvent(
-    timestamp: Timestamp,
-    eventId: String,
-    val reserveBalance: Amount
-) : HistoryEvent(timestamp, eventId) {
-    override val displayAmount = DisplayAmount(reserveBalance, 
AmountType.Neutral)
-}
-
-@JsonTypeName("withdrawn")
-class WithdrawHistoryEvent(
-    timestamp: Timestamp,
-    eventId: String,
-    val amountWithdrawnEffective: Amount
-) : HistoryEvent(timestamp, eventId) {
-    override val icon = R.drawable.transaction_withdrawal
-    override val displayAmount = DisplayAmount(amountWithdrawnEffective, 
AmountType.Positive)
-}
-
-@JsonTypeName("order-accepted")
-class OrderAcceptedHistoryEvent(
-    timestamp: Timestamp,
-    eventId: String
-) : HistoryEvent(timestamp, eventId) {
-    override val icon = R.drawable.ic_add_circle
-}
-
-@JsonTypeName("order-refused")
-class OrderRefusedHistoryEvent(
-    timestamp: Timestamp,
-    eventId: String
-) : HistoryEvent(timestamp, eventId) {
-    override val icon = R.drawable.ic_cancel
-}
-
-@JsonTypeName("payment-sent")
-class PaymentHistoryEvent(
-    timestamp: Timestamp,
-    eventId: String,
-    val amountPaidWithFees: Amount
-) : HistoryEvent(timestamp, eventId) {
-    override val icon = R.drawable.ic_cash_usd_outline
-    override val displayAmount = DisplayAmount(amountPaidWithFees, 
AmountType.Negative)
-}
-
-@JsonTypeName("payment-aborted")
-class PaymentAbortedHistoryEvent(
-    timestamp: Timestamp,
-    eventId: String,
-    amountLost: Amount
-) : HistoryEvent(timestamp, eventId) {
-    override val icon = R.drawable.transaction_payment_aborted
-    override val displayAmount = DisplayAmount(amountLost, AmountType.Negative)
-}
-
-@JsonTypeName("refreshed")
-class RefreshHistoryEvent(
-    timestamp: Timestamp,
-    eventId: String,
-    val amountRefreshedEffective: Amount,
-    val amountRefreshedRaw: Amount
-) : HistoryEvent(timestamp, eventId) {
-    override val icon = R.drawable.transaction_refresh
-    override val displayAmount =
-        DisplayAmount(amountRefreshedRaw - amountRefreshedEffective, 
AmountType.Negative)
-}
-
-@JsonTypeName("order-redirected")
-class OrderRedirectedHistoryEvent(
-    timestamp: Timestamp,
-    eventId: String
-) : HistoryEvent(timestamp, eventId) {
-    override val icon = R.drawable.ic_directions
-}
-
-@JsonTypeName("tip-accepted")
-class TipAcceptedHistoryEvent(
-    timestamp: Timestamp,
-    eventId: String,
-    tipRaw: Amount
-) : HistoryEvent(timestamp, eventId) {
-    override val icon = R.drawable.transaction_tip_accepted
-    override val displayAmount = DisplayAmount(tipRaw, AmountType.Positive)
-}
-
-@JsonTypeName("tip-declined")
-class TipDeclinedHistoryEvent(
-    timestamp: Timestamp,
-    eventId: String,
-    tipAmount: Amount
-) : HistoryEvent(timestamp, eventId) {
-    override val icon = R.drawable.transaction_tip_declined
-    override val displayAmount = DisplayAmount(tipAmount, AmountType.Neutral)
-}
-
-@JsonTypeName("refund")
-class RefundHistoryEvent(
-    timestamp: Timestamp,
-    eventId: String,
-    val amountRefundedEffective: Amount
-) : HistoryEvent(timestamp, eventId) {
-    override val icon = R.drawable.transaction_refund
-    override val displayAmount = DisplayAmount(amountRefundedEffective, 
AmountType.Positive)
-}
diff --git 
a/wallet/src/main/java/net/taler/wallet/history/JsonDialogFragment.kt 
b/wallet/src/main/java/net/taler/wallet/history/JsonDialogFragment.kt
deleted file mode 100644
index 31c2b93..0000000
--- a/wallet/src/main/java/net/taler/wallet/history/JsonDialogFragment.kt
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * This file is part of GNU Taler
- * (C) 2020 Taler Systems S.A.
- *
- * GNU Taler is free software; you can redistribute it and/or modify it under 
the
- * terms of the GNU General Public License as published by the Free Software
- * Foundation; either version 3, or (at your option) any later version.
- *
- * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
- * A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
- */
-
-package net.taler.wallet.history
-
-import android.os.Bundle
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import android.view.ViewGroup.LayoutParams.MATCH_PARENT
-import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
-import androidx.fragment.app.DialogFragment
-import kotlinx.android.synthetic.main.fragment_json.*
-import net.taler.wallet.R
-
-class JsonDialogFragment : DialogFragment() {
-
-    companion object {
-        fun new(json: String): JsonDialogFragment {
-            return JsonDialogFragment().apply {
-                arguments = Bundle().apply { putString("json", json) }
-            }
-        }
-    }
-
-    override fun onCreateView(
-        inflater: LayoutInflater,
-        container: ViewGroup?,
-        savedInstanceState: Bundle?
-    ): View? {
-        return inflater.inflate(R.layout.fragment_json, container, false)
-    }
-
-    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
-        val json = requireArguments().getString("json")
-        jsonView.text = json
-    }
-
-    override fun onStart() {
-        super.onStart()
-        dialog?.window?.setLayout(MATCH_PARENT, WRAP_CONTENT)
-    }
-
-}
diff --git a/wallet/src/main/java/net/taler/wallet/payment/PaymentManager.kt 
b/wallet/src/main/java/net/taler/wallet/payment/PaymentManager.kt
index 5c73d6c..2427afb 100644
--- a/wallet/src/main/java/net/taler/wallet/payment/PaymentManager.kt
+++ b/wallet/src/main/java/net/taler/wallet/payment/PaymentManager.kt
@@ -26,11 +26,32 @@ import net.taler.common.Amount
 import net.taler.common.ContractTerms
 import net.taler.wallet.TAG
 import net.taler.wallet.backend.WalletBackendApi
+import net.taler.wallet.payment.PayStatus.AlreadyPaid
+import net.taler.wallet.payment.PayStatus.InsufficientBalance
+import net.taler.wallet.payment.PreparePayResponse.AlreadyConfirmedResponse
+import net.taler.wallet.payment.PreparePayResponse.InsufficientBalanceResponse
+import net.taler.wallet.payment.PreparePayResponse.PaymentPossibleResponse
 import org.json.JSONObject
 import java.net.MalformedURLException
 
 val REGEX_PRODUCT_IMAGE = 
Regex("^data:image/(jpeg|png);base64,([A-Za-z0-9+/=]+)$")
 
+sealed class PayStatus {
+    object None : PayStatus()
+    object Loading : PayStatus()
+    data class Prepared(
+        val contractTerms: ContractTerms,
+        val proposalId: String,
+        val amountRaw: Amount,
+        val amountEffective: Amount
+    ) : PayStatus()
+
+    data class InsufficientBalance(val contractTerms: ContractTerms) : 
PayStatus()
+    object AlreadyPaid : PayStatus()
+    data class Error(val error: String) : PayStatus()
+    data class Success(val currency: String) : PayStatus()
+}
+
 class PaymentManager(
     private val walletBackendApi: WalletBackendApi,
     private val mapper: ObjectMapper
@@ -42,52 +63,27 @@ class PaymentManager(
     private val mDetailsShown = MutableLiveData<Boolean>()
     internal val detailsShown: LiveData<Boolean> = mDetailsShown
 
-    private var currentPayRequestId = 0
-
     @UiThread
     fun preparePay(url: String) {
         mPayStatus.value = PayStatus.Loading
         mDetailsShown.value = false
 
-        val args = JSONObject(mapOf("url" to url))
-
-        currentPayRequestId += 1
-        val payRequestId = currentPayRequestId
-
+        val args = JSONObject(mapOf("talerPayUri" to url))
         walletBackendApi.sendRequest("preparePay", args) { isError, result ->
-            when {
-                isError -> {
-                    Log.v(TAG, "got preparePay error result")
-                    mPayStatus.value = PayStatus.Error(result.toString())
-                }
-                payRequestId != this.currentPayRequestId -> {
-                    Log.v(TAG, "preparePay result was for old request")
-                }
-                else -> {
-                    val status = result.getString("status")
-                    try {
-                        mPayStatus.postValue(getPayStatusUpdate(status, 
result))
-                    } catch (e: Exception) {
-                        Log.e(TAG, "Error getting PayStatusUpdate", e)
-                        mPayStatus.postValue(PayStatus.Error(e.message ?: 
"unknown error"))
-                    }
-                }
+            if (isError) {
+                handleError("preparePay", result.toString(2))
+                return@sendRequest
+            }
+            val response: PreparePayResponse = 
mapper.readValue(result.toString())
+            Log.e(TAG, "PreparePayResponse $response")
+            mPayStatus.value = when (response) {
+                is PaymentPossibleResponse -> response.toPayStatusPrepared()
+                is InsufficientBalanceResponse -> 
InsufficientBalance(response.contractTerms)
+                is AlreadyConfirmedResponse -> AlreadyPaid
             }
         }
     }
 
-    private fun getPayStatusUpdate(status: String, json: JSONObject) = when 
(status) {
-        "payment-possible" -> PayStatus.Prepared(
-            contractTerms = getContractTerms(json),
-            proposalId = json.getString("proposalId"),
-            totalFees = Amount.fromJsonObject(json.getJSONObject("totalFees"))
-        )
-        "paid" -> PayStatus.AlreadyPaid(getContractTerms(json))
-        "insufficient-balance" -> 
PayStatus.InsufficientBalance(getContractTerms(json))
-        "error" -> PayStatus.Error("got some error")
-        else -> PayStatus.Error("unknown status")
-    }
-
     private fun getContractTerms(json: JSONObject): ContractTerms {
         val terms: ContractTerms = 
mapper.readValue(json.getString("contractTermsRaw"))
         // validate product images
@@ -101,16 +97,13 @@ class PaymentManager(
         return terms
     }
 
-    @UiThread
-    fun toggleDetailsShown() {
-        val oldValue = mDetailsShown.value ?: false
-        mDetailsShown.value = !oldValue
-    }
-
     fun confirmPay(proposalId: String, currency: String) {
         val args = JSONObject(mapOf("proposalId" to proposalId))
-
-        walletBackendApi.sendRequest("confirmPay", args) { _, _ ->
+        walletBackendApi.sendRequest("confirmPay", args) { isError, result ->
+            if (isError) {
+                handleError("preparePay", result.toString())
+                return@sendRequest
+            }
             mPayStatus.postValue(PayStatus.Success(currency))
         }
     }
@@ -129,8 +122,9 @@ class PaymentManager(
 
         Log.i(TAG, "aborting proposal")
 
-        walletBackendApi.sendRequest("abortProposal", args) { isError, _ ->
+        walletBackendApi.sendRequest("abortProposal", args) { isError, result 
->
             if (isError) {
+                handleError("abortProposal", result.toString(2))
                 Log.e(TAG, "received error response to abortProposal")
                 return@sendRequest
             }
@@ -138,24 +132,20 @@ class PaymentManager(
         }
     }
 
+    @UiThread
+    fun toggleDetailsShown() {
+        val oldValue = mDetailsShown.value ?: false
+        mDetailsShown.value = !oldValue
+    }
+
     @UiThread
     fun resetPayStatus() {
         mPayStatus.value = PayStatus.None
     }
 
-}
-
-sealed class PayStatus {
-    object None : PayStatus()
-    object Loading : PayStatus()
-    data class Prepared(
-        val contractTerms: ContractTerms,
-        val proposalId: String,
-        val totalFees: Amount
-    ) : PayStatus()
+    private fun handleError(operation: String, msg: String) {
+        Log.e(TAG, "got $operation error result $msg")
+        mPayStatus.value = PayStatus.Error(msg)
+    }
 
-    data class InsufficientBalance(val contractTerms: ContractTerms) : 
PayStatus()
-    data class AlreadyPaid(val contractTerms: ContractTerms) : PayStatus()
-    data class Error(val error: String) : PayStatus()
-    data class Success(val currency: String) : PayStatus()
 }
diff --git a/wallet/src/main/java/net/taler/wallet/payment/PaymentResponses.kt 
b/wallet/src/main/java/net/taler/wallet/payment/PaymentResponses.kt
new file mode 100644
index 0000000..1ff8867
--- /dev/null
+++ b/wallet/src/main/java/net/taler/wallet/payment/PaymentResponses.kt
@@ -0,0 +1,61 @@
+/*
+ * 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.payment
+
+import com.fasterxml.jackson.annotation.JsonTypeInfo
+import com.fasterxml.jackson.annotation.JsonTypeInfo.Id.NAME
+import com.fasterxml.jackson.annotation.JsonTypeName
+import net.taler.common.Amount
+import net.taler.common.ContractTerms
+
+@JsonTypeInfo(use = NAME, property = "status")
+sealed class PreparePayResponse(open val proposalId: String) {
+    @JsonTypeName("payment-possible")
+    data class PaymentPossibleResponse(
+        override val proposalId: String,
+        val amountRaw: Amount,
+        val amountEffective: Amount,
+        val contractTerms: ContractTerms
+    ) : PreparePayResponse(proposalId) {
+        fun toPayStatusPrepared() = PayStatus.Prepared(
+            contractTerms = contractTerms,
+            proposalId = proposalId,
+            amountRaw = amountRaw,
+            amountEffective = amountEffective
+        )
+    }
+
+    @JsonTypeName("insufficient-balance")
+    data class InsufficientBalanceResponse(
+        override val proposalId: String,
+        val contractTerms: ContractTerms
+    ) : PreparePayResponse(proposalId)
+
+    @JsonTypeName("already-confirmed")
+    data class AlreadyConfirmedResponse(
+        override val proposalId: String,
+        /**
+         * Did the payment succeed?
+         */
+        val paid: Boolean,
+
+        /**
+         * Redirect URL for the fulfillment page, only given if paid==true.
+         */
+        val nextUrl: String?
+    ) : PreparePayResponse(proposalId)
+}
diff --git 
a/wallet/src/main/java/net/taler/wallet/payment/PromptPaymentFragment.kt 
b/wallet/src/main/java/net/taler/wallet/payment/PromptPaymentFragment.kt
index 6f806b7..ce2b6f7 100644
--- a/wallet/src/main/java/net/taler/wallet/payment/PromptPaymentFragment.kt
+++ b/wallet/src/main/java/net/taler/wallet/payment/PromptPaymentFragment.kt
@@ -95,7 +95,8 @@ class PromptPaymentFragment : Fragment(), 
ProductImageClickListener {
         when (payStatus) {
             is PayStatus.Prepared -> {
                 showLoading(false)
-                showOrder(payStatus.contractTerms, payStatus.totalFees)
+                val fees = payStatus.amountEffective - payStatus.amountRaw
+                showOrder(payStatus.contractTerms, fees)
                 confirmButton.isEnabled = true
                 confirmButton.setOnClickListener {
                     model.showProgressBar.value = true
@@ -109,7 +110,7 @@ class PromptPaymentFragment : Fragment(), 
ProductImageClickListener {
             }
             is PayStatus.InsufficientBalance -> {
                 showLoading(false)
-                showOrder(payStatus.contractTerms, null)
+                showOrder(payStatus.contractTerms)
                 errorView.setText(R.string.payment_balance_insufficient)
                 errorView.fadeIn()
             }
@@ -141,7 +142,7 @@ class PromptPaymentFragment : Fragment(), 
ProductImageClickListener {
         }
     }
 
-    private fun showOrder(contractTerms: ContractTerms, totalFees: Amount?) {
+    private fun showOrder(contractTerms: ContractTerms, totalFees: Amount? = 
null) {
         orderView.text = contractTerms.summary
         adapter.setItems(contractTerms.products)
         if (contractTerms.products.size == 1) 
paymentManager.toggleDetailsShown()
diff --git 
a/wallet/src/main/java/net/taler/wallet/pending/PendingOperationsManager.kt 
b/wallet/src/main/java/net/taler/wallet/pending/PendingOperationsManager.kt
index 6c58b81..7027687 100644
--- a/wallet/src/main/java/net/taler/wallet/pending/PendingOperationsManager.kt
+++ b/wallet/src/main/java/net/taler/wallet/pending/PendingOperationsManager.kt
@@ -32,7 +32,7 @@ class PendingOperationsManager(private val walletBackendApi: 
WalletBackendApi) {
     val pendingOperations = MutableLiveData<List<PendingOperationInfo>>()
 
     internal fun getPending() {
-        walletBackendApi.sendRequest("getPendingOperations", null) { isError, 
result ->
+        walletBackendApi.sendRequest("getPendingOperations") { isError, result 
->
             if (isError) {
                 Log.i(TAG, "got getPending error result: $result")
                 return@sendRequest
@@ -51,7 +51,7 @@ class PendingOperationsManager(private val walletBackendApi: 
WalletBackendApi) {
     }
 
     fun retryPendingNow() {
-        walletBackendApi.sendRequest("retryPendingNow", null)
+        walletBackendApi.sendRequest("retryPendingNow")
     }
 
 }
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 d8204b6..bd37b37 100644
--- a/wallet/src/main/java/net/taler/wallet/transactions/TransactionManager.kt
+++ b/wallet/src/main/java/net/taler/wallet/transactions/TransactionManager.kt
@@ -102,12 +102,4 @@ class TransactionManager(
         }
     }
 
-    @UiThread
-    fun hasPending(currency: String): Boolean {
-        val result = mTransactions[currency]?.value ?: return false
-        return if (result is TransactionsResult.Success) {
-            result.transactions.any { it.pending }
-        } else false
-    }
-
 }
diff --git 
a/wallet/src/main/java/net/taler/wallet/transactions/TransactionsFragment.kt 
b/wallet/src/main/java/net/taler/wallet/transactions/TransactionsFragment.kt
index 526aa94..2ae58c3 100644
--- a/wallet/src/main/java/net/taler/wallet/transactions/TransactionsFragment.kt
+++ b/wallet/src/main/java/net/taler/wallet/transactions/TransactionsFragment.kt
@@ -112,7 +112,7 @@ class TransactionsFragment : Fragment(), 
OnTransactionClickListener, ActionMode.
     override fun onActivityCreated(savedInstanceState: Bundle?) {
         super.onActivityCreated(savedInstanceState)
         model.balances.observe(viewLifecycleOwner, Observer { balances ->
-            balances[currency]?.available?.let { amount ->
+            balances.find { it.currency == currency }?.available?.let { amount 
->
                 requireActivity().title =
                     getString(R.string.transactions_detail_title_balance, 
amount)
             }
diff --git a/wallet/src/main/java/net/taler/wallet/withdraw/ExchangeFees.kt 
b/wallet/src/main/java/net/taler/wallet/withdraw/ExchangeFees.kt
deleted file mode 100644
index 9c815c9..0000000
--- a/wallet/src/main/java/net/taler/wallet/withdraw/ExchangeFees.kt
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * This file is part of GNU Taler
- * (C) 2020 Taler Systems S.A.
- *
- * GNU Taler is free software; you can redistribute it and/or modify it under 
the
- * terms of the GNU General Public License as published by the Free Software
- * Foundation; either version 3, or (at your option) any later version.
- *
- * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
- * A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
- */
-
-package net.taler.wallet.withdraw
-
-import net.taler.common.Amount
-import net.taler.common.Timestamp
-import org.json.JSONObject
-
-data class CoinFee(
-    val coin: Amount,
-    val quantity: Int,
-    val feeDeposit: Amount,
-    val feeRefresh: Amount,
-    val feeRefund: Amount,
-    val feeWithdraw: Amount
-)
-
-data class WireFee(
-    val start: Timestamp,
-    val end: Timestamp,
-    val wireFee: Amount,
-    val closingFee: Amount
-)
-
-data class ExchangeFees(
-    val withdrawFee: Amount,
-    val overhead: Amount,
-    val earliestDepositExpiration: Timestamp,
-    val coinFees: List<CoinFee>,
-    val wireFees: List<WireFee>
-) {
-    companion object {
-        fun fromExchangeWithdrawDetailsJson(json: JSONObject): ExchangeFees {
-            val earliestDepositExpiration =
-                json.getJSONObject("earliestDepositExpiration").getLong("t_ms")
-            val selectedDenoms = json.getJSONObject("selectedDenoms")
-            val denoms = selectedDenoms.getJSONArray("selectedDenoms")
-            val coinFees = ArrayList<CoinFee>(denoms.length())
-            for (i in 0 until denoms.length()) {
-                val denom = denoms.getJSONObject(i)
-                val d = denom.getJSONObject("denom")
-                val coinFee = CoinFee(
-                    coin = Amount.fromJsonObject(d.getJSONObject("value")),
-                    quantity = denom.getInt("count"),
-                    feeDeposit = 
Amount.fromJsonObject(d.getJSONObject("feeDeposit")),
-                    feeRefresh = 
Amount.fromJsonObject(d.getJSONObject("feeRefresh")),
-                    feeRefund = 
Amount.fromJsonObject(d.getJSONObject("feeRefund")),
-                    feeWithdraw = 
Amount.fromJsonObject(d.getJSONObject("feeWithdraw"))
-                )
-                coinFees.add(coinFee)
-            }
-
-            val wireFeesJson = json.getJSONObject("wireFees")
-            val feesForType = wireFeesJson.getJSONObject("feesForType")
-            val bankFees = feesForType.getJSONArray("x-taler-bank")
-            val wireFees = ArrayList<WireFee>(bankFees.length())
-            for (i in 0 until bankFees.length()) {
-                val fee = bankFees.getJSONObject(i)
-                val startStamp =
-                    fee.getJSONObject("startStamp").getLong("t_ms")
-                val endStamp =
-                    fee.getJSONObject("endStamp").getLong("t_ms")
-                val wireFee = WireFee(
-                    start = Timestamp(startStamp),
-                    end = Timestamp(endStamp),
-                    wireFee = 
Amount.fromJsonObject(fee.getJSONObject("wireFee")),
-                    closingFee = 
Amount.fromJsonObject(fee.getJSONObject("closingFee"))
-                )
-                wireFees.add(wireFee)
-            }
-
-            return ExchangeFees(
-                withdrawFee = 
Amount.fromJsonObject(json.getJSONObject("withdrawFee")),
-                overhead = 
Amount.fromJsonObject(json.getJSONObject("overhead")),
-                earliestDepositExpiration = 
Timestamp(earliestDepositExpiration),
-                coinFees = coinFees,
-                wireFees = wireFees
-            )
-        }
-    }
-}
diff --git 
a/wallet/src/main/java/net/taler/wallet/withdraw/ManualWithdrawFragment.kt 
b/wallet/src/main/java/net/taler/wallet/withdraw/ManualWithdrawFragment.kt
index 55f931d..9788d1c 100644
--- a/wallet/src/main/java/net/taler/wallet/withdraw/ManualWithdrawFragment.kt
+++ b/wallet/src/main/java/net/taler/wallet/withdraw/ManualWithdrawFragment.kt
@@ -60,7 +60,7 @@ class ManualWithdrawFragment : Fragment() {
             val amount = Amount(exchangeItem.currency, value, 0)
             amountView.hideKeyboard()
             Toast.makeText(view.context, "Not implemented: $amount", 
LENGTH_SHORT).show()
-            withdrawManager.getWithdrawalDetails(exchangeItem, amount)
+            withdrawManager.getWithdrawalDetails(exchangeItem.exchangeBaseUrl, 
amount)
         }
     }
 
diff --git 
a/wallet/src/main/java/net/taler/wallet/withdraw/PromptWithdrawFragment.kt 
b/wallet/src/main/java/net/taler/wallet/withdraw/PromptWithdrawFragment.kt
index 331554b..5a98a89 100644
--- a/wallet/src/main/java/net/taler/wallet/withdraw/PromptWithdrawFragment.kt
+++ b/wallet/src/main/java/net/taler/wallet/withdraw/PromptWithdrawFragment.kt
@@ -20,6 +20,8 @@ import android.os.Bundle
 import android.view.LayoutInflater
 import android.view.View
 import android.view.ViewGroup
+import android.widget.Toast
+import android.widget.Toast.LENGTH_SHORT
 import androidx.fragment.app.Fragment
 import androidx.fragment.app.activityViewModels
 import androidx.lifecycle.Observer
@@ -34,7 +36,7 @@ import net.taler.wallet.MainViewModel
 import net.taler.wallet.R
 import net.taler.wallet.cleanExchange
 import net.taler.wallet.withdraw.WithdrawStatus.Loading
-import net.taler.wallet.withdraw.WithdrawStatus.TermsOfServiceReviewRequired
+import net.taler.wallet.withdraw.WithdrawStatus.TosReviewRequired
 import net.taler.wallet.withdraw.WithdrawStatus.Withdrawing
 
 class PromptWithdrawFragment : Fragment() {
@@ -59,17 +61,13 @@ class PromptWithdrawFragment : Fragment() {
 
     private fun showWithdrawStatus(status: WithdrawStatus?): Any = when 
(status) {
         is WithdrawStatus.ReceivedDetails -> {
-            showContent(status.amount, status.fee, status.exchange)
+            showContent(status.amountRaw, status.amountEffective, 
status.exchangeBaseUrl)
             confirmWithdrawButton.apply {
                 text = getString(R.string.withdraw_button_confirm)
                 setOnClickListener {
                     it.fadeOut()
                     confirmProgressBar.fadeIn()
-                    withdrawManager.acceptWithdrawal(
-                        status.talerWithdrawUri,
-                        status.exchange,
-                        status.amount.currency
-                    )
+                    withdrawManager.acceptWithdrawal()
                 }
                 isEnabled = true
             }
@@ -87,8 +85,8 @@ class PromptWithdrawFragment : Fragment() {
         is Withdrawing -> {
             model.showProgressBar.value = true
         }
-        is TermsOfServiceReviewRequired -> {
-            showContent(status.amount, status.fee, status.exchange)
+        is TosReviewRequired -> {
+            showContent(status.amountRaw, status.amountEffective, 
status.exchangeBaseUrl)
             confirmWithdrawButton.apply {
                 text = getString(R.string.withdraw_button_tos)
                 setOnClickListener {
@@ -104,20 +102,20 @@ class PromptWithdrawFragment : Fragment() {
         null -> model.showProgressBar.value = false
     }
 
-    private fun showContent(amount: Amount, fee: Amount, exchange: String) {
+    private fun showContent(amountRaw: Amount, amountEffective: Amount, 
exchange: String) {
         model.showProgressBar.value = false
         progressBar.fadeOut()
 
         introView.fadeIn()
-        effectiveAmountView.text = (amount - fee).toString()
+        effectiveAmountView.text = amountEffective.toString()
         effectiveAmountView.fadeIn()
 
         chosenAmountLabel.fadeIn()
-        chosenAmountView.text = amount.toString()
+        chosenAmountView.text = amountRaw.toString()
         chosenAmountView.fadeIn()
 
         feeLabel.fadeIn()
-        feeView.text = getString(R.string.amount_negative, fee.toString())
+        feeView.text = getString(R.string.amount_negative, (amountRaw - 
amountEffective).toString())
         feeView.fadeIn()
 
         exchangeIntroView.fadeIn()
@@ -125,7 +123,7 @@ class PromptWithdrawFragment : Fragment() {
         withdrawExchangeUrl.fadeIn()
         selectExchangeButton.fadeIn()
         selectExchangeButton.setOnClickListener {
-            
findNavController().navigate(R.id.action_promptWithdraw_to_selectExchangeFragment)
+            Toast.makeText(context, "Not yet implemented", LENGTH_SHORT).show()
         }
 
         withdrawCard.fadeIn()
diff --git 
a/wallet/src/main/java/net/taler/wallet/withdraw/ReviewExchangeTosFragment.kt 
b/wallet/src/main/java/net/taler/wallet/withdraw/ReviewExchangeTosFragment.kt
index ffaef5a..db1f326 100644
--- 
a/wallet/src/main/java/net/taler/wallet/withdraw/ReviewExchangeTosFragment.kt
+++ 
b/wallet/src/main/java/net/taler/wallet/withdraw/ReviewExchangeTosFragment.kt
@@ -55,7 +55,7 @@ class ReviewExchangeTosFragment : Fragment() {
         }
         withdrawManager.withdrawStatus.observe(viewLifecycleOwner, Observer {
             when (it) {
-                is WithdrawStatus.TermsOfServiceReviewRequired -> {
+                is WithdrawStatus.TosReviewRequired -> {
                     val sections = try {
                         // TODO remove next line once exchange delivers proper 
markdown
                         val text = it.tosText.replace("****************", 
"================")
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 ea65e7c..e14a747 100644
--- a/wallet/src/main/java/net/taler/wallet/withdraw/WithdrawManager.kt
+++ b/wallet/src/main/java/net/taler/wallet/withdraw/WithdrawManager.kt
@@ -17,38 +17,57 @@
 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 net.taler.common.Amount
 import net.taler.wallet.TAG
 import net.taler.wallet.backend.WalletBackendApi
+import net.taler.wallet.exchanges.ExchangeFees
 import net.taler.wallet.exchanges.ExchangeItem
 import net.taler.wallet.withdraw.WithdrawStatus.ReceivedDetails
 import org.json.JSONObject
 
 sealed class WithdrawStatus {
-    data class Loading(val talerWithdrawUri: String) : WithdrawStatus()
-    data class TermsOfServiceReviewRequired(
-        val talerWithdrawUri: String,
-        val exchange: String,
+    data class Loading(val talerWithdrawUri: String? = null) : WithdrawStatus()
+    data class TosReviewRequired(
+        val talerWithdrawUri: String? = null,
+        val exchangeBaseUrl: String,
+        val amountRaw: Amount,
+        val amountEffective: Amount,
         val tosText: String,
-        val tosEtag: String,
-        val amount: Amount,
-        val fee: Amount
+        val tosEtag: String
     ) : WithdrawStatus()
 
     data class ReceivedDetails(
-        val talerWithdrawUri: String,
-        val exchange: String,
-        val amount: Amount,
-        val fee: Amount
+        val talerWithdrawUri: String? = null,
+        val exchangeBaseUrl: String,
+        val amountRaw: Amount,
+        val amountEffective: Amount
     ) : WithdrawStatus()
 
-    data class Withdrawing(val talerWithdrawUri: String) : WithdrawStatus()
+    object Withdrawing : WithdrawStatus()
     data class Success(val currency: String) : WithdrawStatus()
     data class Error(val message: String?) : WithdrawStatus()
 }
 
-class WithdrawManager(private val walletBackendApi: WalletBackendApi) {
+data class WithdrawalDetailsForUri(
+    val amount: Amount,
+    val defaultExchangeBaseUrl: String?,
+    val possibleExchanges: List<ExchangeItem>
+)
+
+data class WithdrawalDetails(
+    val tosAccepted: Boolean,
+    val amountRaw: Amount,
+    val amountEffective: Amount
+)
+
+class WithdrawManager(
+    private val walletBackendApi: WalletBackendApi,
+    private val mapper: ObjectMapper
+) {
 
     val withdrawStatus = MutableLiveData<WithdrawStatus>()
     val testWithdrawalInProgress = MutableLiveData(false)
@@ -58,149 +77,127 @@ class WithdrawManager(private val walletBackendApi: 
WalletBackendApi) {
 
     fun withdrawTestkudos() {
         testWithdrawalInProgress.value = true
-
-        walletBackendApi.sendRequest("withdrawTestkudos", null) { _, _ ->
+        walletBackendApi.sendRequest("withdrawTestkudos") { _, _ ->
             testWithdrawalInProgress.postValue(false)
         }
     }
 
-    fun getWithdrawalDetails(exchangeItem: ExchangeItem, amount: Amount) {
+    fun getWithdrawalDetails(uri: String) {
+        withdrawStatus.value = WithdrawStatus.Loading(uri)
         val args = JSONObject().apply {
-            put("exchangeBaseUrl", exchangeItem.exchangeBaseUrl)
-            put("amount", amount.toJSONString())
+            put("talerWithdrawUri", uri)
         }
-        walletBackendApi.sendRequest("getWithdrawalDetailsForAmount", args) { 
isError, result ->
-            // 
{"rawAmount":"TESTKUDOS:5","effectiveAmount":"TESTKUDOS:4.8","paytoUris":["payto:\/\/x-taler-bank\/bank.test.taler.net\/Exchange"],"tosAccepted":false}
+        walletBackendApi.sendRequest("getWithdrawalDetailsForUri", args) { 
isError, result ->
             if (isError) {
-                Log.e(TAG, "$result")
+                handleError("getWithdrawalDetailsForUri", result)
+                return@sendRequest
+            }
+            val details: WithdrawalDetailsForUri = 
mapper.readValue(result.toString())
+            if (details.defaultExchangeBaseUrl == null) {
+                // TODO go to exchange selection screen instead
+                val chosenExchange = 
details.possibleExchanges[0].exchangeBaseUrl
+                getWithdrawalDetails(chosenExchange, details.amount, uri)
             } else {
-                Log.e(TAG, "$result")
+                getWithdrawalDetails(details.defaultExchangeBaseUrl, 
details.amount, uri)
             }
         }
     }
 
-    fun getWithdrawalInfo(talerWithdrawUri: String) {
+    fun getWithdrawalDetails(exchangeBaseUrl: String, amount: Amount, uri: 
String? = null) {
+        withdrawStatus.value = WithdrawStatus.Loading(uri)
         val args = JSONObject().apply {
-            put("talerWithdrawUri", talerWithdrawUri)
+            put("exchangeBaseUrl", exchangeBaseUrl)
+            put("amount", amount.toJSONString())
         }
-        withdrawStatus.value = WithdrawStatus.Loading(talerWithdrawUri)
-
-        walletBackendApi.sendRequest("getWithdrawDetailsForUri", args) { 
isError, result ->
+        walletBackendApi.sendRequest("getWithdrawalDetailsForAmount", args) { 
isError, result ->
             if (isError) {
-                Log.e(TAG, "Error getWithdrawDetailsForUri 
${result.toString(4)}")
-                val message = if (result.has("message")) 
result.getString("message") else null
-                withdrawStatus.postValue(WithdrawStatus.Error(message))
+                handleError("getWithdrawalDetailsForAmount", result)
                 return@sendRequest
             }
-            Log.v(TAG, "got getWithdrawDetailsForUri result")
-            val status = withdrawStatus.value
-            if (status !is WithdrawStatus.Loading) {
-                Log.v(TAG, "ignoring withdrawal info result, not loading.")
-                return@sendRequest
-            }
-            val wi = result.getJSONObject("bankWithdrawDetails")
-            val suggestedExchange = wi.getString("suggestedExchange")
-            // We just use the suggested exchange, in the future there will be
-            // a selection dialog.
-            getWithdrawalInfoWithExchange(talerWithdrawUri, suggestedExchange)
+            val details: WithdrawalDetails = 
mapper.readValue(result.toString())
+            if (details.tosAccepted)
+                withdrawStatus.value = ReceivedDetails(
+                    talerWithdrawUri = uri,
+                    exchangeBaseUrl = exchangeBaseUrl,
+                    amountRaw = details.amountRaw,
+                    amountEffective = details.amountEffective
+                )
+            else getExchangeTos(exchangeBaseUrl, details, uri)
         }
     }
 
-    private fun getWithdrawalInfoWithExchange(talerWithdrawUri: String, 
selectedExchange: String) {
+    private fun getExchangeTos(exchangeBaseUrl: String, details: 
WithdrawalDetails, uri: String?) {
         val args = JSONObject().apply {
-            put("talerWithdrawUri", talerWithdrawUri)
-            put("selectedExchange", selectedExchange)
+            put("exchangeBaseUrl", exchangeBaseUrl)
         }
-
-        walletBackendApi.sendRequest("getWithdrawDetailsForUri", args) { 
isError, result ->
+        walletBackendApi.sendRequest("getExchangeTos", args) { isError, result 
->
             if (isError) {
-                Log.e(TAG, "Error getWithdrawDetailsForUri 
${result.toString(4)}")
-                val message = if (result.has("message")) 
result.getString("message") else null
-                withdrawStatus.postValue(WithdrawStatus.Error(message))
-                return@sendRequest
-            }
-            Log.v(TAG, "got getWithdrawDetailsForUri result (with exchange 
details)")
-            val status = withdrawStatus.value
-            if (status !is WithdrawStatus.Loading) {
-                Log.w(TAG, "ignoring withdrawal info result, not loading.")
+                handleError("getExchangeTos", result)
                 return@sendRequest
             }
-            val wi = result.getJSONObject("bankWithdrawDetails")
-            val amount = Amount.fromJsonObject(wi.getJSONObject("amount"))
-
-            val ei = result.getJSONObject("exchangeWithdrawDetails")
-            val termsOfServiceAccepted = 
ei.getBoolean("termsOfServiceAccepted")
-
-            exchangeFees = ExchangeFees.fromExchangeWithdrawDetailsJson(ei)
-
-            val withdrawFee = 
Amount.fromJsonObject(ei.getJSONObject("withdrawFee"))
-            val overhead = Amount.fromJsonObject(ei.getJSONObject("overhead"))
-            val fee = withdrawFee + overhead
-
-            if (!termsOfServiceAccepted) {
-                val exchange = ei.getJSONObject("exchangeInfo")
-                val tosText = exchange.getString("termsOfServiceText")
-                val tosEtag = exchange.optString("termsOfServiceLastEtag", 
"undefined")
-                withdrawStatus.postValue(
-                    WithdrawStatus.TermsOfServiceReviewRequired(
-                        status.talerWithdrawUri,
-                        selectedExchange, tosText, tosEtag,
-                        amount, fee
-                    )
-                )
-            } else {
-                withdrawStatus.postValue(
-                    ReceivedDetails(
-                        status.talerWithdrawUri,
-                        selectedExchange, amount,
-                        fee
-                    )
-                )
-            }
+            withdrawStatus.value = WithdrawStatus.TosReviewRequired(
+                talerWithdrawUri = uri,
+                exchangeBaseUrl = exchangeBaseUrl,
+                amountRaw = details.amountRaw,
+                amountEffective = details.amountEffective,
+                tosText = result.getString("tos"),
+                tosEtag = result.getString("currentEtag")
+            )
         }
     }
 
-    fun acceptWithdrawal(talerWithdrawUri: String, selectedExchange: String, 
currency: String) {
-        val args = JSONObject()
-        args.put("talerWithdrawUri", talerWithdrawUri)
-        args.put("selectedExchange", selectedExchange)
-
-        withdrawStatus.value = WithdrawStatus.Withdrawing(talerWithdrawUri)
-
-        walletBackendApi.sendRequest("acceptWithdrawal", args) { isError, 
result ->
+    /**
+     * Accept the currently displayed terms of service.
+     */
+    fun acceptCurrentTermsOfService() {
+        val s = withdrawStatus.value as WithdrawStatus.TosReviewRequired
+        val args = JSONObject().apply {
+            put("exchangeBaseUrl", s.exchangeBaseUrl)
+            put("etag", s.tosEtag)
+        }
+        walletBackendApi.sendRequest("setExchangeTosAccepted", args) { 
isError, result ->
             if (isError) {
-                Log.v(TAG, "got acceptWithdrawal error result: 
${result.toString(2)}")
-                return@sendRequest
-            }
-            Log.v(TAG, "got acceptWithdrawal result")
-            val status = withdrawStatus.value
-            if (status !is WithdrawStatus.Withdrawing) {
-                Log.w(TAG, "ignoring acceptWithdrawal result, invalid state: 
$status")
+                handleError("setExchangeTosAccepted", result)
                 return@sendRequest
             }
-            withdrawStatus.postValue(WithdrawStatus.Success(currency))
+            withdrawStatus.value = ReceivedDetails(
+                talerWithdrawUri = s.talerWithdrawUri,
+                exchangeBaseUrl = s.exchangeBaseUrl,
+                amountRaw = s.amountRaw,
+                amountEffective = s.amountEffective
+            )
         }
     }
 
-    /**
-     * Accept the currently displayed terms of service.
-     */
-    fun acceptCurrentTermsOfService() {
-        val s = withdrawStatus.value
-        check(s is WithdrawStatus.TermsOfServiceReviewRequired)
+    @UiThread
+    fun acceptWithdrawal() {
+        val status = withdrawStatus.value as ReceivedDetails
 
+        val operation = if (status.talerWithdrawUri == null)
+            "acceptManualWithdrawal" else "acceptBankIntegratedWithdrawal"
         val args = JSONObject().apply {
-            put("exchangeBaseUrl", s.exchange)
-            put("etag", s.tosEtag)
+            put("exchangeBaseUrl", status.exchangeBaseUrl)
+            if (status.talerWithdrawUri == null) {
+                put("amount", status.amountRaw)
+            } else {
+                put("talerWithdrawUri", status.talerWithdrawUri)
+            }
         }
-        walletBackendApi.sendRequest("acceptExchangeTermsOfService", args) { 
isError, result ->
+        withdrawStatus.value = WithdrawStatus.Withdrawing
+        walletBackendApi.sendRequest(operation, args) { isError, result ->
             if (isError) {
-                Log.e(TAG, "Error acceptExchangeTermsOfService 
${result.toString(4)}")
+                handleError(operation, result)
                 return@sendRequest
             }
-            val status = ReceivedDetails(s.talerWithdrawUri, s.exchange, 
s.amount, s.fee)
-            withdrawStatus.postValue(status)
+            withdrawStatus.value = 
WithdrawStatus.Success(status.amountRaw.currency)
         }
     }
 
+    @UiThread
+    private fun handleError(operation: String, result: JSONObject) {
+        Log.e(TAG, "Error $operation ${result.toString(2)}")
+        val message = if (result.has("message")) result.getString("message") 
else null
+        withdrawStatus.value = WithdrawStatus.Error(message)
+    }
+
 }
diff --git a/wallet/src/main/res/drawable/ic_directions.xml 
b/wallet/src/main/res/drawable/ic_directions.xml
deleted file mode 100644
index 229d69c..0000000
--- a/wallet/src/main/res/drawable/ic_directions.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<!--
-  ~ This file is part of GNU Taler
-  ~ (C) 2020 Taler Systems S.A.
-  ~
-  ~ GNU Taler is free software; you can redistribute it and/or modify it under 
the
-  ~ terms of the GNU General Public License as published by the Free Software
-  ~ Foundation; either version 3, or (at your option) any later version.
-  ~
-  ~ GNU Taler is distributed in the hope that it will be useful, but WITHOUT 
ANY
-  ~ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
-  ~ A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
-  ~
-  ~ You should have received a copy of the GNU General Public License along 
with
-  ~ GNU Taler; see the file COPYING.  If not, see 
<http://www.gnu.org/licenses/>
-  -->
-
-<vector xmlns:android="http://schemas.android.com/apk/res/android";
-    android:width="24dp"
-    android:height="24dp"
-    android:viewportWidth="24.0"
-    android:viewportHeight="24.0">
-    <path
-        android:fillColor="#FF000000"
-        android:pathData="M21.71,11.29l-9,-9c-0.39,-0.39 -1.02,-0.39 
-1.41,0l-9,9c-0.39,0.39 -0.39,1.02 0,1.41l9,9c0.39,0.39 1.02,0.39 
1.41,0l9,-9c0.39,-0.38 0.39,-1.01 0,-1.41zM14,14.5V12h-4v3H8v-4c0,-0.55 0.45,-1 
1,-1h5V7.5l3.5,3.5 -3.5,3.5z" />
-</vector>
diff --git a/anastasis-ui/src/main/res/drawable/ic_baseline_check.xml 
b/wallet/src/main/res/drawable/ic_edit.xml
similarity index 51%
copy from anastasis-ui/src/main/res/drawable/ic_baseline_check.xml
copy to wallet/src/main/res/drawable/ic_edit.xml
index 219e80e..69d2830 100644
--- a/anastasis-ui/src/main/res/drawable/ic_baseline_check.xml
+++ b/wallet/src/main/res/drawable/ic_edit.xml
@@ -5,6 +5,6 @@
     android:viewportWidth="24"
     android:viewportHeight="24">
     <path
-        android:fillColor="@android:color/white"
-        android:pathData="M9,16.17L4.83,12l-1.42,1.41L9,19 21,7l-1.41,-1.41z" 
/>
+        android:fillColor="#FFFFFF"
+        
android:pathData="M3,17.25V21h3.75L17.81,9.94l-3.75,-3.75L3,17.25zM20.71,7.04c0.39,-0.39
 0.39,-1.02 0,-1.41l-2.34,-2.34c-0.39,-0.39 -1.02,-0.39 -1.41,0l-1.83,1.83 
3.75,3.75 1.83,-1.83z" />
 </vector>
diff --git a/wallet/src/main/res/drawable/ic_history.xml 
b/wallet/src/main/res/drawable/ic_history.xml
deleted file mode 100644
index d9f75ea..0000000
--- a/wallet/src/main/res/drawable/ic_history.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<vector xmlns:android="http://schemas.android.com/apk/res/android";
-    android:width="24dp"
-    android:height="24dp"
-    android:viewportWidth="24.0"
-    android:viewportHeight="24.0">
-    <path
-        android:fillColor="#FF000000"
-        android:pathData="M13,3c-4.97,0 -9,4.03 -9,9L1,12l3.89,3.89 
0.07,0.14L9,12L6,12c0,-3.87 3.13,-7 7,-7s7,3.13 7,7 -3.13,7 -7,7c-1.93,0 
-3.68,-0.79 -4.94,-2.06l-1.42,1.42C8.27,19.99 10.51,21 13,21c4.97,0 9,-4.03 
9,-9s-4.03,-9 -9,-9zM12,8v5l4.28,2.54 0.72,-1.21 -3.5,-2.08L13.5,8L12,8z" />
-</vector>
diff --git a/wallet/src/main/res/drawable/transaction_payment_aborted.xml 
b/wallet/src/main/res/drawable/transaction_payment_aborted.xml
deleted file mode 100644
index 8d47c26..0000000
--- a/wallet/src/main/res/drawable/transaction_payment_aborted.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<!--
-  ~ This file is part of GNU Taler
-  ~ (C) 2020 Taler Systems S.A.
-  ~
-  ~ GNU Taler is free software; you can redistribute it and/or modify it under 
the
-  ~ terms of the GNU General Public License as published by the Free Software
-  ~ Foundation; either version 3, or (at your option) any later version.
-  ~
-  ~ GNU Taler is distributed in the hope that it will be useful, but WITHOUT 
ANY
-  ~ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
-  ~ A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
-  ~
-  ~ You should have received a copy of the GNU General Public License along 
with
-  ~ GNU Taler; see the file COPYING.  If not, see 
<http://www.gnu.org/licenses/>
-  -->
-
-<vector xmlns:android="http://schemas.android.com/apk/res/android";
-    android:width="24dp"
-    android:height="24dp"
-    android:viewportWidth="24"
-    android:viewportHeight="24">
-    <path
-        android:fillColor="#000"
-        android:pathData="M15.46 18.12L16.88 19.54L19 17.41L21.12 19.54L22.54 
18.12L20.41 16L22.54 13.88L21.12 12.46L19 14.59L16.88 12.46L15.46 13.88L17.59 
16M14.97 11.62C14.86 10.28 13.58 8.97 12 9C10.3 9.04 9 10.3 9 12C9 13.7 10.3 
14.94 12 15C12.39 15 12.77 14.92 13.14 14.77C13.41 13.67 13.86 12.63 14.97 
11.62M13 16H7C7 14.9 6.1 14 5 14V10C6.1 10 7 9.1 7 8H17C17 9.1 17.9 10 19 
10V10.05C19.67 10.06 20.34 10.18 21 10.4V6H3V18H13.32C13.1 17.33 13 16.66 13 
16Z" />
-</vector>
diff --git a/wallet/src/main/res/drawable/transaction_tip_declined.xml 
b/wallet/src/main/res/drawable/transaction_tip_declined.xml
deleted file mode 100644
index 4bd1633..0000000
--- a/wallet/src/main/res/drawable/transaction_tip_declined.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<!--
-  ~ This file is part of GNU Taler
-  ~ (C) 2020 Taler Systems S.A.
-  ~
-  ~ GNU Taler is free software; you can redistribute it and/or modify it under 
the
-  ~ terms of the GNU General Public License as published by the Free Software
-  ~ Foundation; either version 3, or (at your option) any later version.
-  ~
-  ~ GNU Taler is distributed in the hope that it will be useful, but WITHOUT 
ANY
-  ~ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
-  ~ A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
-  ~
-  ~ You should have received a copy of the GNU General Public License along 
with
-  ~ GNU Taler; see the file COPYING.  If not, see 
<http://www.gnu.org/licenses/>
-  -->
-
-<vector xmlns:android="http://schemas.android.com/apk/res/android";
-    android:width="24dp"
-    android:height="24dp"
-    android:viewportWidth="24"
-    android:viewportHeight="24">
-    <path
-        android:fillColor="#000"
-        android:pathData="M10 4A4 4 0 0 0 6 8A4 4 0 0 0 10 12A4 4 0 0 0 14 8A4 
4 0 0 0 10 4M17.5 13C15 13 13 15 13 17.5C13 20 15 22 17.5 22C20 22 22 20 22 
17.5C22 15 20 13 17.5 13M10 14C5.58 14 2 15.79 2 18V20H11.5A6.5 6.5 0 0 1 11 
17.5A6.5 6.5 0 0 1 11.95 14.14C11.32 14.06 10.68 14 10 14M17.5 14.5C19.16 14.5 
20.5 15.84 20.5 17.5C20.5 18.06 20.35 18.58 20.08 19L16 14.92C16.42 14.65 16.94 
14.5 17.5 14.5M14.92 16L19 20.08C18.58 20.35 18.06 20.5 17.5 20.5C15.84 20.5 
14.5 19.16 14.5 17.5C14. [...]
-</vector>
diff --git a/wallet/src/main/res/layout/fragment_json.xml 
b/wallet/src/main/res/layout/fragment_json.xml
deleted file mode 100644
index d9bca8f..0000000
--- a/wallet/src/main/res/layout/fragment_json.xml
+++ /dev/null
@@ -1,40 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?><!--
-  ~ 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/>
-  -->
-
-<ScrollView xmlns:android="http://schemas.android.com/apk/res/android";
-    xmlns:app="http://schemas.android.com/apk/res-auto";
-    xmlns:tools="http://schemas.android.com/tools";
-    android:layout_width="wrap_content"
-    android:layout_height="wrap_content"
-    android:orientation="vertical">
-
-    <TextView
-        android:id="@+id/jsonView"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_marginStart="8dp"
-        android:layout_marginTop="8dp"
-        android:layout_marginEnd="8dp"
-        android:layout_marginBottom="8dp"
-        android:fontFamily="monospace"
-        android:textSize="12sp"
-        app:layout_constraintBottom_toBottomOf="parent"
-        app:layout_constraintEnd_toEndOf="parent"
-        app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintTop_toTopOf="parent"
-        tools:text="[JSON]" />
-
-</ScrollView>
diff --git a/wallet/src/main/res/layout/fragment_prompt_withdraw.xml 
b/wallet/src/main/res/layout/fragment_prompt_withdraw.xml
index c9c9402..6bca4ef 100644
--- a/wallet/src/main/res/layout/fragment_prompt_withdraw.xml
+++ b/wallet/src/main/res/layout/fragment_prompt_withdraw.xml
@@ -162,12 +162,12 @@
 
     <ImageButton
         android:id="@+id/selectExchangeButton"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
+        android:layout_width="48dp"
+        android:layout_height="48dp"
         android:layout_marginEnd="16dp"
         android:backgroundTint="@color/colorPrimary"
         android:contentDescription="@string/nav_exchange_fees"
-        android:src="@drawable/ic_cash_usd_outline"
+        android:src="@drawable/ic_edit"
         android:tint="?attr/colorOnPrimary"
         android:visibility="invisible"
         app:layout_constraintBottom_toBottomOf="@+id/withdrawExchangeUrl"
diff --git a/wallet/src/main/res/layout/fragment_transactions.xml 
b/wallet/src/main/res/layout/fragment_transactions.xml
index 547da24..aaf638c 100644
--- a/wallet/src/main/res/layout/fragment_transactions.xml
+++ b/wallet/src/main/res/layout/fragment_transactions.xml
@@ -27,7 +27,7 @@
         android:scrollbars="vertical"
         android:visibility="invisible"
         app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
-        tools:listitem="@layout/list_item_history"
+        tools:listitem="@layout/list_item_transaction"
         tools:visibility="visible" />
 
     <TextView
diff --git a/wallet/src/main/res/layout/list_item_history.xml 
b/wallet/src/main/res/layout/list_item_history.xml
deleted file mode 100644
index bc94738..0000000
--- a/wallet/src/main/res/layout/list_item_history.xml
+++ /dev/null
@@ -1,75 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?><!--
-  ~ 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/>
-  -->
-
-<androidx.constraintlayout.widget.ConstraintLayout 
xmlns:android="http://schemas.android.com/apk/res/android";
-    xmlns:app="http://schemas.android.com/apk/res-auto";
-    xmlns:tools="http://schemas.android.com/tools";
-    android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:background="@drawable/selectable_background"
-    android:foreground="?attr/selectableItemBackground"
-    android:paddingStart="16dp"
-    android:paddingTop="8dp"
-    android:paddingEnd="16dp"
-    android:paddingBottom="8dp">
-
-    <ImageView
-        android:id="@+id/icon"
-        android:layout_width="32dp"
-        android:layout_height="32dp"
-        app:layout_constraintBottom_toBottomOf="parent"
-        app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintTop_toTopOf="parent"
-        app:tint="?android:colorControlNormal"
-        tools:ignore="ContentDescription"
-        tools:src="@drawable/ic_cash_usd_outline" />
-
-    <TextView
-        android:id="@+id/title"
-        style="@style/TransactionTitle"
-        android:layout_width="0dp"
-        android:layout_height="wrap_content"
-        android:layout_marginStart="16dp"
-        android:layout_marginEnd="8dp"
-        app:layout_constraintEnd_toStartOf="@+id/amount"
-        app:layout_constraintStart_toEndOf="@+id/icon"
-        app:layout_constraintTop_toTopOf="parent"
-        tools:text="@string/payment_title" />
-
-    <TextView
-        android:id="@+id/amount"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:textSize="24sp"
-        app:layout_constraintBottom_toBottomOf="parent"
-        app:layout_constraintEnd_toEndOf="parent"
-        app:layout_constraintTop_toTopOf="parent"
-        tools:text="- 1337.23" />
-
-    <TextView
-        android:id="@+id/time"
-        android:layout_width="0dp"
-        android:layout_height="wrap_content"
-        android:layout_marginTop="8dp"
-        android:layout_marginEnd="8dp"
-        android:textSize="14sp"
-        app:layout_constraintBottom_toBottomOf="parent"
-        app:layout_constraintEnd_toStartOf="@+id/amount"
-        app:layout_constraintStart_toStartOf="@+id/title"
-        app:layout_constraintTop_toBottomOf="@+id/title"
-        tools:text="23 min ago" />
-
-</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/wallet/src/main/res/layout/payment_bottom_bar.xml 
b/wallet/src/main/res/layout/payment_bottom_bar.xml
index a19ac4e..dbc60ae 100644
--- a/wallet/src/main/res/layout/payment_bottom_bar.xml
+++ b/wallet/src/main/res/layout/payment_bottom_bar.xml
@@ -32,7 +32,6 @@
             android:layout_height="wrap_content"
             android:layout_marginStart="8dp"
             android:layout_marginTop="8dp"
-            android:layout_marginBottom="27dp"
             android:text="@string/payment_label_amount_total"
             android:visibility="invisible"
             app:layout_constraintBottom_toTopOf="@+id/confirmButton"
diff --git a/wallet/src/main/res/menu/activity_main_drawer.xml 
b/wallet/src/main/res/menu/activity_main_drawer.xml
index 62abc32..d1cc462 100644
--- a/wallet/src/main/res/menu/activity_main_drawer.xml
+++ b/wallet/src/main/res/menu/activity_main_drawer.xml
@@ -43,10 +43,6 @@
                     android:id="@+id/nav_pending_operations"
                     android:icon="@drawable/ic_sync"
                     android:title="@string/pending_operations_title" />
-                <item
-                    android:id="@+id/nav_history"
-                    android:icon="@drawable/ic_history"
-                    android:title="@string/nav_history" />
             </group>
         </menu>
     </item>
diff --git a/wallet/src/main/res/navigation/nav_graph.xml 
b/wallet/src/main/res/navigation/nav_graph.xml
index 93db557..285fac9 100644
--- a/wallet/src/main/res/navigation/nav_graph.xml
+++ b/wallet/src/main/res/navigation/nav_graph.xml
@@ -133,7 +133,7 @@
     </fragment>
     <fragment
         android:id="@+id/selectExchangeFragment"
-        android:name="net.taler.wallet.withdraw.SelectExchangeFragment"
+        android:name="net.taler.wallet.exchanges.SelectExchangeFragment"
         android:label="@string/nav_exchange_fees"
         tools:layout="@layout/fragment_select_exchange" />
 
@@ -143,12 +143,6 @@
         android:label="@string/pending_operations_title"
         tools:layout="@layout/fragment_pending_operations" />
 
-    <fragment
-        android:id="@+id/nav_history"
-        android:name="net.taler.wallet.history.DevHistoryFragment"
-        android:label="@string/nav_history"
-        tools:layout="@layout/fragment_transactions" />
-
     <fragment
         android:id="@+id/nav_uri_input"
         android:name="net.taler.wallet.UriInputFragment"
@@ -173,10 +167,6 @@
         android:id="@+id/action_global_pending_operations"
         app:destination="@id/nav_pending_operations" />
 
-    <action
-        android:id="@+id/action_global_history"
-        app:destination="@id/nav_history" />
-
     <action
         android:id="@+id/action_nav_transaction_detail"
         app:destination="@id/nav_transactions_detail" />
diff --git a/wallet/src/main/res/values/strings.xml 
b/wallet/src/main/res/values/strings.xml
index 421d4ab..1e629a6 100644
--- a/wallet/src/main/res/values/strings.xml
+++ b/wallet/src/main/res/values/strings.xml
@@ -40,7 +40,6 @@ GNU Taler is immune against many types of fraud, such as 
phishing of credit card
     <string name="nav_prompt_withdraw">Withdraw Digital Cash</string>
     <string name="nav_exchange_tos">Exchange\'s Terms of Service</string>
     <string name="nav_exchange_fees">Exchange Fees</string>
-    <string name="nav_history">Event History</string>
     <string name="nav_error">Error</string>
 
     <string name="button_back">Go Back</string>
diff --git a/wallet/src/main/res/xml/settings_backup.xml 
b/wallet/src/main/res/xml/settings_backup.xml
index 52b72ac..f8c5839 100644
--- a/wallet/src/main/res/xml/settings_backup.xml
+++ b/wallet/src/main/res/xml/settings_backup.xml
@@ -14,7 +14,7 @@
   ~ GNU Taler; see the file COPYING.  If not, see 
<http://www.gnu.org/licenses/>
   -->
 
-<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android";
+<PreferenceScreen
     xmlns:app="http://schemas.android.com/apk/res-auto";>
 
     <SwitchPreferenceCompat
diff --git 
a/wallet/src/test/java/net/taler/wallet/crypto/Base32CrockfordTest.kt 
b/wallet/src/test/java/net/taler/wallet/crypto/Base32CrockfordTest.kt
deleted file mode 100644
index 30332fc..0000000
--- a/wallet/src/test/java/net/taler/wallet/crypto/Base32CrockfordTest.kt
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * This file is part of GNU Taler
- * (C) 2020 Taler Systems S.A.
- *
- * GNU Taler is free software; you can redistribute it and/or modify it under 
the
- * terms of the GNU General Public License as published by the Free Software
- * Foundation; either version 3, or (at your option) any later version.
- *
- * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
- * A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
- */
-
-package net.taler.wallet.crypto
-
-import org.junit.Test
-
-class Base32CrockfordTest {
-    @Test
-    fun testBasic() {
-        val inputStr = "Hello, World"
-        val data = inputStr.toByteArray(Charsets.UTF_8)
-        val enc = Base32Crockford.encode(data)
-        println(enc)
-        val dec = Base32Crockford.decode(enc)
-        val recoveredInputStr = dec.toString(Charsets.UTF_8)
-        println(recoveredInputStr)
-
-        val foo =
-            
Base32Crockford.decode("51R7ARKCD5HJTTV5F4G0M818E9SP280A40G2GVH04CR30H2365338E9G6RT4AH1N6H13EGHR70RK6H1S6X2M4CSP8CSK8E1G88VKJH25610KGCHR8RWM4DJ47123CH9K89334D1S8N24ACJ48CR3EH256MR3AH1R711KCE9N6S134GSN6RW46D1H6CV3CDHJ6D0KEDHR6D24CD248MWKADHJ6WT34D25712KCD2474V46EA18H2M4GHM6WTK2E216S14CD238GSK0G9G692KCDHM6RW34CT16MV3CG9P60S34C1G70SMCHHQ8CVKJG9K6CVK6GHK70R46HJ26CR4AE9M8523ADHS8RR3EE1R74S32CHP6N1K0GT38D1M6C1R84TM2E9N8MSK2C1J71248E9H6H1MCD9J70VK4GSG6124CCHR6RS4ADSH8N0M4H1G84R4CD1G
 [...]
-        println(foo.toString(Charsets.UTF_8))
-    }
-}

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]