gnunet-svn
[Top][All Lists]
Advanced

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

[taler-taler-android] 03/04: Split out common code into multiplatform Ko


From: gnunet
Subject: [taler-taler-android] 03/04: Split out common code into multiplatform Kotlin library
Date: Thu, 30 Jul 2020 22:12:23 +0200

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 8815105bf2462787885214a12af927d484226f21
Author: Torsten Grote <t@grobox.de>
AuthorDate: Thu Jul 30 16:40:23 2020 -0300

    Split out common code into multiplatform Kotlin library
---
 .gitignore                                         |   1 +
 .gitlab-ci.yml                                     |   1 +
 .idea/gradle.xml                                   |   1 +
 anastasis-ui/build.gradle                          |   2 +-
 build.gradle                                       |   4 -
 cashier/.gitlab-ci.yml                             |   1 +
 cashier/build.gradle                               |   2 +-
 .../main/java/net/taler/cashier/MainViewModel.kt   |  16 ++-
 merchant-lib/.gitlab-ci.yml                        |   1 +
 merchant-lib/build.gradle                          |   2 +-
 .../main/java/net/taler/merchantlib/Response.kt    |   3 +-
 .../java/net/taler/merchantlib/MerchantApiTest.kt  |   1 +
 merchant-terminal/.gitlab-ci.yml                   |   1 +
 settings.gradle                                    |   3 +
 taler-kotlin-android/.gitignore                    |   1 +
 taler-kotlin-android/.gitlab-ci.yml                |  12 ++
 .../build.gradle                                   |   6 +
 .../consumer-rules.pro                             |   0
 .../proguard-rules.pro                             |   0
 .../src/main/AndroidManifest.xml                   |   0
 .../src/main/java/net/taler/common/AmountMixin.kt  |  51 +++++++
 .../src/main/java/net/taler/common/AndroidUtils.kt |   0
 .../main/java/net/taler/common/ByteArrayUtils.kt   |   0
 .../main/java/net/taler/common/CombinedLiveData.kt |   0
 .../main/java/net/taler/common/ContractTerms.kt    |   4 +-
 .../src/main/java/net/taler/common/Event.kt        |   0
 .../src/main/java/net/taler/common/NfcManager.kt   |   0
 .../main/java/net/taler/common/QrCodeManager.kt    |   0
 .../src/main/java/net/taler/common/SignedAmount.kt |   0
 .../src/main/java/net/taler/common/TalerUtils.kt   |   3 +-
 .../main/res/drawable/selectable_background.xml    |   0
 .../src/main/res/values-night/colors.xml           |   0
 .../src/main/res/values/colors.xml                 |   0
 .../src/main/res/values/strings.xml                |   0
 .../java/net/taler/common/ContractTermsTest.kt     |  74 +++++++++++
 taler-kotlin-common/.gitlab-ci.yml                 |   6 +-
 taler-kotlin-common/build.gradle                   | 130 +++++++++---------
 .../kotlin}/net/taler/common/Amount.kt             |  70 ++--------
 .../src/commonMain/kotlin/net/taler/common/Time.kt |  81 ++++++++++++
 .../kotlin}/net/taler/common/Version.kt            |   0
 .../kotlin}/net/taler/common/AmountTest.kt         | 147 +++++----------------
 .../kotlin/net/taler/common/TestUtils.kt}          |  28 +---
 .../kotlin}/net/taler/common/VersionTest.kt        |   6 +-
 .../kotlin/net/taler/common/Time.kt}               |  25 +---
 .../kotlin/net/taler/common/Time.kt}               |  25 +---
 .../kotlin/net/taler/common/Time.kt}               |  25 +---
 wallet/.gitlab-ci.yml                              |   1 +
 wallet/build.gradle                                |  10 +-
 .../main/java/net/taler/wallet/MainViewModel.kt    |   5 +
 .../net/taler/wallet/exchanges/ExchangeFees.kt     |  53 +-------
 .../net/taler/wallet/payment/PaymentResponses.kt   |  13 +-
 51 files changed, 404 insertions(+), 411 deletions(-)

diff --git a/.gitignore b/.gitignore
index 7e4952a..caf9ce6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,7 @@
 *.iml
 .gradle
 /local.properties
+/.idea/artifacts
 /.idea/caches
 /.idea/libraries
 /.idea/misc.xml
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 48f1aec..6dc4426 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -14,6 +14,7 @@ include:
   - local: 'merchant-lib/.gitlab-ci.yml'
   - local: 'merchant-terminal/.gitlab-ci.yml'
   - local: 'taler-kotlin-common/.gitlab-ci.yml'
+  - local: 'taler-kotlin-android/.gitlab-ci.yml'
   - local: 'wallet/.gitlab-ci.yml'
 
 after_script:
diff --git a/.idea/gradle.xml b/.idea/gradle.xml
index 581abbf..01ed15f 100644
--- a/.idea/gradle.xml
+++ b/.idea/gradle.xml
@@ -14,6 +14,7 @@
             <option value="$PROJECT_DIR$/cashier" />
             <option value="$PROJECT_DIR$/merchant-lib" />
             <option value="$PROJECT_DIR$/merchant-terminal" />
+            <option value="$PROJECT_DIR$/taler-kotlin-android" />
             <option value="$PROJECT_DIR$/taler-kotlin-common" />
             <option value="$PROJECT_DIR$/wallet" />
           </set>
diff --git a/anastasis-ui/build.gradle b/anastasis-ui/build.gradle
index 0391c7c..ff0eec5 100644
--- a/anastasis-ui/build.gradle
+++ b/anastasis-ui/build.gradle
@@ -51,7 +51,7 @@ android {
 }
 
 dependencies {
-    implementation project(":taler-kotlin-common")
+    implementation project(":taler-kotlin-android")
 
     implementation 'com.google.android.material:material:1.2.0-beta01'
     implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
diff --git a/build.gradle b/build.gradle
index 76f687e..442d232 100644
--- a/build.gradle
+++ b/build.gradle
@@ -24,7 +24,3 @@ allprojects {
         maven { url 'https://jitpack.io' }
     }
 }
-
-task clean(type: Delete) {
-    delete rootProject.buildDir
-}
diff --git a/cashier/.gitlab-ci.yml b/cashier/.gitlab-ci.yml
index 6a7baed..6b73dee 100644
--- a/cashier/.gitlab-ci.yml
+++ b/cashier/.gitlab-ci.yml
@@ -6,6 +6,7 @@ cashier_test:
     changes:
       - cashier/**/*
       - taler-kotlin-common/**/*
+      - taler-kotlin-android/**/*
       - build.gradle
   script: ./gradlew :cashier:check :cashier:assembleRelease
   artifacts:
