From a8c811f6cdf4bf1b787ebaaa9fd220588fd1ffcf Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Wed, 22 Jul 2020 16:53:06 -0300 Subject: [pos] migrate order posting and checking to v1 API and merchant-lib --- .../main/java/net/taler/merchantlib/MerchantApi.kt | 48 ++++++++++++- .../java/net/taler/merchantlib/MerchantConfig.kt | 37 ++++++++++ .../java/net/taler/merchantlib/PostOrderRequest.kt | 83 ++++++++++++++++++++++ .../main/java/net/taler/merchantlib/Response.kt | 63 ++++++++++++++++ 4 files changed, 230 insertions(+), 1 deletion(-) create mode 100644 merchant-lib/src/main/java/net/taler/merchantlib/MerchantConfig.kt create mode 100644 merchant-lib/src/main/java/net/taler/merchantlib/PostOrderRequest.kt create mode 100644 merchant-lib/src/main/java/net/taler/merchantlib/Response.kt (limited to 'merchant-lib/src/main/java') 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 3406f78..335e42d 100644 --- a/merchant-lib/src/main/java/net/taler/merchantlib/MerchantApi.kt +++ b/merchant-lib/src/main/java/net/taler/merchantlib/MerchantApi.kt @@ -21,6 +21,16 @@ 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.request.get +import io.ktor.client.request.header +import io.ktor.client.request.post +import io.ktor.http.ContentType.Application.Json +import io.ktor.http.HttpHeaders.Authorization +import io.ktor.http.contentType +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonConfiguration +import net.taler.common.ContractTerms +import net.taler.merchantlib.Response.Companion.failure +import net.taler.merchantlib.Response.Companion.success class MerchantApi(private val httpClient: HttpClient) { @@ -30,10 +40,46 @@ class MerchantApi(private val httpClient: HttpClient) { return httpClient.get("$baseUrl/config") } + suspend fun postOrder( + merchantConfig: MerchantConfig, + contractTerms: ContractTerms + ): Response = response { + httpClient.post(merchantConfig.urlFor("private/orders")) { + header(Authorization, "ApiKey ${merchantConfig.apiKey}") + contentType(Json) + body = PostOrderRequest(contractTerms) + } as PostOrderResponse + } + + suspend fun checkOrder( + merchantConfig: MerchantConfig, + orderId: String + ): Response = response { + httpClient.get(merchantConfig.urlFor("private/orders/$orderId")) { + header(Authorization, "ApiKey ${merchantConfig.apiKey}") + } as CheckPaymentResponse + } + + private suspend fun response(request: suspend () -> T): Response { + return try { + success(request()) + } catch (e: Throwable) { + failure(e) + } + } } private fun getDefaultHttpClient(): HttpClient = HttpClient(OkHttp) { install(JsonFeature) { - serializer = KotlinxSerializer() + serializer = getSerializer() } } + +fun getSerializer() = KotlinxSerializer( + Json( + JsonConfiguration( + encodeDefaults = false, + ignoreUnknownKeys = true + ) + ) +) diff --git a/merchant-lib/src/main/java/net/taler/merchantlib/MerchantConfig.kt b/merchant-lib/src/main/java/net/taler/merchantlib/MerchantConfig.kt new file mode 100644 index 0000000..71185b9 --- /dev/null +++ b/merchant-lib/src/main/java/net/taler/merchantlib/MerchantConfig.kt @@ -0,0 +1,37 @@ +/* + * 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 + */ + +package net.taler.merchantlib + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class MerchantConfig( + @SerialName("base_url") + val baseUrl: String, + val instance: String, + @SerialName("api_key") + val apiKey: String +) { + fun urlFor(endpoint: String, params: Map? = null): String { + val sb = StringBuilder(baseUrl) + if (sb.last() != '/') sb.append('/') + sb.append("instances/$instance/") + sb.append(endpoint) + return sb.toString() + } +} diff --git a/merchant-lib/src/main/java/net/taler/merchantlib/PostOrderRequest.kt b/merchant-lib/src/main/java/net/taler/merchantlib/PostOrderRequest.kt new file mode 100644 index 0000000..a6e74d6 --- /dev/null +++ b/merchant-lib/src/main/java/net/taler/merchantlib/PostOrderRequest.kt @@ -0,0 +1,83 @@ +/* + * 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 + */ + +package net.taler.merchantlib + +import kotlinx.serialization.Decoder +import kotlinx.serialization.Encoder +import kotlinx.serialization.KSerializer +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlinx.serialization.Serializer +import kotlinx.serialization.json.JsonInput +import kotlinx.serialization.json.JsonObject +import net.taler.common.ContractTerms + +@Serializable +data class PostOrderRequest( + @SerialName("order") + val contractTerms: ContractTerms + +) + +@Serializable +data class PostOrderResponse( + @SerialName("order_id") + val orderId: String +) + +@Serializable +sealed class CheckPaymentResponse { + abstract val paid: Boolean + + @Serializer(forClass = CheckPaymentResponse::class) + companion object : KSerializer { + override fun deserialize(decoder: Decoder): CheckPaymentResponse { + val input = decoder as JsonInput + val tree = input.decodeJson() as JsonObject + val paid = tree.getPrimitive("paid").boolean +// return if (paid) decoder.json.fromJson(Paid.serializer(), tree) +// else decoder.json.fromJson(Unpaid.serializer(), tree) + // manual parsing due to https://github.com/Kotlin/kotlinx.serialization/issues/576 + return if (paid) Paid( + refunded = tree.getPrimitive("refunded").boolean + ) else Unpaid( + talerPayUri = tree.getPrimitive("taler_pay_uri").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 + data class Unpaid( + override val paid: Boolean = false, + @SerialName("taler_pay_uri") + val talerPayUri: String, + @SerialName("already_paid_order_id") + val alreadyPaidOrderId: String? = null + ) : CheckPaymentResponse() + + @Serializable + data class Paid( + override val paid: Boolean = true, + val refunded: Boolean + ) : CheckPaymentResponse() + +} diff --git a/merchant-lib/src/main/java/net/taler/merchantlib/Response.kt b/merchant-lib/src/main/java/net/taler/merchantlib/Response.kt new file mode 100644 index 0000000..23fa101 --- /dev/null +++ b/merchant-lib/src/main/java/net/taler/merchantlib/Response.kt @@ -0,0 +1,63 @@ +/* + * 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 + */ + +package net.taler.merchantlib + +import io.ktor.client.call.receive +import io.ktor.client.features.ClientRequestException +import kotlinx.serialization.Serializable + +class Response private constructor( + private val value: Any? +) { + + companion object { + fun success(value: T): Response = + Response(value) + + fun failure(e: Throwable): Response = + Response(Failure(e)) + } + + val isFailure: Boolean get() = value is Failure + + suspend fun handle(onFailure: ((String) -> Any)? = null, onSuccess: ((T) -> Any)? = null) { + if (value is Failure) onFailure?.let { it(getFailureString(value)) } + else onSuccess?.let { + @Suppress("UNCHECKED_CAST") + it(value as T) + } + } + + private suspend fun getFailureString(failure: Failure): String = when (failure.exception) { + is ClientRequestException -> getExceptionString(failure.exception) + else -> failure.exception.toString() + } + + private suspend fun getExceptionString(e: ClientRequestException): String { + val error: Error = e.response.receive() + return "Error ${error.code}: ${error.hint}" + } + + private class Failure(val exception: Throwable) + + @Serializable + private class Error( + val code: Int?, + val hint: String? + ) + +} -- cgit v1.2.3