gnunet-svn
[Top][All Lists]
Advanced

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

[taler-taler-android] 04/04: [wallet] upgrade wallet-core and adapt paym


From: gnunet
Subject: [taler-taler-android] 04/04: [wallet] upgrade wallet-core and adapt payment API
Date: Tue, 11 Aug 2020 22:35:48 +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 3ab6f1569b307b155de6049ad7207e10bdf97567
Author: Torsten Grote <t@grobox.de>
AuthorDate: Tue Aug 11 17:35:16 2020 -0300

    [wallet] upgrade wallet-core and adapt payment API
---
 wallet/build.gradle                                |  6 +-
 .../main/java/net/taler/wallet/MainViewModel.kt    |  2 +-
 .../net/taler/wallet/backend/WalletBackendApi.kt   | 21 +++++++
 .../net/taler/wallet/backend/WalletResponse.kt     | 45 ++++++++++++--
 .../net/taler/wallet/payment/PaymentManager.kt     | 72 ++++++++++++----------
 .../net/taler/wallet/payment/PaymentResponses.kt   | 17 +++++
 .../taler/wallet/payment/PromptPaymentFragment.kt  |  7 +--
 .../net/taler/wallet/transactions/Transactions.kt  |  2 +
 .../net/taler/wallet/backend/WalletResponseTest.kt | 42 +++++++++++--
 9 files changed, 163 insertions(+), 51 deletions(-)

diff --git a/wallet/build.gradle b/wallet/build.gradle
index d0fd97d..329e271 100644
--- a/wallet/build.gradle
+++ b/wallet/build.gradle
@@ -24,7 +24,7 @@ plugins {
     id "de.undercouch.download"
 }
 
