gnunet-svn
[Top][All Lists]
Advanced

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

[taler-taler-android] branch master updated (4a6630d -> a94022d)


From: gnunet
Subject: [taler-taler-android] branch master updated (4a6630d -> a94022d)
Date: Mon, 18 May 2020 14:47:17 +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 4a6630d  [pos] fix string commit
     new e74f39e  [wallet] separate history and transactions UI
     new bedd7b0  [wallet] render transaction list from new transactions API
     new 171a1ae  [wallet] clean up old history code that not needed anymore
     new 69093aa  [wallet] Remove success pages for withdrawal and payment
     new 7f36a54  [wallet] add clickable actions to transaction details screen
     new dfc0db1  [wallet] provide extra info in transactions when withdrawal 
requires confirmation
     new 3565fc0  [wallet] remove workarounds for fixed core bugs
     new 6028376  [wallet] cache transactions per currency
     new 49b9fb9  [wallet] show currency's transaction list after successful 
operations
     new ebaffcd  [wallet] show a pending badge next to balances with pending 
transactions
     new 290b062  [wallet] remove unused strings after moving to new 
transaction API
     new 40ccf51  [wallet] show generic transaction titles in detail action bar
     new a94022d  [wallet] upgrade to latest core and fix sorting bug

The 13 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:
 .../src/main/java/net/taler/common/AndroidUtils.kt |  11 +
 .../main/java/net/taler/common/ContractTerms.kt    |   4 +
 .../src/main/java/net/taler/common/Event.kt        |  51 +++
 wallet/build.gradle                                |   4 +-
 .../src/main/java/net/taler/wallet/MainActivity.kt |   6 +-
 .../src/main/java/net/taler/wallet/MainFragment.kt |  26 +-
 .../main/java/net/taler/wallet/MainViewModel.kt    |  43 +-
 .../net/taler/wallet/balances/BalanceAdapter.kt    |  10 +-
 .../net/taler/wallet/balances/BalancesFragment.kt  |   5 +-
 .../net/taler/wallet/history/DevHistoryAdapter.kt  | 110 +++++
 .../DevHistoryFragment.kt}                         |  67 +--
 .../net/taler/wallet/history/DevHistoryManager.kt  |  75 ++++
 .../java/net/taler/wallet/history/HistoryEvent.kt  | 199 +++++++++
 .../JsonDialogFragment.kt                          |   2 +-
 .../net/taler/wallet/payment/PaymentManager.kt     |   6 +-
 .../wallet/payment/PaymentSuccessfulFragment.kt    |  49 --
 .../taler/wallet/payment/PromptPaymentFragment.kt  |  13 +-
 .../wallet/transactions/ReserveTransaction.kt      |  59 ---
 .../net/taler/wallet/transactions/Transaction.kt   | 497 ---------------------
 .../wallet/transactions/TransactionAdapter.kt      | 113 ++---
 .../transactions/TransactionDetailFragment.kt      |  86 ++--
 .../wallet/transactions/TransactionManager.kt      |  95 ++--
 .../net/taler/wallet/transactions/Transactions.kt  | 187 ++++++++
 .../wallet/transactions/TransactionsFragment.kt    |  13 +-
 .../wallet/withdraw/PromptWithdrawFragment.kt      |  14 +-
 .../net/taler/wallet/withdraw/WithdrawManager.kt   |   9 +-
 .../wallet/withdraw/WithdrawSuccessfulFragment.kt  |  44 --
 .../res/drawable/{side_nav_bar.xml => badge.xml}   |  12 +-
 wallet/src/main/res/drawable/ic_check_circle.xml   |  26 --
 .../src/main/res/drawable/ic_history.xml           |   0
 .../src/main/res/layout/fragment_already_paid.xml  |   2 +-
 .../res/layout/fragment_payment_successful.xml     |  63 ---
 ...t_paid.xml => fragment_transaction_payment.xml} |   2 +
 ...raw.xml => fragment_transaction_withdrawal.xml} |  53 +--
 .../src/main/res/layout/fragment_transactions.xml  |   2 +-
 .../res/layout/fragment_withdraw_successful.xml    |  63 ---
 wallet/src/main/res/layout/list_item_balance.xml   |  30 +-
 ..._item_transaction.xml => list_item_history.xml} |   2 +-
 .../src/main/res/layout/list_item_transaction.xml  |  55 ++-
 wallet/src/main/res/menu/activity_main_drawer.xml  |  27 +-
 wallet/src/main/res/menu/transactions_detail.xml   |   4 -
 wallet/src/main/res/navigation/nav_graph.xml       |  37 +-
 wallet/src/main/res/values/strings.xml             |  33 +-
 .../wallet/transactions/ReserveTransactionTest.kt  |  52 ---
 .../taler/wallet/transactions/TransactionTest.kt   | 470 -------------------
 45 files changed, 1068 insertions(+), 1663 deletions(-)
 create mode 100644 taler-kotlin-common/src/main/java/net/taler/common/Event.kt
 create mode 100644 
wallet/src/main/java/net/taler/wallet/history/DevHistoryAdapter.kt
 copy wallet/src/main/java/net/taler/wallet/{balances/BalancesFragment.kt => 
history/DevHistoryFragment.kt} (50%)
 create mode 100644 
wallet/src/main/java/net/taler/wallet/history/DevHistoryManager.kt
 create mode 100644 
wallet/src/main/java/net/taler/wallet/history/HistoryEvent.kt
 rename wallet/src/main/java/net/taler/wallet/{transactions => 
history}/JsonDialogFragment.kt (97%)
 delete mode 100644 
wallet/src/main/java/net/taler/wallet/payment/PaymentSuccessfulFragment.kt
 delete mode 100644 
wallet/src/main/java/net/taler/wallet/transactions/ReserveTransaction.kt
 delete mode 100644 
wallet/src/main/java/net/taler/wallet/transactions/Transaction.kt
 create mode 100644 
wallet/src/main/java/net/taler/wallet/transactions/Transactions.kt
 delete mode 100644 
wallet/src/main/java/net/taler/wallet/withdraw/WithdrawSuccessfulFragment.kt
 copy wallet/src/main/res/drawable/{side_nav_bar.xml => badge.xml} (78%)
 delete mode 100644 wallet/src/main/res/drawable/ic_check_circle.xml
 copy merchant-terminal/src/main/res/drawable/ic_history_black_24dp.xml => 
wallet/src/main/res/drawable/ic_history.xml (100%)
 delete mode 100644 wallet/src/main/res/layout/fragment_payment_successful.xml
 rename wallet/src/main/res/layout/{fragment_event_paid.xml => 
fragment_transaction_payment.xml} (98%)
 rename wallet/src/main/res/layout/{fragment_event_withdraw.xml => 
fragment_transaction_withdrawal.xml} (79%)
 delete mode 100644 wallet/src/main/res/layout/fragment_withdraw_successful.xml
 copy wallet/src/main/res/layout/{list_item_transaction.xml => 
list_item_history.xml} (98%)
 delete mode 100644 
wallet/src/test/java/net/taler/wallet/transactions/ReserveTransactionTest.kt
 delete mode 100644 
wallet/src/test/java/net/taler/wallet/transactions/TransactionTest.kt

diff --git a/taler-kotlin-common/src/main/java/net/taler/common/AndroidUtils.kt 
b/taler-kotlin-common/src/main/java/net/taler/common/AndroidUtils.kt
index 64d5656..3ab45ad 100644
--- a/taler-kotlin-common/src/main/java/net/taler/common/AndroidUtils.kt
+++ b/taler-kotlin-common/src/main/java/net/taler/common/AndroidUtils.kt
@@ -18,9 +18,12 @@ package net.taler.common
 
 import android.content.Context
 import android.content.Context.CONNECTIVITY_SERVICE
+import android.content.Intent
+import android.content.pm.PackageManager.MATCH_DEFAULT_ONLY
 import android.net.ConnectivityManager
 import android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET
 import android.os.Build.VERSION.SDK_INT
+import android.os.Looper
 import android.text.format.DateUtils.DAY_IN_MILLIS
 import android.text.format.DateUtils.FORMAT_ABBREV_ALL
 import android.text.format.DateUtils.FORMAT_ABBREV_MONTH
@@ -58,6 +61,10 @@ fun View.fadeOut(endAction: () -> Unit = {}) {
     }.start()
 }
 
+fun assertUiThread() {
+    check(Looper.getMainLooper().thread == Thread.currentThread())
+}
+
 /**
  * Use this with 'when' expressions when you need it to handle all 
possibilities/branches.
  */
@@ -75,6 +82,10 @@ fun Context.isOnline(): Boolean {
     }
 }
 
+fun Intent.isSafe(context: Context): Boolean {
+    return context.packageManager.queryIntentActivities(this, 
MATCH_DEFAULT_ONLY).isNotEmpty()
+}
+
 fun Fragment.navigate(directions: NavDirections) = 
findNavController().navigate(directions)
 
 fun Long.toRelativeTime(context: Context): CharSequence {
diff --git 
a/taler-kotlin-common/src/main/java/net/taler/common/ContractTerms.kt 
b/taler-kotlin-common/src/main/java/net/taler/common/ContractTerms.kt
index cd417ef..8b8e02d 100644
--- a/taler-kotlin-common/src/main/java/net/taler/common/ContractTerms.kt
+++ b/taler-kotlin-common/src/main/java/net/taler/common/ContractTerms.kt
@@ -67,6 +67,10 @@ data class ContractProduct(
     }
 }
 
