gnunet-svn
[Top][All Lists]
Advanced

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

[libeufin] branch master updated (b6e9340 -> 13bfc9f)


From: gnunet
Subject: [libeufin] branch master updated (b6e9340 -> 13bfc9f)
Date: Wed, 29 Apr 2020 21:44:39 +0200

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

marcello pushed a change to branch master
in repository libeufin.

    from b6e9340  Integration test.
     new 46d92a7  Remove notion of "bank customer" from Sandbox.
     new 52222ba  remove empty lines
     new 8287005  Integration test.
     new f49ae7d  Integration test.
     new 793875b  More "history logic" to the Sandbox.
     new 13bfc9f  Abstracting on "bank account".

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


Summary of changes:
 integration-tests/test-ebics.py                    | 132 ++++++
 integration-tests/test.py                          |  61 ---
 nexus/src/main/kotlin/tech/libeufin/nexus/DB.kt    |  33 +-
 .../src/main/kotlin/tech/libeufin/nexus/Helpers.kt |  30 +-
 nexus/src/main/kotlin/tech/libeufin/nexus/JSON.kt  |  27 +-
 nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt  | 143 +++---
 nexus/src/main/kotlin/tech/libeufin/nexus/taler.kt |  31 +-
 nexus/src/test/kotlin/DbTest.kt                    |  53 ---
 nexus/src/test/kotlin/PainGeneration.kt            |  24 +-
 nexus/src/test/kotlin/XPathTest.kt                 |   4 +-
 .../src/main/kotlin/tech/libeufin/sandbox/DB.kt    | 124 ++---
 .../tech/libeufin/sandbox/EbicsProtocolBackend.kt  | 499 +++++++++++----------
 .../src/main/kotlin/tech/libeufin/sandbox/JSON.kt  | 130 +-----
 .../src/main/kotlin/tech/libeufin/sandbox/Main.kt  | 236 +++-------
 sandbox/src/test/kotlin/CamtGeneration.kt          |  45 --
 sandbox/src/test/kotlin/DbTest.kt                  | 137 ------
 util/src/main/kotlin/ebics_h004/EbicsResponse.kt   |   1 -
 util/src/main/kotlin/zip.kt                        |  26 +-
 18 files changed, 655 insertions(+), 1081 deletions(-)
 create mode 100755 integration-tests/test-ebics.py
 delete mode 100755 integration-tests/test.py
 delete mode 100644 nexus/src/test/kotlin/DbTest.kt
 delete mode 100644 sandbox/src/test/kotlin/CamtGeneration.kt
 delete mode 100644 sandbox/src/test/kotlin/DbTest.kt

diff --git a/integration-tests/test-ebics.py b/integration-tests/test-ebics.py
new file mode 100755
index 0000000..695f581
--- /dev/null
+++ b/integration-tests/test-ebics.py
@@ -0,0 +1,132 @@
+#!/usr/bin/env python3
+
+from requests import post, get
+
+# Steps implemented in this test.
+#
+# 1 Prepare the Sandbox to run the test.
+#  -> Make a EBICS host, and make a EBICS subscriber
+#     for the test runner.
+#
+# 2 Prepare the Nexus to run the test.
+#  -> Make a Nexus user, and make a EBICS transport
+#     entity associated with that user.
+#
+# 3 Upload keys from Nexus to the Bank (INI & HIA)
+# 4 Download key from the Bank (HPB) to the Nexus
+#
+# 5 Request history from the Nexus to the Bank (C53).
+# 6 Verify that history is empty.
+# 7 Issue a payment from Nexus (Prepare & trigger CCT)
+# 8 Request history again, from Nexus to Bank.
+# 9 Verify that previous payment shows up.
+
+
+# Nexus user details
+USERNAME="person"
+
+# EBICS details
+EBICS_URL="http://localhost:5000/ebicsweb";
+HOST_ID="HOST01"
+PARTNER_ID="PARTNER1"
+USER_ID="USER1"
+EBICS_VERSION = "H004"
+
+#0 Prepare Sandbox (make Ebics host & one subscriber)
+resp = post(
+    "http://localhost:5000/admin/ebics-host";,
+    json=dict(
+       hostID=HOST_ID,
+       ebicsVersion=EBICS_VERSION
+    )
+)
+
+assert(resp.status_code == 200)
+
+resp = post(
+    "http://localhost:5000/admin/ebics-subscriber";,
+    json=dict(
+        hostID=HOST_ID,
+       partnerID=PARTNER_ID,
+       userID=USER_ID
+    )
+)
+
+assert(resp.status_code == 200)
+
+#1 Create a Nexus user
+
+resp = post(
+    "http://localhost:5001/users/{}".format(USERNAME),
+    json=dict(
+       password="secret"
+    )
+)
+
+assert(resp.status_code == 200)
+
+#2 Create a EBICS user
+resp = post(
+    "http://localhost:5001/ebics/subscribers/{}".format(USERNAME),
+    json=dict(
+       ebicsURL=EBICS_URL,
+       hostID=HOST_ID,
+       partnerID=PARTNER_ID,
+       userID=USER_ID
+    )
+)
+
+assert(resp.status_code == 200)
+
+#3 Upload keys to the bank INI & HIA
+resp = post(
+    "http://localhost:5001/ebics/subscribers/{}/sendINI".format(USERNAME),
+    json=dict()
+)
+
+assert(resp.status_code == 200)
+
+resp = post(
+    "http://localhost:5001/ebics/subscribers/{}/sendHIA".format(USERNAME),
+    json=dict()
+)
+
+assert(resp.status_code == 200)
+
+#4 Download keys from the bank HPB
+resp = post(
+    "http://localhost:5001/ebics/subscribers/{}/sync".format(USERNAME),
+    json=dict()
+)
+
+assert(resp.status_code == 200)
+
+#5 Request history via EBICS
+resp = post(
+    
"http://localhost:5001/ebics/subscribers/{}/collect-transactions-c53".format(USERNAME),
+    json=dict()
+)
+
+assert(resp.status_code == 200)
+
+resp = get(
+    "http://localhost:5001/users/{}/history".format(USERNAME)
+)
+
+assert(
+    resp.status_code == 200 and \
+    len(resp.json().get("payments")) == 0
+)
+
+#6 Prepare a payment (via pure Nexus service)
+resp = post(
+    "http://localhost:5001/users/{}/prepare-payment".format(USERNAME),
+    json=dict()
+)
+
+assert(resp.status_code == 200)
+
+
+
+#7 Execute such payment via EBICS
+#8 Request history again via EBICS
diff --git a/integration-tests/test.py b/integration-tests/test.py
deleted file mode 100755
index 2bb4623..0000000
--- a/integration-tests/test.py
+++ /dev/null
@@ -1,61 +0,0 @@
-#!/usr/bin/env python3
-
-from requests import post, get
-
-#1 Create a Nexus user
-USERNAME="person"
-
-resp = post(
-    "http://localhost:5001/users/{}".format(USERNAME),
-    json=dict(
-       password="secret"
-    )
-)
-
-assert(resp.status_code == 200)
-
-#2 Create a EBICS user
-
-resp = post(
-    "http://localhost:5001/ebics/subscribers/{}".format(USERNAME),
-    json=dict(
-       ebicsURL="http://localhost:5000/ebicsweb";,
-       hostID="HOST01",
-       partnerID="PARTNER1",
-       userID="USER1"
-    )
-)
-
-assert(resp.status_code == 200)
-
-#3 Upload keys to the bank INI & HIA
-resp = post(
-    "http://localhost:5001/ebics/subscribers/{}/sendINI".format(USERNAME),
-    json=dict()
-)
-
-assert(resp.status_code == 200)
-
-resp = post(
-    "http://localhost:5001/ebics/subscribers/{}/sendHIA".format(USERNAME),
-    json=dict()
-)
-
-assert(resp.status_code == 200)
-
-#4 Download keys from the bank HPB
-resp = post(
-    "http://localhost:5001/ebics/subscribers/{}/sync".format(USERNAME),
-    json=dict()
-)
-
-assert(resp.status_code == 200)
-
-#5 Request history
-#6 Prepare a payment
-#7 Execute such payment via EBICS
-#8 Request history again
-#9 Exit
-
-# The Nexus should connect to the Sandbox for
-# these tests!
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/DB.kt 
b/nexus/src/main/kotlin/tech/libeufin/nexus/DB.kt
index 8eafd3a..342b6d9 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/DB.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/DB.kt
@@ -5,9 +5,6 @@ import org.jetbrains.exposed.dao.*
 import org.jetbrains.exposed.sql.*
 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.nullable
-import tech.libeufin.nexus.EbicsSubscribersTable.primaryKey
 import tech.libeufin.util.IntIdTableWithAmount
 import java.sql.Connection
 
