aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTorsten Grote <t@grobox.de>2020-08-24 17:10:49 -0300
committerTorsten Grote <t@grobox.de>2020-08-24 17:10:49 -0300
commit39dcd04750eef1581d0bdde394371ef9ca2808b9 (patch)
treea33a9c9f2683d911ff4fd2b691d91f15d117ce39
parent35bc91761ad1f8336f331c6b04cff8bf4d9ae064 (diff)
downloadtaler-android-39dcd04750eef1581d0bdde394371ef9ca2808b9.tar.gz
taler-android-39dcd04750eef1581d0bdde394371ef9ca2808b9.tar.bz2
taler-android-39dcd04750eef1581d0bdde394371ef9ca2808b9.zip
Get rid of Jackson and only use multi-platform serialization
-rw-r--r--.idea/compiler.xml4
-rw-r--r--merchant-lib/src/main/java/net/taler/merchantlib/MerchantApi.kt1
-rw-r--r--merchant-lib/src/main/java/net/taler/merchantlib/Orders.kt37
-rw-r--r--taler-kotlin-android/build.gradle2
-rw-r--r--taler-kotlin-android/src/main/java/net/taler/common/ContractTerms.kt18
-rw-r--r--taler-kotlin-android/src/main/java/net/taler/lib/android/Serialization.kt21
-rw-r--r--taler-kotlin-android/src/main/java/net/taler/lib/common/AmountMixin.kt51
-rw-r--r--taler-kotlin-android/src/main/java/net/taler/lib/common/TimestampMixin.kt39
-rw-r--r--taler-kotlin-android/src/test/java/net/taler/common/ContractTermsTest.kt21
-rw-r--r--wallet/build.gradle5
-rw-r--r--wallet/proguard-rules.pro33
-rw-r--r--wallet/src/main/java/net/taler/wallet/MainViewModel.kt17
-rw-r--r--wallet/src/main/java/net/taler/wallet/backend/WalletBackendApi.kt36
-rw-r--r--wallet/src/main/java/net/taler/wallet/backend/WalletResponse.kt13
-rw-r--r--wallet/src/main/java/net/taler/wallet/payment/PaymentManager.kt25
-rw-r--r--wallet/src/main/java/net/taler/wallet/payment/PaymentResponses.kt41
-rw-r--r--wallet/src/main/java/net/taler/wallet/transactions/TransactionAdapter.kt2
-rw-r--r--wallet/src/main/java/net/taler/wallet/transactions/TransactionManager.kt9
-rw-r--r--wallet/src/main/java/net/taler/wallet/transactions/Transactions.kt164
-rw-r--r--wallet/src/test/java/net/taler/wallet/backend/WalletResponseTest.kt16
20 files changed, 165 insertions, 390 deletions
diff --git a/.idea/compiler.xml b/.idea/compiler.xml
index 2b48706..9389bf3 100644
--- a/.idea/compiler.xml
+++ b/.idea/compiler.xml
@@ -12,6 +12,10 @@
<entry name="!?*.clj" />
</wildcardResourcePatterns>
<bytecodeTargetLevel>
+ <module name="common_commonMain" target="1.6" />
+ <module name="common_commonTest" target="1.6" />
+ <module name="common_jvmMain" target="1.6" />
+ <module name="common_jvmTest" target="1.6" />
<module name="taler-kotlin-common_jvmMain" target="1.6" />
<module name="taler-kotlin-common_jvmTest" target="1.6" />
</bytecodeTargetLevel>
diff --git a/merchant-lib/src/main/java/net/taler/merchantlib/MerchantApi.kt b/merchant-lib/src/main/java/net/taler/merchantlib/MerchantApi.kt
index ea5b996..a467c41 100644
--- a/merchant-lib/src/main/java/net/taler/merchantlib/MerchantApi.kt
+++ b/merchant-lib/src/main/java/net/taler/merchantlib/MerchantApi.kt
@@ -118,5 +118,6 @@ fun getSerializer() = KotlinxSerializer(
Json {
encodeDefaults = false
ignoreUnknownKeys = true
+ classDiscriminator = "order_status"
}
)
diff --git a/merchant-lib/src/main/java/net/taler/merchantlib/Orders.kt b/merchant-lib/src/main/java/net/taler/merchantlib/Orders.kt
index 9c23ef1..9242df3 100644
--- a/merchant-lib/src/main/java/net/taler/merchantlib/Orders.kt
+++ b/merchant-lib/src/main/java/net/taler/merchantlib/Orders.kt
@@ -16,17 +16,10 @@
package net.taler.merchantlib
-import kotlinx.serialization.KSerializer
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
-import kotlinx.serialization.Serializer
-import kotlinx.serialization.encoding.Decoder
-import kotlinx.serialization.encoding.Encoder
-import kotlinx.serialization.json.JsonDecoder
-import kotlinx.serialization.json.JsonObject
-import kotlinx.serialization.json.boolean
-import kotlinx.serialization.json.jsonPrimitive
import net.taler.common.ContractTerms
+import net.taler.lib.android.CustomClassDiscriminator
import net.taler.lib.common.Duration
@Serializable
@@ -44,33 +37,12 @@ data class PostOrderResponse(
)
@Serializable
-sealed class CheckPaymentResponse {
+sealed class CheckPaymentResponse: CustomClassDiscriminator {
+ override val discriminator: String = "order_status"
abstract val paid: Boolean
- @Suppress("EXPERIMENTAL_API_USAGE")
- @Serializer(forClass = CheckPaymentResponse::class)
- companion object : KSerializer<CheckPaymentResponse> {
- override fun deserialize(decoder: Decoder): CheckPaymentResponse {
- val input = decoder as JsonDecoder
- val tree = input.decodeJsonElement() as JsonObject
- val orderStatus = tree.getValue("order_status").jsonPrimitive.content
-// return if (orderStatus == "paid") decoder.json.decodeFromJsonElement(Paid.serializer(), tree)
-// else decoder.json.decodeFromJsonElement(Unpaid.serializer(), tree)
- // manual parsing due to https://github.com/Kotlin/kotlinx.serialization/issues/576
- return if (orderStatus == "paid") Paid(
- refunded = tree.getValue("refunded").jsonPrimitive.boolean
- ) else Unpaid(
- talerPayUri = tree.getValue("taler_pay_uri").jsonPrimitive.content
- )
- }
-
- override fun serialize(encoder: Encoder, value: CheckPaymentResponse) = when (value) {
- is Unpaid -> Unpaid.serializer().serialize(encoder, value)
- is Paid -> Paid.serializer().serialize(encoder, value)
- }
- }
-
@Serializable
+ @SerialName("unpaid")
data class Unpaid(
override val paid: Boolean = false,
@SerialName("taler_pay_uri")
@@ -80,6 +52,7 @@ sealed class CheckPaymentResponse {
) : CheckPaymentResponse()
@Serializable
+ @SerialName("paid")
data class Paid(
override val paid: Boolean = true,
val refunded: Boolean
diff --git a/taler-kotlin-android/build.gradle b/taler-kotlin-android/build.gradle
index 5cb0b0e..6d992a0 100644
--- a/taler-kotlin-android/build.gradle
+++ b/taler-kotlin-android/build.gradle
@@ -67,8 +67,6 @@ dependencies {
// JSON parsing and serialization
api "org.jetbrains.kotlinx:kotlinx-serialization-core:1.0.0-RC"
- implementation "com.fasterxml.jackson.module:jackson-module-kotlin:2.10.2"
- implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
lintPublish 'com.github.thirdegg:lint-rules:0.0.4-alpha'
diff --git a/taler-kotlin-android/src/main/java/net/taler/common/ContractTerms.kt b/taler-kotlin-android/src/main/java/net/taler/common/ContractTerms.kt
index 8bf77e8..2c50fa9 100644
--- a/taler-kotlin-android/src/main/java/net/taler/common/ContractTerms.kt
+++ b/taler-kotlin-android/src/main/java/net/taler/common/ContractTerms.kt
@@ -17,10 +17,6 @@
package net.taler.common
import androidx.annotation.RequiresApi
-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 kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import net.taler.common.TalerUtils.getLocalizedString
@@ -31,36 +27,24 @@ import net.taler.lib.common.Timestamp
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>,
@SerialName("wire_transfer_deadline")
- @get:JsonProperty("wire_transfer_deadline")
val wireTransferDeadline: Timestamp? = null,
@SerialName("refund_deadline")
- @get:JsonProperty("refund_deadline")
val refundDeadline: Timestamp? = null
)
-@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: Amount
-
- @get:JsonProperty("delivery_location")
abstract val location: String?
abstract val image: String?
-
- @get:JsonIgnore
val localizedDescription: String
@RequiresApi(26)
get() = getLocalizedString(descriptionI18n, description)
@@ -79,12 +63,12 @@ data class ContractProduct(
override val image: String? = null,
val quantity: Int
) : Product() {
- @get:JsonIgnore
val totalPrice: Amount by lazy {
price * quantity
}
}
+@Serializable
data class ContractMerchant(
val name: String
)
diff --git a/taler-kotlin-android/src/main/java/net/taler/lib/android/Serialization.kt b/taler-kotlin-android/src/main/java/net/taler/lib/android/Serialization.kt
new file mode 100644
index 0000000..7eb4480
--- /dev/null
+++ b/taler-kotlin-android/src/main/java/net/taler/lib/android/Serialization.kt
@@ -0,0 +1,21 @@
+/*
+ * 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.lib.android
+
+interface CustomClassDiscriminator {
+ val discriminator: String
+}
diff --git a/taler-kotlin-android/src/main/java/net/taler/lib/common/AmountMixin.kt b/taler-kotlin-android/src/main/java/net/taler/lib/common/AmountMixin.kt
deleted file mode 100644
index 59285b6..0000000
--- a/taler-kotlin-android/src/main/java/net/taler/lib/common/AmountMixin.kt
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * This file is part of GNU Taler
- * (C) 2020 Taler Systems S.A.
- *
- * GNU Taler is free software; you can redistribute it and/or modify it under the
- * terms of the GNU General Public License as published by the Free Software
- * Foundation; either version 3, or (at your option) any later version.
- *
- * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
- */
-
-package net.taler.lib.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-android/src/main/java/net/taler/lib/common/TimestampMixin.kt b/taler-kotlin-android/src/main/java/net/taler/lib/common/TimestampMixin.kt
deleted file mode 100644
index 40c03f6..0000000
--- a/taler-kotlin-android/src/main/java/net/taler/lib/common/TimestampMixin.kt
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * This file is part of GNU Taler
- * (C) 2020 Taler Systems S.A.
- *
- * GNU Taler is free software; you can redistribute it and/or modify it under the
- * terms of the GNU General Public License as published by the Free Software
- * Foundation; either version 3, or (at your option) any later version.
- *
- * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
- */
-
-package net.taler.lib.common
-
-import com.fasterxml.jackson.annotation.JsonProperty
-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
-
-/**
- * Used to support Jackson serialization along with KotlinX.
- */
-abstract class TimestampMixin(
- @get:JsonDeserialize(using = NeverDeserializer::class)
- @get:JsonProperty("t_ms")
- val ms: Long
-)
-
-class NeverDeserializer : StdDeserializer<Long>(Long::class.java) {
- override fun deserialize(p: JsonParser, ctxt: DeserializationContext): Long {
- return if (p.text == "never") -1
- else p.longValue
- }
-}
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
index 0e410ca..3a2cdb4 100644
--- a/taler-kotlin-android/src/test/java/net/taler/common/ContractTermsTest.kt
+++ b/taler-kotlin-android/src/test/java/net/taler/common/ContractTermsTest.kt
@@ -16,28 +16,21 @@
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 net.taler.lib.common.Amount
-import net.taler.lib.common.AmountMixin
+import kotlinx.serialization.decodeFromString
+import kotlinx.serialization.json.Json
import net.taler.lib.common.Timestamp
-import net.taler.lib.common.TimestampMixin
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)
- .addMixIn(Timestamp::class.java, TimestampMixin::class.java)
+ private val json = Json {
+ ignoreUnknownKeys = true
+ }
@Test
fun test() {
- val json = """
+ val jsonStr = """
{
"amount":"TESTKUDOS:0.5",
"extra":{
@@ -72,7 +65,7 @@ class ContractTermsTest {
"nonce":"FK8ZKJRV6VX6YFAG4CDSC6W0DWD084Q09DP81ANF30GRFQYM2KPG"
}
""".trimIndent()
- val contractTerms: ContractTerms = mapper.readValue(json)
+ val contractTerms: ContractTerms = json.decodeFromString(jsonStr)
assertEquals("Essay: 1. The Free Software Definition", contractTerms.summary)
assertEquals(Timestamp.never(), contractTerms.refundDeadline)
}
diff --git a/wallet/build.gradle b/wallet/build.gradle
index 8806a5a..ad3ba29 100644
--- a/wallet/build.gradle
+++ b/wallet/build.gradle
@@ -102,6 +102,8 @@ dependencies {
implementation project(":anastasis-ui")
implementation 'net.taler:akono:0.1'
+ implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
+
implementation 'androidx.preference:preference:1.1.1'
implementation 'com.google.android.material:material:1.2.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.0'
@@ -129,9 +131,6 @@ dependencies {
implementation "io.noties.markwon:ext-tables:$markwon_version"
implementation "io.noties.markwon:recycler:$markwon_version"
- // JSON parsing and serialization
- implementation 'com.fasterxml.jackson.module:jackson-module-kotlin:2.10.2'
-
testImplementation 'junit:junit:4.13'
testImplementation 'org.json:json:20200518'
androidTestImplementation 'androidx.test:runner:1.2.0'
diff --git a/wallet/proguard-rules.pro b/wallet/proguard-rules.pro
index 147334d..27f3799 100644
--- a/wallet/proguard-rules.pro
+++ b/wallet/proguard-rules.pro
@@ -23,36 +23,3 @@
-keep class akono.AkonoJni {*;}
-keep class net.taler.wallet.** {*;}
-
-# Jackson
--keep @com.fasterxml.jackson.annotation.JsonIgnoreProperties class * { *; }
--keep @com.fasterxml.jackson.annotation.JsonCreator class * { *; }
--keep @com.fasterxml.jackson.annotation.JsonValue class * { *; }
--keep class com.fasterxml.** { *; }
--keep class org.codehaus.** { *; }
--keepnames class com.fasterxml.jackson.** { *; }
--keepclassmembers public final enum com.fasterxml.jackson.annotation.JsonAutoDetect$Visibility {
- public static final com.fasterxml.jackson.annotation.JsonAutoDetect$Visibility *;
-}
-
--keep class * extends com.fasterxml.** { *; }
--keep class * implements com.fasterxml.** { *; }
-
--keep class * {
- @com.fasterxml.** *;
-}
-
-# KotlinX serialization
--keep @kotlinx.serialization.Serializable class * { *; }
-
-# Kotlin reflection
--dontwarn kotlin.reflect.**
--keep class kotlin.** { *; }
--keep class org.jetbrains.annotations.** { *; }
-
-
-# General
--keepattributes SourceFile,LineNumberTable,*Annotation*,EnclosingMethod,Signature,Exceptions,InnerClasses
--dontobfuscate
--dontoptimize
--dontshrink \ No newline at end of file
diff --git a/wallet/src/main/java/net/taler/wallet/MainViewModel.kt b/wallet/src/main/java/net/taler/wallet/MainViewModel.kt
index 3fc49a9..9e49f54 100644
--- a/wallet/src/main/java/net/taler/wallet/MainViewModel.kt
+++ b/wallet/src/main/java/net/taler/wallet/MainViewModel.kt
@@ -24,18 +24,11 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.distinctUntilChanged
import androidx.lifecycle.viewModelScope
-import com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES
-import com.fasterxml.jackson.databind.ObjectMapper
-import com.fasterxml.jackson.module.kotlin.KotlinModule
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import net.taler.common.Event
import net.taler.common.assertUiThread
import net.taler.common.toEvent
-import net.taler.lib.common.Amount
-import net.taler.lib.common.AmountMixin
-import net.taler.lib.common.Timestamp
-import net.taler.lib.common.TimestampMixin
import net.taler.wallet.backend.WalletBackendApi
import net.taler.wallet.balances.BalanceItem
import net.taler.wallet.balances.BalanceResponse
@@ -70,12 +63,6 @@ class MainViewModel(val app: Application) : AndroidViewModel(app) {
var merchantVersion: String? = null
private set
- private val mapper = ObjectMapper()
- .registerModule(KotlinModule())
- .configure(FAIL_ON_UNKNOWN_PROPERTIES, false)
- .addMixIn(Amount::class.java, AmountMixin::class.java)
- .addMixIn(Timestamp::class.java, TimestampMixin::class.java)
-
private val api = WalletBackendApi(app) { payload ->
if (payload.optString("operation") == "init") {
val result = payload.getJSONObject("result")
@@ -99,9 +86,9 @@ class MainViewModel(val app: Application) : AndroidViewModel(app) {
}
val withdrawManager = WithdrawManager(api, viewModelScope)
- val paymentManager = PaymentManager(api, viewModelScope, mapper)
+ val paymentManager = PaymentManager(api, viewModelScope)
val pendingOperationsManager: PendingOperationsManager = PendingOperationsManager(api)
- val transactionManager: TransactionManager = TransactionManager(api, viewModelScope, mapper)
+ val transactionManager: TransactionManager = TransactionManager(api, viewModelScope)
val refundManager = RefundManager(api, viewModelScope)
val exchangeManager: ExchangeManager = ExchangeManager(api, viewModelScope)
diff --git a/wallet/src/main/java/net/taler/wallet/backend/WalletBackendApi.kt b/wallet/src/main/java/net/taler/wallet/backend/WalletBackendApi.kt
index 693fe7e..c6261bf 100644
--- a/wallet/src/main/java/net/taler/wallet/backend/WalletBackendApi.kt
+++ b/wallet/src/main/java/net/taler/wallet/backend/WalletBackendApi.kt
@@ -26,12 +26,11 @@ import android.os.IBinder
import android.os.Message
import android.os.Messenger
import android.util.Log
-import com.fasterxml.jackson.databind.ObjectMapper
-import com.fasterxml.jackson.module.kotlin.readValue
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.serialization.KSerializer
import kotlinx.serialization.json.Json
+import net.taler.lib.android.CustomClassDiscriminator
import net.taler.wallet.backend.WalletBackendService.Companion.MSG_COMMAND
import net.taler.wallet.backend.WalletBackendService.Companion.MSG_NOTIFY
import net.taler.wallet.backend.WalletBackendService.Companion.MSG_REPLY
@@ -43,14 +42,12 @@ import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.atomic.AtomicInteger
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
+import kotlin.reflect.full.companionObjectInstance
class WalletBackendApi(
private val app: Application,
private val notificationHandler: ((payload: JSONObject) -> Unit)
) {
- private val json = Json {
- ignoreUnknownKeys = true
- }
private var walletBackendMessenger: Messenger? = null
private val queuedMessages = LinkedList<Message>()
private val handlers = ConcurrentHashMap<Int, (isError: Boolean, message: JSONObject) -> Unit>()
@@ -147,12 +144,18 @@ class WalletBackendApi(
}
}
- suspend fun <T> request(
+ suspend inline fun <reified T> request(
operation: String,
serializer: KSerializer<T>? = null,
- args: (JSONObject.() -> JSONObject)? = null
+ noinline args: (JSONObject.() -> JSONObject)? = null
): WalletResponse<T> = withContext(Dispatchers.Default) {
suspendCoroutine { cont ->
+ val json = Json {
+ ignoreUnknownKeys = true
+ (T::class.companionObjectInstance as? CustomClassDiscriminator)?.let {
+ classDiscriminator = it.discriminator
+ }
+ }
sendRequest(operation, args?.invoke(JSONObject())) { isError, message ->
val response = if (isError) {
val error = json.decodeFromString(WalletErrorInfo.serializer(), message.toString())
@@ -167,25 +170,6 @@ class WalletBackendApi(
}
}
- suspend inline fun <reified T> request(
- operation: String,
- mapper: ObjectMapper,
- noinline args: (JSONObject.() -> JSONObject)? = null
- ): WalletResponse<T> = withContext(Dispatchers.Default) {
- suspendCoroutine { cont ->
- sendRequest(operation, args?.invoke(JSONObject())) { isError, message ->
- val response = if (isError) {
- val error: WalletErrorInfo = mapper.readValue(message.toString())
- WalletResponse.Error(error)
- } else {
- val t: T = mapper.readValue(message.toString())
- WalletResponse.Success(t)
- }
- cont.resume(response)
- }
- }
- }
-
fun destroy() {
// FIXME: implement this!
}
diff --git a/wallet/src/main/java/net/taler/wallet/backend/WalletResponse.kt b/wallet/src/main/java/net/taler/wallet/backend/WalletResponse.kt
index 57ce82e..4b39ff8 100644
--- a/wallet/src/main/java/net/taler/wallet/backend/WalletResponse.kt
+++ b/wallet/src/main/java/net/taler/wallet/backend/WalletResponse.kt
@@ -16,11 +16,6 @@
package net.taler.wallet.backend
-import com.fasterxml.jackson.core.JsonParser
-import com.fasterxml.jackson.databind.DeserializationContext
-import com.fasterxml.jackson.databind.JsonNode
-import com.fasterxml.jackson.databind.annotation.JsonDeserialize
-import com.fasterxml.jackson.databind.deser.std.StdDeserializer
import kotlinx.serialization.KSerializer
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@@ -72,7 +67,6 @@ data class WalletErrorInfo(
// Error details, type depends on talerErrorCode
@Serializable(JSONObjectDeserializer::class)
- @JsonDeserialize(using = JsonObjectDeserializer::class)
val details: JSONObject?
) {
val userFacingMsg: String
@@ -107,10 +101,3 @@ class JSONObjectDeserializer : KSerializer<JSONObject> {
error("not supported")
}
}
-
-class JsonObjectDeserializer : StdDeserializer<JSONObject>(JSONObject::class.java) {
- override fun deserialize(p: JsonParser, ctxt: DeserializationContext): JSONObject {
- val node: JsonNode = p.codec.readTree(p)
- return JSONObject(node.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
index 4924752..befcd83 100644
--- a/wallet/src/main/java/net/taler/wallet/payment/PaymentManager.kt
+++ b/wallet/src/main/java/net/taler/wallet/payment/PaymentManager.kt
@@ -20,12 +20,10 @@ 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 kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
-import net.taler.lib.common.Amount
import net.taler.common.ContractTerms
+import net.taler.lib.common.Amount
import net.taler.wallet.TAG
import net.taler.wallet.backend.WalletBackendApi
import net.taler.wallet.backend.WalletErrorInfo
@@ -34,8 +32,6 @@ import net.taler.wallet.payment.PayStatus.InsufficientBalance
import net.taler.wallet.payment.PreparePayResponse.AlreadyConfirmedResponse
import net.taler.wallet.payment.PreparePayResponse.InsufficientBalanceResponse
import net.taler.wallet.payment.PreparePayResponse.PaymentPossibleResponse
-import org.json.JSONObject
-import java.net.MalformedURLException
val REGEX_PRODUCT_IMAGE = Regex("^data:image/(jpeg|png);base64,([A-Za-z0-9+/=]+)$")
@@ -63,7 +59,6 @@ sealed class PayStatus {
class PaymentManager(
private val api: WalletBackendApi,
private val scope: CoroutineScope,
- private val mapper: ObjectMapper
) {
private val mPayStatus = MutableLiveData<PayStatus>(PayStatus.None)
@@ -76,7 +71,7 @@ class PaymentManager(
fun preparePay(url: String) = scope.launch {
mPayStatus.value = PayStatus.Loading
mDetailsShown.value = false
- api.request<PreparePayResponse>("preparePay", mapper) {
+ api.request("preparePay", PreparePayResponse.serializer()) {
put("talerPayUri", url)
}.onError {
handleError("preparePay", it)
@@ -93,20 +88,6 @@ class PaymentManager(
}
}
- // TODO validate product images (or leave to wallet-core?)
- 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
- }
-
fun confirmPay(proposalId: String, currency: String) = scope.launch {
api.request("confirmPay", ConfirmPayResult.serializer()) {
put("proposalId", proposalId)
@@ -128,7 +109,7 @@ class PaymentManager(
internal fun abortProposal(proposalId: String) = scope.launch {
Log.i(TAG, "aborting proposal")
- api.request<String>("abortProposal", mapper) {
+ api.request<Unit>("abortProposal") {
put("proposalId", proposalId)
}.onError {
Log.e(TAG, "received error response to abortProposal")
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 c490654..19007b0 100644
--- a/wallet/src/main/java/net/taler/wallet/payment/PaymentResponses.kt
+++ b/wallet/src/main/java/net/taler/wallet/payment/PaymentResponses.kt
@@ -16,42 +16,47 @@
package net.taler.wallet.payment
-import com.fasterxml.jackson.annotation.JsonTypeInfo
-import com.fasterxml.jackson.annotation.JsonTypeInfo.Id.NAME
-import com.fasterxml.jackson.annotation.JsonTypeName
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import net.taler.common.ContractTerms
+import net.taler.lib.android.CustomClassDiscriminator
import net.taler.lib.common.Amount
import net.taler.wallet.transactions.TransactionError
-@JsonTypeInfo(use = NAME, property = "status")
-sealed class PreparePayResponse(open val proposalId: String) {
- @JsonTypeName("payment-possible")
+@Serializable
+sealed class PreparePayResponse {
+ companion object : CustomClassDiscriminator {
+ override val discriminator: String = "status"
+ }
+
+ @Serializable
+ @SerialName("payment-possible")
data class PaymentPossibleResponse(
- override val proposalId: String,
+ val proposalId: String,
val amountRaw: Amount,
val amountEffective: Amount,
- val contractTerms: ContractTerms
- ) : PreparePayResponse(proposalId) {
+ val contractTerms: ContractTerms,
+ ) : PreparePayResponse() {
fun toPayStatusPrepared() = PayStatus.Prepared(
contractTerms = contractTerms,
proposalId = proposalId,
amountRaw = amountRaw,
- amountEffective = amountEffective
+ amountEffective = amountEffective,
)
}
- @JsonTypeName("insufficient-balance")
+ @Serializable
+ @SerialName("insufficient-balance")
data class InsufficientBalanceResponse(
- override val proposalId: String,
+ val proposalId: String,
val amountRaw: Amount,
- val contractTerms: ContractTerms
- ) : PreparePayResponse(proposalId)
+ val contractTerms: ContractTerms,
+ ) : PreparePayResponse()
- @JsonTypeName("already-confirmed")
+ @Serializable
+ @SerialName("already-confirmed")
data class AlreadyConfirmedResponse(
- override val proposalId: String,
+ val proposalId: String,
/**
* Did the payment succeed?
*/
@@ -62,8 +67,8 @@ sealed class PreparePayResponse(open val proposalId: String) {
/**
* Redirect URL for the fulfillment page, only given if paid==true.
*/
- val nextUrl: String?
- ) : PreparePayResponse(proposalId)
+ val nextUrl: String?,
+ ) : PreparePayResponse()
}
@Serializable
diff --git a/wallet/src/main/java/net/taler/wallet/transactions/TransactionAdapter.kt b/wallet/src/main/java/net/taler/wallet/transactions/TransactionAdapter.kt
index f494b05..9dc2d23 100644
--- a/wallet/src/main/java/net/taler/wallet/transactions/TransactionAdapter.kt
+++ b/wallet/src/main/java/net/taler/wallet/transactions/TransactionAdapter.kt
@@ -104,7 +104,7 @@ internal class TransactionAdapter(
private fun bindExtraInfo(transaction: Transaction) {
if (transaction.error != null) {
extraInfoView.text =
- context.getString(R.string.payment_error, transaction.error.text)
+ context.getString(R.string.payment_error, transaction.error!!.text)
extraInfoView.setTextColor(red)
extraInfoView.visibility = VISIBLE
} else if (transaction is TransactionWithdrawal && !transaction.confirmed) {
diff --git a/wallet/src/main/java/net/taler/wallet/transactions/TransactionManager.kt b/wallet/src/main/java/net/taler/wallet/transactions/TransactionManager.kt
index e9b1b71..6b5a79b 100644
--- a/wallet/src/main/java/net/taler/wallet/transactions/TransactionManager.kt
+++ b/wallet/src/main/java/net/taler/wallet/transactions/TransactionManager.kt
@@ -20,11 +20,11 @@ import androidx.annotation.UiThread
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.switchMap
-import com.fasterxml.jackson.databind.ObjectMapper
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import net.taler.wallet.backend.WalletBackendApi
import java.util.HashMap
+import java.util.LinkedList
sealed class TransactionsResult {
class Error(val msg: String) : TransactionsResult()
@@ -33,8 +33,7 @@ sealed class TransactionsResult {
class TransactionManager(
private val api: WalletBackendApi,
- private val scope: CoroutineScope,
- private val mapper: ObjectMapper
+ private val scope: CoroutineScope
) {
private val mProgress = MutableLiveData<Boolean>()
@@ -64,14 +63,14 @@ class TransactionManager(
}
if (liveData.value == null) mProgress.value = true
- api.request<Transactions>("getTransactions", mapper) {
+ api.request("getTransactions", Transactions.serializer()) {
if (searchQuery != null) put("search", searchQuery)
put("currency", currency)
}.onError {
liveData.postValue(TransactionsResult.Error(it.userFacingMsg))
mProgress.postValue(false)
}.onSuccess { result ->
- val transactions = result.transactions
+ val transactions = LinkedList(result.transactions)
// TODO remove when fixed in wallet-core
val comparator = compareBy<Transaction>(
{ it.pending },
diff --git a/wallet/src/main/java/net/taler/wallet/transactions/Transactions.kt b/wallet/src/main/java/net/taler/wallet/transactions/Transactions.kt
index 1ed6788..8c00540 100644
--- a/wallet/src/main/java/net/taler/wallet/transactions/Transactions.kt
+++ b/wallet/src/main/java/net/taler/wallet/transactions/Transactions.kt
@@ -20,42 +20,30 @@ import android.content.Context
import androidx.annotation.DrawableRes
import androidx.annotation.LayoutRes
import androidx.annotation.StringRes
-import com.fasterxml.jackson.annotation.JsonProperty
-import com.fasterxml.jackson.annotation.JsonSubTypes
-import com.fasterxml.jackson.annotation.JsonSubTypes.Type
-import com.fasterxml.jackson.annotation.JsonTypeInfo
-import com.fasterxml.jackson.annotation.JsonTypeInfo.As.PROPERTY
-import com.fasterxml.jackson.annotation.JsonTypeInfo.Id.NAME
-import com.fasterxml.jackson.annotation.JsonTypeName
+import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
-import net.taler.lib.common.Amount
+import kotlinx.serialization.Transient
import net.taler.common.ContractMerchant
import net.taler.common.ContractProduct
+import net.taler.lib.common.Amount
import net.taler.lib.common.Timestamp
import net.taler.wallet.R
import net.taler.wallet.cleanExchange
import net.taler.wallet.transactions.WithdrawalDetails.ManualTransfer
import net.taler.wallet.transactions.WithdrawalDetails.TalerBankIntegrationApi
-import java.util.LinkedList
-data class Transactions(val transactions: LinkedList<Transaction>)
+@Serializable
+data class Transactions(val transactions: List<Transaction>)
+
+@Serializable
+sealed class Transaction {
+ abstract val transactionId: String
+ abstract val timestamp: Timestamp
+ abstract val pending: Boolean
+ abstract val error: TransactionError?
+ abstract val amountRaw: Amount
+ abstract val amountEffective: Amount
-@JsonTypeInfo(use = NAME, include = PROPERTY, property = "type")
-@JsonSubTypes(
- Type(value = TransactionWithdrawal::class, name = "withdrawal"),
- Type(value = TransactionPayment::class, name = "payment"),
- Type(value = TransactionRefund::class, name = "refund"),
- Type(value = TransactionTip::class, name = "tip"),
- Type(value = TransactionRefresh::class, name = "refresh")
-)
-abstract class Transaction(
- val transactionId: String,
- val timestamp: Timestamp,
- val pending: Boolean,
- val error: TransactionError? = null,
- val amountRaw: Amount,
- val amountEffective: Amount
-) {
@get:DrawableRes
abstract val icon: Int
@@ -79,24 +67,26 @@ sealed class AmountType {
@Serializable
data class TransactionError(
private val ec: Int,
- private val hint: String?
+ private val hint: String? = null,
) {
val text get() = if (hint == null) "$ec" else "$ec $hint"
}
-@JsonTypeName("withdrawal")
+@Serializable
+@SerialName("withdrawal")
class TransactionWithdrawal(
- transactionId: String,
- timestamp: Timestamp,
- pending: Boolean,
+ override val transactionId: String,
+ override val timestamp: Timestamp,
+ override val pending: Boolean,
val exchangeBaseUrl: String,
val withdrawalDetails: WithdrawalDetails,
- error: TransactionError? = null,
- amountRaw: Amount,
- amountEffective: Amount
-) : Transaction(transactionId, timestamp, pending, error, amountRaw, amountEffective) {
+ override val error: TransactionError? = null,
+ override val amountRaw: Amount,
+ override val amountEffective: Amount
+) : Transaction() {
override val icon = R.drawable.transaction_withdrawal
override val detailPageLayout = R.layout.fragment_transaction_withdrawal
+ @Transient
override val amountType = AmountType.Positive
override fun getTitle(context: Context) = cleanExchange(exchangeBaseUrl)
override val generalTitleRes = R.string.withdraw_title
@@ -107,13 +97,10 @@ class TransactionWithdrawal(
)
}
-@JsonTypeInfo(use = NAME, include = PROPERTY, property = "type")
-@JsonSubTypes(
- Type(value = TalerBankIntegrationApi::class, name = "taler-bank-integration-api"),
- Type(value = ManualTransfer::class, name = "manual-transfer")
-)
+@Serializable
sealed class WithdrawalDetails {
- @JsonTypeName("manual-transfer")
+ @Serializable
+ @SerialName("manual-transfer")
class ManualTransfer(
/**
* Payto URIs that the exchange supports.
@@ -123,7 +110,8 @@ sealed class WithdrawalDetails {
val exchangePaytoUris: List<String>
) : WithdrawalDetails()
- @JsonTypeName("taler-bank-integration-api")
+ @Serializable
+ @SerialName("taler-bank-integration-api")
class TalerBankIntegrationApi(
/**
* Set to true if the bank has confirmed the withdrawal, false if not.
@@ -136,71 +124,77 @@ sealed class WithdrawalDetails {
/**
* If the withdrawal is unconfirmed, this can include a URL for user-initiated confirmation.
*/
- val bankConfirmationUrl: String?
+ val bankConfirmationUrl: String? = null,
) : WithdrawalDetails()
}
-@JsonTypeName("payment")
+@Serializable
+@SerialName("payment")
class TransactionPayment(
- transactionId: String,
- timestamp: Timestamp,
- pending: Boolean,
+ override val transactionId: String,
+ override val timestamp: Timestamp,
+ override val pending: Boolean,
val info: TransactionInfo,
val status: PaymentStatus,
- error: TransactionError? = null,
- amountRaw: Amount,
- amountEffective: Amount
-) : Transaction(transactionId, timestamp, pending, error, amountRaw, amountEffective) {
+ override val error: TransactionError? = null,
+ override val amountRaw: Amount,
+ override val amountEffective: Amount
+) : Transaction() {
override val icon = R.drawable.ic_cash_usd_outline
override val detailPageLayout = R.layout.fragment_transaction_payment
+ @Transient
override val amountType = AmountType.Negative
override fun getTitle(context: Context) = info.merchant.name
override val generalTitleRes = R.string.payment_title
}
+@Serializable
class TransactionInfo(
val orderId: String,
val merchant: ContractMerchant,
val summary: String,
- @get:JsonProperty("summary_i18n")
- val summaryI18n: Map<String, String>?,
+ @SerialName("summary_i18n")
+ val summaryI18n: Map<String, String>? = null,
val products: List<ContractProduct>,
val fulfillmentUrl: String
)
+@Serializable
enum class PaymentStatus {
- @JsonProperty("aborted")
+ @SerialName("aborted")
Aborted,
- @JsonProperty("failed")
+ @SerialName("failed")
Failed,
- @JsonProperty("paid")
+ @SerialName("paid")
Paid,
- @JsonProperty("accepted")
+ @SerialName("accepted")
Accepted
}
-@JsonTypeName("refund")
+@Serializable
+@SerialName("refund")
class TransactionRefund(
- transactionId: String,
- timestamp: Timestamp,
- pending: Boolean,
+ override val transactionId: String,
+ override val timestamp: Timestamp,
+ override val pending: Boolean,
val refundedTransactionId: String,
val info: TransactionInfo,
/**
* Part of the refund that couldn't be applied because the refund permissions were expired
*/
val amountInvalid: Amount? = null,
- error: TransactionError? = null,
- @JsonProperty("amountEffective") // TODO remove when fixed in wallet-core
- amountRaw: Amount,
- @JsonProperty("amountRaw") // TODO remove when fixed in wallet-core
- amountEffective: Amount
-) : Transaction(transactionId, timestamp, pending, error, amountRaw, amountEffective) {
+ override val error: TransactionError? = null,
+ @SerialName("amountEffective") // TODO remove when fixed in wallet-core
+ override val amountRaw: Amount,
+ @SerialName("amountRaw") // TODO remove when fixed in wallet-core
+ override val amountEffective: Amount
+) : Transaction() {
override val icon = R.drawable.transaction_refund
override val detailPageLayout = R.layout.fragment_transaction_payment
+ @Transient
override val amountType = AmountType.Positive
override fun getTitle(context: Context): String {
return context.getString(R.string.transaction_refund_from, info.merchant.name)
@@ -209,20 +203,22 @@ class TransactionRefund(
override val generalTitleRes = R.string.refund_title
}
-@JsonTypeName("tip")
+@Serializable
+@SerialName("tip")
class TransactionTip(
- transactionId: String,
- timestamp: Timestamp,
- pending: Boolean,
+ override val transactionId: String,
+ override val timestamp: Timestamp,
+ override val pending: Boolean,
// TODO status: TipStatus,
val exchangeBaseUrl: String,
val merchant: ContractMerchant,
- error: TransactionError? = null,
- amountRaw: Amount,
- amountEffective: Amount
-) : Transaction(transactionId, timestamp, pending, error, amountRaw, amountEffective) {
+ override val error: TransactionError? = null,
+ override val amountRaw: Amount,
+ override val amountEffective: Amount
+) : Transaction() {
override val icon = R.drawable.transaction_tip_accepted // TODO different when declined
override val detailPageLayout = R.layout.fragment_transaction_payment
+ @Transient
override val amountType = AmountType.Positive
override fun getTitle(context: Context): String {
return context.getString(R.string.transaction_tip_from, merchant.name)
@@ -231,18 +227,20 @@ class TransactionTip(
override val generalTitleRes = R.string.tip_title
}
-@JsonTypeName("refresh")
+@Serializable
+@SerialName("refresh")
class TransactionRefresh(
- transactionId: String,
- timestamp: Timestamp,
- pending: Boolean,
+ override val transactionId: String,
+ override val timestamp: Timestamp,
+ override val pending: Boolean,
val exchangeBaseUrl: String,
- error: TransactionError? = null,
- amountRaw: Amount,
- amountEffective: Amount
-) : Transaction(transactionId, timestamp, pending, error, amountRaw, amountEffective) {
+ override val error: TransactionError? = null,
+ override val amountRaw: Amount,
+ override val amountEffective: Amount
+) : Transaction() {
override val icon = R.drawable.transaction_refresh
override val detailPageLayout = R.layout.fragment_transaction_withdrawal
+ @Transient
override val amountType = AmountType.Negative
override fun getTitle(context: Context): String {
return context.getString(R.string.transaction_refresh)
diff --git a/wallet/src/test/java/net/taler/wallet/backend/WalletResponseTest.kt b/wallet/src/test/java/net/taler/wallet/backend/WalletResponseTest.kt
index a81626c..4872149 100644
--- a/wallet/src/test/java/net/taler/wallet/backend/WalletResponseTest.kt
+++ b/wallet/src/test/java/net/taler/wallet/backend/WalletResponseTest.kt
@@ -16,15 +16,7 @@
package net.taler.wallet.backend
-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 kotlinx.serialization.json.Json
-import net.taler.lib.common.Amount
-import net.taler.lib.common.AmountMixin
-import net.taler.lib.common.Timestamp
-import net.taler.lib.common.TimestampMixin
import net.taler.wallet.balances.BalanceResponse
import org.junit.Assert.assertEquals
import org.junit.Test
@@ -35,12 +27,6 @@ class WalletResponseTest {
ignoreUnknownKeys = true
}
- private val mapper = ObjectMapper()
- .registerModule(KotlinModule())
- .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
- .addMixIn(Amount::class.java, AmountMixin::class.java)
- .addMixIn(Timestamp::class.java, TimestampMixin::class.java)
-
@Test
fun testBalanceResponse() {
val serializer = WalletResponse.Success.serializer(BalanceResponse.serializer())
@@ -82,8 +68,6 @@ class WalletResponseTest {
}
""".trimIndent()
val info = json.decodeFromString(WalletErrorInfo.serializer(), infoJson)
- val infoJackson: WalletErrorInfo = mapper.readValue(infoJson)
println(info.userFacingMsg)
- assertEquals(info.userFacingMsg, infoJackson.userFacingMsg)
}
}