aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAntoine A <>2024-04-30 12:35:43 +0900
committerAntoine A <>2024-04-30 12:52:13 +0900
commit602a3b6e8afa3462a4f34cd6bb92005fec84c90b (patch)
tree47d3b873f20d438b025ea9ba443269d1e9b11b11
parenteeba7b9890a3fae2aebac4d7ca9423af8acd7e7d (diff)
downloadlibeufin-602a3b6e8afa3462a4f34cd6bb92005fec84c90b.tar.gz
libeufin-602a3b6e8afa3462a4f34cd6bb92005fec84c90b.tar.bz2
libeufin-602a3b6e8afa3462a4f34cd6bb92005fec84c90b.zip
nexus: revenue API
-rw-r--r--bank/src/main/kotlin/tech/libeufin/bank/Constants.kt3
-rw-r--r--bank/src/main/kotlin/tech/libeufin/bank/TalerMessage.kt23
-rw-r--r--bank/src/main/kotlin/tech/libeufin/bank/db/Database.kt2
-rw-r--r--bank/src/test/kotlin/RevenueApiTest.kt1
-rw-r--r--bank/src/test/kotlin/WireGatewayApiTest.kt13
-rw-r--r--common/src/main/kotlin/Constants.kt3
-rw-r--r--common/src/main/kotlin/TalerMessage.kt28
-rw-r--r--database-versioning/libeufin-nexus-procedures.sql1
-rw-r--r--nexus/conf/test.conf5
-rw-r--r--nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt1
-rw-r--r--nexus/src/main/kotlin/tech/libeufin/nexus/api/RevenueApi.kt45
-rw-r--r--nexus/src/main/kotlin/tech/libeufin/nexus/db/Database.kt12
-rw-r--r--nexus/src/main/kotlin/tech/libeufin/nexus/db/ExchangeDAO.kt4
-rw-r--r--nexus/src/main/kotlin/tech/libeufin/nexus/db/PaymentDAO.kt25
-rw-r--r--nexus/src/test/kotlin/RevenueApiTest.kt65
-rw-r--r--nexus/src/test/kotlin/WireGatewayApiTest.kt14
16 files changed, 193 insertions, 52 deletions
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/Constants.kt b/bank/src/main/kotlin/tech/libeufin/bank/Constants.kt
index d91d779d..d10ba59a 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/Constants.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/Constants.kt
@@ -39,5 +39,4 @@ const val IBAN_ALLOCATION_RETRY_COUNTER: Int = 5
// API version
const val COREBANK_API_VERSION: String = "4:7:0"
const val CONVERSION_API_VERSION: String = "0:0:0"
-const val INTEGRATION_API_VERSION: String = "2:0:2"
-const val REVENUE_API_VERSION: String = "0:0:0" \ No newline at end of file
+const val INTEGRATION_API_VERSION: String = "2:0:2" \ No newline at end of file
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/TalerMessage.kt b/bank/src/main/kotlin/tech/libeufin/bank/TalerMessage.kt
index 2527bae6..79e9a7e7 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/TalerMessage.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/TalerMessage.kt
@@ -327,14 +327,6 @@ data class TalerIntegrationConfigResponse(
val version: String = INTEGRATION_API_VERSION
}
-@Serializable
-data class RevenueConfig(
- val currency: String
-) {
- val name: String = "taler-revenue"
- val version: String = REVENUE_API_VERSION
-}
-
enum class CreditDebitInfo {
credit, debit
}
@@ -546,21 +538,6 @@ data class ConversionResponse(
val amount_credit: TalerAmount,
)
-@Serializable
-data class RevenueIncomingHistory(
- val incoming_transactions : List<RevenueIncomingBankTransaction>,
- val credit_account: String
-)
-
-@Serializable
-data class RevenueIncomingBankTransaction(
- val row_id: Long,
- val date: TalerProtocolTimestamp,
- val amount: TalerAmount,
- val debit_account: String,
- val subject: String
-)
-
/**
* Response to GET /public-accounts
*/
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/db/Database.kt b/bank/src/main/kotlin/tech/libeufin/bank/db/Database.kt
index 19293280..1236b1ad 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/db/Database.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/db/Database.kt
@@ -102,7 +102,7 @@ class Database(dbConfig: DatabaseConfig, internal val bankCurrency: String, inte
/** Listen for new taler incoming transactions to [exchange] */
suspend fun <R> listenIncoming(exchange: Long, lambda: suspend (Flow<Long>) -> R): R
= listen(incomingTxFlows, exchange, lambda)
- /** Listen for new taler outgoing transactions to [merchant] */
+ /** Listen for new incoming transactions to [merchant] */
suspend fun <R> listenRevenue(merchant: Long, lambda: suspend (Flow<Long>) -> R): R
= listen(revenueTxFlows, merchant, lambda)
/** Listen for new withdrawal confirmations */
diff --git a/bank/src/test/kotlin/RevenueApiTest.kt b/bank/src/test/kotlin/RevenueApiTest.kt
index 6c694842..3c692f90 100644
--- a/bank/src/test/kotlin/RevenueApiTest.kt
+++ b/bank/src/test/kotlin/RevenueApiTest.kt
@@ -19,7 +19,6 @@
import io.ktor.http.*
import org.junit.Test
-import tech.libeufin.bank.RevenueIncomingHistory
import tech.libeufin.common.*
class RevenueApiTest {
diff --git a/bank/src/test/kotlin/WireGatewayApiTest.kt b/bank/src/test/kotlin/WireGatewayApiTest.kt
index 1c6c60ef..10482bdb 100644
--- a/bank/src/test/kotlin/WireGatewayApiTest.kt
+++ b/bank/src/test/kotlin/WireGatewayApiTest.kt
@@ -30,7 +30,7 @@ class WireGatewayApiTest {
client.getA("/accounts/merchant/taler-wire-gateway/config").assertOk()
}
- // Testing the POST /transfer call from the TWG API.
+ // POST /accounts/{USERNAME}/taler-wire-gateway/transfer
@Test
fun transfer() = bankSetup { _ ->
val valid_req = obj {
@@ -121,9 +121,7 @@ class WireGatewayApiTest {
}.assertBadRequest()
}
- /**
- * Testing the /history/incoming call from the TWG API.
- */
+ // GET /accounts/{USERNAME}/taler-wire-gateway/history/incoming
@Test
fun historyIncoming() = bankSetup {
// Give Foo reasonable debt allowance:
@@ -159,10 +157,7 @@ class WireGatewayApiTest {
)
}
-
- /**
- * Testing the /history/outgoing call from the TWG API.
- */
+ // GET /accounts/{USERNAME}/taler-wire-gateway/history/outgoing
@Test
fun historyOutgoing() = bankSetup {
setMaxDebt("exchange", "KUDOS:1000000")
@@ -193,7 +188,7 @@ class WireGatewayApiTest {
)
}
- // Testing the /admin/add-incoming call from the TWG API.
+ // POST /accounts/{USERNAME}/taler-wire-gateway/admin/add-incoming
@Test
fun addIncoming() = bankSetup { _ ->
val valid_req = obj {
diff --git a/common/src/main/kotlin/Constants.kt b/common/src/main/kotlin/Constants.kt
index 0e48cf1e..f6760a44 100644
--- a/common/src/main/kotlin/Constants.kt
+++ b/common/src/main/kotlin/Constants.kt
@@ -26,4 +26,5 @@ const val SERIALIZATION_RETRY: Int = 10
const val MAX_BODY_LENGTH: Long = 4 * 1024 // 4kB
// API version
-const val WIRE_GATEWAY_API_VERSION: String = "0:2:0" \ No newline at end of file
+const val WIRE_GATEWAY_API_VERSION: String = "0:2:0"
+const val REVENUE_API_VERSION: String = "0:0:0" \ No newline at end of file
diff --git a/common/src/main/kotlin/TalerMessage.kt b/common/src/main/kotlin/TalerMessage.kt
index 6610376f..58723b4e 100644
--- a/common/src/main/kotlin/TalerMessage.kt
+++ b/common/src/main/kotlin/TalerMessage.kt
@@ -75,6 +75,7 @@ data class IncomingHistory(
val credit_account: String
)
+/** Inner request GET /taler-wire-gateway/history/incoming */
@Serializable
data class IncomingReserveTransaction(
val type: String = "RESERVE",
@@ -92,6 +93,7 @@ data class OutgoingHistory(
val debit_account: String
)
+/** Inner request GET /taler-wire-gateway/history/outgoing */
@Serializable
data class OutgoingTransaction(
val row_id: Long, // DB row ID of the payment.
@@ -101,3 +103,29 @@ data class OutgoingTransaction(
val wtid: ShortHashCode,
val exchange_base_url: String,
)
+
+/** Response GET /taler-revenue/config */
+@Serializable
+data class RevenueConfig(
+ val currency: String
+) {
+ val name: String = "taler-revenue"
+ val version: String = REVENUE_API_VERSION
+}
+
+/** Request GET /taler-revenue/history */
+@Serializable
+data class RevenueIncomingHistory(
+ val incoming_transactions : List<RevenueIncomingBankTransaction>,
+ val credit_account: String
+)
+
+/** Inner request GET /taler-revenue/history */
+@Serializable
+data class RevenueIncomingBankTransaction(
+ val row_id: Long,
+ val date: TalerProtocolTimestamp,
+ val amount: TalerAmount,
+ val debit_account: String,
+ val subject: String
+) \ No newline at end of file
diff --git a/database-versioning/libeufin-nexus-procedures.sql b/database-versioning/libeufin-nexus-procedures.sql
index 83c664a3..db256da7 100644
--- a/database-versioning/libeufin-nexus-procedures.sql
+++ b/database-versioning/libeufin-nexus-procedures.sql
@@ -131,6 +131,7 @@ ELSE
,in_debit_payto_uri
,in_bank_id
) RETURNING incoming_transaction_id INTO out_tx_id;
+ PERFORM pg_notify('revenue_tx', out_tx_id::text);
END IF;
END $$;
COMMENT ON FUNCTION register_incoming
diff --git a/nexus/conf/test.conf b/nexus/conf/test.conf
index f4edd11a..fc4b0946 100644
--- a/nexus/conf/test.conf
+++ b/nexus/conf/test.conf
@@ -20,4 +20,9 @@ IGNORE_TRANSACTIONS_BEFORE = 2024-04-04
[nexus-httpd-wire-gateway-api]
ENABLED = YES
AUTH_METHOD = bearer-token
+AUTH_BEARER_TOKEN = secret-token
+
+[nexus-httpd-revenue-api]
+ENABLED = YES
+AUTH_METHOD = bearer-token
AUTH_BEARER_TOKEN = secret-token \ No newline at end of file
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt
index 387bf68b..d4f133bd 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt
@@ -73,6 +73,7 @@ fun Instant.fmtDateTime(): String =
fun Application.nexusApi(db: Database, cfg: NexusConfig) = talerApi(logger) {
wireGatewayApi(db, cfg)
+ revenueApi(db, cfg)
}
/**
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/api/RevenueApi.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/api/RevenueApi.kt
new file mode 100644
index 00000000..e1435a44
--- /dev/null
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/api/RevenueApi.kt
@@ -0,0 +1,45 @@
+/*
+ * 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.response.*
+import io.ktor.server.routing.*
+import tech.libeufin.nexus.*
+import tech.libeufin.nexus.db.*
+import tech.libeufin.common.*
+
+fun Routing.revenueApi(db: Database, cfg: NexusConfig) = authApi(cfg.revenueApiCfg) {
+ get("/taler-revenue/config") {
+ call.respond(RevenueConfig(
+ currency = cfg.currency
+ ))
+ }
+ get("/taler-revenue/history") {
+ val params = HistoryParams.extract(context.request.queryParameters)
+ val items = db.payment.revenueHistory(params)
+
+ if (items.isEmpty()) {
+ call.respond(HttpStatusCode.NoContent)
+ } else {
+ call.respond(RevenueIncomingHistory(items, cfg.payto))
+ }
+ }
+} \ No newline at end of file
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/db/Database.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/db/Database.kt
index 01c512ef..25cfaa59 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/db/Database.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/db/Database.kt
@@ -49,9 +49,14 @@ class Database(dbConfig: DatabaseConfig, val bankCurrency: String): DbPool(dbCon
private val outgoingTxFlows: MutableSharedFlow<Long> = MutableSharedFlow()
private val incomingTxFlows: MutableSharedFlow<Long> = MutableSharedFlow()
+ private val revenueTxFlows: MutableSharedFlow<Long> = MutableSharedFlow()
init {
watchNotifications(pgSource, "libeufin_nexus", LoggerFactory.getLogger("libeufin-nexus-db-watcher"), mapOf(
+ "revenue_tx" to {
+ val id = it.toLong()
+ revenueTxFlows.emit(id)
+ },
"outgoing_tx" to {
val id = it.toLong()
outgoingTxFlows.emit(id)
@@ -65,8 +70,11 @@ class Database(dbConfig: DatabaseConfig, val bankCurrency: String): DbPool(dbCon
/** Listen for new taler outgoing transactions */
suspend fun <R> listenOutgoing(lambda: suspend (Flow<Long>) -> R): R
- = lambda(outgoingTxFlows as Flow<Long>)
+ = lambda(outgoingTxFlows)
/** Listen for new taler incoming transactions */
suspend fun <R> listenIncoming(lambda: suspend (Flow<Long>) -> R): R
- = lambda(incomingTxFlows as Flow<Long>)
+ = lambda(incomingTxFlows)
+ /** Listen for new incoming transactions */
+ suspend fun <R> listenRevenue(lambda: suspend (Flow<Long>) -> R): R
+ = lambda(revenueTxFlows)
} \ No newline at end of file
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/db/ExchangeDAO.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/db/ExchangeDAO.kt
index 13da1f2a..6f3a3a3a 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/db/ExchangeDAO.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/db/ExchangeDAO.kt
@@ -54,9 +54,7 @@ class ExchangeDAO(private val db: Database) {
/** Query [exchangeId] history of taler outgoing transactions */
suspend fun outgoingHistory(
params: HistoryParams
- ): List<OutgoingTransaction>
- // Outgoing transactions can be initiated or recovered. We take the first data to
- // reach database : the initiation first else the recovered transaction.
+ ): List<OutgoingTransaction>
= db.poolHistoryGlobal(params, db::listenOutgoing, """
SELECT
outgoing_transaction_id
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 4609def8..d9bdee00 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/db/PaymentDAO.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/db/PaymentDAO.kt
@@ -20,7 +20,7 @@
package tech.libeufin.nexus.db
import tech.libeufin.common.*
-import tech.libeufin.common.db.one
+import tech.libeufin.common.db.*
import tech.libeufin.nexus.IncomingPayment
import tech.libeufin.nexus.OutgoingPayment
import java.time.Instant
@@ -141,4 +141,27 @@ class PaymentDAO(private val db: Database) {
}
}
}
+
+ /** Query history of incoming transactions */
+ suspend fun revenueHistory(
+ params: HistoryParams
+ ): List<RevenueIncomingBankTransaction>
+ = db.poolHistoryGlobal(params, db::listenRevenue, """
+ SELECT
+ incoming_transaction_id
+ ,execution_time
+ ,(amount).val AS amount_val
+ ,(amount).frac AS amount_frac
+ ,debit_payto_uri
+ ,wire_transfer_subject
+ FROM incoming_transactions WHERE
+ """, "incoming_transaction_id") {
+ RevenueIncomingBankTransaction(
+ row_id = it.getLong("incoming_transaction_id"),
+ date = it.getTalerTimestamp("execution_time"),
+ amount = it.getAmount("amount", db.bankCurrency),
+ debit_account = it.getString("debit_payto_uri"),
+ subject = it.getString("wire_transfer_subject")
+ )
+ }
} \ No newline at end of file
diff --git a/nexus/src/test/kotlin/RevenueApiTest.kt b/nexus/src/test/kotlin/RevenueApiTest.kt
new file mode 100644
index 00000000..5440998d
--- /dev/null
+++ b/nexus/src/test/kotlin/RevenueApiTest.kt
@@ -0,0 +1,65 @@
+/*
+ * 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/>
+ */
+
+import io.ktor.http.*
+import org.junit.Test
+import tech.libeufin.common.*
+import tech.libeufin.nexus.*
+
+class RevenueApiTest {
+ // GET /taler-revenue/config
+ @Test
+ fun config() = serverSetup {
+ authRoutine(HttpMethod.Get, "/taler-revenue/config")
+
+ client.getA("/taler-revenue/config").assertOk()
+ }
+
+ // GET /taler-revenue/history
+ @Test
+ fun history() = serverSetup { db ->
+ authRoutine(HttpMethod.Get, "/taler-revenue/history")
+
+ historyRoutine<RevenueIncomingHistory>(
+ url = "/taler-revenue/history",
+ ids = { it.incoming_transactions.map { it.row_id } },
+ registered = listOf(
+ {
+ // Transactions using clean transfer logic
+ talerableIn(db)
+ },
+ {
+ // Common credit transactions
+ ingestIncomingPayment(db, genInPay("ignored"))
+ }
+ ),
+ ignored = listOf(
+ {
+ // Ignore debit transactions
+ talerableOut(db)
+ }
+ )
+ )
+ }
+
+ @Test
+ fun noApi() = serverSetup("mini.conf") {
+ client.getA("/taler-revenue/config").assertNotImplemented()
+ }
+} \ No newline at end of file
diff --git a/nexus/src/test/kotlin/WireGatewayApiTest.kt b/nexus/src/test/kotlin/WireGatewayApiTest.kt
index 4bc22f06..55e5666e 100644
--- a/nexus/src/test/kotlin/WireGatewayApiTest.kt
+++ b/nexus/src/test/kotlin/WireGatewayApiTest.kt
@@ -27,7 +27,7 @@ import tech.libeufin.common.*
import tech.libeufin.nexus.*
class WireGatewayApiTest {
- // GET /accounts/{USERNAME}/taler-wire-gateway/config
+ // GET /taler-wire-gateway/config
@Test
fun config() = serverSetup { _ ->
authRoutine(HttpMethod.Get, "/taler-wire-gateway/config")
@@ -35,7 +35,7 @@ class WireGatewayApiTest {
client.getA("/taler-wire-gateway/config").assertOk()
}
- // Testing the POST /transfer call from the TWG API.
+ // POST /taler-wire-gateway/transfer
@Test
fun transfer() = serverSetup { _ ->
val valid_req = obj {
@@ -102,9 +102,7 @@ class WireGatewayApiTest {
}.assertBadRequest()
}
- /**
- * Testing the /history/incoming call from the TWG API.
- */
+ // GET /taler-wire-gateway/history/incoming
@Test
fun historyIncoming() = serverSetup { db ->
authRoutine(HttpMethod.Get, "/taler-wire-gateway/history/incoming")
@@ -139,9 +137,7 @@ class WireGatewayApiTest {
)
}
- /**
- * Testing the /history/outgoing call from the TWG API.
- */
+ // GET /taler-wire-gateway/history/outgoing
@Test
fun historyOutgoing() = serverSetup { db ->
authRoutine(HttpMethod.Get, "/taler-wire-gateway/history/outgoing")
@@ -174,7 +170,7 @@ class WireGatewayApiTest {
)
}
- // Testing the /admin/add-incoming call from the TWG API.
+ // POST /taler-wire-gateway/admin/add-incoming
@Test
fun addIncoming() = serverSetup { _ ->
val valid_req = obj {