commit 0c3190f186bcfaee3364e3d178e8bfd38ed995a1
parent 4ff2df763817b66c998da3bd274efd70585d0a85
Author: Antoine A <>
Date: Tue, 18 Mar 2025 13:36:06 +0100
nexus: clean multi ID logging and clean ebics BTS logic
Diffstat:
7 files changed, 45 insertions(+), 47 deletions(-)
diff --git a/database-versioning/libeufin-nexus-procedures.sql b/database-versioning/libeufin-nexus-procedures.sql
@@ -107,10 +107,10 @@ out_found=FOUND;
IF out_found THEN
-- Check metadata
-- TODO take subject if missing and more detailed credit payto
- IF local_subject IS DISTINCT FROM in_subject THEN
+ IF in_subject IS NOT NULL AND local_subject != in_subject THEN
RAISE NOTICE 'outgoing tx %: stored subject is ''%'' got ''%''', in_end_to_end_id, local_subject, in_subject;
END IF;
- IF local_credit_payto IS DISTINCT FROM in_credit_payto THEN
+ IF in_credit_payto IS NOT NULL AND local_credit_payto != in_credit_payto THEN
RAISE NOTICE 'outgoing tx %: stored subject credit payto is % got %', in_end_to_end_id, local_credit_payto, in_credit_payto;
END IF;
IF local_amount IS DISTINCT FROM in_amount THEN
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/cli/EbicsFetch.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/cli/EbicsFetch.kt
@@ -459,7 +459,7 @@ class EbicsFetch: CliktCommand() {
val hkd = EbicsAdministrative.parseHKD(stream)
val supportedOrder = hkd.partner.orders.map { it.order }
logger.debug {
- val fmt = supportedOrder.map(EbicsOrder::description).joinToString(", ")
+ val fmt = supportedOrder.map(EbicsOrder::description).joinToString(" ")
"HKD: ${fmt}"
}
selectedOrder select supportedOrder
@@ -478,7 +478,7 @@ class EbicsFetch: CliktCommand() {
val orders = client.download(EbicsOrder.V3.HAA, null, null, false) { stream ->
val haa = EbicsAdministrative.parseHAA(stream)
logger.debug {
- val orders = haa.orders.map(EbicsOrder::description).joinToString(", ")
+ val orders = haa.orders.map(EbicsOrder::description).joinToString(" ")
"HAA: ${orders}"
}
selectedOrder select haa.orders
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/cli/EbicsSetup.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/cli/EbicsSetup.kt
@@ -285,7 +285,7 @@ class EbicsSetup: CliktCommand() {
if (account.currency != null && account.currency != cfg.currency)
logger.error("Expected CURRENCY '${cfg.currency}' from config got '${account.currency}' from bank")
} else if (partner.accounts.isNotEmpty()) {
- val ibans = partner.accounts.map { it.iban }.joinToString(", ")
+ val ibans = partner.accounts.map { it.iban }.joinToString(" ")
logger.error("Expected IBAN ${cfg.ebics.account.iban} from config got $ibans from bank")
}
@@ -294,14 +294,14 @@ class EbicsSetup: CliktCommand() {
// Check partner support required orders
val unsupportedOrder = requireOrders subtract partner.orders.map { it.order }
if (unsupportedOrder.isNotEmpty()) {
- logger.warn("Unsupported orders: {}", unsupportedOrder.map(EbicsOrder::description).joinToString(", "))
+ logger.warn("Unsupported orders: {}", unsupportedOrder.map(EbicsOrder::description).joinToString(" "))
}
// Check user is authorized for required orders
if (user != null) {
val unauthorizedOrders = requireOrders subtract user.permissions subtract unsupportedOrder
if (unauthorizedOrders.isNotEmpty()) {
- logger.warn("Unauthorized orders: {}", unauthorizedOrders.map(EbicsOrder::description).joinToString(", "))
+ logger.warn("Unauthorized orders: {}", unauthorizedOrders.map(EbicsOrder::description).joinToString(" "))
}
logger.info("Subscriber status: {}", user.status.description)
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsCommon.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsCommon.kt
@@ -95,7 +95,7 @@ suspend fun EbicsBTS.postBTS(
xmlReq: ByteArray,
phase: String,
stepLogger: StepLogger? = null
-): EbicsResponse<BTSResponse> {
+): BTSResponse {
val doc = client.postToBank(cfg.host.baseUrl, xmlReq, phase, stepLogger)
try {
XMLUtil.verifyEbicsDocument(
@@ -123,7 +123,7 @@ suspend fun EbicsBTS.postBTS(
append(response.bankCode)
}
}
- return response
+ return response.okOrFail(phase)
}
/** High level EBICS client */
@@ -173,7 +173,13 @@ class EbicsClient(
val tId = db.ebics.first()
if (tId == null) break
val xml = impl.downloadReceipt(tId, false)
- impl.postBTS(client, xml, "Closing pending")
+ try {
+ impl.postBTS(client, xml, "Closing pending")
+ } catch (e: Exception) {
+ if (e !is EbicsError.Code || e.technicalCode != EbicsReturnCode.EBICS_TX_UNKNOWN_TXID) {
+ throw e
+ }
+ }
db.ebics.remove(tId)
}
@@ -183,8 +189,7 @@ class EbicsClient(
val (tId, initContent) = withContext(NonCancellable) {
// Init phase
val initReq = impl.downloadInitialization(startDate, endDate)
- val initResp = impl.postBTS(client, initReq, "Download init", txLog.step("init"))
- val initContent = initResp.okOrFail("Download init $description")
+ val initContent = impl.postBTS(client, initReq, "Download init $description", txLog.step("init"))
val tId = requireNotNull(initContent.transactionID) {
"Download init $description: missing transaction ID"
}
@@ -205,8 +210,7 @@ class EbicsClient(
val segments = mutableListOf(firstSegment)
for (x in 2 .. howManySegments) {
val transReq = impl.downloadTransfer(x, howManySegments, tId)
- val transResp = impl.postBTS(client, transReq, "Download transfer", txLog.step("transfer$x"))
- .okOrFail("Download transfer $description")
+ val transResp = impl.postBTS(client, transReq, "Download transfer $description", txLog.step("transfer$x"))
val segment = requireNotNull(transResp.segment) {
"Download transfer: missing encrypted segment"
}
@@ -238,8 +242,7 @@ class EbicsClient(
// First send a proper EBICS transaction receipt
val xml = impl.downloadReceipt(tId, res.isSuccess && !peek)
- impl.postBTS(client, xml, "Download receipt", txLog.step("receipt"))
- .okOrFail("Download receipt $description")
+ impl.postBTS(client, xml, "Download receipt $description", txLog.step("receipt"))
runCatching { db.ebics.remove(tId) }
// Then throw business logic exception if any
return res.getOrThrow()
@@ -264,8 +267,7 @@ class EbicsClient(
// Init phase
val initXml = impl.uploadInitialization(preparedPayload)
- val initResp = impl.postBTS(client, initXml, "Upload init", txLog.step("init"))
- .okOrFail("Upload init $description")
+ val initResp = impl.postBTS(client, initXml, "Upload init $description", txLog.step("init"))
val tId = requireNotNull(initResp.transactionID) {
"Upload init $description: missing transaction ID"
}
@@ -278,8 +280,7 @@ class EbicsClient(
// Transfer phase
for (i in 1..preparedPayload.segments.size) {
val transferXml = impl.uploadTransfer(tId, preparedPayload, i)
- impl.postBTS(client, transferXml, "Upload transfer", txLog.step("transfer$i"))
- .okOrFail("Upload transfer $description")
+ impl.postBTS(client, transferXml, "Upload transfer $description", txLog.step("transfer$i"))
}
return orderId
}
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsOrder.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsOrder.kt
@@ -116,7 +116,7 @@ sealed class EbicsOrder(val schema: String) {
}
infix fun Collection<EbicsOrder>.select(other: Collection<EbicsOrder>): List<EbicsOrder>
- = other.filter { order -> this.any { filter -> filter.match(order) } }
+ = this.flatMap { filter -> other.filter { order -> filter.match(order) } }
enum class OrderDoc {
/// EBICS acknowledgement - CustomerAcknowledgement HAC pain.002
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/iso20022/camt.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/iso20022/camt.kt
@@ -51,12 +51,12 @@ data class IncomingId(
append(uetr.toString())
}
if (txId != null) {
- if (length != 1) append(", ")
+ if (length != 1) append(" ")
append("tx=")
append(txId)
}
if (acctSvcrRef != null) {
- if (length != 1) append(", ")
+ if (length != 1) append(" ")
append("ref=")
append(acctSvcrRef)
}
@@ -92,12 +92,12 @@ data class OutgoingId(
append(msgId.toString())
}
if (endToEndId != null) {
- if (length != 1) append(", ")
+ if (length != 1) append(" ")
append("e2e=")
append(endToEndId)
}
if (acctSvcrRef != null) {
- if (length != 1) append(", ")
+ if (length != 1) append(" ")
append("ref=")
append(acctSvcrRef)
}
@@ -126,7 +126,7 @@ data class BatchId(
append(msgId.toString())
}
if (acctSvcrRef != null) {
- if (length != 1) append(", ")
+ if (length != 1) append(" ")
append("ref=")
append(acctSvcrRef)
}
@@ -445,8 +445,8 @@ private fun XmlDestructor.optBankTransactionCode(): BankTransactionCode? {
}
/** Parse transaction wire transfer subject */
-private fun XmlDestructor.wireTransferSubject(): String? {
- return opt("RmtInf")?.map("Ustrd") { text() }?.joinToString("")?.trim()
+private fun XmlDestructor.wireTransferSubject(): String? = opt("RmtInf") {
+ map("Ustrd") { text() }?.joinToString("")?.trim()
}
/** Parse account information */
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/test/TxCheck.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/test/TxCheck.kt
@@ -1,6 +1,6 @@
/*
* This file is part of LibEuFin.
- * Copyright (C) 2024 Taler Systems S.A.
+ * Copyright (C) 2024-2025 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
@@ -24,10 +24,7 @@ import tech.libeufin.common.*
import tech.libeufin.nexus.BankPublicKeysFile
import tech.libeufin.nexus.ClientPrivateKeysFile
import tech.libeufin.nexus.NexusEbicsConfig
-import tech.libeufin.nexus.ebics.EbicsBTS
-import tech.libeufin.nexus.ebics.EbicsOrder
-import tech.libeufin.nexus.ebics.postBTS
-import tech.libeufin.nexus.ebics.prepareUploadPayload
+import tech.libeufin.nexus.ebics.*
import tech.libeufin.nexus.logger
data class TxCheckResult(
@@ -58,28 +55,28 @@ suspend fun txCheck(
suspend fun EbicsBTS.close(id: String, phase: String) {
val xml = downloadReceipt(id, false)
- postBTS(client, xml, phase).okOrFail(phase)
+ postBTS(client, xml, phase)
}
val firstTxId = fetch.postBTS(client, fetch.downloadInitialization(null, null), "Init first fetch")
- .okOrFail("Init first fetch")
.transactionID!!
try {
- fetch.postBTS(client, fetch.downloadInitialization(null, null), "Init second fetch").ok()?.run {
+ try {
+ val id = fetch.postBTS(client, fetch.downloadInitialization(null, null), "Init second fetch").transactionID!!
result.concurrentFetchAndFetch = true
- fetch.close(transactionID!!, "Init second fetch")
- }
+ fetch.close(id, "Init second fetch")
+ } catch (e: EbicsError.Code) {}
+
var paylod = prepareUploadPayload(cfg, clientKeys, bankKeys, ByteArray(2000000).rand())
- val submitId = submit.postBTS(client, submit.uploadInitialization(paylod), "Init first submit").ok()?.run {
- result.concurrentFetchAndSubmit = true
- transactionID!!
- }
- if (submitId != null) {
- submit.postBTS(client, submit.uploadTransfer(submitId, paylod, 1), "Submit upload").okOrFail("Submit first upload")
- submit.postBTS(client, submit.uploadInitialization(paylod), "Init second submit").ok()?.run {
+ try {
+ val submitId = submit.postBTS(client, submit.uploadInitialization(paylod), "Init first submit"). transactionID!!
+ result.concurrentFetchAndSubmit = true
+ submit.postBTS(client, submit.uploadTransfer(submitId, paylod, 1), "Submit first upload")
+ try {
+ submit.postBTS(client, submit.uploadInitialization(paylod), "Init second submit")
result.concurrentSubmitAndSubmit = true
- }
- }
+ } catch (e: EbicsError.Code) {}
+ } catch (e: EbicsError.Code) {}
} finally {
fetch.close(firstTxId, "Close first fetch")
}