gnunet-svn
[Top][All Lists]
Advanced

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

[libeufin] branch master updated (f73bdad -> 7033a39)


From: gnunet
Subject: [libeufin] branch master updated (f73bdad -> 7033a39)
Date: Sun, 10 May 2020 01:15:16 +0200

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

ms pushed a change to branch master
in repository libeufin.

    from f73bdad  POST preapred-payments/submit
     new e752f65  POST ../prepared-payments
     new ce65389  POST ../collected-transactions
     new 4b42c65  Organize JSON types.
     new d0a36f7  API migration
     new 7033a39  POST ../collected-transactions

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


Summary of changes:
 nexus/build.gradle                                 |   2 +-
 nexus/src/main/kotlin/tech/libeufin/nexus/DB.kt    |  64 ++++----
 .../src/main/kotlin/tech/libeufin/nexus/Helpers.kt | 105 +++++++------
 nexus/src/main/kotlin/tech/libeufin/nexus/JSON.kt  | 166 +++++++++++++--------
 nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt  | 156 ++++++++++++++++---
 .../kotlin/tech/libeufin/nexus/MainDeprecated.kt   |  14 +-
 nexus/src/main/kotlin/tech/libeufin/nexus/taler.kt |  25 +---
 nexus/src/test/kotlin/PainGeneration.kt            |   6 +-
 util/src/main/kotlin/strings.kt                    |  13 ++
 9 files changed, 358 insertions(+), 193 deletions(-)

