gnunet-svn
[Top][All Lists]
Advanced

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

[taler-taler-android] 01/01: Merge all three apps into one repository


From: gnunet
Subject: [taler-taler-android] 01/01: Merge all three apps into one repository
Date: Wed, 18 Mar 2020 18:25:16 +0100

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

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

commit a4796ec47d89a851b260b6fc195494547208a025
Author: Torsten Grote <address@hidden>
AuthorDate: Wed Mar 18 14:24:41 2020 -0300

    Merge all three apps into one repository
---
 .gitignore                                         |  19 +
 .gitlab-ci.yml                                     |  20 +
 .idea/codeStyles/Project.xml                       | 124 ++++
 .idea/codeStyles/codeStyleConfig.xml               |   5 +
 .idea/compiler.xml                                 |  15 +
 .idea/copyright/Taler.xml                          |   7 +
 .idea/copyright/profiles_settings.xml              |   7 +
 .idea/dictionaries/user.xml                        |  14 +
 .idea/encodings.xml                                |   6 +
 .idea/gradle.xml                                   |  23 +
 .idea/scopes/Copyright_Files.xml                   |   3 +
 COPYING                                            | 674 +++++++++++++++++++++
 akono/.gitignore                                   |   1 +
 akono/build.gradle                                 |  18 +
 artwork/ic_bottom_left.svg                         |  56 ++
 artwork/ic_bottom_right.svg                        |  56 ++
 artwork/ic_launcher_cashier.svg                    |  55 ++
 build.gradle                                       |  24 +
 cashier/.gitignore                                 |   1 +
 cashier/.gitlab-ci.yml                             |  35 ++
 cashier/README.md                                  |  10 +
 cashier/build.gradle                               |  72 +++
 cashier/lint.xml                                   |   4 +
 cashier/proguard-rules.pro                         |  21 +
 cashier/src/main/AndroidManifest.xml               |  32 +
 cashier/src/main/ic_launcher-web.png               | Bin 0 -> 30434 bytes
 cashier/src/main/java/net/taler/cashier/Amount.kt  |  45 ++
 .../main/java/net/taler/cashier/BalanceFragment.kt | 182 ++++++
 .../main/java/net/taler/cashier/ConfigFragment.kt  | 139 +++++
 .../src/main/java/net/taler/cashier/HttpHelper.kt  | 102 ++++
 .../main/java/net/taler/cashier/MainActivity.kt    |  62 ++
 .../main/java/net/taler/cashier/MainViewModel.kt   | 148 +++++
 cashier/src/main/java/net/taler/cashier/Utils.kt   |  91 +++
 .../net/taler/cashier/withdraw/ErrorFragment.kt    |  55 ++
 .../java/net/taler/cashier/withdraw/NfcManager.kt  | 234 +++++++
 .../net/taler/cashier/withdraw/QrCodeManager.kt    |  42 ++
 .../taler/cashier/withdraw/TransactionFragment.kt  | 174 ++++++
 .../net/taler/cashier/withdraw/WithdrawManager.kt  | 232 +++++++
 cashier/src/main/res/drawable-w550dp/ic_arrow.xml  |  11 +
 cashier/src/main/res/drawable/ic_arrow.xml         |  11 +
 cashier/src/main/res/drawable/ic_check_circle.xml  |  10 +
 cashier/src/main/res/drawable/ic_clear.xml         |   9 +
 cashier/src/main/res/drawable/ic_error.xml         |  11 +
 .../main/res/drawable/ic_launcher_foreground.xml   |  15 +
 cashier/src/main/res/drawable/ic_withdraw.xml      |  10 +
 .../main/res/layout-w550dp/fragment_balance.xml    | 222 +++++++
 .../res/layout-w550dp/fragment_transaction.xml     | 111 ++++
 cashier/src/main/res/layout/activity_main.xml      |  51 ++
 cashier/src/main/res/layout/fragment_balance.xml   | 225 +++++++
 cashier/src/main/res/layout/fragment_config.xml    | 112 ++++
 cashier/src/main/res/layout/fragment_error.xml     |  65 ++
 .../src/main/res/layout/fragment_transaction.xml   | 100 +++
 cashier/src/main/res/menu/balance.xml              |  30 +
 .../src/main/res/mipmap-anydpi-v26/ic_launcher.xml |   5 +
 cashier/src/main/res/mipmap-hdpi/ic_launcher.png   | Bin 0 -> 3687 bytes
 cashier/src/main/res/mipmap-mdpi/ic_launcher.png   | Bin 0 -> 2408 bytes
 cashier/src/main/res/mipmap-xhdpi/ic_launcher.png  | Bin 0 -> 4875 bytes
 cashier/src/main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 7673 bytes
 .../src/main/res/mipmap-xxxhdpi/ic_launcher.png    | Bin 0 -> 10362 bytes
 cashier/src/main/res/navigation/nav_graph.xml      |  73 +++
 cashier/src/main/res/values-night/colors.xml       |  20 +
 cashier/src/main/res/values/colors.xml             |  10 +
 cashier/src/main/res/values/dimens.xml             |   3 +
 .../src/main/res/values/ic_launcher_background.xml |   4 +
 cashier/src/main/res/values/strings.xml            |  39 ++
 cashier/src/main/res/values/styles.xml             |  28 +
 cashier/src/main/res/xml/backup_descriptor.xml     |  19 +
 gradle.properties                                  |  21 +
 gradle/wrapper/gradle-wrapper.jar                  | Bin 0 -> 54329 bytes
 gradle/wrapper/gradle-wrapper.properties           |   6 +
 gradlew                                            | 172 ++++++
 gradlew.bat                                        |  84 +++
 merchant-terminal/.gitignore                       |   1 +
 merchant-terminal/.gitlab-ci.yml                   |  36 ++
 merchant-terminal/build.gradle                     |  76 +++
 merchant-terminal/proguard-rules.pro               |  21 +
 merchant-terminal/src/main/AndroidManifest.xml     |  56 ++
 merchant-terminal/src/main/ic_taler_logo-web.png   | Bin 0 -> 25951 bytes
 .../src/main/java/net/taler/merchantpos/Amount.kt  |  48 ++
 .../java/net/taler/merchantpos/MainActivity.kt     | 123 ++++
 .../java/net/taler/merchantpos/MainViewModel.kt    |  51 ++
 .../main/java/net/taler/merchantpos/NfcManager.kt  | 233 +++++++
 .../java/net/taler/merchantpos/QrCodeManager.kt    |  42 ++
 .../src/main/java/net/taler/merchantpos/Utils.kt   | 155 +++++
 .../merchantpos/config/ConfigFetcherFragment.kt    |  66 ++
 .../net/taler/merchantpos/config/ConfigManager.kt  | 181 ++++++
 .../net/taler/merchantpos/config/MerchantConfig.kt |  47 ++
 .../merchantpos/config/MerchantConfigFragment.kt   | 165 +++++
 .../taler/merchantpos/config/MerchantRequest.kt    |  41 ++
 .../taler/merchantpos/history/HistoryManager.kt    | 106 ++++
 .../merchantpos/history/MerchantHistoryFragment.kt | 160 +++++
 .../taler/merchantpos/history/RefundFragment.kt    |  99 +++
 .../net/taler/merchantpos/history/RefundManager.kt | 111 ++++
 .../taler/merchantpos/history/RefundUriFragment.kt |  65 ++
 .../taler/merchantpos/order/CategoriesFragment.kt  | 106 ++++
 .../net/taler/merchantpos/order/Definitions.kt     | 205 +++++++
 .../java/net/taler/merchantpos/order/LiveOrder.kt  | 109 ++++
 .../net/taler/merchantpos/order/OrderFragment.kt   | 115 ++++
 .../net/taler/merchantpos/order/OrderManager.kt    | 196 ++++++
 .../taler/merchantpos/order/OrderStateFragment.kt  | 213 +++++++
 .../taler/merchantpos/order/ProductsFragment.kt    | 111 ++++
 .../java/net/taler/merchantpos/payment/Payment.kt  |  29 +
 .../taler/merchantpos/payment/PaymentManager.kt    | 154 +++++
 .../merchantpos/payment/PaymentSuccessFragment.kt  |  44 ++
 .../merchantpos/payment/ProcessPaymentFragment.kt  |  96 +++
 .../src/main/res/color/button_bottom.xml           |   5 +
 .../src/main/res/drawable/ic_cash_refund.xml       |   9 +
 .../src/main/res/drawable/ic_check_circle.xml      |  10 +
 .../main/res/drawable/ic_history_black_24dp.xml    |   9 +
 .../main/res/drawable/ic_launcher_background.xml   |  74 +++
 .../src/main/res/drawable/ic_menu_manage.xml       |   9 +
 .../src/main/res/drawable/ic_move_money_24dp.xml   |   9 +
 .../main/res/drawable/selectable_background.xml    |   5 +
 .../src/main/res/drawable/side_nav_bar.xml         |   9 +
 .../src/main/res/layout/activity_main.xml          |  42 ++
 .../src/main/res/layout/app_bar_main.xml           |  53 ++
 .../src/main/res/layout/fragment_categories.xml    |  46 ++
 .../main/res/layout/fragment_config_fetcher.xml    |  45 ++
 .../main/res/layout/fragment_merchant_config.xml   | 152 +++++
 .../main/res/layout/fragment_merchant_history.xml  |  29 +
 .../src/main/res/layout/fragment_order.xml         | 138 +++++
 .../src/main/res/layout/fragment_order_state.xml   |  52 ++
 .../main/res/layout/fragment_payment_success.xml   |  78 +++
 .../main/res/layout/fragment_process_payment.xml   | 110 ++++
 .../src/main/res/layout/fragment_products.xml      |  44 ++
 .../src/main/res/layout/fragment_refund.xml        | 122 ++++
 .../src/main/res/layout/fragment_refund_uri.xml    |  93 +++
 .../src/main/res/layout/list_item_category.xml     |  33 +
 .../src/main/res/layout/list_item_history.xml      |  97 +++
 .../src/main/res/layout/list_item_order.xml        |  61 ++
 .../src/main/res/layout/list_item_product.xml      |  56 ++
 .../src/main/res/layout/nav_header_main.xml        |  55 ++
 .../src/main/res/menu/activity_main_drawer.xml     |  36 ++
 .../main/res/mipmap-anydpi-v26/ic_taler_logo.xml   |   5 +
 .../res/mipmap-anydpi-v26/ic_taler_logo_round.xml  |   5 +
 .../res/mipmap-hdpi/ic_launcher_foreground.png     | Bin 0 -> 4307 bytes
 .../src/main/res/mipmap-hdpi/ic_taler_logo.png     | Bin 0 -> 2347 bytes
 .../main/res/mipmap-hdpi/ic_taler_logo_round.png   | Bin 0 -> 3638 bytes
 .../res/mipmap-mdpi/ic_launcher_foreground.png     | Bin 0 -> 2625 bytes
 .../src/main/res/mipmap-mdpi/ic_taler_logo.png     | Bin 0 -> 1532 bytes
 .../main/res/mipmap-mdpi/ic_taler_logo_round.png   | Bin 0 -> 2240 bytes
 .../res/mipmap-xhdpi/ic_launcher_foreground.png    | Bin 0 -> 6077 bytes
 .../src/main/res/mipmap-xhdpi/ic_taler_logo.png    | Bin 0 -> 3336 bytes
 .../main/res/mipmap-xhdpi/ic_taler_logo_round.png  | Bin 0 -> 5273 bytes
 .../res/mipmap-xxhdpi/ic_launcher_foreground.png   | Bin 0 -> 10228 bytes
 .../src/main/res/mipmap-xxhdpi/ic_taler_logo.png   | Bin 0 -> 5422 bytes
 .../main/res/mipmap-xxhdpi/ic_taler_logo_round.png | Bin 0 -> 8454 bytes
 .../res/mipmap-xxxhdpi/ic_launcher_foreground.png  | Bin 0 -> 14083 bytes
 .../src/main/res/mipmap-xxxhdpi/ic_taler_logo.png  | Bin 0 -> 7786 bytes
 .../res/mipmap-xxxhdpi/ic_taler_logo_round.png     | Bin 0 -> 12377 bytes
 .../src/main/res/navigation/nav_graph.xml          | 137 +++++
 .../src/main/res/values-night/colors.xml           |   5 +
 merchant-terminal/src/main/res/values/colors.xml   |  14 +
 merchant-terminal/src/main/res/values/dimens.xml   |   6 +
 merchant-terminal/src/main/res/values/strings.xml  |  68 +++
 merchant-terminal/src/main/res/values/styles.xml   |  21 +
 .../src/main/res/xml/backup_descriptor.xml         |   4 +
 .../taler/merchantpos/order/OrderManagerTest.kt    | 151 +++++
 nightly-stats.patch                                |  38 ++
 settings.gradle                                    |  17 +
 wallet/.gitignore                                  |   2 +
 wallet/.gitlab-ci.yml                              |  42 ++
 wallet/README.md                                   |  40 ++
 wallet/build.gradle                                |  81 +++
 wallet/proguard-rules.pro                          |  21 +
 .../net/taler/wallet/ExampleInstrumentedTest.kt    |  38 ++
 wallet/src/main/AndroidManifest.xml                |  81 +++
 wallet/src/main/ic_launcher-web.png                | Bin 0 -> 14129 bytes
 wallet/src/main/java/net/taler/wallet/Amount.kt    | 141 +++++
 .../main/java/net/taler/wallet/BalanceFragment.kt  | 198 ++++++
 .../net/taler/wallet/HostCardEmulatorService.kt    | 187 ++++++
 .../src/main/java/net/taler/wallet/MainActivity.kt | 209 +++++++
 wallet/src/main/java/net/taler/wallet/Settings.kt  | 140 +++++
 wallet/src/main/java/net/taler/wallet/Utils.kt     |  40 ++
 .../main/java/net/taler/wallet/WalletViewModel.kt  | 124 ++++
 .../net/taler/wallet/backend/WalletBackendApi.kt   | 141 +++++
 .../taler/wallet/backend/WalletBackendService.kt   | 239 ++++++++
 .../main/java/net/taler/wallet/crypto/Encoding.kt  | 134 ++++
 .../java/net/taler/wallet/history/HistoryEvent.kt  | 452 ++++++++++++++
 .../net/taler/wallet/history/HistoryManager.kt     |  71 +++
 .../net/taler/wallet/history/JsonDialogFragment.kt |  50 ++
 .../net/taler/wallet/history/ReserveTransaction.kt |  58 ++
 .../taler/wallet/history/WalletHistoryAdapter.kt   | 243 ++++++++
 .../taler/wallet/history/WalletHistoryFragment.kt  | 115 ++++
 .../taler/wallet/payment/AlreadyPaidFragment.kt    |  47 ++
 .../java/net/taler/wallet/payment/ContractTerms.kt |  56 ++
 .../net/taler/wallet/payment/PaymentManager.kt     | 160 +++++
 .../wallet/payment/PaymentSuccessfulFragment.kt    |  49 ++
 .../net/taler/wallet/payment/ProductAdapter.kt     |  92 +++
 .../taler/wallet/payment/ProductImageFragment.kt   |  52 ++
 .../taler/wallet/payment/PromptPaymentFragment.kt  | 168 +++++
 .../wallet/pending/PendingOperationsFragment.kt    | 180 ++++++
 .../wallet/pending/PendingOperationsManager.kt     |  64 ++
 .../net/taler/wallet/withdraw/ErrorFragment.kt     |  64 ++
 .../wallet/withdraw/PromptWithdrawFragment.kt      | 109 ++++
 .../wallet/withdraw/ReviewExchangeTosFragment.kt   |  80 +++
 .../net/taler/wallet/withdraw/WithdrawManager.kt   | 209 +++++++
 .../wallet/withdraw/WithdrawSuccessfulFragment.kt  |  44 ++
 .../main/res/drawable/history_payment_aborted.xml  |  25 +
 wallet/src/main/res/drawable/history_refresh.xml   |  28 +
 wallet/src/main/res/drawable/history_refund.xml    |  25 +
 .../src/main/res/drawable/history_tip_accepted.xml |  25 +
 .../src/main/res/drawable/history_tip_declined.xml |  25 +
 wallet/src/main/res/drawable/history_withdrawn.xml |  25 +
 .../src/main/res/drawable/ic_account_balance.xml   |  25 +
 .../res/drawable/ic_account_balance_wallet.xml     |   9 +
 wallet/src/main/res/drawable/ic_add_circle.xml     |  25 +
 wallet/src/main/res/drawable/ic_cancel.xml         |  25 +
 .../src/main/res/drawable/ic_cash_usd_outline.xml  |  25 +
 wallet/src/main/res/drawable/ic_check_circle.xml   |  26 +
 wallet/src/main/res/drawable/ic_directions.xml     |  25 +
 wallet/src/main/res/drawable/ic_error.xml          |  25 +
 .../main/res/drawable/ic_history_black_24dp.xml    |  25 +
 .../src/main/res/drawable/ic_home_black_24dp.xml   |  25 +
 .../main/res/drawable/ic_launcher_foreground.xml   |  68 +++
 wallet/src/main/res/drawable/ic_scan_qr.xml        |  10 +
 wallet/src/main/res/drawable/ic_settings.xml       |   9 +
 wallet/src/main/res/drawable/ic_sync.xml           |   9 +
 wallet/src/main/res/drawable/pending_border.xml    |  37 ++
 wallet/src/main/res/drawable/side_nav_bar.xml      |  24 +
 .../main/res/layout-w550dp/payment_bottom_bar.xml  | 123 ++++
 wallet/src/main/res/layout/activity_main.xml       |  40 ++
 wallet/src/main/res/layout/app_bar_main.xml        |  75 +++
 .../src/main/res/layout/fragment_already_paid.xml  |  52 ++
 wallet/src/main/res/layout/fragment_error.xml      |  97 +++
 wallet/src/main/res/layout/fragment_json.xml       |  41 ++
 .../res/layout/fragment_payment_successful.xml     |  63 ++
 .../res/layout/fragment_pending_operations.xml     |  34 ++
 .../src/main/res/layout/fragment_product_image.xml |  24 +
 .../main/res/layout/fragment_prompt_payment.xml    |  44 ++
 .../main/res/layout/fragment_prompt_withdraw.xml   | 171 ++++++
 .../res/layout/fragment_review_exchange_tos.xml    | 105 ++++
 wallet/src/main/res/layout/fragment_settings.xml   | 104 ++++
 .../src/main/res/layout/fragment_show_balance.xml  |  91 +++
 .../src/main/res/layout/fragment_show_history.xml  |  47 ++
 .../res/layout/fragment_withdraw_successful.xml    |  63 ++
 wallet/src/main/res/layout/history_payment.xml     |  87 +++
 wallet/src/main/res/layout/history_receive.xml     | 105 ++++
 wallet/src/main/res/layout/history_row.xml         |  73 +++
 wallet/src/main/res/layout/list_item_balance.xml   |  77 +++
 wallet/src/main/res/layout/list_item_product.xml   |  75 +++
 .../main/res/layout/list_item_product_single.xml   |  78 +++
 wallet/src/main/res/layout/nav_header_main.xml     |  73 +++
 wallet/src/main/res/layout/payment_bottom_bar.xml  | 123 ++++
 wallet/src/main/res/layout/payment_details.xml     | 119 ++++
 wallet/src/main/res/layout/pending_row.xml         |  48 ++
 wallet/src/main/res/menu/activity_main_drawer.xml  |  41 ++
 wallet/src/main/res/menu/balance.xml               |  28 +
 wallet/src/main/res/menu/history.xml               |  31 +
 wallet/src/main/res/menu/pending_operations.xml    |  24 +
 .../src/main/res/mipmap-anydpi-v26/ic_launcher.xml |  21 +
 .../res/mipmap-anydpi-v26/ic_launcher_round.xml    |  21 +
 wallet/src/main/res/mipmap-hdpi/ic_launcher.png    | Bin 0 -> 1611 bytes
 .../src/main/res/mipmap-hdpi/ic_launcher_round.png | Bin 0 -> 2898 bytes
 wallet/src/main/res/mipmap-mdpi/ic_launcher.png    | Bin 0 -> 1101 bytes
 .../src/main/res/mipmap-mdpi/ic_launcher_round.png | Bin 0 -> 1836 bytes
 wallet/src/main/res/mipmap-xhdpi/ic_launcher.png   | Bin 0 -> 2314 bytes
 .../main/res/mipmap-xhdpi/ic_launcher_round.png    | Bin 0 -> 4158 bytes
 wallet/src/main/res/mipmap-xxhdpi/ic_launcher.png  | Bin 0 -> 3405 bytes
 .../main/res/mipmap-xxhdpi/ic_launcher_round.png   | Bin 0 -> 6328 bytes
 wallet/src/main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 4592 bytes
 .../main/res/mipmap-xxxhdpi/ic_launcher_round.png  | Bin 0 -> 8828 bytes
 wallet/src/main/res/navigation/nav_graph.xml       | 125 ++++
 wallet/src/main/res/values/colors.xml              |  25 +
 wallet/src/main/res/values/dimens.xml              |  24 +
 .../src/main/res/values/ic_launcher_background.xml |  20 +
 wallet/src/main/res/values/strings.xml             | 105 ++++
 wallet/src/main/res/values/styles.xml              |  46 ++
 wallet/src/main/res/xml/apduservice.xml            |  25 +
 wallet/src/main/res/xml/backup_descriptor.xml      |  20 +
 .../test/java/net/taler/wallet/ExampleUnitTest.kt  |  33 +
 .../net/taler/wallet/crypto/Base32CrockfordTest.kt |  35 ++
 .../net/taler/wallet/history/HistoryEventTest.kt   | 459 ++++++++++++++
 .../taler/wallet/history/ReserveTransactionTest.kt |  52 ++
 274 files changed, 17431 insertions(+)

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..52aa44f
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,19 @@
+*.iml
+.gradle
+/local.properties
+/.idea/caches
+/.idea/libraries
+/.idea/misc.xml
+/.idea/modules.xml
+/.idea/workspace.xml
+/.idea/navEditor.xml
+/.idea/assetWizardSettings.xml
+/.idea/runConfigurations.xml
+/.idea/vcs.xml
+.DS_Store
+/build
+/captures
+.externalNativeBuild
+.cxx
+/akono/akono.aar
+/*/release/
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
new file mode 100644
index 0000000..e51d33b
--- /dev/null
+++ b/.gitlab-ci.yml
@@ -0,0 +1,20 @@
+image: registry.gitlab.com/fdroid/ci-images-client:latest
+
+cache:
+  paths:
+    - .gradle/wrapper
+    - .gradle/caches
+
+stages:
+  - test
+  - deploy
+
+include:
+  - local: 'cashier/.gitlab-ci.yml'
+  - local: 'merchant-terminal/.gitlab-ci.yml'
+  - local: 'wallet/.gitlab-ci.yml'
+
+after_script:
+  # this file changes every time but should not be cached
+  - rm -f $GRADLE_USER_HOME/caches/modules-2/modules-2.lock
+  - rm -fr $GRADLE_USER_HOME/caches/*/plugin-resolution/
diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
new file mode 100644
index 0000000..26724fb
--- /dev/null
+++ b/.idea/codeStyles/Project.xml
@@ -0,0 +1,124 @@
+<component name="ProjectCodeStyleConfiguration">
+  <code_scheme name="Project" version="173">
+    <JetCodeStyleSettings>
+      <option name="NAME_COUNT_TO_USE_STAR_IMPORT" value="2147483647" />
+      <option name="NAME_COUNT_TO_USE_STAR_IMPORT_FOR_MEMBERS" 
value="2147483647" />
+      <option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
+    </JetCodeStyleSettings>
+    <codeStyleSettings language="XML">
+      <indentOptions>
+        <option name="CONTINUATION_INDENT_SIZE" value="4" />
+      </indentOptions>
+      <arrangement>
+        <rules>
+          <section>
+            <rule>
+              <match>
+                <AND>
+                  <NAME>xmlns:android</NAME>
+                  <XML_ATTRIBUTE />
+                  <XML_NAMESPACE>^$</XML_NAMESPACE>
+                </AND>
+              </match>
+            </rule>
+          </section>
+          <section>
+            <rule>
+              <match>
+                <AND>
+                  <NAME>xmlns:.*</NAME>
+                  <XML_ATTRIBUTE />
+                  <XML_NAMESPACE>^$</XML_NAMESPACE>
+                </AND>
+              </match>
+              <order>BY_NAME</order>
+            </rule>
+          </section>
+          <section>
+            <rule>
+              <match>
+                <AND>
+                  <NAME>.*:id</NAME>
+                  <XML_ATTRIBUTE />
+                  
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
+                </AND>
+              </match>
+            </rule>
+          </section>
+          <section>
+            <rule>
+              <match>
+                <AND>
+                  <NAME>.*:name</NAME>
+                  <XML_ATTRIBUTE />
+                  
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
+                </AND>
+              </match>
+            </rule>
+          </section>
+          <section>
+            <rule>
+              <match>
+                <AND>
+                  <NAME>name</NAME>
+                  <XML_ATTRIBUTE />
+                  <XML_NAMESPACE>^$</XML_NAMESPACE>
+                </AND>
+              </match>
+            </rule>
+          </section>
+          <section>
+            <rule>
+              <match>
+                <AND>
+                  <NAME>style</NAME>
+                  <XML_ATTRIBUTE />
+                  <XML_NAMESPACE>^$</XML_NAMESPACE>
+                </AND>
+              </match>
+            </rule>
+          </section>
+          <section>
+            <rule>
+              <match>
+                <AND>
+                  <NAME>.*</NAME>
+                  <XML_ATTRIBUTE />
+                  <XML_NAMESPACE>^$</XML_NAMESPACE>
+                </AND>
+              </match>
+              <order>BY_NAME</order>
+            </rule>
+          </section>
+          <section>
+            <rule>
+              <match>
+                <AND>
+                  <NAME>.*</NAME>
+                  <XML_ATTRIBUTE />
+                  
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
+                </AND>
+              </match>
+              <order>ANDROID_ATTRIBUTE_ORDER</order>
+            </rule>
+          </section>
+          <section>
+            <rule>
+              <match>
+                <AND>
+                  <NAME>.*</NAME>
+                  <XML_ATTRIBUTE />
+                  <XML_NAMESPACE>.*</XML_NAMESPACE>
+                </AND>
+              </match>
+              <order>BY_NAME</order>
+            </rule>
+          </section>
+        </rules>
+      </arrangement>
+    </codeStyleSettings>
+    <codeStyleSettings language="kotlin">
+      <option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
+    </codeStyleSettings>
+  </code_scheme>
+</component>
\ No newline at end of file
diff --git a/.idea/codeStyles/codeStyleConfig.xml 
b/.idea/codeStyles/codeStyleConfig.xml
new file mode 100644
index 0000000..79ee123
--- /dev/null
+++ b/.idea/codeStyles/codeStyleConfig.xml
@@ -0,0 +1,5 @@
+<component name="ProjectCodeStyleConfiguration">
+  <state>
+    <option name="USE_PER_PROJECT_SETTINGS" value="true" />
+  </state>
+</component>
\ No newline at end of file
diff --git a/.idea/compiler.xml b/.idea/compiler.xml
new file mode 100644
index 0000000..40ed937
--- /dev/null
+++ b/.idea/compiler.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="CompilerConfiguration">
+    <wildcardResourcePatterns>
+      <entry name="!?*.java" />
+      <entry name="!?*.form" />
+      <entry name="!?*.class" />
+      <entry name="!?*.groovy" />
+      <entry name="!?*.scala" />
+      <entry name="!?*.flex" />
+      <entry name="!?*.kt" />
+      <entry name="!?*.clj" />
+    </wildcardResourcePatterns>
+  </component>
+</project>
\ No newline at end of file
diff --git a/.idea/copyright/Taler.xml b/.idea/copyright/Taler.xml
new file mode 100644
index 0000000..96abfa5
--- /dev/null
+++ b/.idea/copyright/Taler.xml
@@ -0,0 +1,7 @@
+<component name="CopyrightManager">
+  <copyright>
+    <option name="keyword" value="(Copyright|Public License)" />
+    <option name="notice" value="This file is part of GNU Taler&#10;(C) 
&amp;#36;today.year Taler Systems S.A.&#10;&#10;GNU Taler is free software; you 
can redistribute it and/or modify it under the&#10;terms of the GNU General 
Public License as published by the Free Software&#10;Foundation; either version 
3, or (at your option) any later version.&#10;&#10;GNU Taler is distributed in 
the hope that it will be useful, but WITHOUT ANY&#10;WARRANTY; without even the 
implied warranty of MERCH [...]
+    <option name="myName" value="Taler" />
+  </copyright>
+</component>
\ No newline at end of file
diff --git a/.idea/copyright/profiles_settings.xml 
b/.idea/copyright/profiles_settings.xml
new file mode 100644
index 0000000..31766eb
--- /dev/null
+++ b/.idea/copyright/profiles_settings.xml
@@ -0,0 +1,7 @@
+<component name="CopyrightManager">
+  <settings default="Taler">
+    <module2copyright>
+      <element module="Copyright Files" copyright="Taler" />
+    </module2copyright>
+  </settings>
+</component>
\ No newline at end of file
diff --git a/.idea/dictionaries/user.xml b/.idea/dictionaries/user.xml
new file mode 100644
index 0000000..4693d75
--- /dev/null
+++ b/.idea/dictionaries/user.xml
@@ -0,0 +1,14 @@
+<component name="ProjectDictionaryState">
+  <dictionary name="user">
+    <words>
+      <w>abcdef</w>
+      <w>aiddescription</w>
+      <w>akono</w>
+      <w>apdu</w>
+      <w>servicedesc</w>
+      <w>snackbar</w>
+      <w>taler</w>
+      <w>testkudos</w>
+    </words>
+  </dictionary>
+</component>
diff --git a/.idea/encodings.xml b/.idea/encodings.xml
new file mode 100644
index 0000000..97626ba
--- /dev/null
+++ b/.idea/encodings.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="Encoding">
+    <file url="PROJECT" charset="UTF-8" />
+  </component>
+</project>
\ No newline at end of file
diff --git a/.idea/gradle.xml b/.idea/gradle.xml
new file mode 100644
index 0000000..65dee6e
--- /dev/null
+++ b/.idea/gradle.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="GradleMigrationSettings" migrationVersion="1" />
+  <component name="GradleSettings">
+    <option name="linkedExternalProjectsSettings">
+      <GradleProjectSettings>
+        <option name="testRunner" value="PLATFORM" />
+        <option name="distributionType" value="DEFAULT_WRAPPED" />
+        <option name="externalProjectPath" value="$PROJECT_DIR$" />
+        <option name="modules">
+          <set>
+            <option value="$PROJECT_DIR$" />
+            <option value="$PROJECT_DIR$/akono" />
+            <option value="$PROJECT_DIR$/cashier" />
+            <option value="$PROJECT_DIR$/merchant-terminal" />
+            <option value="$PROJECT_DIR$/wallet" />
+          </set>
+        </option>
+        <option name="resolveModulePerSourceSet" value="false" />
+      </GradleProjectSettings>
+    </option>
+  </component>
+</project>
\ No newline at end of file
diff --git a/.idea/scopes/Copyright_Files.xml b/.idea/scopes/Copyright_Files.xml
new file mode 100644
index 0000000..d0ebcb8
--- /dev/null
+++ b/.idea/scopes/Copyright_Files.xml
@@ -0,0 +1,3 @@
+<component name="DependencyValidationManager">
+  <scope name="Copyright Files" 
pattern="file[cashier]:src/main/java//*||file[cashier]:src/main/res/layout//*||file[cashier]:src/main/res/layout-w550dp//*||file[merchant-terminal]:src/main/java//*||file[merchant-terminal]:src/main/res/layout//*||file[wallet]:src/main/java//*||file[wallet]:src/main/res/layout//*||file[wallet]:src/main/res/layout-w550dp//*"
 />
+</component>
\ No newline at end of file
diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..94a9ed0
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,674 @@
+                    GNU GENERAL PUBLIC LICENSE
+                       Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+  The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works.  By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.  We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors.  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+  To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights.  Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received.  You must make sure that they, too, receive
+or can get the source code.  And you must show them these terms so they
+know their rights.
+
+  Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+  For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software.  For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+  Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so.  This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software.  The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable.  Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products.  If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+  Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary.  To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                       TERMS AND CONDITIONS
+
+  0. Definitions.
+
+  "This License" refers to version 3 of the GNU General Public License.
+
+  "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+  "The Program" refers to any copyrightable work licensed under this
+License.  Each licensee is addressed as "you".  "Licensees" and
+"recipients" may be individuals or organizations.
+
+  To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy.  The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+  A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+  To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy.  Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+  To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies.  Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+  An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License.  If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+  1. Source Code.
+
+  The "source code" for a work means the preferred form of the work
+for making modifications to it.  "Object code" means any non-source
+form of a work.
+
+  A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+  The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form.  A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+  The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities.  However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work.  For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+  The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+  The Corresponding Source for a work in source code form is that
+same work.
+
+  2. Basic Permissions.
+
+  All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met.  This License explicitly affirms your unlimited
+permission to run the unmodified Program.  The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work.  This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+  You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force.  You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright.  Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+  Conveying under any other circumstances is permitted solely under
+the conditions stated below.  Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+  3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+  No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+  When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+  4. Conveying Verbatim Copies.
+
+  You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+  You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+  5. Conveying Modified Source Versions.
+
+  You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+    a) The work must carry prominent notices stating that you modified
+    it, and giving a relevant date.
+
+    b) The work must carry prominent notices stating that it is
+    released under this License and any conditions added under section
+    7.  This requirement modifies the requirement in section 4 to
+    "keep intact all notices".
+
+    c) You must license the entire work, as a whole, under this
+    License to anyone who comes into possession of a copy.  This
+    License will therefore apply, along with any applicable section 7
+    additional terms, to the whole of the work, and all its parts,
+    regardless of how they are packaged.  This License gives no
+    permission to license the work in any other way, but it does not
+    invalidate such permission if you have separately received it.
+
+    d) If the work has interactive user interfaces, each must display
+    Appropriate Legal Notices; however, if the Program has interactive
+    interfaces that do not display Appropriate Legal Notices, your
+    work need not make them do so.
+
+  A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit.  Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+  6. Conveying Non-Source Forms.
+
+  You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+    a) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by the
+    Corresponding Source fixed on a durable physical medium
+    customarily used for software interchange.
+
+    b) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by a
+    written offer, valid for at least three years and valid for as
+    long as you offer spare parts or customer support for that product
+    model, to give anyone who possesses the object code either (1) a
+    copy of the Corresponding Source for all the software in the
+    product that is covered by this License, on a durable physical
+    medium customarily used for software interchange, for a price no
+    more than your reasonable cost of physically performing this
+    conveying of source, or (2) access to copy the
+    Corresponding Source from a network server at no charge.
+
+    c) Convey individual copies of the object code with a copy of the
+    written offer to provide the Corresponding Source.  This
+    alternative is allowed only occasionally and noncommercially, and
+    only if you received the object code with such an offer, in accord
+    with subsection 6b.
+
+    d) Convey the object code by offering access from a designated
+    place (gratis or for a charge), and offer equivalent access to the
+    Corresponding Source in the same way through the same place at no
+    further charge.  You need not require recipients to copy the
+    Corresponding Source along with the object code.  If the place to
+    copy the object code is a network server, the Corresponding Source
+    may be on a different server (operated by you or a third party)
+    that supports equivalent copying facilities, provided you maintain
+    clear directions next to the object code saying where to find the
+    Corresponding Source.  Regardless of what server hosts the
+    Corresponding Source, you remain obligated to ensure that it is
+    available for as long as needed to satisfy these requirements.
+
+    e) Convey the object code using peer-to-peer transmission, provided
+    you inform other peers where the object code and Corresponding
+    Source of the work are being offered to the general public at no
+    charge under subsection 6d.
+
+  A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+  A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling.  In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage.  For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product.  A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+  "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source.  The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+  If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information.  But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+  The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed.  Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+  Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+  7. Additional Terms.
+
+  "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law.  If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+  When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it.  (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.)  You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+  Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+    a) Disclaiming warranty or limiting liability differently from the
+    terms of sections 15 and 16 of this License; or
+
+    b) Requiring preservation of specified reasonable legal notices or
+    author attributions in that material or in the Appropriate Legal
+    Notices displayed by works containing it; or
+
+    c) Prohibiting misrepresentation of the origin of that material, or
+    requiring that modified versions of such material be marked in
+    reasonable ways as different from the original version; or
+
+    d) Limiting the use for publicity purposes of names of licensors or
+    authors of the material; or
+
+    e) Declining to grant rights under trademark law for use of some
+    trade names, trademarks, or service marks; or
+
+    f) Requiring indemnification of licensors and authors of that
+    material by anyone who conveys the material (or modified versions of
+    it) with contractual assumptions of liability to the recipient, for
+    any liability that these contractual assumptions directly impose on
+    those licensors and authors.
+
+  All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10.  If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term.  If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+  If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+  Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+  8. Termination.
+
+  You may not propagate or modify a covered work except as expressly
+provided under this License.  Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+  However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+  Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+  Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License.  If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+  9. Acceptance Not Required for Having Copies.
+
+  You are not required to accept this License in order to receive or
+run a copy of the Program.  Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance.  However,
+nothing other than this License grants you permission to propagate or
+modify any covered work.  These actions infringe copyright if you do
+not accept this License.  Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+  10. Automatic Licensing of Downstream Recipients.
+
+  Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License.  You are not responsible
+for enforcing compliance by third parties with this License.
+
+  An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations.  If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+  You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License.  For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+  11. Patents.
+
+  A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based.  The
+work thus licensed is called the contributor's "contributor version".
+
+  A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version.  For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+  Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+  In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement).  To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+  If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients.  "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+  If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+  A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License.  You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+  Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+  12. No Surrender of Others' Freedom.
+
+  If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all.  For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+  13. Use with the GNU Affero General Public License.
+
+  Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work.  The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+  14. Revised Versions of this License.
+
+  The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+  Each version is given a distinguishing version number.  If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation.  If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+  If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+  Later license versions may give you additional or different
+permissions.  However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+  15. Disclaimer of Warranty.
+
+  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. Limitation of Liability.
+
+  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+  17. Interpretation of Sections 15 and 16.
+
+  If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program 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 of the License, or
+    (at your option) any later version.
+
+    This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+  If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+    <program>  Copyright (C) <year>  <name of author>
+    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+  You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+<http://www.gnu.org/licenses/>.
+
+  The GNU General Public License does not permit incorporating your program
+into proprietary programs.  If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.  But first, please read
+<http://www.gnu.org/philosophy/why-not-lgpl.html>.
diff --git a/akono/.gitignore b/akono/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/akono/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/akono/build.gradle b/akono/build.gradle
new file mode 100644
index 0000000..45fbf89
--- /dev/null
+++ b/akono/build.gradle
@@ -0,0 +1,18 @@
+/*
+ * 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/>
+ */
+
+configurations.maybeCreate("default")
+artifacts.add("default", file('akono.aar'))
\ No newline at end of file
diff --git a/artwork/ic_bottom_left.svg b/artwork/ic_bottom_left.svg
new file mode 100644
index 0000000..c3aa7e4
--- /dev/null
+++ b/artwork/ic_bottom_left.svg
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/";
+   xmlns:cc="http://creativecommons.org/ns#";
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#";
+   xmlns:svg="http://www.w3.org/2000/svg";
+   xmlns="http://www.w3.org/2000/svg";
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd";
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape";
+   height="24"
+   version="1.1"
+   viewBox="0 0 24 24"
+   width="24"
+   id="svg4"
+   sodipodi:docname="ic_bottom_left.svg"
+   inkscape:version="0.92.4 (unknown)">
+  <metadata
+     id="metadata10">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage"; />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs8" />
+  <sodipodi:namedview
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1"
+     objecttolerance="10"
+     gridtolerance="10"
+     guidetolerance="10"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:window-width="1920"
+     inkscape:window-height="982"
+     id="namedview6"
+     showgrid="false"
+     inkscape:zoom="9.8333333"
+     inkscape:cx="-10.728814"
+     inkscape:cy="12"
+     inkscape:window-x="1920"
+     inkscape:window-y="72"
+     inkscape:window-maximized="0"
+     inkscape:current-layer="svg4" />
+  <path
+     d="M 20,5.41 18.59,4 7,15.59 V 9 H 5 V 19 H 15 V 17 H 8.41"
+     id="path2"
+     inkscape:connector-curvature="0"
+     style="fill:#000000" />
+</svg>
diff --git a/artwork/ic_bottom_right.svg b/artwork/ic_bottom_right.svg
new file mode 100644
index 0000000..26869ba
--- /dev/null
+++ b/artwork/ic_bottom_right.svg
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/";
+   xmlns:cc="http://creativecommons.org/ns#";
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#";
+   xmlns:svg="http://www.w3.org/2000/svg";
+   xmlns="http://www.w3.org/2000/svg";
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd";
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape";
+   height="24"
+   version="1.1"
+   viewBox="0 0 24 24"
+   width="24"
+   id="svg4"
+   sodipodi:docname="ic_bottom_right.svg"
+   inkscape:version="0.92.4 (unknown)">
+  <metadata
+     id="metadata10">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage"; />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs8" />
+  <sodipodi:namedview
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1"
+     objecttolerance="10"
+     gridtolerance="10"
+     guidetolerance="10"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:window-width="1920"
+     inkscape:window-height="982"
+     id="namedview6"
+     showgrid="false"
+     inkscape:zoom="9.8333333"
+     inkscape:cx="-10.728814"
+     inkscape:cy="12"
+     inkscape:window-x="1920"
+     inkscape:window-y="72"
+     inkscape:window-maximized="0"
+     inkscape:current-layer="svg4" />
+  <path
+     d="M 5,5.41 6.41,4 18,15.59 V 9 h 2 V 19 H 10 v -2 h 6.59"
+     id="path2"
+     inkscape:connector-curvature="0"
+     style="fill:#000000" />
+</svg>
diff --git a/artwork/ic_launcher_cashier.svg b/artwork/ic_launcher_cashier.svg
new file mode 100644
index 0000000..4868fe4
--- /dev/null
+++ b/artwork/ic_launcher_cashier.svg
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/";
+   xmlns:cc="http://creativecommons.org/ns#";
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#";
+   xmlns:svg="http://www.w3.org/2000/svg";
+   xmlns="http://www.w3.org/2000/svg";
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd";
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape";
+   version="1.1"
+   width="24"
+   height="24"
+   viewBox="0 0 24 24"
+   id="svg4"
+   sodipodi:docname="ic_launcher.svg"
+   inkscape:version="0.92.4 (unknown)">
+  <metadata
+     id="metadata10">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage"; />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs8" />
+  <sodipodi:namedview
+     pagecolor="#000000"
+     bordercolor="#666666"
+     borderopacity="1"
+     objecttolerance="10"
+     gridtolerance="10"
+     guidetolerance="10"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:window-width="1920"
+     inkscape:window-height="982"
+     id="namedview6"
+     showgrid="false"
+     inkscape:zoom="9.8333335"
+     inkscape:cx="-21.143993"
+     inkscape:cy="20.1778"
+     inkscape:window-x="1920"
+     inkscape:window-y="72"
+     inkscape:window-maximized="0"
+     inkscape:current-layer="svg4" />
+  <path
+     d="M 6 3 L 6 6 L 9 6 L 9 7 L 6.25 7 C 5.05 7 4.0507812 8 4.0507812 9 L 
3.5 16 L 20.5 16 L 20 9 C 19.8 8 18.800781 7 17.800781 7 L 11 7 L 11 6 L 14 6 L 
14 3 L 6 3 z M 7 4 L 13 4 L 13 5 L 7 5 L 7 4 z M 6 9 L 8 9 L 8 10 L 6 10 L 6 9 
z M 9 9 L 11 9 L 11 10 L 9 10 L 9 9 z M 13 9 L 18 9 L 18 11 L 13 11 L 13 9 z M 
6 11 L 8 11 L 8 12 L 6 12 L 6 11 z M 9 11 L 11 11 L 11 12 L 9 12 L 9 11 z M 6 
13 L 8 13 L 8 14 L 6 14 L 6 13 z M 9 13 L 11 13 L 11 14 L 9 14 L 9 13 z M 2 17 
L 2 21 L 22 21 L 22 1 [...]
+     id="path2"
+     style="fill:#f9f9f9;fill-opacity:1" />
+</svg>
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000..f286dfe
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,24 @@
+buildscript {
+    ext.kotlin_version = '1.3.70'
+    ext.nav_version = "2.2.1"
+    repositories {
+        google()
+        jcenter()
+    }
+    dependencies {
+        classpath 'com.android.tools.build:gradle:3.6.1'
+        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
+        classpath 
"androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version"
+    }
+}
+
+allprojects {
+    repositories {
+        google()
+        jcenter()
+    }
+}
+
+task clean(type: Delete) {
+    delete rootProject.buildDir
+}
diff --git a/cashier/.gitignore b/cashier/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/cashier/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/cashier/.gitlab-ci.yml b/cashier/.gitlab-ci.yml
new file mode 100644
index 0000000..f8cc7f3
--- /dev/null
+++ b/cashier/.gitlab-ci.yml
@@ -0,0 +1,35 @@
+image: registry.gitlab.com/fdroid/ci-images-client:latest
+
+cashier_test:
+  stage: test
+  only:
+    changes:
+      - "cashier"
+  script: ./gradlew :cashier:lint :cashier:assembleRelease
+
+cashier_deploy_nightly:
+  stage: deploy
+  only:
+    refs:
+      - master
+    changes:
+      - "cashier"
+  script:
+    # Ensure that key exists
+    - test -z "$DEBUG_KEYSTORE" && exit 0
+    # Rename nightly app
+    - sed -i
+        's,<string name="app_name">.*</string>,<string name="app_name">Cashier 
Nightly</string>,'
+        cashier/src/main/res/values*/strings.xml
+    # Set time-based version code
+    - export versionCode=$(date '+%s')
+    - sed -i "s,^\(\s*versionCode\) *[0-9].*,\1 $versionCode," 
cashier/build.gradle
+    # Set nightly application ID
+    - sed -i "s,^\(\s*applicationId\) \"*[a-z\.].*\",\1 
\"net.taler.cashier.nightly\"," cashier/build.gradle
+    # Build the APK
+    - ./gradlew :cashier:assembleDebug
+    # START only needed while patch not accepted/released upstream
+    - apt update && apt install patch
+    - patch /usr/lib/python3/dist-packages/fdroidserver/nightly.py 
nightly-stats.patch
+    # END
+    - CI_PROJECT_URL="https://gitlab.com/gnu-taler/fdroid-repo"; 
CI_PROJECT_PATH="gnu-taler/fdroid-repo" fdroid nightly -v
diff --git a/cashier/README.md b/cashier/README.md
new file mode 100644
index 0000000..e884f25
--- /dev/null
+++ b/cashier/README.md
@@ -0,0 +1,10 @@
+# GNU Taler Cashier App
+
+The purpose of this app is to enable people (a cashier) to take cash and give 
out e-cash.
+
+## Building
+
+You can import the project into Android Studio
+or build it with Gradle on the command line:
+
+    $ ./gradlew build
diff --git a/cashier/build.gradle b/cashier/build.gradle
new file mode 100644
index 0000000..5915f8a
--- /dev/null
+++ b/cashier/build.gradle
@@ -0,0 +1,72 @@
+/*
+ * 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/>
+ */
+
+apply plugin: 'com.android.application'
+apply plugin: 'kotlin-android'
+apply plugin: 'kotlin-android-extensions'
+apply plugin: 'androidx.navigation.safeargs.kotlin'
+
+android {
+    compileSdkVersion 29
+    buildToolsVersion "29.0.3"
+
+    defaultConfig {
+        applicationId "net.taler.cashier"
+        minSdkVersion 23
+        targetSdkVersion 29
+        versionCode 1
+        versionName "0.1"
+        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+    }
+
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles 
getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+        }
+    }
+
+    kotlinOptions {
+        jvmTarget = "1.8"
+    }
+
+}
+
+dependencies {
+    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
+    implementation 'androidx.appcompat:appcompat:1.1.0'
+    implementation 'androidx.core:core-ktx:1.2.0'
+    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
+    implementation 'androidx.security:security-crypto:1.0.0-alpha02'
+    implementation 'com.google.android.material:material:1.1.0'
+
+    implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
+    implementation "androidx.navigation:navigation-ui-ktx:$nav_version"
+
+    // ViewModel and LiveData
+    def lifecycle_version = "2.2.0"
+    implementation 
"androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
+
+    // QR codes
+    implementation 'com.google.zxing:core:3.4.0'
+
+    implementation "com.squareup.okhttp3:okhttp:3.12.6"
+
+    testImplementation 'junit:junit:4.13'
+
+    androidTestImplementation 'androidx.test.ext:junit:1.1.1'
+    androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
+}
diff --git a/cashier/lint.xml b/cashier/lint.xml
new file mode 100644
index 0000000..164e244
--- /dev/null
+++ b/cashier/lint.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<lint>
+
+</lint>
diff --git a/cashier/proguard-rules.pro b/cashier/proguard-rules.pro
new file mode 100644
index 0000000..f1b4245
--- /dev/null
+++ b/cashier/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+#   http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+#   public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
diff --git a/cashier/src/main/AndroidManifest.xml 
b/cashier/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..345c9a1
--- /dev/null
+++ b/cashier/src/main/AndroidManifest.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android";
+    xmlns:tools="http://schemas.android.com/tools";
+    package="net.taler.cashier">
+
+    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+    <uses-permission android:name="android.permission.INTERNET" />
+    <uses-permission android:name="android.permission.NFC" />
+
+    <application
+        android:allowBackup="true"
+        android:icon="@mipmap/ic_launcher"
+        android:label="@string/app_name"
+        android:fullBackupContent="@xml/backup_descriptor"
+        android:supportsRtl="true"
+        android:theme="@style/AppTheme"
+        android:roundIcon="@mipmap/ic_launcher"
+        tools:ignore="GoogleAppIndexingWarning">
+
+        <activity
+            android:name=".MainActivity"
+            android:label="@string/app_name"
+            android:theme="@style/AppTheme.NoActionBar">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+
+    </application>
+
+</manifest>
diff --git a/cashier/src/main/ic_launcher-web.png 
b/cashier/src/main/ic_launcher-web.png
new file mode 100644
index 0000000..04a58c6
Binary files /dev/null and b/cashier/src/main/ic_launcher-web.png differ
diff --git a/cashier/src/main/java/net/taler/cashier/Amount.kt 
b/cashier/src/main/java/net/taler/cashier/Amount.kt
new file mode 100644
index 0000000..2c237c8
--- /dev/null
+++ b/cashier/src/main/java/net/taler/cashier/Amount.kt
@@ -0,0 +1,45 @@
+/*
+ * 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.cashier
+
+data class Amount(val currency: String, val amount: String) {
+
+    companion object {
+
+        private val SIGNED_REGEX = Regex("""([+\-])(\w+):([0-9.]+)""")
+
+        @Suppress("unused")
+        fun fromString(strAmount: String): Amount {
+            val components = strAmount.split(":")
+            return Amount(components[0], components[1])
+        }
+
+        fun fromStringSigned(strAmount: String): Amount? {
+            val groups = SIGNED_REGEX.matchEntire(strAmount)?.groupValues ?: 
emptyList()
+            if (groups.size < 4) return null
+            var amount = groups[3].toDoubleOrNull() ?: return null
+            if (groups[1] == "-") amount *= -1
+            val currency = groups[2]
+            val amountStr = amount.toString()
+            // only display as many digits as required to precisely render the 
balance
+            return Amount(currency, amountStr.removeSuffix(".0"))
+        }
+    }
+
+    override fun toString() = "$amount $currency"
+
+}
diff --git a/cashier/src/main/java/net/taler/cashier/BalanceFragment.kt 
b/cashier/src/main/java/net/taler/cashier/BalanceFragment.kt
new file mode 100644
index 0000000..b3a0221
--- /dev/null
+++ b/cashier/src/main/java/net/taler/cashier/BalanceFragment.kt
@@ -0,0 +1,182 @@
+/*
+ * 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.cashier
+
+import android.annotation.SuppressLint
+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.VISIBLE
+import android.view.ViewGroup
+import android.view.inputmethod.EditorInfo
+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_balance.*
+import 
net.taler.cashier.BalanceFragmentDirections.Companion.actionBalanceFragmentToTransactionFragment
+import net.taler.cashier.withdraw.LastTransaction
+import net.taler.cashier.withdraw.WithdrawStatus
+
+sealed class BalanceResult {
+    object Error : BalanceResult()
+    object Offline : BalanceResult()
+    class Success(val amount: Amount) : BalanceResult()
+}
+
+class BalanceFragment : Fragment() {
+
+    private val viewModel: MainViewModel by activityViewModels()
+    private val withdrawManager by lazy { viewModel.withdrawManager }
+
+    override fun onCreateView(
+        inflater: LayoutInflater, container: ViewGroup?,
+        savedInstanceState: Bundle?
+    ): View? {
+        setHasOptionsMenu(true)
+        return inflater.inflate(R.layout.fragment_balance, container, false)
+    }
+
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+        withdrawManager.lastTransaction.observe(viewLifecycleOwner, Observer { 
lastTransaction ->
+            onLastTransaction(lastTransaction)
+        })
+        viewModel.balance.observe(viewLifecycleOwner, Observer { result ->
+            when (result) {
+                is BalanceResult.Success -> onBalanceUpdated(result.amount)
+                else -> onBalanceUpdated(null, result is BalanceResult.Offline)
+            }
+        })
+        button5.setOnClickListener { onAmountButtonPressed(5) }
+        button10.setOnClickListener { onAmountButtonPressed(10) }
+        button20.setOnClickListener { onAmountButtonPressed(20) }
+        button50.setOnClickListener { onAmountButtonPressed(50) }
+
+        if (savedInstanceState != null) {
+            
amountView.editText!!.setText(savedInstanceState.getCharSequence("amountView"))
+        }
+        amountView.editText!!.setOnEditorActionListener { _, actionId, _ ->
+            if (actionId == EditorInfo.IME_ACTION_GO) {
+                onAmountConfirmed(getAmountFromView())
+                true
+            } else false
+        }
+        viewModel.currency.observe(viewLifecycleOwner, Observer { currency ->
+            currencyView.text = currency
+        })
+        confirmWithdrawalButton.setOnClickListener { 
onAmountConfirmed(getAmountFromView()) }
+    }
+
+    override fun onStart() {
+        super.onStart()
+        // update balance if there's a config
+        if (viewModel.hasConfig()) {
+            viewModel.getBalance()
+        }
+    }
+
+    override fun onSaveInstanceState(outState: Bundle) {
+        super.onSaveInstanceState(outState)
+        // for some reason automatic restore isn't working at the moment!?
+        amountView?.editText?.text.let {
+            outState.putCharSequence("amountView", it)
+        }
+    }
+
+    override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
+        inflater.inflate(R.menu.balance, menu)
+        super.onCreateOptionsMenu(menu, inflater)
+    }
+
+    override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) {
+        R.id.action_reconfigure -> {
+            findNavController().navigate(viewModel.configDestination)
+            true
+        }
+        R.id.action_lock -> {
+            viewModel.lock()
+            findNavController().navigate(viewModel.configDestination)
+            true
+        }
+        else -> super.onOptionsItemSelected(item)
+    }
+
+    private fun onBalanceUpdated(amount: Amount?, isOffline: Boolean = false) {
+        val uiList = listOf(
+            introView,
+            button5, button10, button20, button50,
+            amountView, currencyView, confirmWithdrawalButton
+        )
+        if (amount == null) {
+            balanceView.text =
+                getString(if (isOffline) R.string.balance_offline else 
R.string.balance_error)
+            uiList.forEach { it.fadeOut() }
+        } else {
+            @SuppressLint("SetTextI18n")
+            balanceView.text = "${amount.amount} ${amount.currency}"
+            uiList.forEach { it.fadeIn() }
+        }
+        progressBar.fadeOut()
+    }
+
+    private fun onAmountButtonPressed(amount: Int) {
+        amountView.editText!!.setText(amount.toString())
+        amountView.error = null
+    }
+
+    private fun getAmountFromView(): Int {
+        val str = amountView.editText!!.text.toString()
+        if (str.isBlank()) return 0
+        return Integer.parseInt(str)
+    }
+
+    private fun onAmountConfirmed(amount: Int) {
+        if (amount <= 0) {
+            amountView.error = getString(R.string.withdraw_error_zero)
+        } else if (!withdrawManager.hasSufficientBalance(amount)) {
+            amountView.error = 
getString(R.string.withdraw_error_insufficient_balance)
+        } else {
+            amountView.error = null
+            withdrawManager.withdraw(amount)
+            actionBalanceFragmentToTransactionFragment().let {
+                findNavController().navigate(it)
+            }
+        }
+    }
+
+    private fun onLastTransaction(lastTransaction: LastTransaction?) {
+        val status = lastTransaction?.withdrawStatus
+        val text = when (status) {
+            is WithdrawStatus.Success -> getString(
+                R.string.transaction_last_success, 
lastTransaction.withdrawAmount
+            )
+            is WithdrawStatus.Aborted -> 
getString(R.string.transaction_last_aborted)
+            else -> getString(R.string.transaction_last_error)
+        }
+        lastTransactionView.text = text
+        val drawable = if (status == WithdrawStatus.Success)
+            R.drawable.ic_check_circle
+        else
+            R.drawable.ic_error
+        
lastTransactionView.setCompoundDrawablesRelativeWithIntrinsicBounds(drawable, 
0, 0, 0)
+        lastTransactionView.visibility = VISIBLE
+    }
+
+}
diff --git a/cashier/src/main/java/net/taler/cashier/ConfigFragment.kt 
b/cashier/src/main/java/net/taler/cashier/ConfigFragment.kt
new file mode 100644
index 0000000..b9a97e5
--- /dev/null
+++ b/cashier/src/main/java/net/taler/cashier/ConfigFragment.kt
@@ -0,0 +1,139 @@
+/*
+ * 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.cashier
+
+import android.os.Bundle
+import android.text.method.LinkMovementMethod
+import android.view.LayoutInflater
+import android.view.View
+import android.view.View.INVISIBLE
+import android.view.View.VISIBLE
+import android.view.ViewGroup
+import android.view.inputmethod.InputMethodManager
+import androidx.core.content.ContextCompat.getSystemService
+import androidx.core.text.HtmlCompat
+import androidx.core.text.HtmlCompat.FROM_HTML_MODE_LEGACY
+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_config.*
+
+private const val URL_BANK_TEST = "https://bank.test.taler.net";
+private const val URL_BANK_TEST_REGISTER = "$URL_BANK_TEST/accounts/register"
+
+class ConfigFragment : Fragment() {
+
+    private val viewModel: MainViewModel by activityViewModels()
+
+    override fun onCreateView(
+        inflater: LayoutInflater,
+        container: ViewGroup?,
+        savedInstanceState: Bundle?
+    ): View? {
+        return inflater.inflate(R.layout.fragment_config, container, false)
+    }
+
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+        if (savedInstanceState == null) {
+            if (viewModel.config.bankUrl.isBlank()) {
+                urlView.editText!!.setText(URL_BANK_TEST)
+            } else {
+                urlView.editText!!.setText(viewModel.config.bankUrl)
+            }
+            usernameView.editText!!.setText(viewModel.config.username)
+            passwordView.editText!!.setText(viewModel.config.password)
+        } else {
+            
urlView.editText!!.setText(savedInstanceState.getCharSequence("urlView"))
+            
usernameView.editText!!.setText(savedInstanceState.getCharSequence("usernameView"))
+            
passwordView.editText!!.setText(savedInstanceState.getCharSequence("passwordView"))
+        }
+        saveButton.setOnClickListener {
+            val config = Config(
+                bankUrl = urlView.editText!!.text.toString(),
+                username = usernameView.editText!!.text.toString(),
+                password = passwordView.editText!!.text.toString()
+            )
+            if (checkConfig(config)) {
+                // show progress
+                saveButton.visibility = INVISIBLE
+                progressBar.visibility = VISIBLE
+                // kick off check and observe result
+                viewModel.checkAndSaveConfig(config)
+                viewModel.configResult.observe(viewLifecycleOwner, 
onConfigResult)
+                // hide keyboard
+                val inputMethodManager =
+                    getSystemService(requireContext(), 
InputMethodManager::class.java)!!
+                inputMethodManager.hideSoftInputFromWindow(view.windowToken, 0)
+            }
+        }
+        demoView.text = HtmlCompat.fromHtml(
+            getString(R.string.config_demo_hint, URL_BANK_TEST_REGISTER), 
FROM_HTML_MODE_LEGACY
+        )
+        demoView.movementMethod = LinkMovementMethod.getInstance()
+    }
+
+    override fun onStart() {
+        super.onStart()
+        // focus on password if it is the only missing value (like after 
locking)
+        if (urlView.editText!!.text.isNotBlank()
+            && usernameView.editText!!.text.isNotBlank()
+            && passwordView.editText!!.text.isBlank()) {
+            passwordView.editText!!.requestFocus()
+        }
+    }
+
+    override fun onSaveInstanceState(outState: Bundle) {
+        super.onSaveInstanceState(outState)
+        // for some reason automatic restore isn't working at the moment!?
+        outState.putCharSequence("urlView", urlView.editText?.text)
+        outState.putCharSequence("usernameView", usernameView.editText?.text)
+        outState.putCharSequence("passwordView", passwordView.editText?.text)
+    }
+
+    private fun checkConfig(config: Config): Boolean {
+        if (!config.bankUrl.startsWith("https://";)) {
+            urlView.error = getString(R.string.config_bank_url_error)
+            urlView.requestFocus()
+            return false
+        }
+        if (config.username.isBlank()) {
+            usernameView.error = getString(R.string.config_username_error)
+            usernameView.requestFocus()
+            return false
+        }
+        urlView.isErrorEnabled = false
+        return true
+    }
+
+    private val onConfigResult = Observer<ConfigResult> { result ->
+        if (result == null) return@Observer
+        if (result.success) {
+            val action = 
ConfigFragmentDirections.actionConfigFragmentToBalanceFragment()
+            findNavController().navigate(action)
+        } else {
+            val res = if (result.authError) R.string.config_error_auth else 
R.string.config_error
+            Snackbar.make(view!!, res, LENGTH_LONG).show()
+        }
+        saveButton.visibility = VISIBLE
+        progressBar.visibility = INVISIBLE
+        viewModel.configResult.removeObservers(viewLifecycleOwner)
+    }
+
+}
diff --git a/cashier/src/main/java/net/taler/cashier/HttpHelper.kt 
b/cashier/src/main/java/net/taler/cashier/HttpHelper.kt
new file mode 100644
index 0000000..06b06db
--- /dev/null
+++ b/cashier/src/main/java/net/taler/cashier/HttpHelper.kt
@@ -0,0 +1,102 @@
+/*
+ * 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.cashier
+
+import android.util.Log
+import androidx.annotation.WorkerThread
+import okhttp3.Credentials
+import okhttp3.MediaType
+import okhttp3.OkHttpClient
+import okhttp3.Request
+import okhttp3.RequestBody
+import org.json.JSONObject
+
+object HttpHelper {
+
+    private val TAG = HttpHelper::class.java.simpleName
+    private const val MIME_TYPE_JSON = "application/json"
+
+    @WorkerThread
+    fun makeJsonGetRequest(url: String, config: Config): HttpJsonResult {
+        val request = Request.Builder()
+            .addHeader("Accept", MIME_TYPE_JSON)
+            .url(url)
+            .get()
+            .build()
+        val response = try {
+            getHttpClient(config.username, config.password)
+            .newCall(request)
+            .execute()
+        } catch (e: Exception) {
+            Log.e(TAG, "Error retrieving $url", e)
+            return HttpJsonResult.Error(500)
+        }
+        return if (response.code() == 200 && response.body() != null) {
+            val jsonObject = JSONObject(response.body()!!.string())
+            HttpJsonResult.Success(jsonObject)
+        } else {
+            Log.e(TAG, "Received status ${response.code()} from $url expected 
200")
+            HttpJsonResult.Error(response.code())
+        }
+    }
+
+    private val MEDIA_TYPE_JSON = MediaType.parse("$MIME_TYPE_JSON; 
charset=utf-8")
+
+    @WorkerThread
+    fun makeJsonPostRequest(url: String, body: String, config: Config): 
HttpJsonResult {
+        val request = Request.Builder()
+            .addHeader("Accept", MIME_TYPE_JSON)
+            .url(url)
+            .post(RequestBody.create(MEDIA_TYPE_JSON, body))
+            .build()
+        val response = try {
+            getHttpClient(config.username, config.password)
+                .newCall(request)
+                .execute()
+        } catch (e: Exception) {
+            Log.e(TAG, "Error retrieving $url", e)
+            return HttpJsonResult.Error(500)
+        }
+        return if (response.code() == 200 && response.body() != null) {
+            val jsonObject = JSONObject(response.body()!!.string())
+            HttpJsonResult.Success(jsonObject)
+        } else {
+            Log.e(TAG, "Received status ${response.code()} from $url expected 
200")
+            HttpJsonResult.Error(response.code())
+        }
+    }
+
+    private fun getHttpClient(username: String, password: String) =
+        OkHttpClient.Builder().authenticator { _, response ->
+            val credential = Credentials.basic(username, password)
+            if (credential == response.request().header("Authorization")) {
+                // If we already failed with these credentials, don't retry
+                return@authenticator null
+            }
+            response
+                .request()
+                .newBuilder()
+                .header("Authorization", credential)
+                .build()
+        }.build()
+
+}
+
+sealed class HttpJsonResult {
+    class Error(val statusCode: Int) : HttpJsonResult()
+    class Success(val json: JSONObject) : HttpJsonResult()
+}
diff --git a/cashier/src/main/java/net/taler/cashier/MainActivity.kt 
b/cashier/src/main/java/net/taler/cashier/MainActivity.kt
new file mode 100644
index 0000000..b238054
--- /dev/null
+++ b/cashier/src/main/java/net/taler/cashier/MainActivity.kt
@@ -0,0 +1,62 @@
+/*
+ * 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.cashier
+
+import android.content.Intent
+import android.content.Intent.*
+import android.os.Bundle
+import androidx.activity.viewModels
+import androidx.appcompat.app.AppCompatActivity
+import androidx.navigation.NavController
+import androidx.navigation.fragment.NavHostFragment
+import kotlinx.android.synthetic.main.activity_main.*
+
+class MainActivity : AppCompatActivity() {
+
+    private val viewModel: MainViewModel by viewModels()
+    private lateinit var nav: NavController
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        setContentView(R.layout.activity_main)
+        setSupportActionBar(toolbar)
+        val navHostFragment =
+            supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as 
NavHostFragment
+        nav = navHostFragment.navController
+    }
+
+    override fun onStart() {
+        super.onStart()
+        if (!viewModel.hasConfig()) {
+            nav.navigate(viewModel.configDestination)
+        }
+    }
+
+    override fun onBackPressed() {
+        if (!viewModel.hasConfig() && nav.currentDestination?.id == 
R.id.configFragment) {
+            // we are in the configuration screen and need a config to continue
+            val intent = Intent(ACTION_MAIN).apply {
+                addCategory(CATEGORY_HOME)
+                flags = FLAG_ACTIVITY_NEW_TASK
+            }
+            startActivity(intent)
+        } else {
+            super.onBackPressed()
+        }
+    }
+
+}
diff --git a/cashier/src/main/java/net/taler/cashier/MainViewModel.kt 
b/cashier/src/main/java/net/taler/cashier/MainViewModel.kt
new file mode 100644
index 0000000..3874038
--- /dev/null
+++ b/cashier/src/main/java/net/taler/cashier/MainViewModel.kt
@@ -0,0 +1,148 @@
+/*
+ * 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.cashier
+
+import android.annotation.SuppressLint
+import android.app.Application
+import android.util.Log
+import androidx.annotation.UiThread
+import androidx.annotation.WorkerThread
+import androidx.lifecycle.AndroidViewModel
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.viewModelScope
+import androidx.security.crypto.EncryptedSharedPreferences
+import 
androidx.security.crypto.EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV
+import 
androidx.security.crypto.EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
+import androidx.security.crypto.MasterKeys
+import androidx.security.crypto.MasterKeys.AES256_GCM_SPEC
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import net.taler.cashier.Amount.Companion.fromStringSigned
+import net.taler.cashier.HttpHelper.makeJsonGetRequest
+import net.taler.cashier.withdraw.WithdrawManager
+
+private val TAG = MainViewModel::class.java.simpleName
+
+private const val PREF_NAME = "net.taler.cashier.prefs"
+private const val PREF_KEY_BANK_URL = "bankUrl"
+private const val PREF_KEY_USERNAME = "username"
+private const val PREF_KEY_PASSWORD = "password"
+private const val PREF_KEY_CURRENCY = "currency"
+
+class MainViewModel(private val app: Application) : AndroidViewModel(app) {
+
+    val configDestination = 
ConfigFragmentDirections.actionGlobalConfigFragment()
+
+    private val masterKeyAlias = MasterKeys.getOrCreate(AES256_GCM_SPEC)
+    private val prefs = EncryptedSharedPreferences.create(
+        PREF_NAME, masterKeyAlias, app, AES256_SIV, AES256_GCM
+    )
+
+    internal var config = Config(
+        bankUrl = prefs.getString(PREF_KEY_BANK_URL, "")!!,
+        username = prefs.getString(PREF_KEY_USERNAME, "")!!,
+        password = prefs.getString(PREF_KEY_PASSWORD, "")!!
+    )
+
+    private val mCurrency = MutableLiveData<String>(
+        prefs.getString(PREF_KEY_CURRENCY, null)
+    )
+    internal val currency: LiveData<String> = mCurrency
+
+    private val mConfigResult = MutableLiveData<ConfigResult>()
+    val configResult: LiveData<ConfigResult> = mConfigResult
+
+    private val mBalance = MutableLiveData<BalanceResult>()
+    val balance: LiveData<BalanceResult> = mBalance
+
+    internal val withdrawManager = WithdrawManager(app, this)
+
+    fun hasConfig() = config.bankUrl.isNotEmpty()
+            && config.username.isNotEmpty()
+            && config.password.isNotEmpty()
+
+    /**
+     * Start observing [configResult] after calling this to get the result 
async.
+     * Warning: Ignore null results that are used to reset old results.
+     */
+    @UiThread
+    fun checkAndSaveConfig(config: Config) {
+        mConfigResult.value = null
+        viewModelScope.launch(Dispatchers.IO) {
+            val url = "${config.bankUrl}/accounts/${config.username}/balance"
+            Log.d(TAG, "Checking config: $url")
+            val result = when (val response = makeJsonGetRequest(url, config)) 
{
+                is HttpJsonResult.Success -> {
+                    val balance = response.json.getString("balance")
+                    val amount = fromStringSigned(balance)!!
+                    mCurrency.postValue(amount.currency)
+                    prefs.edit().putString(PREF_KEY_CURRENCY, 
amount.currency).apply()
+                    // save config
+                    saveConfig(config)
+                    ConfigResult(true)
+                }
+                is HttpJsonResult.Error -> {
+                    val authError = response.statusCode == 401
+                    ConfigResult(false, authError)
+                }
+            }
+            mConfigResult.postValue(result)
+        }
+    }
+
+    @WorkerThread
+    @SuppressLint("ApplySharedPref")
+    private fun saveConfig(config: Config) {
+        this.config = config
+        prefs.edit()
+            .putString(PREF_KEY_BANK_URL, config.bankUrl)
+            .putString(PREF_KEY_USERNAME, config.username)
+            .putString(PREF_KEY_PASSWORD, config.password)
+            .commit()
+    }
+
+    fun getBalance() = viewModelScope.launch(Dispatchers.IO) {
+        check(hasConfig()) { "No config to get balance" }
+        val url = "${config.bankUrl}/accounts/${config.username}/balance"
+        Log.d(TAG, "Checking balance at $url")
+        val result = when (val response = makeJsonGetRequest(url, config)) {
+            is HttpJsonResult.Success -> {
+                val balance = response.json.getString("balance")
+                fromStringSigned(balance)?.let { BalanceResult.Success(it) } 
?: BalanceResult.Error
+            }
+            is HttpJsonResult.Error -> {
+                if (app.isOnline()) BalanceResult.Error
+                else BalanceResult.Offline
+            }
+        }
+        mBalance.postValue(result)
+    }
+
+    fun lock() {
+        saveConfig(config.copy(password = ""))
+    }
+
+}
+
+data class Config(
+    val bankUrl: String,
+    val username: String,
+    val password: String
+)
+
+class ConfigResult(val success: Boolean, val authError: Boolean = false)
diff --git a/cashier/src/main/java/net/taler/cashier/Utils.kt 
b/cashier/src/main/java/net/taler/cashier/Utils.kt
new file mode 100644
index 0000000..62f7a77
--- /dev/null
+++ b/cashier/src/main/java/net/taler/cashier/Utils.kt
@@ -0,0 +1,91 @@
+/*
+ * 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.cashier
+
+import android.content.Context
+import android.content.Context.CONNECTIVITY_SERVICE
+import android.net.ConnectivityManager
+import android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET
+import android.os.Build.VERSION.SDK_INT
+import android.view.View
+import android.view.View.INVISIBLE
+import android.view.View.VISIBLE
+
+object Utils {
+
+    private const val HEX_CHARS = "0123456789ABCDEF"
+
+    fun hexStringToByteArray(data: String): ByteArray {
+        val result = ByteArray(data.length / 2)
+
+        for (i in data.indices step 2) {
+            val firstIndex = HEX_CHARS.indexOf(data[i])
+            val secondIndex = HEX_CHARS.indexOf(data[i + 1])
+
+            val octet = firstIndex.shl(4).or(secondIndex)
+            result[i.shr(1)] = octet.toByte()
+        }
+        return result
+    }
+
+
+    private val HEX_CHARS_ARRAY = HEX_CHARS.toCharArray()
+
+    @Suppress("unused")
+    fun toHex(byteArray: ByteArray): String {
+        val result = StringBuffer()
+
+        byteArray.forEach {
+            val octet = it.toInt()
+            val firstIndex = (octet and 0xF0).ushr(4)
+            val secondIndex = octet and 0x0F
+            result.append(HEX_CHARS_ARRAY[firstIndex])
+            result.append(HEX_CHARS_ARRAY[secondIndex])
+        }
+        return result.toString()
+    }
+
+}
+
+fun View.fadeIn(endAction: () -> Unit = {}) {
+    if (visibility == VISIBLE) return
+    alpha = 0f
+    visibility = VISIBLE
+    animate().alpha(1f).withEndAction {
+        endAction.invoke()
+    }.start()
+}
+
+fun View.fadeOut(endAction: () -> Unit = {}) {
+    if (visibility == INVISIBLE) return
+    animate().alpha(0f).withEndAction {
+        visibility = INVISIBLE
+        alpha = 1f
+        endAction.invoke()
+    }.start()
+}
+
+fun Context.isOnline(): Boolean {
+    val cm = getSystemService(CONNECTIVITY_SERVICE) as ConnectivityManager
+    return if (SDK_INT < 29) {
+        @Suppress("DEPRECATION")
+        cm.activeNetworkInfo?.isConnected == true
+    } else {
+        val capabilities = cm.getNetworkCapabilities(cm.activeNetwork) ?: 
return false
+        capabilities.hasCapability(NET_CAPABILITY_INTERNET)
+    }
+}
diff --git a/cashier/src/main/java/net/taler/cashier/withdraw/ErrorFragment.kt 
b/cashier/src/main/java/net/taler/cashier/withdraw/ErrorFragment.kt
new file mode 100644
index 0000000..ceffcec
--- /dev/null
+++ b/cashier/src/main/java/net/taler/cashier/withdraw/ErrorFragment.kt
@@ -0,0 +1,55 @@
+/*
+ * 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.cashier.withdraw
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+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_error.*
+import net.taler.cashier.MainViewModel
+import net.taler.cashier.R
+
+class ErrorFragment : Fragment() {
+
+    private val viewModel: MainViewModel by activityViewModels()
+    private val withdrawManager by lazy { viewModel.withdrawManager }
+
+    override fun onCreateView(
+        inflater: LayoutInflater, container: ViewGroup?,
+        savedInstanceState: Bundle?
+    ): View? {
+        return inflater.inflate(R.layout.fragment_error, container, false)
+    }
+
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+        withdrawManager.withdrawStatus.observe(viewLifecycleOwner, Observer { 
status ->
+            if (status is WithdrawStatus.Aborted) {
+                textView.setText(R.string.transaction_aborted)
+            }
+        })
+        withdrawManager.completeTransaction()
+        backButton.setOnClickListener {
+            findNavController().popBackStack()
+        }
+    }
+
+}
diff --git a/cashier/src/main/java/net/taler/cashier/withdraw/NfcManager.kt 
b/cashier/src/main/java/net/taler/cashier/withdraw/NfcManager.kt
new file mode 100644
index 0000000..a487b5f
--- /dev/null
+++ b/cashier/src/main/java/net/taler/cashier/withdraw/NfcManager.kt
@@ -0,0 +1,234 @@
+/*
+ * 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.cashier.withdraw
+
+import android.app.Activity
+import android.content.Context
+import android.nfc.NfcAdapter
+import android.nfc.NfcAdapter.FLAG_READER_NFC_A
+import android.nfc.NfcAdapter.FLAG_READER_SKIP_NDEF_CHECK
+import android.nfc.NfcAdapter.getDefaultAdapter
+import android.nfc.Tag
+import android.nfc.tech.IsoDep
+import android.util.Log
+import net.taler.cashier.Utils.hexStringToByteArray
+import org.json.JSONObject
+import java.io.ByteArrayOutputStream
+import java.net.URL
+import javax.net.ssl.HttpsURLConnection
+
+@Suppress("unused")
+private const val TALER_AID = "A0000002471001"
+
+class NfcManager : NfcAdapter.ReaderCallback {
+
+    companion object {
+        const val TAG = "taler-merchant"
+
+        /**
+         * Returns true if NFC is supported and false otherwise.
+         */
+        fun hasNfc(context: Context): Boolean {
+            return getNfcAdapter(context) != null
+        }
+
+        /**
+         * Enables NFC reader mode. Don't forget to call [stop] afterwards.
+         */
+        fun start(activity: Activity, nfcManager: NfcManager) {
+            getNfcAdapter(activity)?.enableReaderMode(activity, nfcManager, 
nfcManager.flags, null)
+        }
+
+        /**
+         * Disables NFC reader mode. Call after [start].
+         */
+        fun stop(activity: Activity) {
+            getNfcAdapter(activity)?.disableReaderMode(activity)
+        }
+
+        private fun getNfcAdapter(context: Context): NfcAdapter? {
+            return getDefaultAdapter(context)
+        }
+    }
+
+    private val flags = FLAG_READER_NFC_A or FLAG_READER_SKIP_NDEF_CHECK
+
+    private var tagString: String? = null
+    private var currentTag: IsoDep? = null
+
+    fun setTagString(tagString: String) {
+        this.tagString = tagString
+    }
+
+    override fun onTagDiscovered(tag: Tag?) {
+
+        Log.v(TAG, "tag discovered")
+
+        val isoDep = IsoDep.get(tag)
+        isoDep.connect()
+
+        currentTag = isoDep
+
+        isoDep.transceive(apduSelectFile())
+
+        val tagString: String? = tagString
+        if (tagString != null) {
+            isoDep.transceive(apduPutTalerData(1, tagString.toByteArray()))
+        }
+
+        // FIXME: use better pattern for sleeps in between requests
+        // -> start with fast polling, poll more slowly if no requests are 
coming
+
+        while (true) {
+            try {
+                val reqFrame = isoDep.transceive(apduGetData())
+                if (reqFrame.size < 2) {
+                    Log.v(TAG, "request frame too small")
+                    break
+                }
+                val req = ByteArray(reqFrame.size - 2)
+                if (req.isEmpty()) {
+                    continue
+                }
+                reqFrame.copyInto(req, 0, 0, reqFrame.size - 2)
+                val jsonReq = JSONObject(req.toString(Charsets.UTF_8))
+                val reqId = jsonReq.getInt("id")
+                Log.v(TAG, "got request $jsonReq")
+                val jsonInnerReq = jsonReq.getJSONObject("request")
+                val method = jsonInnerReq.getString("method")
+                val urlStr = jsonInnerReq.getString("url")
+                Log.v(TAG, "url '$urlStr'")
+                Log.v(TAG, "method '$method'")
+                val url = URL(urlStr)
+                val conn: HttpsURLConnection = url.openConnection() as 
HttpsURLConnection
+                conn.setRequestProperty("Accept", "application/json")
+                conn.connectTimeout = 5000
+                conn.doInput = true
+                when (method) {
+                    "get" -> {
+                        conn.requestMethod = "GET"
+                    }
+                    "postJson" -> {
+                        conn.requestMethod = "POST"
+                        conn.doOutput = true
+                        conn.setRequestProperty("Content-Type", 
"application/json; utf-8")
+                        val body = jsonInnerReq.getString("body")
+                        
conn.outputStream.write(body.toByteArray(Charsets.UTF_8))
+                    }
+                    else -> {
+                        throw Exception("method not supported")
+                    }
+                }
+                Log.v(TAG, "connecting")
+                conn.connect()
+                Log.v(TAG, "connected")
+
+                val statusCode = conn.responseCode
+                val tunnelResp = JSONObject()
+                tunnelResp.put("id", reqId)
+                tunnelResp.put("status", conn.responseCode)
+
+                if (statusCode == 200) {
+                    val stream = conn.inputStream
+                    val httpResp = stream.buffered().readBytes()
+                    tunnelResp.put("responseJson", 
JSONObject(httpResp.toString(Charsets.UTF_8)))
+                }
+
+                Log.v(TAG, "sending: $tunnelResp")
+
+                isoDep.transceive(apduPutTalerData(2, 
tunnelResp.toString().toByteArray()))
+            } catch (e: Exception) {
+                Log.v(TAG, "exception during NFC loop: $e")
+                break
+            }
+        }
+
+        isoDep.close()
+    }
+
+    private fun writeApduLength(stream: ByteArrayOutputStream, size: Int) {
+        when {
+            size == 0 -> {
+                // No size field needed!
+            }
+            size <= 255 -> // One byte size field
+                stream.write(size)
+            size <= 65535 -> {
+                stream.write(0)
+                // FIXME: is this supposed to be little or big endian?
+                stream.write(size and 0xFF)
+                stream.write((size ushr 8) and 0xFF)
+            }
+            else -> throw Error("payload too big")
+        }
+    }
+
+    private fun apduSelectFile(): ByteArray {
+        return hexStringToByteArray("00A4040007A0000002471001")
+    }
+
+    private fun apduPutData(payload: ByteArray): ByteArray {
+        val stream = ByteArrayOutputStream()
+
+        // Class
+        stream.write(0x00)
+
+        // Instruction 0xDA = put data
+        stream.write(0xDA)
+
+        // Instruction parameters
+        // (proprietary encoding)
+        stream.write(0x01)
+        stream.write(0x00)
+
+        writeApduLength(stream, payload.size)
+
+        stream.write(payload)
+
+        return stream.toByteArray()
+    }
+
+    private fun apduPutTalerData(talerInst: Int, payload: ByteArray): 
ByteArray {
+        val realPayload = ByteArrayOutputStream()
+        realPayload.write(talerInst)
+        realPayload.write(payload)
+        return apduPutData(realPayload.toByteArray())
+    }
+
+    private fun apduGetData(): ByteArray {
+        val stream = ByteArrayOutputStream()
+
+        // Class
+        stream.write(0x00)
+
+        // Instruction 0xCA = get data
+        stream.write(0xCA)
+
+        // Instruction parameters
+        // (proprietary encoding)
+        stream.write(0x01)
+        stream.write(0x00)
+
+        // Max expected response size, two
+        // zero bytes denotes 65536
+        stream.write(0x0)
+        stream.write(0x0)
+
+        return stream.toByteArray()
+    }
+
+}
diff --git a/cashier/src/main/java/net/taler/cashier/withdraw/QrCodeManager.kt 
b/cashier/src/main/java/net/taler/cashier/withdraw/QrCodeManager.kt
new file mode 100644
index 0000000..e3ffa92
--- /dev/null
+++ b/cashier/src/main/java/net/taler/cashier/withdraw/QrCodeManager.kt
@@ -0,0 +1,42 @@
+/*
+ * 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.cashier.withdraw
+
+import android.graphics.Bitmap
+import android.graphics.Bitmap.Config.RGB_565
+import android.graphics.Color.BLACK
+import android.graphics.Color.WHITE
+import com.google.zxing.BarcodeFormat.QR_CODE
+import com.google.zxing.qrcode.QRCodeWriter
+
+object QrCodeManager {
+
+    fun makeQrCode(text: String, size: Int = 256): Bitmap {
+        val qrCodeWriter = QRCodeWriter()
+        val bitMatrix = qrCodeWriter.encode(text, QR_CODE, size, size)
+        val height = bitMatrix.height
+        val width = bitMatrix.width
+        val bmp = Bitmap.createBitmap(width, height, RGB_565)
+        for (x in 0 until width) {
+            for (y in 0 until height) {
+                bmp.setPixel(x, y, if (bitMatrix.get(x, y)) BLACK else WHITE)
+            }
+        }
+        return bmp
+    }
+
+}
diff --git 
a/cashier/src/main/java/net/taler/cashier/withdraw/TransactionFragment.kt 
b/cashier/src/main/java/net/taler/cashier/withdraw/TransactionFragment.kt
new file mode 100644
index 0000000..8b782b0
--- /dev/null
+++ b/cashier/src/main/java/net/taler/cashier/withdraw/TransactionFragment.kt
@@ -0,0 +1,174 @@
+/*
+ * 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.cashier.withdraw
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.View.INVISIBLE
+import android.view.View.VISIBLE
+import android.view.ViewGroup
+import androidx.core.content.ContextCompat.getColor
+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_transaction.*
+import net.taler.cashier.MainViewModel
+import net.taler.cashier.R
+import net.taler.cashier.fadeIn
+import net.taler.cashier.fadeOut
+import 
net.taler.cashier.withdraw.TransactionFragmentDirections.Companion.actionTransactionFragmentToBalanceFragment
+import 
net.taler.cashier.withdraw.TransactionFragmentDirections.Companion.actionTransactionFragmentToErrorFragment
+import net.taler.cashier.withdraw.WithdrawResult.Error
+import net.taler.cashier.withdraw.WithdrawResult.InsufficientBalance
+import net.taler.cashier.withdraw.WithdrawResult.Success
+
+class TransactionFragment : Fragment() {
+
+    private val viewModel: MainViewModel by activityViewModels()
+    private val withdrawManager by lazy { viewModel.withdrawManager }
+    private val nfcManager = NfcManager()
+
+    override fun onCreateView(
+        inflater: LayoutInflater, container: ViewGroup?,
+        savedInstanceState: Bundle?
+    ): View? {
+        return inflater.inflate(R.layout.fragment_transaction, container, 
false)
+    }
+
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+        withdrawManager.withdrawAmount.observe(viewLifecycleOwner, Observer { 
amount ->
+            amountView.text = amount
+        })
+        withdrawManager.withdrawResult.observe(viewLifecycleOwner, Observer { 
result ->
+            onWithdrawResultReceived(result)
+        })
+        withdrawManager.withdrawStatus.observe(viewLifecycleOwner, Observer { 
status ->
+            onWithdrawStatusChanged(status)
+        })
+
+        // change intro text depending on whether NFC is available or not
+        val hasNfc = NfcManager.hasNfc(requireContext())
+        val intro = if (hasNfc) R.string.transaction_intro_nfc else 
R.string.transaction_intro
+        introView.setText(intro)
+
+        cancelButton.setOnClickListener {
+            findNavController().popBackStack()
+        }
+    }
+
+    override fun onStart() {
+        super.onStart()
+        if (withdrawManager.withdrawResult.value is Success) {
+            NfcManager.start(requireActivity(), nfcManager)
+        }
+    }
+
+    override fun onStop() {
+        super.onStop()
+        NfcManager.stop(requireActivity())
+    }
+
+    override fun onDestroy() {
+        super.onDestroy()
+        if (!requireActivity().isChangingConfigurations) {
+            withdrawManager.abort()
+        }
+    }
+
+    private fun onWithdrawResultReceived(result: WithdrawResult?) {
+        if (result != null) {
+            progressBar.animate()
+                .alpha(0f)
+                .withEndAction { progressBar?.visibility = INVISIBLE }
+                .setDuration(750)
+                .start()
+        }
+        when (result) {
+            is InsufficientBalance -> {
+                val c = getColor(requireContext(), 
R.color.design_default_color_error)
+                introView.setTextColor(c)
+                introView.text = 
getString(R.string.withdraw_error_insufficient_balance)
+            }
+            is Error -> {
+                val c = getColor(requireContext(), 
R.color.design_default_color_error)
+                introView.setTextColor(c)
+                introView.text = result.msg
+            }
+            is Success -> {
+                // start NFC
+                nfcManager.setTagString(result.talerUri)
+                NfcManager.start(
+                    requireActivity(),
+                    nfcManager
+                )
+                // show QR code
+                qrCodeView.alpha = 0f
+                qrCodeView.animate()
+                    .alpha(1f)
+                    .withStartAction {
+                        qrCodeView.visibility = VISIBLE
+                        qrCodeView.setImageBitmap(result.qrCode)
+                    }
+                    .setDuration(750)
+                    .start()
+            }
+        }
+    }
+
+    private fun onWithdrawStatusChanged(status: WithdrawStatus?): Any = when 
(status) {
+        is WithdrawStatus.SelectionDone -> {
+            qrCodeView.fadeOut {
+                qrCodeView?.setImageResource(R.drawable.ic_arrow)
+                qrCodeView?.fadeIn()
+            }
+            introView.fadeOut {
+                introView?.text = getString(R.string.transaction_intro_scanned)
+                introView?.fadeIn {
+                    confirmButton?.isEnabled = true
+                    confirmButton?.setOnClickListener {
+                        withdrawManager.confirm(status.withdrawalId)
+                    }
+                }
+            }
+        }
+        is WithdrawStatus.Confirming -> {
+            confirmButton.isEnabled = false
+            qrCodeView.fadeOut()
+            progressBar.fadeIn()
+        }
+        is WithdrawStatus.Success -> {
+            withdrawManager.completeTransaction()
+            actionTransactionFragmentToBalanceFragment().let {
+                findNavController().navigate(it)
+            }
+        }
+        is WithdrawStatus.Aborted -> onError()
+        is WithdrawStatus.Error -> onError()
+        null -> {
+            // no-op
+        }
+    }
+
+    private fun onError() {
+        actionTransactionFragmentToErrorFragment().let {
+            findNavController().navigate(it)
+        }
+    }
+
+}
diff --git 
a/cashier/src/main/java/net/taler/cashier/withdraw/WithdrawManager.kt 
b/cashier/src/main/java/net/taler/cashier/withdraw/WithdrawManager.kt
new file mode 100644
index 0000000..4c618ac
--- /dev/null
+++ b/cashier/src/main/java/net/taler/cashier/withdraw/WithdrawManager.kt
@@ -0,0 +1,232 @@
+/*
+ * 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.cashier.withdraw
+
+import android.app.Application
+import android.graphics.Bitmap
+import android.os.CountDownTimer
+import android.util.Log
+import androidx.annotation.UiThread
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.viewModelScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.launch
+import net.taler.cashier.BalanceResult
+import net.taler.cashier.HttpHelper.makeJsonGetRequest
+import net.taler.cashier.HttpHelper.makeJsonPostRequest
+import net.taler.cashier.HttpJsonResult.Error
+import net.taler.cashier.HttpJsonResult.Success
+import net.taler.cashier.MainViewModel
+import net.taler.cashier.R
+import org.json.JSONObject
+import java.util.concurrent.TimeUnit.MINUTES
+import java.util.concurrent.TimeUnit.SECONDS
+
+private val TAG = WithdrawManager::class.java.simpleName
+
+private val INTERVAL = SECONDS.toMillis(1)
+private val TIMEOUT = MINUTES.toMillis(2)
+
+class WithdrawManager(
+    private val app: Application,
+    private val viewModel: MainViewModel
+) {
+    private val scope
+        get() = viewModel.viewModelScope
+
+    private val config
+        get() = viewModel.config
+
+    private val currency: String?
+        get() = viewModel.currency.value
+
+    private var withdrawStatusCheck: Job? = null
+
+    private val mWithdrawAmount = MutableLiveData<String>()
+    val withdrawAmount: LiveData<String> = mWithdrawAmount
+
+    private val mWithdrawResult = MutableLiveData<WithdrawResult>()
+    val withdrawResult: LiveData<WithdrawResult> = mWithdrawResult
+
+    private val mWithdrawStatus = MutableLiveData<WithdrawStatus>()
+    val withdrawStatus: LiveData<WithdrawStatus> = mWithdrawStatus
+
+    private val mLastTransaction = MutableLiveData<LastTransaction>()
+    val lastTransaction: LiveData<LastTransaction> = mLastTransaction
+
+    @UiThread
+    fun hasSufficientBalance(amount: Int): Boolean {
+        val balanceResult = viewModel.balance.value
+        if (balanceResult !is BalanceResult.Success) return false
+        val balanceStr = balanceResult.amount.amount
+        val balanceDouble = balanceStr.toDouble()
+        return amount <= balanceDouble
+    }
+
+    @UiThread
+    fun withdraw(amount: Int) {
+        check(amount > 0) { "Withdraw amount was <= 0" }
+        check(currency != null) { "Currency is null" }
+        mWithdrawResult.value = null
+        mWithdrawAmount.value = "$amount $currency"
+        scope.launch(Dispatchers.IO) {
+            val url = 
"${config.bankUrl}/accounts/${config.username}/withdrawals"
+            Log.d(TAG, "Starting withdrawal at $url")
+            val body = JSONObject(mapOf("amount" to 
"${currency}:${amount}")).toString()
+            when (val result = makeJsonPostRequest(url, body, config)) {
+                is Success -> {
+                    val talerUri = result.json.getString("taler_withdraw_uri")
+                    val withdrawResult = WithdrawResult.Success(
+                        id = result.json.getString("withdrawal_id"),
+                        talerUri = talerUri,
+                        qrCode = QrCodeManager.makeQrCode(talerUri)
+                    )
+                    mWithdrawResult.postValue(withdrawResult)
+                    timer.start()
+                }
+                is Error -> {
+                    val errorStr = app.getString(R.string.withdraw_error_fetch)
+                    mWithdrawResult.postValue(WithdrawResult.Error(errorStr))
+                }
+            }
+        }
+    }
+
+    private val timer: CountDownTimer = object : CountDownTimer(TIMEOUT, 
INTERVAL) {
+        override fun onTick(millisUntilFinished: Long) {
+            val result = withdrawResult.value
+            if (result is WithdrawResult.Success) {
+                // check for active jobs and only do one at a time
+                val hasActiveCheck = withdrawStatusCheck?.isActive ?: false
+                if (!hasActiveCheck) {
+                    withdrawStatusCheck = checkWithdrawStatus(result.id)
+                }
+            } else {
+                cancel()
+            }
+        }
+
+        override fun onFinish() {
+            abort()
+            mWithdrawStatus.postValue(WithdrawStatus.Error)
+            cancel()
+        }
+    }
+
+    private fun checkWithdrawStatus(withdrawalId: String) = 
scope.launch(Dispatchers.IO) {
+        val url = 
"${config.bankUrl}/accounts/${config.username}/withdrawals/${withdrawalId}"
+        Log.d(TAG, "Checking withdraw status at $url")
+        val response = makeJsonGetRequest(url, config)
+        if (response !is Success) return@launch  // ignore errors and continue 
trying
+        val oldStatus = mWithdrawStatus.value
+        when {
+            response.json.getBoolean("aborted") -> {
+                cancelWithdrawStatusCheck()
+                mWithdrawStatus.postValue(WithdrawStatus.Aborted)
+            }
+            response.json.getBoolean("confirmation_done") -> {
+                if (oldStatus !is WithdrawStatus.Success) {
+                    cancelWithdrawStatusCheck()
+                    mWithdrawStatus.postValue(WithdrawStatus.Success)
+                    viewModel.getBalance()
+                }
+            }
+            response.json.getBoolean("selection_done") -> {
+                // only update status, if there's none, yet
+                // so we don't re-notify or overwrite newer status info
+                if (oldStatus == null) {
+                    
mWithdrawStatus.postValue(WithdrawStatus.SelectionDone(withdrawalId))
+                }
+            }
+        }
+    }
+
+    private fun cancelWithdrawStatusCheck() {
+        timer.cancel()
+        withdrawStatusCheck?.cancel()
+    }
+
+    /**
+     * Aborts the last [withdrawResult], if it exists und there is no 
[withdrawStatus].
+     * Otherwise this is a no-op.
+     */
+    @UiThread
+    fun abort() {
+        val result = withdrawResult.value
+        val status = withdrawStatus.value
+        if (result is WithdrawResult.Success && status == null) {
+            cancelWithdrawStatusCheck()
+            abort(result.id)
+        }
+    }
+
+    private fun abort(withdrawalId: String) = scope.launch(Dispatchers.IO) {
+        val url = 
"${config.bankUrl}/accounts/${config.username}/withdrawals/${withdrawalId}/abort"
+        Log.d(TAG, "Aborting withdrawal at $url")
+        makeJsonPostRequest(url, "", config)
+    }
+
+    @UiThread
+    fun confirm(withdrawalId: String) {
+        mWithdrawStatus.value = WithdrawStatus.Confirming
+        scope.launch(Dispatchers.IO) {
+            val url =
+                
"${config.bankUrl}/accounts/${config.username}/withdrawals/${withdrawalId}/confirm"
+            Log.d(TAG, "Confirming withdrawal at $url")
+            when (val result = makeJsonPostRequest(url, "", config)) {
+                is Success -> {
+                    // no-op still waiting for [timer] to confirm our 
confirmation
+                }
+                is Error -> {
+                    Log.e(TAG, "Error confirming withdrawal. Status code: 
${result.statusCode}")
+                    mWithdrawStatus.postValue(WithdrawStatus.Error)
+                }
+            }
+        }
+    }
+
+    @UiThread
+    fun completeTransaction() {
+        mLastTransaction.value = LastTransaction(withdrawAmount.value!!, 
withdrawStatus.value!!)
+        withdrawStatusCheck = null
+        mWithdrawAmount.value = null
+        mWithdrawResult.value = null
+        mWithdrawStatus.value = null
+    }
+
+}
+
+sealed class WithdrawResult {
+    object InsufficientBalance : WithdrawResult()
+    class Error(val msg: String) : WithdrawResult()
+    class Success(val id: String, val talerUri: String, val qrCode: Bitmap) : 
WithdrawResult()
+}
+
+sealed class WithdrawStatus {
+    object Error : WithdrawStatus()
+    object Aborted : WithdrawStatus()
+    class SelectionDone(val withdrawalId: String) : WithdrawStatus()
+    object Confirming : WithdrawStatus()
+    object Success : WithdrawStatus()
+}
+
+data class LastTransaction(
+    val withdrawAmount: String,
+    val withdrawStatus: WithdrawStatus
+)
diff --git a/cashier/src/main/res/drawable-w550dp/ic_arrow.xml 
b/cashier/src/main/res/drawable-w550dp/ic_arrow.xml
new file mode 100644
index 0000000..331ea06
--- /dev/null
+++ b/cashier/src/main/res/drawable-w550dp/ic_arrow.xml
@@ -0,0 +1,11 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android";
+    android:width="24dp"
+    android:height="24dp"
+    android:alpha="0.56"
+    android:tint="@color/green"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <path
+        android:fillColor="#000000"
+        android:pathData="M20,5.41 L18.59,4 7,15.59V9H5V19H15V17H8.41" />
+</vector>
diff --git a/cashier/src/main/res/drawable/ic_arrow.xml 
b/cashier/src/main/res/drawable/ic_arrow.xml
new file mode 100644
index 0000000..d7578bd
--- /dev/null
+++ b/cashier/src/main/res/drawable/ic_arrow.xml
@@ -0,0 +1,11 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android";
+    android:width="24dp"
+    android:height="24dp"
+    android:alpha="0.56"
+    android:tint="@color/green"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <path
+        android:fillColor="#000000"
+        android:pathData="M5,5.41 L6.41,4 18,15.59V9h2V19H10v-2h6.59" />
+</vector>
diff --git a/cashier/src/main/res/drawable/ic_check_circle.xml 
b/cashier/src/main/res/drawable/ic_check_circle.xml
new file mode 100644
index 0000000..d43d6ba
--- /dev/null
+++ b/cashier/src/main/res/drawable/ic_check_circle.xml
@@ -0,0 +1,10 @@
+<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="#FF000000"
+        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/cashier/src/main/res/drawable/ic_clear.xml 
b/cashier/src/main/res/drawable/ic_clear.xml
new file mode 100644
index 0000000..f50fd99
--- /dev/null
+++ b/cashier/src/main/res/drawable/ic_clear.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android";
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24.0"
+    android:viewportHeight="24.0">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 
5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z" />
+</vector>
diff --git a/cashier/src/main/res/drawable/ic_error.xml 
b/cashier/src/main/res/drawable/ic_error.xml
new file mode 100644
index 0000000..b7e22a0
--- /dev/null
+++ b/cashier/src/main/res/drawable/ic_error.xml
@@ -0,0 +1,11 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android";
+    android:width="24dp"
+    android:height="24dp"
+    android:alpha="0.56"
+    android:tint="@color/red"
+    android:viewportWidth="24.0"
+    android:viewportHeight="24.0">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 
10,-10S17.52,2 12,2zM13,17h-2v-2h2v2zM13,13h-2L11,7h2v6z" />
+</vector>
diff --git a/cashier/src/main/res/drawable/ic_launcher_foreground.xml 
b/cashier/src/main/res/drawable/ic_launcher_foreground.xml
new file mode 100644
index 0000000..fbaac05
--- /dev/null
+++ b/cashier/src/main/res/drawable/ic_launcher_foreground.xml
@@ -0,0 +1,15 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android";
+    xmlns:tools="http://schemas.android.com/tools";
+    android:width="108dp"
+    android:height="108dp"
+    android:viewportWidth="48"
+    android:viewportHeight="48">
+  <group
+      android:translateX="12"
+      android:translateY="12">
+    <path
+        android:pathData="M6,3L6,6L9,6L9,7L6.25,7C5.05,7 4.0508,8 
4.0508,9L3.5,16L20.5,16L20,9C19.8,8 18.8008,7 
17.8008,7L11,7L11,6L14,6L14,3L6,3zM7,4L13,4L13,5L7,5L7,4zM6,9L8,9L8,10L6,10L6,9zM9,9L11,9L11,10L9,10L9,9zM13,9L18,9L18,11L13,11L13,9zM6,11L8,11L8,12L6,12L6,11zM9,11L11,11L11,12L9,12L9,11zM6,13L8,13L8,14L6,14L6,13zM9,13L11,13L11,14L9,14L9,13zM2,17L2,21L22,21L22,17L2,17zM4.7422,17.291L7.2695,17.291L7.2695,17.7793L6.3574,17.7793L6.3574,20.6777L5.6543,20.6777L5.6543,17.7793L4.7422,
 [...]
+        android:fillColor="#f9f9f9"
+        tools:ignore="VectorPath" />
+  </group>
+</vector>
diff --git a/cashier/src/main/res/drawable/ic_withdraw.xml 
b/cashier/src/main/res/drawable/ic_withdraw.xml
new file mode 100644
index 0000000..b694a2b
--- /dev/null
+++ b/cashier/src/main/res/drawable/ic_withdraw.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector xmlns:android="http://schemas.android.com/apk/res/android";
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <path
+        android:fillColor="#000000"
+        android:pathData="M3 0V3H0V5H3V8H5V5H8V3H5V0H3M9 3V6H6V9H3V19C3 20.1 
3.89 21 5 21H19C20.11 21 21 20.11 21 19V18H12C10.9 18 10 17.11 10 16V8C10 6.9 
10.89 6 12 6H21V5C21 3.9 20.11 3 19 3H9M12 8V16H22V8H12M16 10.5C16.83 10.5 17.5 
11.17 17.5 12C17.5 12.83 16.83 13.5 16 13.5C15.17 13.5 14.5 12.83 14.5 12C14.5 
11.17 15.17 10.5 16 10.5Z" />
+</vector>
diff --git a/cashier/src/main/res/layout-w550dp/fragment_balance.xml 
b/cashier/src/main/res/layout-w550dp/fragment_balance.xml
new file mode 100644
index 0000000..d04698b
--- /dev/null
+++ b/cashier/src/main/res/layout-w550dp/fragment_balance.xml
@@ -0,0 +1,222 @@
+<?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=".BalanceFragment">
+
+    <TextView
+        android:id="@+id/lastTransactionView"
+        style="@style/Widget.MaterialComponents.Snackbar.FullWidth"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:background="?attr/colorPrimaryDark"
+        android:drawableStart="@drawable/ic_check_circle"
+        android:drawablePadding="8dp"
+        android:drawableTint="?attr/colorOnPrimarySurface"
+        android:gravity="center_vertical"
+        android:padding="8dp"
+        android:textColor="?attr/colorOnPrimarySurface"
+        android:visibility="gone"
+        app:layout_constraintEnd_toStartOf="@+id/guideline"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        tools:text="@string/transaction_last_success"
+        tools:visibility="visible" />
+
+    <View
+        android:id="@+id/balanceBackground"
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        android:background="@color/background"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="@+id/guideline"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/lastTransactionView" />
+
+    <TextView
+        android:id="@+id/balanceLabel"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="32dp"
+        android:layout_marginTop="32dp"
+        android:layout_marginEnd="32dp"
+        android:text="@string/balance_current_label"
+        android:textAppearance="@style/TextAppearance.AppCompat.Medium"
+        app:layout_constraintBottom_toTopOf="@+id/balanceView"
+        app:layout_constraintEnd_toStartOf="@+id/guideline"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/lastTransactionView"
+        app:layout_constraintVertical_bias="0.0"
+        app:layout_constraintVertical_chainStyle="packed" />
+
+    <TextView
+        android:id="@+id/balanceView"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_margin="@dimen/default_margin"
+        android:gravity="center"
+        android:textAppearance="@style/TextAppearance.AppCompat.Headline"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toStartOf="@+id/guideline"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/balanceLabel"
+        tools:text="100 KUDOS" />
+
+    <ProgressBar
+        android:id="@+id/progressBar"
+        style="?android:attr/progressBarStyle"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        app:layout_constraintBottom_toBottomOf="@+id/balanceView"
+        app:layout_constraintEnd_toEndOf="@+id/balanceView"
+        app:layout_constraintStart_toStartOf="@+id/balanceView"
+        app:layout_constraintTop_toTopOf="@+id/balanceView" />
+
+    <androidx.constraintlayout.widget.Guideline
+        android:id="@+id/guideline"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:orientation="vertical"
+        app:layout_constraintGuide_percent="0.5" />
+
+    <TextView
+        android:id="@+id/introView"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_margin="32dp"
+        android:text="@string/withdraw_into"
+        android:textAlignment="center"
+        android:textAppearance="@style/TextAppearance.AppCompat.Medium"
+        android:visibility="invisible"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="@+id/guideline"
+        app:layout_constraintTop_toTopOf="parent"
+        tools:visibility="visible" />
+
+    <Button
+        android:id="@+id/button5"
+        style="@style/AmountButton"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="5"
+        android:visibility="invisible"
+        app:layout_constraintEnd_toStartOf="@+id/button10"
+        app:layout_constraintHorizontal_chainStyle="packed"
+        app:layout_constraintStart_toStartOf="@+id/guideline"
+        app:layout_constraintTop_toBottomOf="@+id/introView"
+        tools:ignore="HardcodedText"
+        tools:visibility="visible" />
+
+    <Button
+        android:id="@+id/button10"
+        style="@style/AmountButton"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="10"
+        android:visibility="invisible"
+        app:layout_constraintEnd_toStartOf="@+id/button20"
+        app:layout_constraintStart_toEndOf="@+id/button5"
+        app:layout_constraintTop_toBottomOf="@+id/introView"
+        tools:ignore="HardcodedText"
+        tools:visibility="visible" />
+
+    <Button
+        android:id="@+id/button20"
+        style="@style/AmountButton"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="20"
+        android:visibility="invisible"
+        app:layout_constraintEnd_toStartOf="@+id/button50"
+        app:layout_constraintStart_toEndOf="@+id/button10"
+        app:layout_constraintTop_toBottomOf="@+id/introView"
+        tools:ignore="HardcodedText"
+        tools:visibility="visible" />
+
+    <Button
+        android:id="@+id/button50"
+        style="@style/AmountButton"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="50"
+        android:visibility="invisible"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toEndOf="@+id/button20"
+        app:layout_constraintTop_toBottomOf="@+id/introView"
+        tools:ignore="HardcodedText"
+        tools:visibility="visible" />
+
+    <com.google.android.material.textfield.TextInputLayout
+        android:id="@+id/amountView"
+        
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.Dense"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_margin="32dp"
+        android:hint="@string/withdraw_input_amount"
+        android:visibility="invisible"
+        app:endIconDrawable="@drawable/ic_clear"
+        app:endIconMode="clear_text"
+        app:endIconTint="?attr/colorControlNormal"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="@+id/guideline"
+        app:layout_constraintTop_toBottomOf="@+id/button5"
+        tools:visibility="visible">
+
+        <com.google.android.material.textfield.TextInputEditText
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:ems="6"
+            android:imeOptions="actionGo"
+            android:inputType="number"
+            android:maxLength="4" />
+
+    </com.google.android.material.textfield.TextInputLayout>
+
+    <TextView
+        android:id="@+id/currencyView"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_margin="16dp"
+        android:visibility="invisible"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toEndOf="@+id/amountView"
+        app:layout_constraintTop_toTopOf="@+id/amountView"
+        tools:text="TESTKUDOS"
+        tools:visibility="visible" />
+
+    <com.google.android.material.button.MaterialButton
+        android:id="@+id/confirmWithdrawalButton"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_margin="@dimen/default_margin"
+        android:backgroundTint="@color/green"
+        android:drawableLeft="@drawable/ic_withdraw"
+        android:drawableTint="?attr/colorOnPrimarySurface"
+        android:text="@string/withdraw_button_confirm"
+        android:visibility="invisible"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="@+id/guideline"
+        app:layout_constraintTop_toBottomOf="@+id/amountView"
+        app:layout_constraintVertical_bias="1.0"
+        tools:ignore="RtlHardcoded"
+        tools:visibility="visible" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/cashier/src/main/res/layout-w550dp/fragment_transaction.xml 
b/cashier/src/main/res/layout-w550dp/fragment_transaction.xml
new file mode 100644
index 0000000..610ed28
--- /dev/null
+++ b/cashier/src/main/res/layout-w550dp/fragment_transaction.xml
@@ -0,0 +1,111 @@
+<?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.TransactionFragment">
+
+    <TextView
+        android:id="@+id/introView"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_margin="32dp"
+        android:gravity="center_horizontal"
+        android:text="@string/transaction_intro"
+        android:textAppearance="@style/TextAppearance.AppCompat.Medium"
+        app:layout_constraintEnd_toStartOf="@+id/guideline"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+
+    <TextView
+        android:id="@+id/amountView"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_margin="32dp"
+        android:gravity="center_horizontal"
+        android:textAppearance="@style/TextAppearance.AppCompat.Headline"
+        app:layout_constraintEnd_toStartOf="@+id/guideline"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/introView"
+        tools:text="50 KUDOS" />
+
+    <androidx.constraintlayout.widget.Guideline
+        android:id="@+id/guideline"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:orientation="vertical"
+        app:layout_constraintGuide_percent="0.5" />
+
+    <ImageView
+        android:id="@+id/qrCodeView"
+        android:layout_width="256dp"
+        android:layout_height="256dp"
+        android:layout_margin="32dp"
+        android:keepScreenOn="true"
+        android:visibility="invisible"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="@+id/guideline"
+        app:layout_constraintTop_toTopOf="parent"
+        tools:ignore="ContentDescription"
+        tools:src="@drawable/ic_arrow"
+        tools:visibility="visible" />
+
+    <ProgressBar
+        android:id="@+id/progressBar"
+        style="?android:attr/progressBarStyleLarge"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        app:layout_constraintBottom_toBottomOf="@+id/qrCodeView"
+        app:layout_constraintEnd_toEndOf="@+id/qrCodeView"
+        app:layout_constraintStart_toStartOf="@+id/qrCodeView"
+        app:layout_constraintTop_toTopOf="@+id/qrCodeView" />
+
+    <Button
+        android:id="@+id/cancelButton"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_margin="16dp"
+        android:backgroundTint="@color/red"
+        android:text="@string/transaction_abort"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toStartOf="@+id/confirmButton"
+        app:layout_constraintHorizontal_bias="0.5"
+        app:layout_constraintHorizontal_chainStyle="spread_inside"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/qrCodeView"
+        app:layout_constraintVertical_bias="1.0" />
+
+    <Button
+        android:id="@+id/confirmButton"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_margin="16dp"
+        android:backgroundTint="@color/green"
+        android:enabled="false"
+        android:text="@string/transaction_confirm"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintEnd_toStartOf="@+id/guideline"
+        app:layout_constraintHorizontal_bias="0.5"
+        app:layout_constraintStart_toEndOf="@+id/cancelButton"
+        tools:enabled="true" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/cashier/src/main/res/layout/activity_main.xml 
b/cashier/src/main/res/layout/activity_main.xml
new file mode 100644
index 0000000..e41b842
--- /dev/null
+++ b/cashier/src/main/res/layout/activity_main.xml
@@ -0,0 +1,51 @@
+<?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.coordinatorlayout.widget.CoordinatorLayout 
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=".MainActivity">
+
+    <com.google.android.material.appbar.AppBarLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:theme="@style/AppTheme.AppBarOverlay">
+
+        <com.google.android.material.appbar.MaterialToolbar
+            android:id="@+id/toolbar"
+            style="@style/AppTheme.Toolbar"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content" />
+
+    </com.google.android.material.appbar.AppBarLayout>
+
+    <androidx.fragment.app.FragmentContainerView
+        android:id="@+id/nav_host_fragment"
+        android:name="androidx.navigation.fragment.NavHostFragment"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        app:defaultNavHost="true"
+        app:layout_behavior="@string/appbar_scrolling_view_behavior"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintLeft_toLeftOf="parent"
+        app:layout_constraintRight_toRightOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        app:navGraph="@navigation/nav_graph" />
+
+</androidx.coordinatorlayout.widget.CoordinatorLayout>
diff --git a/cashier/src/main/res/layout/fragment_balance.xml 
b/cashier/src/main/res/layout/fragment_balance.xml
new file mode 100644
index 0000000..5dafc59
--- /dev/null
+++ b/cashier/src/main/res/layout/fragment_balance.xml
@@ -0,0 +1,225 @@
+<?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=".BalanceFragment">
+
+    <TextView
+        android:id="@+id/lastTransactionView"
+        style="@style/Widget.MaterialComponents.Snackbar.FullWidth"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:background="?attr/colorPrimaryDark"
+        android:drawableStart="@drawable/ic_check_circle"
+        android:drawablePadding="8dp"
+        android:drawableTint="?attr/colorOnPrimarySurface"
+        android:gravity="center_vertical"
+        android:padding="8dp"
+        android:textColor="?attr/colorOnPrimarySurface"
+        android:visibility="gone"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        tools:text="@string/transaction_last_success"
+        tools:visibility="visible" />
+
+    <View
+        android:id="@+id/balanceBackground"
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        android:background="@color/background"
+        app:layout_constraintBottom_toBottomOf="@+id/balanceView"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/lastTransactionView" />
+
+    <TextView
+        android:id="@+id/balanceLabel"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_margin="@dimen/default_margin"
+        android:text="@string/balance_current_label"
+        android:textAppearance="@style/TextAppearance.AppCompat.Medium"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/lastTransactionView" />
+
+    <TextView
+        android:id="@+id/balanceView"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:paddingStart="@dimen/default_margin"
+        android:paddingTop="8dp"
+        android:gravity="center"
+        android:paddingEnd="@dimen/default_margin"
+        android:paddingBottom="@dimen/default_margin"
+        android:textAppearance="@style/TextAppearance.AppCompat.Headline"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/balanceLabel"
+        tools:text="100 KUDOS" />
+
+    <ProgressBar
+        android:id="@+id/progressBar"
+        style="?android:attr/progressBarStyle"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        app:layout_constraintBottom_toBottomOf="@+id/balanceView"
+        app:layout_constraintEnd_toEndOf="@+id/balanceView"
+        app:layout_constraintStart_toStartOf="@+id/balanceView"
+        app:layout_constraintTop_toTopOf="@+id/balanceView" />
+
+    <TextView
+        android:id="@+id/introView"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="@dimen/default_margin"
+        android:layout_marginTop="32dp"
+        android:layout_marginEnd="@dimen/default_margin"
+        android:text="@string/withdraw_into"
+        android:textAlignment="center"
+        android:textAppearance="@style/TextAppearance.AppCompat.Medium"
+        android:visibility="invisible"
+        app:layout_constraintBottom_toTopOf="@+id/button5"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/balanceBackground"
+        app:layout_constraintVertical_bias="0.25"
+        app:layout_constraintVertical_chainStyle="packed"
+        tools:visibility="visible" />
+
+    <Button
+        android:id="@+id/button5"
+        style="@style/AmountButton"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="5"
+        android:visibility="invisible"
+        app:layout_constraintBottom_toTopOf="@+id/amountView"
+        app:layout_constraintEnd_toStartOf="@+id/button10"
+        app:layout_constraintHorizontal_bias="0.5"
+        app:layout_constraintHorizontal_chainStyle="packed"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/introView"
+        tools:ignore="HardcodedText"
+        tools:visibility="visible" />
+
+    <Button
+        android:id="@+id/button10"
+        style="@style/AmountButton"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="10"
+        android:visibility="invisible"
+        app:layout_constraintEnd_toStartOf="@+id/button20"
+        app:layout_constraintStart_toEndOf="@+id/button5"
+        app:layout_constraintTop_toBottomOf="@+id/introView"
+        tools:ignore="HardcodedText"
+        tools:visibility="visible" />
+
+    <Button
+        android:id="@+id/button20"
+        style="@style/AmountButton"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="20"
+        android:visibility="invisible"
+        app:layout_constraintEnd_toStartOf="@+id/button50"
+        app:layout_constraintStart_toEndOf="@+id/button10"
+        app:layout_constraintTop_toBottomOf="@+id/introView"
+        tools:ignore="HardcodedText"
+        tools:visibility="visible" />
+
+    <Button
+        android:id="@+id/button50"
+        style="@style/AmountButton"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="50"
+        android:visibility="invisible"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toEndOf="@+id/button20"
+        app:layout_constraintTop_toBottomOf="@+id/introView"
+        tools:ignore="HardcodedText"
+        tools:visibility="visible" />
+
+    <com.google.android.material.textfield.TextInputLayout
+        android:id="@+id/amountView"
+        
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.Dense"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="@dimen/default_margin"
+        android:layout_marginTop="@dimen/default_margin"
+        android:hint="@string/withdraw_input_amount"
+        android:visibility="invisible"
+        app:endIconDrawable="@drawable/ic_clear"
+        app:endIconMode="clear_text"
+        app:endIconTint="?attr/colorControlNormal"
+        app:layout_constraintBottom_toTopOf="@+id/confirmWithdrawalButton"
+        app:layout_constraintEnd_toStartOf="@+id/currencyView"
+        app:layout_constraintHorizontal_bias="0.5"
+        app:layout_constraintHorizontal_chainStyle="packed"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/button5"
+        tools:visibility="visible">
+
+        <com.google.android.material.textfield.TextInputEditText
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:ems="6"
+            android:imeOptions="actionGo"
+            android:inputType="number"
+            android:maxLength="4" />
+
+    </com.google.android.material.textfield.TextInputLayout>
+
+    <TextView
+        android:id="@+id/currencyView"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_margin="@dimen/default_margin"
+        android:textAppearance="@style/TextAppearance.AppCompat.Medium"
+        android:visibility="invisible"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintHorizontal_bias="0.5"
+        app:layout_constraintStart_toEndOf="@+id/amountView"
+        app:layout_constraintTop_toTopOf="@+id/amountView"
+        tools:text="TESTKUDOS"
+        tools:visibility="visible" />
+
+    <com.google.android.material.button.MaterialButton
+        android:id="@+id/confirmWithdrawalButton"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_margin="@dimen/default_margin"
+        android:backgroundTint="@color/green"
+        android:drawableLeft="@drawable/ic_withdraw"
+        android:drawableTint="?attr/colorOnPrimarySurface"
+        android:text="@string/withdraw_button_confirm"
+        android:visibility="invisible"
+        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/amountView"
+        tools:ignore="RtlHardcoded"
+        tools:visibility="visible" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/cashier/src/main/res/layout/fragment_config.xml 
b/cashier/src/main/res/layout/fragment_config.xml
new file mode 100644
index 0000000..47ec6f9
--- /dev/null
+++ b/cashier/src/main/res/layout/fragment_config.xml
@@ -0,0 +1,112 @@
+<?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">
+
+    <com.google.android.material.textfield.TextInputLayout
+        android:id="@+id/urlView"
+        
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.Dense"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_margin="@dimen/default_margin"
+        android:hint="@string/config_bank_url"
+        app:endIconMode="clear_text"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent">
+
+        <com.google.android.material.textfield.TextInputEditText
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:inputType="textUri" />
+
+    </com.google.android.material.textfield.TextInputLayout>
+
+    <com.google.android.material.textfield.TextInputLayout
+        android:id="@+id/usernameView"
+        
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.Dense"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_margin="@dimen/default_margin"
+        android:hint="@string/config_username"
+        app:boxBackgroundMode="outline"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/urlView">
+
+        <com.google.android.material.textfield.TextInputEditText
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:inputType="text" />
+
+    </com.google.android.material.textfield.TextInputLayout>
+
+    <com.google.android.material.textfield.TextInputLayout
+        android:id="@+id/passwordView"
+        
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.Dense"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_margin="@dimen/default_margin"
+        android:hint="@string/config_password"
+        app:boxBackgroundMode="outline"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/usernameView"
+        app:passwordToggleEnabled="true">
+
+        <com.google.android.material.textfield.TextInputEditText
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:inputType="textWebPassword" />
+
+    </com.google.android.material.textfield.TextInputLayout>
+
+    <Button
+        android:id="@+id/saveButton"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_margin="@dimen/default_margin"
+        android:text="@string/config_button_save"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/passwordView" />
+
+    <ProgressBar
+        android:id="@+id/progressBar"
+        style="?android:attr/progressBarStyle"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:visibility="invisible"
+        app:layout_constraintBottom_toBottomOf="@+id/saveButton"
+        app:layout_constraintEnd_toEndOf="@+id/saveButton"
+        app:layout_constraintStart_toStartOf="@+id/saveButton"
+        app:layout_constraintTop_toTopOf="@+id/saveButton"
+        tools:visibility="visible" />
+
+    <TextView
+        android:id="@+id/demoView"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_margin="@dimen/default_margin"
+        android:text="@string/config_demo_hint"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/saveButton" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/cashier/src/main/res/layout/fragment_error.xml 
b/cashier/src/main/res/layout/fragment_error.xml
new file mode 100644
index 0000000..ac34c85
--- /dev/null
+++ b/cashier/src/main/res/layout/fragment_error.xml
@@ -0,0 +1,65 @@
+<?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"
+    tools:context=".withdraw.ErrorFragment">
+
+    <ImageView
+        android:id="@+id/imageView"
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        android:layout_margin="32dp"
+        android:src="@drawable/ic_error"
+        app:layout_constraintBottom_toTopOf="@+id/textView"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintHorizontal_bias="0.5"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        tools:ignore="ContentDescription" />
+
+    <androidx.appcompat.widget.AppCompatTextView
+        android:id="@+id/textView"
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        android:layout_margin="32dp"
+        android:text="@string/transaction_error"
+        android:textAlignment="center"
+        android:textColor="@color/red"
+        app:autoSizeMaxTextSize="42sp"
+        app:autoSizeTextType="uniform"
+        app:layout_constraintBottom_toTopOf="@+id/backButton"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintHorizontal_bias="0.5"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/imageView" />
+
+    <Button
+        android:id="@+id/backButton"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_margin="16dp"
+        android:text="@string/transaction_button_back"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/cashier/src/main/res/layout/fragment_transaction.xml 
b/cashier/src/main/res/layout/fragment_transaction.xml
new file mode 100644
index 0000000..3affbf2
--- /dev/null
+++ b/cashier/src/main/res/layout/fragment_transaction.xml
@@ -0,0 +1,100 @@
+<?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.TransactionFragment">
+
+    <TextView
+        android:id="@+id/introView"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_margin="32dp"
+        android:gravity="center_horizontal"
+        android:text="@string/transaction_intro"
+        android:textAppearance="@style/TextAppearance.AppCompat.Medium"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+
+    <TextView
+        android:id="@+id/amountView"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_margin="32dp"
+        android:gravity="center_horizontal"
+        android:textAppearance="@style/TextAppearance.AppCompat.Headline"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/introView"
+        tools:text="50 KUDOS" />
+
+    <ImageView
+        android:id="@+id/qrCodeView"
+        android:layout_width="256dp"
+        android:layout_height="256dp"
+        android:layout_margin="32dp"
+        android:keepScreenOn="true"
+        android:visibility="invisible"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/amountView"
+        tools:ignore="ContentDescription"
+        tools:src="@drawable/ic_arrow"
+        tools:visibility="visible" />
+
+    <ProgressBar
+        android:id="@+id/progressBar"
+        style="?android:attr/progressBarStyleLarge"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        app:layout_constraintBottom_toBottomOf="@+id/qrCodeView"
+        app:layout_constraintEnd_toEndOf="@+id/qrCodeView"
+        app:layout_constraintStart_toStartOf="@+id/qrCodeView"
+        app:layout_constraintTop_toTopOf="@+id/qrCodeView" />
+
+    <Button
+        android:id="@+id/cancelButton"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_margin="16dp"
+        android:backgroundTint="@color/red"
+        android:text="@string/transaction_abort"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toStartOf="@+id/confirmButton"
+        app:layout_constraintHorizontal_chainStyle="spread_inside"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/qrCodeView"
+        app:layout_constraintVertical_bias="1.0" />
+
+    <Button
+        android:id="@+id/confirmButton"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_margin="16dp"
+        android:backgroundTint="@color/green"
+        android:enabled="false"
+        android:text="@string/transaction_confirm"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toEndOf="@+id/cancelButton"
+        tools:enabled="true" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/cashier/src/main/res/menu/balance.xml 
b/cashier/src/main/res/menu/balance.xml
new file mode 100644
index 0000000..bc64af3
--- /dev/null
+++ b/cashier/src/main/res/menu/balance.xml
@@ -0,0 +1,30 @@
+<?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/>
+  -->
+
+<menu xmlns:android="http://schemas.android.com/apk/res/android";
+    xmlns:app="http://schemas.android.com/apk/res-auto";>
+
+    <item
+        android:id="@+id/action_reconfigure"
+        android:title="@string/action_reconfigure"
+        app:showAsAction="never" />
+    <item
+        android:id="@+id/action_lock"
+        android:title="@string/action_lock"
+        app:showAsAction="never" />
+
+</menu>
diff --git a/cashier/src/main/res/mipmap-anydpi-v26/ic_launcher.xml 
b/cashier/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 0000000..7353dbd
--- /dev/null
+++ b/cashier/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android";>
+    <background android:drawable="@color/ic_launcher_background"/>
+    <foreground android:drawable="@drawable/ic_launcher_foreground"/>
+</adaptive-icon>
\ No newline at end of file
diff --git a/cashier/src/main/res/mipmap-hdpi/ic_launcher.png 
b/cashier/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..c52928c
Binary files /dev/null and b/cashier/src/main/res/mipmap-hdpi/ic_launcher.png 
differ
diff --git a/cashier/src/main/res/mipmap-mdpi/ic_launcher.png 
b/cashier/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..b97178b
Binary files /dev/null and b/cashier/src/main/res/mipmap-mdpi/ic_launcher.png 
differ
diff --git a/cashier/src/main/res/mipmap-xhdpi/ic_launcher.png 
b/cashier/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..8f92c07
Binary files /dev/null and b/cashier/src/main/res/mipmap-xhdpi/ic_launcher.png 
differ
diff --git a/cashier/src/main/res/mipmap-xxhdpi/ic_launcher.png 
b/cashier/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..214cbea
Binary files /dev/null and b/cashier/src/main/res/mipmap-xxhdpi/ic_launcher.png 
differ
diff --git a/cashier/src/main/res/mipmap-xxxhdpi/ic_launcher.png 
b/cashier/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..b959cd3
Binary files /dev/null and 
b/cashier/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/cashier/src/main/res/navigation/nav_graph.xml 
b/cashier/src/main/res/navigation/nav_graph.xml
new file mode 100644
index 0000000..49f8881
--- /dev/null
+++ b/cashier/src/main/res/navigation/nav_graph.xml
@@ -0,0 +1,73 @@
+<?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/>
+  -->
+
+<navigation 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/nav_graph"
+    app:startDestination="@id/balanceFragment"
+    tools:ignore="UnusedNavigation">
+
+    <fragment
+        android:id="@+id/configFragment"
+        android:name="net.taler.cashier.ConfigFragment"
+        android:label="ConfigFragment"
+        tools:layout="@layout/fragment_config">
+        <action
+            android:id="@+id/action_configFragment_to_balanceFragment"
+            app:destination="@id/balanceFragment"
+            app:launchSingleTop="true"
+            app:popUpTo="@id/balanceFragment" />
+    </fragment>
+
+    <fragment
+        android:id="@+id/balanceFragment"
+        android:name="net.taler.cashier.BalanceFragment"
+        android:label="fragment_balance"
+        tools:layout="@layout/fragment_balance">
+        <action
+            android:id="@+id/action_balanceFragment_to_transactionFragment"
+            app:destination="@id/transactionFragment" />
+    </fragment>
+
+    <fragment
+        android:id="@+id/transactionFragment"
+        android:name="net.taler.cashier.withdraw.TransactionFragment"
+        android:label="fragment_transaction"
+        tools:layout="@layout/fragment_transaction">
+        <action
+            android:id="@+id/action_transactionFragment_to_errorFragment"
+            app:destination="@id/errorFragment"
+            app:launchSingleTop="true"
+            app:popUpTo="@+id/balanceFragment" />
+        <action
+            android:id="@+id/action_transactionFragment_to_balanceFragment"
+            app:destination="@id/balanceFragment"
+            app:launchSingleTop="true"
+            app:popUpTo="@+id/balanceFragment" />
+    </fragment>
+
+    <fragment
+        android:id="@+id/errorFragment"
+        android:name="net.taler.cashier.withdraw.ErrorFragment"
+        tools:layout="@layout/fragment_error" />
+
+    <action
+        android:id="@+id/action_global_configFragment"
+        app:destination="@id/configFragment"
+        app:launchSingleTop="true" />
+
+</navigation>
diff --git a/cashier/src/main/res/values-night/colors.xml 
b/cashier/src/main/res/values-night/colors.xml
new file mode 100644
index 0000000..55dde58
--- /dev/null
+++ b/cashier/src/main/res/values-night/colors.xml
@@ -0,0 +1,20 @@
+<?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/>
+  -->
+
+<resources>
+    <color name="background">#222222</color>
+</resources>
diff --git a/cashier/src/main/res/values/colors.xml 
b/cashier/src/main/res/values/colors.xml
new file mode 100644
index 0000000..61338da
--- /dev/null
+++ b/cashier/src/main/res/values/colors.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <color name="colorPrimary">#1565C0</color>
+    <color name="colorPrimaryDark">#6A1B9A</color>
+    <color name="colorAccent">#D81B60</color>
+
+    <color name="background">#F1F1F1</color>
+    <color name="green">#388E3C</color>
+    <color name="red">#D32F2F</color>
+</resources>
diff --git a/cashier/src/main/res/values/dimens.xml 
b/cashier/src/main/res/values/dimens.xml
new file mode 100644
index 0000000..9d9d85a
--- /dev/null
+++ b/cashier/src/main/res/values/dimens.xml
@@ -0,0 +1,3 @@
+<resources>
+    <dimen name="default_margin">16dp</dimen>
+</resources>
diff --git a/cashier/src/main/res/values/ic_launcher_background.xml 
b/cashier/src/main/res/values/ic_launcher_background.xml
new file mode 100644
index 0000000..3862264
--- /dev/null
+++ b/cashier/src/main/res/values/ic_launcher_background.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <color name="ic_launcher_background">#1565C0</color>
+</resources>
\ No newline at end of file
diff --git a/cashier/src/main/res/values/strings.xml 
b/cashier/src/main/res/values/strings.xml
new file mode 100644
index 0000000..5df5bfa
--- /dev/null
+++ b/cashier/src/main/res/values/strings.xml
@@ -0,0 +1,39 @@
+<resources>
+    <string name="app_name">Taler Cashier</string>
+
+    <string name="config_bank_url">Bank API address</string>
+    <string name="config_username">Username</string>
+    <string name="config_password">Password</string>
+    <string name="config_button_save">Save</string>
+    <string name="config_bank_url_error">The address in invalid.</string>
+    <string name="config_username_error">Please enter your username</string>
+    <string name="config_error">Error retrieving configuration</string>
+    <string name="config_error_auth">Invalid username or password</string>
+    <string name="config_demo_hint">For testing, you can <![CDATA[<a 
href="%s">create a test account at the demo bank</a>]]>.</string>
+
+    <string name="balance_current_label">Current balance</string>
+    <string name="balance_error">ERROR</string>
+    <string name="balance_offline">Offline. Please connect to the 
Internet</string>
+    <string name="action_reconfigure">Reconfigure</string>
+    <string name="action_lock">Lock</string>
+
+    <string name="withdraw_input_amount">Amount</string>
+    <string name="withdraw_into">How much e-cash should be withdrawn?</string>
+    <string name="withdraw_error_zero">Enter positive amount</string>
+    <string name="withdraw_error_insufficient_balance">Insufficient 
balance</string>
+    <string name="withdraw_error_fetch">Error communicating with bank</string>
+    <string name="withdraw_button_confirm">Withdraw</string>
+
+    <string name="transaction_intro">Scan code\nwith the Taler wallet app\nto 
get</string>
+    <string name="transaction_intro_nfc">Scan code or use NFC\nwith the Taler 
wallet app\nto get</string>
+    <string name="transaction_intro_scanned">Please confirm the 
transaction!</string>
+    <string name="transaction_confirm">Confirm</string>
+    <string name="transaction_abort">Abort</string>
+    <string name="transaction_error">Transaction error</string>
+    <string name="transaction_aborted">Transaction aborted</string>
+    <string name="transaction_button_back">Go back</string>
+    <string name="transaction_last_success">Last Transaction: %s 
withdrawn</string>
+    <string name="transaction_last_aborted">Last Transaction: Aborted</string>
+    <string name="transaction_last_error">Last Transaction: Error</string>
+
+</resources>
diff --git a/cashier/src/main/res/values/styles.xml 
b/cashier/src/main/res/values/styles.xml
new file mode 100644
index 0000000..4339684
--- /dev/null
+++ b/cashier/src/main/res/values/styles.xml
@@ -0,0 +1,28 @@
+<resources>
+
+    <style name="AppTheme" 
parent="Theme.MaterialComponents.DayNight.DarkActionBar">
+        <item name="colorPrimary">@color/colorPrimary</item>
+        <item 
name="colorOnPrimary">@color/design_default_color_background</item>
+        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
+        <item name="colorSecondary">@color/colorAccent</item>
+        <item 
name="colorOnSecondary">@color/design_default_color_background</item>
+        <item name="colorAccent">@color/colorAccent</item>
+    </style>
+
+    <style name="AppTheme.NoActionBar">
+        <item name="windowActionBar">false</item>
+        <item name="windowNoTitle">true</item>
+    </style>
+
+    <style name="AppTheme.AppBarOverlay" 
parent="ThemeOverlay.MaterialComponents.ActionBar" />
+
+    <style name="AppTheme.Toolbar" 
parent="Widget.MaterialComponents.Toolbar.Primary" />
+
+    <style name="AmountButton" parent="Widget.MaterialComponents.Button">
+        <item name="android:minWidth">48dp</item>
+        <item name="android:layout_marginStart">@dimen/default_margin</item>
+        <item name="android:layout_marginEnd">@dimen/default_margin</item>
+        <item name="android:layout_marginTop">16dp</item>
+    </style>
+
+</resources>
diff --git a/cashier/src/main/res/xml/backup_descriptor.xml 
b/cashier/src/main/res/xml/backup_descriptor.xml
new file mode 100644
index 0000000..a298494
--- /dev/null
+++ b/cashier/src/main/res/xml/backup_descriptor.xml
@@ -0,0 +1,19 @@
+<?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/>
+  -->
+
+<full-backup-content>
+
+</full-backup-content>
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 0000000..00f6d64
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1,21 @@
+# Project-wide Gradle settings.
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+org.gradle.jvmargs=-Xmx1536m
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# 
http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
+# AndroidX package structure to make it clearer which packages are bundled 
with the
+# Android operating system, and which are packaged with your app's APK
+# https://developer.android.com/topic/libraries/support-library/androidx-rn
+android.useAndroidX=true
+# Automatically convert third-party libraries to use AndroidX
+android.enableJetifier=false
+# Kotlin code style for this project: "official" or "obsolete":
+kotlin.code.style=official
diff --git a/gradle/wrapper/gradle-wrapper.jar 
b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..f6b961f
Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties 
b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..daff887
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Wed Mar 18 13:36:36 BRT 2020
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip
diff --git a/gradlew b/gradlew
new file mode 100755
index 0000000..cccdd3d
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,172 @@
+#!/usr/bin/env sh
+
+##############################################################################
+##
+##  Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+    ls=`ls -ld "$PRG"`
+    link=`expr "$ls" : '.*-> \(.*\)$'`
+    if expr "$link" : '/.*' > /dev/null; then
+        PRG="$link"
+    else
+        PRG=`dirname "$PRG"`"/$link"
+    fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to 
pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+    echo "$*"
+}
+
+die () {
+    echo
+    echo "$*"
+    echo
+    exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+  CYGWIN* )
+    cygwin=true
+    ;;
+  Darwin* )
+    darwin=true
+    ;;
+  MINGW* )
+    msys=true
+    ;;
+  NONSTOP* )
+    nonstop=true
+    ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+        # IBM's JDK on AIX uses strange locations for the executables
+        JAVACMD="$JAVA_HOME/jre/sh/java"
+    else
+        JAVACMD="$JAVA_HOME/bin/java"
+    fi
+    if [ ! -x "$JAVACMD" ] ; then
+        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+    fi
+else
+    JAVACMD="java"
+    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 
'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; 
then
+    MAX_FD_LIMIT=`ulimit -H -n`
+    if [ $? -eq 0 ] ; then
+        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+            MAX_FD="$MAX_FD_LIMIT"
+        fi
+        ulimit -n $MAX_FD
+        if [ $? -ne 0 ] ; then
+            warn "Could not set maximum file descriptor limit: $MAX_FD"
+        fi
+    else
+        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+    fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" 
\"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+    APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+    JAVACMD=`cygpath --unix "$JAVACMD"`
+
+    # We build the pattern for arguments to be converted via cygpath
+    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+    SEP=""
+    for dir in $ROOTDIRSRAW ; do
+        ROOTDIRS="$ROOTDIRS$SEP$dir"
+        SEP="|"
+    done
+    OURCYGPATTERN="(^($ROOTDIRS))"
+    # Add a user-defined pattern to the cygpath arguments
+    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+    fi
+    # Now convert the arguments - kludge to limit ourselves to /bin/sh
+    i=0
+    for arg in "$@" ; do
+        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### 
Determine if an option
+
+        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### 
Added a condition
+            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+        else
+            eval `echo args$i`="\"$arg\""
+        fi
+        i=$((i+1))
+    done
+    case $i in
+        (0) set -- ;;
+        (1) set -- "$args0" ;;
+        (2) set -- "$args0" "$args1" ;;
+        (3) set -- "$args0" "$args1" "$args2" ;;
+        (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+        (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+        (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+        (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" 
"$args6" ;;
+        (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" 
"$args6" "$args7" ;;
+        (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" 
"$args6" "$args7" "$args8" ;;
+    esac
+fi
+
+# Escape application args
+save () {
+    for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; 
done
+    echo " "
+}
+APP_ARGS=$(save "$@")
+
+# Collect all arguments for the java command, following the shell quoting and 
substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 
"\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" 
org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+# by default we should be in the correct project dir, but when run from Finder 
on Mac, the cwd is wrong
+if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
+  cd "$(dirname "$0")"
+fi
+
+exec "$JAVACMD" "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 0000000..e95643d
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,84 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem  Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS 
to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your 
PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windows variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% 
"-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" 
org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code 
instead of
+rem the _cmd.exe /c_ return code!
+if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/merchant-terminal/.gitignore b/merchant-terminal/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/merchant-terminal/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/merchant-terminal/.gitlab-ci.yml b/merchant-terminal/.gitlab-ci.yml
new file mode 100644
index 0000000..4c03405
--- /dev/null
+++ b/merchant-terminal/.gitlab-ci.yml
@@ -0,0 +1,36 @@
+merchant_test:
+  stage: test
+  only:
+    changes:
+      - "merchant-terminal"
+  script: ./gradlew :merchant-terminal:lint :merchant-terminal:assembleRelease
+
+merchant_deploy_nightly:
+  stage: deploy
+  only:
+    refs:
+      - master
+    changes:
+      - "merchant-terminal"
+  script:
+    # Ensure that key exists
+    - test -z "$DEBUG_KEYSTORE" && exit 0
+    # Rename nightly app
+    - sed -i
+      's,<string name="app_name">.*</string>,<string name="app_name">Merchant 
PoS Nightly</string>,'
+      merchant-terminal/src/main/res/values*/strings.xml
+    # Set time-based version code
+    - export versionCode=$(date '+%s')
+    - sed -i "s,^\(\s*versionCode\) *[0-9].*,\1 $versionCode," 
merchant-terminal/build.gradle
+    # Add commit to version name
+    - export versionName=$(git rev-parse --short=7 HEAD)
+    - sed -i "s,^\(\s*versionName\ *\"[0-9].*\)\",\1 ($versionName)\"," 
merchant-terminal/build.gradle
+    # Set nightly application ID
+    - sed -i "s,^\(\s*applicationId\) \"*[a-z\.].*\",\1 
\"net.taler.merchantpos.nightly\"," merchant-terminal/build.gradle
+    # Build the APK
+    - ./gradlew :merchant-terminal:assembleDebug
+    # START only needed while patch not accepted/released upstream
+    - apt update && apt install patch
+    - patch /usr/lib/python3/dist-packages/fdroidserver/nightly.py 
nightly-stats.patch
+    # END
+    - CI_PROJECT_URL="https://gitlab.com/gnu-taler/fdroid-repo"; 
CI_PROJECT_PATH="gnu-taler/fdroid-repo" fdroid nightly -v
diff --git a/merchant-terminal/build.gradle b/merchant-terminal/build.gradle
new file mode 100644
index 0000000..594cab3
--- /dev/null
+++ b/merchant-terminal/build.gradle
@@ -0,0 +1,76 @@
+apply plugin: 'com.android.application'
+apply plugin: 'kotlin-android'
+apply plugin: 'kotlin-android-extensions'
+apply plugin: "androidx.navigation.safeargs.kotlin"
+
+android {
+    compileSdkVersion 29
+    buildToolsVersion "29.0.3"
+    defaultConfig {
+        applicationId "net.taler.merchantpos"
+        minSdkVersion 26
+        targetSdkVersion 29
+        versionCode 1
+        versionName "1.0"
+        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+    }
+    buildTypes {
+        release {
+            minifyEnabled true
+            proguardFiles 
getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+        }
+    }
+
+    compileOptions {
+        sourceCompatibility = 1.8
+        targetCompatibility = 1.8
+    }
+
+    kotlinOptions {
+        jvmTarget = "1.8"
+    }
+
+    testOptions {
+        unitTests {
+            includeAndroidResources = true
+        }
+    }
+
+    lintOptions {
+        abortOnError true
+        ignoreWarnings false
+    }
+}
+
+dependencies {
+    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
+    implementation 'androidx.appcompat:appcompat:1.1.0'
+    implementation 'androidx.core:core-ktx:1.2.0'
+    implementation 'com.google.android.material:material:1.1.0'
+    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
+    implementation "androidx.recyclerview:recyclerview:1.1.0"
+    implementation "androidx.recyclerview:recyclerview-selection:1.1.0-rc01"
+
+    // Navigation
+    implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
+    implementation "androidx.navigation:navigation-ui-ktx:$nav_version"
+
+    // ViewModel and LiveData
+    def lifecycle_version = "2.2.0"
+    implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
+    implementation 
"androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"
+
+    // HTTP Requests
+    implementation 'com.android.volley:volley:1.1.1'
+
+    // QR codes
+    implementation 'com.google.zxing:core:3.4.0'
+
+    implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.0.0"
+
+    // JSON parsing and serialization
+    implementation "com.fasterxml.jackson.module:jackson-module-kotlin:2.10.2"
+
+    testImplementation 'androidx.test.ext:junit:1.1.1'
+    testImplementation 'org.robolectric:robolectric:4.3.1'
+}
diff --git a/merchant-terminal/proguard-rules.pro 
b/merchant-terminal/proguard-rules.pro
new file mode 100644
index 0000000..f1b4245
--- /dev/null
+++ b/merchant-terminal/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+#   http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+#   public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
diff --git a/merchant-terminal/src/main/AndroidManifest.xml 
b/merchant-terminal/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..f52995f
--- /dev/null
+++ b/merchant-terminal/src/main/AndroidManifest.xml
@@ -0,0 +1,56 @@
+<?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/>
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android";
+        xmlns:tools="http://schemas.android.com/tools";
+        package="net.taler.merchantpos">
+
+    <uses-permission android:name="android.permission.NFC" />
+    <uses-permission android:name="android.permission.INTERNET" />
+
+    <uses-feature
+            android:name="android.hardware.nfc"
+            android:required="false" />
+
+    <uses-feature
+            android:name="android.hardware.telephony"
+            android:required="false" />
+
+    <application
+            android:allowBackup="true"
+            android:fullBackupContent="@xml/backup_descriptor"
+            android:icon="@mipmap/ic_taler_logo"
+            android:label="@string/app_name"
+            android:roundIcon="@mipmap/ic_taler_logo_round"
+            android:supportsRtl="true"
+            android:theme="@style/AppTheme"
+            tools:ignore="GoogleAppIndexingWarning">
+        <activity
+                android:name=".MainActivity"
+                android:label="@string/app_name"
+                android:screenOrientation="landscape"
+                android:theme="@style/AppTheme.NoActionBar"
+                tools:ignore="LockedOrientationActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+</manifest>
diff --git a/merchant-terminal/src/main/ic_taler_logo-web.png 
b/merchant-terminal/src/main/ic_taler_logo-web.png
new file mode 100644
index 0000000..e3b8075
Binary files /dev/null and b/merchant-terminal/src/main/ic_taler_logo-web.png 
differ
diff --git a/merchant-terminal/src/main/java/net/taler/merchantpos/Amount.kt 
b/merchant-terminal/src/main/java/net/taler/merchantpos/Amount.kt
new file mode 100644
index 0000000..17ddd61
--- /dev/null
+++ b/merchant-terminal/src/main/java/net/taler/merchantpos/Amount.kt
@@ -0,0 +1,48 @@
+/*
+ * 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.merchantpos
+
+import org.json.JSONObject
+
+data class Amount(val currency: String, val amount: String) {
+    @Suppress("unused")
+    fun isZero(): Boolean {
+        return amount.toDouble() == 0.0
+    }
+
+    companion object {
+        private const val FRACTIONAL_BASE = 1e8
+
+        @Suppress("unused")
+        fun fromJson(jsonAmount: JSONObject): Amount {
+            val amountCurrency = jsonAmount.getString("currency")
+            val amountValue = jsonAmount.getString("value")
+            val amountFraction = jsonAmount.getString("fraction")
+            val amountIntValue = Integer.parseInt(amountValue)
+            val amountIntFraction = Integer.parseInt(amountFraction)
+            return Amount(
+                amountCurrency,
+                (amountIntValue + amountIntFraction / 
FRACTIONAL_BASE).toString()
+            )
+        }
+
+        fun fromString(strAmount: String): Amount {
+            val components = strAmount.split(":")
+            return Amount(components[0], components[1])
+        }
+    }
+}
diff --git 
a/merchant-terminal/src/main/java/net/taler/merchantpos/MainActivity.kt 
b/merchant-terminal/src/main/java/net/taler/merchantpos/MainActivity.kt
new file mode 100644
index 0000000..0c6bdfa
--- /dev/null
+++ b/merchant-terminal/src/main/java/net/taler/merchantpos/MainActivity.kt
@@ -0,0 +1,123 @@
+/*
+ * 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.merchantpos
+
+import android.content.Intent
+import android.content.Intent.ACTION_MAIN
+import android.content.Intent.CATEGORY_HOME
+import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
+import android.os.Bundle
+import android.os.Handler
+import android.view.MenuItem
+import android.widget.Toast
+import android.widget.Toast.LENGTH_SHORT
+import androidx.activity.viewModels
+import androidx.appcompat.app.AppCompatActivity
+import androidx.core.view.GravityCompat.START
+import androidx.lifecycle.Observer
+import androidx.navigation.NavController
+import androidx.navigation.fragment.NavHostFragment
+import androidx.navigation.ui.AppBarConfiguration
+import androidx.navigation.ui.setupWithNavController
+import 
com.google.android.material.navigation.NavigationView.OnNavigationItemSelectedListener
+import kotlinx.android.synthetic.main.activity_main.*
+import kotlinx.android.synthetic.main.app_bar_main.*
+
+class MainActivity : AppCompatActivity(), OnNavigationItemSelectedListener {
+
+    private val model: MainViewModel by viewModels()
+    private val nfcManager = NfcManager()
+
+    private lateinit var nav: NavController
+
+    private var reallyExit = false
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        setContentView(R.layout.activity_main)
+
+        model.paymentManager.payment.observe(this, Observer { payment ->
+            payment?.talerPayUri?.let {
+                nfcManager.setTagString(it)
+            }
+        })
+
+        val navHostFragment =
+            supportFragmentManager.findFragmentById(R.id.navHostFragment) as 
NavHostFragment
+        nav = navHostFragment.navController
+
+        nav_view.setupWithNavController(nav)
+        nav_view.setNavigationItemSelectedListener(this)
+
+        setSupportActionBar(toolbar)
+        val appBarConfiguration = AppBarConfiguration(nav.graph, drawer_layout)
+        toolbar.setupWithNavController(nav, appBarConfiguration)
+    }
+
+    override fun onStart() {
+        super.onStart()
+        if (!model.configManager.config.isValid() && 
nav.currentDestination?.id != R.id.nav_settings) {
+            nav.navigate(R.id.action_global_merchantSettings)
+        } else if (model.configManager.merchantConfig == null && 
nav.currentDestination?.id != R.id.configFetcher) {
+            nav.navigate(R.id.action_global_configFetcher)
+        }
+    }
+
+    public override fun onResume() {
+        super.onResume()
+        // TODO should we only read tags when a payment is to be made?
+        NfcManager.start(this, nfcManager)
+    }
+
+    public override fun onPause() {
+        super.onPause()
+        NfcManager.stop(this)
+    }
+
+    override fun onNavigationItemSelected(item: MenuItem): Boolean {
+        when (item.itemId) {
+            R.id.nav_order -> nav.navigate(R.id.action_global_order)
+            R.id.nav_history -> 
nav.navigate(R.id.action_global_merchantHistory)
+            R.id.nav_settings -> 
nav.navigate(R.id.action_global_merchantSettings)
+        }
+        drawer_layout.closeDrawer(START)
+        return true
+    }
+
+    override fun onBackPressed() {
+        val currentDestination = nav.currentDestination?.id
+        if (drawer_layout.isDrawerOpen(START)) {
+            drawer_layout.closeDrawer(START)
+        } else if (currentDestination == R.id.nav_settings && 
!model.configManager.config.isValid()) {
+            // we are in the configuration screen and need a config to continue
+            val intent = Intent(ACTION_MAIN).apply {
+                addCategory(CATEGORY_HOME)
+                flags = FLAG_ACTIVITY_NEW_TASK
+            }
+            startActivity(intent)
+        } else if (currentDestination == R.id.nav_order) {
+            if (reallyExit) super.onBackPressed()
+            else {
+                // this closes the app and causes orders to be lost, so let's 
confirm first
+                reallyExit = true
+                Toast.makeText(this, R.string.toast_back_to_exit, 
LENGTH_SHORT).show()
+                Handler().postDelayed({ reallyExit = false }, 3000)
+            }
+        } else super.onBackPressed()
+    }
+
+}
diff --git 
a/merchant-terminal/src/main/java/net/taler/merchantpos/MainViewModel.kt 
b/merchant-terminal/src/main/java/net/taler/merchantpos/MainViewModel.kt
new file mode 100644
index 0000000..3fe472d
--- /dev/null
+++ b/merchant-terminal/src/main/java/net/taler/merchantpos/MainViewModel.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.merchantpos
+
+import android.app.Application
+import androidx.lifecycle.AndroidViewModel
+import androidx.lifecycle.viewModelScope
+import com.android.volley.toolbox.Volley
+import 
com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES
+import com.fasterxml.jackson.databind.ObjectMapper
+import com.fasterxml.jackson.module.kotlin.KotlinModule
+import net.taler.merchantpos.config.ConfigManager
+import net.taler.merchantpos.history.HistoryManager
+import net.taler.merchantpos.history.RefundManager
+import net.taler.merchantpos.order.OrderManager
+import net.taler.merchantpos.payment.PaymentManager
+
+class MainViewModel(app: Application) : AndroidViewModel(app) {
+
+    private val mapper = ObjectMapper()
+        .registerModule(KotlinModule())
+        .configure(FAIL_ON_UNKNOWN_PROPERTIES, false)
+    private val queue = Volley.newRequestQueue(app)
+
+    val orderManager = OrderManager(app, mapper)
+    val configManager = ConfigManager(app, viewModelScope, mapper, 
queue).apply {
+        addConfigurationReceiver(orderManager)
+    }
+    val paymentManager = PaymentManager(configManager, queue, mapper)
+    val historyManager = HistoryManager(configManager, queue, mapper)
+    val refundManager = RefundManager(configManager, queue)
+
+    override fun onCleared() {
+        queue.cancelAll { !it.isCanceled }
+    }
+
+}
diff --git 
a/merchant-terminal/src/main/java/net/taler/merchantpos/NfcManager.kt 
b/merchant-terminal/src/main/java/net/taler/merchantpos/NfcManager.kt
new file mode 100644
index 0000000..09c1470
--- /dev/null
+++ b/merchant-terminal/src/main/java/net/taler/merchantpos/NfcManager.kt
@@ -0,0 +1,233 @@
+/*
+ * 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.merchantpos
+
+import android.app.Activity
+import android.content.Context
+import android.nfc.NfcAdapter
+import android.nfc.NfcAdapter.FLAG_READER_NFC_A
+import android.nfc.NfcAdapter.FLAG_READER_SKIP_NDEF_CHECK
+import android.nfc.Tag
+import android.nfc.tech.IsoDep
+import android.util.Log
+import net.taler.merchantpos.Utils.hexStringToByteArray
+import org.json.JSONObject
+import java.io.ByteArrayOutputStream
+import java.net.URL
+import javax.net.ssl.HttpsURLConnection
+
+@Suppress("unused")
+private const val TALER_AID = "A0000002471001"
+
+class NfcManager : NfcAdapter.ReaderCallback {
+
+    companion object {
+        const val TAG = "taler-merchant"
+
+        /**
+         * Returns true if NFC is supported and false otherwise.
+         */
+        fun hasNfc(context: Context): Boolean {
+            return getNfcAdapter(context) != null
+        }
+
+        /**
+         * Enables NFC reader mode. Don't forget to call [stop] afterwards.
+         */
+        fun start(activity: Activity, nfcManager: NfcManager) {
+            getNfcAdapter(activity)?.enableReaderMode(activity, nfcManager, 
nfcManager.flags, null)
+        }
+
+        /**
+         * Disables NFC reader mode. Call after [start].
+         */
+        fun stop(activity: Activity) {
+            getNfcAdapter(activity)?.disableReaderMode(activity)
+        }
+
+        private fun getNfcAdapter(context: Context): NfcAdapter? {
+            return NfcAdapter.getDefaultAdapter(context)
+        }
+    }
+
+    private val flags = FLAG_READER_NFC_A or FLAG_READER_SKIP_NDEF_CHECK
+
+    private var tagString: String? = null
+    private var currentTag: IsoDep? = null
+
+    fun setTagString(tagString: String) {
+        this.tagString = tagString
+    }
+
+    override fun onTagDiscovered(tag: Tag?) {
+
+        Log.v(TAG, "tag discovered")
+
+        val isoDep = IsoDep.get(tag)
+        isoDep.connect()
+
+        currentTag = isoDep
+
+        isoDep.transceive(apduSelectFile())
+
+        val tagString: String? = tagString
+        if (tagString != null) {
+            isoDep.transceive(apduPutTalerData(1, tagString.toByteArray()))
+        }
+
+        // FIXME: use better pattern for sleeps in between requests
+        // -> start with fast polling, poll more slowly if no requests are 
coming
+
+        while (true) {
+            try {
+                val reqFrame = isoDep.transceive(apduGetData())
+                if (reqFrame.size < 2) {
+                    Log.v(TAG, "request frame too small")
+                    break
+                }
+                val req = ByteArray(reqFrame.size - 2)
+                if (req.isEmpty()) {
+                    continue
+                }
+                reqFrame.copyInto(req, 0, 0, reqFrame.size - 2)
+                val jsonReq = JSONObject(req.toString(Charsets.UTF_8))
+                val reqId = jsonReq.getInt("id")
+                Log.v(TAG, "got request $jsonReq")
+                val jsonInnerReq = jsonReq.getJSONObject("request")
+                val method = jsonInnerReq.getString("method")
+                val urlStr = jsonInnerReq.getString("url")
+                Log.v(TAG, "url '$urlStr'")
+                Log.v(TAG, "method '$method'")
+                val url = URL(urlStr)
+                val conn: HttpsURLConnection = url.openConnection() as 
HttpsURLConnection
+                conn.setRequestProperty("Accept", "application/json")
+                conn.connectTimeout = 5000
+                conn.doInput = true
+                when (method) {
+                    "get" -> {
+                        conn.requestMethod = "GET"
+                    }
+                    "postJson" -> {
+                        conn.requestMethod = "POST"
+                        conn.doOutput = true
+                        conn.setRequestProperty("Content-Type", 
"application/json; utf-8")
+                        val body = jsonInnerReq.getString("body")
+                        
conn.outputStream.write(body.toByteArray(Charsets.UTF_8))
+                    }
+                    else -> {
+                        throw Exception("method not supported")
+                    }
+                }
+                Log.v(TAG, "connecting")
+                conn.connect()
+                Log.v(TAG, "connected")
+
+                val statusCode = conn.responseCode
+                val tunnelResp = JSONObject()
+                tunnelResp.put("id", reqId)
+                tunnelResp.put("status", conn.responseCode)
+
+                if (statusCode == 200) {
+                    val stream = conn.inputStream
+                    val httpResp = stream.buffered().readBytes()
+                    tunnelResp.put("responseJson", 
JSONObject(httpResp.toString(Charsets.UTF_8)))
+                }
+
+                Log.v(TAG, "sending: $tunnelResp")
+
+                isoDep.transceive(apduPutTalerData(2, 
tunnelResp.toString().toByteArray()))
+            } catch (e: Exception) {
+                Log.v(TAG, "exception during NFC loop: $e")
+                break
+            }
+        }
+
+        isoDep.close()
+    }
+
+    private fun writeApduLength(stream: ByteArrayOutputStream, size: Int) {
+        when {
+            size == 0 -> {
+                // No size field needed!
+            }
+            size <= 255 -> // One byte size field
+                stream.write(size)
+            size <= 65535 -> {
+                stream.write(0)
+                // FIXME: is this supposed to be little or big endian?
+                stream.write(size and 0xFF)
+                stream.write((size ushr 8) and 0xFF)
+            }
+            else -> throw Error("payload too big")
+        }
+    }
+
+    private fun apduSelectFile(): ByteArray {
+        return hexStringToByteArray("00A4040007A0000002471001")
+    }
+
+    private fun apduPutData(payload: ByteArray): ByteArray {
+        val stream = ByteArrayOutputStream()
+
+        // Class
+        stream.write(0x00)
+
+        // Instruction 0xDA = put data
+        stream.write(0xDA)
+
+        // Instruction parameters
+        // (proprietary encoding)
+        stream.write(0x01)
+        stream.write(0x00)
+
+        writeApduLength(stream, payload.size)
+
+        stream.write(payload)
+
+        return stream.toByteArray()
+    }
+
+    private fun apduPutTalerData(talerInst: Int, payload: ByteArray): 
ByteArray {
+        val realPayload = ByteArrayOutputStream()
+        realPayload.write(talerInst)
+        realPayload.write(payload)
+        return apduPutData(realPayload.toByteArray())
+    }
+
+    private fun apduGetData(): ByteArray {
+        val stream = ByteArrayOutputStream()
+
+        // Class
+        stream.write(0x00)
+
+        // Instruction 0xCA = get data
+        stream.write(0xCA)
+
+        // Instruction parameters
+        // (proprietary encoding)
+        stream.write(0x01)
+        stream.write(0x00)
+
+        // Max expected response size, two
+        // zero bytes denotes 65536
+        stream.write(0x0)
+        stream.write(0x0)
+
+        return stream.toByteArray()
+    }
+
+}
diff --git 
a/merchant-terminal/src/main/java/net/taler/merchantpos/QrCodeManager.kt 
b/merchant-terminal/src/main/java/net/taler/merchantpos/QrCodeManager.kt
new file mode 100644
index 0000000..595e7ac
--- /dev/null
+++ b/merchant-terminal/src/main/java/net/taler/merchantpos/QrCodeManager.kt
@@ -0,0 +1,42 @@
+/*
+ * 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.merchantpos
+
+import android.graphics.Bitmap
+import android.graphics.Bitmap.Config.RGB_565
+import android.graphics.Color.BLACK
+import android.graphics.Color.WHITE
+import com.google.zxing.BarcodeFormat.QR_CODE
+import com.google.zxing.qrcode.QRCodeWriter
+
+object QrCodeManager {
+
+    fun makeQrCode(text: String, size: Int = 256): Bitmap {
+        val qrCodeWriter = QRCodeWriter()
+        val bitMatrix = qrCodeWriter.encode(text, QR_CODE, size, size)
+        val height = bitMatrix.height
+        val width = bitMatrix.width
+        val bmp = Bitmap.createBitmap(width, height, RGB_565)
+        for (x in 0 until width) {
+            for (y in 0 until height) {
+                bmp.setPixel(x, y, if (bitMatrix.get(x, y)) BLACK else WHITE)
+            }
+        }
+        return bmp
+    }
+
+}
diff --git a/merchant-terminal/src/main/java/net/taler/merchantpos/Utils.kt 
b/merchant-terminal/src/main/java/net/taler/merchantpos/Utils.kt
new file mode 100644
index 0000000..a0c30d6
--- /dev/null
+++ b/merchant-terminal/src/main/java/net/taler/merchantpos/Utils.kt
@@ -0,0 +1,155 @@
+/*
+ * 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.merchantpos
+
+import android.content.Context
+import android.text.format.DateUtils.DAY_IN_MILLIS
+import android.text.format.DateUtils.FORMAT_ABBREV_MONTH
+import android.text.format.DateUtils.FORMAT_ABBREV_RELATIVE
+import android.text.format.DateUtils.FORMAT_NO_YEAR
+import android.text.format.DateUtils.FORMAT_SHOW_DATE
+import android.text.format.DateUtils.FORMAT_SHOW_TIME
+import android.text.format.DateUtils.MINUTE_IN_MILLIS
+import android.text.format.DateUtils.formatDateTime
+import android.text.format.DateUtils.getRelativeTimeSpanString
+import android.view.View
+import android.view.View.INVISIBLE
+import android.view.View.VISIBLE
+import androidx.annotation.StringRes
+import androidx.fragment.app.Fragment
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MediatorLiveData
+import androidx.lifecycle.Observer
+import androidx.navigation.NavController
+import androidx.navigation.NavDirections
+import androidx.navigation.fragment.findNavController
+import 
com.google.android.material.snackbar.BaseTransientBottomBar.ANIMATION_MODE_FADE
+import com.google.android.material.snackbar.BaseTransientBottomBar.Duration
+import com.google.android.material.snackbar.Snackbar.make
+
+object Utils {
+
+    private const val HEX_CHARS = "0123456789ABCDEF"
+
+    fun hexStringToByteArray(data: String): ByteArray {
+        val result = ByteArray(data.length / 2)
+
+        for (i in data.indices step 2) {
+            val firstIndex = HEX_CHARS.indexOf(data[i])
+            val secondIndex = HEX_CHARS.indexOf(data[i + 1])
+
+            val octet = firstIndex.shl(4).or(secondIndex)
+            result[i.shr(1)] = octet.toByte()
+        }
+        return result
+    }
+
+
+    private val HEX_CHARS_ARRAY = HEX_CHARS.toCharArray()
+
+    @Suppress("unused")
+    fun toHex(byteArray: ByteArray): String {
+        val result = StringBuffer()
+
+        byteArray.forEach {
+            val octet = it.toInt()
+            val firstIndex = (octet and 0xF0).ushr(4)
+            val secondIndex = octet and 0x0F
+            result.append(HEX_CHARS_ARRAY[firstIndex])
+            result.append(HEX_CHARS_ARRAY[secondIndex])
+        }
+        return result.toString()
+    }
+
+}
+
+fun View.fadeIn(endAction: () -> Unit = {}) {
+    if (visibility == VISIBLE) return
+    alpha = 0f
+    visibility = VISIBLE
+    animate().alpha(1f).withEndAction {
+        if (context != null) endAction.invoke()
+    }.start()
+}
+
+fun View.fadeOut(endAction: () -> Unit = {}) {
+    if (visibility == INVISIBLE) return
+    animate().alpha(0f).withEndAction {
+        if (context == null) return@withEndAction
+        visibility = INVISIBLE
+        alpha = 1f
+        endAction.invoke()
+    }.start()
+}
+
+fun topSnackbar(view: View, text: CharSequence, @Duration duration: Int) {
+    make(view, text, duration)
+        .setAnimationMode(ANIMATION_MODE_FADE)
+        .setAnchorView(R.id.navHostFragment)
+        .show()
+}
+
+fun topSnackbar(view: View, @StringRes resId: Int, @Duration duration: Int) {
+    topSnackbar(view, view.resources.getText(resId), duration)
+}
+
+fun NavDirections.navigate(nav: NavController) = nav.navigate(this)
+
+fun Fragment.navigate(directions: NavDirections) = 
findNavController().navigate(directions)
+
+fun Long.toRelativeTime(context: Context): CharSequence {
+    val now = System.currentTimeMillis()
+    return if (now - this > DAY_IN_MILLIS * 2) {
+        val flags = FORMAT_SHOW_TIME or FORMAT_SHOW_DATE or 
FORMAT_ABBREV_MONTH or FORMAT_NO_YEAR
+        formatDateTime(context, this, flags)
+    } else getRelativeTimeSpanString(this, now, MINUTE_IN_MILLIS, 
FORMAT_ABBREV_RELATIVE)
+}
+
+class CombinedLiveData<T, K, S>(
+    source1: LiveData<T>,
+    source2: LiveData<K>,
+    private val combine: (data1: T?, data2: K?) -> S
+) : MediatorLiveData<S>() {
+
+    private var data1: T? = null
+    private var data2: K? = null
+
+    init {
+        super.addSource(source1) { t ->
+            data1 = t
+            value = combine(data1, data2)
+        }
+        super.addSource(source2) { k ->
+            data2 = k
+            value = combine(data1, data2)
+        }
+    }
+
+    override fun <S : Any?> addSource(source: LiveData<S>, onChanged: 
Observer<in S>) {
+        throw UnsupportedOperationException()
+    }
+
+    override fun <T : Any?> removeSource(toRemote: LiveData<T>) {
+        throw UnsupportedOperationException()
+    }
+}
+
+/**
+ * Use this with 'when' expressions when you need it to handle all 
possibilities/branches.
+ */
+val <T> T.exhaustive: T
+    get() = this
diff --git 
a/merchant-terminal/src/main/java/net/taler/merchantpos/config/ConfigFetcherFragment.kt
 
b/merchant-terminal/src/main/java/net/taler/merchantpos/config/ConfigFetcherFragment.kt
new file mode 100644
index 0000000..c370e33
--- /dev/null
+++ 
b/merchant-terminal/src/main/java/net/taler/merchantpos/config/ConfigFetcherFragment.kt
@@ -0,0 +1,66 @@
+/*
+ * 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.merchantpos.config
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+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_SHORT
+import net.taler.merchantpos.MainViewModel
+import net.taler.merchantpos.R
+import 
net.taler.merchantpos.config.ConfigFetcherFragmentDirections.Companion.actionConfigFetcherToMerchantSettings
+import 
net.taler.merchantpos.config.ConfigFetcherFragmentDirections.Companion.actionConfigFetcherToOrder
+import net.taler.merchantpos.navigate
+
+class ConfigFetcherFragment : Fragment() {
+
+    private val model: MainViewModel by activityViewModels()
+    private val configManager by lazy { model.configManager }
+
+    override fun onCreateView(
+        inflater: LayoutInflater, container: ViewGroup?,
+        savedInstanceState: Bundle?
+    ): View? {
+        return inflater.inflate(R.layout.fragment_config_fetcher, container, 
false)
+    }
+
+    override fun onActivityCreated(savedInstanceState: Bundle?) {
+        super.onActivityCreated(savedInstanceState)
+        configManager.fetchConfig(configManager.config, false)
+        configManager.configUpdateResult.observe(viewLifecycleOwner, Observer 
{ result ->
+            when (result) {
+                null -> return@Observer
+                is ConfigUpdateResult.Error -> onNetworkError(result.msg)
+                is ConfigUpdateResult.Success -> {
+                    actionConfigFetcherToOrder().navigate(findNavController())
+                }
+            }
+        })
+    }
+
+    private fun onNetworkError(msg: String) {
+        Snackbar.make(view!!, msg, LENGTH_SHORT).show()
+        actionConfigFetcherToMerchantSettings().navigate(findNavController())
+    }
+
+}
diff --git 
a/merchant-terminal/src/main/java/net/taler/merchantpos/config/ConfigManager.kt 
b/merchant-terminal/src/main/java/net/taler/merchantpos/config/ConfigManager.kt
new file mode 100644
index 0000000..edb8059
--- /dev/null
+++ 
b/merchant-terminal/src/main/java/net/taler/merchantpos/config/ConfigManager.kt
@@ -0,0 +1,181 @@
+/*
+ * 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.merchantpos.config
+
+import android.content.Context
+import android.content.Context.MODE_PRIVATE
+import android.util.Base64.NO_WRAP
+import android.util.Base64.encodeToString
+import android.util.Log
+import androidx.annotation.UiThread
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import com.android.volley.Request.Method.GET
+import com.android.volley.RequestQueue
+import com.android.volley.Response.ErrorListener
+import com.android.volley.Response.Listener
+import com.android.volley.VolleyError
+import com.android.volley.toolbox.JsonObjectRequest
+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.merchantpos.R
+import org.json.JSONObject
+
+private const val SETTINGS_NAME = "taler-merchant-terminal"
+
+private const val SETTINGS_CONFIG_URL = "configUrl"
+private const val SETTINGS_USERNAME = "username"
+private const val SETTINGS_PASSWORD = "password"
+
+internal const val CONFIG_URL_DEMO = 
"https://docs.taler.net/_static/sample-pos-config.json";
+internal const val CONFIG_USERNAME_DEMO = ""
+internal const val CONFIG_PASSWORD_DEMO = ""
+
+private val TAG = ConfigManager::class.java.simpleName
+
+interface ConfigurationReceiver {
+    /**
+     * Returns null if the configuration was valid, or a error string for user 
display otherwise.
+     */
+    suspend fun onConfigurationReceived(json: JSONObject, currency: String): 
String?
+}
+
+class ConfigManager(
+    private val context: Context,
+    private val scope: CoroutineScope,
+    private val mapper: ObjectMapper,
+    private val queue: RequestQueue
+) {
+
+    private val prefs = context.getSharedPreferences(SETTINGS_NAME, 
MODE_PRIVATE)
+    private val configurationReceivers = ArrayList<ConfigurationReceiver>()
+
+    var config = Config(
+        configUrl = prefs.getString(SETTINGS_CONFIG_URL, CONFIG_URL_DEMO)!!,
+        username = prefs.getString(SETTINGS_USERNAME, CONFIG_USERNAME_DEMO)!!,
+        password = prefs.getString(SETTINGS_PASSWORD, CONFIG_PASSWORD_DEMO)!!
+    )
+    var merchantConfig: MerchantConfig? = null
+        private set
+
+    private val mConfigUpdateResult = MutableLiveData<ConfigUpdateResult>()
+    val configUpdateResult: LiveData<ConfigUpdateResult> = mConfigUpdateResult
+
+    fun addConfigurationReceiver(receiver: ConfigurationReceiver) {
+        configurationReceivers.add(receiver)
+    }
+
+    @UiThread
+    fun fetchConfig(config: Config, save: Boolean, savePassword: Boolean = 
false) {
+        mConfigUpdateResult.value = null
+        val configToSave = if (save) {
+            if (savePassword) config else config.copy(password = "")
+        } else null
+
+        val stringRequest = object : JsonObjectRequest(GET, config.configUrl, 
null,
+            Listener { onConfigReceived(it, configToSave) },
+            ErrorListener { onNetworkError(it) }
+        ) {
+            // send basic auth header
+            override fun getHeaders(): MutableMap<String, String> {
+                val credentials = "${config.username}:${config.password}"
+                val auth = ("Basic ${encodeToString(credentials.toByteArray(), 
NO_WRAP)}")
+                return mutableMapOf("Authorization" to auth)
+            }
+        }
+        queue.add(stringRequest)
+    }
+
+    @UiThread
+    private fun onConfigReceived(json: JSONObject, config: Config?) {
+        val merchantConfig: MerchantConfig = try {
+            mapper.readValue(json.getString("config"))
+        } catch (e: Exception) {
+            Log.e(TAG, "Error parsing merchant config", e)
+            val msg = context.getString(R.string.config_error_malformed)
+            mConfigUpdateResult.value = ConfigUpdateResult.Error(msg)
+            return
+        }
+
+        val params = mapOf("instance" to merchantConfig.instance)
+        val req = MerchantRequest(GET, merchantConfig, "config", params, null,
+            Listener { onMerchantConfigReceived(config, json, merchantConfig, 
it) },
+            ErrorListener { onNetworkError(it) }
+        )
+        queue.add(req)
+    }
+
+    private fun onMerchantConfigReceived(
+        newConfig: Config?,
+        configJson: JSONObject,
+        merchantConfig: MerchantConfig,
+        json: JSONObject
+    ) = scope.launch(Dispatchers.Default) {
+        val currency = json.getString("currency")
+
+        for (receiver in configurationReceivers) {
+            val result = try {
+                receiver.onConfigurationReceived(configJson, currency)
+            } catch (e: Exception) {
+                Log.e(TAG, "Error handling configuration by 
${receiver::class.java.simpleName}", e)
+                context.getString(R.string.config_error_unknown)
+            }
+            if (result != null) {  // error
+                mConfigUpdateResult.postValue(ConfigUpdateResult.Error(result))
+                return@launch
+            }
+        }
+        newConfig?.let {
+            config = it
+            saveConfig(it)
+        }
+        this@ConfigManager.merchantConfig = merchantConfig.copy(currency = 
currency)
+        mConfigUpdateResult.postValue(ConfigUpdateResult.Success(currency))
+    }
+
+    fun forgetPassword() {
+        config = config.copy(password = "")
+        saveConfig(config)
+        merchantConfig = null
+    }
+
+    private fun saveConfig(config: Config) {
+        prefs.edit()
+            .putString(SETTINGS_CONFIG_URL, config.configUrl)
+            .putString(SETTINGS_USERNAME, config.username)
+            .putString(SETTINGS_PASSWORD, config.password)
+            .apply()
+    }
+
+    @UiThread
+    private fun onNetworkError(it: VolleyError?) {
+        val msg = context.getString(
+            if (it?.networkResponse?.statusCode == 401) 
R.string.config_auth_error
+            else R.string.config_error_network
+        )
+        mConfigUpdateResult.value = ConfigUpdateResult.Error(msg)
+    }
+
+}
+
+sealed class ConfigUpdateResult {
+    data class Error(val msg: String) : ConfigUpdateResult()
+    data class Success(val currency: String) : ConfigUpdateResult()
+}
diff --git 
a/merchant-terminal/src/main/java/net/taler/merchantpos/config/MerchantConfig.kt
 
b/merchant-terminal/src/main/java/net/taler/merchantpos/config/MerchantConfig.kt
new file mode 100644
index 0000000..2050e28
--- /dev/null
+++ 
b/merchant-terminal/src/main/java/net/taler/merchantpos/config/MerchantConfig.kt
@@ -0,0 +1,47 @@
+/*
+ * 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.merchantpos.config
+
+import android.net.Uri
+import com.fasterxml.jackson.annotation.JsonProperty
+
+data class Config(
+    val configUrl: String,
+    val username: String,
+    val password: String
+) {
+    fun isValid() = !configUrl.isBlank()
+    fun hasPassword() = !password.isBlank()
+}
+
+data class MerchantConfig(
+    @JsonProperty("base_url")
+    val baseUrl: String,
+    val instance: String,
+    @JsonProperty("api_key")
+    val apiKey: String,
+    val currency: String?
+) {
+    fun urlFor(endpoint: String, params: Map<String, String>?): String {
+        val uriBuilder = Uri.parse(baseUrl).buildUpon()
+        uriBuilder.appendPath(endpoint)
+        params?.forEach {
+            uriBuilder.appendQueryParameter(it.key, it.value)
+        }
+        return uriBuilder.toString()
+    }
+}
diff --git 
a/merchant-terminal/src/main/java/net/taler/merchantpos/config/MerchantConfigFragment.kt
 
b/merchant-terminal/src/main/java/net/taler/merchantpos/config/MerchantConfigFragment.kt
new file mode 100644
index 0000000..aad1c93
--- /dev/null
+++ 
b/merchant-terminal/src/main/java/net/taler/merchantpos/config/MerchantConfigFragment.kt
@@ -0,0 +1,165 @@
+/*
+ * 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.merchantpos.config
+
+import android.net.Uri
+import android.os.Bundle
+import android.text.method.LinkMovementMethod
+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 com.google.android.material.snackbar.BaseTransientBottomBar.LENGTH_LONG
+import com.google.android.material.snackbar.Snackbar
+import kotlinx.android.synthetic.main.fragment_merchant_config.*
+import net.taler.merchantpos.MainViewModel
+import net.taler.merchantpos.R
+import 
net.taler.merchantpos.config.MerchantConfigFragmentDirections.Companion.actionSettingsToOrder
+import net.taler.merchantpos.navigate
+import net.taler.merchantpos.topSnackbar
+
+/**
+ * Fragment that displays merchant settings.
+ */
+class MerchantConfigFragment : Fragment() {
+
+    private val model: MainViewModel by activityViewModels()
+    private val configManager by lazy { model.configManager }
+
+    override fun onCreateView(
+        inflater: LayoutInflater,
+        container: ViewGroup?,
+        savedInstanceState: Bundle?
+    ): View? {
+        return inflater.inflate(R.layout.fragment_merchant_config, container, 
false)
+    }
+
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+        configUrlView.editText!!.setOnFocusChangeListener { _, hasFocus ->
+            if (!hasFocus) checkForUrlCredentials()
+        }
+        okButton.setOnClickListener {
+            checkForUrlCredentials()
+            val inputUrl = configUrlView.editText!!.text
+            val url = if (inputUrl.startsWith("http")) {
+                inputUrl.toString()
+            } else {
+                "https://$inputUrl".also { 
configUrlView.editText!!.setText(it) }
+            }
+            progressBar.visibility = VISIBLE
+            okButton.visibility = INVISIBLE
+            val config = Config(
+                configUrl = url,
+                username = usernameView.editText!!.text.toString(),
+                password = passwordView.editText!!.text.toString()
+            )
+            configManager.fetchConfig(config, true, 
savePasswordCheckBox.isChecked)
+            configManager.configUpdateResult.observe(viewLifecycleOwner, 
Observer { result ->
+                if (onConfigUpdate(result)) {
+                    
configManager.configUpdateResult.removeObservers(viewLifecycleOwner)
+                }
+            })
+        }
+        forgetPasswordButton.setOnClickListener {
+            configManager.forgetPassword()
+            passwordView.editText!!.text = null
+            forgetPasswordButton.visibility = GONE
+        }
+        configDocsView.movementMethod = LinkMovementMethod.getInstance()
+        updateView(savedInstanceState == null)
+    }
+
+    override fun onStart() {
+        super.onStart()
+        // focus password if this is the only empty field
+        if (passwordView.editText!!.text.isBlank()
+            && !configUrlView.editText!!.text.isBlank()
+            && !usernameView.editText!!.text.isBlank()
+        ) {
+            passwordView.requestFocus()
+        }
+    }
+
+    private fun updateView(isInitialization: Boolean = false) {
+        val config = configManager.config
+        configUrlView.editText!!.setText(
+            if (isInitialization && config.configUrl.isBlank()) CONFIG_URL_DEMO
+            else config.configUrl
+        )
+        usernameView.editText!!.setText(
+            if (isInitialization && config.username.isBlank()) 
CONFIG_USERNAME_DEMO
+            else config.username
+        )
+        passwordView.editText!!.setText(
+            if (isInitialization && config.password.isBlank()) 
CONFIG_PASSWORD_DEMO
+            else config.password
+        )
+        forgetPasswordButton.visibility = if (config.hasPassword()) VISIBLE 
else GONE
+    }
+
+    private fun checkForUrlCredentials() {
+        val text = configUrlView.editText!!.text.toString()
+        Uri.parse(text)?.userInfo?.let { userInfo ->
+            if (userInfo.contains(':')) {
+                val (user, pass) = userInfo.split(':')
+                val strippedUrl = text.replace("${userInfo}@", "")
+                configUrlView.editText!!.setText(strippedUrl)
+                usernameView.editText!!.setText(user)
+                passwordView.editText!!.setText(pass)
+            }
+        }
+    }
+
+    /**
+     * Processes updated config and returns true, if observer can be removed.
+     */
+    private fun onConfigUpdate(result: ConfigUpdateResult?) = when (result) {
+        null -> false
+        is ConfigUpdateResult.Error -> {
+            onError(result.msg)
+            true
+        }
+        is ConfigUpdateResult.Success -> {
+            onConfigReceived(result.currency)
+            true
+        }
+    }
+
+    private fun onConfigReceived(currency: String) {
+        onResultReceived()
+        updateView()
+        topSnackbar(view!!, getString(R.string.config_changed, currency), 
LENGTH_LONG)
+        actionSettingsToOrder().navigate(findNavController())
+    }
+
+    private fun onError(msg: String) {
+        onResultReceived()
+        Snackbar.make(view!!, msg, LENGTH_LONG).show()
+    }
+
+    private fun onResultReceived() {
+        progressBar.visibility = INVISIBLE
+        okButton.visibility = VISIBLE
+    }
+
+}
diff --git 
a/merchant-terminal/src/main/java/net/taler/merchantpos/config/MerchantRequest.kt
 
b/merchant-terminal/src/main/java/net/taler/merchantpos/config/MerchantRequest.kt
new file mode 100644
index 0000000..8d95378
--- /dev/null
+++ 
b/merchant-terminal/src/main/java/net/taler/merchantpos/config/MerchantRequest.kt
@@ -0,0 +1,41 @@
+/*
+ * 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.merchantpos.config
+
+
+import android.util.ArrayMap
+import com.android.volley.Response
+import com.android.volley.toolbox.JsonObjectRequest
+import org.json.JSONObject
+
+class MerchantRequest(
+    method: Int,
+    private val merchantConfig: MerchantConfig,
+    endpoint: String,
+    params: Map<String, String>?,
+    jsonRequest: JSONObject?,
+    listener: Response.Listener<JSONObject>,
+    errorListener: Response.ErrorListener
+) :
+    JsonObjectRequest(method, merchantConfig.urlFor(endpoint, params), 
jsonRequest, listener, errorListener) {
+
+    override fun getHeaders(): MutableMap<String, String> {
+        val headerMap = ArrayMap<String, String>()
+        headerMap["Authorization"] = "ApiKey " + merchantConfig.apiKey
+        return headerMap
+    }
+}
diff --git 
a/merchant-terminal/src/main/java/net/taler/merchantpos/history/HistoryManager.kt
 
b/merchant-terminal/src/main/java/net/taler/merchantpos/history/HistoryManager.kt
new file mode 100644
index 0000000..594e7cc
--- /dev/null
+++ 
b/merchant-terminal/src/main/java/net/taler/merchantpos/history/HistoryManager.kt
@@ -0,0 +1,106 @@
+/*
+ * 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.merchantpos.history
+
+import android.util.Log
+import androidx.annotation.UiThread
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import com.android.volley.Request.Method.GET
+import com.android.volley.Request.Method.POST
+import com.android.volley.RequestQueue
+import com.android.volley.Response.ErrorListener
+import com.android.volley.Response.Listener
+import com.fasterxml.jackson.annotation.JsonIgnore
+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.databind.ObjectMapper
+import com.fasterxml.jackson.module.kotlin.readValue
+import net.taler.merchantpos.Amount
+import net.taler.merchantpos.config.ConfigManager
+import net.taler.merchantpos.config.MerchantRequest
+import org.json.JSONObject
+
+@JsonInclude(NON_EMPTY)
+class Timestamp(
+    @JsonProperty("t_ms")
+    val ms: Long
+)
+
+data class HistoryItem(
+    @JsonProperty("order_id")
+    val orderId: String,
+    @JsonProperty("amount")
+    val amountStr: String,
+    val summary: String,
+    val timestamp: Timestamp
+) {
+    @get:JsonIgnore
+    val amount: Amount by lazy { Amount.fromString(amountStr) }
+
+    @get:JsonIgnore
+    val time = timestamp.ms
+}
+
+sealed class HistoryResult {
+    object Error : HistoryResult()
+    class Success(val items: List<HistoryItem>) : HistoryResult()
+}
+
+class HistoryManager(
+    private val configManager: ConfigManager,
+    private val queue: RequestQueue,
+    private val mapper: ObjectMapper
+) {
+
+    private val mIsLoading = MutableLiveData(false)
+    val isLoading: LiveData<Boolean> = mIsLoading
+
+    private val mItems = MutableLiveData<HistoryResult>()
+    val items: LiveData<HistoryResult> = mItems
+
+    @UiThread
+    internal fun fetchHistory() {
+        mIsLoading.value = true
+        val merchantConfig = configManager.merchantConfig!!
+        val params = mapOf("instance" to merchantConfig.instance)
+        val req = MerchantRequest(GET, merchantConfig, "history", params, null,
+            Listener { onHistoryResponse(it) },
+            ErrorListener { onHistoryError() })
+        queue.add(req)
+    }
+
+    @UiThread
+    private fun onHistoryResponse(body: JSONObject) {
+        mIsLoading.value = false
+        val items = arrayListOf<HistoryItem>()
+        val historyJson = body.getJSONArray("history")
+        for (i in 0 until historyJson.length()) {
+            val historyItem: HistoryItem = 
mapper.readValue(historyJson.getString(i))
+            items.add(historyItem)
+        }
+        mItems.value = HistoryResult.Success(items)
+    }
+
+    @UiThread
+    private fun onHistoryError() {
+        mIsLoading.value = false
+        mItems.value = HistoryResult.Error
+    }
+
+}
diff --git 
a/merchant-terminal/src/main/java/net/taler/merchantpos/history/MerchantHistoryFragment.kt
 
b/merchant-terminal/src/main/java/net/taler/merchantpos/history/MerchantHistoryFragment.kt
new file mode 100644
index 0000000..0c53f71
--- /dev/null
+++ 
b/merchant-terminal/src/main/java/net/taler/merchantpos/history/MerchantHistoryFragment.kt
@@ -0,0 +1,160 @@
+/*
+ * 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.merchantpos.history
+
+import android.annotation.SuppressLint
+import android.os.Bundle
+import android.util.Log
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.ImageButton
+import android.widget.TextView
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.activityViewModels
+import androidx.lifecycle.Observer
+import androidx.recyclerview.widget.DividerItemDecoration
+import androidx.recyclerview.widget.DividerItemDecoration.VERTICAL
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView.Adapter
+import androidx.recyclerview.widget.RecyclerView.ViewHolder
+import com.google.android.material.snackbar.Snackbar
+import com.google.android.material.snackbar.Snackbar.LENGTH_SHORT
+import kotlinx.android.synthetic.main.fragment_merchant_history.*
+import net.taler.merchantpos.MainViewModel
+import net.taler.merchantpos.R
+import net.taler.merchantpos.exhaustive
+import net.taler.merchantpos.history.HistoryItemAdapter.HistoryItemViewHolder
+import 
net.taler.merchantpos.history.MerchantHistoryFragmentDirections.Companion.actionGlobalMerchantSettings
+import 
net.taler.merchantpos.history.MerchantHistoryFragmentDirections.Companion.actionNavHistoryToRefundFragment
+import net.taler.merchantpos.navigate
+import net.taler.merchantpos.toRelativeTime
+import java.util.*
+
+private interface RefundClickListener {
+    fun onRefundClicked(item: HistoryItem)
+}
+
+/**
+ * Fragment to display the merchant's payment history, received from the 
backend.
+ */
+class MerchantHistoryFragment : Fragment(), RefundClickListener {
+
+    companion object {
+        const val TAG = "taler-merchant"
+    }
+
+    private val model: MainViewModel by activityViewModels()
+    private val historyManager by lazy { model.historyManager }
+    private val refundManager by lazy { model.refundManager }
+
+    private val historyListAdapter = HistoryItemAdapter(this)
+
+    override fun onCreateView(
+        inflater: LayoutInflater, container: ViewGroup?,
+        savedInstanceState: Bundle?
+    ): View? {
+        return inflater.inflate(R.layout.fragment_merchant_history, container, 
false)
+    }
+
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+        list_history.apply {
+            layoutManager = LinearLayoutManager(requireContext())
+            addItemDecoration(DividerItemDecoration(context, VERTICAL))
+            adapter = historyListAdapter
+        }
+
+        swipeRefresh.setOnRefreshListener {
+            Log.v(TAG, "refreshing!")
+            historyManager.fetchHistory()
+        }
+        historyManager.isLoading.observe(viewLifecycleOwner, Observer { 
loading ->
+            Log.v(TAG, "setting refreshing to $loading")
+            swipeRefresh.isRefreshing = loading
+        })
+        historyManager.items.observe(viewLifecycleOwner, Observer { result ->
+            when (result) {
+                is HistoryResult.Error -> onError()
+                is HistoryResult.Success -> 
historyListAdapter.setData(result.items)
+            }.exhaustive
+        })
+    }
+
+    override fun onStart() {
+        super.onStart()
+        if (model.configManager.merchantConfig?.instance == null) {
+            navigate(actionGlobalMerchantSettings())
+        } else {
+            historyManager.fetchHistory()
+        }
+    }
+
+    private fun onError() {
+        Snackbar.make(view!!, R.string.error_network, LENGTH_SHORT).show()
+    }
+
+    override fun onRefundClicked(item: HistoryItem) {
+        refundManager.startRefund(item)
+        navigate(actionNavHistoryToRefundFragment())
+    }
+
+}
+
+private class HistoryItemAdapter(private val listener: RefundClickListener) :
+    Adapter<HistoryItemViewHolder>() {
+
+    private val items = ArrayList<HistoryItem>()
+
+    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): 
HistoryItemViewHolder {
+        val v =
+            
LayoutInflater.from(parent.context).inflate(R.layout.list_item_history, parent, 
false)
+        return HistoryItemViewHolder(v)
+    }
+
+    override fun getItemCount() = items.size
+
+    override fun onBindViewHolder(holder: HistoryItemViewHolder, position: 
Int) {
+        holder.bind(items[position])
+    }
+
+    fun setData(items: List<HistoryItem>) {
+        this.items.clear()
+        this.items.addAll(items)
+        this.notifyDataSetChanged()
+    }
+
+    private inner class HistoryItemViewHolder(private val v: View) : 
ViewHolder(v) {
+
+        private val orderSummaryView: TextView = 
v.findViewById(R.id.orderSummaryView)
+        private val orderAmountView: TextView = 
v.findViewById(R.id.orderAmountView)
+        private val orderTimeView: TextView = 
v.findViewById(R.id.orderTimeView)
+        private val orderIdView: TextView = v.findViewById(R.id.orderIdView)
+        private val refundButton: ImageButton = 
v.findViewById(R.id.refundButton)
+
+        fun bind(item: HistoryItem) {
+            orderSummaryView.text = item.summary
+            val amount = item.amount
+            @SuppressLint("SetTextI18n")
+            orderAmountView.text = "${amount.amount} ${amount.currency}"
+            orderIdView.text = v.context.getString(R.string.history_ref_no, 
item.orderId)
+            orderTimeView.text = item.time.toRelativeTime(v.context)
+            refundButton.setOnClickListener { listener.onRefundClicked(item) }
+        }
+
+    }
+
+}
diff --git 
a/merchant-terminal/src/main/java/net/taler/merchantpos/history/RefundFragment.kt
 
b/merchant-terminal/src/main/java/net/taler/merchantpos/history/RefundFragment.kt
new file mode 100644
index 0000000..1797cea
--- /dev/null
+++ 
b/merchant-terminal/src/main/java/net/taler/merchantpos/history/RefundFragment.kt
@@ -0,0 +1,99 @@
+/*
+ * 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.merchantpos.history
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.annotation.StringRes
+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.BaseTransientBottomBar.LENGTH_LONG
+import com.google.android.material.snackbar.Snackbar
+import kotlinx.android.synthetic.main.fragment_refund.*
+import net.taler.merchantpos.MainViewModel
+import net.taler.merchantpos.R
+import net.taler.merchantpos.fadeIn
+import net.taler.merchantpos.fadeOut
+import 
net.taler.merchantpos.history.RefundFragmentDirections.Companion.actionRefundFragmentToRefundUriFragment
+import net.taler.merchantpos.history.RefundResult.Error
+import net.taler.merchantpos.history.RefundResult.PastDeadline
+import net.taler.merchantpos.history.RefundResult.Success
+import net.taler.merchantpos.navigate
+
+class RefundFragment : Fragment() {
+
+    private val model: MainViewModel by activityViewModels()
+    private val refundManager by lazy { model.refundManager }
+
+    override fun onCreateView(
+        inflater: LayoutInflater, container: ViewGroup?,
+        savedInstanceState: Bundle?
+    ): View? {
+        return inflater.inflate(R.layout.fragment_refund, container, false)
+    }
+
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+        val item = refundManager.toBeRefunded ?: throw IllegalStateException()
+        amountInputView.setText(item.amount.amount)
+        currencyView.text = item.amount.currency
+        abortButton.setOnClickListener { findNavController().navigateUp() }
+        refundButton.setOnClickListener { onRefundButtonClicked(item) }
+
+        refundManager.refundResult.observe(viewLifecycleOwner, Observer { 
result ->
+            onRefundResultChanged(result)
+        })
+    }
+
+    private fun onRefundButtonClicked(item: HistoryItem) {
+        val inputAmount = amountInputView.text.toString().toDouble()
+        if (inputAmount > item.amount.amount.toDouble()) {
+            amountView.error = getString(R.string.refund_error_max_amount, 
item.amount.amount)
+            return
+        }
+        if (inputAmount <= 0.0) {
+            amountView.error = getString(R.string.refund_error_zero)
+            return
+        }
+        amountView.error = null
+        refundButton.fadeOut()
+        progressBar.fadeIn()
+        refundManager.refund(item, inputAmount, 
reasonInputView.text.toString())
+    }
+
+    private fun onRefundResultChanged(result: RefundResult?): Any = when 
(result) {
+        Error -> onError(R.string.refund_error_backend)
+        PastDeadline -> onError(R.string.refund_error_deadline)
+        is Success -> {
+            progressBar.fadeOut()
+            refundButton.fadeIn()
+            navigate(actionRefundFragmentToRefundUriFragment())
+        }
+        null -> { // no-op
+        }
+    }
+
+    private fun onError(@StringRes res: Int) {
+        Snackbar.make(view!!, res, LENGTH_LONG).show()
+        progressBar.fadeOut()
+        refundButton.fadeIn()
+    }
+
+}
diff --git 
a/merchant-terminal/src/main/java/net/taler/merchantpos/history/RefundManager.kt
 
b/merchant-terminal/src/main/java/net/taler/merchantpos/history/RefundManager.kt
new file mode 100644
index 0000000..270b3b8
--- /dev/null
+++ 
b/merchant-terminal/src/main/java/net/taler/merchantpos/history/RefundManager.kt
@@ -0,0 +1,111 @@
+/*
+ * 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.merchantpos.history
+
+import android.util.Log
+import androidx.annotation.UiThread
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import com.android.volley.Request.Method.POST
+import com.android.volley.RequestQueue
+import com.android.volley.Response.ErrorListener
+import com.android.volley.Response.Listener
+import net.taler.merchantpos.config.ConfigManager
+import net.taler.merchantpos.config.MerchantRequest
+import org.json.JSONObject
+
+sealed class RefundResult {
+    object Error : RefundResult()
+    object PastDeadline : RefundResult()
+    class Success(
+        val refundUri: String,
+        val item: HistoryItem,
+        val amount: Double,
+        val reason: String
+    ) : RefundResult()
+}
+
+class RefundManager(
+    private val configManager: ConfigManager,
+    private val queue: RequestQueue
+) {
+
+    var toBeRefunded: HistoryItem? = null
+        private set
+
+    private val mRefundResult = MutableLiveData<RefundResult>()
+    internal val refundResult: LiveData<RefundResult> = mRefundResult
+
+    @UiThread
+    internal fun startRefund(item: HistoryItem) {
+        toBeRefunded = item
+        mRefundResult.value = null
+    }
+
+    @UiThread
+    internal fun refund(item: HistoryItem, amount: Double, reason: String) {
+        val merchantConfig = configManager.merchantConfig!!
+        val refundRequest = mapOf(
+            "order_id" to item.orderId,
+            "refund" to "${item.amount.currency}:$amount",
+            "reason" to reason
+        )
+        val body = JSONObject(refundRequest)
+        val req = MerchantRequest(POST, merchantConfig, "refund", null, body,
+            Listener { onRefundResponse(it, item, amount, reason) },
+            ErrorListener { onRefundError() }
+        )
+        queue.add(req)
+    }
+
+    @UiThread
+    private fun onRefundResponse(
+        json: JSONObject,
+        item: HistoryItem,
+        amount: Double,
+        reason: String
+    ) {
+        if (!json.has("contract_terms")) {
+            Log.e("TEST", "json: $json")
+            onRefundError()
+            return
+        }
+
+        val contractTerms = json.getJSONObject("contract_terms")
+        val refundDeadline = if (contractTerms.has("refund_deadline")) {
+            contractTerms.getJSONObject("refund_deadline").getLong("t_ms")
+        } else null
+        val autoRefund = contractTerms.has("auto_refund")
+        val refundUri = json.getString("taler_refund_uri")
+
+        Log.e("TEST", "refundDeadline: $refundDeadline")
+        if (refundDeadline != null) Log.e(
+            "TEST",
+            "refundDeadline passed: ${System.currentTimeMillis() > 
refundDeadline}"
+        )
+        Log.e("TEST", "autoRefund: $autoRefund")
+        Log.e("TEST", "refundUri: $refundUri")
+
+        mRefundResult.value = RefundResult.Success(refundUri, item, amount, 
reason)
+    }
+
+    @UiThread
+    private fun onRefundError() {
+        mRefundResult.value = RefundResult.Error
+    }
+
+}
diff --git 
a/merchant-terminal/src/main/java/net/taler/merchantpos/history/RefundUriFragment.kt
 
b/merchant-terminal/src/main/java/net/taler/merchantpos/history/RefundUriFragment.kt
new file mode 100644
index 0000000..f2bd569
--- /dev/null
+++ 
b/merchant-terminal/src/main/java/net/taler/merchantpos/history/RefundUriFragment.kt
@@ -0,0 +1,65 @@
+/*
+ * 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.merchantpos.history
+
+import android.annotation.SuppressLint
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.activityViewModels
+import androidx.navigation.fragment.findNavController
+import kotlinx.android.synthetic.main.fragment_refund_uri.*
+import net.taler.merchantpos.MainViewModel
+import net.taler.merchantpos.NfcManager.Companion.hasNfc
+import net.taler.merchantpos.QrCodeManager.makeQrCode
+import net.taler.merchantpos.R
+
+class RefundUriFragment : Fragment() {
+
+    private val model: MainViewModel by activityViewModels()
+    private val refundManager by lazy { model.refundManager }
+
+    override fun onCreateView(
+        inflater: LayoutInflater, container: ViewGroup?,
+        savedInstanceState: Bundle?
+    ): View? {
+        return inflater.inflate(R.layout.fragment_refund_uri, container, false)
+    }
+
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+        super.onViewCreated(view, savedInstanceState)
+        val result = refundManager.refundResult.value
+        if (result !is RefundResult.Success) throw IllegalStateException()
+
+        refundQrcodeView.setImageBitmap(makeQrCode(result.refundUri))
+
+        val introRes =
+            if (hasNfc(requireContext())) R.string.refund_intro_nfc else 
R.string.refund_intro
+        refundIntroView.setText(introRes)
+
+        @SuppressLint("SetTextI18n")
+        refundAmountView.text = "${result.amount} 
${result.item.amount.currency}"
+
+        refundRefView.text =
+            getString(R.string.refund_order_ref, result.item.orderId, 
result.reason)
+
+        cancelRefundButton.setOnClickListener { 
findNavController().navigateUp() }
+    }
+
+}
diff --git 
a/merchant-terminal/src/main/java/net/taler/merchantpos/order/CategoriesFragment.kt
 
b/merchant-terminal/src/main/java/net/taler/merchantpos/order/CategoriesFragment.kt
new file mode 100644
index 0000000..34b97c0
--- /dev/null
+++ 
b/merchant-terminal/src/main/java/net/taler/merchantpos/order/CategoriesFragment.kt
@@ -0,0 +1,106 @@
+/*
+ * 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.merchantpos.order
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.View.INVISIBLE
+import android.view.ViewGroup
+import android.widget.Button
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.activityViewModels
+import androidx.lifecycle.Observer
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+import androidx.recyclerview.widget.RecyclerView.Adapter
+import kotlinx.android.synthetic.main.fragment_categories.*
+import net.taler.merchantpos.MainViewModel
+import net.taler.merchantpos.R
+import net.taler.merchantpos.order.CategoryAdapter.CategoryViewHolder
+
+interface CategorySelectionListener {
+    fun onCategorySelected(category: Category)
+}
+
+class CategoriesFragment : Fragment(), CategorySelectionListener {
+
+    private val viewModel: MainViewModel by activityViewModels()
+    private val orderManager by lazy { viewModel.orderManager }
+    private val adapter = CategoryAdapter(this)
+
+    override fun onCreateView(
+        inflater: LayoutInflater,
+        container: ViewGroup?,
+        savedInstanceState: Bundle?
+    ): View? {
+        return inflater.inflate(R.layout.fragment_categories, container, false)
+    }
+
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+        categoriesList.apply {
+            adapter = this@CategoriesFragment.adapter
+            layoutManager = LinearLayoutManager(requireContext())
+        }
+
+        orderManager.categories.observe(viewLifecycleOwner, Observer { 
categories ->
+            adapter.setItems(categories)
+            progressBar.visibility = INVISIBLE
+        })
+    }
+
+    override fun onCategorySelected(category: Category) {
+        orderManager.setCurrentCategory(category)
+    }
+
+}
+
+private class CategoryAdapter(
+    private val listener: CategorySelectionListener
+) : Adapter<CategoryViewHolder>() {
+
+    private val categories = ArrayList<Category>()
+
+    override fun getItemCount() = categories.size
+
+    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): 
CategoryViewHolder {
+        val view =
+            
LayoutInflater.from(parent.context).inflate(R.layout.list_item_category, 
parent, false)
+        return CategoryViewHolder(view)
+    }
+
+    override fun onBindViewHolder(holder: CategoryViewHolder, position: Int) {
+        holder.bind(categories[position])
+    }
+
+    fun setItems(items: List<Category>) {
+        categories.clear()
+        categories.addAll(items)
+        notifyDataSetChanged()
+    }
+
+    private inner class CategoryViewHolder(v: View) : 
RecyclerView.ViewHolder(v) {
+        private val button: Button = v.findViewById(R.id.button)
+
+        fun bind(category: Category) {
+            button.text = category.localizedName
+            button.isPressed = category.selected
+            button.setOnClickListener { listener.onCategorySelected(category) }
+        }
+    }
+
+}
diff --git 
a/merchant-terminal/src/main/java/net/taler/merchantpos/order/Definitions.kt 
b/merchant-terminal/src/main/java/net/taler/merchantpos/order/Definitions.kt
new file mode 100644
index 0000000..63eda17
--- /dev/null
+++ b/merchant-terminal/src/main/java/net/taler/merchantpos/order/Definitions.kt
@@ -0,0 +1,205 @@
+/*
+ * 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.merchantpos.order
+
+import androidx.core.os.LocaleListCompat
+import com.fasterxml.jackson.annotation.JsonIgnore
+import com.fasterxml.jackson.annotation.JsonInclude
+import com.fasterxml.jackson.annotation.JsonInclude.Include.NON_NULL
+import com.fasterxml.jackson.annotation.JsonProperty
+import net.taler.merchantpos.Amount
+import java.util.*
+import java.util.Locale.LanguageRange
+import kotlin.collections.ArrayList
+import kotlin.collections.HashMap
+
+data class Category(
+    val id: Int,
+    val name: String,
+    @JsonProperty("name_i18n")
+    val nameI18n: Map<String, String>?
+) {
+    var selected: Boolean = false
+    val localizedName: String get() = getLocalizedString(nameI18n, name)
+}
+
+@JsonInclude(NON_NULL)
+abstract class Product {
+    @get:JsonProperty("product_id")
+    abstract val productId: String?
+    abstract val description: String
+    @get:JsonProperty("description_i18n")
+    abstract val descriptionI18n: Map<String, String>?
+    abstract val price: String
+    @get:JsonProperty("delivery_location")
+    abstract val location: String?
+    abstract val image: String?
+    @get:JsonIgnore
+    val localizedDescription: String
+        get() = getLocalizedString(descriptionI18n, description)
+}
+
+data class ConfigProduct(
+    @JsonIgnore
+    val id: String = UUID.randomUUID().toString(),
+    override val productId: String?,
+    override val description: String,
+    override val descriptionI18n: Map<String, String>?,
+    override val price: String,
+    override val location: String?,
+    override val image: String?,
+    val categories: List<Int>,
+    @JsonIgnore
+    val quantity: Int = 0
+) : Product() {
+    val priceAsDouble by lazy { Amount.fromString(price).amount.toDouble() }
+
+    override fun equals(other: Any?) = other is ConfigProduct && id == other.id
+    override fun hashCode() = id.hashCode()
+}
+
+data class ContractProduct(
+    override val productId: String?,
+    override val description: String,
+    override val descriptionI18n: Map<String, String>?,
+    override val price: String,
+    override val location: String?,
+    override val image: String?,
+    val quantity: Int
+) : Product() {
+    constructor(product: ConfigProduct) : this(
+        product.productId,
+        product.description,
+        product.descriptionI18n,
+        product.price,
+        product.location,
+        product.image,
+        product.quantity
+    )
+}
+
+private fun getLocalizedString(map: Map<String, String>?, default: String): 
String {
+    // just return the default, if it is the only element
+    if (map == null) return default
+    // create a priority list of language ranges from system locales
+    val locales = LocaleListCompat.getDefault()
+    val priorityList = ArrayList<LanguageRange>(locales.size())
+    for (i in 0 until locales.size()) {
+        priorityList.add(LanguageRange(locales[i].toLanguageTag()))
+    }
+    // create a list of locales available in the given map
+    val availableLocales = map.keys.mapNotNull {
+        if (it == "_") return@mapNotNull null
+        val list = it.split("_")
+        when (list.size) {
+            1 -> Locale(list[0])
+            2 -> Locale(list[0], list[1])
+            3 -> Locale(list[0], list[1], list[2])
+            else -> null
+        }
+    }
+    val match = Locale.lookup(priorityList, availableLocales)
+    return match?.toString()?.let { map[it] } ?: default
+}
+
+data class Order(val id: Int, val availableCategories: Map<Int, Category>) {
+    val products = ArrayList<ConfigProduct>()
+    val title: String = id.toString()
+    val summary: String
+        get() {
+            if (products.size == 1) return products[0].description
+            return getCategoryQuantities().map { (category: Category, 
quantity: Int) ->
+                "$quantity x ${category.localizedName}"
+            }.joinToString()
+        }
+    val total: Double
+        get() {
+            var total = 0.0
+            products.forEach { product ->
+                val price = product.priceAsDouble
+                total += price * product.quantity
+            }
+            return total
+        }
+    val totalAsString: String
+        get() = String.format("%.2f", total)
+
+    operator fun plus(product: ConfigProduct): Order {
+        val i = products.indexOf(product)
+        if (i == -1) {
+            products.add(product.copy(quantity = 1))
+        } else {
+            val quantity = products[i].quantity
+            products[i] = products[i].copy(quantity = quantity + 1)
+        }
+        return this
+    }
+
+    operator fun minus(product: ConfigProduct): Order {
+        val i = products.indexOf(product)
+        if (i == -1) return this
+        val quantity = products[i].quantity
+        if (quantity <= 1) {
+            products.remove(product)
+        } else {
+            products[i] = products[i].copy(quantity = quantity - 1)
+        }
+        return this
+    }
+
+    private fun getCategoryQuantities(): HashMap<Category, Int> {
+        val categories = HashMap<Category, Int>()
+        products.forEach { product ->
+            val categoryId = product.categories[0]
+            val category = availableCategories.getValue(categoryId)
+            val oldQuantity = categories[category] ?: 0
+            categories[category] = oldQuantity + product.quantity
+        }
+        return categories
+    }
+
+    /**
+     * Returns a map of i18n summaries for each locale present in *all* given 
[Category]s
+     * or null if there's no locale that fulfills this criteria.
+     */
+    val summaryI18n: Map<String, String>?
+        get() {
+            if (products.size == 1) return products[0].descriptionI18n
+            val categoryQuantities = getCategoryQuantities()
+            // get all available locales
+            val availableLocales = categoryQuantities.mapNotNull { (category, 
_) ->
+                val nameI18n = category.nameI18n
+                // if one category doesn't have locales, we can return null 
here already
+                nameI18n?.keys ?: return null
+            }.flatten().toHashSet()
+            // remove all locales not supported by all categories
+            categoryQuantities.forEach { (category, _) ->
+                // category.nameI18n should be non-null now
+                availableLocales.retainAll(category.nameI18n!!.keys)
+                if (availableLocales.isEmpty()) return null
+            }
+            return availableLocales.map { locale ->
+                Pair(
+                    locale, categoryQuantities.map { (category, quantity) ->
+                        // category.nameI18n should be non-null now
+                        "$quantity x ${category.nameI18n!![locale]}"
+                    }.joinToString()
+                )
+            }.toMap()
+        }
+
+}
diff --git 
a/merchant-terminal/src/main/java/net/taler/merchantpos/order/LiveOrder.kt 
b/merchant-terminal/src/main/java/net/taler/merchantpos/order/LiveOrder.kt
new file mode 100644
index 0000000..ff6061a
--- /dev/null
+++ b/merchant-terminal/src/main/java/net/taler/merchantpos/order/LiveOrder.kt
@@ -0,0 +1,109 @@
+/*
+ * 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.merchantpos.order
+
+import androidx.annotation.UiThread
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.Transformations
+import net.taler.merchantpos.CombinedLiveData
+import net.taler.merchantpos.order.RestartState.DISABLED
+import net.taler.merchantpos.order.RestartState.ENABLED
+import net.taler.merchantpos.order.RestartState.UNDO
+
+internal enum class RestartState { ENABLED, DISABLED, UNDO }
+
+internal interface LiveOrder {
+    val order: LiveData<Order>
+    val orderTotal: LiveData<Double>
+    val restartState: LiveData<RestartState>
+    val modifyOrderAllowed: LiveData<Boolean>
+    val lastAddedProduct: ConfigProduct?
+    val selectedProductKey: String?
+    fun restartOrUndo()
+    fun selectOrderLine(product: ConfigProduct?)
+    fun increaseSelectedOrderLine()
+    fun decreaseSelectedOrderLine()
+}
+
+internal class MutableLiveOrder(
+    val id: Int,
+    private val productsByCategory: HashMap<Category, ArrayList<ConfigProduct>>
+) : LiveOrder {
+    private val availableCategories: Map<Int, Category>
+        get() = productsByCategory.keys.map { it.id to it }.toMap()
+    override val order: MutableLiveData<Order> = MutableLiveData(Order(id, 
availableCategories))
+    override val orderTotal: LiveData<Double> = Transformations.map(order) { 
it.total }
+    override val restartState = MutableLiveData(DISABLED)
+    private val selectedOrderLine = MutableLiveData<ConfigProduct>()
+    override val selectedProductKey: String?
+        get() = selectedOrderLine.value?.id
+    override val modifyOrderAllowed =
+        CombinedLiveData(restartState, selectedOrderLine) { restartState, 
selectedOrderLine ->
+            restartState != DISABLED && selectedOrderLine != null
+        }
+    override var lastAddedProduct: ConfigProduct? = null
+    private var undoOrder: Order? = null
+
+    @UiThread
+    internal fun addProduct(product: ConfigProduct) {
+        lastAddedProduct = product
+        order.value = order.value!! + product
+        restartState.value = ENABLED
+    }
+
+    @UiThread
+    internal fun removeProduct(product: ConfigProduct) {
+        val modifiedOrder = order.value!! - product
+        order.value = modifiedOrder
+        restartState.value = if (modifiedOrder.products.isEmpty()) DISABLED 
else ENABLED
+    }
+
+    @UiThread
+    internal fun isEmpty() = order.value!!.products.isEmpty()
+
+    @UiThread
+    override fun restartOrUndo() {
+        if (restartState.value == UNDO) {
+            order.value = undoOrder
+            restartState.value = ENABLED
+            undoOrder = null
+        } else {
+            undoOrder = order.value
+            order.value = Order(id, availableCategories)
+            restartState.value = UNDO
+        }
+    }
+
+    @UiThread
+    override fun selectOrderLine(product: ConfigProduct?) {
+        selectedOrderLine.value = product
+    }
+
+    @UiThread
+    override fun increaseSelectedOrderLine() {
+        val orderLine = selectedOrderLine.value ?: throw 
IllegalStateException()
+        addProduct(orderLine)
+    }
+
+    @UiThread
+    override fun decreaseSelectedOrderLine() {
+        val orderLine = selectedOrderLine.value ?: throw 
IllegalStateException()
+        removeProduct(orderLine)
+    }
+
+}
diff --git 
a/merchant-terminal/src/main/java/net/taler/merchantpos/order/OrderFragment.kt 
b/merchant-terminal/src/main/java/net/taler/merchantpos/order/OrderFragment.kt
new file mode 100644
index 0000000..49f7cf2
--- /dev/null
+++ 
b/merchant-terminal/src/main/java/net/taler/merchantpos/order/OrderFragment.kt
@@ -0,0 +1,115 @@
+/*
+ * 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.merchantpos.order
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+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.transition.TransitionManager.beginDelayedTransition
+import kotlinx.android.synthetic.main.fragment_order.*
+import net.taler.merchantpos.MainViewModel
+import net.taler.merchantpos.R
+import net.taler.merchantpos.navigate
+import 
net.taler.merchantpos.order.OrderFragmentDirections.Companion.actionGlobalConfigFetcher
+import 
net.taler.merchantpos.order.OrderFragmentDirections.Companion.actionOrderToMerchantSettings
+import 
net.taler.merchantpos.order.OrderFragmentDirections.Companion.actionOrderToProcessPayment
+import net.taler.merchantpos.order.RestartState.ENABLED
+import net.taler.merchantpos.order.RestartState.UNDO
+
+class OrderFragment : Fragment() {
+
+    private val viewModel: MainViewModel by activityViewModels()
+    private val orderManager by lazy { viewModel.orderManager }
+    private val paymentManager by lazy { viewModel.paymentManager }
+
+    override fun onCreateView(
+        inflater: LayoutInflater,
+        container: ViewGroup?,
+        savedInstanceState: Bundle?
+    ): View? {
+        return inflater.inflate(R.layout.fragment_order, container, false)
+    }
+
+    override fun onActivityCreated(savedInstanceState: Bundle?) {
+        super.onActivityCreated(savedInstanceState)
+        orderManager.currentOrderId.observe(viewLifecycleOwner, Observer { 
orderId ->
+            val liveOrder = orderManager.getOrder(orderId)
+            onOrderSwitched(orderId, liveOrder)
+            // add a new OrderStateFragment for each order
+            // as switching its internals (like we do here) would be too messy
+            childFragmentManager.beginTransaction()
+                .replace(R.id.fragment1, OrderStateFragment())
+                .commit()
+        })
+    }
+
+    override fun onStart() {
+        super.onStart()
+        if (!viewModel.configManager.config.isValid()) {
+            actionOrderToMerchantSettings().navigate(findNavController())
+        } else if (viewModel.configManager.merchantConfig?.currency == null) {
+            actionGlobalConfigFetcher().navigate(findNavController())
+        }
+    }
+
+    private fun onOrderSwitched(orderId: Int, liveOrder: LiveOrder) {
+        // order title
+        liveOrder.order.observe(viewLifecycleOwner, Observer { order ->
+            activity?.title = getString(R.string.order_label_title, 
order.title)
+        })
+        // restart button
+        restartButton.setOnClickListener { liveOrder.restartOrUndo() }
+        liveOrder.restartState.observe(viewLifecycleOwner, Observer { state ->
+            beginDelayedTransition(view as ViewGroup)
+            if (state == UNDO) {
+                restartButton.setText(R.string.order_undo)
+                restartButton.isEnabled = true
+                completeButton.isEnabled = false
+            } else {
+                restartButton.setText(R.string.order_restart)
+                restartButton.isEnabled = state == ENABLED
+                completeButton.isEnabled = state == ENABLED
+            }
+        })
+        // -1 and +1 buttons
+        liveOrder.modifyOrderAllowed.observe(viewLifecycleOwner, Observer { 
allowed ->
+            minusButton.isEnabled = allowed
+            plusButton.isEnabled = allowed
+        })
+        minusButton.setOnClickListener { liveOrder.decreaseSelectedOrderLine() 
}
+        plusButton.setOnClickListener { liveOrder.increaseSelectedOrderLine() }
+        // previous and next button
+        prevButton.isEnabled = orderManager.hasPreviousOrder(orderId)
+        orderManager.hasNextOrder(orderId).observe(viewLifecycleOwner, 
Observer { hasNextOrder ->
+            nextButton.isEnabled = hasNextOrder
+        })
+        prevButton.setOnClickListener { orderManager.previousOrder() }
+        nextButton.setOnClickListener { orderManager.nextOrder() }
+        // complete button
+        completeButton.setOnClickListener {
+            val order = liveOrder.order.value ?: return@setOnClickListener
+            paymentManager.createPayment(order)
+            actionOrderToProcessPayment().navigate(findNavController())
+        }
+    }
+
+}
diff --git 
a/merchant-terminal/src/main/java/net/taler/merchantpos/order/OrderManager.kt 
b/merchant-terminal/src/main/java/net/taler/merchantpos/order/OrderManager.kt
new file mode 100644
index 0000000..48ddc57
--- /dev/null
+++ 
b/merchant-terminal/src/main/java/net/taler/merchantpos/order/OrderManager.kt
@@ -0,0 +1,196 @@
+/*
+ * 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.merchantpos.order
+
+import android.content.Context
+import android.util.Log
+import androidx.annotation.UiThread
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.Transformations.map
+import com.fasterxml.jackson.core.type.TypeReference
+import com.fasterxml.jackson.databind.ObjectMapper
+import net.taler.merchantpos.Amount.Companion.fromString
+import net.taler.merchantpos.R
+import net.taler.merchantpos.config.ConfigurationReceiver
+import net.taler.merchantpos.order.RestartState.ENABLED
+import org.json.JSONObject
+
+class OrderManager(
+    private val context: Context,
+    private val mapper: ObjectMapper
+) : ConfigurationReceiver {
+
+    companion object {
+        val TAG = OrderManager::class.java.simpleName
+    }
+
+    private var orderCounter: Int = 0
+    private val mCurrentOrderId = MutableLiveData<Int>()
+    internal val currentOrderId: LiveData<Int> = mCurrentOrderId
+
+    private val productsByCategory = HashMap<Category, 
ArrayList<ConfigProduct>>()
+
+    private val orders = LinkedHashMap<Int, MutableLiveOrder>()
+
+    private val mProducts = MutableLiveData<List<ConfigProduct>>()
+    internal val products: LiveData<List<ConfigProduct>> = mProducts
+
+    private val mCategories = MutableLiveData<List<Category>>()
+    internal val categories: LiveData<List<Category>> = mCategories
+
+    override suspend fun onConfigurationReceived(json: JSONObject, currency: 
String): String? {
+        // parse categories
+        val categoriesStr = json.getJSONArray("categories").toString()
+        val categoriesType = object : TypeReference<List<Category>>() {}
+        val categories: List<Category> = mapper.readValue(categoriesStr, 
categoriesType)
+        if (categories.isEmpty()) {
+            Log.e(TAG, "No valid category found.")
+            return context.getString(R.string.config_error_category)
+        }
+        // pre-select the first category
+        categories[0].selected = true
+
+        // parse products (live data gets updated in setCurrentCategory())
+        val productsStr = json.getJSONArray("products").toString()
+        val productsType = object : TypeReference<List<ConfigProduct>>() {}
+        val products: List<ConfigProduct> = mapper.readValue(productsStr, 
productsType)
+
+        // group products by categories
+        productsByCategory.clear()
+        products.forEach { product ->
+            val productCurrency = fromString(product.price).currency
+            if (productCurrency != currency) {
+                Log.e(TAG, "Product $product has currency $productCurrency, 
$currency expected")
+                return context.getString(
+                    R.string.config_error_currency, product.description, 
productCurrency, currency
+                )
+            }
+            product.categories.forEach { categoryId ->
+                val category = categories.find { it.id == categoryId }
+                if (category == null) {
+                    Log.e(TAG, "Product $product has unknown category 
$categoryId")
+                    return context.getString(
+                        R.string.config_error_product_category_id, 
product.description, categoryId
+                    )
+                }
+                if (productsByCategory.containsKey(category)) {
+                    productsByCategory[category]?.add(product)
+                } else {
+                    productsByCategory[category] = 
ArrayList<ConfigProduct>().apply { add(product) }
+                }
+            }
+        }
+        return if (productsByCategory.size > 0) {
+            mCategories.postValue(categories)
+            mProducts.postValue(productsByCategory[categories[0]])
+            // Initialize first empty order, note this won't work when 
updating config mid-flight
+            if (orders.isEmpty()) {
+                val id = orderCounter++
+                orders[id] = MutableLiveOrder(id, productsByCategory)
+                mCurrentOrderId.postValue(id)
+            }
+            null // success, no error string
+        } else context.getString(R.string.config_error_product_zero)
+    }
+
+    @UiThread
+    internal fun getOrder(orderId: Int): LiveOrder {
+        return orders[orderId] ?: throw IllegalArgumentException()
+    }
+
+    @UiThread
+    internal fun nextOrder() {
+        val currentId = currentOrderId.value!!
+        var foundCurrentOrder = false
+        var nextId: Int? = null
+        for (orderId in orders.keys) {
+            if (foundCurrentOrder) {
+                nextId = orderId
+                break
+            }
+            if (orderId == currentId) foundCurrentOrder = true
+        }
+        if (nextId == null) {
+            nextId = orderCounter++
+            orders[nextId] = MutableLiveOrder(nextId, productsByCategory)
+        }
+        val currentOrder = order(currentId)
+        if (currentOrder.isEmpty()) orders.remove(currentId)
+        else currentOrder.lastAddedProduct = null  // not needed anymore and 
it would get selected
+        mCurrentOrderId.value = nextId
+    }
+
+    @UiThread
+    internal fun previousOrder() {
+        val currentId = currentOrderId.value!!
+        var previousId: Int? = null
+        var foundCurrentOrder = false
+        for (orderId in orders.keys) {
+            if (orderId == currentId) {
+                foundCurrentOrder = true
+                break
+            }
+            previousId = orderId
+        }
+        if (previousId == null || !foundCurrentOrder) {
+            throw AssertionError("Could not find previous order for 
$currentId")
+        }
+        val currentOrder = order(currentId)
+        // remove current order if empty, or lastAddedProduct as it is not 
needed anymore
+        // and would get selected when navigating back instead of last 
selection
+        if (currentOrder.isEmpty()) orders.remove(currentId)
+        else currentOrder.lastAddedProduct = null
+        mCurrentOrderId.value = previousId
+    }
+
+    fun hasPreviousOrder(currentOrderId: Int): Boolean {
+        return currentOrderId != orders.keys.first()
+    }
+
+    fun hasNextOrder(currentOrderId: Int) = 
map(order(currentOrderId).restartState) { state ->
+        state == ENABLED || currentOrderId != orders.keys.last()
+    }
+
+    internal fun setCurrentCategory(category: Category) {
+        val newCategories = categories.value?.apply {
+            forEach { if (it.selected) it.selected = false }
+            category.selected = true
+        }
+        mCategories.postValue(newCategories)
+        mProducts.postValue(productsByCategory[category])
+    }
+
+    @UiThread
+    internal fun addProduct(orderId: Int, product: ConfigProduct) {
+        order(orderId).addProduct(product)
+    }
+
+    @UiThread
+    internal fun onOrderPaid(orderId: Int) {
+        if (currentOrderId.value == orderId) {
+            if (hasPreviousOrder(orderId)) previousOrder()
+            else nextOrder()
+        }
+        orders.remove(orderId)
+    }
+
+    private fun order(orderId: Int): MutableLiveOrder {
+        return orders[orderId] ?: throw IllegalStateException()
+    }
+
+}
diff --git 
a/merchant-terminal/src/main/java/net/taler/merchantpos/order/OrderStateFragment.kt
 
b/merchant-terminal/src/main/java/net/taler/merchantpos/order/OrderStateFragment.kt
new file mode 100644
index 0000000..1b70016
--- /dev/null
+++ 
b/merchant-terminal/src/main/java/net/taler/merchantpos/order/OrderStateFragment.kt
@@ -0,0 +1,213 @@
+/*
+ * 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.merchantpos.order
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.MotionEvent
+import android.view.View
+import android.view.ViewGroup
+import android.widget.TextView
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.activityViewModels
+import androidx.lifecycle.Observer
+import androidx.recyclerview.selection.ItemDetailsLookup
+import androidx.recyclerview.selection.ItemKeyProvider
+import androidx.recyclerview.selection.SelectionPredicates
+import androidx.recyclerview.selection.SelectionTracker
+import androidx.recyclerview.selection.StorageStrategy
+import androidx.recyclerview.widget.AsyncListDiffer
+import androidx.recyclerview.widget.DiffUtil
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+import androidx.recyclerview.widget.RecyclerView.Adapter
+import androidx.recyclerview.widget.RecyclerView.ViewHolder
+import kotlinx.android.synthetic.main.fragment_order_state.*
+import net.taler.merchantpos.MainViewModel
+import net.taler.merchantpos.R
+import net.taler.merchantpos.fadeIn
+import net.taler.merchantpos.fadeOut
+import net.taler.merchantpos.order.OrderAdapter.OrderLineLookup
+import net.taler.merchantpos.order.OrderAdapter.OrderViewHolder
+
+class OrderStateFragment : Fragment() {
+
+    private val viewModel: MainViewModel by activityViewModels()
+    private val orderManager by lazy { viewModel.orderManager }
+    private val liveOrder by lazy { 
orderManager.getOrder(orderManager.currentOrderId.value!!) }
+    private val adapter = OrderAdapter()
+    private var tracker: SelectionTracker<String>? = null
+
+    override fun onCreateView(
+        inflater: LayoutInflater,
+        container: ViewGroup?,
+        savedInstanceState: Bundle?
+    ): View? {
+        return inflater.inflate(R.layout.fragment_order_state, container, 
false)
+    }
+
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+        orderList.apply {
+            adapter = this@OrderStateFragment.adapter
+            layoutManager = LinearLayoutManager(requireContext())
+        }
+        val detailsLookup = OrderLineLookup(orderList)
+        val tracker = SelectionTracker.Builder(
+            "order-selection-id",
+            orderList,
+            adapter.keyProvider,
+            detailsLookup,
+            StorageStrategy.createStringStorage()
+        ).withSelectionPredicate(
+            SelectionPredicates.createSelectSingleAnything()
+        ).build()
+        savedInstanceState?.let { tracker.onRestoreInstanceState(it) }
+        adapter.tracker = tracker
+        this.tracker = tracker
+        if (savedInstanceState == null) {
+            // select last selected order line when re-creating this fragment
+            // do it before attaching the tracker observer
+            liveOrder.selectedProductKey?.let { tracker.select(it) }
+        }
+        tracker.addObserver(object : 
SelectionTracker.SelectionObserver<String>() {
+            override fun onItemStateChanged(key: String, selected: Boolean) {
+                super.onItemStateChanged(key, selected)
+                val item = if (selected) adapter.getItemByKey(key) else null
+                liveOrder.selectOrderLine(item)
+            }
+        })
+        liveOrder.order.observe(viewLifecycleOwner, Observer { order ->
+            onOrderChanged(order, tracker)
+        })
+        liveOrder.orderTotal.observe(viewLifecycleOwner, Observer { orderTotal 
->
+            if (orderTotal == 0.0) {
+                totalView.fadeOut()
+                totalView.text = null
+            } else {
+                val currency = viewModel.configManager.merchantConfig?.currency
+                totalView.text = getString(R.string.order_total, orderTotal, 
currency)
+                totalView.fadeIn()
+            }
+        })
+    }
+
+    override fun onSaveInstanceState(outState: Bundle) {
+        super.onSaveInstanceState(outState)
+        tracker?.onSaveInstanceState(outState)
+    }
+
+    private fun onOrderChanged(order: Order, tracker: 
SelectionTracker<String>) {
+        adapter.setItems(order.products) {
+            liveOrder.lastAddedProduct?.let {
+                val position = adapter.findPosition(it)
+                if (position >= 0) {
+                    // orderList can be null m(
+                    orderList?.scrollToPosition(position)
+                    orderList?.post { this.tracker?.select(it.id) }
+                }
+            }
+            // workaround for bug: SelectionObserver doesn't update when 
removing selected item
+            if (tracker.hasSelection()) {
+                val key = tracker.selection.first()
+                val product = order.products.find { it.id == key }
+                if (product == null) tracker.clearSelection()
+            }
+        }
+    }
+
+}
+
+private class OrderAdapter : Adapter<OrderViewHolder>() {
+
+    lateinit var tracker: SelectionTracker<String>
+    val keyProvider = OrderKeyProvider()
+    private val itemCallback = object : DiffUtil.ItemCallback<ConfigProduct>() 
{
+        override fun areItemsTheSame(oldItem: ConfigProduct, newItem: 
ConfigProduct): Boolean {
+            return oldItem == newItem
+        }
+
+        override fun areContentsTheSame(oldItem: ConfigProduct, newItem: 
ConfigProduct): Boolean {
+            return oldItem.quantity == newItem.quantity
+        }
+    }
+    private val differ = AsyncListDiffer(this, itemCallback)
+
+    override fun getItemCount() = differ.currentList.size
+
+    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): 
OrderViewHolder {
+        val view =
+            
LayoutInflater.from(parent.context).inflate(R.layout.list_item_order, parent, 
false)
+        return OrderViewHolder(view)
+    }
+
+    override fun onBindViewHolder(holder: OrderViewHolder, position: Int) {
+        val item = getItem(position)!!
+        holder.bind(item, tracker.isSelected(item.id))
+    }
+
+    fun setItems(items: List<ConfigProduct>, commitCallback: () -> Unit) {
+        // toMutableList() is needed for some reason, otherwise doesn't update 
adapter
+        differ.submitList(items.toMutableList(), commitCallback)
+    }
+
+    fun getItem(position: Int): ConfigProduct? = differ.currentList[position]
+
+    fun getItemByKey(key: String): ConfigProduct? {
+        return differ.currentList.find { it.id == key }
+    }
+
+    fun findPosition(product: ConfigProduct): Int {
+        return differ.currentList.indexOf(product)
+    }
+
+    private inner class OrderViewHolder(private val v: View) : ViewHolder(v) {
+        private val quantity: TextView = v.findViewById(R.id.quantity)
+        private val name: TextView = v.findViewById(R.id.name)
+        private val price: TextView = v.findViewById(R.id.price)
+
+        fun bind(product: ConfigProduct, selected: Boolean) {
+            v.isActivated = selected
+            quantity.text = product.quantity.toString()
+            name.text = product.localizedDescription
+            price.text = String.format("%.2f", product.priceAsDouble * 
product.quantity)
+        }
+    }
+
+    private inner class OrderKeyProvider : 
ItemKeyProvider<String>(SCOPE_MAPPED) {
+        override fun getKey(position: Int) = getItem(position)!!.id
+        override fun getPosition(key: String): Int {
+            return differ.currentList.indexOfFirst { it.id == key }
+        }
+    }
+
+    internal class OrderLineLookup(private val list: RecyclerView) : 
ItemDetailsLookup<String>() {
+        override fun getItemDetails(e: MotionEvent): ItemDetails<String>? {
+            list.findChildViewUnder(e.x, e.y)?.let { view ->
+                val holder = list.getChildViewHolder(view)
+                val adapter = list.adapter as OrderAdapter
+                val position = holder.adapterPosition
+                return object : ItemDetails<String>() {
+                    override fun getPosition(): Int = position
+                    override fun getSelectionKey(): String = 
adapter.keyProvider.getKey(position)
+                    override fun inSelectionHotspot(e: MotionEvent) = true
+                }
+            }
+            return null
+        }
+    }
+
+}
diff --git 
a/merchant-terminal/src/main/java/net/taler/merchantpos/order/ProductsFragment.kt
 
b/merchant-terminal/src/main/java/net/taler/merchantpos/order/ProductsFragment.kt
new file mode 100644
index 0000000..4704ad0
--- /dev/null
+++ 
b/merchant-terminal/src/main/java/net/taler/merchantpos/order/ProductsFragment.kt
@@ -0,0 +1,111 @@
+/*
+ * 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.merchantpos.order
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.View.INVISIBLE
+import android.view.ViewGroup
+import android.widget.TextView
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.activityViewModels
+import androidx.lifecycle.Observer
+import androidx.recyclerview.widget.GridLayoutManager
+import androidx.recyclerview.widget.RecyclerView.Adapter
+import androidx.recyclerview.widget.RecyclerView.ViewHolder
+import kotlinx.android.synthetic.main.fragment_products.*
+import net.taler.merchantpos.MainViewModel
+import net.taler.merchantpos.R
+import net.taler.merchantpos.order.ProductAdapter.ProductViewHolder
+
+interface ProductSelectionListener {
+    fun onProductSelected(product: ConfigProduct)
+}
+
+class ProductsFragment : Fragment(), ProductSelectionListener {
+
+    private val viewModel: MainViewModel by activityViewModels()
+    private val orderManager by lazy { viewModel.orderManager }
+    private val adapter = ProductAdapter(this)
+
+    override fun onCreateView(
+        inflater: LayoutInflater,
+        container: ViewGroup?,
+        savedInstanceState: Bundle?
+    ): View? {
+        return inflater.inflate(R.layout.fragment_products, container, false)
+    }
+
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+        productsList.apply {
+            adapter = this@ProductsFragment.adapter
+            layoutManager = GridLayoutManager(requireContext(), 3)
+        }
+
+        orderManager.products.observe(viewLifecycleOwner, Observer { products 
->
+            if (products == null) {
+                adapter.setItems(emptyList())
+            } else {
+                adapter.setItems(products)
+            }
+            progressBar.visibility = INVISIBLE
+        })
+    }
+
+    override fun onProductSelected(product: ConfigProduct) {
+        orderManager.addProduct(orderManager.currentOrderId.value!!, product)
+    }
+
+}
+
+private class ProductAdapter(
+    private val listener: ProductSelectionListener
+) : Adapter<ProductViewHolder>() {
+
+    private val products = ArrayList<ConfigProduct>()
+
+    override fun getItemCount() = products.size
+
+    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): 
ProductViewHolder {
+        val view =
+            
LayoutInflater.from(parent.context).inflate(R.layout.list_item_product, parent, 
false)
+        return ProductViewHolder(view)
+    }
+
+    override fun onBindViewHolder(holder: ProductViewHolder, position: Int) {
+        holder.bind(products[position])
+    }
+
+    fun setItems(items: List<ConfigProduct>) {
+        products.clear()
+        products.addAll(items)
+        notifyDataSetChanged()
+    }
+
+    private inner class ProductViewHolder(private val v: View) : ViewHolder(v) 
{
+        private val name: TextView = v.findViewById(R.id.name)
+        private val price: TextView = v.findViewById(R.id.price)
+
+        fun bind(product: ConfigProduct) {
+            name.text = product.localizedDescription
+            price.text = product.priceAsDouble.toString()
+            v.setOnClickListener { listener.onProductSelected(product) }
+        }
+    }
+
+}
diff --git 
a/merchant-terminal/src/main/java/net/taler/merchantpos/payment/Payment.kt 
b/merchant-terminal/src/main/java/net/taler/merchantpos/payment/Payment.kt
new file mode 100644
index 0000000..b7e4a4b
--- /dev/null
+++ b/merchant-terminal/src/main/java/net/taler/merchantpos/payment/Payment.kt
@@ -0,0 +1,29 @@
+/*
+ * 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.merchantpos.payment
+
+import net.taler.merchantpos.order.Order
+
+data class Payment(
+    val order: Order,
+    val summary: String,
+    val currency: String,
+    val orderId: String? = null,
+    val talerPayUri: String? = null,
+    val paid: Boolean = false,
+    val error: Boolean = false
+)
diff --git 
a/merchant-terminal/src/main/java/net/taler/merchantpos/payment/PaymentManager.kt
 
b/merchant-terminal/src/main/java/net/taler/merchantpos/payment/PaymentManager.kt
new file mode 100644
index 0000000..7f15816
--- /dev/null
+++ 
b/merchant-terminal/src/main/java/net/taler/merchantpos/payment/PaymentManager.kt
@@ -0,0 +1,154 @@
+/*
+ * 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.merchantpos.payment
+
+import android.os.CountDownTimer
+import android.util.Log
+import androidx.annotation.UiThread
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import com.android.volley.Request.Method.GET
+import com.android.volley.Request.Method.POST
+import com.android.volley.RequestQueue
+import com.android.volley.Response.ErrorListener
+import com.android.volley.Response.Listener
+import com.android.volley.VolleyError
+import com.fasterxml.jackson.databind.ObjectMapper
+import net.taler.merchantpos.config.ConfigManager
+import net.taler.merchantpos.config.MerchantRequest
+import net.taler.merchantpos.order.ContractProduct
+import net.taler.merchantpos.order.Order
+import org.json.JSONArray
+import org.json.JSONObject
+import java.net.URLEncoder
+import java.util.concurrent.TimeUnit.MINUTES
+import java.util.concurrent.TimeUnit.SECONDS
+
+private val TIMEOUT = MINUTES.toMillis(2)
+private val CHECK_INTERVAL = SECONDS.toMillis(1)
+private const val FULFILLMENT_PREFIX = "taler://fulfillment-success/"
+
+class PaymentManager(
+    private val configManager: ConfigManager,
+    private val queue: RequestQueue,
+    private val mapper: ObjectMapper
+) {
+
+    companion object {
+        val TAG = PaymentManager::class.java.simpleName
+    }
+
+    private val mPayment = MutableLiveData<Payment>()
+    val payment: LiveData<Payment> = mPayment
+
+    private val checkTimer = object : CountDownTimer(TIMEOUT, CHECK_INTERVAL) {
+        override fun onTick(millisUntilFinished: Long) {
+            val orderId = payment.value?.orderId
+            if (orderId == null) cancel()
+            else checkPayment(orderId)
+        }
+
+        override fun onFinish() {
+            payment.value?.copy(error = true)?.let { mPayment.value = it }
+        }
+    }
+
+    @UiThread
+    fun createPayment(order: Order) {
+        val merchantConfig = configManager.merchantConfig!!
+
+        val currency = merchantConfig.currency!!
+        val amount = "$currency:${order.totalAsString}"
+        val summary = order.summary
+        val summaryI18n = order.summaryI18n
+
+        mPayment.value = Payment(order, summary, currency)
+
+        val fulfillmentId = "${System.currentTimeMillis()}-${order.hashCode()}"
+        val fulfillmentUrl =
+            "${FULFILLMENT_PREFIX}${URLEncoder.encode(summary, 
"UTF-8")}#$fulfillmentId"
+        val body = JSONObject().apply {
+            put("order", JSONObject().apply {
+                put("amount", amount)
+                put("summary", summary)
+                if (summaryI18n != null) put("summary_i18n", order.summaryI18n)
+                // fulfillment_url needs to be unique per order
+                put("fulfillment_url", fulfillmentUrl)
+                put("instance", "default")
+                put("products", order.getProductsJson())
+            })
+        }
+
+        Log.d(TAG, body.toString(4))
+
+        val req = MerchantRequest(POST, merchantConfig, "order", null, body,
+            Listener { onOrderCreated(it) },
+            ErrorListener { onNetworkError(it) }
+        )
+        queue.add(req)
+    }
+
+    private fun Order.getProductsJson(): JSONArray {
+        val contractProducts = products.map { ContractProduct(it) }
+        val productsStr = mapper.writeValueAsString(contractProducts)
+        return JSONArray(productsStr)
+    }
+
+    private fun onOrderCreated(orderResponse: JSONObject) {
+        val orderId = orderResponse.getString("order_id")
+        mPayment.value = mPayment.value!!.copy(orderId = orderId)
+        checkTimer.start()
+    }
+
+    private fun checkPayment(orderId: String) {
+        val merchantConfig = configManager.merchantConfig!!
+        val params = mapOf(
+            "order_id" to orderId,
+            "instance" to merchantConfig.instance
+        )
+
+        val req = MerchantRequest(GET, merchantConfig, "check-payment", 
params, null,
+            Listener { onPaymentChecked(it) },
+            ErrorListener { onNetworkError(it) })
+        queue.add(req)
+    }
+
+    /**
+     * Called when the /check-payment response gave a result.
+     */
+    private fun onPaymentChecked(checkPaymentResponse: JSONObject) {
+        val currentValue = requireNotNull(mPayment.value)
+        if (checkPaymentResponse.getBoolean("paid")) {
+            mPayment.value = currentValue.copy(paid = true)
+            checkTimer.cancel()
+        } else if (currentValue.talerPayUri == null) {
+            val talerPayUri = checkPaymentResponse.getString("taler_pay_uri")
+            mPayment.value = currentValue.copy(talerPayUri = talerPayUri)
+        }
+    }
+
+    private fun onNetworkError(volleyError: VolleyError) {
+        Log.e(PaymentManager::class.java.simpleName, volleyError.toString())
+        cancelPayment()
+    }
+
+    fun cancelPayment() {
+        mPayment.value = mPayment.value!!.copy(error = true)
+        checkTimer.cancel()
+    }
+
+}
diff --git 
a/merchant-terminal/src/main/java/net/taler/merchantpos/payment/PaymentSuccessFragment.kt
 
b/merchant-terminal/src/main/java/net/taler/merchantpos/payment/PaymentSuccessFragment.kt
new file mode 100644
index 0000000..10d538d
--- /dev/null
+++ 
b/merchant-terminal/src/main/java/net/taler/merchantpos/payment/PaymentSuccessFragment.kt
@@ -0,0 +1,44 @@
+/*
+ * 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.merchantpos.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_success.*
+import net.taler.merchantpos.R
+
+class PaymentSuccessFragment : Fragment() {
+
+    override fun onCreateView(
+        inflater: LayoutInflater, container: ViewGroup?,
+        savedInstanceState: Bundle?
+    ): View? {
+        return inflater.inflate(R.layout.fragment_payment_success, container, 
false)
+    }
+
+    override fun onActivityCreated(savedInstanceState: Bundle?) {
+        super.onActivityCreated(savedInstanceState)
+        paymentButton.setOnClickListener {
+            findNavController().navigateUp()
+        }
+    }
+
+}
diff --git 
a/merchant-terminal/src/main/java/net/taler/merchantpos/payment/ProcessPaymentFragment.kt
 
b/merchant-terminal/src/main/java/net/taler/merchantpos/payment/ProcessPaymentFragment.kt
new file mode 100644
index 0000000..24f67f1
--- /dev/null
+++ 
b/merchant-terminal/src/main/java/net/taler/merchantpos/payment/ProcessPaymentFragment.kt
@@ -0,0 +1,96 @@
+/*
+ * 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.merchantpos.payment
+
+import android.annotation.SuppressLint
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+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.BaseTransientBottomBar.LENGTH_LONG
+import kotlinx.android.synthetic.main.fragment_process_payment.*
+import net.taler.merchantpos.MainViewModel
+import net.taler.merchantpos.NfcManager.Companion.hasNfc
+import net.taler.merchantpos.QrCodeManager.makeQrCode
+import net.taler.merchantpos.R
+import net.taler.merchantpos.fadeIn
+import net.taler.merchantpos.fadeOut
+import net.taler.merchantpos.navigate
+import 
net.taler.merchantpos.payment.ProcessPaymentFragmentDirections.Companion.actionProcessPaymentToPaymentSuccess
+import net.taler.merchantpos.topSnackbar
+
+class ProcessPaymentFragment : Fragment() {
+
+    private val model: MainViewModel by activityViewModels()
+    private val paymentManager by lazy { model.paymentManager }
+
+    override fun onCreateView(
+        inflater: LayoutInflater, container: ViewGroup?,
+        savedInstanceState: Bundle?
+    ): View? {
+        return inflater.inflate(R.layout.fragment_process_payment, container, 
false)
+    }
+
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+        val introRes =
+            if (hasNfc(requireContext())) R.string.payment_intro_nfc else 
R.string.payment_intro
+        payIntroView.setText(introRes)
+        paymentManager.payment.observe(viewLifecycleOwner, Observer { payment 
->
+            onPaymentStateChanged(payment)
+        })
+        cancelPaymentButton.setOnClickListener {
+            onPaymentCancel()
+        }
+    }
+
+    private fun onPaymentStateChanged(payment: Payment) {
+        if (payment.error) {
+            topSnackbar(view!!, R.string.error_network, LENGTH_LONG)
+            findNavController().navigateUp()
+            return
+        }
+        if (payment.paid) {
+            model.orderManager.onOrderPaid(payment.order.id)
+            
actionProcessPaymentToPaymentSuccess().navigate(findNavController())
+            return
+        }
+        payIntroView.fadeIn()
+        @SuppressLint("SetTextI18n")
+        amountView.text = "${payment.order.totalAsString} ${payment.currency}"
+        payment.orderId?.let {
+            orderRefView.text = getString(R.string.payment_order_ref, it)
+            orderRefView.fadeIn()
+        }
+        payment.talerPayUri?.let {
+            val qrcodeBitmap = makeQrCode(it)
+            qrcodeView.setImageBitmap(qrcodeBitmap)
+            qrcodeView.fadeIn()
+            progressBar.fadeOut()
+        }
+    }
+
+    private fun onPaymentCancel() {
+        paymentManager.cancelPayment()
+        findNavController().navigateUp()
+        topSnackbar(view!!, R.string.payment_canceled, LENGTH_LONG)
+    }
+
+}
diff --git a/merchant-terminal/src/main/res/color/button_bottom.xml 
b/merchant-terminal/src/main/res/color/button_bottom.xml
new file mode 100644
index 0000000..83363e9
--- /dev/null
+++ b/merchant-terminal/src/main/res/color/button_bottom.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<selector xmlns:android="http://schemas.android.com/apk/res/android";>
+    <item android:color="@color/bottomButtons" android:state_enabled="true" />
+    <item android:alpha="0.12" android:color="?attr/colorOnSurface" />
+</selector>
diff --git a/merchant-terminal/src/main/res/drawable/ic_cash_refund.xml 
b/merchant-terminal/src/main/res/drawable/ic_cash_refund.xml
new file mode 100644
index 0000000..7359ca3
--- /dev/null
+++ b/merchant-terminal/src/main/res/drawable/ic_cash_refund.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android";
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24"
+        android:viewportHeight="24">
+    <path
+            android:fillColor="#000000"
+            android:pathData="M3,11H21V23H3V11M12,15A2,2 0 0,1 14,17A2,2 0 0,1 
12,19A2,2 0 0,1 10,17A2,2 0 0,1 12,15M7,13A2,2 0 0,1 5,15V19A2,2 0 0,1 
7,21H17A2,2 0 0,1 19,19V15A2,2 0 0,1 
17,13H7M17,5V10H15.5V6.5H9.88L12.3,8.93L11.24,10L7,5.75L11.24,1.5L12.3,2.57L9.88,5H17Z"
 />
+</vector>
diff --git a/merchant-terminal/src/main/res/drawable/ic_check_circle.xml 
b/merchant-terminal/src/main/res/drawable/ic_check_circle.xml
new file mode 100644
index 0000000..61e1b5a
--- /dev/null
+++ b/merchant-terminal/src/main/res/drawable/ic_check_circle.xml
@@ -0,0 +1,10 @@
+<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/merchant-terminal/src/main/res/drawable/ic_history_black_24dp.xml
new file mode 100644
index 0000000..a61de1b
--- /dev/null
+++ b/merchant-terminal/src/main/res/drawable/ic_history_black_24dp.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android";
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M13,3c-4.97,0 -9,4.03 -9,9L1,12l3.89,3.89 
0.07,0.14L9,12L6,12c0,-3.87 3.13,-7 7,-7s7,3.13 7,7 -3.13,7 -7,7c-1.93,0 
-3.68,-0.79 -4.94,-2.06l-1.42,1.42C8.27,19.99 10.51,21 13,21c4.97,0 9,-4.03 
9,-9s-4.03,-9 -9,-9zM12,8v5l4.28,2.54 0.72,-1.21 -3.5,-2.08L13.5,8L12,8z"/>
+</vector>
diff --git a/merchant-terminal/src/main/res/drawable/ic_launcher_background.xml 
b/merchant-terminal/src/main/res/drawable/ic_launcher_background.xml
new file mode 100644
index 0000000..2408e30
--- /dev/null
+++ b/merchant-terminal/src/main/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector
+    android:height="108dp"
+    android:width="108dp"
+    android:viewportHeight="108"
+    android:viewportWidth="108"
+    xmlns:android="http://schemas.android.com/apk/res/android";>
+    <path android:fillColor="#008577"
+          android:pathData="M0,0h108v108h-108z"/>
+    <path android:fillColor="#00000000" android:pathData="M9,0L9,108"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+    <path android:fillColor="#00000000" android:pathData="M19,0L19,108"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+    <path android:fillColor="#00000000" android:pathData="M29,0L29,108"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+    <path android:fillColor="#00000000" android:pathData="M39,0L39,108"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+    <path android:fillColor="#00000000" android:pathData="M49,0L49,108"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+    <path android:fillColor="#00000000" android:pathData="M59,0L59,108"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+    <path android:fillColor="#00000000" android:pathData="M69,0L69,108"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+    <path android:fillColor="#00000000" android:pathData="M79,0L79,108"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+    <path android:fillColor="#00000000" android:pathData="M89,0L89,108"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+    <path android:fillColor="#00000000" android:pathData="M99,0L99,108"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+    <path android:fillColor="#00000000" android:pathData="M0,9L108,9"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+    <path android:fillColor="#00000000" android:pathData="M0,19L108,19"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+    <path android:fillColor="#00000000" android:pathData="M0,29L108,29"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+    <path android:fillColor="#00000000" android:pathData="M0,39L108,39"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+    <path android:fillColor="#00000000" android:pathData="M0,49L108,49"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+    <path android:fillColor="#00000000" android:pathData="M0,59L108,59"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+    <path android:fillColor="#00000000" android:pathData="M0,69L108,69"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+    <path android:fillColor="#00000000" android:pathData="M0,79L108,79"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+    <path android:fillColor="#00000000" android:pathData="M0,89L108,89"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+    <path android:fillColor="#00000000" android:pathData="M0,99L108,99"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+    <path android:fillColor="#00000000" android:pathData="M19,29L89,29"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+    <path android:fillColor="#00000000" android:pathData="M19,39L89,39"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+    <path android:fillColor="#00000000" android:pathData="M19,49L89,49"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+    <path android:fillColor="#00000000" android:pathData="M19,59L89,59"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+    <path android:fillColor="#00000000" android:pathData="M19,69L89,69"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+    <path android:fillColor="#00000000" android:pathData="M19,79L89,79"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+    <path android:fillColor="#00000000" android:pathData="M29,19L29,89"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+    <path android:fillColor="#00000000" android:pathData="M39,19L39,89"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+    <path android:fillColor="#00000000" android:pathData="M49,19L49,89"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+    <path android:fillColor="#00000000" android:pathData="M59,19L59,89"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+    <path android:fillColor="#00000000" android:pathData="M69,19L69,89"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+    <path android:fillColor="#00000000" android:pathData="M79,19L79,89"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+</vector>
diff --git a/merchant-terminal/src/main/res/drawable/ic_menu_manage.xml 
b/merchant-terminal/src/main/res/drawable/ic_menu_manage.xml
new file mode 100644
index 0000000..a0e423c
--- /dev/null
+++ b/merchant-terminal/src/main/res/drawable/ic_menu_manage.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android";
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+            android:fillColor="#FF000000"
+            android:pathData="M22.7,19l-9.1,-9.1c0.9,-2.3 0.4,-5 -1.5,-6.9 
-2,-2 -5,-2.4 -7.4,-1.3L9,6 6,9 1.6,4.7C0.4,7.1 0.9,10.1 2.9,12.1c1.9,1.9 
4.6,2.4 6.9,1.5l9.1,9.1c0.4,0.4 1,0.4 1.4,0l2.3,-2.3c0.5,-0.4 0.5,-1.1 
0.1,-1.4z"/>
+</vector>
\ No newline at end of file
diff --git a/merchant-terminal/src/main/res/drawable/ic_move_money_24dp.xml 
b/merchant-terminal/src/main/res/drawable/ic_move_money_24dp.xml
new file mode 100644
index 0000000..349f48f
--- /dev/null
+++ b/merchant-terminal/src/main/res/drawable/ic_move_money_24dp.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android";
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M19,3L4.99,3c-1.11,0 -1.98,0.9 -1.98,2L3,19c0,1.1 
0.88,2 1.99,2L19,21c1.1,0 2,-0.9 2,-2L21,5c0,-1.1 -0.9,-2 
-2,-2zM19,15h-4c0,1.66 -1.35,3 -3,3s-3,-1.34 
-3,-3L4.99,15L4.99,5L19,5v10zM16,10h-2L14,7h-4v3L8,10l4,4 4,-4z"/>
+</vector>
diff --git a/merchant-terminal/src/main/res/drawable/selectable_background.xml 
b/merchant-terminal/src/main/res/drawable/selectable_background.xml
new file mode 100644
index 0000000..b82de92
--- /dev/null
+++ b/merchant-terminal/src/main/res/drawable/selectable_background.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<selector xmlns:android="http://schemas.android.com/apk/res/android";>
+    <item android:drawable="@color/selectedBackground" 
android:state_activated="true" />
+    <item android:drawable="@android:color/transparent" />
+</selector>
\ No newline at end of file
diff --git a/merchant-terminal/src/main/res/drawable/side_nav_bar.xml 
b/merchant-terminal/src/main/res/drawable/side_nav_bar.xml
new file mode 100644
index 0000000..50dc048
--- /dev/null
+++ b/merchant-terminal/src/main/res/drawable/side_nav_bar.xml
@@ -0,0 +1,9 @@
+<shape xmlns:android="http://schemas.android.com/apk/res/android";
+       android:shape="rectangle">
+    <gradient
+            android:angle="135"
+            android:centerColor="@color/colorPrimaryDark"
+            android:endColor="@color/colorPrimaryDark"
+            android:startColor="@color/colorPrimary"
+            android:type="linear"/>
+</shape>
\ No newline at end of file
diff --git a/merchant-terminal/src/main/res/layout/activity_main.xml 
b/merchant-terminal/src/main/res/layout/activity_main.xml
new file mode 100644
index 0000000..6523caa
--- /dev/null
+++ b/merchant-terminal/src/main/res/layout/activity_main.xml
@@ -0,0 +1,42 @@
+<?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.drawerlayout.widget.DrawerLayout
+        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/drawer_layout"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:fitsSystemWindows="true"
+        tools:openDrawer="start">
+
+    <include
+            layout="@layout/app_bar_main"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"/>
+
+    <com.google.android.material.navigation.NavigationView
+            android:id="@+id/nav_view"
+            android:layout_width="wrap_content"
+            android:layout_height="match_parent"
+            android:layout_gravity="start"
+            android:fitsSystemWindows="true"
+            app:menu="@menu/activity_main_drawer"
+            app:headerLayout="@layout/nav_header_main" />
+
+</androidx.drawerlayout.widget.DrawerLayout>
diff --git a/merchant-terminal/src/main/res/layout/app_bar_main.xml 
b/merchant-terminal/src/main/res/layout/app_bar_main.xml
new file mode 100644
index 0000000..0254c71
--- /dev/null
+++ b/merchant-terminal/src/main/res/layout/app_bar_main.xml
@@ -0,0 +1,53 @@
+<?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.coordinatorlayout.widget.CoordinatorLayout 
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=".MainActivity">
+
+    <com.google.android.material.appbar.AppBarLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:theme="@style/AppTheme.AppBarOverlay">
+
+        <androidx.appcompat.widget.Toolbar
+                android:id="@+id/toolbar"
+                android:layout_width="match_parent"
+                android:layout_height="?attr/actionBarSize"
+                android:background="?attr/colorPrimary"
+                app:popupTheme="@style/AppTheme.PopupOverlay" />
+
+    </com.google.android.material.appbar.AppBarLayout>
+
+    <androidx.fragment.app.FragmentContainerView
+            android:id="@+id/navHostFragment"
+            android:name="androidx.navigation.fragment.NavHostFragment"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            app:defaultNavHost="true"
+            app:layout_insetEdge="top"
+            app:layout_behavior="@string/appbar_scrolling_view_behavior"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintLeft_toLeftOf="parent"
+            app:layout_constraintRight_toRightOf="parent"
+            app:layout_constraintTop_toTopOf="parent"
+            app:navGraph="@navigation/nav_graph" />
+
+</androidx.coordinatorlayout.widget.CoordinatorLayout>
diff --git a/merchant-terminal/src/main/res/layout/fragment_categories.xml 
b/merchant-terminal/src/main/res/layout/fragment_categories.xml
new file mode 100644
index 0000000..a90585f
--- /dev/null
+++ b/merchant-terminal/src/main/res/layout/fragment_categories.xml
@@ -0,0 +1,46 @@
+<?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";
+        android:layout_width="match_parent"
+        xmlns:tools="http://schemas.android.com/tools";
+        android:layout_marginStart="8dp"
+        android:layout_marginEnd="8dp"
+        android:layout_height="match_parent">
+
+    <androidx.recyclerview.widget.RecyclerView
+            android:id="@+id/categoriesList"
+            android:layout_width="0dp"
+            tools:listitem="@layout/list_item_category"
+            android:layout_height="0dp"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent" />
+
+    <ProgressBar
+            android:id="@+id/progressBar"
+            style="?android:attr/progressBarStyleLarge"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/merchant-terminal/src/main/res/layout/fragment_config_fetcher.xml 
b/merchant-terminal/src/main/res/layout/fragment_config_fetcher.xml
new file mode 100644
index 0000000..af7dcaf
--- /dev/null
+++ b/merchant-terminal/src/main/res/layout/fragment_config_fetcher.xml
@@ -0,0 +1,45 @@
+<?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";
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_margin="16dp">
+
+    <TextView
+            android:id="@+id/titleView"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_margin="16dp"
+            android:text="@string/config_fetching"
+            android:textAppearance="@style/TextAppearance.AppCompat.Headline"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent" />
+
+    <ProgressBar
+            android:id="@+id/progressBar"
+            style="?android:attr/progressBarStyleLarge"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_margin="16dp"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toBottomOf="@+id/titleView" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/merchant-terminal/src/main/res/layout/fragment_merchant_config.xml 
b/merchant-terminal/src/main/res/layout/fragment_merchant_config.xml
new file mode 100644
index 0000000..2541887
--- /dev/null
+++ b/merchant-terminal/src/main/res/layout/fragment_merchant_config.xml
@@ -0,0 +1,152 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ This file is part of GNU Taler
+  ~ (C) 2020 Taler Systems S.A.
+  ~
+  ~ GNU Taler is free software; you can redistribute it and/or modify it under 
the
+  ~ terms of the GNU General Public License as published by the Free Software
+  ~ Foundation; either version 3, or (at your option) any later version.
+  ~
+  ~ GNU Taler is distributed in the hope that it will be useful, but WITHOUT 
ANY
+  ~ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
+  ~ A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+  ~
+  ~ You should have received a copy of the GNU General Public License along 
with
+  ~ GNU Taler; see the file COPYING.  If not, see 
<http://www.gnu.org/licenses/>
+  -->
+
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android";
+        xmlns:app="http://schemas.android.com/apk/res-auto";
+        xmlns:tools="http://schemas.android.com/tools";
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:fillViewport="true">
+
+    <androidx.constraintlayout.widget.ConstraintLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            tools:context=".config.MerchantConfigFragment">
+
+        <com.google.android.material.textfield.TextInputLayout
+                android:id="@+id/configUrlView"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_margin="16dp"
+                android:hint="@string/config_url"
+                app:boxBackgroundColor="@android:color/transparent"
+                app:boxBackgroundMode="outline"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toTopOf="parent">
+
+            <com.google.android.material.textfield.TextInputEditText
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:inputType="textUri" />
+
+        </com.google.android.material.textfield.TextInputLayout>
+
+        <com.google.android.material.textfield.TextInputLayout
+                android:id="@+id/usernameView"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_margin="16dp"
+                android:hint="@string/config_username"
+                app:boxBackgroundColor="@android:color/transparent"
+                app:boxBackgroundMode="outline"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toBottomOf="@+id/configUrlView">
+
+            <com.google.android.material.textfield.TextInputEditText
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:inputType="text" />
+
+        </com.google.android.material.textfield.TextInputLayout>
+
+        <com.google.android.material.textfield.TextInputLayout
+                android:id="@+id/passwordView"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_margin="16dp"
+                android:hint="@string/config_password"
+                app:boxBackgroundColor="@android:color/transparent"
+                app:boxBackgroundMode="outline"
+                app:endIconMode="password_toggle"
+                app:layout_constraintEnd_toStartOf="@+id/forgetPasswordButton"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toBottomOf="@+id/usernameView">
+
+            <com.google.android.material.textfield.TextInputEditText
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:inputType="textWebPassword" />
+
+        </com.google.android.material.textfield.TextInputLayout>
+
+        <Button
+                android:id="@+id/forgetPasswordButton"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_margin="16dp"
+                android:text="@string/config_forget_password"
+                android:visibility="gone"
+                app:layout_constraintBottom_toBottomOf="@+id/passwordView"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintTop_toTopOf="@+id/passwordView"
+                tools:visibility="visible" />
+
+        <CheckBox
+                android:id="@+id/savePasswordCheckBox"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_marginStart="16dp"
+                android:layout_marginTop="16dp"
+                android:layout_marginBottom="16dp"
+                android:checked="true"
+                android:text="@string/config_save_password"
+                app:layout_constraintBottom_toBottomOf="parent"
+                app:layout_constraintEnd_toStartOf="@+id/okButton"
+                app:layout_constraintHorizontal_chainStyle="spread_inside"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toBottomOf="@+id/passwordView"
+                app:layout_constraintVertical_bias="0.0" />
+
+        <com.google.android.material.button.MaterialButton
+                android:id="@+id/okButton"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_margin="16dp"
+                android:text="@string/config_ok"
+                app:layout_constraintBottom_toBottomOf="parent"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toEndOf="@+id/savePasswordCheckBox"
+                app:layout_constraintTop_toBottomOf="@+id/passwordView"
+                app:layout_constraintVertical_bias="0.0" />
+
+        <ProgressBar
+                android:id="@+id/progressBar"
+                style="?android:attr/progressBarStyle"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:visibility="invisible"
+                app:layout_constraintBottom_toBottomOf="@+id/okButton"
+                app:layout_constraintEnd_toEndOf="@+id/okButton"
+                app:layout_constraintStart_toStartOf="@+id/okButton"
+                app:layout_constraintTop_toTopOf="@+id/okButton"
+                tools:visibility="visible" />
+
+        <TextView
+                android:id="@+id/configDocsView"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_margin="16dp"
+                android:text="@string/config_docs"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toBottomOf="@+id/okButton" />
+
+    </androidx.constraintlayout.widget.ConstraintLayout>
+
+</ScrollView>
diff --git 
a/merchant-terminal/src/main/res/layout/fragment_merchant_history.xml 
b/merchant-terminal/src/main/res/layout/fragment_merchant_history.xml
new file mode 100644
index 0000000..21e6f08
--- /dev/null
+++ b/merchant-terminal/src/main/res/layout/fragment_merchant_history.xml
@@ -0,0 +1,29 @@
+<?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.swiperefreshlayout.widget.SwipeRefreshLayout 
xmlns:android="http://schemas.android.com/apk/res/android";
+        android:id="@+id/swipeRefresh"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+
+    <androidx.recyclerview.widget.RecyclerView
+            android:id="@+id/list_history"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:scrollbars="vertical" />
+
+</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
diff --git a/merchant-terminal/src/main/res/layout/fragment_order.xml 
b/merchant-terminal/src/main/res/layout/fragment_order.xml
new file mode 100644
index 0000000..4af9c77
--- /dev/null
+++ b/merchant-terminal/src/main/res/layout/fragment_order.xml
@@ -0,0 +1,138 @@
+<?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">
+
+    <androidx.fragment.app.FragmentContainerView
+            android:id="@+id/fragment1"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:layout_marginBottom="8dp"
+            app:layout_constraintBottom_toTopOf="@+id/restartButton"
+            app:layout_constraintEnd_toStartOf="@+id/guideline1"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent"
+            tools:layout="@layout/fragment_order_state" />
+
+    <androidx.constraintlayout.widget.Guideline
+            android:id="@+id/guideline1"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:orientation="vertical"
+            app:layout_constraintGuide_percent="0.25" />
+
+    <androidx.fragment.app.FragmentContainerView
+            android:id="@+id/fragment2"
+            android:name="net.taler.merchantpos.order.ProductsFragment"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:layout_marginBottom="8dp"
+            app:layout_constraintBottom_toTopOf="@+id/restartButton"
+            app:layout_constraintEnd_toStartOf="@+id/guideline2"
+            app:layout_constraintStart_toStartOf="@+id/guideline1"
+            app:layout_constraintTop_toTopOf="parent"
+            tools:layout="@layout/fragment_products" />
+
+    <androidx.constraintlayout.widget.Guideline
+            android:id="@+id/guideline2"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:orientation="vertical"
+            app:layout_constraintGuide_percent="0.75" />
+
+    <androidx.fragment.app.FragmentContainerView
+            android:id="@+id/fragment3"
+            android:name="net.taler.merchantpos.order.CategoriesFragment"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:layout_marginBottom="8dp"
+            app:layout_constraintBottom_toTopOf="@+id/restartButton"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="@+id/guideline2"
+            app:layout_constraintTop_toTopOf="parent"
+            tools:layout="@layout/fragment_categories" />
+
+    <Button
+            android:id="@+id/restartButton"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="8dp"
+            android:backgroundTint="@color/button_bottom"
+            android:text="@string/order_restart"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintStart_toStartOf="parent" />
+
+    <Button
+            android:id="@+id/plusButton"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="16dp"
+            android:minWidth="48dp"
+            android:text="+1"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintStart_toEndOf="@+id/minusButton"
+            tools:ignore="HardcodedText" />
+
+    <Button
+            android:id="@+id/minusButton"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="32dp"
+            android:minWidth="48dp"
+            android:text="-1"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintStart_toEndOf="@+id/restartButton"
+            tools:ignore="HardcodedText" />
+
+    <Button
+            android:id="@+id/prevButton"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="32dp"
+            android:backgroundTint="@color/button_bottom"
+            android:text="@string/order_previous"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintStart_toEndOf="@+id/plusButton" />
+
+    <Button
+            android:id="@+id/nextButton"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="16dp"
+            android:backgroundTint="@color/button_bottom"
+            android:text="@string/order_next"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintStart_toEndOf="@+id/prevButton" />
+
+    <Button
+            android:id="@+id/completeButton"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="32dp"
+            android:layout_marginEnd="8dp"
+            android:backgroundTint="@color/button_bottom"
+            android:text="@string/order_complete"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintHorizontal_bias="1.0"
+            app:layout_constraintStart_toEndOf="@+id/nextButton" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/merchant-terminal/src/main/res/layout/fragment_order_state.xml 
b/merchant-terminal/src/main/res/layout/fragment_order_state.xml
new file mode 100644
index 0000000..7d6b258
--- /dev/null
+++ b/merchant-terminal/src/main/res/layout/fragment_order_state.xml
@@ -0,0 +1,52 @@
+<?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">
+
+    <androidx.recyclerview.widget.RecyclerView
+            android:id="@+id/orderList"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            app:layout_constraintBottom_toTopOf="@+id/totalView"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent"
+            tools:listitem="@layout/list_item_order" />
+
+    <TextView
+            android:id="@+id/totalView"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:background="@color/highlightedBackground"
+            android:elevation="2dp"
+            android:gravity="center_vertical|end"
+            android:padding="8dp"
+            android:textColor="?android:textColorPrimary"
+            android:textSize="16sp"
+            android:visibility="invisible"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toBottomOf="@+id/orderList"
+            tools:text="Total: 23.75 TESTKUDOS"
+            tools:visibility="visible" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/merchant-terminal/src/main/res/layout/fragment_payment_success.xml 
b/merchant-terminal/src/main/res/layout/fragment_payment_success.xml
new file mode 100644
index 0000000..1bc9be7
--- /dev/null
+++ b/merchant-terminal/src/main/res/layout/fragment_payment_success.xml
@@ -0,0 +1,78 @@
+<?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=".payment.PaymentSuccessFragment">
+
+    <ImageView
+            android:id="@+id/paymentIcon"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:layout_margin="16dp"
+            android:src="@drawable/ic_check_circle"
+            app:layout_constraintBottom_toTopOf="@+id/paymentLabel"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintHorizontal_bias="0.5"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent"
+            app:layout_constraintVertical_chainStyle="spread_inside"
+            tools:ignore="ContentDescription" />
+
+    <TextView
+            android:id="@+id/paymentLabel"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:layout_margin="16dp"
+            android:gravity="center_horizontal|top"
+            android:text="@string/payment_received"
+            android:textColor="@color/green"
+            app:autoSizeMaxTextSize="42sp"
+            app:autoSizeTextType="uniform"
+            app:layout_constraintBottom_toTopOf="@+id/paymentButton"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toBottomOf="@+id/paymentIcon" />
+
+    <androidx.constraintlayout.widget.Guideline
+            android:id="@+id/guidelineLeft"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:orientation="vertical"
+            app:layout_constraintGuide_percent="0.25" />
+
+    <androidx.constraintlayout.widget.Guideline
+            android:id="@+id/guidelineRight"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:orientation="vertical"
+            app:layout_constraintGuide_percent="0.75" />
+
+    <Button
+            android:id="@+id/paymentButton"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_margin="16dp"
+            android:text="@string/payment_back_button"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintEnd_toStartOf="@+id/guidelineRight"
+            app:layout_constraintStart_toStartOf="@+id/guidelineLeft" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/merchant-terminal/src/main/res/layout/fragment_process_payment.xml 
b/merchant-terminal/src/main/res/layout/fragment_process_payment.xml
new file mode 100644
index 0000000..6cd8ea1
--- /dev/null
+++ b/merchant-terminal/src/main/res/layout/fragment_process_payment.xml
@@ -0,0 +1,110 @@
+<?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=".payment.ProcessPaymentFragment">
+
+    <ImageView
+            android:id="@+id/qrcodeView"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:layout_margin="32dp"
+            android:visibility="invisible"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintEnd_toStartOf="@+id/guideline"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent"
+            tools:ignore="ContentDescription"
+            tools:src="@tools:sample/avatars"
+            tools:visibility="visible" />
+
+    <ProgressBar
+            android:id="@+id/progressBar"
+            style="?android:attr/progressBarStyleLarge"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            app:layout_constraintBottom_toBottomOf="@+id/qrcodeView"
+            app:layout_constraintEnd_toEndOf="@+id/qrcodeView"
+            app:layout_constraintStart_toStartOf="@+id/qrcodeView"
+            app:layout_constraintTop_toTopOf="@+id/qrcodeView" />
+
+    <androidx.constraintlayout.widget.Guideline
+            android:id="@+id/guideline"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:orientation="vertical"
+            app:layout_constraintGuide_percent="0.54" />
+
+    <TextView
+            android:id="@+id/payIntroView"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_margin="16dp"
+            android:text="@string/payment_intro_nfc"
+            android:textAlignment="center"
+            android:textSize="24sp"
+            android:visibility="invisible"
+            app:layout_constraintBottom_toTopOf="@+id/amountView"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="@+id/guideline"
+            app:layout_constraintTop_toTopOf="parent"
+            app:layout_constraintVertical_chainStyle="spread"
+            tools:visibility="visible" />
+
+    <TextView
+            android:id="@+id/amountView"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_margin="16dp"
+            android:textAppearance="@style/TextAppearance.AppCompat.Headline"
+            app:layout_constraintBottom_toTopOf="@+id/orderRefView"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="@+id/guideline"
+            app:layout_constraintTop_toBottomOf="@+id/payIntroView"
+            tools:text="10.49 TESTKUDOS" />
+
+    <TextView
+            android:id="@+id/orderRefView"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_margin="16dp"
+            android:textAlignment="center"
+            android:visibility="invisible"
+            app:layout_constraintBottom_toTopOf="@id/cancelPaymentButton"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="@+id/guideline"
+            app:layout_constraintTop_toBottomOf="@+id/amountView"
+            tools:text="@string/payment_order_ref"
+            tools:visibility="visible" />
+
+    <Button
+            android:id="@+id/cancelPaymentButton"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_margin="16dp"
+            android:backgroundTint="@color/red"
+            android:text="@string/payment_cancel"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintHorizontal_bias="0.0"
+            app:layout_constraintStart_toStartOf="@+id/guideline" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/merchant-terminal/src/main/res/layout/fragment_products.xml 
b/merchant-terminal/src/main/res/layout/fragment_products.xml
new file mode 100644
index 0000000..f0e86e7
--- /dev/null
+++ b/merchant-terminal/src/main/res/layout/fragment_products.xml
@@ -0,0 +1,44 @@
+<?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";
+        android:layout_width="match_parent"
+        xmlns:tools="http://schemas.android.com/tools";
+        android:layout_height="match_parent">
+
+    <androidx.recyclerview.widget.RecyclerView
+            android:id="@+id/productsList"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            app:layout_constraintTop_toTopOf="parent"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            tools:listitem="@layout/list_item_product" />
+
+    <ProgressBar
+            android:id="@+id/progressBar"
+            style="?android:attr/progressBarStyleLarge"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/merchant-terminal/src/main/res/layout/fragment_refund.xml 
b/merchant-terminal/src/main/res/layout/fragment_refund.xml
new file mode 100644
index 0000000..5a78cdd
--- /dev/null
+++ b/merchant-terminal/src/main/res/layout/fragment_refund.xml
@@ -0,0 +1,122 @@
+<?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=".history.RefundFragment">
+
+    <com.google.android.material.textfield.TextInputLayout
+            android:id="@+id/amountView"
+            
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.Dense"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_margin="16dp"
+            android:hint="@string/refund_amount"
+            app:boxBackgroundMode="outline"
+            app:endIconMode="clear_text"
+            app:endIconTint="?attr/colorControlNormal"
+            app:layout_constraintBottom_toTopOf="@+id/reasonView"
+            app:layout_constraintEnd_toStartOf="@+id/currencyView"
+            app:layout_constraintHorizontal_chainStyle="packed"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent"
+            app:layout_constraintVertical_chainStyle="spread">
+
+        <com.google.android.material.textfield.TextInputEditText
+                android:id="@+id/amountInputView"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:ems="6"
+                android:inputType="numberDecimal"
+                tools:text="23.42" />
+
+    </com.google.android.material.textfield.TextInputLayout>
+
+    <TextView
+            android:id="@+id/currencyView"
+            android:layout_width="wrap_content"
+            android:layout_height="0dp"
+            android:layout_marginStart="8dp"
+            android:gravity="start|center_vertical"
+            app:layout_constraintBottom_toBottomOf="@+id/amountView"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toEndOf="@+id/amountView"
+            app:layout_constraintTop_toTopOf="@+id/amountView"
+            tools:text="TESTKUDOS" />
+
+    <com.google.android.material.textfield.TextInputLayout
+            android:id="@+id/reasonView"
+            
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.Dense"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_margin="16dp"
+            android:hint="@string/refund_reason"
+            app:endIconMode="clear_text"
+            app:layout_constraintBottom_toTopOf="@+id/abortButton"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintHorizontal_bias="0.5"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toBottomOf="@+id/amountView">
+
+        <com.google.android.material.textfield.TextInputEditText
+                android:id="@+id/reasonInputView"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                
android:inputType="textAutoComplete|textAutoCorrect|textMultiLine" />
+
+    </com.google.android.material.textfield.TextInputLayout>
+
+    <Button
+            android:id="@+id/abortButton"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_margin="16dp"
+            android:backgroundTint="@color/red"
+            android:text="@string/refund_abort"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintEnd_toStartOf="@+id/refundButton"
+            app:layout_constraintHorizontal_bias="0.76"
+            app:layout_constraintHorizontal_chainStyle="spread_inside"
+            app:layout_constraintStart_toStartOf="parent" />
+
+    <Button
+            android:id="@+id/refundButton"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_margin="16dp"
+            android:backgroundTint="@color/green"
+            android:text="@string/refund_confirm"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintHorizontal_bias="0.5"
+            app:layout_constraintStart_toEndOf="@+id/abortButton" />
+
+    <ProgressBar
+            android:id="@+id/progressBar"
+            style="?android:attr/progressBarStyle"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:visibility="invisible"
+            app:layout_constraintBottom_toBottomOf="@+id/refundButton"
+            app:layout_constraintEnd_toEndOf="@+id/refundButton"
+            app:layout_constraintStart_toStartOf="@+id/refundButton"
+            app:layout_constraintTop_toTopOf="@+id/refundButton"
+            tools:visibility="visible" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/merchant-terminal/src/main/res/layout/fragment_refund_uri.xml 
b/merchant-terminal/src/main/res/layout/fragment_refund_uri.xml
new file mode 100644
index 0000000..8447d28
--- /dev/null
+++ b/merchant-terminal/src/main/res/layout/fragment_refund_uri.xml
@@ -0,0 +1,93 @@
+<?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=".payment.ProcessPaymentFragment">
+
+    <ImageView
+            android:id="@+id/refundQrcodeView"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:layout_margin="32dp"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintEnd_toStartOf="@+id/guideline"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent"
+            tools:ignore="ContentDescription"
+            tools:src="@tools:sample/avatars" />
+
+    <androidx.constraintlayout.widget.Guideline
+            android:id="@+id/guideline"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:orientation="vertical"
+            app:layout_constraintGuide_percent="0.54" />
+
+    <TextView
+            android:id="@+id/refundIntroView"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_margin="16dp"
+            android:text="@string/refund_intro_nfc"
+            android:textAlignment="center"
+            android:textSize="24sp"
+            app:layout_constraintBottom_toTopOf="@+id/refundAmountView"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="@+id/guideline"
+            app:layout_constraintTop_toTopOf="parent"
+            app:layout_constraintVertical_chainStyle="spread" />
+
+    <TextView
+            android:id="@+id/refundAmountView"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_margin="16dp"
+            android:textAppearance="@style/TextAppearance.AppCompat.Headline"
+            app:layout_constraintBottom_toTopOf="@+id/refundRefView"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="@+id/guideline"
+            app:layout_constraintTop_toBottomOf="@+id/refundIntroView"
+            tools:text="10.49 TESTKUDOS" />
+
+    <TextView
+            android:id="@+id/refundRefView"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_margin="16dp"
+            android:textAlignment="center"
+            app:layout_constraintBottom_toTopOf="@id/cancelRefundButton"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="@+id/guideline"
+            app:layout_constraintTop_toBottomOf="@+id/refundAmountView"
+            tools:text="@string/refund_order_ref" />
+
+    <Button
+            android:id="@+id/cancelRefundButton"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_margin="16dp"
+            android:backgroundTint="@color/red"
+            android:text="@string/refund_abort"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintHorizontal_bias="0.0"
+            app:layout_constraintStart_toStartOf="@+id/guideline" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/merchant-terminal/src/main/res/layout/list_item_category.xml 
b/merchant-terminal/src/main/res/layout/list_item_category.xml
new file mode 100644
index 0000000..cbdbd34
--- /dev/null
+++ b/merchant-terminal/src/main/res/layout/list_item_category.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ This file is part of GNU Taler
+  ~ (C) 2020 Taler Systems S.A.
+  ~
+  ~ GNU Taler is free software; you can redistribute it and/or modify it under 
the
+  ~ terms of the GNU General Public License as published by the Free Software
+  ~ Foundation; either version 3, or (at your option) any later version.
+  ~
+  ~ GNU Taler is distributed in the hope that it will be useful, but WITHOUT 
ANY
+  ~ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
+  ~ A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+  ~
+  ~ You should have received a copy of the GNU General Public License along 
with
+  ~ GNU Taler; see the file COPYING.  If not, see 
<http://www.gnu.org/licenses/>
+  -->
+
+<androidx.constraintlayout.widget.ConstraintLayout 
xmlns:android="http://schemas.android.com/apk/res/android";
+        xmlns:app="http://schemas.android.com/apk/res-auto";
+        xmlns:tools="http://schemas.android.com/tools";
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content">
+
+    <Button
+            android:id="@+id/button"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent"
+            tools:text="Snacks" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/merchant-terminal/src/main/res/layout/list_item_history.xml 
b/merchant-terminal/src/main/res/layout/list_item_history.xml
new file mode 100644
index 0000000..fe485ba
--- /dev/null
+++ b/merchant-terminal/src/main/res/layout/list_item_history.xml
@@ -0,0 +1,97 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ This file is part of GNU Taler
+  ~ (C) 2020 Taler Systems S.A.
+  ~
+  ~ GNU Taler is free software; you can redistribute it and/or modify it under 
the
+  ~ terms of the GNU General Public License as published by the Free Software
+  ~ Foundation; either version 3, or (at your option) any later version.
+  ~
+  ~ GNU Taler is distributed in the hope that it will be useful, but WITHOUT 
ANY
+  ~ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
+  ~ A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+  ~
+  ~ You should have received a copy of the GNU General Public License along 
with
+  ~ GNU Taler; see the file COPYING.  If not, see 
<http://www.gnu.org/licenses/>
+  -->
+
+<androidx.constraintlayout.widget.ConstraintLayout 
xmlns:android="http://schemas.android.com/apk/res/android";
+        xmlns:app="http://schemas.android.com/apk/res-auto";
+        xmlns:tools="http://schemas.android.com/tools";
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:padding="16dp">
+
+    <TextView
+            android:id="@+id/orderSummaryView"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:textColor="?android:attr/textColorPrimary"
+            android:textSize="20sp"
+            android:textStyle="bold"
+            app:layout_constraintEnd_toStartOf="@+id/orderAmountView"
+            app:layout_constraintHorizontal_bias="1.0"
+            app:layout_constraintHorizontal_chainStyle="spread_inside"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent"
+            tools:text="One Cappuccino or another name that can be so long 
that it spans more than one line" />
+
+    <TextView
+            android:id="@+id/orderAmountView"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="8dp"
+            android:layout_marginEnd="16dp"
+            android:textColor="?android:attr/textColorPrimary"
+            android:textSize="20sp"
+            android:textStyle="bold"
+            app:layout_constraintBottom_toBottomOf="@+id/orderSummaryView"
+            app:layout_constraintEnd_toStartOf="@+id/refundButton"
+            app:layout_constraintStart_toEndOf="@+id/orderSummaryView"
+            app:layout_constraintTop_toTopOf="parent"
+            app:layout_constraintVertical_bias="0.0"
+            tools:text="23.42 TESTKUDOS" />
+
+    <TextView
+            android:id="@+id/orderIdView"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="8dp"
+            android:text="@string/history_ref_no"
+            android:textAllCaps="false"
+            android:textSize="20sp"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintEnd_toStartOf="@+id/orderTimeView"
+            app:layout_constraintHorizontal_bias="0.5"
+            app:layout_constraintHorizontal_chainStyle="spread_inside"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toBottomOf="@+id/orderSummaryView" />
+
+    <TextView
+            android:id="@+id/orderTimeView"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="8dp"
+            android:layout_marginTop="8dp"
+            android:layout_marginEnd="16dp"
+            android:textSize="20sp"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintEnd_toStartOf="@+id/refundButton"
+            app:layout_constraintStart_toEndOf="@+id/orderIdView"
+            app:layout_constraintTop_toBottomOf="@+id/orderAmountView"
+            app:layout_constraintVertical_bias="1.0"
+            tools:text="3 hrs. ago" />
+
+    <ImageButton
+            android:id="@+id/refundButton"
+            android:layout_width="48dp"
+            android:layout_height="48dp"
+            android:backgroundTint="?colorPrimary"
+            android:contentDescription="@string/history_refund"
+            android:tint="?attr/colorOnPrimary"
+            android:visibility="gone"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintTop_toTopOf="parent"
+            app:srcCompat="@drawable/ic_cash_refund" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/merchant-terminal/src/main/res/layout/list_item_order.xml 
b/merchant-terminal/src/main/res/layout/list_item_order.xml
new file mode 100644
index 0000000..f88364d
--- /dev/null
+++ b/merchant-terminal/src/main/res/layout/list_item_order.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ This file is part of GNU Taler
+  ~ (C) 2020 Taler Systems S.A.
+  ~
+  ~ GNU Taler is free software; you can redistribute it and/or modify it under 
the
+  ~ terms of the GNU General Public License as published by the Free Software
+  ~ Foundation; either version 3, or (at your option) any later version.
+  ~
+  ~ GNU Taler is distributed in the hope that it will be useful, but WITHOUT 
ANY
+  ~ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
+  ~ A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+  ~
+  ~ You should have received a copy of the GNU General Public License along 
with
+  ~ GNU Taler; see the file COPYING.  If not, see 
<http://www.gnu.org/licenses/>
+  -->
+
+<androidx.constraintlayout.widget.ConstraintLayout 
xmlns:android="http://schemas.android.com/apk/res/android";
+        xmlns:app="http://schemas.android.com/apk/res-auto";
+        xmlns:tools="http://schemas.android.com/tools";
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:background="@drawable/selectable_background"
+        android:minHeight="48dp"
+        android:padding="8dp">
+
+    <TextView
+            android:id="@+id/quantity"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:gravity="end"
+            android:minWidth="24dp"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="@+id/name"
+            app:layout_constraintVertical_bias="0.0"
+            tools:text="31" />
+
+    <TextView
+            android:id="@+id/name"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="8dp"
+            android:layout_marginEnd="8dp"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintEnd_toStartOf="@+id/price"
+            app:layout_constraintStart_toEndOf="@+id/quantity"
+            app:layout_constraintTop_toTopOf="parent"
+            tools:text="An order product item that in some cases could have a 
very long name" />
+
+    <TextView
+            android:id="@+id/price"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintTop_toTopOf="@+id/name"
+            app:layout_constraintVertical_bias="0.0"
+            tools:text="23.42" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/merchant-terminal/src/main/res/layout/list_item_product.xml 
b/merchant-terminal/src/main/res/layout/list_item_product.xml
new file mode 100644
index 0000000..1037bef
--- /dev/null
+++ b/merchant-terminal/src/main/res/layout/list_item_product.xml
@@ -0,0 +1,56 @@
+<?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/>
+  -->
+
+<com.google.android.material.card.MaterialCardView 
xmlns:android="http://schemas.android.com/apk/res/android";
+        xmlns:app="http://schemas.android.com/apk/res-auto";
+        xmlns:tools="http://schemas.android.com/tools";
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_margin="4dp"
+        android:clickable="true"
+        android:focusable="true"
+        app:cardUseCompatPadding="true">
+
+    <androidx.constraintlayout.widget.ConstraintLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:padding="8dp">
+
+        <TextView
+                android:id="@+id/name"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:textColor="?android:textColorPrimary"
+                android:textStyle="bold"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toTopOf="parent"
+                tools:text="Steak and two Eggs" />
+
+        <TextView
+                android:id="@+id/price"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="8dp"
+                android:textColor="?android:textColorSecondary"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintTop_toBottomOf="@+id/name"
+                tools:text="7.95" />
+
+    </androidx.constraintlayout.widget.ConstraintLayout>
+
+</com.google.android.material.card.MaterialCardView>
\ No newline at end of file
diff --git a/merchant-terminal/src/main/res/layout/nav_header_main.xml 
b/merchant-terminal/src/main/res/layout/nav_header_main.xml
new file mode 100644
index 0000000..14bbd51
--- /dev/null
+++ b/merchant-terminal/src/main/res/layout/nav_header_main.xml
@@ -0,0 +1,55 @@
+<?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/>
+  -->
+
+<LinearLayout 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="@dimen/nav_header_height"
+        android:background="@drawable/side_nav_bar"
+        android:gravity="bottom"
+        android:orientation="vertical"
+        android:paddingLeft="@dimen/activity_horizontal_margin"
+        android:paddingTop="@dimen/activity_vertical_margin"
+        android:paddingRight="@dimen/activity_horizontal_margin"
+        android:paddingBottom="@dimen/activity_vertical_margin"
+        android:theme="@style/AppTheme">
+
+    <ImageView
+            android:id="@+id/imageView"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:paddingTop="@dimen/nav_header_vertical_spacing"
+            app:srcCompat="@mipmap/ic_taler_logo_round"
+            tools:ignore="ContentDescription" />
+
+    <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:paddingTop="@dimen/nav_header_vertical_spacing"
+            android:text="@string/project_name"
+            android:textAppearance="@style/TextAppearance.AppCompat.Body1"
+            android:textColor="#FFF" />
+
+    <TextView
+            android:id="@+id/textView"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/app_name_short"
+            android:textColor="#FFF" />
+
+</LinearLayout>
diff --git a/merchant-terminal/src/main/res/menu/activity_main_drawer.xml 
b/merchant-terminal/src/main/res/menu/activity_main_drawer.xml
new file mode 100644
index 0000000..1303605
--- /dev/null
+++ b/merchant-terminal/src/main/res/menu/activity_main_drawer.xml
@@ -0,0 +1,36 @@
+<?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/>
+  -->
+
+<menu xmlns:android="http://schemas.android.com/apk/res/android";
+        xmlns:tools="http://schemas.android.com/tools";
+        tools:showIn="navigation_view">
+
+    <group android:checkableBehavior="single">
+        <item
+                android:id="@+id/nav_order"
+                android:icon="@drawable/ic_move_money_24dp"
+                android:title="@string/menu_order" />
+        <item
+                android:id="@+id/nav_history"
+                android:icon="@drawable/ic_history_black_24dp"
+                android:title="@string/menu_history" />
+        <item
+                android:id="@+id/nav_settings"
+                android:icon="@drawable/ic_menu_manage"
+                android:title="@string/menu_settings" />
+    </group>
+</menu>
diff --git a/merchant-terminal/src/main/res/mipmap-anydpi-v26/ic_taler_logo.xml 
b/merchant-terminal/src/main/res/mipmap-anydpi-v26/ic_taler_logo.xml
new file mode 100644
index 0000000..c4a603d
--- /dev/null
+++ b/merchant-terminal/src/main/res/mipmap-anydpi-v26/ic_taler_logo.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android";>
+    <background android:drawable="@drawable/ic_launcher_background"/>
+    <foreground android:drawable="@mipmap/ic_launcher_foreground"/>
+</adaptive-icon>
\ No newline at end of file
diff --git 
a/merchant-terminal/src/main/res/mipmap-anydpi-v26/ic_taler_logo_round.xml 
b/merchant-terminal/src/main/res/mipmap-anydpi-v26/ic_taler_logo_round.xml
new file mode 100644
index 0000000..c4a603d
--- /dev/null
+++ b/merchant-terminal/src/main/res/mipmap-anydpi-v26/ic_taler_logo_round.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android";>
+    <background android:drawable="@drawable/ic_launcher_background"/>
+    <foreground android:drawable="@mipmap/ic_launcher_foreground"/>
+</adaptive-icon>
\ No newline at end of file
diff --git 
a/merchant-terminal/src/main/res/mipmap-hdpi/ic_launcher_foreground.png 
b/merchant-terminal/src/main/res/mipmap-hdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000..75273ec
Binary files /dev/null and 
b/merchant-terminal/src/main/res/mipmap-hdpi/ic_launcher_foreground.png differ
diff --git a/merchant-terminal/src/main/res/mipmap-hdpi/ic_taler_logo.png 
b/merchant-terminal/src/main/res/mipmap-hdpi/ic_taler_logo.png
new file mode 100644
index 0000000..eaecede
Binary files /dev/null and 
b/merchant-terminal/src/main/res/mipmap-hdpi/ic_taler_logo.png differ
diff --git a/merchant-terminal/src/main/res/mipmap-hdpi/ic_taler_logo_round.png 
b/merchant-terminal/src/main/res/mipmap-hdpi/ic_taler_logo_round.png
new file mode 100644
index 0000000..caa2a3e
Binary files /dev/null and 
b/merchant-terminal/src/main/res/mipmap-hdpi/ic_taler_logo_round.png differ
diff --git 
a/merchant-terminal/src/main/res/mipmap-mdpi/ic_launcher_foreground.png 
b/merchant-terminal/src/main/res/mipmap-mdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000..a450287
Binary files /dev/null and 
b/merchant-terminal/src/main/res/mipmap-mdpi/ic_launcher_foreground.png differ
diff --git a/merchant-terminal/src/main/res/mipmap-mdpi/ic_taler_logo.png 
b/merchant-terminal/src/main/res/mipmap-mdpi/ic_taler_logo.png
new file mode 100644
index 0000000..e1f7374
Binary files /dev/null and 
b/merchant-terminal/src/main/res/mipmap-mdpi/ic_taler_logo.png differ
diff --git a/merchant-terminal/src/main/res/mipmap-mdpi/ic_taler_logo_round.png 
b/merchant-terminal/src/main/res/mipmap-mdpi/ic_taler_logo_round.png
new file mode 100644
index 0000000..e92d2d3
Binary files /dev/null and 
b/merchant-terminal/src/main/res/mipmap-mdpi/ic_taler_logo_round.png differ
diff --git 
a/merchant-terminal/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png 
b/merchant-terminal/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000..a5e875c
Binary files /dev/null and 
b/merchant-terminal/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png differ
diff --git a/merchant-terminal/src/main/res/mipmap-xhdpi/ic_taler_logo.png 
b/merchant-terminal/src/main/res/mipmap-xhdpi/ic_taler_logo.png
new file mode 100644
index 0000000..5ca4409
Binary files /dev/null and 
b/merchant-terminal/src/main/res/mipmap-xhdpi/ic_taler_logo.png differ
diff --git 
a/merchant-terminal/src/main/res/mipmap-xhdpi/ic_taler_logo_round.png 
b/merchant-terminal/src/main/res/mipmap-xhdpi/ic_taler_logo_round.png
new file mode 100644
index 0000000..12b9056
Binary files /dev/null and 
b/merchant-terminal/src/main/res/mipmap-xhdpi/ic_taler_logo_round.png differ
diff --git 
a/merchant-terminal/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png 
b/merchant-terminal/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000..e9d1fc9
Binary files /dev/null and 
b/merchant-terminal/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png differ
diff --git a/merchant-terminal/src/main/res/mipmap-xxhdpi/ic_taler_logo.png 
b/merchant-terminal/src/main/res/mipmap-xxhdpi/ic_taler_logo.png
new file mode 100644
index 0000000..a786efa
Binary files /dev/null and 
b/merchant-terminal/src/main/res/mipmap-xxhdpi/ic_taler_logo.png differ
diff --git 
a/merchant-terminal/src/main/res/mipmap-xxhdpi/ic_taler_logo_round.png 
b/merchant-terminal/src/main/res/mipmap-xxhdpi/ic_taler_logo_round.png
new file mode 100644
index 0000000..b22a84e
Binary files /dev/null and 
b/merchant-terminal/src/main/res/mipmap-xxhdpi/ic_taler_logo_round.png differ
diff --git 
a/merchant-terminal/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png 
b/merchant-terminal/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000..f8037d1
Binary files /dev/null and 
b/merchant-terminal/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png 
differ
diff --git a/merchant-terminal/src/main/res/mipmap-xxxhdpi/ic_taler_logo.png 
b/merchant-terminal/src/main/res/mipmap-xxxhdpi/ic_taler_logo.png
new file mode 100644
index 0000000..0e9df6a
Binary files /dev/null and 
b/merchant-terminal/src/main/res/mipmap-xxxhdpi/ic_taler_logo.png differ
diff --git 
a/merchant-terminal/src/main/res/mipmap-xxxhdpi/ic_taler_logo_round.png 
b/merchant-terminal/src/main/res/mipmap-xxxhdpi/ic_taler_logo_round.png
new file mode 100644
index 0000000..6bef9bd
Binary files /dev/null and 
b/merchant-terminal/src/main/res/mipmap-xxxhdpi/ic_taler_logo_round.png differ
diff --git a/merchant-terminal/src/main/res/navigation/nav_graph.xml 
b/merchant-terminal/src/main/res/navigation/nav_graph.xml
new file mode 100644
index 0000000..2e337f2
--- /dev/null
+++ b/merchant-terminal/src/main/res/navigation/nav_graph.xml
@@ -0,0 +1,137 @@
+<?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/>
+  -->
+
+<navigation 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/nav_graph"
+        app:startDestination="@+id/nav_order"
+        tools:ignore="UnusedNavigation">
+
+    <fragment
+            android:id="@+id/nav_order"
+            android:name="net.taler.merchantpos.order.OrderFragment"
+            android:label=""
+            tools:layout="@layout/fragment_order">
+        <action
+                android:id="@+id/action_order_to_merchantSettings"
+                app:destination="@+id/nav_settings"
+                app:launchSingleTop="true"
+                app:popUpTo="@+id/nav_graph"
+                app:popUpToInclusive="true" />
+        <action
+                android:id="@+id/action_order_self"
+                app:destination="@+id/nav_order"
+                app:popUpTo="@+id/nav_graph" />
+        <action
+                android:id="@+id/action_order_to_processPayment"
+                app:destination="@+id/processPayment" />
+    </fragment>
+
+    <fragment
+            android:id="@+id/processPayment"
+            android:name="net.taler.merchantpos.payment.ProcessPaymentFragment"
+            android:label="@string/payment_process_label"
+            tools:layout="@layout/fragment_process_payment">
+        <action
+                android:id="@+id/action_processPayment_to_paymentSuccess"
+                app:destination="@+id/paymentSuccess"
+                app:popUpTo="@id/nav_order" />
+    </fragment>
+
+    <fragment
+            android:id="@+id/nav_history"
+            
android:name="net.taler.merchantpos.history.MerchantHistoryFragment"
+            android:label="@string/history_label"
+            tools:layout="@layout/fragment_merchant_history">
+        <action
+                android:id="@+id/action_nav_history_to_refundFragment"
+                app:destination="@id/refundFragment" />
+    </fragment>
+
+    <fragment
+            android:id="@+id/refundFragment"
+            android:name="net.taler.merchantpos.history.RefundFragment"
+            android:label="@string/history_refund"
+            tools:layout="@layout/fragment_refund">
+        <action
+                android:id="@+id/action_refundFragment_to_refundUriFragment"
+                app:destination="@id/refundUriFragment" />
+    </fragment>
+
+    <fragment
+            android:id="@+id/refundUriFragment"
+            android:name="net.taler.merchantpos.history.RefundUriFragment"
+            android:label="@string/history_refund"
+            tools:layout="@layout/fragment_refund_uri" />
+
+    <fragment
+            android:id="@+id/nav_settings"
+            android:name="net.taler.merchantpos.config.MerchantConfigFragment"
+            android:label="@string/config_label"
+            tools:layout="@layout/fragment_merchant_config">
+        <action
+                android:id="@+id/action_settings_to_order"
+                app:destination="@+id/nav_order"
+                app:launchSingleTop="true"
+                app:popUpTo="@+id/nav_graph"
+                app:popUpToInclusive="true" />
+    </fragment>
+
+    <fragment
+            android:id="@+id/configFetcher"
+            android:name="net.taler.merchantpos.config.ConfigFetcherFragment"
+            android:label="@string/config_fetching_label"
+            tools:layout="@layout/fragment_config_fetcher">
+        <action
+                android:id="@+id/action_configFetcher_to_merchantSettings"
+                app:destination="@+id/nav_settings"
+                app:launchSingleTop="true"
+                app:popUpTo="@+id/nav_graph"
+                app:popUpToInclusive="true" />
+        <action
+                android:id="@+id/action_configFetcher_to_order"
+                app:destination="@+id/nav_order"
+                app:launchSingleTop="true"
+                app:popUpTo="@+id/nav_graph"
+                app:popUpToInclusive="true" />
+    </fragment>
+
+    <fragment
+            android:id="@+id/paymentSuccess"
+            android:name="net.taler.merchantpos.payment.PaymentSuccessFragment"
+            android:label="@string/payment_received"
+            tools:layout="@layout/fragment_payment_success" />
+
+    <action
+            android:id="@+id/action_global_order"
+            app:destination="@+id/nav_order"
+            app:launchSingleTop="true"
+            app:popUpTo="@+id/nav_graph" />
+    <action
+            android:id="@+id/action_global_merchantHistory"
+            app:destination="@+id/nav_history"
+            app:launchSingleTop="true" />
+    <action
+            android:id="@+id/action_global_merchantSettings"
+            app:destination="@+id/nav_settings"
+            app:launchSingleTop="true" />
+    <action
+            android:id="@+id/action_global_configFetcher"
+            app:destination="@+id/configFetcher"
+            app:launchSingleTop="true" />
+
+</navigation>
diff --git a/merchant-terminal/src/main/res/values-night/colors.xml 
b/merchant-terminal/src/main/res/values-night/colors.xml
new file mode 100644
index 0000000..10bdbb9
--- /dev/null
+++ b/merchant-terminal/src/main/res/values-night/colors.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <color name="highlightedBackground">#2E2E2E</color>
+    <color name="selectedBackground">#363636</color>
+</resources>
diff --git a/merchant-terminal/src/main/res/values/colors.xml 
b/merchant-terminal/src/main/res/values/colors.xml
new file mode 100644
index 0000000..bf0c849
--- /dev/null
+++ b/merchant-terminal/src/main/res/values/colors.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <color name="colorPrimary">#795548</color>
+    <color name="colorPrimaryDark">#5D4037</color>
+    <color name="colorAccent">#FFEB3B</color>
+
+    <color name="highlightedBackground">#E4E4E4</color>
+    <color name="selectedBackground">#DADADA</color>
+    <color name="bottomButtons">#9E9D24</color>
+
+    <color name="green">#388E3C</color>
+    <color name="red">#C62828</color>
+
+</resources>
diff --git a/merchant-terminal/src/main/res/values/dimens.xml 
b/merchant-terminal/src/main/res/values/dimens.xml
new file mode 100644
index 0000000..eedc3c6
--- /dev/null
+++ b/merchant-terminal/src/main/res/values/dimens.xml
@@ -0,0 +1,6 @@
+<resources>
+    <dimen name="activity_horizontal_margin">16dp</dimen>
+    <dimen name="activity_vertical_margin">16dp</dimen>
+    <dimen name="nav_header_vertical_spacing">8dp</dimen>
+    <dimen name="nav_header_height">176dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/merchant-terminal/src/main/res/values/strings.xml 
b/merchant-terminal/src/main/res/values/strings.xml
new file mode 100644
index 0000000..77c7e03
--- /dev/null
+++ b/merchant-terminal/src/main/res/values/strings.xml
@@ -0,0 +1,68 @@
+<resources>
+    <string name="app_name">Taler Merchant PoS Terminal</string>
+    <string name="app_name_short">Merchant Terminal</string>
+    <string name="project_name">GNU Taler</string>
+
+    <string name="menu_order">Orders</string>
+    <string name="menu_history">History</string>
+    <string name="menu_settings">Settings</string>
+
+    <string name="order_label_title">Order #%s</string>
+    <!-- The first placeholder is the amount and the second the currency -->
+    <string name="order_total">Total: %1$.2f %2$s</string>
+    <string name="order_restart">Restart</string>
+    <string name="order_undo">Undo</string>
+    <string name="order_previous">Prev</string>
+    <string name="order_next">Next</string>
+    <string name="order_complete">Complete</string>
+
+    <string name="config_label">Merchant Settings</string>
+    <string name="config_url">Configuration URL</string>
+    <string name="config_username">Username</string>
+    <string name="config_password">Password</string>
+    <string name="config_ok">Fetch Configuration</string>
+    <string name="config_auth_error">Error: Invalid username or 
password</string>
+    <string name="config_error_network">Error: Could not connect to 
configuration server</string>
+    <string name="config_error_category">Error: No valid product category 
found</string>
+    <string name="config_error_malformed">Error: The configuration JSON is 
malformed</string>
+    <string name="config_error_currency">Error: Product %1$s has currency 
%2$s, but %3$s expected</string>
+    <string name="config_error_product_category_id">Error: Product %1$s 
references unknown category ID %2$d</string>
+    <string name="config_error_product_zero">Error: No valid products 
found</string>
+    <string name="config_error_unknown">Error: Invalid Configuration</string>
+    <string name="config_fetching">Fetching Configuration…</string>
+    <string name="config_save_password">Remember Password</string>
+    <string name="config_forget_password">Forget</string>
+    <string name="config_changed">Changed to new merchant using %s</string>
+    <string name="config_fetching_label">Fetching Configuration</string>
+    <string name="config_docs">Please refer to <a 
href="https://docs.taler.net/taler-merchant-pos-terminal.html#apis-and-data-formats";>the
 documentation</a> for the configuration format.</string>
+
+    <string name="payment_intro_nfc">Please scan QR Code or use NFC to 
pay</string>
+    <string name="payment_intro">Please scan QR Code to pay</string>
+    <string name="payment_cancel">Cancel Payment</string>
+    <string name="payment_received">Payment received</string>
+    <string name="payment_back_button">Continue</string>
+    <string name="payment_order_ref">Order Reference: %s</string>
+    <string name="payment_process_label">Customer Payment Required</string>
+    <string name="payment_canceled">Payment Canceled</string>
+
+    <string name="history_label">Payment History</string>
+    <string name="history_received_at">Received at</string>
+    <string name="history_ref_no">Ref. No: %s</string>
+    <string name="history_refund">Refund Order</string>
+    <string name="refund_amount">Amount</string>
+    <string name="refund_reason">Refund reason</string>
+    <string name="refund_abort">Abort</string>
+    <string name="refund_confirm">Give Refund</string>
+    <string name="refund_error_max_amount">Greater than order amount of 
%s</string>
+    <string name="refund_error_zero">Needs to be positive amount</string>
+    <string name="refund_error_backend">Error processing refund</string>
+    <string name="refund_error_deadline">Refund deadline has passed</string>
+    <string name="refund_intro_nfc">Please scan QR Code or use NFC to give 
refund</string>
+    <string name="refund_intro">Please scan QR Code to give refund</string>
+    <string name="refund_order_ref">Order Reference: %1$s\n\n%2$s</string>
+
+    <string name="error_network">Network Error</string>
+
+    <string name="toast_back_to_exit">Click BACK again to exit</string>
+
+</resources>
diff --git a/merchant-terminal/src/main/res/values/styles.xml 
b/merchant-terminal/src/main/res/values/styles.xml
new file mode 100644
index 0000000..4445a01
--- /dev/null
+++ b/merchant-terminal/src/main/res/values/styles.xml
@@ -0,0 +1,21 @@
+<resources>
+    <!-- Base application theme. -->
+    <style name="AppTheme" 
parent="Theme.MaterialComponents.DayNight.DarkActionBar">
+        <!-- Customize your theme here. -->
+        <item name="colorPrimary">@color/colorPrimary</item>
+        <item name="colorOnPrimary">@android:color/white</item>
+        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
+        <item name="colorAccent">@color/colorAccent</item>
+    </style>
+
+    <style name="AppTheme.NoActionBar">
+        <item name="windowActionBar">false</item>
+        <item name="windowNoTitle">true</item>
+        <item name="android:statusBarColor">@android:color/transparent</item>
+    </style>
+
+    <style name="AppTheme.AppBarOverlay" 
parent="ThemeOverlay.AppCompat.Dark.ActionBar" />
+
+    <style name="AppTheme.PopupOverlay" parent="ThemeOverlay.AppCompat.Light" 
/>
+
+</resources>
diff --git a/merchant-terminal/src/main/res/xml/backup_descriptor.xml 
b/merchant-terminal/src/main/res/xml/backup_descriptor.xml
new file mode 100644
index 0000000..6fd6103
--- /dev/null
+++ b/merchant-terminal/src/main/res/xml/backup_descriptor.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<full-backup-content>
+    <!-- Exclude specific shared preferences that contain GCM registration Id 
-->
+</full-backup-content>
diff --git 
a/merchant-terminal/src/test/java/net/taler/merchantpos/order/OrderManagerTest.kt
 
b/merchant-terminal/src/test/java/net/taler/merchantpos/order/OrderManagerTest.kt
new file mode 100644
index 0000000..cdb928a
--- /dev/null
+++ 
b/merchant-terminal/src/test/java/net/taler/merchantpos/order/OrderManagerTest.kt
@@ -0,0 +1,151 @@
+/*
+ * 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.merchantpos.order
+
+import android.app.Application
+import androidx.test.core.app.ApplicationProvider.getApplicationContext
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import 
com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES
+import com.fasterxml.jackson.databind.ObjectMapper
+import com.fasterxml.jackson.module.kotlin.KotlinModule
+import kotlinx.coroutines.runBlocking
+import net.taler.merchantpos.R
+import org.json.JSONObject
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertNull
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class OrderManagerTest {
+
+    private val mapper = ObjectMapper()
+        .registerModule(KotlinModule())
+        .configure(FAIL_ON_UNKNOWN_PROPERTIES, false)
+
+    private val app: Application = getApplicationContext()
+    private val orderManager = OrderManager(app, mapper)
+
+    @Test
+    fun `config test missing categories`() = runBlocking {
+        val json = JSONObject(
+            """
+            { "categories": [] }
+        """.trimIndent()
+        )
+        val result = orderManager.onConfigurationReceived(json, "KUDOS")
+        assertEquals(app.getString(R.string.config_error_category), result)
+    }
+
+    @Test
+    fun `config test currency mismatch`() = runBlocking {
+        val json = JSONObject(
+            """{
+            "categories": [
+                {
+                    "id": 1,
+                    "name": "Snacks"
+                }
+            ],
+            "products": [
+                {
+                    "product_id": "631361561",
+                    "description": "Chips",
+                    "price": "WRONGCURRENCY:1.00",
+                    "categories": [ 1 ],
+                    "delivery_location": "cafeteria"
+                }
+            ]
+        }""".trimIndent()
+        )
+        val result = orderManager.onConfigurationReceived(json, "KUDOS")
+        val expectedStr = app.getString(
+            R.string.config_error_currency, "Chips", "WRONGCURRENCY", "KUDOS"
+        )
+        assertEquals(expectedStr, result)
+    }
+
+    @Test
+    fun `config test unknown category ID`() = runBlocking {
+        val json = JSONObject(
+            """{
+            "categories": [
+                {
+                    "id": 1,
+                    "name": "Snacks"
+                }
+            ],
+            "products": [
+                {
+                    "product_id": "631361561",
+                    "description": "Chips",
+                    "price": "KUDOS:1.00",
+                    "categories": [ 2 ]
+                }
+            ]
+        }""".trimIndent()
+        )
+        val result = orderManager.onConfigurationReceived(json, "KUDOS")
+        val expectedStr = app.getString(
+            R.string.config_error_product_category_id, "Chips", 2
+        )
+        assertEquals(expectedStr, result)
+    }
+
+    @Test
+    fun `config test no products`() = runBlocking {
+        val json = JSONObject(
+            """{
+            "categories": [
+                {
+                    "id": 1,
+                    "name": "Snacks"
+                }
+            ],
+            "products": []
+        }""".trimIndent()
+        )
+        val result = orderManager.onConfigurationReceived(json, "KUDOS")
+        val expectedStr = app.getString(R.string.config_error_product_zero)
+        assertEquals(expectedStr, result)
+    }
+
+    @Test
+    fun `config test valid config gets accepted`() = runBlocking {
+        val json = JSONObject(
+            """{
+            "categories": [
+                {
+                    "id": 1,
+                    "name": "Snacks"
+                }
+            ],
+            "products": [
+                {
+                    "product_id": "631361561",
+                    "description": "Chips",
+                    "price": "KUDOS:1.00",
+                    "categories": [ 1 ]
+                }
+            ]
+        }""".trimIndent()
+        )
+        val result = orderManager.onConfigurationReceived(json, "KUDOS")
+        assertNull(result)
+    }
+
+}
diff --git a/nightly-stats.patch b/nightly-stats.patch
new file mode 100644
index 0000000..689f46a
--- /dev/null
+++ b/nightly-stats.patch
@@ -0,0 +1,38 @@
+diff --git a/fdroidserver/nightly.py b/fdroidserver/nightly.py
+index 0a3a8012..ae3aa0e3 100644
+--- a/fdroidserver/nightly.py
++++ b/fdroidserver/nightly.py
+@@ -170,6 +170,7 @@ def main():
+         git_mirror_path = os.path.join(repo_basedir, 'git-mirror')
+         git_mirror_repodir = os.path.join(git_mirror_path, 'fdroid', 'repo')
+         git_mirror_metadatadir = os.path.join(git_mirror_path, 'fdroid', 
'metadata')
++        git_mirror_statsdir = os.path.join(git_mirror_path, 'fdroid', 'stats')
+         if not os.path.isdir(git_mirror_repodir):
+             logging.debug(_('cloning {url}').format(url=clone_url))
+             try:
+@@ -217,6 +218,8 @@ Last updated: {date}'''.format(repo_git_base=repo_git_base,
+             common.local_rsync(options, git_mirror_repodir + '/', 'repo/')
+         if os.path.isdir(git_mirror_metadatadir):
+             common.local_rsync(options, git_mirror_metadatadir + '/', 
'metadata/')
++        if os.path.isdir(git_mirror_statsdir):
++            common.local_rsync(options, git_mirror_statsdir + '/', 'stats/')
+ 
+         ssh_private_key_file = _ssh_key_from_debug_keystore()
+         # this is needed for GitPython to find the SSH key
+@@ -246,7 +249,7 @@ Last updated: {date}'''.format(repo_git_base=repo_git_base,
+         config += "keydname = '%s'\n" % DISTINGUISHED_NAME
+         config += "make_current_version_link = False\n"
+         config += "accepted_formats = ('txt', 'yml')\n"
+-        # TODO add update_stats = True
++        config += "update_stats = True\n"
+         with open('config.py', 'w') as fp:
+             fp.write(config)
+         os.chmod('config.py', 0o600)
+@@ -293,6 +296,7 @@ Last updated: {date}'''.format(repo_git_base=repo_git_base,
+         subprocess.check_call(['fdroid', 'update', '--rename-apks', 
'--create-metadata', '--verbose'],
+                               cwd=repo_basedir)
+         common.local_rsync(options, repo_basedir + '/metadata/', 
git_mirror_metadatadir + '/')
++        common.local_rsync(options, repo_basedir + '/stats/', 
git_mirror_statsdir + '/')
+         mirror_git_repo.git.add(all=True)
+         mirror_git_repo.index.commit("update app metadata")
+ 
diff --git a/settings.gradle b/settings.gradle
new file mode 100644
index 0000000..a1882de
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1,17 @@
+/*
+ * 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/>
+ */
+
+include ':akono', ':cashier', ':merchant-terminal', ':wallet'
diff --git a/wallet/.gitignore b/wallet/.gitignore
new file mode 100644
index 0000000..741e19c
--- /dev/null
+++ b/wallet/.gitignore
@@ -0,0 +1,2 @@
+/build
+/src/main/assets
diff --git a/wallet/.gitlab-ci.yml b/wallet/.gitlab-ci.yml
new file mode 100644
index 0000000..4c1f9a8
--- /dev/null
+++ b/wallet/.gitlab-ci.yml
@@ -0,0 +1,42 @@
+.binary_deps:
+  only:
+    changes:
+      - "wallet"
+  before_script:
+    - wget 
"https://git.taler.net/wallet-android.git/plain/akono.aar?h=binary-deps"; -O 
akono/akono.aar
+    - mkdir -p app/src/main/assets
+    - wget 
"https://git.taler.net/wallet-android.git/plain/taler-wallet-android.js?h=binary-deps";
 -O app/src/main/assets/taler-wallet-android.js
+
+wallet_test:
+  stage: test
+  extends: .binary_deps
+  script: ./gradlew :wallet:lint :wallet:assembleRelease
+
+wallet_deploy_nightly:
+  stage: deploy
+  extends: .binary_deps
+  only:
+    refs:
+      - master
+  script:
+    # Ensure that key exists
+    - test -z "$DEBUG_KEYSTORE" && exit 0
+    # Rename nightly app
+    - sed -i
+      's,<string name="app_name">.*</string>,<string name="app_name">Taler 
Wallet Nightly</string>,'
+      wallet/src/main/res/values*/strings.xml
+    # Set time-based version code
+    - export versionCode=$(date '+%s')
+    - sed -i "s,^\(\s*versionCode\) *[0-9].*,\1 $versionCode," 
wallet/build.gradle
+    # Add commit to version name
+    - export versionName=$(git rev-parse --short=7 HEAD)
+    - sed -i "s,^\(\s*versionName\ *\"[0-9].*\)\",\1 ($versionName)\"," 
wallet/build.gradle
+    # Set nightly application ID
+    - sed -i "s,^\(\s*applicationId\) \"*[a-z\.].*\",\1 
\"net.taler.wallet.nightly\"," wallet/build.gradle
+    # Build the APK
+    - ./gradlew :wallet:assembleDebug
+    # START only needed while patch not accepted/released upstream
+    - apt update && apt install patch
+    - patch /usr/lib/python3/dist-packages/fdroidserver/nightly.py 
nightly-stats.patch
+    # END
+    - CI_PROJECT_URL="https://gitlab.com/gnu-taler/fdroid-repo"; 
CI_PROJECT_PATH="gnu-taler/fdroid-repo" fdroid nightly -v
diff --git a/wallet/README.md b/wallet/README.md
new file mode 100644
index 0000000..63b128b
--- /dev/null
+++ b/wallet/README.md
@@ -0,0 +1,40 @@
+GNU Taler Wallet
+================
+
+This package implements a GNU Taler wallet for Android.
+It is currently a UI for the wallet writen in TypeScript.
+
+
+Building
+========
+
+Currently, building the wallet for Android requires manually copying two
+dependencies:
+
+`akono.aar` -> `../akono/akono.aar`
+`taler-wallet-android.js` -> `src/main/assets/taler-wallet-android.js`
+
+After that, the Android wallet can be built with Gradle:
+
+    $ ./gradlew build
+
+
+Obtaining Dependencies
+======================
+
+There are two ways of obtaining the dependencies.  The easiest one is
+to use the pre-built versions, which are stored in the "binary-deps"
+branch of this repository.
+
+An easy way to access them is using a git worktree:
+
+    $ git fetch origin binary-deps
+    $ git worktree add binary-deps binary-deps
+    $ cp binary-deps/akono.aar ../akono/akono.aar
+    $ cp binary-deps/taler-wallet-android.js 
src/main/assets/taler-wallet-android.js
+    $ git worktree remove binary-deps
+
+Alternatively, you can build them yourself from the respective repositories:
+
+ * git://git.taler.net/akono.git
+ * git://git.taler.net/wallet-core.git
diff --git a/wallet/build.gradle b/wallet/build.gradle
new file mode 100644
index 0000000..c31e392
--- /dev/null
+++ b/wallet/build.gradle
@@ -0,0 +1,81 @@
+/*
+ * This file is part of GNU Taler
+ * (C) 2020 Taler Systems S.A.
+ *
+ * GNU Taler is free software; you can redistribute it and/or modify it under 
the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 3, or (at your option) any later version.
+ *
+ * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
+ * A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+ */
+
+apply plugin: 'com.android.application'
+apply plugin: 'kotlin-android'
+apply plugin: 'kotlin-android-extensions'
+
+android {
+    compileSdkVersion 29
+    buildToolsVersion "29.0.3"
+    defaultConfig {
+        applicationId "net.taler.wallet"
+        minSdkVersion 21
+        targetSdkVersion 29
+        versionCode 6
+        versionName "0.6.0pre8"
+        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+    }
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles 
getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+        }
+    }
+
+    compileOptions {
+        sourceCompatibility = 1.8
+        targetCompatibility = 1.8
+    }
+
+    kotlinOptions {
+        jvmTarget = "1.8"
+    }
+}
+
+dependencies {
+    implementation project(":akono")
+
+    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
+    implementation 'androidx.appcompat:appcompat:1.1.0'
+    implementation 'androidx.core:core-ktx:1.2.0'
+    implementation 'com.google.android.material:material:1.1.0'
+    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
+
+    // Navigation Library
+    implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
+    implementation "androidx.navigation:navigation-ui-ktx:$nav_version"
+
+    // ViewModel and LiveData
+    def lifecycle_version = "2.2.0"
+    implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
+    implementation 
"androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"
+    implementation 
"androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
+
+    // QR codes
+    implementation 'com.google.zxing:core:3.4.0'
+    implementation 'com.journeyapps:zxing-android-embedded:3.2.0@aar'
+
+    // Nicer ProgressBar
+    implementation 'me.zhanghai.android.materialprogressbar:library:1.6.1'
+
+    // JSON parsing and serialization
+    implementation 'com.fasterxml.jackson.module:jackson-module-kotlin:2.10.2'
+
+    testImplementation 'junit:junit:4.13'
+    androidTestImplementation 'androidx.test:runner:1.2.0'
+    androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
+}
diff --git a/wallet/proguard-rules.pro b/wallet/proguard-rules.pro
new file mode 100644
index 0000000..f1b4245
--- /dev/null
+++ b/wallet/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+#   http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+#   public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
diff --git 
a/wallet/src/androidTest/java/net/taler/wallet/ExampleInstrumentedTest.kt 
b/wallet/src/androidTest/java/net/taler/wallet/ExampleInstrumentedTest.kt
new file mode 100644
index 0000000..5f0c423
--- /dev/null
+++ b/wallet/src/androidTest/java/net/taler/wallet/ExampleInstrumentedTest.kt
@@ -0,0 +1,38 @@
+/*
+ * 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
+
+import androidx.test.InstrumentationRegistry
+import androidx.test.runner.AndroidJUnit4
+import org.junit.Assert.assertEquals
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+@RunWith(AndroidJUnit4::class)
+class ExampleInstrumentedTest {
+    @Test
+    fun useAppContext() {
+        // Context of the app under test.
+        val appContext = InstrumentationRegistry.getTargetContext()
+        assertEquals("net.taler.wallet", appContext.packageName)
+    }
+}
diff --git a/wallet/src/main/AndroidManifest.xml 
b/wallet/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..a61483d
--- /dev/null
+++ b/wallet/src/main/AndroidManifest.xml
@@ -0,0 +1,81 @@
+<?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/>
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android";
+        xmlns:tools="http://schemas.android.com/tools";
+        package="net.taler.wallet">
+
+    <uses-permission android:name="android.permission.NFC" />
+
+    <uses-feature
+            android:name="android.hardware.telephony"
+            android:required="false" />
+    <uses-feature
+            android:name="android.hardware.nfc.hce"
+            android:required="false" />
+
+    <application
+            android:allowBackup="true"
+            android:fullBackupContent="@xml/backup_descriptor"
+            android:icon="@mipmap/ic_launcher"
+            android:label="@string/app_name"
+            android:roundIcon="@mipmap/ic_launcher_round"
+            android:supportsRtl="true"
+            android:theme="@style/AppTheme"
+            tools:ignore="GoogleAppIndexingWarning">
+
+        <activity
+                android:name=".MainActivity"
+                android:label="@string/app_name"
+                android:theme="@style/AppTheme.NoActionBar">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+            <intent-filter>
+                <action android:name="android.intent.action.VIEW" />
+
+                <category android:name="android.intent.category.DEFAULT" />
+                <category android:name="android.intent.category.BROWSABLE" />
+
+                <data android:scheme="taler" />
+            </intent-filter>
+        </activity>
+
+        <activity
+                android:name="com.journeyapps.barcodescanner.CaptureActivity"
+                android:screenOrientation="fullSensor"
+                tools:replace="screenOrientation" />
+
+        <service
+                android:name=".HostCardEmulatorService"
+                android:exported="true"
+                android:permission="android.permission.BIND_NFC_SERVICE">
+            <intent-filter>
+                <action 
android:name="android.nfc.cardemulation.action.HOST_APDU_SERVICE" />
+            </intent-filter>
+
+            <meta-data
+                    android:name="android.nfc.cardemulation.host_apdu_service"
+                    android:resource="@xml/apduservice" />
+        </service>
+
+        <service
+                android:name=".backend.WalletBackendService"
+                android:process=":WalletBackendService" />
+    </application>
+
+</manifest>
diff --git a/wallet/src/main/ic_launcher-web.png 
b/wallet/src/main/ic_launcher-web.png
new file mode 100644
index 0000000..f0f6be7
Binary files /dev/null and b/wallet/src/main/ic_launcher-web.png differ
diff --git a/wallet/src/main/java/net/taler/wallet/Amount.kt 
b/wallet/src/main/java/net/taler/wallet/Amount.kt
new file mode 100644
index 0000000..a19e9bc
--- /dev/null
+++ b/wallet/src/main/java/net/taler/wallet/Amount.kt
@@ -0,0 +1,141 @@
+/*
+ * 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/>
+ */
+
+@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS")
+
+package net.taler.wallet
+
+import com.fasterxml.jackson.core.JsonParser
+import com.fasterxml.jackson.databind.DeserializationContext
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize
+import com.fasterxml.jackson.databind.deser.std.StdDeserializer
+import org.json.JSONObject
+import kotlin.math.round
+
+private const val FRACTIONAL_BASE = 1e8
+
+@JsonDeserialize(using = AmountDeserializer::class)
+data class Amount(val currency: String, val amount: String) {
+    fun isZero(): Boolean {
+        return amount.toDouble() == 0.0
+    }
+
+    companion object {
+        fun fromJson(jsonAmount: JSONObject): Amount {
+            val amountCurrency = jsonAmount.getString("currency")
+            val amountValue = jsonAmount.getString("value")
+            val amountFraction = jsonAmount.getString("fraction")
+            val amountIntValue = Integer.parseInt(amountValue)
+            val amountIntFraction = Integer.parseInt(amountFraction)
+            return Amount(
+                amountCurrency,
+                (amountIntValue + amountIntFraction / 
FRACTIONAL_BASE).toString()
+            )
+        }
+
+        fun fromString(strAmount: String): Amount {
+            val components = strAmount.split(":")
+            return Amount(components[0], components[1])
+        }
+    }
+
+    override fun toString(): String {
+        return String.format("%.2f $currency", amount.toDouble())
+    }
+}
+
+class AmountDeserializer : StdDeserializer<Amount>(Amount::class.java) {
+    override fun deserialize(p: JsonParser, ctxt: DeserializationContext): 
Amount {
+        val node = p.codec.readValue(p, String::class.java)
+        return Amount.fromString(node)
+    }
+}
+
+class ParsedAmount(
+    /**
+     * name of the currency using either a three-character ISO 4217 currency 
code,
+     * or a regional currency identifier starting with a "*" followed by at 
most 10 characters.
+     * ISO 4217 exponents in the name are not supported,
+     * although the "fraction" is corresponds to an ISO 4217 exponent of 6.
+     */
+    val currency: String,
+
+    /**
+     * unsigned 32 bit value in the currency,
+     * note that "1" here would correspond to 1 EUR or 1 USD, depending on 
currency, not 1 cent.
+     */
+    val value: UInt,
+
+    /**
+     * unsigned 32 bit fractional value to be added to value
+     * representing an additional currency fraction,
+     * in units of one millionth (1e-6) of the base currency value.
+     * For example, a fraction of 500,000 would correspond to 50 cents.
+     */
+    val fraction: Double
+) {
+    companion object {
+        fun parseAmount(str: String): ParsedAmount {
+            val split = str.split(":")
+            check(split.size == 2)
+            val currency = split[0]
+            val valueSplit = split[1].split(".")
+            val value = valueSplit[0].toUInt()
+            val fraction: Double = if (valueSplit.size > 1) {
+                round("0.${valueSplit[1]}".toDouble() * FRACTIONAL_BASE)
+            } else 0.0
+            return ParsedAmount(currency, value, fraction)
+        }
+    }
+
+    operator fun minus(other: ParsedAmount): ParsedAmount {
+        check(currency == other.currency) { "Can only subtract from same 
currency" }
+        var resultValue = value
+        var resultFraction = fraction
+        if (resultFraction < other.fraction) {
+            if (resultValue < 1u) {
+                return ParsedAmount(currency, 0u, 0.0)
+            }
+            resultValue--
+            resultFraction += FRACTIONAL_BASE
+        }
+        check(resultFraction >= other.fraction)
+        resultFraction -= other.fraction
+        if (resultValue < other.value) {
+            return ParsedAmount(currency, 0u, 0.0)
+        }
+        resultValue -= other.value
+        return ParsedAmount(currency, resultValue, resultFraction)
+    }
+
+    fun isZero(): Boolean {
+        return value == 0u && fraction == 0.0
+    }
+
+    @Suppress("unused")
+    fun toJSONString(): String {
+        return "$currency:${getValueString()}"
+    }
+
+    override fun toString(): String {
+        return "${getValueString()} $currency"
+    }
+
+    private fun getValueString(): String {
+        return "$value${(fraction / FRACTIONAL_BASE).toString().substring(1)}"
+    }
+
+}
diff --git a/wallet/src/main/java/net/taler/wallet/BalanceFragment.kt 
b/wallet/src/main/java/net/taler/wallet/BalanceFragment.kt
new file mode 100644
index 0000000..84a1b3c
--- /dev/null
+++ b/wallet/src/main/java/net/taler/wallet/BalanceFragment.kt
@@ -0,0 +1,198 @@
+/*
+ * 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
+
+import android.os.Bundle
+import android.transition.TransitionManager.beginDelayedTransition
+import android.util.Log
+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.View.VISIBLE
+import android.view.ViewGroup
+import android.widget.TextView
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.activityViewModels
+import androidx.lifecycle.Observer
+import androidx.recyclerview.widget.DividerItemDecoration
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.LinearLayoutManager.VERTICAL
+import androidx.recyclerview.widget.RecyclerView.Adapter
+import androidx.recyclerview.widget.RecyclerView.ViewHolder
+import com.google.zxing.integration.android.IntentIntegrator
+import com.google.zxing.integration.android.IntentIntegrator.QR_CODE_TYPES
+import kotlinx.android.synthetic.main.fragment_show_balance.*
+import net.taler.wallet.BalanceAdapter.BalanceViewHolder
+
+class BalanceFragment : Fragment() {
+
+    private val model: WalletViewModel by activityViewModels()
+    private val withdrawManager by lazy { model.withdrawManager }
+
+    private var reloadBalanceMenuItem: MenuItem? = null
+    private val balancesAdapter = BalanceAdapter()
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        setHasOptionsMenu(true)
+    }
+
+    override fun onCreateView(
+        inflater: LayoutInflater,
+        container: ViewGroup?,
+        savedInstanceState: Bundle?
+    ): View? {
+        return inflater.inflate(R.layout.fragment_show_balance, container, 
false)
+    }
+
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+        balancesList.apply {
+            layoutManager = LinearLayoutManager(context)
+            adapter = balancesAdapter
+            addItemDecoration(DividerItemDecoration(context, VERTICAL))
+        }
+
+        model.balances.observe(viewLifecycleOwner, Observer {
+            onBalancesChanged(it)
+        })
+
+        model.devMode.observe(viewLifecycleOwner, Observer { enabled ->
+            delayedTransition()
+            testWithdrawButton.visibility = if (enabled) VISIBLE else GONE
+            reloadBalanceMenuItem?.isVisible = enabled
+        })
+        testWithdrawButton.setOnClickListener {
+            withdrawManager.withdrawTestkudos()
+        }
+        withdrawManager.testWithdrawalInProgress.observe(viewLifecycleOwner, 
Observer { loading ->
+            Log.v("taler-wallet", "observing balance loading $loading in show 
balance")
+            testWithdrawButton.isEnabled = !loading
+            model.showProgressBar.value = loading
+        })
+
+        scanButton.setOnClickListener {
+            IntentIntegrator(activity).apply {
+                setPrompt("")
+                setBeepEnabled(true)
+                setOrientationLocked(false)
+            }.initiateScan(QR_CODE_TYPES)
+        }
+    }
+
+    override fun onStart() {
+        super.onStart()
+        model.loadBalances()
+    }
+
+    override fun onOptionsItemSelected(item: MenuItem): Boolean {
+        return when (item.itemId) {
+            R.id.reload_balance -> {
+                model.loadBalances()
+                true
+            }
+            R.id.developer_mode -> {
+                item.isChecked = !item.isChecked
+                model.devMode.value = item.isChecked
+                true
+            }
+            else -> super.onOptionsItemSelected(item)
+        }
+    }
+
+    override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
+        inflater.inflate(R.menu.balance, menu)
+        menu.findItem(R.id.developer_mode).isChecked = model.devMode.value!!
+        reloadBalanceMenuItem = menu.findItem(R.id.reload_balance).apply {
+            isVisible = model.devMode.value!!
+        }
+        super.onCreateOptionsMenu(menu, inflater)
+    }
+
+    private fun onBalancesChanged(balances: List<BalanceItem>) {
+        delayedTransition()
+        if (balances.isEmpty()) {
+            balancesEmptyState.visibility = VISIBLE
+            balancesList.visibility = GONE
+        } else {
+            balancesAdapter.setItems(balances)
+            balancesEmptyState.visibility = GONE
+            balancesList.visibility = VISIBLE
+        }
+    }
+
+    private fun delayedTransition() {
+        beginDelayedTransition(view as ViewGroup)
+    }
+
+}
+
+class BalanceAdapter : Adapter<BalanceViewHolder>() {
+
+    private var items = emptyList<BalanceItem>()
+
+    init {
+        setHasStableIds(false)
+    }
+
+    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): 
BalanceViewHolder {
+        val v =
+            
LayoutInflater.from(parent.context).inflate(R.layout.list_item_balance, parent, 
false)
+        return BalanceViewHolder(v)
+    }
+
+    override fun getItemCount() = items.size
+
+    override fun onBindViewHolder(holder: BalanceViewHolder, position: Int) {
+        val item = items[position]
+        holder.bind(item)
+    }
+
+    fun setItems(items: List<BalanceItem>) {
+        this.items = items
+        this.notifyDataSetChanged()
+    }
+
+    class BalanceViewHolder(private val v: View) : ViewHolder(v) {
+        private val currencyView: TextView = 
v.findViewById(R.id.balance_currency)
+        private val amountView: TextView = v.findViewById(R.id.balance_amount)
+        private val balanceInboundAmount: TextView = 
v.findViewById(R.id.balanceInboundAmount)
+        private val balanceInboundLabel: TextView = 
v.findViewById(R.id.balanceInboundLabel)
+
+        fun bind(item: BalanceItem) {
+            currencyView.text = item.available.currency
+            amountView.text = item.available.amount
+
+            val amountIncoming = item.pendingIncoming
+            if (amountIncoming.isZero()) {
+                balanceInboundAmount.visibility = GONE
+                balanceInboundLabel.visibility = GONE
+            } else {
+                balanceInboundAmount.visibility = VISIBLE
+                balanceInboundLabel.visibility = VISIBLE
+                balanceInboundAmount.text = v.context.getString(
+                    R.string.balances_inbound_amount,
+                    amountIncoming.amount,
+                    amountIncoming.currency
+                )
+            }
+        }
+    }
+
+}
diff --git a/wallet/src/main/java/net/taler/wallet/HostCardEmulatorService.kt 
b/wallet/src/main/java/net/taler/wallet/HostCardEmulatorService.kt
new file mode 100644
index 0000000..93f1d3f
--- /dev/null
+++ b/wallet/src/main/java/net/taler/wallet/HostCardEmulatorService.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
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.net.Uri
+import android.nfc.cardemulation.HostApduService
+import android.os.Bundle
+import android.util.Log
+import java.io.ByteArrayInputStream
+import java.io.ByteArrayOutputStream
+import java.util.concurrent.ConcurrentLinkedDeque
+
+fun makeApduSuccessResponse(payload: ByteArray): ByteArray {
+    val stream = ByteArrayOutputStream()
+    stream.write(payload)
+    stream.write(0x90)
+    stream.write(0x00)
+    return stream.toByteArray()
+}
+
+
+fun makeApduFailureResponse(): ByteArray {
+    val stream = ByteArrayOutputStream()
+    stream.write(0x6F)
+    stream.write(0x00)
+    return stream.toByteArray()
+}
+
+
+fun readApduBodySize(stream: ByteArrayInputStream): Int {
+    val b0 = stream.read()
+    if (b0 == -1) {
+        return 0
+    }
+    if (b0 != 0) {
+        return b0
+    }
+    val b1 = stream.read()
+    val b2 = stream.read()
+
+    return (b1 shl 8) and b2
+}
+
+
+class HostCardEmulatorService: HostApduService() {
+
+    val queuedRequests: ConcurrentLinkedDeque<String> = ConcurrentLinkedDeque()
+    private lateinit var receiver: BroadcastReceiver
+
+    override fun onCreate() {
+        super.onCreate()
+        receiver = object : BroadcastReceiver() {
+            override fun onReceive(p0: Context?, p1: Intent?) {
+                queuedRequests.addLast(p1!!.getStringExtra("tunnelMessage"))
+            }
+        }
+        IntentFilter(HTTP_TUNNEL_REQUEST).also { filter ->
+            registerReceiver(receiver, filter)
+        }
+    }
+
+    override fun onDestroy() {
+        super.onDestroy()
+        unregisterReceiver(receiver)
+    }
+
+    override fun onDeactivated(reason: Int) {
+        Log.d(TAG, "Deactivated: $reason")
+        Intent().also { intent ->
+            intent.action = MERCHANT_NFC_DISCONNECTED
+            sendBroadcast(intent)
+        }
+    }
+
+    override fun processCommandApdu(commandApdu: ByteArray?,
+                                    extras: Bundle?): ByteArray {
+
+        Log.d(TAG, "Processing command APDU")
+
+        if (commandApdu == null) {
+            Log.d(TAG, "APDU is null")
+            return makeApduFailureResponse()
+        }
+
+        val stream = ByteArrayInputStream(commandApdu)
+
+        val command = stream.read()
+
+        if (command != 0) {
+            Log.d(TAG, "APDU has invalid command")
+            return makeApduFailureResponse()
+        }
+
+        val instruction = stream.read()
+
+        // Read instruction parameters, currently ignored.
+        stream.read()
+        stream.read()
+
+        if (instruction == SELECT_INS) {
+            // FIXME: validate body!
+            return makeApduSuccessResponse(ByteArray(0))
+        }
+
+        if (instruction == GET_INS) {
+            val req = queuedRequests.poll()
+            return if (req != null) {
+                Log.v(TAG,"sending tunnel request")
+                makeApduSuccessResponse(req.toByteArray(Charsets.UTF_8))
+            } else {
+                makeApduSuccessResponse(ByteArray(0))
+            }
+        }
+
+        if (instruction == PUT_INS) {
+            val bodySize = readApduBodySize(stream)
+            val talerInstr = stream.read()
+            val bodyBytes = stream.readBytes()
+            if (1 + bodyBytes.size != bodySize) {
+                Log.w(TAG, "mismatched body size ($bodySize vs 
${bodyBytes.size}")
+            }
+
+            when (talerInstr) {
+                1 -> {
+                    val url = String(bodyBytes, Charsets.UTF_8)
+                    Log.v(TAG, "got URL: '$url'")
+
+                    Intent(this, MainActivity::class.java).also { intent ->
+                        intent.data = Uri.parse(url)
+                        intent.action = Intent.ACTION_VIEW
+                        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+                        startActivity(intent)
+                    }
+                }
+                2 -> {
+                    Log.v(TAG, "got http response: 
${bodyBytes.toString(Charsets.UTF_8)}")
+
+                    Intent().also { intent ->
+                        intent.action = HTTP_TUNNEL_RESPONSE
+                        intent.putExtra("response", 
bodyBytes.toString(Charsets.UTF_8))
+                        sendBroadcast(intent)
+                    }
+                }
+                else -> {
+                    Log.v(TAG, "taler instruction $talerInstr unknown")
+                }
+            }
+
+            return makeApduSuccessResponse(ByteArray(0))
+        }
+
+        return makeApduFailureResponse()
+    }
+
+    companion object {
+        const val TAG = "taler-wallet-hce"
+        const val SELECT_INS = 0xA4
+        const val PUT_INS = 0xDA
+        const val GET_INS = 0xCA
+
+        const val TRIGGER_PAYMENT_ACTION = "net.taler.TRIGGER_PAYMENT_ACTION"
+
+        const val MERCHANT_NFC_CONNECTED = "net.taler.MERCHANT_NFC_CONNECTED"
+        const val MERCHANT_NFC_DISCONNECTED = 
"net.taler.MERCHANT_NFC_DISCONNECTED"
+
+        const val HTTP_TUNNEL_RESPONSE = "net.taler.HTTP_TUNNEL_RESPONSE"
+        const val HTTP_TUNNEL_REQUEST = "net.taler.HTTP_TUNNEL_REQUEST"
+    }
+}
\ No newline at end of file
diff --git a/wallet/src/main/java/net/taler/wallet/MainActivity.kt 
b/wallet/src/main/java/net/taler/wallet/MainActivity.kt
new file mode 100644
index 0000000..c2f20f7
--- /dev/null
+++ b/wallet/src/main/java/net/taler/wallet/MainActivity.kt
@@ -0,0 +1,209 @@
+/*
+ * 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
+
+import android.annotation.SuppressLint
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.Intent.ACTION_VIEW
+import android.content.IntentFilter
+import android.os.Bundle
+import android.util.Log
+import android.view.MenuItem
+import android.view.View.GONE
+import android.view.View.INVISIBLE
+import android.view.View.VISIBLE
+import android.widget.TextView
+import androidx.activity.viewModels
+import androidx.appcompat.app.AppCompatActivity
+import androidx.core.view.GravityCompat.START
+import androidx.lifecycle.Observer
+import androidx.navigation.NavController
+import androidx.navigation.fragment.NavHostFragment
+import androidx.navigation.ui.AppBarConfiguration
+import androidx.navigation.ui.setupWithNavController
+import 
com.google.android.material.navigation.NavigationView.OnNavigationItemSelectedListener
+import com.google.android.material.snackbar.Snackbar
+import com.google.android.material.snackbar.Snackbar.LENGTH_SHORT
+import com.google.zxing.integration.android.IntentIntegrator
+import 
com.google.zxing.integration.android.IntentIntegrator.parseActivityResult
+import kotlinx.android.synthetic.main.activity_main.*
+import kotlinx.android.synthetic.main.app_bar_main.*
+import net.taler.wallet.BuildConfig.VERSION_CODE
+import net.taler.wallet.BuildConfig.VERSION_NAME
+import net.taler.wallet.HostCardEmulatorService.Companion.HTTP_TUNNEL_RESPONSE
+import 
net.taler.wallet.HostCardEmulatorService.Companion.MERCHANT_NFC_CONNECTED
+import 
net.taler.wallet.HostCardEmulatorService.Companion.MERCHANT_NFC_DISCONNECTED
+import 
net.taler.wallet.HostCardEmulatorService.Companion.TRIGGER_PAYMENT_ACTION
+import java.util.Locale.ROOT
+
+class MainActivity : AppCompatActivity(), OnNavigationItemSelectedListener,
+    ResetDialogEventListener {
+
+    private val model: WalletViewModel by viewModels()
+
+    private lateinit var nav: NavController
+
+    @SuppressLint("SetTextI18n")
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        setContentView(R.layout.activity_main)
+
+        val navHostFragment =
+            supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as 
NavHostFragment
+        nav = navHostFragment.navController
+        nav_view.setupWithNavController(nav)
+        nav_view.setNavigationItemSelectedListener(this)
+        if (savedInstanceState == null) {
+            nav_view.menu.getItem(0).isChecked = true
+        }
+
+        setSupportActionBar(toolbar)
+        val appBarConfiguration = AppBarConfiguration(
+            setOf(R.id.showBalance, R.id.settings, R.id.walletHistory, 
R.id.nav_pending_operations), drawer_layout
+        )
+        toolbar.setupWithNavController(nav, appBarConfiguration)
+
+        model.showProgressBar.observe(this, Observer { show ->
+            progress_bar.visibility = if (show) VISIBLE else INVISIBLE
+        })
+
+        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
+            if (enabled) {
+                @SuppressLint("SetTextI18n")
+                versionView.text = "$VERSION_NAME ($VERSION_CODE)"
+                versionView.visibility = VISIBLE
+            } else versionView.visibility = GONE
+        })
+
+        if (intent.action == ACTION_VIEW) intent.dataString?.let { uri ->
+            handleTalerUri(uri, "intent")
+        }
+
+        //model.startTunnel()
+
+        registerReceiver(triggerPaymentReceiver, 
IntentFilter(TRIGGER_PAYMENT_ACTION))
+        registerReceiver(nfcConnectedReceiver, 
IntentFilter(MERCHANT_NFC_CONNECTED))
+        registerReceiver(nfcDisconnectedReceiver, 
IntentFilter(MERCHANT_NFC_DISCONNECTED))
+        registerReceiver(tunnelResponseReceiver, 
IntentFilter(HTTP_TUNNEL_RESPONSE))
+    }
+
+    override fun onBackPressed() {
+        if (drawer_layout.isDrawerOpen(START)) drawer_layout.closeDrawer(START)
+        else super.onBackPressed()
+    }
+
+    override fun onNavigationItemSelected(item: MenuItem): Boolean {
+        when (item.itemId) {
+            R.id.nav_home -> nav.navigate(R.id.showBalance)
+            R.id.nav_settings -> nav.navigate(R.id.settings)
+            R.id.nav_history -> nav.navigate(R.id.walletHistory)
+            R.id.nav_pending_operations -> 
nav.navigate(R.id.nav_pending_operations)
+        }
+        drawer_layout.closeDrawer(START)
+        return true
+    }
+
+    override fun onActivityResult(requestCode: Int, resultCode: Int, data: 
Intent?) {
+        super.onActivityResult(requestCode, resultCode, data)
+        if (requestCode == IntentIntegrator.REQUEST_CODE) {
+            parseActivityResult(requestCode, resultCode, data)?.contents?.let 
{ contents ->
+                handleTalerUri(contents, "QR code")
+            }
+        }
+    }
+
+    override fun onDestroy() {
+        unregisterReceiver(triggerPaymentReceiver)
+        unregisterReceiver(nfcConnectedReceiver)
+        unregisterReceiver(nfcDisconnectedReceiver)
+        unregisterReceiver(tunnelResponseReceiver)
+        super.onDestroy()
+    }
+
+    private fun handleTalerUri(url: String, from: String) {
+        when {
+            url.toLowerCase(ROOT).startsWith("taler://pay/") -> {
+                Log.v(TAG, "navigating!")
+                nav.navigate(R.id.action_showBalance_to_promptPayment)
+                model.paymentManager.preparePay(url)
+            }
+            url.toLowerCase(ROOT).startsWith("taler://withdraw/") -> {
+                Log.v(TAG, "navigating!")
+                nav.navigate(R.id.action_showBalance_to_promptWithdraw)
+                model.withdrawManager.getWithdrawalInfo(url)
+            }
+            url.toLowerCase(ROOT).startsWith("taler://refund/") -> {
+                // TODO implement refunds
+                Snackbar.make(nav_view, "Refunds are not yet implemented", 
LENGTH_SHORT).show()
+            }
+            else -> {
+                Snackbar.make(
+                    nav_view,
+                    "URL from $from doesn't contain a supported Taler Uri.",
+                    LENGTH_SHORT
+                ).show()
+            }
+        }
+    }
+
+    private val triggerPaymentReceiver = object : BroadcastReceiver() {
+        override fun onReceive(context: Context, intent: Intent) {
+            if (nav.currentDestination?.id == R.id.promptPayment) return
+            intent.extras?.getString("contractUrl")?.let { url ->
+                nav.navigate(R.id.action_global_promptPayment)
+                model.paymentManager.preparePay(url)
+            }
+        }
+    }
+
+    private val nfcConnectedReceiver = object : BroadcastReceiver() {
+        override fun onReceive(context: Context, intent: Intent) {
+            Log.v(TAG, "got MERCHANT_NFC_CONNECTED")
+            //model.startTunnel()
+        }
+    }
+
+    private val nfcDisconnectedReceiver = object : BroadcastReceiver() {
+        override fun onReceive(context: Context, intent: Intent) {
+            Log.v(TAG, "got MERCHANT_NFC_DISCONNECTED")
+            //model.stopTunnel()
+        }
+    }
+
+    private val tunnelResponseReceiver = object : BroadcastReceiver() {
+        override fun onReceive(context: Context, intent: Intent) {
+            Log.v("taler-tunnel", "got HTTP_TUNNEL_RESPONSE")
+            intent.getStringExtra("response")?.let {
+                model.tunnelResponse(it)
+            }
+        }
+    }
+
+    override fun onResetConfirmed() {
+        model.dangerouslyReset()
+        Snackbar.make(nav_view, "Wallet has been reset", LENGTH_SHORT).show()
+    }
+
+    override fun onResetCancelled() {
+        Snackbar.make(nav_view, "Reset cancelled", LENGTH_SHORT).show()
+    }
+
+}
diff --git a/wallet/src/main/java/net/taler/wallet/Settings.kt 
b/wallet/src/main/java/net/taler/wallet/Settings.kt
new file mode 100644
index 0000000..6d10412
--- /dev/null
+++ b/wallet/src/main/java/net/taler/wallet/Settings.kt
@@ -0,0 +1,140 @@
+/*
+ * 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
+
+import android.app.Dialog
+import android.content.Context
+import android.content.Intent
+import android.content.Intent.ACTION_CREATE_DOCUMENT
+import android.content.Intent.ACTION_OPEN_DOCUMENT
+import android.content.Intent.CATEGORY_OPENABLE
+import android.content.Intent.EXTRA_TITLE
+import android.os.Bundle
+import android.util.Log
+import android.view.LayoutInflater
+import android.view.View
+import android.view.View.GONE
+import android.view.View.VISIBLE
+import android.view.ViewGroup
+import androidx.appcompat.app.AlertDialog
+import androidx.fragment.app.DialogFragment
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.activityViewModels
+import androidx.lifecycle.Observer
+import kotlinx.android.synthetic.main.fragment_settings.*
+
+
+interface ResetDialogEventListener {
+    fun onResetConfirmed()
+    fun onResetCancelled()
+}
+
+
+class ResetDialogFragment : DialogFragment() {
+    private lateinit var listener: ResetDialogEventListener
+
+    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
+        return activity?.let {
+            // Use the Builder class for convenient dialog construction
+            val builder = AlertDialog.Builder(it)
+            builder.setMessage("Do you really want to reset the wallet and 
lose all coins and purchases?  Consider making a backup first.")
+                .setPositiveButton("Reset") { _, _ ->
+                    listener.onResetConfirmed()
+                }
+                .setNegativeButton("Cancel") { _, _ ->
+                    listener.onResetCancelled()
+                }
+            // Create the AlertDialog object and return it
+            builder.create()
+        } ?: throw IllegalStateException("Activity cannot be null")
+    }
+
+    override fun onAttach(context: Context) {
+        super.onAttach(context)
+        // Verify that the host activity implements the callback interface
+        try {
+            // Instantiate the NoticeDialogListener so we can send events to 
the host
+            listener = context as ResetDialogEventListener
+        } catch (e: ClassCastException) {
+            // The activity doesn't implement the interface, throw exception
+            throw ClassCastException((context.toString() +
+                    " must implement ResetDialogEventListener"))
+        }
+    }
+}
+
+class Settings : Fragment() {
+
+    companion object {
+        private const val TAG = "taler-wallet"
+        private const val CREATE_FILE = 1
+        private const val PICK_FILE = 2
+    }
+
+    private val model: WalletViewModel by activityViewModels()
+
+    override fun onCreateView(
+        inflater: LayoutInflater, container: ViewGroup?,
+        savedInstanceState: Bundle?
+    ): View? {
+        return inflater.inflate(R.layout.fragment_settings, container, false)
+    }
+
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+        model.devMode.observe(viewLifecycleOwner, Observer { enabled ->
+            val visibility = if (enabled) VISIBLE else GONE
+            devSettingsTitle.visibility = visibility
+            button_reset_wallet_dangerously.visibility = visibility
+        })
+
+        textView4.text = BuildConfig.VERSION_NAME
+        button_reset_wallet_dangerously.setOnClickListener {
+            val d = ResetDialogFragment()
+            d.show(parentFragmentManager, "walletResetDialog")
+        }
+        button_backup_export.setOnClickListener {
+            val intent = Intent(ACTION_CREATE_DOCUMENT).apply {
+                addCategory(CATEGORY_OPENABLE)
+                type = "application/json"
+                putExtra(EXTRA_TITLE, "taler-wallet-backup.json")
+
+                // Optionally, specify a URI for the directory that should be 
opened in
+                // the system file picker before your app creates the document.
+                //putExtra(DocumentsContract.EXTRA_INITIAL_URI, 
pickerInitialUri)
+            }
+            startActivityForResult(intent, CREATE_FILE)
+        }
+        button_backup_import.setOnClickListener {
+            val intent = Intent(ACTION_OPEN_DOCUMENT).apply {
+                addCategory(CATEGORY_OPENABLE)
+                type = "application/json"
+
+                //putExtra(DocumentsContract.EXTRA_INITIAL_URI, 
pickerInitialUri)
+            }
+            startActivityForResult(intent, PICK_FILE)
+        }
+    }
+
+    override fun onActivityResult(requestCode: Int, resultCode: Int, data: 
Intent?) {
+        if (data == null) return
+        when (requestCode) {
+            CREATE_FILE -> Log.i(TAG, "got createFile result with URL 
${data.data}")
+            PICK_FILE -> Log.i(TAG, "got pickFile result with URL 
${data.data}")
+        }
+    }
+
+}
diff --git a/wallet/src/main/java/net/taler/wallet/Utils.kt 
b/wallet/src/main/java/net/taler/wallet/Utils.kt
new file mode 100644
index 0000000..fb0b3ae
--- /dev/null
+++ b/wallet/src/main/java/net/taler/wallet/Utils.kt
@@ -0,0 +1,40 @@
+/*
+ * 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
+
+import android.view.View
+import android.view.View.INVISIBLE
+import android.view.View.VISIBLE
+
+fun View.fadeIn(endAction: () -> Unit = {}) {
+    if (visibility == VISIBLE) return
+    alpha = 0f
+    visibility = VISIBLE
+    animate().alpha(1f).withEndAction {
+        if (context != null) endAction.invoke()
+    }.start()
+}
+
+fun View.fadeOut(endAction: () -> Unit = {}) {
+    if (visibility == INVISIBLE) return
+    animate().alpha(0f).withEndAction {
+        if (context == null) return@withEndAction
+        visibility = INVISIBLE
+        alpha = 1f
+        endAction.invoke()
+    }.start()
+}
diff --git a/wallet/src/main/java/net/taler/wallet/WalletViewModel.kt 
b/wallet/src/main/java/net/taler/wallet/WalletViewModel.kt
new file mode 100644
index 0000000..14a800f
--- /dev/null
+++ b/wallet/src/main/java/net/taler/wallet/WalletViewModel.kt
@@ -0,0 +1,124 @@
+/*
+ * 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
+
+import android.app.Application
+import android.util.Log
+import androidx.annotation.UiThread
+import androidx.lifecycle.AndroidViewModel
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.distinctUntilChanged
+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.wallet.backend.WalletBackendApi
+import net.taler.wallet.history.HistoryManager
+import net.taler.wallet.payment.PaymentManager
+import net.taler.wallet.pending.PendingOperationsManager
+import net.taler.wallet.withdraw.WithdrawManager
+import org.json.JSONObject
+
+const val TAG = "taler-wallet"
+
+data class BalanceItem(val available: Amount, val pendingIncoming: Amount)
+
+class WalletViewModel(val app: Application) : AndroidViewModel(app) {
+
+    private val mBalances = MutableLiveData<List<BalanceItem>>()
+    val balances: LiveData<List<BalanceItem>> = 
mBalances.distinctUntilChanged()
+
+    val devMode = MutableLiveData(BuildConfig.DEBUG)
+    val showProgressBar = MutableLiveData<Boolean>()
+
+    private var activeGetBalance = 0
+
+    private val walletBackendApi = WalletBackendApi(app, {
+        activeGetBalance = 0
+        loadBalances()
+        pendingOperationsManager.getPending()
+    }) {
+        Log.i(TAG, "Received notification from wallet-core")
+        loadBalances()
+        pendingOperationsManager.getPending()
+    }
+
+    private val mapper = ObjectMapper()
+        .registerModule(KotlinModule())
+        .configure(FAIL_ON_UNKNOWN_PROPERTIES, false)
+
+    val withdrawManager = WithdrawManager(walletBackendApi)
+    val paymentManager = PaymentManager(walletBackendApi, mapper)
+    val pendingOperationsManager: PendingOperationsManager =
+        PendingOperationsManager(walletBackendApi)
+    val historyManager = HistoryManager(walletBackendApi, mapper)
+
+    override fun onCleared() {
+        walletBackendApi.destroy()
+        super.onCleared()
+    }
+
+    @UiThread
+    fun loadBalances() {
+        if (activeGetBalance > 0) {
+            return
+        }
+        activeGetBalance++
+        showProgressBar.value = true
+        walletBackendApi.sendRequest("getBalances", null) { isError, result ->
+            activeGetBalance--
+            if (isError) {
+                return@sendRequest
+            }
+            val balanceList = mutableListOf<BalanceItem>()
+            val byCurrency = result.getJSONObject("byCurrency")
+            val currencyList = byCurrency.keys().asSequence().toList().sorted()
+            for (currency in currencyList) {
+                val jsonAmount = byCurrency.getJSONObject(currency)
+                    .getJSONObject("available")
+                val amount = Amount.fromJson(jsonAmount)
+                val jsonAmountIncoming = byCurrency.getJSONObject(currency)
+                    .getJSONObject("pendingIncoming")
+                val amountIncoming = Amount.fromJson(jsonAmountIncoming)
+                balanceList.add(BalanceItem(amount, amountIncoming))
+            }
+            mBalances.postValue(balanceList)
+            showProgressBar.postValue(false)
+        }
+    }
+
+    @UiThread
+    fun dangerouslyReset() {
+        walletBackendApi.sendRequest("reset", null)
+        withdrawManager.testWithdrawalInProgress.value = false
+        mBalances.value = emptyList()
+    }
+
+    fun startTunnel() {
+        walletBackendApi.sendRequest("startTunnel", null)
+    }
+
+    fun stopTunnel() {
+        walletBackendApi.sendRequest("stopTunnel", null)
+    }
+
+    fun tunnelResponse(resp: String) {
+        val respJson = JSONObject(resp)
+        walletBackendApi.sendRequest("tunnelResponse", respJson)
+    }
+
+}
diff --git a/wallet/src/main/java/net/taler/wallet/backend/WalletBackendApi.kt 
b/wallet/src/main/java/net/taler/wallet/backend/WalletBackendApi.kt
new file mode 100644
index 0000000..d447287
--- /dev/null
+++ b/wallet/src/main/java/net/taler/wallet/backend/WalletBackendApi.kt
@@ -0,0 +1,141 @@
+/*
+ * This file is part of GNU Taler
+ * (C) 2020 Taler Systems S.A.
+ *
+ * GNU Taler is free software; you can redistribute it and/or modify it under 
the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 3, or (at your option) any later version.
+ *
+ * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
+ * A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+ */
+
+
+package net.taler.wallet.backend
+
+import android.app.Application
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.content.ServiceConnection
+import android.os.Handler
+import android.os.IBinder
+import android.os.Message
+import android.os.Messenger
+import android.util.Log
+import android.util.SparseArray
+import org.json.JSONObject
+import java.lang.ref.WeakReference
+import java.util.*
+
+class WalletBackendApi(
+    private val app: Application,
+    private val onConnected: (() -> Unit),
+    private val notificationHandler: (() -> Unit)
+) {
+
+    private var walletBackendMessenger: Messenger? = null
+    private val queuedMessages = LinkedList<Message>()
+    private val handlers = SparseArray<(isError: Boolean, message: JSONObject) 
-> Unit>()
+    private var nextRequestID = 1
+
+    private val walletBackendConn = object : ServiceConnection {
+        override fun onServiceDisconnected(p0: ComponentName?) {
+            Log.w(TAG, "wallet backend service disconnected (crash?)")
+            walletBackendMessenger = null
+        }
+
+        override fun onServiceConnected(componentName: ComponentName?, binder: 
IBinder?) {
+            Log.i(TAG, "connected to wallet backend service")
+            val bm = Messenger(binder)
+            walletBackendMessenger = bm
+            pumpQueue(bm)
+            val msg = Message.obtain(null, 
WalletBackendService.MSG_SUBSCRIBE_NOTIFY)
+            msg.replyTo = incomingMessenger
+            bm.send(msg)
+            onConnected.invoke()
+        }
+    }
+
+    private class IncomingHandler(strongApi: WalletBackendApi) : Handler() {
+        private val weakApi = WeakReference(strongApi)
+        override fun handleMessage(msg: Message) {
+            val api = weakApi.get() ?: return
+            when (msg.what) {
+                WalletBackendService.MSG_REPLY -> {
+                    val requestID = msg.data.getInt("requestID", 0)
+                    val operation = msg.data.getString("operation", "??")
+                    Log.i(TAG, "got reply for operation $operation 
($requestID)")
+                    val h = api.handlers.get(requestID)
+                    if (h == null) {
+                        Log.e(TAG, "request ID not associated with a handler")
+                        return
+                    }
+                    val response = msg.data.getString("response")
+                    if (response == null) {
+                        Log.e(TAG, "response did not contain response payload")
+                        return
+                    }
+                    val isError = msg.data.getBoolean("isError")
+                    val json = JSONObject(response)
+                    h(isError, json)
+                }
+                WalletBackendService.MSG_NOTIFY -> {
+                    api.notificationHandler.invoke()
+                }
+            }
+        }
+    }
+
+    private val incomingMessenger = Messenger(IncomingHandler(this))
+
+    init {
+        Intent(app, WalletBackendService::class.java).also { intent ->
+            app.bindService(intent, walletBackendConn, 
Context.BIND_AUTO_CREATE)
+        }
+    }
+
+    private fun pumpQueue(bm: Messenger) {
+        while (true) {
+            val msg = queuedMessages.pollFirst() ?: return
+            bm.send(msg)
+        }
+    }
+
+
+    fun sendRequest(
+        operation: String,
+        args: JSONObject?,
+        onResponse: (isError: Boolean, message: JSONObject) -> Unit = { _, _ 
-> }
+    ) {
+        val requestID = nextRequestID++
+        Log.i(TAG, "sending request for operation $operation ($requestID)")
+        val msg = Message.obtain(null, WalletBackendService.MSG_COMMAND)
+        handlers.put(requestID, onResponse)
+        msg.replyTo = incomingMessenger
+        val data = msg.data
+        data.putString("operation", operation)
+        data.putInt("requestID", requestID)
+        if (args != null) {
+            data.putString("args", args.toString())
+        }
+        val bm = walletBackendMessenger
+        if (bm != null) {
+            bm.send(msg)
+        } else {
+            queuedMessages.add(msg)
+        }
+    }
+
+    fun destroy() {
+        // FIXME: implement this!
+    }
+
+    companion object {
+        const val TAG = "WalletBackendApi"
+    }
+}
diff --git 
a/wallet/src/main/java/net/taler/wallet/backend/WalletBackendService.kt 
b/wallet/src/main/java/net/taler/wallet/backend/WalletBackendService.kt
new file mode 100644
index 0000000..0b71774
--- /dev/null
+++ b/wallet/src/main/java/net/taler/wallet/backend/WalletBackendService.kt
@@ -0,0 +1,239 @@
+/*
+ * This file is part of GNU Taler
+ * (C) 2020 Taler Systems S.A.
+ *
+ * GNU Taler is free software; you can redistribute it and/or modify it under 
the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 3, or (at your option) any later version.
+ *
+ * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
+ * A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+ */
+
+
+package net.taler.wallet.backend
+
+import akono.AkonoJni
+import android.app.Service
+import android.content.Intent
+import android.os.Handler
+import android.os.IBinder
+import android.os.Message
+import android.os.Messenger
+import android.os.RemoteException
+import android.util.Log
+import net.taler.wallet.HostCardEmulatorService
+import org.json.JSONObject
+import java.lang.ref.WeakReference
+import java.util.*
+import java.util.concurrent.ConcurrentHashMap
+import kotlin.system.exitProcess
+
+private const val TAG = "taler-wallet-backend"
+
+class RequestData(val clientRequestID: Int, val messenger: Messenger)
+
+
+class WalletBackendService : Service() {
+    /**
+     * Target we publish for clients to send messages to IncomingHandler.
+     */
+    private val messenger: Messenger = Messenger(IncomingHandler(this))
+
+    private lateinit var akono: AkonoJni
+
+    private var initialized = false
+
+    private var nextRequestID = 1
+
+    private val requests = ConcurrentHashMap<Int, RequestData>()
+
+    private val subscribers = LinkedList<Messenger>()
+
+    override fun onCreate() {
+        val talerWalletAndroidCode = 
assets.open("taler-wallet-android.js").use {
+            it.readBytes().toString(Charsets.UTF_8)
+        }
+
+
+        Log.i(TAG, "onCreate in wallet backend service")
+        akono = AkonoJni()
+        akono.putModuleCode("taler-wallet-android", talerWalletAndroidCode)
+        akono.setMessageHandler(object : AkonoJni.MessageHandler {
+            override fun handleMessage(message: String) {
+                this@WalletBackendService.handleAkonoMessage(message)
+            }
+        })
+        akono.evalNodeCode("console.log('hello world from taler 
wallet-android')")
+        //akono.evalNodeCode("require('source-map-support').install();")
+        akono.evalNodeCode("require('akono');")
+        akono.evalNodeCode("tw = require('taler-wallet-android');")
+        akono.evalNodeCode("tw.installAndroidWalletListener();")
+        sendInitMessage()
+        initialized = true
+        super.onCreate()
+    }
+
+    private fun sendInitMessage() {
+        val msg = JSONObject()
+        msg.put("operation", "init")
+        val args = JSONObject()
+        msg.put("args", args)
+        args.put("persistentStoragePath", 
"${application.filesDir}/talerwalletdb-v30.json")
+        akono.sendMessage(msg.toString())
+    }
+
+    /**
+     * Handler of incoming messages from clients.
+     */
+    class IncomingHandler(
+        service: WalletBackendService
+    ) : Handler() {
+
+        private val serviceWeakRef = WeakReference(service)
+
+        override fun handleMessage(msg: Message) {
+            val svc = serviceWeakRef.get() ?: return
+            when (msg.what) {
+                MSG_COMMAND -> {
+                    val data = msg.data
+                    val serviceRequestID = svc.nextRequestID++
+                    val clientRequestID = data.getInt("requestID", 0)
+                    if (clientRequestID == 0) {
+                        Log.e(TAG, "client requestID missing")
+                        return
+                    }
+                    val args = data.getString("args")
+                    val argsObj = if (args == null) {
+                        JSONObject()
+                    } else {
+                        JSONObject(args)
+                    }
+                    val operation = data.getString("operation", "")
+                    if (operation == "") {
+                        Log.e(TAG, "client command missing")
+                        return
+                    }
+                    Log.i(TAG, "got request for operation $operation")
+                    val request = JSONObject()
+                    request.put("operation", operation)
+                    request.put("id", serviceRequestID)
+                    request.put("args", argsObj)
+                    svc.akono.sendMessage(request.toString(2))
+                    Log.i(
+                        TAG,
+                        "mapping service request ID $serviceRequestID to 
client request ID $clientRequestID"
+                    )
+                    svc.requests[serviceRequestID] = 
RequestData(clientRequestID, msg.replyTo)
+                }
+                MSG_SUBSCRIBE_NOTIFY -> {
+                    Log.i(TAG, "subscribing client")
+                    val r = msg.replyTo
+                    if (r == null) {
+                        Log.e(
+                            TAG,
+                            "subscriber did not specify replyTo object in 
MSG_SUBSCRIBE_NOTIFY"
+                        )
+                    } else {
+                        svc.subscribers.add(msg.replyTo)
+                    }
+                }
+                MSG_UNSUBSCRIBE_NOTIFY -> {
+                    Log.i(TAG, "unsubscribing client")
+                    svc.subscribers.remove(msg.replyTo)
+                }
+                else -> {
+                    Log.e(TAG, "unknown message from client")
+                    super.handleMessage(msg)
+                }
+            }
+        }
+    }
+
+    override fun onBind(p0: Intent?): IBinder? {
+        return messenger.binder
+    }
+
+    private fun sendNotify() {
+        var rm: LinkedList<Messenger>? = null
+        for (s in subscribers) {
+            val m = Message.obtain(null, MSG_NOTIFY)
+            try {
+                s.send(m)
+            } catch (e: RemoteException) {
+                if (rm == null) {
+                    rm = LinkedList()
+                }
+                rm.add(s)
+                subscribers.remove(s)
+            }
+        }
+        if (rm != null) {
+            for (s in rm) {
+                subscribers.remove(s)
+            }
+        }
+    }
+
+    private fun handleAkonoMessage(messageStr: String) {
+        Log.v(TAG, "got back message: $messageStr")
+        val message = JSONObject(messageStr)
+        when (message.getString("type")) {
+            "notification" -> {
+                sendNotify()
+            }
+            "tunnelHttp" -> {
+                Log.v(TAG, "got http tunnel request!")
+                Intent().also { intent ->
+                    intent.action = HostCardEmulatorService.HTTP_TUNNEL_REQUEST
+                    intent.putExtra("tunnelMessage", messageStr)
+                    application.sendBroadcast(intent)
+                }
+            }
+            "response" -> {
+                when (val operation = message.getString("operation")) {
+                    "init" -> {
+                        Log.v(TAG, "got response for init operation")
+                        sendNotify()
+                    }
+                    "reset" -> {
+                        exitProcess(1)
+                    }
+                    else -> {
+                        val id = message.getInt("id")
+                        Log.v(TAG, "got response for operation $operation")
+                        val rd = requests[id]
+                        if (rd == null) {
+                            Log.e(TAG, "wallet returned unknown request ID 
($id)")
+                            return
+                        }
+                        val m = Message.obtain(null, MSG_REPLY)
+                        val b = m.data
+                        if (message.has("result")) {
+                            val respJson = message.getJSONObject("result")
+                            b.putString("response", respJson.toString(2))
+                        } else {
+                            b.putString("response", "{}")
+                        }
+                        b.putBoolean("isError", message.getBoolean("isError"))
+                        b.putInt("requestID", rd.clientRequestID)
+                        b.putString("operation", operation)
+                        rd.messenger.send(m)
+                    }
+                }
+            }
+        }
+    }
+
+    companion object {
+        const val MSG_SUBSCRIBE_NOTIFY = 1
+        const val MSG_UNSUBSCRIBE_NOTIFY = 2
+        const val MSG_COMMAND = 3
+        const val MSG_REPLY = 4
+        const val MSG_NOTIFY = 5
+    }
+}
diff --git a/wallet/src/main/java/net/taler/wallet/crypto/Encoding.kt 
b/wallet/src/main/java/net/taler/wallet/crypto/Encoding.kt
new file mode 100644
index 0000000..25a59be
--- /dev/null
+++ b/wallet/src/main/java/net/taler/wallet/crypto/Encoding.kt
@@ -0,0 +1,134 @@
+/*
+ * This file is part of GNU Taler
+ * (C) 2020 Taler Systems S.A.
+ *
+ * GNU Taler is free software; you can redistribute it and/or modify it under 
the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 3, or (at your option) any later version.
+ *
+ * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
+ * A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+ */
+
+package net.taler.wallet.crypto
+
+import java.io.ByteArrayOutputStream
+
+class EncodingException : Exception("Invalid encoding")
+
+
+object Base32Crockford {
+
+    private fun ByteArray.getIntAt(index: Int): Int {
+        val x = this[index].toInt()
+        return if (x >= 0) x else (x + 256)
+    }
+
+    private var encTable = "0123456789ABCDEFGHJKMNPQRSTVWXYZ"
+
+    fun encode(data: ByteArray): String {
+        val sb = StringBuilder()
+        val size = data.size
+        var bitBuf = 0
+        var numBits = 0
+        var pos = 0
+        while (pos < size || numBits > 0) {
+            if (pos < size && numBits < 5) {
+                val d = data.getIntAt(pos++)
+                bitBuf = (bitBuf shl 8) or d
+                numBits += 8
+            }
+            if (numBits < 5) {
+                // zero-padding
+                bitBuf = bitBuf shl (5 - numBits)
+                numBits = 5
+            }
+            val v = bitBuf.ushr(numBits - 5) and 31
+            sb.append(encTable[v])
+            numBits -= 5
+        }
+        return sb.toString()
+    }
+
+    fun decode(encoded: String, out: ByteArrayOutputStream) {
+        val size = encoded.length
+        var bitpos = 0
+        var bitbuf = 0
+        var readPosition = 0
+
+        while (readPosition < size || bitpos > 0) {
+            //println("at position $readPosition with bitpos $bitpos")
+            if (readPosition < size) {
+                val v = getValue(encoded[readPosition++])
+                bitbuf = (bitbuf shl 5) or v
+                bitpos += 5
+            }
+            while (bitpos >= 8) {
+                val d = (bitbuf ushr (bitpos - 8)) and 0xFF
+                out.write(d)
+                bitpos -= 8
+            }
+            if (readPosition == size && bitpos > 0) {
+                bitbuf = (bitbuf shl (8 - bitpos)) and 0xFF
+                bitpos = if (bitbuf == 0) 0 else 8
+            }
+        }
+    }
+
+    fun decode(encoded: String): ByteArray {
+        val out = ByteArrayOutputStream()
+        decode(encoded, out)
+        return out.toByteArray()
+    }
+
+    private fun getValue(chr: Char): Int {
+        var a = chr
+        when (a) {
+            'O', 'o' -> a = '0'
+            'i', 'I', 'l', 'L' -> a = '1'
+            'u', 'U' -> a = 'V'
+        }
+        if (a in '0'..'9')
+            return a - '0'
+        if (a in 'a'..'z')
+            a = Character.toUpperCase(a)
+        var dec = 0
+        if (a in 'A'..'Z') {
+            if ('I' < a) dec++
+            if ('L' < a) dec++
+            if ('O' < a) dec++
+            if ('U' < a) dec++
+            return a - 'A' + 10 - dec
+        }
+        throw EncodingException()
+    }
+
+    /**
+     * Compute the length of the resulting string when encoding data of the 
given size
+     * in bytes.
+     *
+     * @param dataSize size of the data to encode in bytes
+     * @return size of the string that would result from encoding
+     */
+    @Suppress("unused")
+    fun calculateEncodedStringLength(dataSize: Int): Int {
+        return (dataSize * 8 + 4) / 5
+    }
+
+    /**
+     * Compute the length of the resulting data in bytes when decoding a 
(valid) string of the
+     * given size.
+     *
+     * @param stringSize size of the string to decode
+     * @return size of the resulting data in bytes
+     */
+    @Suppress("unused")
+    fun calculateDecodedDataLength(stringSize: Int): Int {
+        return stringSize * 5 / 8
+    }
+}
+
diff --git a/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..9e5c99d
--- /dev/null
+++ b/wallet/src/main/java/net/taler/wallet/history/HistoryEvent.kt
@@ -0,0 +1,452 @@
+/*
+ * 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 androidx.annotation.LayoutRes
+import androidx.annotation.StringRes
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties
+import com.fasterxml.jackson.annotation.JsonInclude
+import com.fasterxml.jackson.annotation.JsonInclude.Include.NON_EMPTY
+import com.fasterxml.jackson.annotation.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.wallet.ParsedAmount.Companion.parseAmount
+import net.taler.wallet.R
+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 Timestamp(
+    @JsonProperty("t_ms")
+    val ms: Long
+)
+
+@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
+)
+
+typealias History = ArrayList<HistoryEvent>
+
+@JsonTypeInfo(
+    use = NAME,
+    include = PROPERTY,
+    property = "type",
+    defaultImpl = HistoryUnknownEvent::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 = ReserveBalanceUpdatedEvent::class, name = 
"reserve-balance-updated"),
+    Type(value = HistoryWithdrawnEvent::class, name = "withdrawn"),
+    Type(value = HistoryOrderAcceptedEvent::class, name = "order-accepted"),
+    Type(value = HistoryOrderRefusedEvent::class, name = "order-refused"),
+    Type(value = HistoryOrderRedirectedEvent::class, name = 
"order-redirected"),
+    Type(value = HistoryPaymentSentEvent::class, name = "payment-sent"),
+    Type(value = HistoryPaymentAbortedEvent::class, name = "payment-aborted"),
+    Type(value = HistoryTipAcceptedEvent::class, name = "tip-accepted"),
+    Type(value = HistoryTipDeclinedEvent::class, name = "tip-declined"),
+    Type(value = HistoryRefundedEvent::class, name = "refund"),
+    Type(value = HistoryRefreshedEvent::class, name = "refreshed")
+)
+@JsonIgnoreProperties(
+    value = [
+        "eventId"
+    ]
+)
+abstract class HistoryEvent(
+    val timestamp: Timestamp,
+    @get:LayoutRes
+    open val layout: Int = R.layout.history_row,
+    @get:StringRes
+    open val title: Int = 0,
+    @get:DrawableRes
+    open val icon: Int = R.drawable.ic_account_balance,
+    open val showToUser: Boolean = false
+) {
+    open lateinit var json: JSONObject
+}
+
+
+class HistoryUnknownEvent(timestamp: Timestamp) : HistoryEvent(timestamp) {
+    override val title = R.string.history_event_unknown
+}
+
+@JsonTypeName("exchange-added")
+class ExchangeAddedEvent(
+    timestamp: Timestamp,
+    val exchangeBaseUrl: String,
+    val builtIn: Boolean
+) : HistoryEvent(timestamp) {
+    override val title = R.string.history_event_exchange_added
+}
+
+@JsonTypeName("exchange-updated")
+class ExchangeUpdatedEvent(
+    timestamp: Timestamp,
+    val exchangeBaseUrl: String
+) : HistoryEvent(timestamp) {
+    override val title = R.string.history_event_exchange_updated
+}
+
+
+@JsonTypeName("reserve-balance-updated")
+class ReserveBalanceUpdatedEvent(
+    timestamp: Timestamp,
+    val newHistoryTransactions: List<ReserveTransaction>,
+    /**
+     * Condensed information about the reserve.
+     */
+    val reserveShortInfo: ReserveShortInfo,
+    /**
+     * Amount currently left in the reserve.
+     */
+    val amountReserveBalance: String,
+    /**
+     * Amount we expected to be in the reserve at that time,
+     * considering ongoing withdrawals from that reserve.
+     */
+    val amountExpected: String
+) : HistoryEvent(timestamp) {
+    override val title = R.string.history_event_reserve_balance_updated
+}
+
+@JsonTypeName("withdrawn")
+class HistoryWithdrawnEvent(
+    timestamp: Timestamp,
+    /**
+     * 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 withdrawSessionId: String,
+    val withdrawalSource: WithdrawalSource,
+    /**
+     * Amount that has been subtracted from the reserve's balance
+     * for this withdrawal.
+     */
+    val amountWithdrawnRaw: String,
+    /**
+     * Amount that actually was added to the wallet's balance.
+     */
+    val amountWithdrawnEffective: String
+) : HistoryEvent(timestamp) {
+    override val layout = R.layout.history_receive
+    override val title = R.string.history_event_withdrawn
+    override val icon = R.drawable.history_withdrawn
+    override val showToUser = true
+}
+
+@JsonTypeName("order-accepted")
+class HistoryOrderAcceptedEvent(
+    timestamp: Timestamp,
+    /**
+     * Condensed info about the order.
+     */
+    val orderShortInfo: OrderShortInfo
+) : HistoryEvent(timestamp) {
+    override val icon = R.drawable.ic_add_circle
+    override val title = R.string.history_event_order_accepted
+}
+
+@JsonTypeName("order-refused")
+class HistoryOrderRefusedEvent(
+    timestamp: Timestamp,
+    /**
+     * Condensed info about the order.
+     */
+    val orderShortInfo: OrderShortInfo
+) : HistoryEvent(timestamp) {
+    override val icon = R.drawable.ic_cancel
+    override val title = R.string.history_event_order_refused
+}
+
+@JsonTypeName("payment-sent")
+class HistoryPaymentSentEvent(
+    timestamp: Timestamp,
+    /**
+     * 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: String,
+    /**
+     * Session ID that the payment was (re-)submitted under.
+     */
+    val sessionId: String?
+) : HistoryEvent(timestamp) {
+    override val layout = R.layout.history_payment
+    override val title = R.string.history_event_payment_sent
+    override val icon = R.drawable.ic_cash_usd_outline
+    override val showToUser = true
+}
+
+@JsonTypeName("payment-aborted")
+class HistoryPaymentAbortedEvent(
+    timestamp: Timestamp,
+    /**
+     * 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: String
+) : HistoryEvent(timestamp) {
+    override val layout = R.layout.history_payment
+    override val title = R.string.history_event_payment_aborted
+    override val icon = R.drawable.history_payment_aborted
+    override val showToUser = true
+}
+
+@JsonTypeName("refreshed")
+class HistoryRefreshedEvent(
+    timestamp: Timestamp,
+    /**
+     * Amount that is now available again because it has
+     * been refreshed.
+     */
+    val amountRefreshedEffective: String,
+    /**
+     * Amount that we spent for refreshing.
+     */
+    val amountRefreshedRaw: String,
+    /**
+     * 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
+) : HistoryEvent(timestamp) {
+    override val layout = R.layout.history_payment
+    override val icon = R.drawable.history_refresh
+    override val title = R.string.history_event_refreshed
+    override val showToUser =
+        !(parseAmount(amountRefreshedRaw) - 
parseAmount(amountRefreshedEffective)).isZero()
+}
+
+@JsonTypeName("order-redirected")
+class HistoryOrderRedirectedEvent(
+    timestamp: Timestamp,
+    /**
+     * 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
+) : HistoryEvent(timestamp) {
+    override val icon = R.drawable.ic_directions
+    override val title = R.string.history_event_order_redirected
+}
+
+@JsonTypeName("tip-accepted")
+class HistoryTipAcceptedEvent(
+    timestamp: Timestamp,
+    /**
+     * Unique identifier for the tip to query more information.
+     */
+    val tipId: String,
+    /**
+     * Raw amount of the tip, without extra fees that apply.
+     */
+    val tipRaw: String
+) : HistoryEvent(timestamp) {
+    override val icon = R.drawable.history_tip_accepted
+    override val title = R.string.history_event_tip_accepted
+    override val layout = R.layout.history_receive
+    override val showToUser = true
+}
+
+@JsonTypeName("tip-declined")
+class HistoryTipDeclinedEvent(
+    timestamp: Timestamp,
+    /**
+     * Unique identifier for the tip to query more information.
+     */
+    val tipId: String,
+    /**
+     * Raw amount of the tip, without extra fees that apply.
+     */
+    val tipAmount: String
+) : HistoryEvent(timestamp) {
+    override val icon = R.drawable.history_tip_declined
+    override val title = R.string.history_event_tip_declined
+    override val layout = R.layout.history_receive
+    override val showToUser = true
+}
+
+@JsonTypeName("refund")
+class HistoryRefundedEvent(
+    timestamp: Timestamp,
+    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: String,
+    /**
+     * Amount that has been refunded by the merchant.
+     */
+    val amountRefundedRaw: String,
+    /**
+     * Amount will be added to the wallet's balance after fees and refreshing.
+     */
+    val amountRefundedEffective: String
+) : HistoryEvent(timestamp) {
+    override val icon = R.drawable.history_refund
+    override val title = R.string.history_event_refund
+    override val layout = R.layout.history_receive
+    override val showToUser = true
+}
+
+@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: String,
+    /**
+     * Summary of the proposal, given by the merchant.
+     */
+    val summary: String
+)
diff --git a/wallet/src/main/java/net/taler/wallet/history/HistoryManager.kt 
b/wallet/src/main/java/net/taler/wallet/history/HistoryManager.kt
new file mode 100644
index 0000000..c350daa
--- /dev/null
+++ b/wallet/src/main/java/net/taler/wallet/history/HistoryManager.kt
@@ -0,0 +1,71 @@
+/*
+ * 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.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.Dispatchers
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.onCompletion
+import kotlinx.coroutines.flow.onStart
+import net.taler.wallet.backend.WalletBackendApi
+
+@Suppress("EXPERIMENTAL_API_USAGE")
+class HistoryManager(
+    private val walletBackendApi: WalletBackendApi,
+    private val mapper: ObjectMapper
+) {
+
+    private val mProgress = MutableLiveData<Boolean>()
+    val progress: LiveData<Boolean> = mProgress
+
+    val showAll = MutableLiveData<Boolean>()
+
+    val history: LiveData<History> = showAll.switchMap { showAll ->
+        loadHistory(showAll)
+            .onStart { mProgress.postValue(true) }
+            .onCompletion { mProgress.postValue(false) }
+            .asLiveData(Dispatchers.IO)
+    }
+
+    private fun loadHistory(showAll: Boolean) = callbackFlow {
+        walletBackendApi.sendRequest("getHistory", null) { isError, result ->
+            if (isError) {
+                // TODO show error message in [WalletHistory] fragment
+                close()
+                return@sendRequest
+            }
+            val history = History()
+            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
+            offer(if (showAll) history else history.filter { it.showToUser } 
as History)
+            close()
+        }
+        awaitClose()
+    }
+
+}
diff --git 
a/wallet/src/main/java/net/taler/wallet/history/JsonDialogFragment.kt 
b/wallet/src/main/java/net/taler/wallet/history/JsonDialogFragment.kt
new file mode 100644
index 0000000..f51dba9
--- /dev/null
+++ b/wallet/src/main/java/net/taler/wallet/history/JsonDialogFragment.kt
@@ -0,0 +1,50 @@
+/*
+ * This file is part of GNU Taler
+ * (C) 2020 Taler Systems S.A.
+ *
+ * GNU Taler is free software; you can redistribute it and/or modify it under 
the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 3, or (at your option) any later version.
+ *
+ * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
+ * A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+ */
+
+package net.taler.wallet.history
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.fragment.app.DialogFragment
+import kotlinx.android.synthetic.main.fragment_json.*
+import net.taler.wallet.R
+
+class JsonDialogFragment : DialogFragment() {
+
+    companion object {
+        fun new(json: String): JsonDialogFragment {
+            return JsonDialogFragment().apply {
+                arguments = Bundle().apply { putString("json", json) }
+            }
+        }
+    }
+
+    override fun onCreateView(
+        inflater: LayoutInflater,
+        container: ViewGroup?,
+        savedInstanceState: Bundle?
+    ): View? {
+        return inflater.inflate(R.layout.fragment_json, container, false)
+    }
+
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+        val json = arguments!!.getString("json")
+        jsonView.text = json
+    }
+
+}
diff --git 
a/wallet/src/main/java/net/taler/wallet/history/ReserveTransaction.kt 
b/wallet/src/main/java/net/taler/wallet/history/ReserveTransaction.kt
new file mode 100644
index 0000000..45c539c
--- /dev/null
+++ b/wallet/src/main/java/net/taler/wallet/history/ReserveTransaction.kt
@@ -0,0 +1,58 @@
+/*
+ * 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 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
+
+
+@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/history/WalletHistoryAdapter.kt 
b/wallet/src/main/java/net/taler/wallet/history/WalletHistoryAdapter.kt
new file mode 100644
index 0000000..71bdebc
--- /dev/null
+++ b/wallet/src/main/java/net/taler/wallet/history/WalletHistoryAdapter.kt
@@ -0,0 +1,243 @@
+/*
+ * 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.annotation.SuppressLint
+import android.graphics.Paint.STRIKE_THRU_TEXT_FLAG
+import android.text.format.DateUtils.DAY_IN_MILLIS
+import android.text.format.DateUtils.FORMAT_ABBREV_MONTH
+import android.text.format.DateUtils.FORMAT_ABBREV_RELATIVE
+import android.text.format.DateUtils.FORMAT_NO_YEAR
+import android.text.format.DateUtils.FORMAT_SHOW_DATE
+import android.text.format.DateUtils.FORMAT_SHOW_TIME
+import android.text.format.DateUtils.MINUTE_IN_MILLIS
+import android.text.format.DateUtils.formatDateTime
+import android.text.format.DateUtils.getRelativeTimeSpanString
+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.annotation.CallSuper
+import androidx.core.net.toUri
+import androidx.recyclerview.widget.RecyclerView.Adapter
+import androidx.recyclerview.widget.RecyclerView.ViewHolder
+import net.taler.wallet.BuildConfig
+import net.taler.wallet.ParsedAmount
+import net.taler.wallet.ParsedAmount.Companion.parseAmount
+import net.taler.wallet.R
+
+
+internal class WalletHistoryAdapter(
+    private val listener: OnEventClickListener,
+    private var history: History = History()
+) : Adapter<WalletHistoryAdapter.HistoryEventViewHolder>() {
+
+    init {
+        setHasStableIds(false)
+    }
+
+    override fun getItemViewType(position: Int): Int = history[position].layout
+
+    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): 
HistoryEventViewHolder {
+        val view = LayoutInflater.from(parent.context).inflate(viewType, 
parent, false)
+        return when (viewType) {
+            R.layout.history_receive -> HistoryReceiveViewHolder(view)
+            R.layout.history_payment -> HistoryPaymentViewHolder(view)
+            else -> GenericHistoryEventViewHolder(view)
+        }
+    }
+
+    override fun getItemCount(): Int = history.size
+
+    override fun onBindViewHolder(holder: HistoryEventViewHolder, position: 
Int) {
+        val event = history[position]
+        holder.bind(event)
+    }
+
+    fun update(updatedHistory: History) {
+        this.history = updatedHistory
+        this.notifyDataSetChanged()
+    }
+
+    internal abstract inner class HistoryEventViewHolder(protected val v: 
View) : ViewHolder(v) {
+
+        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)
+
+        @CallSuper
+        open fun bind(event: HistoryEvent) {
+            if (BuildConfig.DEBUG) {  // doesn't produce recycling issues, no 
need to cover all cases
+                v.setOnClickListener { listener.onEventClicked(event) }
+            } else {
+                v.background = null
+            }
+            icon.setImageResource(event.icon)
+            if (event.title == 0) title.text = event::class.java.simpleName
+            else title.setText(event.title)
+            time.text = getRelativeTime(event.timestamp.ms)
+        }
+
+        private fun getRelativeTime(timestamp: Long): CharSequence {
+            val now = System.currentTimeMillis()
+            return if (now - timestamp > DAY_IN_MILLIS * 2) {
+                formatDateTime(
+                    v.context,
+                    timestamp,
+                    FORMAT_SHOW_TIME or FORMAT_SHOW_DATE or 
FORMAT_ABBREV_MONTH or FORMAT_NO_YEAR
+                )
+            } else {
+                getRelativeTimeSpanString(timestamp, now, MINUTE_IN_MILLIS, 
FORMAT_ABBREV_RELATIVE)
+            }
+        }
+
+    }
+
+    internal inner class GenericHistoryEventViewHolder(v: View) : 
HistoryEventViewHolder(v) {
+
+        private val info: TextView = v.findViewById(R.id.info)
+
+        override fun bind(event: HistoryEvent) {
+            super.bind(event)
+            info.text = when (event) {
+                is ExchangeAddedEvent -> event.exchangeBaseUrl
+                is ExchangeUpdatedEvent -> event.exchangeBaseUrl
+                is ReserveBalanceUpdatedEvent -> 
parseAmount(event.amountReserveBalance).toString()
+                is HistoryPaymentSentEvent -> event.orderShortInfo.summary
+                is HistoryOrderAcceptedEvent -> event.orderShortInfo.summary
+                is HistoryOrderRefusedEvent -> event.orderShortInfo.summary
+                is HistoryOrderRedirectedEvent -> 
event.newOrderShortInfo.summary
+                else -> ""
+            }
+        }
+
+    }
+
+    internal inner class HistoryReceiveViewHolder(v: View) : 
HistoryEventViewHolder(v) {
+
+        private val summary: TextView = v.findViewById(R.id.summary)
+        private val amountWithdrawn: TextView = 
v.findViewById(R.id.amountWithdrawn)
+        private val feeLabel: TextView = v.findViewById(R.id.feeLabel)
+        private val fee: TextView = v.findViewById(R.id.fee)
+
+        override fun bind(event: HistoryEvent) {
+            super.bind(event)
+            when (event) {
+                is HistoryWithdrawnEvent -> bind(event)
+                is HistoryRefundedEvent -> bind(event)
+                is HistoryTipAcceptedEvent -> bind(event)
+                is HistoryTipDeclinedEvent -> bind(event)
+            }
+        }
+
+        private fun bind(event: HistoryWithdrawnEvent) {
+            title.text = getHostname(event.exchangeBaseUrl)
+            summary.setText(event.title)
+
+            val parsedEffective = parseAmount(event.amountWithdrawnEffective)
+            val parsedRaw = parseAmount(event.amountWithdrawnRaw)
+            showAmounts(parsedEffective, parsedRaw)
+        }
+
+        private fun bind(event: HistoryRefundedEvent) {
+            title.text = event.orderShortInfo.summary
+            summary.setText(event.title)
+
+            val parsedEffective = parseAmount(event.amountRefundedEffective)
+            val parsedRaw = parseAmount(event.amountRefundedRaw)
+            showAmounts(parsedEffective, parsedRaw)
+        }
+
+        private fun bind(event: HistoryTipAcceptedEvent) {
+            title.setText(event.title)
+            summary.text = null
+            val amount = parseAmount(event.tipRaw)
+            showAmounts(amount, amount)
+        }
+
+        private fun bind(event: HistoryTipDeclinedEvent) {
+            title.setText(event.title)
+            summary.text = null
+            val amount = parseAmount(event.tipAmount)
+            showAmounts(amount, amount)
+            amountWithdrawn.paintFlags = amountWithdrawn.paintFlags or 
STRIKE_THRU_TEXT_FLAG
+        }
+
+        private fun showAmounts(effective: ParsedAmount, raw: ParsedAmount) {
+            @SuppressLint("SetTextI18n")
+            amountWithdrawn.text = "+$raw"
+            val calculatedFee = raw - effective
+            if (calculatedFee.isZero()) {
+                fee.visibility = GONE
+                feeLabel.visibility = GONE
+            } else {
+                @SuppressLint("SetTextI18n")
+                fee.text = "-$calculatedFee"
+                fee.visibility = VISIBLE
+                feeLabel.visibility = VISIBLE
+            }
+            amountWithdrawn.paintFlags = fee.paintFlags
+        }
+
+        private fun getHostname(url: String): String {
+            return url.toUri().host!!
+        }
+
+    }
+
+    internal inner class HistoryPaymentViewHolder(v: View) : 
HistoryEventViewHolder(v) {
+
+        private val summary: TextView = v.findViewById(R.id.summary)
+        private val amountPaidWithFees: TextView = 
v.findViewById(R.id.amountPaidWithFees)
+
+        override fun bind(event: HistoryEvent) {
+            super.bind(event)
+            summary.setText(event.title)
+            when (event) {
+                is HistoryPaymentSentEvent -> bind(event)
+                is HistoryPaymentAbortedEvent -> bind(event)
+                is HistoryRefreshedEvent -> bind(event)
+            }
+        }
+
+        private fun bind(event: HistoryPaymentSentEvent) {
+            title.text = event.orderShortInfo.summary
+            @SuppressLint("SetTextI18n")
+            amountPaidWithFees.text = 
"-${parseAmount(event.amountPaidWithFees)}"
+        }
+
+        private fun bind(event: HistoryPaymentAbortedEvent) {
+            title.text = event.orderShortInfo.summary
+            @SuppressLint("SetTextI18n")
+            amountPaidWithFees.text = "-${parseAmount(event.amountLost)}"
+        }
+
+        private fun bind(event: HistoryRefreshedEvent) {
+            title.text = ""
+            val fee =
+                parseAmount(event.amountRefreshedRaw) - 
parseAmount(event.amountRefreshedEffective)
+            @SuppressLint("SetTextI18n")
+            if (fee.isZero()) amountPaidWithFees.text = null
+            else amountPaidWithFees.text = "-$fee"
+        }
+
+    }
+
+}
diff --git 
a/wallet/src/main/java/net/taler/wallet/history/WalletHistoryFragment.kt 
b/wallet/src/main/java/net/taler/wallet/history/WalletHistoryFragment.kt
new file mode 100644
index 0000000..4f8ab82
--- /dev/null
+++ b/wallet/src/main/java/net/taler/wallet/history/WalletHistoryFragment.kt
@@ -0,0 +1,115 @@
+/*
+ * This file is part of GNU Taler
+ * (C) 2020 Taler Systems S.A.
+ *
+ * GNU Taler is free software; you can redistribute it and/or modify it under 
the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 3, or (at your option) any later version.
+ *
+ * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
+ * A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+ */
+
+package net.taler.wallet.history
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.Menu
+import android.view.MenuInflater
+import android.view.MenuItem
+import android.view.View
+import android.view.View.INVISIBLE
+import android.view.View.VISIBLE
+import android.view.ViewGroup
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.activityViewModels
+import androidx.lifecycle.Observer
+import androidx.recyclerview.widget.DividerItemDecoration
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.LinearLayoutManager.VERTICAL
+import kotlinx.android.synthetic.main.fragment_show_balance.*
+import kotlinx.android.synthetic.main.fragment_show_history.*
+import net.taler.wallet.R
+import net.taler.wallet.WalletViewModel
+
+interface OnEventClickListener {
+    fun onEventClicked(event: HistoryEvent)
+}
+
+class WalletHistoryFragment : Fragment(), OnEventClickListener {
+
+    private val model: WalletViewModel by activityViewModels()
+    private val historyManager by lazy { model.historyManager }
+    private lateinit var showAllItem: MenuItem
+    private var reloadHistoryItem: MenuItem? = null
+    private val historyAdapter = WalletHistoryAdapter(this)
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        setHasOptionsMenu(true)
+    }
+
+    override fun onCreateView(
+        inflater: LayoutInflater, container: ViewGroup?,
+        savedInstanceState: Bundle?
+    ): View? {
+        return inflater.inflate(R.layout.fragment_show_history, container, 
false)
+    }
+
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+        historyList.apply {
+            layoutManager = LinearLayoutManager(context)
+            adapter = historyAdapter
+            addItemDecoration(DividerItemDecoration(context, VERTICAL))
+        }
+
+        model.devMode.observe(viewLifecycleOwner, Observer { enabled ->
+            reloadHistoryItem?.isVisible = enabled
+        })
+        historyManager.progress.observe(viewLifecycleOwner, Observer { show ->
+            historyProgressBar.visibility = if (show) VISIBLE else INVISIBLE
+        })
+        historyManager.history.observe(viewLifecycleOwner, Observer { history 
->
+            historyEmptyState.visibility = if (history.isEmpty()) VISIBLE else 
INVISIBLE
+            historyAdapter.update(history)
+        })
+
+        // kicks off initial load, needs to be adapted if showAll state is 
ever saved
+        if (savedInstanceState == null) historyManager.showAll.value = 
model.devMode.value
+    }
+
+    override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
+        inflater.inflate(R.menu.history, menu)
+        showAllItem = menu.findItem(R.id.show_all_history)
+        showAllItem.isChecked = historyManager.showAll.value == true
+        reloadHistoryItem = menu.findItem(R.id.reload_history).apply {
+            isVisible = model.devMode.value!!
+        }
+    }
+
+    override fun onOptionsItemSelected(item: MenuItem): Boolean {
+        return when (item.itemId) {
+            R.id.show_all_history -> {
+                item.isChecked = !item.isChecked
+                historyManager.showAll.value = item.isChecked
+                true
+            }
+            R.id.reload_history -> {
+                historyManager.showAll.value = showAllItem.isChecked
+                true
+            }
+            else -> super.onOptionsItemSelected(item)
+        }
+    }
+
+    override fun onEventClicked(event: HistoryEvent) {
+        if (model.devMode.value != true) return
+        JsonDialogFragment.new(event.json.toString(4))
+            .show(parentFragmentManager, null)
+    }
+
+}
diff --git 
a/wallet/src/main/java/net/taler/wallet/payment/AlreadyPaidFragment.kt 
b/wallet/src/main/java/net/taler/wallet/payment/AlreadyPaidFragment.kt
new file mode 100644
index 0000000..33e3a1d
--- /dev/null
+++ b/wallet/src/main/java/net/taler/wallet/payment/AlreadyPaidFragment.kt
@@ -0,0 +1,47 @@
+/*
+ * 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_already_paid.*
+import net.taler.wallet.R
+
+/**
+ * Display the message that the user already paid for the order
+ * that the merchant is proposing.
+ */
+class AlreadyPaidFragment : Fragment() {
+
+    override fun onCreateView(
+        inflater: LayoutInflater, container: ViewGroup?,
+        savedInstanceState: Bundle?
+    ): View? {
+        return inflater.inflate(R.layout.fragment_already_paid, container, 
false)
+    }
+
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+        backButton.setOnClickListener {
+            findNavController().navigateUp()
+        }
+    }
+
+}
diff --git a/wallet/src/main/java/net/taler/wallet/payment/ContractTerms.kt 
b/wallet/src/main/java/net/taler/wallet/payment/ContractTerms.kt
new file mode 100644
index 0000000..da91dea
--- /dev/null
+++ b/wallet/src/main/java/net/taler/wallet/payment/ContractTerms.kt
@@ -0,0 +1,56 @@
+/*
+ * This file is part of GNU Taler
+ * (C) 2020 Taler Systems S.A.
+ *
+ * GNU Taler is free software; you can redistribute it and/or modify it under 
the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 3, or (at your option) any later version.
+ *
+ * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
+ * A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+ */
+
+package net.taler.wallet.payment
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties
+import com.fasterxml.jackson.annotation.JsonProperty
+import net.taler.wallet.Amount
+
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+data class ContractTerms(
+    val summary: String,
+    val products: List<ContractProduct>,
+    val amount: Amount
+)
+
+interface Product {
+    val id: String?
+    val description: String
+    val price: Amount
+    val location: String?
+    val image: String?
+}
+
+@JsonIgnoreProperties("totalPrice")
+data class ContractProduct(
+    @JsonProperty("product_id")
+    override val id: String?,
+    override val description: String,
+    override val price: Amount,
+    @JsonProperty("delivery_location")
+    override val location: String?,
+    override val image: String?,
+    val quantity: Int
+) : Product {
+
+    val totalPrice: Amount by lazy {
+        val amount = price.amount.toDouble() * quantity
+        Amount(price.currency, amount.toString())
+    }
+
+}
diff --git a/wallet/src/main/java/net/taler/wallet/payment/PaymentManager.kt 
b/wallet/src/main/java/net/taler/wallet/payment/PaymentManager.kt
new file mode 100644
index 0000000..ee0edaf
--- /dev/null
+++ b/wallet/src/main/java/net/taler/wallet/payment/PaymentManager.kt
@@ -0,0 +1,160 @@
+/*
+ * 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.util.Log
+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 net.taler.wallet.Amount
+import net.taler.wallet.TAG
+import net.taler.wallet.backend.WalletBackendApi
+import org.json.JSONObject
+import java.net.MalformedURLException
+
+val REGEX_PRODUCT_IMAGE = 
Regex("^data:image/(jpeg|png);base64,([A-Za-z0-9+/=]+)$")
+
+class PaymentManager(
+    private val walletBackendApi: WalletBackendApi,
+    private val mapper: ObjectMapper
+) {
+
+    private val mPayStatus = MutableLiveData<PayStatus>(PayStatus.None)
+    internal val payStatus: LiveData<PayStatus> = mPayStatus
+
+    private val mDetailsShown = MutableLiveData<Boolean>()
+    internal val detailsShown: LiveData<Boolean> = mDetailsShown
+
+    private var currentPayRequestId = 0
+
+    @UiThread
+    fun preparePay(url: String) {
+        mPayStatus.value = PayStatus.Loading
+        mDetailsShown.value = false
+
+        val args = JSONObject(mapOf("url" to url))
+
+        currentPayRequestId += 1
+        val payRequestId = currentPayRequestId
+
+        walletBackendApi.sendRequest("preparePay", args) { isError, result ->
+            when {
+                isError -> {
+                    Log.v(TAG, "got preparePay error result")
+                    mPayStatus.value = PayStatus.Error(result.toString())
+                }
+                payRequestId != this.currentPayRequestId -> {
+                    Log.v(TAG, "preparePay result was for old request")
+                }
+                else -> {
+                    val status = result.getString("status")
+                    try {
+                        mPayStatus.postValue(getPayStatusUpdate(status, 
result))
+                    } catch (e: Exception) {
+                        Log.e(TAG, "Error getting PayStatusUpdate", e)
+                        mPayStatus.postValue(PayStatus.Error(e.message ?: 
"unknown error"))
+                    }
+                }
+            }
+        }
+    }
+
+    private fun getPayStatusUpdate(status: String, json: JSONObject) = when 
(status) {
+        "payment-possible" -> PayStatus.Prepared(
+            contractTerms = getContractTerms(json),
+            proposalId = json.getString("proposalId"),
+            totalFees = Amount.fromJson(json.getJSONObject("totalFees"))
+        )
+        "paid" -> PayStatus.AlreadyPaid(getContractTerms(json))
+        "insufficient-balance" -> 
PayStatus.InsufficientBalance(getContractTerms(json))
+        "error" -> PayStatus.Error("got some error")
+        else -> PayStatus.Error("unknown status")
+    }
+
+    private fun getContractTerms(json: JSONObject): ContractTerms {
+        val terms: ContractTerms = 
mapper.readValue(json.getString("contractTermsRaw"))
+        // validate product images
+        terms.products.forEach { product ->
+            product.image?.let { image ->
+                if (REGEX_PRODUCT_IMAGE.matchEntire(image) == null) {
+                    throw MalformedURLException("Invalid image data URL for 
${product.description}")
+                }
+            }
+        }
+        return terms
+    }
+
+    @UiThread
+    fun toggleDetailsShown() {
+        val oldValue = mDetailsShown.value ?: false
+        mDetailsShown.value = !oldValue
+    }
+
+    fun confirmPay(proposalId: String) {
+        val args = JSONObject(mapOf("proposalId" to proposalId))
+
+        walletBackendApi.sendRequest("confirmPay", args) { _, _ ->
+            mPayStatus.postValue(PayStatus.Success)
+        }
+    }
+
+    @UiThread
+    fun abortPay() {
+        val ps = payStatus.value
+        if (ps is PayStatus.Prepared) {
+            abortProposal(ps.proposalId)
+        }
+        resetPayStatus()
+    }
+
+    internal fun abortProposal(proposalId: String) {
+        val args = JSONObject(mapOf("proposalId" to proposalId))
+
+        Log.i(TAG, "aborting proposal")
+
+        walletBackendApi.sendRequest("abortProposal", args) { isError, _ ->
+            if (isError) {
+                Log.e(TAG, "received error response to abortProposal")
+                return@sendRequest
+            }
+            mPayStatus.postValue(PayStatus.None)
+        }
+    }
+
+    @UiThread
+    fun resetPayStatus() {
+        mPayStatus.value = PayStatus.None
+    }
+
+}
+
+sealed class PayStatus {
+    object None : PayStatus()
+    object Loading : PayStatus()
+    data class Prepared(
+        val contractTerms: ContractTerms,
+        val proposalId: String,
+        val totalFees: Amount
+    ) : PayStatus()
+
+    data class InsufficientBalance(val contractTerms: ContractTerms) : 
PayStatus()
+    data class AlreadyPaid(val contractTerms: ContractTerms) : PayStatus()
+    data class Error(val error: String) : PayStatus()
+    object Success : 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
new file mode 100644
index 0000000..2084c45
--- /dev/null
+++ b/wallet/src/main/java/net/taler/wallet/payment/PaymentSuccessfulFragment.kt
@@ -0,0 +1,49 @@
+/*
+ * 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.wallet.R
+import net.taler.wallet.fadeIn
+
+/**
+ * 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/ProductAdapter.kt 
b/wallet/src/main/java/net/taler/wallet/payment/ProductAdapter.kt
new file mode 100644
index 0000000..4b1b062
--- /dev/null
+++ b/wallet/src/main/java/net/taler/wallet/payment/ProductAdapter.kt
@@ -0,0 +1,92 @@
+/*
+ * 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.graphics.Bitmap
+import android.graphics.BitmapFactory.decodeByteArray
+import android.util.Base64
+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
+import androidx.recyclerview.widget.RecyclerView.ViewHolder
+import net.taler.wallet.R
+import net.taler.wallet.payment.ProductAdapter.ProductViewHolder
+
+internal interface ProductImageClickListener {
+    fun onImageClick(image: Bitmap)
+}
+
+internal class ProductAdapter(private val listener: ProductImageClickListener) 
:
+    RecyclerView.Adapter<ProductViewHolder>() {
+
+    private val items = ArrayList<ContractProduct>()
+
+    override fun getItemCount() = items.size
+
+    override fun getItemViewType(position: Int): Int {
+        return if (itemCount == 1) 1 else 0
+    }
+
+    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): 
ProductViewHolder {
+        val res =
+            if (viewType == 1) R.layout.list_item_product_single else 
R.layout.list_item_product
+        val view = LayoutInflater.from(parent.context).inflate(res, parent, 
false)
+        return ProductViewHolder(view)
+    }
+
+    override fun onBindViewHolder(holder: ProductViewHolder, position: Int) {
+        holder.bind(items[position])
+    }
+
+    fun setItems(items: List<ContractProduct>) {
+        this.items.clear()
+        this.items.addAll(items)
+        notifyDataSetChanged()
+    }
+
+    internal inner class ProductViewHolder(v: View) : ViewHolder(v) {
+        private val quantity: TextView = v.findViewById(R.id.quantity)
+        private val image: ImageView = v.findViewById(R.id.image)
+        private val name: TextView = v.findViewById(R.id.name)
+        private val price: TextView = v.findViewById(R.id.price)
+
+        fun bind(product: ContractProduct) {
+            quantity.text = product.quantity.toString()
+            if (product.image == null) {
+                image.visibility = GONE
+            } else {
+                image.visibility = VISIBLE
+                // product.image was validated before, so non-null below
+                val match = REGEX_PRODUCT_IMAGE.matchEntire(product.image)!!
+                val decodedString = Base64.decode(match.groups[2]!!.value, 
Base64.DEFAULT)
+                val bitmap = decodeByteArray(decodedString, 0, 
decodedString.size)
+                image.setImageBitmap(bitmap)
+                if (itemCount > 1) image.setOnClickListener {
+                    listener.onImageClick(bitmap)
+                }
+            }
+            name.text = product.description
+            price.text = product.totalPrice.toString()
+        }
+    }
+
+}
diff --git 
a/wallet/src/main/java/net/taler/wallet/payment/ProductImageFragment.kt 
b/wallet/src/main/java/net/taler/wallet/payment/ProductImageFragment.kt
new file mode 100644
index 0000000..02414a6
--- /dev/null
+++ b/wallet/src/main/java/net/taler/wallet/payment/ProductImageFragment.kt
@@ -0,0 +1,52 @@
+/*
+ * 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.graphics.Bitmap
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.fragment.app.DialogFragment
+import kotlinx.android.synthetic.main.fragment_product_image.*
+import net.taler.wallet.R
+
+class ProductImageFragment private constructor() : DialogFragment() {
+
+    companion object {
+        private const val IMAGE = "image"
+
+        fun new(image: Bitmap) = ProductImageFragment().apply {
+            arguments = Bundle().apply {
+                putParcelable(IMAGE, image)
+            }
+        }
+    }
+
+    override fun onCreateView(
+        inflater: LayoutInflater, container: ViewGroup?,
+        savedInstanceState: Bundle?
+    ): View? {
+        return inflater.inflate(R.layout.fragment_product_image, container, 
false)
+    }
+
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+        val bitmap = arguments!!.getParcelable<Bitmap>(IMAGE)
+        productImageView.setImageBitmap(bitmap)
+    }
+
+}
diff --git 
a/wallet/src/main/java/net/taler/wallet/payment/PromptPaymentFragment.kt 
b/wallet/src/main/java/net/taler/wallet/payment/PromptPaymentFragment.kt
new file mode 100644
index 0000000..44dcf26
--- /dev/null
+++ b/wallet/src/main/java/net/taler/wallet/payment/PromptPaymentFragment.kt
@@ -0,0 +1,168 @@
+/*
+ * 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.annotation.SuppressLint
+import android.graphics.Bitmap
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.View.GONE
+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.lifecycle.observe
+import androidx.navigation.fragment.findNavController
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.transition.TransitionManager.beginDelayedTransition
+import kotlinx.android.synthetic.main.payment_bottom_bar.*
+import kotlinx.android.synthetic.main.payment_details.*
+import net.taler.wallet.Amount
+import net.taler.wallet.R
+import net.taler.wallet.WalletViewModel
+import net.taler.wallet.fadeIn
+import net.taler.wallet.fadeOut
+
+/**
+ * Show a payment and ask the user to accept/decline.
+ */
+class PromptPaymentFragment : Fragment(), ProductImageClickListener {
+
+    private val model: WalletViewModel by activityViewModels()
+    private val paymentManager by lazy { model.paymentManager }
+    private val adapter = ProductAdapter(this)
+
+    override fun onCreateView(
+        inflater: LayoutInflater, container: ViewGroup?,
+        savedInstanceState: Bundle?
+    ): View? {
+        return inflater.inflate(R.layout.fragment_prompt_payment, container, 
false)
+    }
+
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+        paymentManager.payStatus.observe(viewLifecycleOwner, 
this::onPaymentStatusChanged)
+        paymentManager.detailsShown.observe(viewLifecycleOwner, Observer { 
shown ->
+            beginDelayedTransition(view as ViewGroup)
+            val res = if (shown) R.string.payment_hide_details else 
R.string.payment_show_details
+            detailsButton.setText(res)
+            productsList.visibility = if (shown) VISIBLE else GONE
+        })
+
+        detailsButton.setOnClickListener {
+            paymentManager.toggleDetailsShown()
+        }
+        productsList.apply {
+            adapter = this@PromptPaymentFragment.adapter
+            layoutManager = LinearLayoutManager(requireContext())
+        }
+
+        abortButton.setOnClickListener {
+            paymentManager.abortPay()
+            findNavController().navigateUp()
+        }
+    }
+
+    override fun onDestroy() {
+        super.onDestroy()
+        if (!requireActivity().isChangingConfigurations) {
+            paymentManager.abortPay()
+        }
+    }
+
+    private fun showLoading(show: Boolean) {
+        model.showProgressBar.value = show
+        if (show) {
+            progressBar.fadeIn()
+        } else {
+            progressBar.fadeOut()
+        }
+    }
+
+    private fun onPaymentStatusChanged(payStatus: PayStatus) {
+        when (payStatus) {
+            is PayStatus.Prepared -> {
+                showLoading(false)
+                showOrder(payStatus.contractTerms, payStatus.totalFees)
+                confirmButton.isEnabled = true
+                confirmButton.setOnClickListener {
+                    model.showProgressBar.value = true
+                    paymentManager.confirmPay(payStatus.proposalId)
+                    confirmButton.fadeOut()
+                    confirmProgressBar.fadeIn()
+                }
+            }
+            is PayStatus.InsufficientBalance -> {
+                showLoading(false)
+                showOrder(payStatus.contractTerms, null)
+                errorView.setText(R.string.payment_balance_insufficient)
+                errorView.fadeIn()
+            }
+            is PayStatus.Success -> {
+                showLoading(false)
+                paymentManager.resetPayStatus()
+                
findNavController().navigate(R.id.action_promptPayment_to_paymentSuccessful)
+            }
+            is PayStatus.AlreadyPaid -> {
+                showLoading(false)
+                paymentManager.resetPayStatus()
+                
findNavController().navigate(R.id.action_promptPayment_to_alreadyPaid)
+            }
+            is PayStatus.Error -> {
+                showLoading(false)
+                errorView.text = getString(R.string.payment_error, 
payStatus.error)
+                errorView.fadeIn()
+            }
+            is PayStatus.None -> {
+                // No payment active.
+                showLoading(false)
+            }
+            is PayStatus.Loading -> {
+                // Wait until loaded ...
+                showLoading(true)
+            }
+        }
+    }
+
+    private fun showOrder(contractTerms: ContractTerms, totalFees: Amount?) {
+        orderView.text = contractTerms.summary
+        adapter.setItems(contractTerms.products)
+        if (contractTerms.products.size == 1) 
paymentManager.toggleDetailsShown()
+        val amount = contractTerms.amount
+        @SuppressLint("SetTextI18n")
+        totalView.text = "${amount.amount} ${amount.currency}"
+        if (totalFees != null && !totalFees.isZero()) {
+            val fee = "${totalFees.amount} ${totalFees.currency}"
+            feeView.text = getString(R.string.payment_fee, fee)
+            feeView.fadeIn()
+        } else {
+            feeView.visibility = GONE
+        }
+        orderLabelView.fadeIn()
+        orderView.fadeIn()
+        if (contractTerms.products.size > 1) detailsButton.fadeIn()
+        totalLabelView.fadeIn()
+        totalView.fadeIn()
+    }
+
+    override fun onImageClick(image: Bitmap) {
+        val f = ProductImageFragment.new(image)
+        f.show(parentFragmentManager, "image")
+    }
+
+}
diff --git 
a/wallet/src/main/java/net/taler/wallet/pending/PendingOperationsFragment.kt 
b/wallet/src/main/java/net/taler/wallet/pending/PendingOperationsFragment.kt
new file mode 100644
index 0000000..946e5ba
--- /dev/null
+++ b/wallet/src/main/java/net/taler/wallet/pending/PendingOperationsFragment.kt
@@ -0,0 +1,180 @@
+/*
+ * 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.pending
+
+import android.os.Bundle
+import android.util.Log
+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.View.VISIBLE
+import android.view.ViewGroup
+import android.widget.LinearLayout
+import android.widget.TextView
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.activityViewModels
+import androidx.lifecycle.Observer
+import androidx.recyclerview.widget.DividerItemDecoration
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+import com.google.android.material.snackbar.Snackbar
+import com.google.android.material.snackbar.Snackbar.LENGTH_SHORT
+import kotlinx.android.synthetic.main.fragment_pending_operations.*
+import net.taler.wallet.R
+import net.taler.wallet.TAG
+import net.taler.wallet.WalletViewModel
+import org.json.JSONObject
+
+interface PendingOperationClickListener {
+    fun onPendingOperationClick(type: String, detail: JSONObject)
+    fun onPendingOperationActionClick(type: String, detail: JSONObject)
+}
+
+class PendingOperationsFragment : Fragment(), PendingOperationClickListener {
+
+    private val model: WalletViewModel by activityViewModels()
+    private val pendingOperationsManager by lazy { 
model.pendingOperationsManager }
+
+    private val pendingAdapter = PendingOperationsAdapter(emptyList(), this)
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        setHasOptionsMenu(true)
+    }
+
+    override fun onCreateView(
+        inflater: LayoutInflater,
+        container: ViewGroup?,
+        savedInstanceState: Bundle?
+    ): View? {
+        return inflater.inflate(R.layout.fragment_pending_operations, 
container, false)
+    }
+
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+        super.onViewCreated(view, savedInstanceState)
+
+        list_pending.apply {
+            val myLayoutManager = LinearLayoutManager(requireContext())
+            val myItemDecoration =
+                DividerItemDecoration(requireContext(), 
myLayoutManager.orientation)
+            layoutManager = myLayoutManager
+            adapter = pendingAdapter
+            addItemDecoration(myItemDecoration)
+        }
+
+        pendingOperationsManager.pendingOperations.observe(viewLifecycleOwner, 
Observer {
+            updatePending(it)
+        })
+    }
+
+    override fun onOptionsItemSelected(item: MenuItem): Boolean {
+        return when (item.itemId) {
+            R.id.retry_pending -> {
+                pendingOperationsManager.retryPendingNow()
+                true
+            }
+            else -> super.onOptionsItemSelected(item)
+        }
+    }
+
+    override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
+        inflater.inflate(R.menu.pending_operations, menu)
+        super.onCreateOptionsMenu(menu, inflater)
+    }
+
+    private fun updatePending(pendingOperations: List<PendingOperationInfo>) {
+        pendingAdapter.update(pendingOperations)
+    }
+
+    override fun onPendingOperationClick(type: String, detail: JSONObject) {
+        Snackbar.make(view!!, "No detail view for $type implemented yet.", 
LENGTH_SHORT).show()
+    }
+
+    override fun onPendingOperationActionClick(type: String, detail: 
JSONObject) {
+        when (type) {
+            "proposal-choice" -> {
+                Log.v(TAG, "got action click on proposal-choice")
+                val proposalId = detail.optString("proposalId", "")
+                if (proposalId == "") {
+                    return
+                }
+                model.paymentManager.abortProposal(proposalId)
+            }
+        }
+    }
+
+}
+
+class PendingOperationsAdapter(
+    private var items: List<PendingOperationInfo>,
+    private val listener: PendingOperationClickListener
+) :
+    RecyclerView.Adapter<PendingOperationsAdapter.MyViewHolder>() {
+
+    init {
+        setHasStableIds(false)
+    }
+
+    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): 
MyViewHolder {
+        val rowView =
+            LayoutInflater.from(parent.context).inflate(R.layout.pending_row, 
parent, false)
+        return MyViewHolder(rowView)
+    }
+
+    override fun getItemCount(): Int {
+        return items.size
+    }
+
+    override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
+        val p = items[position]
+        val pendingContainer = 
holder.rowView.findViewById<LinearLayout>(R.id.pending_container)
+        pendingContainer.setOnClickListener {
+            listener.onPendingOperationClick(p.type, p.detail)
+        }
+        when (p.type) {
+            "proposal-choice" -> {
+                val btn1 = 
holder.rowView.findViewById<TextView>(R.id.button_pending_action_1)
+                btn1.text = 
btn1.context.getString(R.string.pending_operations_refuse)
+                btn1.visibility = VISIBLE
+                btn1.setOnClickListener {
+                    listener.onPendingOperationActionClick(p.type, p.detail)
+                }
+            }
+            else -> {
+                val btn1 = 
holder.rowView.findViewById<TextView>(R.id.button_pending_action_1)
+                btn1.text = 
btn1.context.getString(R.string.pending_operations_no_action)
+                btn1.visibility = GONE
+                btn1.setOnClickListener {}
+            }
+        }
+        val textView = holder.rowView.findViewById<TextView>(R.id.pending_text)
+        val subTextView = 
holder.rowView.findViewById<TextView>(R.id.pending_subtext)
+        subTextView.text = p.detail.toString(1)
+        textView.text = p.type
+    }
+
+    fun update(items: List<PendingOperationInfo>) {
+        this.items = items
+        this.notifyDataSetChanged()
+    }
+
+    class MyViewHolder(val rowView: View) : RecyclerView.ViewHolder(rowView)
+
+}
diff --git 
a/wallet/src/main/java/net/taler/wallet/pending/PendingOperationsManager.kt 
b/wallet/src/main/java/net/taler/wallet/pending/PendingOperationsManager.kt
new file mode 100644
index 0000000..2125dbc
--- /dev/null
+++ b/wallet/src/main/java/net/taler/wallet/pending/PendingOperationsManager.kt
@@ -0,0 +1,64 @@
+/*
+ * 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.pending
+
+import android.util.Log
+import androidx.lifecycle.MutableLiveData
+import net.taler.wallet.TAG
+import net.taler.wallet.backend.WalletBackendApi
+import org.json.JSONObject
+
+open class PendingOperationInfo(
+    val type: String,
+    val detail: JSONObject
+)
+
+class PendingOperationsManager(private val walletBackendApi: WalletBackendApi) 
{
+
+    private var activeGetPending = 0
+
+    val pendingOperations = MutableLiveData<List<PendingOperationInfo>>()
+
+    internal fun getPending() {
+        if (activeGetPending > 0) {
+            return
+        }
+        activeGetPending++
+        walletBackendApi.sendRequest("getPendingOperations", null) { isError, 
result ->
+            activeGetPending--
+            if (isError) {
+                Log.i(TAG, "got getPending error result")
+                return@sendRequest
+            }
+            Log.i(TAG, "got getPending result")
+            val pendingList = mutableListOf<PendingOperationInfo>()
+            val pendingJson = result.getJSONArray("pendingOperations")
+            for (i in 0 until pendingJson.length()) {
+                val p = pendingJson.getJSONObject(i)
+                val type = p.getString("type")
+                pendingList.add(PendingOperationInfo(type, p))
+            }
+            Log.i(TAG, "Got ${pendingList.size} pending operations")
+            pendingOperations.postValue((pendingList))
+        }
+    }
+
+    fun retryPendingNow() {
+        walletBackendApi.sendRequest("retryPendingNow", null)
+    }
+
+}
diff --git a/wallet/src/main/java/net/taler/wallet/withdraw/ErrorFragment.kt 
b/wallet/src/main/java/net/taler/wallet/withdraw/ErrorFragment.kt
new file mode 100644
index 0000000..f0f6610
--- /dev/null
+++ b/wallet/src/main/java/net/taler/wallet/withdraw/ErrorFragment.kt
@@ -0,0 +1,64 @@
+/*
+ * 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.View.GONE
+import android.view.View.VISIBLE
+import android.view.ViewGroup
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.activityViewModels
+import androidx.navigation.fragment.findNavController
+import kotlinx.android.synthetic.main.fragment_error.*
+import net.taler.wallet.R
+import net.taler.wallet.WalletViewModel
+
+class ErrorFragment : Fragment() {
+
+    private val model: WalletViewModel by activityViewModels()
+    private val withdrawManager by lazy { model.withdrawManager }
+
+    override fun onCreateView(
+        inflater: LayoutInflater, container: ViewGroup?,
+        savedInstanceState: Bundle?
+    ): View? {
+        return inflater.inflate(R.layout.fragment_error, container, false)
+    }
+
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+        super.onViewCreated(view, savedInstanceState)
+
+        errorTitle.setText(R.string.withdraw_error_title)
+        errorMessage.setText(R.string.withdraw_error_message)
+
+        // show dev error message if dev mode is on
+        val status = withdrawManager.withdrawStatus.value
+        if (model.devMode.value == true && status is WithdrawStatus.Error) {
+            errorDevMessage.visibility = VISIBLE
+            errorDevMessage.text = status.message
+        } else {
+            errorDevMessage.visibility = GONE
+        }
+
+        backButton.setOnClickListener {
+            findNavController().navigateUp()
+        }
+    }
+
+}
diff --git 
a/wallet/src/main/java/net/taler/wallet/withdraw/PromptWithdrawFragment.kt 
b/wallet/src/main/java/net/taler/wallet/withdraw/PromptWithdrawFragment.kt
new file mode 100644
index 0000000..454816b
--- /dev/null
+++ b/wallet/src/main/java/net/taler/wallet/withdraw/PromptWithdrawFragment.kt
@@ -0,0 +1,109 @@
+/*
+ * 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.annotation.SuppressLint
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+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_prompt_withdraw.*
+import net.taler.wallet.R
+import net.taler.wallet.WalletViewModel
+import net.taler.wallet.fadeIn
+import net.taler.wallet.fadeOut
+import net.taler.wallet.withdraw.WithdrawStatus.Loading
+import net.taler.wallet.withdraw.WithdrawStatus.TermsOfServiceReviewRequired
+import net.taler.wallet.withdraw.WithdrawStatus.Withdrawing
+
+class PromptWithdrawFragment : Fragment() {
+
+    private val model: WalletViewModel by activityViewModels()
+    private val withdrawManager by lazy { model.withdrawManager }
+
+    override fun onCreateView(
+        inflater: LayoutInflater, container: ViewGroup?,
+        savedInstanceState: Bundle?
+    ): View? {
+        return inflater.inflate(R.layout.fragment_prompt_withdraw, container, 
false)
+    }
+
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+        super.onViewCreated(view, savedInstanceState)
+
+        button_cancel_withdraw.setOnClickListener {
+            withdrawManager.cancelCurrentWithdraw()
+            findNavController().navigateUp()
+        }
+
+        button_confirm_withdraw.setOnClickListener {
+            val status = withdrawManager.withdrawStatus.value
+            if (status !is WithdrawStatus.ReceivedDetails) throw 
AssertionError()
+            it.fadeOut()
+            confirmProgressBar.fadeIn()
+            withdrawManager.acceptWithdrawal(status.talerWithdrawUri, 
status.suggestedExchange)
+        }
+
+        withdrawManager.withdrawStatus.observe(viewLifecycleOwner, Observer {
+            showWithdrawStatus(it)
+        })
+    }
+
+    private fun showWithdrawStatus(status: WithdrawStatus?) = when (status) {
+        is WithdrawStatus.ReceivedDetails -> {
+            model.showProgressBar.value = false
+            progressBar.fadeOut()
+
+            introView.fadeIn()
+            @SuppressLint("SetTextI18n")
+            withdrawAmountView.text = "${status.amount.amount} 
${status.amount.currency}"
+            withdrawAmountView.fadeIn()
+            feeView.fadeIn()
+
+            exchangeIntroView.fadeIn()
+            withdrawExchangeUrl.text = status.suggestedExchange
+            withdrawExchangeUrl.fadeIn()
+
+            button_confirm_withdraw.isEnabled = true
+        }
+        is WithdrawStatus.Success -> {
+            model.showProgressBar.value = false
+            withdrawManager.withdrawStatus.value = null
+            
findNavController().navigate(R.id.action_promptWithdraw_to_withdrawSuccessful)
+        }
+        is Loading -> {
+            model.showProgressBar.value = true
+        }
+        is Withdrawing -> {
+            model.showProgressBar.value = true
+        }
+        is TermsOfServiceReviewRequired -> {
+            model.showProgressBar.value = false
+            
findNavController().navigate(R.id.action_promptWithdraw_to_reviewExchangeTOS)
+        }
+        is WithdrawStatus.Error -> {
+            model.showProgressBar.value = false
+            
findNavController().navigate(R.id.action_promptWithdraw_to_errorFragment)
+        }
+        null -> model.showProgressBar.value = false
+    }
+
+}
diff --git 
a/wallet/src/main/java/net/taler/wallet/withdraw/ReviewExchangeTosFragment.kt 
b/wallet/src/main/java/net/taler/wallet/withdraw/ReviewExchangeTosFragment.kt
new file mode 100644
index 0000000..cd01a33
--- /dev/null
+++ 
b/wallet/src/main/java/net/taler/wallet/withdraw/ReviewExchangeTosFragment.kt
@@ -0,0 +1,80 @@
+/*
+ * 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.fragment.app.activityViewModels
+import androidx.lifecycle.Observer
+import androidx.navigation.fragment.findNavController
+import kotlinx.android.synthetic.main.fragment_review_exchange_tos.*
+import net.taler.wallet.R
+import net.taler.wallet.WalletViewModel
+import net.taler.wallet.fadeIn
+import net.taler.wallet.fadeOut
+
+class ReviewExchangeTosFragment : Fragment() {
+
+    private val model: WalletViewModel by activityViewModels()
+    private val withdrawManager by lazy { model.withdrawManager }
+
+    override fun onCreateView(
+        inflater: LayoutInflater,
+        container: ViewGroup?,
+        savedInstanceState: Bundle?
+    ): View? {
+        return inflater.inflate(R.layout.fragment_review_exchange_tos, 
container, false)
+    }
+
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+        super.onViewCreated(view, savedInstanceState)
+        acceptTosCheckBox.isChecked = false
+        acceptTosCheckBox.setOnCheckedChangeListener { _, isChecked ->
+            acceptTosButton.isEnabled = isChecked
+        }
+        abortTosButton.setOnClickListener {
+            withdrawManager.cancelCurrentWithdraw()
+            findNavController().navigateUp()
+        }
+        acceptTosButton.setOnClickListener {
+            withdrawManager.acceptCurrentTermsOfService()
+        }
+        withdrawManager.withdrawStatus.observe(viewLifecycleOwner, Observer {
+            when (it) {
+                is WithdrawStatus.TermsOfServiceReviewRequired -> {
+                    tosTextView.text = it.tosText
+                    tosTextView.fadeIn()
+                    acceptTosCheckBox.fadeIn()
+                    progressBar.fadeOut()
+                }
+                is WithdrawStatus.Loading -> {
+                    
findNavController().navigate(R.id.action_reviewExchangeTOS_to_promptWithdraw)
+                }
+                is WithdrawStatus.ReceivedDetails -> {
+                    
findNavController().navigate(R.id.action_reviewExchangeTOS_to_promptWithdraw)
+                }
+                else -> {
+                }
+            }
+        })
+    }
+
+}
diff --git a/wallet/src/main/java/net/taler/wallet/withdraw/WithdrawManager.kt 
b/wallet/src/main/java/net/taler/wallet/withdraw/WithdrawManager.kt
new file mode 100644
index 0000000..e3af757
--- /dev/null
+++ b/wallet/src/main/java/net/taler/wallet/withdraw/WithdrawManager.kt
@@ -0,0 +1,209 @@
+/*
+ * 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.util.Log
+import androidx.lifecycle.MutableLiveData
+import net.taler.wallet.Amount
+import net.taler.wallet.TAG
+import net.taler.wallet.backend.WalletBackendApi
+import org.json.JSONObject
+
+sealed class WithdrawStatus {
+    data class Loading(val talerWithdrawUri: String) : WithdrawStatus()
+    data class TermsOfServiceReviewRequired(
+        val talerWithdrawUri: String,
+        val exchangeBaseUrl: String,
+        val tosText: String,
+        val tosEtag: String,
+        val amount: Amount,
+        val suggestedExchange: String
+    ) : WithdrawStatus()
+
+    data class ReceivedDetails(
+        val talerWithdrawUri: String,
+        val amount: Amount,
+        val suggestedExchange: String
+    ) : WithdrawStatus()
+
+    data class Withdrawing(val talerWithdrawUri: String) : WithdrawStatus()
+
+    object Success : WithdrawStatus()
+    data class Error(val message: String?) : WithdrawStatus()
+}
+
+class WithdrawManager(private val walletBackendApi: WalletBackendApi) {
+
+    val withdrawStatus = MutableLiveData<WithdrawStatus>()
+    val testWithdrawalInProgress = MutableLiveData(false)
+
+    private var currentWithdrawRequestId = 0
+
+    fun withdrawTestkudos() {
+        testWithdrawalInProgress.value = true
+
+        walletBackendApi.sendRequest("withdrawTestkudos", null) { _, _ ->
+            testWithdrawalInProgress.postValue(false)
+        }
+    }
+
+    fun getWithdrawalInfo(talerWithdrawUri: String) {
+        val args = JSONObject()
+        args.put("talerWithdrawUri", talerWithdrawUri)
+
+        withdrawStatus.value = WithdrawStatus.Loading(talerWithdrawUri)
+
+        this.currentWithdrawRequestId++
+        val myWithdrawRequestId = this.currentWithdrawRequestId
+
+        walletBackendApi.sendRequest("getWithdrawDetailsForUri", args) { 
isError, result ->
+            if (isError) {
+                Log.e(TAG, "Error getWithdrawDetailsForUri 
${result.toString(4)}")
+                val message = if (result.has("message")) 
result.getString("message") else null
+                withdrawStatus.postValue(WithdrawStatus.Error(message))
+                return@sendRequest
+            }
+            if (myWithdrawRequestId != this.currentWithdrawRequestId) {
+                val mismatch = "$myWithdrawRequestId != 
${this.currentWithdrawRequestId}"
+                Log.w(TAG, "Got withdraw result for different request id 
$mismatch")
+                return@sendRequest
+            }
+            Log.v(TAG, "got getWithdrawDetailsForUri result")
+            val status = withdrawStatus.value
+            if (status !is WithdrawStatus.Loading) {
+                Log.v(TAG, "ignoring withdrawal info result, not loading.")
+                return@sendRequest
+            }
+            val wi = result.getJSONObject("bankWithdrawDetails")
+            val suggestedExchange = wi.getString("suggestedExchange")
+            // We just use the suggested exchange, in the future there will be
+            // a selection dialog.
+            getWithdrawalInfoWithExchange(talerWithdrawUri, suggestedExchange)
+        }
+    }
+
+    private fun getWithdrawalInfoWithExchange(talerWithdrawUri: String, 
selectedExchange: String) {
+        val args = JSONObject()
+        args.put("talerWithdrawUri", talerWithdrawUri)
+        args.put("selectedExchange", selectedExchange)
+
+        this.currentWithdrawRequestId++
+        val myWithdrawRequestId = this.currentWithdrawRequestId
+
+        walletBackendApi.sendRequest("getWithdrawDetailsForUri", args) { 
isError, result ->
+            if (isError) {
+                Log.e(TAG, "Error getWithdrawDetailsForUri 
${result.toString(4)}")
+                val message = if (result.has("message")) 
result.getString("message") else null
+                withdrawStatus.postValue(WithdrawStatus.Error(message))
+                return@sendRequest
+            }
+            if (myWithdrawRequestId != this.currentWithdrawRequestId) {
+                val mismatch = "$myWithdrawRequestId != 
${this.currentWithdrawRequestId}"
+                Log.w(TAG, "Got withdraw result for different request id 
$mismatch")
+                return@sendRequest
+            }
+            Log.v(TAG, "got getWithdrawDetailsForUri result (with exchange 
details)")
+            val status = withdrawStatus.value
+            if (status !is WithdrawStatus.Loading) {
+                Log.v(TAG, "ignoring withdrawal info result, not loading.")
+                return@sendRequest
+            }
+            val wi = result.getJSONObject("bankWithdrawDetails")
+            val suggestedExchange = wi.getString("suggestedExchange")
+            val amount = Amount.fromJson(wi.getJSONObject("amount"))
+
+            val ei = result.getJSONObject("exchangeWithdrawDetails")
+            val termsOfServiceAccepted = 
ei.getBoolean("termsOfServiceAccepted")
+
+            if (!termsOfServiceAccepted) {
+                val exchange = ei.getJSONObject("exchangeInfo")
+                val tosText = exchange.getString("termsOfServiceText")
+                val tosEtag = exchange.optString("termsOfServiceLastEtag", 
"undefined")
+                withdrawStatus.postValue(
+                    WithdrawStatus.TermsOfServiceReviewRequired(
+                        status.talerWithdrawUri,
+                        selectedExchange,
+                        tosText,
+                        tosEtag,
+                        amount,
+                        suggestedExchange
+                    )
+                )
+            } else {
+                withdrawStatus.postValue(
+                    WithdrawStatus.ReceivedDetails(
+                        status.talerWithdrawUri,
+                        amount,
+                        suggestedExchange
+                    )
+                )
+            }
+        }
+    }
+
+    fun acceptWithdrawal(talerWithdrawUri: String, selectedExchange: String) {
+        val args = JSONObject()
+        args.put("talerWithdrawUri", talerWithdrawUri)
+        args.put("selectedExchange", selectedExchange)
+
+        withdrawStatus.value = WithdrawStatus.Withdrawing(talerWithdrawUri)
+
+        walletBackendApi.sendRequest("acceptWithdrawal", args) { isError, _ ->
+            if (isError) {
+                Log.v(TAG, "got acceptWithdrawal error result")
+                return@sendRequest
+            }
+            Log.v(TAG, "got acceptWithdrawal result")
+            val status = withdrawStatus.value
+            if (status !is WithdrawStatus.Withdrawing) {
+                Log.v(TAG, "ignoring acceptWithdrawal result, invalid state")
+            }
+            withdrawStatus.postValue(WithdrawStatus.Success)
+        }
+    }
+
+    /**
+     * Accept the currently displayed terms of service.
+     */
+    fun acceptCurrentTermsOfService() {
+        when (val s = withdrawStatus.value) {
+            is WithdrawStatus.TermsOfServiceReviewRequired -> {
+                val args = JSONObject()
+                args.put("exchangeBaseUrl", s.exchangeBaseUrl)
+                args.put("etag", s.tosEtag)
+                walletBackendApi.sendRequest("acceptExchangeTermsOfService", 
args) { isError, _ ->
+                    if (isError) {
+                        return@sendRequest
+                    }
+                    withdrawStatus.postValue(
+                        WithdrawStatus.ReceivedDetails(
+                            s.talerWithdrawUri,
+                            s.amount,
+                            s.suggestedExchange
+                        )
+                    )
+                }
+            }
+        }
+    }
+
+    fun cancelCurrentWithdraw() {
+        currentWithdrawRequestId++
+        withdrawStatus.value = null
+    }
+
+}
diff --git 
a/wallet/src/main/java/net/taler/wallet/withdraw/WithdrawSuccessfulFragment.kt 
b/wallet/src/main/java/net/taler/wallet/withdraw/WithdrawSuccessfulFragment.kt
new file mode 100644
index 0000000..5daeff1
--- /dev/null
+++ 
b/wallet/src/main/java/net/taler/wallet/withdraw/WithdrawSuccessfulFragment.kt
@@ -0,0 +1,44 @@
+/*
+ * 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/history_payment_aborted.xml 
b/wallet/src/main/res/drawable/history_payment_aborted.xml
new file mode 100644
index 0000000..03cd7b2
--- /dev/null
+++ b/wallet/src/main/res/drawable/history_payment_aborted.xml
@@ -0,0 +1,25 @@
+<!--
+  ~ This file is part of GNU Taler
+  ~ (C) 2020 Taler Systems S.A.
+  ~
+  ~ GNU Taler is free software; you can redistribute it and/or modify it under 
the
+  ~ terms of the GNU General Public License as published by the Free Software
+  ~ Foundation; either version 3, or (at your option) any later version.
+  ~
+  ~ GNU Taler is distributed in the hope that it will be useful, but WITHOUT 
ANY
+  ~ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
+  ~ A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+  ~
+  ~ You should have received a copy of the GNU General Public License along 
with
+  ~ GNU Taler; see the file COPYING.  If not, see 
<http://www.gnu.org/licenses/>
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android";
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24"
+        android:viewportHeight="24">
+    <path
+            android:fillColor="#000"
+            android:pathData="M15.46 18.12L16.88 19.54L19 17.41L21.12 
19.54L22.54 18.12L20.41 16L22.54 13.88L21.12 12.46L19 14.59L16.88 12.46L15.46 
13.88L17.59 16M14.97 11.62C14.86 10.28 13.58 8.97 12 9C10.3 9.04 9 10.3 9 12C9 
13.7 10.3 14.94 12 15C12.39 15 12.77 14.92 13.14 14.77C13.41 13.67 13.86 12.63 
14.97 11.62M13 16H7C7 14.9 6.1 14 5 14V10C6.1 10 7 9.1 7 8H17C17 9.1 17.9 10 19 
10V10.05C19.67 10.06 20.34 10.18 21 10.4V6H3V18H13.32C13.1 17.33 13 16.66 13 
16Z" />
+</vector>
diff --git a/wallet/src/main/res/drawable/history_refresh.xml 
b/wallet/src/main/res/drawable/history_refresh.xml
new file mode 100644
index 0000000..f5d8972
--- /dev/null
+++ b/wallet/src/main/res/drawable/history_refresh.xml
@@ -0,0 +1,28 @@
+<!--
+  ~ This file is part of GNU Taler
+  ~ (C) 2020 Taler Systems S.A.
+  ~
+  ~ GNU Taler is free software; you can redistribute it and/or modify it under 
the
+  ~ terms of the GNU General Public License as published by the Free Software
+  ~ Foundation; either version 3, or (at your option) any later version.
+  ~
+  ~ GNU Taler is distributed in the hope that it will be useful, but WITHOUT 
ANY
+  ~ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
+  ~ A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+  ~
+  ~ You should have received a copy of the GNU General Public License along 
with
+  ~ GNU Taler; see the file COPYING.  If not, see 
<http://www.gnu.org/licenses/>
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android";
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24"
+        android:viewportHeight="24">
+    <path
+            android:fillColor="#FF000000"
+            android:pathData="M14.97,11.62C14.86,10.28 13.58,8.97 
12,9c-1.7,0.04 -3,1.3 -3,3 0,1.7 1.3,2.94 3,3 0.39,0 0.77,-0.08 1.14,-0.23 
0.27,-1.1 0.72,-2.14 1.83,-3.15M13,16H7C7,14.9 6.1,14 5,14V10C6.1,10 7,9.1 
7,8h10c0,1.1 0.9,2 2,2v0.05c0.67,0.01 1.34,0.13 2,0.35V6H3V18H13.32C13.1,17.33 
13,16.66 13,16Z" />
+    <path
+            android:fillColor="#FF000000"
+            android:pathData="M19,12 L16.75,14.25 19,16.5V15c1.38,0 2.5,1.12 
2.5,2.5 0,0.4 -0.09,0.78 -0.26,1.12l1.09,1.09C22.75,19.08 23,18.32 
23,17.5c0,-2.21 -1.79,-4 -4,-4V12m-3.33,3.29C15.25,15.92 15,16.68 
15,17.5c0,2.21 1.79,4 4,4V23L21.25,20.75 19,18.5V20c-1.38,0 -2.5,-1.12 
-2.5,-2.5 0,-0.4 0.09,-0.78 0.26,-1.12z" />
+</vector>
diff --git a/wallet/src/main/res/drawable/history_refund.xml 
b/wallet/src/main/res/drawable/history_refund.xml
new file mode 100644
index 0000000..60872a9
--- /dev/null
+++ b/wallet/src/main/res/drawable/history_refund.xml
@@ -0,0 +1,25 @@
+<!--
+  ~ This file is part of GNU Taler
+  ~ (C) 2020 Taler Systems S.A.
+  ~
+  ~ GNU Taler is free software; you can redistribute it and/or modify it under 
the
+  ~ terms of the GNU General Public License as published by the Free Software
+  ~ Foundation; either version 3, or (at your option) any later version.
+  ~
+  ~ GNU Taler is distributed in the hope that it will be useful, but WITHOUT 
ANY
+  ~ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
+  ~ A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+  ~
+  ~ You should have received a copy of the GNU General Public License along 
with
+  ~ GNU Taler; see the file COPYING.  If not, see 
<http://www.gnu.org/licenses/>
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android";
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24"
+        android:viewportHeight="24">
+    <path
+            android:fillColor="#000"
+            android:pathData="M3,11H21V23H3V11M12,15A2,2 0 0,1 14,17A2,2 0 0,1 
12,19A2,2 0 0,1 10,17A2,2 0 0,1 12,15M7,13A2,2 0 0,1 5,15V19A2,2 0 0,1 
7,21H17A2,2 0 0,1 19,19V15A2,2 0 0,1 
17,13H7M17,5V10H15.5V6.5H9.88L12.3,8.93L11.24,10L7,5.75L11.24,1.5L12.3,2.57L9.88,5H17Z"
 />
+</vector>
diff --git a/wallet/src/main/res/drawable/history_tip_accepted.xml 
b/wallet/src/main/res/drawable/history_tip_accepted.xml
new file mode 100644
index 0000000..794d1bf
--- /dev/null
+++ b/wallet/src/main/res/drawable/history_tip_accepted.xml
@@ -0,0 +1,25 @@
+<!--
+  ~ This file is part of GNU Taler
+  ~ (C) 2020 Taler Systems S.A.
+  ~
+  ~ GNU Taler is free software; you can redistribute it and/or modify it under 
the
+  ~ terms of the GNU General Public License as published by the Free Software
+  ~ Foundation; either version 3, or (at your option) any later version.
+  ~
+  ~ GNU Taler is distributed in the hope that it will be useful, but WITHOUT 
ANY
+  ~ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
+  ~ A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+  ~
+  ~ You should have received a copy of the GNU General Public License along 
with
+  ~ GNU Taler; see the file COPYING.  If not, see 
<http://www.gnu.org/licenses/>
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android";
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24"
+        android:viewportHeight="24">
+    <path
+            android:fillColor="#000"
+            android:pathData="M18,21L15,18L18,15V17H22V19H18V21M10,4A4,4 0 0,1 
14,8A4,4 0 0,1 10,12A4,4 0 0,1 6,8A4,4 0 0,1 10,4M10,14C11.15,14 12.25,14.12 
13.24,14.34C12.46,15.35 12,16.62 12,18C12,18.7 12.12,19.37 
12.34,20H2V18C2,15.79 5.58,14 10,14Z" />
+</vector>
diff --git a/wallet/src/main/res/drawable/history_tip_declined.xml 
b/wallet/src/main/res/drawable/history_tip_declined.xml
new file mode 100644
index 0000000..4838ee4
--- /dev/null
+++ b/wallet/src/main/res/drawable/history_tip_declined.xml
@@ -0,0 +1,25 @@
+<!--
+  ~ This file is part of GNU Taler
+  ~ (C) 2020 Taler Systems S.A.
+  ~
+  ~ GNU Taler is free software; you can redistribute it and/or modify it under 
the
+  ~ terms of the GNU General Public License as published by the Free Software
+  ~ Foundation; either version 3, or (at your option) any later version.
+  ~
+  ~ GNU Taler is distributed in the hope that it will be useful, but WITHOUT 
ANY
+  ~ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
+  ~ A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+  ~
+  ~ You should have received a copy of the GNU General Public License along 
with
+  ~ GNU Taler; see the file COPYING.  If not, see 
<http://www.gnu.org/licenses/>
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android";
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24"
+        android:viewportHeight="24">
+    <path
+            android:fillColor="#000"
+            android:pathData="M10 4A4 4 0 0 0 6 8A4 4 0 0 0 10 12A4 4 0 0 0 14 
8A4 4 0 0 0 10 4M17.5 13C15 13 13 15 13 17.5C13 20 15 22 17.5 22C20 22 22 20 22 
17.5C22 15 20 13 17.5 13M10 14C5.58 14 2 15.79 2 18V20H11.5A6.5 6.5 0 0 1 11 
17.5A6.5 6.5 0 0 1 11.95 14.14C11.32 14.06 10.68 14 10 14M17.5 14.5C19.16 14.5 
20.5 15.84 20.5 17.5C20.5 18.06 20.35 18.58 20.08 19L16 14.92C16.42 14.65 16.94 
14.5 17.5 14.5M14.92 16L19 20.08C18.58 20.35 18.06 20.5 17.5 20.5C15.84 20.5 
14.5 19.16 14.5 17.5 [...]
+</vector>
diff --git a/wallet/src/main/res/drawable/history_withdrawn.xml 
b/wallet/src/main/res/drawable/history_withdrawn.xml
new file mode 100644
index 0000000..f524474
--- /dev/null
+++ b/wallet/src/main/res/drawable/history_withdrawn.xml
@@ -0,0 +1,25 @@
+<!--
+  ~ This file is part of GNU Taler
+  ~ (C) 2020 Taler Systems S.A.
+  ~
+  ~ GNU Taler is free software; you can redistribute it and/or modify it under 
the
+  ~ terms of the GNU General Public License as published by the Free Software
+  ~ Foundation; either version 3, or (at your option) any later version.
+  ~
+  ~ GNU Taler is distributed in the hope that it will be useful, but WITHOUT 
ANY
+  ~ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
+  ~ A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+  ~
+  ~ You should have received a copy of the GNU General Public License along 
with
+  ~ GNU Taler; see the file COPYING.  If not, see 
<http://www.gnu.org/licenses/>
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android";
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24"
+        android:viewportHeight="24">
+    <path
+            android:fillColor="#000"
+            android:pathData="M3 0V3H0V5H3V8H5V5H8V3H5V0H3M9 3V6H6V9H3V19C3 
20.1 3.89 21 5 21H19C20.11 21 21 20.11 21 19V18H12C10.9 18 10 17.11 10 16V8C10 
6.9 10.89 6 12 6H21V5C21 3.9 20.11 3 19 3H9M12 8V16H22V8H12M16 10.5C16.83 10.5 
17.5 11.17 17.5 12C17.5 12.83 16.83 13.5 16 13.5C15.17 13.5 14.5 12.83 14.5 
12C14.5 11.17 15.17 10.5 16 10.5Z" />
+</vector>
diff --git a/wallet/src/main/res/drawable/ic_account_balance.xml 
b/wallet/src/main/res/drawable/ic_account_balance.xml
new file mode 100644
index 0000000..e9f51a2
--- /dev/null
+++ b/wallet/src/main/res/drawable/ic_account_balance.xml
@@ -0,0 +1,25 @@
+<!--
+  ~ This file is part of GNU Taler
+  ~ (C) 2020 Taler Systems S.A.
+  ~
+  ~ GNU Taler is free software; you can redistribute it and/or modify it under 
the
+  ~ terms of the GNU General Public License as published by the Free Software
+  ~ Foundation; either version 3, or (at your option) any later version.
+  ~
+  ~ GNU Taler is distributed in the hope that it will be useful, but WITHOUT 
ANY
+  ~ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
+  ~ A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+  ~
+  ~ You should have received a copy of the GNU General Public License along 
with
+  ~ GNU Taler; see the file COPYING.  If not, see 
<http://www.gnu.org/licenses/>
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android";
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+            android:fillColor="#FF000000"
+            
android:pathData="M4,10v7h3v-7L4,10zM10,10v7h3v-7h-3zM2,22h19v-3L2,19v3zM16,10v7h3v-7h-3zM11.5,1L2,6v2h19L21,6l-9.5,-5z"
 />
+</vector>
diff --git a/wallet/src/main/res/drawable/ic_account_balance_wallet.xml 
b/wallet/src/main/res/drawable/ic_account_balance_wallet.xml
new file mode 100644
index 0000000..514b118
--- /dev/null
+++ b/wallet/src/main/res/drawable/ic_account_balance_wallet.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android";
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+            android:fillColor="#FF000000"
+            android:pathData="M21,18v1c0,1.1 -0.9,2 -2,2L5,21c-1.11,0 -2,-0.9 
-2,-2L3,5c0,-1.1 0.89,-2 2,-2h14c1.1,0 2,0.9 2,2v1h-9c-1.11,0 -2,0.9 
-2,2v8c0,1.1 0.89,2 2,2h9zM12,16h10L22,8L12,8v8zM16,13.5c-0.83,0 -1.5,-0.67 
-1.5,-1.5s0.67,-1.5 1.5,-1.5 1.5,0.67 1.5,1.5 -0.67,1.5 -1.5,1.5z" />
+</vector>
diff --git a/wallet/src/main/res/drawable/ic_add_circle.xml 
b/wallet/src/main/res/drawable/ic_add_circle.xml
new file mode 100644
index 0000000..c32faa6
--- /dev/null
+++ b/wallet/src/main/res/drawable/ic_add_circle.xml
@@ -0,0 +1,25 @@
+<!--
+  ~ This file is part of GNU Taler
+  ~ (C) 2020 Taler Systems S.A.
+  ~
+  ~ GNU Taler is free software; you can redistribute it and/or modify it under 
the
+  ~ terms of the GNU General Public License as published by the Free Software
+  ~ Foundation; either version 3, or (at your option) any later version.
+  ~
+  ~ GNU Taler is distributed in the hope that it will be useful, but WITHOUT 
ANY
+  ~ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
+  ~ A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+  ~
+  ~ You should have received a copy of the GNU General Public License along 
with
+  ~ GNU Taler; see the file COPYING.  If not, see 
<http://www.gnu.org/licenses/>
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android";
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+            android:fillColor="#FF000000"
+            android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 
10,-10S17.52,2 12,2zM17,13h-4v4h-2v-4L7,13v-2h4L11,7h2v4h4v2z" />
+</vector>
diff --git a/wallet/src/main/res/drawable/ic_cancel.xml 
b/wallet/src/main/res/drawable/ic_cancel.xml
new file mode 100644
index 0000000..6dc55cf
--- /dev/null
+++ b/wallet/src/main/res/drawable/ic_cancel.xml
@@ -0,0 +1,25 @@
+<!--
+  ~ This file is part of GNU Taler
+  ~ (C) 2020 Taler Systems S.A.
+  ~
+  ~ GNU Taler is free software; you can redistribute it and/or modify it under 
the
+  ~ terms of the GNU General Public License as published by the Free Software
+  ~ Foundation; either version 3, or (at your option) any later version.
+  ~
+  ~ GNU Taler is distributed in the hope that it will be useful, but WITHOUT 
ANY
+  ~ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
+  ~ A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+  ~
+  ~ You should have received a copy of the GNU General Public License along 
with
+  ~ GNU Taler; see the file COPYING.  If not, see 
<http://www.gnu.org/licenses/>
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android";
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M12,2C6.47,2 2,6.47 2,12s4.47,10 10,10 10,-4.47 
10,-10S17.53,2 12,2zM17,15.59L15.59,17 12,13.41 8.41,17 7,15.59 10.59,12 7,8.41 
8.41,7 12,10.59 15.59,7 17,8.41 13.41,12 17,15.59z"/>
+</vector>
diff --git a/wallet/src/main/res/drawable/ic_cash_usd_outline.xml 
b/wallet/src/main/res/drawable/ic_cash_usd_outline.xml
new file mode 100644
index 0000000..428a466
--- /dev/null
+++ b/wallet/src/main/res/drawable/ic_cash_usd_outline.xml
@@ -0,0 +1,25 @@
+<!--
+  ~ This file is part of GNU Taler
+  ~ (C) 2020 Taler Systems S.A.
+  ~
+  ~ GNU Taler is free software; you can redistribute it and/or modify it under 
the
+  ~ terms of the GNU General Public License as published by the Free Software
+  ~ Foundation; either version 3, or (at your option) any later version.
+  ~
+  ~ GNU Taler is distributed in the hope that it will be useful, but WITHOUT 
ANY
+  ~ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
+  ~ A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+  ~
+  ~ You should have received a copy of the GNU General Public License along 
with
+  ~ GNU Taler; see the file COPYING.  If not, see 
<http://www.gnu.org/licenses/>
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android";
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24"
+        android:viewportHeight="24">
+    <path
+            android:fillColor="#000"
+            android:pathData="M20,18H4V6H20M20,4H4C2.89,4 2,4.89 2,6V18A2,2 0 
0,0 4,20H20A2,2 0 0,0 22,18V6C22,4.89 21.1,4 20,4M11,17H13V16H14A1,1 0 0,0 
15,15V12A1,1 0 0,0 14,11H11V10H15V8H13V7H11V8H10A1,1 0 0,0 9,9V12A1,1 0 0,0 
10,13H13V14H9V16H11V17Z" />
+</vector>
diff --git a/wallet/src/main/res/drawable/ic_check_circle.xml 
b/wallet/src/main/res/drawable/ic_check_circle.xml
new file mode 100644
index 0000000..c299cc3
--- /dev/null
+++ b/wallet/src/main/res/drawable/ic_check_circle.xml
@@ -0,0 +1,26 @@
+<!--
+  ~ 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/wallet/src/main/res/drawable/ic_directions.xml 
b/wallet/src/main/res/drawable/ic_directions.xml
new file mode 100644
index 0000000..7fc7fe7
--- /dev/null
+++ b/wallet/src/main/res/drawable/ic_directions.xml
@@ -0,0 +1,25 @@
+<!--
+  ~ This file is part of GNU Taler
+  ~ (C) 2020 Taler Systems S.A.
+  ~
+  ~ GNU Taler is free software; you can redistribute it and/or modify it under 
the
+  ~ terms of the GNU General Public License as published by the Free Software
+  ~ Foundation; either version 3, or (at your option) any later version.
+  ~
+  ~ GNU Taler is distributed in the hope that it will be useful, but WITHOUT 
ANY
+  ~ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
+  ~ A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+  ~
+  ~ You should have received a copy of the GNU General Public License along 
with
+  ~ GNU Taler; see the file COPYING.  If not, see 
<http://www.gnu.org/licenses/>
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android";
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M21.71,11.29l-9,-9c-0.39,-0.39 -1.02,-0.39 
-1.41,0l-9,9c-0.39,0.39 -0.39,1.02 0,1.41l9,9c0.39,0.39 1.02,0.39 
1.41,0l9,-9c0.39,-0.38 0.39,-1.01 0,-1.41zM14,14.5V12h-4v3H8v-4c0,-0.55 0.45,-1 
1,-1h5V7.5l3.5,3.5 -3.5,3.5z"/>
+</vector>
diff --git a/wallet/src/main/res/drawable/ic_error.xml 
b/wallet/src/main/res/drawable/ic_error.xml
new file mode 100644
index 0000000..1f705af
--- /dev/null
+++ b/wallet/src/main/res/drawable/ic_error.xml
@@ -0,0 +1,25 @@
+<!--
+  ~ This file is part of GNU Taler
+  ~ (C) 2020 Taler Systems S.A.
+  ~
+  ~ GNU Taler is free software; you can redistribute it and/or modify it under 
the
+  ~ terms of the GNU General Public License as published by the Free Software
+  ~ Foundation; either version 3, or (at your option) any later version.
+  ~
+  ~ GNU Taler is distributed in the hope that it will be useful, but WITHOUT 
ANY
+  ~ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
+  ~ A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+  ~
+  ~ You should have received a copy of the GNU General Public License along 
with
+  ~ GNU Taler; see the file COPYING.  If not, see 
<http://www.gnu.org/licenses/>
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android";
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 
10,-10S17.52,2 12,2zM13,17h-2v-2h2v2zM13,13h-2L11,7h2v6z"/>
+</vector>
diff --git a/wallet/src/main/res/drawable/ic_history_black_24dp.xml 
b/wallet/src/main/res/drawable/ic_history_black_24dp.xml
new file mode 100644
index 0000000..4404ee4
--- /dev/null
+++ b/wallet/src/main/res/drawable/ic_history_black_24dp.xml
@@ -0,0 +1,25 @@
+<!--
+  ~ This file is part of GNU Taler
+  ~ (C) 2020 Taler Systems S.A.
+  ~
+  ~ GNU Taler is free software; you can redistribute it and/or modify it under 
the
+  ~ terms of the GNU General Public License as published by the Free Software
+  ~ Foundation; either version 3, or (at your option) any later version.
+  ~
+  ~ GNU Taler is distributed in the hope that it will be useful, but WITHOUT 
ANY
+  ~ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
+  ~ A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+  ~
+  ~ You should have received a copy of the GNU General Public License along 
with
+  ~ GNU Taler; see the file COPYING.  If not, see 
<http://www.gnu.org/licenses/>
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android";
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M13,3c-4.97,0 -9,4.03 -9,9L1,12l3.89,3.89 
0.07,0.14L9,12L6,12c0,-3.87 3.13,-7 7,-7s7,3.13 7,7 -3.13,7 -7,7c-1.93,0 
-3.68,-0.79 -4.94,-2.06l-1.42,1.42C8.27,19.99 10.51,21 13,21c4.97,0 9,-4.03 
9,-9s-4.03,-9 -9,-9zM12,8v5l4.28,2.54 0.72,-1.21 -3.5,-2.08L13.5,8L12,8z"/>
+</vector>
diff --git a/wallet/src/main/res/drawable/ic_home_black_24dp.xml 
b/wallet/src/main/res/drawable/ic_home_black_24dp.xml
new file mode 100644
index 0000000..ed8aa1e
--- /dev/null
+++ b/wallet/src/main/res/drawable/ic_home_black_24dp.xml
@@ -0,0 +1,25 @@
+<!--
+  ~ This file is part of GNU Taler
+  ~ (C) 2020 Taler Systems S.A.
+  ~
+  ~ GNU Taler is free software; you can redistribute it and/or modify it under 
the
+  ~ terms of the GNU General Public License as published by the Free Software
+  ~ Foundation; either version 3, or (at your option) any later version.
+  ~
+  ~ GNU Taler is distributed in the hope that it will be useful, but WITHOUT 
ANY
+  ~ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
+  ~ A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+  ~
+  ~ You should have received a copy of the GNU General Public License along 
with
+  ~ GNU Taler; see the file COPYING.  If not, see 
<http://www.gnu.org/licenses/>
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android";
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M10,20v-6h4v6h5v-8h3L12,3 2,12h3v8z"/>
+</vector>
diff --git a/wallet/src/main/res/drawable/ic_launcher_foreground.xml 
b/wallet/src/main/res/drawable/ic_launcher_foreground.xml
new file mode 100644
index 0000000..028c873
--- /dev/null
+++ b/wallet/src/main/res/drawable/ic_launcher_foreground.xml
@@ -0,0 +1,68 @@
+<!--
+  ~ 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="108dp"
+        android:height="108dp"
+        android:viewportWidth="237.28813"
+        android:viewportHeight="237.2881">
+    <group
+            android:translateX="48.64407"
+            android:translateY="48.644062">
+        <path
+                android:fillAlpha="1"
+                android:fillColor="#ffffff"
+                
android:pathData="m31.669,82.748h-4.702v-19.684h-6.041v-4.112h16.783v4.112L31.669,63.064Z"
+                android:strokeWidth="0.81604069"
+                android:strokeColor="#00000000" />
+        <path
+                android:fillAlpha="1"
+                android:fillColor="#ffffff"
+                android:pathData="m50.301,74.364q-2.614,0 -3.65,0.669 
-1.036,0.669 -1.036,2.295 0,1.211 0.717,1.929 0.717,0.717 1.944,0.717 1.849,0 
2.869,-1.387 1.02,-1.403 
1.02,-3.905v-0.319zM56.804,72.563v10.185h-4.638v-1.992q-0.845,1.179 
-2.168,1.817 -1.323,0.638 -2.917,0.638 -3.044,0 -4.75,-1.61 -1.689,-1.61 
-1.689,-4.495 0,-3.124 2.024,-4.606 2.024,-1.498 
6.264,-1.498h3.235v-0.781q0,-1.132 -0.829,-1.705 -0.813,-0.59 -2.407,-0.59 
-1.674,0 -3.251,0.43 -1.562,0.414 -3.267,1.339v-3.985q [...]
+                android:strokeWidth="0.81604069"
+                android:strokeColor="#00000000" />
+        <path
+                android:fillAlpha="1"
+                android:fillColor="#ffffff"
+                
android:pathData="m64.964,75.305v-13.771h-4.734v-3.586h9.404v17.357q0,2.104 
0.653,2.98 0.653,0.877 2.215,0.877h3.73v3.586h-5.037q-3.331,0 -4.781,-1.721 
-1.45,-1.721 -1.45,-5.722z"
+                android:strokeWidth="0.81604069"
+                android:strokeColor="#00000000" />
+        <path
+                android:fillAlpha="1"
+                android:fillColor="#ffffff"
+                android:pathData="m96.012,81.871q-1.626,0.669 -3.315,1.004 
-1.689,0.335 -3.57,0.335 -4.479,0 -6.853,-2.391 -2.359,-2.407 -2.359,-6.917 
0,-4.367 2.279,-6.901 2.279,-2.534 6.216,-2.534 3.969,0 6.152,2.359 2.199,2.343 
2.199,6.614v1.897h-12.097q0.016,2.104 1.243,3.14 1.227,1.036 3.666,1.036 1.61,0 
3.172,-0.462 1.562,-0.462 3.267,-1.466zM92.059,71.83q-0.032,-1.849 
-0.956,-2.789 -0.908,-0.956 -2.694,-0.956 -1.61,0 -2.566,0.988 -0.956,0.972 
-1.132,2.773z"
+                android:strokeWidth="0.81604069"
+                android:strokeColor="#00000000" />
+        <path
+                android:fillAlpha="1"
+                android:fillColor="#ffffff"
+                android:pathData="m116.445,69.822q-0.765,-0.701 -1.801,-1.052 
-1.02,-0.351 -2.247,-0.351 -1.482,0 -2.598,0.526 -1.1,0.51 -1.705,1.498 
-0.383,0.606 -0.542,1.466 -0.143,0.861 
-0.143,2.614v8.224h-4.67v-17.851h4.67v2.773q0.685,-1.53 2.104,-2.359 
1.419,-0.845 3.315,-0.845 0.956,0 1.865,0.239 0.924,0.223 1.753,0.669z"
+                android:strokeWidth="0.81604069"
+                android:strokeColor="#00000000" />
+        <path
+                android:fillAlpha="1"
+                android:fillColor="#ae1010"
+                android:pathData="M25.843,97.583L16.433,97.583L0,70.865 
16.433,44.111l9.409,0l-16.522,26.754z"
+                android:strokeWidth="2.03518677"
+                android:strokeColor="#00000000" />
+        <path
+                android:fillAlpha="1"
+                android:fillColor="#ae1010"
+                android:pathData="m109.483,97.667 l17.087,-27.134 
-17.041,-27.171l9.712,0l17.041,27.171 -17.041,27.134z"
+                android:strokeWidth="2.08855891"
+                android:strokeColor="#00000000" />
+    </group>
+</vector>
diff --git a/wallet/src/main/res/drawable/ic_scan_qr.xml 
b/wallet/src/main/res/drawable/ic_scan_qr.xml
new file mode 100644
index 0000000..2ca8a69
--- /dev/null
+++ b/wallet/src/main/res/drawable/ic_scan_qr.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android";
+        android:width="24dp"
+        android:height="24dp"
+        android:tint="?attr/colorOnPrimarySurface"
+        android:viewportWidth="24"
+        android:viewportHeight="24">
+    <path
+            android:fillColor="#000"
+            
android:pathData="M4,4H10V10H4V4M20,4V10H14V4H20M14,15H16V13H14V11H16V13H18V11H20V13H18V15H20V18H18V20H16V18H13V20H11V16H14V15M16,15V18H18V15H16M4,20V14H10V20H4M6,6V8H8V6H6M16,6V8H18V6H16M6,16V18H8V16H6M4,11H6V13H4V11M9,11H13V15H11V13H9V11M11,6H13V10H11V6M2,2V6H0V2A2,2
 0 0,1 2,0H6V2H2M22,0A2,2 0 0,1 24,2V6H22V2H18V0H22M2,18V22H6V24H2A2,2 0 0,1 
0,22V18H2M22,22V18H24V22A2,2 0 0,1 22,24H18V22H22Z" />
+</vector>
diff --git a/wallet/src/main/res/drawable/ic_settings.xml 
b/wallet/src/main/res/drawable/ic_settings.xml
new file mode 100644
index 0000000..7cadd58
--- /dev/null
+++ b/wallet/src/main/res/drawable/ic_settings.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android";
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24"
+        android:viewportHeight="24">
+    <path
+            android:fillColor="#FF000000"
+            android:pathData="M19.1,12.9a2.8,2.8 0,0 0,0.1 -0.9,2.8 2.8,0 0,0 
-0.1,-0.9l2.1,-1.6a0.7,0.7 0,0 0,0.1 -0.6L19.4,5.5a0.7,0.7 0,0 0,-0.6 
-0.2l-2.4,1a6.5,6.5 0,0 0,-1.6 -0.9l-0.4,-2.6a0.5,0.5 0,0 0,-0.5 
-0.4H10.1a0.5,0.5 0,0 0,-0.5 0.4L9.3,5.4a5.6,5.6 0,0 0,-1.7 0.9l-2.4,-1a0.4,0.4 
0,0 0,-0.5 0.2l-2,3.4c-0.1,0.2 0,0.4 0.2,0.6l2,1.6a2.8,2.8 0,0 0,-0.1 0.9,2.8 
2.8,0 0,0 0.1,0.9L2.8,14.5a0.7,0.7 0,0 0,-0.1 0.6l1.9,3.4a0.7,0.7 0,0 0,0.6 
0.2l2.4,-1a6.5,6.5 0,0 0,1.6 0.9l0.4,2.6a0.5, [...]
+</vector>
diff --git a/wallet/src/main/res/drawable/ic_sync.xml 
b/wallet/src/main/res/drawable/ic_sync.xml
new file mode 100644
index 0000000..78593fc
--- /dev/null
+++ b/wallet/src/main/res/drawable/ic_sync.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android";
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+            android:fillColor="#FF000000"
+            android:pathData="M12,4L12,1L8,5l4,4L12,6c3.31,0 6,2.69 6,6 0,1.01 
-0.25,1.97 -0.7,2.8l1.46,1.46C19.54,15.03 20,13.57 20,12c0,-4.42 -3.58,-8 
-8,-8zM12,18c-3.31,0 -6,-2.69 -6,-6 0,-1.01 0.25,-1.97 
0.7,-2.8L5.24,7.74C4.46,8.97 4,10.43 4,12c0,4.42 3.58,8 8,8v3l4,-4 -4,-4v3z" />
+</vector>
diff --git a/wallet/src/main/res/drawable/pending_border.xml 
b/wallet/src/main/res/drawable/pending_border.xml
new file mode 100644
index 0000000..bb50fea
--- /dev/null
+++ b/wallet/src/main/res/drawable/pending_border.xml
@@ -0,0 +1,37 @@
+<?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/>
+  -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android";
+        android:shape="rectangle" >
+
+    <!-- View background color -->
+    <solid
+            android:color="@android:color/transparent" >
+    </solid>
+
+    <!-- View border color and width -->
+    <stroke
+            android:width="1dp"
+            android:color="@color/colorPrimary" >
+    </stroke>
+
+    <!-- The radius makes the corners rounded -->
+    <corners
+            android:radius="2dp"   >
+    </corners>
+
+</shape>
\ No newline at end of file
diff --git a/wallet/src/main/res/drawable/side_nav_bar.xml 
b/wallet/src/main/res/drawable/side_nav_bar.xml
new file mode 100644
index 0000000..6be80a8
--- /dev/null
+++ b/wallet/src/main/res/drawable/side_nav_bar.xml
@@ -0,0 +1,24 @@
+<!--
+  ~ This file is part of GNU Taler
+  ~ (C) 2020 Taler Systems S.A.
+  ~
+  ~ GNU Taler is free software; you can redistribute it and/or modify it under 
the
+  ~ terms of the GNU General Public License as published by the Free Software
+  ~ Foundation; either version 3, or (at your option) any later version.
+  ~
+  ~ GNU Taler is distributed in the hope that it will be useful, but WITHOUT 
ANY
+  ~ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
+  ~ A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+  ~
+  ~ You should have received a copy of the GNU General Public License along 
with
+  ~ GNU Taler; see the file COPYING.  If not, see 
<http://www.gnu.org/licenses/>
+  -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android";
+       android:shape="rectangle">
+    <gradient
+            android:angle="135"
+            android:startColor="@color/colorPrimary"
+            android:endColor="@color/colorPrimaryDark"
+            android:type="linear"/>
+</shape>
\ No newline at end of file
diff --git a/wallet/src/main/res/layout-w550dp/payment_bottom_bar.xml 
b/wallet/src/main/res/layout-w550dp/payment_bottom_bar.xml
new file mode 100644
index 0000000..d9e2f59
--- /dev/null
+++ b/wallet/src/main/res/layout-w550dp/payment_bottom_bar.xml
@@ -0,0 +1,123 @@
+<?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/>
+  -->
+
+<com.google.android.material.card.MaterialCardView 
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/bottomView"
+        style="@style/BottomCard"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        tools:showIn="@layout/fragment_prompt_payment">
+
+    <androidx.constraintlayout.widget.ConstraintLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content">
+
+        <TextView
+                android:id="@+id/totalLabelView"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginStart="8dp"
+                android:text="@string/payment_label_amount_total"
+                android:visibility="invisible"
+                app:layout_constraintBottom_toBottomOf="parent"
+                app:layout_constraintEnd_toStartOf="@+id/totalView"
+                app:layout_constraintHorizontal_bias="1.0"
+                app:layout_constraintHorizontal_chainStyle="packed"
+                app:layout_constraintStart_toEndOf="@+id/abortButton"
+                app:layout_constraintTop_toTopOf="@+id/totalView"
+                app:layout_constraintVertical_bias="0.0"
+                tools:visibility="visible" />
+
+        <TextView
+                android:id="@+id/totalView"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginStart="8dp"
+                android:layout_marginTop="8dp"
+                android:layout_marginEnd="16dp"
+                android:textColor="?android:attr/textColorPrimary"
+                android:textStyle="bold"
+                android:visibility="invisible"
+                app:layout_constraintBottom_toTopOf="@+id/feeView"
+                app:layout_constraintEnd_toStartOf="@+id/confirmButton"
+                app:layout_constraintHorizontal_chainStyle="packed"
+                app:layout_constraintStart_toEndOf="@+id/totalLabelView"
+                app:layout_constraintTop_toTopOf="parent"
+                app:layout_goneMarginBottom="8dp"
+                tools:text="10 TESTKUDOS"
+                tools:visibility="visible" />
+
+        <TextView
+                android:id="@+id/feeView"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginStart="8dp"
+                android:layout_marginEnd="16dp"
+                android:layout_marginBottom="8dp"
+                android:visibility="gone"
+                app:layout_constraintBottom_toBottomOf="parent"
+                app:layout_constraintEnd_toStartOf="@+id/confirmButton"
+                app:layout_constraintHorizontal_bias="1.0"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toBottomOf="@+id/totalView"
+                tools:text="@string/payment_fee"
+                tools:visibility="visible" />
+
+        <Button
+                android:id="@+id/abortButton"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_margin="8dp"
+                android:backgroundTint="@color/red"
+                android:text="@string/payment_button_abort"
+                app:layout_constraintBottom_toBottomOf="parent"
+                app:layout_constraintEnd_toStartOf="@+id/confirmButton"
+                app:layout_constraintHorizontal_chainStyle="spread_inside"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toTopOf="parent" />
+
+        <Button
+                android:id="@+id/confirmButton"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_margin="8dp"
+                android:backgroundTint="@color/green"
+                android:enabled="false"
+                android:text="@string/payment_button_confirm"
+                app:layout_constraintBottom_toBottomOf="parent"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintHorizontal_bias="0.5"
+                app:layout_constraintStart_toEndOf="@+id/abortButton"
+                app:layout_constraintTop_toTopOf="parent"
+                tools:enabled="true" />
+
+        <ProgressBar
+                android:id="@+id/confirmProgressBar"
+                style="?android:attr/progressBarStyle"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:visibility="invisible"
+                app:layout_constraintBottom_toBottomOf="@+id/confirmButton"
+                app:layout_constraintEnd_toEndOf="@+id/confirmButton"
+                app:layout_constraintStart_toStartOf="@+id/confirmButton"
+                app:layout_constraintTop_toTopOf="@+id/confirmButton"
+                tools:visibility="visible" />
+
+    </androidx.constraintlayout.widget.ConstraintLayout>
+
+</com.google.android.material.card.MaterialCardView>
diff --git a/wallet/src/main/res/layout/activity_main.xml 
b/wallet/src/main/res/layout/activity_main.xml
new file mode 100644
index 0000000..0612306
--- /dev/null
+++ b/wallet/src/main/res/layout/activity_main.xml
@@ -0,0 +1,40 @@
+<?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.drawerlayout.widget.DrawerLayout 
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/drawer_layout"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:fitsSystemWindows="true"
+        tools:openDrawer="start">
+
+    <include
+            layout="@layout/app_bar_main"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent" />
+
+    <com.google.android.material.navigation.NavigationView
+            android:id="@+id/nav_view"
+            android:layout_width="wrap_content"
+            android:layout_height="match_parent"
+            android:layout_gravity="start"
+            android:fitsSystemWindows="false"
+            app:headerLayout="@layout/nav_header_main"
+            app:menu="@menu/activity_main_drawer" />
+
+</androidx.drawerlayout.widget.DrawerLayout>
diff --git a/wallet/src/main/res/layout/app_bar_main.xml 
b/wallet/src/main/res/layout/app_bar_main.xml
new file mode 100644
index 0000000..d976be8
--- /dev/null
+++ b/wallet/src/main/res/layout/app_bar_main.xml
@@ -0,0 +1,75 @@
+<?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.coordinatorlayout.widget.CoordinatorLayout 
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=".MainActivity">
+
+    <com.google.android.material.appbar.AppBarLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:theme="@style/AppTheme.AppBarOverlay">
+
+        <androidx.constraintlayout.widget.ConstraintLayout
+                android:id="@+id/relativeLayout"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content">
+
+            <com.google.android.material.appbar.MaterialToolbar
+                    android:id="@+id/toolbar"
+                    style="@style/AppTheme.Toolbar"
+                    android:layout_width="0dp"
+                    android:layout_height="wrap_content"
+                    app:layout_constraintEnd_toEndOf="parent"
+                    app:layout_constraintStart_toStartOf="parent"
+                    app:layout_constraintTop_toTopOf="parent" />
+
+            <me.zhanghai.android.materialprogressbar.MaterialProgressBar
+                    android:id="@+id/progress_bar"
+                    
style="@style/Widget.MaterialProgressBar.ProgressBar.Horizontal"
+                    android:layout_width="0dp"
+                    android:layout_height="4dp"
+                    android:elevation="4dp"
+                    android:indeterminate="true"
+                    android:visibility="invisible"
+                    app:layout_constraintBottom_toBottomOf="@+id/toolbar"
+                    app:layout_constraintEnd_toEndOf="parent"
+                    app:layout_constraintStart_toStartOf="parent"
+                    app:mpb_progressStyle="horizontal"
+                    app:mpb_useIntrinsicPadding="false"
+                    tools:visibility="visible" />
+
+        </androidx.constraintlayout.widget.ConstraintLayout>
+
+    </com.google.android.material.appbar.AppBarLayout>
+
+    <androidx.fragment.app.FragmentContainerView
+            android:id="@+id/nav_host_fragment"
+            android:name="androidx.navigation.fragment.NavHostFragment"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            app:defaultNavHost="true"
+            app:layout_behavior="@string/appbar_scrolling_view_behavior"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintLeft_toLeftOf="parent"
+            app:layout_constraintRight_toRightOf="parent"
+            app:layout_constraintTop_toTopOf="parent"
+            app:navGraph="@navigation/nav_graph" />
+
+</androidx.coordinatorlayout.widget.CoordinatorLayout>
diff --git a/wallet/src/main/res/layout/fragment_already_paid.xml 
b/wallet/src/main/res/layout/fragment_already_paid.xml
new file mode 100644
index 0000000..d36fe69
--- /dev/null
+++ b/wallet/src/main/res/layout/fragment_already_paid.xml
@@ -0,0 +1,52 @@
+<?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/>
+  -->
+
+<LinearLayout 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"
+        android:layout_margin="15dp"
+        android:orientation="vertical"
+        tools:context=".payment.PaymentSuccessfulFragment">
+
+    <Space
+            android:layout_width="match_parent"
+            android:layout_height="0dp"
+            android:layout_weight="1" />
+
+    <TextView
+            android:layout_width="match_parent"
+            android:layout_height="50dp"
+            android:layout_gravity="center"
+            android:text="@string/payment_already_paid"
+            android:textAlignment="center"
+            android:textColor="@android:color/holo_green_dark"
+            app:autoSizeTextType="uniform" />
+
+
+    <Space
+            android:layout_width="match_parent"
+            android:layout_height="0dp"
+            android:layout_weight="1" />
+
+    <Button
+            android:id="@+id/backButton"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="@string/button_back" />
+
+</LinearLayout>
diff --git a/wallet/src/main/res/layout/fragment_error.xml 
b/wallet/src/main/res/layout/fragment_error.xml
new file mode 100644
index 0000000..3d977dd
--- /dev/null
+++ b/wallet/src/main/res/layout/fragment_error.xml
@@ -0,0 +1,97 @@
+<?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.ErrorFragment">
+
+    <ImageView
+            android:id="@+id/errorImageView"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:layout_margin="16dp"
+            android:alpha="0.56"
+            android:src="@drawable/ic_error"
+            android:tint="@color/red"
+            app:layout_constraintBottom_toTopOf="@+id/errorTitle"
+            app:layout_constraintDimensionRatio="1:1"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent"
+            app:layout_constraintVertical_bias="0.0"
+            app:layout_constraintVertical_chainStyle="packed"
+            tools:ignore="ContentDescription" />
+
+    <TextView
+            android:id="@+id/errorTitle"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_margin="16dp"
+            android:gravity="center_horizontal|top"
+            android:minHeight="64dp"
+            android:textColor="@color/red"
+            app:autoSizeMaxTextSize="40sp"
+            app:autoSizeTextType="uniform"
+            app:layout_constraintBottom_toTopOf="@+id/errorMessage"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toBottomOf="@+id/errorImageView"
+            tools:text="@string/withdraw_error_title" />
+
+    <TextView
+            android:id="@+id/errorMessage"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_margin="16dp"
+            android:gravity="center"
+            android:textAppearance="@style/TextAppearance.AppCompat.Medium"
+            app:layout_constraintBottom_toTopOf="@+id/errorDevMessage"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toBottomOf="@+id/errorTitle"
+            tools:text="@string/withdraw_error_message" />
+
+    <TextView
+            android:id="@+id/errorDevMessage"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_margin="16dp"
+            android:fontFamily="monospace"
+            android:gravity="center"
+            android:textColor="@color/red"
+            android:textIsSelectable="true"
+            android:visibility="gone"
+            app:layout_constraintBottom_toTopOf="@+id/backButton"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toBottomOf="@+id/errorMessage"
+            tools:text="Error: Fetching keys failed: unexpected status for 
keys: 502"
+            tools:visibility="visible" />
+
+    <Button
+            android:id="@+id/backButton"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_margin="16dp"
+            android:text="@string/button_back"
+            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_json.xml 
b/wallet/src/main/res/layout/fragment_json.xml
new file mode 100644
index 0000000..1e0c047
--- /dev/null
+++ b/wallet/src/main/res/layout/fragment_json.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ This file is part of GNU Taler
+  ~ (C) 2020 Taler Systems S.A.
+  ~
+  ~ GNU Taler is free software; you can redistribute it and/or modify it under 
the
+  ~ terms of the GNU General Public License as published by the Free Software
+  ~ Foundation; either version 3, or (at your option) any later version.
+  ~
+  ~ GNU Taler is distributed in the hope that it will be useful, but WITHOUT 
ANY
+  ~ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
+  ~ A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+  ~
+  ~ You should have received a copy of the GNU General Public License along 
with
+  ~ GNU Taler; see the file COPYING.  If not, see 
<http://www.gnu.org/licenses/>
+  -->
+
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android";
+        xmlns:app="http://schemas.android.com/apk/res-auto";
+        xmlns:tools="http://schemas.android.com/tools";
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:orientation="vertical">
+
+    <TextView
+            android:id="@+id/jsonView"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="8dp"
+            android:layout_marginTop="8dp"
+            android:layout_marginEnd="8dp"
+            android:layout_marginBottom="8dp"
+            android:fontFamily="monospace"
+            android:textSize="12sp"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent"
+            tools:text="[JSON]" />
+
+</ScrollView>
diff --git a/wallet/src/main/res/layout/fragment_payment_successful.xml 
b/wallet/src/main/res/layout/fragment_payment_successful.xml
new file mode 100644
index 0000000..cf9e5e8
--- /dev/null
+++ b/wallet/src/main/res/layout/fragment_payment_successful.xml
@@ -0,0 +1,63 @@
+<?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_pending_operations.xml 
b/wallet/src/main/res/layout/fragment_pending_operations.xml
new file mode 100644
index 0000000..26c1be1
--- /dev/null
+++ b/wallet/src/main/res/layout/fragment_pending_operations.xml
@@ -0,0 +1,34 @@
+<?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">
+
+    <androidx.recyclerview.widget.RecyclerView
+            android:id="@+id/list_pending"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:scrollbars="vertical"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent"
+            tools:listitem="@layout/pending_row" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/wallet/src/main/res/layout/fragment_product_image.xml 
b/wallet/src/main/res/layout/fragment_product_image.xml
new file mode 100644
index 0000000..9f65d4d
--- /dev/null
+++ b/wallet/src/main/res/layout/fragment_product_image.xml
@@ -0,0 +1,24 @@
+<?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/>
+  -->
+
+<ImageView xmlns:android="http://schemas.android.com/apk/res/android";
+        xmlns:tools="http://schemas.android.com/tools";
+        android:id="@+id/productImageView"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        tools:ignore="ContentDescription">
+
+</ImageView>
\ No newline at end of file
diff --git a/wallet/src/main/res/layout/fragment_prompt_payment.xml 
b/wallet/src/main/res/layout/fragment_prompt_payment.xml
new file mode 100644
index 0000000..26cbeb6
--- /dev/null
+++ b/wallet/src/main/res/layout/fragment_prompt_payment.xml
@@ -0,0 +1,44 @@
+<?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=".payment.PromptPaymentFragment">
+
+    <include
+            android:id="@+id/scrollView"
+            layout="@layout/payment_details"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            app:layout_constraintBottom_toTopOf="@+id/bottomView"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent" />
+
+    <include
+            android:id="@+id/bottomView"
+            layout="@layout/payment_bottom_bar"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toBottomOf="@+id/scrollView" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/wallet/src/main/res/layout/fragment_prompt_withdraw.xml 
b/wallet/src/main/res/layout/fragment_prompt_withdraw.xml
new file mode 100644
index 0000000..1114c17
--- /dev/null
+++ b/wallet/src/main/res/layout/fragment_prompt_withdraw.xml
@@ -0,0 +1,171 @@
+<?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.PromptWithdrawFragment">
+
+    <TextView
+            android:id="@+id/introView"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="16dp"
+            android:layout_marginEnd="16dp"
+            android:layout_marginBottom="8dp"
+            android:gravity="center"
+            android:text="@string/withdraw_do_you_want"
+            android:visibility="invisible"
+            app:layout_constraintBottom_toTopOf="@+id/withdrawAmountView"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintHorizontal_bias="0.5"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent"
+            app:layout_constraintVertical_chainStyle="packed"
+            tools:visibility="visible" />
+
+    <TextView
+            android:id="@+id/withdrawAmountView"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="16dp"
+            android:layout_marginEnd="16dp"
+            android:gravity="center"
+            android:textAppearance="@style/TextAppearance.AppCompat.Headline"
+            android:visibility="invisible"
+            app:layout_constraintBottom_toTopOf="@+id/feeView"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toBottomOf="@+id/introView"
+            tools:text="10.00 TESTKUDOS"
+            tools:visibility="visible" />
+
+    <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:gravity="center"
+            android:text="@string/withdraw_fees"
+            android:visibility="invisible"
+            app:layout_constraintBottom_toTopOf="@+id/exchangeIntroView"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintHorizontal_bias="0.5"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toBottomOf="@+id/withdrawAmountView"
+            tools:visibility="visible" />
+
+    <TextView
+            android:id="@+id/exchangeIntroView"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="16dp"
+            android:layout_marginTop="32dp"
+            android:layout_marginEnd="16dp"
+            android:layout_marginBottom="8dp"
+            android:gravity="center"
+            android:text="@string/withdraw_exchange"
+            android:visibility="invisible"
+            app:layout_constraintBottom_toTopOf="@+id/withdrawExchangeUrl"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintHorizontal_bias="0.5"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toBottomOf="@+id/feeView"
+            tools:visibility="visible" />
+
+    <TextView
+            android:id="@+id/withdrawExchangeUrl"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="16dp"
+            android:layout_marginEnd="16dp"
+            android:gravity="center"
+            android:textSize="25sp"
+            android:visibility="invisible"
+            app:layout_constraintBottom_toTopOf="@+id/withdrawCard"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toBottomOf="@+id/exchangeIntroView"
+            tools:text="(exchange base url)"
+            tools:visibility="visible" />
+
+    <ProgressBar
+            android:id="@+id/progressBar"
+            style="?android:attr/progressBarStyleLarge"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            app:layout_constraintBottom_toTopOf="@+id/withdrawCard"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent" />
+
+    <com.google.android.material.card.MaterialCardView
+            android:id="@+id/withdrawCard"
+            style="@style/BottomCard"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="parent">
+
+        <androidx.constraintlayout.widget.ConstraintLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:padding="8dp">
+
+            <Button
+                    android:id="@+id/button_cancel_withdraw"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:backgroundTint="@color/red"
+                    android:text="@string/button_cancel"
+                    app:layout_constraintBottom_toBottomOf="parent"
+                    
app:layout_constraintEnd_toStartOf="@+id/button_confirm_withdraw"
+                    app:layout_constraintHorizontal_chainStyle="spread_inside"
+                    app:layout_constraintStart_toStartOf="parent" />
+
+            <Button
+                    android:id="@+id/button_confirm_withdraw"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:backgroundTint="@color/green"
+                    android:enabled="false"
+                    android:text="@string/withdraw_button_confirm"
+                    app:layout_constraintBottom_toBottomOf="parent"
+                    app:layout_constraintEnd_toEndOf="parent"
+                    
app:layout_constraintStart_toEndOf="@+id/button_cancel_withdraw" />
+
+            <ProgressBar
+                    android:id="@+id/confirmProgressBar"
+                    style="?android:attr/progressBarStyle"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:visibility="invisible"
+                    
app:layout_constraintBottom_toBottomOf="@+id/button_confirm_withdraw"
+                    
app:layout_constraintEnd_toEndOf="@+id/button_confirm_withdraw"
+                    
app:layout_constraintStart_toStartOf="@+id/button_confirm_withdraw"
+                    
app:layout_constraintTop_toTopOf="@+id/button_confirm_withdraw"
+                    tools:visibility="visible" />
+
+        </androidx.constraintlayout.widget.ConstraintLayout>
+
+    </com.google.android.material.card.MaterialCardView>
+
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/wallet/src/main/res/layout/fragment_review_exchange_tos.xml 
b/wallet/src/main/res/layout/fragment_review_exchange_tos.xml
new file mode 100644
index 0000000..61a61f1
--- /dev/null
+++ b/wallet/src/main/res/layout/fragment_review_exchange_tos.xml
@@ -0,0 +1,105 @@
+<?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.ReviewExchangeTosFragment">
+
+    <ScrollView
+            android:id="@+id/tosScrollView"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            app:layout_constraintBottom_toTopOf="@+id/buttonCard"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent">
+
+        <TextView
+                android:id="@+id/tosTextView"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:padding="16dp"
+                android:visibility="invisible"
+                tools:text="@tools:sample/lorem/random"
+                tools:visibility="visible" />
+
+    </ScrollView>
+
+    <ProgressBar
+            android:id="@+id/progressBar"
+            style="?android:attr/progressBarStyleLarge"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            app:layout_constraintBottom_toBottomOf="@+id/tosScrollView"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent" />
+
+    <com.google.android.material.card.MaterialCardView
+            android:id="@+id/buttonCard"
+            style="@style/BottomCard"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="parent">
+
+        <androidx.constraintlayout.widget.ConstraintLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:padding="8dp">
+
+            <CheckBox
+                    android:id="@+id/acceptTosCheckBox"
+                    android:layout_width="0dp"
+                    android:layout_height="wrap_content"
+                    android:text="@string/exchange_tos_accept"
+                    android:visibility="invisible"
+                    app:layout_constraintBottom_toTopOf="@+id/acceptTosButton"
+                    app:layout_constraintEnd_toEndOf="parent"
+                    app:layout_constraintStart_toStartOf="parent"
+                    tools:visibility="visible" />
+
+            <Button
+                    android:id="@+id/abortTosButton"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:backgroundTint="@color/red"
+                    android:text="@string/button_cancel"
+                    app:layout_constraintBottom_toBottomOf="parent"
+                    app:layout_constraintEnd_toStartOf="@+id/acceptTosButton"
+                    app:layout_constraintHorizontal_chainStyle="spread_inside"
+                    app:layout_constraintStart_toStartOf="parent" />
+
+            <Button
+                    android:id="@+id/acceptTosButton"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:backgroundTint="@color/green"
+                    android:enabled="false"
+                    android:text="@string/exchange_tos_button_continue"
+                    app:layout_constraintBottom_toBottomOf="parent"
+                    app:layout_constraintEnd_toEndOf="parent"
+                    app:layout_constraintStart_toEndOf="@+id/abortTosButton" />
+
+        </androidx.constraintlayout.widget.ConstraintLayout>
+
+    </com.google.android.material.card.MaterialCardView>
+
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/wallet/src/main/res/layout/fragment_settings.xml 
b/wallet/src/main/res/layout/fragment_settings.xml
new file mode 100644
index 0000000..2fa0fcc
--- /dev/null
+++ b/wallet/src/main/res/layout/fragment_settings.xml
@@ -0,0 +1,104 @@
+<?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/>
+  -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android";
+        xmlns:tools="http://schemas.android.com/tools";
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_margin="10dp"
+        android:orientation="vertical"
+        tools:context=".Settings">
+
+
+    <TextView
+            android:id="@+id/editText2"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:ems="10"
+            android:text="@string/settings_version"
+            android:textSize="18sp" />
+
+
+    <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal">
+
+        <TextView
+                android:id="@+id/textView5"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_weight="1"
+                android:text="@string/app_name" />
+
+        <TextView
+                android:id="@+id/textView4"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_weight="1"
+                tools:text="0.6.0pre8" />
+
+    </LinearLayout>
+
+    <Space
+            android:layout_width="0dp"
+            android:layout_height="15dp" />
+
+    <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:ems="10"
+            android:text="@string/settings_backups"
+            android:textSize="18sp"
+            android:visibility="gone" />
+
+    <Button
+            android:id="@+id/button_backup_export"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/settings_export_to_file"
+            android:visibility="gone" />
+
+    <Button
+            android:id="@+id/button_backup_import"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/settings_import_from_file"
+            android:visibility="gone" />
+
+
+    <TextView
+            android:id="@+id/devSettingsTitle"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:ems="10"
+            android:text="@string/settings_developer"
+            android:textSize="18sp" />
+
+    <!--
+    <Button
+            android:text="Withdraw TESTKUDOS"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:id="@+id/button_withdraw_testkudos"/>-->
+
+    <Button
+            android:id="@+id/button_reset_wallet_dangerously"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/settings_reset" />
+
+</LinearLayout>
diff --git a/wallet/src/main/res/layout/fragment_show_balance.xml 
b/wallet/src/main/res/layout/fragment_show_balance.xml
new file mode 100644
index 0000000..5bc6ee8
--- /dev/null
+++ b/wallet/src/main/res/layout/fragment_show_balance.xml
@@ -0,0 +1,91 @@
+<?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">
+
+    <androidx.recyclerview.widget.RecyclerView
+            android:id="@+id/balancesList"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:visibility="gone"
+            app:layout_constraintBottom_toTopOf="@+id/scanButton"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent"
+            app:layout_constraintVertical_bias="0.0"
+            app:layout_constraintVertical_chainStyle="packed"
+            tools:layout_height="200dp"
+            tools:listitem="@layout/list_item_balance"
+            tools:visibility="visible" />
+
+    <TextView
+            android:id="@+id/balancesEmptyState"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:autoLink="web"
+            android:gravity="center"
+            android:padding="16dp"
+            android:text="@string/balances_empty_state"
+            android:textSize="18sp"
+            android:visibility="gone"
+            app:layout_constraintBottom_toTopOf="@+id/scanButton"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent"
+            tools:visibility="gone" />
+
+    <androidx.constraintlayout.widget.Barrier
+            android:id="@+id/barrier"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            app:barrierAllowsGoneWidgets="false"
+            app:barrierDirection="bottom"
+            app:constraint_referenced_ids="balancesList, balancesEmptyState" />
+
+    <Button
+            android:id="@+id/scanButton"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_margin="16dp"
+            android:drawableLeft="@drawable/ic_scan_qr"
+            android:padding="16dp"
+            android:text="@string/button_scan_qr_code"
+            app:layout_constraintBottom_toTopOf="@+id/testWithdrawButton"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toBottomOf="@+id/barrier"
+            app:layout_constraintVertical_chainStyle="packed"
+            tools:ignore="RtlHardcoded" />
+
+    <Button
+            android:id="@+id/testWithdrawButton"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="16dp"
+            android:layout_marginTop="16dp"
+            android:layout_marginEnd="16dp"
+            android:padding="16dp"
+            android:text="@string/withdraw_button_testkudos"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toBottomOf="@+id/scanButton"
+            tools:visibility="visible" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/wallet/src/main/res/layout/fragment_show_history.xml 
b/wallet/src/main/res/layout/fragment_show_history.xml
new file mode 100644
index 0000000..3e84b0f
--- /dev/null
+++ b/wallet/src/main/res/layout/fragment_show_history.xml
@@ -0,0 +1,47 @@
+<?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/>
+  -->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android";
+        xmlns:tools="http://schemas.android.com/tools";
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+
+    <androidx.recyclerview.widget.RecyclerView
+            android:id="@+id/historyList"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:scrollbars="vertical" />
+
+    <TextView
+            android:id="@+id/historyEmptyState"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center"
+            android:text="@string/history_empty"
+            android:visibility="invisible"
+            tools:visibility="visible" />
+
+    <ProgressBar
+            android:id="@+id/historyProgressBar"
+            style="?android:progressBarStyleLarge"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center"
+            android:visibility="invisible"
+            tools:visibility="visible" />
+
+</FrameLayout>
diff --git a/wallet/src/main/res/layout/fragment_withdraw_successful.xml 
b/wallet/src/main/res/layout/fragment_withdraw_successful.xml
new file mode 100644
index 0000000..2b7c308
--- /dev/null
+++ b/wallet/src/main/res/layout/fragment_withdraw_successful.xml
@@ -0,0 +1,63 @@
+<?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/history_payment.xml 
b/wallet/src/main/res/layout/history_payment.xml
new file mode 100644
index 0000000..dd135e7
--- /dev/null
+++ b/wallet/src/main/res/layout/history_payment.xml
@@ -0,0 +1,87 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ This file is part of GNU Taler
+  ~ (C) 2020 Taler Systems S.A.
+  ~
+  ~ GNU Taler is free software; you can redistribute it and/or modify it under 
the
+  ~ terms of the GNU General Public License as published by the Free Software
+  ~ Foundation; either version 3, or (at your option) any later version.
+  ~
+  ~ GNU Taler is distributed in the hope that it will be useful, but WITHOUT 
ANY
+  ~ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
+  ~ A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+  ~
+  ~ You should have received a copy of the GNU General Public License along 
with
+  ~ GNU Taler; see the file COPYING.  If not, see 
<http://www.gnu.org/licenses/>
+  -->
+
+<androidx.constraintlayout.widget.ConstraintLayout 
xmlns:android="http://schemas.android.com/apk/res/android";
+        xmlns:app="http://schemas.android.com/apk/res-auto";
+        xmlns:tools="http://schemas.android.com/tools";
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="16dp"
+        android:layout_marginTop="8dp"
+        android:layout_marginEnd="16dp"
+        android:layout_marginBottom="8dp"
+        android:background="?attr/selectableItemBackground">
+
+    <ImageView
+            android:id="@+id/icon"
+            android:layout_width="32dp"
+            android:layout_height="32dp"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent"
+            app:srcCompat="@drawable/history_withdrawn"
+            app:tint="?android:colorControlNormal"
+            tools:ignore="ContentDescription" />
+
+    <TextView
+            android:id="@+id/title"
+            style="@style/HistoryTitle"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="8dp"
+            android:layout_marginEnd="8dp"
+            app:layout_constraintEnd_toStartOf="@+id/amountPaidWithFees"
+            app:layout_constraintStart_toEndOf="@+id/icon"
+            app:layout_constraintTop_toTopOf="parent"
+            tools:text="Lots of books with very long titles" />
+
+    <TextView
+            android:id="@+id/summary"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="8dp"
+            android:layout_marginTop="8dp"
+            app:layout_constrainedWidth="true"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintEnd_toStartOf="@+id/amountPaidWithFees"
+            app:layout_constraintHorizontal_bias="0.0"
+            app:layout_constraintStart_toEndOf="@+id/icon"
+            app:layout_constraintTop_toBottomOf="@+id/title"
+            app:layout_constraintVertical_bias="0.0"
+            tools:text="@string/history_event_payment_sent" />
+
+    <TextView
+            android:id="@+id/amountPaidWithFees"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textColor="@color/red"
+            android:textSize="16sp"
+            app:layout_constraintBottom_toTopOf="@+id/time"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintTop_toTopOf="parent"
+            app:layout_constraintVertical_bias="0.0"
+            tools:text="0.2 TESTKUDOS" />
+
+    <TextView
+            android:id="@+id/time"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textSize="14sp"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintEnd_toEndOf="parent"
+            tools:text="23 min ago" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/wallet/src/main/res/layout/history_receive.xml 
b/wallet/src/main/res/layout/history_receive.xml
new file mode 100644
index 0000000..1f76376
--- /dev/null
+++ b/wallet/src/main/res/layout/history_receive.xml
@@ -0,0 +1,105 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ This file is part of GNU Taler
+  ~ (C) 2020 Taler Systems S.A.
+  ~
+  ~ GNU Taler is free software; you can redistribute it and/or modify it under 
the
+  ~ terms of the GNU General Public License as published by the Free Software
+  ~ Foundation; either version 3, or (at your option) any later version.
+  ~
+  ~ GNU Taler is distributed in the hope that it will be useful, but WITHOUT 
ANY
+  ~ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
+  ~ A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+  ~
+  ~ You should have received a copy of the GNU General Public License along 
with
+  ~ GNU Taler; see the file COPYING.  If not, see 
<http://www.gnu.org/licenses/>
+  -->
+
+<androidx.constraintlayout.widget.ConstraintLayout 
xmlns:android="http://schemas.android.com/apk/res/android";
+        xmlns:app="http://schemas.android.com/apk/res-auto";
+        xmlns:tools="http://schemas.android.com/tools";
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="16dp"
+        android:layout_marginTop="8dp"
+        android:layout_marginEnd="16dp"
+        android:layout_marginBottom="8dp"
+        android:background="?attr/selectableItemBackground">
+
+    <ImageView
+            android:id="@+id/icon"
+            android:layout_width="32dp"
+            android:layout_height="32dp"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent"
+            app:srcCompat="@drawable/history_withdrawn"
+            app:tint="?android:colorControlNormal"
+            tools:ignore="ContentDescription" />
+
+    <TextView
+            android:id="@+id/title"
+            style="@style/HistoryTitle"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="8dp"
+            android:layout_marginEnd="8dp"
+            android:text="@string/history_event_withdrawn"
+            app:layout_constraintEnd_toStartOf="@+id/amountWithdrawn"
+            app:layout_constraintStart_toEndOf="@+id/icon"
+            app:layout_constraintTop_toTopOf="parent" />
+
+    <TextView
+            android:id="@+id/summary"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="8dp"
+            android:layout_marginTop="8dp"
+            android:layout_marginEnd="8dp"
+            android:layout_marginBottom="8dp"
+            app:layout_constrainedWidth="true"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintEnd_toStartOf="@+id/feeLabel"
+            app:layout_constraintStart_toEndOf="@+id/icon"
+            app:layout_constraintTop_toBottomOf="@+id/title"
+            tools:text="http://taler.quite-long-exchange.url"; />
+
+    <TextView
+            android:id="@+id/feeLabel"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginEnd="2dp"
+            android:text="@string/history_fee_label"
+            app:layout_constraintEnd_toStartOf="@+id/fee"
+            app:layout_constraintTop_toTopOf="@+id/fee" />
+
+    <TextView
+            android:id="@+id/fee"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textColor="@color/red"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintTop_toBottomOf="@+id/amountWithdrawn"
+            tools:text="0.2 TESTKUDOS" />
+
+    <TextView
+            android:id="@+id/amountWithdrawn"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textColor="@color/green"
+            android:textSize="16sp"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintTop_toTopOf="parent"
+            tools:text="10 TESTKUDOS" />
+
+    <TextView
+            android:id="@+id/time"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="8dp"
+            android:textSize="14sp"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintTop_toBottomOf="@+id/fee"
+            tools:text="23 min. ago" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/wallet/src/main/res/layout/history_row.xml 
b/wallet/src/main/res/layout/history_row.xml
new file mode 100644
index 0000000..8f0db1f
--- /dev/null
+++ b/wallet/src/main/res/layout/history_row.xml
@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ This file is part of GNU Taler
+  ~ (C) 2020 Taler Systems S.A.
+  ~
+  ~ GNU Taler is free software; you can redistribute it and/or modify it under 
the
+  ~ terms of the GNU General Public License as published by the Free Software
+  ~ Foundation; either version 3, or (at your option) any later version.
+  ~
+  ~ GNU Taler is distributed in the hope that it will be useful, but WITHOUT 
ANY
+  ~ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
+  ~ A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+  ~
+  ~ You should have received a copy of the GNU General Public License along 
with
+  ~ GNU Taler; see the file COPYING.  If not, see 
<http://www.gnu.org/licenses/>
+  -->
+
+<androidx.constraintlayout.widget.ConstraintLayout 
xmlns:android="http://schemas.android.com/apk/res/android";
+        xmlns:app="http://schemas.android.com/apk/res-auto";
+        xmlns:tools="http://schemas.android.com/tools";
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_margin="15dp"
+        android:background="?attr/selectableItemBackground">
+
+    <ImageView
+            android:id="@+id/icon"
+            android:layout_width="32dp"
+            android:layout_height="32dp"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent"
+            app:srcCompat="@drawable/ic_account_balance"
+            app:tint="?android:colorControlNormal"
+            tools:ignore="ContentDescription" />
+
+    <TextView
+            android:id="@+id/title"
+            style="@style/HistoryTitle"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="8dp"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toEndOf="@+id/icon"
+            app:layout_constraintTop_toTopOf="parent"
+            app:layout_constraintVertical_chainStyle="packed"
+            tools:text="My History Event" />
+
+    <TextView
+            android:id="@+id/info"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="8dp"
+            android:layout_marginTop="8dp"
+            android:layout_marginEnd="8dp"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintEnd_toStartOf="@+id/time"
+            app:layout_constraintStart_toEndOf="@+id/icon"
+            app:layout_constraintTop_toBottomOf="@+id/title"
+            tools:text="TextView" />
+
+    <TextView
+            android:id="@+id/time"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="8dp"
+            android:gravity="end"
+            android:textSize="14sp"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintTop_toBottomOf="@+id/title"
+            tools:text="3 days ago" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/wallet/src/main/res/layout/list_item_balance.xml 
b/wallet/src/main/res/layout/list_item_balance.xml
new file mode 100644
index 0000000..f9c37b7
--- /dev/null
+++ b/wallet/src/main/res/layout/list_item_balance.xml
@@ -0,0 +1,77 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ This file is part of GNU Taler
+  ~ (C) 2020 Taler Systems S.A.
+  ~
+  ~ GNU Taler is free software; you can redistribute it and/or modify it under 
the
+  ~ terms of the GNU General Public License as published by the Free Software
+  ~ Foundation; either version 3, or (at your option) any later version.
+  ~
+  ~ GNU Taler is distributed in the hope that it will be useful, but WITHOUT 
ANY
+  ~ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
+  ~ A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+  ~
+  ~ You should have received a copy of the GNU General Public License along 
with
+  ~ GNU Taler; see the file COPYING.  If not, see 
<http://www.gnu.org/licenses/>
+  -->
+
+<androidx.constraintlayout.widget.ConstraintLayout 
xmlns:android="http://schemas.android.com/apk/res/android";
+        xmlns:app="http://schemas.android.com/apk/res-auto";
+        xmlns:tools="http://schemas.android.com/tools";
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:padding="16dp">
+
+    <TextView
+            android:id="@+id/balance_amount"
+            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_constraintHorizontal_bias="0.0"
+            app:layout_constraintHorizontal_chainStyle="packed"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent"
+            tools:text="100.50" />
+
+    <TextView
+            android:id="@+id/balance_currency"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            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"
+            tools:text="TESTKUDOS" />
+
+    <TextView
+            android:id="@+id/balanceInboundAmount"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textColor="@color/green"
+            android:textSize="20sp"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintEnd_toStartOf="@+id/balanceInboundLabel"
+            app:layout_constraintHorizontal_bias="0.0"
+            app:layout_constraintHorizontal_chainStyle="packed"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toBottomOf="@+id/balance_amount"
+            tools:text="+10 TESTKUDOS"
+            tools:visibility="visible" />
+
+    <TextView
+            android:id="@+id/balanceInboundLabel"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="8dp"
+            android:text="@string/balances_inbound_label"
+            android:textColor="@color/green"
+            app:layout_constraintBottom_toBottomOf="@+id/balanceInboundAmount"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toEndOf="@+id/balanceInboundAmount"
+            app:layout_constraintTop_toTopOf="@+id/balanceInboundAmount"
+            tools:visibility="visible" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/wallet/src/main/res/layout/list_item_product.xml 
b/wallet/src/main/res/layout/list_item_product.xml
new file mode 100644
index 0000000..fe6ba23
--- /dev/null
+++ b/wallet/src/main/res/layout/list_item_product.xml
@@ -0,0 +1,75 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ This file is part of GNU Taler
+  ~ (C) 2020 Taler Systems S.A.
+  ~
+  ~ GNU Taler is free software; you can redistribute it and/or modify it under 
the
+  ~ terms of the GNU General Public License as published by the Free Software
+  ~ Foundation; either version 3, or (at your option) any later version.
+  ~
+  ~ GNU Taler is distributed in the hope that it will be useful, but WITHOUT 
ANY
+  ~ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
+  ~ A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+  ~
+  ~ You should have received a copy of the GNU General Public License along 
with
+  ~ GNU Taler; see the file COPYING.  If not, see 
<http://www.gnu.org/licenses/>
+  -->
+
+<androidx.constraintlayout.widget.ConstraintLayout 
xmlns:android="http://schemas.android.com/apk/res/android";
+        xmlns:app="http://schemas.android.com/apk/res-auto";
+        xmlns:tools="http://schemas.android.com/tools";
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:padding="8dp">
+
+    <TextView
+            android:id="@+id/quantity"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:gravity="end"
+            android:minWidth="24dp"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent"
+            app:layout_constraintVertical_bias="0.0"
+            tools:text="31" />
+
+    <ImageView
+            android:id="@+id/image"
+            android:layout_width="wrap_content"
+            android:layout_height="0dp"
+            android:layout_marginStart="8dp"
+            app:layout_constrainedWidth="true"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintDimensionRatio="H,4:3"
+            app:layout_constraintEnd_toStartOf="@+id/name"
+            app:layout_constraintStart_toEndOf="@+id/quantity"
+            app:layout_constraintTop_toTopOf="parent"
+            app:layout_constraintWidth_max="64dp"
+            tools:ignore="ContentDescription"
+            tools:srcCompat="@tools:sample/avatars"
+            tools:visibility="visible" />
+
+    <TextView
+            android:id="@+id/name"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="8dp"
+            android:layout_marginEnd="8dp"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintEnd_toStartOf="@+id/price"
+            app:layout_constraintStart_toEndOf="@+id/image"
+            app:layout_constraintTop_toTopOf="parent"
+            app:layout_constraintVertical_bias="0.0"
+            tools:text="A product item that in some cases could have a very 
long name" />
+
+    <TextView
+            android:id="@+id/price"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintTop_toTopOf="parent"
+            app:layout_constraintVertical_bias="0.0"
+            tools:text="23.42" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/wallet/src/main/res/layout/list_item_product_single.xml 
b/wallet/src/main/res/layout/list_item_product_single.xml
new file mode 100644
index 0000000..a08f1f8
--- /dev/null
+++ b/wallet/src/main/res/layout/list_item_product_single.xml
@@ -0,0 +1,78 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ This file is part of GNU Taler
+  ~ (C) 2020 Taler Systems S.A.
+  ~
+  ~ GNU Taler is free software; you can redistribute it and/or modify it under 
the
+  ~ terms of the GNU General Public License as published by the Free Software
+  ~ Foundation; either version 3, or (at your option) any later version.
+  ~
+  ~ GNU Taler is distributed in the hope that it will be useful, but WITHOUT 
ANY
+  ~ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
+  ~ A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+  ~
+  ~ You should have received a copy of the GNU General Public License along 
with
+  ~ GNU Taler; see the file COPYING.  If not, see 
<http://www.gnu.org/licenses/>
+  -->
+
+<androidx.constraintlayout.widget.ConstraintLayout 
xmlns:android="http://schemas.android.com/apk/res/android";
+        xmlns:app="http://schemas.android.com/apk/res-auto";
+        xmlns:tools="http://schemas.android.com/tools";
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:padding="8dp">
+
+    <TextView
+            android:id="@+id/quantity"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:visibility="gone"
+            app:layout_constraintEnd_toStartOf="@+id/name"
+            app:layout_constraintHorizontal_bias="0.5"
+            app:layout_constraintHorizontal_chainStyle="packed"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent"
+            app:layout_constraintVertical_bias="0.0"
+            tools:text="31" />
+
+    <ImageView
+            android:id="@+id/image"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="8dp"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toBottomOf="@+id/name"
+            tools:ignore="ContentDescription"
+            tools:srcCompat="@tools:sample/avatars"
+            tools:visibility="visible" />
+
+    <TextView
+            android:id="@+id/name"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="8dp"
+            android:layout_marginEnd="8dp"
+            android:visibility="gone"
+            app:layout_constrainedWidth="true"
+            app:layout_constraintBottom_toTopOf="@+id/image"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintEnd_toStartOf="@+id/price"
+            app:layout_constraintHorizontal_bias="0.5"
+            app:layout_constraintStart_toEndOf="@+id/quantity"
+            app:layout_constraintTop_toTopOf="parent"
+            app:layout_goneMarginEnd="0dp"
+            tools:text="A product item that can have a very long name that 
wraps over two lines" />
+
+    <TextView
+            android:id="@+id/price"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:visibility="gone"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintTop_toTopOf="parent"
+            app:layout_constraintVertical_bias="0.0"
+            tools:text="23.42" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/wallet/src/main/res/layout/nav_header_main.xml 
b/wallet/src/main/res/layout/nav_header_main.xml
new file mode 100644
index 0000000..5574c1f
--- /dev/null
+++ b/wallet/src/main/res/layout/nav_header_main.xml
@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ This file is part of GNU Taler
+  ~ (C) 2020 Taler Systems S.A.
+  ~
+  ~ GNU Taler is free software; you can redistribute it and/or modify it under 
the
+  ~ terms of the GNU General Public License as published by the Free Software
+  ~ Foundation; either version 3, or (at your option) any later version.
+  ~
+  ~ GNU Taler is distributed in the hope that it will be useful, but WITHOUT 
ANY
+  ~ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
+  ~ A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+  ~
+  ~ You should have received a copy of the GNU General Public License along 
with
+  ~ GNU Taler; see the file COPYING.  If not, see 
<http://www.gnu.org/licenses/>
+  -->
+
+<androidx.constraintlayout.widget.ConstraintLayout 
xmlns:android="http://schemas.android.com/apk/res/android";
+        xmlns:app="http://schemas.android.com/apk/res-auto";
+        xmlns:tools="http://schemas.android.com/tools";
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:background="@drawable/side_nav_bar"
+        android:theme="@style/ThemeOverlay.AppCompat.Dark">
+
+    <ImageView
+            android:id="@+id/talerLogoView"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_margin="16dp"
+            android:contentDescription="@string/nav_header_desc"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent"
+            app:srcCompat="@mipmap/ic_launcher_round" />
+
+    <TextView
+            android:id="@+id/gnuView"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="16dp"
+            android:layout_marginTop="8dp"
+            android:layout_marginEnd="16dp"
+            android:text="@string/nav_header_title"
+            android:textAppearance="@style/TextAppearance.AppCompat.Body1"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toBottomOf="@+id/talerLogoView" />
+
+    <TextView
+            android:id="@+id/walletView"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="16dp"
+            android:layout_marginTop="4dp"
+            android:layout_marginBottom="16dp"
+            android:text="@string/nav_header_subtitle"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toBottomOf="@+id/gnuView" />
+
+    <TextView
+            android:id="@+id/versionView"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="8dp"
+            android:layout_marginEnd="16dp"
+            app:layout_constraintBottom_toBottomOf="@+id/walletView"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintHorizontal_bias="0.0"
+            app:layout_constraintStart_toEndOf="@+id/walletView"
+            app:layout_constraintTop_toTopOf="@+id/walletView"
+            tools:text="0.6.9-pre15" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/wallet/src/main/res/layout/payment_bottom_bar.xml 
b/wallet/src/main/res/layout/payment_bottom_bar.xml
new file mode 100644
index 0000000..8fdf0f8
--- /dev/null
+++ b/wallet/src/main/res/layout/payment_bottom_bar.xml
@@ -0,0 +1,123 @@
+<?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/>
+  -->
+
+<com.google.android.material.card.MaterialCardView 
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";
+        style="@style/BottomCard"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        tools:showIn="@layout/fragment_prompt_payment">
+
+    <androidx.constraintlayout.widget.ConstraintLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content">
+
+        <TextView
+                android:id="@+id/totalLabelView"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginStart="8dp"
+                android:layout_marginTop="8dp"
+                android:layout_marginBottom="8dp"
+                android:text="@string/payment_label_amount_total"
+                android:visibility="invisible"
+                app:layout_constraintBottom_toTopOf="@+id/abortButton"
+                app:layout_constraintEnd_toStartOf="@+id/totalView"
+                app:layout_constraintHorizontal_bias="1.0"
+                app:layout_constraintHorizontal_chainStyle="spread_inside"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toTopOf="parent"
+                app:layout_constraintVertical_bias="0.0"
+                tools:visibility="visible" />
+
+        <TextView
+                android:id="@+id/totalView"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginStart="8dp"
+                android:layout_marginTop="8dp"
+                android:layout_marginEnd="8dp"
+                android:textColor="?android:attr/textColorPrimary"
+                android:textStyle="bold"
+                android:visibility="invisible"
+                app:layout_constraintBottom_toTopOf="@+id/feeView"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintHorizontal_bias="1.0"
+                app:layout_constraintHorizontal_chainStyle="packed"
+                app:layout_constraintStart_toEndOf="@+id/totalLabelView"
+                app:layout_constraintTop_toTopOf="parent"
+                app:layout_constraintVertical_bias="0.0"
+                tools:text="10 TESTKUDOS"
+                tools:visibility="visible" />
+
+        <TextView
+                android:id="@+id/feeView"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginStart="8dp"
+                android:layout_marginEnd="8dp"
+                android:visibility="gone"
+                app:layout_constraintBottom_toTopOf="@+id/confirmButton"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintHorizontal_bias="1.0"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toBottomOf="@+id/totalView"
+                tools:text="@string/payment_fee"
+                tools:visibility="visible" />
+
+        <Button
+                android:id="@+id/abortButton"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_margin="8dp"
+                android:backgroundTint="@color/red"
+                android:text="@string/payment_button_abort"
+                app:layout_constraintBottom_toBottomOf="parent"
+                app:layout_constraintEnd_toStartOf="@+id/confirmButton"
+                app:layout_constraintHorizontal_chainStyle="spread_inside"
+                app:layout_constraintStart_toStartOf="parent" />
+
+        <Button
+                android:id="@+id/confirmButton"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_margin="8dp"
+                android:backgroundTint="@color/green"
+                android:enabled="false"
+                android:text="@string/payment_button_confirm"
+                app:layout_constraintBottom_toBottomOf="parent"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintHorizontal_bias="0.5"
+                app:layout_constraintStart_toEndOf="@+id/abortButton"
+                app:layout_constraintTop_toBottomOf="@+id/feeView"
+                tools:enabled="true" />
+
+        <ProgressBar
+                android:id="@+id/confirmProgressBar"
+                style="?android:attr/progressBarStyle"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:visibility="invisible"
+                app:layout_constraintBottom_toBottomOf="@+id/confirmButton"
+                app:layout_constraintEnd_toEndOf="@+id/confirmButton"
+                app:layout_constraintStart_toStartOf="@+id/confirmButton"
+                app:layout_constraintTop_toTopOf="@+id/confirmButton"
+                tools:visibility="visible" />
+
+    </androidx.constraintlayout.widget.ConstraintLayout>
+
+</com.google.android.material.card.MaterialCardView>
diff --git a/wallet/src/main/res/layout/payment_details.xml 
b/wallet/src/main/res/layout/payment_details.xml
new file mode 100644
index 0000000..60d1d73
--- /dev/null
+++ b/wallet/src/main/res/layout/payment_details.xml
@@ -0,0 +1,119 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ This file is part of GNU Taler
+  ~ (C) 2020 Taler Systems S.A.
+  ~
+  ~ GNU Taler is free software; you can redistribute it and/or modify it under 
the
+  ~ terms of the GNU General Public License as published by the Free Software
+  ~ Foundation; either version 3, or (at your option) any later version.
+  ~
+  ~ GNU Taler is distributed in the hope that it will be useful, but WITHOUT 
ANY
+  ~ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
+  ~ A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+  ~
+  ~ You should have received a copy of the GNU General Public License along 
with
+  ~ GNU Taler; see the file COPYING.  If not, see 
<http://www.gnu.org/licenses/>
+  -->
+
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android";
+        xmlns:app="http://schemas.android.com/apk/res-auto";
+        xmlns:tools="http://schemas.android.com/tools";
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        android:fillViewport="true"
+        tools:showIn="@layout/fragment_prompt_payment">
+
+    <androidx.constraintlayout.widget.ConstraintLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content">
+
+        <TextView
+                android:id="@+id/errorView"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_margin="@dimen/activity_horizontal_margin"
+                android:textAlignment="center"
+                android:textColor="@android:color/holo_red_dark"
+                android:textSize="22sp"
+                android:visibility="gone"
+                app:layout_constraintBottom_toTopOf="@+id/orderLabelView"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toTopOf="parent"
+                app:layout_constraintVertical_chainStyle="packed"
+                tools:text="@string/payment_balance_insufficient"
+                tools:visibility="visible" />
+
+        <TextView
+                android:id="@+id/orderLabelView"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_marginStart="@dimen/activity_horizontal_margin"
+                android:layout_marginTop="@dimen/activity_horizontal_margin"
+                android:layout_marginEnd="@dimen/activity_horizontal_margin"
+                android:text="@string/payment_label_order_summary"
+                android:textAlignment="center"
+                android:visibility="invisible"
+                app:layout_constraintBottom_toTopOf="@+id/orderView"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toBottomOf="@+id/errorView"
+                tools:visibility="visible" />
+
+        <TextView
+                android:id="@+id/orderView"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_margin="@dimen/activity_horizontal_margin"
+                android:layout_marginTop="16dp"
+                android:textAlignment="center"
+                
android:textAppearance="@style/TextAppearance.AppCompat.Headline"
+                android:textSize="25sp"
+                android:visibility="invisible"
+                app:layout_constraintBottom_toTopOf="@+id/detailsButton"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toBottomOf="@+id/orderLabelView"
+                tools:text="2 x Cappuccino, 1 x Hot Meals, 1 x Dessert"
+                tools:visibility="visible" />
+
+        <Button
+                android:id="@+id/detailsButton"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/payment_show_details"
+                android:visibility="gone"
+                app:layout_constraintBottom_toTopOf="@+id/productsList"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toBottomOf="@+id/orderView"
+                tools:visibility="visible" />
+
+        <androidx.recyclerview.widget.RecyclerView
+                android:id="@+id/productsList"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_margin="@dimen/activity_horizontal_margin"
+                android:visibility="gone"
+                app:layout_constraintBottom_toBottomOf="parent"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toBottomOf="@+id/detailsButton"
+                tools:listitem="@layout/list_item_product"
+                tools:visibility="visible" />
+
+        <ProgressBar
+                android:id="@+id/progressBar"
+                style="?android:attr/progressBarStyleLarge"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:indeterminate="false"
+                android:visibility="invisible"
+                app:layout_constraintBottom_toBottomOf="parent"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toTopOf="parent"
+                tools:visibility="visible" />
+
+    </androidx.constraintlayout.widget.ConstraintLayout>
+
+</ScrollView>
diff --git a/wallet/src/main/res/layout/pending_row.xml 
b/wallet/src/main/res/layout/pending_row.xml
new file mode 100644
index 0000000..3505398
--- /dev/null
+++ b/wallet/src/main/res/layout/pending_row.xml
@@ -0,0 +1,48 @@
+<?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/>
+  -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android";
+        xmlns:tools="http://schemas.android.com/tools";
+        android:id="@+id/pending_container"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_margin="3dp"
+        android:padding="3dp"
+        android:background="@drawable/pending_border"
+        android:orientation="vertical">
+
+    <TextView
+            android:id="@+id/pending_text"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textSize="24sp"
+            tools:text="My Pending Operation" />
+
+    <Button
+            android:id="@+id/button_pending_action_1"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            tools:text="Cancel Operation" />
+
+    <TextView
+            android:id="@+id/pending_subtext"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textSize="14sp"
+            tools:text="My further details" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/wallet/src/main/res/menu/activity_main_drawer.xml 
b/wallet/src/main/res/menu/activity_main_drawer.xml
new file mode 100644
index 0000000..5eee6cc
--- /dev/null
+++ b/wallet/src/main/res/menu/activity_main_drawer.xml
@@ -0,0 +1,41 @@
+<?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/>
+  -->
+
+<menu xmlns:android="http://schemas.android.com/apk/res/android";
+        xmlns:tools="http://schemas.android.com/tools";
+        tools:showIn="@layout/activity_main">
+
+    <group android:checkableBehavior="single">
+        <item
+                android:id="@+id/nav_home"
+                android:icon="@drawable/ic_account_balance_wallet"
+                android:title="@string/balances_title"
+                tools:checked="true" />
+        <item
+                android:id="@+id/nav_history"
+                android:icon="@drawable/ic_history_black_24dp"
+                android:title="@string/menu_history" />
+        <item
+                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>
+
+</menu>
diff --git a/wallet/src/main/res/menu/balance.xml 
b/wallet/src/main/res/menu/balance.xml
new file mode 100644
index 0000000..7ac3a9f
--- /dev/null
+++ b/wallet/src/main/res/menu/balance.xml
@@ -0,0 +1,28 @@
+<?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/>
+  -->
+
+<menu xmlns:android="http://schemas.android.com/apk/res/android";
+        xmlns:app="http://schemas.android.com/apk/res-auto";>
+    <item
+            android:id="@+id/reload_balance"
+            android:title="@string/menu_balance_reload"
+            app:showAsAction="never" />
+    <item
+            android:id="@+id/developer_mode"
+            android:checkable="true"
+            android:title="@string/menu_developer_mode"
+            app:showAsAction="never" />
+</menu>
diff --git a/wallet/src/main/res/menu/history.xml 
b/wallet/src/main/res/menu/history.xml
new file mode 100644
index 0000000..755323b
--- /dev/null
+++ b/wallet/src/main/res/menu/history.xml
@@ -0,0 +1,31 @@
+<?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/>
+  -->
+
+<menu xmlns:android="http://schemas.android.com/apk/res/android";
+        xmlns:app="http://schemas.android.com/apk/res-auto";>
+    <item
+            android:id="@+id/show_all_history"
+            android:checkable="true"
+            android:checked="false"
+            android:title="@string/history_show_all"
+            app:showAsAction="never" />
+    <item
+            android:id="@+id/reload_history"
+            android:orderInCategory="100"
+            android:title="@string/history_reload"
+            app:showAsAction="never" />
+</menu>
diff --git a/wallet/src/main/res/menu/pending_operations.xml 
b/wallet/src/main/res/menu/pending_operations.xml
new file mode 100644
index 0000000..980ea66
--- /dev/null
+++ b/wallet/src/main/res/menu/pending_operations.xml
@@ -0,0 +1,24 @@
+<?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/>
+  -->
+
+<menu xmlns:android="http://schemas.android.com/apk/res/android";
+        xmlns:app="http://schemas.android.com/apk/res-auto";>
+    <item
+            android:id="@+id/retry_pending"
+            android:orderInCategory="100"
+            android:title="@string/menu_retry_pending_operations"
+            app:showAsAction="never" />
+</menu>
diff --git a/wallet/src/main/res/mipmap-anydpi-v26/ic_launcher.xml 
b/wallet/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 0000000..7acad4e
--- /dev/null
+++ b/wallet/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,21 @@
+<?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/>
+  -->
+
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android";>
+    <background android:drawable="@color/ic_launcher_background"/>
+    <foreground android:drawable="@drawable/ic_launcher_foreground"/>
+</adaptive-icon>
\ No newline at end of file
diff --git a/wallet/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml 
b/wallet/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 0000000..7acad4e
--- /dev/null
+++ b/wallet/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,21 @@
+<?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/>
+  -->
+
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android";>
+    <background android:drawable="@color/ic_launcher_background"/>
+    <foreground android:drawable="@drawable/ic_launcher_foreground"/>
+</adaptive-icon>
\ No newline at end of file
diff --git a/wallet/src/main/res/mipmap-hdpi/ic_launcher.png 
b/wallet/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..2bfb696
Binary files /dev/null and b/wallet/src/main/res/mipmap-hdpi/ic_launcher.png 
differ
diff --git a/wallet/src/main/res/mipmap-hdpi/ic_launcher_round.png 
b/wallet/src/main/res/mipmap-hdpi/ic_launcher_round.png
new file mode 100644
index 0000000..c7ae940
Binary files /dev/null and 
b/wallet/src/main/res/mipmap-hdpi/ic_launcher_round.png differ
diff --git a/wallet/src/main/res/mipmap-mdpi/ic_launcher.png 
b/wallet/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..c104056
Binary files /dev/null and b/wallet/src/main/res/mipmap-mdpi/ic_launcher.png 
differ
diff --git a/wallet/src/main/res/mipmap-mdpi/ic_launcher_round.png 
b/wallet/src/main/res/mipmap-mdpi/ic_launcher_round.png
new file mode 100644
index 0000000..1130914
Binary files /dev/null and 
b/wallet/src/main/res/mipmap-mdpi/ic_launcher_round.png differ
diff --git a/wallet/src/main/res/mipmap-xhdpi/ic_launcher.png 
b/wallet/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..7144a11
Binary files /dev/null and b/wallet/src/main/res/mipmap-xhdpi/ic_launcher.png 
differ
diff --git a/wallet/src/main/res/mipmap-xhdpi/ic_launcher_round.png 
b/wallet/src/main/res/mipmap-xhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..d63ccd3
Binary files /dev/null and 
b/wallet/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ
diff --git a/wallet/src/main/res/mipmap-xxhdpi/ic_launcher.png 
b/wallet/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..bc3155e
Binary files /dev/null and b/wallet/src/main/res/mipmap-xxhdpi/ic_launcher.png 
differ
diff --git a/wallet/src/main/res/mipmap-xxhdpi/ic_launcher_round.png 
b/wallet/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..028fe60
Binary files /dev/null and 
b/wallet/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ
diff --git a/wallet/src/main/res/mipmap-xxxhdpi/ic_launcher.png 
b/wallet/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..ccc81eb
Binary files /dev/null and b/wallet/src/main/res/mipmap-xxxhdpi/ic_launcher.png 
differ
diff --git a/wallet/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png 
b/wallet/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..da3ce45
Binary files /dev/null and 
b/wallet/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ
diff --git a/wallet/src/main/res/navigation/nav_graph.xml 
b/wallet/src/main/res/navigation/nav_graph.xml
new file mode 100644
index 0000000..549ca01
--- /dev/null
+++ b/wallet/src/main/res/navigation/nav_graph.xml
@@ -0,0 +1,125 @@
+<?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/>
+  -->
+
+<navigation 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/nav_graph"
+        app:startDestination="@id/showBalance"
+        tools:ignore="UnusedNavigation">
+
+    <fragment
+            android:id="@+id/showBalance"
+            android:name="net.taler.wallet.BalanceFragment"
+            android:label="@string/balances_title"
+            tools:layout="@layout/fragment_show_balance">
+        <action
+                android:id="@+id/action_showBalance_to_promptPayment"
+                app:destination="@id/promptPayment" />
+        <action
+                android:id="@+id/action_showBalance_to_promptWithdraw"
+                app:destination="@id/promptWithdraw" />
+    </fragment>
+    <fragment
+            android:id="@+id/promptPayment"
+            android:name="net.taler.wallet.payment.PromptPaymentFragment"
+            android:label="Review Payment"
+            tools:layout="@layout/fragment_prompt_payment">
+        <action
+                android:id="@+id/action_promptPayment_to_paymentSuccessful"
+                app:destination="@id/paymentSuccessful"
+                app:popUpTo="@id/showBalance" />
+        <action
+                android:id="@+id/action_promptPayment_to_alreadyPaid"
+                app:destination="@id/alreadyPaid"
+                app:popUpTo="@id/showBalance" />
+    </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/settings"
+            android:name="net.taler.wallet.Settings"
+            android:label="Settings"
+            tools:layout="@layout/fragment_settings" />
+    <fragment
+            android:id="@+id/walletHistory"
+            android:name="net.taler.wallet.history.WalletHistoryFragment"
+            android:label="@string/history_title"
+            tools:layout="@layout/fragment_show_history" />
+    <fragment
+            android:id="@+id/alreadyPaid"
+            android:name="net.taler.wallet.payment.AlreadyPaidFragment"
+            android:label="Already Paid"
+            tools:layout="@layout/fragment_already_paid" />
+
+    <fragment
+            android:id="@+id/promptWithdraw"
+            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_withdrawSuccessful"
+                app:destination="@id/withdrawSuccessful"
+                app:popUpTo="@id/showBalance" />
+        <action
+                android:id="@+id/action_promptWithdraw_to_reviewExchangeTOS"
+                app:destination="@id/reviewExchangeTOS"
+                app:popUpTo="@id/showBalance" />
+        <action
+                android:id="@+id/action_promptWithdraw_to_errorFragment"
+                app:destination="@id/errorFragment"
+                app:popUpTo="@id/showBalance" />
+    </fragment>
+
+    <fragment
+            android:id="@+id/withdrawSuccessful"
+            android:name="net.taler.wallet.withdraw.WithdrawSuccessfulFragment"
+            android:label="Withdrawal Confirmed"
+            tools:layout="@layout/fragment_withdraw_successful" />
+    <fragment
+            android:id="@+id/reviewExchangeTOS"
+            android:name="net.taler.wallet.withdraw.ReviewExchangeTosFragment"
+            android:label="@string/nav_exchange_tos"
+            tools:layout="@layout/fragment_review_exchange_tos">
+        <action
+                android:id="@+id/action_reviewExchangeTOS_to_promptWithdraw"
+                app:destination="@id/promptWithdraw"
+                app:popUpTo="@id/showBalance" />
+    </fragment>
+
+    <fragment
+            android:id="@+id/nav_pending_operations"
+            android:name="net.taler.wallet.pending.PendingOperationsFragment"
+            android:label="Pending Operations"
+            tools:layout="@layout/fragment_pending_operations" />
+    <fragment
+            android:id="@+id/errorFragment"
+            android:name="net.taler.wallet.withdraw.ErrorFragment"
+            android:label="@string/nav_error"
+            tools:layout="@layout/fragment_error" />
+
+    <action
+            android:id="@+id/action_global_promptPayment"
+            app:destination="@id/promptPayment" />
+
+    <action
+            android:id="@+id/action_global_pending_operations"
+            app:destination="@id/nav_pending_operations" />
+
+</navigation>
\ No newline at end of file
diff --git a/wallet/src/main/res/values/colors.xml 
b/wallet/src/main/res/values/colors.xml
new file mode 100644
index 0000000..2d1f0d7
--- /dev/null
+++ b/wallet/src/main/res/values/colors.xml
@@ -0,0 +1,25 @@
+<?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/>
+  -->
+
+<resources>
+    <color name="colorPrimary">#283593</color>
+    <color name="colorPrimaryDark">#1A237E</color>
+    <color name="colorAccent">#AE1010</color>
+
+    <color name="red">#C62828</color>
+    <color name="green">#558B2F</color>
+</resources>
diff --git a/wallet/src/main/res/values/dimens.xml 
b/wallet/src/main/res/values/dimens.xml
new file mode 100644
index 0000000..2bbc14d
--- /dev/null
+++ b/wallet/src/main/res/values/dimens.xml
@@ -0,0 +1,24 @@
+<!--
+  ~ This file is part of GNU Taler
+  ~ (C) 2020 Taler Systems S.A.
+  ~
+  ~ GNU Taler is free software; you can redistribute it and/or modify it under 
the
+  ~ terms of the GNU General Public License as published by the Free Software
+  ~ Foundation; either version 3, or (at your option) any later version.
+  ~
+  ~ GNU Taler is distributed in the hope that it will be useful, but WITHOUT 
ANY
+  ~ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
+  ~ A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+  ~
+  ~ You should have received a copy of the GNU General Public License along 
with
+  ~ GNU Taler; see the file COPYING.  If not, see 
<http://www.gnu.org/licenses/>
+  -->
+
+<resources>
+    <!-- Default screen margins, per the Android Design guidelines. -->
+    <dimen name="activity_horizontal_margin">16dp</dimen>
+    <dimen name="activity_vertical_margin">16dp</dimen>
+    <dimen name="nav_header_vertical_spacing">8dp</dimen>
+    <dimen name="nav_header_height">176dp</dimen>
+    <dimen name="fab_margin">16dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/wallet/src/main/res/values/ic_launcher_background.xml 
b/wallet/src/main/res/values/ic_launcher_background.xml
new file mode 100644
index 0000000..758b965
--- /dev/null
+++ b/wallet/src/main/res/values/ic_launcher_background.xml
@@ -0,0 +1,20 @@
+<?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/>
+  -->
+
+<resources>
+    <color name="ic_launcher_background">#000000</color>
+</resources>
\ No newline at end of file
diff --git a/wallet/src/main/res/values/strings.xml 
b/wallet/src/main/res/values/strings.xml
new file mode 100644
index 0000000..8981e04
--- /dev/null
+++ b/wallet/src/main/res/values/strings.xml
@@ -0,0 +1,105 @@
+<!--
+  ~ 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/>
+  -->
+
+<resources>
+    <string name="app_name">Taler Wallet</string>
+
+    <string name="nav_header_title">GNU Taler</string>
+    <string name="nav_header_subtitle">Wallet</string>
+    <string name="nav_header_desc">Navigation header</string>
+
+    <string name="nav_prompt_withdraw">Withdraw Digital Cash</string>
+    <string name="nav_exchange_tos">Exchange\'s Terms of Service</string>
+    <string name="nav_error">Error</string>
+
+    <string name="button_back">Go Back</string>
+    <string name="button_cancel">Cancel</string>
+    <string name="button_continue">Continue</string>
+    <string name="button_scan_qr_code">Scan Taler QR Code</string>
+
+    <string name="menu_history">History</string>
+    <string name="menu_settings">Settings</string>
+    <string name="menu_balance_reload">Reload balances</string>
+    <string name="menu_developer_mode">Developer Mode</string>
+    <string name="menu_retry_pending_operations">Retry Pending 
Operations</string>
+
+    <string name="servicedesc">my service</string>
+    <string name="aiddescription">my aid</string>
+
+    <string name="balances_title">Balances</string>
+    <string name="balances_inbound_amount">+%1s %2s</string>
+    <string name="balances_inbound_label">inbound</string>
+    <string name="balances_empty_state">There is no digital cash in your 
wallet.\n\nYou can get test money from the demo 
bank:\n\nhttps://bank.demo.taler.net</string>
+
+    <string name="history_title">History</string>
+    <string name="history_fee_label">Fee:</string>
+    <string name="history_show_all">Show All</string>
+    <string name="history_reload">Reload History</string>
+    <string name="history_empty">The wallet history is empty</string>
+
+    <!-- HistoryEvents -->
+    <string name="history_event_exchange_added">Exchange Added</string>
+    <string name="history_event_exchange_updated">Exchange Updated</string>
+    <string name="history_event_reserve_balance_updated">Reserve Balance 
Updated</string>
+    <string name="history_event_payment_sent">Payment</string>
+    <string name="history_event_payment_aborted">Payment Aborted</string>
+    <string name="history_event_withdrawn">Withdraw</string>
+    <string name="history_event_order_accepted">Purchase Confirmed</string>
+    <string name="history_event_order_refused">Purchase Cancelled</string>
+    <string name="history_event_tip_accepted">Tip Accepted</string>
+    <string name="history_event_tip_declined">Tip Declined</string>
+    <string name="history_event_order_redirected">Purchase Redirected</string>
+    <string name="history_event_refund">Refund</string>
+    <string name="history_event_refreshed">Obtained change</string>
+    <string name="history_event_unknown">Unknown Event</string>
+
+    <string name="payment_fee">+%s payment fee</string>
+    <string name="payment_button_confirm">Confirm Payment</string>
+    <string name="payment_button_abort">Abort</string>
+    <string name="payment_label_amount_total">Total Amount:</string>
+    <string name="payment_label_order_summary">Order</string>
+    <string name="payment_error">Error: %s</string>
+    <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_already_paid">You\'ve already paid for this 
order.</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_do_you_want">Do you want to withdraw</string>
+    <string name="withdraw_fees">(minus exchange fees not shown in this 
prototype)</string>
+    <string name="withdraw_exchange">Using the exchange provider</string>
+    <string name="withdraw_button_testkudos">Withdraw TESTKUDOS</string>
+    <string name="withdraw_button_confirm">Confirm Withdraw</string>
+    <string name="withdraw_error_title">Withdrawal Error</string>
+    <string name="withdraw_error_message">Withdrawing is currently not 
possible. Please try again later!</string>
+
+    <string name="pending_operations_title">Pending Operations</string>
+    <string name="pending_operations_refuse">Refuse Proposal</string>
+    <string name="pending_operations_no_action">(no action)</string>
+
+    <string name="settings_version">Version Information</string>
+    <string name="exchange_tos_accept">Accept Terms of Service</string>
+    <string name="exchange_tos_button_continue">Continue</string>
+    <string name="settings_backups">Backups</string>
+    <string name="settings_export_to_file">Export wallet to file</string>
+    <string name="settings_import_from_file">Import from file</string>
+    <string name="settings_developer">Developer Settings (use with 
caution!)</string>
+    <string name="settings_reset">Reset Wallet (dangerous!)</string>
+
+</resources>
diff --git a/wallet/src/main/res/values/styles.xml 
b/wallet/src/main/res/values/styles.xml
new file mode 100644
index 0000000..83f3e3a
--- /dev/null
+++ b/wallet/src/main/res/values/styles.xml
@@ -0,0 +1,46 @@
+<!--
+  ~ 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/>
+  -->
+
+<resources>
+
+    <style name="AppTheme" 
parent="Theme.MaterialComponents.DayNight.DarkActionBar">
+        <item name="colorPrimary">@color/colorPrimary</item>
+        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
+        <item name="colorAccent">@color/colorAccent</item>
+        <item name="colorOnPrimary">@android:color/white</item>
+    </style>
+
+    <style name="AppTheme.NoActionBar">
+        <item name="windowActionBar">false</item>
+        <item name="windowNoTitle">true</item>
+        <item name="android:statusBarColor">@android:color/transparent</item>
+    </style>
+
+    <style name="AppTheme.AppBarOverlay" 
parent="ThemeOverlay.MaterialComponents.ActionBar" />
+
+    <style name="AppTheme.Toolbar" 
parent="Widget.MaterialComponents.Toolbar.Primary" />
+
+    <style name="HistoryTitle">
+        <item name="android:textSize">17sp</item>
+        <item name="android:textColor">?android:textColorPrimary</item>
+    </style>
+
+    <style name="BottomCard">
+        <item name="cardCornerRadius">0dp</item>
+        <item name="cardElevation">8dp</item>
+    </style>
+
+</resources>
diff --git a/wallet/src/main/res/xml/apduservice.xml 
b/wallet/src/main/res/xml/apduservice.xml
new file mode 100644
index 0000000..fde348c
--- /dev/null
+++ b/wallet/src/main/res/xml/apduservice.xml
@@ -0,0 +1,25 @@
+<?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/>
+  -->
+
+<host-apdu-service xmlns:android="http://schemas.android.com/apk/res/android";
+        android:description="@string/servicedesc"
+        android:requireDeviceUnlock="true">
+    <aid-group android:description="@string/aiddescription"
+            android:category="other">
+        <aid-filter android:name="F00054414C4552"/>
+    </aid-group>
+</host-apdu-service>
\ No newline at end of file
diff --git a/wallet/src/main/res/xml/backup_descriptor.xml 
b/wallet/src/main/res/xml/backup_descriptor.xml
new file mode 100644
index 0000000..731d404
--- /dev/null
+++ b/wallet/src/main/res/xml/backup_descriptor.xml
@@ -0,0 +1,20 @@
+<?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/>
+  -->
+
+<full-backup-content>
+    <!-- Exclude specific shared preferences that contain GCM registration Id 
-->
+</full-backup-content>
diff --git a/wallet/src/test/java/net/taler/wallet/ExampleUnitTest.kt 
b/wallet/src/test/java/net/taler/wallet/ExampleUnitTest.kt
new file mode 100644
index 0000000..de74f68
--- /dev/null
+++ b/wallet/src/test/java/net/taler/wallet/ExampleUnitTest.kt
@@ -0,0 +1,33 @@
+/*
+ * 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
+
+import org.junit.Test
+
+import org.junit.Assert.*
+
+/**
+ * Example local unit test, which will execute on the development machine 
(host).
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+class ExampleUnitTest {
+    @Test
+    fun addition_isCorrect() {
+        assertEquals(4, 2 + 2)
+    }
+}
diff --git 
a/wallet/src/test/java/net/taler/wallet/crypto/Base32CrockfordTest.kt 
b/wallet/src/test/java/net/taler/wallet/crypto/Base32CrockfordTest.kt
new file mode 100644
index 0000000..7c8cb4c
--- /dev/null
+++ b/wallet/src/test/java/net/taler/wallet/crypto/Base32CrockfordTest.kt
@@ -0,0 +1,35 @@
+/*
+ * This file is part of GNU Taler
+ * (C) 2020 Taler Systems S.A.
+ *
+ * GNU Taler is free software; you can redistribute it and/or modify it under 
the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 3, or (at your option) any later version.
+ *
+ * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
+ * A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+ */
+
+package net.taler.wallet.crypto
+
+import org.junit.Test
+
+class Base32CrockfordTest {
+    @Test
+    fun testBasic() {
+        val inputStr = "Hello, World"
+        val data = inputStr.toByteArray(Charsets.UTF_8)
+        val enc = Base32Crockford.encode(data)
+        println(enc)
+        val dec = Base32Crockford.decode(enc)
+        val recoveredInputStr = dec.toString(Charsets.UTF_8)
+        println(recoveredInputStr)
+
+        val foo = 
Base32Crockford.decode("51R7ARKCD5HJTTV5F4G0M818E9SP280A40G2GVH04CR30H2365338E9G6RT4AH1N6H13EGHR70RK6H1S6X2M4CSP8CSK8E1G88VKJH25610KGCHR8RWM4DJ47123CH9K89334D1S8N24ACJ48CR3EH256MR3AH1R711KCE9N6S134GSN6RW46D1H6CV3CDHJ6D0KEDHR6D24CD248MWKADHJ6WT34D25712KCD2474V46EA18H2M4GHM6WTK2E216S14CD238GSK0G9G692KCDHM6RW34CT16MV3CG9P60S34C1G70SMCHHQ8CVKJG9K6CVK6GHK70R46HJ26CR4AE9M8523ADHS8RR3EE1R74S32CHP6N1K0GT38D1M6C1R84TM2E9N8MSK2C1J71248E9H6H1MCD9J70VK4GSG6124CCHR6RS4ADSH8N0M4H1G84
 [...]
+        println(foo.toString(Charsets.UTF_8))
+    }
+}
diff --git a/wallet/src/test/java/net/taler/wallet/history/HistoryEventTest.kt 
b/wallet/src/test/java/net/taler/wallet/history/HistoryEventTest.kt
new file mode 100644
index 0000000..ba18dfb
--- /dev/null
+++ b/wallet/src/test/java/net/taler/wallet/history/HistoryEventTest.kt
@@ -0,0 +1,459 @@
+/*
+ * 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 com.fasterxml.jackson.databind.ObjectMapper
+import com.fasterxml.jackson.module.kotlin.KotlinModule
+import com.fasterxml.jackson.module.kotlin.readValue
+import net.taler.wallet.history.RefreshReason.PAY
+import net.taler.wallet.history.ReserveType.MANUAL
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertTrue
+import org.junit.Test
+import kotlin.random.Random
+
+class HistoryEventTest {
+
+    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 = "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 ReserveBalanceUpdatedEvent`() {
+        val json = """{
+            "type": "reserve-balance-updated",
+            "eventId": 
"reserve-balance-updated;K0H10Q6HB9WH0CKHQQMNH5C6GA7A9AR1E2XSS9G1KG3ZXMBVT26G",
+            "amountExpected": "TESTKUDOS:23",
+            "amountReserveBalance": "TESTKUDOS:10",
+            "timestamp": {
+                "t_ms": $timestamp
+            },
+            "newHistoryTransactions": [
+                {
+                    "amount": "TESTKUDOS:10",
+                    "sender_account_url": 
"payto:\/\/x-taler-bank\/bank.test.taler.net\/894",
+                    "timestamp": {
+                        "t_ms": $timestamp
+                    },
+                    "wire_reference": "00000000004TR",
+                    "type": "DEPOSIT"
+                }
+            ],
+            "reserveShortInfo": {
+                "exchangeBaseUrl": "https:\/\/exchange.test.taler.net\/",
+                "reserveCreationDetail": {
+                    "type": "manual"
+                },
+                "reservePub": 
"BRT2P0YMQSD5F48V9XHVNH73ZTS6EZC0KCQCPGPZQWTSQB77615G"
+            }
+        }""".trimIndent()
+        val event: ReserveBalanceUpdatedEvent = mapper.readValue(json)
+
+        assertEquals(timestamp, event.timestamp.ms)
+        assertEquals("TESTKUDOS:23", event.amountExpected)
+        assertEquals("TESTKUDOS:10", event.amountReserveBalance)
+        assertEquals(1, event.newHistoryTransactions.size)
+        assertTrue(event.newHistoryTransactions[0] is 
ReserveDepositTransaction)
+        assertEquals(exchangeBaseUrl, event.reserveShortInfo.exchangeBaseUrl)
+    }
+
+    @Test
+    fun `test HistoryWithdrawnEvent`() {
+        val json = """{
+            "type": "withdrawn",
+            "withdrawSessionId": 
"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: HistoryWithdrawnEvent = mapper.readValue(json)
+
+        assertEquals(
+            "974FT7JDNR20EQKNR21G1HV9PB6T5AZHYHX9NHR51Q30ZK3T10S0",
+            event.withdrawSessionId
+        )
+        assertEquals("TESTKUDOS:9.8", event.amountWithdrawnEffective)
+        assertEquals("TESTKUDOS:10", event.amountWithdrawnRaw)
+        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)
+        assertEquals("2019.364-01RAQ68DQ7AWR", info.orderId)
+        assertEquals("Essay: Foreword", info.summary)
+    }
+
+    @Test
+    fun `test HistoryOrderAcceptedEvent`() {
+        val json = """{
+            "type": "order-accepted",
+            "eventId": 
"order-accepted;EP5MH4R5C9RMNA06YS1QGEJ3EY682PY8R1SGRFRP74EV735N3ATG",
+            "orderShortInfo": {
+                "amount": "${orderShortInfo.amount}",
+                "orderId": "${orderShortInfo.orderId}",
+                "merchantBaseUrl": "${orderShortInfo.merchantBaseUrl}",
+                "proposalId": "${orderShortInfo.proposalId}",
+                "summary": "${orderShortInfo.summary}"
+            },
+            "timestamp": {
+                "t_ms": $timestamp
+            }
+        }""".trimIndent()
+        val event: HistoryOrderAcceptedEvent = mapper.readValue(json)
+
+        assertEquals(orderShortInfo, event.orderShortInfo)
+        assertEquals(timestamp, event.timestamp.ms)
+    }
+
+    @Test
+    fun `test HistoryOrderRefusedEvent`() {
+        val json = """{
+            "type": "order-refused",
+            "eventId": 
"order-refused;9RJGAYXKWX0Y3V37H66606SXSA7V2CV255EBFS4G1JSH6W1EG7F0",
+            "orderShortInfo": {
+                "amount": "${orderShortInfo.amount}",
+                "orderId": "${orderShortInfo.orderId}",
+                "merchantBaseUrl": "${orderShortInfo.merchantBaseUrl}",
+                "proposalId": "${orderShortInfo.proposalId}",
+                "summary": "${orderShortInfo.summary}"
+            },
+            "timestamp": {
+                "t_ms": $timestamp
+            }
+        }""".trimIndent()
+        val event: HistoryOrderRefusedEvent = mapper.readValue(json)
+
+        assertEquals(orderShortInfo, event.orderShortInfo)
+        assertEquals(timestamp, event.timestamp.ms)
+    }
+
+    @Test
+    fun `test HistoryPaymentSentEvent`() {
+        val json = """{
+            "type": "payment-sent",
+            "eventId": 
"payment-sent;EP5MH4R5C9RMNA06YS1QGEJ3EY682PY8R1SGRFRP74EV735N3ATG",
+            "orderShortInfo": {
+                "amount": "${orderShortInfo.amount}",
+                "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: HistoryPaymentSentEvent = mapper.readValue(json)
+
+        assertEquals(orderShortInfo, event.orderShortInfo)
+        assertEquals(false, event.replay)
+        assertEquals(6, event.numCoins)
+        assertEquals("KUDOS:0.6", event.amountPaidWithFees)
+        assertEquals("e4f436c4-3c5c-4aee-81d2-26e425c09520", event.sessionId)
+        assertEquals(timestamp, event.timestamp.ms)
+    }
+
+    @Test
+    fun `test HistoryPaymentSentEvent without sessionId`() {
+        val json = """{
+            "type": "payment-sent",
+            "eventId": 
"payment-sent;EP5MH4R5C9RMNA06YS1QGEJ3EY682PY8R1SGRFRP74EV735N3ATG",
+            "orderShortInfo": {
+                "amount": "${orderShortInfo.amount}",
+                "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: HistoryPaymentSentEvent = mapper.readValue(json)
+
+        assertEquals(orderShortInfo, event.orderShortInfo)
+        assertEquals(true, event.replay)
+        assertEquals(6, event.numCoins)
+        assertEquals("KUDOS:0.6", event.amountPaidWithFees)
+        assertEquals(null, event.sessionId)
+        assertEquals(timestamp, event.timestamp.ms)
+    }
+
+    @Test
+    fun `test HistoryPaymentAbortedEvent`() {
+        val json = """{
+            "type": "payment-aborted",
+            "eventId": 
"payment-sent;898724XGQ1GGMZB4WY3KND582NSP74FZ60BX0Y87FF81H0FJ8XD0",
+            "orderShortInfo": {
+                "amount": "${orderShortInfo.amount}",
+                "orderId": "${orderShortInfo.orderId}",
+                "merchantBaseUrl": "${orderShortInfo.merchantBaseUrl}",
+                "proposalId": "${orderShortInfo.proposalId}",
+                "summary": "${orderShortInfo.summary}"
+            },
+            "timestamp": {
+              "t_ms": $timestamp
+            },
+            "amountLost": "KUDOS:0.1"
+          }""".trimIndent()
+        val event: HistoryPaymentAbortedEvent = mapper.readValue(json)
+
+        assertEquals(orderShortInfo, event.orderShortInfo)
+        assertEquals("KUDOS:0.1", event.amountLost)
+        assertEquals(timestamp, event.timestamp.ms)
+    }
+
+    @Test
+    fun `test HistoryTipAcceptedEvent`() {
+        val json = """{
+            "type": "tip-accepted",
+            "timestamp": {
+              "t_ms": $timestamp
+            },
+            "eventId": 
"tip-accepted;898724XGQ1GGMZB4WY3KND582NSP74FZ60BX0Y87FF81H0FJ8XD0",
+            "tipId": 
"tip-accepted;898724XGQ1GGMZB4WY3KND582NSP74FZ60BX0Y87FF81H0FJ8XD0",
+            "tipRaw": "KUDOS:4"
+          }""".trimIndent()
+        val event: HistoryTipAcceptedEvent = mapper.readValue(json)
+
+        
assertEquals("tip-accepted;898724XGQ1GGMZB4WY3KND582NSP74FZ60BX0Y87FF81H0FJ8XD0",
 event.tipId)
+        assertEquals("KUDOS:4", event.tipRaw)
+        assertEquals(timestamp, event.timestamp.ms)
+    }
+
+    @Test
+    fun `test HistoryTipDeclinedEvent`() {
+        val json = """{
+            "type": "tip-declined",
+            "timestamp": {
+              "t_ms": $timestamp
+            },
+            "eventId": 
"tip-accepted;898724XGQ1GGMZB4WY3KND582NSP74FZ60BX0Y87FF81H0FJ8XD0",
+            "tipId": 
"tip-accepted;998724XGQ1GGMZB4WY3KND582NSP74FZ60BX0Y87FF81H0FJ8XD0",
+            "tipAmount": "KUDOS:4"
+          }""".trimIndent()
+        val event: HistoryTipDeclinedEvent = mapper.readValue(json)
+
+        
assertEquals("tip-accepted;998724XGQ1GGMZB4WY3KND582NSP74FZ60BX0Y87FF81H0FJ8XD0",
 event.tipId)
+        assertEquals("KUDOS:4", event.tipAmount)
+        assertEquals(timestamp, event.timestamp.ms)
+    }
+
+    @Test
+    fun `test HistoryRefundedEvent`() {
+        val json = """{
+            "type": "refund",
+            "eventId": 
"refund;898724XGQ1GGMZB4WY3KND582NSP74FZ60BX0Y87FF81H0FJ8XD0",
+            "refundGroupId": "refund;998724",
+            "orderShortInfo": {
+                "amount": "${orderShortInfo.amount}",
+                "orderId": "${orderShortInfo.orderId}",
+                "merchantBaseUrl": "${orderShortInfo.merchantBaseUrl}",
+                "proposalId": "${orderShortInfo.proposalId}",
+                "summary": "${orderShortInfo.summary}"
+            },
+            "timestamp": {
+              "t_ms": $timestamp
+            },
+            "amountRefundedRaw": "KUDOS:1.0",
+            "amountRefundedInvalid": "KUDOS:0.5",
+            "amountRefundedEffective": "KUDOS:0.4"
+          }""".trimIndent()
+        val event: HistoryRefundedEvent = mapper.readValue(json)
+
+        assertEquals("refund;998724", event.refundGroupId)
+        assertEquals("KUDOS:1.0", event.amountRefundedRaw)
+        assertEquals("KUDOS:0.5", event.amountRefundedInvalid)
+        assertEquals("KUDOS:0.4", event.amountRefundedEffective)
+        assertEquals(orderShortInfo, event.orderShortInfo)
+        assertEquals(timestamp, event.timestamp.ms)
+    }
+
+    @Test
+    fun `test HistoryRefreshedEvent`() {
+        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: HistoryRefreshedEvent = mapper.readValue(json)
+
+        assertEquals("KUDOS:0", event.amountRefreshedEffective)
+        assertEquals("KUDOS:1", event.amountRefreshedRaw)
+        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 HistoryOrderRedirectedEvent`() {
+        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 event: HistoryOrderRedirectedEvent = mapper.readValue(json)
+
+        assertEquals("898724XGQ1GGMZB4WY3KND582NSP74FZ60BX0Y87FF81H0FJ8XD0", 
event.alreadyPaidOrderShortInfo.proposalId)
+        assertEquals("https://backend.demo.taler.net/public/instances/FSF/";, 
event.alreadyPaidOrderShortInfo.merchantBaseUrl)
+        assertEquals("2019.354-01P25CD66P8NG", 
event.alreadyPaidOrderShortInfo.orderId)
+        assertEquals("KUDOS:0.5", event.alreadyPaidOrderShortInfo.amount)
+        assertEquals("Essay: 1. The Free Software Definition", 
event.alreadyPaidOrderShortInfo.summary)
+
+        assertEquals("621J6D5SXG7M17TYA26945DYKNQZPW4600MZ1W8MADA1RRR49F8G", 
event.newOrderShortInfo.proposalId)
+        assertEquals("https://backend.demo.taler.net/public/instances/FSF/";, 
event.newOrderShortInfo.merchantBaseUrl)
+        assertEquals("2019.364-01M4QH6KPMJY4", event.newOrderShortInfo.orderId)
+        assertEquals("KUDOS:0.5", event.newOrderShortInfo.amount)
+        assertEquals("Essay: 1. The Free Software Definition", 
event.newOrderShortInfo.summary)
+
+        assertEquals(timestamp, event.timestamp.ms)
+    }
+
+    @Test
+    fun `test HistoryUnknownEvent`() {
+        val json = """{
+            "type": "does not exist",
+            "timestamp": {
+              "t_ms": $timestamp
+            },
+            "eventId": 
"does-not-exist;898724XGQ1GGMZB4WY3KND582NSP74FZ60BX0Y87FF81H0FJ8XD0"
+          }""".trimIndent()
+        val event: HistoryEvent = mapper.readValue(json)
+
+        assertEquals(HistoryUnknownEvent::class.java, event.javaClass)
+        assertEquals(timestamp, event.timestamp.ms)
+    }
+
+}
diff --git 
a/wallet/src/test/java/net/taler/wallet/history/ReserveTransactionTest.kt 
b/wallet/src/test/java/net/taler/wallet/history/ReserveTransactionTest.kt
new file mode 100644
index 0000000..d3d66f5
--- /dev/null
+++ b/wallet/src/test/java/net/taler/wallet/history/ReserveTransactionTest.kt
@@ -0,0 +1,52 @@
+/*
+ * 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 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)
+    }
+
+}

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



reply via email to

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