gnunet-svn
[Top][All Lists]
Advanced

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

[libeufin] branch master updated (8fbb4e5 -> e5e2ba2)


From: gnunet
Subject: [libeufin] branch master updated (8fbb4e5 -> e5e2ba2)
Date: Tue, 28 Apr 2020 20:32:38 +0200

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

marcello pushed a change to branch master
in repository libeufin.

    from 8fbb4e5  Fix tests after last changes.
     new 55a63c2  Remove many helpers from main nexus file.
     new e5e2ba2  More separation for transport types.

The 2 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:
 nexus/src/main/kotlin/tech/libeufin/nexus/DB.kt    |    6 +-
 .../src/main/kotlin/tech/libeufin/nexus/Helpers.kt |  293 +++-
 nexus/src/main/kotlin/tech/libeufin/nexus/JSON.kt  |  104 +-
 nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt  | 1440 ++++++--------------
 nexus/src/main/kotlin/tech/libeufin/nexus/taler.kt |    7 +-
 nexus/src/test/kotlin/authentication.kt            |   18 +-
 6 files changed, 791 insertions(+), 1077 deletions(-)

diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/DB.kt 
b/nexus/src/main/kotlin/tech/libeufin/nexus/DB.kt
index c10037c..24fddf3 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/DB.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/DB.kt
@@ -210,13 +210,15 @@ class EbicsSubscriberEntity(id: EntityID<Int>) : 
Entity<Int>(id) {
 
 object NexusUsersTable : IdTable<String>() {
     override val id = varchar("id", ID_MAX_LENGTH).entityId().primaryKey()
-    val ebicsSubscriber = reference("ebicsSubscriber", EbicsSubscribersTable)
+    val ebicsSubscriber = reference("ebicsSubscriber", 
EbicsSubscribersTable).nullable()
+    val testSubscriber = reference("testSubscriber", 
EbicsSubscribersTable).nullable()
     val password = EbicsSubscribersTable.blob("password").nullable()
 }
 
 class NexusUserEntity(id: EntityID<String>) : Entity<String>(id) {
     companion object : EntityClass<String, NexusUserEntity>(NexusUsersTable)
-    var ebicsSubscriber by EbicsSubscriberEntity referencedOn  
NexusUsersTable.ebicsSubscriber
+    var ebicsSubscriber by EbicsSubscriberEntity optionalReferencedOn 
NexusUsersTable.ebicsSubscriber
+    var testSubscriber by EbicsSubscriberEntity optionalReferencedOn 
NexusUsersTable.testSubscriber
     var password by NexusUsersTable.password
 }
 
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/Helpers.kt 
b/nexus/src/main/kotlin/tech/libeufin/nexus/Helpers.kt
index 7462d31..a9353a0 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/Helpers.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/Helpers.kt
@@ -2,14 +2,295 @@ package tech.libeufin.nexus
 
 import io.ktor.application.ApplicationCall
 import io.ktor.http.HttpStatusCode
-import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
 import org.jetbrains.exposed.sql.and
 import org.jetbrains.exposed.sql.transactions.transaction
 import org.joda.time.DateTime
 import org.joda.time.format.DateTimeFormat
+import tech.libeufin.util.Amount
 import tech.libeufin.util.CryptoUtil
+import tech.libeufin.util.EbicsClientSubscriberDetails
 import tech.libeufin.util.base64ToBytes
 import javax.sql.rowset.serial.SerialBlob
+import java.util.Random
+import tech.libeufin.util.ebics_h004.EbicsTypes
+import java.security.interfaces.RSAPublicKey
+import tech.libeufin.util.*
+import java.time.format.DateTimeFormatter
+import java.time.ZonedDateTime
+import java.time.Instant
+import java.time.ZoneId
+
+fun getSubscriberEntityFromNexusUserId(nexusUserId: String?): 
EbicsSubscriberEntity {
+    return transaction {
+        val nexusUser = expectNexusIdTransaction(expectId(nexusUserId))
+        getEbicsSubscriberFromUser(nexusUser)
+    }
+}
+
+fun calculateRefund(amount: String): Amount {
+    // fixme: must apply refund fees!
+    return Amount(amount)
+}
+
+/**
+ * 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
+}
+
+/**
+ * Get EBICS subscriber details from bank account id.
+ * bank account id => ... => ebics details
+ */
+fun getSubscriberDetailsFromBankAccount(bankAccountId: String): 
EbicsClientSubscriberDetails {
+    return transaction {
+        val map = EbicsToBankAccountEntity.find {
+            EbicsToBankAccountsTable.bankAccount eq bankAccountId
+        }.firstOrNull() ?: throw NexusError(
+            HttpStatusCode.NotFound,
+            "Such bank account '$bankAccountId' has no EBICS subscriber 
associated"
+        )
+        getSubscriberDetailsInternal(map.ebicsSubscriber)
+    }
+}
+
+/**
+ * Given a nexus user id, returns the _list_ of bank accounts associated to it.
+ *
+ * @param id the subscriber id
+ * @return the bank account associated with this user.  Can/should be adapted 
to
+ * return multiple bank accounts.
+ */
+fun getBankAccountFromNexusUserId(id: String): BankAccountEntity {
+    logger.debug("Looking up bank account of user '$id'")
+    val map = transaction {
+        UserToBankAccountEntity.find {
+            UserToBankAccountsTable.nexusUser eq id
+        }
+    }.firstOrNull() ?: throw NexusError(
+        HttpStatusCode.NotFound,
+        "Such user '$id' does not have any bank account associated"
+    )
+    return map.bankAccount
+}
+
+fun getSubscriberDetailsInternal(subscriber: EbicsSubscriberEntity): 
EbicsClientSubscriberDetails {
+    var bankAuthPubValue: RSAPublicKey? = null
+    if (subscriber.bankAuthenticationPublicKey != null) {
+        bankAuthPubValue = CryptoUtil.loadRsaPublicKey(
+            subscriber.bankAuthenticationPublicKey?.toByteArray()!!
+        )
+    }
+    var bankEncPubValue: RSAPublicKey? = null
+    if (subscriber.bankEncryptionPublicKey != null) {
+        bankEncPubValue = CryptoUtil.loadRsaPublicKey(
+            subscriber.bankEncryptionPublicKey?.toByteArray()!!
+        )
+    }
+    return EbicsClientSubscriberDetails(
+        bankAuthPub = bankAuthPubValue,
+        bankEncPub = bankEncPubValue,
+
+        ebicsUrl = subscriber.ebicsURL,
+        hostId = subscriber.hostID,
+        userId = subscriber.userID,
+        partnerId = subscriber.partnerID,
+
+        customerSignPriv = 
CryptoUtil.loadRsaPrivateKey(subscriber.signaturePrivateKey.toByteArray()),
+        customerAuthPriv = 
CryptoUtil.loadRsaPrivateKey(subscriber.authenticationPrivateKey.toByteArray()),
+        customerEncPriv = 
CryptoUtil.loadRsaPrivateKey(subscriber.encryptionPrivateKey.toByteArray())
+    )
+}
+
+/** Return non null Ebics subscriber, or throw error otherwise. */
+fun getEbicsSubscriberFromUser(nexusUser: NexusUserEntity): 
EbicsSubscriberEntity {
+    return nexusUser.ebicsSubscriber ?: throw NexusError(
+        HttpStatusCode.NotFound,
+        "Ebics subscriber was never activated"
+    )
+}
+
+fun getSubscriberDetailsFromNexusUserId(id: String): 
EbicsClientSubscriberDetails {
+    return transaction {
+        val nexusUser = expectNexusIdTransaction(id)
+        getSubscriberDetailsInternal(nexusUser.ebicsSubscriber ?: throw 
NexusError(
+            HttpStatusCode.NotFound,
+            "Cannot get details for non-activated subscriber!"
+        ))
+    }
+}
+
+/**
+ * Create a PAIN.001 XML document according to the input data.
+ * Needs to be called within a transaction block.
+ */
+fun createPain001document(pain001Entity: Pain001Entity): 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(pain001Entity.id.value.toString())
+                    }
+                    element("CreDtTm") {
+                        val dateMillis = transaction {
+                            pain001Entity.date
+                        }
+                        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(pain001Entity.sum.toString())
+                    }
+                    element("InitgPty/Nm") {
+                        text(pain001Entity.debtorAccount)
+                    }
+                }
+                element("PmtInf") {
+                    element("PmtInfId") {
+                        text(pain001Entity.id.value.toString())
+                    }
+                    element("PmtMtd") {
+                        text("TRF")
+                    }
+                    element("BtchBookg") {
+                        text("true")
+                    }
+                    element("NbOfTxs") {
+                        text("1")
+                    }
+                    element("CtrlSum") {
+                        text(pain001Entity.sum.toString())
+                    }
+                    element("PmtTpInf/SvcLvl/Cd") {
+                        text("SEPA")
+                    }
+                    element("ReqdExctnDt") {
+                        val dateMillis = transaction {
+                            pain001Entity.date
+                        }
+                        text(DateTime(dateMillis).toString("Y-MM-dd"))
+                    }
+                    element("Dbtr/Nm") {
+                        text(pain001Entity.debtorAccount)
+                    }
+                    element("DbtrAcct/Id/IBAN") {
+                        text(transaction {
+                            
BankAccountEntity.findById(pain001Entity.debtorAccount)?.iban ?: throw 
NexusError(HttpStatusCode.NotFound,"Debtor IBAN not found in database")
+                        })
+                    }
+                    element("DbtrAgt/FinInstnId/BIC") {
+
+                        text(transaction {
+                            
BankAccountEntity.findById(pain001Entity.debtorAccount)?.bankCode ?: throw 
NexusError(HttpStatusCode.NotFound,"Debtor BIC not found in database")
+                        })
+                    }
+                    element("ChrgBr") {
+                        text("SLEV")
+                    }
+                    element("CdtTrfTxInf") {
+                        element("PmtId") {
+                            element("EndToEndId") {
+                                // text(pain001Entity.id.value.toString())
+                                text("NOTPROVIDED")
+                            }
+                        }
+                        element("Amt/InstdAmt") {
+                            attribute("Ccy", pain001Entity.currency)
+                            text(pain001Entity.sum.toString())
+                        }
+                        element("CdtrAgt/FinInstnId/BIC") {
+                            text(pain001Entity.creditorBic)
+                        }
+                        element("Cdtr/Nm") {
+                            text(pain001Entity.creditorName)
+                        }
+                        element("CdtrAcct/Id/IBAN") {
+                            text(pain001Entity.creditorIban)
+                        }
+                        element("RmtInf/Ustrd") {
+                            text(pain001Entity.subject)
+                        }
+                    }
+                }
+            }
+        }
+    }
+    return s
+}
+
+/**
+ * Insert one row in the database, and leaves it marked as non-submitted.
+ * @param debtorAccountId the mnemonic id assigned by the bank to one bank
+ * account of the subscriber that is creating the pain entity.  In this case,
+ * it will be the account whose money will pay the wire transfer being defined
+ * by this pain document.
+ */
+fun createPain001entity(entry: Pain001Data, debtorAccountId: String): 
Pain001Entity {
+    val randomId = Random().nextLong()
+    return transaction {
+        Pain001Entity.new {
+            subject = entry.subject
+            sum = entry.sum
+            debtorAccount = debtorAccountId
+            creditorName = entry.creditorName
+            creditorBic = entry.creditorBic
+            creditorIban = entry.creditorIban
+            date = DateTime.now().millis
+            paymentId = randomId
+            msgId = randomId
+            endToEndId = randomId
+        }
+    }
+}
 
 /**
  * Inserts spaces every 2 characters, and a newline after 8 pairs.
@@ -134,6 +415,16 @@ fun subscriberHasRights(subscriber: EbicsSubscriberEntity, 
bankAccount: BankAcco
     return row != null
 }
 
+/** Check if the nexus user is allowed to use the claimed bank account.  */
+fun userHasRights(subscriber: NexusUserEntity, bankAccount: 
BankAccountEntity): Boolean {
+    val row = transaction {
+        UserToBankAccountEntity.find {
+            UserToBankAccountsTable.bankAccount eq bankAccount.id and
+                    (UserToBankAccountsTable.nexusUser eq subscriber.id)
+        }.firstOrNull()
+    }
+    return row != null
+}
 
 fun parseDate(date: String): DateTime {
     return DateTime.parse(date, DateTimeFormat.forPattern("YYYY-MM-DD"))
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/JSON.kt 
b/nexus/src/main/kotlin/tech/libeufin/nexus/JSON.kt
index 9108b41..201b7a1 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/JSON.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/JSON.kt
@@ -4,6 +4,7 @@ import tech.libeufin.util.Amount
 import tech.libeufin.util.EbicsDateRange
 import tech.libeufin.util.EbicsOrderParams
 import tech.libeufin.util.EbicsStandardOrderParams
+import java.lang.NullPointerException
 import java.time.LocalDate
 
 data class EbicsBackupRequestJson(
@@ -54,54 +55,12 @@ data class EbicsKeysBackupJson(
     val passphrase: String? = null
 )
 
-
 data class EbicsPubKeyInfo(
     val authPub: String,
     val encPub: String,
     val sigPub: String
 )
 
-/**
- * This object is POSTed by clients _after_ having created
- * a EBICS subscriber at the sandbox.
- */
-data class EbicsSubscriberInfoRequestJson(
-    val ebicsURL: String,
-    val hostID: String,
-    val partnerID: String,
-    val userID: String,
-    val systemID: String? = null,
-    val password: String? = null
-)
-
-/**
- * Contain the ID that identifies the new user in the Nexus system.
- */
-data class EbicsSubscriberInfoResponseJson(
-    val nexusUserID: String,
-    val ebicsURL: String,
-    val hostID: String,
-    val partnerID: String,
-    val userID: String,
-    val systemID: String? = null
-)
-
-data class Pain001Data(
-    val creditorIban: String,
-    val creditorBic: String,
-    val creditorName: String,
-    val sum: Amount,
-    val currency: String = "EUR",
-    val subject: String
-)
-
-/**
- * Admin call that tells all the subscribers managed by Nexus.
- */
-data class EbicsSubscribersResponseJson(
-    val ebicsSubscribers: MutableList<EbicsSubscriberInfoResponseJson> = 
mutableListOf()
-)
-
 data class ProtocolAndVersionJson(
     val protocol: String,
     val version: String
@@ -131,6 +90,56 @@ data class BankAccountsInfoResponse(
     var accounts: MutableList<BankAccountInfoElement> = mutableListOf()
 )
 
+/** THE NEXUS USER */
+
+/** SHOWS details about one user */
+data class NexusUser(
+    val userID: String,
+    val transports: MutableList<Any> = mutableListOf()
+)
+
+/** Instructs the nexus to CREATE a new user */
+data class NexusUserRequest(
+    val userID: String,
+    val password: String?
+)
+
+/** Collection of all the nexus users existing in the system */
+data class NexusUsers(
+    val users: MutableList<NexusUser> = mutableListOf()
+)
+
+/************************************/
+
+/** TRANSPORT TYPES */
+
+/** Instructs the nexus to CREATE a new Ebics subscriber.
+ * Note that the nexus user to which the subscriber must be
+ * associated is extracted from other HTTP details.
+ *
+ * This same structure can be user to SHOW one Ebics subscriber
+ * existing at the nexus.
+ */
+data class EbicsSubscriber(
+    val ebicsURL: String,
+    val hostID: String,
+    val partnerID: String,
+    val userID: String,
+    val systemID: String? = null
+)
+
+/** Type representing the "test" transport.  Test transport
+ * does not cooperate with the bank/sandbox in order to obtain
+ * data about one user.  All the data is just mocked internally
+ * at the NEXUS.
+ */
+class TestSubscriber()
+
+
+/** PAYMENT INSTRUCTIONS TYPES */
+
+/** Represents a prepared payment at the nexus.  This structure is
+ * used to SHOW a prepared payment to the called.  */
 data class PaymentInfoElement(
     val debtorAccount: String,
     val creditorIban: String,
@@ -140,7 +149,16 @@ data class PaymentInfoElement(
     val sum: Amount,
     val submitted: Boolean
 )
-
 data class PaymentsInfo(
     var payments: MutableList<PaymentInfoElement> = mutableListOf()
+)
+
+/** This structure is used to INSTRUCT the nexus to prepare such payment.  */
+data class Pain001Data(
+    val creditorIban: String,
+    val creditorBic: String,
+    val creditorName: String,
+    val sum: Amount,
+    val currency: String = "EUR",
+    val subject: String
 )
\ No newline at end of file
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt 
b/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt
index 288f878..8f3f7a1 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt
@@ -43,27 +43,16 @@ import kotlinx.coroutines.io.ByteReadChannel
 import kotlinx.coroutines.io.jvm.javaio.toByteReadChannel
 import kotlinx.coroutines.io.jvm.javaio.toInputStream
 import kotlinx.io.core.ExperimentalIoApi
-import org.jetbrains.exposed.sql.SizedIterable
-import org.jetbrains.exposed.sql.StdOutSqlLogger
-import org.jetbrains.exposed.sql.addLogger
 import org.jetbrains.exposed.sql.and
 import org.jetbrains.exposed.sql.transactions.transaction
-import org.joda.time.DateTime
 import org.slf4j.Logger
 import org.slf4j.LoggerFactory
 import org.slf4j.event.Level
 import tech.libeufin.util.*
-import tech.libeufin.util.ebics_h004.EbicsTypes
 import tech.libeufin.util.ebics_h004.HTDResponseOrderData
-import java.security.interfaces.RSAPublicKey
 import java.text.DateFormat
 import java.text.SimpleDateFormat
-import java.time.Instant
-import java.time.ZoneId
-import java.time.ZonedDateTime
-import java.time.format.DateTimeFormatter
 import java.util.*
-import java.util.zip.Inflater
 import java.util.zip.InflaterInputStream
 import javax.crypto.EncryptedPrivateKeyInfo
 import javax.sql.rowset.serial.SerialBlob
@@ -77,267 +66,6 @@ fun isProduction(): Boolean {
     return System.getenv("NEXUS_PRODUCTION") != null
 }
 
-fun getSubscriberEntityFromNexusUserId(nexusUserId: String): 
EbicsSubscriberEntity {
-    return transaction {
-        val nexusUser = expectNexusIdTransaction(nexusUserId)
-        nexusUser.ebicsSubscriber
-    }
-}
-
-fun calculateRefund(amount: String): Amount {
-    // fixme: must apply refund fees!
-    return Amount(amount)
-}
-
-/**
- * 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
-}
-
-/**
- * Get EBICS subscriber details from bank account id.
- * bank account id => ... => ebics details
- */
-fun getSubscriberDetailsFromBankAccount(bankAccountId: String): 
EbicsClientSubscriberDetails {
-    return transaction {
-        val map = EbicsToBankAccountEntity.find {
-            EbicsToBankAccountsTable.bankAccount eq bankAccountId
-        }.firstOrNull() ?: throw NexusError(
-            HttpStatusCode.NotFound,
-            "Such bank account '$bankAccountId' has no EBICS subscriber 
associated"
-        )
-        getSubscriberDetailsInternal(map.ebicsSubscriber)
-    }
-}
-
-/**
- * Given a nexus user id, returns the _list_ of bank accounts associated to it.
- *
- * @param id the subscriber id
- * @return the bank account associated with this user.  Can/should be adapted 
to
- * return multiple bank accounts.
- */
-fun getBankAccountFromNexusUserId(id: String): BankAccountEntity {
-    logger.debug("Looking up bank account of user '$id'")
-    val map = transaction {
-        UserToBankAccountEntity.find {
-            UserToBankAccountsTable.nexusUser eq id
-        }
-    }.firstOrNull() ?: throw NexusError(
-        HttpStatusCode.NotFound,
-        "Such user '$id' does not have any bank account associated"
-    )
-    return map.bankAccount
-}
-
-fun getSubscriberDetailsInternal(subscriber: EbicsSubscriberEntity): 
EbicsClientSubscriberDetails {
-    var bankAuthPubValue: RSAPublicKey? = null
-    if (subscriber.bankAuthenticationPublicKey != null) {
-        bankAuthPubValue = CryptoUtil.loadRsaPublicKey(
-            subscriber.bankAuthenticationPublicKey?.toByteArray()!!
-        )
-    }
-    var bankEncPubValue: RSAPublicKey? = null
-    if (subscriber.bankEncryptionPublicKey != null) {
-        bankEncPubValue = CryptoUtil.loadRsaPublicKey(
-            subscriber.bankEncryptionPublicKey?.toByteArray()!!
-        )
-    }
-    return EbicsClientSubscriberDetails(
-        bankAuthPub = bankAuthPubValue,
-        bankEncPub = bankEncPubValue,
-
-        ebicsUrl = subscriber.ebicsURL,
-        hostId = subscriber.hostID,
-        userId = subscriber.userID,
-        partnerId = subscriber.partnerID,
-
-        customerSignPriv = 
CryptoUtil.loadRsaPrivateKey(subscriber.signaturePrivateKey.toByteArray()),
-        customerAuthPriv = 
CryptoUtil.loadRsaPrivateKey(subscriber.authenticationPrivateKey.toByteArray()),
-        customerEncPriv = 
CryptoUtil.loadRsaPrivateKey(subscriber.encryptionPrivateKey.toByteArray())
-    )
-}
-
-fun getSubscriberDetailsFromNexusUserId(id: String): 
EbicsClientSubscriberDetails {
-    return transaction {
-        val nexusUser = expectNexusIdTransaction(id)
-        getSubscriberDetailsInternal(nexusUser.ebicsSubscriber)
-    }
-}
-
-/**
- * Create a PAIN.001 XML document according to the input data.
- * Needs to be called within a transaction block.
- */
-fun createPain001document(pain001Entity: Pain001Entity): 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(pain001Entity.id.value.toString())
-                    }
-                    element("CreDtTm") {
-                        val dateMillis = transaction {
-                            pain001Entity.date
-                        }
-                        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(pain001Entity.sum.toString())
-                    }
-                    element("InitgPty/Nm") {
-                        text(pain001Entity.debtorAccount)
-                    }
-                }
-                element("PmtInf") {
-                    element("PmtInfId") {
-                        text(pain001Entity.id.value.toString())
-                    }
-                    element("PmtMtd") {
-                        text("TRF")
-                    }
-                    element("BtchBookg") {
-                        text("true")
-                    }
-                    element("NbOfTxs") {
-                        text("1")
-                    }
-                    element("CtrlSum") {
-                        text(pain001Entity.sum.toString())
-                    }
-                    element("PmtTpInf/SvcLvl/Cd") {
-                        text("SEPA")
-                    }
-                    element("ReqdExctnDt") {
-                        val dateMillis = transaction {
-                            pain001Entity.date
-                        }
-                        text(DateTime(dateMillis).toString("Y-MM-dd"))
-                    }
-                    element("Dbtr/Nm") {
-                        text(pain001Entity.debtorAccount)
-                    }
-                    element("DbtrAcct/Id/IBAN") {
-                        text(transaction {
-                            
BankAccountEntity.findById(pain001Entity.debtorAccount)?.iban ?: throw 
NexusError(HttpStatusCode.NotFound,"Debtor IBAN not found in database")
-                        })
-                    }
-                    element("DbtrAgt/FinInstnId/BIC") {
-
-                        text(transaction {
-                            
BankAccountEntity.findById(pain001Entity.debtorAccount)?.bankCode ?: throw 
NexusError(HttpStatusCode.NotFound,"Debtor BIC not found in database")
-                        })
-                    }
-                    element("ChrgBr") {
-                        text("SLEV")
-                    }
-                    element("CdtTrfTxInf") {
-                        element("PmtId") {
-                            element("EndToEndId") {
-                                // text(pain001Entity.id.value.toString())
-                                text("NOTPROVIDED")
-                            }
-                        }
-                        element("Amt/InstdAmt") {
-                            attribute("Ccy", pain001Entity.currency)
-                            text(pain001Entity.sum.toString())
-                        }
-                        element("CdtrAgt/FinInstnId/BIC") {
-                            text(pain001Entity.creditorBic)
-                        }
-                        element("Cdtr/Nm") {
-                            text(pain001Entity.creditorName)
-                        }
-                        element("CdtrAcct/Id/IBAN") {
-                            text(pain001Entity.creditorIban)
-                        }
-                        element("RmtInf/Ustrd") {
-                            text(pain001Entity.subject)
-                        }
-                    }
-                }
-            }
-        }
-    }
-    return s
-}
-
-/**
- * Insert one row in the database, and leaves it marked as non-submitted.
- * @param debtorAccountId the mnemonic id assigned by the bank to one bank
- * account of the subscriber that is creating the pain entity.  In this case,
- * it will be the account whose money will pay the wire transfer being defined
- * by this pain document.
- */
-fun createPain001entity(entry: Pain001Data, debtorAccountId: String): 
Pain001Entity {
-    val randomId = Random().nextLong()
-    return transaction {
-        Pain001Entity.new {
-            subject = entry.subject
-            sum = entry.sum
-            debtorAccount = debtorAccountId
-            creditorName = entry.creditorName
-            creditorBic = entry.creditorBic
-            creditorIban = entry.creditorIban
-            date = DateTime.now().millis
-            paymentId = randomId
-            msgId = randomId
-            endToEndId = randomId
-        }
-    }
-}
-
 @ExperimentalIoApi
 @KtorExperimentalAPI
 fun main() {
@@ -405,6 +133,9 @@ fun main() {
         }
 
         routing {
+
+            /** General / debug endpoints */
+
             get("/") {
                 call.respondText("Hello by Nexus!\n")
                 return@get
@@ -416,54 +147,71 @@ fun main() {
                 return@get
             }
 
-            post("/ebics/subscribers/{id}/sendPTK") {
-                val id = expectId(call.parameters["id"])
-                val paramsJson = call.receive<EbicsStandardOrderParamsJson>()
-                val orderParams = paramsJson.toOrderParams()
-                println("PTK order params: $orderParams")
-                val subscriberData = getSubscriberDetailsFromNexusUserId(id)
-                val response = doEbicsDownloadTransaction(client, 
subscriberData, "PTK", orderParams)
-                when (response) {
-                    is EbicsDownloadSuccessResult -> {
-                        call.respondText(
-                            response.orderData.toString(Charsets.UTF_8),
-                            ContentType.Text.Plain,
-                            HttpStatusCode.OK
-                        )
-                    }
-                    is EbicsDownloadBankErrorResult -> {
-                        call.respond(
-                            HttpStatusCode.BadGateway,
-                            EbicsErrorJson(EbicsErrorDetailJson("bankError", 
response.returnCode.errorCode))
-                        )
+            /** USER endpoints (no EBICS) */
+
+            /** Lists the users known to this system */
+            get("/users") {
+                val ret = NexusUsers()
+                transaction {
+                    NexusUserEntity.all().forEach {
+                        val nexusUser = NexusUser(userID = it.id.value)
+                        val ebicsSubscriber = it.ebicsSubscriber
+                        if (ebicsSubscriber != null) {
+                            nexusUser.transports.add(
+                                EbicsSubscriber(
+                                    userID = ebicsSubscriber.userID,
+                                    ebicsURL = ebicsSubscriber.ebicsURL,
+                                    hostID = ebicsSubscriber.hostID,
+                                    partnerID = ebicsSubscriber.partnerID,
+                                    systemID = ebicsSubscriber.systemID
+                                )
+                            )
+                        }
+                        if (it.testSubscriber != null) {
+                            nexusUser.transports.add(TestSubscriber())
+                        }
                     }
                 }
-                return@post
+                call.respond(ret)
+                return@get
             }
 
-            post("/ebics/subscribers/{id}/sendHAC") {
-                val id = expectId(call.parameters["id"])
-                val paramsJson = call.receive<EbicsStandardOrderParamsJson>()
-                val orderParams = paramsJson.toOrderParams()
-                val subscriberData = getSubscriberDetailsFromNexusUserId(id)
-                val response = doEbicsDownloadTransaction(client, 
subscriberData, "HAC", orderParams)
-                when (response) {
-                    is EbicsDownloadSuccessResult -> {
-                        call.respondText(
-                            response.orderData.toString(Charsets.UTF_8),
-                            ContentType.Text.Plain,
-                            HttpStatusCode.OK
-                        )
-                    }
-                    is EbicsDownloadBankErrorResult -> {
-                        call.respond(
-                            HttpStatusCode.BadGateway,
-                            EbicsErrorJson(EbicsErrorDetailJson("bankError", 
response.returnCode.errorCode))
-                        )
+            /** Get all the details associated with a NEXUS user */
+            get("/user/{id}") {
+                val response = transaction {
+                    val nexusUser = 
expectNexusIdTransaction(call.parameters["id"])
+                    NexusUser(
+                        userID = nexusUser.id.value
+                    )
+                }
+                call.respond(HttpStatusCode.OK, response)
+                return@get
+            }
+
+            /** Make a new NEXUS user in the system */
+            post("/users/{id}") {
+                val newUserId = expectId(call.parameters["id"])
+                val body = call.receive<NexusUserRequest>()
+                transaction {
+                    NexusUserEntity.new(id = newUserId) {
+                        password = if (body.password != null) {
+                            
SerialBlob(CryptoUtil.hashStringSHA256(body.password))
+                        } else {
+                            logger.debug("No password set for $newUserId")
+                            null
+                        }
                     }
                 }
+                call.respondText(
+                    "New NEXUS user registered. ID: $newUserId",
+                    ContentType.Text.Plain,
+                    HttpStatusCode.OK
+                )
+                return@post
             }
-            get("/ebics/subscribers/{id}/accounts") {
+
+            /** List all the bank accounts associated with a given NEXUS user. 
 */
+            get("/users/{id}/accounts") {
                 // this information is only avaiable *after* HTD or HKD has 
been called
                 val id = expectId(call.parameters["id"])
                 val ret = BankAccountsInfoResponse()
@@ -487,40 +235,14 @@ fun main() {
                 )
                 return@get
             }
-            /**
-             * This endpoint gathers all the data needed to create a payment 
and persists it
-             * into the database.  However, it does NOT perform the payment 
itself!
-             */
-            post("/ebics/subscribers/{id}/accounts/{acctid}/prepare-payment") {
-                val acctid = transaction {
-                    val accountInfo = 
expectAcctidTransaction(call.parameters["acctid"])
-                    val nexusUser = 
expectNexusIdTransaction(call.parameters["id"])
-                    if (!subscriberHasRights(nexusUser.ebicsSubscriber, 
accountInfo)) {
-                        throw NexusError(
-                            HttpStatusCode.BadRequest,
-                            "Claimed bank account '${accountInfo.id}' doesn't 
belong to user '${nexusUser.id.value}'!"
-                        )
-                    }
-                    accountInfo.id.value
-                }
-                val pain001data = call.receive<Pain001Data>()
-                createPain001entity(pain001data, acctid)
-                call.respondText(
-                    "Payment instructions persisted in DB",
-                    ContentType.Text.Plain, HttpStatusCode.OK
-                )
-                return@post
-            }
-            /**
-             * list all the prepared payments related to customer {id}
-             */
-            get("/ebics/subscribers/{id}/payments") {
+            /** Show list of payments prepared by calling user.  */
+            get("/users/{id}/payments") {
                 val nexusUserId = expectId(call.parameters["id"])
                 val ret = PaymentsInfo()
                 transaction {
                     val nexusUser = expectNexusIdTransaction(nexusUserId)
-                    val bankAccountsMap = EbicsToBankAccountEntity.find {
-                        EbicsToBankAccountsTable.ebicsSubscriber eq 
nexusUser.ebicsSubscriber.id
+                    val bankAccountsMap = UserToBankAccountEntity.find {
+                        UserToBankAccountsTable.nexusUser eq nexusUser.id
                     }
                     bankAccountsMap.forEach {
                         Pain001Entity.find {
@@ -543,441 +265,131 @@ fun main() {
                 call.respond(ret)
                 return@get
             }
-            /**
-             * This function triggers the Nexus to perform all those 
un-submitted payments.
-             * Ideally, this logic will be moved into some more automatic 
mechanism.
-             * NOTE: payments are not yet marked as "done" after this function 
returns.  This
-             * should be done AFTER the PAIN.002 data corresponding to a 
payment witnesses it.
-             */
-            post("/ebics/admin/execute-payments") {
-                val (paymentRowId, painDoc: String, debtorAccount) = 
transaction {
-                    val entity = Pain001Entity.find {
-                        (Pain001Table.submitted eq false) and 
(Pain001Table.invalid eq false)
-                    }.firstOrNull() ?: throw 
NexusError(HttpStatusCode.Accepted, reason = "No ready payments found")
-                    Triple(entity.id, createPain001document(entity), 
entity.debtorAccount)
-                }
-                logger.debug("Uploading PAIN.001: ${painDoc}")
-                val subscriberDetails = 
getSubscriberDetailsFromBankAccount(debtorAccount)
-                doEbicsUploadTransaction(
-                    client,
-                    subscriberDetails,
-                    "CCT",
-                    painDoc.toByteArray(Charsets.UTF_8),
-                    EbicsStandardOrderParams()
-                )
-                /* flow here == no errors occurred */
-                transaction {
-                    val payment = Pain001Entity.findById(paymentRowId) ?: 
throw NexusError(
-                        HttpStatusCode.InternalServerError,
-                        "Severe internal error: could not find payment in DB 
after having submitted it to the bank"
-                    )
-                    payment.submitted = true
+            post("/users/{id}/accounts/{acctid}/prepare-payment") {
+                val acctid = transaction {
+                    val accountInfo = 
expectAcctidTransaction(call.parameters["acctid"])
+                    val nexusUser = 
expectNexusIdTransaction(call.parameters["id"])
+                    if (!userHasRights(nexusUser, accountInfo)) {
+                        throw NexusError(
+                            HttpStatusCode.BadRequest,
+                            "Claimed bank account '${accountInfo.id}' doesn't 
belong to user '${nexusUser.id.value}'!"
+                        )
+                    }
+                    accountInfo.id.value
                 }
+                val pain001data = call.receive<Pain001Data>()
+                createPain001entity(pain001data, acctid)
                 call.respondText(
-                    "CCT message submitted to the bank",
-                    ContentType.Text.Plain,
-                    HttpStatusCode.OK
+                    "Payment instructions persisted in DB",
+                    ContentType.Text.Plain, HttpStatusCode.OK
                 )
                 return@post
             }
-            /**
-             * This function triggers the Nexus to perform all those 
un-submitted payments.
-             * Ideally, this logic will be moved into some more automatic 
mechanism.
-             * NOTE: payments are not yet marked as "done" after this function 
returns.  This
-             * should be done AFTER the PAIN.002 data corresponding to a 
payment witnesses it.
-             */
-            post("/ebics/admin/execute-payments-ccc") {
-                val (paymentRowId, painDoc: String, debtorAccount) = 
transaction {
-                    val entity = Pain001Entity.find {
-                        (Pain001Table.submitted eq false) and 
(Pain001Table.invalid eq false)
-                    }.firstOrNull() ?: throw 
NexusError(HttpStatusCode.Accepted, reason = "No ready payments found")
-                    Triple(entity.id, createPain001document(entity), 
entity.debtorAccount)
-                }
-                logger.debug("Uploading PAIN.001 via CCC: ${painDoc}")
-                val subscriberDetails = 
getSubscriberDetailsFromBankAccount(debtorAccount)
-                doEbicsUploadTransaction(
-                    client,
-                    subscriberDetails,
-                    "CCC",
-                    painDoc.toByteArray(Charsets.UTF_8).zip(),
-                    EbicsStandardOrderParams()
-                )
-                /* flow here == no errors occurred */
+
+            /** Associate a EBICS subscriber to the existing user */
+            post("/ebics/{id}/subscriber") {
+                val body = call.receive<EbicsSubscriber>()
+                val pairA = CryptoUtil.generateRsaKeyPair(2048)
+                val pairB = CryptoUtil.generateRsaKeyPair(2048)
+                val pairC = CryptoUtil.generateRsaKeyPair(2048)
+
                 transaction {
-                    val payment = Pain001Entity.findById(paymentRowId) ?: 
throw NexusError(
-                        HttpStatusCode.InternalServerError,
-                        "Severe internal error: could not find payment in DB 
after having submitted it to the bank"
-                    )
-                    payment.submitted = true
+                    val newEbicsSubscriber = EbicsSubscriberEntity.new {
+                        ebicsURL = body.ebicsURL
+                        hostID = body.hostID
+                        partnerID = body.partnerID
+                        userID = body.userID
+                        systemID = body.systemID
+                        signaturePrivateKey = SerialBlob(pairA.private.encoded)
+                        encryptionPrivateKey = 
SerialBlob(pairB.private.encoded)
+                        authenticationPrivateKey = 
SerialBlob(pairC.private.encoded)
+                    }
+                    val nexusUser = 
expectNexusIdTransaction(call.parameters["id"])
+                    nexusUser.ebicsSubscriber = newEbicsSubscriber
                 }
-                call.respondText(
-                    "CCC message submitted to the bank",
-                    ContentType.Text.Plain,
-                    HttpStatusCode.OK
-                )
-                return@post
-            }
-
-            post("/ebics/subscribers/{id}/fetch-payment-status") {
-                val id = expectId(call.parameters["id"])
-                val paramsJson = call.receive<EbicsStandardOrderParamsJson>()
-                val orderParams = paramsJson.toOrderParams()
-                val subscriberData = getSubscriberDetailsFromNexusUserId(id)
-                val response = doEbicsDownloadTransaction(
-                    client,
-                    subscriberData,
-                    "CRZ",
-                    orderParams
-                )
-                when (response) {
-                    is EbicsDownloadSuccessResult ->
-                        call.respondText(
-                            response.orderData.toString(Charsets.UTF_8),
-                            ContentType.Text.Plain,
-                            HttpStatusCode.OK
-                        )
-                    /**
-                     * NOTE: flow gets here when the bank-technical return 
code is
-                     * different from 000000.  This happens also for 090005 
(no data available)
-                     */
-                    else -> call.respond(NexusErrorJson("Could not download 
any PAIN.002"))
-                }
-                return@post
-            }
-            post("/ebics/subscribers/{id}/collect-transactions-c52") {
-                // FIXME(florian): Download C52 and store the result in the 
right database table
 
             }
-            get("/ebics/subscribers/{id}/show-collected-transactions-c53") {
-                val id = expectId(call.parameters["id"])
-                var ret = ""
-                transaction {
-                    val subscriber: EbicsSubscriberEntity = 
getSubscriberEntityFromNexusUserId(id)
-                    RawBankTransactionEntity.find {
-                        RawBankTransactionsTable.nexusSubscriber eq 
subscriber.id.value
-                    }.forEach {
-                        ret += "###\nDebitor: ${it.debitorIban}\nCreditor: 
${it.creditorIban}\nAmount: ${it.currency}:${it.amount}\nDate: 
${it.bookingDate}\n"
-                    }
-                }
-                call.respondText(
-                    ret,
-                    ContentType.Text.Plain,
-                    HttpStatusCode.OK
-                )
-
-                return@get
-            }
-
-            /* Taler class will initialize all the relevant handlers.  */
-            Taler(this)
-
-            post("/ebics/subscribers/{id}/collect-transactions-c53") {
-                val id = expectId(call.parameters["id"])
-                val paramsJson = call.receive<EbicsStandardOrderParamsJson>()
-                val orderParams = paramsJson.toOrderParams()
-                val subscriberData = getSubscriberDetailsFromNexusUserId(id)
-                when (val response = doEbicsDownloadTransaction(client, 
subscriberData, "C53", orderParams)) {
-                    is EbicsDownloadSuccessResult -> {
-                        /**
-                         * The current code is _heavily_ dependent on the way 
GLS returns
-                         * data.  For example, GLS makes one ZIP entry for 
each "Ntry" element
-                         * (a bank transfer), but per the specifications one 
bank can choose to
-                         * return all the "Ntry" elements into one single ZIP 
entry, or even unzipped
-                         * at all.
-                         */
-                        response.orderData.unzipWithLoop {
-                            val fileName = it.first
-                            val camt53doc = 
XMLUtil.parseStringIntoDom(it.second)
-                            transaction {
-                                RawBankTransactionEntity.new {
-                                    sourceFileName = fileName
-                                    unstructuredRemittanceInformation = 
camt53doc.pickString("//*[local-name()='Ntry']//*[local-name()='Amt']/@Ccy")
-                                    transactionType = 
camt53doc.pickString("//*[local-name()='Ntry']//*[local-name()='CdtDbtInd']")
-                                    currency = 
camt53doc.pickString("//*[local-name()='Ntry']//*[local-name()='Amt']/@Ccy")
-                                    amount = 
camt53doc.pickString("//*[local-name()='Ntry']//*[local-name()='Amt']")
-                                    status = 
camt53doc.pickString("//*[local-name()='Ntry']//*[local-name()='Sts']")
-                                    bookingDate = 
parseDate(camt53doc.pickString("//*[local-name()='BookgDt']//*[local-name()='Dt']")).millis
-                                    nexusSubscriber = 
getSubscriberEntityFromNexusUserId(id)
-                                    creditorName = 
camt53doc.pickString("//*[local-name()='RltdPties']//*[local-name()='Dbtr']//*[local-name()='Nm']")
-                                    creditorIban = 
camt53doc.pickString("//*[local-name()='CdtrAcct']//*[local-name()='IBAN']")
-                                    debitorName = 
camt53doc.pickString("//*[local-name()='RltdPties']//*[local-name()='Dbtr']//*[local-name()='Nm']")
-                                    debitorIban = 
camt53doc.pickString("//*[local-name()='DbtrAcct']//*[local-name()='IBAN']")
-                                    counterpartBic = 
camt53doc.pickString("//*[local-name()='RltdAgts']//*[local-name()='BIC']")
-                                }
-                            }
-                        }
-                        call.respondText(
-                            "C53 data persisted into the database (WIP).",
-                            ContentType.Text.Plain,
-                            HttpStatusCode.OK
-                        )
-                    }
-                    is EbicsDownloadBankErrorResult -> {
-                        call.respond(
-                            HttpStatusCode.BadGateway,
-                            EbicsErrorJson(EbicsErrorDetailJson("bankError", 
response.returnCode.errorCode))
-                        )
-                    }
-                }
-                return@post
-            }
-            post("/ebics/subscribers/{id}/collect-transactions-c54") {
-                // FIXME(florian): Download C54 and store the result in the 
right database table
-            }
-            post("/ebics/subscribers/{id}/sendC52") {
-                val id = expectId(call.parameters["id"])
-                val paramsJson = call.receive<EbicsStandardOrderParamsJson>()
-                val orderParams = paramsJson.toOrderParams()
-                val subscriberData = getSubscriberDetailsFromNexusUserId(id)
-                val response = doEbicsDownloadTransaction(client, 
subscriberData, "C52", orderParams)
-                when (response) {
-                    is EbicsDownloadSuccessResult -> {
-                        call.respondText(
-                            response.orderData.prettyPrintUnzip(),
-                            ContentType.Text.Plain,
-                            HttpStatusCode.OK
-                        )
-                    }
-                    is EbicsDownloadBankErrorResult -> {
-                        call.respond(
-                            HttpStatusCode.BadGateway,
-                            EbicsErrorJson(EbicsErrorDetailJson("bankError", 
response.returnCode.errorCode))
-                        )
-                    }
-                }
-            }
-            post("/ebics/subscribers/{id}/sendCRZ") {
-                val id = expectId(call.parameters["id"])
-                val paramsJson = call.receive<EbicsStandardOrderParamsJson>()
-                val orderParams = paramsJson.toOrderParams()
-                val subscriberData = getSubscriberDetailsFromNexusUserId(id)
-                val response = doEbicsDownloadTransaction(client, 
subscriberData, "CRZ", orderParams)
-                when (response) {
-                    is EbicsDownloadSuccessResult -> {
-                        call.respondText(
-                            response.orderData.prettyPrintUnzip(),
-                            ContentType.Text.Plain,
-                            HttpStatusCode.OK
-                        )
-                    }
-                    is EbicsDownloadBankErrorResult -> {
-                        call.respond(
-                            HttpStatusCode.BadGateway,
-                            EbicsErrorJson(EbicsErrorDetailJson("bankError", 
response.returnCode.errorCode))
-                        )
-                    }
-                }
-            }
-            post("/ebics/subscribers/{id}/sendC53") {
-                val id = expectId(call.parameters["id"])
-                val paramsJson = call.receive<EbicsStandardOrderParamsJson>()
-                val orderParams = paramsJson.toOrderParams()
-                val subscriberData = getSubscriberDetailsFromNexusUserId(id)
-                val response = doEbicsDownloadTransaction(client, 
subscriberData, "C53", orderParams)
-                when (response) {
-                    is EbicsDownloadSuccessResult -> {
-                        call.respondText(
-                            response.orderData.prettyPrintUnzip(),
-                            ContentType.Text.Plain,
-                            HttpStatusCode.OK
-                        )
-                    }
-                    is EbicsDownloadBankErrorResult -> {
-                        call.respond(
-                            HttpStatusCode.BadGateway,
-                            EbicsErrorJson(EbicsErrorDetailJson("bankError", 
response.returnCode.errorCode))
-                        )
-                    }
-                }
-            }
-            post("/ebics/subscribers/{id}/sendC54") {
-                val id = expectId(call.parameters["id"])
-                val paramsJson = call.receive<EbicsStandardOrderParamsJson>()
-                val orderParams = paramsJson.toOrderParams()
-                val subscriberData = getSubscriberDetailsFromNexusUserId(id)
-                val response = doEbicsDownloadTransaction(client, 
subscriberData, "C54", orderParams)
-                when (response) {
-                    is EbicsDownloadSuccessResult -> {
-                        call.respondText(
-                            response.orderData.toString(Charsets.UTF_8),
-                            ContentType.Text.Plain,
-                            HttpStatusCode.OK
-                        )
-                    }
-                    is EbicsDownloadBankErrorResult -> {
-                        call.respond(
-                            HttpStatusCode.BadGateway,
-                            EbicsErrorJson(EbicsErrorDetailJson("bankError", 
response.returnCode.errorCode))
-                        )
-                    }
-                }
-                return@post
-            }
-            get("/ebics/subscribers/{id}/sendHTD") {
-                val customerIdAtNexus = expectId(call.parameters["id"])
-                val subscriberData = 
getSubscriberDetailsFromNexusUserId(customerIdAtNexus)
-                val response = doEbicsDownloadTransaction(
-                    client,
-                    subscriberData,
-                    "HTD",
-                    EbicsStandardOrderParams()
-                )
-                when (response) {
-                    is EbicsDownloadSuccessResult -> {
-                        call.respondText(
-                            response.orderData.toString(Charsets.UTF_8),
-                            ContentType.Text.Plain,
-                            HttpStatusCode.OK
-                        )
-                    }
-                    is EbicsDownloadBankErrorResult -> {
-                        call.respond(
-                            HttpStatusCode.BadGateway,
-                            EbicsErrorJson(EbicsErrorDetailJson("bankError", 
response.returnCode.errorCode))
-                        )
-                    }
-                }
-                return@get
-            }
-            post("/ebics/subscribers/{id}/sendHAA") {
-                val id = expectId(call.parameters["id"])
-                val subscriberData = getSubscriberDetailsFromNexusUserId(id)
-                val response = doEbicsDownloadTransaction(client, 
subscriberData, "HAA", EbicsStandardOrderParams())
-                when (response) {
-                    is EbicsDownloadSuccessResult -> {
-                        call.respondText(
-                            response.orderData.toString(Charsets.UTF_8),
-                            ContentType.Text.Plain,
-                            HttpStatusCode.OK
-                        )
-                    }
-                    is EbicsDownloadBankErrorResult -> {
-                        call.respond(
-                            HttpStatusCode.BadGateway,
-                            EbicsErrorJson(EbicsErrorDetailJson("bankError", 
response.returnCode.errorCode))
-                        )
-                    }
+            post("/ebics/subscribers/{id}/restoreBackup") {
+                val body = call.receive<EbicsKeysBackupJson>()
+                val nexusId = expectId(call.parameters["id"])
+                val subscriber = transaction {
+                    NexusUserEntity.findById(nexusId)
                 }
-                return@post
-            }
-
-            post("/ebics/subscribers/{id}/sendHVZ") {
-                val id = expectId(call.parameters["id"])
-                val subscriberData = getSubscriberDetailsFromNexusUserId(id)
-                // FIXME: order params are wrong
-                val response = doEbicsDownloadTransaction(client, 
subscriberData, "HVZ", EbicsStandardOrderParams())
-                when (response) {
-                    is EbicsDownloadSuccessResult -> {
-                        call.respondText(
-                            response.orderData.toString(Charsets.UTF_8),
-                            ContentType.Text.Plain,
-                            HttpStatusCode.OK
-                        )
-                    }
-                    is EbicsDownloadBankErrorResult -> {
-                        call.respond(
-                            HttpStatusCode.BadGateway,
-                            EbicsErrorJson(EbicsErrorDetailJson("bankError", 
response.returnCode.errorCode))
-                        )
-                    }
+                if (subscriber != null) {
+                    call.respond(
+                        HttpStatusCode.Conflict,
+                        NexusErrorJson("ID exists, please choose a new one")
+                    )
+                    return@post
                 }
-                return@post
-            }
-
-            post("/ebics/subscribers/{id}/sendHVU") {
-                val id = expectId(call.parameters["id"])
-                val subscriberData = getSubscriberDetailsFromNexusUserId(id)
-                // FIXME: order params are wrong
-                val response = doEbicsDownloadTransaction(client, 
subscriberData, "HVU", EbicsStandardOrderParams())
-                when (response) {
-                    is EbicsDownloadSuccessResult -> {
-                        call.respondText(
-                            response.orderData.toString(Charsets.UTF_8),
-                            ContentType.Text.Plain,
-                            HttpStatusCode.OK
-                        )
-                    }
-                    is EbicsDownloadBankErrorResult -> {
-                        call.respond(
-                            HttpStatusCode.BadGateway,
-                            EbicsErrorJson(EbicsErrorDetailJson("bankError", 
response.returnCode.errorCode))
+                val (authKey, encKey, sigKey) = try {
+                    Triple(
+                        CryptoUtil.decryptKey(
+                            
EncryptedPrivateKeyInfo(base64ToBytes(body.authBlob)), body.passphrase!!
+                        ),
+                        CryptoUtil.decryptKey(
+                            
EncryptedPrivateKeyInfo(base64ToBytes(body.encBlob)), body.passphrase
+                        ),
+                        CryptoUtil.decryptKey(
+                            
EncryptedPrivateKeyInfo(base64ToBytes(body.sigBlob)), body.passphrase
                         )
-                    }
+                    )
+                } catch (e: Exception) {
+                    e.printStackTrace()
+                    logger.info("Restoring keys failed, probably due to wrong 
passphrase")
+                    throw NexusError(HttpStatusCode.BadRequest, reason = "Bad 
backup given")
                 }
-                return@post
-            }
-
-            post("/ebics/subscribers/{id}/sendHPD") {
-                val id = expectId(call.parameters["id"])
-                val subscriberData = getSubscriberDetailsFromNexusUserId(id)
-                val response = doEbicsDownloadTransaction(client, 
subscriberData, "HPD", EbicsStandardOrderParams())
-                when (response) {
-                    is EbicsDownloadSuccessResult -> {
-                        call.respondText(
-                            response.orderData.toString(Charsets.UTF_8),
-                            ContentType.Text.Plain,
-                            HttpStatusCode.OK
-                        )
-                    }
-                    is EbicsDownloadBankErrorResult -> {
-                        call.respond(
-                            HttpStatusCode.BadGateway,
-                            EbicsErrorJson(EbicsErrorDetailJson("bankError", 
response.returnCode.errorCode))
-                        )
+                logger.info("Restoring keys, creating new user: $nexusId")
+                try {
+                    transaction {
+                        NexusUserEntity.new(id = nexusId) {
+                            ebicsSubscriber = EbicsSubscriberEntity.new {
+                                ebicsURL = body.ebicsURL
+                                hostID = body.hostID
+                                partnerID = body.partnerID
+                                userID = body.userID
+                                signaturePrivateKey = 
SerialBlob(sigKey.encoded)
+                                encryptionPrivateKey = 
SerialBlob(encKey.encoded)
+                                authenticationPrivateKey = 
SerialBlob(authKey.encoded)
+                            }
+                        }
                     }
+                } catch (e: Exception) {
+                    print(e)
+                    call.respond(NexusErrorJson("Could not store the new 
account into database"))
+                    return@post
                 }
+                call.respondText(
+                    "Keys successfully restored",
+                    ContentType.Text.Plain,
+                    HttpStatusCode.OK
+                )
                 return@post
             }
 
-            get("/ebics/subscribers/{id}/sendHKD") {
-                val id = expectId(call.parameters["id"])
-                val subscriberData = getSubscriberDetailsFromNexusUserId(id)
-                val response = doEbicsDownloadTransaction(
-                    client,
-                    subscriberData,
-                    "HKD",
-                    EbicsStandardOrderParams()
-                )
-                when (response) {
-                    is EbicsDownloadSuccessResult -> {
-                        call.respondText(
-                            response.orderData.toString(Charsets.UTF_8),
-                            ContentType.Text.Plain,
-                            HttpStatusCode.OK
-                        )
-                    }
-                    is EbicsDownloadBankErrorResult -> {
-                        call.respond(
-                            HttpStatusCode.BadGateway,
-                            EbicsErrorJson(EbicsErrorDetailJson("bankError", 
response.returnCode.errorCode))
-                        )
-                    }
-                }
-                return@get
-            }
+            /** EBICS CONVENIENCE */
 
-            post("/ebics/subscribers/{id}/sendTSD") {
-                val id = expectId(call.parameters["id"])
-                val subscriberData = getSubscriberDetailsFromNexusUserId(id)
-                val response = doEbicsDownloadTransaction(client, 
subscriberData, "TSD", EbicsGenericOrderParams())
-                when (response) {
-                    is EbicsDownloadSuccessResult -> {
-                        call.respondText(
-                            response.orderData.toString(Charsets.UTF_8),
-                            ContentType.Text.Plain,
-                            HttpStatusCode.OK
-                        )
-                    }
-                    is EbicsDownloadBankErrorResult -> {
-                        call.respond(
-                            HttpStatusCode.BadGateway,
-                            EbicsErrorJson(EbicsErrorDetailJson("bankError", 
response.returnCode.errorCode))
-                        )
-                    }
+            get("/ebics/subscribers/{id}/pubkeys") {
+                val nexusUser = expectNexusIdTransaction(call.parameters["id"])
+                val response = transaction {
+                    val subscriber = getEbicsSubscriberFromUser(nexusUser)
+                    val authPriv = 
CryptoUtil.loadRsaPrivateKey(subscriber.authenticationPrivateKey.toByteArray())
+                    val authPub = CryptoUtil.getRsaPublicFromPrivate(authPriv)
+                    val encPriv = 
CryptoUtil.loadRsaPrivateKey(subscriber.encryptionPrivateKey.toByteArray())
+                    val encPub = CryptoUtil.getRsaPublicFromPrivate(encPriv)
+                    val sigPriv = 
CryptoUtil.loadRsaPrivateKey(subscriber.signaturePrivateKey.toByteArray())
+                    val sigPub = CryptoUtil.getRsaPublicFromPrivate(sigPriv)
+                    EbicsPubKeyInfo(
+                        bytesToBase64(authPub.encoded),
+                        bytesToBase64(encPub.encoded),
+                        bytesToBase64(sigPub.encoded)
+                    )
                 }
-                return@post
+                call.respond(
+                    HttpStatusCode.OK,
+                    response
+                )
             }
-
             get("/ebics/subscribers/{id}/keyletter") {
                 val nexusUserId = expectId(call.parameters["id"])
                 var usernameLine = "TODO"
@@ -1004,7 +416,7 @@ fun main() {
                 var hostID = ""
                 transaction {
                     val nexusUser = expectNexusIdTransaction(nexusUserId)
-                    val subscriber = nexusUser.ebicsSubscriber
+                    val subscriber = getEbicsSubscriberFromUser(nexusUser)
                     val signPubTmp = CryptoUtil.getRsaPublicFromPrivate(
                         
CryptoUtil.loadRsaPrivateKey(subscriber.signaturePrivateKey.toByteArray())
                     )
@@ -1106,231 +518,223 @@ fun main() {
                     HttpStatusCode.OK
                 )
             }
-            /**
-             * Lists the EBICS subscribers known to this service.
-             */
-            get("/ebics/subscribers") {
-                val ret = EbicsSubscribersResponseJson()
-                transaction {
-                    NexusUserEntity.all().forEach {
-                        ret.ebicsSubscribers.add(
-                            EbicsSubscriberInfoResponseJson(
-                                hostID = it.ebicsSubscriber.hostID,
-                                partnerID = it.ebicsSubscriber.partnerID,
-                                systemID = it.ebicsSubscriber.systemID,
-                                ebicsURL = it.ebicsSubscriber.ebicsURL,
-                                userID = it.ebicsSubscriber.userID,
-                                nexusUserID = it.id.value
-                            )
-                        )
-                    }
-                }
-                call.respond(ret)
-                return@get
-            }
-
-            /**
-             * Get all the details associated with a NEXUS user.
-             */
-            get("/ebics/subscribers/{id}") {
-                val nexusUserId = expectId(call.parameters["id"])
-                val response = transaction {
-                    val nexusUser = expectNexusIdTransaction(nexusUserId)
-                    EbicsSubscriberInfoResponseJson(
-                        nexusUserID = nexusUser.id.value,
-                        hostID = nexusUser.ebicsSubscriber.hostID,
-                        partnerID = nexusUser.ebicsSubscriber.partnerID,
-                        systemID = nexusUser.ebicsSubscriber.systemID,
-                        ebicsURL = nexusUser.ebicsSubscriber.ebicsURL,
-                        userID = nexusUser.ebicsSubscriber.userID
-                    )
-                }
-                call.respond(HttpStatusCode.OK, response)
-                return@get
-            }
 
-            get("/ebics/{id}/sendHev") {
-                val id = expectId(call.parameters["id"])
-                val subscriberData = getSubscriberDetailsFromNexusUserId(id)
-                val request = makeEbicsHEVRequest(subscriberData)
-                val response = client.postToBank(subscriberData.ebicsUrl, 
request)
-                val versionDetails = parseEbicsHEVResponse(response)
-                call.respond(
-                    HttpStatusCode.OK,
-                    EbicsHevResponseJson(versionDetails.versions.map { 
ebicsVersionSpec ->
-                        ProtocolAndVersionJson(
-                            ebicsVersionSpec.protocol,
-                            ebicsVersionSpec.version
-                        )
-                    })
-                )
-                return@get
-            }
 
-            /**
-             * Make a new NEXUS user in the system.  This user gets (also) a 
new EBICS
-             * user associated.
-             */
-            post("/{id}/subscribers") {
-                val newUserId = call.parameters["id"]
-                val body = call.receive<EbicsSubscriberInfoRequestJson>()
-                val pairA = CryptoUtil.generateRsaKeyPair(2048)
-                val pairB = CryptoUtil.generateRsaKeyPair(2048)
-                val pairC = CryptoUtil.generateRsaKeyPair(2048)
-                transaction {
-                    val newEbicsSubscriber = EbicsSubscriberEntity.new {
-                        ebicsURL = body.ebicsURL
-                        hostID = body.hostID
-                        partnerID = body.partnerID
-                        userID = body.userID
-                        systemID = body.systemID
-                        signaturePrivateKey = SerialBlob(pairA.private.encoded)
-                        encryptionPrivateKey = 
SerialBlob(pairB.private.encoded)
-                        authenticationPrivateKey = 
SerialBlob(pairC.private.encoded)
-                    }
-                    NexusUserEntity.new(id = newUserId) {
-                        ebicsSubscriber = newEbicsSubscriber
-                        password = if (body.password != null) {
-                            
SerialBlob(CryptoUtil.hashStringSHA256(body.password))
-                        } else {
-                            logger.debug("No password set for $newUserId")
-                            null
-                        }
-                    }
-                }
-                call.respondText(
-                    "New NEXUS user registered. ID: $newUserId",
-                    ContentType.Text.Plain,
-                    HttpStatusCode.OK
-                )
-                return@post
-            }
+            /** STATE CHANGES VIA EBICS */
 
-            post("/ebics/subscribers/{id}/sendIni") {
-                val id = expectId(call.parameters["id"])
-                val subscriberData = getSubscriberDetailsFromNexusUserId(id)
-                val iniRequest = makeEbicsIniRequest(subscriberData)
-                val responseStr = client.postToBank(
-                    subscriberData.ebicsUrl,
-                    iniRequest
-                )
-                val resp = 
parseAndDecryptEbicsKeyManagementResponse(subscriberData, responseStr)
-                if (resp.technicalReturnCode != EbicsReturnCode.EBICS_OK) {
-                    throw 
NexusError(HttpStatusCode.InternalServerError,"Unexpected INI response code: 
${resp.technicalReturnCode}")
+            post("/ebics/admin/execute-payments") {
+                val (paymentRowId, painDoc: String, debtorAccount) = 
transaction {
+                    val entity = Pain001Entity.find {
+                        (Pain001Table.submitted eq false) and 
(Pain001Table.invalid eq false)
+                    }.firstOrNull() ?: throw 
NexusError(HttpStatusCode.Accepted, reason = "No ready payments found")
+                    Triple(entity.id, createPain001document(entity), 
entity.debtorAccount)
                 }
-                call.respondText("Bank accepted signature key\n", 
ContentType.Text.Plain, HttpStatusCode.OK)
-                return@post
-            }
-
-            post("/ebics/subscribers/{id}/sendHia") {
-                val id = expectId(call.parameters["id"])
-                val subscriberData = getSubscriberDetailsFromNexusUserId(id)
-                val hiaRequest = makeEbicsHiaRequest(subscriberData)
-                val responseStr = client.postToBank(
-                    subscriberData.ebicsUrl,
-                    hiaRequest
+                logger.debug("Uploading PAIN.001: ${painDoc}")
+                val subscriberDetails = 
getSubscriberDetailsFromBankAccount(debtorAccount)
+                doEbicsUploadTransaction(
+                    client,
+                    subscriberDetails,
+                    "CCT",
+                    painDoc.toByteArray(Charsets.UTF_8),
+                    EbicsStandardOrderParams()
                 )
-                val resp = 
parseAndDecryptEbicsKeyManagementResponse(subscriberData, responseStr)
-                if (resp.technicalReturnCode != EbicsReturnCode.EBICS_OK) {
-                    throw 
NexusError(HttpStatusCode.InternalServerError,"Unexpected HIA response code: 
${resp.technicalReturnCode}")
+                /* flow here == no errors occurred */
+                transaction {
+                    val payment = Pain001Entity.findById(paymentRowId) ?: 
throw NexusError(
+                        HttpStatusCode.InternalServerError,
+                        "Severe internal error: could not find payment in DB 
after having submitted it to the bank"
+                    )
+                    payment.submitted = true
                 }
                 call.respondText(
-                    "Bank accepted authentication and encryption keys\n",
+                    "CCT message submitted to the bank",
                     ContentType.Text.Plain,
                     HttpStatusCode.OK
                 )
                 return@post
             }
-
-            post("/ebics/subscribers/{id}/restoreBackup") {
-                val body = call.receive<EbicsKeysBackupJson>()
-                val nexusId = expectId(call.parameters["id"])
-                val subscriber = transaction {
-                    NexusUserEntity.findById(nexusId)
-                }
-                if (subscriber != null) {
-                    call.respond(
-                        HttpStatusCode.Conflict,
-                        NexusErrorJson("ID exists, please choose a new one")
-                    )
-                    return@post
-                }
-                val (authKey, encKey, sigKey) = try {
-                    Triple(
-                        CryptoUtil.decryptKey(
-                            
EncryptedPrivateKeyInfo(base64ToBytes(body.authBlob)), body.passphrase!!
-                        ),
-                        CryptoUtil.decryptKey(
-                            
EncryptedPrivateKeyInfo(base64ToBytes(body.encBlob)), body.passphrase
-                        ),
-                        CryptoUtil.decryptKey(
-                            
EncryptedPrivateKeyInfo(base64ToBytes(body.sigBlob)), body.passphrase
-                        )
-                    )
-                } catch (e: Exception) {
-                    e.printStackTrace()
-                    logger.info("Restoring keys failed, probably due to wrong 
passphrase")
-                    throw NexusError(HttpStatusCode.BadRequest, reason = "Bad 
backup given")
+            post("/ebics/admin/execute-payments-ccc") {
+                val (paymentRowId, painDoc: String, debtorAccount) = 
transaction {
+                    val entity = Pain001Entity.find {
+                        (Pain001Table.submitted eq false) and 
(Pain001Table.invalid eq false)
+                    }.firstOrNull() ?: throw 
NexusError(HttpStatusCode.Accepted, reason = "No ready payments found")
+                    Triple(entity.id, createPain001document(entity), 
entity.debtorAccount)
                 }
-                logger.info("Restoring keys, creating new user: $nexusId")
-                try {
-                    transaction {
-                        val newNexusUser = NexusUserEntity.new(id = nexusId) {
-                            ebicsSubscriber = EbicsSubscriberEntity.new {
-                                ebicsURL = body.ebicsURL
-                                hostID = body.hostID
-                                partnerID = body.partnerID
-                                userID = body.userID
-                                signaturePrivateKey = 
SerialBlob(sigKey.encoded)
-                                encryptionPrivateKey = 
SerialBlob(encKey.encoded)
-                                authenticationPrivateKey = 
SerialBlob(authKey.encoded)
-                            }
-                        }
-                    }
-                } catch (e: Exception) {
-                    print(e)
-                    call.respond(NexusErrorJson("Could not store the new 
account into database"))
-                    return@post
+                logger.debug("Uploading PAIN.001 via CCC: ${painDoc}")
+                val subscriberDetails = 
getSubscriberDetailsFromBankAccount(debtorAccount)
+                doEbicsUploadTransaction(
+                    client,
+                    subscriberDetails,
+                    "CCC",
+                    painDoc.toByteArray(Charsets.UTF_8).zip(),
+                    EbicsStandardOrderParams()
+                )
+                /* flow here == no errors occurred */
+                transaction {
+                    val payment = Pain001Entity.findById(paymentRowId) ?: 
throw NexusError(
+                        HttpStatusCode.InternalServerError,
+                        "Severe internal error: could not find payment in DB 
after having submitted it to the bank"
+                    )
+                    payment.submitted = true
                 }
                 call.respondText(
-                    "Keys successfully restored",
+                    "CCC message submitted to the bank",
                     ContentType.Text.Plain,
                     HttpStatusCode.OK
                 )
                 return@post
             }
+            post("/ebics/subscribers/{id}/collect-transactions-c52") {
+                // FIXME(florian): Download C52 and store the result in the 
right database table
 
-            get("/ebics/subscribers/{id}/pubkeys") {
-                val nexusId = expectId(call.parameters["id"])
+            }
+            /** exports keys backup copy */
+            post("/ebics/subscribers/{id}/backup") {
+                val body = call.receive<EbicsBackupRequestJson>()
                 val response = transaction {
-                    val nexusUser = expectNexusIdTransaction(nexusId)
-                    val subscriber = nexusUser.ebicsSubscriber
-                    val authPriv = 
CryptoUtil.loadRsaPrivateKey(subscriber.authenticationPrivateKey.toByteArray())
-                    val authPub = CryptoUtil.getRsaPublicFromPrivate(authPriv)
-                    val encPriv = 
CryptoUtil.loadRsaPrivateKey(subscriber.encryptionPrivateKey.toByteArray())
-                    val encPub = CryptoUtil.getRsaPublicFromPrivate(encPriv)
-                    val sigPriv = 
CryptoUtil.loadRsaPrivateKey(subscriber.signaturePrivateKey.toByteArray())
-                    val sigPub = CryptoUtil.getRsaPublicFromPrivate(sigPriv)
-                    EbicsPubKeyInfo(
-                        bytesToBase64(authPub.encoded),
-                        bytesToBase64(encPub.encoded),
-                        bytesToBase64(sigPub.encoded)
+                    val nexusUser = 
expectNexusIdTransaction(call.parameters["id"])
+                    val subscriber = getEbicsSubscriberFromUser(nexusUser)
+                    EbicsKeysBackupJson(
+                        userID = subscriber.userID,
+                        hostID = subscriber.hostID,
+                        partnerID = subscriber.partnerID,
+                        ebicsURL = subscriber.ebicsURL,
+                        authBlob = bytesToBase64(
+                            CryptoUtil.encryptKey(
+                                
subscriber.authenticationPrivateKey.toByteArray(),
+                                body.passphrase
+                            )
+                        ),
+                        encBlob = bytesToBase64(
+                            CryptoUtil.encryptKey(
+                                subscriber.encryptionPrivateKey.toByteArray(),
+                                body.passphrase
+                            )
+                        ),
+                        sigBlob = bytesToBase64(
+                            CryptoUtil.encryptKey(
+                                subscriber.signaturePrivateKey.toByteArray(),
+                                body.passphrase
+                            )
+                        )
                     )
                 }
+                call.response.headers.append("Content-Disposition", 
"attachment")
                 call.respond(
                     HttpStatusCode.OK,
                     response
                 )
             }
+
+            /** Download keys from bank */
+            post("/ebics/subscribers/{id}/sync") {
+                val nexusUser = expectNexusIdTransaction(call.parameters["id"])
+                val subscriberDetails = 
getSubscriberDetailsFromNexusUserId(nexusUser.id.value)
+                val hpbRequest = makeEbicsHpbRequest(subscriberDetails)
+                val responseStr = 
client.postToBank(subscriberDetails.ebicsUrl, hpbRequest)
+                val response = 
parseAndDecryptEbicsKeyManagementResponse(subscriberDetails, responseStr)
+                val orderData = response.orderData ?: throw NexusError(
+                    HttpStatusCode.InternalServerError,
+                    "HPB response has no order data"
+                )
+                val hpbData = parseEbicsHpbOrder(orderData)
+                // put bank's keys into database.
+                transaction {
+                    val ebicsSubscriber = getEbicsSubscriberFromUser(nexusUser)
+                    ebicsSubscriber.bankAuthenticationPublicKey = 
SerialBlob(hpbData.authenticationPubKey.encoded)
+                    ebicsSubscriber.bankEncryptionPublicKey = 
SerialBlob(hpbData.encryptionPubKey.encoded)
+                }
+                call.respondText("Bank keys stored in database\n", 
ContentType.Text.Plain, HttpStatusCode.OK)
+                return@post
+            }
+            post("/ebics/subscribers/{id}/fetch-payment-status") {
+                val id = expectId(call.parameters["id"])
+                val paramsJson = call.receive<EbicsStandardOrderParamsJson>()
+                val orderParams = paramsJson.toOrderParams()
+                val subscriberData = getSubscriberDetailsFromNexusUserId(id)
+                val response = doEbicsDownloadTransaction(
+                    client,
+                    subscriberData,
+                    "CRZ",
+                    orderParams
+                )
+                when (response) {
+                    is EbicsDownloadSuccessResult ->
+                        call.respondText(
+                            response.orderData.toString(Charsets.UTF_8),
+                            ContentType.Text.Plain,
+                            HttpStatusCode.OK
+                        )
+                    /**
+                     * NOTE: flow gets here when the bank-technical return 
code is
+                     * different from 000000.  This happens also for 090005 
(no data available)
+                     */
+                    else -> call.respond(NexusErrorJson("Could not download 
any PAIN.002"))
+                }
+                return@post
+            }
+            post("/ebics/subscribers/{id}/collect-transactions-c53") {
+                val id = expectId(call.parameters["id"])
+                val paramsJson = call.receive<EbicsStandardOrderParamsJson>()
+                val orderParams = paramsJson.toOrderParams()
+                val subscriberData = getSubscriberDetailsFromNexusUserId(id)
+                when (val response = doEbicsDownloadTransaction(client, 
subscriberData, "C53", orderParams)) {
+                    is EbicsDownloadSuccessResult -> {
+                        /**
+                         * The current code is _heavily_ dependent on the way 
GLS returns
+                         * data.  For example, GLS makes one ZIP entry for 
each "Ntry" element
+                         * (a bank transfer), but per the specifications one 
bank can choose to
+                         * return all the "Ntry" elements into one single ZIP 
entry, or even unzipped
+                         * at all.
+                         */
+                        response.orderData.unzipWithLoop {
+                            val fileName = it.first
+                            val camt53doc = 
XMLUtil.parseStringIntoDom(it.second)
+                            transaction {
+                                RawBankTransactionEntity.new {
+                                    sourceFileName = fileName
+                                    unstructuredRemittanceInformation = 
camt53doc.pickString("//*[local-name()='Ntry']//*[local-name()='Amt']/@Ccy")
+                                    transactionType = 
camt53doc.pickString("//*[local-name()='Ntry']//*[local-name()='CdtDbtInd']")
+                                    currency = 
camt53doc.pickString("//*[local-name()='Ntry']//*[local-name()='Amt']/@Ccy")
+                                    amount = 
camt53doc.pickString("//*[local-name()='Ntry']//*[local-name()='Amt']")
+                                    status = 
camt53doc.pickString("//*[local-name()='Ntry']//*[local-name()='Sts']")
+                                    bookingDate = 
parseDate(camt53doc.pickString("//*[local-name()='BookgDt']//*[local-name()='Dt']")).millis
+                                    nexusSubscriber = 
getSubscriberEntityFromNexusUserId(id)
+                                    creditorName = 
camt53doc.pickString("//*[local-name()='RltdPties']//*[local-name()='Dbtr']//*[local-name()='Nm']")
+                                    creditorIban = 
camt53doc.pickString("//*[local-name()='CdtrAcct']//*[local-name()='IBAN']")
+                                    debitorName = 
camt53doc.pickString("//*[local-name()='RltdPties']//*[local-name()='Dbtr']//*[local-name()='Nm']")
+                                    debitorIban = 
camt53doc.pickString("//*[local-name()='DbtrAcct']//*[local-name()='IBAN']")
+                                    counterpartBic = 
camt53doc.pickString("//*[local-name()='RltdAgts']//*[local-name()='BIC']")
+                                }
+                            }
+                        }
+                        call.respondText(
+                            "C53 data persisted into the database (WIP).",
+                            ContentType.Text.Plain,
+                            HttpStatusCode.OK
+                        )
+                    }
+                    is EbicsDownloadBankErrorResult -> {
+                        call.respond(
+                            HttpStatusCode.BadGateway,
+                            EbicsErrorJson(EbicsErrorDetailJson("bankError", 
response.returnCode.errorCode))
+                        )
+                    }
+                }
+                return@post
+            }
+            post("/ebics/subscribers/{id}/collect-transactions-c54") {
+                // FIXME(florian): Download C54 and store the result in the 
right database table
+            }
             /**
              * This endpoint downloads bank account information associated 
with the
              * calling EBICS subscriber.
              */
             post("/ebics/subscribers/{id}/fetch-accounts") {
-                val nexusUserId = expectId(call.parameters["id"])
+                val nexusUser = 
expectNexusIdTransaction((call.parameters["id"]))
                 val paramsJson = call.receive<EbicsStandardOrderParamsJson>()
                 val orderParams = paramsJson.toOrderParams()
-                val subscriberData = 
getSubscriberDetailsFromNexusUserId(nexusUserId)
+                val subscriberData = 
getSubscriberDetailsFromNexusUserId(nexusUser.id.value)
                 val response = doEbicsDownloadTransaction(client, 
subscriberData, "HTD", orderParams)
                 when (response) {
                     is EbicsDownloadSuccessResult -> {
@@ -1342,9 +746,8 @@ fun main() {
                                     iban = 
extractFirstIban(it.accountNumberList) ?: throw 
NexusError(HttpStatusCode.NotFound, reason = "bank gave no IBAN")
                                     bankCode = 
extractFirstBic(it.bankCodeList) ?: throw NexusError(HttpStatusCode.NotFound, 
reason = "bank gave no BIC")
                                 }
-                                val nexusUser = 
expectNexusIdTransaction(nexusUserId)
                                 EbicsToBankAccountEntity.new {
-                                    ebicsSubscriber = nexusUser.ebicsSubscriber
+                                    ebicsSubscriber = 
getEbicsSubscriberFromUser(nexusUser)
                                     this.bankAccount = bankAccount
                                 }
                             }
@@ -1366,89 +769,98 @@ fun main() {
                 return@post
             }
 
-            /* performs a keys backup */
-            post("/ebics/subscribers/{id}/backup") {
-                val nexusId = expectId(call.parameters["id"])
-                val body = call.receive<EbicsBackupRequestJson>()
-                val response = transaction {
-                    val nexusUser = expectNexusIdTransaction(nexusId)
-                    val subscriber = nexusUser.ebicsSubscriber
-                    EbicsKeysBackupJson(
-                        userID = subscriber.userID,
-                        hostID = subscriber.hostID,
-                        partnerID = subscriber.partnerID,
-                        ebicsURL = subscriber.ebicsURL,
-                        authBlob = bytesToBase64(
-                            CryptoUtil.encryptKey(
-                                
subscriber.authenticationPrivateKey.toByteArray(),
-                                body.passphrase
-                            )
-                        ),
-                        encBlob = bytesToBase64(
-                            CryptoUtil.encryptKey(
-                                subscriber.encryptionPrivateKey.toByteArray(),
-                                body.passphrase
-                            )
-                        ),
-                        sigBlob = bytesToBase64(
-                            CryptoUtil.encryptKey(
-                                subscriber.signaturePrivateKey.toByteArray(),
-                                body.passphrase
-                            )
+            /** EBICS MESSAGES / DEBUG */
+
+            // FIXME: some messages include a ZIPped payload.
+            post("/ebics/subscribers/{id}/send{MSG}") {
+                val id = expectId(call.parameters["id"])
+                val MSG = expectId(call.parameters["MSG"])
+                val paramsJson = call.receive<EbicsStandardOrderParamsJson>()
+                val orderParams = paramsJson.toOrderParams()
+                println("$MSG order params: $orderParams")
+                val subscriberData = getSubscriberDetailsFromNexusUserId(id)
+                val response = doEbicsDownloadTransaction(
+                    client,
+                    subscriberData,
+                    MSG,
+                    orderParams
+                )
+                when (response) {
+                    is EbicsDownloadSuccessResult -> {
+                        call.respondText(
+                            response.orderData.toString(Charsets.UTF_8),
+                            ContentType.Text.Plain,
+                            HttpStatusCode.OK
                         )
-                    )
+                    }
+                    is EbicsDownloadBankErrorResult -> {
+                        call.respond(
+                            HttpStatusCode.BadGateway,
+                            EbicsErrorJson(EbicsErrorDetailJson("bankError", 
response.returnCode.errorCode))
+                        )
+                    }
                 }
-                call.response.headers.append("Content-Disposition", 
"attachment")
+                return@post
+            }
+            get("/ebics/{id}/sendHEV") {
+                val id = expectId(call.parameters["id"])
+                val subscriberData = getSubscriberDetailsFromNexusUserId(id)
+                val request = makeEbicsHEVRequest(subscriberData)
+                val response = client.postToBank(subscriberData.ebicsUrl, 
request)
+                val versionDetails = parseEbicsHEVResponse(response)
                 call.respond(
                     HttpStatusCode.OK,
-                    response
+                    EbicsHevResponseJson(versionDetails.versions.map { 
ebicsVersionSpec ->
+                        ProtocolAndVersionJson(
+                            ebicsVersionSpec.protocol,
+                            ebicsVersionSpec.version
+                        )
+                    })
+                )
+                return@get
+            }
+            post("/ebics/subscribers/{id}/sendINI") {
+                val id = expectId(call.parameters["id"])
+                val subscriberData = getSubscriberDetailsFromNexusUserId(id)
+                val iniRequest = makeEbicsIniRequest(subscriberData)
+                val responseStr = client.postToBank(
+                    subscriberData.ebicsUrl,
+                    iniRequest
                 )
+                val resp = 
parseAndDecryptEbicsKeyManagementResponse(subscriberData, responseStr)
+                if (resp.technicalReturnCode != EbicsReturnCode.EBICS_OK) {
+                    throw 
NexusError(HttpStatusCode.InternalServerError,"Unexpected INI response code: 
${resp.technicalReturnCode}")
+                }
+                call.respondText("Bank accepted signature key\n", 
ContentType.Text.Plain, HttpStatusCode.OK)
+                return@post
             }
 
-            post("/ebics/subscribers/{id}/sendTSU") {
+            post("/ebics/subscribers/{id}/sendHIA") {
                 val id = expectId(call.parameters["id"])
                 val subscriberData = getSubscriberDetailsFromNexusUserId(id)
-                val payload = "PAYLOAD"
-                doEbicsUploadTransaction(
-                    client,
-                    subscriberData,
-                    "TSU",
-                    payload.toByteArray(Charsets.UTF_8),
-                    EbicsGenericOrderParams()
+                val hiaRequest = makeEbicsHiaRequest(subscriberData)
+                val responseStr = client.postToBank(
+                    subscriberData.ebicsUrl,
+                    hiaRequest
                 )
+                val resp = 
parseAndDecryptEbicsKeyManagementResponse(subscriberData, responseStr)
+                if (resp.technicalReturnCode != EbicsReturnCode.EBICS_OK) {
+                    throw 
NexusError(HttpStatusCode.InternalServerError,"Unexpected HIA response code: 
${resp.technicalReturnCode}")
+                }
                 call.respondText(
-                    "TST INITIALIZATION & TRANSACTION phases succeeded\n",
+                    "Bank accepted authentication and encryption keys\n",
                     ContentType.Text.Plain,
                     HttpStatusCode.OK
                 )
-            }
-
-            post("/ebics/subscribers/{id}/sync") {
-                val nexusId = expectId(call.parameters["id"])
-                val subscriberDetails = 
getSubscriberDetailsFromNexusUserId(nexusId)
-                val hpbRequest = makeEbicsHpbRequest(subscriberDetails)
-                val responseStr = 
client.postToBank(subscriberDetails.ebicsUrl, hpbRequest)
-
-                val response = 
parseAndDecryptEbicsKeyManagementResponse(subscriberDetails, responseStr)
-                val orderData =
-                    response.orderData ?: throw 
NexusError(HttpStatusCode.InternalServerError, "HPB response has no order data")
-                val hpbData = parseEbicsHpbOrder(orderData)
-
-                // put bank's keys into database.
-                transaction {
-                    val nexusUser = expectNexusIdTransaction(nexusId)
-                    nexusUser.ebicsSubscriber.bankAuthenticationPublicKey = 
SerialBlob(hpbData.authenticationPubKey.encoded)
-                    nexusUser.ebicsSubscriber.bankEncryptionPublicKey = 
SerialBlob(hpbData.encryptionPubKey.encoded)
-                }
-                call.respondText("Bank keys stored in database\n", 
ContentType.Text.Plain, HttpStatusCode.OK)
-                return@post
-            }
-            post("/test/intercept") {
-                call.respondText(call.receive<String>() + "\n")
                 return@post
             }
+
+            /** PLUGINS */
+            /* Taler class will initialize all the relevant handlers.  */
+            Taler(this)
         }
     }
+
     logger.info("Up and running")
     server.start(wait = true)
 }
\ 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 7e6c7d9..b942ac8 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/taler.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/taler.kt
@@ -284,7 +284,7 @@ class Taler(app: Route) {
                         creditorIban = creditorObj.iban
                         counterpartBic = creditorObj.bic
                         bookingDate = DateTime.now().millis
-                        nexusSubscriber = nexusUser.ebicsSubscriber
+                        nexusSubscriber = getEbicsSubscriberFromUser(nexusUser)
                         status = "BOOK"
                     }
                 } else null
@@ -370,8 +370,7 @@ class Taler(app: Route) {
          * all the prepared payments.  */
         
app.post("/ebics/taler/{id}/accounts/{acctid}/refund-invalid-payments") {
             transaction {
-                val nexusUser = expectNexusIdTransaction(call.parameters["id"])
-                val subscriber = nexusUser.ebicsSubscriber
+                val subscriber = 
getSubscriberEntityFromNexusUserId(call.parameters["id"])
                 val acctid = expectAcctidTransaction(call.parameters["acctid"])
                 if (!subscriberHasRights(subscriber, acctid)) {
                     throw NexusError(
@@ -549,7 +548,7 @@ class Taler(app: Route) {
                         val nexusUser = expectNexusIdTransaction(exchangeId)
                         EbicsToBankAccountEntity.new {
                             bankAccount = newBankAccount
-                            ebicsSubscriber = nexusUser.ebicsSubscriber
+                            ebicsSubscriber = 
getEbicsSubscriberFromUser(nexusUser)
                         }
                     }
                 }
diff --git a/nexus/src/test/kotlin/authentication.kt 
b/nexus/src/test/kotlin/authentication.kt
index 2e9c5be..7cbb36d 100644
--- a/nexus/src/test/kotlin/authentication.kt
+++ b/nexus/src/test/kotlin/authentication.kt
@@ -17,21 +17,11 @@ class AuthenticationTest {
             SchemaUtils.create(NexusUsersTable)
             NexusUserEntity.new(id = "username") {
                 password = SerialBlob(CryptoUtil.hashStringSHA256("password"))
-                ebicsSubscriber = EbicsSubscriberEntity.new {
-                    ebicsURL = "ebics url"
-                    hostID = "host"
-                    partnerID = "partner"
-                    userID = "user"
-                    systemID = "system"
-                    signaturePrivateKey = 
SerialBlob("signturePrivateKey".toByteArray())
-                    authenticationPrivateKey = 
SerialBlob("authenticationPrivateKey".toByteArray())
-                    encryptionPrivateKey = 
SerialBlob("encryptionPrivateKey".toByteArray())
-                }
             }
             // base64 of "username:password" == "dXNlcm5hbWU6cGFzc3dvcmQ="
-            val (username: String, hashedPass: ByteArray) = 
extractUserAndHashedPassword(
+            val hashedPass= extractUserAndHashedPassword(
                 "Basic dXNlcm5hbWU6cGFzc3dvcmQ="
-            )
+            ).second
             val row = NexusUserEntity.findById("username")
             assert(row?.password == SerialBlob(hashedPass))
         }
@@ -39,7 +29,9 @@ class AuthenticationTest {
 
     @Test
     fun basicAuthHeaderTest() {
-        val (username: String, hashedPass: ByteArray) = 
extractUserAndHashedPassword("Basic dXNlcm5hbWU6cGFzc3dvcmQ=")
+        val hashedPass = extractUserAndHashedPassword(
+            "Basic dXNlcm5hbWU6cGFzc3dvcmQ="
+        ).second
         
assert(CryptoUtil.hashStringSHA256("password").contentEquals(hashedPass))
     }
 }
\ No newline at end of file

-- 
To stop receiving notification emails like this one, please contact
address@hidden.



reply via email to

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