diff options
author | Antoine A <> | 2024-04-09 15:43:48 +0200 |
---|---|---|
committer | Antoine A <> | 2024-04-09 15:46:02 +0200 |
commit | af3b104fb49d3066bd72102b3b0996fc39cdbf75 (patch) | |
tree | defd2a69edbdbbb3da7bb2b37860d49c4e4ca82d /nexus/src/main | |
parent | ed18c6cb21f98a71c9cec2bd1cc2b84ae1520b7d (diff) | |
download | libeufin-af3b104fb49d3066bd72102b3b0996fc39cdbf75.tar.gz libeufin-af3b104fb49d3066bd72102b3b0996fc39cdbf75.tar.bz2 libeufin-af3b104fb49d3066bd72102b3b0996fc39cdbf75.zip |
nexus: wire gateway /admin/add-incoming
Diffstat (limited to 'nexus/src/main')
4 files changed, 170 insertions, 14 deletions
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsFetch.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsFetch.kt index 610b2c8b..4cd88fbf 100644 --- a/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsFetch.kt +++ b/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsFetch.kt @@ -28,6 +28,7 @@ import io.ktor.client.plugins.* import kotlinx.coroutines.* import tech.libeufin.common.* import tech.libeufin.nexus.db.* +import tech.libeufin.nexus.db.PaymentDAO.* import tech.libeufin.nexus.ebics.* import java.io.IOException import java.io.InputStream @@ -120,11 +121,16 @@ suspend fun ingestIncomingPayment( ) { runCatching { parseIncomingTxMetadata(payment.wireTransferSubject) }.fold( onSuccess = { reservePub -> - val result = db.payment.registerTalerableIncoming(payment, reservePub) - if (result.new) { - logger.info("$payment") - } else { - logger.debug("$payment already seen") + val res = db.payment.registerTalerableIncoming(payment, reservePub) + when (res) { + IncomingRegistrationResult.ReservePubReuse -> throw Error("TODO reserve pub reuse") + is IncomingRegistrationResult.Success -> { + if (res.new) { + logger.info("$payment") + } else { + logger.debug("$payment already seen") + } + } } }, onFailure = { e -> diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt index 08bb23f1..41cb58b0 100644 --- a/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt +++ b/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt @@ -33,10 +33,13 @@ import com.github.ajalt.clikt.parameters.groups.provideDelegate import com.github.ajalt.clikt.parameters.options.convert import com.github.ajalt.clikt.parameters.options.option import com.github.ajalt.clikt.parameters.options.versionOption +import io.ktor.server.application.* import org.slf4j.Logger import org.slf4j.LoggerFactory import tech.libeufin.common.* +import tech.libeufin.common.api.* import tech.libeufin.common.db.DatabaseConfig +import tech.libeufin.nexus.api.* import tech.libeufin.nexus.db.Database import tech.libeufin.nexus.db.InitiatedPayment import java.nio.file.Path @@ -105,6 +108,16 @@ class NexusConfig(val config: TalerConfig) { val fetch = NexusFetchConfig(config) } +fun NexusConfig.checkCurrency(amount: TalerAmount) { + if (amount.currency != currency) throw badRequest( + "Wrong currency: expected regional $currency got ${amount.currency}", + TalerErrorCode.GENERIC_CURRENCY_MISMATCH + ) +} + +fun Application.nexusApi(db: Database, cfg: NexusConfig) = talerApi(logger) { + wireGatewayApi(db, cfg) +} /** * Abstracts the config loading diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/api/WireGatewayApi.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/api/WireGatewayApi.kt new file mode 100644 index 00000000..f7374204 --- /dev/null +++ b/nexus/src/main/kotlin/tech/libeufin/nexus/api/WireGatewayApi.kt @@ -0,0 +1,134 @@ +/* + * This file is part of LibEuFin. + * Copyright (C) 2024 Taler Systems S.A. + + * LibEuFin is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation; either version 3, or + * (at your option) any later version. + + * LibEuFin 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 Affero General + * Public License for more details. + + * You should have received a copy of the GNU Affero General Public + * License along with LibEuFin; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/> + */ + +package tech.libeufin.nexus.api + +import io.ktor.http.* +import io.ktor.server.application.* +import io.ktor.server.request.* +import io.ktor.server.response.* +import io.ktor.server.routing.* +import io.ktor.util.pipeline.* +import tech.libeufin.common.* +import tech.libeufin.nexus.* +import tech.libeufin.nexus.db.* +import tech.libeufin.nexus.db.PaymentDAO.* +import java.time.Instant + + +fun Routing.wireGatewayApi(db: Database, cfg: NexusConfig) { + get("/taler-wire-gateway/config") { + call.respond(WireGatewayConfig( + currency = cfg.currency + )) + } + post("/taler-wire-gateway/transfer") { + val req = call.receive<TransferRequest>() + cfg.checkCurrency(req.amount) + // TODO + /*val res = db.exchange.transfer( + req = req, + login = username, + now = Instant.now() + ) + when (res) { + is TransferResult.UnknownExchange -> throw unknownAccount(username) + is TransferResult.NotAnExchange -> throw conflict( + "$username is not an exchange account.", + TalerErrorCode.BANK_ACCOUNT_IS_NOT_EXCHANGE + ) + is TransferResult.UnknownCreditor -> throw unknownCreditorAccount(req.credit_account.canonical) + is TransferResult.BothPartyAreExchange -> throw conflict( + "Wire transfer attempted with credit and debit party being both exchange account", + TalerErrorCode.BANK_ACCOUNT_IS_EXCHANGE + ) + is TransferResult.ReserveUidReuse -> throw conflict( + "request_uid used already", + TalerErrorCode.BANK_TRANSFER_REQUEST_UID_REUSED + ) + is TransferResult.BalanceInsufficient -> throw conflict( + "Insufficient balance for exchange", + TalerErrorCode.BANK_UNALLOWED_DEBIT + ) + is TransferResult.Success -> call.respond( + TransferResponse( + timestamp = res.timestamp, + row_id = res.id + ) + ) + }*/ + } + /*suspend fun <T> PipelineContext<Unit, ApplicationCall>.historyEndpoint( + reduce: (List<T>, String) -> Any, + dbLambda: suspend ExchangeDAO.(HistoryParams, Long, BankPaytoCtx) -> List<T> + ) { + val params = HistoryParams.extract(context.request.queryParameters) + val bankAccount = call.bankInfo(db, ctx.payto) + + if (!bankAccount.isTalerExchange) + throw conflict( + "$username is not an exchange account.", + TalerErrorCode.BANK_ACCOUNT_IS_NOT_EXCHANGE + ) + + val items = db.exchange.dbLambda(params, bankAccount.bankAccountId, ctx.payto) + + if (items.isEmpty()) { + call.respond(HttpStatusCode.NoContent) + } else { + call.respond(reduce(items, bankAccount.payto)) + } + }*/ + /*get("/taler-wire-gateway/history/incoming") { + historyEndpoint(::IncomingHistory, ExchangeDAO::incomingHistory) + } + get("/taler-wire-gateway/history/outgoing") { + historyEndpoint(::OutgoingHistory, ExchangeDAO::outgoingHistory) + }*/ + post("/taler-wire-gateway/admin/add-incoming") { + val req = call.receive<AddIncomingRequest>() + cfg.checkCurrency(req.amount) + val timestamp = Instant.now() + val bankId = run { + val bytes = ByteArray(16) + kotlin.random.Random.nextBytes(bytes) + Base32Crockford.encode(bytes) + } + val res = db.payment.registerTalerableIncoming(IncomingPayment( + amount = req.amount, + debitPaytoUri = req.debit_account.toString(), + wireTransferSubject = "Manual incoming ${req.reserve_pub}", + executionTime = Instant.now(), + bankId = bankId + ), req.reserve_pub) + when (res) { + IncomingRegistrationResult.ReservePubReuse -> throw conflict( + "reserve_pub used already", + TalerErrorCode.BANK_DUPLICATE_RESERVE_PUB_SUBJECT + ) + // TODO timestamp when idempotent + is IncomingRegistrationResult.Success -> call.respond( + AddIncomingResponse( + timestamp = TalerProtocolTimestamp(timestamp), + row_id = res.id + ) + ) + } + } +}
\ No newline at end of file diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/db/PaymentDAO.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/db/PaymentDAO.kt index d316267f..05548b99 100644 --- a/nexus/src/main/kotlin/tech/libeufin/nexus/db/PaymentDAO.kt +++ b/nexus/src/main/kotlin/tech/libeufin/nexus/db/PaymentDAO.kt @@ -96,10 +96,10 @@ class PaymentDAO(private val db: Database) { } /** Incoming payments registration result */ - data class IncomingRegistrationResult( - val id: Long, - val new: Boolean - ) + sealed interface IncomingRegistrationResult { + data class Success(val id: Long, val new: Boolean): IncomingRegistrationResult + data object ReservePubReuse: IncomingRegistrationResult + } /** Register an talerable incoming payment */ suspend fun registerTalerableIncoming( @@ -107,7 +107,7 @@ class PaymentDAO(private val db: Database) { reservePub: EddsaPublicKey ): IncomingRegistrationResult = db.conn { conn -> val stmt = conn.prepareStatement(""" - SELECT out_found, out_tx_id + SELECT out_reserve_pub_reuse, out_found, out_tx_id FROM register_incoming_and_talerable((?,?)::taler_amount,?,?,?,?,?) """) val executionTime = paymentData.executionTime.micros() @@ -119,10 +119,13 @@ class PaymentDAO(private val db: Database) { stmt.setString(6, paymentData.bankId) stmt.setBytes(7, reservePub.raw) stmt.one { - IncomingRegistrationResult( - it.getLong("out_tx_id"), - !it.getBoolean("out_found") - ) + when { + it.getBoolean("out_reserve_pub_reuse") -> IncomingRegistrationResult.ReservePubReuse + else -> IncomingRegistrationResult.Success( + it.getLong("out_tx_id"), + !it.getBoolean("out_found") + ) + } } } }
\ No newline at end of file |