commit 8db1a45e62b8b1f117ee90af0e1db6ee53d6c008
parent b7207403cb0c66b4e2f635b2e06c7943d2e1478e
Author: MS <ms@taler.net>
Date: Fri, 10 Nov 2023 19:03:24 +0100
nexus fetch
supporting more EBICS formats and an explicit start date
of the returned documents
Diffstat:
5 files changed, 55 insertions(+), 13 deletions(-)
diff --git a/contrib/libeufin-nexus.conf b/contrib/libeufin-nexus.conf
@@ -43,11 +43,11 @@ CONFIG = postgres:///libeufin-nexus
SQL_DIR = $DATADIR/sql/
[nexus-fetch]
-FREQUENCY = 30s # used when long-polling is not supported
+FREQUENCY = 30s
STATEMENT_LOG_DIRECTORY = /tmp/ebics-messages/
[nexus-submit]
-FREQUENCY = 30s # use 0 to always submit immediately (via LISTEN trigger)
+FREQUENCY = 30s
[nexus-httpd]
PORT = 8080
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsFetch.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsFetch.kt
@@ -303,6 +303,10 @@ fun maybeLogFile(cfg: EbicsSetupConfig, content: ByteArray) {
* @param httpClient HTTP client handle to reach the bank
* @param clientKeys EBICS subscriber private keys.
* @param bankKeys bank public keys.
+ * @param pinnedStart explicit start date for the downloaded documents.
+ * This parameter makes the last incoming transaction timestamp in
+ * the database IGNORED. Only useful when running in --transient
+ * mode to download past documents / debug.
*/
private suspend fun fetchDocuments(
cfg: EbicsSetupConfig,
@@ -310,16 +314,17 @@ private suspend fun fetchDocuments(
httpClient: HttpClient,
clientKeys: ClientPrivateKeysFile,
bankKeys: BankPublicKeysFile,
- whichDocument: SupportedDocument = SupportedDocument.CAMT_054
+ whichDocument: SupportedDocument = SupportedDocument.CAMT_054,
+ pinnedStart: Instant? = null
) {
// maybe get last execution_date.
- val lastExecutionTime: Instant? = db.incomingPaymentLastExecTime()
+ val lastExecutionTime: Instant? = pinnedStart ?: db.incomingPaymentLastExecTime()
logger.debug("Fetching documents from timestamp: $lastExecutionTime")
val req = when(whichDocument) {
- SupportedDocument.PAIN_002 -> prepAckRequest(startDate = lastExecutionTime)
- SupportedDocument.CAMT_052 -> prepReportRequest(startDate = lastExecutionTime)
- SupportedDocument.CAMT_053 -> prepStatementRequest(startDate = lastExecutionTime)
- SupportedDocument.CAMT_054 -> prepNotificationRequest(startDate = lastExecutionTime, isAppendix = false)
+ SupportedDocument.PAIN_002 -> prepAckRequest(lastExecutionTime)
+ SupportedDocument.CAMT_052 -> prepReportRequest(lastExecutionTime)
+ SupportedDocument.CAMT_053 -> prepStatementRequest(lastExecutionTime)
+ SupportedDocument.CAMT_054 -> prepNotificationRequest(lastExecutionTime, isAppendix = true)
}
val maybeContent = downloadHelper(
cfg,
@@ -333,6 +338,19 @@ private suspend fun fetchDocuments(
maybeLogFile(cfg, maybeContent)
}
+/**
+ * Turns a YYYY-MM-DD date string into Instant. Used
+ * to parse the --pinned-start CLI options. Fails the
+ * process, if the input is invalid.
+ *
+ * @param dashedDate pinned start command line option.
+ * @return [Instant]
+ */
+fun parseDashedDate(dashedDate: String): Instant =
+ doOrFail {
+ LocalDate.parse(dashedDate).atStartOfDay(ZoneId.of("UTC")).toInstant()
+ }
+
enum class SupportedDocument {
PAIN_002,
CAMT_053,
@@ -354,6 +372,20 @@ class EbicsFetch: CliktCommand("Fetches bank records. Defaults to camt.054 noti
help = "Downloads only camt.053 statements"
).flag(default = false)
+ private val onlyAck by option(
+ help = "Downloads only pain.002 acknowledgements"
+ ).flag(default = false)
+
+ private val onlyReports by option(
+ help = "Downloads only camt.052 intraday reports"
+ ).flag(default = false)
+
+ private val pinnedStart by option(
+ help = "constant YYYY-MM-DD date for the earliest document to download " +
+ "(only consumed in --transient mode). The latest document is always" +
+ " until the current time."
+ )
+
/**
* This function collects the main steps of fetching banking records.
* In this current version, it does not implement long polling, instead
@@ -380,9 +412,19 @@ class EbicsFetch: CliktCommand("Fetches bank records. Defaults to camt.054 noti
exitProcess(1)
}
val httpClient = HttpClient()
- val whichDoc = if (onlyStatements) SupportedDocument.CAMT_053 else SupportedDocument.CAMT_054
+
+ var whichDoc = SupportedDocument.CAMT_054
+ if (onlyAck) whichDoc = SupportedDocument.PAIN_002
+ if (onlyReports) whichDoc = SupportedDocument.CAMT_052
+ if (onlyStatements) whichDoc = SupportedDocument.CAMT_053
+
if (transient) {
logger.info("Transient mode: fetching once and returning.")
+ val pinnedStartVal = pinnedStart
+ val pinnedStartArg = if (pinnedStartVal != null) {
+ logger.debug("Pinning start date to: $pinnedStartVal")
+ parseDashedDate(pinnedStartVal)
+ } else null
runBlocking {
fetchDocuments(
cfg,
@@ -390,7 +432,8 @@ class EbicsFetch: CliktCommand("Fetches bank records. Defaults to camt.054 noti
httpClient,
clientKeys,
bankKeys,
- whichDoc
+ whichDoc,
+ pinnedStartArg
)
}
return
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsSubmit.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsSubmit.kt
@@ -134,7 +134,7 @@ private suspend fun submitInitiatedPayment(
}
// Submission succeeded, storing the pain.001 to file.
val logDir: String? = cfg.config.lookupString(
- "[neuxs-submit]",
+ "neuxs-submit",
"SUBMISSIONS_LOG_DIRECTORY"
)
if (logDir != null) {
diff --git a/nexus/src/test/kotlin/Ebics.kt b/nexus/src/test/kotlin/Ebics.kt
@@ -15,7 +15,6 @@ import kotlin.test.assertNull
import kotlin.test.assertTrue
class Ebics {
-
// Checks XML is valid and INI.
@Test
fun iniMessage() {
diff --git a/util/src/main/kotlin/Ebics.kt b/util/src/main/kotlin/Ebics.kt
@@ -462,7 +462,7 @@ enum class EbicsReturnCode(val errorCode: String) {
EBICS_DOWNLOAD_POSTPROCESS_SKIPPED("011001"),
EBICS_TX_SEGMENT_NUMBER_UNDERRUN("011101"),
EBICS_AUTHENTICATION_FAILED ("061001"),
- EBICS_EBICS_AUTHORISATION_ORDER_IDENTIFIER_FAILED("090003"),
+ EBICS_AUTHORISATION_ORDER_IDENTIFIER_FAILED("090003"),
EBICS_NO_DOWNLOAD_DATA_AVAILABLE("090005"),
EBICS_INVALID_USER_OR_USER_STATE("091002"),
EBICS_EBICS_INVALID_USER_STATE("091004"),