[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[libeufin] branch master updated: refactor, remove Helpers.kt
From: |
gnunet |
Subject: |
[libeufin] branch master updated: refactor, remove Helpers.kt |
Date: |
Thu, 18 Jun 2020 16:24:11 +0200 |
This is an automated email from the git hooks/post-receive script.
dold pushed a commit to branch master
in repository libeufin.
The following commit(s) were added to refs/heads/master by this push:
new af14b96 refactor, remove Helpers.kt
af14b96 is described below
commit af14b96c76d388d36da73d068f6a91cf4ec46ca8
Author: Florian Dold <florian.dold@gmail.com>
AuthorDate: Thu Jun 18 19:54:03 2020 +0530
refactor, remove Helpers.kt
---
.../src/main/kotlin/tech/libeufin/nexus/Helpers.kt | 355 ---------------------
nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt | 36 ++-
.../tech/libeufin/nexus/bankaccount/BankAccount.kt | 171 +++++++++-
3 files changed, 204 insertions(+), 358 deletions(-)
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/Helpers.kt
b/nexus/src/main/kotlin/tech/libeufin/nexus/Helpers.kt
deleted file mode 100644
index 13a993a..0000000
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/Helpers.kt
+++ /dev/null
@@ -1,355 +0,0 @@
-/*
- * This file is part of LibEuFin.
- * Copyright (C) 2020 Taler Systems S.A.
- *
- * LibEuFin is free software; you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation; either version 3, or
- * (at your option) any later version.
- *
- * LibEuFin is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
- * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General
- * Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public
- * License along with LibEuFin; see the file COPYING. If not, see
- * <http://www.gnu.org/licenses/>
- */
-
-package tech.libeufin.nexus
-
-import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
-import io.ktor.http.HttpStatusCode
-import org.jetbrains.exposed.sql.SortOrder
-import org.jetbrains.exposed.sql.and
-import org.jetbrains.exposed.sql.transactions.transaction
-import org.w3c.dom.Document
-import tech.libeufin.util.*
-import java.time.Instant
-import java.time.ZoneId
-import java.time.ZonedDateTime
-import java.time.format.DateTimeFormatter
-
-
-/**
- * Check if the transaction is already found in the database.
- */
-private fun findDuplicate(bankAccountId: String, acctSvcrRef: String):
RawBankTransactionEntity? {
- // FIXME: make this generic depending on transaction identification scheme
- val ati = "AcctSvcrRef:$acctSvcrRef"
- return transaction {
- RawBankTransactionEntity.find {
- (RawBankTransactionsTable.accountTransactionId eq ati) and
(RawBankTransactionsTable.bankAccount eq bankAccountId)
- }.firstOrNull()
- }
-}
-
-/**
- * retrieves the initiated payment and marks it as "performed
- * by the bank". This avoids to submit it again.
- */
-fun markInitiatedAsConfirmed(subject: String, debtorIban: String, rawUuid:
Long) {
- // not introducing a 'transaction {}' block since
- // this function should be always be invoked from one.
- val initiatedPayment = PaymentInitiationEntity.find {
- PaymentInitiationsTable.subject eq subject and
- (PaymentInitiationsTable.debitorIban eq debtorIban)
- }.firstOrNull()
- if (initiatedPayment == null) {
- logger.info("Payment '$subject' was never programmatically prepared")
- return
- }
- val rawEntity = RawBankTransactionEntity.findById(rawUuid) ?: throw
NexusError(
- HttpStatusCode.InternalServerError, "Raw payment '$rawUuid'
disappeared from database"
- )
- initiatedPayment.rawConfirmation = rawEntity
-}
-
-fun processCamtMessage(
- bankAccountId: String,
- camtDoc: Document
-) {
- logger.info("processing CAMT message")
- transaction {
- val acct = NexusBankAccountEntity.findById(bankAccountId)
- if (acct == null) {
- throw NexusError(HttpStatusCode.NotFound, "user not found")
- }
- val transactions = getTransactions(camtDoc)
- logger.info("found ${transactions.size} transactions")
- txloop@for (tx in transactions) {
- val acctSvcrRef = tx.accountServicerReference
- if (acctSvcrRef == null) {
- // FIXME(dold): Report this!
- logger.error("missing account servicer reference in
transaction")
- continue
- }
- val duplicate = findDuplicate(bankAccountId, acctSvcrRef)
- if (duplicate != null) {
- // FIXME(dold): See if an old transaction needs to be
superseded by this one
- // https://bugs.gnunet.org/view.php?id=6381
- break
- }
-
- val rawEntity = RawBankTransactionEntity.new {
- bankAccount = acct
- accountTransactionId = "AcctSvcrRef:$acctSvcrRef"
- amount = tx.amount
- currency = tx.currency
- transactionJson =
jacksonObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(tx)
- creditDebitIndicator = tx.creditDebitIndicator.name
- status = tx.status
- }
- if (tx.creditDebitIndicator == CreditDebitIndicator.DBIT) {
- // assuming batches contain always one element, as aren't fully
- // implemented now.
- val uniqueBatchElement = tx.details.get(0)
- markInitiatedAsConfirmed(
- // if the user has two initiated payments under the same
- // IBAN with the same subject, then this logic will cause
- // problems. But a programmatic user should take care of
this.
- uniqueBatchElement.unstructuredRemittanceInformation,
- if (uniqueBatchElement.relatedParties.debtorAccount !is
AccountIdentificationIban) {
- throw NexusError(
- HttpStatusCode.InternalServerError,
- "Parsed CAMT didn't have IBAN in debtor!"
- )
- } else {
- uniqueBatchElement.relatedParties.debtorAccount.iban
- },
- rawEntity.id.value
- )
- }
- }
- }
-}
-
-/**
- * Create new transactions for an account based on bank messages it
- * did not see before.
- */
-fun ingestBankMessagesIntoAccount(
- bankConnectionId: String,
- bankAccountId: String
-) {
- transaction {
- val conn = NexusBankConnectionEntity.findById(bankConnectionId)
- if (conn == null) {
- throw NexusError(HttpStatusCode.InternalServerError, "connection
not found")
- }
- val acct = NexusBankAccountEntity.findById(bankAccountId)
- if (acct == null) {
- throw NexusError(HttpStatusCode.InternalServerError, "account not
found")
- }
- var lastId = acct.highestSeenBankMessageId
- NexusBankMessageEntity.find {
- (NexusBankMessagesTable.bankConnection eq conn.id) and
- (NexusBankMessagesTable.id greater
acct.highestSeenBankMessageId)
- }.orderBy(Pair(NexusBankMessagesTable.id, SortOrder.ASC)).forEach {
- // FIXME: check if it's CAMT first!
- val doc =
XMLUtil.parseStringIntoDom(it.message.bytes.toString(Charsets.UTF_8))
- processCamtMessage(bankAccountId, doc)
- lastId = it.id.value
- }
- acct.highestSeenBankMessageId = lastId
- }
-}
-
-/**
- * Create a PAIN.001 XML document according to the input data.
- * Needs to be called within a transaction block.
- */
-fun createPain001document(paymentData: PaymentInitiationEntity): String {
- /**
- * Every PAIN.001 document contains at least three IDs:
- *
- * 1) MsgId: a unique id for the message itself
- * 2) PmtInfId: the unique id for the payment's set of information
- * 3) EndToEndId: a unique id to be shared between the debtor and
- * creditor that uniquely identifies the transaction
- *
- * For now and for simplicity, since every PAIN entry in the database
- * has a unique ID, and the three values aren't required to be mutually
different,
- * we'll assign the SAME id (= the row id) to all the three aforementioned
- * PAIN id types.
- */
- val debitorBankAccountLabel = transaction {
- val debitorBankAcount = NexusBankAccountEntity.find {
- NexusBankAccountsTable.iban eq paymentData.debitorIban and
- (NexusBankAccountsTable.bankCode eq paymentData.debitorBic)
- }.firstOrNull() ?: throw NexusError(
- HttpStatusCode.NotFound,
- "Please download bank accounts details first (HTD)"
- )
- debitorBankAcount.id.value
- }
-
- val s = constructXml(indent = true) {
- root("Document") {
- attribute("xmlns",
"urn:iso:std:iso:20022:tech:xsd:pain.001.001.03")
- attribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance")
- attribute("xsi:schemaLocation",
"urn:iso:std:iso:20022:tech:xsd:pain.001.001.03 pain.001.001.03.xsd")
- element("CstmrCdtTrfInitn") {
- element("GrpHdr") {
- element("MsgId") {
- text(paymentData.id.value.toString())
- }
- element("CreDtTm") {
- val dateMillis = transaction {
- paymentData.preparationDate
- }
- val dateFormatter =
DateTimeFormatter.ISO_OFFSET_DATE_TIME
- val instant = Instant.ofEpochSecond(dateMillis / 1000)
- val zoned = ZonedDateTime.ofInstant(instant,
ZoneId.systemDefault())
- text(dateFormatter.format(zoned))
- }
- element("NbOfTxs") {
- text("1")
- }
- element("CtrlSum") {
- text(paymentData.sum.toString())
- }
- element("InitgPty/Nm") {
- text(debitorBankAccountLabel)
- }
- }
- element("PmtInf") {
- element("PmtInfId") {
- text(paymentData.id.value.toString())
- }
- element("PmtMtd") {
- text("TRF")
- }
- element("BtchBookg") {
- text("true")
- }
- element("NbOfTxs") {
- text("1")
- }
- element("CtrlSum") {
- text(paymentData.sum.toString())
- }
- element("PmtTpInf/SvcLvl/Cd") {
- text("SEPA")
- }
- element("ReqdExctnDt") {
- val dateMillis = transaction {
- paymentData.preparationDate
- }
- text(importDateFromMillis(dateMillis).toDashedDate())
- }
- element("Dbtr/Nm") {
- text(debitorBankAccountLabel)
- }
- element("DbtrAcct/Id/IBAN") {
- text(paymentData.debitorIban)
- }
- element("DbtrAgt/FinInstnId/BIC") {
- text(paymentData.debitorBic)
- }
- element("ChrgBr") {
- text("SLEV")
- }
- element("CdtTrfTxInf") {
- element("PmtId") {
- element("EndToEndId") {
- // text(pain001Entity.id.value.toString())
- text("NOTPROVIDED")
- }
- }
- element("Amt/InstdAmt") {
- attribute("Ccy", paymentData.currency)
- text(paymentData.sum.toString())
- }
- element("CdtrAgt/FinInstnId/BIC") {
- text(paymentData.creditorBic)
- }
- element("Cdtr/Nm") {
- text(paymentData.creditorName)
- }
- element("CdtrAcct/Id/IBAN") {
- text(paymentData.creditorIban)
- }
- element("RmtInf/Ustrd") {
- text(paymentData.subject)
- }
- }
- }
- }
- }
- }
- return s
-}
-
-/**
- * Retrieve prepared payment from database, raising exception
- * if not found.
- */
-fun getPreparedPayment(uuid: Long): PaymentInitiationEntity {
- return transaction {
- PaymentInitiationEntity.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
- * account of the subscriber that is creating the pain entity. In this case,
- * it will be the account whose money will pay the wire transfer being defined
- * by this pain document.
- */
-fun addPreparedPayment(paymentData: Pain001Data, debitorAccount:
NexusBankAccountEntity): PaymentInitiationEntity {
- return transaction {
- PaymentInitiationEntity.new {
- bankAccount = debitorAccount
- 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 = Instant.now().toEpochMilli()
- endToEndId = 0
- }
- }
-}
-
-fun ensureNonNull(param: String?): String {
- return param ?: throw NexusError(
- HttpStatusCode.BadRequest, "Bad ID given: ${param}"
- )
-}
-
-fun ensureLong(param: String?): Long {
- val asString = ensureNonNull(param)
- return asString.toLongOrNull() ?: throw NexusError(
- HttpStatusCode.BadRequest, "Parameter is not a number: ${param}"
- )
-}
-
-/**
- * This helper function parses a Authorization:-header line, decode the
credentials
- * and returns a pair made of username and hashed (sha256) password. The
hashed value
- * will then be compared with the one kept into the database.
- */
-fun extractUserAndPassword(authorizationHeader: String): Pair<String, String> {
- logger.debug("Authenticating: $authorizationHeader")
- val (username, password) = try {
- val split = authorizationHeader.split(" ")
- val plainUserAndPass = String(base64ToBytes(split[1]), Charsets.UTF_8)
- plainUserAndPass.split(":")
- } catch (e: java.lang.Exception) {
- throw NexusError(
- HttpStatusCode.BadRequest,
- "invalid Authorization:-header received"
- )
- }
- return Pair(username, password)
-}
\ 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 f053d17..52e7b90 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt
@@ -66,8 +66,7 @@ import org.jetbrains.exposed.sql.transactions.transaction
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.slf4j.event.Level
-import tech.libeufin.nexus.bankaccount.submitAllPreparedPayments
-import tech.libeufin.nexus.bankaccount.submitPreparedPayment
+import tech.libeufin.nexus.bankaccount.*
import tech.libeufin.nexus.ebics.*
import tech.libeufin.util.*
import tech.libeufin.util.CryptoUtil.hashpw
@@ -346,6 +345,39 @@ private suspend fun fetchTransactionsInternal(
}
}
+fun ensureNonNull(param: String?): String {
+ return param ?: throw NexusError(
+ HttpStatusCode.BadRequest, "Bad ID given: ${param}"
+ )
+}
+
+fun ensureLong(param: String?): Long {
+ val asString = ensureNonNull(param)
+ return asString.toLongOrNull() ?: throw NexusError(
+ HttpStatusCode.BadRequest, "Parameter is not a number: ${param}"
+ )
+}
+
+/**
+ * This helper function parses a Authorization:-header line, decode the
credentials
+ * and returns a pair made of username and hashed (sha256) password. The
hashed value
+ * will then be compared with the one kept into the database.
+ */
+fun extractUserAndPassword(authorizationHeader: String): Pair<String, String> {
+ logger.debug("Authenticating: $authorizationHeader")
+ val (username, password) = try {
+ val split = authorizationHeader.split(" ")
+ val plainUserAndPass = String(base64ToBytes(split[1]), Charsets.UTF_8)
+ plainUserAndPass.split(":")
+ } catch (e: java.lang.Exception) {
+ throw NexusError(
+ HttpStatusCode.BadRequest,
+ "invalid Authorization:-header received"
+ )
+ }
+ return Pair(username, password)
+}
+
fun serverMain(dbName: String) {
dbCreateTables(dbName)
val client = HttpClient {
diff --git
a/nexus/src/main/kotlin/tech/libeufin/nexus/bankaccount/BankAccount.kt
b/nexus/src/main/kotlin/tech/libeufin/nexus/bankaccount/BankAccount.kt
index 0a8de16..662d516 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/bankaccount/BankAccount.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/bankaccount/BankAccount.kt
@@ -19,13 +19,18 @@
package tech.libeufin.nexus.bankaccount
+import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import io.ktor.client.HttpClient
import io.ktor.http.HttpStatusCode
+import org.jetbrains.exposed.sql.SortOrder
import org.jetbrains.exposed.sql.and
import org.jetbrains.exposed.sql.not
import org.jetbrains.exposed.sql.transactions.transaction
+import org.w3c.dom.Document
import tech.libeufin.nexus.*
import tech.libeufin.nexus.ebics.submitEbicsPaymentInitiation
+import tech.libeufin.util.XMLUtil
+import java.time.Instant
suspend fun submitPreparedPayment(httpClient: HttpClient, paymentInitiationId:
Long) {
@@ -77,4 +82,168 @@ suspend fun submitAllPreparedPayments(httpClient:
HttpClient) {
workQueue.forEach {
submitPreparedPayment(httpClient, it.id)
}
-}
\ No newline at end of file
+}
+
+
+/**
+ * Check if the transaction is already found in the database.
+ */
+private fun findDuplicate(bankAccountId: String, acctSvcrRef: String):
RawBankTransactionEntity? {
+ // FIXME: make this generic depending on transaction identification scheme
+ val ati = "AcctSvcrRef:$acctSvcrRef"
+ return transaction {
+ RawBankTransactionEntity.find {
+ (RawBankTransactionsTable.accountTransactionId eq ati) and
(RawBankTransactionsTable.bankAccount eq bankAccountId)
+ }.firstOrNull()
+ }
+}
+
+/**
+ * retrieves the initiated payment and marks it as "performed
+ * by the bank". This avoids to submit it again.
+ */
+fun markInitiatedAsConfirmed(subject: String, debtorIban: String, rawUuid:
Long) {
+ // not introducing a 'transaction {}' block since
+ // this function should be always be invoked from one.
+ val initiatedPayment = PaymentInitiationEntity.find {
+ PaymentInitiationsTable.subject eq subject and
+ (PaymentInitiationsTable.debitorIban eq debtorIban)
+ }.firstOrNull()
+ if (initiatedPayment == null) {
+ logger.info("Payment '$subject' was never programmatically prepared")
+ return
+ }
+ val rawEntity = RawBankTransactionEntity.findById(rawUuid) ?: throw
NexusError(
+ HttpStatusCode.InternalServerError, "Raw payment '$rawUuid'
disappeared from database"
+ )
+ initiatedPayment.rawConfirmation = rawEntity
+}
+
+fun processCamtMessage(
+ bankAccountId: String,
+ camtDoc: Document
+) {
+ logger.info("processing CAMT message")
+ transaction {
+ val acct = NexusBankAccountEntity.findById(bankAccountId)
+ if (acct == null) {
+ throw NexusError(HttpStatusCode.NotFound, "user not found")
+ }
+ val transactions = getTransactions(camtDoc)
+ logger.info("found ${transactions.size} transactions")
+ txloop@for (tx in transactions) {
+ val acctSvcrRef = tx.accountServicerReference
+ if (acctSvcrRef == null) {
+ // FIXME(dold): Report this!
+ logger.error("missing account servicer reference in
transaction")
+ continue
+ }
+ val duplicate = findDuplicate(bankAccountId, acctSvcrRef)
+ if (duplicate != null) {
+ // FIXME(dold): See if an old transaction needs to be
superseded by this one
+ // https://bugs.gnunet.org/view.php?id=6381
+ break
+ }
+
+ val rawEntity = RawBankTransactionEntity.new {
+ bankAccount = acct
+ accountTransactionId = "AcctSvcrRef:$acctSvcrRef"
+ amount = tx.amount
+ currency = tx.currency
+ transactionJson =
jacksonObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(tx)
+ creditDebitIndicator = tx.creditDebitIndicator.name
+ status = tx.status
+ }
+ if (tx.creditDebitIndicator == CreditDebitIndicator.DBIT) {
+ // assuming batches contain always one element, as aren't fully
+ // implemented now.
+ val uniqueBatchElement = tx.details.get(0)
+ markInitiatedAsConfirmed(
+ // if the user has two initiated payments under the same
+ // IBAN with the same subject, then this logic will cause
+ // problems. But a programmatic user should take care of
this.
+ uniqueBatchElement.unstructuredRemittanceInformation,
+ if (uniqueBatchElement.relatedParties.debtorAccount !is
AccountIdentificationIban) {
+ throw NexusError(
+ HttpStatusCode.InternalServerError,
+ "Parsed CAMT didn't have IBAN in debtor!"
+ )
+ } else {
+ uniqueBatchElement.relatedParties.debtorAccount.iban
+ },
+ rawEntity.id.value
+ )
+ }
+ }
+ }
+}
+
+/**
+ * Create new transactions for an account based on bank messages it
+ * did not see before.
+ */
+fun ingestBankMessagesIntoAccount(
+ bankConnectionId: String,
+ bankAccountId: String
+) {
+ transaction {
+ val conn = NexusBankConnectionEntity.findById(bankConnectionId)
+ if (conn == null) {
+ throw NexusError(HttpStatusCode.InternalServerError, "connection
not found")
+ }
+ val acct = NexusBankAccountEntity.findById(bankAccountId)
+ if (acct == null) {
+ throw NexusError(HttpStatusCode.InternalServerError, "account not
found")
+ }
+ var lastId = acct.highestSeenBankMessageId
+ NexusBankMessageEntity.find {
+ (NexusBankMessagesTable.bankConnection eq conn.id) and
+ (NexusBankMessagesTable.id greater
acct.highestSeenBankMessageId)
+ }.orderBy(Pair(NexusBankMessagesTable.id, SortOrder.ASC)).forEach {
+ // FIXME: check if it's CAMT first!
+ val doc =
XMLUtil.parseStringIntoDom(it.message.bytes.toString(Charsets.UTF_8))
+ processCamtMessage(bankAccountId, doc)
+ lastId = it.id.value
+ }
+ acct.highestSeenBankMessageId = lastId
+ }
+}
+
+/**
+ * Retrieve prepared payment from database, raising exception
+ * if not found.
+ */
+fun getPreparedPayment(uuid: Long): PaymentInitiationEntity {
+ return transaction {
+ PaymentInitiationEntity.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
+ * account of the subscriber that is creating the pain entity. In this case,
+ * it will be the account whose money will pay the wire transfer being defined
+ * by this pain document.
+ */
+fun addPreparedPayment(paymentData: Pain001Data, debitorAccount:
NexusBankAccountEntity): PaymentInitiationEntity {
+ return transaction {
+ PaymentInitiationEntity.new {
+ bankAccount = debitorAccount
+ 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 = Instant.now().toEpochMilli()
+ endToEndId = 0
+ }
+ }
+}
--
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.
[Prev in Thread] |
Current Thread |
[Next in Thread] |
- [libeufin] branch master updated: refactor, remove Helpers.kt,
gnunet <=