diff options
Diffstat (limited to 'merchant-lib')
9 files changed, 83 insertions, 78 deletions
diff --git a/merchant-lib/build.gradle b/merchant-lib/build.gradle index f53baa2..5d4bc48 100644 --- a/merchant-lib/build.gradle +++ b/merchant-lib/build.gradle @@ -21,15 +21,11 @@ plugins { } android { - compileSdkVersion 30 - //noinspection GradleDependency - buildToolsVersion "$build_tools_version" + namespace 'net.taler.merchantlib' + compileSdk 34 defaultConfig { minSdkVersion 21 - targetSdkVersion 30 - versionCode 1 - versionName "0.1" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" consumerProguardFiles "consumer-rules.pro" @@ -45,10 +41,12 @@ android { proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } - compileOptions { - sourceCompatibility = 1.8 - targetCompatibility = 1.8 + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + kotlinOptions { + jvmTarget = "17" } } @@ -58,9 +56,17 @@ dependencies { api "io.ktor:ktor-client:$ktor_version" api "io.ktor:ktor-client-okhttp:$ktor_version" api "io.ktor:ktor-client-serialization-jvm:$ktor_version" + api "io.ktor:ktor-client-content-negotiation:$ktor_version" + implementation "io.ktor:ktor-serialization-kotlinx-json:$ktor_version" + implementation "io.ktor:ktor-server-call-logging:$ktor_version" testImplementation "junit:junit:$junit_version" testImplementation "io.ktor:ktor-client-mock-jvm:$ktor_version" - testImplementation "io.ktor:ktor-client-logging-jvm:$ktor_version" - testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.5.1' + testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.3' +} + +tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).configureEach { + kotlinOptions { + freeCompilerArgs += "-opt-in=kotlin.RequiresOptIn" + } } diff --git a/merchant-lib/src/main/AndroidManifest.xml b/merchant-lib/src/main/AndroidManifest.xml index 1408b33..40b4a25 100644 --- a/merchant-lib/src/main/AndroidManifest.xml +++ b/merchant-lib/src/main/AndroidManifest.xml @@ -14,8 +14,7 @@ ~ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> --> -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="net.taler.merchantlib"> +<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <uses-permission android:name="android.permission.INTERNET" /> 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 0d22f91..950cea6 100644 --- a/merchant-lib/src/main/java/net/taler/merchantlib/MerchantApi.kt +++ b/merchant-lib/src/main/java/net/taler/merchantlib/MerchantApi.kt @@ -17,16 +17,19 @@ package net.taler.merchantlib import io.ktor.client.HttpClient +import io.ktor.client.call.body import io.ktor.client.engine.okhttp.OkHttp -import io.ktor.client.features.json.JsonFeature -import io.ktor.client.features.json.serializer.KotlinxSerializer +import io.ktor.client.plugins.contentnegotiation.ContentNegotiation +import io.ktor.client.request.HttpRequestBuilder import io.ktor.client.request.delete import io.ktor.client.request.get import io.ktor.client.request.header import io.ktor.client.request.post +import io.ktor.client.request.setBody import io.ktor.http.ContentType.Application.Json import io.ktor.http.HttpHeaders.Authorization import io.ktor.http.contentType +import io.ktor.serialization.kotlinx.json.json import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext @@ -40,7 +43,7 @@ class MerchantApi( suspend fun getConfig(baseUrl: String): Response<ConfigResponse> = withContext(ioDispatcher) { response { - httpClient.get("$baseUrl/config") as ConfigResponse + httpClient.get("$baseUrl/config").body() } } @@ -50,10 +53,10 @@ class MerchantApi( ): Response<PostOrderResponse> = withContext(ioDispatcher) { response { httpClient.post(merchantConfig.urlFor("private/orders")) { - header(Authorization, "ApiKey ${merchantConfig.apiKey}") + auth(merchantConfig) contentType(Json) - body = orderRequest - } as PostOrderResponse + setBody(orderRequest) + }.body() } } @@ -63,8 +66,8 @@ class MerchantApi( ): Response<CheckPaymentResponse> = withContext(ioDispatcher) { response { httpClient.get(merchantConfig.urlFor("private/orders/$orderId")) { - header(Authorization, "ApiKey ${merchantConfig.apiKey}") - } as CheckPaymentResponse + auth(merchantConfig) + }.body() } } @@ -74,8 +77,8 @@ class MerchantApi( ): Response<Unit> = withContext(ioDispatcher) { response { httpClient.delete(merchantConfig.urlFor("private/orders/$orderId")) { - header(Authorization, "ApiKey ${merchantConfig.apiKey}") - } as Unit + auth(merchantConfig) + }.body() } } @@ -83,8 +86,8 @@ class MerchantApi( withContext(ioDispatcher) { response { httpClient.get(merchantConfig.urlFor("private/orders")) { - header(Authorization, "ApiKey ${merchantConfig.apiKey}") - } as OrderHistory + auth(merchantConfig) + }.body() } } @@ -95,29 +98,29 @@ class MerchantApi( ): Response<RefundResponse> = withContext(ioDispatcher) { response { httpClient.post(merchantConfig.urlFor("private/orders/$orderId/refund")) { - header(Authorization, "ApiKey ${merchantConfig.apiKey}") + auth(merchantConfig) contentType(Json) - body = request - } as RefundResponse + setBody(request) + }.body() } } + + private fun HttpRequestBuilder.auth(merchantConfig: MerchantConfig) { + header(Authorization, "Bearer ${merchantConfig.apiKey}") + } } fun getDefaultHttpClient(): HttpClient = HttpClient(OkHttp) { + expectSuccess = true engine { config { retryOnConnectionFailure(true) } } - install(JsonFeature) { - serializer = getSerializer() + install(ContentNegotiation) { + json(Json { + encodeDefaults = false + ignoreUnknownKeys = true + }) } } - -fun getSerializer() = KotlinxSerializer( - Json { - encodeDefaults = false - ignoreUnknownKeys = true - classDiscriminator = "order_status" - } -) diff --git a/merchant-lib/src/main/java/net/taler/merchantlib/OrderHistory.kt b/merchant-lib/src/main/java/net/taler/merchantlib/OrderHistory.kt index dfd989b..b1ff5b1 100644 --- a/merchant-lib/src/main/java/net/taler/merchantlib/OrderHistory.kt +++ b/merchant-lib/src/main/java/net/taler/merchantlib/OrderHistory.kt @@ -18,8 +18,8 @@ package net.taler.merchantlib import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable -import net.taler.lib.common.Amount -import net.taler.lib.common.Timestamp +import net.taler.common.Amount +import net.taler.common.Timestamp @Serializable data class OrderHistory( 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 166ca3c..9348ded 100644 --- a/merchant-lib/src/main/java/net/taler/merchantlib/Orders.kt +++ b/merchant-lib/src/main/java/net/taler/merchantlib/Orders.kt @@ -16,18 +16,19 @@ package net.taler.merchantlib +import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable +import kotlinx.serialization.json.JsonClassDiscriminator import net.taler.common.ContractTerms -import net.taler.lib.android.CustomClassDiscriminator -import net.taler.lib.common.Duration +import net.taler.common.RelativeTime @Serializable data class PostOrderRequest( @SerialName("order") val contractTerms: ContractTerms, @SerialName("refund_delay") - val refundDelay: Duration? = null + val refundDelay: RelativeTime? = null ) @Serializable @@ -36,9 +37,10 @@ data class PostOrderResponse( val orderId: String ) +@OptIn(ExperimentalSerializationApi::class) @Serializable -sealed class CheckPaymentResponse: CustomClassDiscriminator { - override val discriminator: String = "order_status" +@JsonClassDiscriminator("order_status") +sealed class CheckPaymentResponse { abstract val paid: Boolean @Serializable diff --git a/merchant-lib/src/main/java/net/taler/merchantlib/Refunds.kt b/merchant-lib/src/main/java/net/taler/merchantlib/Refunds.kt index b78b571..58073fa 100644 --- a/merchant-lib/src/main/java/net/taler/merchantlib/Refunds.kt +++ b/merchant-lib/src/main/java/net/taler/merchantlib/Refunds.kt @@ -18,8 +18,7 @@ package net.taler.merchantlib import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable -import net.taler.lib.common.Amount - +import net.taler.common.Amount @Serializable data class RefundRequest( /** 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 6fd0e37..55de077 100644 --- a/merchant-lib/src/main/java/net/taler/merchantlib/Response.kt +++ b/merchant-lib/src/main/java/net/taler/merchantlib/Response.kt @@ -16,10 +16,8 @@ package net.taler.merchantlib -import io.ktor.client.call.receive -import io.ktor.client.features.ClientRequestException -import io.ktor.client.features.ResponseException -import io.ktor.client.features.ServerResponseException +import io.ktor.client.call.body +import io.ktor.client.plugins.ResponseException import kotlinx.serialization.Serializable class Response<out T> private constructor( @@ -64,16 +62,15 @@ class Response<out T> private constructor( } private suspend fun getFailureString(failure: Failure): String = when (failure.exception) { - is ClientRequestException -> getExceptionString(failure.exception) - is ServerResponseException -> getExceptionString(failure.exception) + is ResponseException -> getExceptionString(failure.exception) else -> failure.exception.toString() } private suspend fun getExceptionString(e: ResponseException): String { val response = e.response return try { - val error: Error = response.receive() - "Error ${error.code}: ${error.hint}" + val error: Error = response.body() + "Error ${error.code} (${response.status.value}): ${error.hint} ${error.detail}" } catch (ex: Exception) { "Status code: ${response.status.value}" } @@ -84,6 +81,7 @@ class Response<out T> private constructor( @Serializable private class Error( val code: Int?, - val hint: String? + val hint: String?, + val detail: String? = null, ) } 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 6abacfd..cf627c6 100644 --- a/merchant-lib/src/test/java/net/taler/merchantlib/MerchantApiTest.kt +++ b/merchant-lib/src/test/java/net/taler/merchantlib/MerchantApiTest.kt @@ -19,11 +19,11 @@ package net.taler.merchantlib import io.ktor.http.HttpStatusCode.Companion.NotFound import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.test.TestCoroutineDispatcher +import kotlinx.coroutines.test.UnconfinedTestDispatcher +import net.taler.common.Amount import net.taler.common.ContractProduct import net.taler.common.ContractTerms -import net.taler.lib.common.Amount -import net.taler.lib.common.Timestamp +import net.taler.common.Timestamp import net.taler.merchantlib.MockHttpClient.giveJsonResponse import net.taler.merchantlib.MockHttpClient.httpClient import org.junit.Assert.assertEquals @@ -33,7 +33,7 @@ import org.junit.Test @ExperimentalCoroutinesApi class MerchantApiTest { - private val api = MerchantApi(httpClient, TestCoroutineDispatcher()) + private val api = MerchantApi(httpClient, UnconfinedTestDispatcher()) private val merchantConfig = MerchantConfig( baseUrl = "http://example.net/instances/testInstance", apiKey = "apiKeyFooBar" @@ -80,7 +80,7 @@ class MerchantApiTest { { "product_id": "${product.productId}", "description": "${product.description}", - "price": "${product.price.toJSONString()}", + "price": "${product.price!!.toJSONString()}", "quantity": ${product.quantity} } ] @@ -172,7 +172,7 @@ class MerchantApiTest { "order_id": "2020.217-0281FGXCS25P2", "row_id": 183, "timestamp": { - "t_ms": 1596542338000 + "t_s": 1596542338 }, "amount": "TESTKUDOS:1", "summary": "Chips", @@ -183,7 +183,7 @@ class MerchantApiTest { "order_id": "2020.216-01G2ZPXSP6BYT", "row_id": 154, "timestamp": { - "t_ms": 1596468174000 + "t_s": 1596468174 }, "amount": "TESTKUDOS:0.8", "summary": "Peanuts", @@ -202,7 +202,7 @@ class MerchantApiTest { assertEquals(true, order1.paid) assertEquals(true, order1.refundable) assertEquals("Chips", order1.summary) - assertEquals(Timestamp(1596542338000), order1.timestamp) + assertEquals(Timestamp.fromMillis(1596542338000), order1.timestamp) val order2 = it.orders[1] assertEquals(Amount("TESTKUDOS", 0, 80000000), order2.amount) @@ -210,7 +210,7 @@ class MerchantApiTest { assertEquals(false, order2.paid) assertEquals(false, order2.refundable) assertEquals("Peanuts", order2.summary) - assertEquals(Timestamp(1596468174000), order2.timestamp) + assertEquals(Timestamp.fromMillis(1596468174000), order2.timestamp) } } diff --git a/merchant-lib/src/test/java/net/taler/merchantlib/MockHttpClient.kt b/merchant-lib/src/test/java/net/taler/merchantlib/MockHttpClient.kt index c8e6f22..32c7ee1 100644 --- a/merchant-lib/src/test/java/net/taler/merchantlib/MockHttpClient.kt +++ b/merchant-lib/src/test/java/net/taler/merchantlib/MockHttpClient.kt @@ -20,11 +20,7 @@ import io.ktor.client.HttpClient import io.ktor.client.engine.mock.MockEngine import io.ktor.client.engine.mock.MockEngineConfig import io.ktor.client.engine.mock.respond -import io.ktor.client.features.json.JsonFeature -import io.ktor.client.features.logging.LogLevel -import io.ktor.client.features.logging.Logger -import io.ktor.client.features.logging.Logging -import io.ktor.client.features.logging.SIMPLE +import io.ktor.client.plugins.contentnegotiation.ContentNegotiation import io.ktor.http.ContentType.Application.Json import io.ktor.http.HttpStatusCode import io.ktor.http.Url @@ -32,29 +28,31 @@ import io.ktor.http.content.TextContent import io.ktor.http.fullPath import io.ktor.http.headersOf import io.ktor.http.hostWithPort +import io.ktor.serialization.kotlinx.json.json +import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json.Default.parseToJsonElement import org.junit.Assert.assertEquals object MockHttpClient { val httpClient = HttpClient(MockEngine) { - install(JsonFeature) { - serializer = getSerializer() - } - install(Logging) { - logger = Logger.SIMPLE - level = LogLevel.ALL - } engine { addHandler { error("No response handler set") } } + expectSuccess = true + install(ContentNegotiation) { + json(Json { + encodeDefaults = false + ignoreUnknownKeys = true + }) + } } fun HttpClient.giveJsonResponse( url: String, expectedBody: String? = null, statusCode: HttpStatusCode = HttpStatusCode.OK, - jsonProducer: () -> String + jsonProducer: () -> String, ) { val httpConfig = engineConfig as MockEngineConfig httpConfig.requestHandlers.removeAt(0) |