libeufin

Integration and sandbox testing for FinTech APIs and data formats
Log | Files | Refs | Submodules | README | LICENSE

commit 029d0258b5ea5cce71f544b1e874975115e4218f
parent 0a44db6147a3041de0039c48b85f865b8b1b26e2
Author: Marcello Stanisci <stanisci.m@gmail.com>
Date:   Tue, 18 Feb 2020 21:02:14 +0100

PAIN001 table.

Diffstat:
Anexus/src/main/kotlin/tech/libeufin/nexus/DB.kt | 129+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dnexus/src/main/kotlin/tech/libeufin/nexus/Db.kt | 114-------------------------------------------------------------------------------
Msandbox/src/main/kotlin/tech/libeufin/sandbox/DB.kt | 61+++----------------------------------------------------------
Msandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt | 4++--
Autil/src/main/kotlin/DBTypes.kt | 68++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 202 insertions(+), 174 deletions(-)

diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/DB.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/DB.kt @@ -0,0 +1,128 @@ +package tech.libeufin.nexus + +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.primaryKey +import tech.libeufin.util.IntIdTableWithAmount +import java.sql.Connection + +const val ID_MAX_LENGTH = 50 + + +//object EbicsRawBankTransactionsTable : IdTable<Long>() { +// override val id = EbicsSubscribersTable.long("id").entityId().primaryKey() +// +// val nexusSubscriber = reference("subscriber", EbicsSubscribersTable) +// +// /** +// * How did we learn about this transaction? C52 / C53 / C54 +// */ +// val sourceType = text("sourceType") +// +// val sourceFileName = text("sourceFileName") +// +// +// +// /** +// * "Subject" of the SEPA transaction +// */ +// val unstructuredRemittanceInformation = text("unstructuredRemittanceInformation") +// +// /** +// * Is it a credit or debit transaction? +// */ +// val transactionType = text("transactionType") +// +// val currency = text("currency") +// +// val amount = text("amount") +// +// val creditorIban = text("creditorIban") +// +// val debitorIban = text("creditorIban") +//} +// +// +///** +// * This table gets populated by the HTD request. +// * +// * It stores which subscriber has access to which bank accounts via EBICS. +// * +// * When making a payment, we need to refer to one of these accounts +// */ +//object EbicsBankAccountsTable { +// +//} + +object Pain001Table : IntIdTableWithAmount() { + val msgId = integer("msgId").uniqueIndex() + val paymentId = integer("paymentId").uniqueIndex() // id for this system + val date = date("fileDate") + val sum = amount("sum") + val debtorAccount = text("debtorAccount") + val endToEndId = integer("EndToEndId").uniqueIndex() // id for this and the creditor system + val subject = text("subject") + val creditorIban = text("creditorIban") + val creditorBic = text("creditorBic") + val creditorName = text("creditorName") + val submitted = bool("submitted") // indicates whether the PAIN message was sent to the bank. +} + +object EbicsAccountsInfoTable : IdTable<String>() { + override val id = varchar("id", ID_MAX_LENGTH).entityId().primaryKey() + val subscriber = reference("subscriber", EbicsSubscribersTable) + val accountHolder = text("accountHolder").nullable() + val iban = text("iban") + val bankCode = text("bankCode") +} + +class EbicsAccountInfoEntity(id: EntityID<String>) : Entity<String>(id) { + companion object : EntityClass<String, EbicsAccountInfoEntity>(EbicsAccountsInfoTable) + var subscriber by EbicsSubscriberEntity referencedOn EbicsAccountsInfoTable.subscriber + var accountHolder by EbicsAccountsInfoTable.accountHolder + var iban by EbicsAccountsInfoTable.iban + var bankCode by EbicsAccountsInfoTable.bankCode +} + +object EbicsSubscribersTable : IdTable<String>() { + override val id = varchar("id", ID_MAX_LENGTH).entityId().primaryKey() + val ebicsURL = text("ebicsURL") + val hostID = text("hostID") + val partnerID = text("partnerID") + val userID = text("userID") + val systemID = text("systemID").nullable() + val signaturePrivateKey = blob("signaturePrivateKey") + val encryptionPrivateKey = blob("encryptionPrivateKey") + val authenticationPrivateKey = blob("authenticationPrivateKey") + val bankEncryptionPublicKey = blob("bankEncryptionPublicKey").nullable() + val bankAuthenticationPublicKey = blob("bankAuthenticationPublicKey").nullable() +} + +class EbicsSubscriberEntity(id: EntityID<String>) : Entity<String>(id) { + companion object : EntityClass<String, EbicsSubscriberEntity>(EbicsSubscribersTable) + var ebicsURL by EbicsSubscribersTable.ebicsURL + var hostID by EbicsSubscribersTable.hostID + var partnerID by EbicsSubscribersTable.partnerID + var userID by EbicsSubscribersTable.userID + var systemID by EbicsSubscribersTable.systemID + var signaturePrivateKey by EbicsSubscribersTable.signaturePrivateKey + var encryptionPrivateKey by EbicsSubscribersTable.encryptionPrivateKey + var authenticationPrivateKey by EbicsSubscribersTable.authenticationPrivateKey + var bankEncryptionPublicKey by EbicsSubscribersTable.bankEncryptionPublicKey + var bankAuthenticationPublicKey by EbicsSubscribersTable.bankAuthenticationPublicKey +} + +fun dbCreateTables() { + Database.connect("jdbc:sqlite:libeufin-nexus.sqlite3", "org.sqlite.JDBC") + TransactionManager.manager.defaultIsolationLevel = Connection.TRANSACTION_SERIALIZABLE + transaction { + addLogger(StdOutSqlLogger) + SchemaUtils.create( + EbicsSubscribersTable, + EbicsAccountsInfoTable + ) + } +} +\ No newline at end of file diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/Db.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/Db.kt @@ -1,113 +0,0 @@ -package tech.libeufin.nexus - -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.primaryKey -import java.sql.Connection - -const val ID_MAX_LENGTH = 50 - - -//object EbicsRawBankTransactionsTable : IdTable<Long>() { -// override val id = EbicsSubscribersTable.long("id").entityId().primaryKey() -// -// val nexusSubscriber = reference("subscriber", EbicsSubscribersTable) -// -// /** -// * How did we learn about this transaction? C52 / C53 / C54 -// */ -// val sourceType = text("sourceType") -// -// val sourceFileName = text("sourceFileName") -// -// -// -// /** -// * "Subject" of the SEPA transaction -// */ -// val unstructuredRemittanceInformation = text("unstructuredRemittanceInformation") -// -// /** -// * Is it a credit or debit transaction? -// */ -// val transactionType = text("transactionType") -// -// val currency = text("currency") -// -// val amount = text("amount") -// -// val creditorIban = text("creditorIban") -// -// val debitorIban = text("creditorIban") -//} -// -// -///** -// * This table gets populated by the HTD request. -// * -// * It stores which subscriber has access to which bank accounts via EBICS. -// * -// * When making a payment, we need to refer to one of these accounts -// */ -//object EbicsBankAccountsTable { -// -//} - -object EbicsAccountsInfoTable : IdTable<String>() { - override val id = varchar("id", ID_MAX_LENGTH).entityId().primaryKey() - val subscriber = reference("subscriber", EbicsSubscribersTable) - val accountHolder = text("accountHolder").nullable() - val iban = text("iban") - val bankCode = text("bankCode") -} - -class EbicsAccountInfoEntity(id: EntityID<String>) : Entity<String>(id) { - companion object : EntityClass<String, EbicsAccountInfoEntity>(EbicsAccountsInfoTable) - var subscriber by EbicsSubscriberEntity referencedOn EbicsAccountsInfoTable.subscriber - var accountHolder by EbicsAccountsInfoTable.accountHolder - var iban by EbicsAccountsInfoTable.iban - var bankCode by EbicsAccountsInfoTable.bankCode -} - -object EbicsSubscribersTable : IdTable<String>() { - override val id = varchar("id", ID_MAX_LENGTH).entityId().primaryKey() - val ebicsURL = text("ebicsURL") - val hostID = text("hostID") - val partnerID = text("partnerID") - val userID = text("userID") - val systemID = text("systemID").nullable() - val signaturePrivateKey = blob("signaturePrivateKey") - val encryptionPrivateKey = blob("encryptionPrivateKey") - val authenticationPrivateKey = blob("authenticationPrivateKey") - val bankEncryptionPublicKey = blob("bankEncryptionPublicKey").nullable() - val bankAuthenticationPublicKey = blob("bankAuthenticationPublicKey").nullable() -} - -class EbicsSubscriberEntity(id: EntityID<String>) : Entity<String>(id) { - companion object : EntityClass<String, EbicsSubscriberEntity>(EbicsSubscribersTable) - var ebicsURL by EbicsSubscribersTable.ebicsURL - var hostID by EbicsSubscribersTable.hostID - var partnerID by EbicsSubscribersTable.partnerID - var userID by EbicsSubscribersTable.userID - var systemID by EbicsSubscribersTable.systemID - var signaturePrivateKey by EbicsSubscribersTable.signaturePrivateKey - var encryptionPrivateKey by EbicsSubscribersTable.encryptionPrivateKey - var authenticationPrivateKey by EbicsSubscribersTable.authenticationPrivateKey - var bankEncryptionPublicKey by EbicsSubscribersTable.bankEncryptionPublicKey - var bankAuthenticationPublicKey by EbicsSubscribersTable.bankAuthenticationPublicKey -} - -fun dbCreateTables() { - Database.connect("jdbc:sqlite:libeufin-nexus.sqlite3", "org.sqlite.JDBC") - TransactionManager.manager.defaultIsolationLevel = Connection.TRANSACTION_SERIALIZABLE - transaction { - addLogger(StdOutSqlLogger) - SchemaUtils.create( - EbicsSubscribersTable, - EbicsAccountsInfoTable - ) - } -} -\ No newline at end of file diff --git a/sandbox/src/main/kotlin/tech/libeufin/sandbox/DB.kt b/sandbox/src/main/kotlin/tech/libeufin/sandbox/DB.kt @@ -24,12 +24,15 @@ 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 +import tech.libeufin.util.IntIdTableWithAmount + const val CUSTOMER_NAME_MAX_LENGTH = 20 @@ -39,8 +42,6 @@ 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? -const val NUMBER_MAX_DIGITS = 20 -const val SCALE_TWO = 2 /** * All the states to give a subscriber. @@ -95,62 +96,6 @@ enum class KeyState { RELEASED } -/** - * Any number can become a Amount IF it does NOT need to be rounded to comply to the scale == 2. - */ -typealias Amount = BigDecimal - -open class IntIdTableWithAmount : IntIdTable() { - - class AmountColumnType : ColumnType() { - override fun sqlType(): String = "DECIMAL(${NUMBER_MAX_DIGITS}, ${SCALE_TWO})" - - override fun valueFromDB(value: Any): Any { - - val valueFromDB = super.valueFromDB(value) - - try { - return when (valueFromDB) { - is BigDecimal -> valueFromDB.setScale(SCALE_TWO, RoundingMode.UNNECESSARY) - is Double -> BigDecimal.valueOf(valueFromDB).setScale(SCALE_TWO, RoundingMode.UNNECESSARY) - is Float -> BigDecimal(java.lang.Float.toString(valueFromDB)).setScale( - SCALE_TWO, - RoundingMode.UNNECESSARY - ) - is Int -> BigDecimal(valueFromDB) - is Long -> BigDecimal.valueOf(valueFromDB) - else -> valueFromDB - } - } catch (e: Exception) { - e.printStackTrace() - throw BadAmount(value) - } - } - - override fun valueToDB(value: Any?): Any? { - try { - (value as BigDecimal).setScale(SCALE_TWO, RoundingMode.UNNECESSARY) - } catch (e: Exception) { - e.printStackTrace() - throw BadAmount(value) - } - - if (value.compareTo(BigDecimal.ZERO) == 0) { - LOGGER.error("Cannot have transactions of ZERO amount") - throw BadAmount(value) - } - return super.valueToDB(value) - } - } - - /** - * Make sure the number entered by upper layers does not need any rounding - * to conform to scale == 2 - */ - fun amount(name: String): Column<Amount> { - return registerColumn(name, AmountColumnType()) - } -} object BankTransactionsTable : IntIdTableWithAmount() { diff --git a/sandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt b/sandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt @@ -48,6 +48,7 @@ import org.slf4j.Logger import org.slf4j.LoggerFactory import org.slf4j.event.Level import org.w3c.dom.Document +import tech.libeufin.util.Amount import tech.libeufin.util.CryptoUtil import java.lang.ArithmeticException import java.math.BigDecimal @@ -58,7 +59,6 @@ import javax.xml.bind.JAXBContext class CustomerNotFound(id: String?) : Exception("Customer ${id} not found") class BadInputData(inputData: String?) : Exception("Customer provided invalid input data: ${inputData}") -class BadAmount(badValue: Any?) : Exception("Value '${badValue}' is not a valid amount") class UnacceptableFractional(badNumber: BigDecimal) : Exception( "Unacceptable fractional part ${badNumber}" ) @@ -149,7 +149,7 @@ fun sampleData() { nextOrderID = 1 bankCustomer = customerEntity } - for (i in listOf<Amount>(Amount("-0.44"), Amount("6.02"))) { + for (i in listOf(Amount("-0.44"), Amount("6.02"))) { BankTransactionEntity.new { counterpart = "IBAN" amount = i diff --git a/util/src/main/kotlin/DBTypes.kt b/util/src/main/kotlin/DBTypes.kt @@ -0,0 +1,67 @@ +package tech.libeufin.util + +import org.jetbrains.exposed.dao.IntIdTable +import org.jetbrains.exposed.sql.Column +import org.jetbrains.exposed.sql.ColumnType +import java.math.BigDecimal +import java.math.RoundingMode + +const val SCALE_TWO = 2 +const val NUMBER_MAX_DIGITS = 20 +class BadAmount(badValue: Any?) : Exception("Value '${badValue}' is not a valid amount") + +/** + * Any number can become a Amount IF it does NOT need to be rounded to comply to the scale == 2. + */ +typealias Amount = BigDecimal + +open class IntIdTableWithAmount : IntIdTable() { + + class AmountColumnType : ColumnType() { + override fun sqlType(): String = "DECIMAL(${NUMBER_MAX_DIGITS}, ${SCALE_TWO})" + + override fun valueFromDB(value: Any): Any { + + val valueFromDB = super.valueFromDB(value) + + try { + return when (valueFromDB) { + is BigDecimal -> valueFromDB.setScale(SCALE_TWO, RoundingMode.UNNECESSARY) + is Double -> BigDecimal.valueOf(valueFromDB).setScale(SCALE_TWO, RoundingMode.UNNECESSARY) + is Float -> BigDecimal(java.lang.Float.toString(valueFromDB)).setScale( + SCALE_TWO, + RoundingMode.UNNECESSARY + ) + is Int -> BigDecimal(valueFromDB) + is Long -> BigDecimal.valueOf(valueFromDB) + else -> valueFromDB + } + } catch (e: Exception) { + e.printStackTrace() + throw BadAmount(value) + } + } + + override fun valueToDB(value: Any?): Any? { + try { + (value as BigDecimal).setScale(SCALE_TWO, RoundingMode.UNNECESSARY) + } catch (e: Exception) { + e.printStackTrace() + throw BadAmount(value) + } + + if (value.compareTo(BigDecimal.ZERO) == 0) { + throw BadAmount(value) + } + return super.valueToDB(value) + } + } + + /** + * Make sure the number entered by upper layers does not need any rounding + * to conform to scale == 2 + */ + fun amount(name: String): Column<Amount> { + return registerColumn(name, AmountColumnType()) + } +} +\ No newline at end of file