CheckoutBackend.kt
/*
* This file is part of GNU Taler
* (C) 2026 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 <http://www.gnu.org/licenses/>
*/
package net.taler.merchantpos.checkout
import net.taler.merchantlib.CheckPaymentResponse
import net.taler.merchantlib.ExternalRefundRequest
import net.taler.merchantlib.MerchantApi
import net.taler.merchantlib.MerchantConfig
import net.taler.merchantlib.PostOrderRequest
sealed interface BackendResult<out T>
{
data class Success<T>(
val value: T,
) : BackendResult<T>
data class Failure(
val message: String,
) : BackendResult<Nothing>
}
interface CheckoutBackend
{
/** Creates the order and returns its backend ID. */
suspend fun createOrder(request: PostOrderRequest): BackendResult<String>
/** True when paid, false when it exists unpaid, null when unknown or unreachable. */
suspend fun isPaid(orderId: String): Boolean?
/** Completes an order paid entirely outside Taler, without a wallet payment. */
suspend fun collect(orderId: String): BackendResult<Unit>
/** Records money given back outside Taler on the order. */
suspend fun recordExternalRefund(
orderId: String,
request: ExternalRefundRequest,
): BackendResult<Unit>
/** Deletes the order; [force] is required when it already records external payments. */
suspend fun deleteOrder(
orderId: String,
force: Boolean = false,
): BackendResult<Unit>
}
internal class MerchantCheckoutBackend(
private val api: MerchantApi,
private val config: MerchantConfig,
) : CheckoutBackend
{
override suspend fun createOrder(request: PostOrderRequest): BackendResult<String>
{
var result: BackendResult<String> = noResponse
api.postOrder(config, request).handle({ result = BackendResult.Failure(it) }) {
result = BackendResult.Success(it.orderId)
}
return result
}
override suspend fun isPaid(orderId: String): Boolean?
{
var paid: Boolean? = null
api.checkOrder(config, orderId).handle(null) { paid = it is CheckPaymentResponse.Paid }
return paid
}
override suspend fun collect(orderId: String): BackendResult<Unit>
{
var result: BackendResult<Unit> = noResponse
api.collectOrder(config, orderId).handle({ result = BackendResult.Failure(it) }) {
result = BackendResult.Success(Unit)
}
return result
}
override suspend fun recordExternalRefund(
orderId: String,
request: ExternalRefundRequest,
): BackendResult<Unit>
{
var result: BackendResult<Unit> = noResponse
api.refundExternal(config, orderId, request).handle({ result = BackendResult.Failure(it) }) {
result = BackendResult.Success(Unit)
}
return result
}
override suspend fun deleteOrder(
orderId: String,
force: Boolean,
): BackendResult<Unit>
{
var result: BackendResult<Unit> = noResponse
api.deleteOrder(config, orderId, force = force).handle({ result = BackendResult.Failure(it) }) {
result = BackendResult.Success(Unit)
}
return result
}
private companion object
{
val noResponse = BackendResult.Failure("No response from the merchant backend")
}
}