aboutsummaryrefslogtreecommitdiff
path: root/nexus/src/main
diff options
context:
space:
mode:
authorAntoine A <>2024-04-09 15:43:48 +0200
committerAntoine A <>2024-04-09 15:46:02 +0200
commitaf3b104fb49d3066bd72102b3b0996fc39cdbf75 (patch)
treedefd2a69edbdbbb3da7bb2b37860d49c4e4ca82d /nexus/src/main
parented18c6cb21f98a71c9cec2bd1cc2b84ae1520b7d (diff)
downloadlibeufin-af3b104fb49d3066bd72102b3b0996fc39cdbf75.tar.gz
libeufin-af3b104fb49d3066bd72102b3b0996fc39cdbf75.tar.bz2
libeufin-af3b104fb49d3066bd72102b3b0996fc39cdbf75.zip
nexus: wire gateway /admin/add-incoming
Diffstat (limited to 'nexus/src/main')
-rw-r--r--nexus/src/main/kotlin/tech/libeufin/nexus/EbicsFetch.kt16
-rw-r--r--nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt13
-rw-r--r--nexus/src/main/kotlin/tech/libeufin/nexus/api/WireGatewayApi.kt134
-rw-r--r--nexus/src/main/kotlin/tech/libeufin/nexus/db/PaymentDAO.kt21
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