libeufin

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

commit e83b5f62a30a794e8bcb993141648db7e28a8ca9
parent e904129310ec75ed98bf1bfefbec73c1e5f29fc6
Author: MS <ms@taler.net>
Date:   Mon,  7 Dec 2020 18:00:50 +0100

Camt ingestion.

Whenever the parser isn't able to ingest one Camt document,
Nexus throws an exception and flags the document as "undigested".
This way, every ingested message can only have one transaction.

Before this change, invalid documents were allowed to *look*
like ingested but having acutally no sub-transactions.  In practice,
that was a *unstructured* way of classifying undigested messages.

Diffstat:
Mnexus/src/main/kotlin/tech/libeufin/nexus/bankaccount/BankAccount.kt | 8+++-----
Mnexus/src/main/kotlin/tech/libeufin/nexus/iso20022/Iso20022.kt | 48++++++------------------------------------------
Mnexus/src/main/kotlin/tech/libeufin/nexus/server/NexusServer.kt | 4+++-
3 files changed, 12 insertions(+), 48 deletions(-)

diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/bankaccount/BankAccount.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/bankaccount/BankAccount.kt @@ -106,7 +106,6 @@ suspend fun submitAllPaymentInitiations(httpClient: HttpClient, accountid: Strin } } - /** * Check if the transaction is already found in the database. */ @@ -130,7 +129,7 @@ fun processCamtMessage(bankAccountId: String, camtDoc: Document, code: String): val res = try { parseCamtMessage(camtDoc) } catch (e: CamtParsingError) { - logger.warn("Invalid CAMT received from bank") + logger.warn("Invalid CAMT received from bank: $e") return@transaction false } val stamp = ZonedDateTime.parse(res.creationDateTime, DateTimeFormatter.ISO_DATE_TIME).toInstant().toEpochMilli() @@ -148,7 +147,6 @@ fun processCamtMessage(bankAccountId: String, camtDoc: Document, code: String): } } } - val entries = res.reports.map { it.entries }.flatten() logger.info("found ${entries.size} transactions") txloop@ for (tx in entries) { @@ -164,7 +162,6 @@ fun processCamtMessage(bankAccountId: String, camtDoc: Document, code: String): // https://bugs.gnunet.org/view.php?id=6381 break } - val rawEntity = NexusBankTransactionEntity.new { bankAccount = acct accountTransactionId = "AcctSvcrRef:$acctSvcrRef" @@ -190,7 +187,8 @@ fun processCamtMessage(bankAccountId: String, camtDoc: Document, code: String): paymentInitiation.confirmationTransaction = rawEntity } } - // FIXME: find matching PaymentInitiation by PaymentInformationID, message ID or whatever is present + // FIXME: find matching PaymentInitiation + // by PaymentInformationID, message ID or whatever is present } } return@transaction true diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/iso20022/Iso20022.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/iso20022/Iso20022.kt @@ -633,18 +633,6 @@ private fun XmlElementDestructor.extractCurrencyAmount(): CurrencyAmount { ) } -private fun XmlElementDestructor.maybeExtractTxCurrencyAmount(): CurrencyAmount? { - NexusAssert( - this.focusElement.tagName == "TxDtls", - "Wrong place to fetch a detailed amount" - ) - return maybeUniqueChildNamed("AmtDtls") { - requireUniqueChildNamed("TxAmt") { - maybeExtractCurrencyAmount() - } - } -} - private fun XmlElementDestructor.maybeExtractCurrencyAmount(): CurrencyAmount? { return maybeUniqueChildNamed("Amt") { CurrencyAmount( @@ -667,26 +655,14 @@ private fun XmlElementDestructor.extractMaybeCurrencyExchange(): CurrencyExchang } } -// FIXME: move to util module. -private fun currencyAmountSum(amount1: CurrencyAmount?, amount2: CurrencyAmount?): CurrencyAmount? { - if (amount1 == null) return amount2 - if (amount2 == null) return amount1 - - if (amount1.currency != amount2.currency) throw NexusError( - HttpStatusCode.InternalServerError, - "Trying to sum two amount with different currencies" - ) - return CurrencyAmount(currency = amount1.currency, value = amount1.value + amount2.value) -} - private fun XmlElementDestructor.extractBatches( inheritableAmount: CurrencyAmount?, outerCreditDebitIndicator: CreditDebitIndicator ): List<Batch> { - if (mapEachChildNamed("NtryDtls") {}.size != 1) return mutableListOf() + if (mapEachChildNamed("NtryDtls") {}.size != 1) throw CamtParsingError("This money movement is not a singleton #0") var txs = requireUniqueChildNamed("NtryDtls") { if (mapEachChildNamed("TxDtls") {}.size != 1) { - return@requireUniqueChildNamed mutableListOf<BatchTransaction>() + throw CamtParsingError("This money movement is not a singleton #1") } requireUniqueChildNamed("TxDtls") { val details = extractTransactionDetails(outerCreditDebitIndicator) @@ -779,17 +755,6 @@ private fun XmlElementDestructor.extractTransactionDetails( ) } -private fun XmlElementDestructor.extractSingleDetails( - outerAmount: CurrencyAmount, - outerCreditDebitIndicator: CreditDebitIndicator -): TransactionDetails { - return requireUniqueChildNamed("NtryDtls") { - requireUniqueChildNamed("TxDtls") { - extractTransactionDetails(outerCreditDebitIndicator) - } - } -} - private fun XmlElementDestructor.extractInnerBkTxCd(creditDebitIndicator: CreditDebitIndicator): String { val domain = maybeUniqueChildNamed("Domn") { maybeUniqueChildNamed("Cd") { focusElement.textContent } } @@ -853,7 +818,8 @@ private fun XmlElementDestructor.extractInnerTransactions(): CamtReport { amount = extractCurrencyAmount() ) } - + // Note: multiple Ntry's *are* allowed. What is not allowed is + // multiple money transactions *within* one Ntry element. val entries = mapEachChildNamed("Ntry") { val amount = extractCurrencyAmount() val status = requireUniqueChildNamed("Sts") { focusElement.textContent }.let { @@ -883,8 +849,6 @@ private fun XmlElementDestructor.extractInnerTransactions(): CamtReport { maybeUniqueChildNamed("InstdAmt") { extractCurrencyAmount() } } - // For now, only support account servicer reference as id - CamtBankAccountEntry( amount = amount, status = status, @@ -916,7 +880,8 @@ private fun XmlElementDestructor.extractInnerTransactions(): CamtReport { } /** - * Extract a list of transactions from an ISO20022 camt.052 / camt.053 message. + * Extract a list of transactions from + * an ISO20022 camt.052 / camt.053 message. */ fun parseCamtMessage(doc: Document): CamtParseResult { return destructXml(doc) { @@ -939,7 +904,6 @@ fun parseCamtMessage(doc: Document): CamtParseResult { } } } - val messageId = requireOnlyChild { requireUniqueChildNamed("GrpHdr") { requireUniqueChildNamed("MsgId") { focusElement.textContent } diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/server/NexusServer.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/server/NexusServer.kt @@ -688,7 +688,9 @@ fun serverMain(dbName: String, host: String) { transaction { authenticateRequest(call.request).id.value NexusBankTransactionEntity.all().map { - val tx = jacksonObjectMapper().readValue(it.transactionJson, CamtBankAccountEntry::class.java) + val tx = jacksonObjectMapper().readValue( + it.transactionJson, CamtBankAccountEntry::class.java + ) ret.transactions.add(tx) } }