diff --git a/nexus/build.gradle b/nexus/build.gradle
index ff58a78..b2a264a 100644
--- a/nexus/build.gradle
+++ b/nexus/build.gradle
@@ -16,7 +16,7 @@ plugins {
 
 sourceSets {
     main.kotlin.srcDirs = ["src/main/kotlin"]
-    main.kotlin.excludes = ["**/MainDeprecated.kt"]
+    main.kotlin.excludes = ["**/MainDeprecated.kt", "**/taler.kt"]
 }
 
 task installToPrefix(type: Copy) {
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/DB.kt 
b/nexus/src/main/kotlin/tech/libeufin/nexus/DB.kt
index ba5f7f8..957e3ca 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/DB.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/DB.kt
@@ -5,8 +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.BankAccountsTable.entityId
-import tech.libeufin.nexus.BankAccountsTable.primaryKey
 import tech.libeufin.util.amount
 import java.sql.Connection
 
@@ -19,7 +17,7 @@ const val ID_MAX_LENGTH = 50
  * in the PAIN-table.
  */
 object TalerRequestedPayments: LongIdTable() {
-    val preparedPayment = reference("payment", Pain001Table)
+    val preparedPayment = reference("payment", PreparedPaymentsTable)
     val requestUId = text("request_uid")
     val amount = text("amount")
     val exchangeBaseUrl = text("exchange_base_url")
@@ -34,7 +32,7 @@ object TalerRequestedPayments: LongIdTable() {
 
 class TalerRequestedPaymentEntity(id: EntityID<Long>) : LongEntity(id) {
     companion object : 
LongEntityClass<TalerRequestedPaymentEntity>(TalerRequestedPayments)
-    var preparedPayment by Pain001Entity referencedOn 
TalerRequestedPayments.preparedPayment
+    var preparedPayment by PreparedPaymentEntity referencedOn 
TalerRequestedPayments.preparedPayment
     var requestUId by TalerRequestedPayments.requestUId
     var amount by TalerRequestedPayments.amount
     var exchangeBaseUrl by TalerRequestedPayments.exchangeBaseUrl
@@ -91,13 +89,12 @@ object RawBankTransactionsTable : LongIdTable() {
     val transactionType = text("transactionType") /* DBIT or CRDT */
     val currency = text("currency")
     val amount = text("amount")
-    val creditorIban = text("creditorIban")
-    val creditorName = text("creditorBic")
-    val debitorIban = text("debitorIban")
-    val debitorName = text("debitorName")
+    val counterpartIban = text("counterpartIban")
     val counterpartBic = text("counterpartBic")
+    val counterpartName = text("counterpartName")
     val bookingDate = long("bookingDate")
     val status = text("status") // BOOK or other.
+    val bankAccount = reference("bankAccount", BankAccountsTable)
 }
 
 class RawBankTransactionEntity(id: EntityID<Long>) : LongEntity(id) {
@@ -107,22 +104,23 @@ class RawBankTransactionEntity(id: EntityID<Long>) : 
LongEntity(id) {
     var transactionType by RawBankTransactionsTable.transactionType
     var currency by RawBankTransactionsTable.currency
     var amount by RawBankTransactionsTable.amount
-    var debitorIban by RawBankTransactionsTable.debitorIban
-    var debitorName by RawBankTransactionsTable.debitorName
-    var creditorName by RawBankTransactionsTable.creditorName
-    var creditorIban by RawBankTransactionsTable.creditorIban
+    var counterpartIban by RawBankTransactionsTable.counterpartIban
     var counterpartBic by RawBankTransactionsTable.counterpartBic
+    var counterpartName by RawBankTransactionsTable.counterpartName
     var bookingDate by RawBankTransactionsTable.bookingDate
     var nexusUser by NexusUserEntity referencedOn 
RawBankTransactionsTable.nexusUser
     var status by RawBankTransactionsTable.status
+    var bankAccount by BankAccountEntity referencedOn 
RawBankTransactionsTable.bankAccount
 }
 /**
  * Represent a prepare payment.
  */
-object Pain001Table : IdTable<String>() {
+object PreparedPaymentsTable : IdTable<String>() {
+    /** the UUID representing this payment in the system */
     override val id = BankAccountsTable.varchar("id", 
ID_MAX_LENGTH).entityId().primaryKey()
     val paymentId = long("paymentId")
-    val fileDate = long("fileDate")
+    val preparationDate = long("preparationDate")
+    val submissionDate = long("submissionDate").nullable()
     val sum = amount("sum")
     val currency = varchar("currency", length = 3).default("EUR")
     val endToEndId = long("EndToEndId")
@@ -139,25 +137,27 @@ object Pain001Table : IdTable<String>() {
      * this state can be reached when the payment gets listed in a CRZ
      * response OR when the payment doesn't show up in a C52/C53 response */
     val invalid = bool("invalid").default(false)
+    /** never really used, but it makes sure the user always exists  */
     val nexusUser = reference("nexusUser", NexusUsersTable)
 }
-class Pain001Entity(id: EntityID<String>) : Entity<String>(id) {
-    companion object : EntityClass<String, Pain001Entity>(Pain001Table)
-    var paymentId by Pain001Table.paymentId
-    var date by Pain001Table.fileDate
-    var sum by Pain001Table.sum
-    var currency by Pain001Table.currency
-    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
-    var creditorBic by Pain001Table.creditorBic
-    var creditorName by Pain001Table.creditorName
-    var submitted by Pain001Table.submitted
-    var invalid by Pain001Table.invalid
-    var nexusUser by NexusUserEntity referencedOn Pain001Table.nexusUser
+class PreparedPaymentEntity(id: EntityID<String>) : Entity<String>(id) {
+    companion object : EntityClass<String, 
PreparedPaymentEntity>(PreparedPaymentsTable)
+    var paymentId by PreparedPaymentsTable.paymentId
+    var preparationDate by PreparedPaymentsTable.preparationDate
+    var submissionDate by PreparedPaymentsTable.submissionDate
+    var sum by PreparedPaymentsTable.sum
+    var currency by PreparedPaymentsTable.currency
+    var debitorIban by PreparedPaymentsTable.debitorIban
+    var debitorBic by PreparedPaymentsTable.debitorBic
+    var debitorName by PreparedPaymentsTable.debitorName
+    var endToEndId by PreparedPaymentsTable.endToEndId
+    var subject by PreparedPaymentsTable.subject
+    var creditorIban by PreparedPaymentsTable.creditorIban
+    var creditorBic by PreparedPaymentsTable.creditorBic
+    var creditorName by PreparedPaymentsTable.creditorName
+    var submitted by PreparedPaymentsTable.submitted
+    var invalid by PreparedPaymentsTable.invalid
+    var nexusUser by NexusUserEntity referencedOn 
PreparedPaymentsTable.nexusUser
 }
 
 /**
@@ -236,7 +236,7 @@ fun dbCreateTables() {
     transaction {
         addLogger(StdOutSqlLogger)
         SchemaUtils.create(
-            Pain001Table,
+            PreparedPaymentsTable,
             EbicsSubscribersTable,
             BankAccountsTable,
             RawBankTransactionsTable,
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/Helpers.kt 
b/nexus/src/main/kotlin/tech/libeufin/nexus/Helpers.kt
index 638d2fc..2b5eb66 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/Helpers.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/Helpers.kt
@@ -1,11 +1,9 @@
 package tech.libeufin.nexus
 
-import io.ktor.application.ApplicationCall
 import io.ktor.http.HttpStatusCode
 import org.jetbrains.exposed.sql.and
 import org.jetbrains.exposed.sql.transactions.transaction
 import org.joda.time.DateTime
-import org.joda.time.format.DateTimeFormat
 import tech.libeufin.util.Amount
 import tech.libeufin.util.CryptoUtil
 import tech.libeufin.util.EbicsClientSubscriberDetails
@@ -62,23 +60,18 @@ fun extractFirstBic(bankCodes: 
List<EbicsTypes.AbstractBankCode>?): String? {
 }
 
 /**
- * Given a nexus user id, returns the _list_ of bank accounts associated to it.
- *
- * @param id the subscriber id
- * @return the bank account associated with this user.  Can/should be adapted 
to
- * return multiple bank accounts.
+ * Retrieve bank account details, only if user owns it.
  */
-fun getBankAccountFromNexusUserId(id: String): BankAccountEntity {
-    logger.debug("Looking up bank account of user '$id'")
-    val map = transaction {
-        BankAccountMapEntity.find {
-            BankAccountMapsTable.nexusUser eq id
-        }
-    }.firstOrNull() ?: throw NexusError(
+fun getBankAccount(userId: String, accountId: String): BankAccountEntity {
+    return transaction {
+        val bankAccountMap = BankAccountMapEntity.find {
+            BankAccountMapsTable.nexusUser eq userId
+        }.firstOrNull() ?: throw NexusError(
         HttpStatusCode.NotFound,
-        "Such user '$id' does not have any bank account associated"
-    )
-    return map.bankAccount
+        "Bank account '$accountId' not found"
+        )
+        bankAccountMap.bankAccount
+    }
 }
 
 /**
@@ -156,7 +149,7 @@ fun getSubscriberDetailsFromNexusUserId(id: String): 
EbicsClientSubscriberDetail
  * Create a PAIN.001 XML document according to the input data.
  * Needs to be called within a transaction block.
  */
-fun createPain001document(pain001Entity: Pain001Entity): String {
+fun createPain001document(paymentData: PreparedPaymentEntity): String {
     /**
      * Every PAIN.001 document contains at least three IDs:
      *
@@ -172,8 +165,8 @@ fun createPain001document(pain001Entity: Pain001Entity): 
String {
      */
     val debitorBankAccountLabel = transaction {
         val debitorBankAcount = BankAccountEntity.find {
-            BankAccountsTable.iban eq pain001Entity.debitorIban and
-                    (BankAccountsTable.bankCode eq pain001Entity.debitorBic)
+            BankAccountsTable.iban eq paymentData.debitorIban and
+                    (BankAccountsTable.bankCode eq paymentData.debitorBic)
         }.firstOrNull() ?: throw NexusError(
             HttpStatusCode.NotFound,
             "Please download bank accounts details first (HTD)"
@@ -189,11 +182,11 @@ fun createPain001document(pain001Entity: Pain001Entity): 
String {
             element("CstmrCdtTrfInitn") {
                 element("GrpHdr") {
                     element("MsgId") {
-                        text(pain001Entity.id.value.toString())
+                        text(paymentData.id.value.toString())
                     }
                     element("CreDtTm") {
                         val dateMillis = transaction {
-                            pain001Entity.date
+                            paymentData.preparationDate
                         }
                         val dateFormatter = 
DateTimeFormatter.ISO_OFFSET_DATE_TIME
                         val instant = Instant.ofEpochSecond(dateMillis / 1000)
@@ -204,7 +197,7 @@ fun createPain001document(pain001Entity: Pain001Entity): 
String {
                         text("1")
                     }
                     element("CtrlSum") {
-                        text(pain001Entity.sum.toString())
+                        text(paymentData.sum.toString())
                     }
                     element("InitgPty/Nm") {
                         text(debitorBankAccountLabel)
@@ -212,7 +205,7 @@ fun createPain001document(pain001Entity: Pain001Entity): 
String {
                 }
                 element("PmtInf") {
                     element("PmtInfId") {
-                        text(pain001Entity.id.value.toString())
+                        text(paymentData.id.value.toString())
                     }
                     element("PmtMtd") {
                         text("TRF")
@@ -224,14 +217,14 @@ fun createPain001document(pain001Entity: Pain001Entity): 
String {
                         text("1")
                     }
                     element("CtrlSum") {
-                        text(pain001Entity.sum.toString())
+                        text(paymentData.sum.toString())
                     }
                     element("PmtTpInf/SvcLvl/Cd") {
                         text("SEPA")
                     }
                     element("ReqdExctnDt") {
                         val dateMillis = transaction {
-                            pain001Entity.date
+                            paymentData.preparationDate
                         }
                         text(DateTime(dateMillis).toString("Y-MM-dd"))
                     }
@@ -239,10 +232,10 @@ fun createPain001document(pain001Entity: Pain001Entity): 
String {
                         text(debitorBankAccountLabel)
                     }
                     element("DbtrAcct/Id/IBAN") {
-                        text(pain001Entity.debitorIban)
+                        text(paymentData.debitorIban)
                     }
                     element("DbtrAgt/FinInstnId/BIC") {
-                        text(pain001Entity.debitorBic)
+                        text(paymentData.debitorBic)
                     }
                     element("ChrgBr") {
                         text("SLEV")
@@ -255,20 +248,20 @@ fun createPain001document(pain001Entity: Pain001Entity): 
String {
                             }
                         }
                         element("Amt/InstdAmt") {
-                            attribute("Ccy", pain001Entity.currency)
-                            text(pain001Entity.sum.toString())
+                            attribute("Ccy", paymentData.currency)
+                            text(paymentData.sum.toString())
                         }
                         element("CdtrAgt/FinInstnId/BIC") {
-                            text(pain001Entity.creditorBic)
+                            text(paymentData.creditorBic)
                         }
                         element("Cdtr/Nm") {
-                            text(pain001Entity.creditorName)
+                            text(paymentData.creditorName)
                         }
                         element("CdtrAcct/Id/IBAN") {
-                            text(pain001Entity.creditorIban)
+                            text(paymentData.creditorIban)
                         }
                         element("RmtInf/Ustrd") {
-                            text(pain001Entity.subject)
+                            text(paymentData.subject)
                         }
                     }
                 }
@@ -278,6 +271,19 @@ fun createPain001document(pain001Entity: Pain001Entity): 
String {
     return s
 }
 
+/**
+ * Retrieve prepared payment from database, raising exception
+ * if not found.
+ */
+fun getPreparedPayment(uuid: String): PreparedPaymentEntity {
+    return transaction {
+        PreparedPaymentEntity.findById(uuid)
+    } ?: throw NexusError(
+        HttpStatusCode.NotFound,
+        "Payment '$uuid' not found"
+    )
+}
+
 /**
  * Insert one row in the database, and leaves it marked as non-submitted.
  * @param debtorAccountId the mnemonic id assigned by the bank to one bank
@@ -285,19 +291,20 @@ 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, nexusUser: NexusUserEntity): 
Pain001Entity {
+fun addPreparedPayment(paymentData: Pain001Data, nexusUser: NexusUserEntity): 
PreparedPaymentEntity {
     val randomId = Random().nextLong()
     return transaction {
-        Pain001Entity.new(randomId.toString()) {
-            subject = entry.subject
-            sum = entry.sum
-            debitorIban = entry.debitorIban
-            debitorBic = entry.debitorBic
-            debitorName = entry.debitorName
-            creditorName = entry.creditorName
-            creditorBic = entry.creditorBic
-            creditorIban = entry.creditorIban
-            date = DateTime.now().millis
+        val debitorAccount = getBankAccount(nexusUser.id.value, 
paymentData.debitorAccount)
+        PreparedPaymentEntity.new(randomId.toString()) {
+            subject = paymentData.subject
+            sum = paymentData.sum
+            debitorIban = debitorAccount.iban
+            debitorBic = debitorAccount.bankCode
+            debitorName = debitorAccount.accountHolder
+            creditorName = paymentData.creditorName
+            creditorBic = paymentData.creditorBic
+            creditorIban = paymentData.creditorIban
+            preparationDate = DateTime.now().millis
             paymentId = randomId
             endToEndId = randomId
             this.nexusUser = nexusUser
@@ -322,14 +329,6 @@ fun extractNexusUser(param: String?): NexusUserEntity {
     }
 }
 
-/* Needs a transaction{} block to be called */
-fun expectAcctidTransaction(param: String?): BankAccountEntity {
-    if (param == null) {
-        throw NexusError(HttpStatusCode.BadRequest, "Null Acctid given")
-    }
-    return BankAccountEntity.findById(param) ?: throw 
NexusError(HttpStatusCode.NotFound, "Account: $param not found")
-}
-
 /**
  * This helper function parses a Authorization:-header line, decode the 
credentials
  * and returns a pair made of username and hashed (sha256) password.  The 
hashed value
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/JSON.kt 
b/nexus/src/main/kotlin/tech/libeufin/nexus/JSON.kt
index c6d6f01..ded37a1 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/JSON.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/JSON.kt
@@ -12,9 +12,7 @@ data class NexusErrorJson(
     val message: String
 )
 
-data class EbicsStandardOrderParamsJson(
-    val dateRange: EbicsDateRangeJson?
-) {
+data class EbicsStandardOrderParamsJson(val dateRange: EbicsDateRangeJson?) {
     fun toOrderParams(): EbicsOrderParams {
         var dateRange: EbicsDateRange? = if (this.dateRange != null) {
             EbicsDateRange(
@@ -29,9 +27,7 @@ data class EbicsStandardOrderParamsJson(
 }
 
 data class EbicsDateRangeJson(
-    /**
-     * ISO 8601 calendar dates: YEAR-MONTH(01-12)-DAY(1-31)
-     */
+    /** ISO 8601 calendar dates: YEAR-MONTH(01-12)-DAY(1-31) */
     val start: String?,
     val end: String?
 )
@@ -76,89 +72,139 @@ data class EbicsErrorJson(
     val error: EbicsErrorDetailJson
 )
 
-data class BankAccount(
-    var holder: String,
-    var iban: String,
-    var bic: String,
-    var account: String
+/** Instructs the nexus to CREATE a new Ebics subscriber.
+ * Note that the nexus user to which the subscriber must be
+ * associated is extracted from other HTTP details.
+ *
+ * This same structure can be user to SHOW one Ebics subscriber
+ * existing at the nexus.
+ */
+data class EbicsSubscriber(
+    val ebicsURL: String,
+    val hostID: String,
+    val partnerID: String,
+    val userID: String,
+    val systemID: String? = null
 )
 
-data class BankAccounts(
-    var accounts: MutableList<BankAccount> = mutableListOf()
+data class RawPayments(
+    var payments: MutableList<RawPayment> = mutableListOf()
 )
 
-/** THE NEXUS USER */
+/*************************************************
+ *  API types (used as requests/responses types) *
+ *************************************************/
 
-/** SHOWS details about one user */
-data class NexusUser(
-    val userID: String,
-    val transports: MutableList<Any> = mutableListOf()
+/** Response type of "GET /prepared-payments/{uuid}" */
+data class PaymentStatus(
+    val uuid: String,
+    val submitted: Boolean,
+    val creditorIban: String,
+    val creditorBic: String,
+    val creditorName: String,
+    val amount: String,
+    val subject: String,
+    val submissionDate: String,
+    val preparationDate: String
+)
+
+/** Response type of "GET /collected-transactions" */
+data class Transaction(
+    val account: String,
+    val counterpartIban: String,
+    val counterpartBic: String,
+    val counterpartName: String,
+    val amount: String,
+    val subject: String,
+    val date: String
 )
 
-/** is "UserResponse" in the API spec */
+data class Transactions(
+    val transactions: MutableList<Transaction> = mutableListOf()
+)
+
+/** Request type of "POST /prepared-payments/submit" */
+data class SubmitPayment(
+    val uuid: String,
+    val transport: String?
+)
+
+/** Request type of "POST /collected-transactions" */
+data class CollectedTransaction(
+    val transport: String?,
+    val start: String?,
+    val end: String?
+)
+
+/** Request type of "POST /prepared-payments" */
+data class PreparedPaymentRequest(
+    val iban: String,
+    val bic: String,
+    val name: String,
+    val amount: String,
+    val subject: String
+)
+
+/** Response type of "POST /prepared-payments" */
+data class PreparedPaymentResponse(
+    val uuid: String
+)
+
+/** Response type of "GET /user" */
 data class UserResponse(
     val username: String,
     val superuser: Boolean
 )
 
-/** Instructs the nexus to CREATE a new user */
+/** Request type of "POST /users" */
 data class User(
     val username: String,
     val password: String
 )
 
-/** Collection of all the nexus users existing in the system */
-data class Users(
-    val users: MutableList<NexusUser> = mutableListOf()
+/** Response (list's element) type of "GET /bank-accounts" */
+data class BankAccount(
+    var holder: String,
+    var iban: String,
+    var bic: String,
+    var account: String
 )
 
-/************************************/
-
-/** TRANSPORT TYPES */
-
-/** Instructs the nexus to CREATE a new Ebics subscriber.
- * Note that the nexus user to which the subscriber must be
- * associated is extracted from other HTTP details.
- *
- * This same structure can be user to SHOW one Ebics subscriber
- * existing at the nexus.
- */
-data class EbicsSubscriber(
-    val ebicsURL: String,
-    val hostID: String,
-    val partnerID: String,
-    val userID: String,
-    val systemID: String? = null
+/** Response type of "GET /bank-accounts" */
+data class BankAccounts(
+    var accounts: MutableList<BankAccount> = mutableListOf()
 )
 
-/** Type representing the "test" transport.  Test transport
- * does not cooperate with the bank/sandbox in order to obtain
- * data about one user.  All the data is just mocked internally
- * at the NEXUS.
- */
-class TestSubscriber()
-
 
-/** PAYMENT INSTRUCTIONS TYPES */
+/**********************************************************************
+ * Convenience types (ONLY used to gather data together in one place) *
+ **********************************************************************/
 
-/** 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 debitorAccount: String,
     val sum: Amount,
-    val currency: String = "EUR",
+    val currency: String,
     val subject: String
 )
 
-data class RawPayments(
-    var payments: MutableList<RawPayment> = mutableListOf()
-)
 
-data class SubmitPayment(
-    val uuid: String,
-    val transport: String?
-)
\ No newline at end of file
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt 
b/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt
index e0d61f2..4a6daf7 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt
@@ -50,12 +50,8 @@ import org.slf4j.Logger
 import org.slf4j.LoggerFactory
 import org.slf4j.event.Level
 import tech.libeufin.util.*
-import tech.libeufin.util.ebics_h004.HTDResponseOrderData
 import java.text.DateFormat
-import java.text.SimpleDateFormat
-import java.util.*
 import java.util.zip.InflaterInputStream
-import javax.crypto.EncryptedPrivateKeyInfo
 import javax.sql.rowset.serial.SerialBlob
 
 data class NexusError(val statusCode: HttpStatusCode, val reason: String) : 
Exception()
@@ -187,27 +183,18 @@ fun main() {
             post("/bank-accounts/{accountid}/prepared-payments/submit") {
                 val userId = 
authenticateRequest(call.request.headers["Authorization"])
                 val body = call.receive<SubmitPayment>()
-
-                // 1 find payment.
-                val preparedPayment = transaction {
-                    Pain001Entity.findById(body.uuid)
-                } ?: throw NexusError(
-                    HttpStatusCode.NotFound,
-                    "Could not find prepared payment: ${body.uuid}"
+                val preparedPayment = getPreparedPayment(body.uuid)
+                if (preparedPayment.nexusUser.id.value != userId) throw 
NexusError(
+                    HttpStatusCode.Forbidden,
+                    "No rights over such payment"
                 )
-
-                // 2 check if was submitted yet
                 if (preparedPayment.submitted) {
                     throw NexusError(
                         HttpStatusCode.PreconditionFailed,
                         "Payment ${body.uuid} was submitted already"
                     )
                 }
-
-                // 3 submit
                 val pain001document = createPain001document(preparedPayment)
-
-                // 4 check if the user has a instance in such bank transport.
                 when (body.transport) {
                     "ebics" -> {
                         val subscriberDetails = 
getSubscriberDetailsFromNexusUserId(userId)
@@ -221,7 +208,7 @@ fun main() {
                         )
                         /** mark payment as 'submitted' */
                         transaction {
-                            val payment = Pain001Entity.findById(body.uuid) ?: 
throw NexusError(
+                            val payment = 
PreparedPaymentEntity.findById(body.uuid) ?: throw NexusError(
                                 HttpStatusCode.InternalServerError,
                                 "Severe internal error: could not find payment 
in DB after having submitted it to the bank"
                             )
@@ -232,6 +219,7 @@ fun main() {
                             ContentType.Text.Plain,
                             HttpStatusCode.OK
                         )
+                        preparedPayment.submissionDate = DateTime.now().millis
                     }
                     else -> throw NexusError(
                         HttpStatusCode.NotImplemented,
@@ -244,24 +232,153 @@ fun main() {
              * Shows information about one particular prepared payment.
              */
             get("/bank-accounts/{accountid}/prepared-payments/{uuid}") {
+                val userId = 
authenticateRequest(call.request.headers["Authorization"])
+                val preparedPayment = 
getPreparedPayment(expectId(call.parameters["uuid"]))
+                if (preparedPayment.nexusUser.id.value != userId) throw 
NexusError(
+                    HttpStatusCode.Forbidden,
+                    "No rights over such payment"
+                )
+                call.respond(
+                    PaymentStatus(
+                        uuid = preparedPayment.id.value,
+                        submitted = preparedPayment.submitted,
+                        creditorName = preparedPayment.creditorName,
+                        creditorBic = preparedPayment.creditorBic,
+                        creditorIban = preparedPayment.creditorIban,
+                        amount = 
"${preparedPayment.sum}:${preparedPayment.currency}",
+                        subject = preparedPayment.subject,
+                        submissionDate = 
DateTime(preparedPayment.submissionDate).toDashedDate(),
+                        preparationDate = 
DateTime(preparedPayment.preparationDate).toDashedDate()
+                    )
+                )
                 return@get
             }
             /**
              * Adds a new prepared payment.
              */
             post("/bank-accounts/{accountid}/prepared-payments") {
+                val userId = 
authenticateRequest(call.request.headers["Authorization"])
+                val bankAccount = getBankAccount(userId, 
expectId(call.parameters["accountid"]))
+                val body = call.receive<PreparedPaymentRequest>()
+                val amount = parseAmount(body.amount)
+                val paymentEntity = addPreparedPayment(
+                    Pain001Data(
+                        creditorIban = body.iban,
+                        creditorBic = body.bic,
+                        creditorName = body.name,
+                        debitorAccount = bankAccount.id.value,
+                        sum = amount.amount,
+                        currency = amount.currency,
+                        subject = body.subject
+                    ),
+                    extractNexusUser(userId)
+                )
+                call.respond(
+                    HttpStatusCode.OK,
+                    PreparedPaymentResponse(uuid = paymentEntity.id.value)
+                )
                 return@post
             }
             /**
              * Downloads new transactions from the bank.
              */
             post("/bank-accounts/{accountid}/collected-transactions") {
+                val userId = 
authenticateRequest(call.request.headers["Authorization"])
+                val body = call.receive<CollectedTransaction>()
+                when (body.transport) {
+                    "ebics" -> {
+                        val orderParams = EbicsStandardOrderParamsJson(
+                            EbicsDateRangeJson(
+                                body.start,
+                                body.end
+                            )
+                        ).toOrderParams()
+                        val subscriberData = 
getSubscriberDetailsFromNexusUserId(userId)
+                        when (val response = 
doEbicsDownloadTransaction(client, subscriberData, "C53", orderParams)) {
+                            is EbicsDownloadSuccessResult -> {
+                                /**
+                                 * The current code is _heavily_ dependent on 
the way GLS returns
+                                 * data.  For example, GLS makes one ZIP entry 
for each "Ntry" element
+                                 * (a bank transfer), but per the 
specifications one bank can choose to
+                                 * return all the "Ntry" elements into one 
single ZIP entry, or even unzipped
+                                 * at all.
+                                 */
+                                response.orderData.unzipWithLambda {
+                                    logger.debug("C53 entry: ${it.second}")
+                                    val fileName = it.first
+                                    val camt53doc = 
XMLUtil.parseStringIntoDom(it.second)
+                                    transaction {
+                                        RawBankTransactionEntity.new {
+                                            bankAccount = 
getBankAccountFromIban(camt53doc.pickString("//*[local-name()='Stmt']/Acct/Id/IBAN"))
+                                            sourceFileName = fileName
+                                            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 = 
parseDashedDate(camt53doc.pickString("//*[local-name()='BookgDt']//*[local-name()='Dt']")).millis
+                                            nexusUser = 
extractNexusUser(userId)
+                                            counterpartIban = 
camt53doc.pickString("//*[local-name()='${if (this.transactionType == "DBIT") 
"CdtrAcct" else "DbtrAcct"}']//*[local-name()='IBAN']")
+                                            counterpartName = 
camt53doc.pickString("//*[local-name()='RltdPties']//*[local-name()='${if 
(this.transactionType == "DBIT") "Cdtr" else "Dbtr"}']//*[local-name()='Nm']")
+                                            counterpartBic = 
camt53doc.pickString("//*[local-name()='RltdAgts']//*[local-name()='BIC']")
+                                        }
+                                    }
+                                }
+                                call.respondText(
+                                    "C53 data persisted into the database 
(WIP).",
+                                    ContentType.Text.Plain,
+                                    HttpStatusCode.OK
+                                )
+                            }
+                            is EbicsDownloadBankErrorResult -> {
+                                call.respond(
+                                    HttpStatusCode.BadGateway,
+                                    EbicsErrorJson(
+                                        EbicsErrorDetailJson(
+                                            "bankError",
+                                            response.returnCode.errorCode
+                                        )
+                                    )
+                                )
+                            }
+                        }
+                    }
+                    else -> throw NexusError(
+                        HttpStatusCode.NotImplemented,
+                        "Bank transport ${body.transport} is not implemented"
+                    )
+                }
                 return@post
             }
             /**
-             * Queries list of transactions ALREADY downloaded from the bank.
+             * Asks list of transactions ALREADY downloaded from the bank.
              */
             get("/bank-accounts/{accountid}/collected-transactions") {
+                val userId = 
authenticateRequest(call.request.headers["Authorization"])
+                val start = call.request.queryParameters["start"]
+                val end = call.request.queryParameters["end"]
+                val ret = Transactions()
+                transaction {
+                    RawBankTransactionEntity.find {
+                        RawBankTransactionsTable.nexusUser eq userId and
+                                RawBankTransactionsTable.bookingDate.between(
+                                    parseDashedDate(start ?: "1970-01-01"),
+                                    parseDashedDate(end ?: 
DateTime.now().toDashedDate())
+                                )
+                    }.forEach {
+                        ret.transactions.add(
+                            Transaction(
+                                account = it.bankAccount.id.value,
+                                counterpartBic = it.counterpartBic,
+                                counterpartIban = it.counterpartIban,
+                                counterpartName = it.counterpartName,
+                                date = DateTime(it.bookingDate).toDashedDate(),
+                                subject = it.unstructuredRemittanceInformation,
+                                amount = "${it.currency}:${it.amount}"
+                            )
+                        )
+                    }
+                }
                 return@get
             }
             /**
@@ -284,6 +401,7 @@ fun main() {
             post("/bank-transports/{transportName}/sync{MSG}") {
                 return@post
             }
+
             /**
              * Hello endpoint.
              */
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/MainDeprecated.kt 
b/nexus/src/main/kotlin/tech/libeufin/nexus/MainDeprecated.kt
index 547f0b2..f589d76 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/MainDeprecated.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/MainDeprecated.kt
@@ -46,8 +46,6 @@ 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
 import tech.libeufin.util.*
 import tech.libeufin.util.ebics_h004.HTDResponseOrderData
@@ -258,8 +256,8 @@ fun main() {
                         BankAccountMapsTable.nexusUser eq nexusUser.id
                     }
                     bankAccountsMap.forEach {
-                        Pain001Entity.find {
-                            Pain001Table.debitorIban eq it.bankAccount.iban
+                        PreparedPaymentEntity.find {
+                            PreparedPaymentsTable.debitorIban eq 
it.bankAccount.iban
                         }.forEach {
                             ret.payments.add(
                                 RawPayment(
@@ -287,7 +285,7 @@ fun main() {
                         )
                     }
                 }
-                createPain001entity(pain001data, nexusUser)
+                addPreparedPayment(pain001data, nexusUser)
                 call.respondText(
                     "Payment instructions persisted in DB",
                     ContentType.Text.Plain, HttpStatusCode.OK
@@ -560,8 +558,8 @@ fun main() {
 
             post("/ebics/execute-payments") {
                 val (paymentRowId, painDoc, subscriber) = transaction {
-                    val entity = Pain001Entity.find {
-                        (Pain001Table.submitted eq false) and 
(Pain001Table.invalid eq false)
+                    val entity = PreparedPaymentEntity.find {
+                        (PreparedPaymentsTable.submitted eq false) and 
(PreparedPaymentsTable.invalid eq false)
                     }.firstOrNull() ?: throw 
NexusError(HttpStatusCode.Accepted, reason = "No ready payments found")
                     Triple(entity.id, createPain001document(entity), 
entity.nexusUser.ebicsSubscriber)
                 }
@@ -581,7 +579,7 @@ fun main() {
                 )
                 /* flow here == no errors occurred */
                 transaction {
-                    val payment = Pain001Entity.findById(paymentRowId) ?: 
throw NexusError(
+                    val payment = PreparedPaymentEntity.findById(paymentRowId) 
?: throw NexusError(
                         HttpStatusCode.InternalServerError,
                         "Severe internal error: could not find payment in DB 
after having submitted it to the bank"
                     )
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/taler.kt 
b/nexus/src/main/kotlin/tech/libeufin/nexus/taler.kt
index bb16050..8a97107 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/taler.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/taler.kt
@@ -90,11 +90,6 @@ class Taler(app: Route) {
         val iban: String,
         val bic: String = "NOTGIVEN"
     )
-    data class AmountWithCurrency(
-        val currency: String,
-        val amount: Amount
-    )
-
     /**
      * Helper functions
      */
@@ -130,12 +125,6 @@ class Taler(app: Route) {
         throw NexusError(HttpStatusCode.BadRequest, "invalid payto URI 
($paytoUri)")
     }
 
-    fun parseAmount(amount: String): AmountWithCurrency {
-        val match = Regex("([A-Z]+):([0-9]+(\\.[0-9]+)?)").find(amount) ?: 
throw
-                NexusError(HttpStatusCode.BadRequest, "invalid payto URI 
($amount)")
-        val (currency, number) = match.destructured
-        return AmountWithCurrency(currency, Amount(number))
-    }
     /** Sort query results in descending order for negative deltas, and 
ascending otherwise.  */
     private fun <T : Entity<Long>> SizedIterable<T>.orderTaler(delta: Int): 
List<T> {
         return if (delta < 0) {
@@ -254,13 +243,14 @@ class Taler(app: Route) {
                         )
                     }
                 }
-                val pain001 = createPain001entity(
+                val pain001 = addPreparedPayment(
                     Pain001Data(
                         creditorIban = creditorData.iban,
                         creditorBic = creditorData.bic,
                         creditorName = creditorData.name,
                         subject = transferRequest.wtid,
-                        sum = parseAmount(transferRequest.amount).amount,
+                        sum = amountObj.amount,
+                        currency = amountObj.currency,
                         debitorName = exchangeBankAccount.accountHolder,
                         debitorBic = exchangeBankAccount.bankCode,
                         debitorIban = exchangeBankAccount.iban
@@ -273,7 +263,7 @@ class Taler(app: Route) {
                         unstructuredRemittanceInformation = 
transferRequest.wtid
                         transactionType = "DBIT"
                         currency = amountObj.currency
-                        amount = amountObj.amount.toPlainString()
+                        this.amount = amountObj.amount.toPlainString()
                         debitorName = "Exchange Company"
                         debitorIban = exchangeBankAccount.iban
                         creditorName = creditorObj.name
@@ -330,7 +320,7 @@ class Taler(app: Route) {
                     currency = amount.currency
                     this.amount = amount.amount.toPlainString()
                     creditorIban = exchangeBankAccount.iban
-                    creditorName = exchangeBankAccount.accountHolder ?: 
"Exchange default name for tests"
+                    creditorName = exchangeBankAccount.accountHolder
                     debitorIban = debtor.iban
                     debitorName = debtor.name
                     counterpartBic = debtor.bic
@@ -378,7 +368,7 @@ class Taler(app: Route) {
                 TalerIncomingPaymentEntity.find {
                     TalerIncomingPayments.refunded eq false and 
(TalerIncomingPayments.valid eq false)
                 }.forEach {
-                    createPain001entity(
+                    addPreparedPayment(
                         Pain001Data(
                             creditorName = it.payment.debitorName,
                             creditorIban = it.payment.debitorIban,
@@ -387,7 +377,8 @@ class Taler(app: Route) {
                             subject = "Taler refund",
                             debitorIban = requesterBankAccount.iban,
                             debitorBic = requesterBankAccount.bankCode,
-                            debitorName = requesterBankAccount.accountHolder
+                            debitorName = requesterBankAccount.accountHolder,
+                            currency = it.payment.currency
                         ),
                         nexusUser
                     )
diff --git a/nexus/src/test/kotlin/PainGeneration.kt 
b/nexus/src/test/kotlin/PainGeneration.kt
index 4eef6d8..e257c50 100644
--- a/nexus/src/test/kotlin/PainGeneration.kt
+++ b/nexus/src/test/kotlin/PainGeneration.kt
@@ -14,14 +14,14 @@ class PainTest {
         Database.connect("jdbc:h2:mem:test;DB_CLOSE_DELAY=-1", driver = 
"org.h2.Driver")
         transaction {
             SchemaUtils.create(BankAccountsTable)
-            SchemaUtils.create(Pain001Table)
+            SchemaUtils.create(PreparedPaymentsTable)
             SchemaUtils.create(NexusUsersTable)
             BankAccountEntity.new(id = "acctid") {
                 accountHolder = "Account Holder"
                 iban = "DEBIT IBAN"
                 bankCode = "DEBIT BIC"
             }
-            Pain001Entity.new {
+            PreparedPaymentEntity.new {
                 sum = Amount(1)
                 debitorIban = "DEBIT IBAN"
                 debitorBic = "DEBIT BIC"
@@ -42,7 +42,7 @@ class PainTest {
     @Test
     fun testPain001document() {
         transaction {
-            val s = createPain001document(Pain001Entity.all().first())
+            val s = createPain001document(PreparedPaymentEntity.all().first())
             println(s)
         }
     }
diff --git a/util/src/main/kotlin/strings.kt b/util/src/main/kotlin/strings.kt
index 68cfff3..f8b0c21 100644
--- a/util/src/main/kotlin/strings.kt
+++ b/util/src/main/kotlin/strings.kt
@@ -1,5 +1,6 @@
 package tech.libeufin.util
 
+import io.ktor.http.HttpStatusCode
 import java.math.BigInteger
 import java.util.*
 
@@ -69,4 +70,16 @@ fun chunkString(input: String): String {
         ret.append(input[i])
     }
     return ret.toString().toUpperCase()
+}
+
+data class AmountWithCurrency(
+    val currency: String,
+    val amount: Amount
+)
+
+fun parseAmount(amount: String): AmountWithCurrency {
+    val match = Regex("([A-Z]+):([0-9]+(\\.[0-9]+)?)").find(amount) ?: throw
+    UtilError(HttpStatusCode.BadRequest, "invalid payto URI ($amount)")
+    val (currency, number) = match.destructured
+    return AmountWithCurrency(currency, Amount(number))
 }
\ No newline at end of file

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



reply via email to

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