summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTorsten Grote <t@grobox.de>2020-08-04 12:46:48 -0300
committerTorsten Grote <t@grobox.de>2020-08-04 12:48:19 -0300
commita35337b991f8b3d38c3480fb76acd9fc6df47885 (patch)
treea7389fd479cc13292de41db5b64ec5ffc848ae9d
parent7e445c534ed34bb8097920d3505130da5c3b1d17 (diff)
downloadwallet-kotlin-a35337b991f8b3d38c3480fb76acd9fc6df47885.tar.gz
wallet-kotlin-a35337b991f8b3d38c3480fb76acd9fc6df47885.tar.bz2
wallet-kotlin-a35337b991f8b3d38c3480fb76acd9fc6df47885.zip
Add exchange management to public wallet API
The API already works, but data is stored in-memory only. There's a live integration test available, but ignored in CI.
-rw-r--r--src/commonMain/kotlin/net/taler/wallet/kotlin/WalletApi.kt51
-rw-r--r--src/commonMain/kotlin/net/taler/wallet/kotlin/exchange/Exchange.kt37
-rw-r--r--src/commonMain/kotlin/net/taler/wallet/kotlin/operations/Withdraw.kt7
-rw-r--r--src/commonTest/kotlin/net/taler/wallet/kotlin/WalletApiTest.kt59
4 files changed, 140 insertions, 14 deletions
diff --git a/src/commonMain/kotlin/net/taler/wallet/kotlin/WalletApi.kt b/src/commonMain/kotlin/net/taler/wallet/kotlin/WalletApi.kt
index 61a3a1a..11fd181 100644
--- a/src/commonMain/kotlin/net/taler/wallet/kotlin/WalletApi.kt
+++ b/src/commonMain/kotlin/net/taler/wallet/kotlin/WalletApi.kt
@@ -16,13 +16,25 @@
package net.taler.wallet.kotlin
+import io.ktor.client.HttpClient
+import net.taler.wallet.kotlin.crypto.Crypto
+import net.taler.wallet.kotlin.crypto.CryptoFactory
+import net.taler.wallet.kotlin.crypto.Signature
+import net.taler.wallet.kotlin.exchange.Exchange
+import net.taler.wallet.kotlin.exchange.ExchangeListItem
+import net.taler.wallet.kotlin.exchange.GetExchangeTosResult
import net.taler.wallet.kotlin.operations.Withdraw
import net.taler.wallet.kotlin.operations.WithdrawalDetails
import net.taler.wallet.kotlin.operations.WithdrawalDetailsForUri
public class WalletApi {
- private val withdrawManager = Withdraw()
+ private val httpClient: HttpClient = getDefaultHttpClient()
+ private val db: Db = DbFactory().openDb()
+ private val crypto: Crypto = CryptoFactory.getCrypto()
+ private val signature: Signature = Signature(crypto)
+ private val exchangeManager: Exchange = Exchange(crypto, signature, httpClient, db = db)
+ private val withdrawManager = Withdraw(httpClient, db, crypto, signature, exchangeManager)
public fun getVersions(): SupportedVersions {
return SupportedVersions(
@@ -42,7 +54,10 @@ public class WalletApi {
)
}
- public suspend fun getWithdrawalDetailsForAmount(exchangeBaseUrl: String, amount: Amount): WithdrawalDetails {
+ public suspend fun getWithdrawalDetailsForAmount(
+ exchangeBaseUrl: String,
+ amount: Amount
+ ): WithdrawalDetails {
val details = withdrawManager.getWithdrawalDetails(exchangeBaseUrl, amount)
return WithdrawalDetails(
tosAccepted = details.exchange.termsOfServiceAccepted,
@@ -51,4 +66,36 @@ public class WalletApi {
)
}
+ public suspend fun listExchanges(): List<ExchangeListItem> {
+ return db.listExchanges().mapNotNull { exchange ->
+ ExchangeListItem.fromExchangeRecord(exchange)
+ }
+ }
+
+ public suspend fun addExchange(exchangeBaseUrl: String): ExchangeListItem {
+ val exchange = exchangeManager.updateFromUrl(exchangeBaseUrl)
+ db.put(exchange)
+ return ExchangeListItem.fromExchangeRecord(exchange) ?: TODO("error handling")
+ }
+
+ public suspend fun getExchangeTos(exchangeBaseUrl: String): GetExchangeTosResult {
+ val record = db.getExchangeByBaseUrl(exchangeBaseUrl) ?: TODO("error handling")
+ return GetExchangeTosResult(
+ tos = record.termsOfServiceText ?: TODO("error handling"),
+ currentEtag = record.termsOfServiceLastEtag ?: TODO("error handling"),
+ acceptedEtag = record.termsOfServiceAcceptedEtag
+ )
+ }
+
+ public suspend fun setExchangeTosAccepted(exchangeBaseUrl: String, acceptedEtag: String) {
+ db.transaction {
+ val record = getExchangeByBaseUrl(exchangeBaseUrl) ?: TODO("error handling")
+ val updatedRecord = record.copy(
+ termsOfServiceAcceptedEtag = acceptedEtag,
+ termsOfServiceAcceptedTimestamp = Timestamp.now()
+ )
+ put(updatedRecord)
+ }
+ }
+
}
diff --git a/src/commonMain/kotlin/net/taler/wallet/kotlin/exchange/Exchange.kt b/src/commonMain/kotlin/net/taler/wallet/kotlin/exchange/Exchange.kt
index 3af430f..7a6ac7f 100644
--- a/src/commonMain/kotlin/net/taler/wallet/kotlin/exchange/Exchange.kt
+++ b/src/commonMain/kotlin/net/taler/wallet/kotlin/exchange/Exchange.kt
@@ -223,3 +223,40 @@ internal class Exchange(
}
}
+
+
+data class ExchangeListItem(
+ val exchangeBaseUrl: String,
+ val currency: String,
+ val paytoUris: List<String>
+) {
+ companion object {
+ fun fromExchangeRecord(exchange: ExchangeRecord): ExchangeListItem? {
+ return if (exchange.details == null || exchange.wireInfo == null) null
+ else ExchangeListItem(
+ exchangeBaseUrl = exchange.baseUrl,
+ currency = exchange.details.currency,
+ paytoUris = exchange.wireInfo.accounts.map {
+ it.paytoUri
+ }
+ )
+ }
+ }
+}
+
+data class GetExchangeTosResult(
+ /**
+ * Markdown version of the current ToS.
+ */
+ val tos: String,
+
+ /**
+ * Version tag of the current ToS.
+ */
+ val currentEtag: String,
+
+ /**
+ * Version tag of the last ToS that the user has accepted, if any.
+ */
+ val acceptedEtag: String? = null
+)
diff --git a/src/commonMain/kotlin/net/taler/wallet/kotlin/operations/Withdraw.kt b/src/commonMain/kotlin/net/taler/wallet/kotlin/operations/Withdraw.kt
index 3981012..e51e9ec 100644
--- a/src/commonMain/kotlin/net/taler/wallet/kotlin/operations/Withdraw.kt
+++ b/src/commonMain/kotlin/net/taler/wallet/kotlin/operations/Withdraw.kt
@@ -35,6 +35,7 @@ import net.taler.wallet.kotlin.exchange.DenominationStatus.Unverified
import net.taler.wallet.kotlin.exchange.DenominationStatus.VerifiedBad
import net.taler.wallet.kotlin.exchange.DenominationStatus.VerifiedGood
import net.taler.wallet.kotlin.exchange.Exchange
+import net.taler.wallet.kotlin.exchange.ExchangeListItem
import net.taler.wallet.kotlin.exchange.ExchangeRecord
import net.taler.wallet.kotlin.exchange.ExchangeWireInfo
import net.taler.wallet.kotlin.exchange.SelectedDenomination
@@ -286,12 +287,6 @@ data class WithdrawalDetailsForUri(
val possibleExchanges: List<ExchangeListItem>
)
-data class ExchangeListItem(
- val exchangeBaseUrl: String,
- val currency: String,
- val paytoUris: List<String>
-)
-
data class WithdrawalDetails(
/**
* Did the user accept the current version of the exchange's terms of service?
diff --git a/src/commonTest/kotlin/net/taler/wallet/kotlin/WalletApiTest.kt b/src/commonTest/kotlin/net/taler/wallet/kotlin/WalletApiTest.kt
index 9498792..7971be5 100644
--- a/src/commonTest/kotlin/net/taler/wallet/kotlin/WalletApiTest.kt
+++ b/src/commonTest/kotlin/net/taler/wallet/kotlin/WalletApiTest.kt
@@ -19,12 +19,19 @@ package net.taler.wallet.kotlin
import kotlin.test.Ignore
import kotlin.test.Test
import kotlin.test.assertEquals
+import kotlin.test.assertTrue
-@Ignore // Live-Test
+@Ignore // Live-Test which fails when test environment changes or is not available
class WalletApiTest {
private val api = WalletApi()
+ private val exchangeBaseUrl = "https://exchange.test.taler.net/"
+ private val withdrawUri =
+ "taler://withdraw/bank.test.taler.net/8adabbf8-fe61-4d31-a2d1-89350b5be8fa"
+ private val paytoUris = listOf("payto://x-taler-bank/bank.test.taler.net/Exchange")
+ private val tosEtag = "0"
+
@Test
fun testGetVersions() {
api.getVersions()
@@ -32,15 +39,55 @@ class WalletApiTest {
@Test
fun testGetWithdrawalDetails() = runCoroutine {
- val detailsForUri =
- api.getWithdrawalDetailsForUri("taler://withdraw/bank.test.taler.net/8adabbf8-fe61-4d31-a2d1-89350b5be8fa")
+ val detailsForUri = api.getWithdrawalDetailsForUri(withdrawUri)
assertEquals(Amount("TESTKUDOS", 5, 0), detailsForUri.amount)
- assertEquals("https://exchange.test.taler.net/", detailsForUri.defaultExchangeBaseUrl)
+ assertEquals(exchangeBaseUrl, detailsForUri.defaultExchangeBaseUrl)
- val detailsForAmount =
- api.getWithdrawalDetailsForAmount(detailsForUri.defaultExchangeBaseUrl!!, detailsForUri.amount)
+ val detailsForAmount = api.getWithdrawalDetailsForAmount(
+ detailsForUri.defaultExchangeBaseUrl!!,
+ detailsForUri.amount
+ )
assertEquals(Amount("TESTKUDOS", 5, 0), detailsForAmount.amountRaw)
assertEquals(Amount("TESTKUDOS", 4, 80000000), detailsForAmount.amountEffective)
+ assertEquals(false, detailsForAmount.tosAccepted)
+ }
+
+ @Test
+ fun testExchangeManagement() = runCoroutine {
+ // initially we have no exchanges
+ assertTrue(api.listExchanges().isEmpty())
+
+ // test exchange gets added correctly
+ val exchange = api.addExchange(exchangeBaseUrl)
+ assertEquals(exchangeBaseUrl, exchange.exchangeBaseUrl)
+ assertEquals("TESTKUDOS", exchange.currency)
+ assertEquals(paytoUris, exchange.paytoUris)
+
+ // added exchange got added to list
+ val exchanges = api.listExchanges()
+ assertEquals(1, exchanges.size)
+ assertEquals(exchange, exchanges[0])
+
+ // ToS are not accepted
+ val tosResult = api.getExchangeTos(exchangeBaseUrl)
+ assertEquals(null, tosResult.acceptedEtag)
+ assertEquals(tosEtag, tosResult.currentEtag)
+ assertTrue(tosResult.tos.length > 100)
+
+ // withdrawal details also show ToS as not accepted
+ val withdrawalDetails =
+ api.getWithdrawalDetailsForAmount(exchangeBaseUrl, Amount.zero("TESTKUDOS"))
+ assertEquals(false, withdrawalDetails.tosAccepted)
+
+ // accept ToS
+ api.setExchangeTosAccepted(exchangeBaseUrl, tosResult.currentEtag)
+ val newTosResult = api.getExchangeTos(exchangeBaseUrl)
+ assertEquals(newTosResult.currentEtag, newTosResult.acceptedEtag)
+
+ // withdrawal details now show ToS as accepted as well
+ val newWithdrawalDetails =
+ api.getWithdrawalDetailsForAmount(exchangeBaseUrl, Amount.zero("TESTKUDOS"))
+ assertEquals(true, newWithdrawalDetails.tosAccepted)
}
}