gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[libeufin] branch master updated (95c75f5 -> 508adb4)


From: gnunet
Subject: [libeufin] branch master updated (95c75f5 -> 508adb4)
Date: Thu, 18 Jun 2020 15:27:02 +0200

This is an automated email from the git hooks/post-receive script.

dold pushed a change to branch master
in repository libeufin.

    from 95c75f5  Adjust comments.
     new 12c1a45  Inline helper function
     new 0cd1658  cleanup
     new a31d6b7  refactor payment initiations
     new 87f0950  error message
     new 508adb4  message

The 5 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 integration-tests/test-ebics-highlevel.py          |   7 +-
 nexus/build.gradle                                 |   1 -
 nexus/src/main/kotlin/tech/libeufin/nexus/DB.kt    |  55 +++++----
 .../src/main/kotlin/tech/libeufin/nexus/Helpers.kt |  82 ++----------
 .../main/kotlin/tech/libeufin/nexus/Iso20022.kt    | 137 ++++++++++++++++++++-
 nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt  |  40 +-----
 .../tech/libeufin/nexus/bankaccount/BankAccount.kt |  80 ++++++++++++
 .../kotlin/tech/libeufin/nexus/ebics/EbicsNexus.kt |  95 +++++++++++---
 nexus/src/main/kotlin/tech/libeufin/nexus/taler.kt |  59 ---------
 .../src/main/kotlin/tech/libeufin/sandbox/DB.kt    |   4 +-
 .../tech/libeufin/sandbox/EbicsProtocolBackend.kt  |  40 +++---
 util/src/main/kotlin/JSON.kt                       |   4 +-
 12 files changed, 364 insertions(+), 240 deletions(-)
 create mode 100644 
nexus/src/main/kotlin/tech/libeufin/nexus/bankaccount/BankAccount.kt

diff --git a/integration-tests/test-ebics-highlevel.py 
b/integration-tests/test-ebics-highlevel.py
index bbdfe53..5ebb480 100755
--- a/integration-tests/test-ebics-highlevel.py
+++ b/integration-tests/test-ebics-highlevel.py
@@ -220,7 +220,10 @@ resp = assertResponse(
     )
 )
 
