gnunet-svn
[Top][All Lists]
Advanced

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

[libeufin] branch master updated: move towards new API


From: gnunet
Subject: [libeufin] branch master updated: move towards new API
Date: Wed, 20 May 2020 23:21:39 +0200

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

dold pushed a commit to branch master
in repository libeufin.

The following commit(s) were added to refs/heads/master by this push:
     new b06990b  move towards new API
b06990b is described below

commit b06990b56aceda9bfe5769e198a2f8ba7f467fee
Author: Florian Dold <address@hidden>
AuthorDate: Thu May 21 02:51:19 2020 +0530

    move towards new API
---
 integration-tests/test-ebics.py                    |   47 +-
 nexus/src/main/kotlin/tech/libeufin/nexus/DB.kt    |   46 +-
 .../main/kotlin/tech/libeufin/nexus/EbicsClient.kt |   80 +-
 .../src/main/kotlin/tech/libeufin/nexus/Helpers.kt |  151 +--
 nexus/src/main/kotlin/tech/libeufin/nexus/JSON.kt  |   39 +-
 nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt  |  721 +++++++------
 nexus/src/main/kotlin/tech/libeufin/nexus/taler.kt | 1112 ++++++++++----------
 nexus/src/test/kotlin/authentication.kt            |    7 +-
 nexus/src/test/kotlin/taler.kt                     |   44 +-
 util/src/main/kotlin/Ebics.kt                      |    8 +
 10 files changed, 1133 insertions(+), 1122 deletions(-)

diff --git a/integration-tests/test-ebics.py b/integration-tests/test-ebics.py
index ac633aa..0837813 100755
--- a/integration-tests/test-ebics.py
+++ b/integration-tests/test-ebics.py
@@ -19,7 +19,7 @@ import base64
 #  -> (a) Make a Nexus user, (b) make a EBICS subscriber
 #     associated to that user
 #
-# 2 Prepare the Ebics transport for the nexus user.
+# 2 Prepare the Ebics bank connection for the nexus user.
 #  -> (a) Upload keys from Nexus to the Bank (INI & HIA),
 #     (b) Download key from the Bank (HPB) to the Nexus,
 #     and (c) Fetch the bank account owned by that subscriber
@@ -178,12 +178,15 @@ assertResponse(
     )
 )
 
