libeufin

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

commit 96bbb55757be2dcc7b0c7341ffc52d14fdacec36
parent 8c26eab27768ae507a5460770ace216fd43f882c
Author: MS <ms@taler.net>
Date:   Tue, 29 Nov 2022 19:44:01 +0100

Avoid retrying invalid Pain.001.

Diffstat:
Mnexus/src/main/kotlin/tech/libeufin/nexus/DB.kt | 2++
Mnexus/src/main/kotlin/tech/libeufin/nexus/bankaccount/BankAccount.kt | 13++++++++++---
Mnexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsNexus.kt | 25+++++++++++++++++--------
Mnexus/src/test/kotlin/DownloadAndSubmit.kt | 6+++---
Mnexus/src/test/kotlin/MakeEnv.kt | 4++--
Mutil/src/main/kotlin/XMLUtil.kt | 6+++++-
6 files changed, 39 insertions(+), 17 deletions(-)

diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/DB.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/DB.kt @@ -211,6 +211,7 @@ object PaymentInitiationsTable : LongIdTable() { val creditorBic = text("creditorBic").nullable() val creditorName = text("creditorName") val submitted = bool("submitted").default(false) + var invalid = bool("invalid").nullable() val messageId = text("messageId") /** @@ -234,6 +235,7 @@ class PaymentInitiationEntity(id: EntityID<Long>) : LongEntity(id) { var creditorBic by PaymentInitiationsTable.creditorBic var creditorName by PaymentInitiationsTable.creditorName var submitted by PaymentInitiationsTable.submitted + var invalid by PaymentInitiationsTable.invalid var paymentInformationId by PaymentInitiationsTable.paymentInformationId var instructionId by PaymentInitiationsTable.instructionId var messageId by PaymentInitiationsTable.messageId diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/bankaccount/BankAccount.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/bankaccount/BankAccount.kt @@ -84,21 +84,28 @@ suspend fun submitAllPaymentInitiations(httpClient: HttpClient, accountid: Strin HttpStatusCode.NotFound, "account not found" ) + /** + * Skip submitted and invalid preparations. + */ PaymentInitiationEntity.find { - (PaymentInitiationsTable.submitted eq false) and ( - PaymentInitiationsTable.bankAccount eq account.id) + // Not submitted. + (PaymentInitiationsTable.submitted eq false) and + // From the correct bank account. + (PaymentInitiationsTable.bankAccount eq account.id) }.forEach { - // Filter out non EBICS. + if (it.invalid == true) return@forEach val defaultBankConnectionId = it.bankAccount.defaultBankConnection?.id ?: throw NexusError( HttpStatusCode.NotFound, "Default bank connection not found. Can't submit Pain document" ) + // Rare, but filter out bank accounts without a bank connection. val bankConnection = NexusBankConnectionEntity.findById(defaultBankConnectionId) ?: throw NexusError( HttpStatusCode.InternalServerError, "Bank connection '$defaultBankConnectionId' " + "(pointed by bank account '${it.bankAccount.bankAccountName}')" + " not found in the database." ) + // Filter out non EBICS. if (bankConnection.type != "ebics") { logger.info("Skipping non-implemented bank connection '${bankConnection.type}'") return@forEach diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsNexus.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsNexus.kt @@ -44,8 +44,10 @@ import org.jetbrains.exposed.sql.and import org.jetbrains.exposed.sql.insert import org.jetbrains.exposed.sql.select import org.jetbrains.exposed.sql.statements.api.ExposedBlob +import org.jetbrains.exposed.sql.transactions.TransactionManager import org.jetbrains.exposed.sql.transactions.transaction import tech.libeufin.nexus.* +import tech.libeufin.nexus.bankaccount.getPaymentInitiation import tech.libeufin.nexus.iso20022.NexusPaymentInitiationData import tech.libeufin.nexus.iso20022.createPain001document import tech.libeufin.nexus.logger @@ -531,9 +533,18 @@ class EbicsBankConnectionProtocol: BankConnectionProtocol { ) logger.debug("Sending Pain.001: ${paymentInitiation.paymentInformationId}," + " for payment: '${paymentInitiation.subject}'") - if (!XMLUtil.validateFromString(painMessage)) throw NexusError( - HttpStatusCode.InternalServerError, "Pain.001 message is invalid." - ) + if (!XMLUtil.validateFromString(painMessage)) { + logger.error("Pain.001 ${paymentInitiation.paymentInformationId}" + + " is invalid, not submitting it and flag as invalid.") + val payment = getPaymentInitiation(paymentInitiationId) + payment.invalid = true + // The following commit prevents the thrown error + // to lose the database transaction data. + TransactionManager.current().commit() + throw NexusError( + HttpStatusCode.InternalServerError, "Pain.001 message is invalid." + ) + } object { val subscriberDetails = subscriberDetails val painMessage = painMessage @@ -546,12 +557,10 @@ class EbicsBankConnectionProtocol: BankConnectionProtocol { r.painMessage.toByteArray(Charsets.UTF_8), EbicsStandardOrderParams() ) - // Mark the payment as submitted. transaction { - val paymentInitiation = PaymentInitiationEntity.findById(paymentInitiationId) - ?: throw NexusError(HttpStatusCode.NotFound, "payment initiation not found") - paymentInitiation.submitted = true - paymentInitiation.submissionDate = LocalDateTime.now().millis() + val payment = getPaymentInitiation(paymentInitiationId) + payment.submitted = true + payment.submissionDate = LocalDateTime.now().millis() } } diff --git a/nexus/src/test/kotlin/DownloadAndSubmit.kt b/nexus/src/test/kotlin/DownloadAndSubmit.kt @@ -91,7 +91,7 @@ fun getCustomEbicsServer(r: EbicsResponses, endpoint: String = "/ebicsweb"): App * and having had access to runTask and TaskSchedule, that * are now 'private'. */ -@Ignore +// @Ignore class DownloadAndSubmit { /** * Instruct the server to return invalid CAMT content. @@ -122,7 +122,7 @@ class DownloadAndSubmit { level = FetchLevel.REPORT, "foo" ), - "mock-bank-account" + "foo" ) } } @@ -147,7 +147,7 @@ class DownloadAndSubmit { ), transaction { NexusBankAccountEntity.findByName( - "mock-bank-account" + "foo" ) ?: throw Exception("Test failed") } ) diff --git a/nexus/src/test/kotlin/MakeEnv.kt b/nexus/src/test/kotlin/MakeEnv.kt @@ -86,7 +86,7 @@ fun prepNexusDb() { bankAuthenticationPublicKey = ExposedBlob(bankKeys.auth.public.encoded) } val a = NexusBankAccountEntity.new { - bankAccountName = "mock-bank-account" + bankAccountName = "foo" iban = FOO_USER_IBAN bankCode = "SANDBOXX" defaultBankConnection = c @@ -94,7 +94,7 @@ fun prepNexusDb() { accountHolder = "foo" } val b = NexusBankAccountEntity.new { - bankAccountName = "bar-bank-account" + bankAccountName = "bar" iban = BAR_USER_IBAN bankCode = "SANDBOXX" defaultBankConnection = c diff --git a/util/src/main/kotlin/XMLUtil.kt b/util/src/main/kotlin/XMLUtil.kt @@ -252,7 +252,11 @@ class XMLUtil private constructor() { try { getEbicsValidator().validate(xmlDoc) } catch (e: Exception) { - e.printStackTrace() + /** + * Would be convenient to return also the error + * message to the caller, so that it can link it + * to a document ID in the logs. + */ logger.warn("Validation failed: ${e}") return false }