@@ -86,7 +83,7 @@ class TalerIncomingPaymentEntity(id: EntityID<Long>) : 
LongEntity(id) {
  * CAMT message.
  */
 object RawBankTransactionsTable : LongIdTable() {
-    val nexusSubscriber = reference("subscriber", EbicsSubscribersTable)
+    val nexusUser = reference("nexusUser", NexusUsersTable)
     val sourceFileName = text("sourceFileName") /* ZIP entry's name */
     val unstructuredRemittanceInformation = 
text("unstructuredRemittanceInformation")
     val transactionType = text("transactionType") /* DBIT or CRDT */
@@ -114,15 +111,11 @@ class RawBankTransactionEntity(id: EntityID<Long>) : 
LongEntity(id) {
     var creditorIban by RawBankTransactionsTable.creditorIban
     var counterpartBic by RawBankTransactionsTable.counterpartBic
     var bookingDate by RawBankTransactionsTable.bookingDate
-    var nexusSubscriber by EbicsSubscriberEntity referencedOn 
RawBankTransactionsTable.nexusSubscriber
+    var nexusUser by NexusUserEntity referencedOn 
RawBankTransactionsTable.nexusUser
     var status by RawBankTransactionsTable.status
 }
-
 /**
- * NOTE: every column in this table corresponds to a particular
- * value described in the pain.001 official documentation; therefore
- * this table is not really suitable to hold custom data (like Taler-related,
- * for example)
+ * Represent a prepare payment.
  */
 object Pain001Table : IntIdTableWithAmount() {
     val msgId = long("msgId").uniqueIndex().autoIncrement()
@@ -130,23 +123,22 @@ object Pain001Table : IntIdTableWithAmount() {
     val fileDate = long("fileDate")
     val sum = amount("sum")
     val currency = varchar("currency", length = 3).default("EUR")
-    val debtorAccount = text("debtorAccount")
     val endToEndId = long("EndToEndId")
     val subject = text("subject")
     val creditorIban = text("creditorIban")
     val creditorBic = text("creditorBic")
     val creditorName = text("creditorName")
-
+    val debitorIban = text("debitorIban")
+    val debitorBic = text("debitorBic")
+    val debitorName = text("debitorName").nullable()
     /* Indicates whether the PAIN message was sent to the bank. */
     val submitted = bool("submitted").default(false)
-
     /* Indicates whether the bank didn't perform the payment: note that
-    * 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
-    */
+     * 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)
+    val nexusUser = reference("nexusUser", NexusUsersTable)
 }
-
 class Pain001Entity(id: EntityID<Int>) : IntEntity(id) {
     companion object : IntEntityClass<Pain001Entity>(Pain001Table)
     var msgId by Pain001Table.msgId
@@ -154,7 +146,9 @@ class Pain001Entity(id: EntityID<Int>) : IntEntity(id) {
     var date by Pain001Table.fileDate
     var sum by Pain001Table.sum
     var currency by Pain001Table.currency
-    var debtorAccount by Pain001Table.debtorAccount
+    var debitorIban by Pain001Table.debitorIban
+    var debitorBic by Pain001Table.debitorBic
+    var debitorName by Pain001Table.debitorName
     var endToEndId by Pain001Table.endToEndId
     var subject by Pain001Table.subject
     var creditorIban by Pain001Table.creditorIban
@@ -162,6 +156,7 @@ class Pain001Entity(id: EntityID<Int>) : IntEntity(id) {
     var creditorName by Pain001Table.creditorName
     var submitted by Pain001Table.submitted
     var invalid by Pain001Table.invalid
+    var nexusUser by NexusUserEntity referencedOn Pain001Table.nexusUser
 }
 
 /**
@@ -209,7 +204,7 @@ class EbicsSubscriberEntity(id: EntityID<Int>) : 
Entity<Int>(id) {
 }
 
 object NexusUsersTable : IdTable<String>() {
-    override val id = varchar("id", ID_MAX_LENGTH).entityId().primaryKey()
+    override val id = varchar("id", ID_MAX_LENGTH).entityId()
     val ebicsSubscriber = reference("ebicsSubscriber", 
EbicsSubscribersTable).nullable()
     val testSubscriber = reference("testSubscriber", 
EbicsSubscribersTable).nullable()
     val password = blob("password").nullable()
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/Helpers.kt 
b/nexus/src/main/kotlin/tech/libeufin/nexus/Helpers.kt
index 50f558e..6da439f 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/Helpers.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/Helpers.kt
@@ -164,6 +164,16 @@ fun createPain001document(pain001Entity: Pain001Entity): 
String {
      * we'll assign the SAME id (= the row id) to all the three aforementioned
      * PAIN id types.
      */
+    val debitorBankAccountLabel = transaction {
+        val debitorBankAcount = BankAccountEntity.find {
+            BankAccountsTable.iban eq pain001Entity.debitorIban and
+                    (BankAccountsTable.bankCode eq pain001Entity.debitorBic)
+        }.firstOrNull() ?: throw NexusError(
+            HttpStatusCode.NotFound,
+            "Please download bank accounts details first (HTD)"
+        )
+        debitorBankAcount.id.value
+    }
 
     val s = constructXml(indent = true) {
         root("Document") {
@@ -191,7 +201,7 @@ fun createPain001document(pain001Entity: Pain001Entity): 
String {
                         text(pain001Entity.sum.toString())
                     }
                     element("InitgPty/Nm") {
-                        text(pain001Entity.debtorAccount)
+                        text(debitorBankAccountLabel)
                     }
                 }
                 element("PmtInf") {
@@ -220,18 +230,13 @@ fun createPain001document(pain001Entity: Pain001Entity): 
String {
                         text(DateTime(dateMillis).toString("Y-MM-dd"))
                     }
                     element("Dbtr/Nm") {
-                        text(pain001Entity.debtorAccount)
+                        text(debitorBankAccountLabel)
                     }
                     element("DbtrAcct/Id/IBAN") {
-                        text(transaction {
-                            
BankAccountEntity.findById(pain001Entity.debtorAccount)?.iban ?: throw 
NexusError(HttpStatusCode.NotFound,"Debtor IBAN not found in database")
-                        })
+                        text(pain001Entity.debitorIban)
                     }
                     element("DbtrAgt/FinInstnId/BIC") {
-
-                        text(transaction {
-                            
BankAccountEntity.findById(pain001Entity.debtorAccount)?.bankCode ?: throw 
NexusError(HttpStatusCode.NotFound,"Debtor BIC not found in database")
-                        })
+                        text(pain001Entity.debitorBic)
                     }
                     element("ChrgBr") {
                         text("SLEV")
@@ -274,13 +279,15 @@ fun createPain001document(pain001Entity: Pain001Entity): 
String {
  * it will be the account whose money will pay the wire transfer being defined
  * by this pain document.
  */
-fun createPain001entity(entry: Pain001Data, debtorAccountId: String): 
Pain001Entity {
+fun createPain001entity(entry: Pain001Data, nexusUser: NexusUserEntity): 
Pain001Entity {
     val randomId = Random().nextLong()
     return transaction {
         Pain001Entity.new {
             subject = entry.subject
             sum = entry.sum
-            debtorAccount = debtorAccountId
+            debitorIban = entry.debitorIban
+            debitorBic = entry.debitorBic
+            debitorName = entry.debitorName
             creditorName = entry.creditorName
             creditorBic = entry.creditorBic
             creditorIban = entry.creditorIban
@@ -288,6 +295,7 @@ fun createPain001entity(entry: Pain001Data, 
debtorAccountId: String): Pain001Ent
             paymentId = randomId
             msgId = randomId
             endToEndId = randomId
+            this.nexusUser = nexusUser
         }
     }
 }
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/JSON.kt 
b/nexus/src/main/kotlin/tech/libeufin/nexus/JSON.kt
index 7336045..63acf4b 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/JSON.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/JSON.kt
@@ -1,9 +1,6 @@
 package tech.libeufin.nexus
 
-import tech.libeufin.util.Amount
-import tech.libeufin.util.EbicsDateRange
-import tech.libeufin.util.EbicsOrderParams
-import tech.libeufin.util.EbicsStandardOrderParams
+import tech.libeufin.util.*
 import java.lang.NullPointerException
 import java.time.LocalDate
 
@@ -137,27 +134,19 @@ class TestSubscriber()
 
 /** PAYMENT INSTRUCTIONS TYPES */
 
-/** Represents a prepared payment at the nexus.  This structure is
- * used to SHOW a prepared payment to the called.  */
-data class PaymentInfoElement(
-    val debtorAccount: String,
-    val creditorIban: String,
-    val creditorBic: String,
-    val creditorName: String,
-    val subject: String,
-    val sum: Amount,
-    val submitted: Boolean
-)
-data class PaymentsInfo(
-    var payments: MutableList<PaymentInfoElement> = mutableListOf()
-)
-
 /** This structure is used to INSTRUCT the nexus to prepare such payment.  */
 data class Pain001Data(
     val creditorIban: String,
     val creditorBic: String,
     val creditorName: String,
+    val debitorIban: String,
+    val debitorBic: String,
+    val debitorName: String?,
     val sum: Amount,
     val currency: String = "EUR",
     val subject: String
+)
+
+data class RawPayments(
+    var payments: MutableList<RawPayment> = mutableListOf()
 )
\ No newline at end of file
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt 
b/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt
index a5dc64a..5094fab 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt
@@ -45,6 +45,7 @@ import kotlinx.coroutines.io.jvm.javaio.toInputStream
 import kotlinx.io.core.ExperimentalIoApi
 import org.jetbrains.exposed.sql.and
 import org.jetbrains.exposed.sql.transactions.transaction
+import org.joda.time.DateTime
 import org.slf4j.Logger
 import org.slf4j.LoggerFactory
 import org.slf4j.event.Level
@@ -134,14 +135,13 @@ fun main() {
 
         routing {
 
-            /** General / debug endpoints */
+            /** GENERAL / DEBUG ENDPOINTS */
 
             get("/") {
                 call.respondText("Hello by Nexus!\n")
                 return@get
             }
-
-            get("/taler/test-auth") {
+            get("/test-auth") {
                 authenticateRequest(call.request.headers["Authorization"])
                 call.respondText("Authenticated!", ContentType.Text.Plain, 
HttpStatusCode.OK)
                 return@get
@@ -149,6 +149,31 @@ fun main() {
 
             /** USER endpoints (no EBICS) */
 
+            get("/users/{id}/history") {
+                /** NOTE: behaviour could be augmented by filtering on date 
range.  */
+                val nexusUser = extractNexusUser(call.parameters["id"])
+                val ret = RawPayments()
+                transaction {
+                    RawBankTransactionEntity.find {
+                        RawBankTransactionsTable.nexusUser eq 
nexusUser.id.value
+                    }.forEach {
+                        ret.payments.add(
+                            RawPayment(
+                                debitorIban = it.debitorIban,
+                                creditorIban = it.creditorIban,
+                                subject = it.unstructuredRemittanceInformation,
+                                date = DateTime(it.bookingDate).toDashedDate(),
+                                amount = "${it.currency}:${it.amount}"
+                            )
+                        )
+                    }
+                }
+                call.respond(
+                    HttpStatusCode.OK,
+                    ret
+                )
+                return@get
+            }
             /** Lists the users known to this system */
             get("/users") {
                 val ret = NexusUsers()
@@ -175,9 +200,8 @@ fun main() {
                 call.respond(ret)
                 return@get
             }
-
             /** Get all the details associated with a NEXUS user */
-            get("/user/{id}") {
+            get("/users/{id}") {
                 val response = transaction {
                     val nexusUser = extractNexusUser(call.parameters["id"])
                     NexusUser(
@@ -187,7 +211,6 @@ fun main() {
                 call.respond(HttpStatusCode.OK, response)
                 return@get
             }
-
             /** Make a new NEXUS user in the system */
             post("/users/{id}") {
                 val newUserId = expectId(call.parameters["id"])
@@ -209,8 +232,7 @@ fun main() {
                 )
                 return@post
             }
-
-            /** List all the bank accounts associated with a given NEXUS user. 
 */
+            /** Show bank accounts associated with a given NEXUS user */
             get("/users/{id}/accounts") {
                 // this information is only avaiable *after* HTD or HKD has 
been called
                 val id = expectId(call.parameters["id"])
@@ -235,10 +257,10 @@ fun main() {
                 )
                 return@get
             }
-            /** Show list of payments prepared by calling user.  */
+            /** Show PREPARED payments */
             get("/users/{id}/payments") {
                 val nexusUserId = expectId(call.parameters["id"])
-                val ret = PaymentsInfo()
+                val ret = RawPayments()
                 transaction {
                     val nexusUser = extractNexusUser(nexusUserId)
                     val bankAccountsMap = UserToBankAccountEntity.find {
@@ -246,17 +268,15 @@ fun main() {
                     }
                     bankAccountsMap.forEach {
                         Pain001Entity.find {
-                            Pain001Table.debtorAccount eq it.bankAccount.iban
+                            Pain001Table.debitorIban eq it.bankAccount.iban
                         }.forEach {
                             ret.payments.add(
-                                PaymentInfoElement(
-                                    debtorAccount = it.debtorAccount,
+                                RawPayment(
                                     creditorIban = it.creditorIban,
-                                    creditorBic = it.creditorBic,
-                                    creditorName = it.creditorName,
+                                    debitorIban = "FIXME",
                                     subject = it.subject,
-                                    sum = it.sum,
-                                    submitted = it.submitted // whether Nexus 
processed and sent to the bank
+                                    amount = "${it.currency}:${it.sum}",
+                                    date = DateTime(it.date).toDashedDate()
                                 )
                             )
                         }
@@ -265,26 +285,50 @@ fun main() {
                 call.respond(ret)
                 return@get
             }
-            post("/users/{id}/accounts/{acctid}/prepare-payment") {
-                val acctid = transaction {
+            post("/users/{id}/accounts/prepare-payment") {
+                val nexusUser = extractNexusUser(call.parameters["id"])
+                transaction {
                     val accountInfo = 
expectAcctidTransaction(call.parameters["acctid"])
-                    val nexusUser = extractNexusUser(call.parameters["id"])
                     if (!userHasRights(nexusUser, accountInfo)) {
                         throw NexusError(
                             HttpStatusCode.BadRequest,
                             "Claimed bank account '${accountInfo.id}' doesn't 
belong to user '${nexusUser.id.value}'!"
                         )
                     }
-                    accountInfo.id.value
+
                 }
                 val pain001data = call.receive<Pain001Data>()
-                createPain001entity(pain001data, acctid)
+                createPain001entity(pain001data, nexusUser)
                 call.respondText(
                     "Payment instructions persisted in DB",
                     ContentType.Text.Plain, HttpStatusCode.OK
                 )
                 return@post
             }
+            get("/users/{id}/raw-payments") {
+                val nexusUser = extractNexusUser(call.parameters["id"])
+                var ret = RawPayments()
+                transaction {
+                    RawBankTransactionEntity.find {
+                        RawBankTransactionsTable.nexusUser eq 
nexusUser.id.value
+                    }.forEach {
+                        ret.payments.add(
+                            RawPayment(
+                                creditorIban = it.creditorIban,
+                                debitorIban = it.debitorIban,
+                                subject = it.unstructuredRemittanceInformation,
+                                date = DateTime(it.bookingDate).toDashedDate(),
+                                amount = it.amount + " " + it.currency
+                            )
+                        )
+                    }
+                }
+                call.respond(
+                    HttpStatusCode.OK,
+                    ret
+                )
+                return@get
+            }
             /** Associate a EBICS subscriber to the existing user */
             post("/ebics/subscribers/{id}") {
                 val nexusUser = extractNexusUser(call.parameters["id"])
@@ -526,17 +570,22 @@ fun main() {
             /** STATE CHANGES VIA EBICS */
 
             post("/ebics/admin/execute-payments") {
-                val (paymentRowId, painDoc: String, debtorAccount) = 
transaction {
+                val (paymentRowId, painDoc, subscriber) = transaction {
                     val entity = Pain001Entity.find {
                         (Pain001Table.submitted eq false) and 
(Pain001Table.invalid eq false)
                     }.firstOrNull() ?: throw 
NexusError(HttpStatusCode.Accepted, reason = "No ready payments found")
-                    Triple(entity.id, createPain001document(entity), 
entity.debtorAccount)
+                    Triple(entity.id, createPain001document(entity), 
entity.nexusUser.ebicsSubscriber)
+                }
+                if (subscriber == null) {
+                    throw NexusError(
+                        HttpStatusCode.NotFound,
+                        "Ebics subscriber wasn't found for this prepared 
payment."
+                    )
                 }
                 logger.debug("Uploading PAIN.001: ${painDoc}")
-                val subscriberDetails = 
getSubscriberDetailsFromBankAccount(debtorAccount)
                 doEbicsUploadTransaction(
                     client,
-                    subscriberDetails,
+                    getSubscriberDetailsInternal(subscriber),
                     "CCT",
                     painDoc.toByteArray(Charsets.UTF_8),
                     EbicsStandardOrderParams()
@@ -556,41 +605,6 @@ fun main() {
                 )
                 return@post
             }
-            post("/ebics/admin/execute-payments-ccc") {
-                val (paymentRowId, painDoc: String, debtorAccount) = 
transaction {
-                    val entity = Pain001Entity.find {
-                        (Pain001Table.submitted eq false) and 
(Pain001Table.invalid eq false)
-                    }.firstOrNull() ?: throw 
NexusError(HttpStatusCode.Accepted, reason = "No ready payments found")
-                    Triple(entity.id, createPain001document(entity), 
entity.debtorAccount)
-                }
-                logger.debug("Uploading PAIN.001 via CCC: ${painDoc}")
-                val subscriberDetails = 
getSubscriberDetailsFromBankAccount(debtorAccount)
-                doEbicsUploadTransaction(
-                    client,
-                    subscriberDetails,
-                    "CCC",
-                    painDoc.toByteArray(Charsets.UTF_8).zip(),
-                    EbicsStandardOrderParams()
-                )
-                /* flow here == no errors occurred */
-                transaction {
-                    val payment = Pain001Entity.findById(paymentRowId) ?: 
throw NexusError(
-                        HttpStatusCode.InternalServerError,
-                        "Severe internal error: could not find payment in DB 
after having submitted it to the bank"
-                    )
-                    payment.submitted = true
-                }
-                call.respondText(
-                    "CCC message submitted to the bank",
-                    ContentType.Text.Plain,
-                    HttpStatusCode.OK
-                )
-                return@post
-            }
-            post("/ebics/subscribers/{id}/collect-transactions-c52") {
-                // FIXME(florian): Download C52 and store the result in the 
right database table
-
-            }
             /** exports keys backup copy */
             post("/ebics/subscribers/{id}/backup") {
                 val body = call.receive<EbicsBackupRequestJson>()
@@ -628,7 +642,6 @@ fun main() {
                     response
                 )
             }
-
             /** Download keys from bank */
             post("/ebics/subscribers/{id}/sync") {
                 val nexusUser = extractNexusUser(call.parameters["id"])
@@ -690,19 +703,19 @@ fun main() {
                          * return all the "Ntry" elements into one single ZIP 
entry, or even unzipped
                          * at all.
                          */
-                        response.orderData.unzipWithLoop {
+                        response.orderData.unzipWithLambda {
                             val fileName = it.first
                             val camt53doc = 
XMLUtil.parseStringIntoDom(it.second)
                             transaction {
                                 RawBankTransactionEntity.new {
                                     sourceFileName = fileName
-                                    unstructuredRemittanceInformation = 
camt53doc.pickString("//*[local-name()='Ntry']//*[local-name()='Amt']/@Ccy")
+                                    unstructuredRemittanceInformation = 
camt53doc.pickString("//*[local-name()='Ntry']//*[local-name()='Ustrd']")
                                     transactionType = 
camt53doc.pickString("//*[local-name()='Ntry']//*[local-name()='CdtDbtInd']")
                                     currency = 
camt53doc.pickString("//*[local-name()='Ntry']//*[local-name()='Amt']/@Ccy")
                                     amount = 
camt53doc.pickString("//*[local-name()='Ntry']//*[local-name()='Amt']")
                                     status = 
camt53doc.pickString("//*[local-name()='Ntry']//*[local-name()='Sts']")
                                     bookingDate = 
parseDate(camt53doc.pickString("//*[local-name()='BookgDt']//*[local-name()='Dt']")).millis
-                                    nexusSubscriber = 
getSubscriberEntityFromNexusUserId(id)
+                                    nexusUser = extractNexusUser(id)
                                     creditorName = 
camt53doc.pickString("//*[local-name()='RltdPties']//*[local-name()='Dbtr']//*[local-name()='Nm']")
                                     creditorIban = 
camt53doc.pickString("//*[local-name()='CdtrAcct']//*[local-name()='IBAN']")
                                     debitorName = 
camt53doc.pickString("//*[local-name()='RltdPties']//*[local-name()='Dbtr']//*[local-name()='Nm']")
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/taler.kt 
b/nexus/src/main/kotlin/tech/libeufin/nexus/taler.kt
index 6e4b110..640fce1 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/taler.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/taler.kt
@@ -236,14 +236,11 @@ class Taler(app: Route) {
             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 = 
getBankAccountFromNexusUserId(exchangeId)
                 val nexusUser = extractNexusUser(exchangeId)
-                /**
-                 * Checking the UID has the desired characteristics.
-                 */
+                /** Checking the UID has the desired characteristics */
                 TalerRequestedPaymentEntity.find {
                     TalerRequestedPayments.requestUId eq 
transferRequest.request_uid
                 }.forEach {
@@ -264,11 +261,13 @@ class Taler(app: Route) {
                         creditorBic = creditorData.bic,
                         creditorName = creditorData.name,
                         subject = transferRequest.wtid,
-                        sum = parseAmount(transferRequest.amount).amount
+                        sum = parseAmount(transferRequest.amount).amount,
+                        debitorName = exchangeBankAccount.accountHolder,
+                        debitorBic = exchangeBankAccount.bankCode,
+                        debitorIban = exchangeBankAccount.iban
                     ),
-                    exchangeBankAccount.id.value
+                    nexusUser
                 )
-
                 val rawEbics = if (!isProduction()) {
                     RawBankTransactionEntity.new {
                         sourceFileName = "test"
@@ -282,7 +281,7 @@ class Taler(app: Route) {
                         creditorIban = creditorObj.iban
                         counterpartBic = creditorObj.bic
                         bookingDate = DateTime.now().millis
-                        nexusSubscriber = getEbicsSubscriberFromUser(nexusUser)
+                        this.nexusUser = nexusUser
                         status = "BOOK"
                     }
                 } else null
@@ -338,7 +337,7 @@ class Taler(app: Route) {
                     counterpartBic = debtor.bic
                     bookingDate = DateTime.now().millis
                     status = "BOOK"
-                    nexusSubscriber = 
getSubscriberEntityFromNexusUserId(exchangeId)
+                    nexusUser = extractNexusUser(exchangeId)
                 }
                 /** This payment is "valid by default" and will be returned
                  * as soon as the exchange will ask for new payments.  */
@@ -368,14 +367,15 @@ class Taler(app: Route) {
          * all the prepared payments.  */
         
app.post("/ebics/taler/{id}/accounts/{acctid}/refund-invalid-payments") {
             transaction {
-                val subscriber = 
getSubscriberEntityFromNexusUserId(call.parameters["id"])
+                val nexusUser = extractNexusUser(call.parameters["id"])
                 val acctid = expectAcctidTransaction(call.parameters["acctid"])
-                if (!subscriberHasRights(subscriber, acctid)) {
+                if 
(!subscriberHasRights(getEbicsSubscriberFromUser(nexusUser), acctid)) {
                     throw NexusError(
                         HttpStatusCode.Forbidden,
-                        "Such subscriber (${subscriber.id}) can't drive such 
account (${acctid.id})"
+                        "The requester can't drive such account (${acctid.id})"
                     )
                 }
+                val requesterBankAccount = 
getBankAccountFromNexusUserId(nexusUser.id.value)
                 TalerIncomingPaymentEntity.find {
                     TalerIncomingPayments.refunded eq false and 
(TalerIncomingPayments.valid eq false)
                 }.forEach {
@@ -385,9 +385,12 @@ class Taler(app: Route) {
                             creditorIban = it.payment.debitorIban,
                             creditorBic = it.payment.counterpartBic,
                             sum = calculateRefund(it.payment.amount),
-                            subject = "Taler refund"
+                            subject = "Taler refund",
+                            debitorIban = requesterBankAccount.iban,
+                            debitorBic = requesterBankAccount.bankCode,
+                            debitorName = requesterBankAccount.accountHolder
                         ),
-                        acctid.id.value
+                        nexusUser
                     )
                     it.refunded = true
                 }
diff --git a/nexus/src/test/kotlin/DbTest.kt b/nexus/src/test/kotlin/DbTest.kt
deleted file mode 100644
index cf4e7c6..0000000
--- a/nexus/src/test/kotlin/DbTest.kt
+++ /dev/null
@@ -1,53 +0,0 @@
-package tech.libeufin.nexus
-
-import org.junit.Before
-import org.junit.Test
-
-import org.jetbrains.exposed.sql.Database
-import org.jetbrains.exposed.sql.transactions.transaction
-import org.jetbrains.exposed.sql.SchemaUtils
-import tech.libeufin.util.Amount
-import javax.sql.rowset.serial.SerialBlob
-
-
-class DbTest {
-
-    @Before
-    fun connectAndMakeTables() {
-        Database.connect("jdbc:h2:mem:test;DB_CLOSE_DELAY=-1", driver = 
"org.h2.Driver")
-        transaction {
-            SchemaUtils.create(EbicsSubscribersTable)
-            SchemaUtils.create(Pain001Table)
-        }
-    }
-
-    @Test
-    fun makeEbicsSubscriber() {
-        transaction {
-            EbicsSubscriberEntity.new {
-                ebicsURL = "ebics url"
-                hostID = "host"
-                partnerID = "partner"
-                userID = "user"
-                systemID = "system"
-                signaturePrivateKey = 
SerialBlob("signturePrivateKey".toByteArray())
-                authenticationPrivateKey = 
SerialBlob("authenticationPrivateKey".toByteArray())
-                encryptionPrivateKey = 
SerialBlob("encryptionPrivateKey".toByteArray())
-            }
-        }
-    }
-
-    @Test
-    fun testPain001() {
-        createPain001entity(
-            Pain001Data(
-                creditorBic = "cb",
-                creditorIban = "ci",
-                creditorName = "cn",
-                sum = Amount(2),
-                subject = "s"
-            ),
-            "debtor acctid"
-        )
-    }
-}
\ No newline at end of file
diff --git a/nexus/src/test/kotlin/PainGeneration.kt 
b/nexus/src/test/kotlin/PainGeneration.kt
index 8f29f45..f412db6 100644
--- a/nexus/src/test/kotlin/PainGeneration.kt
+++ b/nexus/src/test/kotlin/PainGeneration.kt
@@ -12,28 +12,17 @@ import javax.sql.rowset.serial.SerialBlob
 
 
 class PainTest {
-
     @Before
     fun prepare() {
         Database.connect("jdbc:h2:mem:test;DB_CLOSE_DELAY=-1", driver = 
"org.h2.Driver")
         transaction {
-            SchemaUtils.create(EbicsSubscribersTable)
             SchemaUtils.create(BankAccountsTable)
             SchemaUtils.create(Pain001Table)
-            EbicsSubscriberEntity.new {
-                ebicsURL = "ebics url"
-                hostID = "host"
-                partnerID = "partner"
-                userID = "user"
-                systemID = "system"
-                signaturePrivateKey = 
SerialBlob("signturePrivateKey".toByteArray())
-                authenticationPrivateKey = 
SerialBlob("authenticationPrivateKey".toByteArray())
-                encryptionPrivateKey = 
SerialBlob("encryptionPrivateKey".toByteArray())
-            }
+            SchemaUtils.create(NexusUsersTable)
             BankAccountEntity.new(id = "acctid") {
                 accountHolder = "Account Holder"
-                iban = "IBAN"
-                bankCode = "BIC"
+                iban = "DEBIT IBAN"
+                bankCode = "DEBIT BIC"
             }
         }
     }
@@ -41,9 +30,12 @@ class PainTest {
     @Test
     fun testPain001document() {
         transaction {
+            val nu = NexusUserEntity.new(id = "mock") { }
             val pain001Entity = Pain001Entity.new {
                 sum = Amount(1)
-                debtorAccount = "acctid"
+                debitorIban = "DEBIT IBAN"
+                debitorBic = "DEBIT BIC"
+                debitorName = "DEBIT NAME"
                 subject = "subject line"
                 creditorIban = "CREDIT IBAN"
                 creditorBic = "CREDIT BIC"
@@ -52,7 +44,7 @@ class PainTest {
                 msgId = 1
                 endToEndId = 1
                 date = DateTime.now().millis
-
+                nexusUser = nu
             }
             val s = createPain001document(pain001Entity)
             println(s)
diff --git a/nexus/src/test/kotlin/XPathTest.kt 
b/nexus/src/test/kotlin/XPathTest.kt
index f381175..bb1b93b 100644
--- a/nexus/src/test/kotlin/XPathTest.kt
+++ b/nexus/src/test/kotlin/XPathTest.kt
@@ -13,10 +13,8 @@ class XPathTest {
               <node>lorem ipsum</node>
             </root>""".trimIndent()
         val doc: Document = XMLUtil.parseStringIntoDom(xml)
-        val node = XMLUtil.getNodeFromXpath(doc, "/*[local-name()='root']")
-        assert(node != null)
+        XMLUtil.getNodeFromXpath(doc, "/*[local-name()='root']")
         val text = XMLUtil.getStringFromXpath(doc, "//*[local-name()='node']")
-        assert(text != null)
         println(text)
     }
 }
diff --git a/sandbox/src/main/kotlin/tech/libeufin/sandbox/DB.kt 
b/sandbox/src/main/kotlin/tech/libeufin/sandbox/DB.kt
index b9d872b..7ce01a1 100644
--- a/sandbox/src/main/kotlin/tech/libeufin/sandbox/DB.kt
+++ b/sandbox/src/main/kotlin/tech/libeufin/sandbox/DB.kt
@@ -19,27 +19,12 @@
 
 package tech.libeufin.sandbox
 
-import io.ktor.http.HttpStatusCode
 import org.jetbrains.exposed.dao.*
 import org.jetbrains.exposed.sql.*
 import org.jetbrains.exposed.sql.transactions.TransactionManager
 import org.jetbrains.exposed.sql.transactions.transaction
-import tech.libeufin.util.IntIdTableWithAmount
-import java.lang.ArithmeticException
-import java.math.BigDecimal
-import java.math.MathContext
-import java.math.RoundingMode
-import java.sql.Blob
 import java.sql.Connection
 
-const val CUSTOMER_NAME_MAX_LENGTH = 20
-const val EBICS_HOST_ID_MAX_LENGTH = 10
-const val EBICS_USER_ID_MAX_LENGTH = 10
-const val EBICS_PARTNER_ID_MAX_LENGTH = 10
-const val EBICS_SYSTEM_ID_MAX_LENGTH = 10
-const val MAX_ID_LENGTH = 21 // enough to contain IBANs
-const val MAX_SUBJECT_LENGTH = 140 // okay?
-
 /**
  * All the states to give a subscriber.
  */
@@ -93,48 +78,6 @@ enum class KeyState {
     RELEASED
 }
 
-
-object BankTransactionsTable : IntIdTableWithAmount() {
-    /* Using varchar to store the IBAN - or possibly other formats
-     * - from the counterpart.  */
-    val counterpart = varchar("counterpart", MAX_ID_LENGTH)
-    val amount = amount("amount")
-    val subject = varchar("subject", MAX_SUBJECT_LENGTH)
-    val operationDate = long("operationDate")
-    val valueDate = long("valueDate")
-    val localCustomer = reference("localCustomer", BankCustomersTable)
-}
-
-class BankTransactionEntity(id: EntityID<Int>) : IntEntity(id) {
-    companion object : 
IntEntityClass<BankTransactionEntity>(BankTransactionsTable)
-    /* the id of the local customer involved in this transaction,
-    * either as the credit or the debit part; makes lookups easier */
-    var localCustomer by BankCustomerEntity referencedOn 
BankTransactionsTable.localCustomer
-    /* keeping as strings, as to allow hosting IBANs and/or other
-    * unobvious formats.  */
-    var counterpart by BankTransactionsTable.counterpart
-    var subject by BankTransactionsTable.subject
-    var operationDate by BankTransactionsTable.operationDate
-    var valueDate by BankTransactionsTable.valueDate
-    var amount by BankTransactionsTable.amount
-}
-
-
-/**
- * This table information *not* related to EBICS, for all
- * its customers.
- */
-object BankCustomersTable : IntIdTable() {
-    // Customer ID is the default 'id' field provided by the constructor.
-    val customerName = varchar("customerName", CUSTOMER_NAME_MAX_LENGTH)
-}
-
-class BankCustomerEntity(id: EntityID<Int>) : IntEntity(id) {
-    companion object : IntEntityClass<BankCustomerEntity>(BankCustomersTable)
-    var customerName by BankCustomersTable.customerName
-}
-
-
 /**
  * This table stores RSA public keys of subscribers.
  */
@@ -142,10 +85,6 @@ object EbicsSubscriberPublicKeysTable : IntIdTable() {
     val rsaPublicKey = blob("rsaPublicKey")
     val state = enumeration("state", KeyState::class)
 }
-
-/**
- * Definition of a row in the [EbicsSubscriberPublicKeyEntity] table
- */
 class EbicsSubscriberPublicKeyEntity(id: EntityID<Int>) : IntEntity(id) {
     companion object : 
IntEntityClass<EbicsSubscriberPublicKeyEntity>(EbicsSubscriberPublicKeysTable)
 
@@ -153,7 +92,9 @@ class EbicsSubscriberPublicKeyEntity(id: EntityID<Int>) : 
IntEntity(id) {
     var state by EbicsSubscriberPublicKeysTable.state
 }
 
-
+/**
+ * Ebics 'host'(s) that are served by one Sandbox instance.
+ */
 object EbicsHostsTable : IntIdTable() {
     val hostID = text("hostID")
     val ebicsVersion = text("ebicsVersion")
@@ -161,11 +102,8 @@ object EbicsHostsTable : IntIdTable() {
     val encryptionPrivateKey = blob("encryptionPrivateKey")
     val authenticationPrivateKey = blob("authenticationPrivateKey")
 }
-
-
 class EbicsHostEntity(id: EntityID<Int>) : IntEntity(id) {
     companion object : IntEntityClass<EbicsHostEntity>(EbicsHostsTable)
-
     var hostId by EbicsHostsTable.hostID
     var ebicsVersion by EbicsHostsTable.ebicsVersion
     var signaturePrivateKey by EbicsHostsTable.signaturePrivateKey
@@ -174,8 +112,7 @@ class EbicsHostEntity(id: EntityID<Int>) : IntEntity(id) {
 }
 
 /**
- * Subscribers table.  This table associates users with partners
- * and systems.  Each value can appear multiple times in the same column.
+ * Ebics Subscribers table.
  */
 object EbicsSubscribersTable : IntIdTable() {
     val userId = text("userID")
@@ -187,28 +124,23 @@ object EbicsSubscribersTable : IntIdTable() {
     val authenticationKey = reference("authorizationKey", 
EbicsSubscriberPublicKeysTable).nullable()
     val nextOrderID = integer("nextOrderID")
     val state = enumeration("state", SubscriberState::class)
-    val bankCustomer = reference("bankCustomer", BankCustomersTable)
 }
-
-
 class EbicsSubscriberEntity(id: EntityID<Int>) : IntEntity(id) {
     companion object : 
IntEntityClass<EbicsSubscriberEntity>(EbicsSubscribersTable)
-
     var userId by EbicsSubscribersTable.userId
     var partnerId by EbicsSubscribersTable.partnerId
     var systemId by EbicsSubscribersTable.systemId
     var hostId by EbicsSubscribersTable.hostId
-
     var signatureKey by EbicsSubscriberPublicKeyEntity optionalReferencedOn 
EbicsSubscribersTable.signatureKey
     var encryptionKey by EbicsSubscriberPublicKeyEntity optionalReferencedOn 
EbicsSubscribersTable.encryptionKey
     var authenticationKey by EbicsSubscriberPublicKeyEntity 
optionalReferencedOn EbicsSubscribersTable.authenticationKey
-
     var nextOrderID by EbicsSubscribersTable.nextOrderID
     var state by EbicsSubscribersTable.state
-    var bankCustomer by BankCustomerEntity referencedOn 
EbicsSubscribersTable.bankCustomer
 }
 
-
+/**
+ * Details of a download order.
+ */
 object EbicsDownloadTransactionsTable : IdTable<String>() {
     override val id = text("transactionID").entityId()
     val orderType = text("orderType")
@@ -220,7 +152,6 @@ object EbicsDownloadTransactionsTable : IdTable<String>() {
     val segmentSize = integer("segmentSize")
     val receiptReceived = bool("receiptReceived")
 }
-
 class EbicsDownloadTransactionEntity(id: EntityID<String>) : 
Entity<String>(id) {
     companion object : EntityClass<String, 
EbicsDownloadTransactionEntity>(EbicsDownloadTransactionsTable)
     var orderType by EbicsDownloadTransactionsTable.orderType
@@ -233,6 +164,9 @@ class EbicsDownloadTransactionEntity(id: EntityID<String>) 
: Entity<String>(id)
     var receiptReceived by EbicsDownloadTransactionsTable.receiptReceived
 }
 
+/**
+ * Details of a upload order.
+ */
 object EbicsUploadTransactionsTable : IdTable<String>() {
     override val id = text("transactionID").entityId()
     val orderType = text("orderType")
@@ -243,10 +177,8 @@ object EbicsUploadTransactionsTable : IdTable<String>() {
     val lastSeenSegment = integer("lastSeenSegment")
     val transactionKeyEnc = blob("transactionKeyEnc")
 }
-
 class EbicsUploadTransactionEntity(id: EntityID<String>) : Entity<String>(id) {
     companion object : EntityClass<String, 
EbicsUploadTransactionEntity>(EbicsUploadTransactionsTable)
-
     var orderType by EbicsUploadTransactionsTable.orderType
     var orderID by EbicsUploadTransactionsTable.orderID
     var host by EbicsHostEntity referencedOn EbicsUploadTransactionsTable.host
@@ -256,6 +188,9 @@ class EbicsUploadTransactionEntity(id: EntityID<String>) : 
Entity<String>(id) {
     var transactionKeyEnc by EbicsUploadTransactionsTable.transactionKeyEnc
 }
 
+/**
+ * FIXME: document this.
+ */
 object EbicsOrderSignaturesTable : IntIdTable() {
     val orderID = text("orderID")
     val orderType = text("orderType")
@@ -264,7 +199,6 @@ object EbicsOrderSignaturesTable : IntIdTable() {
     val signatureAlgorithm = text("signatureAlgorithm")
     val signatureValue = blob("signatureValue")
 }
-
 class EbicsOrderSignatureEntity(id: EntityID<Int>) : IntEntity(id) {
     companion object : 
IntEntityClass<EbicsOrderSignatureEntity>(EbicsOrderSignaturesTable)
     var orderID by EbicsOrderSignaturesTable.orderID
@@ -275,35 +209,55 @@ class EbicsOrderSignatureEntity(id: EntityID<Int>) : 
IntEntity(id) {
     var signatureValue by EbicsOrderSignaturesTable.signatureValue
 }
 
+/**
+ * FIXME: document this.
+ */
 object EbicsUploadTransactionChunksTable : IdTable<String>() {
     override val id =
         text("transactionID").entityId()
     val chunkIndex = integer("chunkIndex")
     val chunkContent = blob("chunkContent")
 }
-
-class EbicsUploadTransactionChunkEntity(id : EntityID<String>): 
Entity<String>(id) {
+class EbicsUploadTransactionChunkEntity(id: EntityID<String>) : 
Entity<String>(id) {
     companion object : EntityClass<String, 
EbicsUploadTransactionChunkEntity>(EbicsUploadTransactionChunksTable)
     var chunkIndex by EbicsUploadTransactionChunksTable.chunkIndex
     var chunkContent by EbicsUploadTransactionChunksTable.chunkContent
 }
 
+/**
+ * Table that keeps all the payments initiated by pain.
+ */
+object PaymentsTable : IntIdTable() {
+    val creditorIban = text("creditorIban")
+    val debitorIban = text("debitorIban")
+    val subject = text("subject")
+    val amount = text("amount")
+    val date = long("date")
+    val ebicsSubscriber = reference("ebicsSubscriber", EbicsSubscribersTable)
+}
+class PaymentEntity(id: EntityID<Int>) : IntEntity(id) {
+    companion object : IntEntityClass<PaymentEntity>(PaymentsTable)
+    var creditorIban by PaymentsTable.creditorIban
+    var debitorIban by PaymentsTable.debitorIban
+    var subject by PaymentsTable.subject
+    var amount by PaymentsTable.amount
+    var date by PaymentsTable.date
+    var ebicsSubscriber by EbicsSubscriberEntity referencedOn 
PaymentsTable.ebicsSubscriber
+}
 
 fun dbCreateTables() {
     Database.connect("jdbc:sqlite:libeufin-sandbox.sqlite3", "org.sqlite.JDBC")
     TransactionManager.manager.defaultIsolationLevel = 
Connection.TRANSACTION_SERIALIZABLE
-
     transaction {
         addLogger(StdOutSqlLogger)
         SchemaUtils.create(
-            BankCustomersTable,
-            BankTransactionsTable,
             EbicsSubscribersTable,
             EbicsHostsTable,
             EbicsDownloadTransactionsTable,
             EbicsUploadTransactionsTable,
             EbicsUploadTransactionChunksTable,
-            EbicsOrderSignaturesTable
+            EbicsOrderSignaturesTable,
+            PaymentsTable
         )
     }
 }
\ No newline at end of file
diff --git 
a/sandbox/src/main/kotlin/tech/libeufin/sandbox/EbicsProtocolBackend.kt 
b/sandbox/src/main/kotlin/tech/libeufin/sandbox/EbicsProtocolBackend.kt
index 0113413..5788064 100644
--- a/sandbox/src/main/kotlin/tech/libeufin/sandbox/EbicsProtocolBackend.kt
+++ b/sandbox/src/main/kotlin/tech/libeufin/sandbox/EbicsProtocolBackend.kt
@@ -54,6 +54,7 @@ import org.joda.time.DateTime
 import org.joda.time.Instant
 import java.io.BufferedInputStream
 import java.io.ByteArrayInputStream
+import java.nio.charset.Charset
 import java.time.ZoneId
 import java.time.ZonedDateTime
 import java.time.format.DateTimeFormatter
@@ -140,27 +141,12 @@ private suspend fun 
ApplicationCall.respondEbicsKeyManagement(
     respondText(text, ContentType.Application.Xml, HttpStatusCode.OK)
 }
 
-fun buildCamtString(history: SizedIterable<BankTransactionEntity>, type: Int): 
String {
-
-    /**
-     * Booking period: we keep two "lines" of booking periods; one for c52 and 
one for c53.
-     * Each line's period ends when the user requests the c52/c53, and a new 
period is started.
-     */
-
-    /**
-     * Checklist of data to be retrieved from the database.
-     *
-     * - IBAN(s): debitor and creditor's
-     * - last IDs (of all kinds) ?
-     */
-
-    /**
-     * What needs to be calculated before filling the document:
-     *
-     * - The balance _after_ all the transactions from the fresh
-     *   booking period.
-     */
-
+/**
+ * Returns a list of camt strings, representing each one payment
+ * accounted in the history.  It is up to the caller to then construct
+ * the final ZIP file to return in the response.
+ */
+fun buildCamtString(type: Int, history: MutableList<RawPayment>): 
MutableList<String> {
     /**
      * ID types required:
      *
@@ -173,258 +159,277 @@ fun buildCamtString(history: 
SizedIterable<BankTransactionEntity>, type: Int): S
      * - Proprietary code of the bank transaction
      * - Id of the servicer (Issuer and Code)
      */
-
     val now = DateTime.now()
-
-    return constructXml(indent = true) {
-        root("Document") {
-            attribute("xmlns", 
"urn:iso:std:iso:20022:tech:xsd:camt.053.001.02")
-            attribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance";)
-            attribute("xsi:schemaLocation", 
"urn:iso:std:iso:20022:tech:xsd:camt.053.001.02 camt.053.001.02.xsd")
-            element("BkToCstmrStmt") {
-                element("GrpHdr") {
-                    element("MsgId") {
-                        text("0")
-                    }
-                    element("CreDtTm") {
-                        text(now.toZonedString())
-                    }
-                    element("MsgPgntn") {
-                        element("PgNb") {
-                            text("001")
-                        }
-                        element("LastPgInd") {
-                            text("true")
-                        }
-                    }
-                }
-                element(if (type == 52) "Rpt" else "Stmt") {
-                    element("Id") {
-                        text("0")
-                    }
-                    element("ElctrncSeqNb") {
-                        text("0")
-                    }
-                    element("LglSeqNb") {
-                        text("0")
-                    }
-                    element("CreDtTm") {
-                        text(now.toZonedString())
-                    }
-
-                    element("Acct") {
-                        // mandatory account identifier
-                        element("Id/IBAN") {
-                            text("GB33BUKB20201555555555")
-                        }
-                        element("Ccy") {
-                            text("EUR")
-                        }
-                        element("Ownr/Nm") {
-                            text("Max Mustermann")
-                        }
-                        element("Svcr/FinInstnId") {
-                            element("BIC") {
-                                text("GENODEM1GLS")
+    val ret = mutableListOf<String>()
+    history.forEach {
+        ret.add(
+            constructXml(indent = true) {
+                root("Document") {
+                    attribute("xmlns", 
"urn:iso:std:iso:20022:tech:xsd:camt.053.001.02")
+                    attribute("xmlns:xsi", 
"http://www.w3.org/2001/XMLSchema-instance";)
+                    attribute("xsi:schemaLocation", 
"urn:iso:std:iso:20022:tech:xsd:camt.053.001.02 camt.053.001.02.xsd")
+                    element("BkToCstmrStmt") {
+                        element("GrpHdr") {
+                            element("MsgId") {
+                                text("0")
                             }
-                            element("Nm") {
-                                text("Libeufin Bank")
+                            element("CreDtTm") {
+                                text(now.toZonedString())
                             }
-                            element("Othr") {
-                                element("Id") {
-                                    text("0")
+                            element("MsgPgntn") {
+                                element("PgNb") {
+                                    text("001")
                                 }
-                                element("Issr") {
-                                    text("XY")
+                                element("LastPgInd") {
+                                    text("true")
                                 }
                             }
                         }
-                    }
-                    element("Bal") {
-                        element("Tp/CdOrPrtry/Cd") {
-                            /* Balance type, in a coded format.  PRCD stands
-                               for "Previously closed booked" and shows the
-                               balance at the time _before_ all the entries
-                               reported in this document were posted to the
-                               involved bank account.  */
-                            text("PRCD")
-                        }
-                        element("Amt") {
-                            attribute("Ccy", "EUR")
-                            text(Amount(1).toPlainString())
-                        }
-                        element("CdtDbtInd") {
-                            text("DBIT")
-                            // CRDT or DBIT here
-                        }
-                        element("Dt/Dt") {
-                            // date of this balance
-                            text(now.toDashedDate())
-                        }
-                    }
-                    element("Bal") {
-                        element("Tp/CdOrPrtry/Cd") {
-                            /* CLBD stands for "Closing booked balance", and it
-                               is calculated by summing the PRCD with all the
-                               entries reported in this document */
-                            text("CLBD")
-                        }
-                        element("Amt") {
-                            attribute("Ccy", "EUR")
-                            text(Amount(1).toPlainString())
-                        }
-                        element("CdtDbtInd") {
-                            // CRDT or DBIT here
-                            text("DBIT")
-                        }
-                        element("Dt/Dt") {
-                            text(now.toDashedDate())
-                        }
-                    }
-                    // history.forEach {
-                        element("Ntry") {
-                            element("Amt") {
-                                attribute("Ccy", "EUR")
-                                text(Amount(1).toPlainString())
-                            }
-                            element("CdtDbtInd") {
-                                text("DBIT")
+                        element(if (type == 52) "Rpt" else "Stmt") {
+                            element("Id") {
+                                text("0")
                             }
-                            element("Sts") {
-                                /* Status of the entry (see 2.4.2.15.5 from 
the ISO20022 reference document.)
-                                 * From the original text:
-                                 * "Status of an entry on the books of the 
account servicer" */
-                                text("BOOK")
+                            element("ElctrncSeqNb") {
+                                text("0")
                             }
-                            element("BookgDt/Dt") {
-                                text(now.toDashedDate())
-                            } // date of the booking
-                            element("ValDt/Dt") {
-                                text(now.toDashedDate())
-                            } // date of assets' actual (un)availability
-                            element("AcctSvcrRef") {
+                            element("LglSeqNb") {
                                 text("0")
                             }
-                            element("BkTxCd") {
-                                /*  "Set of elements used to fully identify 
the type of underlying
-                                 *   transaction resulting in an entry".  */
-                                element("Domn") {
-                                    element("Cd") {
-                                        text("PMNT")
+                            element("CreDtTm") {
+                                text(now.toZonedString())
+                            }
+
+                            element("Acct") {
+                                // mandatory account identifier
+                                element("Id/IBAN") {
+                                    text("GB33BUKB20201555555555")
+                                }
+                                element("Ccy") {
+                                    text("EUR")
+                                }
+                                element("Ownr/Nm") {
+                                    text("Max Mustermann")
+                                }
+                                element("Svcr/FinInstnId") {
+                                    element("BIC") {
+                                        text("GENODEM1GLS")
                                     }
-                                    element("Fmly") {
-                                        element("Cd") {
-                                            text("ICDT")
+                                    element("Nm") {
+                                        text("Libeufin Bank")
+                                    }
+                                    element("Othr") {
+                                        element("Id") {
+                                            text("0")
                                         }
-                                        element("SubFmlyCd") {
-                                            text("ESCT")
+                                        element("Issr") {
+                                            text("XY")
                                         }
                                     }
                                 }
-                                element("Prtry") {
-                                    element("Cd") {
-                                        text("0")
-                                    }
-                                    element("Issr") {
-                                        text("XY")
-                                    }
+                            }
+                            element("Bal") {
+                                element("Tp/CdOrPrtry/Cd") {
+                                    /* Balance type, in a coded format.  PRCD 
stands
+                                       for "Previously closed booked" and 
shows the
+                                       balance at the time _before_ all the 
entries
+                                       reported in this document were posted 
to the
+                                       involved bank account.  */
+                                    text("PRCD")
+                                }
+                                element("Amt") {
+                                    attribute("Ccy", "EUR")
+                                    text(Amount(1).toPlainString())
+                                }
+                                element("CdtDbtInd") {
+                                    text("DBIT")
+                                    // CRDT or DBIT here
+                                }
+                                element("Dt/Dt") {
+                                    // date of this balance
+                                    text(now.toDashedDate())
                                 }
                             }
-                            element("NtryDtls/TxDtls") {
-                                element("Refs") {
-                                    element("MsgId") {
-                                        text("0")
-                                    }
-                                    element("PmtInfId") {
-                                        text("0")
-                                    }
-                                    element("EndToEndId") {
-                                        text("NOTPROVIDED")
-                                    }
+                            element("Bal") {
+                                element("Tp/CdOrPrtry/Cd") {
+                                    /* CLBD stands for "Closing booked 
balance", and it
+                                       is calculated by summing the PRCD with 
all the
+                                       entries reported in this document */
+                                    text("CLBD")
                                 }
-                                element("AmtDtls/TxAmt/Amt") {
+                                element("Amt") {
                                     attribute("Ccy", "EUR")
                                     text(Amount(1).toPlainString())
                                 }
-                                element("BkTxCd") {
-                                    element("Domn") {
-                                        element("Cd") {
-                                            text("PMNT")
+                                element("CdtDbtInd") {
+                                    // CRDT or DBIT here
+                                    text("DBIT")
+                                }
+                                element("Dt/Dt") {
+                                    text(now.toDashedDate())
+                                }
+                            }
+                            /**
+                             * NOTE: instead of looping here, please emulate 
GLS behaviour of
+                             * creating ONE ZIP entry per CAMT document.  */
+                            history.forEach {
+                                element("Ntry") {
+                                    element("Amt") {
+                                        attribute("Ccy", "EUR")
+                                        text(Amount(1).toPlainString())
+                                    }
+                                    element("CdtDbtInd") {
+                                        text("DBIT")
+                                    }
+                                    element("Sts") {
+                                        /* Status of the entry (see 2.4.2.15.5 
from the ISO20022 reference document.)
+                                         * From the original text:
+                                         * "Status of an entry on the books of 
the account servicer" */
+                                        text("BOOK")
+                                    }
+                                    element("BookgDt/Dt") {
+                                        text(now.toDashedDate())
+                                    } // date of the booking
+                                    element("ValDt/Dt") {
+                                        text(now.toDashedDate())
+                                    } // date of assets' actual 
(un)availability
+                                    element("AcctSvcrRef") {
+                                        text("0")
+                                    }
+                                    element("BkTxCd") {
+                                        /*  "Set of elements used to fully 
identify the type of underlying
+                                         *   transaction resulting in an 
entry".  */
+                                        element("Domn") {
+                                            element("Cd") {
+                                                text("PMNT")
+                                            }
+                                            element("Fmly") {
+                                                element("Cd") {
+                                                    text("ICDT")
+                                                }
+                                                element("SubFmlyCd") {
+                                                    text("ESCT")
+                                                }
+                                            }
                                         }
-                                        element("Fmly") {
+                                        element("Prtry") {
                                             element("Cd") {
-                                                text("ICDT")
+                                                text("0")
                                             }
-                                            element("SubFmlyCd") {
-                                                text("ESCT")
+                                            element("Issr") {
+                                                text("XY")
                                             }
                                         }
                                     }
-                                    element("Prtry") {
-                                        element("Cd") {
-                                            text("0")
+                                    element("NtryDtls/TxDtls") {
+                                        element("Refs") {
+                                            element("MsgId") {
+                                                text("0")
+                                            }
+                                            element("PmtInfId") {
+                                                text("0")
+                                            }
+                                            element("EndToEndId") {
+                                                text("NOTPROVIDED")
+                                            }
                                         }
-                                        element("Issr") {
-                                            text("XY")
+                                        element("AmtDtls/TxAmt/Amt") {
+                                            attribute("Ccy", "EUR")
+                                            text(Amount(1).toPlainString())
+                                        }
+                                        element("BkTxCd") {
+                                            element("Domn") {
+                                                element("Cd") {
+                                                    text("PMNT")
+                                                }
+                                                element("Fmly") {
+                                                    element("Cd") {
+                                                        text("ICDT")
+                                                    }
+                                                    element("SubFmlyCd") {
+                                                        text("ESCT")
+                                                    }
+                                                }
+                                            }
+                                            element("Prtry") {
+                                                element("Cd") {
+                                                    text("0")
+                                                }
+                                                element("Issr") {
+                                                    text("XY")
+                                                }
+                                            }
+                                        }
+                                        element("RltdPties") {
+                                            element("Dbtr/Nm") {
+                                                text("Max Mustermann")
+                                            }
+                                            element("DbtrAcct/Id/IBAN") {
+                                                text("GB33BUKB20201555555555")
+                                            }
+                                            element("Cdtr/Nm") {
+                                                text("Lina Musterfrau")
+                                            }
+                                            element("CdtrAcct/Id/IBAN") {
+                                                text("DE75512108001245126199")
+                                            }
+                                        }
+                                        element("RltdAgts") {
+                                            element("CdtrAgt/FinInstnId/BIC") {
+                                                text("GENODEM1GLS")
+                                            }
+                                        }
+                                        element("RmtInf/Ustrd") {
+                                            text("made up subject")
                                         }
                                     }
-                                }
-                                element("RltdPties") {
-                                    element("Dbtr/Nm") {
-                                        text("Max Mustermann")
-                                    }
-                                    element("DbtrAcct/Id/IBAN") {
-                                        text("GB33BUKB20201555555555")
-                                    }
-                                    element("Cdtr/Nm") {
-                                        text("Lina Musterfrau")
-                                    }
-                                    element("CdtrAcct/Id/IBAN") {
-                                        text("DE75512108001245126199")
-                                    }
-                                }
-                                element("RltdAgts") {
-                                    element("CdtrAgt/FinInstnId/BIC") {
-                                        text("GENODEM1GLS")
+                                    element("AddtlNtryInf") {
+                                        text("additional information not 
given")
                                     }
                                 }
-                                element("RmtInf/Ustrd") {
-                                    text("made up subject")
-                                }
-                            }
-                            element("AddtlNtryInf") {
-                                text("additional information not given")
                             }
                         }
-                    // }
+                    }
                 }
             }
-        }
+        )
     }
+    return ret
 }
 
 /**
  * Builds CAMT response.
  *
- * @param history the list of all the history elements
  * @param type 52 or 53.
  */
-private fun constructCamtResponse(type: Int, customerId: Int, header: 
EbicsRequest.Header): String {
-
+private fun constructCamtResponse(
+    type: Int,
+    header: EbicsRequest.Header,
+    subscriber: EbicsSubscriberEntity
+): MutableList<String> {
     val dateRange = (header.static.orderDetails?.orderParams as 
EbicsRequest.StandardOrderParams).dateRange
-    val (start: org.joda.time.DateTime, end: org.joda.time.DateTime) = if 
(dateRange != null) {
+    val (start: DateTime, end: DateTime) = if (dateRange != null) {
         Pair(DateTime(dateRange.start.toGregorianCalendar().time), 
DateTime(dateRange.end.toGregorianCalendar().time))
     } else Pair(DateTime(0), DateTime.now())
-    val history = extractHistory(
-        customerId,
-        start,
-        end
-    )
-    logger.debug("${history.count()} history elements found for account 
$customerId")
-    return buildCamtString(history, type)
+    val history = mutableListOf<RawPayment>()
+    transaction {
+        PaymentEntity.find {
+            PaymentsTable.ebicsSubscriber eq subscriber.id.value
+        }.forEach {
+            history.add(
+                RawPayment(
+                    subject = it.subject,
+                    creditorIban = it.creditorIban,
+                    debitorIban = it.debitorIban,
+                    date = DateTime(it.date).toDashedDate(),
+                    amount = it.amount
+                )
+            )
+        }
+        history
+    }
+    return buildCamtString(type, history)
 }
 
-
 private fun handleEbicsTSD(requestContext: RequestContext): ByteArray {
     return "Hello World".toByteArray()
 }
@@ -433,24 +438,41 @@ private fun handleEbicsPTK(requestContext: 
RequestContext): ByteArray {
     return "Hello I am a dummy PTK response.".toByteArray()
 }
 
-private fun handleEbicsC52(requestContext: RequestContext): ByteArray {
-    val subscriber = requestContext.subscriber
-    val camt = constructCamtResponse(
-        52,
-        subscriber.bankCustomer.id.value,
-        requestContext.requestObject.header
-    )
-    return camt.toByteArray().zip()
+/**
+ * Process a payment request in the pain.001 format.
+ */
+private fun handleCct(paymentRequest: String, ebicsSubscriber: 
EbicsSubscriberEntity) {
+    /**
+     * NOTE: this function is ONLY required to store some details
+     * to put then in the camt report.  IBANs / amount / subject / names?
+     */
+    val painDoc = XMLUtil.parseStringIntoDom(paymentRequest)
+    val creditorIban = 
painDoc.pickString("//*[local-name()='CdtrAcct']//*[local-name()='IBAN']")
+    val debitorIban = 
painDoc.pickString("//*[local-name()='DbtrAcct']//*[local-name()='IBAN']")
+    val subject = painDoc.pickString("//*[local-name()='Ustrd']")
+    val currency = painDoc.pickString("//*[local-name()='InstdAmt']/@ccy")
+    val amount = painDoc.pickString("//*[local-name()='InstdAmt']")
+
+    transaction {
+        PaymentEntity.new {
+            this.creditorIban = creditorIban
+            this.debitorIban = debitorIban
+            this.subject = subject
+            this.amount = "${currency}:${amount}"
+            this.ebicsSubscriber = ebicsSubscriber
+        }
+    }
 }
 
 private fun handleEbicsC53(requestContext: RequestContext): ByteArray {
-    val subscriber = requestContext.subscriber
     val camt = constructCamtResponse(
         53,
-        subscriber.bankCustomer.id.value,
-        requestContext.requestObject.header
+        requestContext.requestObject.header,
+        requestContext.subscriber
     )
-    return camt.toByteArray().zip()
+    return camt.map {
+        it.toByteArray(Charsets.UTF_8)
+    }.zip()
 }
 
 private suspend fun ApplicationCall.handleEbicsHia(header: 
EbicsUnsecuredRequest.Header, orderData: ByteArray) {
@@ -612,7 +634,6 @@ private suspend fun ApplicationCall.receiveEbicsXml(): 
Document {
     return requestDocument
 }
 
-
 fun handleEbicsHtd(): ByteArray {
     val htd = HTDResponseOrderData().apply {
         this.partnerInfo = EbicsTypes.PartnerInfo().apply {
@@ -814,7 +835,6 @@ private fun 
handleEbicsDownloadTransactionInitialization(requestContext: Request
         "HTD" -> handleEbicsHtd()
         "HKD" -> handleEbicsHkd()
         /* Temporarily handling C52/C53 with same logic */
-        "C52" -> handleEbicsC52(requestContext)
         "C53" -> handleEbicsC53(requestContext)
         "TSD" -> handleEbicsTSD(requestContext)
         "PTK" -> handleEbicsPTK(requestContext)
@@ -909,7 +929,6 @@ private fun 
handleEbicsUploadTransactionInitialization(requestContext: RequestCo
     return EbicsResponse.createForUploadInitializationPhase(transactionID, 
orderID)
 }
 
-
 private fun handleEbicsUploadTransactionTransmission(requestContext: 
RequestContext): EbicsResponse {
     val uploadTransaction = requestContext.uploadTransaction ?: throw 
EbicsInvalidRequestError()
     val requestObject = requestContext.requestObject
@@ -952,6 +971,10 @@ private fun 
handleEbicsUploadTransactionTransmission(requestContext: RequestCont
             }
         }
 
+        /** Handling a payment request */
+        if ("CCT" == 
requestContext.requestObject.header.static.orderDetails?.orderType) {
+            handleCct(unzippedData.toString(Charsets.UTF_8), 
requestContext.subscriber)
+        }
         return EbicsResponse.createForUploadTransferPhase(
             requestTransactionID,
             requestSegmentNumber,
diff --git a/sandbox/src/main/kotlin/tech/libeufin/sandbox/JSON.kt 
b/sandbox/src/main/kotlin/tech/libeufin/sandbox/JSON.kt
index 997cf57..3d4209b 100644
--- a/sandbox/src/main/kotlin/tech/libeufin/sandbox/JSON.kt
+++ b/sandbox/src/main/kotlin/tech/libeufin/sandbox/JSON.kt
@@ -19,147 +19,43 @@
 
 package tech.libeufin.sandbox
 
-/**
- * Error message.
- */
+/** Error message */
 data class SandboxError(
     val message: String
 )
 
 /**
- * Request for POST /admin/customers
- */
-data class CustomerRequest(
-    val name: String
-)
-
-data class CustomerResponse(
-    val id: Int
-)
-
-data class CustomerBalance(
-    val name: String,
-    val balance: String
-)
-
-/**
- * Response for GET /admin/customers/:id
+ * Used to show the list of Ebics hosts that exist
+ * in the system.
  */
-data class CustomerInfo(
-    val name: String,
-    val ebicsInfo: CustomerEbicsInfo
-)
-
-data class CustomerEbicsInfo(
-    val userId: String
-)
-
-data class CustomerHistoryRequest(
-    val start: String?,
-    val end: String?
-)
-
-data class CustomerHistoryResponseElement(
-    var amount: String,
-    val subject: String,
-    val counterpart: String,
-    val operationDate: String,
-    val valueDate: String
-)
-
-data class CustomerHistoryResponse(
-    var history: MutableList<CustomerHistoryResponseElement> = mutableListOf()
-)
-
-/**
- * Wrapper type around initialization letters
- * for RSA keys.
- */
-data class IniHiaLetters(
-    val ini: IniLetter,
-    val hia: HiaLetter
-)
-
-/**
- * Request for INI letter.
- */
-data class IniLetter(
-    val userId: String,
-    val customerId: String,
-    val name: String,
-    val date: String,
-    val time: String,
-    val recipient: String,
-    val public_exponent_length: Int,
-    val public_exponent: String,
-    val public_modulus_length: Int,
-    val public_modulus: String,
-    val hash: String
-)
-
-/**
- * Request for HIA letter.
- */
-data class HiaLetter(
-    val userId: String,
-    val customerId: String,
-    val name: String,
-    val date: String,
-    val time: String,
-    val recipient: String,
-    val ia_public_exponent_length: Int,
-    val ia_public_exponent: String,
-    val ia_public_modulus_length: Int,
-    val ia_public_modulus: String,
-    val ia_hash: String,
-    val enc_public_exponent_length: Int,
-    val enc_public_exponent: String,
-    val enc_public_modulus_length: Int,
-    val enc_public_modulus: String,
-    val enc_hash: String
-)
-
-data class EbicsSubscribersResponse(
-    val subscribers: List<String>
-)
-
-data class EbicsSubscriberResponse(
-    val id: String,
-    val partnerID: String,
-    val userID: String,
-    val systemID: String?,
-    val state: String
-)
-
 data class EbicsHostsResponse(
     val ebicsHosts: List<String>
 )
 
+/**
+ * Used to show information about ONE particular
+ * Ebics host that is active in the system.
+ */
 data class EbicsHostResponse(
     val hostID: String,
     val ebicsVersion: String
 )
 
 data class EbicsHostCreateRequest(
-    val hostId: String,
+    val hostID: String,
     val ebicsVersion: String
 )
 
-data class AdminAddSubscriberRequest(
-    val name: String, // person's name
+/**
+ * Used to create AND show one Ebics subscriber in the system.
+ */
+data class EbicsSubscriberElement(
     val hostID: String,
     val partnerID: String,
     val userID: String,
     val systemID: String? = null
 )
 
-data class AdminSubscriberElement(
-    var name: String,
-    var userId: String,
-    var partnerID: String,
-    var hostID: String
-)
-
 data class AdminGetSubscribers(
-    var subscribers: MutableList<AdminSubscriberElement> = mutableListOf()
+    var subscribers: MutableList<EbicsSubscriberElement> = mutableListOf()
 )
diff --git a/sandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt 
b/sandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt
index 2af9c6a..96afb72 100644
--- a/sandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt
+++ b/sandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt
@@ -60,20 +60,6 @@ class UnacceptableFractional(badNumber: BigDecimal) : 
Exception(
     "Unacceptable fractional part ${badNumber}"
 )
 val LOGGER: Logger = LoggerFactory.getLogger("tech.libeufin.sandbox")
-fun findCustomer(id: String?): BankCustomerEntity {
-
-    val idN = try {
-        id!!.toInt()
-    } catch (e: Exception) {
-        e.printStackTrace()
-        throw BadInputData(id)
-    }
-
-    return transaction {
-        addLogger(StdOutSqlLogger)
-        BankCustomerEntity.findById(idN) ?: throw CustomerNotFound(id)
-    }
-}
 
 fun findEbicsSubscriber(partnerID: String, userID: String, systemID: String?): 
EbicsSubscriberEntity? {
     return if (systemID == null) {
@@ -120,82 +106,8 @@ fun BigDecimal.signToString(): String {
     // minus sign is added by default already.
 }
 
-fun sampleData() {
-    transaction {
-        val pairA = CryptoUtil.generateRsaKeyPair(2048)
-        val pairB = CryptoUtil.generateRsaKeyPair(2048)
-        val pairC = CryptoUtil.generateRsaKeyPair(2048)
-        EbicsHostEntity.new {
-            hostId = "host01"
-            ebicsVersion = "H004"
-            authenticationPrivateKey = SerialBlob(pairA.private.encoded)
-            encryptionPrivateKey = SerialBlob(pairB.private.encoded)
-            signaturePrivateKey = SerialBlob(pairC.private.encoded)
-        }
-        val customerEntity = BankCustomerEntity.new {
-            addLogger(StdOutSqlLogger)
-            customerName = "Mina"
-        }
-        LOGGER.debug("Creating customer number: ${customerEntity.id}")
-        EbicsSubscriberEntity.new {
-            partnerId = "PARTNER1"
-            userId = "USER1"
-            systemId = null
-            hostId = "HOST01"
-            state = SubscriberState.NEW
-            nextOrderID = 1
-            bankCustomer = customerEntity
-        }
-        for (i in listOf(Amount("-0.44"), Amount("6.02"))) {
-            BankTransactionEntity.new {
-                counterpart = "IBAN"
-                amount = i
-                subject = "transaction $i"
-                operationDate = DateTime.now().millis
-                valueDate = DateTime.now().millis
-                localCustomer = customerEntity
-            }
-        }
-    }
-}
-
-/**
- * @param id the customer whose history must be returned.  This
- * id is local to the bank and is not reused/encoded into other
- * EBICS id values.
- *
- * @return result set of all the operations related to the customer
- * identified by @p id.
- */
-fun extractHistory(id: Int, start: DateTime, end: DateTime): 
SizedIterable<BankTransactionEntity> {
-    LOGGER.debug("Fetching history from ${start.toLocalDateTime()} to 
${end.toLocalDateTime()}")
-    return transaction {
-        addLogger(StdOutSqlLogger)
-        BankTransactionEntity.find {
-            BankTransactionsTable.localCustomer eq id and 
BankTransactionsTable.valueDate.between(start.millis, end.millis)
-        }
-    }
-}
-
-fun calculateBalance(id: Int, start: String?, end: String?): BigDecimal {
-    val s = if (start != null) DateTime.parse(start) else DateTime(0)
-    val e = if (end != null) DateTime.parse(end) else DateTime.now()
-
-    var ret = BigDecimal(0)
-
-    transaction {
-        BankTransactionEntity.find {
-            BankTransactionsTable.localCustomer eq id and 
BankTransactionsTable.operationDate.between(s.millis, e.millis)
-        }.forEach { ret += it.amount }
-    }
-    return ret
-}
-
 fun main() {
-
     dbCreateTables()
-    sampleData()
-
     val server = embeddedServer(Netty, port = 5000) {
         install(CallLogging) {
             this.level = Level.DEBUG
@@ -225,63 +137,15 @@ fun main() {
             }
         }
         routing {
-            post("/{id}/history") {
-                val req = call.receive<CustomerHistoryRequest>()
-                val customer = findCustomer(call.parameters["id"])
-                val ret = CustomerHistoryResponse()
-                val history = extractHistory(
-                    customer.id.value,
-                    DateTime.parse(req.start ?: "1970-01-01"),
-                    DateTime.parse(req.end ?: "3000-01-01")
-                )
-
-                transaction {
-                    history.forEach {
-                        ret.history.add(
-                            CustomerHistoryResponseElement(
-                                subject = it.subject,
-                                amount = 
"${it.amount.signToString()}${it.amount} EUR",
-                                counterpart = it.counterpart,
-                                operationDate = 
DateTime(it.operationDate).toString("Y-M-d"),
-                                valueDate = 
DateTime(it.valueDate).toString("Y-M-d")
-                            )
-                        )
-                    }
-                }
-                call.respond(ret)
-                return@post
-            }
-            get("/{id}/balance") {
-                val customer = findCustomer(call.parameters["id"])
-                val balance = calculateBalance(customer.id.value, null, null)
-                call.respond(
-                    CustomerBalance(
-                    name = customer.customerName,
-                    balance = "${balance} EUR"
-                    )
-                )
-                return@get
-            }
-            get("/admin/subscribers") {
-                var ret = AdminGetSubscribers()
-                transaction {
-                    EbicsSubscriberEntity.all().forEach {
-                        ret.subscribers.add(
-                            AdminSubscriberElement(
-                            userId = it.userId, partnerID = it.partnerId, 
hostID = it.hostId, name = it.bankCustomer.customerName))
-                    }
-                }
-                call.respond(ret)
-                return@get
+            get("/") {
+                call.respondText("Hello Sandbox!\n", ContentType.Text.Plain)
             }
-            post("/admin/add/subscriber") {
-                val body = call.receive<AdminAddSubscriberRequest>()
 
+            /** EBICS ADMIN ENDPOINTS */
+
+            post("/admin/ebics-subscriber") {
+                val body = call.receive<EbicsSubscriberElement>()
                 transaction {
-                    val customerEntity = BankCustomerEntity.new {
-                        addLogger(StdOutSqlLogger)
-                        customerName = body.name
-                    }
                     EbicsSubscriberEntity.new {
                         partnerId = body.partnerID
                         userId = body.userID
@@ -289,24 +153,48 @@ fun main() {
                         hostId = body.hostID
                         state = SubscriberState.NEW
                         nextOrderID = 1
-                        bankCustomer = customerEntity
                     }
                 }
-
-                call.respondText("Subscriber created.", 
ContentType.Text.Plain, HttpStatusCode.OK)
+                call.respondText(
+                    "Subscriber created.",
+                    ContentType.Text.Plain, HttpStatusCode.OK
+                )
                 return@post
             }
-
-            get("/") {
-                call.respondText("Hello LibEuFin!\n", ContentType.Text.Plain)
+            get("/admin/ebics-subscribers") {
+                var ret = AdminGetSubscribers()
+                transaction {
+                    EbicsSubscriberEntity.all().forEach {
+                        ret.subscribers.add(
+                            EbicsSubscriberElement(
+                                userID = it.userId,
+                                partnerID = it.partnerId,
+                                hostID = it.hostId
+                            )
+                        )
+                    }
+                }
+                call.respond(ret)
+                return@get
             }
-            get("/ebics/hosts") {
-                val ebicsHosts = transaction {
-                    EbicsHostEntity.all().map { it.hostId }
+            /* Show details about ONE Ebics host */
+            get("/ebics/hosts/{id}") {
+                val resp = transaction {
+                    val host = EbicsHostEntity.find { EbicsHostsTable.hostID 
eq call.parameters["id"]!! }.firstOrNull()
+                    if (host == null) null
+                    else EbicsHostResponse(
+                        host.hostId,
+                        host.ebicsVersion
+                    )
                 }
-                call.respond(EbicsHostsResponse(ebicsHosts))
+                if (resp == null) call.respond(
+                    HttpStatusCode.NotFound,
+                    SandboxError("host not found")
+                )
+                else call.respond(resp)
             }
-            post("/ebics/hosts") {
+            /** Create a new EBICS host. */
+            post("/admin/ebics-host") {
                 val req = call.receive<EbicsHostCreateRequest>()
                 val pairA = CryptoUtil.generateRsaKeyPair(2048)
                 val pairB = CryptoUtil.generateRsaKeyPair(2048)
@@ -315,7 +203,7 @@ fun main() {
                     addLogger(StdOutSqlLogger)
                     EbicsHostEntity.new {
                         this.ebicsVersion = req.ebicsVersion
-                        this.hostId = req.hostId
+                        this.hostId = req.hostID
                         this.authenticationPrivateKey = 
SerialBlob(pairA.private.encoded)
                         this.encryptionPrivateKey = 
SerialBlob(pairB.private.encoded)
                         this.signaturePrivateKey = 
SerialBlob(pairC.private.encoded)
@@ -323,47 +211,25 @@ fun main() {
                     }
                 }
                 call.respondText(
-                    "Host created.",
+                    "Host '${req.hostID}' created.",
                     ContentType.Text.Plain,
                     HttpStatusCode.OK
                 )
                 return@post
             }
-            get("/ebics/hosts/{id}") {
-                val resp = transaction {
-                    val host = EbicsHostEntity.find { EbicsHostsTable.hostID 
eq call.parameters["id"]!! }.firstOrNull()
-                    if (host == null) null
-                    else EbicsHostResponse(host.hostId, host.ebicsVersion)
-                }
-                if (resp == null) call.respond(
-                    HttpStatusCode.NotFound,
-                    SandboxError("host not found")
-                )
-                else call.respond(resp)
-            }
-            get("/ebics/subscribers") {
-                val subscribers = transaction {
-                    EbicsSubscriberEntity.all().map { it.id.value.toString() }
-                }
-                call.respond(EbicsSubscribersResponse(subscribers))
-            }
-            get("/ebics/subscribers/{id}") {
-                val resp = transaction {
-                    val id = call.parameters["id"]!!
-                    val subscriber = 
EbicsSubscriberEntity.findById(id.toInt())!!
-                    EbicsSubscriberResponse(
-                        id,
-                        subscriber.partnerId,
-                        subscriber.userId,
-                        subscriber.systemId,
-                        subscriber.state.name
-                    )
+            /* Show ONLY names of all the Ebics hosts */
+            get("/ebics/hosts") {
+                val ebicsHosts = transaction {
+                    EbicsHostEntity.all().map { it.hostId }
                 }
-                call.respond(resp)
+                call.respond(EbicsHostsResponse(ebicsHosts))
             }
+
+            /** MAIN EBICS handler. */
             post("/ebicsweb") {
                 call.ebicsweb()
             }
+
         }
     }
     LOGGER.info("Up and running")
diff --git a/sandbox/src/test/kotlin/CamtGeneration.kt 
b/sandbox/src/test/kotlin/CamtGeneration.kt
deleted file mode 100644
index 2b789ab..0000000
--- a/sandbox/src/test/kotlin/CamtGeneration.kt
+++ /dev/null
@@ -1,45 +0,0 @@
-package tech.libeufin.sandbox
-
-import org.jetbrains.exposed.sql.transactions.transaction
-import org.joda.time.DateTime
-import org.junit.Test
-import org.junit.Before
-import tech.libeufin.util.Amount
-import org.jetbrains.exposed.sql.SchemaUtils
-import org.jetbrains.exposed.sql.Database
-
-class CamtGeneration {
-
-    @Before
-    fun connectAndMakeTables() {
-        Database.connect("jdbc:h2:mem:test;DB_CLOSE_DELAY=-1", driver = 
"org.h2.Driver")
-        transaction {
-            SchemaUtils.create(BankCustomersTable)
-            SchemaUtils.create(BankTransactionsTable)
-        }
-    }
-
-    @Test
-    fun generateCamt() {
-        transaction {
-            val customer = BankCustomerEntity.new {
-                customerName = "payer"
-            }
-            for (iter in 1..5) {
-                BankTransactionEntity.new {
-                    localCustomer = customer
-                    counterpart = "IBAN${iter}"
-                    subject = "subject #${iter}"
-                    operationDate = DateTime.parse("3000-01-01").millis
-                    valueDate = DateTime.parse("3000-01-02").millis
-                    amount = Amount("${iter}.0")
-                }
-            }
-            val string = buildCamtString(
-                BankTransactionEntity.all(),
-                53
-            )
-            println(string)
-        }
-    }
-}
\ No newline at end of file
diff --git a/sandbox/src/test/kotlin/DbTest.kt 
b/sandbox/src/test/kotlin/DbTest.kt
deleted file mode 100644
index a0ff853..0000000
--- a/sandbox/src/test/kotlin/DbTest.kt
+++ /dev/null
@@ -1,137 +0,0 @@
-package tech.libeufin.sandbox
-
-import org.jetbrains.exposed.exceptions.ExposedSQLException
-import org.jetbrains.exposed.sql.Database
-import org.jetbrains.exposed.sql.SchemaUtils
-import org.jetbrains.exposed.sql.transactions.transaction
-import org.joda.time.DateTime
-import org.junit.Before
-import org.junit.Test
-import java.io.ByteArrayOutputStream
-import java.io.PrintStream
-import java.math.BigDecimal
-import java.math.BigInteger
-import java.math.MathContext
-import java.math.RoundingMode
-import kotlin.math.abs
-import kotlin.test.assertEquals
-import kotlin.test.assertFailsWith
-import kotlin.test.assertTrue
-import tech.libeufin.util.Amount
-import tech.libeufin.util.BadAmount
-
-
-class DbTest {
-
-    @Before
-    fun muteStderr() {
-        System.setErr(PrintStream(ByteArrayOutputStream()))
-    }
-
-    @Before
-    fun connectAndMakeTables() {
-        Database.connect("jdbc:h2:mem:test;DB_CLOSE_DELAY=-1", driver = 
"org.h2.Driver")
-        transaction {
-            SchemaUtils.create(BankTransactionsTable)
-            SchemaUtils.create(BankCustomersTable)
-        }
-    }
-
-    @Test
-    fun customers() {
-        transaction {
-            BankCustomerEntity.new {
-                customerName = "Test Name"
-            }
-            findCustomer("1")
-        }
-    }
-
-
-    @Test
-    fun goodAmount() {
-
-        transaction {
-            BankTransactionEntity.new {
-                amount = Amount("1")
-                counterpart = "IBAN"
-                subject = "Salary"
-                operationDate = DateTime.now().millis
-                valueDate = DateTime.now().millis
-                localCustomer = BankCustomerEntity.new {
-                    customerName = "employee"
-                }
-            }
-
-            BankTransactionEntity.new {
-                amount = Amount("1.11")
-                counterpart = "IBAN"
-                subject = "Salary"
-                operationDate = DateTime.now().millis
-                valueDate = DateTime.now().millis
-                localCustomer = BankCustomerEntity.new {
-                    customerName = "employee"
-                }
-            }
-
-            BankTransactionEntity.new {
-                amount = Amount("1.110000000000") // BigDecimal does not crop 
the trailing zeros
-                counterpart = "IBAN"
-                subject = "Salary"
-                operationDate = DateTime.now().millis
-                valueDate = DateTime.now().millis
-                localCustomer = BankCustomerEntity.new {
-                    customerName = "employee"
-                }
-            }
-        }
-    }
-
-    @Test
-    fun badAmount() {
-        assertFailsWith<BadAmount> {
-            transaction {
-                BankTransactionEntity.new {
-                    amount = Amount("1.10001")
-                    counterpart = "IBAN"
-                    subject = "Salary"
-                    operationDate = DateTime.now().millis
-                    valueDate = DateTime.now().millis
-                    localCustomer = BankCustomerEntity.new {
-                        customerName = "employee"
-                    }
-                }
-            }
-        }
-    }
-
-    @Test
-    fun timeBasedQuery() {
-
-
-        val NQUERIES = 10
-
-        transaction {
-
-            for (i in 1..NQUERIES) {
-                BankTransactionEntity.new {
-                    amount = Amount("1")
-                    counterpart = "IBAN"
-                    subject = "Salary"
-                    operationDate = DateTime.now().millis
-                    valueDate = DateTime.now().millis
-                    localCustomer = BankCustomerEntity.new {
-                        customerName = "employee"
-                    }
-                }
-            }
-
-            assertEquals(
-                NQUERIES,
-                BankTransactionEntity.find {
-                    
BankTransactionsTable.valueDate.between(DateTime.parse("1970-01-01").millis, 
DateTime.parse("2999-12-31").millis)
-                }.count()
-            )
-        }
-    }
-}
\ No newline at end of file
diff --git a/util/src/main/kotlin/ebics_h004/EbicsResponse.kt 
b/util/src/main/kotlin/ebics_h004/EbicsResponse.kt
index 67a8b02..bdc17b0 100644
--- a/util/src/main/kotlin/ebics_h004/EbicsResponse.kt
+++ b/util/src/main/kotlin/ebics_h004/EbicsResponse.kt
@@ -150,7 +150,6 @@ class EbicsResponse {
             }
         }
 
-
         fun createForDownloadReceiptPhase(transactionID: String, positiveAck: 
Boolean): EbicsResponse {
             return EbicsResponse().apply {
                 this.version = "H004"
diff --git a/util/src/main/kotlin/zip.kt b/util/src/main/kotlin/zip.kt
index e64d81b..09144fc 100644
--- a/util/src/main/kotlin/zip.kt
+++ b/util/src/main/kotlin/zip.kt
@@ -8,18 +8,20 @@ import org.apache.commons.compress.archivers.zip.ZipFile
 import org.apache.commons.compress.utils.IOUtils
 import org.apache.commons.compress.utils.SeekableInMemoryByteChannel
 
-
-fun ByteArray.zip(): ByteArray {
-
+fun List<ByteArray>.zip(): ByteArray {
     val baos = ByteArrayOutputStream()
-    val asf = 
ArchiveStreamFactory().createArchiveOutputStream(ArchiveStreamFactory.ZIP, baos)
-    val zae = ZipArchiveEntry("File 1")
-    asf.putArchiveEntry(zae) // link Zip archive to output stream.
-
-    val bais = ByteArrayInputStream(this)
-    IOUtils.copy(bais, asf)
-    bais.close()
-    asf.closeArchiveEntry()
+    val asf = ArchiveStreamFactory().createArchiveOutputStream(
+        ArchiveStreamFactory.ZIP,
+        baos
+    )
+    for (fileIndex in this.indices) {
+        val zae = ZipArchiveEntry("File $fileIndex")
+        asf.putArchiveEntry(zae)
+        val bais = ByteArrayInputStream(this[fileIndex])
+        IOUtils.copy(bais, asf)
+        bais.close()
+        asf.closeArchiveEntry()
+    }
     asf.finish()
     baos.close()
     return baos.toByteArray()
@@ -37,7 +39,7 @@ fun ByteArray.prettyPrintUnzip(): String {
     return s.toString()
 }
 
-fun ByteArray.unzipWithLoop(process: (Pair<String, String>) -> Unit) {
+fun ByteArray.unzipWithLambda(process: (Pair<String, String>) -> Unit) {
     val mem = SeekableInMemoryByteChannel(this)
     val zipFile = ZipFile(mem)
     zipFile.getEntriesInPhysicalOrder().iterator().forEach {

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



reply via email to

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