libeufin

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

commit 15f78547d0c812f98fe8b779a5d3edd90de37586
parent 96c26bc859c64eccf9b2b663af918d1a8fdfa762
Author: Florian Dold <florian.dold@gmail.com>
Date:   Thu, 18 Jun 2020 22:57:55 +0530

include various IDs

Diffstat:
Mnexus/src/main/kotlin/tech/libeufin/nexus/DB.kt | 14+++++++-------
Mnexus/src/main/kotlin/tech/libeufin/nexus/Iso20022.kt | 11+++++++----
Mnexus/src/main/kotlin/tech/libeufin/nexus/JSON.kt | 4++--
Mnexus/src/main/kotlin/tech/libeufin/nexus/Main.kt | 4+++-
Mnexus/src/main/kotlin/tech/libeufin/nexus/bankaccount/BankAccount.kt | 65++++++++++++++---------------------------------------------------
Mnexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsNexus.kt | 13++++++-------
6 files changed, 39 insertions(+), 72 deletions(-)

diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/DB.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/DB.kt @@ -170,14 +170,14 @@ object PaymentInitiationsTable : LongIdTable() { val submissionDate = long("submissionDate").nullable() val sum = amount("sum") val currency = varchar("currency", length = 3).default("EUR") - val endToEndId = long("EndToEndId") + val endToEndId = text("endToEndId") + val messageId = text("messageId") + val paymentInformationId = text("paymentInformationId") + val instructionId = text("instructionId") val subject = text("subject") val creditorIban = text("creditorIban") val creditorBic = text("creditorBic").nullable() val creditorName = text("creditorName") - val debitorIban = text("debitorIban") - val debitorBic = text("debitorBic") - val debitorName = text("debitorName").nullable() val submitted = bool("submitted").default(false) /** @@ -195,15 +195,15 @@ class PaymentInitiationEntity(id: EntityID<Long>) : LongEntity(id) { var submissionDate by PaymentInitiationsTable.submissionDate var sum by PaymentInitiationsTable.sum var currency by PaymentInitiationsTable.currency - var debitorIban by PaymentInitiationsTable.debitorIban - var debitorBic by PaymentInitiationsTable.debitorBic - var debitorName by PaymentInitiationsTable.debitorName var endToEndId by PaymentInitiationsTable.endToEndId var subject by PaymentInitiationsTable.subject var creditorIban by PaymentInitiationsTable.creditorIban var creditorBic by PaymentInitiationsTable.creditorBic var creditorName by PaymentInitiationsTable.creditorName var submitted by PaymentInitiationsTable.submitted + var paymentInformationId by PaymentInitiationsTable.paymentInformationId + var messageId by PaymentInitiationsTable.messageId + var instructionId by PaymentInitiationsTable.instructionId var rawConfirmation by RawBankTransactionEntity optionalReferencedOn PaymentInitiationsTable.rawConfirmation } diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/Iso20022.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/Iso20022.kt @@ -219,7 +219,7 @@ class CamtParsingError(msg: String) : Exception(msg) */ data class NexusPaymentInitiationData( val debtorIban: String, - val debtorBic: String, + val debtorBic: String?, val debtorName: String, val messageId: String, val paymentInformationId: String, @@ -228,7 +228,8 @@ data class NexusPaymentInitiationData( val subject: String, val preparationTimestamp: Long, val creditorName: String, - val creditorIban: String + val creditorIban: String, + val instructionId: String ) /** @@ -304,8 +305,10 @@ fun createPain001document(paymentData: NexusPaymentInitiationData): String { element("DbtrAcct/Id/IBAN") { text(paymentData.debtorIban) } - element("DbtrAgt/FinInstnId/BIC") { - text(paymentData.debtorBic) + paymentData.debtorBic?.let { + element("DbtrAgt/FinInstnId/BIC") { + text(paymentData.debtorBic) + } } element("ChrgBr") { text("SLEV") diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/JSON.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/JSON.kt @@ -190,10 +190,10 @@ data class EbicsNewTransport( /** Response type of "GET /prepared-payments/{uuid}" */ data class PaymentStatus( - val uuid: String, + val paymentInitiationId: String, val submitted: Boolean, val creditorIban: String, - val creditorBic: String, + val creditorBic: String?, val creditorName: String, val amount: String, val subject: String, diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt @@ -572,7 +572,7 @@ fun serverMain(dbName: String) { val sd = res.preparedPayment.submissionDate call.respond( PaymentStatus( - uuid = res.preparedPayment.id.value.toString(), + paymentInitiationId = res.preparedPayment.id.value.toString(), submitted = res.preparedPayment.submitted, creditorName = res.preparedPayment.creditorName, creditorBic = res.preparedPayment.creditorBic, @@ -587,6 +587,8 @@ fun serverMain(dbName: String) { ) return@get } + + /** * Adds a new prepared payment. */ diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/bankaccount/BankAccount.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/bankaccount/BankAccount.kt @@ -63,26 +63,22 @@ suspend fun submitAllPreparedPayments(httpClient: HttpClient) { logger.debug("auto-submitter started") val workQueue = mutableListOf<Submission>() transaction { - NexusBankAccountEntity.all().forEach { - val defaultBankConnectionId = it.defaultBankConnection?.id ?: throw NexusError( + PaymentInitiationEntity.find { + PaymentInitiationsTable.submitted eq false + }.forEach { + val defaultBankConnectionId = it.bankAccount.defaultBankConnection?.id ?: throw NexusError( HttpStatusCode.BadRequest, "needs default bank connection" ) val bankConnection = NexusBankConnectionEntity.findById(defaultBankConnectionId) ?: throw NexusError( HttpStatusCode.InternalServerError, - "Bank account '${it.id.value}' doesn't map to any bank connection (named '${it.defaultBankConnection}')" + "Bank account '${it.id.value}' doesn't map to any bank connection (named '${defaultBankConnectionId}')" ) if (bankConnection.type != "ebics") { logger.info("Skipping non-implemented bank connection '${bankConnection.type}'") return@forEach } - val bankAccount: NexusBankAccountEntity = it - PaymentInitiationEntity.find { - PaymentInitiationsTable.debitorIban eq bankAccount.iban and - not(PaymentInitiationsTable.submitted) - }.forEach { - workQueue.add(Submission(it.id.value)) - } + workQueue.add(Submission(it.id.value)) } } workQueue.forEach { @@ -104,27 +100,6 @@ private fun findDuplicate(bankAccountId: String, acctSvcrRef: String): RawBankTr } } -/** - * retrieves the initiated payment and marks it as "performed - * by the bank". This avoids to submit it again. - */ -fun markInitiatedAsConfirmed(subject: String, debtorIban: String, rawUuid: Long) { - // not introducing a 'transaction {}' block since - // this function should be always be invoked from one. - val initiatedPayment = PaymentInitiationEntity.find { - PaymentInitiationsTable.subject eq subject and - (PaymentInitiationsTable.debitorIban eq debtorIban) - }.firstOrNull() - if (initiatedPayment == null) { - logger.info("Payment '$subject' was never programmatically prepared") - return - } - val rawEntity = RawBankTransactionEntity.findById(rawUuid) ?: throw NexusError( - HttpStatusCode.InternalServerError, "Raw payment '$rawUuid' disappeared from database" - ) - initiatedPayment.rawConfirmation = rawEntity -} - fun processCamtMessage( bankAccountId: String, camtDoc: Document @@ -161,21 +136,7 @@ fun processCamtMessage( status = tx.status } if (tx.creditDebitIndicator == CreditDebitIndicator.DBIT) { - // assuming batches contain always one element, as aren't fully - // implemented now. - val uniqueBatchElement = tx.details.get(0) - - // if the user has two initiated payments under the same - // IBAN with the same subject, then this logic will cause - // problems. But a programmatic user should take care of this. - // FIXME(dold): Actually, we should do the matching via the Refs of the camt message. - if (uniqueBatchElement.relatedParties.debtorAccount is AccountIdentificationIban) { - markInitiatedAsConfirmed( - uniqueBatchElement.unstructuredRemittanceInformation, - uniqueBatchElement.relatedParties.debtorAccount.iban, - rawEntity.id.value - ) - } + // FIXME: find matching PaymentInitiation by PaymentInformationID, message ID or whatever is present } } } @@ -234,19 +195,21 @@ fun getPreparedPayment(uuid: Long): PaymentInitiationEntity { * by this pain document. */ fun addPreparedPayment(paymentData: Pain001Data, debitorAccount: NexusBankAccountEntity): PaymentInitiationEntity { + val now = Instant.now().toEpochMilli() + val nowHex = now.toString(16) return transaction { PaymentInitiationEntity.new { bankAccount = debitorAccount subject = paymentData.subject sum = paymentData.sum - debitorIban = debitorAccount.iban - debitorBic = debitorAccount.bankCode - debitorName = debitorAccount.accountHolder creditorName = paymentData.creditorName creditorBic = paymentData.creditorBic creditorIban = paymentData.creditorIban - preparationDate = Instant.now().toEpochMilli() - endToEndId = 0 + preparationDate = now + messageId = "leuf-m-pain1-$nowHex" + endToEndId = "leuf-e-$nowHex" + paymentInformationId = "leuf-p-$nowHex" + instructionId = "leuf-i-$nowHex" } } } diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsNexus.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsNexus.kt @@ -642,19 +642,18 @@ suspend fun submitEbicsPaymentInitiation(httpClient: HttpClient, paymentInitiati val subscriberDetails = getEbicsSubscriberDetails(connId.value) val painMessage = createPain001document( NexusPaymentInitiationData( - debtorIban = paymentInitiation.debitorIban, + debtorIban = paymentInitiation.bankAccount.iban, + debtorBic = paymentInitiation.bankAccount.bankCode, + debtorName = paymentInitiation.bankAccount.accountHolder, currency = paymentInitiation.currency, amount = paymentInitiation.sum.toString(), creditorIban = paymentInitiation.creditorIban, creditorName = paymentInitiation.creditorName, - debtorBic = paymentInitiation.creditorBic, - // FIXME(dold): Put date in here as well - messageId = paymentInitiation.id.toString(), - // FIXME(dold): Put date in here as well - paymentInformationId = paymentInitiation.id.toString(), + messageId = paymentInitiation.messageId, + paymentInformationId = paymentInitiation.paymentInformationId, preparationTimestamp = paymentInitiation.preparationDate, subject = paymentInitiation.subject, - debtorName = paymentInitiation.bankAccount.accountHolder + instructionId = paymentInitiation.instructionId )) object { val subscriberDetails = subscriberDetails