-def walletCoreVersion = "v0.7.1-dev.18"
+def walletCoreVersion = "v0.7.1-dev.19"
 
 static def versionCodeEpoch() {
     return (new Date().getTime() / 1000).toInteger()
@@ -48,7 +48,7 @@ android {
         minSdkVersion 24
         targetSdkVersion 29
         versionCode 6
-        versionName "0.7.1.dev.18"
+        versionName "0.7.1.dev.19"
         testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
         buildConfigField "String", "WALLET_CORE_VERSION", 
"\"$walletCoreVersion\""
     }
@@ -103,7 +103,7 @@ dependencies {
     implementation 'net.taler:akono:0.1'
 
     implementation 'androidx.preference:preference:1.1.1'
-    implementation 'com.google.android.material:material:1.1.0'
+    implementation 'com.google.android.material:material:1.2.0'
     implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
 
     // Lists and Selection
diff --git a/wallet/src/main/java/net/taler/wallet/MainViewModel.kt 
b/wallet/src/main/java/net/taler/wallet/MainViewModel.kt
index 330704e..24a8f1e 100644
--- a/wallet/src/main/java/net/taler/wallet/MainViewModel.kt
+++ b/wallet/src/main/java/net/taler/wallet/MainViewModel.kt
@@ -99,7 +99,7 @@ class MainViewModel(val app: Application) : 
AndroidViewModel(app) {
     }
 
     val withdrawManager = WithdrawManager(api, viewModelScope)
-    val paymentManager = PaymentManager(api, mapper)
+    val paymentManager = PaymentManager(api, viewModelScope, mapper)
     val pendingOperationsManager: PendingOperationsManager = 
PendingOperationsManager(api)
     val transactionManager: TransactionManager = TransactionManager(api, 
viewModelScope, mapper)
     val refundManager = RefundManager(api)
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 ea8f26f..5ca2255 100644
--- a/wallet/src/main/java/net/taler/wallet/backend/WalletBackendApi.kt
+++ b/wallet/src/main/java/net/taler/wallet/backend/WalletBackendApi.kt
@@ -26,6 +26,8 @@ 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
@@ -166,6 +168,25 @@ class WalletBackendApi(
         }
     }
 
+    suspend inline fun <reified T> request(
+        operation: String,
+        mapper: ObjectMapper,
+        noinline args: (JSONObject.() -> JSONObject)? = null
+    ): WalletResponse<T> = withContext(Dispatchers.Default) {
+        suspendCoroutine<WalletResponse<T>> { cont ->
+            sendRequest(operation, args?.invoke(JSONObject())) { isError, 
message ->
+                val response = if (isError) {
+                    val error: WalletErrorInfo = 
mapper.readValue(message.toString())
+                    WalletResponse.Error<T>(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 05a53f3..ab3d42e 100644
--- a/wallet/src/main/java/net/taler/wallet/backend/WalletResponse.kt
+++ b/wallet/src/main/java/net/taler/wallet/backend/WalletResponse.kt
@@ -16,10 +16,23 @@
 
 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.Decoder
+import kotlinx.serialization.Encoder
+import kotlinx.serialization.KSerializer
+import kotlinx.serialization.PrimitiveDescriptor
+import kotlinx.serialization.PrimitiveKind.STRING
 import kotlinx.serialization.SerialName
 import kotlinx.serialization.Serializable
+import kotlinx.serialization.json.JsonInput
+import kotlinx.serialization.json.JsonObject
 import org.json.JSONObject
 
+
 @Serializable
 sealed class WalletResponse<T> {
     @Serializable
@@ -58,9 +71,10 @@ data class WalletErrorInfo(
     // for the instance of the error.
     val message: String,
 
-    // Error details, type depends
-    // on talerErrorCode
-    val details: String?
+    // Error details, type depends on talerErrorCode
+    @Serializable(JSONObjectDeserializer::class)
+    @JsonDeserialize(using = JsonObjectDeserializer::class)
+    val details: JSONObject?
 ) {
     val userFacingMsg: String
         get() {
@@ -68,8 +82,7 @@ data class WalletErrorInfo(
                 append(talerErrorCode)
                 append(" ")
                 append(message)
-                details?.let { it ->
-                    val details = JSONObject(it)
+                details?.let { details ->
                     details.optJSONObject("errorResponse")?.let { 
errorResponse ->
                         append("\n\n")
                         append(errorResponse.optString("code"))
@@ -80,3 +93,25 @@ data class WalletErrorInfo(
             }.toString()
         }
 }
+
+class JSONObjectDeserializer : KSerializer<JSONObject> {
+
+    override val descriptor = PrimitiveDescriptor("JSONObjectDeserializer", 
STRING)
+
+    override fun deserialize(decoder: Decoder): JSONObject {
+        val input = decoder as JsonInput
+        val tree = input.decodeJson() as JsonObject
+        return JSONObject(tree.toString())
+    }
+
+    override fun serialize(encoder: Encoder, value: 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 db21da4..041fcd3 100644
--- a/wallet/src/main/java/net/taler/wallet/payment/PaymentManager.kt
+++ b/wallet/src/main/java/net/taler/wallet/payment/PaymentManager.kt
@@ -22,11 +22,13 @@ 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.common.Amount
 import net.taler.common.ContractTerms
 import net.taler.wallet.TAG
 import net.taler.wallet.backend.WalletBackendApi
-import net.taler.wallet.getErrorString
+import net.taler.wallet.backend.WalletErrorInfo
 import net.taler.wallet.payment.PayStatus.AlreadyPaid
 import net.taler.wallet.payment.PayStatus.InsufficientBalance
 import net.taler.wallet.payment.PreparePayResponse.AlreadyConfirmedResponse
@@ -47,14 +49,20 @@ sealed class PayStatus {
         val amountEffective: Amount
     ) : PayStatus()
 
-    data class InsufficientBalance(val contractTerms: ContractTerms) : 
PayStatus()
+    data class InsufficientBalance(
+        val contractTerms: ContractTerms,
+        val amountRaw: Amount
+    ) : PayStatus()
+
+    // TODO bring user to fulfilment URI
     object AlreadyPaid : PayStatus()
     data class Error(val error: String) : PayStatus()
     data class Success(val currency: String) : PayStatus()
 }
 
 class PaymentManager(
-    private val walletBackendApi: WalletBackendApi,
+    private val api: WalletBackendApi,
+    private val scope: CoroutineScope,
     private val mapper: ObjectMapper
 ) {
 
@@ -65,21 +73,21 @@ class PaymentManager(
     internal val detailsShown: LiveData<Boolean> = mDetailsShown
 
     @UiThread
-    fun preparePay(url: String) {
+    fun preparePay(url: String) = scope.launch {
         mPayStatus.value = PayStatus.Loading
         mDetailsShown.value = false
-
-        val args = JSONObject(mapOf("talerPayUri" to url))
-        walletBackendApi.sendRequest("preparePay", args) { isError, result ->
-            if (isError) {
-                handleError("preparePay", getErrorString(result))
-                return@sendRequest
-            }
-            val response: PreparePayResponse = 
mapper.readValue(result.toString())
-            Log.e(TAG, "PreparePayResponse $response")
+        api.request<PreparePayResponse>("preparePay", mapper) {
+            put("talerPayUri", url)
+        }.onError {
+            handleError("preparePay", it)
+        }.onSuccess { response ->
+            Log.e(TAG, "PreparePayResponse $response") // TODO remove
             mPayStatus.value = when (response) {
                 is PaymentPossibleResponse -> response.toPayStatusPrepared()
-                is InsufficientBalanceResponse -> 
InsufficientBalance(response.contractTerms)
+                is InsufficientBalanceResponse -> InsufficientBalance(
+                    response.contractTerms,
+                    response.amountRaw
+                )
                 is AlreadyConfirmedResponse -> AlreadyPaid
             }
         }
@@ -99,13 +107,12 @@ class PaymentManager(
         return terms
     }
 
-    fun confirmPay(proposalId: String, currency: String) {
-        val args = JSONObject(mapOf("proposalId" to proposalId))
-        walletBackendApi.sendRequest("confirmPay", args) { isError, result ->
-            if (isError) {
-                handleError("preparePay", getErrorString(result))
-                return@sendRequest
-            }
+    fun confirmPay(proposalId: String, currency: String) = scope.launch {
+        api.request("confirmPay", ConfirmPayResult.serializer()) {
+            put("proposalId", proposalId)
+        }.onError {
+            handleError("confirmPay", it)
+        }.onSuccess {
             mPayStatus.postValue(PayStatus.Success(currency))
         }
     }
@@ -119,17 +126,14 @@ class PaymentManager(
         resetPayStatus()
     }
 
-    internal fun abortProposal(proposalId: String) {
-        val args = JSONObject(mapOf("proposalId" to proposalId))
-
+    internal fun abortProposal(proposalId: String) = scope.launch {
         Log.i(TAG, "aborting proposal")
-
-        walletBackendApi.sendRequest("abortProposal", args) { isError, result 
->
-            if (isError) {
-                handleError("abortProposal", getErrorString(result))
-                Log.e(TAG, "received error response to abortProposal")
-                return@sendRequest
-            }
+        api.request<String>("abortProposal", mapper) {
+            put("proposalId", proposalId)
+        }.onError {
+            Log.e(TAG, "received error response to abortProposal")
+            handleError("abortProposal", it)
+        }.onSuccess {
             mPayStatus.postValue(PayStatus.None)
         }
     }
@@ -145,9 +149,9 @@ class PaymentManager(
         mPayStatus.value = PayStatus.None
     }
 
-    private fun handleError(operation: String, msg: String) {
-        Log.e(TAG, "got $operation error result $msg")
-        mPayStatus.value = PayStatus.Error(msg)
+    private fun handleError(operation: String, error: WalletErrorInfo) {
+        Log.e(TAG, "got $operation error result $error")
+        mPayStatus.value = PayStatus.Error(error.userFacingMsg)
     }
 
 }
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 1ff8867..120489d 100644
--- a/wallet/src/main/java/net/taler/wallet/payment/PaymentResponses.kt
+++ b/wallet/src/main/java/net/taler/wallet/payment/PaymentResponses.kt
@@ -19,8 +19,11 @@ 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.Amount
 import net.taler.common.ContractTerms
+import net.taler.wallet.transactions.TransactionError
 
 @JsonTypeInfo(use = NAME, property = "status")
 sealed class PreparePayResponse(open val proposalId: String) {
@@ -42,6 +45,7 @@ sealed class PreparePayResponse(open val proposalId: String) {
     @JsonTypeName("insufficient-balance")
     data class InsufficientBalanceResponse(
         override val proposalId: String,
+        val amountRaw: Amount,
         val contractTerms: ContractTerms
     ) : PreparePayResponse(proposalId)
 
@@ -52,6 +56,8 @@ sealed class PreparePayResponse(open val proposalId: String) {
          * Did the payment succeed?
          */
         val paid: Boolean,
+        val amountRaw: Amount,
+        val amountEffective: Amount,
 
         /**
          * Redirect URL for the fulfillment page, only given if paid==true.
@@ -59,3 +65,14 @@ sealed class PreparePayResponse(open val proposalId: String) 
{
         val nextUrl: String?
     ) : PreparePayResponse(proposalId)
 }
+
+@Serializable
+sealed class ConfirmPayResult {
+    @Serializable
+    @SerialName("done")
+    data class Done(val nextUrl: String) : ConfirmPayResult()
+
+    @Serializable
+    @SerialName("pending")
+    data class Pending(val lastError: TransactionError) : ConfirmPayResult()
+}
diff --git 
a/wallet/src/main/java/net/taler/wallet/payment/PromptPaymentFragment.kt 
b/wallet/src/main/java/net/taler/wallet/payment/PromptPaymentFragment.kt
index ce2b6f7..40664e3 100644
--- a/wallet/src/main/java/net/taler/wallet/payment/PromptPaymentFragment.kt
+++ b/wallet/src/main/java/net/taler/wallet/payment/PromptPaymentFragment.kt
@@ -96,7 +96,7 @@ class PromptPaymentFragment : Fragment(), 
ProductImageClickListener {
             is PayStatus.Prepared -> {
                 showLoading(false)
                 val fees = payStatus.amountEffective - payStatus.amountRaw
-                showOrder(payStatus.contractTerms, fees)
+                showOrder(payStatus.contractTerms, payStatus.amountRaw, fees)
                 confirmButton.isEnabled = true
                 confirmButton.setOnClickListener {
                     model.showProgressBar.value = true
@@ -110,7 +110,7 @@ class PromptPaymentFragment : Fragment(), 
ProductImageClickListener {
             }
             is PayStatus.InsufficientBalance -> {
                 showLoading(false)
-                showOrder(payStatus.contractTerms)
+                showOrder(payStatus.contractTerms, payStatus.amountRaw)
                 errorView.setText(R.string.payment_balance_insufficient)
                 errorView.fadeIn()
             }
@@ -142,11 +142,10 @@ class PromptPaymentFragment : Fragment(), 
ProductImageClickListener {
         }
     }
 
-    private fun showOrder(contractTerms: ContractTerms, totalFees: Amount? = 
null) {
+    private fun showOrder(contractTerms: ContractTerms, amount:Amount, 
totalFees: Amount? = null) {
         orderView.text = contractTerms.summary
         adapter.setItems(contractTerms.products)
         if (contractTerms.products.size == 1) 
paymentManager.toggleDetailsShown()
-        val amount = contractTerms.amount
         totalView.text = amount.toString()
         if (totalFees != null && !totalFees.isZero()) {
             feeView.text = getString(R.string.payment_fee, totalFees)
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 721522c..1ba7e79 100644
--- a/wallet/src/main/java/net/taler/wallet/transactions/Transactions.kt
+++ b/wallet/src/main/java/net/taler/wallet/transactions/Transactions.kt
@@ -27,6 +27,7 @@ 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.Serializable
 import net.taler.common.Amount
 import net.taler.common.ContractMerchant
 import net.taler.common.ContractProduct
@@ -74,6 +75,7 @@ sealed class AmountType {
     object Neutral : AmountType()
 }
 
+@Serializable
 data class TransactionError(
     private val ec: Int,
     private val hint: String?
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 b7d7c68..698c90a 100644
--- a/wallet/src/test/java/net/taler/wallet/backend/WalletResponseTest.kt
+++ b/wallet/src/test/java/net/taler/wallet/backend/WalletResponseTest.kt
@@ -16,17 +16,31 @@
 
 package net.taler.wallet.backend
 
-import junit.framework.Assert.assertEquals
-import kotlinx.serialization.UnstableDefault
+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 kotlinx.serialization.json.JsonConfiguration
+import net.taler.common.Amount
+import net.taler.common.AmountMixin
+import net.taler.common.Timestamp
+import net.taler.common.TimestampMixin
 import net.taler.wallet.balances.BalanceResponse
+import org.junit.Assert.assertEquals
 import org.junit.Test
 
-@UnstableDefault
 class WalletResponseTest {
 
-    private val json = Json(JsonConfiguration(ignoreUnknownKeys = true))
+    private val json = Json(
+        JsonConfiguration.Stable.copy(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() {
@@ -53,4 +67,24 @@ class WalletResponseTest {
         )
         assertEquals(1, response.result.balances.size)
     }
+
+    @Test
+    fun testWalletErrorInfo() {
+        val infoJson = """
+            {
+                "talerErrorCode":7001,
+                "talerErrorHint":"Error: WALLET_UNEXPECTED_EXCEPTION",
+                "details":{
+                  "httpStatusCode": 401,
+                  "requestUrl": 
"https:\/\/backend.demo.taler.net\/-\/FSF\/orders\/2020.224-02XC8W52BHH3G\/claim",
+                  "requestMethod": "POST"
+                },
+                "message":"unexpected exception: Error: BUG: invariant 
violation (purchase status)"
+            }
+        """.trimIndent()
+        val info = json.parse(WalletErrorInfo.serializer(), infoJson)
+        val infoJackson: WalletErrorInfo = mapper.readValue(infoJson)
+        println(info.userFacingMsg)
+        assertEquals(info.userFacingMsg, infoJackson.userFacingMsg)
+    }
 }

-- 
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]