diff --git a/cashier/build.gradle b/cashier/build.gradle
index 0d06c60..641a039 100644
--- a/cashier/build.gradle
+++ b/cashier/build.gradle
@@ -54,7 +54,7 @@ android {
 }
 
 dependencies {
-    implementation project(":taler-kotlin-common")
+    implementation project(":taler-kotlin-android")
     implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
     implementation 'androidx.security:security-crypto:1.0.0-rc02'
     implementation 'com.google.android.material:material:1.1.0'
diff --git a/cashier/src/main/java/net/taler/cashier/MainViewModel.kt 
b/cashier/src/main/java/net/taler/cashier/MainViewModel.kt
index c8d9a3b..a4fd35e 100644
--- a/cashier/src/main/java/net/taler/cashier/MainViewModel.kt
+++ b/cashier/src/main/java/net/taler/cashier/MainViewModel.kt
@@ -40,6 +40,7 @@ import net.taler.common.isOnline
 
 private val TAG = MainViewModel::class.java.simpleName
 
+private const val VERSION_BANK = "0:0:0"
 private const val PREF_NAME = "net.taler.cashier.prefs"
 private const val PREF_KEY_BANK_URL = "bankUrl"
 private const val PREF_KEY_USERNAME = "username"
@@ -86,20 +87,21 @@ class MainViewModel(private val app: Application) : 
AndroidViewModel(app) {
     fun checkAndSaveConfig(config: Config) {
         mConfigResult.value = null
         viewModelScope.launch(Dispatchers.IO) {
-            val url = "${config.bankUrl}/accounts/${config.username}/balance"
+            val url = "${config.bankUrl}/config"
             Log.d(TAG, "Checking config: $url")
             val result = when (val response = makeJsonGetRequest(url, config)) 
{
                 is HttpJsonResult.Success -> {
-                    val balance = response.json.getString("balance")
+                    val version = response.json.getString("version")
+                    // TODO check if version is compatible
+                    val currency = response.json.getString("currency")
                     try {
-                        val amount = SignedAmount.fromJSONString(balance)
-                        mCurrency.postValue(amount.amount.currency)
-                        prefs.edit().putString(PREF_KEY_CURRENCY, 
amount.amount.currency).apply()
+                        mCurrency.postValue(currency)
+                        prefs.edit().putString(PREF_KEY_CURRENCY, 
currency).apply()
                         // save config
                         saveConfig(config)
                         ConfigResult.Success
-                    } catch (e: AmountParserException) {
-                        ConfigResult.Error(false, "Invalid Amount: $balance")
+                    } catch (e: Exception) {
+                        ConfigResult.Error(false, "Invalid Config: 
${response.json}")
                     }
                 }
                 is HttpJsonResult.Error -> {
diff --git a/merchant-lib/.gitlab-ci.yml b/merchant-lib/.gitlab-ci.yml
index 62a7516..8f7c7c2 100644
--- a/merchant-lib/.gitlab-ci.yml
+++ b/merchant-lib/.gitlab-ci.yml
@@ -2,6 +2,7 @@ merchant_lib_test:
   stage: test
   only:
     changes:
+      - taler-kotlin-common/**/*
       - merchant-lib/**/*
       - build.gradle
   script: ./gradlew :merchant-lib:check
diff --git a/merchant-lib/build.gradle b/merchant-lib/build.gradle
index 128f4c1..33e8379 100644
--- a/merchant-lib/build.gradle
+++ b/merchant-lib/build.gradle
@@ -45,7 +45,7 @@ android {
 }
 
 dependencies {
-    api project(":taler-kotlin-common")
+    api project(":taler-kotlin-android")
 
     implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
 
diff --git a/merchant-lib/src/main/java/net/taler/merchantlib/Response.kt 
b/merchant-lib/src/main/java/net/taler/merchantlib/Response.kt
index 9aefa5f..65a12a9 100644
--- a/merchant-lib/src/main/java/net/taler/merchantlib/Response.kt
+++ b/merchant-lib/src/main/java/net/taler/merchantlib/Response.kt
@@ -16,7 +16,6 @@
 
 package net.taler.merchantlib
 
-import android.util.Log
 import io.ktor.client.call.receive
 import io.ktor.client.features.ClientRequestException
 import io.ktor.client.features.ResponseException
@@ -32,7 +31,7 @@ class Response<out T> private constructor(
             return try {
                 success(request())
             } catch (e: Throwable) {
-                Log.e("merchant-lib", "Error", e)
+                println(e)
                 failure(e)
             }
         }
diff --git 
a/merchant-lib/src/test/java/net/taler/merchantlib/MerchantApiTest.kt 
b/merchant-lib/src/test/java/net/taler/merchantlib/MerchantApiTest.kt
index ea5a12a..deed81e 100644
--- a/merchant-lib/src/test/java/net/taler/merchantlib/MerchantApiTest.kt
+++ b/merchant-lib/src/test/java/net/taler/merchantlib/MerchantApiTest.kt
@@ -113,6 +113,7 @@ class MerchantApiTest {
         val unpaidResponse = CheckPaymentResponse.Unpaid(false, 
"http://taler.net/foo";)
         
httpClient.giveJsonResponse("http://example.net/instances/testInstance/private/orders/$orderId";)
 {
             """{
+                "order_status": "unpaid",
                 "paid": ${unpaidResponse.paid},
                 "taler_pay_uri": "${unpaidResponse.talerPayUri}"
             }""".trimIndent()
diff --git a/merchant-terminal/.gitlab-ci.yml b/merchant-terminal/.gitlab-ci.yml
index 74ac21f..d159902 100644
--- a/merchant-terminal/.gitlab-ci.yml
+++ b/merchant-terminal/.gitlab-ci.yml
@@ -5,6 +5,7 @@ merchant_test:
       - merchant-terminal/**/*
       - merchant-lib/**/*
       - taler-kotlin-common/**/*
+      - taler-kotlin-android/**/*
       - build.gradle
   script: ./gradlew :merchant-terminal:check :merchant-terminal:assembleRelease
   artifacts:
diff --git a/settings.gradle b/settings.gradle
index 14d898d..6175852 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -1,4 +1,7 @@
 include ':cashier', ':merchant-terminal', ':wallet'
 include ':taler-kotlin-common'
+include ':taler-kotlin-android'
 include ':merchant-lib'
 include ':anastasis-ui'
+
+enableFeaturePreview('GRADLE_METADATA')
diff --git a/taler-kotlin-android/.gitignore b/taler-kotlin-android/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/taler-kotlin-android/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/taler-kotlin-android/.gitlab-ci.yml 
b/taler-kotlin-android/.gitlab-ci.yml
new file mode 100644
index 0000000..bb5af21
--- /dev/null
+++ b/taler-kotlin-android/.gitlab-ci.yml
@@ -0,0 +1,12 @@
+taler_kotlin_android_test:
+  stage: test
+  only:
+    changes:
+      - taler-kotlin-android/**/*
+      - taler-kotlin-common/**/*
+      - build.gradle
+  script: ./gradlew :taler-kotlin-android:check
+  artifacts:
+    paths:
+      - taler-kotlin-android/build/reports/lint-results.html
+    expire_in: 1 week
diff --git a/taler-kotlin-common/build.gradle 
b/taler-kotlin-android/build.gradle
similarity index 95%
copy from taler-kotlin-common/build.gradle
copy to taler-kotlin-android/build.gradle
index dd083b7..d6d6003 100644
--- a/taler-kotlin-common/build.gradle
+++ b/taler-kotlin-android/build.gradle
@@ -43,9 +43,15 @@ android {
         }
     }
 
+    packagingOptions {
+        exclude("META-INF/*.kotlin_module")
+    }
+
 }
 
 dependencies {
+    api project(":taler-kotlin-common")
+
     implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
     implementation 'androidx.appcompat:appcompat:1.1.0'
     implementation 'androidx.core:core-ktx:1.3.0'
diff --git a/taler-kotlin-common/consumer-rules.pro 
b/taler-kotlin-android/consumer-rules.pro
similarity index 100%
rename from taler-kotlin-common/consumer-rules.pro
rename to taler-kotlin-android/consumer-rules.pro
diff --git a/taler-kotlin-common/proguard-rules.pro 
b/taler-kotlin-android/proguard-rules.pro
similarity index 100%
rename from taler-kotlin-common/proguard-rules.pro
rename to taler-kotlin-android/proguard-rules.pro
diff --git a/taler-kotlin-common/src/main/AndroidManifest.xml 
b/taler-kotlin-android/src/main/AndroidManifest.xml
similarity index 100%
rename from taler-kotlin-common/src/main/AndroidManifest.xml
rename to taler-kotlin-android/src/main/AndroidManifest.xml
diff --git a/taler-kotlin-android/src/main/java/net/taler/common/AmountMixin.kt 
b/taler-kotlin-android/src/main/java/net/taler/common/AmountMixin.kt
new file mode 100644
index 0000000..f9b1330
--- /dev/null
+++ b/taler-kotlin-android/src/main/java/net/taler/common/AmountMixin.kt
@@ -0,0 +1,51 @@
+/*
+ * This file is part of GNU Taler
+ * (C) 2020 Taler Systems S.A.
+ *
+ * GNU Taler is free software; you can redistribute it and/or modify it under 
the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 3, or (at your option) any later version.
+ *
+ * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
+ * A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+ */
+
+package net.taler.common
+
+import com.fasterxml.jackson.core.JsonGenerator
+import com.fasterxml.jackson.core.JsonParser
+import com.fasterxml.jackson.databind.DeserializationContext
+import com.fasterxml.jackson.databind.JsonMappingException
+import com.fasterxml.jackson.databind.SerializerProvider
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize
+import com.fasterxml.jackson.databind.annotation.JsonSerialize
+import com.fasterxml.jackson.databind.deser.std.StdDeserializer
+import com.fasterxml.jackson.databind.ser.std.StdSerializer
+
+/**
+ * Used to support Jackson serialization along with KotlinX.
+ */
+@JsonSerialize(using = AmountSerializer::class)
+@JsonDeserialize(using = AmountDeserializer::class)
+abstract class AmountMixin
+
+class AmountSerializer : StdSerializer<Amount>(Amount::class.java) {
+    override fun serialize(value: Amount, gen: JsonGenerator, provider: 
SerializerProvider) {
+        gen.writeString(value.toJSONString())
+    }
+}
+
+class AmountDeserializer : StdDeserializer<Amount>(Amount::class.java) {
+    override fun deserialize(p: JsonParser, ctxt: DeserializationContext): 
Amount {
+        val node = p.codec.readValue(p, String::class.java)
+        try {
+            return Amount.fromJSONString(node)
+        } catch (e: AmountParserException) {
+            throw JsonMappingException(p, "Error parsing Amount", e)
+        }
+    }
+}
diff --git a/taler-kotlin-common/src/main/java/net/taler/common/AndroidUtils.kt 
b/taler-kotlin-android/src/main/java/net/taler/common/AndroidUtils.kt
similarity index 100%
rename from taler-kotlin-common/src/main/java/net/taler/common/AndroidUtils.kt
rename to taler-kotlin-android/src/main/java/net/taler/common/AndroidUtils.kt
diff --git 
a/taler-kotlin-common/src/main/java/net/taler/common/ByteArrayUtils.kt 
b/taler-kotlin-android/src/main/java/net/taler/common/ByteArrayUtils.kt
similarity index 100%
rename from taler-kotlin-common/src/main/java/net/taler/common/ByteArrayUtils.kt
rename to taler-kotlin-android/src/main/java/net/taler/common/ByteArrayUtils.kt
diff --git 
a/taler-kotlin-common/src/main/java/net/taler/common/CombinedLiveData.kt 
b/taler-kotlin-android/src/main/java/net/taler/common/CombinedLiveData.kt
similarity index 100%
rename from 
taler-kotlin-common/src/main/java/net/taler/common/CombinedLiveData.kt
rename to 
taler-kotlin-android/src/main/java/net/taler/common/CombinedLiveData.kt
diff --git 
a/taler-kotlin-common/src/main/java/net/taler/common/ContractTerms.kt 
b/taler-kotlin-android/src/main/java/net/taler/common/ContractTerms.kt
similarity index 96%
rename from taler-kotlin-common/src/main/java/net/taler/common/ContractTerms.kt
rename to taler-kotlin-android/src/main/java/net/taler/common/ContractTerms.kt
index b891ef7..0d5fe5b 100644
--- a/taler-kotlin-common/src/main/java/net/taler/common/ContractTerms.kt
+++ b/taler-kotlin-android/src/main/java/net/taler/common/ContractTerms.kt
@@ -18,7 +18,6 @@ package net.taler.common
 
 import androidx.annotation.RequiresApi
 import com.fasterxml.jackson.annotation.JsonIgnore
-import com.fasterxml.jackson.annotation.JsonIgnoreProperties
 import com.fasterxml.jackson.annotation.JsonInclude
 import com.fasterxml.jackson.annotation.JsonInclude.Include.NON_EMPTY
 import com.fasterxml.jackson.annotation.JsonInclude.Include.NON_NULL
@@ -28,13 +27,14 @@ import kotlinx.serialization.Serializable
 import net.taler.common.TalerUtils.getLocalizedString
 
 @Serializable
-@JsonIgnoreProperties(ignoreUnknown = true)
 data class ContractTerms(
     val summary: String,
     @SerialName("summary_i18n")
+    @get:JsonProperty("summary_i18n")
     val summaryI18n: Map<String, String>? = null,
     val amount: Amount,
     @SerialName("fulfillment_url")
+    @get:JsonProperty("fulfillment_url")
     val fulfillmentUrl: String,
     val products: List<ContractProduct>
 )
diff --git a/taler-kotlin-common/src/main/java/net/taler/common/Event.kt 
b/taler-kotlin-android/src/main/java/net/taler/common/Event.kt
similarity index 100%
rename from taler-kotlin-common/src/main/java/net/taler/common/Event.kt
rename to taler-kotlin-android/src/main/java/net/taler/common/Event.kt
diff --git a/taler-kotlin-common/src/main/java/net/taler/common/NfcManager.kt 
b/taler-kotlin-android/src/main/java/net/taler/common/NfcManager.kt
similarity index 100%
rename from taler-kotlin-common/src/main/java/net/taler/common/NfcManager.kt
rename to taler-kotlin-android/src/main/java/net/taler/common/NfcManager.kt
diff --git 
a/taler-kotlin-common/src/main/java/net/taler/common/QrCodeManager.kt 
b/taler-kotlin-android/src/main/java/net/taler/common/QrCodeManager.kt
similarity index 100%
rename from taler-kotlin-common/src/main/java/net/taler/common/QrCodeManager.kt
rename to taler-kotlin-android/src/main/java/net/taler/common/QrCodeManager.kt
diff --git a/taler-kotlin-common/src/main/java/net/taler/common/SignedAmount.kt 
b/taler-kotlin-android/src/main/java/net/taler/common/SignedAmount.kt
similarity index 100%
copy from taler-kotlin-common/src/main/java/net/taler/common/SignedAmount.kt
copy to taler-kotlin-android/src/main/java/net/taler/common/SignedAmount.kt
diff --git a/taler-kotlin-common/src/main/java/net/taler/common/TalerUtils.kt 
b/taler-kotlin-android/src/main/java/net/taler/common/TalerUtils.kt
similarity index 97%
rename from taler-kotlin-common/src/main/java/net/taler/common/TalerUtils.kt
rename to taler-kotlin-android/src/main/java/net/taler/common/TalerUtils.kt
index 444caa4..bb2e78a 100644
--- a/taler-kotlin-common/src/main/java/net/taler/common/TalerUtils.kt
+++ b/taler-kotlin-android/src/main/java/net/taler/common/TalerUtils.kt
@@ -18,8 +18,7 @@ package net.taler.common
 
 import androidx.annotation.RequiresApi
 import androidx.core.os.LocaleListCompat
-import java.util.*
-import kotlin.collections.ArrayList
+import java.util.Locale
 
 object TalerUtils {
 
diff --git 
a/taler-kotlin-common/src/main/res/drawable/selectable_background.xml 
b/taler-kotlin-android/src/main/res/drawable/selectable_background.xml
similarity index 100%
rename from taler-kotlin-common/src/main/res/drawable/selectable_background.xml
rename to taler-kotlin-android/src/main/res/drawable/selectable_background.xml
diff --git a/taler-kotlin-common/src/main/res/values-night/colors.xml 
b/taler-kotlin-android/src/main/res/values-night/colors.xml
similarity index 100%
rename from taler-kotlin-common/src/main/res/values-night/colors.xml
rename to taler-kotlin-android/src/main/res/values-night/colors.xml
diff --git a/taler-kotlin-common/src/main/res/values/colors.xml 
b/taler-kotlin-android/src/main/res/values/colors.xml
similarity index 100%
rename from taler-kotlin-common/src/main/res/values/colors.xml
rename to taler-kotlin-android/src/main/res/values/colors.xml
diff --git a/taler-kotlin-common/src/main/res/values/strings.xml 
b/taler-kotlin-android/src/main/res/values/strings.xml
similarity index 100%
rename from taler-kotlin-common/src/main/res/values/strings.xml
rename to taler-kotlin-android/src/main/res/values/strings.xml
diff --git 
a/taler-kotlin-android/src/test/java/net/taler/common/ContractTermsTest.kt 
b/taler-kotlin-android/src/test/java/net/taler/common/ContractTermsTest.kt
new file mode 100644
index 0000000..79a7598
--- /dev/null
+++ b/taler-kotlin-android/src/test/java/net/taler/common/ContractTermsTest.kt
@@ -0,0 +1,74 @@
+/*
+ * This file is part of GNU Taler
+ * (C) 2020 Taler Systems S.A.
+ *
+ * GNU Taler is free software; you can redistribute it and/or modify it under 
the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 3, or (at your option) any later version.
+ *
+ * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
+ * A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+ */
+
+package net.taler.common
+
+import com.fasterxml.jackson.databind.DeserializationFeature
+import com.fasterxml.jackson.databind.ObjectMapper
+import com.fasterxml.jackson.module.kotlin.KotlinModule
+import com.fasterxml.jackson.module.kotlin.readValue
+import org.junit.Assert.assertEquals
+import org.junit.Test
+
+class ContractTermsTest {
+
+    private val mapper = ObjectMapper()
+        .registerModule(KotlinModule())
+        .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
+        .addMixIn(Amount::class.java, AmountMixin::class.java)
+
+    @Test
+    fun test() {
+        val json = """
+            {
+              "amount":"TESTKUDOS:0.5",
+              "extra":{
+                "article_name":"1._The_Free_Software_Definition"
+              },
+              
"fulfillment_url":"https://shop.test.taler.net/essay/1._The_Free_Software_Definition";,
+              "summary":"Essay: 1. The Free Software Definition",
+              "refund_deadline":{"t_ms":1596128414000},
+              "wire_transfer_deadline":{"t_ms":1596128564000},
+              "products":[],
+              
"h_wire":"KV40K023N8EC1F5100TYNS23C4XN68Y1Z3PTJSWFGTMCNYD54KT4S791V2VQ91SZANN86VDAA369M4VEZ0KR6DN71EVRRZA71K681M0",
+              "wire_method":"x-taler-bank",
+              "order_id":"2020.212-01M9VKEAPF76C",
+              "timestamp":{"t_ms":1596128114000},
+              "pay_deadline":{"t_ms":"never"},
+              "max_wire_fee":"TESTKUDOS:1",
+              "max_fee":"TESTKUDOS:1",
+              "wire_fee_amortization":3,
+              
"merchant_base_url":"https://backend.test.taler.net/instances/blog/";,
+              "merchant":{"name":"Blog","instance":"blog"},
+              "exchanges":[
+                {
+                    "url":"https://exchange.test.taler.net/";,
+                    
"master_pub":"DY95EXAHQ2BKM2WK9YHZHYG1R7PPMMJPY14FNGP662DAKE35AKQG"
+                },
+                {
+                    "url":"https://exchange.test.taler.net/";,
+                    
"master_pub":"DY95EXAHQ2BKM2WK9YHZHYG1R7PPMMJPY14FNGP662DAKE35AKQG"}
+                ],
+                "auditors":[],
+                
"merchant_pub":"8DR9NKSZY1CXFRE47NEYXM0K85C4ZGAYH7Y7VZ22GPNF0BRFNYNG",
+                "nonce":"FK8ZKJRV6VX6YFAG4CDSC6W0DWD084Q09DP81ANF30GRFQYM2KPG"
+              }
+        """.trimIndent()
+        val contractTerms: ContractTerms = mapper.readValue(json)
+        assertEquals("Essay: 1. The Free Software Definition", 
contractTerms.summary)
+    }
+
+}
diff --git a/taler-kotlin-common/.gitlab-ci.yml 
b/taler-kotlin-common/.gitlab-ci.yml
index 49d3e98..c241e31 100644
--- a/taler-kotlin-common/.gitlab-ci.yml
+++ b/taler-kotlin-common/.gitlab-ci.yml
@@ -4,8 +4,4 @@ taler_kotlin_common_test:
     changes:
       - taler-kotlin-common/**/*
       - build.gradle
-  script: ./gradlew :taler-kotlin-common:check
-  artifacts:
-    paths:
-      - taler-kotlin-common/build/reports/lint-results.html
-    expire_in: 1 week
+  script: ./gradlew :taler-kotlin-common:jvmTest
diff --git a/taler-kotlin-common/build.gradle b/taler-kotlin-common/build.gradle
index dd083b7..129881d 100644
--- a/taler-kotlin-common/build.gradle
+++ b/taler-kotlin-common/build.gradle
@@ -1,72 +1,82 @@
-/*
- * This file is part of GNU Taler
- * (C) 2020 Taler Systems S.A.
- *
- * GNU Taler is free software; you can redistribute it and/or modify it under 
the
- * terms of the GNU General Public License as published by the Free Software
- * Foundation; either version 3, or (at your option) any later version.
- *
- * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
- * A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
- */
-
 plugins {
-    id 'com.android.library'
-    id 'kotlin-android'
-    id 'kotlin-android-extensions'
+    id 'org.jetbrains.kotlin.multiplatform'
     id 'kotlinx-serialization'
 }
 
-android {
-    compileSdkVersion 29
-    //noinspection GradleDependency
-    buildToolsVersion "$build_tools_version"
+group 'net.taler'
+version '0.0.1'
 
-    defaultConfig {
-        minSdkVersion 24
-        targetSdkVersion 29
-        versionCode 1
-        versionName "0.1"
+apply plugin: 'maven-publish'
 
-        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
-        consumerProguardFiles 'consumer-rules.pro'
+kotlin {
+    jvm()
+    // This is for iPhone simulator
+    // Switch here to iosArm64 (or iosArm32) to build library for iPhone device
+    iosX64("ios") {
+        binaries {
+            framework()
+        }
     }
-
-    buildTypes {
-        release {
-            minifyEnabled false
-            proguardFiles 
getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+    linuxX64("linux")
+    js {
+        browser {
+        }
+        nodejs {
+        }
+    }
+    sourceSets {
+        def serialization_version = "0.20.0"
+        commonMain {
+            dependencies {
+                implementation kotlin('stdlib-common')
+                implementation 
"org.jetbrains.kotlinx:kotlinx-serialization-runtime-common:$serialization_version"
+            }
+        }
+        commonTest {
+            dependencies {
+                implementation kotlin('test-common')
+                implementation kotlin('test-annotations-common')
+            }
+        }
+        jvmMain {
+            dependencies {
+                implementation kotlin('stdlib-jdk8')
+                implementation 
"org.jetbrains.kotlinx:kotlinx-serialization-runtime:$serialization_version"
+            }
+        }
+        jvmTest {
+            dependencies {
+                implementation kotlin('test')
+                implementation kotlin('test-junit')
+            }
+        }
+        jsMain {
+            dependencies {
+                implementation kotlin('stdlib-js')
+                implementation 
"org.jetbrains.kotlinx:kotlinx-serialization-runtime-js:$serialization_version"
+            }
+        }
+        jsTest {
+            dependencies {
+                implementation kotlin('test-js')
+            }
+        }
+        nativeMain {
+            dependsOn commonMain
+            dependencies {
+                implementation 
"org.jetbrains.kotlinx:kotlinx-serialization-runtime-native:$serialization_version"
+            }
+        }
+        nativeTest {
+            dependsOn commonTest
+        }
+        configure([targets.linux, targets.ios]) {
+            compilations.main.source(sourceSets.nativeMain)
+            compilations.test.source(sourceSets.nativeTest)
         }
     }
-
 }
 
-dependencies {
-    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
-    implementation 'androidx.appcompat:appcompat:1.1.0'
-    implementation 'androidx.core:core-ktx:1.3.0'
-
-    // Navigation
-    implementation "androidx.navigation:navigation-ui-ktx:$nav_version"
-    implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
-
-    // ViewModel and LiveData
-    def lifecycle_version = "2.2.0"
-    implementation 
"androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
-
-    // QR codes
-    implementation 'com.google.zxing:core:3.4.0'  // needs minSdkVersion 24+
-
-    // JSON parsing and serialization
-    api "org.jetbrains.kotlinx:kotlinx-serialization-runtime:0.20.0"
-    implementation "com.fasterxml.jackson.module:jackson-module-kotlin:2.10.2"
-
-    lintChecks 'com.github.thirdegg:lint-rules:0.0.4-alpha'
-
-    testImplementation 'junit:junit:4.13'
-    testImplementation 'org.json:json:20190722'
+configurations {
+    compileClasspath
 }
diff --git a/taler-kotlin-common/src/main/java/net/taler/common/Amount.kt 
b/taler-kotlin-common/src/commonMain/kotlin/net/taler/common/Amount.kt
similarity index 70%
rename from taler-kotlin-common/src/main/java/net/taler/common/Amount.kt
rename to taler-kotlin-common/src/commonMain/kotlin/net/taler/common/Amount.kt
index 992f93b..84d10c5 100644
--- a/taler-kotlin-common/src/main/java/net/taler/common/Amount.kt
+++ b/taler-kotlin-common/src/commonMain/kotlin/net/taler/common/Amount.kt
@@ -16,23 +16,12 @@
 
 package net.taler.common
 
-import android.annotation.SuppressLint
-import com.fasterxml.jackson.core.JsonGenerator
-import com.fasterxml.jackson.core.JsonParser
-import com.fasterxml.jackson.databind.DeserializationContext
-import com.fasterxml.jackson.databind.JsonMappingException
-import com.fasterxml.jackson.databind.SerializerProvider
-import com.fasterxml.jackson.databind.annotation.JsonDeserialize
-import com.fasterxml.jackson.databind.annotation.JsonSerialize
-import com.fasterxml.jackson.databind.deser.std.StdDeserializer
-import com.fasterxml.jackson.databind.ser.std.StdSerializer
 import kotlinx.serialization.Decoder
 import kotlinx.serialization.Encoder
 import kotlinx.serialization.KSerializer
 import kotlinx.serialization.Serializable
 import kotlinx.serialization.Serializer
-import org.json.JSONObject
-import java.lang.Math.floorDiv
+import kotlin.math.floor
 import kotlin.math.pow
 import kotlin.math.roundToInt
 
@@ -40,8 +29,6 @@ class AmountParserException(msg: String? = null, cause: 
Throwable? = null) : Exc
 class AmountOverflowException(msg: String? = null, cause: Throwable? = null) : 
Exception(msg, cause)
 
 @Serializable(with = KotlinXAmountSerializer::class)
-@JsonSerialize(using = AmountSerializer::class)
-@JsonDeserialize(using = AmountDeserializer::class)
 data class Amount(
     /**
      * name of the currency using either a three-character ISO 4217 currency 
code,
@@ -70,29 +57,21 @@ data class Amount(
 
         private const val FRACTIONAL_BASE: Int = 100000000 // 1e8
 
-        @Suppress("unused")
-        private val REGEX = 
Regex("""^[-_*A-Za-z0-9]{1,12}:([0-9]+)\.?([0-9]+)?$""")
         private val REGEX_CURRENCY = Regex("""^[-_*A-Za-z0-9]{1,12}$""")
-        private val MAX_VALUE = 2.0.pow(52)
+        val MAX_VALUE = 2.0.pow(52).toLong()
         private const val MAX_FRACTION_LENGTH = 8
-        private const val MAX_FRACTION = 99_999_999
+        const val MAX_FRACTION = 99_999_999
 
-        @Throws(AmountParserException::class)
-        @SuppressLint("CheckedExceptions")
         fun zero(currency: String): Amount {
             return Amount(checkCurrency(currency), 0, 0)
         }
 
-        @Throws(AmountParserException::class)
-        @SuppressLint("CheckedExceptions")
         fun fromJSONString(str: String): Amount {
             val split = str.split(":")
             if (split.size != 2) throw AmountParserException("Invalid Amount 
Format")
             return fromString(split[0], split[1])
         }
 
-        @Throws(AmountParserException::class)
-        @SuppressLint("CheckedExceptions")
         fun fromString(currency: String, str: String): Amount {
             // value
             val valueSplit = str.split(".")
@@ -110,31 +89,23 @@ data class Amount(
             return Amount(checkCurrency(currency), value, fraction)
         }
 
-        @Throws(AmountParserException::class)
-        @SuppressLint("CheckedExceptions")
-        fun fromJsonObject(json: JSONObject): Amount {
-            val currency = checkCurrency(json.optString("currency"))
-            val value = checkValue(json.optString("value").toLongOrNull())
-            val fraction = 
checkFraction(json.optString("fraction").toIntOrNull())
-            return Amount(currency, value, fraction)
-        }
+        fun min(currency: String): Amount = Amount(currency, 0, 1)
+        fun max(currency: String): Amount = Amount(currency, MAX_VALUE, 
MAX_FRACTION)
+
 
-        @Throws(AmountParserException::class)
-        private fun checkCurrency(currency: String): String {
+        internal fun checkCurrency(currency: String): String {
             if (!REGEX_CURRENCY.matches(currency))
                 throw AmountParserException("Invalid currency: $currency")
             return currency
         }
 
-        @Throws(AmountParserException::class)
-        private fun checkValue(value: Long?): Long {
+        internal fun checkValue(value: Long?): Long {
             if (value == null || value > MAX_VALUE)
                 throw AmountParserException("Value $value greater than 
$MAX_VALUE")
             return value
         }
 
-        @Throws(AmountParserException::class)
-        private fun checkFraction(fraction: Int?): Int {
+        internal fun checkFraction(fraction: Int?): Int {
             if (fraction == null || fraction > MAX_FRACTION)
                 throw AmountParserException("Fraction $fraction greater than 
$MAX_FRACTION")
             return fraction
@@ -153,25 +124,23 @@ data class Amount(
             "$value.$fractionStr"
         }
 
-    @Throws(AmountOverflowException::class)
     operator fun plus(other: Amount): Amount {
         check(currency == other.currency) { "Can only subtract from same 
currency" }
-        val resultValue = value + other.value + floorDiv(fraction + 
other.fraction, FRACTIONAL_BASE)
+        val resultValue = value + other.value + floor((fraction + 
other.fraction).toDouble() / FRACTIONAL_BASE).toLong()
         if (resultValue > MAX_VALUE)
             throw AmountOverflowException()
         val resultFraction = (fraction + other.fraction) % FRACTIONAL_BASE
         return Amount(currency, resultValue, resultFraction)
     }
 
-    @Throws(AmountOverflowException::class)
     operator fun times(factor: Int): Amount {
+        // TODO consider replacing with a faster implementation
         if (factor == 0) return zero(currency)
         var result = this
         for (i in 1 until factor) result += this
         return result
     }
 
-    @Throws(AmountOverflowException::class)
     operator fun minus(other: Amount): Amount {
         check(currency == other.currency) { "Can only subtract from same 
currency" }
         var resultValue = value
@@ -227,20 +196,3 @@ object KotlinXAmountSerializer: KSerializer<Amount> {
         return Amount.fromJSONString(decoder.decodeString())
     }
 }
-
-class AmountSerializer : StdSerializer<Amount>(Amount::class.java) {
-    override fun serialize(value: Amount, gen: JsonGenerator, provider: 
SerializerProvider) {
-        gen.writeString(value.toJSONString())
-    }
-}
-
-class AmountDeserializer : StdDeserializer<Amount>(Amount::class.java) {
-    override fun deserialize(p: JsonParser, ctxt: DeserializationContext): 
Amount {
-        val node = p.codec.readValue(p, String::class.java)
-        try {
-            return Amount.fromJSONString(node)
-        } catch (e: AmountParserException) {
-            throw JsonMappingException(p, "Error parsing Amount", e)
-        }
-    }
-}
diff --git a/taler-kotlin-common/src/commonMain/kotlin/net/taler/common/Time.kt 
b/taler-kotlin-common/src/commonMain/kotlin/net/taler/common/Time.kt
new file mode 100644
index 0000000..962e004
--- /dev/null
+++ b/taler-kotlin-common/src/commonMain/kotlin/net/taler/common/Time.kt
@@ -0,0 +1,81 @@
+/*
+ * This file is part of GNU Taler
+ * (C) 2020 Taler Systems S.A.
+ *
+ * GNU Taler is free software; you can redistribute it and/or modify it under 
the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 3, or (at your option) any later version.
+ *
+ * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
+ * A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+ */
+
+package net.taler.common
+
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+import net.taler.common.Duration.Companion.FOREVER
+import kotlin.math.max
+
+expect fun nowMillis(): Long
+
+@Serializable
+data class Timestamp(
+    @SerialName("t_ms")
+    val ms: Long
+) : Comparable<Timestamp> {
+
+    companion object {
+        const val NEVER: Long = -1  // TODO or UINT64_MAX?
+        fun now(): Timestamp = Timestamp(nowMillis())
+    }
+
+    /**
+     * Returns a copy of this [Timestamp] rounded to seconds.
+     */
+    fun truncateSeconds(): Timestamp {
+        if (ms == NEVER) return Timestamp(ms)
+        return Timestamp((ms / 1000L) * 1000L)
+    }
+
+    operator fun minus(other: Timestamp): Duration = when {
+        ms == NEVER -> Duration(FOREVER)
+        other.ms == NEVER -> throw Error("Invalid argument for timestamp 
comparision")
+        ms < other.ms -> Duration(0)
+        else -> Duration(ms - other.ms)
+    }
+
+    operator fun minus(other: Duration): Timestamp = when {
+        ms == NEVER -> this
+        other.ms == FOREVER -> Timestamp(0)
+        else -> Timestamp(max(0, ms - other.ms))
+    }
+
+    override fun compareTo(other: Timestamp): Int {
+        return if (ms == NEVER) {
+            if (other.ms == NEVER) 0
+            else 1
+        } else {
+            if (other.ms == NEVER) -1
+            else ms.compareTo(other.ms)
+        }
+    }
+
+}
+
+@Serializable
+data class Duration(
+    /**
+     * Duration in milliseconds.
+     */
+    @SerialName("d_ms")
+    val ms: Long
+) {
+    companion object {
+        const val FOREVER: Long = -1  // TODO or UINT64_MAX?
+    }
+}
diff --git a/taler-kotlin-common/src/main/java/net/taler/common/Version.kt 
b/taler-kotlin-common/src/commonMain/kotlin/net/taler/common/Version.kt
similarity index 100%
rename from taler-kotlin-common/src/main/java/net/taler/common/Version.kt
rename to taler-kotlin-common/src/commonMain/kotlin/net/taler/common/Version.kt
diff --git a/taler-kotlin-common/src/test/java/net/taler/common/AmountTest.kt 
b/taler-kotlin-common/src/commonTest/kotlin/net/taler/common/AmountTest.kt
similarity index 65%
rename from taler-kotlin-common/src/test/java/net/taler/common/AmountTest.kt
rename to 
taler-kotlin-common/src/commonTest/kotlin/net/taler/common/AmountTest.kt
index 97d9667..e184307 100644
--- a/taler-kotlin-common/src/test/java/net/taler/common/AmountTest.kt
+++ b/taler-kotlin-common/src/commonTest/kotlin/net/taler/common/AmountTest.kt
@@ -16,20 +16,26 @@
 
 package net.taler.common
 
-import com.fasterxml.jackson.databind.ObjectMapper
-import com.fasterxml.jackson.module.kotlin.KotlinModule
-import com.fasterxml.jackson.module.kotlin.readValue
-import org.json.JSONObject
-import org.junit.Assert.assertEquals
-import org.junit.Assert.assertFalse
-import org.junit.Assert.assertTrue
-import org.junit.Assert.fail
-import org.junit.Test
+import kotlin.random.Random
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertFalse
+import kotlin.test.assertTrue
+import kotlin.test.fail
 
 class AmountTest {
 
+    companion object {
+        fun getRandomAmount() = getRandomAmount(getRandomString(1, 
Random.nextInt(1, 12)))
+        fun getRandomAmount(currency: String): Amount {
+            val value = Random.nextLong(0, Amount.MAX_VALUE)
+            val fraction = Random.nextInt(0, Amount.MAX_FRACTION)
+            return Amount(currency, value, fraction)
+        }
+    }
+
     @Test
-    fun `test fromJSONString() works`() {
+    fun testFromJSONString() {
         var str = "TESTKUDOS:23.42"
         var amount = Amount.fromJSONString(str)
         assertEquals(str, amount.toJSONString())
@@ -56,7 +62,7 @@ class AmountTest {
     }
 
     @Test
-    fun `test fromJSONString() accepts max values, rejects above`() {
+    fun testFromJSONStringAcceptsMaxValuesRejectsAbove() {
         val maxValue = 4503599627370496
         val str = "TESTKUDOS123:$maxValue.99999999"
         val amount = Amount.fromJSONString(str)
@@ -82,35 +88,7 @@ class AmountTest {
     }
 
     @Test
-    fun `test JSON deserialization()`() {
-        val mapper = ObjectMapper().registerModule(KotlinModule())
-        var str = "TESTKUDOS:23.42"
-        var amount: Amount = mapper.readValue("\"$str\"")
-        assertEquals(str, amount.toJSONString())
-        assertEquals("TESTKUDOS", amount.currency)
-        assertEquals(23, amount.value)
-        assertEquals((0.42 * 1e8).toInt(), amount.fraction)
-        assertEquals("23.42 TESTKUDOS", amount.toString())
-
-        str = "EUR:500000000.00000001"
-        amount = mapper.readValue("\"$str\"")
-        assertEquals(str, amount.toJSONString())
-        assertEquals("EUR", amount.currency)
-        assertEquals(500000000, amount.value)
-        assertEquals(1, amount.fraction)
-        assertEquals("500000000.00000001 EUR", amount.toString())
-
-        str = "EUR:1500000000.00000003"
-        amount = mapper.readValue("\"$str\"")
-        assertEquals(str, amount.toJSONString())
-        assertEquals("EUR", amount.currency)
-        assertEquals(1500000000, amount.value)
-        assertEquals(3, amount.fraction)
-        assertEquals("1500000000.00000003 EUR", amount.toString())
-    }
-
-    @Test
-    fun `test fromJSONString() rejections`() {
+    fun testFromJSONStringRejections() {
         assertThrows<AmountParserException> {
             Amount.fromJSONString("TESTKUDOS:0,5")
         }
@@ -132,71 +110,7 @@ class AmountTest {
     }
 
     @Test
-    fun `test fromJsonObject() works`() {
-        val map = mapOf(
-            "currency" to "TESTKUDOS",
-            "value" to "23",
-            "fraction" to "42000000"
-        )
-
-        val amount = Amount.fromJsonObject(JSONObject(map))
-        assertEquals("TESTKUDOS:23.42", amount.toJSONString())
-        assertEquals("TESTKUDOS", amount.currency)
-        assertEquals(23, amount.value)
-        assertEquals(42000000, amount.fraction)
-        assertEquals("23.42 TESTKUDOS", amount.toString())
-    }
-
-    @Test
-    fun `test fromJsonObject() accepts max values, rejects above`() {
-        val maxValue = 4503599627370496
-        val maxFraction = 99999999
-        var map = mapOf(
-            "currency" to "TESTKUDOS123",
-            "value" to "$maxValue",
-            "fraction" to "$maxFraction"
-        )
-
-        val amount = Amount.fromJsonObject(JSONObject(map))
-        assertEquals("TESTKUDOS123:$maxValue.$maxFraction", 
amount.toJSONString())
-        assertEquals("TESTKUDOS123", amount.currency)
-        assertEquals(maxValue, amount.value)
-        assertEquals(maxFraction, amount.fraction)
-        assertEquals("$maxValue.$maxFraction TESTKUDOS123", amount.toString())
-
-        // longer currency not accepted
-        assertThrows<AmountParserException>("longer currency was accepted") {
-            map = mapOf(
-                "currency" to "TESTKUDOS1234",
-                "value" to "$maxValue",
-                "fraction" to "$maxFraction"
-            )
-            Amount.fromJsonObject(JSONObject(map))
-        }
-
-        // max value + 1 not accepted
-        assertThrows<AmountParserException>("max value + 1 was accepted") {
-            map = mapOf(
-                "currency" to "TESTKUDOS123",
-                "value" to "${maxValue + 1}",
-                "fraction" to "$maxFraction"
-            )
-            Amount.fromJsonObject(JSONObject(map))
-        }
-
-        // max fraction + 1 not accepted
-        assertThrows<AmountParserException>("max fraction + 1 was accepted") {
-            map = mapOf(
-                "currency" to "TESTKUDOS123",
-                "value" to "$maxValue",
-                "fraction" to "${maxFraction + 1}"
-            )
-            Amount.fromJsonObject(JSONObject(map))
-        }
-    }
-
-    @Test
-    fun `test addition`() {
+    fun testAddition() {
         assertEquals(
             Amount.fromJSONString("EUR:2"),
             Amount.fromJSONString("EUR:1") + Amount.fromJSONString("EUR:1")
@@ -218,7 +132,7 @@ class AmountTest {
     }
 
     @Test
-    fun `test times`() {
+    fun testTimes() {
         assertEquals(
             Amount.fromJSONString("EUR:2"),
             Amount.fromJSONString("EUR:2") * 1
@@ -231,6 +145,12 @@ class AmountTest {
             Amount.fromJSONString("EUR:4.5"),
             Amount.fromJSONString("EUR:1.5") * 3
         )
+        assertEquals(Amount.fromJSONString("EUR:0"), 
Amount.fromJSONString("EUR:1.11") * 0)
+        assertEquals(Amount.fromJSONString("EUR:1.11"), 
Amount.fromJSONString("EUR:1.11") * 1)
+        assertEquals(Amount.fromJSONString("EUR:2.22"), 
Amount.fromJSONString("EUR:1.11") * 2)
+        assertEquals(Amount.fromJSONString("EUR:3.33"), 
Amount.fromJSONString("EUR:1.11") * 3)
+        assertEquals(Amount.fromJSONString("EUR:4.44"), 
Amount.fromJSONString("EUR:1.11") * 4)
+        assertEquals(Amount.fromJSONString("EUR:5.55"), 
Amount.fromJSONString("EUR:1.11") * 5)
         assertEquals(
             Amount.fromJSONString("EUR:1500000000.00000003"),
             Amount.fromJSONString("EUR:500000000.00000001") * 3
@@ -241,7 +161,7 @@ class AmountTest {
     }
 
     @Test
-    fun `test subtraction`() {
+    fun testSubtraction() {
         assertEquals(
             Amount.fromJSONString("EUR:0"),
             Amount.fromJSONString("EUR:1") - Amount.fromJSONString("EUR:1")
@@ -263,7 +183,7 @@ class AmountTest {
     }
 
     @Test
-    fun `test isZero()`() {
+    fun testIsZero() {
         assertTrue(Amount.zero("EUR").isZero())
         assertTrue(Amount.fromJSONString("EUR:0").isZero())
         assertTrue(Amount.fromJSONString("EUR:0.0").isZero())
@@ -276,14 +196,17 @@ class AmountTest {
     }
 
     @Test
-    fun `test comparision`() {
+    fun testComparision() {
         assertTrue(Amount.fromJSONString("EUR:0") <= 
Amount.fromJSONString("EUR:0"))
         assertTrue(Amount.fromJSONString("EUR:0") <= 
Amount.fromJSONString("EUR:0.00000001"))
         assertTrue(Amount.fromJSONString("EUR:0") < 
Amount.fromJSONString("EUR:0.00000001"))
         assertTrue(Amount.fromJSONString("EUR:0") < 
Amount.fromJSONString("EUR:1"))
-        assertTrue(Amount.fromJSONString("EUR:0") == 
Amount.fromJSONString("EUR:0"))
-        assertTrue(Amount.fromJSONString("EUR:42") == 
Amount.fromJSONString("EUR:42"))
-        assertTrue(Amount.fromJSONString("EUR:42.00000001") == 
Amount.fromJSONString("EUR:42.00000001"))
+        assertEquals(Amount.fromJSONString("EUR:0"), 
Amount.fromJSONString("EUR:0"))
+        assertEquals(Amount.fromJSONString("EUR:42"), 
Amount.fromJSONString("EUR:42"))
+        assertEquals(
+            Amount.fromJSONString("EUR:42.00000001"),
+            Amount.fromJSONString("EUR:42.00000001")
+        )
         assertTrue(Amount.fromJSONString("EUR:42.00000001") >= 
Amount.fromJSONString("EUR:42.00000001"))
         assertTrue(Amount.fromJSONString("EUR:42.00000002") >= 
Amount.fromJSONString("EUR:42.00000001"))
         assertTrue(Amount.fromJSONString("EUR:42.00000002") > 
Amount.fromJSONString("EUR:42.00000001"))
diff --git a/taler-kotlin-common/src/main/java/net/taler/common/SignedAmount.kt 
b/taler-kotlin-common/src/commonTest/kotlin/net/taler/common/TestUtils.kt
similarity index 52%
copy from taler-kotlin-common/src/main/java/net/taler/common/SignedAmount.kt
copy to taler-kotlin-common/src/commonTest/kotlin/net/taler/common/TestUtils.kt
index 03a0d6e..e3a6c17 100644
--- a/taler-kotlin-common/src/main/java/net/taler/common/SignedAmount.kt
+++ b/taler-kotlin-common/src/commonTest/kotlin/net/taler/common/TestUtils.kt
@@ -16,25 +16,11 @@
 
 package net.taler.common
 
-import android.annotation.SuppressLint
+import kotlin.random.Random
 
-data class SignedAmount(
-    val positive: Boolean,
-    val amount: Amount
-) {
-
-    companion object {
-        @Throws(AmountParserException::class)
-        @SuppressLint("CheckedExceptions")
-        fun fromJSONString(str: String): SignedAmount = when (str.substring(0, 
1)) {
-            "-" -> SignedAmount(false, Amount.fromJSONString(str.substring(1)))
-            "+" -> SignedAmount(true, Amount.fromJSONString(str.substring(1)))
-            else -> SignedAmount(true, Amount.fromJSONString(str))
-        }
-    }
-
-    override fun toString(): String {
-        return if (positive) "$amount" else "-$amount"
-    }
-
-}
\ No newline at end of file
+private val charPool: List<Char> = ('a'..'z') + ('A'..'Z') + ('0'..'9')
+fun getRandomString(minLength: Int = 1, maxLength: Int = Random.nextInt(0, 
1337)) =
+    (minLength..maxLength)
+        .map { Random.nextInt(0, charPool.size) }
+        .map(charPool::get)
+        .joinToString("")
diff --git a/taler-kotlin-common/src/test/java/net/taler/common/VersionTest.kt 
b/taler-kotlin-common/src/commonTest/kotlin/net/taler/common/VersionTest.kt
similarity index 95%
rename from taler-kotlin-common/src/test/java/net/taler/common/VersionTest.kt
rename to 
taler-kotlin-common/src/commonTest/kotlin/net/taler/common/VersionTest.kt
index 70f30eb..f4f17ea 100644
--- a/taler-kotlin-common/src/test/java/net/taler/common/VersionTest.kt
+++ b/taler-kotlin-common/src/commonTest/kotlin/net/taler/common/VersionTest.kt
@@ -16,9 +16,9 @@
 
 package net.taler.common
 
-import org.junit.Assert.assertEquals
-import org.junit.Assert.assertNull
-import org.junit.Test
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertNull
 
 class VersionTest {
 
diff --git a/taler-kotlin-common/src/main/java/net/taler/common/SignedAmount.kt 
b/taler-kotlin-common/src/jsMain/kotlin/net/taler/common/Time.kt
similarity index 52%
copy from taler-kotlin-common/src/main/java/net/taler/common/SignedAmount.kt
copy to taler-kotlin-common/src/jsMain/kotlin/net/taler/common/Time.kt
index 03a0d6e..b114022 100644
--- a/taler-kotlin-common/src/main/java/net/taler/common/SignedAmount.kt
+++ b/taler-kotlin-common/src/jsMain/kotlin/net/taler/common/Time.kt
@@ -16,25 +16,8 @@
 
 package net.taler.common
 
-import android.annotation.SuppressLint
+import kotlin.js.Date
 
-data class SignedAmount(
-    val positive: Boolean,
-    val amount: Amount
-) {
-
-    companion object {
-        @Throws(AmountParserException::class)
-        @SuppressLint("CheckedExceptions")
-        fun fromJSONString(str: String): SignedAmount = when (str.substring(0, 
1)) {
-            "-" -> SignedAmount(false, Amount.fromJSONString(str.substring(1)))
-            "+" -> SignedAmount(true, Amount.fromJSONString(str.substring(1)))
-            else -> SignedAmount(true, Amount.fromJSONString(str))
-        }
-    }
-
-    override fun toString(): String {
-        return if (positive) "$amount" else "-$amount"
-    }
-
-}
\ No newline at end of file
+actual fun nowMillis(): Long {
+    return Date().getMilliseconds().toLong()
+}
diff --git a/taler-kotlin-common/src/main/java/net/taler/common/SignedAmount.kt 
b/taler-kotlin-common/src/jvmMain/kotlin/net/taler/common/Time.kt
similarity index 52%
copy from taler-kotlin-common/src/main/java/net/taler/common/SignedAmount.kt
copy to taler-kotlin-common/src/jvmMain/kotlin/net/taler/common/Time.kt
index 03a0d6e..6cd9040 100644
--- a/taler-kotlin-common/src/main/java/net/taler/common/SignedAmount.kt
+++ b/taler-kotlin-common/src/jvmMain/kotlin/net/taler/common/Time.kt
@@ -16,25 +16,6 @@
 
 package net.taler.common
 
-import android.annotation.SuppressLint
-
-data class SignedAmount(
-    val positive: Boolean,
-    val amount: Amount
-) {
-
-    companion object {
-        @Throws(AmountParserException::class)
-        @SuppressLint("CheckedExceptions")
-        fun fromJSONString(str: String): SignedAmount = when (str.substring(0, 
1)) {
-            "-" -> SignedAmount(false, Amount.fromJSONString(str.substring(1)))
-            "+" -> SignedAmount(true, Amount.fromJSONString(str.substring(1)))
-            else -> SignedAmount(true, Amount.fromJSONString(str))
-        }
-    }
-
-    override fun toString(): String {
-        return if (positive) "$amount" else "-$amount"
-    }
-
-}
\ No newline at end of file
+actual fun nowMillis(): Long {
+    return System.currentTimeMillis()
+}
diff --git a/taler-kotlin-common/src/main/java/net/taler/common/SignedAmount.kt 
b/taler-kotlin-common/src/nativeMain/kotlin/net/taler/common/Time.kt
similarity index 52%
rename from taler-kotlin-common/src/main/java/net/taler/common/SignedAmount.kt
rename to taler-kotlin-common/src/nativeMain/kotlin/net/taler/common/Time.kt
index 03a0d6e..8a4091a 100644
--- a/taler-kotlin-common/src/main/java/net/taler/common/SignedAmount.kt
+++ b/taler-kotlin-common/src/nativeMain/kotlin/net/taler/common/Time.kt
@@ -16,25 +16,8 @@
 
 package net.taler.common
 
-import android.annotation.SuppressLint
+import kotlin.system.getTimeMillis
 
-data class SignedAmount(
-    val positive: Boolean,
-    val amount: Amount
-) {
-
-    companion object {
-        @Throws(AmountParserException::class)
-        @SuppressLint("CheckedExceptions")
-        fun fromJSONString(str: String): SignedAmount = when (str.substring(0, 
1)) {
-            "-" -> SignedAmount(false, Amount.fromJSONString(str.substring(1)))
-            "+" -> SignedAmount(true, Amount.fromJSONString(str.substring(1)))
-            else -> SignedAmount(true, Amount.fromJSONString(str))
-        }
-    }
-
-    override fun toString(): String {
-        return if (positive) "$amount" else "-$amount"
-    }
-
-}
\ No newline at end of file
+actual fun nowMillis(): Long {
+    return getTimeMillis()
+}
diff --git a/wallet/.gitlab-ci.yml b/wallet/.gitlab-ci.yml
index 56768f7..c417aa9 100644
--- a/wallet/.gitlab-ci.yml
+++ b/wallet/.gitlab-ci.yml
@@ -4,6 +4,7 @@ wallet_test:
     changes:
       - wallet/**/*
       - taler-kotlin-common/**/*
+      - taler-kotlin-android/**/*
       - build.gradle
   script: ./gradlew :wallet:check :wallet:assembleRelease
   artifacts:
diff --git a/wallet/build.gradle b/wallet/build.gradle
index 8cca8dc..1761018 100644
--- a/wallet/build.gradle
+++ b/wallet/build.gradle
@@ -23,7 +23,7 @@ plugins {
     id "de.undercouch.download"
 }
 
-def walletCoreVersion = "v0.7.1-dev.14"
+def walletCoreVersion = "v0.7.1-dev.16"
 
 static def versionCodeEpoch() {
     return (new Date().getTime() / 1000).toInteger()
@@ -47,7 +47,7 @@ android {
         minSdkVersion 24
         targetSdkVersion 29
         versionCode 6
-        versionName "0.7.1.dev.14"
+        versionName "0.7.1.dev.16"
         testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
         buildConfigField "String", "WALLET_CORE_VERSION", 
"\"$walletCoreVersion\""
     }
@@ -83,6 +83,10 @@ android {
         jvmTarget = "1.8"
     }
 
+    packagingOptions {
+        exclude("META-INF/*.kotlin_module")
+    }
+
     lintOptions {
         abortOnError true
         ignoreWarnings false
@@ -93,7 +97,7 @@ android {
 }
 
 dependencies {
-    implementation project(":taler-kotlin-common")
+    implementation project(":taler-kotlin-android")
     implementation project(":anastasis-ui")
     implementation 'net.taler:akono:0.1'
 
diff --git a/wallet/src/main/java/net/taler/wallet/MainViewModel.kt 
b/wallet/src/main/java/net/taler/wallet/MainViewModel.kt
index 3d725d0..ffa2dea 100644
--- a/wallet/src/main/java/net/taler/wallet/MainViewModel.kt
+++ b/wallet/src/main/java/net/taler/wallet/MainViewModel.kt
@@ -24,10 +24,13 @@ import androidx.lifecycle.LiveData
 import androidx.lifecycle.MutableLiveData
 import androidx.lifecycle.distinctUntilChanged
 import androidx.lifecycle.viewModelScope
+import com.fasterxml.jackson.databind.DeserializationFeature
 import 
com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES
 import com.fasterxml.jackson.databind.ObjectMapper
 import com.fasterxml.jackson.module.kotlin.KotlinModule
 import com.fasterxml.jackson.module.kotlin.readValue
+import net.taler.common.Amount
+import net.taler.common.AmountMixin
 import net.taler.common.Event
 import net.taler.common.assertUiThread
 import net.taler.common.toEvent
@@ -91,6 +94,8 @@ class MainViewModel(val app: Application) : 
AndroidViewModel(app) {
     private val mapper = ObjectMapper()
         .registerModule(KotlinModule())
         .configure(FAIL_ON_UNKNOWN_PROPERTIES, false)
+        .addMixIn(Amount::class.java, AmountMixin::class.java)
+        .enable(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT)
 
     val withdrawManager = WithdrawManager(walletBackendApi, mapper)
     val paymentManager = PaymentManager(walletBackendApi, mapper)
diff --git a/wallet/src/main/java/net/taler/wallet/exchanges/ExchangeFees.kt 
b/wallet/src/main/java/net/taler/wallet/exchanges/ExchangeFees.kt
index ae90b98..a026283 100644
--- a/wallet/src/main/java/net/taler/wallet/exchanges/ExchangeFees.kt
+++ b/wallet/src/main/java/net/taler/wallet/exchanges/ExchangeFees.kt
@@ -18,7 +18,6 @@ package net.taler.wallet.exchanges
 
 import net.taler.common.Amount
 import net.taler.common.Timestamp
-import org.json.JSONObject
 
 data class CoinFee(
     val coin: Amount,
@@ -42,54 +41,4 @@ data class ExchangeFees(
     val earliestDepositExpiration: Timestamp,
     val coinFees: List<CoinFee>,
     val wireFees: List<WireFee>
-) {
-    companion object {
-        fun fromExchangeWithdrawDetailsJson(json: JSONObject): ExchangeFees {
-            val earliestDepositExpiration =
-                json.getJSONObject("earliestDepositExpiration").getLong("t_ms")
-            val selectedDenoms = json.getJSONObject("selectedDenoms")
-            val denoms = selectedDenoms.getJSONArray("selectedDenoms")
-            val coinFees = ArrayList<CoinFee>(denoms.length())
-            for (i in 0 until denoms.length()) {
-                val denom = denoms.getJSONObject(i)
-                val d = denom.getJSONObject("denom")
-                val coinFee = CoinFee(
-                    coin = Amount.fromJsonObject(d.getJSONObject("value")),
-                    quantity = denom.getInt("count"),
-                    feeDeposit = 
Amount.fromJsonObject(d.getJSONObject("feeDeposit")),
-                    feeRefresh = 
Amount.fromJsonObject(d.getJSONObject("feeRefresh")),
-                    feeRefund = 
Amount.fromJsonObject(d.getJSONObject("feeRefund")),
-                    feeWithdraw = 
Amount.fromJsonObject(d.getJSONObject("feeWithdraw"))
-                )
-                coinFees.add(coinFee)
-            }
-
-            val wireFeesJson = json.getJSONObject("wireFees")
-            val feesForType = wireFeesJson.getJSONObject("feesForType")
-            val bankFees = feesForType.getJSONArray("x-taler-bank")
-            val wireFees = ArrayList<WireFee>(bankFees.length())
-            for (i in 0 until bankFees.length()) {
-                val fee = bankFees.getJSONObject(i)
-                val startStamp =
-                    fee.getJSONObject("startStamp").getLong("t_ms")
-                val endStamp =
-                    fee.getJSONObject("endStamp").getLong("t_ms")
-                val wireFee = WireFee(
-                    start = Timestamp(startStamp),
-                    end = Timestamp(endStamp),
-                    wireFee = 
Amount.fromJsonObject(fee.getJSONObject("wireFee")),
-                    closingFee = 
Amount.fromJsonObject(fee.getJSONObject("closingFee"))
-                )
-                wireFees.add(wireFee)
-            }
-
-            return ExchangeFees(
-                withdrawFee = 
Amount.fromJsonObject(json.getJSONObject("withdrawFee")),
-                overhead = 
Amount.fromJsonObject(json.getJSONObject("overhead")),
-                earliestDepositExpiration = 
Timestamp(earliestDepositExpiration),
-                coinFees = coinFees,
-                wireFees = wireFees
-            )
-        }
-    }
-}
+)
diff --git a/wallet/src/main/java/net/taler/wallet/payment/PaymentResponses.kt 
b/wallet/src/main/java/net/taler/wallet/payment/PaymentResponses.kt
index 4c5b010..d2f8e6c 100644
--- a/wallet/src/main/java/net/taler/wallet/payment/PaymentResponses.kt
+++ b/wallet/src/main/java/net/taler/wallet/payment/PaymentResponses.kt
@@ -16,23 +16,12 @@
 
 package net.taler.wallet.payment
 
-import com.fasterxml.jackson.annotation.JsonSubTypes
-import com.fasterxml.jackson.annotation.JsonSubTypes.Type
 import com.fasterxml.jackson.annotation.JsonTypeInfo
-import com.fasterxml.jackson.annotation.JsonTypeInfo.As.PROPERTY
 import com.fasterxml.jackson.annotation.JsonTypeInfo.Id.NAME
 import com.fasterxml.jackson.annotation.JsonTypeName
 import net.taler.common.ContractTerms
-import net.taler.wallet.payment.PreparePayResponse.AlreadyConfirmedResponse
-import net.taler.wallet.payment.PreparePayResponse.InsufficientBalanceResponse
-import net.taler.wallet.payment.PreparePayResponse.PaymentPossibleResponse
 
-@JsonTypeInfo(use = NAME, include = PROPERTY, property = "status")
-@JsonSubTypes(
-    Type(value = PaymentPossibleResponse::class, name = "payment-possible"),
-    Type(value = AlreadyConfirmedResponse::class, name = "already-confirmed"),
-    Type(value = InsufficientBalanceResponse::class, name = 
"insufficient-balance")
-)
+@JsonTypeInfo(use = NAME, property = "status")
 sealed class PreparePayResponse(open val proposalId: String) {
     @JsonTypeName("payment-possible")
     data class PaymentPossibleResponse(

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



reply via email to

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