+data class ContractMerchant(
+    val name: String
+)
+
 @JsonInclude(NON_EMPTY)
 class Timestamp(
     @JsonProperty("t_ms")
diff --git a/taler-kotlin-common/src/main/java/net/taler/common/Event.kt 
b/taler-kotlin-common/src/main/java/net/taler/common/Event.kt
new file mode 100644
index 0000000..779247f
--- /dev/null
+++ b/taler-kotlin-common/src/main/java/net/taler/common/Event.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 androidx.lifecycle.LiveData
+import androidx.lifecycle.Observer
+import java.util.concurrent.atomic.AtomicBoolean
+
+/**
+ * Used as a wrapper for data that is exposed via a [LiveData] that represents 
an one-time event.
+ */
+open class Event<out T>(private val content: T) {
+
+    private val isConsumed = AtomicBoolean(false)
+
+    /**
+     * Returns the content and prevents its use again.
+     */
+    fun getIfNotConsumed(): T? {
+        return if (isConsumed.compareAndSet(false, true)) content else null
+    }
+
+}
+
+fun <T> T.toEvent() = Event(this)
+
+/**
+ * An [Observer] for [Event]s, simplifying the pattern of checking if the 
[Event]'s content has
+ * already been consumed.
+ *
+ * [onEvent] is *only* called if the [Event]'s contents has not been consumed.
+ */
+class EventObserver<T>(private val onEvent: (T) -> Unit) : Observer<Event<T>> {
+    override fun onChanged(event: Event<T>?) {
+        event?.getIfNotConsumed()?.let { onEvent(it) }
+    }
+}
diff --git a/wallet/build.gradle b/wallet/build.gradle
index f976b24..b977f91 100644
--- a/wallet/build.gradle
+++ b/wallet/build.gradle
@@ -23,7 +23,7 @@ plugins {
     id "de.undercouch.download"
 }
 
-def walletCoreVersion = "v0.7.1-dev.3"
+def walletCoreVersion = "v0.7.1-dev.6"
 
 android {
     compileSdkVersion 29
@@ -35,7 +35,7 @@ android {
         minSdkVersion 24
         targetSdkVersion 29
         versionCode 6
-        versionName "0.7.1.dev.3"
+        versionName "0.7.1.dev.6"
         testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
         buildConfigField "String", "WALLET_CORE_VERSION", 
"\"$walletCoreVersion\""
     }
diff --git a/wallet/src/main/java/net/taler/wallet/MainActivity.kt 
b/wallet/src/main/java/net/taler/wallet/MainActivity.kt
index b6e9a7a..f626e4f 100644
--- a/wallet/src/main/java/net/taler/wallet/MainActivity.kt
+++ b/wallet/src/main/java/net/taler/wallet/MainActivity.kt
@@ -75,7 +75,7 @@ class MainActivity : AppCompatActivity(), 
OnNavigationItemSelectedListener {
 
         setSupportActionBar(toolbar)
         val appBarConfiguration = AppBarConfiguration(
-            setOf(R.id.nav_main, R.id.nav_settings, 
R.id.nav_pending_operations),
+            setOf(R.id.nav_main, R.id.nav_settings, 
R.id.nav_pending_operations, R.id.nav_history),
             drawer_layout
         )
         toolbar.setupWithNavController(nav, appBarConfiguration)
@@ -86,7 +86,7 @@ class MainActivity : AppCompatActivity(), 
OnNavigationItemSelectedListener {
 
         val versionView: TextView = 
nav_view.getHeaderView(0).findViewById(R.id.versionView)
         model.devMode.observe(this, Observer { enabled ->
-            nav_view.menu.findItem(R.id.nav_pending_operations).isVisible = 
enabled
+            nav_view.menu.findItem(R.id.nav_dev).isVisible = enabled
             if (enabled) {
                 @SuppressLint("SetTextI18n")
                 versionView.text = "$VERSION_NAME ($VERSION_CODE)"
@@ -116,6 +116,7 @@ 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
@@ -156,6 +157,7 @@ class MainActivity : AppCompatActivity(), 
OnNavigationItemSelectedListener {
                     model.showProgressBar.value = false
                     val res = when (status) {
                         is RefundStatus.Error -> R.string.refund_error
+                        // TODO once wallet-core exposes currency, navigate to 
its transaction list
                         is RefundStatus.Success -> R.string.refund_success
                     }
                     Snackbar.make(nav_view, res, LENGTH_LONG).show()
diff --git a/wallet/src/main/java/net/taler/wallet/MainFragment.kt 
b/wallet/src/main/java/net/taler/wallet/MainFragment.kt
index 2905238..26c5a90 100644
--- a/wallet/src/main/java/net/taler/wallet/MainFragment.kt
+++ b/wallet/src/main/java/net/taler/wallet/MainFragment.kt
@@ -23,14 +23,21 @@ import android.view.ViewGroup
 import androidx.fragment.app.Fragment
 import androidx.fragment.app.activityViewModels
 import androidx.lifecycle.Observer
+import androidx.navigation.fragment.findNavController
 import kotlinx.android.synthetic.main.fragment_main.*
+import net.taler.common.EventObserver
+import net.taler.wallet.CurrencyMode.MULTI
+import net.taler.wallet.CurrencyMode.SINGLE
+import net.taler.wallet.balances.BalanceItem
 import net.taler.wallet.balances.BalancesFragment
 import net.taler.wallet.transactions.TransactionsFragment
 
+enum class CurrencyMode { SINGLE, MULTI }
+
 class MainFragment : Fragment() {
 
     private val model: MainViewModel by activityViewModels()
-    private var currentTag: String? = null
+    private var currencyMode: CurrencyMode? = null
 
     override fun onCreateView(
         inflater: LayoutInflater,
@@ -44,6 +51,13 @@ class MainFragment : Fragment() {
         model.balances.observe(viewLifecycleOwner, Observer {
             onBalancesChanged(it.values.toList())
         })
+        model.transactionsEvent.observe(viewLifecycleOwner, EventObserver { 
currency ->
+            // we only need to navigate to a dedicated list, when in 
multi-currency mode
+            if (currencyMode == MULTI) {
+                model.transactionManager.selectedCurrency = currency
+                
findNavController().navigate(R.id.action_nav_main_to_nav_transactions)
+            }
+        })
 
         mainFab.setOnClickListener {
             scanQrCode(requireActivity())
@@ -56,17 +70,17 @@ class MainFragment : Fragment() {
     }
 
     private fun onBalancesChanged(balances: List<BalanceItem>) {
-        val tag = if (balances.size == 1) "single" else "multi"
-        if (currentTag != tag) {
-            val f = if (tag == "single") {
+        val mode = if (balances.size == 1) SINGLE else MULTI
+        if (currencyMode != mode) {
+            val f = if (mode == SINGLE) {
                 model.transactionManager.selectedCurrency = 
balances[0].available.currency
                 TransactionsFragment()
             } else {
                 BalancesFragment()
             }
-            currentTag = tag
+            currencyMode = mode
             childFragmentManager.beginTransaction()
-                .replace(R.id.mainFragmentContainer, f, tag)
+                .replace(R.id.mainFragmentContainer, f, mode.name)
                 .commitNow()
         }
     }
diff --git a/wallet/src/main/java/net/taler/wallet/MainViewModel.kt 
b/wallet/src/main/java/net/taler/wallet/MainViewModel.kt
index 230c310..75cab67 100644
--- a/wallet/src/main/java/net/taler/wallet/MainViewModel.kt
+++ b/wallet/src/main/java/net/taler/wallet/MainViewModel.kt
@@ -23,11 +23,17 @@ import androidx.lifecycle.AndroidViewModel
 import androidx.lifecycle.LiveData
 import androidx.lifecycle.MutableLiveData
 import androidx.lifecycle.distinctUntilChanged
+import androidx.lifecycle.viewModelScope
 import 
com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES
 import com.fasterxml.jackson.databind.ObjectMapper
 import com.fasterxml.jackson.module.kotlin.KotlinModule
 import net.taler.common.Amount
+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.history.DevHistoryManager
 import net.taler.wallet.payment.PaymentManager
 import net.taler.wallet.pending.PendingOperationsManager
 import net.taler.wallet.refund.RefundManager
@@ -43,8 +49,6 @@ private val transactionNotifications = listOf(
     "withdraw-group-finished"
 )
 
-data class BalanceItem(val available: Amount, val pendingIncoming: Amount)
-
 class MainViewModel(val app: Application) : AndroidViewModel(app) {
 
     private val mBalances = MutableLiveData<Map<String, BalanceItem>>()
@@ -69,14 +73,16 @@ class MainViewModel(val app: Application) : 
AndroidViewModel(app) {
             Log.i(TAG, "Received notification from wallet-core: 
${payload.toString(2)}")
             loadBalances()
             if (payload.optString("type") in transactionNotifications) {
-                // update transaction list
-                // TODO do this in a better way
-                transactionManager.showAll.value?.let {
-                    transactionManager.showAll.postValue(it)
-                }
+                assertUiThread()
+                // TODO notification API should give us a currency to update
+                // update currently selected transaction list
+                transactionManager.loadTransactions()
+            }
+            // refresh pending ops and history with each notification
+            if (devMode.value == true) {
+                pendingOperationsManager.getPending()
+                historyManager.loadHistory()
             }
-            // refresh pending ops with each notification
-            if (devMode.value == true) pendingOperationsManager.getPending()
         }
     }
 
@@ -88,9 +94,15 @@ class MainViewModel(val app: Application) : 
AndroidViewModel(app) {
     val paymentManager = PaymentManager(walletBackendApi, mapper)
     val pendingOperationsManager: PendingOperationsManager =
         PendingOperationsManager(walletBackendApi)
-    val transactionManager: TransactionManager = 
TransactionManager(walletBackendApi, mapper)
+    val historyManager: DevHistoryManager =
+        DevHistoryManager(walletBackendApi, viewModelScope, mapper)
+    val transactionManager: TransactionManager =
+        TransactionManager(walletBackendApi, viewModelScope, mapper)
     val refundManager = RefundManager(walletBackendApi)
 
+    private val mTransactionsEvent = MutableLiveData<Event<String>>()
+    val transactionsEvent: LiveData<Event<String>> = mTransactionsEvent
+
     override fun onCleared() {
         walletBackendApi.destroy()
         super.onCleared()
@@ -114,13 +126,22 @@ class MainViewModel(val app: Application) : 
AndroidViewModel(app) {
                 val jsonAmountIncoming = byCurrency.getJSONObject(currency)
                     .getJSONObject("pendingIncoming")
                 val amountIncoming = Amount.fromJsonObject(jsonAmountIncoming)
-                balanceMap[currency] = BalanceItem(amount, amountIncoming)
+                val hasPending = transactionManager.hasPending(currency)
+                balanceMap[currency] = BalanceItem(amount, amountIncoming, 
hasPending)
             }
             mBalances.postValue(balanceMap)
             showProgressBar.postValue(false)
         }
     }
 
+    /**
+     * Navigates to the given currency's transaction list, when [MainFragment] 
is shown.
+     */
+    @UiThread
+    fun showTransactions(currency: String) {
+        mTransactionsEvent.value = currency.toEvent()
+    }
+
     @UiThread
     fun dangerouslyReset() {
         walletBackendApi.sendRequest("reset", null)
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 0ccfeb2..be50364 100644
--- a/wallet/src/main/java/net/taler/wallet/balances/BalanceAdapter.kt
+++ b/wallet/src/main/java/net/taler/wallet/balances/BalanceAdapter.kt
@@ -24,10 +24,12 @@ import android.view.ViewGroup
 import android.widget.TextView
 import androidx.recyclerview.widget.RecyclerView
 import androidx.recyclerview.widget.RecyclerView.Adapter
-import net.taler.wallet.BalanceItem
+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)
+
 class BalanceAdapter(private val listener: BalanceClickListener) : 
Adapter<BalanceViewHolder>() {
 
     private var items = emptyList<BalanceItem>()
@@ -55,10 +57,11 @@ class BalanceAdapter(private val listener: 
BalanceClickListener) : Adapter<Balan
     }
 
     inner class BalanceViewHolder(private val v: View) : 
RecyclerView.ViewHolder(v) {
-        private val currencyView: TextView = 
v.findViewById(R.id.balance_currency)
-        private val amountView: TextView = v.findViewById(R.id.balance_amount)
+        private val currencyView: TextView = 
v.findViewById(R.id.balanceCurrencyView)
+        private val amountView: TextView = 
v.findViewById(R.id.balanceAmountView)
         private val balanceInboundAmount: TextView = 
v.findViewById(R.id.balanceInboundAmount)
         private val balanceInboundLabel: TextView = 
v.findViewById(R.id.balanceInboundLabel)
+        private val pendingView: TextView = v.findViewById(R.id.pendingView)
 
         fun bind(item: BalanceItem) {
             v.setOnClickListener { 
listener.onBalanceClick(item.available.currency) }
@@ -75,6 +78,7 @@ class BalanceAdapter(private val listener: 
BalanceClickListener) : Adapter<Balan
                 balanceInboundAmount.text =
                     v.context.getString(R.string.amount_positive, 
amountIncoming)
             }
+            pendingView.visibility = if (item.hasPending) VISIBLE else GONE
         }
     }
 
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 0a2b29a..22dd992 100644
--- a/wallet/src/main/java/net/taler/wallet/balances/BalancesFragment.kt
+++ b/wallet/src/main/java/net/taler/wallet/balances/BalancesFragment.kt
@@ -27,12 +27,10 @@ import android.view.ViewGroup
 import androidx.fragment.app.Fragment
 import androidx.fragment.app.activityViewModels
 import androidx.lifecycle.Observer
-import androidx.navigation.fragment.findNavController
 import androidx.recyclerview.widget.DividerItemDecoration
 import androidx.recyclerview.widget.LinearLayoutManager.VERTICAL
 import kotlinx.android.synthetic.main.fragment_balances.*
 import net.taler.common.fadeIn
-import net.taler.wallet.BalanceItem
 import net.taler.wallet.MainViewModel
 import net.taler.wallet.R
 
@@ -79,8 +77,7 @@ class BalancesFragment : Fragment(),
     }
 
     override fun onBalanceClick(currency: String) {
-        model.transactionManager.selectedCurrency = currency
-        findNavController().navigate(R.id.action_nav_main_to_nav_transactions)
+        model.showTransactions(currency)
     }
 
 }
diff --git a/wallet/src/main/java/net/taler/wallet/history/DevHistoryAdapter.kt 
b/wallet/src/main/java/net/taler/wallet/history/DevHistoryAdapter.kt
new file mode 100644
index 0000000..a2684e1
--- /dev/null
+++ b/wallet/src/main/java/net/taler/wallet/history/DevHistoryAdapter.kt
@@ -0,0 +1,110 @@
+/*
+ * 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/balances/BalancesFragment.kt 
b/wallet/src/main/java/net/taler/wallet/history/DevHistoryFragment.kt
similarity index 50%
copy from wallet/src/main/java/net/taler/wallet/balances/BalancesFragment.kt
copy to wallet/src/main/java/net/taler/wallet/history/DevHistoryFragment.kt
index 0a2b29a..c3c07a3 100644
--- a/wallet/src/main/java/net/taler/wallet/balances/BalancesFragment.kt
+++ b/wallet/src/main/java/net/taler/wallet/history/DevHistoryFragment.kt
@@ -14,73 +14,74 @@
  * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 
-package net.taler.wallet.balances
+package net.taler.wallet.history
 
 import android.os.Bundle
-import android.transition.TransitionManager.beginDelayedTransition
 import android.view.LayoutInflater
 import android.view.View
-import android.view.View.GONE
 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.navigation.fragment.findNavController
 import androidx.recyclerview.widget.DividerItemDecoration
 import androidx.recyclerview.widget.LinearLayoutManager.VERTICAL
-import kotlinx.android.synthetic.main.fragment_balances.*
+import kotlinx.android.synthetic.main.fragment_transactions.*
 import net.taler.common.fadeIn
-import net.taler.wallet.BalanceItem
+import net.taler.common.fadeOut
 import net.taler.wallet.MainViewModel
 import net.taler.wallet.R
 
-interface BalanceClickListener {
-    fun onBalanceClick(currency: String)
+internal interface OnEventClickListener {
+    fun onTransactionClicked(historyEvent: HistoryEvent)
 }
 
-class BalancesFragment : Fragment(),
-    BalanceClickListener {
+class DevHistoryFragment : Fragment(),
+    OnEventClickListener {
 
     private val model: MainViewModel by activityViewModels()
-
-    private val balancesAdapter = BalanceAdapter(this)
+    private val historyManager by lazy { model.historyManager }
+    private val historyAdapter by lazy { DevHistoryAdapter(this) }
 
     override fun onCreateView(
-        inflater: LayoutInflater,
-        container: ViewGroup?,
+        inflater: LayoutInflater, container: ViewGroup?,
         savedInstanceState: Bundle?
     ): View? {
-        return inflater.inflate(R.layout.fragment_balances, container, false)
+        return inflater.inflate(R.layout.fragment_transactions, container, 
false)
     }
 
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
-        mainList.apply {
-            adapter = balancesAdapter
+        if (savedInstanceState == null) historyManager.loadHistory()
+
+        list.apply {
+            adapter = historyAdapter
             addItemDecoration(DividerItemDecoration(context, VERTICAL))
         }
-
-        model.balances.observe(viewLifecycleOwner, Observer {
-            onBalancesChanged(it.values.toList())
+        historyManager.progress.observe(viewLifecycleOwner, Observer { show ->
+            progressBar.visibility = if (show) VISIBLE else INVISIBLE
+        })
+        historyManager.history.observe(viewLifecycleOwner, Observer { result ->
+            onHistoryResult(result)
         })
     }
 
-    private fun onBalancesChanged(balances: List<BalanceItem>) {
-        beginDelayedTransition(view as ViewGroup)
-        if (balances.isEmpty()) {
-            mainEmptyState.visibility = VISIBLE
-            mainList.visibility = GONE
-        } else {
-            balancesAdapter.setItems(balances)
-            mainEmptyState.visibility = INVISIBLE
-            mainList.fadeIn()
-        }
+    override fun onTransactionClicked(historyEvent: HistoryEvent) {
+        JsonDialogFragment.new(historyEvent.json.toString(2))
+            .show(parentFragmentManager, null)
     }
 
-    override fun onBalanceClick(currency: String) {
-        model.transactionManager.selectedCurrency = currency
-        findNavController().navigate(R.id.action_nav_main_to_nav_transactions)
+    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
new file mode 100644
index 0000000..9052d6e
--- /dev/null
+++ b/wallet/src/main/java/net/taler/wallet/history/DevHistoryManager.kt
@@ -0,0 +1,75 @@
+/*
+ * 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
new file mode 100644
index 0000000..3cbe7d7
--- /dev/null
+++ b/wallet/src/main/java/net/taler/wallet/history/HistoryEvent.kt
@@ -0,0 +1,199 @@
+/*
+ * 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/transactions/JsonDialogFragment.kt 
b/wallet/src/main/java/net/taler/wallet/history/JsonDialogFragment.kt
similarity index 97%
rename from 
wallet/src/main/java/net/taler/wallet/transactions/JsonDialogFragment.kt
rename to wallet/src/main/java/net/taler/wallet/history/JsonDialogFragment.kt
index 2337059..31c2b93 100644
--- a/wallet/src/main/java/net/taler/wallet/transactions/JsonDialogFragment.kt
+++ b/wallet/src/main/java/net/taler/wallet/history/JsonDialogFragment.kt
@@ -14,7 +14,7 @@
  * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 
-package net.taler.wallet.transactions
+package net.taler.wallet.history
 
 import android.os.Bundle
 import android.view.LayoutInflater
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 8aaebbc..5c73d6c 100644
--- a/wallet/src/main/java/net/taler/wallet/payment/PaymentManager.kt
+++ b/wallet/src/main/java/net/taler/wallet/payment/PaymentManager.kt
@@ -107,11 +107,11 @@ class PaymentManager(
         mDetailsShown.value = !oldValue
     }
 
-    fun confirmPay(proposalId: String) {
+    fun confirmPay(proposalId: String, currency: String) {
         val args = JSONObject(mapOf("proposalId" to proposalId))
 
         walletBackendApi.sendRequest("confirmPay", args) { _, _ ->
-            mPayStatus.postValue(PayStatus.Success)
+            mPayStatus.postValue(PayStatus.Success(currency))
         }
     }
 
@@ -157,5 +157,5 @@ sealed class PayStatus {
     data class InsufficientBalance(val contractTerms: ContractTerms) : 
PayStatus()
     data class AlreadyPaid(val contractTerms: ContractTerms) : PayStatus()
     data class Error(val error: String) : PayStatus()
-    object Success : PayStatus()
+    data class Success(val currency: String) : PayStatus()
 }
diff --git 
a/wallet/src/main/java/net/taler/wallet/payment/PaymentSuccessfulFragment.kt 
b/wallet/src/main/java/net/taler/wallet/payment/PaymentSuccessfulFragment.kt
deleted file mode 100644
index 2a868b0..0000000
--- a/wallet/src/main/java/net/taler/wallet/payment/PaymentSuccessfulFragment.kt
+++ /dev/null
@@ -1,49 +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.payment
-
-import android.os.Bundle
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import androidx.fragment.app.Fragment
-import androidx.navigation.fragment.findNavController
-import kotlinx.android.synthetic.main.fragment_payment_successful.*
-import net.taler.common.fadeIn
-import net.taler.wallet.R
-
-/**
- * Fragment that shows the success message for a payment.
- */
-class PaymentSuccessfulFragment : Fragment() {
-
-    override fun onCreateView(
-        inflater: LayoutInflater, container: ViewGroup?,
-        savedInstanceState: Bundle?
-    ): View? {
-        return inflater.inflate(R.layout.fragment_payment_successful, 
container, false)
-    }
-
-    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
-        successImageView.fadeIn()
-        successTextView.fadeIn()
-        backButton.setOnClickListener {
-            findNavController().navigateUp()
-        }
-    }
-
-}
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 6d31879..6f806b7 100644
--- a/wallet/src/main/java/net/taler/wallet/payment/PromptPaymentFragment.kt
+++ b/wallet/src/main/java/net/taler/wallet/payment/PromptPaymentFragment.kt
@@ -30,14 +30,16 @@ import androidx.lifecycle.observe
 import androidx.navigation.fragment.findNavController
 import androidx.recyclerview.widget.LinearLayoutManager
 import androidx.transition.TransitionManager.beginDelayedTransition
+import com.google.android.material.snackbar.Snackbar
+import com.google.android.material.snackbar.Snackbar.LENGTH_LONG
 import kotlinx.android.synthetic.main.payment_bottom_bar.*
 import kotlinx.android.synthetic.main.payment_details.*
 import net.taler.common.Amount
 import net.taler.common.ContractTerms
 import net.taler.common.fadeIn
 import net.taler.common.fadeOut
-import net.taler.wallet.R
 import net.taler.wallet.MainViewModel
+import net.taler.wallet.R
 
 /**
  * Show a payment and ask the user to accept/decline.
@@ -97,7 +99,10 @@ class PromptPaymentFragment : Fragment(), 
ProductImageClickListener {
                 confirmButton.isEnabled = true
                 confirmButton.setOnClickListener {
                     model.showProgressBar.value = true
-                    paymentManager.confirmPay(payStatus.proposalId)
+                    paymentManager.confirmPay(
+                        payStatus.proposalId,
+                        payStatus.contractTerms.amount.currency
+                    )
                     confirmButton.fadeOut()
                     confirmProgressBar.fadeIn()
                 }
@@ -111,7 +116,9 @@ class PromptPaymentFragment : Fragment(), 
ProductImageClickListener {
             is PayStatus.Success -> {
                 showLoading(false)
                 paymentManager.resetPayStatus()
-                
findNavController().navigate(R.id.action_promptPayment_to_paymentSuccessful)
+                
findNavController().navigate(R.id.action_promptPayment_to_nav_main)
+                model.showTransactions(payStatus.currency)
+                Snackbar.make(requireView(), R.string.payment_initiated, 
LENGTH_LONG).show()
             }
             is PayStatus.AlreadyPaid -> {
                 showLoading(false)
diff --git 
a/wallet/src/main/java/net/taler/wallet/transactions/ReserveTransaction.kt 
b/wallet/src/main/java/net/taler/wallet/transactions/ReserveTransaction.kt
deleted file mode 100644
index e497e9a..0000000
--- a/wallet/src/main/java/net/taler/wallet/transactions/ReserveTransaction.kt
+++ /dev/null
@@ -1,59 +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.transactions
-
-import com.fasterxml.jackson.annotation.JsonProperty
-import com.fasterxml.jackson.annotation.JsonSubTypes
-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.Timestamp
-
-
-@JsonTypeInfo(
-    use = NAME,
-    include = PROPERTY,
-    property = "type"
-)
-@JsonSubTypes(
-    JsonSubTypes.Type(value = ReserveDepositTransaction::class, name = 
"DEPOSIT")
-)
-abstract class ReserveTransaction
-
-
-@JsonTypeName("DEPOSIT")
-class ReserveDepositTransaction(
-    /**
-     * Amount withdrawn.
-     */
-    val amount: String,
-    /**
-     * Sender account payto://-URL
-     */
-    @JsonProperty("sender_account_url")
-    val senderAccountUrl: String,
-    /**
-     * Transfer details uniquely identifying the transfer.
-     */
-    @JsonProperty("wire_reference")
-    val wireReference: String,
-    /**
-     * Timestamp of the incoming wire transfer.
-     */
-    val timestamp: Timestamp
-) : ReserveTransaction()
diff --git a/wallet/src/main/java/net/taler/wallet/transactions/Transaction.kt 
b/wallet/src/main/java/net/taler/wallet/transactions/Transaction.kt
deleted file mode 100644
index 34942d0..0000000
--- a/wallet/src/main/java/net/taler/wallet/transactions/Transaction.kt
+++ /dev/null
@@ -1,497 +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.transactions
-
-import androidx.annotation.DrawableRes
-import androidx.annotation.LayoutRes
-import com.fasterxml.jackson.annotation.JsonInclude
-import com.fasterxml.jackson.annotation.JsonInclude.Include.NON_EMPTY
-import com.fasterxml.jackson.annotation.JsonProperty
-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.cleanExchange
-import org.json.JSONObject
-
-enum class ReserveType {
-    /**
-     * Manually created.
-     */
-    @JsonProperty("manual")
-    MANUAL,
-
-    /**
-     * Withdrawn from a bank that has "tight" Taler integration
-     */
-    @JsonProperty("taler-bank-withdraw")
-    @Suppress("unused")
-    TALER_BANK_WITHDRAW,
-}
-
-@JsonInclude(NON_EMPTY)
-class ReserveCreationDetail(val type: ReserveType, val bankUrl: String?)
-
-enum class RefreshReason {
-    @JsonProperty("manual")
-    @Suppress("unused")
-    MANUAL,
-
-    @JsonProperty("pay")
-    PAY,
-
-    @JsonProperty("refund")
-    @Suppress("unused")
-    REFUND,
-
-    @JsonProperty("abort-pay")
-    @Suppress("unused")
-    ABORT_PAY,
-
-    @JsonProperty("recoup")
-    @Suppress("unused")
-    RECOUP,
-
-    @JsonProperty("backup-restored")
-    @Suppress("unused")
-    BACKUP_RESTORED
-}
-
-@JsonInclude(NON_EMPTY)
-class ReserveShortInfo(
-    /**
-     * The exchange that the reserve will be at.
-     */
-    val exchangeBaseUrl: String,
-    /**
-     * Key to query more details
-     */
-    val reservePub: String,
-    /**
-     * Detail about how the reserve has been created.
-     */
-    val reserveCreationDetail: ReserveCreationDetail
-)
-
-sealed class AmountType {
-    object Positive : AmountType()
-    object Negative : AmountType()
-    object Neutral : AmountType()
-}
-
-class DisplayAmount(
-    val amount: Amount,
-    val type: AmountType
-)
-
-typealias Transactions = ArrayList<Transaction>
-
-@JsonTypeInfo(
-    use = NAME,
-    include = PROPERTY,
-    property = "type",
-    defaultImpl = UnknownTransaction::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 = ReserveBalanceUpdatedTransaction::class, name = 
"reserve-balance-updated"),
-    Type(value = WithdrawTransaction::class, name = "withdrawn"),
-    Type(value = OrderAcceptedTransaction::class, name = "order-accepted"),
-    Type(value = OrderRefusedTransaction::class, name = "order-refused"),
-    Type(value = OrderRedirectedTransaction::class, name = "order-redirected"),
-    Type(value = PaymentTransaction::class, name = "payment-sent"),
-    Type(value = PaymentAbortedTransaction::class, name = "payment-aborted"),
-    Type(value = TipAcceptedTransaction::class, name = "tip-accepted"),
-    Type(value = TipDeclinedTransaction::class, name = "tip-declined"),
-    Type(value = RefundTransaction::class, name = "refund"),
-    Type(value = RefreshTransaction::class, name = "refreshed")
-)
-abstract class Transaction(
-    val timestamp: Timestamp,
-    val eventId: String,
-    @get:LayoutRes
-    open val detailPageLayout: Int = 0,
-    @get:DrawableRes
-    open val icon: Int = R.drawable.ic_account_balance,
-    open val showToUser: Boolean = false
-) {
-    abstract val title: String?
-    open lateinit var json: JSONObject
-    open val displayAmount: DisplayAmount? = null
-    open fun isCurrency(currency: String): Boolean = true
-}
-
-
-class UnknownTransaction(timestamp: Timestamp, eventId: String) : 
Transaction(timestamp, eventId) {
-    override val title: String? = null
-}
-
-@JsonTypeName("exchange-added")
-class ExchangeAddedEvent(
-    timestamp: Timestamp,
-    eventId: String,
-    val exchangeBaseUrl: String,
-    val builtIn: Boolean
-) : Transaction(timestamp, eventId) {
-    override val title = cleanExchange(exchangeBaseUrl)
-}
-
-@JsonTypeName("exchange-updated")
-class ExchangeUpdatedEvent(
-    timestamp: Timestamp,
-    eventId: String,
-    val exchangeBaseUrl: String
-) : Transaction(timestamp, eventId) {
-    override val title = cleanExchange(exchangeBaseUrl)
-}
-
-
-@JsonTypeName("reserve-balance-updated")
-class ReserveBalanceUpdatedTransaction(
-    timestamp: Timestamp,
-    eventId: String,
-    /**
-     * Condensed information about the reserve.
-     */
-    val reserveShortInfo: ReserveShortInfo,
-    /**
-     * Amount currently left in the reserve.
-     */
-    val reserveBalance: Amount,
-    /**
-     * Amount we expected to be in the reserve at that time,
-     * considering ongoing withdrawals from that reserve.
-     */
-    val reserveAwaitedAmount: Amount,
-    /**
-     * Amount that hasn't been withdrawn yet.
-     */
-    val reserveUnclaimedAmount: Amount
-) : Transaction(timestamp, eventId) {
-    override val title: String? = null
-    override val displayAmount = DisplayAmount(reserveBalance, 
AmountType.Neutral)
-    override fun isCurrency(currency: String) = reserveBalance.currency == 
currency
-}
-
-@JsonTypeName("withdrawn")
-class WithdrawTransaction(
-    timestamp: Timestamp,
-    eventId: String,
-    /**
-     * Exchange that was withdrawn from.
-     */
-    val exchangeBaseUrl: String,
-    /**
-     * Unique identifier for the withdrawal session, can be used to
-     * query more detailed information from the wallet.
-     */
-    val withdrawalGroupId: String,
-    val withdrawalSource: WithdrawalSource,
-    /**
-     * Amount that has been subtracted from the reserve's balance
-     * for this withdrawal.
-     */
-    val amountWithdrawnRaw: Amount,
-    /**
-     * Amount that actually was added to the wallet's balance.
-     */
-    val amountWithdrawnEffective: Amount
-) : Transaction(timestamp, eventId) {
-    override val detailPageLayout = R.layout.fragment_event_withdraw
-    override val title = cleanExchange(exchangeBaseUrl)
-    override val icon = R.drawable.transaction_withdrawal
-    override val showToUser = true
-    override val displayAmount = DisplayAmount(amountWithdrawnEffective, 
AmountType.Positive)
-    override fun isCurrency(currency: String) = amountWithdrawnRaw.currency == 
currency
-}
-
-@JsonTypeName("order-accepted")
-class OrderAcceptedTransaction(
-    timestamp: Timestamp,
-    eventId: String,
-    /**
-     * Condensed info about the order.
-     */
-    val orderShortInfo: OrderShortInfo
-) : Transaction(timestamp, eventId) {
-    override val icon = R.drawable.ic_add_circle
-    override val title: String? = null
-    override fun isCurrency(currency: String) = orderShortInfo.amount.currency 
== currency
-}
-
-@JsonTypeName("order-refused")
-class OrderRefusedTransaction(
-    timestamp: Timestamp,
-    eventId: String,
-    /**
-     * Condensed info about the order.
-     */
-    val orderShortInfo: OrderShortInfo
-) : Transaction(timestamp, eventId) {
-    override val icon = R.drawable.ic_cancel
-    override val title: String? = null
-    override fun isCurrency(currency: String) = orderShortInfo.amount.currency 
== currency
-}
-
-@JsonTypeName("payment-sent")
-class PaymentTransaction(
-    timestamp: Timestamp,
-    eventId: String,
-    /**
-     * Condensed info about the order that we already paid for.
-     */
-    val orderShortInfo: OrderShortInfo,
-    /**
-     * Set to true if the payment has been previously sent
-     * to the merchant successfully, possibly with a different session ID.
-     */
-    val replay: Boolean,
-    /**
-     * Number of coins that were involved in the payment.
-     */
-    val numCoins: Int,
-    /**
-     * Amount that was paid, including deposit and wire fees.
-     */
-    val amountPaidWithFees: Amount,
-    /**
-     * Session ID that the payment was (re-)submitted under.
-     */
-    val sessionId: String?
-) : Transaction(timestamp, eventId) {
-    override val detailPageLayout = R.layout.fragment_event_paid
-    override val title = orderShortInfo.summary
-    override val icon = R.drawable.ic_cash_usd_outline
-    override val showToUser = true
-    override val displayAmount = DisplayAmount(amountPaidWithFees, 
AmountType.Negative)
-    override fun isCurrency(currency: String) = orderShortInfo.amount.currency 
== currency
-}
-
-@JsonTypeName("payment-aborted")
-class PaymentAbortedTransaction(
-    timestamp: Timestamp,
-    eventId: String,
-    /**
-     * Condensed info about the order that we already paid for.
-     */
-    val orderShortInfo: OrderShortInfo,
-    /**
-     * Amount that was lost due to refund and refreshing fees.
-     */
-    val amountLost: Amount
-) : Transaction(timestamp, eventId) {
-    override val title = orderShortInfo.summary
-    override val icon = R.drawable.transaction_payment_aborted
-    override val showToUser = true
-    override val displayAmount = DisplayAmount(amountLost, AmountType.Negative)
-    override fun isCurrency(currency: String) = orderShortInfo.amount.currency 
== currency
-}
-
-@JsonTypeName("refreshed")
-class RefreshTransaction(
-    timestamp: Timestamp,
-    eventId: String,
-    /**
-     * Amount that is now available again because it has
-     * been refreshed.
-     */
-    val amountRefreshedEffective: Amount,
-    /**
-     * Amount that we spent for refreshing.
-     */
-    val amountRefreshedRaw: Amount,
-    /**
-     * Why was the refreshing done?
-     */
-    val refreshReason: RefreshReason,
-    val numInputCoins: Int,
-    val numRefreshedInputCoins: Int,
-    val numOutputCoins: Int,
-    /**
-     * Identifier for a refresh group, contains one or
-     * more refresh session IDs.
-     */
-    val refreshGroupId: String
-) : Transaction(timestamp, eventId) {
-    override val icon = R.drawable.transaction_refresh
-    override val title: String? = null
-    override val showToUser = !(amountRefreshedRaw - 
amountRefreshedEffective).isZero()
-    override val displayAmount: DisplayAmount?
-        get() {
-            return if (showToUser) DisplayAmount(
-                amountRefreshedRaw - amountRefreshedEffective,
-                AmountType.Negative
-            )
-            else null
-        }
-
-    override fun isCurrency(currency: String) = amountRefreshedRaw.currency == 
currency
-}
-
-@JsonTypeName("order-redirected")
-class OrderRedirectedTransaction(
-    timestamp: Timestamp,
-    eventId: String,
-    /**
-     * Condensed info about the new order that contains a
-     * product (identified by the fulfillment URL) that we've already paid for.
-     */
-    val newOrderShortInfo: OrderShortInfo,
-    /**
-     * Condensed info about the order that we already paid for.
-     */
-    val alreadyPaidOrderShortInfo: OrderShortInfo
-) : Transaction(timestamp, eventId) {
-    override val icon = R.drawable.ic_directions
-    override val title = newOrderShortInfo.summary
-    override fun isCurrency(currency: String) = 
newOrderShortInfo.amount.currency == currency
-}
-
-@JsonTypeName("tip-accepted")
-class TipAcceptedTransaction(
-    timestamp: Timestamp,
-    eventId: String,
-    /**
-     * Unique identifier for the tip to query more information.
-     */
-    val tipId: String,
-    /**
-     * Raw amount of the tip, without extra fees that apply.
-     */
-    val tipRaw: Amount
-) : Transaction(timestamp, eventId) {
-    override val icon = R.drawable.transaction_tip_accepted
-    override val title: String? = null
-    override val showToUser = true
-    override val displayAmount = DisplayAmount(tipRaw, AmountType.Positive)
-    override fun isCurrency(currency: String) = tipRaw.currency == currency
-}
-
-@JsonTypeName("tip-declined")
-class TipDeclinedTransaction(
-    timestamp: Timestamp,
-    eventId: String,
-    /**
-     * Unique identifier for the tip to query more information.
-     */
-    val tipId: String,
-    /**
-     * Raw amount of the tip, without extra fees that apply.
-     */
-    val tipAmount: Amount
-) : Transaction(timestamp, eventId) {
-    override val icon = R.drawable.transaction_tip_declined
-    override val title: String? = null
-    override val showToUser = true
-    override val displayAmount = DisplayAmount(tipAmount, AmountType.Neutral)
-    override fun isCurrency(currency: String) = tipAmount.currency == currency
-}
-
-@JsonTypeName("refund")
-class RefundTransaction(
-    timestamp: Timestamp,
-    eventId: String,
-    val orderShortInfo: OrderShortInfo,
-    /**
-     * Unique identifier for this refund.
-     * (Identifies multiple refund permissions that were obtained at once.)
-     */
-    val refundGroupId: String,
-    /**
-     * Part of the refund that couldn't be applied because
-     * the refund permissions were expired.
-     */
-    val amountRefundedInvalid: Amount,
-    /**
-     * Amount that has been refunded by the merchant.
-     */
-    val amountRefundedRaw: Amount,
-    /**
-     * Amount will be added to the wallet's balance after fees and refreshing.
-     */
-    val amountRefundedEffective: Amount
-) : Transaction(timestamp, eventId) {
-    override val icon = R.drawable.transaction_refund
-    override val title = orderShortInfo.summary
-    override val detailPageLayout = R.layout.fragment_event_paid
-    override val showToUser = true
-    override val displayAmount = DisplayAmount(amountRefundedEffective, 
AmountType.Positive)
-    override fun isCurrency(currency: String) = amountRefundedRaw.currency == 
currency
-}
-
-@JsonTypeInfo(
-    use = NAME,
-    include = PROPERTY,
-    property = "type"
-)
-@JsonSubTypes(
-    Type(value = WithdrawalSourceReserve::class, name = "reserve")
-)
-abstract class WithdrawalSource
-
-@Suppress("unused")
-@JsonTypeName("tip")
-class WithdrawalSourceTip(
-    val tipId: String
-) : WithdrawalSource()
-
-@JsonTypeName("reserve")
-class WithdrawalSourceReserve(
-    val reservePub: String
-) : WithdrawalSource()
-
-data class OrderShortInfo(
-    /**
-     * Wallet-internal identifier of the proposal.
-     */
-    val proposalId: String,
-    /**
-     * Order ID, uniquely identifies the order within a merchant instance.
-     */
-    val orderId: String,
-    /**
-     * Base URL of the merchant.
-     */
-    val merchantBaseUrl: String,
-    /**
-     * Amount that must be paid for the contract.
-     */
-    val amount: Amount,
-    /**
-     * Summary of the proposal, given by the merchant.
-     */
-    val summary: String
-)
diff --git 
a/wallet/src/main/java/net/taler/wallet/transactions/TransactionAdapter.kt 
b/wallet/src/main/java/net/taler/wallet/transactions/TransactionAdapter.kt
index 440d07f..e6dad6f 100644
--- a/wallet/src/main/java/net/taler/wallet/transactions/TransactionAdapter.kt
+++ b/wallet/src/main/java/net/taler/wallet/transactions/TransactionAdapter.kt
@@ -36,13 +36,11 @@ import net.taler.common.toRelativeTime
 import net.taler.wallet.R
 import net.taler.wallet.transactions.TransactionAdapter.TransactionViewHolder
 
-
 internal class TransactionAdapter(
-    private val devMode: Boolean,
-    private val listener: OnEventClickListener,
-    private var transactions: Transactions = Transactions()
+    private val listener: OnTransactionClickListener
 ) : Adapter<TransactionViewHolder>() {
 
+    private var transactions: List<Transaction> = ArrayList()
     lateinit var tracker: SelectionTracker<String>
     val keyProvider = TransactionKeyProvider()
 
@@ -60,101 +58,74 @@ internal class TransactionAdapter(
 
     override fun onBindViewHolder(holder: TransactionViewHolder, position: 
Int) {
         val transaction = transactions[position]
-        holder.bind(transaction, tracker.isSelected(transaction.eventId))
+        holder.bind(transaction, tracker.isSelected(transaction.transactionId))
     }
 
-    fun update(updatedTransactions: Transactions) {
+    fun update(updatedTransactions: List<Transaction>) {
         this.transactions = updatedTransactions
         this.notifyDataSetChanged()
     }
 
     fun selectAll() = transactions.forEach {
-        tracker.select(it.eventId)
+        tracker.select(it.transactionId)
     }
 
-    internal open inner class TransactionViewHolder(private val v: View) : 
ViewHolder(v) {
-
-        protected val context: Context = v.context
+    internal inner class TransactionViewHolder(private val v: View) : 
ViewHolder(v) {
+        private 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 title: TextView = v.findViewById(R.id.title)
+        private val extraInfoView: TextView = 
v.findViewById(R.id.extraInfoView)
         private val time: TextView = v.findViewById(R.id.time)
         private val amount: TextView = v.findViewById(R.id.amount)
+        private val pendingView: TextView = v.findViewById(R.id.pendingView)
 
         private val selectableForeground = v.foreground
         private val amountColor = amount.currentTextColor
+        private val red = context.getColor(R.color.red)
+        private val green = context.getColor(R.color.green)
 
-        open fun bind(transaction: Transaction, selected: Boolean) {
-            if (devMode || transaction.detailPageLayout != 0) {
-                v.foreground = selectableForeground
-                v.setOnClickListener { 
listener.onTransactionClicked(transaction) }
-            } else {
-                v.foreground = null
-                v.setOnClickListener(null)
-            }
+        fun bind(transaction: Transaction, selected: Boolean) {
+            v.foreground = selectableForeground
+            v.setOnClickListener { listener.onTransactionClicked(transaction) }
             v.isActivated = selected
-            icon.setImageResource(transaction.icon)
 
-            title.text = if (transaction.title == null) {
-                when (transaction) {
-                    is RefreshTransaction -> getRefreshTitle(transaction)
-                    is OrderAcceptedTransaction -> 
context.getString(R.string.transaction_order_accepted)
-                    is OrderRefusedTransaction -> 
context.getString(R.string.transaction_order_refused)
-                    is TipAcceptedTransaction -> 
context.getString(R.string.transaction_tip_accepted)
-                    is TipDeclinedTransaction -> 
context.getString(R.string.transaction_tip_declined)
-                    is ReserveBalanceUpdatedTransaction -> 
context.getString(R.string.transaction_reserve_balance_updated)
-                    else -> transaction::class.java.simpleName
-                }
-            } else transaction.title
-
-            time.text = transaction.timestamp.ms.toRelativeTime(context)
-            bindAmount(transaction.displayAmount)
-        }
-
-        private fun bindAmount(displayAmount: DisplayAmount?) {
-            if (displayAmount == null) {
-                amount.visibility = GONE
+            icon.setImageResource(transaction.icon)
+            title.text = transaction.getTitle(context)
+            if (transaction is TransactionWithdrawal && 
!transaction.confirmed) {
+                extraInfoView.setText(R.string.withdraw_waiting_confirm)
+                extraInfoView.visibility = VISIBLE
             } 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
+                extraInfoView.visibility = GONE
             }
+            time.text = transaction.timestamp.ms.toRelativeTime(context)
+            bindAmount(transaction)
+            pendingView.visibility = if (transaction.pending) VISIBLE else GONE
         }
 
-        private fun getRefreshTitle(transaction: RefreshTransaction): String {
-            val res = when (transaction.refreshReason) {
-                RefreshReason.MANUAL -> 
R.string.transaction_refresh_reason_manual
-                RefreshReason.PAY -> R.string.transaction_refresh_reason_pay
-                RefreshReason.REFUND -> 
R.string.transaction_refresh_reason_refund
-                RefreshReason.ABORT_PAY -> 
R.string.transaction_refresh_reason_abort_pay
-                RefreshReason.RECOUP -> 
R.string.transaction_refresh_reason_recoup
-                RefreshReason.BACKUP_RESTORED -> 
R.string.transaction_refresh_reason_backup_restored
-            }
-            return context.getString(R.string.transaction_refresh) + " " + 
context.getString(res)
+        private fun bindAmount(transaction: Transaction) {
+            val amountStr = transaction.amountEffective.amountStr
+            when (transaction.amountType) {
+                AmountType.Positive -> {
+                    amount.text = context.getString(R.string.amount_positive, 
amountStr)
+                    amount.setTextColor(if (transaction.pending) amountColor 
else green)
+                }
+                AmountType.Negative -> {
+                    amount.text = context.getString(R.string.amount_negative, 
amountStr)
+                    amount.setTextColor(if (transaction.pending) amountColor 
else red)
+                }
+                AmountType.Neutral -> {
+                    amount.text = amountStr
+                    amount.setTextColor(amountColor)
+                }
+            }.exhaustive
         }
-
     }
 
     internal inner class TransactionKeyProvider : 
ItemKeyProvider<String>(SCOPE_MAPPED) {
-        override fun getKey(position: Int) = transactions[position].eventId
+        override fun getKey(position: Int) = 
transactions[position].transactionId
         override fun getPosition(key: String): Int {
-            return transactions.indexOfFirst { it.eventId == key }
+            return transactions.indexOfFirst { it.transactionId == key }
         }
     }
 
diff --git 
a/wallet/src/main/java/net/taler/wallet/transactions/TransactionDetailFragment.kt
 
b/wallet/src/main/java/net/taler/wallet/transactions/TransactionDetailFragment.kt
index 909a7bf..6b58824 100644
--- 
a/wallet/src/main/java/net/taler/wallet/transactions/TransactionDetailFragment.kt
+++ 
b/wallet/src/main/java/net/taler/wallet/transactions/TransactionDetailFragment.kt
@@ -16,23 +16,27 @@
 
 package net.taler.wallet.transactions
 
+import android.content.Intent
+import android.net.Uri
 import android.os.Bundle
 import android.view.LayoutInflater
 import android.view.Menu
 import android.view.MenuInflater
 import android.view.MenuItem
 import android.view.View
+import android.view.View.GONE
 import android.view.ViewGroup
 import android.widget.Toast
 import android.widget.Toast.LENGTH_LONG
 import androidx.core.content.ContextCompat.getColor
 import androidx.fragment.app.Fragment
 import androidx.fragment.app.activityViewModels
-import kotlinx.android.synthetic.main.fragment_event_paid.*
-import kotlinx.android.synthetic.main.fragment_event_withdraw.*
-import kotlinx.android.synthetic.main.fragment_event_withdraw.feeView
-import kotlinx.android.synthetic.main.fragment_event_withdraw.timeView
+import kotlinx.android.synthetic.main.fragment_transaction_payment.*
+import kotlinx.android.synthetic.main.fragment_transaction_withdrawal.*
+import kotlinx.android.synthetic.main.fragment_transaction_withdrawal.feeView
+import kotlinx.android.synthetic.main.fragment_transaction_withdrawal.timeView
 import net.taler.common.Amount
+import net.taler.common.isSafe
 import net.taler.common.toAbsoluteTime
 import net.taler.wallet.MainViewModel
 import net.taler.wallet.R
@@ -42,7 +46,7 @@ class TransactionDetailFragment : Fragment() {
 
     private val model: MainViewModel by activityViewModels()
     private val transactionManager by lazy { model.transactionManager }
-    private val event by lazy { 
requireNotNull(transactionManager.selectedEvent) }
+    private val transaction by lazy { 
requireNotNull(transactionManager.selectedTransaction) }
 
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
@@ -53,21 +57,22 @@ class TransactionDetailFragment : Fragment() {
         inflater: LayoutInflater, container: ViewGroup?,
         savedInstanceState: Bundle?
     ): View? {
-        return inflater.inflate(event.detailPageLayout, container, false)
+        return inflater.inflate(transaction.detailPageLayout, container, false)
     }
 
     override fun onActivityCreated(savedInstanceState: Bundle?) {
         super.onActivityCreated(savedInstanceState)
-        requireActivity().title =
-            if (event.title != null) event.title else 
getString(R.string.transactions_detail_title)
+        requireActivity().apply {
+            title = getString(transaction.generalTitleRes)
+        }
     }
 
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
-        timeView.text = event.timestamp.ms.toAbsoluteTime(requireContext())
-        when (val e = event) {
-            is WithdrawTransaction -> bind(e)
-            is PaymentTransaction -> bind(e)
-            is RefundTransaction -> bind(e)
+        timeView.text = 
transaction.timestamp.ms.toAbsoluteTime(requireContext())
+        when (val e = transaction) {
+            is TransactionWithdrawal -> bind(e)
+            is TransactionPayment -> bind(e)
+            is TransactionRefund -> bind(e)
             else -> Toast.makeText(
                 requireContext(),
                 "event ${e.javaClass} not implement",
@@ -82,46 +87,57 @@ class TransactionDetailFragment : Fragment() {
 
     override fun onOptionsItemSelected(item: MenuItem): Boolean {
         return when (item.itemId) {
-            R.id.show_json -> {
-                
JsonDialogFragment.new(event.json.toString(2)).show(parentFragmentManager, null)
-                true
-            }
             else -> super.onOptionsItemSelected(item)
         }
     }
 
-    private fun bind(event: WithdrawTransaction) {
+    private fun bind(t: TransactionWithdrawal) {
         effectiveAmountLabel.text = getString(R.string.withdraw_total)
-        effectiveAmountView.text = event.amountWithdrawnEffective.toString()
+        effectiveAmountView.text = t.amountEffective.toString()
+        if (t.pending && !t.confirmed && t.bankConfirmationUrl != null) {
+            val i = Intent().apply {
+                data = Uri.parse(t.bankConfirmationUrl)
+            }
+            if (i.isSafe(requireContext())) {
+                confirmWithdrawalButton.setOnClickListener { startActivity(i) }
+            }
+        } else confirmWithdrawalButton.visibility = GONE
         chosenAmountLabel.text = getString(R.string.amount_chosen)
         chosenAmountView.text =
-            getString(R.string.amount_positive, 
event.amountWithdrawnRaw.toString())
-        val fee = event.amountWithdrawnRaw - event.amountWithdrawnEffective
+            getString(R.string.amount_positive, t.amountRaw.toString())
+        val fee = t.amountRaw - t.amountEffective
         feeView.text = getString(R.string.amount_negative, fee.toString())
-        exchangeView.text = cleanExchange(event.exchangeBaseUrl)
+        exchangeView.text = cleanExchange(t.exchangeBaseUrl)
     }
 
-    private fun bind(event: PaymentTransaction) {
-        amountPaidWithFeesView.text = event.amountPaidWithFees.toString()
-        val fee = event.amountPaidWithFees - event.orderShortInfo.amount
-        bindOrderAndFee(event.orderShortInfo, fee)
+    private fun bind(t: TransactionPayment) {
+        amountPaidWithFeesView.text = t.amountEffective.toString()
+        val fee = t.amountEffective - t.amountRaw
+        bindOrderAndFee(t.info, t.amountRaw, fee)
     }
 
-    private fun bind(event: RefundTransaction) {
+    private fun bind(t: TransactionRefund) {
         amountPaidWithFeesLabel.text = getString(R.string.transaction_refund)
         amountPaidWithFeesView.setTextColor(getColor(requireContext(), 
R.color.green))
         amountPaidWithFeesView.text =
-            getString(R.string.amount_positive, 
event.amountRefundedEffective.toString())
-        val fee = event.orderShortInfo.amount - event.amountRefundedEffective
-        bindOrderAndFee(event.orderShortInfo, fee)
+            getString(R.string.amount_positive, t.amountEffective.toString())
+        val fee = t.amountRaw - t.amountEffective
+        bindOrderAndFee(t.info, t.amountRaw, fee)
     }
 
-    private fun bindOrderAndFee(orderShortInfo: OrderShortInfo, fee: Amount) {
-        orderAmountView.text = orderShortInfo.amount.toString()
+    private fun bindOrderAndFee(info: TransactionInfo, raw: Amount, fee: 
Amount) {
+        orderAmountView.text = raw.toString()
         feeView.text = getString(R.string.amount_negative, fee.toString())
-        orderSummaryView.text = orderShortInfo.summary
-        orderIdView.text =
-            getString(R.string.transaction_order_id, orderShortInfo.orderId)
+        orderSummaryView.text = info.summary
+        if (info.fulfillmentUrl.startsWith("http")) {
+            val i = Intent().apply {
+                data = Uri.parse(info.fulfillmentUrl)
+            }
+            if (i.isSafe(requireContext())) {
+                orderSummaryView.setOnClickListener { startActivity(i) }
+            }
+        }
+        orderIdView.text = getString(R.string.transaction_order_id, 
info.orderId)
     }
 
 }
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 549b2a8..d5cee16 100644
--- a/wallet/src/main/java/net/taler/wallet/transactions/TransactionManager.kt
+++ b/wallet/src/main/java/net/taler/wallet/transactions/TransactionManager.kt
@@ -16,72 +16,85 @@
 
 package net.taler.wallet.transactions
 
+import androidx.annotation.UiThread
+import androidx.annotation.WorkerThread
 import androidx.lifecycle.LiveData
 import androidx.lifecycle.MutableLiveData
-import androidx.lifecycle.asLiveData
-import androidx.lifecycle.switchMap
 import com.fasterxml.jackson.databind.ObjectMapper
 import com.fasterxml.jackson.module.kotlin.readValue
+import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.channels.awaitClose
-import kotlinx.coroutines.flow.callbackFlow
-import kotlinx.coroutines.flow.onCompletion
-import kotlinx.coroutines.flow.onStart
 import kotlinx.coroutines.launch
 import net.taler.wallet.backend.WalletBackendApi
+import org.json.JSONObject
+import java.util.*
 
 sealed class TransactionsResult {
     object Error : TransactionsResult()
-    class Success(val transactions: Transactions) : TransactionsResult()
+    class Success(val transactions: List<Transaction>) : TransactionsResult()
 }
 
-@Suppress("EXPERIMENTAL_API_USAGE")
 class TransactionManager(
     private val walletBackendApi: WalletBackendApi,
+    private val scope: CoroutineScope,
     private val mapper: ObjectMapper
 ) {
 
     private val mProgress = MutableLiveData<Boolean>()
     val progress: LiveData<Boolean> = mProgress
 
-    val showAll = MutableLiveData<Boolean>()
-
     var selectedCurrency: String? = null
-    var selectedEvent: Transaction? = null
+    var selectedTransaction: Transaction? = null
 
-    val transactions: LiveData<TransactionsResult> = showAll.switchMap { 
showAll ->
-        loadTransactions(showAll)
-            .onStart { mProgress.postValue(true) }
-            .onCompletion { mProgress.postValue(false) }
-            .asLiveData(Dispatchers.IO)
-    }
+    private val mTransactions = HashMap<String, 
MutableLiveData<TransactionsResult>>()
+    val transactions: LiveData<TransactionsResult>
+        @UiThread
+        get() {
+            val currency = selectedCurrency
+            check(currency != null) { "Did not select currency before getting 
transactions" }
+            return mTransactions.getOrPut(currency) { MutableLiveData() }
+        }
 
-    private fun loadTransactions(showAll: Boolean) = callbackFlow {
-        walletBackendApi.sendRequest("getHistory", null) { isError, result ->
-            launch(Dispatchers.Default) {
-                if (isError) {
-                    offer(TransactionsResult.Error)
-                    close()
-                    return@launch
-                }
-                val transactions = Transactions()
-                val json = result.getJSONArray("history")
-                val currency = selectedCurrency
-                for (i in 0 until json.length()) {
-                    val event: Transaction = 
mapper.readValue(json.getString(i))
-                    event.json = json.getJSONObject(i)
-                    if (currency == null || event.isCurrency(currency)) {
-                        transactions.add(event)
-                    }
-                }
-                transactions.reverse()  // show latest first
-                val filtered =
-                    if (showAll) transactions else transactions.filter { 
it.showToUser } as Transactions
-                offer(TransactionsResult.Success(filtered))
-                close()
+    @UiThread
+    fun loadTransactions() {
+        val currency = selectedCurrency ?: return
+        val liveData = mTransactions.getOrPut(currency) {
+            MutableLiveData<TransactionsResult>()
+        }
+        if (liveData.value == null) mProgress.value = true
+        val request = JSONObject(mapOf("currency" to currency))
+        walletBackendApi.sendRequest("getTransactions", request) { isError, 
result ->
+            scope.launch(Dispatchers.Default) {
+                onTransactionsLoaded(liveData, isError, result)
             }
         }
-        awaitClose()
+    }
+
+    @WorkerThread
+    private fun onTransactionsLoaded(
+        liveData: MutableLiveData<TransactionsResult>,
+        isError: Boolean,
+        result: JSONObject
+    ) {
+        if (isError) {
+            liveData.postValue(TransactionsResult.Error)
+            return
+        }
+        val transactionsArray = result.getString("transactions")
+        val transactions: LinkedList<Transaction> = 
mapper.readValue(transactionsArray)
+        // TODO remove when fixed in wallet-core
+        transactions.sortWith(compareBy({ it.pending }, { it.timestamp.ms }, { 
it.transactionId }))
+        transactions.reverse()  // show latest first
+        mProgress.postValue(false)
+        liveData.postValue(TransactionsResult.Success(transactions))
+    }
+
+    @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/Transactions.kt 
b/wallet/src/main/java/net/taler/wallet/transactions/Transactions.kt
new file mode 100644
index 0000000..55579cc
--- /dev/null
+++ b/wallet/src/main/java/net/taler/wallet/transactions/Transactions.kt
@@ -0,0 +1,187 @@
+/*
+ * 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.transactions
+
+import android.content.Context
+import androidx.annotation.DrawableRes
+import androidx.annotation.LayoutRes
+import androidx.annotation.StringRes
+import com.fasterxml.jackson.annotation.JsonProperty
+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.ContractMerchant
+import net.taler.common.ContractProduct
+import net.taler.common.Timestamp
+import net.taler.wallet.R
+import net.taler.wallet.cleanExchange
+
+@JsonTypeInfo(use = NAME, include = PROPERTY, property = "type")
+@JsonSubTypes(
+    Type(value = TransactionWithdrawal::class, name = "withdrawal"),
+    Type(value = TransactionPayment::class, name = "payment"),
+    Type(value = TransactionRefund::class, name = "refund"),
+    Type(value = TransactionTip::class, name = "tip"),
+    Type(value = TransactionRefresh::class, name = "refresh")
+)
+abstract class Transaction(
+    val transactionId: String,
+    val timestamp: Timestamp,
+    val pending: Boolean,
+    val amountRaw: Amount,
+    val amountEffective: Amount
+) {
+    @get:DrawableRes
+    abstract val icon: Int
+
+    @get:LayoutRes
+    abstract val detailPageLayout: Int
+
+    abstract val amountType: AmountType
+
+    abstract fun getTitle(context: Context): String
+
+    @get:StringRes
+    abstract val generalTitleRes: Int
+}
+
+sealed class AmountType {
+    object Positive : AmountType()
+    object Negative : AmountType()
+    object Neutral : AmountType()
+}
+
+@JsonTypeName("withdrawal")
+class TransactionWithdrawal(
+    transactionId: String,
+    timestamp: Timestamp,
+    pending: Boolean,
+    val exchangeBaseUrl: String,
+    val confirmed: Boolean,
+    val bankConfirmationUrl: String?,
+    amountRaw: Amount,
+    amountEffective: Amount
+) : Transaction(transactionId, timestamp, pending, amountRaw, amountEffective) 
{
+    override val icon = R.drawable.transaction_withdrawal
+    override val detailPageLayout = R.layout.fragment_transaction_withdrawal
+    override val amountType = AmountType.Positive
+    override fun getTitle(context: Context) = cleanExchange(exchangeBaseUrl)
+    override val generalTitleRes = R.string.withdraw_title
+}
+
+@JsonTypeName("payment")
+class TransactionPayment(
+    transactionId: String,
+    timestamp: Timestamp,
+    pending: Boolean,
+    val info: TransactionInfo,
+    val status: PaymentStatus,
+    amountRaw: Amount,
+    amountEffective: Amount
+) : Transaction(transactionId, timestamp, pending, amountRaw, amountEffective) 
{
+    override val icon = R.drawable.ic_cash_usd_outline
+    override val detailPageLayout = R.layout.fragment_transaction_payment
+    override val amountType = AmountType.Negative
+    override fun getTitle(context: Context) = info.merchant.name
+    override val generalTitleRes = R.string.payment_title
+}
+
+class TransactionInfo(
+    val orderId: String,
+    val merchant: ContractMerchant,
+    val summary: String,
+    @get:JsonProperty("summary_i18n")
+    val summaryI18n: Map<String, String>?,
+    val products: List<ContractProduct>,
+    val fulfillmentUrl: String
+)
+
+enum class PaymentStatus {
+    @JsonProperty("aborted")
+    Aborted,
+
+    @JsonProperty("failed")
+    Failed,
+
+    @JsonProperty("paid")
+    Paid,
+
+    @JsonProperty("accepted")
+    Accepted
+}
+
+@JsonTypeName("refund")
+class TransactionRefund(
+    transactionId: String,
+    timestamp: Timestamp,
+    pending: Boolean,
+    val refundedTransactionId: String,
+    val info: TransactionInfo,
+    val amountInvalid: Amount,
+    amountRaw: Amount,
+    amountEffective: Amount
+) : Transaction(transactionId, timestamp, pending, amountRaw, amountEffective) 
{
+    override val icon = R.drawable.transaction_refund
+    override val detailPageLayout = R.layout.fragment_transaction_payment
+    override val amountType = AmountType.Positive
+    override fun getTitle(context: Context): String {
+        return context.getString(R.string.transaction_refund_from, 
info.merchant.name)
+    }
+    override val generalTitleRes = R.string.refund_title
+}
+
+@JsonTypeName("tip")
+class TransactionTip(
+    transactionId: String,
+    timestamp: Timestamp,
+    pending: Boolean,
+    // TODO status: TipStatus,
+    val exchangeBaseUrl: String,
+    val merchant: ContractMerchant,
+    amountRaw: Amount,
+    amountEffective: Amount
+) : Transaction(transactionId, timestamp, pending, amountRaw, amountEffective) 
{
+    override val icon = R.drawable.transaction_tip_accepted // TODO different 
when declined
+    override val detailPageLayout = R.layout.fragment_transaction_payment
+    override val amountType = AmountType.Positive
+    override fun getTitle(context: Context): String {
+        return context.getString(R.string.transaction_tip_from, merchant.name)
+    }
+    override val generalTitleRes = R.string.tip_title
+}
+
+@JsonTypeName("refresh")
+class TransactionRefresh(
+    transactionId: String,
+    timestamp: Timestamp,
+    pending: Boolean,
+    val exchangeBaseUrl: String,
+    amountRaw: Amount,
+    amountEffective: Amount
+) : Transaction(transactionId, timestamp, pending, amountRaw, amountEffective) 
{
+    override val icon = R.drawable.transaction_refresh
+    override val detailPageLayout = R.layout.fragment_transaction_payment
+    override val amountType = AmountType.Negative
+    override fun getTitle(context: Context): String {
+        return context.getString(R.string.transaction_refresh)
+    }
+    override val generalTitleRes = R.string.transaction_refresh
+}
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 e7adaf1..dfd00ea 100644
--- a/wallet/src/main/java/net/taler/wallet/transactions/TransactionsFragment.kt
+++ b/wallet/src/main/java/net/taler/wallet/transactions/TransactionsFragment.kt
@@ -43,16 +43,16 @@ import net.taler.common.fadeOut
 import net.taler.wallet.MainViewModel
 import net.taler.wallet.R
 
-interface OnEventClickListener {
+interface OnTransactionClickListener {
     fun onTransactionClicked(transaction: Transaction)
 }
 
-class TransactionsFragment : Fragment(), OnEventClickListener, 
ActionMode.Callback {
+class TransactionsFragment : Fragment(), OnTransactionClickListener, 
ActionMode.Callback {
 
     private val model: MainViewModel by activityViewModels()
     private val transactionManager by lazy { model.transactionManager }
 
-    private val transactionAdapter by lazy { 
TransactionAdapter(model.devMode.value == true, this) }
+    private val transactionAdapter by lazy { TransactionAdapter(this) }
     private val currency by lazy { transactionManager.selectedCurrency!! }
     private var tracker: SelectionTracker<String>? = null
     private var actionMode: ActionMode? = null
@@ -109,7 +109,7 @@ class TransactionsFragment : Fragment(), 
OnEventClickListener, ActionMode.Callba
         })
 
         // kicks off initial load, needs to be adapted if showAll state is 
ever saved
-        if (savedInstanceState == null) transactionManager.showAll.value = 
model.devMode.value
+        if (savedInstanceState == null) transactionManager.loadTransactions()
     }
 
     override fun onActivityCreated(savedInstanceState: Bundle?) {
@@ -140,11 +140,8 @@ class TransactionsFragment : Fragment(), 
OnEventClickListener, ActionMode.Callba
     override fun onTransactionClicked(transaction: Transaction) {
         if (actionMode != null) return // don't react on clicks while in 
action mode
         if (transaction.detailPageLayout != 0) {
-            transactionManager.selectedEvent = transaction
+            transactionManager.selectedTransaction = transaction
             findNavController().navigate(R.id.action_nav_transaction_detail)
-        } else if (model.devMode.value == true) {
-            JsonDialogFragment.new(transaction.json.toString(2))
-                .show(parentFragmentManager, null)
         }
     }
 
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 747551b..331554b 100644
--- a/wallet/src/main/java/net/taler/wallet/withdraw/PromptWithdrawFragment.kt
+++ b/wallet/src/main/java/net/taler/wallet/withdraw/PromptWithdrawFragment.kt
@@ -24,12 +24,14 @@ import androidx.fragment.app.Fragment
 import androidx.fragment.app.activityViewModels
 import androidx.lifecycle.Observer
 import androidx.navigation.fragment.findNavController
+import com.google.android.material.snackbar.Snackbar
+import com.google.android.material.snackbar.Snackbar.LENGTH_LONG
 import kotlinx.android.synthetic.main.fragment_prompt_withdraw.*
 import net.taler.common.Amount
 import net.taler.common.fadeIn
 import net.taler.common.fadeOut
-import net.taler.wallet.R
 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
@@ -63,7 +65,11 @@ class PromptWithdrawFragment : Fragment() {
                 setOnClickListener {
                     it.fadeOut()
                     confirmProgressBar.fadeIn()
-                    withdrawManager.acceptWithdrawal(status.talerWithdrawUri, 
status.exchange)
+                    withdrawManager.acceptWithdrawal(
+                        status.talerWithdrawUri,
+                        status.exchange,
+                        status.amount.currency
+                    )
                 }
                 isEnabled = true
             }
@@ -71,7 +77,9 @@ class PromptWithdrawFragment : Fragment() {
         is WithdrawStatus.Success -> {
             model.showProgressBar.value = false
             withdrawManager.withdrawStatus.value = null
-            
findNavController().navigate(R.id.action_promptWithdraw_to_withdrawSuccessful)
+            
findNavController().navigate(R.id.action_promptWithdraw_to_nav_main)
+            model.showTransactions(status.currency)
+            Snackbar.make(requireView(), R.string.withdraw_initiated, 
LENGTH_LONG).show()
         }
         is Loading -> {
             model.showProgressBar.value = true
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 6bcd013..75e4daa 100644
--- a/wallet/src/main/java/net/taler/wallet/withdraw/WithdrawManager.kt
+++ b/wallet/src/main/java/net/taler/wallet/withdraw/WithdrawManager.kt
@@ -43,8 +43,7 @@ sealed class WithdrawStatus {
     ) : WithdrawStatus()
 
     data class Withdrawing(val talerWithdrawUri: String) : WithdrawStatus()
-
-    object Success : WithdrawStatus()
+    data class Success(val currency: String) : WithdrawStatus()
     data class Error(val message: String?) : WithdrawStatus()
 }
 
@@ -145,7 +144,7 @@ class WithdrawManager(private val walletBackendApi: 
WalletBackendApi) {
         }
     }
 
-    fun acceptWithdrawal(talerWithdrawUri: String, selectedExchange: String) {
+    fun acceptWithdrawal(talerWithdrawUri: String, selectedExchange: String, 
currency: String) {
         val args = JSONObject()
         args.put("talerWithdrawUri", talerWithdrawUri)
         args.put("selectedExchange", selectedExchange)
@@ -154,7 +153,7 @@ class WithdrawManager(private val walletBackendApi: 
WalletBackendApi) {
 
         walletBackendApi.sendRequest("acceptWithdrawal", args) { isError, 
result ->
             if (isError) {
-                Log.v(TAG, "got acceptWithdrawal error result: 
${result.toString(4)}")
+                Log.v(TAG, "got acceptWithdrawal error result: 
${result.toString(2)}")
                 return@sendRequest
             }
             Log.v(TAG, "got acceptWithdrawal result")
@@ -163,7 +162,7 @@ class WithdrawManager(private val walletBackendApi: 
WalletBackendApi) {
                 Log.w(TAG, "ignoring acceptWithdrawal result, invalid state: 
$status")
                 return@sendRequest
             }
-            withdrawStatus.postValue(WithdrawStatus.Success)
+            withdrawStatus.postValue(WithdrawStatus.Success(currency))
         }
     }
 
diff --git 
a/wallet/src/main/java/net/taler/wallet/withdraw/WithdrawSuccessfulFragment.kt 
b/wallet/src/main/java/net/taler/wallet/withdraw/WithdrawSuccessfulFragment.kt
deleted file mode 100644
index 5daeff1..0000000
--- 
a/wallet/src/main/java/net/taler/wallet/withdraw/WithdrawSuccessfulFragment.kt
+++ /dev/null
@@ -1,44 +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 android.os.Bundle
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import androidx.fragment.app.Fragment
-import androidx.navigation.fragment.findNavController
-import kotlinx.android.synthetic.main.fragment_withdraw_successful.*
-import net.taler.wallet.R
-
-class WithdrawSuccessfulFragment : Fragment() {
-
-    override fun onCreateView(
-        inflater: LayoutInflater, container: ViewGroup?,
-        savedInstanceState: Bundle?
-    ): View? {
-        return inflater.inflate(R.layout.fragment_withdraw_successful, 
container, false)
-    }
-
-    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
-        super.onViewCreated(view, savedInstanceState)
-        backButton.setOnClickListener {
-            findNavController().navigateUp()
-        }
-    }
-
-}
diff --git a/wallet/src/main/res/drawable/side_nav_bar.xml 
b/wallet/src/main/res/drawable/badge.xml
similarity index 78%
copy from wallet/src/main/res/drawable/side_nav_bar.xml
copy to wallet/src/main/res/drawable/badge.xml
index ecc7e71..0b06ce5 100644
--- a/wallet/src/main/res/drawable/side_nav_bar.xml
+++ b/wallet/src/main/res/drawable/badge.xml
@@ -16,9 +16,11 @@
 
 <shape xmlns:android="http://schemas.android.com/apk/res/android";
     android:shape="rectangle">
-    <gradient
-        android:angle="135"
-        android:endColor="@color/colorPrimaryDark"
-        android:startColor="@color/colorPrimary"
-        android:type="linear" />
+    <solid android:color="?android:textColorSecondary" />
+    <corners android:radius="8dp" />
+    <padding
+        android:bottom="2dp"
+        android:left="8dp"
+        android:right="8dp"
+        android:top="2dp" />
 </shape>
\ No newline at end of file
diff --git a/wallet/src/main/res/drawable/ic_check_circle.xml 
b/wallet/src/main/res/drawable/ic_check_circle.xml
deleted file mode 100644
index 375b366..0000000
--- a/wallet/src/main/res/drawable/ic_check_circle.xml
+++ /dev/null
@@ -1,26 +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:alpha="0.56"
-    android:viewportWidth="24.0"
-    android:viewportHeight="24.0">
-    <path
-        android:fillColor="@color/green"
-        android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 
10,-10S17.52,2 12,2zM10,17l-5,-5 1.41,-1.41L10,14.17l7.59,-7.59L19,8l-9,9z" />
-</vector>
diff --git a/merchant-terminal/src/main/res/drawable/ic_history_black_24dp.xml 
b/wallet/src/main/res/drawable/ic_history.xml
similarity index 100%
copy from merchant-terminal/src/main/res/drawable/ic_history_black_24dp.xml
copy to wallet/src/main/res/drawable/ic_history.xml
diff --git a/wallet/src/main/res/layout/fragment_already_paid.xml 
b/wallet/src/main/res/layout/fragment_already_paid.xml
index 5160a2e..18ce0b5 100644
--- a/wallet/src/main/res/layout/fragment_already_paid.xml
+++ b/wallet/src/main/res/layout/fragment_already_paid.xml
@@ -21,7 +21,7 @@
     android:layout_height="match_parent"
     android:layout_margin="15dp"
     android:orientation="vertical"
-    tools:context=".payment.PaymentSuccessfulFragment">
+    tools:context=".payment.AlreadyPaidFragment">
 
     <Space
         android:layout_width="match_parent"
diff --git a/wallet/src/main/res/layout/fragment_payment_successful.xml 
b/wallet/src/main/res/layout/fragment_payment_successful.xml
deleted file mode 100644
index 1c45622..0000000
--- a/wallet/src/main/res/layout/fragment_payment_successful.xml
+++ /dev/null
@@ -1,63 +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:id="@+id/frameLayout"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:layout_margin="16dp"
-    tools:context=".payment.PaymentSuccessfulFragment">
-
-    <ImageView
-        android:id="@+id/successImageView"
-        android:layout_width="0dp"
-        android:layout_height="0dp"
-        android:layout_margin="32dp"
-        android:src="@drawable/ic_check_circle"
-        app:layout_constraintBottom_toTopOf="@+id/successTextView"
-        app:layout_constraintEnd_toEndOf="parent"
-        app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintTop_toTopOf="parent"
-        tools:ignore="ContentDescription" />
-
-    <androidx.appcompat.widget.AppCompatTextView
-        android:id="@+id/successTextView"
-        android:layout_width="0dp"
-        android:layout_height="0dp"
-        android:layout_marginBottom="16dp"
-        android:text="@string/payment_successful"
-        android:textAlignment="center"
-        android:textColor="@color/green"
-        app:autoSizeMaxTextSize="48sp"
-        app:autoSizeMinTextSize="10sp"
-        app:autoSizeTextType="uniform"
-        app:layout_constraintBottom_toTopOf="@+id/backButton"
-        app:layout_constraintEnd_toEndOf="parent"
-        app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintTop_toBottomOf="@+id/successImageView" />
-
-    <Button
-        android:id="@+id/backButton"
-        android:layout_width="0dp"
-        android:layout_height="wrap_content"
-        android:text="@string/payment_back_button"
-        app:layout_constraintBottom_toBottomOf="parent"
-        app:layout_constraintEnd_toEndOf="parent"
-        app:layout_constraintStart_toStartOf="parent" />
-
-</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/wallet/src/main/res/layout/fragment_event_paid.xml 
b/wallet/src/main/res/layout/fragment_transaction_payment.xml
similarity index 98%
rename from wallet/src/main/res/layout/fragment_event_paid.xml
rename to wallet/src/main/res/layout/fragment_transaction_payment.xml
index 3f17464..20ba161 100644
--- a/wallet/src/main/res/layout/fragment_event_paid.xml
+++ b/wallet/src/main/res/layout/fragment_transaction_payment.xml
@@ -104,6 +104,7 @@
         <TextView
             android:id="@+id/orderSummaryView"
             style="@style/TransactionContent"
+            android:textColor="?android:textColorPrimary"
             app:layout_constraintBottom_toTopOf="@+id/orderIdView"
             app:layout_constraintEnd_toEndOf="parent"
             app:layout_constraintStart_toStartOf="parent"
@@ -113,6 +114,7 @@
         <TextView
             android:id="@+id/orderIdView"
             style="@style/TransactionLabel"
+            android:layout_marginBottom="16dp"
             android:text="@string/transaction_order_id"
             app:layout_constraintBottom_toBottomOf="parent"
             app:layout_constraintEnd_toEndOf="parent"
diff --git a/wallet/src/main/res/layout/fragment_event_withdraw.xml 
b/wallet/src/main/res/layout/fragment_transaction_withdrawal.xml
similarity index 79%
rename from wallet/src/main/res/layout/fragment_event_withdraw.xml
rename to wallet/src/main/res/layout/fragment_transaction_withdrawal.xml
index 5d30fcf..5a1e82f 100644
--- a/wallet/src/main/res/layout/fragment_event_withdraw.xml
+++ b/wallet/src/main/res/layout/fragment_transaction_withdrawal.xml
@@ -47,40 +47,39 @@
 
         <TextView
             android:id="@+id/effectiveAmountView"
-            android:layout_width="0dp"
-            android:layout_height="wrap_content"
-            android:layout_marginStart="16dp"
-            android:layout_marginTop="8dp"
-            android:layout_marginEnd="16dp"
-            android:layout_marginBottom="16dp"
-            android:gravity="center"
+            style="@style/TransactionContent"
             android:textColor="@color/green"
-            android:textSize="24sp"
-            app:layout_constraintBottom_toTopOf="@+id/chosenAmountLabel"
+            app:layout_constraintBottom_toTopOf="@+id/confirmWithdrawalButton"
             app:layout_constraintEnd_toEndOf="parent"
             app:layout_constraintStart_toStartOf="parent"
             app:layout_constraintTop_toBottomOf="@+id/effectiveAmountLabel"
             tools:text="23.42 TESTKUDOS" />
 
+        <Button
+            android:id="@+id/confirmWithdrawalButton"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:drawableLeft="@drawable/ic_account_balance"
+            android:drawableTint="?attr/colorOnPrimarySurface"
+            android:text="@string/withdraw_button_confirm_bank"
+            app:layout_constraintBottom_toTopOf="@+id/chosenAmountLabel"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toBottomOf="@+id/effectiveAmountView"
+            tools:ignore="RtlHardcoded" />
+
         <TextView
             android:id="@+id/chosenAmountLabel"
             style="@style/TransactionLabel"
             app:layout_constraintBottom_toTopOf="@+id/chosenAmountView"
             app:layout_constraintEnd_toEndOf="parent"
             app:layout_constraintStart_toStartOf="parent"
-            app:layout_constraintTop_toBottomOf="@+id/effectiveAmountView"
+            app:layout_constraintTop_toBottomOf="@+id/confirmWithdrawalButton"
             tools:text="@string/amount_chosen" />
 
         <TextView
             android:id="@+id/chosenAmountView"
-            android:layout_width="0dp"
-            android:layout_height="wrap_content"
-            android:layout_marginStart="16dp"
-            android:layout_marginTop="8dp"
-            android:layout_marginEnd="16dp"
-            android:layout_marginBottom="16dp"
-            android:gravity="center"
-            android:textSize="24sp"
+            style="@style/TransactionContent"
             app:layout_constraintBottom_toTopOf="@+id/feeLabel"
             app:layout_constraintEnd_toEndOf="parent"
             app:layout_constraintStart_toStartOf="parent"
@@ -98,15 +97,8 @@
 
         <TextView
             android:id="@+id/feeView"
-            android:layout_width="0dp"
-            android:layout_height="wrap_content"
-            android:layout_marginStart="16dp"
-            android:layout_marginTop="8dp"
-            android:layout_marginEnd="16dp"
-            android:layout_marginBottom="16dp"
-            android:gravity="center"
+            style="@style/TransactionContent"
             android:textColor="@color/red"
-            android:textSize="24sp"
             app:layout_constraintBottom_toTopOf="@+id/exchangeLabel"
             app:layout_constraintEnd_toEndOf="parent"
             app:layout_constraintStart_toStartOf="parent"
@@ -124,16 +116,9 @@
 
         <TextView
             android:id="@+id/exchangeView"
-            android:layout_width="0dp"
-            android:layout_height="wrap_content"
-            android:layout_marginStart="16dp"
-            android:layout_marginTop="8dp"
-            android:layout_marginEnd="16dp"
-            android:gravity="center"
-            android:textSize="24sp"
+            style="@style/TransactionContent"
             app:layout_constraintBottom_toBottomOf="parent"
             app:layout_constraintEnd_toEndOf="parent"
-            app:layout_constraintHorizontal_bias="0.5"
             app:layout_constraintStart_toStartOf="parent"
             app:layout_constraintTop_toBottomOf="@+id/exchangeLabel"
             tools:text="exchange.demo.taler.net" />
diff --git a/wallet/src/main/res/layout/fragment_transactions.xml 
b/wallet/src/main/res/layout/fragment_transactions.xml
index aaf638c..547da24 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_transaction"
+        tools:listitem="@layout/list_item_history"
         tools:visibility="visible" />
 
     <TextView
diff --git a/wallet/src/main/res/layout/fragment_withdraw_successful.xml 
b/wallet/src/main/res/layout/fragment_withdraw_successful.xml
deleted file mode 100644
index a422492..0000000
--- a/wallet/src/main/res/layout/fragment_withdraw_successful.xml
+++ /dev/null
@@ -1,63 +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="match_parent"
-    tools:context=".withdraw.WithdrawSuccessfulFragment">
-
-    <TextView
-        android:id="@+id/withdrawHeadlineView"
-        android:layout_width="0dp"
-        android:layout_height="0dp"
-        android:layout_margin="16dp"
-        android:gravity="center_horizontal|bottom"
-        android:text="@string/withdraw_accepted"
-        android:textColor="@color/green"
-        app:autoSizeMaxTextSize="40sp"
-        app:autoSizeTextType="uniform"
-        app:layout_constraintBottom_toTopOf="@+id/withdrawInfoView"
-        app:layout_constraintEnd_toEndOf="parent"
-        app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintTop_toTopOf="parent" />
-
-    <TextView
-        android:id="@+id/withdrawInfoView"
-        android:layout_width="0dp"
-        android:layout_height="0dp"
-        android:layout_margin="16dp"
-        android:text="@string/withdraw_success_info"
-        android:textAlignment="center"
-        android:textAppearance="@style/TextAppearance.AppCompat.Medium"
-        app:layout_constraintBottom_toTopOf="@+id/backButton"
-        app:layout_constraintEnd_toEndOf="parent"
-        app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintTop_toBottomOf="@+id/withdrawHeadlineView" />
-
-    <Button
-        android:id="@+id/backButton"
-        android:layout_width="0dp"
-        android:layout_height="wrap_content"
-        android:layout_margin="16dp"
-        android:text="@string/button_continue"
-        app:layout_constraintBottom_toBottomOf="parent"
-        app:layout_constraintEnd_toEndOf="parent"
-        app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintTop_toBottomOf="@+id/withdrawInfoView" />
-
-</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/wallet/src/main/res/layout/list_item_balance.xml 
b/wallet/src/main/res/layout/list_item_balance.xml
index a9e15df..475e7d6 100644
--- a/wallet/src/main/res/layout/list_item_balance.xml
+++ b/wallet/src/main/res/layout/list_item_balance.xml
@@ -23,12 +23,12 @@
     android:padding="16dp">
 
     <TextView
-        android:id="@+id/balance_amount"
+        android:id="@+id/balanceAmountView"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_marginEnd="8dp"
         android:textSize="40sp"
-        app:layout_constraintEnd_toStartOf="@+id/balance_currency"
+        app:layout_constraintEnd_toStartOf="@+id/balanceCurrencyView"
         app:layout_constraintHorizontal_bias="0.0"
         app:layout_constraintHorizontal_chainStyle="packed"
         app:layout_constraintStart_toStartOf="parent"
@@ -36,15 +36,16 @@
         tools:text="100.50" />
 
     <TextView
-        android:id="@+id/balance_currency"
+        android:id="@+id/balanceCurrencyView"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
+        android:layout_marginEnd="8dp"
         android:textSize="20sp"
-        app:layout_constraintBottom_toBottomOf="@+id/balance_amount"
-        app:layout_constraintEnd_toEndOf="parent"
-        app:layout_constraintHorizontal_bias="0.5"
-        app:layout_constraintStart_toEndOf="@+id/balance_amount"
-        app:layout_constraintTop_toTopOf="@+id/balance_amount"
+        app:layout_constrainedWidth="true"
+        app:layout_constraintBottom_toBottomOf="@+id/balanceAmountView"
+        app:layout_constraintEnd_toStartOf="@+id/pendingView"
+        app:layout_constraintStart_toEndOf="@+id/balanceAmountView"
+        app:layout_constraintTop_toTopOf="@+id/balanceAmountView"
         tools:text="TESTKUDOS" />
 
     <TextView
@@ -58,7 +59,7 @@
         app:layout_constraintHorizontal_bias="0.0"
         app:layout_constraintHorizontal_chainStyle="packed"
         app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintTop_toBottomOf="@+id/balance_amount"
+        app:layout_constraintTop_toBottomOf="@+id/balanceAmountView"
         tools:text="+10 TESTKUDOS"
         tools:visibility="visible" />
 
@@ -75,4 +76,15 @@
         app:layout_constraintTop_toTopOf="@+id/balanceInboundAmount"
         tools:visibility="visible" />
 
+    <TextView
+        android:id="@+id/pendingView"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:background="@drawable/badge"
+        android:text="@string/transaction_pending"
+        android:textColor="?android:textColorPrimaryInverse"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+
 </androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/wallet/src/main/res/layout/list_item_transaction.xml 
b/wallet/src/main/res/layout/list_item_history.xml
similarity index 98%
copy from wallet/src/main/res/layout/list_item_transaction.xml
copy to wallet/src/main/res/layout/list_item_history.xml
index 2fabe1d..bc94738 100644
--- a/wallet/src/main/res/layout/list_item_transaction.xml
+++ b/wallet/src/main/res/layout/list_item_history.xml
@@ -47,7 +47,7 @@
         app:layout_constraintEnd_toStartOf="@+id/amount"
         app:layout_constraintStart_toEndOf="@+id/icon"
         app:layout_constraintTop_toTopOf="parent"
-        tools:text="@string/transaction_payment" />
+        tools:text="@string/payment_title" />
 
     <TextView
         android:id="@+id/amount"
diff --git a/wallet/src/main/res/layout/list_item_transaction.xml 
b/wallet/src/main/res/layout/list_item_transaction.xml
index 2fabe1d..34712a2 100644
--- a/wallet/src/main/res/layout/list_item_transaction.xml
+++ b/wallet/src/main/res/layout/list_item_transaction.xml
@@ -44,20 +44,24 @@
         android:layout_height="wrap_content"
         android:layout_marginStart="16dp"
         android:layout_marginEnd="8dp"
-        app:layout_constraintEnd_toStartOf="@+id/amount"
+        app:layout_constraintEnd_toStartOf="@+id/barrier"
         app:layout_constraintStart_toEndOf="@+id/icon"
         app:layout_constraintTop_toTopOf="parent"
-        tools:text="@string/transaction_payment" />
+        tools:text="@string/payment_title" />
 
     <TextView
-        android:id="@+id/amount"
-        android:layout_width="wrap_content"
+        android:id="@+id/extraInfoView"
+        android:layout_width="0dp"
         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" />
+        android:layout_marginTop="8dp"
+        android:layout_marginEnd="8dp"
+        android:textSize="14sp"
+        android:visibility="gone"
+        app:layout_constraintEnd_toStartOf="@+id/barrier"
+        app:layout_constraintStart_toStartOf="@+id/title"
+        app:layout_constraintTop_toBottomOf="@+id/title"
+        tools:text="@string/withdraw_waiting_confirm"
+        tools:visibility="visible" />
 
     <TextView
         android:id="@+id/time"
@@ -67,9 +71,38 @@
         android:layout_marginEnd="8dp"
         android:textSize="14sp"
         app:layout_constraintBottom_toBottomOf="parent"
-        app:layout_constraintEnd_toStartOf="@+id/amount"
+        app:layout_constraintEnd_toStartOf="@+id/barrier"
         app:layout_constraintStart_toStartOf="@+id/title"
-        app:layout_constraintTop_toBottomOf="@+id/title"
+        app:layout_constraintTop_toBottomOf="@+id/extraInfoView"
         tools:text="23 min ago" />
 
+    <androidx.constraintlayout.widget.Barrier
+        android:id="@+id/barrier"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        app:barrierDirection="start"
+        app:constraint_referenced_ids="amount,pendingView" />
+
+    <TextView
+        android:id="@+id/amount"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textSize="24sp"
+        app:layout_constraintBottom_toTopOf="@+id/pendingView"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        tools:text="- 1337.23" />
+
+    <TextView
+        android:id="@+id/pendingView"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/transaction_pending"
+        android:textSize="14sp"
+        android:visibility="gone"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/amount"
+        tools:visibility="visible" />
+
 </androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/wallet/src/main/res/menu/activity_main_drawer.xml 
b/wallet/src/main/res/menu/activity_main_drawer.xml
index 896ff69..62abc32 100644
--- a/wallet/src/main/res/menu/activity_main_drawer.xml
+++ b/wallet/src/main/res/menu/activity_main_drawer.xml
@@ -18,7 +18,9 @@
     xmlns:tools="http://schemas.android.com/tools";
     tools:showIn="@layout/activity_main">
 
-    <group android:checkableBehavior="single">
+    <group
+        android:id="@+id/nav_group_main"
+        android:checkableBehavior="single">
         <item
             android:id="@+id/nav_home"
             android:icon="@drawable/ic_account_balance_wallet"
@@ -28,10 +30,25 @@
             android:id="@+id/nav_settings"
             android:icon="@drawable/ic_settings"
             android:title="@string/menu_settings" />
-        <item
-            android:id="@+id/nav_pending_operations"
-            android:icon="@drawable/ic_sync"
-            android:title="@string/pending_operations_title" />
     </group>
 
+    <item
+        android:id="@+id/nav_dev"
+        android:title="@string/settings_dev_mode">
+        <menu>
+            <group
+                android:id="@+id/nav_group_dev"
+                android:checkableBehavior="single">
+                <item
+                    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>
+
 </menu>
diff --git a/wallet/src/main/res/menu/transactions_detail.xml 
b/wallet/src/main/res/menu/transactions_detail.xml
index 388e3c4..d4568d4 100644
--- a/wallet/src/main/res/menu/transactions_detail.xml
+++ b/wallet/src/main/res/menu/transactions_detail.xml
@@ -16,8 +16,4 @@
 
 <menu xmlns:android="http://schemas.android.com/apk/res/android";
     xmlns:app="http://schemas.android.com/apk/res-auto";>
-    <item
-        android:id="@+id/show_json"
-        android:title="@string/transactions_detail_json"
-        app:showAsAction="never" />
 </menu>
diff --git a/wallet/src/main/res/navigation/nav_graph.xml 
b/wallet/src/main/res/navigation/nav_graph.xml
index f8d515e..a06abad 100644
--- a/wallet/src/main/res/navigation/nav_graph.xml
+++ b/wallet/src/main/res/navigation/nav_graph.xml
@@ -42,19 +42,14 @@
         android:label="Review Payment"
         tools:layout="@layout/fragment_prompt_payment">
         <action
-            android:id="@+id/action_promptPayment_to_paymentSuccessful"
-            app:destination="@id/paymentSuccessful"
+            android:id="@+id/action_promptPayment_to_nav_main"
+            app:destination="@id/nav_main"
             app:popUpTo="@id/nav_main" />
         <action
             android:id="@+id/action_promptPayment_to_alreadyPaid"
             app:destination="@id/alreadyPaid"
             app:popUpTo="@id/nav_main" />
     </fragment>
-    <fragment
-        android:id="@+id/paymentSuccessful"
-        android:name="net.taler.wallet.payment.PaymentSuccessfulFragment"
-        android:label="Payment Successful"
-        tools:layout="@layout/fragment_payment_successful" />
 
     <fragment
         android:id="@+id/nav_settings"
@@ -71,7 +66,7 @@
         android:id="@+id/nav_transactions_detail"
         android:name="net.taler.wallet.transactions.TransactionDetailFragment"
         android:label="@string/transactions_detail_title"
-        tools:layout="@layout/fragment_event_withdraw" />
+        tools:layout="@layout/fragment_transaction_withdrawal" />
 
     <fragment
         android:id="@+id/alreadyPaid"
@@ -84,28 +79,22 @@
         android:name="net.taler.wallet.withdraw.PromptWithdrawFragment"
         android:label="@string/nav_prompt_withdraw"
         tools:layout="@layout/fragment_prompt_withdraw">
+        <action
+            android:id="@+id/action_promptWithdraw_to_selectExchangeFragment"
+            app:destination="@id/selectExchangeFragment" />
         <action
             android:id="@+id/action_promptWithdraw_to_reviewExchangeTOS"
             app:destination="@id/reviewExchangeTOS" />
         <action
-            android:id="@+id/action_promptWithdraw_to_withdrawSuccessful"
-            app:destination="@id/withdrawSuccessful"
+            android:id="@+id/action_promptWithdraw_to_nav_main"
+            app:destination="@id/nav_main"
             app:popUpTo="@id/nav_main" />
         <action
             android:id="@+id/action_promptWithdraw_to_errorFragment"
             app:destination="@id/errorFragment"
             app:popUpTo="@id/nav_main" />
-        <action
-            android:id="@+id/action_promptWithdraw_to_selectExchangeFragment"
-            app:destination="@id/selectExchangeFragment" />
     </fragment>
 
-    <fragment
-        android:id="@+id/withdrawSuccessful"
-        android:name="net.taler.wallet.withdraw.WithdrawSuccessfulFragment"
-        android:label="@string/withdraw_accepted"
-        tools:layout="@layout/fragment_withdraw_successful" />
-
     <fragment
         android:id="@+id/reviewExchangeTOS"
         android:name="net.taler.wallet.withdraw.ReviewExchangeTosFragment"
@@ -128,6 +117,12 @@
         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/errorFragment"
         android:name="net.taler.wallet.withdraw.ErrorFragment"
@@ -142,6 +137,10 @@
         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 a28545f..e815e9b 100644
--- a/wallet/src/main/res/values/strings.xml
+++ b/wallet/src/main/res/values/strings.xml
@@ -40,10 +40,10 @@ 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>
-    <string name="button_continue">Continue</string>
     <string name="button_scan_qr_code">Scan Taler QR Code</string>
 
     <string name="menu_settings">Settings</string>
@@ -64,30 +64,21 @@ GNU Taler is immune against many types of fraud, such as 
phishing of credit card
     <string name="transactions_error">Could not load transactions</string>
     <string name="transactions_detail_title">Transaction</string>
     <string name="transactions_detail_title_balance">Balance: %s</string>
-    <string name="transactions_detail_json">Show JSON</string>
     <string name="transactions_delete">Delete</string>
     <string name="transactions_select_all">Select All</string>
 
     <!-- Transactions -->
-    <string name="transaction_reserve_balance_updated">Reserve Balance 
Updated</string>
-    <string name="transaction_payment">Payment</string>
     <string name="transaction_paid">Paid</string>
     <string name="transaction_order_total">Total</string>
     <string name="transaction_order">Purchase</string>
     <string name="transaction_order_id">Receipt #%1$s</string>
-    <string name="transaction_order_accepted">Purchase Confirmed</string>
-    <string name="transaction_order_refused">Purchase Cancelled</string>
-    <string name="transaction_tip_accepted">Tip Accepted</string>
-    <string name="transaction_tip_declined">Tip Declined</string>
+    <string name="transaction_tip_from">Tip from %s</string>
     <string name="transaction_refund">Refund</string>
-    <string name="transaction_refresh">Obtained change</string>
-    <string name="transaction_refresh_reason_manual">because of manual 
request</string>
-    <string name="transaction_refresh_reason_pay">for payment</string>
-    <string name="transaction_refresh_reason_refund">for refund</string>
-    <string name="transaction_refresh_reason_abort_pay">to abort 
payment</string>
-    <string name="transaction_refresh_reason_recoup">to recoup funds</string>
-    <string name="transaction_refresh_reason_backup_restored">because of 
restoring from backup</string>
+    <string name="transaction_refund_from">Refund from %s</string>
+    <string name="transaction_pending">PENDING</string>
+    <string name="transaction_refresh">Coin expiry change fee</string>
 
+    <string name="payment_title">Payment</string>
     <string name="payment_fee">+%s payment fee</string>
     <string name="payment_button_confirm">Confirm Payment</string>
     <string name="payment_label_amount_total">Total Amount:</string>
@@ -96,18 +87,19 @@ GNU Taler is immune against many types of fraud, such as 
phishing of credit card
     <string name="payment_balance_insufficient">Balance insufficient!</string>
     <string name="payment_show_details">Show Details</string>
     <string name="payment_hide_details">Hide Details</string>
-    <string name="payment_successful">Payment was successful</string>
-    <string name="payment_back_button">OK</string>
+    <string name="payment_initiated">Payment initiated</string>
     <string name="payment_already_paid_title">Already paid</string>
     <string name="payment_already_paid">You\'ve already paid for this 
purchase.</string>
 
-    <string name="withdraw_accepted">Withdrawal accepted</string>
-    <string name="withdraw_success_info">The wire transfer now needs to be 
confirmed with the bank. Once the wire transfer is complete, the digital cash 
will automatically show in this wallet.</string>
+    <string name="withdraw_initiated">Withdrawal initiated</string>
+    <string name="withdraw_title">Withdrawal</string>
     <string name="withdraw_total">Withdraw</string>
     <string name="withdraw_fees">Fee</string>
     <string name="withdraw_exchange">Exchange</string>
     <string name="withdraw_button_confirm">Confirm Withdraw</string>
+    <string name="withdraw_button_confirm_bank">Confirm with bank</string>
     <string name="withdraw_button_tos">Review Terms</string>
+    <string name="withdraw_waiting_confirm">Waiting for confirmation</string>
     <string name="withdraw_error_title">Withdrawal Error</string>
     <string name="withdraw_error_message">Withdrawing is currently not 
possible. Please try again later!</string>
 
@@ -146,7 +138,10 @@ GNU Taler is immune against many types of fraud, such as 
phishing of credit card
     <string name="settings_reset">Reset Wallet (dangerous!)</string>
     <string name="settings_reset_summary">Throws away your money</string>
 
+    <string name="refund_title">Refund</string>
     <string name="refund_error">Error processing refund</string>
     <string name="refund_success">Refund received</string>
 
+    <string name="tip_title">Tip</string>
+
 </resources>
diff --git 
a/wallet/src/test/java/net/taler/wallet/transactions/ReserveTransactionTest.kt 
b/wallet/src/test/java/net/taler/wallet/transactions/ReserveTransactionTest.kt
deleted file mode 100644
index 4a3c75b..0000000
--- 
a/wallet/src/test/java/net/taler/wallet/transactions/ReserveTransactionTest.kt
+++ /dev/null
@@ -1,52 +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.transactions
-
-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
-import kotlin.random.Random
-
-class ReserveTransactionTest {
-
-    private val mapper = ObjectMapper().registerModule(KotlinModule())
-
-    private val timestamp = Random.nextLong()
-
-    @Test
-    fun `test ExchangeAddedEvent`() {
-        val senderAccountUrl = "payto://x-taler-bank/bank.test.taler.net/894"
-        val json = """{
-            "amount": "TESTKUDOS:10",
-            "sender_account_url": 
"payto:\/\/x-taler-bank\/bank.test.taler.net\/894",
-            "timestamp": {
-                "t_ms": $timestamp
-            },
-            "wire_reference": "00000000004TR",
-            "type": "DEPOSIT"
-        }""".trimIndent()
-        val transaction: ReserveDepositTransaction = mapper.readValue(json)
-
-        assertEquals("TESTKUDOS:10", transaction.amount)
-        assertEquals(senderAccountUrl, transaction.senderAccountUrl)
-        assertEquals("00000000004TR", transaction.wireReference)
-        assertEquals(timestamp, transaction.timestamp.ms)
-    }
-
-}
diff --git 
a/wallet/src/test/java/net/taler/wallet/transactions/TransactionTest.kt 
b/wallet/src/test/java/net/taler/wallet/transactions/TransactionTest.kt
deleted file mode 100644
index 6549434..0000000
--- a/wallet/src/test/java/net/taler/wallet/transactions/TransactionTest.kt
+++ /dev/null
@@ -1,470 +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.transactions
-
-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.wallet.transactions.RefreshReason.PAY
-import net.taler.wallet.transactions.ReserveType.MANUAL
-import org.junit.Assert.assertEquals
-import org.junit.Assert.assertTrue
-import org.junit.Test
-import kotlin.random.Random
-
-class TransactionTest {
-
-    private val mapper = ObjectMapper().registerModule(KotlinModule())
-
-    private val timestamp = Random.nextLong()
-    private val exchangeBaseUrl = "https://exchange.test.taler.net/";
-    private val orderShortInfo = OrderShortInfo(
-        proposalId = "EP5MH4R5C9RMNA06YS1QGEJ3EY682PY8R1SGRFRP74EV735N3ATG",
-        orderId = "2019.364-01RAQ68DQ7AWR",
-        merchantBaseUrl = 
"https://backend.demo.taler.net/public/instances/FSF/";,
-        amount = Amount.fromJSONString("KUDOS:0.5"),
-        summary = "Essay: Foreword"
-    )
-
-    @Test
-    fun `test ExchangeAddedEvent`() {
-        val builtIn = Random.nextBoolean()
-        val json = """{
-            "type": "exchange-added",
-            "builtIn": $builtIn,
-            "eventId": 
"exchange-added;https%3A%2F%2Fexchange.test.taler.net%2F",
-            "exchangeBaseUrl": "https:\/\/exchange.test.taler.net\/",
-            "timestamp": {
-                "t_ms": $timestamp
-            }
-        }""".trimIndent()
-        val event: ExchangeAddedEvent = mapper.readValue(json)
-
-        assertEquals(builtIn, event.builtIn)
-        assertEquals(exchangeBaseUrl, event.exchangeBaseUrl)
-        assertEquals(timestamp, event.timestamp.ms)
-    }
-
-    @Test
-    fun `test ExchangeUpdatedEvent`() {
-        val json = """{
-            "type": "exchange-updated",
-            "eventId": 
"exchange-updated;https%3A%2F%2Fexchange.test.taler.net%2F",
-            "exchangeBaseUrl": "https:\/\/exchange.test.taler.net\/",
-            "timestamp": {
-                "t_ms": $timestamp
-            }
-        }""".trimIndent()
-        val event: ExchangeUpdatedEvent = mapper.readValue(json)
-
-        assertEquals(exchangeBaseUrl, event.exchangeBaseUrl)
-        assertEquals(timestamp, event.timestamp.ms)
-    }
-
-    @Test
-    fun `test ReserveShortInfo`() {
-        val json = """{
-            "exchangeBaseUrl": "https:\/\/exchange.test.taler.net\/",
-            "reserveCreationDetail": {
-                "type": "manual"
-            },
-            "reservePub": 
"BRT2P0YMQSD5F48V9XHVNH73ZTS6EZC0KCQCPGPZQWTSQB77615G"
-        }""".trimIndent()
-        val info: ReserveShortInfo = mapper.readValue(json)
-
-        assertEquals(exchangeBaseUrl, info.exchangeBaseUrl)
-        assertEquals(MANUAL, info.reserveCreationDetail.type)
-        assertEquals("BRT2P0YMQSD5F48V9XHVNH73ZTS6EZC0KCQCPGPZQWTSQB77615G", 
info.reservePub)
-    }
-
-    @Test
-    fun `test ReserveBalanceUpdatedTransaction`() {
-        val json = """{
-            "type": "reserve-balance-updated",
-            "eventId": 
"reserve-balance-updated;K0H10Q6HB9WH0CKHQQMNH5C6GA7A9AR1E2XSS9G1KG3ZXMBVT26G",
-            "reserveAwaitedAmount": "TESTKUDOS:23",
-            "reserveUnclaimedAmount": "TESTKUDOS:0.01",
-            "reserveBalance": "TESTKUDOS:10",
-            "timestamp": {
-                "t_ms": $timestamp
-            },
-            "reserveShortInfo": {
-                "exchangeBaseUrl": "https:\/\/exchange.test.taler.net\/",
-                "reserveCreationDetail": {
-                    "type": "manual"
-                },
-                "reservePub": 
"BRT2P0YMQSD5F48V9XHVNH73ZTS6EZC0KCQCPGPZQWTSQB77615G"
-            }
-        }""".trimIndent()
-        val transaction: ReserveBalanceUpdatedTransaction = 
mapper.readValue(json)
-
-        assertEquals(timestamp, transaction.timestamp.ms)
-        assertEquals("TESTKUDOS:23", 
transaction.reserveAwaitedAmount.toJSONString())
-        assertEquals("TESTKUDOS:10", transaction.reserveBalance.toJSONString())
-        assertEquals("TESTKUDOS:0.01", 
transaction.reserveUnclaimedAmount.toJSONString())
-        assertEquals(exchangeBaseUrl, 
transaction.reserveShortInfo.exchangeBaseUrl)
-    }
-
-    @Test
-    fun `test WithdrawTransaction`() {
-        val json = """{
-            "type": "withdrawn",
-            "withdrawalGroupId": 
"974FT7JDNR20EQKNR21G1HV9PB6T5AZHYHX9NHR51Q30ZK3T10S0",
-            "eventId": 
"withdrawn;974FT7JDNR20EQKNR21G1HV9PB6T5AZHYHX9NHR51Q30ZK3T10S0",
-            "amountWithdrawnEffective": "TESTKUDOS:9.8",
-            "amountWithdrawnRaw": "TESTKUDOS:10",
-            "exchangeBaseUrl": "https:\/\/exchange.test.taler.net\/",
-            "timestamp": {
-                "t_ms": $timestamp
-            },
-            "withdrawalSource": {
-                "type": "reserve",
-                "reservePub": 
"BRT2P0YMQSD5F48V9XHVNH73ZTS6EZC0KCQCPGPZQWTSQB77615G"
-            }
-        }""".trimIndent()
-        val event: WithdrawTransaction = mapper.readValue(json)
-
-        assertEquals(
-            "974FT7JDNR20EQKNR21G1HV9PB6T5AZHYHX9NHR51Q30ZK3T10S0",
-            event.withdrawalGroupId
-        )
-        assertEquals("TESTKUDOS:9.8", 
event.amountWithdrawnEffective.toJSONString())
-        assertEquals("TESTKUDOS:10", event.amountWithdrawnRaw.toJSONString())
-        assertTrue(event.withdrawalSource is WithdrawalSourceReserve)
-        assertEquals(
-            "BRT2P0YMQSD5F48V9XHVNH73ZTS6EZC0KCQCPGPZQWTSQB77615G",
-            (event.withdrawalSource as WithdrawalSourceReserve).reservePub
-        )
-        assertEquals(exchangeBaseUrl, event.exchangeBaseUrl)
-        assertEquals(timestamp, event.timestamp.ms)
-    }
-
-    @Test
-    fun `test OrderShortInfo`() {
-        val json = """{
-            "amount": "KUDOS:0.5",
-            "orderId": "2019.364-01RAQ68DQ7AWR",
-            "merchantBaseUrl": 
"https:\/\/backend.demo.taler.net\/public\/instances\/FSF\/",
-            "proposalId": 
"EP5MH4R5C9RMNA06YS1QGEJ3EY682PY8R1SGRFRP74EV735N3ATG",
-            "summary": "Essay: Foreword"
-        }""".trimIndent()
-        val info: OrderShortInfo = mapper.readValue(json)
-
-        assertEquals("KUDOS:0.5", info.amount.toJSONString())
-        assertEquals("2019.364-01RAQ68DQ7AWR", info.orderId)
-        assertEquals("Essay: Foreword", info.summary)
-    }
-
-    @Test
-    fun `test OrderAcceptedTransaction`() {
-        val json = """{
-            "type": "order-accepted",
-            "eventId": 
"order-accepted;EP5MH4R5C9RMNA06YS1QGEJ3EY682PY8R1SGRFRP74EV735N3ATG",
-            "orderShortInfo": {
-                "amount": "${orderShortInfo.amount.toJSONString()}",
-                "orderId": "${orderShortInfo.orderId}",
-                "merchantBaseUrl": "${orderShortInfo.merchantBaseUrl}",
-                "proposalId": "${orderShortInfo.proposalId}",
-                "summary": "${orderShortInfo.summary}"
-            },
-            "timestamp": {
-                "t_ms": $timestamp
-            }
-        }""".trimIndent()
-        val transaction: OrderAcceptedTransaction = mapper.readValue(json)
-
-        assertEquals(orderShortInfo, transaction.orderShortInfo)
-        assertEquals(timestamp, transaction.timestamp.ms)
-    }
-
-    @Test
-    fun `test OrderRefusedTransaction`() {
-        val json = """{
-            "type": "order-refused",
-            "eventId": 
"order-refused;9RJGAYXKWX0Y3V37H66606SXSA7V2CV255EBFS4G1JSH6W1EG7F0",
-            "orderShortInfo": {
-                "amount": "${orderShortInfo.amount.toJSONString()}",
-                "orderId": "${orderShortInfo.orderId}",
-                "merchantBaseUrl": "${orderShortInfo.merchantBaseUrl}",
-                "proposalId": "${orderShortInfo.proposalId}",
-                "summary": "${orderShortInfo.summary}"
-            },
-            "timestamp": {
-                "t_ms": $timestamp
-            }
-        }""".trimIndent()
-        val transaction: OrderRefusedTransaction = mapper.readValue(json)
-
-        assertEquals(orderShortInfo, transaction.orderShortInfo)
-        assertEquals(timestamp, transaction.timestamp.ms)
-    }
-
-    @Test
-    fun `test PaymentTransaction`() {
-        val json = """{
-            "type": "payment-sent",
-            "eventId": 
"payment-sent;EP5MH4R5C9RMNA06YS1QGEJ3EY682PY8R1SGRFRP74EV735N3ATG",
-            "orderShortInfo": {
-                "amount": "${orderShortInfo.amount.toJSONString()}",
-                "orderId": "${orderShortInfo.orderId}",
-                "merchantBaseUrl": "${orderShortInfo.merchantBaseUrl}",
-                "proposalId": "${orderShortInfo.proposalId}",
-                "summary": "${orderShortInfo.summary}"
-            },
-            "replay": false,
-            "sessionId": "e4f436c4-3c5c-4aee-81d2-26e425c09520",
-            "timestamp": {
-                "t_ms": $timestamp
-            },
-            "numCoins": 6,
-            "amountPaidWithFees": "KUDOS:0.6"
-        }""".trimIndent()
-        val event: PaymentTransaction = mapper.readValue(json)
-
-        assertEquals(orderShortInfo, event.orderShortInfo)
-        assertEquals(false, event.replay)
-        assertEquals(6, event.numCoins)
-        assertEquals("KUDOS:0.6", event.amountPaidWithFees.toJSONString())
-        assertEquals("e4f436c4-3c5c-4aee-81d2-26e425c09520", event.sessionId)
-        assertEquals(timestamp, event.timestamp.ms)
-    }
-
-    @Test
-    fun `test PaymentTransaction without sessionId`() {
-        val json = """{
-            "type": "payment-sent",
-            "eventId": 
"payment-sent;EP5MH4R5C9RMNA06YS1QGEJ3EY682PY8R1SGRFRP74EV735N3ATG",
-            "orderShortInfo": {
-                "amount": "${orderShortInfo.amount.toJSONString()}",
-                "orderId": "${orderShortInfo.orderId}",
-                "merchantBaseUrl": "${orderShortInfo.merchantBaseUrl}",
-                "proposalId": "${orderShortInfo.proposalId}",
-                "summary": "${orderShortInfo.summary}"
-            },
-            "replay": true,
-            "timestamp": {
-                "t_ms": $timestamp
-            },
-            "numCoins": 6,
-            "amountPaidWithFees": "KUDOS:0.6"
-        }""".trimIndent()
-        val event: PaymentTransaction = mapper.readValue(json)
-
-        assertEquals(orderShortInfo, event.orderShortInfo)
-        assertEquals(true, event.replay)
-        assertEquals(6, event.numCoins)
-        assertEquals("KUDOS:0.6", event.amountPaidWithFees.toJSONString())
-        assertEquals(null, event.sessionId)
-        assertEquals(timestamp, event.timestamp.ms)
-    }
-
-    @Test
-    fun `test PaymentAbortedTransaction`() {
-        val json = """{
-            "type": "payment-aborted",
-            "eventId": 
"payment-sent;898724XGQ1GGMZB4WY3KND582NSP74FZ60BX0Y87FF81H0FJ8XD0",
-            "orderShortInfo": {
-                "amount": "${orderShortInfo.amount.toJSONString()}",
-                "orderId": "${orderShortInfo.orderId}",
-                "merchantBaseUrl": "${orderShortInfo.merchantBaseUrl}",
-                "proposalId": "${orderShortInfo.proposalId}",
-                "summary": "${orderShortInfo.summary}"
-            },
-            "timestamp": {
-              "t_ms": $timestamp
-            },
-            "amountLost": "KUDOS:0.1"
-          }""".trimIndent()
-        val transaction: PaymentAbortedTransaction = mapper.readValue(json)
-
-        assertEquals(orderShortInfo, transaction.orderShortInfo)
-        assertEquals("KUDOS:0.1", transaction.amountLost.toJSONString())
-        assertEquals(timestamp, transaction.timestamp.ms)
-    }
-
-    @Test
-    fun `test TipAcceptedTransaction`() {
-        val json = """{
-            "type": "tip-accepted",
-            "timestamp": {
-              "t_ms": $timestamp
-            },
-            "eventId": 
"tip-accepted;898724XGQ1GGMZB4WY3KND582NSP74FZ60BX0Y87FF81H0FJ8XD0",
-            "tipId": 
"tip-accepted;898724XGQ1GGMZB4WY3KND582NSP74FZ60BX0Y87FF81H0FJ8XD0",
-            "tipRaw": "KUDOS:4"
-          }""".trimIndent()
-        val transaction: TipAcceptedTransaction = mapper.readValue(json)
-
-        assertEquals(
-            
"tip-accepted;898724XGQ1GGMZB4WY3KND582NSP74FZ60BX0Y87FF81H0FJ8XD0",
-            transaction.tipId
-        )
-        assertEquals("KUDOS:4", transaction.tipRaw.toJSONString())
-        assertEquals(timestamp, transaction.timestamp.ms)
-    }
-
-    @Test
-    fun `test TipDeclinedTransaction`() {
-        val json = """{
-            "type": "tip-declined",
-            "timestamp": {
-              "t_ms": $timestamp
-            },
-            "eventId": 
"tip-accepted;898724XGQ1GGMZB4WY3KND582NSP74FZ60BX0Y87FF81H0FJ8XD0",
-            "tipId": 
"tip-accepted;998724XGQ1GGMZB4WY3KND582NSP74FZ60BX0Y87FF81H0FJ8XD0",
-            "tipAmount": "KUDOS:4"
-          }""".trimIndent()
-        val transaction: TipDeclinedTransaction = mapper.readValue(json)
-
-        assertEquals(
-            
"tip-accepted;998724XGQ1GGMZB4WY3KND582NSP74FZ60BX0Y87FF81H0FJ8XD0",
-            transaction.tipId
-        )
-        assertEquals("KUDOS:4", transaction.tipAmount.toJSONString())
-        assertEquals(timestamp, transaction.timestamp.ms)
-    }
-
-    @Test
-    fun `test RefundTransaction`() {
-        val json = """{
-            "type": "refund",
-            "eventId": 
"refund;898724XGQ1GGMZB4WY3KND582NSP74FZ60BX0Y87FF81H0FJ8XD0",
-            "refundGroupId": "refund;998724",
-            "orderShortInfo": {
-                "amount": "${orderShortInfo.amount.toJSONString()}",
-                "orderId": "${orderShortInfo.orderId}",
-                "merchantBaseUrl": "${orderShortInfo.merchantBaseUrl}",
-                "proposalId": "${orderShortInfo.proposalId}",
-                "summary": "${orderShortInfo.summary}"
-            },
-            "timestamp": {
-              "t_ms": $timestamp
-            },
-            "amountRefundedRaw": "KUDOS:1",
-            "amountRefundedInvalid": "KUDOS:0.5",
-            "amountRefundedEffective": "KUDOS:0.4"
-          }""".trimIndent()
-        val event: RefundTransaction = mapper.readValue(json)
-
-        assertEquals("refund;998724", event.refundGroupId)
-        assertEquals("KUDOS:1", event.amountRefundedRaw.toJSONString())
-        assertEquals("KUDOS:0.5", event.amountRefundedInvalid.toJSONString())
-        assertEquals("KUDOS:0.4", event.amountRefundedEffective.toJSONString())
-        assertEquals(orderShortInfo, event.orderShortInfo)
-        assertEquals(timestamp, event.timestamp.ms)
-    }
-
-    @Test
-    fun `test RefreshTransaction`() {
-        val json = """{
-            "type": "refreshed",
-            "refreshGroupId": 
"8AVHKJFAN4QV4C11P56NEY83AJMGFF2KF412AN3Y0QBP09RSN640",
-            "eventId": 
"refreshed;8AVHKJFAN4QV4C11P56NEY83AJMGFF2KF412AN3Y0QBP09RSN640",
-            "timestamp": {
-                "t_ms": $timestamp
-            },
-            "refreshReason": "pay",
-            "amountRefreshedEffective": "KUDOS:0",
-            "amountRefreshedRaw": "KUDOS:1",
-            "numInputCoins": 6,
-            "numOutputCoins": 0,
-            "numRefreshedInputCoins": 1
-        }""".trimIndent()
-        val event: RefreshTransaction = mapper.readValue(json)
-
-        assertEquals("KUDOS:0", event.amountRefreshedEffective.toJSONString())
-        assertEquals("KUDOS:1", event.amountRefreshedRaw.toJSONString())
-        assertEquals(6, event.numInputCoins)
-        assertEquals(0, event.numOutputCoins)
-        assertEquals(1, event.numRefreshedInputCoins)
-        assertEquals("8AVHKJFAN4QV4C11P56NEY83AJMGFF2KF412AN3Y0QBP09RSN640", 
event.refreshGroupId)
-        assertEquals(PAY, event.refreshReason)
-        assertEquals(timestamp, event.timestamp.ms)
-    }
-
-    @Test
-    fun `test OrderRedirectedTransaction`() {
-        val json = """{
-            "type": "order-redirected",
-            "eventId": 
"order-redirected;621J6D5SXG7M17TYA26945DYKNQZPW4600MZ1W8MADA1RRR49F8G",
-            "alreadyPaidOrderShortInfo": {
-              "amount": "KUDOS:0.5",
-              "orderId": "2019.354-01P25CD66P8NG",
-              "merchantBaseUrl": 
"https://backend.demo.taler.net/public/instances/FSF/";,
-              "proposalId": 
"898724XGQ1GGMZB4WY3KND582NSP74FZ60BX0Y87FF81H0FJ8XD0",
-              "summary": "Essay: 1. The Free Software Definition"
-            },
-            "newOrderShortInfo": {
-              "amount": "KUDOS:0.5",
-              "orderId": "2019.364-01M4QH6KPMJY4",
-              "merchantBaseUrl": 
"https://backend.demo.taler.net/public/instances/FSF/";,
-              "proposalId": 
"621J6D5SXG7M17TYA26945DYKNQZPW4600MZ1W8MADA1RRR49F8G",
-              "summary": "Essay: 1. The Free Software Definition"
-            },
-            "timestamp": {
-              "t_ms": $timestamp
-            }
-          }""".trimIndent()
-        val transaction: OrderRedirectedTransaction = mapper.readValue(json)
-
-        assertEquals(
-            "898724XGQ1GGMZB4WY3KND582NSP74FZ60BX0Y87FF81H0FJ8XD0",
-            transaction.alreadyPaidOrderShortInfo.proposalId
-        )
-        assertEquals(
-            "https://backend.demo.taler.net/public/instances/FSF/";,
-            transaction.alreadyPaidOrderShortInfo.merchantBaseUrl
-        )
-        assertEquals("2019.354-01P25CD66P8NG", 
transaction.alreadyPaidOrderShortInfo.orderId)
-        assertEquals("KUDOS:0.5", 
transaction.alreadyPaidOrderShortInfo.amount.toJSONString())
-        assertEquals(
-            "Essay: 1. The Free Software Definition",
-            transaction.alreadyPaidOrderShortInfo.summary
-        )
-
-        assertEquals(
-            "621J6D5SXG7M17TYA26945DYKNQZPW4600MZ1W8MADA1RRR49F8G",
-            transaction.newOrderShortInfo.proposalId
-        )
-        assertEquals(
-            "https://backend.demo.taler.net/public/instances/FSF/";,
-            transaction.newOrderShortInfo.merchantBaseUrl
-        )
-        assertEquals("2019.364-01M4QH6KPMJY4", 
transaction.newOrderShortInfo.orderId)
-        assertEquals("KUDOS:0.5", 
transaction.newOrderShortInfo.amount.toJSONString())
-        assertEquals("Essay: 1. The Free Software Definition", 
transaction.newOrderShortInfo.summary)
-
-        assertEquals(timestamp, transaction.timestamp.ms)
-    }
-
-    @Test
-    fun `test UnknownTransaction`() {
-        val json = """{
-            "type": "does not exist",
-            "timestamp": {
-              "t_ms": $timestamp
-            },
-            "eventId": 
"does-not-exist;898724XGQ1GGMZB4WY3KND582NSP74FZ60BX0Y87FF81H0FJ8XD0"
-          }""".trimIndent()
-        val event: Transaction = mapper.readValue(json)
-
-        assertEquals(UnknownTransaction::class.java, event.javaClass)
-        assertEquals(timestamp, event.timestamp.ms)
-    }
-
-}

-- 
To stop receiving notification emails like this one, please contact
address@hidden.



reply via email to

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