-# 1.b, make a ebics transport for the new user.
+print("creating bank connection")
+
+# 1.b, make a ebics bank connection for the new user.
 assertResponse(
     post(
-        "http://localhost:5001/bank-transports";,
+        "http://localhost:5001/bank-connections";,
         json=dict(
-            transport=dict(name="my-ebics", type="ebics"),
+            name="my-ebics",
+            type="ebics",
             data=dict(
                 ebicsURL=EBICS_URL, hostID=HOST_ID, partnerID=PARTNER_ID, 
userID=USER_ID
             ),
@@ -192,19 +195,21 @@ assertResponse(
     )
 )
 
+print("sending ini")
+
 # 2.a, upload keys to the bank (INI & HIA)
 assertResponse(
     post(
-        "http://localhost:5001/bank-transports/sendINI";,
-        json=dict(type="ebics", name="my-ebics"),
+        "http://localhost:5001/bank-connections/my-ebics/ebics/send-ini";,
+        json=dict(),
         headers=dict(Authorization=USER_AUTHORIZATION_HEADER),
     )
 )
 
 assertResponse(
     post(
-        "http://localhost:5001/bank-transports/sendHIA";,
-        json=dict(type="ebics", name="my-ebics"),
+        "http://localhost:5001/bank-connections/my-ebics/ebics/send-hia";,
+        json=dict(),
         headers=dict(Authorization=USER_AUTHORIZATION_HEADER),
     )
 )
@@ -212,8 +217,8 @@ assertResponse(
 # 2.b, download keys from the bank (HPB)
 assertResponse(
     post(
-        "http://localhost:5001/bank-transports/syncHPB";,
-        json=dict(type="ebics", name="my-ebics"),
+        "http://localhost:5001/bank-connections/my-ebics/ebics/send-hpb";,
+        json=dict(),
         headers=dict(Authorization=USER_AUTHORIZATION_HEADER),
     )
 )
@@ -221,8 +226,8 @@ assertResponse(
 # 2.c, fetch bank account information
 assertResponse(
     post(
-        "http://localhost:5001/bank-transports/syncHTD";,
-        json=dict(type="ebics", name="my-ebics"),
+        
"http://localhost:5001/bank-connections/my-ebics/ebics/import-accounts";,
+        json=dict(),
         headers=dict(Authorization=USER_AUTHORIZATION_HEADER),
     )
 )
@@ -230,8 +235,8 @@ assertResponse(
 # 3, ask nexus to download history
 assertResponse(
     post(
-        "http://localhost:5001/bank-accounts/collected-transactions";,
-        json=dict(transport=dict(type="ebics", name="my-ebics")),
+        
f"http://localhost:5001/bank-accounts/{BANK_ACCOUNT_LABEL}/fetch-transactions";,
+        json=dict(),
         headers=dict(Authorization=USER_AUTHORIZATION_HEADER),
     )
 )
@@ -239,9 +244,7 @@ assertResponse(
 # 4, make sure history is empty
 resp = assertResponse(
     get(
-        "http://localhost:5001/bank-accounts/{}/collected-transactions".format(
-            BANK_ACCOUNT_LABEL
-        ),
+        
f"http://localhost:5001/bank-accounts/{BANK_ACCOUNT_LABEL}/transactions";,
         headers=dict(Authorization=USER_AUTHORIZATION_HEADER),
     )
 )
@@ -271,8 +274,8 @@ if PREPARED_PAYMENT_UUID == None:
 # 5.b, submit prepared statement
 assertResponse(
     post(
-        "http://localhost:5001/bank-accounts/prepared-payments/submit";,
-        json=dict(uuid=PREPARED_PAYMENT_UUID),
+        
f"http://localhost:5001/bank-accounts/{BANK_ACCOUNT_LABEL}/prepared-payments/{PREPARED_PAYMENT_UUID}/submit";,
+        json=dict(),
         headers=dict(Authorization=USER_AUTHORIZATION_HEADER),
     )
 )
@@ -280,7 +283,7 @@ assertResponse(
 # 6, request history after payment submission
 assertResponse(
     post(
-        "http://localhost:5001/bank-accounts/collected-transactions";,
+        
f"http://localhost:5001/bank-accounts/{BANK_ACCOUNT_LABEL}/fetch-transactions";,
         json=dict(),
         headers=dict(Authorization=USER_AUTHORIZATION_HEADER),
     )
@@ -288,9 +291,7 @@ assertResponse(
 
 resp = assertResponse(
     get(
-        "http://localhost:5001/bank-accounts/{}/collected-transactions".format(
-            BANK_ACCOUNT_LABEL
-        ),
+        
f"http://localhost:5001/bank-accounts/{BANK_ACCOUNT_LABEL}/transactions";,
         headers=dict(Authorization=USER_AUTHORIZATION_HEADER),
     )
 )
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/DB.kt 
b/nexus/src/main/kotlin/tech/libeufin/nexus/DB.kt
index d44a514..e0d026a 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/DB.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/DB.kt
@@ -8,10 +8,6 @@ import org.jetbrains.exposed.sql.StdOutSqlLogger
 import org.jetbrains.exposed.sql.addLogger
 import org.jetbrains.exposed.sql.transactions.TransactionManager
 import org.jetbrains.exposed.sql.transactions.transaction
-import tech.libeufin.nexus.EbicsSubscribersTable.entityId
-import tech.libeufin.nexus.EbicsSubscribersTable.primaryKey
-import tech.libeufin.nexus.NexusUsersTable.entityId
-import tech.libeufin.nexus.NexusUsersTable.primaryKey
 import tech.libeufin.util.amount
 import java.sql.Connection
 
@@ -152,9 +148,6 @@ object PreparedPaymentsTable : IdTable<String>() {
      * this state can be reached when the payment gets listed in a CRZ
      * response OR when the payment doesn't show up in a C52/C53 response */
     val invalid = bool("invalid").default(false)
-
-    /** never really used, but it makes sure the user always exists  */
-    val nexusUser = reference("nexusUser", NexusUsersTable)
 }
 
 class PreparedPaymentEntity(id: EntityID<String>) : Entity<String>(id) {
@@ -175,7 +168,6 @@ class PreparedPaymentEntity(id: EntityID<String>) : 
Entity<String>(id) {
     var creditorName by PreparedPaymentsTable.creditorName
     var submitted by PreparedPaymentsTable.submitted
     var invalid by PreparedPaymentsTable.invalid
-    var nexusUser by NexusUserEntity referencedOn 
PreparedPaymentsTable.nexusUser
 }
 
 /**
@@ -186,6 +178,7 @@ object BankAccountsTable : IdTable<String>() {
     val accountHolder = text("accountHolder")
     val iban = text("iban")
     val bankCode = text("bankCode")
+    val defaultBankConnection = reference("defaultBankConnection", 
NexusBankConnectionsTable).nullable()
 }
 
 class BankAccountEntity(id: EntityID<String>) : Entity<String>(id) {
@@ -194,10 +187,10 @@ class BankAccountEntity(id: EntityID<String>) : 
Entity<String>(id) {
     var accountHolder by BankAccountsTable.accountHolder
     var iban by BankAccountsTable.iban
     var bankCode by BankAccountsTable.bankCode
+    var defaultBankConnection by NexusBankConnectionEntity 
optionalReferencedOn BankAccountsTable.defaultBankConnection
 }
 
-object EbicsSubscribersTable : IdTable<String>() {
-    override val id = varchar("id", ID_MAX_LENGTH).entityId().primaryKey()
+object EbicsSubscribersTable : IntIdTable() {
     val ebicsURL = text("ebicsURL")
     val hostID = text("hostID")
     val partnerID = text("partnerID")
@@ -208,11 +201,11 @@ object EbicsSubscribersTable : IdTable<String>() {
     val authenticationPrivateKey = blob("authenticationPrivateKey")
     val bankEncryptionPublicKey = blob("bankEncryptionPublicKey").nullable()
     val bankAuthenticationPublicKey = 
blob("bankAuthenticationPublicKey").nullable()
-    var nexusUser = reference("nexusUser", NexusUsersTable)
+    val nexusBankConnection = reference("nexusBankConnection", 
NexusBankConnectionsTable)
 }
 
-class EbicsSubscriberEntity(id: EntityID<String>) : Entity<String>(id) {
-    companion object : EntityClass<String, 
EbicsSubscriberEntity>(EbicsSubscribersTable)
+class EbicsSubscriberEntity(id: EntityID<Int>) : IntEntity(id) {
+    companion object : 
IntEntityClass<EbicsSubscriberEntity>(EbicsSubscribersTable)
 
     var ebicsURL by EbicsSubscribersTable.ebicsURL
     var hostID by EbicsSubscribersTable.hostID
@@ -224,7 +217,7 @@ class EbicsSubscriberEntity(id: EntityID<String>) : 
Entity<String>(id) {
     var authenticationPrivateKey by 
EbicsSubscribersTable.authenticationPrivateKey
     var bankEncryptionPublicKey by 
EbicsSubscribersTable.bankEncryptionPublicKey
     var bankAuthenticationPublicKey by 
EbicsSubscribersTable.bankAuthenticationPublicKey
-    var nexusUser by NexusUserEntity referencedOn 
EbicsSubscribersTable.nexusUser
+    var nexusBankConnection by NexusBankConnectionEntity referencedOn  
EbicsSubscribersTable.nexusBankConnection
 }
 
 object NexusUsersTable : IdTable<String>() {
@@ -240,23 +233,16 @@ class NexusUserEntity(id: EntityID<String>) : 
Entity<String>(id) {
     var superuser by NexusUsersTable.superuser
 }
 
-object BankAccountMapsTable : IntIdTable() {
-    val ebicsSubscriber = reference("ebicsSubscriber", EbicsSubscribersTable)
-    val bankAccount = reference("bankAccount", BankAccountsTable)
-    val nexusUser = reference("nexusUser", NexusUsersTable)
-}
-
-class BankAccountMapEntity(id: EntityID<Int>) : IntEntity(id) {
-    companion object : 
IntEntityClass<BankAccountMapEntity>(BankAccountMapsTable)
-
-    var ebicsSubscriber by EbicsSubscriberEntity referencedOn 
BankAccountMapsTable.ebicsSubscriber
-    var bankAccount by BankAccountEntity referencedOn 
BankAccountMapsTable.bankAccount
-    var nexusUser by NexusUserEntity referencedOn 
BankAccountMapsTable.nexusUser
+object NexusBankConnectionsTable : IdTable<String>() {
+    override val id = 
NexusBankConnectionsTable.text("id").entityId().primaryKey()
+    val type = text("type")
+    val owner = reference("user", NexusUsersTable)
 }
 
-object NexusBankConnectionsTable : IdTable<String>() {
-    override val id = EbicsSubscribersTable.text("id").entityId().primaryKey()
-    
+class NexusBankConnectionEntity(id: EntityID<String>) : Entity<String>(id) {
+    companion object : EntityClass<String, 
NexusBankConnectionEntity>(NexusBankConnectionsTable)
+    var type by NexusBankConnectionsTable.type
+    var owner by NexusUserEntity referencedOn NexusBankConnectionsTable.owner
 }
 
 fun dbCreateTables() {
@@ -272,7 +258,7 @@ fun dbCreateTables() {
             RawBankTransactionsTable,
             TalerIncomingPayments,
             TalerRequestedPayments,
-            BankAccountMapsTable
+            NexusBankConnectionsTable
         )
     }
 }
\ No newline at end of file
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsClient.kt 
b/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsClient.kt
index 055c67c..2e28737 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsClient.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsClient.kt
@@ -1,3 +1,6 @@
+/**
+ * High-level interface for the EBICS protocol.
+ */
 package tech.libeufin.nexus
 
 import io.ktor.client.HttpClient
@@ -6,7 +9,7 @@ import io.ktor.http.HttpStatusCode
 import tech.libeufin.util.*
 import java.util.*
 
-suspend inline fun HttpClient.postToBank(url: String, body: String): String {
+private suspend inline fun HttpClient.postToBank(url: String, body: String): 
String {
     logger.debug("Posting: $body")
     val response: String = try {
         this.post<String>(
@@ -58,7 +61,10 @@ suspend fun doEbicsDownloadTransaction(
             // Success, nothing to do!
         }
         else -> {
-            throw NexusError(HttpStatusCode.InternalServerError, "unexpected 
return code ${initResponse.technicalReturnCode}")
+            throw NexusError(
+                HttpStatusCode.InternalServerError,
+                "unexpected return code ${initResponse.technicalReturnCode}"
+            )
         }
     }
 
@@ -73,13 +79,19 @@ suspend fun doEbicsDownloadTransaction(
     }
 
     val transactionID =
-        initResponse.transactionID ?: throw 
NexusError(HttpStatusCode.InternalServerError, "initial response must contain 
transaction ID")
+        initResponse.transactionID ?: throw NexusError(
+            HttpStatusCode.InternalServerError,
+            "initial response must contain transaction ID"
+        )
 
     val encryptionInfo = initResponse.dataEncryptionInfo
         ?: throw NexusError(HttpStatusCode.InternalServerError, "initial 
response did not contain encryption info")
 
     val initOrderDataEncChunk = initResponse.orderDataEncChunk
-        ?: throw NexusError(HttpStatusCode.InternalServerError,"initial 
response for download transaction does not contain data transfer")
+        ?: throw NexusError(
+            HttpStatusCode.InternalServerError,
+            "initial response for download transaction does not contain data 
transfer"
+        )
 
     payloadChunks.add(initOrderDataEncChunk)
 
@@ -97,7 +109,7 @@ suspend fun doEbicsDownloadTransaction(
         EbicsReturnCode.EBICS_DOWNLOAD_POSTPROCESS_DONE -> {
         }
         else -> {
-            throw NexusError(HttpStatusCode.InternalServerError,"unexpected 
return code")
+            throw NexusError(HttpStatusCode.InternalServerError, "unexpected 
return code")
         }
     }
     return EbicsDownloadSuccessResult(respPayload)
@@ -124,7 +136,10 @@ suspend fun doEbicsUploadTransaction(
     }
 
     val transactionID =
-        initResponse.transactionID ?: throw 
NexusError(HttpStatusCode.InternalServerError,"init response must have 
transaction ID")
+        initResponse.transactionID ?: throw NexusError(
+            HttpStatusCode.InternalServerError,
+            "init response must have transaction ID"
+        )
 
     logger.debug("INIT phase passed!")
     /* now send actual payload */
@@ -147,7 +162,58 @@ suspend fun doEbicsUploadTransaction(
         EbicsReturnCode.EBICS_OK -> {
         }
         else -> {
-            throw NexusError(HttpStatusCode.InternalServerError,"unexpected 
return code")
+            throw NexusError(HttpStatusCode.InternalServerError, "unexpected 
return code")
         }
     }
+}
+
+suspend fun doEbicsHostVersionQuery(client: HttpClient, ebicsBaseUrl: String, 
ebicsHostId: String): EbicsHevDetails {
+    val ebicsHevRequest = makeEbicsHEVRequestRaw(ebicsHostId)
+    val resp = client.postToBank(ebicsBaseUrl, ebicsHevRequest)
+    val versionDetails = parseEbicsHEVResponse(resp)
+    return versionDetails
+}
+
+suspend fun doEbicsIniRequest(
+    client: HttpClient,
+    subscriberDetails: EbicsClientSubscriberDetails
+): EbicsKeyManagementResponseContent {
+    val request = makeEbicsIniRequest(subscriberDetails)
+    val respStr = client.postToBank(
+        subscriberDetails.ebicsUrl,
+        request
+    )
+    val resp = parseAndDecryptEbicsKeyManagementResponse(subscriberDetails, 
respStr)
+    return resp
+}
+
+suspend fun doEbicsHiaRequest(
+    client: HttpClient,
+    subscriberDetails: EbicsClientSubscriberDetails
+): EbicsKeyManagementResponseContent {
+    val request = makeEbicsHiaRequest(subscriberDetails)
+    val respStr = client.postToBank(
+        subscriberDetails.ebicsUrl,
+        request
+    )
+    val resp = parseAndDecryptEbicsKeyManagementResponse(subscriberDetails, 
respStr)
+    return resp
+}
+
+
+suspend fun doEbicsHpbRequest(
+    client: HttpClient,
+    subscriberDetails: EbicsClientSubscriberDetails
+): HpbResponseData {
+    val request = makeEbicsHpbRequest(subscriberDetails)
+    val respStr = client.postToBank(
+        subscriberDetails.ebicsUrl,
+        request
+    )
+    val parsedResponse = 
parseAndDecryptEbicsKeyManagementResponse(subscriberDetails, respStr)
+    val orderData = parsedResponse.orderData ?: throw NexusError(
+        HttpStatusCode.InternalServerError,
+        "Cannot find data in a HPB response"
+    )
+    return parseEbicsHpbOrder(orderData)
 }
\ No newline at end of file
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/Helpers.kt 
b/nexus/src/main/kotlin/tech/libeufin/nexus/Helpers.kt
index 5967af0..0c450a3 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/Helpers.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/Helpers.kt
@@ -1,10 +1,9 @@
 package tech.libeufin.nexus
 
-import com.fasterxml.jackson.databind.JsonNode
-import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
-import com.fasterxml.jackson.module.kotlin.treeToValue
+import io.ktor.application.ApplicationCall
 import io.ktor.client.HttpClient
 import io.ktor.http.HttpStatusCode
+import io.ktor.request.ApplicationRequest
 import org.jetbrains.exposed.sql.and
 import org.jetbrains.exposed.sql.transactions.transaction
 import org.joda.time.DateTime
@@ -58,49 +57,6 @@ fun extractFirstBic(bankCodes: 
List<EbicsTypes.AbstractBankCode>?): String? {
     return null
 }
 
-fun getTransportFromJsonObject(jo: JsonNode): Transport {
-    return jacksonObjectMapper().treeToValue(jo.get("transport"), 
Transport::class.java)
-}
-
-/**
- * Retrieve bank account details, only if user owns it.
- */
-fun getBankAccount(userId: String, accountId: String): BankAccountEntity {
-    return transaction {
-        val bankAccountMap = BankAccountMapEntity.find {
-            BankAccountMapsTable.nexusUser eq userId
-        }.firstOrNull() ?: throw NexusError(
-            HttpStatusCode.NotFound,
-            "Bank account '$accountId' not found"
-        )
-        bankAccountMap.bankAccount
-    }
-}
-
-/**
- * Given a nexus user id, returns the _list_ of bank accounts associated to it.
- *
- * @param id the subscriber id
- * @return the (non-empty) list of bank accounts associated with this user.
- */
-fun getBankAccountsFromNexusUserId(id: String): MutableList<BankAccountEntity> 
{
-    logger.debug("Looking up bank account of user '$id'")
-    val ret = mutableListOf<BankAccountEntity>()
-    transaction {
-        BankAccountMapEntity.find {
-            BankAccountMapsTable.nexusUser eq id
-        }.forEach {
-            ret.add(it.bankAccount)
-        }
-    }
-    if (ret.isEmpty()) {
-        throw NexusError(
-            HttpStatusCode.NotFound,
-            "Such user '$id' does not have any bank account associated"
-        )
-    }
-    return ret
-}
 
 fun getEbicsSubscriberDetailsInternal(subscriber: EbicsSubscriberEntity): 
EbicsClientSubscriberDetails {
     var bankAuthPubValue: RSAPublicKey? = null
@@ -130,32 +86,18 @@ fun getEbicsSubscriberDetailsInternal(subscriber: 
EbicsSubscriberEntity): EbicsC
     )
 }
 
-fun getEbicsTransport(userId: String, transportId: String? = null): 
EbicsSubscriberEntity {
-    val transport = transaction {
-        if (transportId == null) {
-            return@transaction EbicsSubscriberEntity.find {
-                EbicsSubscribersTable.nexusUser eq userId
-            }.firstOrNull()
-        }
-        return@transaction EbicsSubscriberEntity.find {
-            EbicsSubscribersTable.id eq transportId and 
(EbicsSubscribersTable.nexusUser eq userId)
-        }.firstOrNull()
-    }
-        ?: throw NexusError(
-            HttpStatusCode.NotFound,
-            "Could not find ANY Ebics transport for user $userId"
-        )
-    return transport
-}
-
 /**
  * Retrieve Ebics subscriber details given a Transport
  * object and handling the default case (when this latter is null).
  */
-fun getEbicsSubscriberDetails(userId: String, transportId: String?): 
EbicsClientSubscriberDetails {
-    val transport = getEbicsTransport(userId, transportId)
+fun getEbicsSubscriberDetails(userId: String, transportId: String): 
EbicsClientSubscriberDetails {
+    val transport = NexusBankConnectionEntity.findById(transportId)
+    if (transport == null) {
+        throw NexusError(HttpStatusCode.NotFound, "transport not found")
+    }
+    val subscriber = EbicsSubscriberEntity.find { 
EbicsSubscribersTable.nexusBankConnection eq transport.id }.first()
     // transport exists and belongs to caller.
-    return getEbicsSubscriberDetailsInternal(transport)
+    return getEbicsSubscriberDetailsInternal(subscriber)
 }
 
 suspend fun downloadAndPersistC5xEbics(
@@ -164,9 +106,8 @@ suspend fun downloadAndPersistC5xEbics(
     userId: String,
     start: String?, // dashed date YYYY-MM(01-12)-DD(01-31)
     end: String?, // dashed date YYYY-MM(01-12)-DD(01-31)
-    transportId: String?
+    subscriberDetails: EbicsClientSubscriberDetails
 ) {
-    val subscriberDetails = getEbicsSubscriberDetails(userId, transportId)
     val orderParamsJson = EbicsStandardOrderParamsJson(
         EbicsDateRangeJson(start, end)
     )
@@ -188,6 +129,10 @@ suspend fun downloadAndPersistC5xEbics(
                 val fileName = it.first
                 val camt53doc = XMLUtil.parseStringIntoDom(it.second)
                 transaction {
+                    val user = NexusUserEntity.findById(userId)
+                    if (user == null) {
+                        throw NexusError(HttpStatusCode.NotFound, "user not 
found")
+                    }
                     RawBankTransactionEntity.new {
                         bankAccount = getBankAccountFromIban(
                             camt53doc.pickString(
@@ -203,7 +148,7 @@ suspend fun downloadAndPersistC5xEbics(
                         status = 
camt53doc.pickString("//*[local-name()='Ntry']//*[local-name()='Sts']")
                         bookingDate =
                             
parseDashedDate(camt53doc.pickString("//*[local-name()='BookgDt']//*[local-name()='Dt']")).millis
-                        nexusUser = extractNexusUser(userId)
+                        nexusUser = user
                         counterpartIban =
                             camt53doc.pickString("//*[local-name()='${if 
(this.transactionType == "DBIT") "CdtrAcct" else 
"DbtrAcct"}']//*[local-name()='IBAN']")
                         counterpartName =
@@ -222,22 +167,6 @@ suspend fun downloadAndPersistC5xEbics(
     }
 }
 
-suspend fun submitPaymentEbics(
-    client: HttpClient,
-    userId: String,
-    transportId: String?,
-    pain001document: String
-) {
-    logger.debug("Uploading PAIN.001: ${pain001document}")
-    doEbicsUploadTransaction(
-        client,
-        getEbicsSubscriberDetails(userId, transportId),
-        "CCT",
-        pain001document.toByteArray(Charsets.UTF_8),
-        EbicsStandardOrderParams()
-    )
-}
-
 
 /**
  * Create a PAIN.001 XML document according to the input data.
@@ -394,10 +323,9 @@ fun getNexusUser(id: String): NexusUserEntity {
  * it will be the account whose money will pay the wire transfer being defined
  * by this pain document.
  */
-fun addPreparedPayment(paymentData: Pain001Data, nexusUser: NexusUserEntity): 
PreparedPaymentEntity {
+fun addPreparedPayment(paymentData: Pain001Data, debitorAccount: 
BankAccountEntity): PreparedPaymentEntity {
     val randomId = Random().nextLong()
     return transaction {
-        val debitorAccount = getBankAccount(nexusUser.id.value, 
paymentData.debitorAccount)
         PreparedPaymentEntity.new(randomId.toString()) {
             subject = paymentData.subject
             sum = paymentData.sum
@@ -410,7 +338,6 @@ fun addPreparedPayment(paymentData: Pain001Data, nexusUser: 
NexusUserEntity): Pr
             preparationDate = DateTime.now().millis
             paymentId = randomId
             endToEndId = randomId
-            this.nexusUser = nexusUser
         }
     }
 }
@@ -421,25 +348,12 @@ fun ensureNonNull(param: String?): String {
     )
 }
 
-/* Needs a transaction{} block to be called */
-fun extractNexusUser(param: String?): NexusUserEntity {
-    if (param == null) {
-        throw NexusError(HttpStatusCode.BadRequest, "Null Id given")
-    }
-    return transaction {
-        NexusUserEntity.findById(param) ?: throw NexusError(
-            HttpStatusCode.NotFound,
-            "Subscriber: $param not found"
-        )
-    }
-}
-
 /**
  * This helper function parses a Authorization:-header line, decode the 
credentials
  * and returns a pair made of username and hashed (sha256) password.  The 
hashed value
  * will then be compared with the one kept into the database.
  */
-fun extractUserAndHashedPassword(authorizationHeader: String): Pair<String, 
String> {
+fun extractUserAndPassword(authorizationHeader: String): Pair<String, String> {
     logger.debug("Authenticating: $authorizationHeader")
     val (username, password) = try {
         val split = authorizationHeader.split(" ")
@@ -461,11 +375,12 @@ fun extractUserAndHashedPassword(authorizationHeader: 
String): Pair<String, Stri
  * @param authorization the Authorization:-header line.
  * @return user id
  */
-fun authenticateRequest(authorization: String?): NexusUserEntity {
+fun authenticateRequest(request: ApplicationRequest): NexusUserEntity {
+    val authorization = request.headers["Authorization"]
     val headerLine = if (authorization == null) throw NexusError(
         HttpStatusCode.BadRequest, "Authentication:-header line not found"
     ) else authorization
-    val (username, password) = extractUserAndHashedPassword(headerLine)
+    val (username, password) = extractUserAndPassword(headerLine)
     val user = NexusUserEntity.find {
         NexusUsersTable.id eq username
     }.firstOrNull()
@@ -479,22 +394,6 @@ fun authenticateRequest(authorization: String?): 
NexusUserEntity {
 }
 
 
-/**
- * Check if the subscriber has the right to use the (claimed) bank account.
- * @param subscriber id of the EBICS subscriber to check
- * @param bankAccount id of the claimed bank account
- * @return true if the subscriber can use the bank account.
- */
-fun subscriberHasRights(subscriber: EbicsSubscriberEntity, bankAccount: 
BankAccountEntity): Boolean {
-    val row = transaction {
-        BankAccountMapEntity.find {
-            BankAccountMapsTable.bankAccount eq bankAccount.id and
-                    (BankAccountMapsTable.ebicsSubscriber eq subscriber.id)
-        }.firstOrNull()
-    }
-    return row != null
-}
-
 fun getBankAccountFromIban(iban: String): BankAccountEntity {
     return transaction {
         BankAccountEntity.find {
@@ -508,12 +407,6 @@ fun getBankAccountFromIban(iban: String): 
BankAccountEntity {
 
 /** Check if the nexus user is allowed to use the claimed bank account.  */
 fun userHasRights(nexusUser: NexusUserEntity, iban: String): Boolean {
-    val row = transaction {
-        val bankAccount = getBankAccountFromIban(iban)
-        BankAccountMapEntity.find {
-            BankAccountMapsTable.bankAccount eq bankAccount.id and
-                    (BankAccountMapsTable.nexusUser eq nexusUser.id)
-        }.firstOrNull()
-    }
-    return row != null
+    // FIXME: implement permissions
+    return true
 }
\ No newline at end of file
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/JSON.kt 
b/nexus/src/main/kotlin/tech/libeufin/nexus/JSON.kt
index 4dd2a63..4a9d986 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/JSON.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/JSON.kt
@@ -80,11 +80,25 @@ data class RawPayments(
  *  API types (used as requests/responses types) *
  *************************************************/
 data class BankTransport(
-    val transport: Transport,
+    val transport: String,
     val backup: Any? = null,
     val data: Any?
 )
 
+data class BankConnectionInfo(
+    val name: String,
+    val type: String
+)
+
+data class BankConnectionsList(
+    val bankConnections: List<BankConnectionInfo>
+)
+
+data class EbicsHostTestRequest(
+    val ebicsBaseUrl: String,
+    val ebicsHostId: String
+)
+
 /**
  * This object is used twice: as a response to the backup request,
  * and as a request to the backup restore.  Note: in the second case
@@ -137,30 +151,9 @@ data class Transactions(
     val transactions: MutableList<Transaction> = mutableListOf()
 )
 
-/** Specifies the transport to use.  */
-data class Transport(
-    /**
-     * Must match one of the types implemented by nexus:
-     * 'ebics', 'local', possibly 'fints' in the near future!
-     */
-    val type: String,
-    /**
-     * A mnemonic identifier given by the user to one
-     * transport instance.
-     */
-    val name: String
-)
-
-/** Request type of "POST /prepared-payments/submit" */
-data class SubmitPayment(
-    val uuid: String,
-    /** Policy to pick the default transport is still work in progress.  */
-    val transport: tech.libeufin.nexus.Transport?
-)
-
 /** Request type of "POST /collected-transactions" */
 data class CollectedTransaction(
-    val transport: Transport?,
+    val transport: String?,
     val start: String?,
     val end: String?
 )
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt 
b/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt
index 933cb17..b898cc5 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt
@@ -55,9 +55,7 @@ import io.ktor.routing.post
 import io.ktor.routing.routing
 import io.ktor.server.engine.embeddedServer
 import io.ktor.server.netty.Netty
-import io.ktor.util.KtorExperimentalAPI
 import io.ktor.utils.io.ByteReadChannel
-import io.ktor.utils.io.core.ExperimentalIoApi
 import io.ktor.utils.io.jvm.javaio.toByteReadChannel
 import io.ktor.utils.io.jvm.javaio.toInputStream
 import org.jetbrains.exposed.sql.and
@@ -69,110 +67,16 @@ import org.slf4j.event.Level
 import tech.libeufin.util.*
 import tech.libeufin.util.CryptoUtil.hashpw
 import tech.libeufin.util.ebics_h004.HTDResponseOrderData
+import java.util.*
 import java.util.zip.InflaterInputStream
 import javax.crypto.EncryptedPrivateKeyInfo
 import javax.sql.rowset.serial.SerialBlob
 
-data class NexusError(val statusCode: HttpStatusCode, val reason: String) : 
Exception()
+data class NexusError(val statusCode: HttpStatusCode, val reason: String) :
+    Exception("${reason} (HTTP status $statusCode)")
 
 val logger: Logger = LoggerFactory.getLogger("tech.libeufin.nexus")
 
-suspend fun handleEbicsSendMSG(
-    httpClient: HttpClient,
-    userId: String,
-    transportId: String?,
-    msg: String,
-    sync: Boolean
-): String {
-    val subscriber = getEbicsSubscriberDetails(userId, transportId)
-    val response = when (msg.toUpperCase()) {
-        "HIA" -> {
-            val request = makeEbicsHiaRequest(subscriber)
-            httpClient.postToBank(
-                subscriber.ebicsUrl,
-                request
-            )
-        }
-        "INI" -> {
-            val request = makeEbicsIniRequest(subscriber)
-            httpClient.postToBank(
-                subscriber.ebicsUrl,
-                request
-            )
-        }
-        "HPB" -> {
-            /** should NOT put bank's keys into any table.  */
-            val request = makeEbicsHpbRequest(subscriber)
-            val response = httpClient.postToBank(
-                subscriber.ebicsUrl,
-                request
-            )
-            if (sync) {
-                val parsedResponse = 
parseAndDecryptEbicsKeyManagementResponse(subscriber, response)
-                val orderData = parsedResponse.orderData ?: throw NexusError(
-                    HttpStatusCode.InternalServerError,
-                    "Cannot find data in a HPB response"
-                )
-                val hpbData = parseEbicsHpbOrder(orderData)
-                transaction {
-                    val transport = getEbicsTransport(userId, transportId)
-                    transport.bankAuthenticationPublicKey = 
SerialBlob(hpbData.authenticationPubKey.encoded)
-                    transport.bankEncryptionPublicKey = 
SerialBlob(hpbData.encryptionPubKey.encoded)
-                }
-            }
-            return response
-        }
-        "HTD" -> {
-            val response = doEbicsDownloadTransaction(
-                httpClient, subscriber, "HTD", EbicsStandardOrderParams()
-            )
-            when (response) {
-                is EbicsDownloadBankErrorResult -> {
-                    throw NexusError(
-                        HttpStatusCode.BadGateway,
-                        response.returnCode.errorCode
-                    )
-                }
-                is EbicsDownloadSuccessResult -> {
-                    val payload = 
XMLUtil.convertStringToJaxb<HTDResponseOrderData>(
-                        response.orderData.toString(Charsets.UTF_8)
-                    )
-                    if (sync) {
-                        transaction {
-                            payload.value.partnerInfo.accountInfoList?.forEach 
{
-                                val bankAccount = BankAccountEntity.new(id = 
it.id) {
-                                    accountHolder = it.accountHolder ?: 
"NOT-GIVEN"
-                                    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"
-                                    )
-                                }
-                                BankAccountMapEntity.new {
-                                    ebicsSubscriber = 
getEbicsTransport(userId, transportId)
-                                    this.nexusUser = getNexusUser(userId)
-                                    this.bankAccount = bankAccount
-                                }
-                            }
-                        }
-                    }
-                    response.orderData.toString(Charsets.UTF_8)
-                }
-            }
-        }
-        "HEV" -> {
-            val request = makeEbicsHEVRequest(subscriber)
-            httpClient.postToBank(subscriber.ebicsUrl, request)
-        }
-        else -> throw NexusError(
-            HttpStatusCode.NotFound,
-            "Message $msg not found"
-        )
-    }
-    return response
-}
-
 class NexusCommand : CliktCommand() {
     override fun run() = Unit
 }
@@ -214,7 +118,7 @@ fun main(args: Array<String>) {
         .main(args)
 }
 
-suspend inline fun <reified T : Any>ApplicationCall.receiveJson(): T {
+suspend inline fun <reified T : Any> ApplicationCall.receiveJson(): T {
     try {
         return this.receive<T>();
     } catch (e: MissingKotlinParameterException) {
@@ -224,8 +128,93 @@ suspend inline fun <reified T : 
Any>ApplicationCall.receiveJson(): T {
     }
 }
 
-@ExperimentalIoApi
-@KtorExperimentalAPI
+fun createEbicsBankConnection(bankConnectionName: String, user: 
NexusUserEntity, body: JsonNode) {
+    val bankConn = NexusBankConnectionEntity.new(bankConnectionName) {
+        owner = user
+        type = "ebics"
+    }
+    if (body.get("backup") != null) {
+        val backup = jacksonObjectMapper().treeToValue(body, 
EbicsKeysBackupJson::class.java)
+        val (authKey, encKey, sigKey) = try {
+            Triple(
+                CryptoUtil.decryptKey(
+                    EncryptedPrivateKeyInfo(base64ToBytes(backup.authBlob)),
+                    backup.passphrase
+                ),
+                CryptoUtil.decryptKey(
+                    EncryptedPrivateKeyInfo(base64ToBytes(backup.encBlob)),
+                    backup.passphrase
+                ),
+                CryptoUtil.decryptKey(
+                    EncryptedPrivateKeyInfo(base64ToBytes(backup.sigBlob)),
+                    backup.passphrase
+                )
+            )
+        } catch (e: Exception) {
+            e.printStackTrace()
+            logger.info("Restoring keys failed, probably due to wrong 
passphrase")
+            throw NexusError(
+                HttpStatusCode.BadRequest,
+                "Bad backup given"
+            )
+        }
+        try {
+            EbicsSubscriberEntity.new() {
+                ebicsURL = backup.ebicsURL
+                hostID = backup.hostID
+                partnerID = backup.partnerID
+                userID = backup.userID
+                signaturePrivateKey = SerialBlob(sigKey.encoded)
+                encryptionPrivateKey = SerialBlob(encKey.encoded)
+                authenticationPrivateKey = SerialBlob(authKey.encoded)
+                nexusBankConnection = bankConn
+            }
+        } catch (e: Exception) {
+            throw NexusError(
+                HttpStatusCode.BadRequest,
+                "exception: $e"
+            )
+        }
+        return
+    }
+    if (body.get("data") != null) {
+        val data =
+            jacksonObjectMapper().treeToValue((body.get("data")), 
EbicsNewTransport::class.java)
+        val pairA = CryptoUtil.generateRsaKeyPair(2048)
+        val pairB = CryptoUtil.generateRsaKeyPair(2048)
+        val pairC = CryptoUtil.generateRsaKeyPair(2048)
+        EbicsSubscriberEntity.new() {
+            ebicsURL = data.ebicsURL
+            hostID = data.hostID
+            partnerID = data.partnerID
+            userID = data.userID
+            systemID = data.systemID
+            signaturePrivateKey = SerialBlob(pairA.private.encoded)
+            encryptionPrivateKey = SerialBlob(pairB.private.encoded)
+            authenticationPrivateKey = SerialBlob(pairC.private.encoded)
+            nexusBankConnection = bankConn
+        }
+        return
+    }
+    throw NexusError(
+        HttpStatusCode.BadRequest,
+        "Neither restore or new transport were specified."
+    )
+}
+
+fun requireBankConnection(call: ApplicationCall, parameterKey: String): 
NexusBankConnectionEntity {
+    val name = call.parameters[parameterKey]
+    if (name == null) {
+        throw NexusError(HttpStatusCode.InternalServerError, "no parameter for 
bank connection")
+    }
+    val conn = NexusBankConnectionEntity.findById(name)
+    if (conn == null) {
+        throw NexusError(HttpStatusCode.NotFound, "bank connection '$name' not 
found")
+    }
+    return conn
+}
+
+
 fun serverMain() {
     dbCreateTables()
     val client = HttpClient() {
@@ -237,6 +226,7 @@ fun serverMain() {
             this.level = Level.DEBUG
             this.logger = tech.libeufin.nexus.logger
         }
+
         install(ContentNegotiation) {
             jackson {
                 enable(SerializationFeature.INDENT_OUTPUT)
@@ -248,6 +238,7 @@ fun serverMain() {
                 //registerModule(JavaTimeModule())
             }
         }
+
         install(StatusPages) {
             exception<NexusError> { cause ->
                 logger.error("Exception while handling '${call.request.uri}'", 
cause)
@@ -282,6 +273,10 @@ fun serverMain() {
                 return@intercept finish()
             }
         }
+
+        /**
+         * Allow request body compression.  Needed by Taler.
+         */
         receivePipeline.intercept(ApplicationReceivePipeline.Before) {
             if (this.context.request.headers["Content-Encoding"] == "deflate") 
{
                 logger.debug("About to inflate received data")
@@ -293,13 +288,14 @@ fun serverMain() {
             proceed()
             return@intercept
         }
+
         routing {
             /**
              * Shows information about the requesting user.
              */
             get("/user") {
                 val ret = transaction {
-                    val currentUser = 
authenticateRequest(call.request.headers["Authorization"])
+                    val currentUser = authenticateRequest(call.request)
                     UserResponse(
                         username = currentUser.id.value,
                         superuser = currentUser.superuser
@@ -321,13 +317,14 @@ fun serverMain() {
                 call.respond(HttpStatusCode.OK, usersResp)
                 return@get
             }
+
             /**
              * Add a new ordinary user in the system (requires superuser 
privileges)
              */
             post("/users") {
                 val body = call.receiveJson<User>()
                 transaction {
-                    val currentUser = 
authenticateRequest(call.request.headers["Authorization"])
+                    val currentUser = authenticateRequest(call.request)
                     if (!currentUser.superuser) {
                         throw NexusError(HttpStatusCode.Forbidden, "only 
superuser can do that")
                     }
@@ -343,95 +340,112 @@ fun serverMain() {
                 )
                 return@post
             }
+
             get("/bank-connection-protocols") {
                 call.respond(HttpStatusCode.OK, 
BankProtocolsResponse(listOf("ebics", "loopback")))
                 return@get
             }
+
+            post("/bank-connection-protocols/ebics/test-host") {
+                val r = call.receiveJson<EbicsHostTestRequest>()
+                val qr = doEbicsHostVersionQuery(client, r.ebicsBaseUrl, 
r.ebicsHostId)
+                call.respond(HttpStatusCode.OK, qr)
+                return@post
+            }
+
             /**
              * Shows the bank accounts belonging to the requesting user.
              */
             get("/bank-accounts") {
-                val userId = transaction { 
authenticateRequest(call.request.headers["Authorization"]).id.value }
                 val bankAccounts = BankAccounts()
-                getBankAccountsFromNexusUserId(userId).forEach {
-                    bankAccounts.accounts.add(
-                        BankAccount(
-                            holder = it.accountHolder,
-                            iban = it.iban,
-                            bic = it.bankCode,
-                            account = it.id.value
-                        )
-                    )
+                transaction {
+                    authenticateRequest(call.request)
+                    // FIXME(dold): Only return accounts the user has at least 
read access to?
+                    BankAccountEntity.all().forEach {
+                        
bankAccounts.accounts.add(BankAccount(it.accountHolder, it.iban, it.bankCode, 
it.id.value))
+                    }
                 }
+                call.respond(bankAccounts)
                 return@get
             }
+
             /**
-             * Submit one particular payment at the bank.
+             * Submit one particular payment to the bank.
              */
-            post("/bank-accounts/prepared-payments/submit") {
-                val userId = transaction { 
authenticateRequest(call.request.headers["Authorization"]).id.value }
-                val body = call.receive<SubmitPayment>()
-                val preparedPayment = getPreparedPayment(body.uuid)
-                transaction {
-                    if (preparedPayment.nexusUser.id.value != userId) throw 
NexusError(
-                        HttpStatusCode.Forbidden,
-                        "No rights over such payment"
-                    )
+            post("/bank-accounts/{accountid}/prepared-payments/{uuid}/submit") 
{
+                val uuid = ensureNonNull(call.parameters["uuid"])
+                val accountId = ensureNonNull(call.parameters["accountid"])
+                val res = transaction {
+                    val user = authenticateRequest(call.request)
+                    val preparedPayment = getPreparedPayment(uuid)
                     if (preparedPayment.submitted) {
                         throw NexusError(
                             HttpStatusCode.PreconditionFailed,
-                            "Payment ${body.uuid} was submitted already"
+                            "Payment ${uuid} was submitted already"
                         )
                     }
-
+                    val bankAccount = BankAccountEntity.findById(accountId)
+                    if (bankAccount == null) {
+                        throw NexusError(HttpStatusCode.NotFound, "unknown 
bank account")
+                    }
+                    val defaultBankConnection = 
bankAccount.defaultBankConnection
+                    if (defaultBankConnection == null) {
+                        throw NexusError(HttpStatusCode.NotFound, "needs a 
default connection")
+                    }
+                    val subscriberDetails = 
getEbicsSubscriberDetails(user.id.value, defaultBankConnection.id.value);
+                    return@transaction object {
+                        val pain001document = 
createPain001document(preparedPayment)
+                        val bankConnectionType = defaultBankConnection.type
+                        val subscriberDetails = subscriberDetails
+                    }
                 }
-                val pain001document = createPain001document(preparedPayment)
-                if (body.transport != null) {
-                    // type and name aren't null
-                    when (body.transport.type) {
-                        "ebics" -> {
-                            submitPaymentEbics(
-                                client, userId, body.transport.name, 
pain001document
-                            )
-                        }
-                        else -> throw NexusError(
-                            HttpStatusCode.NotFound,
-                            "Transport type '${body.transport.type}' not 
implemented"
+                // type and name aren't null
+                when (res.bankConnectionType) {
+                    "ebics" -> {
+                        logger.debug("Uploading PAIN.001: 
${res.pain001document}")
+                        doEbicsUploadTransaction(
+                            client,
+                            res.subscriberDetails,
+                            "CCT",
+                            res.pain001document.toByteArray(Charsets.UTF_8),
+                            EbicsStandardOrderParams()
                         )
                     }
-                } else {
-                    // default to ebics and "first" transport from user
-                    submitPaymentEbics(
-                        client, userId, null, pain001document
+                    else -> throw NexusError(
+                        HttpStatusCode.NotFound,
+                        "Transport type '${res.bankConnectionType}' not 
implemented"
                     )
                 }
                 transaction {
+                    val preparedPayment = getPreparedPayment(uuid)
                     preparedPayment.submitted = true
                 }
-                call.respondText("Payment ${body.uuid} submitted")
+                call.respondText("Payment ${uuid} submitted")
                 return@post
             }
+
             /**
              * Shows information about one particular prepared payment.
              */
             get("/bank-accounts/{accountid}/prepared-payments/{uuid}") {
-                val userId = transaction { 
authenticateRequest(call.request.headers["Authorization"]).id.value }
-                val preparedPayment = 
getPreparedPayment(ensureNonNull(call.parameters["uuid"]))
-                if (preparedPayment.nexusUser.id.value != userId) throw 
NexusError(
-                    HttpStatusCode.Forbidden,
-                    "No rights over such payment"
-                )
+                val res = transaction {
+                    val user = authenticateRequest(call.request)
+                    val preparedPayment = 
getPreparedPayment(ensureNonNull(call.parameters["uuid"]))
+                    return@transaction object {
+                        val preparedPayment = preparedPayment
+                    }
+                }
                 call.respond(
                     PaymentStatus(
-                        uuid = preparedPayment.id.value,
-                        submitted = preparedPayment.submitted,
-                        creditorName = preparedPayment.creditorName,
-                        creditorBic = preparedPayment.creditorBic,
-                        creditorIban = preparedPayment.creditorIban,
-                        amount = 
"${preparedPayment.sum}:${preparedPayment.currency}",
-                        subject = preparedPayment.subject,
-                        submissionDate = 
DateTime(preparedPayment.submissionDate).toDashedDate(),
-                        preparationDate = 
DateTime(preparedPayment.preparationDate).toDashedDate()
+                        uuid = res.preparedPayment.id.value,
+                        submitted = res.preparedPayment.submitted,
+                        creditorName = res.preparedPayment.creditorName,
+                        creditorBic = res.preparedPayment.creditorBic,
+                        creditorIban = res.preparedPayment.creditorIban,
+                        amount = 
"${res.preparedPayment.sum}:${res.preparedPayment.currency}",
+                        subject = res.preparedPayment.subject,
+                        submissionDate = 
DateTime(res.preparedPayment.submissionDate).toDashedDate(),
+                        preparationDate = 
DateTime(res.preparedPayment.preparationDate).toDashedDate()
                     )
                 )
                 return@get
@@ -440,78 +454,104 @@ fun serverMain() {
              * Adds a new prepared payment.
              */
             post("/bank-accounts/{accountid}/prepared-payments") {
-                val userId = transaction { 
authenticateRequest(call.request.headers["Authorization"]).id.value }
-                val bankAccount = getBankAccount(userId, 
ensureNonNull(call.parameters["accountid"]))
                 val body = call.receive<PreparedPaymentRequest>()
-                val amount = parseAmount(body.amount)
-                val paymentEntity = addPreparedPayment(
-                    Pain001Data(
-                        creditorIban = body.iban,
-                        creditorBic = body.bic,
-                        creditorName = body.name,
-                        debitorAccount = bankAccount.id.value,
-                        sum = amount.amount,
-                        currency = amount.currency,
-                        subject = body.subject
-                    ),
-                    extractNexusUser(userId)
-                )
+                val accountId = ensureNonNull(call.parameters["accountid"])
+                val res = transaction {
+                    authenticateRequest(call.request)
+                    val bankAccount = BankAccountEntity.findById(accountId)
+                    if (bankAccount == null) {
+                        throw NexusError(HttpStatusCode.NotFound, "unknown 
bank account")
+                    }
+                    val amount = parseAmount(body.amount)
+                    val paymentEntity = addPreparedPayment(
+                        Pain001Data(
+                            creditorIban = body.iban,
+                            creditorBic = body.bic,
+                            creditorName = body.name,
+                            debitorAccount = bankAccount.id.value,
+                            sum = amount.amount,
+                            currency = amount.currency,
+                            subject = body.subject
+                        ),
+                        bankAccount
+                    )
+                    return@transaction object {
+                        val uuid = paymentEntity.id.value
+                    }
+                }
                 call.respond(
                     HttpStatusCode.OK,
-                    PreparedPaymentResponse(uuid = paymentEntity.id.value)
+                    PreparedPaymentResponse(uuid = res.uuid)
                 )
                 return@post
             }
+
             /**
              * Downloads new transactions from the bank.
-             *
-             * NOTE: 'accountid' is not used.  Transaction are asked on
-             * the basis of a transport subscriber (regardless of their
-             * bank account details)
              */
-            post("/bank-accounts/collected-transactions") {
-                val userId = transaction { 
authenticateRequest(call.request.headers["Authorization"]).id.value }
-                val body = call.receive<CollectedTransaction>()
-                if (body.transport != null) {
-                    when (body.transport.type) {
-                        "ebics" -> {
-                            downloadAndPersistC5xEbics(
-                                "C53",
-                                client,
-                                userId,
-                                body.start,
-                                body.end,
-                                body.transport.name
-                            )
-                        }
-                        else -> throw NexusError(
+            post("/bank-accounts/{accountid}/fetch-transactions") {
+                val accountid = call.parameters["accountid"]
+                if (accountid == null) {
+                    throw NexusError(
+                        HttpStatusCode.BadRequest,
+                        "Account id missing"
+                    )
+                }
+                val res = transaction {
+                    val user = authenticateRequest(call.request)
+                    val acct = BankAccountEntity.findById(accountid)
+                    if (acct == null) {
+                        throw NexusError(
+                            HttpStatusCode.NotFound,
+                            "Account not found"
+                        )
+                    }
+                    val conn = acct.defaultBankConnection
+                    if (conn == null) {
+                        throw NexusError(
                             HttpStatusCode.BadRequest,
-                            "Transport type '${body.transport.type}' not 
implemented"
+                            "No default bank connection (explicit connection 
not yet supported)"
+                        )
+                    }
+                    val subscriberDetails = 
getEbicsSubscriberDetails(user.id.value, conn.id.value)
+                    return@transaction object {
+                        val connectionType = conn.type
+                        val connectionName = conn.id.value
+                        val userId = user.id.value
+                        val subscriberDetails = subscriberDetails
+                    }
+                }
+                val body = call.receive<CollectedTransaction>()
+                when (res.connectionType) {
+                    "ebics" -> {
+                        downloadAndPersistC5xEbics(
+                            "C53",
+                            client,
+                            res.userId,
+                            body.start,
+                            body.end,
+                            res.subscriberDetails
                         )
                     }
-                } else {
-                    downloadAndPersistC5xEbics(
-                        "C53",
-                        client,
-                        userId,
-                        body.start,
-                        body.end,
-                        null
+                    else -> throw NexusError(
+                        HttpStatusCode.BadRequest,
+                        "Connection type '${res.connectionType}' not 
implemented"
                     )
                 }
                 call.respondText("Collection performed")
                 return@post
             }
+
             /**
              * Asks list of transactions ALREADY downloaded from the bank.
              */
-            get("/bank-accounts/{accountid}/collected-transactions") {
-                val userId = transaction { 
authenticateRequest(call.request.headers["Authorization"]).id.value }
+            get("/bank-accounts/{accountid}/transactions") {
                 val bankAccount = expectNonNull(call.parameters["accountid"])
                 val start = call.request.queryParameters["start"]
                 val end = call.request.queryParameters["end"]
                 val ret = Transactions()
                 transaction {
+                    val userId = transaction { 
authenticateRequest(call.request).id.value }
                     RawBankTransactionEntity.find {
                         RawBankTransactionsTable.nexusUser eq userId and
                                 (RawBankTransactionsTable.bankAccount eq 
bankAccount) and
@@ -536,151 +576,180 @@ fun serverMain() {
                 call.respond(ret)
                 return@get
             }
+
             /**
              * Adds a new bank transport.
              */
-            post("/bank-transports") {
-                val userId = transaction { 
authenticateRequest(call.request.headers["Authorization"]).id.value }
+            post("/bank-connections") {
                 // user exists and is authenticated.
                 val body = call.receive<JsonNode>()
-                val transport: Transport = getTransportFromJsonObject(body)
-                when (transport.type) {
-                    "ebics" -> {
-                        if (body.get("backup") != null) {
-                            val backup = 
jacksonObjectMapper().treeToValue(body, EbicsKeysBackupJson::class.java)
-                            val (authKey, encKey, sigKey) = try {
-                                Triple(
-                                    CryptoUtil.decryptKey(
-                                        
EncryptedPrivateKeyInfo(base64ToBytes(backup.authBlob)),
-                                        backup.passphrase
-                                    ),
-                                    CryptoUtil.decryptKey(
-                                        
EncryptedPrivateKeyInfo(base64ToBytes(backup.encBlob)),
-                                        backup.passphrase
-                                    ),
-                                    CryptoUtil.decryptKey(
-                                        
EncryptedPrivateKeyInfo(base64ToBytes(backup.sigBlob)),
-                                        backup.passphrase
-                                    )
-                                )
-                            } catch (e: Exception) {
-                                e.printStackTrace()
-                                logger.info("Restoring keys failed, probably 
due to wrong passphrase")
-                                throw NexusError(
-                                    HttpStatusCode.BadRequest,
-                                    "Bad backup given"
-                                )
-                            }
-                            logger.info("Restoring keys, creating new user: 
$userId")
-                            try {
-                                transaction {
-                                    EbicsSubscriberEntity.new(transport.name) {
-                                        this.nexusUser = 
extractNexusUser(userId)
-                                        ebicsURL = backup.ebicsURL
-                                        hostID = backup.hostID
-                                        partnerID = backup.partnerID
-                                        userID = backup.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("Backup restored")
-
-                            return@post
+                val bankConnectionName = body.get("name").textValue()
+                val bankConnectionType = body.get("type").textValue()
+                transaction {
+                    val user = authenticateRequest(call.request)
+                    when (bankConnectionType) {
+                        "ebics" -> {
+                            createEbicsBankConnection(bankConnectionName, 
user, body)
                         }
-                        if (body.get("data") != null) {
-                            val data =
-                                
jacksonObjectMapper().treeToValue((body.get("data")), 
EbicsNewTransport::class.java)
-                            val pairA = CryptoUtil.generateRsaKeyPair(2048)
-                            val pairB = CryptoUtil.generateRsaKeyPair(2048)
-                            val pairC = CryptoUtil.generateRsaKeyPair(2048)
-                            transaction {
-                                EbicsSubscriberEntity.new(transport.name) {
-                                    nexusUser = extractNexusUser(userId)
-                                    ebicsURL = data.ebicsURL
-                                    hostID = data.hostID
-                                    partnerID = data.partnerID
-                                    userID = data.userID
-                                    systemID = data.systemID
-                                    signaturePrivateKey = 
SerialBlob(pairA.private.encoded)
-                                    encryptionPrivateKey = 
SerialBlob(pairB.private.encoded)
-                                    authenticationPrivateKey = 
SerialBlob(pairC.private.encoded)
-                                }
-                            }
-                            call.respondText("EBICS user successfully created")
-                            return@post
+                        else -> {
+                            throw NexusError(
+                                HttpStatusCode.BadRequest,
+                                "Invalid bank connection type 
'${bankConnectionType}'"
+                            )
                         }
-                        throw NexusError(
-                            HttpStatusCode.BadRequest,
-                            "Neither restore or new transport were specified."
-                        )
                     }
-                    else -> {
+                }
+                call.respond(object {})
+            }
+
+            get("/bank-connections") {
+                val connList = mutableListOf<BankConnectionInfo>()
+                transaction {
+                    NexusBankConnectionEntity.all().forEach {
+                        connList.add(BankConnectionInfo(it.id.value, it.type))
+                    }
+                }
+                call.respond(BankConnectionsList(connList))
+            }
+
+            post("/bank-connections/{connid}/connect") {
+                throw NotImplementedError()
+            }
+
+            post("/bank-connections/{connid}/ebics/send-ini") {
+                val subscriber = transaction {
+                    val user = authenticateRequest(call.request)
+                    val conn = requireBankConnection(call, "connid")
+                    if (conn.type != "ebics") {
                         throw NexusError(
                             HttpStatusCode.BadRequest,
-                            "Invalid transport type '${transport.type}'"
+                            "bank connection is not of type 'ebics' (but 
'${conn.type}')"
                         )
                     }
+                    getEbicsSubscriberDetails(user.id.value, conn.id.value)
                 }
+                val resp = doEbicsIniRequest(client, subscriber)
+                call.respond(resp)
             }
+
+            post("/bank-connections/{connid}/ebics/send-hia") {
+                val subscriber = transaction {
+                    val user = authenticateRequest(call.request)
+                    val conn = requireBankConnection(call, "connid")
+                    if (conn.type != "ebics") {
+                        throw NexusError(HttpStatusCode.BadRequest, "bank 
connection is not of type 'ebics'")
+                    }
+                    getEbicsSubscriberDetails(user.id.value, conn.id.value)
+                }
+                val resp = doEbicsHiaRequest(client, subscriber)
+                call.respond(resp)
+            }
+
+            post("/bank-connections/{connid}/ebics/send-hpb") {
+                val subscriberDetails = transaction {
+                    val user = authenticateRequest(call.request)
+                    val conn = requireBankConnection(call, "connid")
+                    if (conn.type != "ebics") {
+                        throw NexusError(HttpStatusCode.BadRequest, "bank 
connection is not of type 'ebics'")
+                    }
+                    getEbicsSubscriberDetails(user.id.value, conn.id.value)
+                }
+                val hpbData = doEbicsHpbRequest(client, subscriberDetails)
+                transaction {
+                    val conn = requireBankConnection(call, "connid")
+                    val subscriber =
+                        EbicsSubscriberEntity.find { 
EbicsSubscribersTable.nexusBankConnection eq conn.id }.first()
+                    subscriber.bankAuthenticationPublicKey = 
SerialBlob(hpbData.authenticationPubKey.encoded)
+                    subscriber.bankEncryptionPublicKey = 
SerialBlob(hpbData.encryptionPubKey.encoded)
+                }
+                call.respond(object { })
+            }
+
             /**
-             * Sends to the bank a message "MSG" according to the transport
-             * "transportName".  Does not modify any DB table.
+             * Directly import accounts.  Used for testing.
              */
-            post("/bank-transports/send{MSG}") {
-                val userId = transaction { 
authenticateRequest(call.request.headers["Authorization"]).id.value }
-                val body = call.receive<Transport>()
-                when (body.type) {
-                    "ebics" -> {
-                        val response = handleEbicsSendMSG(
-                            httpClient = client,
-                            userId = userId,
-                            transportId = body.name,
-                            msg = ensureNonNull(call.parameters["MSG"]),
-                            sync = true
+            post("/bank-connections/{connid}/ebics/import-accounts") {
+                val subscriberDetails = transaction {
+                    val user = authenticateRequest(call.request)
+                    val conn = requireBankConnection(call, "connid")
+                    if (conn.type != "ebics") {
+                        throw NexusError(HttpStatusCode.BadRequest, "bank 
connection is not of type 'ebics'")
+                    }
+                    getEbicsSubscriberDetails(user.id.value, conn.id.value)
+                }
+                val response = doEbicsDownloadTransaction(
+                    client, subscriberDetails, "HTD", 
EbicsStandardOrderParams()
+                )
+                when (response) {
+                    is EbicsDownloadBankErrorResult -> {
+                        throw NexusError(
+                            HttpStatusCode.BadGateway,
+                            response.returnCode.errorCode
                         )
-                        call.respondText(response)
                     }
-                    else -> throw NexusError(
-                        HttpStatusCode.NotImplemented,
-                        "Transport '${body.type}' not implemented.  Use 
'ebics'"
-                    )
+                    is EbicsDownloadSuccessResult -> {
+                        val payload = 
XMLUtil.convertStringToJaxb<HTDResponseOrderData>(
+                            response.orderData.toString(Charsets.UTF_8)
+                        )
+                        transaction {
+                            val conn = requireBankConnection(call, "connid")
+                            payload.value.partnerInfo.accountInfoList?.forEach 
{
+                                val bankAccount = BankAccountEntity.new(id = 
it.id) {
+                                    accountHolder = it.accountHolder ?: 
"NOT-GIVEN"
+                                    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"
+                                    )
+                                    defaultBankConnection = conn
+                                }
+                            }
+                        }
+                        response.orderData.toString(Charsets.UTF_8)
+                    }
                 }
-                return@post
+                call.respond(object { })
             }
-            /**
-             * Sends the bank a message "MSG" according to the transport
-             * "transportName".  DOES alterate DB tables.
-             */
-            post("/bank-transports/sync{MSG}") {
-                val userId = transaction { 
authenticateRequest(call.request.headers["Authorization"]).id.value }
-                val body = call.receive<Transport>()
-                when (body.type) {
-                    "ebics" -> {
-                        val response = handleEbicsSendMSG(
-                            httpClient = client,
-                            userId = userId,
-                            transportId = body.name,
-                            msg = ensureNonNull(call.parameters["MSG"]),
-                            sync = true
+
+            post("/bank-connections/{connid}/ebics/download/{msgtype}") {
+                val orderType = 
requireNotNull(call.parameters["msgtype"]).toUpperCase(Locale.ROOT)
+                if (orderType.length != 3) {
+                    throw NexusError(HttpStatusCode.BadRequest, "ebics order 
type must be three characters")
+                }
+                val paramsJson = call.receive<EbicsStandardOrderParamsJson>()
+                val orderParams = paramsJson.toOrderParams()
+                val subscriberDetails = transaction {
+                    val user = authenticateRequest(call.request)
+                    val conn = requireBankConnection(call, "connid")
+                    if (conn.type != "ebics") {
+                        throw NexusError(HttpStatusCode.BadRequest, "bank 
connection is not of type 'ebics'")
+                    }
+                    getEbicsSubscriberDetails(user.id.value, conn.id.value)
+                }
+                val response = doEbicsDownloadTransaction(
+                    client,
+                    subscriberDetails,
+                    orderType,
+                    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.respondText(response)
                     }
-                    else -> throw NexusError(
-                        HttpStatusCode.NotImplemented,
-                        "Transport '${body.type}' not implemented.  Use 
'ebics'"
-                    )
                 }
-                return@post
             }
+
             /**
              * Hello endpoint.
              */
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/taler.kt 
b/nexus/src/main/kotlin/tech/libeufin/nexus/taler.kt
index 7aeacff..c15b370 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/taler.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/taler.kt
@@ -20,559 +20,559 @@ import tech.libeufin.util.*
 import kotlin.math.abs
 import kotlin.math.min
 
-class Taler(app: Route) {
-
-    /** Payment initiating data structures: one endpoint "$BASE_URL/transfer". 
*/
-    private data class TalerTransferRequest(
-        val request_uid: String,
-        val amount: String,
-        val exchange_base_url: String,
-        val wtid: String,
-        val credit_account: String
-    )
-    private data class TalerTransferResponse(
-        // point in time when the nexus put the payment instruction into the 
database.
-        val timestamp: GnunetTimestamp,
-        val row_id: Long
-    )
-
-    /** History accounting data structures */
-    private data class TalerIncomingBankTransaction(
-        val row_id: Long,
-        val date: GnunetTimestamp, // timestamp
-        val amount: String,
-        val credit_account: String, // payto form,
-        val debit_account: String,
-        val reserve_pub: String
-    )
-    private data class TalerIncomingHistory(
-        var incoming_transactions: MutableList<TalerIncomingBankTransaction> = 
mutableListOf()
-    )
-    private data class TalerOutgoingBankTransaction(
-        val row_id: Long,
-        val date: GnunetTimestamp, // timestamp
-        val amount: String,
-        val credit_account: String, // payto form,
-        val debit_account: String,
-        val wtid: String,
-        val exchange_base_url: String
-    )
-    private data class TalerOutgoingHistory(
-        var outgoing_transactions: MutableList<TalerOutgoingBankTransaction> = 
mutableListOf()
-    )
-
-    /** Test APIs' data structures. */
-    private data class TalerAdminAddIncoming(
-        val amount: String,
-        val reserve_pub: String,
-        /**
-         * This account is the one giving money to the exchange.  It doesn't
-         * have to be 'created' as it might (and normally is) simply be a 
payto://
-         * address pointing to a bank account hosted in a different financial
-         * institution.
-         */
-        val debit_account: String
-    )
-
-    private data class GnunetTimestamp(
-        val t_ms: Long
-    )
-    private data class TalerAddIncomingResponse(
-        val timestamp: GnunetTimestamp,
-        val row_id: Long
-    )
-
-    /**
-     * Helper data structures.
-     */
-    data class Payto(
-        val name: String = "NOTGIVEN",
-        val iban: String,
-        val bic: String = "NOTGIVEN"
-    )
-    /**
-     * Helper functions
-     */
-    fun parsePayto(paytoUri: String): Payto {
-        /**
-         * First try to parse a "iban"-type payto URI.  If that fails,
-         * then assume a test is being run under the "x-taler-bank" type.
-         * If that one fails too, throw exception.
-         *
-         * Note: since the Nexus doesn't have the notion of "x-taler-bank",
-         * such URIs must yield a iban-compatible tuple of values.  Therefore,
-         * the plain bank account number maps to a "iban", and the <bank 
hostname>
-         * maps to a "bic".
-         */
-
-
-        /**
-         * payto://iban/BIC?/IBAN?name=<name>
-         * payto://x-taler-bank/<bank hostname>/<plain account number>
-         */
-
-        val ibanMatch = 
Regex("payto://iban/([A-Z0-9]+/)?([A-Z0-9]+)\\?name=(\\w+)").find(paytoUri)
-        if (ibanMatch != null) {
-            val (bic, iban, name) = ibanMatch.destructured
-            return Payto(name, iban, bic.replace("/", ""))
-        }
-        val xTalerBankMatch = 
Regex("payto://x-taler-bank/localhost/([0-9]+)").find(paytoUri)
-        if (xTalerBankMatch != null) {
-            val xTalerBankAcctNo = xTalerBankMatch.destructured.component1()
-            return Payto("Taler Exchange", xTalerBankAcctNo, "localhost")
-        }
-
-        throw NexusError(HttpStatusCode.BadRequest, "invalid payto URI 
($paytoUri)")
-    }
-
-    /** Sort query results in descending order for negative deltas, and 
ascending otherwise.  */
-    private fun <T : Entity<Long>> SizedIterable<T>.orderTaler(delta: Int): 
List<T> {
-        return if (delta < 0) {
-            this.sortedByDescending { it.id }
-        } else {
-            this.sortedBy { it.id }
-        }
-    }
-
-    /**
-     * NOTE: those payto-builders default all to the x-taler-bank transport.
-     * A mechanism to easily switch transport is needed, as production needs
-     * 'iban'.
-     */
-    private fun buildPaytoUri(name: String, iban: String, bic: String): String 
{
-        return "payto://x-taler-bank/localhost/$iban"
-    }
-    private fun buildPaytoUri(iban: String, bic: String): String {
-        return "payto://x-taler-bank/localhost/$iban"
-    }
-
-    /** Builds the comparison operator for history entries based on the sign 
of 'delta'  */
-    private fun getComparisonOperator(delta: Int, start: Long, table: 
IdTable<Long>): Op<Boolean> {
-        return if (delta < 0) {
-            Expression.build {
-                table.id less start
-            }
-        } else {
-            Expression.build {
-                table.id greater start
-            }
-        }
-    }
-    /** Helper handling 'start' being optional and its dependence on 'delta'.  
*/
-    private fun handleStartArgument(start: String?, delta: Int): Long {
-        return expectLong(start) ?: if (delta >= 0) {
-            /**
-             * Using -1 as the smallest value, as some DBMS might use 0 and 
some
-             * others might use 1 as the smallest row id.
-             */
-            -1
-        } else {
-            /**
-             * NOTE: the database currently enforces there MAX_VALUE is always
-             * strictly greater than any row's id in the database.  In fact, 
the
-             * database throws exception whenever a new row is going to occupy
-             * the MAX_VALUE with its id.
-             */
-            Long.MAX_VALUE
-        }
-    }
-
-    /**
-     * The Taler layer cannot rely on the ktor-internal 
JSON-converter/responder,
-     * because this one adds a "charset" extra information in the Content-Type 
header
-     * that makes the GNUnet JSON parser unhappy.
-     *
-     * The workaround is to explicitly convert the 'data class'-object into a 
JSON
-     * string (what this function does), and use the simpler respondText 
method.
-     */
-    private fun customConverter(body: Any): String {
-        return jacksonObjectMapper().writeValueAsString(body)
-    }
-
-    /**
-     * This function indicates whether a payment in the raw table was already 
reported
-     * by some other EBICS message.  It works for both incoming and outgoing 
payments.
-     * Basically, it tries to match all the relevant details with those from 
the records
-     * that are already stored in the local "taler" database.
-     *
-     * @param entry a new raw payment to be checked.
-     * @return true if the payment was already "seen" by the Taler layer, 
false otherwise.
-     */
-    private fun duplicatePayment(entry: RawBankTransactionEntity): Boolean {
-        return false
-    }
-
-    /**
-     * This function checks whether the bank didn't accept one exchange's 
payment initiation.
-     *
-     * @param entry the raw entry to check
-     * @return true if the payment failed, false if it was successful.
-     */
-    private fun paymentFailed(entry: RawBankTransactionEntity): Boolean {
-        return false
-    }
-
-    /** Attach Taler endpoints to the main Web server */
-
-    init {
-        app.get("/taler") {
-            call.respondText("Taler Gateway Hello\n", ContentType.Text.Plain, 
HttpStatusCode.OK)
-            return@get
-        }
-        app.post("/taler/transfer") {
-            val exchangeId = transaction { 
authenticateRequest(call.request.headers["Authorization"]).id.value }
-            val transferRequest = call.receive<TalerTransferRequest>()
-            val amountObj = parseAmount(transferRequest.amount)
-            val creditorObj = parsePayto(transferRequest.credit_account)
-            val opaque_row_id = transaction {
-                val creditorData = parsePayto(transferRequest.credit_account)
-                val exchangeBankAccount = 
getBankAccountsFromNexusUserId(exchangeId).first()
-                val nexusUser = extractNexusUser(exchangeId)
-                /** Checking the UID has the desired characteristics */
-                TalerRequestedPaymentEntity.find {
-                    TalerRequestedPayments.requestUId eq 
transferRequest.request_uid
-                }.forEach {
-                    if (
-                        (it.amount != transferRequest.amount) or
-                        (it.creditAccount != 
transferRequest.exchange_base_url) or
-                        (it.wtid != transferRequest.wtid)
-                    ) {
-                        throw NexusError(
-                            HttpStatusCode.Conflict,
-                            "This uid (${transferRequest.request_uid}) belongs 
to a different payment already"
-                        )
-                    }
-                }
-                val pain001 = addPreparedPayment(
-                    Pain001Data(
-                        creditorIban = creditorData.iban,
-                        creditorBic = creditorData.bic,
-                        creditorName = creditorData.name,
-                        subject = transferRequest.wtid,
-                        sum = amountObj.amount,
-                        currency = amountObj.currency,
-                        debitorAccount = exchangeBankAccount.id.value
-                    ),
-                    nexusUser
-                )
-                val rawEbics = if (!isProduction()) {
-                    RawBankTransactionEntity.new {
-                        sourceFileName = "test"
-                        unstructuredRemittanceInformation = 
transferRequest.wtid
-                        transactionType = "DBIT"
-                        currency = amountObj.currency
-                        this.amount = amountObj.amount.toPlainString()
-                        counterpartBic = creditorObj.bic
-                        counterpartIban = creditorObj.iban
-                        counterpartName = creditorObj.name
-                        bankAccount = exchangeBankAccount
-                        bookingDate = DateTime.now().millis
-                        this.nexusUser = nexusUser
-                        status = "BOOK"
-                    }
-                } else null
-
-                val row = TalerRequestedPaymentEntity.new {
-                    preparedPayment = pain001 // not really used/needed, just 
here to silence warnings
-                    exchangeBaseUrl = transferRequest.exchange_base_url
-                    requestUId = transferRequest.request_uid
-                    amount = transferRequest.amount
-                    wtid = transferRequest.wtid
-                    creditAccount = transferRequest.credit_account
-                    rawConfirmed = rawEbics
-                }
-
-                row.id.value
-            }
-            call.respond(
-                HttpStatusCode.OK,
-                TextContent(
-                    customConverter(
-                        TalerTransferResponse(
-                            /**
-                             * Normally should point to the next round where 
the background
-                             * routine will send new PAIN.001 data to the 
bank; work in progress..
-                             */
-                            timestamp = GnunetTimestamp(DateTime.now().millis),
-                            row_id = opaque_row_id
-                        )
-                    ),
-                    ContentType.Application.Json
-                )
-            )
-            return@post
-        }
-        /** Test-API that creates one new payment addressed to the exchange.  
*/
-        app.post("/taler/admin/add-incoming") {
-            val exchangeId = transaction { 
authenticateRequest(call.request.headers["Authorization"]).id.value }
-            val addIncomingData = call.receive<TalerAdminAddIncoming>()
-            val debtor = parsePayto(addIncomingData.debit_account)
-            val amount = parseAmount(addIncomingData.amount)
-            val (bookingDate, opaque_row_id) = transaction {
-                val exchangeBankAccount = 
getBankAccountsFromNexusUserId(exchangeId).first()
-                val rawPayment = RawBankTransactionEntity.new {
-                    sourceFileName = "test"
-                    unstructuredRemittanceInformation = 
addIncomingData.reserve_pub
-                    transactionType = "CRDT"
-                    currency = amount.currency
-                    this.amount = amount.amount.toPlainString()
-                    counterpartBic = debtor.bic
-                    counterpartName = debtor.name
-                    counterpartIban = debtor.iban
-                    bookingDate = DateTime.now().millis
-                    status = "BOOK"
-                    nexusUser = extractNexusUser(exchangeId)
-                    bankAccount = exchangeBankAccount
-                }
-                /** This payment is "valid by default" and will be returned
-                 * as soon as the exchange will ask for new payments.  */
-                val row = TalerIncomingPaymentEntity.new {
-                    payment = rawPayment
-                    valid = true
-                }
-                Pair(rawPayment.bookingDate, row.id.value)
-            }
-            call.respond(
-                TextContent(
-                    customConverter(
-                        TalerAddIncomingResponse(
-                            timestamp = GnunetTimestamp(bookingDate/ 1000),
-                            row_id = opaque_row_id
-                        )
-                    ),
-                ContentType.Application.Json
-                )
-            )
-            return@post
-        }
-
-        /** This endpoint triggers the refunding of invalid payments.  
'Refunding'
-         * in this context means that nexus _prepares_ the payment instruction 
and
-         * places it into a further table.  Eventually, another routine will 
perform
-         * all the prepared payments.  */
-        
app.post("/ebics/taler/{id}/accounts/{acctid}/refund-invalid-payments") {
-            val userId = transaction { 
authenticateRequest(call.request.headers["Authorization"]).id.value }
-            val nexusUser = getNexusUser(userId)
-            val callerBankAccount = expectNonNull(call.parameters["acctid"])
-            transaction {
-                val bankAccount = getBankAccount(
-                    userId,
-                    callerBankAccount
-                )
-                TalerIncomingPaymentEntity.find {
-                    TalerIncomingPayments.refunded eq false and 
(TalerIncomingPayments.valid eq false)
-                }.forEach {
-                    addPreparedPayment(
-                        Pain001Data(
-                            creditorName = it.payment.counterpartName,
-                            creditorIban = it.payment.counterpartIban,
-                            creditorBic = it.payment.counterpartBic,
-                            sum = calculateRefund(it.payment.amount),
-                            subject = "Taler refund",
-                            currency = it.payment.currency,
-                            debitorAccount = callerBankAccount
-                        ),
-                        nexusUser
-                    )
-                    it.refunded = true
-                }
-            }
-            return@post
-        }
-
-        /** This endpoint triggers the examination of raw incoming payments 
aimed
-         * at separating the good payments (those that will lead to a new 
reserve
-         * being created), from the invalid payments (those with a invalid 
subject
-         * that will soon be refunded.)  Recently, the examination of raw 
OUTGOING
-         * payment was added as well.
-         */
-        app.post("/ebics/taler/{id}/crunch-raw-transactions") {
-            val id = ensureNonNull(call.parameters["id"])
-            // first find highest ID value of already processed rows.
-            transaction {
-                val subscriberAccount = 
getBankAccountsFromNexusUserId(id).first()
-                /**
-                 * Search for fresh incoming payments in the raw table, and 
making pointers
-                 * from the Taler incoming payments table to the found fresh 
(and valid!) payments.
-                 */
-                val latestIncomingPaymentId: Long = 
TalerIncomingPaymentEntity.getLast()
-                RawBankTransactionEntity.find {
-                    /** Those with exchange bank account involved */
-                    RawBankTransactionsTable.bankAccount eq 
subscriberAccount.id.value and
-                            /** Those that are incoming */
-                            (RawBankTransactionsTable.transactionType eq 
"CRDT") and
-                            /** Those that are booked */
-                            (RawBankTransactionsTable.status eq "BOOK") and
-                            /** Those that came later than the latest 
processed payment */
-                            
(RawBankTransactionsTable.id.greater(latestIncomingPaymentId))
-
-                }.forEach {
-                    if (duplicatePayment(it)) {
-                        logger.warn("Incomint payment already seen")
-                        throw NexusError(
-                            HttpStatusCode.InternalServerError,
-                            "Incoming payment already seen"
-                        )
-                    }
-                    if 
(CryptoUtil.checkValidEddsaPublicKey(it.unstructuredRemittanceInformation)) {
-                        TalerIncomingPaymentEntity.new {
-                            payment = it
-                            valid = true
-                        }
-                    } else {
-                        TalerIncomingPaymentEntity.new {
-                            payment = it
-                            valid = false
-                        }
-                    }
-                }
-                /**
-                 * Search for fresh OUTGOING transactions acknowledged by the 
bank.  As well
-                 * searching only for BOOKed transactions, even though status 
changes should
-                 * be really unexpected here.
-                 */
-                val latestOutgoingPaymentId = 
TalerRequestedPaymentEntity.getLast()
-                RawBankTransactionEntity.find {
-                    /** Those that came after the last processed payment */
-                    RawBankTransactionsTable.id greater 
latestOutgoingPaymentId and
-                            /** Those involving the exchange bank account */
-                            (RawBankTransactionsTable.bankAccount eq 
subscriberAccount.id.value) and
-                            /** Those that are outgoing */
-                            (RawBankTransactionsTable.transactionType eq 
"DBIT")
-                }.forEach {
-                    if (paymentFailed(it)) {
-                        logger.error("Bank didn't accept one payment from the 
exchange")
-                        throw NexusError(
-                            HttpStatusCode.InternalServerError,
-                            "Bank didn't accept one payment from the exchange"
-                        )
-                    }
-                    if (duplicatePayment(it)) {
-                        logger.warn("Incomint payment already seen")
-                        throw NexusError(
-                            HttpStatusCode.InternalServerError,
-                            "Outgoing payment already seen"
-                        )
-                    }
-                    var talerRequested = TalerRequestedPaymentEntity.find {
-                        TalerRequestedPayments.wtid eq 
it.unstructuredRemittanceInformation
-                    }.firstOrNull() ?: throw NexusError(
-                        HttpStatusCode.InternalServerError,
-                        "Unrecognized fresh outgoing payment met (subject: 
${it.unstructuredRemittanceInformation})."
-                    )
-                    talerRequested.rawConfirmed = it
-                }
-            }
-
-            call.respondText (
-                "New raw payments Taler-processed",
-                ContentType.Text.Plain,
-                HttpStatusCode.OK
-            )
-            return@post
-        }
-        /** Responds only with the payments that the EXCHANGE made.  Typically 
to
-         * merchants but possibly to refund invalid incoming payments.  A 
payment is
-         * counted only if was once confirmed by the bank.
-         */
-        app.get("/taler/history/outgoing") {
-            /* sanitize URL arguments */
-            val subscriberId = transaction { 
authenticateRequest(call.request.headers["Authorization"]).id.value }
-            val delta: Int = expectInt(call.expectUrlParameter("delta"))
-            val start: Long = 
handleStartArgument(call.request.queryParameters["start"], delta)
-            val startCmpOp = getComparisonOperator(delta, start, 
TalerRequestedPayments)
-            /* retrieve database elements */
-            val history = TalerOutgoingHistory()
-            transaction {
-                /** Retrieve all the outgoing payments from the _clean Taler 
outgoing table_ */
-                val subscriberBankAccount = 
getBankAccountsFromNexusUserId(subscriberId).first()
-                val reqPayments = TalerRequestedPaymentEntity.find {
-                    TalerRequestedPayments.rawConfirmed.isNotNull() and 
startCmpOp
-                }.orderTaler(delta)
-                if (reqPayments.isNotEmpty()) {
-                    reqPayments.subList(0, min(abs(delta), 
reqPayments.size)).forEach {
-                        history.outgoing_transactions.add(
-                            TalerOutgoingBankTransaction(
-                                row_id = it.id.value,
-                                amount = it.amount,
-                                wtid = it.wtid,
-                                date = 
GnunetTimestamp(it.rawConfirmed?.bookingDate?.div(1000) ?: throw NexusError(
-                                    HttpStatusCode.InternalServerError, "Null 
value met after check, VERY strange.")),
-                                credit_account = it.creditAccount,
-                                debit_account = 
buildPaytoUri(subscriberBankAccount.iban, subscriberBankAccount.bankCode),
-                                exchange_base_url = 
"FIXME-to-request-along-subscriber-registration"
-                            )
-                        )
-                    }
-                }
-            }
-            call.respond(
-                HttpStatusCode.OK,
-                TextContent(customConverter(history), 
ContentType.Application.Json)
-            )
-            return@get
-        }
-        /** Responds only with the valid incoming payments */
-        app.get("/taler/history/incoming") {
-            val exchangeId = transaction { 
authenticateRequest(call.request.headers["Authorization"]).id.value }
-            val delta: Int = expectInt(call.expectUrlParameter("delta"))
-            val start: Long = 
handleStartArgument(call.request.queryParameters["start"], delta)
-            val history = TalerIncomingHistory()
-            val startCmpOp = getComparisonOperator(delta, start, 
TalerIncomingPayments)
-            transaction {
-                /**
-                 * Below, the test harness creates the exchange's bank account
-                 * object based on the payto:// given as the funds receiver.
-                 *
-                 * This is needed because nexus takes this information from the
-                 * bank - normally - but tests are currently avoiding any 
interaction
-                 * with banks or sandboxes.
-                 */
-                if (!isProduction()) {
-                    val EXCHANGE_BANKACCOUNT_ID = "exchange-bankaccount-id"
-                    if (BankAccountEntity.findById(EXCHANGE_BANKACCOUNT_ID) == 
null) {
-                        val newBankAccount = BankAccountEntity.new(id = 
EXCHANGE_BANKACCOUNT_ID) {
-                            accountHolder = "Test Exchange"
-                            iban = "42"
-                            bankCode = "localhost"
-                        }
-                        val nexusUser = extractNexusUser(exchangeId)
-                        BankAccountMapEntity.new {
-                            bankAccount = newBankAccount
-                            ebicsSubscriber = getEbicsTransport(exchangeId)
-                            this.nexusUser = nexusUser
-                        }
-                    }
-                }
-                val exchangeBankAccount = 
getBankAccountsFromNexusUserId(exchangeId).first()
-                val orderedPayments = TalerIncomingPaymentEntity.find {
-                    TalerIncomingPayments.valid eq true and startCmpOp
-                }.orderTaler(delta)
-                if (orderedPayments.isNotEmpty()) {
-                    orderedPayments.subList(0, min(abs(delta), 
orderedPayments.size)).forEach {
-                        history.incoming_transactions.add(
-                            TalerIncomingBankTransaction(
-                                date = GnunetTimestamp(it.payment.bookingDate 
/ 1000),
-                                row_id = it.id.value,
-                                amount = 
"${it.payment.currency}:${it.payment.amount}",
-                                reserve_pub = 
it.payment.unstructuredRemittanceInformation,
-                                credit_account = buildPaytoUri(
-                                    it.payment.bankAccount.accountHolder,
-                                    it.payment.bankAccount.iban,
-                                    it.payment.bankAccount.bankCode
-                                ),
-                                debit_account = buildPaytoUri(
-                                    it.payment.counterpartName,
-                                    it.payment.counterpartIban,
-                                    it.payment.counterpartBic
-                                )
-                            )
-                        )
-                    }
-                }
-            }
-            call.respond(TextContent(customConverter(history), 
ContentType.Application.Json))
-            return@get
-        }
-    }
-}
\ No newline at end of file
+//class Taler(app: Route) {
+//
+//    /** Payment initiating data structures: one endpoint 
"$BASE_URL/transfer". */
+//    private data class TalerTransferRequest(
+//        val request_uid: String,
+//        val amount: String,
+//        val exchange_base_url: String,
+//        val wtid: String,
+//        val credit_account: String
+//    )
+//    private data class TalerTransferResponse(
+//        // point in time when the nexus put the payment instruction into the 
database.
+//        val timestamp: GnunetTimestamp,
+//        val row_id: Long
+//    )
+//
+//    /** History accounting data structures */
+//    private data class TalerIncomingBankTransaction(
+//        val row_id: Long,
+//        val date: GnunetTimestamp, // timestamp
+//        val amount: String,
+//        val credit_account: String, // payto form,
+//        val debit_account: String,
+//        val reserve_pub: String
+//    )
+//    private data class TalerIncomingHistory(
+//        var incoming_transactions: MutableList<TalerIncomingBankTransaction> 
= mutableListOf()
+//    )
+//    private data class TalerOutgoingBankTransaction(
+//        val row_id: Long,
+//        val date: GnunetTimestamp, // timestamp
+//        val amount: String,
+//        val credit_account: String, // payto form,
+//        val debit_account: String,
+//        val wtid: String,
+//        val exchange_base_url: String
+//    )
+//    private data class TalerOutgoingHistory(
+//        var outgoing_transactions: MutableList<TalerOutgoingBankTransaction> 
= mutableListOf()
+//    )
+//
+//    /** Test APIs' data structures. */
+//    private data class TalerAdminAddIncoming(
+//        val amount: String,
+//        val reserve_pub: String,
+//        /**
+//         * This account is the one giving money to the exchange.  It doesn't
+//         * have to be 'created' as it might (and normally is) simply be a 
payto://
+//         * address pointing to a bank account hosted in a different financial
+//         * institution.
+//         */
+//        val debit_account: String
+//    )
+//
+//    private data class GnunetTimestamp(
+//        val t_ms: Long
+//    )
+//    private data class TalerAddIncomingResponse(
+//        val timestamp: GnunetTimestamp,
+//        val row_id: Long
+//    )
+//
+//    /**
+//     * Helper data structures.
+//     */
+//    data class Payto(
+//        val name: String = "NOTGIVEN",
+//        val iban: String,
+//        val bic: String = "NOTGIVEN"
+//    )
+//    /**
+//     * Helper functions
+//     */
+//    fun parsePayto(paytoUri: String): Payto {
+//        /**
+//         * First try to parse a "iban"-type payto URI.  If that fails,
+//         * then assume a test is being run under the "x-taler-bank" type.
+//         * If that one fails too, throw exception.
+//         *
+//         * Note: since the Nexus doesn't have the notion of "x-taler-bank",
+//         * such URIs must yield a iban-compatible tuple of values.  
Therefore,
+//         * the plain bank account number maps to a "iban", and the <bank 
hostname>
+//         * maps to a "bic".
+//         */
+//
+//
+//        /**
+//         * payto://iban/BIC?/IBAN?name=<name>
+//         * payto://x-taler-bank/<bank hostname>/<plain account number>
+//         */
+//
+//        val ibanMatch = 
Regex("payto://iban/([A-Z0-9]+/)?([A-Z0-9]+)\\?name=(\\w+)").find(paytoUri)
+//        if (ibanMatch != null) {
+//            val (bic, iban, name) = ibanMatch.destructured
+//            return Payto(name, iban, bic.replace("/", ""))
+//        }
+//        val xTalerBankMatch = 
Regex("payto://x-taler-bank/localhost/([0-9]+)").find(paytoUri)
+//        if (xTalerBankMatch != null) {
+//            val xTalerBankAcctNo = xTalerBankMatch.destructured.component1()
+//            return Payto("Taler Exchange", xTalerBankAcctNo, "localhost")
+//        }
+//
+//        throw NexusError(HttpStatusCode.BadRequest, "invalid payto URI 
($paytoUri)")
+//    }
+//
+//    /** Sort query results in descending order for negative deltas, and 
ascending otherwise.  */
+//    private fun <T : Entity<Long>> SizedIterable<T>.orderTaler(delta: Int): 
List<T> {
+//        return if (delta < 0) {
+//            this.sortedByDescending { it.id }
+//        } else {
+//            this.sortedBy { it.id }
+//        }
+//    }
+//
+//    /**
+//     * NOTE: those payto-builders default all to the x-taler-bank transport.
+//     * A mechanism to easily switch transport is needed, as production needs
+//     * 'iban'.
+//     */
+//    private fun buildPaytoUri(name: String, iban: String, bic: String): 
String {
+//        return "payto://x-taler-bank/localhost/$iban"
+//    }
+//    private fun buildPaytoUri(iban: String, bic: String): String {
+//        return "payto://x-taler-bank/localhost/$iban"
+//    }
+//
+//    /** Builds the comparison operator for history entries based on the sign 
of 'delta'  */
+//    private fun getComparisonOperator(delta: Int, start: Long, table: 
IdTable<Long>): Op<Boolean> {
+//        return if (delta < 0) {
+//            Expression.build {
+//                table.id less start
+//            }
+//        } else {
+//            Expression.build {
+//                table.id greater start
+//            }
+//        }
+//    }
+//    /** Helper handling 'start' being optional and its dependence on 
'delta'.  */
+//    private fun handleStartArgument(start: String?, delta: Int): Long {
+//        return expectLong(start) ?: if (delta >= 0) {
+//            /**
+//             * Using -1 as the smallest value, as some DBMS might use 0 and 
some
+//             * others might use 1 as the smallest row id.
+//             */
+//            -1
+//        } else {
+//            /**
+//             * NOTE: the database currently enforces there MAX_VALUE is 
always
+//             * strictly greater than any row's id in the database.  In fact, 
the
+//             * database throws exception whenever a new row is going to 
occupy
+//             * the MAX_VALUE with its id.
+//             */
+//            Long.MAX_VALUE
+//        }
+//    }
+//
+//    /**
+//     * The Taler layer cannot rely on the ktor-internal 
JSON-converter/responder,
+//     * because this one adds a "charset" extra information in the 
Content-Type header
+//     * that makes the GNUnet JSON parser unhappy.
+//     *
+//     * The workaround is to explicitly convert the 'data class'-object into 
a JSON
+//     * string (what this function does), and use the simpler respondText 
method.
+//     */
+//    private fun customConverter(body: Any): String {
+//        return jacksonObjectMapper().writeValueAsString(body)
+//    }
+//
+//    /**
+//     * This function indicates whether a payment in the raw table was 
already reported
+//     * by some other EBICS message.  It works for both incoming and outgoing 
payments.
+//     * Basically, it tries to match all the relevant details with those from 
the records
+//     * that are already stored in the local "taler" database.
+//     *
+//     * @param entry a new raw payment to be checked.
+//     * @return true if the payment was already "seen" by the Taler layer, 
false otherwise.
+//     */
+//    private fun duplicatePayment(entry: RawBankTransactionEntity): Boolean {
+//        return false
+//    }
+//
+//    /**
+//     * This function checks whether the bank didn't accept one exchange's 
payment initiation.
+//     *
+//     * @param entry the raw entry to check
+//     * @return true if the payment failed, false if it was successful.
+//     */
+//    private fun paymentFailed(entry: RawBankTransactionEntity): Boolean {
+//        return false
+//    }
+//
+//    /** Attach Taler endpoints to the main Web server */
+//
+//    init {
+//        app.get("/taler") {
+//            call.respondText("Taler Gateway Hello\n", 
ContentType.Text.Plain, HttpStatusCode.OK)
+//            return@get
+//        }
+//        app.post("/taler/transfer") {
+//            val exchangeId = transaction { 
authenticateRequest(call.request.headers["Authorization"]).id.value }
+//            val transferRequest = call.receive<TalerTransferRequest>()
+//            val amountObj = parseAmount(transferRequest.amount)
+//            val creditorObj = parsePayto(transferRequest.credit_account)
+//            val opaque_row_id = transaction {
+//                val creditorData = parsePayto(transferRequest.credit_account)
+//                val exchangeBankAccount = 
getBankAccountsFromNexusUserId(exchangeId).first()
+//                val nexusUser = extractNexusUser(exchangeId)
+//                /** Checking the UID has the desired characteristics */
+//                TalerRequestedPaymentEntity.find {
+//                    TalerRequestedPayments.requestUId eq 
transferRequest.request_uid
+//                }.forEach {
+//                    if (
+//                        (it.amount != transferRequest.amount) or
+//                        (it.creditAccount != 
transferRequest.exchange_base_url) or
+//                        (it.wtid != transferRequest.wtid)
+//                    ) {
+//                        throw NexusError(
+//                            HttpStatusCode.Conflict,
+//                            "This uid (${transferRequest.request_uid}) 
belongs to a different payment already"
+//                        )
+//                    }
+//                }
+//                val pain001 = addPreparedPayment(
+//                    Pain001Data(
+//                        creditorIban = creditorData.iban,
+//                        creditorBic = creditorData.bic,
+//                        creditorName = creditorData.name,
+//                        subject = transferRequest.wtid,
+//                        sum = amountObj.amount,
+//                        currency = amountObj.currency,
+//                        debitorAccount = exchangeBankAccount.id.value
+//                    ),
+//                    nexusUser
+//                )
+//                val rawEbics = if (!isProduction()) {
+//                    RawBankTransactionEntity.new {
+//                        sourceFileName = "test"
+//                        unstructuredRemittanceInformation = 
transferRequest.wtid
+//                        transactionType = "DBIT"
+//                        currency = amountObj.currency
+//                        this.amount = amountObj.amount.toPlainString()
+//                        counterpartBic = creditorObj.bic
+//                        counterpartIban = creditorObj.iban
+//                        counterpartName = creditorObj.name
+//                        bankAccount = exchangeBankAccount
+//                        bookingDate = DateTime.now().millis
+//                        this.nexusUser = nexusUser
+//                        status = "BOOK"
+//                    }
+//                } else null
+//
+//                val row = TalerRequestedPaymentEntity.new {
+//                    preparedPayment = pain001 // not really used/needed, 
just here to silence warnings
+//                    exchangeBaseUrl = transferRequest.exchange_base_url
+//                    requestUId = transferRequest.request_uid
+//                    amount = transferRequest.amount
+//                    wtid = transferRequest.wtid
+//                    creditAccount = transferRequest.credit_account
+//                    rawConfirmed = rawEbics
+//                }
+//
+//                row.id.value
+//            }
+//            call.respond(
+//                HttpStatusCode.OK,
+//                TextContent(
+//                    customConverter(
+//                        TalerTransferResponse(
+//                            /**
+//                             * Normally should point to the next round where 
the background
+//                             * routine will send new PAIN.001 data to the 
bank; work in progress..
+//                             */
+//                            timestamp = 
GnunetTimestamp(DateTime.now().millis),
+//                            row_id = opaque_row_id
+//                        )
+//                    ),
+//                    ContentType.Application.Json
+//                )
+//            )
+//            return@post
+//        }
+//        /** Test-API that creates one new payment addressed to the exchange. 
 */
+//        app.post("/taler/admin/add-incoming") {
+//            val exchangeId = transaction { 
authenticateRequest(call.request.headers["Authorization"]).id.value }
+//            val addIncomingData = call.receive<TalerAdminAddIncoming>()
+//            val debtor = parsePayto(addIncomingData.debit_account)
+//            val amount = parseAmount(addIncomingData.amount)
+//            val (bookingDate, opaque_row_id) = transaction {
+//                val exchangeBankAccount = 
getBankAccountsFromNexusUserId(exchangeId).first()
+//                val rawPayment = RawBankTransactionEntity.new {
+//                    sourceFileName = "test"
+//                    unstructuredRemittanceInformation = 
addIncomingData.reserve_pub
+//                    transactionType = "CRDT"
+//                    currency = amount.currency
+//                    this.amount = amount.amount.toPlainString()
+//                    counterpartBic = debtor.bic
+//                    counterpartName = debtor.name
+//                    counterpartIban = debtor.iban
+//                    bookingDate = DateTime.now().millis
+//                    status = "BOOK"
+//                    nexusUser = extractNexusUser(exchangeId)
+//                    bankAccount = exchangeBankAccount
+//                }
+//                /** This payment is "valid by default" and will be returned
+//                 * as soon as the exchange will ask for new payments.  */
+//                val row = TalerIncomingPaymentEntity.new {
+//                    payment = rawPayment
+//                    valid = true
+//                }
+//                Pair(rawPayment.bookingDate, row.id.value)
+//            }
+//            call.respond(
+//                TextContent(
+//                    customConverter(
+//                        TalerAddIncomingResponse(
+//                            timestamp = GnunetTimestamp(bookingDate/ 1000),
+//                            row_id = opaque_row_id
+//                        )
+//                    ),
+//                ContentType.Application.Json
+//                )
+//            )
+//            return@post
+//        }
+//
+//        /** This endpoint triggers the refunding of invalid payments.  
'Refunding'
+//         * in this context means that nexus _prepares_ the payment 
instruction and
+//         * places it into a further table.  Eventually, another routine will 
perform
+//         * all the prepared payments.  */
+//        
app.post("/ebics/taler/{id}/accounts/{acctid}/refund-invalid-payments") {
+//            val userId = transaction { 
authenticateRequest(call.request.headers["Authorization"]).id.value }
+//            val nexusUser = getNexusUser(userId)
+//            val callerBankAccount = expectNonNull(call.parameters["acctid"])
+//            transaction {
+//                val bankAccount = getBankAccount(
+//                    userId,
+//                    callerBankAccount
+//                )
+//                TalerIncomingPaymentEntity.find {
+//                    TalerIncomingPayments.refunded eq false and 
(TalerIncomingPayments.valid eq false)
+//                }.forEach {
+//                    addPreparedPayment(
+//                        Pain001Data(
+//                            creditorName = it.payment.counterpartName,
+//                            creditorIban = it.payment.counterpartIban,
+//                            creditorBic = it.payment.counterpartBic,
+//                            sum = calculateRefund(it.payment.amount),
+//                            subject = "Taler refund",
+//                            currency = it.payment.currency,
+//                            debitorAccount = callerBankAccount
+//                        ),
+//                        nexusUser
+//                    )
+//                    it.refunded = true
+//                }
+//            }
+//            return@post
+//        }
+//
+//        /** This endpoint triggers the examination of raw incoming payments 
aimed
+//         * at separating the good payments (those that will lead to a new 
reserve
+//         * being created), from the invalid payments (those with a invalid 
subject
+//         * that will soon be refunded.)  Recently, the examination of raw 
OUTGOING
+//         * payment was added as well.
+//         */
+//        app.post("/ebics/taler/{id}/crunch-raw-transactions") {
+//            val id = ensureNonNull(call.parameters["id"])
+//            // first find highest ID value of already processed rows.
+//            transaction {
+//                val subscriberAccount = 
getBankAccountsFromNexusUserId(id).first()
+//                /**
+//                 * Search for fresh incoming payments in the raw table, and 
making pointers
+//                 * from the Taler incoming payments table to the found fresh 
(and valid!) payments.
+//                 */
+//                val latestIncomingPaymentId: Long = 
TalerIncomingPaymentEntity.getLast()
+//                RawBankTransactionEntity.find {
+//                    /** Those with exchange bank account involved */
+//                    RawBankTransactionsTable.bankAccount eq 
subscriberAccount.id.value and
+//                            /** Those that are incoming */
+//                            (RawBankTransactionsTable.transactionType eq 
"CRDT") and
+//                            /** Those that are booked */
+//                            (RawBankTransactionsTable.status eq "BOOK") and
+//                            /** Those that came later than the latest 
processed payment */
+//                            
(RawBankTransactionsTable.id.greater(latestIncomingPaymentId))
+//
+//                }.forEach {
+//                    if (duplicatePayment(it)) {
+//                        logger.warn("Incomint payment already seen")
+//                        throw NexusError(
+//                            HttpStatusCode.InternalServerError,
+//                            "Incoming payment already seen"
+//                        )
+//                    }
+//                    if 
(CryptoUtil.checkValidEddsaPublicKey(it.unstructuredRemittanceInformation)) {
+//                        TalerIncomingPaymentEntity.new {
+//                            payment = it
+//                            valid = true
+//                        }
+//                    } else {
+//                        TalerIncomingPaymentEntity.new {
+//                            payment = it
+//                            valid = false
+//                        }
+//                    }
+//                }
+//                /**
+//                 * Search for fresh OUTGOING transactions acknowledged by 
the bank.  As well
+//                 * searching only for BOOKed transactions, even though 
status changes should
+//                 * be really unexpected here.
+//                 */
+//                val latestOutgoingPaymentId = 
TalerRequestedPaymentEntity.getLast()
+//                RawBankTransactionEntity.find {
+//                    /** Those that came after the last processed payment */
+//                    RawBankTransactionsTable.id greater 
latestOutgoingPaymentId and
+//                            /** Those involving the exchange bank account */
+//                            (RawBankTransactionsTable.bankAccount eq 
subscriberAccount.id.value) and
+//                            /** Those that are outgoing */
+//                            (RawBankTransactionsTable.transactionType eq 
"DBIT")
+//                }.forEach {
+//                    if (paymentFailed(it)) {
+//                        logger.error("Bank didn't accept one payment from 
the exchange")
+//                        throw NexusError(
+//                            HttpStatusCode.InternalServerError,
+//                            "Bank didn't accept one payment from the 
exchange"
+//                        )
+//                    }
+//                    if (duplicatePayment(it)) {
+//                        logger.warn("Incomint payment already seen")
+//                        throw NexusError(
+//                            HttpStatusCode.InternalServerError,
+//                            "Outgoing payment already seen"
+//                        )
+//                    }
+//                    var talerRequested = TalerRequestedPaymentEntity.find {
+//                        TalerRequestedPayments.wtid eq 
it.unstructuredRemittanceInformation
+//                    }.firstOrNull() ?: throw NexusError(
+//                        HttpStatusCode.InternalServerError,
+//                        "Unrecognized fresh outgoing payment met (subject: 
${it.unstructuredRemittanceInformation})."
+//                    )
+//                    talerRequested.rawConfirmed = it
+//                }
+//            }
+//
+//            call.respondText (
+//                "New raw payments Taler-processed",
+//                ContentType.Text.Plain,
+//                HttpStatusCode.OK
+//            )
+//            return@post
+//        }
+//        /** Responds only with the payments that the EXCHANGE made.  
Typically to
+//         * merchants but possibly to refund invalid incoming payments.  A 
payment is
+//         * counted only if was once confirmed by the bank.
+//         */
+//        app.get("/taler/history/outgoing") {
+//            /* sanitize URL arguments */
+//            val subscriberId = transaction { 
authenticateRequest(call.request.headers["Authorization"]).id.value }
+//            val delta: Int = expectInt(call.expectUrlParameter("delta"))
+//            val start: Long = 
handleStartArgument(call.request.queryParameters["start"], delta)
+//            val startCmpOp = getComparisonOperator(delta, start, 
TalerRequestedPayments)
+//            /* retrieve database elements */
+//            val history = TalerOutgoingHistory()
+//            transaction {
+//                /** Retrieve all the outgoing payments from the _clean Taler 
outgoing table_ */
+//                val subscriberBankAccount = 
getBankAccountsFromNexusUserId(subscriberId).first()
+//                val reqPayments = TalerRequestedPaymentEntity.find {
+//                    TalerRequestedPayments.rawConfirmed.isNotNull() and 
startCmpOp
+//                }.orderTaler(delta)
+//                if (reqPayments.isNotEmpty()) {
+//                    reqPayments.subList(0, min(abs(delta), 
reqPayments.size)).forEach {
+//                        history.outgoing_transactions.add(
+//                            TalerOutgoingBankTransaction(
+//                                row_id = it.id.value,
+//                                amount = it.amount,
+//                                wtid = it.wtid,
+//                                date = 
GnunetTimestamp(it.rawConfirmed?.bookingDate?.div(1000) ?: throw NexusError(
+//                                    HttpStatusCode.InternalServerError, 
"Null value met after check, VERY strange.")),
+//                                credit_account = it.creditAccount,
+//                                debit_account = 
buildPaytoUri(subscriberBankAccount.iban, subscriberBankAccount.bankCode),
+//                                exchange_base_url = 
"FIXME-to-request-along-subscriber-registration"
+//                            )
+//                        )
+//                    }
+//                }
+//            }
+//            call.respond(
+//                HttpStatusCode.OK,
+//                TextContent(customConverter(history), 
ContentType.Application.Json)
+//            )
+//            return@get
+//        }
+//        /** Responds only with the valid incoming payments */
+//        app.get("/taler/history/incoming") {
+//            val exchangeId = transaction { 
authenticateRequest(call.request.headers["Authorization"]).id.value }
+//            val delta: Int = expectInt(call.expectUrlParameter("delta"))
+//            val start: Long = 
handleStartArgument(call.request.queryParameters["start"], delta)
+//            val history = TalerIncomingHistory()
+//            val startCmpOp = getComparisonOperator(delta, start, 
TalerIncomingPayments)
+//            transaction {
+//                /**
+//                 * Below, the test harness creates the exchange's bank 
account
+//                 * object based on the payto:// given as the funds receiver.
+//                 *
+//                 * This is needed because nexus takes this information from 
the
+//                 * bank - normally - but tests are currently avoiding any 
interaction
+//                 * with banks or sandboxes.
+//                 */
+//                if (!isProduction()) {
+//                    throw Error("currently not implemented")
+//                    val EXCHANGE_BANKACCOUNT_ID = "exchange-bankaccount-id"
+//                    if (BankAccountEntity.findById(EXCHANGE_BANKACCOUNT_ID) 
== null) {
+//                        val newBankAccount = BankAccountEntity.new(id = 
EXCHANGE_BANKACCOUNT_ID) {
+//                            accountHolder = "Test Exchange"
+//                            iban = "42"
+//                            bankCode = "localhost"
+//                        }
+//                        val nexusUser = extractNexusUser(exchangeId)
+//                        BankAccountMapEntity.new {
+//                            bankAccount = newBankAccount
+//                            ebicsSubscriber = getEbicsTransport(exchangeId)
+//                            this.nexusUser = nexusUser
+//                        }
+//                    }
+//                }
+//                val orderedPayments = TalerIncomingPaymentEntity.find {
+//                    TalerIncomingPayments.valid eq true and startCmpOp
+//                }.orderTaler(delta)
+//                if (orderedPayments.isNotEmpty()) {
+//                    orderedPayments.subList(0, min(abs(delta), 
orderedPayments.size)).forEach {
+//                        history.incoming_transactions.add(
+//                            TalerIncomingBankTransaction(
+//                                date = 
GnunetTimestamp(it.payment.bookingDate / 1000),
+//                                row_id = it.id.value,
+//                                amount = 
"${it.payment.currency}:${it.payment.amount}",
+//                                reserve_pub = 
it.payment.unstructuredRemittanceInformation,
+//                                credit_account = buildPaytoUri(
+//                                    it.payment.bankAccount.accountHolder,
+//                                    it.payment.bankAccount.iban,
+//                                    it.payment.bankAccount.bankCode
+//                                ),
+//                                debit_account = buildPaytoUri(
+//                                    it.payment.counterpartName,
+//                                    it.payment.counterpartIban,
+//                                    it.payment.counterpartBic
+//                                )
+//                            )
+//                        )
+//                    }
+//                }
+//            }
+//            call.respond(TextContent(customConverter(history), 
ContentType.Application.Json))
+//            return@get
+//        }
+//    }
+//}
\ No newline at end of file
diff --git a/nexus/src/test/kotlin/authentication.kt 
b/nexus/src/test/kotlin/authentication.kt
index 602f990..bcf3499 100644
--- a/nexus/src/test/kotlin/authentication.kt
+++ b/nexus/src/test/kotlin/authentication.kt
@@ -1,17 +1,12 @@
 package tech.libeufin.nexus
 
-import org.jetbrains.exposed.sql.Database
-import org.jetbrains.exposed.sql.SchemaUtils
-import org.jetbrains.exposed.sql.transactions.transaction
 import org.junit.Test
 import junit.framework.TestCase.assertEquals
-import tech.libeufin.util.CryptoUtil
-import javax.sql.rowset.serial.SerialBlob
 
 class AuthenticationTest {
     @Test
     fun basicAuthHeaderTest() {
-        val pass = extractUserAndHashedPassword(
+        val pass = extractUserAndPassword(
             "Basic dXNlcm5hbWU6cGFzc3dvcmQ="
         ).second
         assertEquals("password", pass);
diff --git a/nexus/src/test/kotlin/taler.kt b/nexus/src/test/kotlin/taler.kt
index df49493..8cb59ad 100644
--- a/nexus/src/test/kotlin/taler.kt
+++ b/nexus/src/test/kotlin/taler.kt
@@ -10,26 +10,26 @@ import tech.libeufin.util.parseAmount
 
 class TalerTest {
 
-    @InternalAPI
-    val taler = Taler(Route(null, RootRouteSelector("unused")))
-
-    @InternalAPI
-    @Test
-    fun paytoParserTest() {
-        val payto = taler.parsePayto("payto://iban/ABC/XYZ?name=foo")
-        assert(payto.bic == "ABC" && payto.iban == "XYZ" && payto.name == 
"foo")
-        val paytoNoBic = taler.parsePayto("payto://iban/XYZ?name=foo")
-        assert(paytoNoBic.bic == "" && paytoNoBic.iban == "XYZ" && 
paytoNoBic.name == "foo")
-    }
-
-    @InternalAPI
-    @Test
-    fun amountParserTest() {
-        val amount = parseAmount("EUR:1")
-        assert(amount.currency == "EUR" && amount.amount.equals(BigDecimal(1)))
-        val amount299 = parseAmount("EUR:2.99")
-        assert(amount299.amount.compareTo(Amount("2.99")) == 0)
-        val amount25 = parseAmount("EUR:2.5")
-        assert(amount25.amount.compareTo(Amount("2.5")) == 0)
-    }
+//    @InternalAPI
+//    val taler = Taler(Route(null, RootRouteSelector("unused")))
+//
+//    @InternalAPI
+//    @Test
+//    fun paytoParserTest() {
+//        val payto = taler.parsePayto("payto://iban/ABC/XYZ?name=foo")
+//        assert(payto.bic == "ABC" && payto.iban == "XYZ" && payto.name == 
"foo")
+//        val paytoNoBic = taler.parsePayto("payto://iban/XYZ?name=foo")
+//        assert(paytoNoBic.bic == "" && paytoNoBic.iban == "XYZ" && 
paytoNoBic.name == "foo")
+//    }
+//
+//    @InternalAPI
+//    @Test
+//    fun amountParserTest() {
+//        val amount = parseAmount("EUR:1")
+//        assert(amount.currency == "EUR" && 
amount.amount.equals(BigDecimal(1)))
+//        val amount299 = parseAmount("EUR:2.99")
+//        assert(amount299.amount.compareTo(Amount("2.99")) == 0)
+//        val amount25 = parseAmount("EUR:2.5")
+//        assert(amount25.amount.compareTo(Amount("2.5")) == 0)
+//    }
 }
\ No newline at end of file
diff --git a/util/src/main/kotlin/Ebics.kt b/util/src/main/kotlin/Ebics.kt
index 690a122..589edbc 100644
--- a/util/src/main/kotlin/Ebics.kt
+++ b/util/src/main/kotlin/Ebics.kt
@@ -488,6 +488,14 @@ fun makeEbicsHEVRequest(subscriberDetails: 
EbicsClientSubscriberDetails): String
     return XMLUtil.convertDomToString(doc)
 }
 
+fun makeEbicsHEVRequestRaw(hostID: String): String {
+    val req = HEVRequest().apply {
+        hostId = hostId
+    }
+    val doc = XMLUtil.convertJaxbToDocument(req)
+    return XMLUtil.convertDomToString(doc)
+}
+
 fun parseEbicsHEVResponse(respStr: String): EbicsHevDetails {
     val resp = try {
         XMLUtil.convertStringToJaxb<HEVResponse>(respStr)

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



reply via email to

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