libeufin

Integration and sandbox testing for FinTech APIs and data formats
Log | Files | Refs | Submodules | README | LICENSE

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:
Mcontrib/libeufin-nexus.conf | 4++--
Mnexus/src/main/kotlin/tech/libeufin/nexus/EbicsFetch.kt | 59+++++++++++++++++++++++++++++++++++++++++++++++++++--------
Mnexus/src/main/kotlin/tech/libeufin/nexus/EbicsSubmit.kt | 2+-
Mnexus/src/test/kotlin/Ebics.kt | 1-
Mutil/src/main/kotlin/Ebics.kt | 2+-
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"),