diff options
author | MS <ms@taler.net> | 2023-11-19 19:36:59 +0100 |
---|---|---|
committer | MS <ms@taler.net> | 2023-11-19 19:36:59 +0100 |
commit | 01461521ac32e94db19a4b5dd7ca7aee0a13e1dd (patch) | |
tree | 19ff978b5de176d48f09197695f03d6f980b76ce | |
parent | cec574c90a1c7e26b27da03c4526917e494abcb1 (diff) | |
download | libeufin-01461521ac32e94db19a4b5dd7ca7aee0a13e1dd.tar.gz libeufin-01461521ac32e94db19a4b5dd7ca7aee0a13e1dd.tar.bz2 libeufin-01461521ac32e94db19a4b5dd7ca7aee0a13e1dd.zip |
nexus
- moving ISO20022 logic to own file
- making some functions private
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 |