commit de7e5a9726a29a39b801cf0a2b1e708c36f7f2aa
parent 1e3e092725d11d8c1e1f76a6fec421606884dc72
Author: MS <ms@taler.net>
Date: Tue, 14 Nov 2023 09:25:26 +0100
nexus fetch: implementing HAC.
Diffstat:
4 files changed, 79 insertions(+), 13 deletions(-)
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsFetch.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsFetch.kt
@@ -47,9 +47,9 @@ data class FetchContext(
*/
val whichDocument: SupportedDocument,
/**
- * EBICS version.
+ * EBICS version. For the HAC message type, version gets switched to EBICS 2.
*/
- val ebicsVersion: EbicsVersion = EbicsVersion.three,
+ var ebicsVersion: EbicsVersion = EbicsVersion.three,
/**
* Start date of the returned documents. Only
* used in --transient mode.
@@ -118,8 +118,13 @@ private suspend inline fun downloadHelper(
*
* @param cfg config handle.
* @param content ZIP bytes from the server.
+ * @param nonZip only true when downloading via HAC (EBICS 2)
*/
-fun maybeLogFile(cfg: EbicsSetupConfig, content: ByteArray) {
+fun maybeLogFile(
+ cfg: EbicsSetupConfig,
+ content: ByteArray,
+ nonZip: Boolean = false
+) {
// Main dir.
val maybeLogDir = cfg.config.lookupString(
"nexus-fetch",
@@ -133,15 +138,23 @@ fun maybeLogFile(cfg: EbicsSetupConfig, content: ByteArray) {
// Creating the combined dir.
val dirs = Path.of(maybeLogDir, subDir)
doOrFail { dirs.createDirectories() }
- // Write each ZIP entry in the combined dir.
- content.unzipForEach { fileName, xmlContent ->
- val f = File(dirs.toString(), "${now.toDbMicros()}_$fileName")
- // Rare: cannot download the same file twice in the same microsecond.
+ fun maybeWrite(f: File, xml: String) {
if (f.exists()) {
logger.error("Log file exists already at: ${f.path}")
exitProcess(1)
}
- doOrFail { f.writeText(xmlContent) }
+ doOrFail { f.writeText(xml) }
+ }
+ if (nonZip) {
+ val f = File(dirs.toString(), "${now.toDbMicros()}_HAC_response.pain.002.xml")
+ maybeWrite(f, content.toString(Charsets.UTF_8))
+ return
+ }
+ // Write each ZIP entry in the combined dir.
+ content.unzipForEach { fileName, xmlContent ->
+ val f = File(dirs.toString(), "${now.toDbMicros()}_$fileName")
+ // Rare: cannot download the same file twice in the same microsecond.
+ maybeWrite(f, xmlContent)
}
}
@@ -174,7 +187,11 @@ private suspend fun fetchDocuments(
logger.debug("Fetching documents from timestamp: $lastExecutionTime")
val maybeContent = downloadHelper(ctx, lastExecutionTime) ?: exitProcess(1) // client is wrong, failing.
if (maybeContent.isEmpty()) return
- maybeLogFile(ctx.cfg, maybeContent)
+ maybeLogFile(
+ ctx.cfg,
+ maybeContent,
+ nonZip = ctx.whichDocument == SupportedDocument.PAIN_002_LOGS
+ )
}
class EbicsFetch: CliktCommand("Fetches bank records. Defaults to camt.054 notifications") {
@@ -200,6 +217,10 @@ class EbicsFetch: CliktCommand("Fetches bank records. Defaults to camt.054 noti
help = "Downloads only camt.052 intraday reports"
).flag(default = false)
+ private val onlyLogs by option(
+ help = "Downloads only EBICS activity logs via pain.002, only available to --transient mode. Config needs log directory"
+ ).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" +
@@ -236,6 +257,7 @@ class EbicsFetch: CliktCommand("Fetches bank records. Defaults to camt.054 noti
if (onlyAck) whichDoc = SupportedDocument.PAIN_002
if (onlyReports) whichDoc = SupportedDocument.CAMT_052
if (onlyStatements) whichDoc = SupportedDocument.CAMT_053
+ if (onlyLogs) whichDoc = SupportedDocument.PAIN_002_LOGS
val ctx = FetchContext(
cfg,
@@ -256,6 +278,8 @@ class EbicsFetch: CliktCommand("Fetches bank records. Defaults to camt.054 noti
}
} else null
ctx.pinnedStart = pinnedStartArg
+ if (whichDoc == SupportedDocument.PAIN_002_LOGS)
+ ctx.ebicsVersion = EbicsVersion.two
runBlocking {
fetchDocuments(db, ctx)
}
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/Ebics2.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/Ebics2.kt
@@ -384,6 +384,28 @@ private fun prepNotificationRequest2(
}
/**
+ * Prepares an EBICS 2 request to get logs from the bank about any
+ * uploaded or downloaded document.
+ *
+ * @param startDate earliest timestamp of the returned document(s). If
+ * null, it defaults to download the unseen documents.
+ * @param endDate latest timestamp of the returned document(s). If
+ * null, it defaults to the current time.
+ * @return [Ebics2Request] object to be first converted in XML and
+ * then be passed to the EBICS downloader.
+ */
+private fun prepLogsRequest2(
+ startDate: Instant? = null,
+ endDate: Instant? = null
+): Ebics2Request {
+ val maybeDateRange = if (startDate != null) EbicsDateRange(startDate, endDate ?: Instant.now()) else null
+ return Ebics2Request(
+ messageType = "HAC",
+ orderParams = EbicsStandardOrderParams(dateRange = maybeDateRange)
+ )
+}
+
+/**
* Abstracts EBICS 2 request creation of a download init phase.
*
* @param whichDoc type of wanted document.
@@ -402,4 +424,5 @@ fun prepEbics2Document(
SupportedDocument.CAMT_052 -> prepReportRequest2(startDate)
SupportedDocument.CAMT_053 -> prepStatementRequest2(startDate)
SupportedDocument.CAMT_054 -> prepNotificationRequest2(startDate)
+ SupportedDocument.PAIN_002_LOGS -> prepLogsRequest2(startDate)
}
\ No newline at end of file
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/Ebics3.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/Ebics3.kt
@@ -272,7 +272,7 @@ private fun getEbics3DateRange(
*
* @return [Ebics3Request.OrderDetails.BTOrderParams]
*/
-private fun prepNotificationRequest3(
+fun prepNotificationRequest3(
startDate: Instant? = null,
endDate: Instant? = null,
isAppendix: Boolean
@@ -309,7 +309,7 @@ private fun prepNotificationRequest3(
*
* @return [Ebics3Request.OrderDetails.BTOrderParams]
*/
-private fun prepAckRequest3(
+fun prepAckRequest3(
startDate: Instant? = null,
endDate: Instant? = null
): Ebics3Request.OrderDetails.BTOrderParams {
@@ -342,7 +342,7 @@ private fun prepAckRequest3(
*
* @return [Ebics3Request.OrderDetails.BTOrderParams]
*/
-private fun prepStatementRequest3(
+fun prepStatementRequest3(
startDate: Instant? = null,
endDate: Instant? = null
): Ebics3Request.OrderDetails.BTOrderParams {
@@ -375,7 +375,7 @@ private fun prepStatementRequest3(
*
* @return [Ebics3Request.OrderDetails.BTOrderParams]
*/
-private fun prepReportRequest3(
+fun prepReportRequest3(
startDate: Instant? = null,
endDate: Instant? = null
): Ebics3Request.OrderDetails.BTOrderParams {
@@ -417,4 +417,5 @@ fun prepEbics3Document(
SupportedDocument.CAMT_052 -> prepReportRequest3(startDate)
SupportedDocument.CAMT_053 -> prepStatementRequest3(startDate)
SupportedDocument.CAMT_054 -> prepReportRequest3(startDate)
+ SupportedDocument.PAIN_002_LOGS -> throw Exception("HAC (--only-logs) not available in EBICS 3")
}
\ No newline at end of file
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsCommon.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsCommon.kt
@@ -65,9 +65,27 @@ enum class EbicsVersion { two, three }
* Which documents can be downloaded via EBICS.
*/
enum class SupportedDocument {
+ /**
+ * Payment acknowledgement.
+ */
PAIN_002,
+ /**
+ * From an HAC request. Informs about any
+ * download/upload activity, including wrong
+ * documents.
+ */
+ PAIN_002_LOGS,
+ /**
+ * Account statements.
+ */
CAMT_053,
+ /**
+ * Account intraday reports.
+ */
CAMT_052,
+ /**
+ * Account notifications.
+ */
CAMT_054
}