libeufin

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

commit ce82d2aea3a2919d019bd37c89e4d4346811fc17
parent be4ffcbcc55d656fbbf983de10d4e483949b127d
Author: Florian Dold <florian.dold@gmail.com>
Date:   Fri, 19 Jun 2020 18:23:30 +0530

store date

Diffstat:
Mnexus/src/main/kotlin/tech/libeufin/nexus/DB.kt | 9+++------
Mnexus/src/main/kotlin/tech/libeufin/nexus/Iso20022.kt | 23++++++++++++++++++++---
Mnexus/src/main/kotlin/tech/libeufin/nexus/bankaccount/BankAccount.kt | 28+++++++++++++++++++++++++---
Mnexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsNexus.kt | 4++--
Mnexus/src/test/kotlin/Iso20022Test.kt | 4++--
Mutil/src/main/kotlin/time.kt | 4++--
6 files changed, 54 insertions(+), 18 deletions(-)

diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/DB.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/DB.kt @@ -220,14 +220,11 @@ object NexusBankAccountsTable : IdTable<String>() { val bankCode = text("bankCode") val defaultBankConnection = reference("defaultBankConnection", NexusBankConnectionsTable).nullable() - // ISO-8601 zoned date time - val lastStatementCreationTimestamp = text("lastStatementCreationTimestamp").nullable() + val lastStatementCreationTimestamp = long("lastStatementCreationTimestamp").nullable() - // ISO-8601 zoned date time - val lastReportCreationTimestamp = text("lastReportCreationTimestamp").nullable() + val lastReportCreationTimestamp = long("lastReportCreationTimestamp").nullable() - // ISO-8601 zoned date time - val lastNotificationCreationTimestamp = text("lastNotificationCreationTimestamp").nullable() + val lastNotificationCreationTimestamp = long("lastNotificationCreationTimestamp").nullable() // Highest bank message ID that this bank account is aware of. val highestSeenBankMessageId = integer("highestSeenBankMessageId") diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/Iso20022.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/Iso20022.kt @@ -513,14 +513,20 @@ private fun XmlElementDestructor.extractInnerTransactions(): List<BankTransactio } } +data class CamtParseResult( + val transactions: List<BankTransaction>, + val messageId: String, + val creationDateTime: String +) + /** * Extract a list of transactions from an ISO20022 camt.052 / camt.053 message. */ -fun getTransactions(doc: Document): List<BankTransaction> { +fun parseCamtMessage(doc: Document): CamtParseResult { return destructXml(doc) { requireRootElement("Document") { // Either bank to customer statement or report - requireOnlyChild { + val transactions = requireOnlyChild { when (it.localName) { "BkToCstmrAcctRpt" -> { mapEachChildNamed("Rpt") { @@ -536,7 +542,18 @@ fun getTransactions(doc: Document): List<BankTransaction> { throw CamtParsingError("expected statement or report") } } + }.flatten() + val messageId = requireOnlyChild { + requireUniqueChildNamed("GrpHdr") { + requireUniqueChildNamed("MsgId") { it.textContent } + } } + val creationDateTime = requireOnlyChild { + requireUniqueChildNamed("GrpHdr") { + requireUniqueChildNamed("CreDtTm") { it.textContent } + } + } + CamtParseResult(transactions, messageId, creationDateTime) } - }.flatten() + } } \ No newline at end of file diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/bankaccount/BankAccount.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/bankaccount/BankAccount.kt @@ -33,6 +33,8 @@ import tech.libeufin.nexus.server.FetchSpecJson import tech.libeufin.nexus.server.Pain001Data import tech.libeufin.util.XMLUtil import java.time.Instant +import java.time.ZonedDateTime +import java.time.format.DateTimeFormatter suspend fun submitPaymentInitiation(httpClient: HttpClient, paymentInitiationId: Long) { @@ -104,7 +106,8 @@ private fun findDuplicate(bankAccountId: String, acctSvcrRef: String): NexusBank fun processCamtMessage( bankAccountId: String, - camtDoc: Document + camtDoc: Document, + code: String ) { logger.info("processing CAMT message") transaction { @@ -112,7 +115,26 @@ fun processCamtMessage( if (acct == null) { throw NexusError(HttpStatusCode.NotFound, "user not found") } - val transactions = getTransactions(camtDoc) + val res = parseCamtMessage(camtDoc) + + val stamp = ZonedDateTime.parse(res.creationDateTime, DateTimeFormatter.ISO_DATE_TIME).toInstant().toEpochMilli() + + when (code) { + "C52" -> { + val s = acct.lastReportCreationTimestamp + if (s != null && stamp > s) { + acct.lastReportCreationTimestamp = stamp + } + } + "C53" -> { + val s = acct.lastStatementCreationTimestamp + if (s != null && stamp > s) { + acct.lastStatementCreationTimestamp = stamp + } + } + } + + val transactions = res.transactions logger.info("found ${transactions.size} transactions") txloop@ for (tx in transactions) { val acctSvcrRef = tx.accountServicerReference @@ -183,7 +205,7 @@ fun ingestBankMessagesIntoAccount( }.orderBy(Pair(NexusBankMessagesTable.id, SortOrder.ASC)).forEach { // FIXME: check if it's CAMT first! val doc = XMLUtil.parseStringIntoDom(it.message.bytes.toString(Charsets.UTF_8)) - processCamtMessage(bankAccountId, doc) + processCamtMessage(bankAccountId, doc, it.code) lastId = it.id.value } acct.highestSeenBankMessageId = lastId diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsNexus.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsNexus.kt @@ -82,10 +82,10 @@ suspend fun fetchEbicsBySpec( } object { val lastStatement = acct.lastStatementCreationTimestamp?.let { - ZonedDateTime.parse(it, DateTimeFormatter.ISO_DATE_TIME) + ZonedDateTime.ofInstant(Instant.ofEpochMilli(it), ZoneOffset.UTC) } val lastReport = acct.lastReportCreationTimestamp?.let { - ZonedDateTime.parse(it, DateTimeFormatter.ISO_DATE_TIME) + ZonedDateTime.ofInstant(Instant.ofEpochMilli(it), ZoneOffset.UTC) } } } diff --git a/nexus/src/test/kotlin/Iso20022Test.kt b/nexus/src/test/kotlin/Iso20022Test.kt @@ -18,8 +18,8 @@ class Iso20022Test { @Test fun testTransactionsImport() { val camt53 = loadXmlResource("iso20022-samples/camt.053.001.02.gesamtbeispiel.xml") - val txs = getTransactions(camt53) - for (tx in txs) { + val r = parseCamtMessage(camt53) + for (tx in r.transactions) { val txStr = jacksonObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(tx) println(txStr) val tx2 = jacksonObjectMapper().readValue(txStr, BankTransaction::class.java) diff --git a/util/src/main/kotlin/time.kt b/util/src/main/kotlin/time.kt @@ -27,11 +27,11 @@ fun LocalDateTime.toZonedString(): String { } fun LocalDateTime.toDashedDate(): String { - return DateTimeFormatter.ISO_OFFSET_DATE.format(this) + return DateTimeFormatter.ISO_DATE.format(this) } fun parseDashedDate(date: String): LocalDateTime { - val dtf = DateTimeFormatter.ISO_OFFSET_DATE + val dtf = DateTimeFormatter.ISO_DATE val asDate = LocalDate.parse(date, dtf) return asDate.atStartOfDay() }