aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMS <ms@taler.net>2023-11-19 19:36:59 +0100
committerMS <ms@taler.net>2023-11-19 19:36:59 +0100
commit01461521ac32e94db19a4b5dd7ca7aee0a13e1dd (patch)
tree19ff978b5de176d48f09197695f03d6f980b76ce
parentcec574c90a1c7e26b27da03c4526917e494abcb1 (diff)
downloadlibeufin-01461521ac32e94db19a4b5dd7ca7aee0a13e1dd.tar.gz
libeufin-01461521ac32e94db19a4b5dd7ca7aee0a13e1dd.tar.bz2
libeufin-01461521ac32e94db19a4b5dd7ca7aee0a13e1dd.zip
nexus
- moving ISO20022 logic to own file - making some functions private
-rw-r--r--nexus/src/main/kotlin/tech/libeufin/nexus/EbicsFetch.kt165
-rw-r--r--nexus/src/main/kotlin/tech/libeufin/nexus/EbicsSubmit.kt2
-rw-r--r--nexus/src/main/kotlin/tech/libeufin/nexus/Iso20022.kt171
3 files changed, 171 insertions, 167 deletions
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsFetch.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsFetch.kt
index e3c8d37b..5112075d 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsFetch.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsFetch.kt
@@ -172,7 +172,7 @@ fun maybeLogFile(
* @param bankFrac fractional value
* @return the Taler fractional value with at most 8 digits.
*/
-fun makeTalerFrac(bankFrac: String): Int {
+private fun makeTalerFrac(bankFrac: String): Int {
if (bankFrac.length > 2) throw Exception("Fractional value has more than 2 digits")
var buf = bankFrac.toIntOrNull() ?: throw Exception("Fractional value not an Int: $bankFrac")
repeat(8 - bankFrac.length) {
@@ -209,165 +209,6 @@ fun getTalerAmount(
)
}
-class WrongPaymentDirection(val msg: String) : Exception(msg)
-
-/**
- * Parses a camt.054 document looking for outgoing payments.
- *
- * @param notifXml input document.
- * @param acceptedCurrency currency accepted by Nexus
- * @return the list of outgoing payments.
- */
-private fun parseOutgoingTxNotif(
- notifXml: String,
- acceptedCurrency: String,
-): List<OutgoingPayment> {
- val ret = mutableListOf<OutgoingPayment>()
- notificationForEachTx(notifXml) { bookDate ->
- requireUniqueChildNamed("CdtDbtInd") {
- if (focusElement.textContent != "DBIT")
- throw WrongPaymentDirection("The payment is not outgoing, won't parse it")
- }
- // Obtaining the amount.
- val amount: TalerAmount = requireUniqueChildNamed("Amt") {
- val currency = focusElement.getAttribute("Ccy")
- if (currency != acceptedCurrency) throw Exception("Currency $currency not supported")
- getTalerAmount(focusElement.textContent, currency)
- }
-
- /**
- * The MsgId extracted in the block below matches the one that
- * was specified as the MsgId element in the pain.001 that originated
- * this outgoing payment. MsgId is considered unique because the
- * bank enforces its uniqueness. Associating MsgId to this outgoing
- * payment is also convenient to match its initiated outgoing payment
- * in the database for reconciliation.
- */
- val uidFromBank = StringBuilder()
- requireUniqueChildNamed("Refs") {
- requireUniqueChildNamed("MsgId") {
- uidFromBank.append(focusElement.textContent)
- }
- }
-
- ret.add(OutgoingPayment(
- amount = amount,
- bankTransferId = uidFromBank.toString(),
- executionTime = bookDate
- ))
- }
- return ret
-}
-
-/**
- * Searches incoming payments in a camt.054 (Detailavisierung) document.
- *
- * @param notifXml camt.054 input document
- * @param acceptedCurrency currency accepted by Nexus.
- * @return the list of incoming payments to ingest in the database.
- */
-private fun parseIncomingTxNotif(
- notifXml: String,
- acceptedCurrency: String
-): List<IncomingPayment> {
- val ret = mutableListOf<IncomingPayment>()
- notificationForEachTx(notifXml) { bookDate ->
- // Check the direction first.
- requireUniqueChildNamed("CdtDbtInd") {
- if (focusElement.textContent != "CRDT")
- throw WrongPaymentDirection("The payment is not incoming, won't parse it")
- }
- val amount: TalerAmount = requireUniqueChildNamed("Amt") {
- val currency = focusElement.getAttribute("Ccy")
- if (currency != acceptedCurrency) throw Exception("Currency $currency not supported")
- getTalerAmount(focusElement.textContent, currency)
- }
- // Obtaining payment UID.
- val uidFromBank: String = requireUniqueChildNamed("Refs") {
- requireUniqueChildNamed("AcctSvcrRef") {
- focusElement.textContent
- }
- }
- // Obtaining payment subject.
- val subject = StringBuilder()
- requireUniqueChildNamed("RmtInf") {
- this.mapEachChildNamed("Ustrd") {
- val piece = this.focusElement.textContent
- subject.append(piece)
- }
- }
-
- // Obtaining the payer's details
- val debtorPayto = StringBuilder("payto://iban/")
- requireUniqueChildNamed("RltdPties") {
- requireUniqueChildNamed("DbtrAcct") {
- requireUniqueChildNamed("Id") {
- requireUniqueChildNamed("IBAN") {
- debtorPayto.append(focusElement.textContent)
- }
- }
- }
- // warn: it might need the postal address too..
- requireUniqueChildNamed("Dbtr") {
- requireUniqueChildNamed("Nm") {
- val urlEncName = URLEncoder.encode(focusElement.textContent, "utf-8")
- debtorPayto.append("?receiver-name=$urlEncName")
- }
- }
- }
- ret.add(
- IncomingPayment(
- amount = amount,
- bankTransferId = uidFromBank,
- debitPaytoUri = debtorPayto.toString(),
- executionTime = bookDate,
- wireTransferSubject = subject.toString()
- )
- )
- }
- return ret
-}
-
-/**
- * Navigates the camt.054 (Detailavisierung) until its leaves, where
- * then it invokes the related parser, according to the payment direction.
- *
- * @param notifXml the input document.
- * @return any incoming payment as a list of [IncomingPayment]
- */
-fun notificationForEachTx(
- notifXml: String,
- directionLambda: XmlElementDestructor.(Instant) -> Unit
-) {
- val notifDoc = XMLUtil.parseStringIntoDom(notifXml)
- destructXml(notifDoc) {
- requireRootElement("Document") {
- requireUniqueChildNamed("BkToCstmrDbtCdtNtfctn") {
- mapEachChildNamed("Ntfctn") {
- mapEachChildNamed("Ntry") {
- requireUniqueChildNamed("Sts") {
- if (focusElement.textContent != "BOOK")
- throw Exception("Found non booked transaction, " +
- "stop parsing. Status was: ${focusElement.textContent}"
- )
- }
- val bookDate: Instant = requireUniqueChildNamed("BookgDt") {
- requireUniqueChildNamed("Dt") {
- parseBookDate(focusElement.textContent)
- }
- }
- mapEachChildNamed("NtryDtls") {
- mapEachChildNamed("TxDtls") {
- directionLambda(this, bookDate)
- }
- }
- }
- }
- }
- }
- }
-}
-
/**
* Converts valid reserve pubs to its binary representation.
*
@@ -417,7 +258,7 @@ fun removeSubjectNoise(subject: String): String? {
* @return [ByteArray] as the reserve public key, or null if the
* payment cannot lead to a Taler withdrawal.
*/
-suspend fun getTalerReservePub(
+private suspend fun getTalerReservePub(
db: Database,
payment: IncomingPayment
): ByteArray? {
@@ -511,7 +352,7 @@ private suspend fun ingestIncomingPayment(
* False should fail the process, since it means that
* the notification could not be parsed.
*/
-fun ingestNotification(
+private fun ingestNotification(
db: Database,
ctx: FetchContext,
content: ByteArray
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsSubmit.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsSubmit.kt
index e11e39c7..588d2727 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsSubmit.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsSubmit.kt
@@ -104,7 +104,7 @@ class NexusSubmitException(
* the pain.001 MsgId element), will be part of
* the filename.
*/
-fun maybeLog(
+private fun maybeLog(
maybeLogDir: String?,
xml: String,
requestUid: String
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/Iso20022.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/Iso20022.kt
index 6a6d850d..ec0381b7 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/Iso20022.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/Iso20022.kt
@@ -1,7 +1,7 @@
package tech.libeufin.nexus
-import tech.libeufin.util.IbanPayto
-import tech.libeufin.util.constructXml
+import tech.libeufin.util.*
+import java.net.URLEncoder
import java.time.Instant
import java.time.ZoneId
import java.time.ZonedDateTime
@@ -37,8 +37,6 @@ fun getAmountNoCurrency(amount: TalerAmount): String {
}
}
-
-
/**
* Create a pain.001 document. It requires the debtor BIC.
*
@@ -155,4 +153,169 @@ fun createPain001(
}
}
}
+}
+
+/**
+ * Thrown if the parser expects DBIT but the transaction
+ * is CRDT, and vice-versa.
+ */
+class WrongPaymentDirection(val msg: String) : Exception(msg)
+
+/**
+ * Parses a camt.054 document looking for outgoing payments.
+ *
+ * @param notifXml input document.
+ * @param acceptedCurrency currency accepted by Nexus
+ * @return the list of outgoing payments.
+ */
+fun parseOutgoingTxNotif(
+ notifXml: String,
+ acceptedCurrency: String,
+): List<OutgoingPayment> {
+ val ret = mutableListOf<OutgoingPayment>()
+ notificationForEachTx(notifXml) { bookDate ->
+ requireUniqueChildNamed("CdtDbtInd") {
+ if (focusElement.textContent != "DBIT")
+ throw WrongPaymentDirection("The payment is not outgoing, won't parse it")
+ }
+ // Obtaining the amount.
+ val amount: TalerAmount = requireUniqueChildNamed("Amt") {
+ val currency = focusElement.getAttribute("Ccy")
+ if (currency != acceptedCurrency) throw Exception("Currency $currency not supported")
+ getTalerAmount(focusElement.textContent, currency)
+ }
+
+ /**
+ * The MsgId extracted in the block below matches the one that
+ * was specified as the MsgId element in the pain.001 that originated
+ * this outgoing payment. MsgId is considered unique because the
+ * bank enforces its uniqueness. Associating MsgId to this outgoing
+ * payment is also convenient to match its initiated outgoing payment
+ * in the database for reconciliation.
+ */
+ val uidFromBank = StringBuilder()
+ requireUniqueChildNamed("Refs") {
+ requireUniqueChildNamed("MsgId") {
+ uidFromBank.append(focusElement.textContent)
+ }
+ }
+
+ ret.add(
+ OutgoingPayment(
+ amount = amount,
+ bankTransferId = uidFromBank.toString(),
+ executionTime = bookDate
+ )
+ )
+ }
+ return ret
+}
+
+/**
+ * Searches incoming payments in a camt.054 (Detailavisierung) document.
+ *
+ * @param notifXml camt.054 input document
+ * @param acceptedCurrency currency accepted by Nexus.
+ * @return the list of incoming payments to ingest in the database.
+ */
+fun parseIncomingTxNotif(
+ notifXml: String,
+ acceptedCurrency: String
+): List<IncomingPayment> {
+ val ret = mutableListOf<IncomingPayment>()
+ notificationForEachTx(notifXml) { bookDate ->
+ // Check the direction first.
+ requireUniqueChildNamed("CdtDbtInd") {
+ if (focusElement.textContent != "CRDT")
+ throw WrongPaymentDirection("The payment is not incoming, won't parse it")
+ }
+ val amount: TalerAmount = requireUniqueChildNamed("Amt") {
+ val currency = focusElement.getAttribute("Ccy")
+ if (currency != acceptedCurrency) throw Exception("Currency $currency not supported")
+ getTalerAmount(focusElement.textContent, currency)
+ }
+ // Obtaining payment UID.
+ val uidFromBank: String = requireUniqueChildNamed("Refs") {
+ requireUniqueChildNamed("AcctSvcrRef") {
+ focusElement.textContent
+ }
+ }
+ // Obtaining payment subject.
+ val subject = StringBuilder()
+ requireUniqueChildNamed("RmtInf") {
+ this.mapEachChildNamed("Ustrd") {
+ val piece = this.focusElement.textContent
+ subject.append(piece)
+ }
+ }
+
+ // Obtaining the payer's details
+ val debtorPayto = StringBuilder("payto://iban/")
+ requireUniqueChildNamed("RltdPties") {
+ requireUniqueChildNamed("DbtrAcct") {
+ requireUniqueChildNamed("Id") {
+ requireUniqueChildNamed("IBAN") {
+ debtorPayto.append(focusElement.textContent)
+ }
+ }
+ }
+ // warn: it might need the postal address too..
+ requireUniqueChildNamed("Dbtr") {
+ requireUniqueChildNamed("Nm") {
+ val urlEncName = URLEncoder.encode(focusElement.textContent, "utf-8")
+ debtorPayto.append("?receiver-name=$urlEncName")
+ }
+ }
+ }
+ ret.add(
+ IncomingPayment(
+ amount = amount,
+ bankTransferId = uidFromBank,
+ debitPaytoUri = debtorPayto.toString(),
+ executionTime = bookDate,
+ wireTransferSubject = subject.toString()
+ )
+ )
+ }
+ return ret
+}
+
+/**
+ * Navigates the camt.054 (Detailavisierung) until its leaves, where
+ * then it invokes the related parser, according to the payment direction.
+ *
+ * @param notifXml the input document.
+ * @return any incoming payment as a list of [IncomingPayment]
+ */
+private fun notificationForEachTx(
+ notifXml: String,
+ directionLambda: XmlElementDestructor.(Instant) -> Unit
+) {
+ val notifDoc = XMLUtil.parseStringIntoDom(notifXml)
+ destructXml(notifDoc) {
+ requireRootElement("Document") {
+ requireUniqueChildNamed("BkToCstmrDbtCdtNtfctn") {
+ mapEachChildNamed("Ntfctn") {
+ mapEachChildNamed("Ntry") {
+ requireUniqueChildNamed("Sts") {
+ if (focusElement.textContent != "BOOK")
+ throw Exception("Found non booked transaction, " +
+ "stop parsing. Status was: ${focusElement.textContent}"
+ )
+ }
+ val bookDate: Instant = requireUniqueChildNamed("BookgDt") {
+ requireUniqueChildNamed("Dt") {
+ parseBookDate(focusElement.textContent)
+ }
+ }
+ mapEachChildNamed("NtryDtls") {
+ mapEachChildNamed("TxDtls") {
+ directionLambda(this, bookDate)
+ }
+ }
+ }
+ }
+ }
+ }
+ }
} \ No newline at end of file