-if len(resp.json().get("transactions")) != 1:
-    fail("Unexpected number of transactions; should be 1")
+transactions = resp.json().get("transactions")
+
+if len(transactions) != 1:
+    print(transactions)
+    fail(f"Unexpected number of transactions ({len(transactions)}); should be 
1")
 
 print("Test passed!")
diff --git a/nexus/build.gradle b/nexus/build.gradle
index 4a20556..069a437 100644
--- a/nexus/build.gradle
+++ b/nexus/build.gradle
@@ -120,4 +120,3 @@ jar {
 run {
     standardInput = System.in
 }
-
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/DB.kt 
b/nexus/src/main/kotlin/tech/libeufin/nexus/DB.kt
index 81cea39..cd2e2d5 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/DB.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/DB.kt
@@ -46,7 +46,7 @@ object TalerRequestedPayments : LongIdTable() {
     // in the sense that a "early" prepared payment might
     // get a "high" id because the bank confirmed it "late".
     val abstractId = long("abstractId").nullable()
-    val preparedPayment = reference("payment", InitiatedPaymentsTable)
+    val preparedPayment = reference("payment", PaymentInitiationsTable)
     val requestUId = text("request_uid")
     val amount = text("amount")
     val exchangeBaseUrl = text("exchange_base_url")
@@ -57,7 +57,7 @@ object TalerRequestedPayments : LongIdTable() {
 class TalerRequestedPaymentEntity(id: EntityID<Long>) : LongEntity(id) {
     companion object : 
LongEntityClass<TalerRequestedPaymentEntity>(TalerRequestedPayments)
     var abstractId by TalerRequestedPayments.abstractId
-    var preparedPayment by InitiatedPaymentEntity referencedOn 
TalerRequestedPayments.preparedPayment
+    var preparedPayment by PaymentInitiationEntity referencedOn 
TalerRequestedPayments.preparedPayment
     var requestUId by TalerRequestedPayments.requestUId
     var amount by TalerRequestedPayments.amount
     var exchangeBaseUrl by TalerRequestedPayments.exchangeBaseUrl
@@ -167,13 +167,17 @@ class RawBankTransactionEntity(id: EntityID<Long>) : 
LongEntity(id) {
 /**
  * Represents a prepared payment.
  */
-object InitiatedPaymentsTable : LongIdTable() {
+object PaymentInitiationsTable : LongIdTable() {
+    /**
+     * Bank account that wants to initiate the payment.
+     */
+    val bankAccount = reference("bankAccount", NexusBankAccountsTable)
     val preparationDate = long("preparationDate")
     val submissionDate = long("submissionDate").nullable()
     val sum = amount("sum")
     val currency = varchar("currency", length = 3).default("EUR")
     val endToEndId = long("EndToEndId")
-    val subject = text("subject")   
+    val subject = text("subject")
     val creditorIban = text("creditorIban")
     val creditorBic = text("creditorBic")
     val creditorName = text("creditorName")
@@ -181,27 +185,32 @@ object InitiatedPaymentsTable : LongIdTable() {
     val debitorBic = text("debitorBic")
     val debitorName = text("debitorName").nullable()
     val submitted = bool("submitted").default(false)
-    // points at the raw transaction witnessing that this
-    // initiated payment was successfully performed.
+
+    /**
+     * Points at the raw transaction witnessing that this
+     * initiated payment was successfully performed.
+     */
     val rawConfirmation = reference("rawConfirmation", 
RawBankTransactionsTable).nullable()
 }
 
-class InitiatedPaymentEntity(id: EntityID<Long>) : LongEntity(id) {
-    companion object : 
LongEntityClass<InitiatedPaymentEntity>(InitiatedPaymentsTable)
-    var preparationDate by InitiatedPaymentsTable.preparationDate
-    var submissionDate by InitiatedPaymentsTable.submissionDate
-    var sum by InitiatedPaymentsTable.sum
-    var currency by InitiatedPaymentsTable.currency
-    var debitorIban by InitiatedPaymentsTable.debitorIban
-    var debitorBic by InitiatedPaymentsTable.debitorBic
-    var debitorName by InitiatedPaymentsTable.debitorName
-    var endToEndId by InitiatedPaymentsTable.endToEndId
-    var subject by InitiatedPaymentsTable.subject
-    var creditorIban by InitiatedPaymentsTable.creditorIban
-    var creditorBic by InitiatedPaymentsTable.creditorBic
-    var creditorName by InitiatedPaymentsTable.creditorName
-    var submitted by InitiatedPaymentsTable.submitted
-    var rawConfirmation by RawBankTransactionEntity optionalReferencedOn  
InitiatedPaymentsTable.rawConfirmation
+class PaymentInitiationEntity(id: EntityID<Long>) : LongEntity(id) {
+    companion object : 
LongEntityClass<PaymentInitiationEntity>(PaymentInitiationsTable)
+
+    var bankAccount by NexusBankAccountEntity referencedOn 
PaymentInitiationsTable.bankAccount
+    var preparationDate by PaymentInitiationsTable.preparationDate
+    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 rawConfirmation by RawBankTransactionEntity optionalReferencedOn 
PaymentInitiationsTable.rawConfirmation
 }
 
 /**
@@ -336,7 +345,7 @@ fun dbCreateTables(dbName: String) {
         addLogger(StdOutSqlLogger)
         SchemaUtils.create(
             NexusUsersTable,
-            InitiatedPaymentsTable,
+            PaymentInitiationsTable,
             EbicsSubscribersTable,
             NexusBankAccountsTable,
             RawBankTransactionsTable,
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/Helpers.kt 
b/nexus/src/main/kotlin/tech/libeufin/nexus/Helpers.kt
index a3567c5..13a993a 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/Helpers.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/Helpers.kt
@@ -26,76 +26,11 @@ import org.jetbrains.exposed.sql.and
 import org.jetbrains.exposed.sql.transactions.transaction
 import org.w3c.dom.Document
 import tech.libeufin.util.*
-import tech.libeufin.util.ebics_h004.EbicsTypes
-import java.security.interfaces.RSAPublicKey
 import java.time.Instant
 import java.time.ZoneId
 import java.time.ZonedDateTime
 import java.time.format.DateTimeFormatter
-import java.util.*
 
-/**
- * Skip national only-numeric bank account ids, and return the first IBAN in 
list
- */
-fun extractFirstIban(bankAccounts: List<EbicsTypes.AbstractAccountNumber>?): 
String? {
-    if (bankAccounts == null)
-        return null
-
-    for (item in bankAccounts) {
-        if (item is EbicsTypes.GeneralAccountNumber) {
-            if (item.international)
-                return item.value
-        }
-    }
-    return null
-}
-
-/**
- * Skip national only-numeric codes, and returns the first BIC in list
- */
-fun extractFirstBic(bankCodes: List<EbicsTypes.AbstractBankCode>?): String? {
-    if (bankCodes == null)
-        return null
-
-    for (item in bankCodes) {
-        if (item is EbicsTypes.GeneralBankCode) {
-            if (item.international)
-                return item.value
-        }
-    }
-    return null
-}
-
-
-fun getEbicsSubscriberDetailsInternal(subscriber: EbicsSubscriberEntity): 
EbicsClientSubscriberDetails {
-    var bankAuthPubValue: RSAPublicKey? = null
-    if (subscriber.bankAuthenticationPublicKey != null) {
-        bankAuthPubValue = CryptoUtil.loadRsaPublicKey(
-            subscriber.bankAuthenticationPublicKey?.bytes!!
-        )
-    }
-    var bankEncPubValue: RSAPublicKey? = null
-    if (subscriber.bankEncryptionPublicKey != null) {
-        bankEncPubValue = CryptoUtil.loadRsaPublicKey(
-            subscriber.bankEncryptionPublicKey?.bytes!!
-        )
-    }
-    return EbicsClientSubscriberDetails(
-        bankAuthPub = bankAuthPubValue,
-        bankEncPub = bankEncPubValue,
-
-        ebicsUrl = subscriber.ebicsURL,
-        hostId = subscriber.hostID,
-        userId = subscriber.userID,
-        partnerId = subscriber.partnerID,
-
-        customerSignPriv = 
CryptoUtil.loadRsaPrivateKey(subscriber.signaturePrivateKey.bytes),
-        customerAuthPriv = 
CryptoUtil.loadRsaPrivateKey(subscriber.authenticationPrivateKey.bytes),
-        customerEncPriv = 
CryptoUtil.loadRsaPrivateKey(subscriber.encryptionPrivateKey.bytes),
-        ebicsIniState = subscriber.ebicsIniState,
-        ebicsHiaState = subscriber.ebicsHiaState
-    )
-}
 
 /**
  * Check if the transaction is already found in the database.
@@ -117,9 +52,9 @@ private fun findDuplicate(bankAccountId: String, 
acctSvcrRef: String): RawBankTr
 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 = InitiatedPaymentEntity.find {
-        InitiatedPaymentsTable.subject eq subject and
-                (InitiatedPaymentsTable.debitorIban eq debtorIban)
+    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")
@@ -225,7 +160,7 @@ fun ingestBankMessagesIntoAccount(
  * Create a PAIN.001 XML document according to the input data.
  * Needs to be called within a transaction block.
  */
-fun createPain001document(paymentData: InitiatedPaymentEntity): String {
+fun createPain001document(paymentData: PaymentInitiationEntity): String {
     /**
      * Every PAIN.001 document contains at least three IDs:
      *
@@ -351,9 +286,9 @@ fun createPain001document(paymentData: 
InitiatedPaymentEntity): String {
  * Retrieve prepared payment from database, raising exception
  * if not found.
  */
-fun getPreparedPayment(uuid: Long): InitiatedPaymentEntity {
+fun getPreparedPayment(uuid: Long): PaymentInitiationEntity {
     return transaction {
-        InitiatedPaymentEntity.findById(uuid)
+        PaymentInitiationEntity.findById(uuid)
     } ?: throw NexusError(
         HttpStatusCode.NotFound,
         "Payment '$uuid' not found"
@@ -368,9 +303,10 @@ fun getPreparedPayment(uuid: Long): InitiatedPaymentEntity 
{
  * it will be the account whose money will pay the wire transfer being defined
  * by this pain document.
  */
-fun addPreparedPayment(paymentData: Pain001Data, debitorAccount: 
NexusBankAccountEntity): InitiatedPaymentEntity {
+fun addPreparedPayment(paymentData: Pain001Data, debitorAccount: 
NexusBankAccountEntity): PaymentInitiationEntity {
     return transaction {
-        InitiatedPaymentEntity.new {
+        PaymentInitiationEntity.new {
+            bankAccount = debitorAccount
             subject = paymentData.subject
             sum = paymentData.sum
             debitorIban = debitorAccount.iban
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/Iso20022.kt 
b/nexus/src/main/kotlin/tech/libeufin/nexus/Iso20022.kt
index 300cd34..2027503 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/Iso20022.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/Iso20022.kt
@@ -17,18 +17,20 @@
  * <http://www.gnu.org/licenses/>
  */
 
-package tech.libeufin.nexus
-
 /**
- * Parse ISO 20022 messages
+ * Parse and generate ISO 20022 messages
  */
+package tech.libeufin.nexus
 
 import com.fasterxml.jackson.annotation.JsonInclude
 import com.fasterxml.jackson.annotation.JsonSubTypes
 import com.fasterxml.jackson.annotation.JsonTypeInfo
 import org.w3c.dom.Document
-import tech.libeufin.util.XmlElementDestructor
-import tech.libeufin.util.destructXml
+import tech.libeufin.util.*
+import java.time.Instant
+import java.time.ZoneId
+import java.time.ZonedDateTime
+import java.time.format.DateTimeFormatter
 
 enum class CreditDebitIndicator {
     DBIT, CRDT
@@ -211,6 +213,131 @@ data class RelatedParties(
 
 class CamtParsingError(msg: String) : Exception(msg)
 
+/**
+ * Data that the LibEuFin nexus uses for payment initiation.
+ * Subset of what ISO 20022 allows.
+ */
+data class NexusPaymentInitiationData(
+    val debtorIban: String,
+    val debtorBic: String,
+    val debtorName: String,
+    val messageId: String,
+    val paymentInformationId: String,
+    val amount: String,
+    val currency: String,
+    val subject: String,
+    val preparationTimestamp: Long,
+    val creditorName: String,
+    val creditorIban: String
+)
+
+/**
+ * Create a PAIN.001 XML document according to the input data.
+ * Needs to be called within a transaction block.
+ */
+fun createPain001document(paymentData: NexusPaymentInitiationData): String {
+    // Every PAIN.001 document contains at least three IDs:
+    //
+    // 1) MsgId: a unique id for the message itself
+    // 2) PmtInfId: the unique id for the payment's set of information
+    // 3) EndToEndId: a unique id to be shared between the debtor and
+    //    creditor that uniquely identifies the transaction
+    //
+    // For now and for simplicity, since every PAIN entry in the database
+    // has a unique ID, and the three values aren't required to be mutually 
different,
+    // we'll assign the SAME id (= the row id) to all the three aforementioned
+    // PAIN id types.
+
+    val s = constructXml(indent = true) {
+        root("Document") {
+            attribute("xmlns", 
"urn:iso:std:iso:20022:tech:xsd:pain.001.001.03")
+            attribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance";)
+            attribute("xsi:schemaLocation", 
"urn:iso:std:iso:20022:tech:xsd:pain.001.001.03 pain.001.001.03.xsd")
+            element("CstmrCdtTrfInitn") {
+                element("GrpHdr") {
+                    element("MsgId") {
+                        text(paymentData.messageId)
+                    }
+                    element("CreDtTm") {
+                        val dateMillis = paymentData.preparationTimestamp
+                        val dateFormatter = 
DateTimeFormatter.ISO_OFFSET_DATE_TIME
+                        val instant = Instant.ofEpochSecond(dateMillis / 1000)
+                        val zoned = ZonedDateTime.ofInstant(instant, 
ZoneId.systemDefault())
+                        text(dateFormatter.format(zoned))
+                    }
+                    element("NbOfTxs") {
+                        text("1")
+                    }
+                    element("CtrlSum") {
+                        text(paymentData.amount)
+                    }
+                    element("InitgPty/Nm") {
+                        text(paymentData.debtorName)
+                    }
+                }
+                element("PmtInf") {
+                    element("PmtInfId") {
+                        text(paymentData.paymentInformationId)
+                    }
+                    element("PmtMtd") {
+                        text("TRF")
+                    }
+                    element("BtchBookg") {
+                        text("true")
+                    }
+                    element("NbOfTxs") {
+                        text("1")
+                    }
+                    element("CtrlSum") {
+                        text(paymentData.amount)
+                    }
+                    element("PmtTpInf/SvcLvl/Cd") {
+                        text("SEPA")
+                    }
+                    element("ReqdExctnDt") {
+                        val dateMillis = paymentData.preparationTimestamp
+                        text(importDateFromMillis(dateMillis).toDashedDate())
+                    }
+                    element("Dbtr/Nm") {
+                        text(paymentData.debtorName)
+                    }
+                    element("DbtrAcct/Id/IBAN") {
+                        text(paymentData.debtorIban)
+                    }
+                    element("DbtrAgt/FinInstnId/BIC") {
+                        text(paymentData.debtorBic)
+                    }
+                    element("ChrgBr") {
+                        text("SLEV")
+                    }
+                    element("CdtTrfTxInf") {
+                        element("PmtId") {
+                            element("EndToEndId") {
+                                // text(pain001Entity.id.value.toString())
+                                text("NOTPROVIDED")
+                            }
+                        }
+                        element("Amt/InstdAmt") {
+                            attribute("Ccy", paymentData.currency)
+                            text(paymentData.amount)
+                        }
+                        element("Cdtr/Nm") {
+                            text(paymentData.creditorName)
+                        }
+                        element("CdtrAcct/Id/IBAN") {
+                            text(paymentData.creditorIban)
+                        }
+                        element("RmtInf/Ustrd") {
+                            text(paymentData.subject)
+                        }
+                    }
+                }
+            }
+        }
+    }
+    return s
+}
+
 private fun XmlElementDestructor.extractDateOrDateTime(): DateOrDateTime {
     return requireOnlyChild {
         when (it.localName) {
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt 
b/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt
index af86c00..f053d17 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt
@@ -66,6 +66,8 @@ import org.jetbrains.exposed.sql.transactions.transaction
 import org.slf4j.Logger
 import org.slf4j.LoggerFactory
 import org.slf4j.event.Level
+import tech.libeufin.nexus.bankaccount.submitAllPreparedPayments
+import tech.libeufin.nexus.bankaccount.submitPreparedPayment
 import tech.libeufin.nexus.ebics.*
 import tech.libeufin.util.*
 import tech.libeufin.util.CryptoUtil.hashpw
@@ -245,7 +247,7 @@ fun moreFrequentBackgroundTasks(httpClient: HttpClient) {
             }
             // FIXME: should be done automatically after raw ingestion
             reportAndIgnoreErrors { ingestTalerTransactions() }
-            reportAndIgnoreErrors { submitPreparedPaymentsViaEbics(httpClient) 
}
+            reportAndIgnoreErrors { submitAllPreparedPayments(httpClient) }
             logger.debug("More frequent background jobs done")
             delay(Duration.ofSeconds(1))
         }
@@ -362,7 +364,6 @@ fun serverMain(dbName: String) {
                     indentObjectsWith(DefaultIndenter("  ", "\n"))
                 })
                 registerModule(KotlinModule(nullisSameAsDefault = true))
-                //registerModule(JavaTimeModule())
             }
         }
 
@@ -518,40 +519,9 @@ fun serverMain(dbName: String) {
                 val uuid = ensureLong(call.parameters["uuid"])
                 val accountId = ensureNonNull(call.parameters["accountid"])
                 val res = transaction {
-                    val user = authenticateRequest(call.request)
-                    val preparedPayment = getPreparedPayment(uuid)
-                    if (preparedPayment.submitted) {
-                        throw NexusError(
-                            HttpStatusCode.PreconditionFailed,
-                            "Payment ${uuid} was submitted already"
-                        )
-                    }
-                    val bankAccount = 
NexusBankAccountEntity.findById(accountId)
-                    if (bankAccount == null) {
-                        throw NexusError(HttpStatusCode.NotFound, "unknown 
bank account")
-                    }
-                    val defaultBankConnection = 
bankAccount.defaultBankConnection
-                        ?: throw NexusError(HttpStatusCode.NotFound, "needs a 
default connection")
-                    return@transaction object {
-                        val pain001document = 
createPain001document(preparedPayment)
-                        val bankConnectionType = defaultBankConnection.type
-                        val connId = defaultBankConnection.id.value
-                    }
-                }
-                // type and name aren't null
-                when (res.bankConnectionType) {
-                    "ebics" -> {
-                        submitEbicsPaymentInitiation(client, res.connId, 
res.pain001document)
-                    }
-                    else -> throw NexusError(
-                        HttpStatusCode.NotFound,
-                        "Transport type '${res.bankConnectionType}' not 
implemented"
-                    )
-                }
-                transaction {
-                    val preparedPayment = getPreparedPayment(uuid)
-                    preparedPayment.submitted = true
+                    authenticateRequest(call.request)
                 }
+                submitPreparedPayment(client, uuid)
                 call.respondText("Payment ${uuid} submitted")
                 return@post
             }
diff --git 
a/nexus/src/main/kotlin/tech/libeufin/nexus/bankaccount/BankAccount.kt 
b/nexus/src/main/kotlin/tech/libeufin/nexus/bankaccount/BankAccount.kt
new file mode 100644
index 0000000..0a8de16
--- /dev/null
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/bankaccount/BankAccount.kt
@@ -0,0 +1,80 @@
+/*
+ * This file is part of LibEuFin.
+ * Copyright (C) 2020 Taler Systems S.A.
+ *
+ * LibEuFin is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation; either version 3, or
+ * (at your option) any later version.
+ *
+ * LibEuFin is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General
+ * Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public
+ * License along with LibEuFin; see the file COPYING.  If not, see
+ * <http://www.gnu.org/licenses/>
+ */
+
+package tech.libeufin.nexus.bankaccount
+
+import io.ktor.client.HttpClient
+import io.ktor.http.HttpStatusCode
+import org.jetbrains.exposed.sql.and
+import org.jetbrains.exposed.sql.not
+import org.jetbrains.exposed.sql.transactions.transaction
+import tech.libeufin.nexus.*
+import tech.libeufin.nexus.ebics.submitEbicsPaymentInitiation
+
+
+suspend fun submitPreparedPayment(httpClient: HttpClient, paymentInitiationId: 
Long) {
+    val type = transaction {
+        val paymentInitiation = 
PaymentInitiationEntity.findById(paymentInitiationId)
+        if (paymentInitiation == null) {
+            throw NexusError(HttpStatusCode.NotFound, "prepared payment not 
found")
+        }
+        paymentInitiation.bankAccount.defaultBankConnection?.type
+    }
+    when (type) {
+        null -> throw NexusError(HttpStatusCode.NotFound, "no default bank 
connection")
+        "ebics" -> submitEbicsPaymentInitiation(httpClient, 
paymentInitiationId)
+    }
+}
+
+/**
+ * Submit all pending prepared payments.
+ */
+suspend fun submitAllPreparedPayments(httpClient: HttpClient) {
+    data class Submission(
+        val id: Long
+    )
+    logger.debug("auto-submitter started")
+    val workQueue = mutableListOf<Submission>()
+    transaction {
+        NexusBankAccountEntity.all().forEach {
+            val defaultBankConnectionId = it.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}')"
+            )
+            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.forEach {
+        submitPreparedPayment(httpClient, it.id)
+    }
+}
\ No newline at end of file
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsNexus.kt 
b/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsNexus.kt
index abbcaf1..ccffa96 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsNexus.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsNexus.kt
@@ -45,9 +45,11 @@ import org.jetbrains.exposed.sql.transactions.transaction
 import tech.libeufin.nexus.*
 import tech.libeufin.nexus.logger
 import tech.libeufin.util.*
+import tech.libeufin.util.ebics_h004.EbicsTypes
 import tech.libeufin.util.ebics_h004.HTDResponseOrderData
 import java.io.ByteArrayOutputStream
 import java.security.interfaces.RSAPrivateCrtKey
+import java.security.interfaces.RSAPublicKey
 import java.time.LocalDate
 import java.time.LocalDateTime
 import java.time.format.DateTimeFormatter
@@ -218,6 +220,36 @@ fun createEbicsBankConnectionFromBackup(
     return
 }
 
+private fun getEbicsSubscriberDetailsInternal(subscriber: 
EbicsSubscriberEntity): EbicsClientSubscriberDetails {
+    var bankAuthPubValue: RSAPublicKey? = null
+    if (subscriber.bankAuthenticationPublicKey != null) {
+        bankAuthPubValue = CryptoUtil.loadRsaPublicKey(
+            subscriber.bankAuthenticationPublicKey?.bytes!!
+        )
+    }
+    var bankEncPubValue: RSAPublicKey? = null
+    if (subscriber.bankEncryptionPublicKey != null) {
+        bankEncPubValue = CryptoUtil.loadRsaPublicKey(
+            subscriber.bankEncryptionPublicKey?.bytes!!
+        )
+    }
+    return EbicsClientSubscriberDetails(
+        bankAuthPub = bankAuthPubValue,
+        bankEncPub = bankEncPubValue,
+
+        ebicsUrl = subscriber.ebicsURL,
+        hostId = subscriber.hostID,
+        userId = subscriber.userID,
+        partnerId = subscriber.partnerID,
+
+        customerSignPriv = 
CryptoUtil.loadRsaPrivateKey(subscriber.signaturePrivateKey.bytes),
+        customerAuthPriv = 
CryptoUtil.loadRsaPrivateKey(subscriber.authenticationPrivateKey.bytes),
+        customerEncPriv = 
CryptoUtil.loadRsaPrivateKey(subscriber.encryptionPrivateKey.bytes),
+        ebicsIniState = subscriber.ebicsIniState,
+        ebicsHiaState = subscriber.ebicsHiaState
+    )
+}
+
 /**
  * Retrieve Ebics subscriber details given a bank connection.
  */
@@ -332,14 +364,17 @@ fun Route.ebicsBankConnectionRoutes(client: HttpClient) {
                 transaction {
                     val conn = requireBankConnection(call, "connid")
                     payload.value.partnerInfo.accountInfoList?.forEach {
-                        val bankAccount = NexusBankAccountEntity.new(id = 
it.id) {
+                        NexusBankAccountEntity.new(id = it.id) {
                             accountHolder = it.accountHolder ?: "NOT-GIVEN"
-                            iban = extractFirstIban(it.accountNumberList)
+                            iban = 
it.accountNumberList?.filterIsInstance<EbicsTypes.GeneralAccountNumber>()
+                                ?.find { it.international }?.value
                                 ?: throw NexusError(HttpStatusCode.NotFound, 
reason = "bank gave no IBAN")
-                            bankCode = extractFirstBic(it.bankCodeList) ?: 
throw NexusError(
-                                HttpStatusCode.NotFound,
-                                reason = "bank gave no BIC"
-                            )
+                            bankCode = 
it.bankCodeList?.filterIsInstance<EbicsTypes.GeneralBankCode>()
+                                ?.find { it.international }?.value
+                                ?: throw NexusError(
+                                    HttpStatusCode.NotFound,
+                                    reason = "bank gave no BIC"
+                                )
                             defaultBankConnection = conn
                             highestSeenBankMessageId = 0
                         }
@@ -423,17 +458,6 @@ fun exportEbicsKeyBackup(bankConnectionId: String, 
passphrase: String): Any {
     )
 }
 
-suspend fun submitEbicsPaymentInitiation(client: HttpClient, connId: String, 
pain001Document: String) {
-    val ebicsSubscriberDetails = transaction { 
getEbicsSubscriberDetails(connId) }
-    logger.debug("Uploading PAIN.001: ${pain001Document}")
-    doEbicsUploadTransaction(
-        client,
-        ebicsSubscriberDetails,
-        "CCT",
-        pain001Document.toByteArray(Charsets.UTF_8),
-        EbicsStandardOrderParams()
-    )
-}
 
 fun getEbicsConnectionDetails(conn: NexusBankConnectionEntity): Any {
     val ebicsSubscriber = transaction { 
getEbicsSubscriberDetails(conn.id.value) }
@@ -607,4 +631,41 @@ fun getEbicsKeyLetterPdf(conn: NexusBankConnectionEntity): 
ByteArray {
     }
     pdfWriter.flush()
     return po.toByteArray()
+}
+
+suspend fun submitEbicsPaymentInitiation(httpClient: HttpClient, 
paymentInitiationId: Long) {
+    val r = transaction {
+        val paymentInitiation = 
PaymentInitiationEntity.findById(paymentInitiationId)
+            ?: throw NexusError(HttpStatusCode.NotFound, "payment initiation 
not found")
+        val connId = paymentInitiation.bankAccount.defaultBankConnection?.id
+            ?: throw NexusError(HttpStatusCode.NotFound, "no default bank 
connection available for submission")
+        val subscriberDetails = getEbicsSubscriberDetails(connId.value)
+        val painMessage = createPain001document(
+            NexusPaymentInitiationData(
+                debtorIban = paymentInitiation.debitorIban,
+                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(),
+                preparationTimestamp = paymentInitiation.preparationDate,
+                subject = paymentInitiation.subject,
+                debtorName = paymentInitiation.bankAccount.accountHolder
+        ))
+        object {
+            val subscriberDetails = subscriberDetails
+            val painMessage = painMessage
+        }
+    }
+    doEbicsUploadTransaction(
+        httpClient,
+        r.subscriberDetails,
+        "CCT",
+        r.painMessage.toByteArray(Charsets.UTF_8),
+        EbicsStandardOrderParams()
+    )
 }
\ No newline at end of file
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/taler.kt 
b/nexus/src/main/kotlin/tech/libeufin/nexus/taler.kt
index 3d7c056..3f2470f 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/taler.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/taler.kt
@@ -379,65 +379,6 @@ private suspend fun talerAddIncoming(call: 
ApplicationCall, httpClient: HttpClie
     )
 }
 
-/**
- * submits ALL the prepared payments from ALL the Taler facades.
- * FIXME(dold): This should not be done here.
- * -> why?  It crawls the *taler* facade to find payment to submit.
- */
-suspend fun submitPreparedPaymentsViaEbics(httpClient: HttpClient) {
-    data class EbicsSubmission(
-        val subscriberDetails: EbicsClientSubscriberDetails,
-        val pain001document: String
-    )
-    logger.debug("auto-submitter started")
-    val workQueue = mutableListOf<EbicsSubmission>()
-    transaction {
-        TalerFacadeStateEntity.all().forEach {
-            val bankConnection = 
NexusBankConnectionEntity.findById(it.bankConnection) ?: throw NexusError(
-                HttpStatusCode.InternalServerError,
-                "Such facade '${it.facade.id.value}' doesn't map to any bank 
connection (named '${it.bankConnection}')"
-            )
-            if (bankConnection.type != "ebics") {
-                logger.info("Skipping non-implemented bank connection 
'${bankConnection.type}'")
-                return@forEach
-            }
-
-            val subscriberEntity = EbicsSubscriberEntity.find {
-                EbicsSubscribersTable.nexusBankConnection eq it.bankConnection
-            }.firstOrNull() ?: throw NexusError(
-                HttpStatusCode.InternalServerError,
-                "Such facade '${it.facade.id.value}' doesn't map to any Ebics 
subscriber"
-            )
-            val bankAccount: NexusBankAccountEntity =
-                NexusBankAccountEntity.findById(it.bankAccount) ?: throw 
NexusError(
-                    HttpStatusCode.InternalServerError,
-                    "Bank account '${it.bankAccount}' not found for facade 
'${it.id.value}'"
-                )
-            InitiatedPaymentEntity.find {
-                InitiatedPaymentsTable.debitorIban eq bankAccount.iban and
-                        not(InitiatedPaymentsTable.submitted)
-            }.forEach {
-                val pain001document = createPain001document(it)
-                logger.debug("Preparing payment: ${pain001document}")
-                val subscriberDetails = 
getEbicsSubscriberDetailsInternal(subscriberEntity)
-                workQueue.add(EbicsSubmission(subscriberDetails, 
pain001document))
-                // FIXME: the payment must be flagged AFTER the submission 
happens.
-                // -> this is an open question: see #6367.
-                it.submitted = true
-            }
-        }
-    }
-    workQueue.forEach {
-        println("submitting prepared payment via EBICS")
-        doEbicsUploadTransaction(
-            httpClient,
-            it.subscriberDetails,
-            "CCT",
-            it.pain001document.toByteArray(Charsets.UTF_8),
-            EbicsStandardOrderParams()
-        )
-    }
-}
 
 private fun ingestIncoming(payment: RawBankTransactionEntity, txDtls: 
TransactionDetails) {
     val subject = txDtls.unstructuredRemittanceInformation
diff --git a/sandbox/src/main/kotlin/tech/libeufin/sandbox/DB.kt 
b/sandbox/src/main/kotlin/tech/libeufin/sandbox/DB.kt
index 13b92ab..cc31bbb 100644
--- a/sandbox/src/main/kotlin/tech/libeufin/sandbox/DB.kt
+++ b/sandbox/src/main/kotlin/tech/libeufin/sandbox/DB.kt
@@ -251,10 +251,10 @@ class EbicsUploadTransactionChunkEntity(id: 
EntityID<String>) : Entity<String>(i
  */
 object PaymentsTable : IntIdTable() {
     val creditorIban = text("creditorIban")
-    val creditorBic = text("creditorBic")
+    val creditorBic = text("creditorBic").nullable()
     val creditorName = text("creditorName")
     val debitorIban = text("debitorIban")
-    val debitorBic = text("debitorBic")
+    val debitorBic = text("debitorBic").nullable()
     val debitorName = text("debitorName")
     val subject = text("subject")
     val amount = text("amount")
diff --git 
a/sandbox/src/main/kotlin/tech/libeufin/sandbox/EbicsProtocolBackend.kt 
b/sandbox/src/main/kotlin/tech/libeufin/sandbox/EbicsProtocolBackend.kt
index 1cf902e..37d3611 100644
--- a/sandbox/src/main/kotlin/tech/libeufin/sandbox/EbicsProtocolBackend.kt
+++ b/sandbox/src/main/kotlin/tech/libeufin/sandbox/EbicsProtocolBackend.kt
@@ -376,23 +376,23 @@ fun buildCamtString(type: Int, subscriberIban: String, 
history: MutableList<RawP
                                             text(it.creditorIban)
                                         }
                                     }
-                                    element("RltdAgts") {
-                                        element("CdtrAgt/FinInstnId/BIC") {
-                                            // FIXME: explain this!
-                                            text(
-                                                if 
(subscriberIban.equals(it.creditorIban))
-                                                    it.debitorBic else 
it.creditorBic
-                                            )
-                                        }
-                                        element("DbtrAgt/FinInstnId/BIC") {
-                                            // FIXME: explain this!
-                                            text(
-                                                if 
(subscriberIban.equals(it.creditorIban))
-                                                    it.creditorBic else 
it.debitorBic
-                                            )
-                                        }
-
-                                    }
+//                                    element("RltdAgts") {
+//                                        element("CdtrAgt/FinInstnId/BIC") {
+//                                            // FIXME: explain this!
+//                                            text(
+//                                                if 
(subscriberIban.equals(it.creditorIban))
+//                                                    it.debitorBic else 
it.creditorBic
+//                                            )
+//                                        }
+//                                        element("DbtrAgt/FinInstnId/BIC") {
+//                                            // FIXME: explain this!
+//                                            text(
+//                                                if 
(subscriberIban.equals(it.creditorIban))
+//                                                    it.creditorBic else 
it.debitorBic
+//                                            )
+//                                        }
+//
+//                                    }
                                     element("RmtInf/Ustrd") {
                                         text(it.subject)
                                     }
@@ -488,10 +488,10 @@ private fun handleCct(paymentRequest: String, 
initiatorName: String) {
      */
     val painDoc = XMLUtil.parseStringIntoDom(paymentRequest)
     val creditorIban = 
painDoc.pickString("//*[local-name()='CdtrAcct']//*[local-name()='IBAN']")
-    val creditorBic = 
painDoc.pickString("//*[local-name()='CdtrAgt']//*[local-name()='BIC']")
+    //val creditorBic = 
painDoc.pickString("//*[local-name()='CdtrAgt']//*[local-name()='BIC']")
     val creditorName = 
painDoc.pickString("//*[local-name()='Cdtr']//*[local-name()='Nm']")
     val debitorIban = 
painDoc.pickString("//*[local-name()='DbtrAcct']//*[local-name()='IBAN']")
-    val debitorBic = 
painDoc.pickString("//*[local-name()='DbtrAgt']//*[local-name()='BIC']")
+    //val debitorBic = 
painDoc.pickString("//*[local-name()='DbtrAgt']//*[local-name()='BIC']")
     val debitorName = initiatorName
     val subject = painDoc.pickString("//*[local-name()='Ustrd']")
     val amount = painDoc.pickString("//*[local-name()='InstdAmt']")
@@ -500,10 +500,8 @@ private fun handleCct(paymentRequest: String, 
initiatorName: String) {
     transaction {
         PaymentEntity.new {
             this.creditorIban = creditorIban
-            this.creditorBic = creditorBic
             this.creditorName = creditorName
             this.debitorIban = debitorIban
-            this.debitorBic = debitorBic
             this.debitorName = debitorName
             this.subject = subject
             this.amount = amount
diff --git a/util/src/main/kotlin/JSON.kt b/util/src/main/kotlin/JSON.kt
index dc36d2b..02a977d 100644
--- a/util/src/main/kotlin/JSON.kt
+++ b/util/src/main/kotlin/JSON.kt
@@ -26,10 +26,10 @@ package tech.libeufin.util
  */
 data class RawPayment(
     val creditorIban: String,
-    val creditorBic: String,
+    val creditorBic: String?,
     val creditorName: String,
     val debitorIban: String,
-    val debitorBic: String,
+    val debitorBic: String?,
     val debitorName: String,
     val amount: String,
     val currency: String,

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]