libeufin

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

commit a1820a97aaef36e0da8b7873dfacfa5363342045
parent 8a74c8241cb89984e044b4d6136a4dc0763773f3
Author: MS <ms@taler.net>
Date:   Wed, 15 Feb 2023 15:04:03 +0100

addressing #6988

Diffstat:
Mnexus/src/main/kotlin/tech/libeufin/nexus/BankConnectionProtocol.kt | 14+++++++++++---
Mnexus/src/main/kotlin/tech/libeufin/nexus/Taler.kt | 1-
Mnexus/src/main/kotlin/tech/libeufin/nexus/bankaccount/BankAccount.kt | 13+++++++++++--
Mnexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsClient.kt | 4+---
Mnexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsNexus.kt | 14+++++++++++++-
Mnexus/src/main/kotlin/tech/libeufin/nexus/server/NexusServer.kt | 20+++++++++++++++++++-
6 files changed, 55 insertions(+), 11 deletions(-)

diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/BankConnectionProtocol.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/BankConnectionProtocol.kt @@ -58,14 +58,22 @@ interface BankConnectionProtocol { // Send to the bank a previously prepared payment instruction. suspend fun submitPaymentInitiation(httpClient: HttpClient, paymentInitiationId: Long) - // Downloads transactions from the bank, according to the specification - // given in the arguments. + /** + * Downloads transactions from the bank, according to the specification + * given in the arguments. + * + * This function returns a possibly empty list of exceptions. + * That helps not to stop fetching if ONE operation fails. Notably, + * C52 and C53 may be asked along one invocation of this function, + * therefore storing the exception on C52 allows the C53 to still + * take place. The caller then decides how to handle the exceptions. + */ suspend fun fetchTransactions( fetchSpec: FetchSpecJson, client: HttpClient, bankConnectionId: String, accountId: String - ) + ): List<Exception>? } fun getConnectionPlugin(connId: String): BankConnectionProtocol { diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/Taler.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/Taler.kt @@ -502,7 +502,6 @@ private suspend fun historyIncoming(call: ApplicationCall) { * In the future, a dedicate "add-incoming" facade should * be provided, offering the mean to store the credentials * at configuration time. - * */ private suspend fun addIncoming(call: ApplicationCall) { val facadeId = ensureNonNull(call.parameters["fcid"]) diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/bankaccount/BankAccount.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/bankaccount/BankAccount.kt @@ -153,7 +153,15 @@ data class CamtTransactionsCount( * Total number of transactions that were included in a report * or a statement. */ - val downloadedTransactions: Int + val downloadedTransactions: Int, + /** + * Exceptions occurred while fetching transactions. Fetching + * transactions can be done via multiple EBICS messages, therefore + * a failing one should not prevent other messages to be sent. + * This list collects all the exceptions that happened during the + * execution of a batch of messages. + */ + var errors: List<Exception>? = null ) /** @@ -426,7 +434,7 @@ suspend fun fetchBankAccountTransactions( * document into the database. This function tries to download * both reports AND statements even if the first one fails. */ - getConnectionPlugin(res.connectionType).fetchTransactions( + val errors: List<Exception>? = getConnectionPlugin(res.connectionType).fetchTransactions( fetchSpec, client, res.connectionName, @@ -437,6 +445,7 @@ suspend fun fetchBankAccountTransactions( ingestFacadeTransactions(accountId, "taler-wire-gateway", ::talerFilter, ::maybeTalerRefunds) ingestFacadeTransactions(accountId, "anastasis", ::anastasisFilter, null) + ingestionResult.errors = errors return ingestionResult } diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsClient.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsClient.kt @@ -239,9 +239,7 @@ suspend fun doEbicsDownloadTransaction( return EbicsDownloadSuccessResult(respPayload) } -/** - * Currently only 1-segment requests. - */ +// Currently only 1-segment requests. suspend fun doEbicsUploadTransaction( client: HttpClient, subscriberDetails: EbicsClientSubscriberDetails, diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsNexus.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsNexus.kt @@ -402,13 +402,20 @@ fun formatHex(ba: ByteArray): String { return out } +/** + * This function returns a possibly empty list of Exception. + * That helps not to stop fetching if ONE operation fails. Notably, + * C52 and C53 may be asked along one invocation of this function, + * therefore storing the exception on C52 allows the C53 to still + * take place. The caller then decides how to handle the exceptions. + */ class EbicsBankConnectionProtocol: BankConnectionProtocol { override suspend fun fetchTransactions( fetchSpec: FetchSpecJson, client: HttpClient, bankConnectionId: String, accountId: String - ) { + ): List<Exception>? { val subscriberDetails = transaction { getEbicsSubscriberDetails(bankConnectionId) } val lastTimes = transaction { val acct = NexusBankAccountEntity.findByName(accountId) @@ -507,6 +514,7 @@ class EbicsBankConnectionProtocol: BankConnectionProtocol { } } // Downloads and stores the bank message into the database. No ingestion. + val errors = mutableListOf<Exception>() for (spec in specs) { try { fetchEbicsC5x( @@ -518,8 +526,12 @@ class EbicsBankConnectionProtocol: BankConnectionProtocol { ) } catch (e: Exception) { logger.warn("Fetching transactions (${spec.orderType}) excepted: ${e.message}.") + errors.add(e) } } + if (errors.size > 0) + return errors + return null } /** * Submit one Pain.001 for one payment initiations. diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/server/NexusServer.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/server/NexusServer.kt @@ -729,7 +729,25 @@ val nexusApp: Application.() -> Unit = { ) } val ingestionResult = fetchBankAccountTransactions(client, fetchSpec, accountid) - call.respond(ingestionResult) + var statusCode = HttpStatusCode.OK + /** + * Client errors are unlikely here, because authentication + * and JSON validity fail earlier. Hence either Nexus or the + * bank had a problem. NOTE: because this handler triggers multiple + * fetches, it is ALSO possible that although one error is reported, + * SOME transactions made it to the database! + */ + if (ingestionResult.errors != null) + /** + * 500 is intentionally generic, because multiple errors + * may suggest different statuses. The response body however + * informs the client about what failed. + */ + statusCode = HttpStatusCode.InternalServerError + call.respond( + status = statusCode, + ingestionResult + ) return@post }