summaryrefslogtreecommitdiff
path: root/merchant-lib
diff options
context:
space:
mode:
Diffstat (limited to 'merchant-lib')
-rw-r--r--merchant-lib/build.gradle29
-rw-r--r--merchant-lib/src/main/AndroidManifest.xml3
-rw-r--r--merchant-lib/src/main/java/net/taler/merchantlib/MerchantApi.kt63
-rw-r--r--merchant-lib/src/main/java/net/taler/merchantlib/OrderHistory.kt4
-rw-r--r--merchant-lib/src/main/java/net/taler/merchantlib/Orders.kt12
-rw-r--r--merchant-lib/src/main/java/net/taler/merchantlib/Refunds.kt3
-rw-r--r--merchant-lib/src/main/java/net/taler/merchantlib/Response.kt18
-rw-r--r--merchant-lib/src/test/java/net/taler/merchantlib/MerchantApiTest.kt32
-rw-r--r--merchant-lib/src/test/java/net/taler/merchantlib/MockHttpClient.kt24
9 files changed, 99 insertions, 89 deletions
diff --git a/merchant-lib/build.gradle b/merchant-lib/build.gradle
index a173cce..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,6 +41,13 @@ android {
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_17
+ targetCompatibility = JavaVersion.VERSION_17
+ }
+ kotlinOptions {
+ jvmTarget = "17"
+ }
}
dependencies {
@@ -53,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:4.13.1'
+ 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.3.9'
+ 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 a467c41..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
@@ -35,47 +38,47 @@ import net.taler.merchantlib.Response.Companion.response
class MerchantApi(
private val httpClient: HttpClient = getDefaultHttpClient(),
- private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO
+ private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO,
) {
suspend fun getConfig(baseUrl: String): Response<ConfigResponse> = withContext(ioDispatcher) {
response {
- httpClient.get("$baseUrl/config") as ConfigResponse
+ httpClient.get("$baseUrl/config").body()
}
}
suspend fun postOrder(
merchantConfig: MerchantConfig,
- orderRequest: PostOrderRequest
+ orderRequest: PostOrderRequest,
): 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()
}
}
suspend fun checkOrder(
merchantConfig: MerchantConfig,
- orderId: String
+ orderId: String,
): Response<CheckPaymentResponse> = withContext(ioDispatcher) {
response {
httpClient.get(merchantConfig.urlFor("private/orders/$orderId")) {
- header(Authorization, "ApiKey ${merchantConfig.apiKey}")
- } as CheckPaymentResponse
+ auth(merchantConfig)
+ }.body()
}
}
suspend fun deleteOrder(
merchantConfig: MerchantConfig,
- orderId: String
+ orderId: String,
): Response<Unit> = withContext(ioDispatcher) {
response {
httpClient.delete(merchantConfig.urlFor("private/orders/$orderId")) {
- header(Authorization, "ApiKey ${merchantConfig.apiKey}")
- } as Unit
+ auth(merchantConfig)
+ }.body()
}
}
@@ -83,41 +86,41 @@ class MerchantApi(
withContext(ioDispatcher) {
response {
httpClient.get(merchantConfig.urlFor("private/orders")) {
- header(Authorization, "ApiKey ${merchantConfig.apiKey}")
- } as OrderHistory
+ auth(merchantConfig)
+ }.body()
}
}
suspend fun giveRefund(
merchantConfig: MerchantConfig,
orderId: String,
- request: RefundRequest
+ request: RefundRequest,
): 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 1b63900..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 e.toString()
+ 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 63188f9..cf627c6 100644
--- a/merchant-lib/src/test/java/net/taler/merchantlib/MerchantApiTest.kt
+++ b/merchant-lib/src/test/java/net/taler/merchantlib/MerchantApiTest.kt
@@ -18,12 +18,12 @@ package net.taler.merchantlib
import io.ktor.http.HttpStatusCode.Companion.NotFound
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestCoroutineDispatcher
-import kotlinx.coroutines.test.runBlockingTest
+import kotlinx.coroutines.runBlocking
+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"
@@ -41,7 +41,7 @@ class MerchantApiTest {
private val orderId = "orderIdFoo"
@Test
- fun testGetConfig() = runBlockingTest {
+ fun testGetConfig() = runBlocking {
httpClient.giveJsonResponse("https://backend.int.taler.net/config") {
"""
{
@@ -56,7 +56,7 @@ class MerchantApiTest {
}
@Test
- fun testPostOrder() = runBlockingTest {
+ fun testPostOrder() = runBlocking {
val product = ContractProduct(
productId = "foo",
description = "bar",
@@ -80,7 +80,7 @@ class MerchantApiTest {
{
"product_id": "${product.productId}",
"description": "${product.description}",
- "price": "${product.price.toJSONString()}",
+ "price": "${product.price!!.toJSONString()}",
"quantity": ${product.quantity}
}
]
@@ -113,7 +113,7 @@ class MerchantApiTest {
}
@Test
- fun testCheckOrder() = runBlockingTest {
+ fun testCheckOrder() = runBlocking {
val unpaidResponse = CheckPaymentResponse.Unpaid(false, "http://taler.net/foo")
httpClient.giveJsonResponse("http://example.net/instances/testInstance/private/orders/$orderId") {
"""{
@@ -142,7 +142,7 @@ class MerchantApiTest {
}
@Test
- fun testDeleteOrder() = runBlockingTest {
+ fun testDeleteOrder() = runBlocking {
httpClient.giveJsonResponse("http://example.net/instances/testInstance/private/orders/$orderId") {
"{}"
}
@@ -165,14 +165,14 @@ class MerchantApiTest {
}
@Test
- fun testGetOrderHistory() = runBlockingTest {
+ fun testGetOrderHistory() = runBlocking {
httpClient.giveJsonResponse("http://example.net/instances/testInstance/private/orders") {
"""{ "orders": [
{
"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,12 +210,12 @@ 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)
}
}
@Test
- fun testGiveRefund() = runBlockingTest {
+ fun testGiveRefund() = runBlocking {
httpClient.giveJsonResponse("http://example.net/instances/testInstance/private/orders/$orderId/refund") {
"""{
"taler_refund_uri": "taler://refund/foo/bar"
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)