gnunet-svn
[Top][All Lists]
Advanced

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

[libeufin] branch master updated (d7257fc -> da0c0cd)


From: gnunet
Subject: [libeufin] branch master updated (d7257fc -> da0c0cd)
Date: Tue, 07 Jul 2020 22:15:12 +0200

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

dold pushed a change to branch master
in repository libeufin.

    from d7257fc  Show bank accounts in home page
     new f75f5ba  ISO 20022 amount progress
     new da0c0cd  parse balances and batches correctly

The 2 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:
 .idea/dictionaries/dold.xml                        |   1 +
 .idea/inspectionProfiles/Project_Default.xml       |   8 +
 nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt  |  19 +-
 nexus/src/main/kotlin/tech/libeufin/nexus/Taler.kt |   9 +-
 .../tech/libeufin/nexus/bankaccount/BankAccount.kt |   6 +-
 .../tech/libeufin/nexus/iso20022/Iso20022.kt       | 572 +++++++++++++++------
 nexus/src/test/kotlin/Iso20022Test.kt              |  17 +-
 .../camt.053/de.camt.053.001.02.xml                | 130 ++++-
 util/src/main/kotlin/ISO20022.kt                   |  69 ---
 9 files changed, 582 insertions(+), 249 deletions(-)
 create mode 100644 .idea/inspectionProfiles/Project_Default.xml
 delete mode 100644 util/src/main/kotlin/ISO20022.kt

diff --git a/.idea/dictionaries/dold.xml b/.idea/dictionaries/dold.xml
index f518024..ce7b473 100644
--- a/.idea/dictionaries/dold.xml
+++ b/.idea/dictionaries/dold.xml
@@ -8,6 +8,7 @@
       <w>cronspec</w>
       <w>dbit</w>
       <w>ebics</w>
+      <w>iban</w>
       <w>infos</w>
       <w>libeufin</w>
       <w>payto</w>
diff --git a/.idea/inspectionProfiles/Project_Default.xml 
b/.idea/inspectionProfiles/Project_Default.xml
new file mode 100644
index 0000000..030f244
--- /dev/null
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -0,0 +1,8 @@
+<component name="InspectionProjectProfileManager">
+  <profile version="1.0">
+    <option name="myName" value="Project Default" />
+    <inspection_tool class="JsonStandardCompliance" enabled="true" 
level="ERROR" enabled_by_default="true">
+      <option name="myWarnAboutComments" value="false" />
+    </inspection_tool>
+  </profile>
+</component>
\ 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 a5b7ac5..9d009f7 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt
@@ -33,9 +33,11 @@ import org.slf4j.Logger
 import org.slf4j.LoggerFactory
 import tech.libeufin.nexus.server.serverMain
 import tech.libeufin.util.CryptoUtil.hashpw
-import ch.qos.logback.classic.Level
-import ch.qos.logback.classic.LoggerContext
+import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
+import tech.libeufin.nexus.iso20022.parseCamtMessage
+import tech.libeufin.util.XMLUtil
 import tech.libeufin.util.setLogLevel
+import java.io.File
 
 val logger: Logger = LoggerFactory.getLogger("tech.libeufin.nexus")
 
@@ -58,6 +60,17 @@ class Serve : CliktCommand("Run nexus HTTP server") {
     }
 }
 
+class ParseCamt : CliktCommand("Parse a camt file") {
+    private val logLevel by option()
+    private val filename by argument()
+    override fun run() {
+        setLogLevel(logLevel)
+        val camtText = File(filename).readText(Charsets.UTF_8)
+        val res = parseCamtMessage(XMLUtil.parseStringIntoDom(camtText))
+        
println(jacksonObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(res))
+    }
+}
+
 class Superuser : CliktCommand("Add superuser or change pw") {
     private val dbName by option().default("libeufin-nexus.sqlite3")
     private val username by argument()
@@ -85,6 +98,6 @@ class Superuser : CliktCommand("Add superuser or change pw") {
 
 fun main(args: Array<String>) {
     NexusCommand()
-        .subcommands(Serve(), Superuser())
+        .subcommands(Serve(), Superuser(), ParseCamt())
         .main(args)
 }
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/Taler.kt 
b/nexus/src/main/kotlin/tech/libeufin/nexus/Taler.kt
index 7e94947..52ef436 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/Taler.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/Taler.kt
@@ -42,7 +42,7 @@ import tech.libeufin.nexus.bankaccount.addPaymentInitiation
 import tech.libeufin.nexus.iso20022.CamtBankAccountEntry
 import tech.libeufin.nexus.iso20022.CreditDebitIndicator
 import tech.libeufin.nexus.iso20022.EntryStatus
-import tech.libeufin.nexus.iso20022.TransactionInfo
+import tech.libeufin.nexus.iso20022.TransactionDetails
 import tech.libeufin.nexus.server.Pain001Data
 import tech.libeufin.nexus.server.authenticateRequest
 import tech.libeufin.nexus.server.expectNonNull
@@ -356,7 +356,7 @@ private suspend fun talerAddIncoming(call: ApplicationCall, 
httpClient: HttpClie
 }
 
 
-private fun ingestIncoming(payment: NexusBankTransactionEntity, txDtls: 
TransactionInfo) {
+private fun ingestIncoming(payment: NexusBankTransactionEntity, txDtls: 
TransactionDetails) {
     val subject = txDtls.unstructuredRemittanceInformation
     val debtorName = txDtls.debtor?.name
     if (debtorName == null) {
@@ -424,12 +424,13 @@ fun ingestTalerTransactions() {
         }.orderBy(Pair(NexusBankTransactionsTable.id, SortOrder.ASC)).forEach {
             // Incoming payment.
             val tx = jacksonObjectMapper().readValue(it.transactionJson, 
CamtBankAccountEntry::class.java)
-            if (tx.transactionInfos.size != 1) {
+            val txDetails = tx.details
+            if (txDetails == null) {
                 // We don't support batch transactions at the moment!
                 logger.warn("batch transactions not supported")
             } else {
                 when (tx.creditDebitIndicator) {
-                    CreditDebitIndicator.CRDT -> ingestIncoming(it, txDtls = 
tx.transactionInfos[0])
+                    CreditDebitIndicator.CRDT -> ingestIncoming(it, txDtls = 
txDetails)
                 }
             }
             lastId = it.id.value
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 0ee3c80..11644c2 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/bankaccount/BankAccount.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/bankaccount/BankAccount.kt
@@ -156,15 +156,15 @@ fun processCamtMessage(
             val rawEntity = NexusBankTransactionEntity.new {
                 bankAccount = acct
                 accountTransactionId = "AcctSvcrRef:$acctSvcrRef"
-                amount = tx.entryAmount.value.toPlainString()
-                currency = tx.entryAmount.currency
+                amount = tx.amount.value.toPlainString()
+                currency = tx.amount.currency
                 transactionJson = 
jacksonObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(tx)
                 creditDebitIndicator = tx.creditDebitIndicator.name
                 status = tx.status
             }
             rawEntity.flush()
             if (tx.creditDebitIndicator == CreditDebitIndicator.DBIT) {
-                val t0 = tx.transactionInfos.getOrNull(0)
+                val t0 = tx.details
                 val msgId = t0?.messageId
                 val pmtInfId = t0?.paymentInformationId
                 if (t0 != null && msgId != null && pmtInfId != null) {
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/iso20022/Iso20022.kt 
b/nexus/src/main/kotlin/tech/libeufin/nexus/iso20022/Iso20022.kt
index 2a9fda8..6958ea8 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/iso20022/Iso20022.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/iso20022/Iso20022.kt
@@ -60,7 +60,16 @@ enum class CashManagementResponseType(@get:JsonValue val 
jsonName: String) {
 
 @JsonInclude(JsonInclude.Include.NON_NULL)
 data class CamtReport(
+    val id: String,
+    val creationDateTime: String?,
+    val legalSequenceNumber: Int?,
+    val electronicSequenceNumber: Int?,
+    val fromDate: String?,
+    val toDate: String?,
+    val reportingSource: String?,
+    val proprietaryReportingSource: String?,
     val account: CashAccount,
+    val balances: List<Balance>,
     val entries: List<CamtBankAccountEntry>
 )
 
@@ -80,6 +89,7 @@ data class CashAccount(
     val otherId: GenericId?
 )
 
+@JsonInclude(JsonInclude.Include.NON_NULL)
 data class Balance(
     val type: String?,
     val subtype: String?,
@@ -91,14 +101,13 @@ data class Balance(
 )
 
 data class CamtParseResult(
-    val reports: List<CamtReport>,
-    val balances: List<Balance>,
-    val messageId: String,
     /**
      * Message type in form of the ISO 20022 message name.
      */
     val messageType: CashManagementResponseType,
-    val creationDateTime: String
+    val messageId: String,
+    val creationDateTime: String,
+    val reports: List<CamtReport>
 )
 
 @JsonInclude(JsonInclude.Include.NON_NULL)
@@ -117,7 +126,7 @@ data class OrganizationIdentification(
 
 /**
  * Identification of a party, which can be a private party
- * or an organiation.
+ * or an organization.
  *
  * Mapping of ISO 20022 PartyIdentification135.
  */
@@ -127,6 +136,7 @@ data class PartyIdentification(
     val countryOfResidence: String?,
     val privateId: PrivateIdentification?,
     val organizationId: OrganizationIdentification?,
+    val postalAddress: PostalAddress?,
 
     /**
      * Identification that applies to both private parties and organizations.
@@ -134,10 +144,48 @@ data class PartyIdentification(
     val otherId: GenericId?
 )
 
+@JsonInclude(JsonInclude.Include.NON_NULL)
+data class PostalAddress(
+    val addressCode: String?,
+    val addressProprietaryId: String?,
+    val addressProprietarySchemeName: String?,
+    val addressProprietaryIssuer: String?,
+    val department: String?,
+    val subDepartment: String?,
+    val streetName: String?,
+    val buildingNumber: String?,
+    val buildingName: String?,
+    val floor: String?,
+    val postBox: String?,
+    val room: String?,
+    val postCode: String?,
+    val townName: String?,
+    val townLocationName: String?,
+    val districtName: String?,
+    val countrySubDivision: String?,
+    val country: String?,
+    val addressLines: List<String>
+)
+
 @JsonInclude(JsonInclude.Include.NON_NULL)
 data class AgentIdentification(
     val name: String?,
+
     val bic: String?,
+
+    /**
+     * Legal entity identification.
+     */
+    val lei: String?,
+
+    val clearingSystemMemberId: String?,
+
+    val clearingSystemCode: String?,
+
+    val proprietaryClearingSystemCode: String?,
+
+    val postalAddress: PostalAddress?,
+
     val otherId: GenericId?
 )
 
@@ -152,29 +200,48 @@ data class CurrencyExchange(
 )
 
 @JsonInclude(JsonInclude.Include.NON_NULL)
-data class TransactionInfo(
-    val batchPaymentInformationId: String?,
-    val batchMessageId: String?,
-
+data class TransactionDetails(
     val debtor: PartyIdentification?,
     val debtorAccount: CashAccount?,
     val debtorAgent: AgentIdentification?,
     val creditor: PartyIdentification?,
     val creditorAccount: CashAccount?,
     val creditorAgent: AgentIdentification?,
+    val ultimateCreditor: PartyIdentification?,
+    val ultimateDebtor: PartyIdentification?,
 
     val endToEndId: String? = null,
     val paymentInformationId: String? = null,
     val messageId: String? = null,
 
-    val amount: CurrencyAmount,
-    val creditDebitIndicator: CreditDebitIndicator,
+    val purpose: String?,
+    val proprietaryPurpose: String?,
 
+    /**
+     * Currency exchange information for the transaction's amount.
+     */
+    val currencyExchange: CurrencyExchange?,
+
+    /**
+     * Amount as given in the payment initiation.
+     * Can be same or different currency as account currency.
+     */
     val instructedAmount: CurrencyAmount?,
-    val transactionAmount: CurrencyAmount?,
 
-    val instructedAmountCurrencyExchange: CurrencyExchange?,
-    val transactionAmountCurrencyExchange: CurrencyExchange?,
+    /**
+     * Raw amount used for currency exchange, before extra charges.
+     * Can be same or different currency as account currency.
+     */
+    val counterValueAmount: CurrencyAmount?,
+
+    /**
+     * Money that was moved between banks.
+     *
+     * For CH, we use the "TxAmt".
+     * For EPC, this amount is either blank or taken
+     * from the "IBC" proprietary amount.
+     */
+    val interBankSettlementAmount: CurrencyAmount?,
 
     /**
      * Unstructured remittance information (=subject line) of the transaction,
@@ -193,9 +260,28 @@ data class ReturnInfo(
     val additionalInfo: String?
 )
 
+data class BatchTransaction(
+    val amount: CurrencyAmount,
+
+    /**
+     * Is this entry debiting or crediting the account
+     * it is reported for?
+     */
+    val creditDebitIndicator: CreditDebitIndicator,
+
+    val details: TransactionDetails
+)
+
+@JsonInclude(JsonInclude.Include.NON_NULL)
+data class Batch(
+    val messageId: String?,
+    val paymentInformationId: String?,
+    val batchTransactions: List<BatchTransaction>
+)
+
 @JsonInclude(JsonInclude.Include.NON_NULL)
 data class CamtBankAccountEntry(
-    val entryAmount: CurrencyAmount,
+    val amount: CurrencyAmount,
 
     /**
      * Is this entry debiting or crediting the account
@@ -212,18 +298,40 @@ data class CamtBankAccountEntry(
      * Code that describes the type of bank transaction
      * in more detail
      */
-
     val bankTransactionCode: String,
-    /**
-     * Transaction details, if this entry contains a single transaction.
-     */
-    val transactionInfos: List<TransactionInfo>,
+
     val valueDate: String?,
+
     val bookingDate: String?,
+
     val accountServicerRef: String?,
-    val entryRef: String?
-)
 
+    val entryRef: String?,
+
+    /**
+     * Currency exchange information for the entry's amount.
+     * Only present if currency exchange happened at the entry level.
+     */
+    val currencyExchange: CurrencyExchange?,
+
+    /**
+     * Value before/after currency exchange before charges have been applied.
+     * Only present if currency exchange happened at the entry level.
+     */
+    val counterValueAmount: CurrencyAmount?,
+
+    /**
+     * Instructed amount.
+     * Only present if currency exchange happens at the entry level.
+     */
+    val instructedAmount: CurrencyAmount?,
+
+    /**
+     * Details of the underlying transaction for type=Simple.
+     */
+    val details: TransactionDetails?,
+    val batches: List<Batch>?
+)
 
 class CamtParsingError(msg: String) : Exception(msg)
 
@@ -371,19 +479,74 @@ private fun XmlElementDestructor.extractDateOrDateTime(): 
String {
     }
 }
 
+private fun XmlElementDestructor.extractInnerPostalAddress(): PostalAddress {
+    return PostalAddress(
+        addressCode = maybeUniqueChildNamed("AdrTp") { 
maybeUniqueChildNamed("Cd") { it.textContent } },
+        addressProprietaryIssuer = maybeUniqueChildNamed("AdrTp") {
+            maybeUniqueChildNamed("Prtry") {
+                maybeUniqueChildNamed("Issr") { it.textContent }
+            }
+        },
+        addressProprietarySchemeName = maybeUniqueChildNamed("AdrTp") {
+            maybeUniqueChildNamed("Prtry") {
+                maybeUniqueChildNamed("SchmeNm") { it.textContent }
+            }
+        },
+        addressProprietaryId = maybeUniqueChildNamed("AdrTp") {
+            maybeUniqueChildNamed("Prtry") {
+                maybeUniqueChildNamed("Id") { it.textContent }
+            }
+        },
+        buildingName = maybeUniqueChildNamed("BldgNm") { it.textContent },
+        buildingNumber = maybeUniqueChildNamed("BldgNb") { it.textContent },
+        country = maybeUniqueChildNamed("Ctry") { it.textContent },
+        countrySubDivision = maybeUniqueChildNamed("CtrySubDvsn") { 
it.textContent },
+        department = maybeUniqueChildNamed("Dept") { it.textContent },
+        districtName = maybeUniqueChildNamed("DstrctNm") { it.textContent },
+        floor = maybeUniqueChildNamed("Flr") { it.textContent },
+        postBox = maybeUniqueChildNamed("PstBx") { it.textContent },
+        postCode = maybeUniqueChildNamed("PstCd") { it.textContent },
+        room = maybeUniqueChildNamed("Room") { it.textContent },
+        streetName = maybeUniqueChildNamed("StrtNm") { it.textContent },
+        subDepartment = maybeUniqueChildNamed("SubDept") { it.textContent },
+        townLocationName = maybeUniqueChildNamed("TwnLctnNm") { it.textContent 
},
+        townName = maybeUniqueChildNamed("TwnNm") { it.textContent },
+        addressLines = mapEachChildNamed("AdrLine") { it.textContent }
+    )
+}
+
 private fun XmlElementDestructor.extractAgent(): AgentIdentification {
     return AgentIdentification(
         name = maybeUniqueChildNamed("FinInstnId") {
-            maybeUniqueChildNamed("Nm") {
-                it.textContent
-            }
+            maybeUniqueChildNamed("Nm") { it.textContent }
         },
         bic = requireUniqueChildNamed("FinInstnId") {
-            requireUniqueChildNamed("BIC") {
-                it.textContent
+            maybeUniqueChildNamed("BIC") { it.textContent }
+        },
+        lei = requireUniqueChildNamed("FinInstnId") {
+            maybeUniqueChildNamed("LEI") { it.textContent }
+        },
+        clearingSystemCode = requireUniqueChildNamed("FinInstnId") {
+            maybeUniqueChildNamed("ClrSysMmbId") {
+                maybeUniqueChildNamed("ClrSysId") {
+                    maybeUniqueChildNamed("Cd") { it.textContent }
+                }
+            }
+        },
+        proprietaryClearingSystemCode = requireUniqueChildNamed("FinInstnId") {
+            maybeUniqueChildNamed("ClrSysMmbId") {
+                maybeUniqueChildNamed("ClrSysId") {
+                    maybeUniqueChildNamed("Prtry") { it.textContent }
+                }
+            }
+        },
+        clearingSystemMemberId = requireUniqueChildNamed("FinInstnId") {
+            maybeUniqueChildNamed("ClrSysMmbId") {
+                maybeUniqueChildNamed("MmbId") { it.textContent }
             }
         },
-        otherId = null
+        otherId = requireUniqueChildNamed("FinInstnId") { 
maybeUniqueChildNamed("Othr") { extractGenericId() } },
+        postalAddress = requireUniqueChildNamed("FinInstnId") { 
maybeUniqueChildNamed("PstlAdr") { extractInnerPostalAddress() } }
     )
 }
 
@@ -434,10 +597,10 @@ private fun XmlElementDestructor.extractParty(): 
PartyIdentification {
         maybeUniqueChildNamed("PrvtId") {
             maybeUniqueChildNamed("DtAndPlcOfBirth") {
                 PrivateIdentification(
-                    birthDate = maybeUniqueChildNamed("BirthDt") { 
it.textContent},
-                    cityOfBirth = maybeUniqueChildNamed("CityOfBirth") { 
it.textContent},
-                    countryOfBirth = maybeUniqueChildNamed("CtryOfBirth") { 
it.textContent},
-                    provinceOfBirth = maybeUniqueChildNamed("PrvcOfBirth") { 
it.textContent}
+                    birthDate = maybeUniqueChildNamed("BirthDt") { 
it.textContent },
+                    cityOfBirth = maybeUniqueChildNamed("CityOfBirth") { 
it.textContent },
+                    countryOfBirth = maybeUniqueChildNamed("CtryOfBirth") { 
it.textContent },
+                    provinceOfBirth = maybeUniqueChildNamed("PrvcOfBirth") { 
it.textContent }
                 )
             }
         }
@@ -446,19 +609,20 @@ private fun XmlElementDestructor.extractParty(): 
PartyIdentification {
     val organizationId = maybeUniqueChildNamed("Id") {
         maybeUniqueChildNamed("OrgId") {
             OrganizationIdentification(
-                bic = maybeUniqueChildNamed("BICOrBEI") { it.textContent} ?: 
maybeUniqueChildNamed("AnyBIC") { it.textContent},
-                lei = maybeUniqueChildNamed("LEI") { it.textContent}
+                bic = maybeUniqueChildNamed("BICOrBEI") { it.textContent }
+                    ?: maybeUniqueChildNamed("AnyBIC") { it.textContent },
+                lei = maybeUniqueChildNamed("LEI") { it.textContent }
             )
         }
     }
 
-
     return PartyIdentification(
         name = maybeUniqueChildNamed("Nm") { it.textContent },
         otherId = otherId,
         privateId = privateId,
         organizationId = organizationId,
-        countryOfResidence = maybeUniqueChildNamed("CtryOfRes") { 
it.textContent }
+        countryOfResidence = maybeUniqueChildNamed("CtryOfRes") { 
it.textContent },
+        postalAddress = maybeUniqueChildNamed("PstlAdr") { 
extractInnerPostalAddress() }
     )
 }
 
@@ -482,7 +646,7 @@ private fun 
XmlElementDestructor.extractMaybeCurrencyExchange(): CurrencyExchang
     return maybeUniqueChildNamed("CcyXchg") {
         CurrencyExchange(
             sourceCurrency = requireUniqueChildNamed("SrcCcy") { 
it.textContent },
-            targetCurrency = requireUniqueChildNamed("TgtCcy") { 
it.textContent },
+            targetCurrency = requireUniqueChildNamed("TrgtCcy") { 
it.textContent },
             contractId = maybeUniqueChildNamed("CtrctId") { it.textContent },
             exchangeRate = requireUniqueChildNamed("XchgRate") { 
it.textContent },
             quotationDate = maybeUniqueChildNamed("QtnDt") { it.textContent },
@@ -491,108 +655,133 @@ private fun 
XmlElementDestructor.extractMaybeCurrencyExchange(): CurrencyExchang
     }
 }
 
-
-private fun XmlElementDestructor.extractTransactionInfos(
+private fun XmlElementDestructor.extractBatches(
     outerAmount: CurrencyAmount,
     outerCreditDebitIndicator: CreditDebitIndicator
-): List<TransactionInfo> {
+): List<Batch> {
+    return mapEachChildNamed("NtryDtls") {
+        val numDtls = mapEachChildNamed("TxDtls") { Unit }.count()
+        var amount = maybeExtractCurrencyAmount()
+        var creditDebitIndicator = maybeExtractCreditDebitIndicator()
+
+        val ttlAmt = maybeUniqueChildNamed("Btch") {
+            maybeUniqueChildNamed("TtlAmt") {
+                CurrencyAmount(
+                    value =  BigDecimal(it.textContent),
+                    currency = it.getAttribute("Ccy")
+                )
+            }
+        }
 
-    val numTxDtls = requireUniqueChildNamed("NtryDtls") {
-        mapEachChildNamed("TxDtls") { Unit }
-    }.count()
+        val ttlCreditDebitIndicator = maybeUniqueChildNamed("Btch") {
+            maybeExtractCreditDebitIndicator()
+        }
 
-    return requireUniqueChildNamed("NtryDtls") {
-        mapEachChildNamed("TxDtls") {
+        if (amount == null && ttlAmt != null && ttlCreditDebitIndicator != 
null) {
+            amount = ttlAmt
+            creditDebitIndicator = ttlCreditDebitIndicator
+        } else if (amount == null && numDtls == 1) {
+            amount = outerAmount
+            creditDebitIndicator = outerCreditDebitIndicator
+        }
 
-            val instructedAmount = maybeUniqueChildNamed("AmtDtls") {
-                maybeUniqueChildNamed("InstrAmt") { extractCurrencyAmount() }
-            }
+        if (amount == null || creditDebitIndicator == null) {
+            throw Error("no amount for inner transaction")
+        }
+        val txs = mapEachChildNamed("TxDtls") {
+            val details = extractTransactionDetails(outerAmount, 
outerCreditDebitIndicator, false)
+            BatchTransaction(amount, creditDebitIndicator, details)
+        }
+        Batch(null, null, txs)
+    }
+}
 
-            val transactionAmount = maybeUniqueChildNamed("AmtDtls") {
-                maybeUniqueChildNamed("TxAmt") { extractCurrencyAmount() }
-            }
+private fun XmlElementDestructor.maybeExtractCreditDebitIndicator(): 
CreditDebitIndicator? {
+    return maybeUniqueChildNamed("CdtDbtInd") { it.textContent }?.let {
+        CreditDebitIndicator.valueOf(it)
+    }
+}
 
-            var amount = maybeExtractCurrencyAmount()
-            var creditDebitIndicator = maybeUniqueChildNamed("CdtDbtInd") { 
it.textContent }?.let {
-                CreditDebitIndicator.valueOf(it)
-            }
-            if (amount == null) {
-                when {
-                    numTxDtls == 1 -> {
-                        amount = outerAmount
-                        creditDebitIndicator = outerCreditDebitIndicator
-                    }
-                    transactionAmount?.currency == outerAmount.currency -> {
-                        amount = transactionAmount
-                        creditDebitIndicator = outerCreditDebitIndicator
-                    }
-                    instructedAmount?.currency == outerAmount.currency -> {
-                        amount = instructedAmount
-                        creditDebitIndicator = outerCreditDebitIndicator
-                    }
-                    else -> {
-                        throw Error("invalid camt, no amount for transaction 
details of entry details")
-                    }
-                }
-            }
+private fun XmlElementDestructor.extractTransactionDetails(
+    outerAmount: CurrencyAmount,
+    outerCreditDebitIndicator: CreditDebitIndicator,
+    batch: Boolean
+): TransactionDetails {
+    val instructedAmount = maybeUniqueChildNamed("AmtDtls") {
+        maybeUniqueChildNamed("InstdAmt") { extractCurrencyAmount() }
+    }
 
-            if (creditDebitIndicator == null) {
-                throw Error("invalid camt, no credit/debit indicator for 
transaction details of entry details")
-            }
+    val creditDebitIndicator = maybeExtractCreditDebitIndicator() ?: 
outerCreditDebitIndicator
 
-            TransactionInfo(
-                batchMessageId = null,
-                batchPaymentInformationId = null,
-                amount = amount,
-                creditDebitIndicator = creditDebitIndicator,
-                instructedAmount = instructedAmount,
-                instructedAmountCurrencyExchange = 
maybeUniqueChildNamed("AmtDtls") {
-                    maybeUniqueChildNamed("InstrAmt") { 
extractMaybeCurrencyExchange() }
-                },
-                transactionAmount = transactionAmount,
-                transactionAmountCurrencyExchange = 
maybeUniqueChildNamed("AmtDtls") {
-                    maybeUniqueChildNamed("TxAmt") { 
extractMaybeCurrencyExchange() }
-                },
-                endToEndId = maybeUniqueChildNamed("Refs") {
-                    maybeUniqueChildNamed("EndToEndId") { it.textContent }
-                },
-                messageId = maybeUniqueChildNamed("Refs") {
-                    maybeUniqueChildNamed("MsgId") { it.textContent }
-                },
-                paymentInformationId = maybeUniqueChildNamed("Refs") {
-                    maybeUniqueChildNamed("PmtInfId") { it.textContent }
-                },
-                unstructuredRemittanceInformation = 
maybeUniqueChildNamed("RmtInf") {
-                    val chunks = mapEachChildNamed("Ustrd", { it.textContent })
-                    if (chunks.isEmpty()) {
-                        null
-                    } else {
-                        chunks.joinToString(separator = "")
-                    }
-                } ?: "",
-                creditorAgent = maybeUniqueChildNamed("CdtrAgt") { 
extractAgent() },
-                debtorAgent = maybeUniqueChildNamed("DbtrAgt") { 
extractAgent() },
-                debtorAccount = maybeUniqueChildNamed("DbtrAgt") { 
extractAccount() },
-                creditorAccount = maybeUniqueChildNamed("CdtrAgt") { 
extractAccount() },
-                debtor = maybeUniqueChildNamed("Dbtr") { extractParty() },
-                creditor = maybeUniqueChildNamed("Cdtr") { extractParty() },
-                returnInfo = maybeUniqueChildNamed("RtrInf") {
-                    ReturnInfo(
-                        originalBankTransactionCode = 
maybeUniqueChildNamed("OrgnlBkTxCd") {
-                            extractInnerBkTxCd(
-                                when (creditDebitIndicator) {
-                                    CreditDebitIndicator.DBIT -> 
CreditDebitIndicator.CRDT
-                                    CreditDebitIndicator.CRDT -> 
CreditDebitIndicator.DBIT
-                                })
-                        },
-                        originator = maybeUniqueChildNamed("Orgtr") { 
extractParty() },
-                        reason = maybeUniqueChildNamed("Rsn") { 
maybeUniqueChildNamed("Cd") { it.textContent } },
-                        proprietaryReason = maybeUniqueChildNamed("Rsn") { 
maybeUniqueChildNamed("Prtry") { it.textContent } },
-                        additionalInfo = maybeUniqueChildNamed("AddtlInf") { 
it.textContent }
+    val currencyExchange = maybeUniqueChildNamed("AmtDtls") {
+        val cxCntrVal = maybeUniqueChildNamed("CntrValAmt") { 
extractMaybeCurrencyExchange() }
+        val cxTx = maybeUniqueChildNamed("TxAmt") { 
extractMaybeCurrencyExchange() }
+        val cxInstr = maybeUniqueChildNamed("InstdAmt") { 
extractMaybeCurrencyExchange() }
+        cxCntrVal ?: cxTx ?: cxInstr
+    }
+
+    return TransactionDetails(
+        instructedAmount = instructedAmount,
+        counterValueAmount = maybeUniqueChildNamed("AmtDtls") {
+            maybeUniqueChildNamed("CntrValAmt") { extractCurrencyAmount() }
+        },
+        currencyExchange = currencyExchange,
+        // FIXME: implement
+        interBankSettlementAmount = null,
+        endToEndId = maybeUniqueChildNamed("Refs") {
+            maybeUniqueChildNamed("EndToEndId") { it.textContent }
+        },
+        messageId = maybeUniqueChildNamed("Refs") {
+            maybeUniqueChildNamed("MsgId") { it.textContent }
+        },
+        paymentInformationId = maybeUniqueChildNamed("Refs") {
+            maybeUniqueChildNamed("PmtInfId") { it.textContent }
+        },
+        unstructuredRemittanceInformation = maybeUniqueChildNamed("RmtInf") {
+            val chunks = mapEachChildNamed("Ustrd") { it.textContent }
+            if (chunks.isEmpty()) {
+                null
+            } else {
+                chunks.joinToString(separator = "")
+            }
+        } ?: "",
+        creditorAgent = maybeUniqueChildNamed("RltdAgts") { 
maybeUniqueChildNamed("CdtrAgt") { extractAgent() } },
+        debtorAgent = maybeUniqueChildNamed("RltdAgts") { 
maybeUniqueChildNamed("DbtrAgt") { extractAgent() } },
+        debtorAccount = maybeUniqueChildNamed("RltdPties") { 
maybeUniqueChildNamed("DbtrAcct") { extractAccount() } },
+        creditorAccount = maybeUniqueChildNamed("RltdPties") { 
maybeUniqueChildNamed("CdtrAcct") { extractAccount() } },
+        debtor = maybeUniqueChildNamed("RltdPties") { 
maybeUniqueChildNamed("Dbtr") { extractParty() } },
+        creditor = maybeUniqueChildNamed("RltdPties") { 
maybeUniqueChildNamed("Cdtr") { extractParty() } },
+        proprietaryPurpose = maybeUniqueChildNamed("Purp") { 
maybeUniqueChildNamed("Prtry") { it.textContent } },
+        purpose = maybeUniqueChildNamed("Purp") { maybeUniqueChildNamed("Cd") 
{ it.textContent } },
+        ultimateCreditor = maybeUniqueChildNamed("RltdPties") { 
maybeUniqueChildNamed("UltmtCdtr") { extractParty() } },
+        ultimateDebtor = maybeUniqueChildNamed("RltdPties") { 
maybeUniqueChildNamed("UltmtDbtr") { extractParty() } },
+        returnInfo = maybeUniqueChildNamed("RtrInf") {
+            ReturnInfo(
+                originalBankTransactionCode = 
maybeUniqueChildNamed("OrgnlBkTxCd") {
+                    extractInnerBkTxCd(
+                        when (creditDebitIndicator) {
+                            CreditDebitIndicator.DBIT -> 
CreditDebitIndicator.CRDT
+                            CreditDebitIndicator.CRDT -> 
CreditDebitIndicator.DBIT
+                        }
                     )
-                }
+                },
+                originator = maybeUniqueChildNamed("Orgtr") { extractParty() },
+                reason = maybeUniqueChildNamed("Rsn") { 
maybeUniqueChildNamed("Cd") { it.textContent } },
+                proprietaryReason = maybeUniqueChildNamed("Rsn") { 
maybeUniqueChildNamed("Prtry") { it.textContent } },
+                additionalInfo = maybeUniqueChildNamed("AddtlInf") { 
it.textContent }
             )
         }
+    )
+}
+
+private fun XmlElementDestructor.extractSingleDetails(
+    outerAmount: CurrencyAmount,
+    outerCreditDebitIndicator: CreditDebitIndicator
+): TransactionDetails {
+    return requireUniqueChildNamed("NtryDtls") {
+        requireUniqueChildNamed("TxDtls") {
+            extractTransactionDetails(outerAmount, outerCreditDebitIndicator, 
false)
+        }
     }
 }
 
@@ -600,21 +789,21 @@ private fun 
XmlElementDestructor.extractInnerBkTxCd(creditDebitIndicator: Credit
 
     val domain = maybeUniqueChildNamed("Domn") { maybeUniqueChildNamed("Cd") { 
it.textContent } }
     val family = maybeUniqueChildNamed("Domn") {
-            maybeUniqueChildNamed("Fmly") {
-                maybeUniqueChildNamed("Cd") { it.textContent }
-            }
+        maybeUniqueChildNamed("Fmly") {
+            maybeUniqueChildNamed("Cd") { it.textContent }
         }
+    }
     val subfamily = maybeUniqueChildNamed("Domn") {
-            maybeUniqueChildNamed("Fmly") {
-                maybeUniqueChildNamed("SubFmlyCd") { it.textContent }
-            }
+        maybeUniqueChildNamed("Fmly") {
+            maybeUniqueChildNamed("SubFmlyCd") { it.textContent }
         }
+    }
     val proprietaryCode = maybeUniqueChildNamed("Prtry") {
-            maybeUniqueChildNamed("Cd") { it.textContent }
-        }
+        maybeUniqueChildNamed("Cd") { it.textContent }
+    }
     val proprietaryIssuer = maybeUniqueChildNamed("Prtry") {
-            maybeUniqueChildNamed("Issr") { it.textContent }
-        }
+        maybeUniqueChildNamed("Issr") { it.textContent }
+    }
 
     if (domain != null && family != null && subfamily != null) {
         return "$domain-$family-$subfamily"
@@ -631,8 +820,36 @@ private fun 
XmlElementDestructor.extractInnerBkTxCd(creditDebitIndicator: Credit
     return "XTND-NTAV-NTAV"
 }
 
+
 private fun XmlElementDestructor.extractInnerTransactions(): CamtReport {
     val account = requireUniqueChildNamed("Acct") { extractAccount() }
+
+    val balances = mapEachChildNamed("Bal") {
+        Balance(
+            type = maybeUniqueChildNamed("Tp") {
+                maybeUniqueChildNamed("CdOrPrtry") {
+                    maybeUniqueChildNamed("Cd") { it.textContent }
+                }
+            },
+            proprietaryType = maybeUniqueChildNamed("Tp") {
+                maybeUniqueChildNamed("CdOrPrtry") {
+                    maybeUniqueChildNamed("Prtry") { it.textContent }
+                }
+            },
+            date = requireUniqueChildNamed("Dt") { extractDateOrDateTime() },
+            creditDebitIndicator = requireUniqueChildNamed("CdtDbtInd") { 
it.textContent }.let {
+                CreditDebitIndicator.valueOf(it)
+            },
+            subtype = maybeUniqueChildNamed("Tp") {
+                maybeUniqueChildNamed("SubTp") { maybeUniqueChildNamed("Cd") { 
it.textContent } }
+            },
+            proprietarySubtype = maybeUniqueChildNamed("Tp") {
+                maybeUniqueChildNamed("SubTp") { 
maybeUniqueChildNamed("Prtry") { it.textContent } }
+            },
+            amount = extractCurrencyAmount()
+        )
+    }
+
     val entries = mapEachChildNamed("Ntry") {
         val amount = extractCurrencyAmount()
         val status = requireUniqueChildNamed("Sts") { it.textContent }.let {
@@ -646,21 +863,71 @@ private fun 
XmlElementDestructor.extractInnerTransactions(): CamtReport {
         }
         val acctSvcrRef = maybeUniqueChildNamed("AcctSvcrRef") { 
it.textContent }
         val entryRef = maybeUniqueChildNamed("NtryRef") { it.textContent }
+
+        val numInnerTxs = mapEachChildNamed("NtryDtls") {
+            mapEachChildNamed("TxDtls") { Unit }
+        }.flatten().count()
+
+        val numBatches = mapEachChildNamed("NtryDtls") {
+            mapEachChildNamed("Btch") { Unit }
+        }.flatten().count()
+
+        val isBatch = numBatches > 0 || numInnerTxs > 1
+
+        val currencyExchange = maybeUniqueChildNamed("AmtDtls") {
+            val cxCntrVal = maybeUniqueChildNamed("CntrValAmt") { 
extractMaybeCurrencyExchange() }
+            val cxTx = maybeUniqueChildNamed("TxAmt") { 
extractMaybeCurrencyExchange() }
+            val cxInstr = maybeUniqueChildNamed("InstrAmt") { 
extractMaybeCurrencyExchange() }
+            cxCntrVal ?: cxTx ?: cxInstr
+        }
+
+        val counterValueAmount = maybeUniqueChildNamed("AmtDtls") {
+            maybeUniqueChildNamed("CntrValAmt") { extractCurrencyAmount() }
+        }
+
+        val instructedAmount = maybeUniqueChildNamed("AmtDtls") {
+            maybeUniqueChildNamed("InstdAmt") { extractCurrencyAmount() }
+        }
+
         // For now, only support account servicer reference as id
-        val transactionInfos = extractTransactionInfos(amount, 
creditDebitIndicator)
+
         CamtBankAccountEntry(
-            entryAmount = amount,
+            amount = amount,
             status = status,
+            currencyExchange = currencyExchange,
+            counterValueAmount = counterValueAmount,
+            instructedAmount = instructedAmount,
             creditDebitIndicator = creditDebitIndicator,
             bankTransactionCode = btc,
-            transactionInfos = transactionInfos,
+            details = if (isBatch) {
+                null
+            } else {
+                extractSingleDetails(amount, creditDebitIndicator)
+            },
+            batches = if (isBatch) {
+                extractBatches(amount, creditDebitIndicator)
+            } else {
+                null
+            },
             bookingDate = maybeUniqueChildNamed("BookgDt") { 
extractDateOrDateTime() },
             valueDate = maybeUniqueChildNamed("ValDt") { 
extractDateOrDateTime() },
             accountServicerRef = acctSvcrRef,
             entryRef = entryRef
         )
     }
-    return CamtReport(account, entries)
+    return CamtReport(
+        account = account,
+        entries = entries,
+        creationDateTime = maybeUniqueChildNamed("CreDtTm") { it.textContent },
+        balances = balances,
+        electronicSequenceNumber = maybeUniqueChildNamed("ElctrncSeqNb") { 
it.textContent.toInt() },
+        legalSequenceNumber = maybeUniqueChildNamed("LglSeqNb") { 
it.textContent.toInt() },
+        fromDate = maybeUniqueChildNamed("FrToDt") { 
maybeUniqueChildNamed("FrDtTm") { it.textContent } },
+        toDate = maybeUniqueChildNamed("FrToDt") { 
maybeUniqueChildNamed("ToDtTm") { it.textContent } },
+        id = requireUniqueChildNamed("Id") { it.textContent },
+        proprietaryReportingSource = maybeUniqueChildNamed("RptgSrc") { 
maybeUniqueChildNamed("Prtry") { it.textContent } },
+        reportingSource = maybeUniqueChildNamed("RptgSrc") { 
maybeUniqueChildNamed("Cd") { it.textContent } }
+    )
 }
 
 /**
@@ -688,22 +955,6 @@ fun parseCamtMessage(doc: Document): CamtParseResult {
                 }
             }
 
-            val balances = requireOnlyChild {
-                mapEachChildNamed("Bal") {
-                    Balance(
-                        type = maybeUniqueChildNamed("Tp") { 
maybeUniqueChildNamed("Cd") { it.textContent } },
-                        proprietaryType = maybeUniqueChildNamed("Tp") { 
maybeUniqueChildNamed("Prtry") { it.textContent } },
-                        date = extractDateOrDateTime(),
-                        creditDebitIndicator = 
requireUniqueChildNamed("CdtDbtInd") { it.textContent }.let {
-                            CreditDebitIndicator.valueOf(it)
-                        },
-                        subtype = maybeUniqueChildNamed("SubTp") { 
maybeUniqueChildNamed("Cd") { it.textContent } },
-                        proprietarySubtype = maybeUniqueChildNamed("SubTp") { 
maybeUniqueChildNamed("Prtry") { it.textContent } },
-                        amount = extractCurrencyAmount()
-                    )
-                }
-            }
-
             val messageId = requireOnlyChild {
                 requireUniqueChildNamed("GrpHdr") {
                     requireUniqueChildNamed("MsgId") { it.textContent }
@@ -723,7 +974,12 @@ fun parseCamtMessage(doc: Document): CamtParseResult {
                     }
                 }
             }
-            CamtParseResult(reports, balances, messageId, messageType, 
creationDateTime)
+            CamtParseResult(
+                reports = reports,
+                messageId = messageId,
+                messageType = messageType,
+                creationDateTime = creationDateTime
+            )
         }
     }
 }
diff --git a/nexus/src/test/kotlin/Iso20022Test.kt 
b/nexus/src/test/kotlin/Iso20022Test.kt
index 78c2f14..2e266fb 100644
--- a/nexus/src/test/kotlin/Iso20022Test.kt
+++ b/nexus/src/test/kotlin/Iso20022Test.kt
@@ -6,6 +6,7 @@ import tech.libeufin.nexus.iso20022.*
 import tech.libeufin.util.XMLUtil
 import java.math.BigDecimal
 import kotlin.test.assertEquals
+import kotlin.test.assertNotNull
 import kotlin.test.assertTrue
 
 fun loadXmlResource(name: String): Document {
@@ -28,31 +29,29 @@ class Iso20022Test {
         assertEquals(1, r.reports.size)
 
         // First Entry
-        
assertTrue(BigDecimal(100).compareTo(r.reports[0].entries[0].entryAmount.value) 
== 0)
-        assertEquals("EUR", r.reports[0].entries[0].entryAmount.currency)
+        
assertTrue(BigDecimal(100).compareTo(r.reports[0].entries[0].amount.value) == 0)
+        assertEquals("EUR", r.reports[0].entries[0].amount.currency)
         assertEquals(CreditDebitIndicator.CRDT, 
r.reports[0].entries[0].creditDebitIndicator)
         assertEquals(EntryStatus.BOOK, r.reports[0].entries[0].status)
         assertEquals(null, r.reports[0].entries[0].entryRef)
         assertEquals("acctsvcrref-001", 
r.reports[0].entries[0].accountServicerRef)
         assertEquals("PMNT-RCDT-ESCT", 
r.reports[0].entries[0].bankTransactionCode)
-        assertEquals(1, r.reports[0].entries[0].transactionInfos.size)
-        assertEquals("EUR", 
r.reports[0].entries[0].transactionInfos[0].amount.currency)
-        
assertTrue(BigDecimal(100).compareTo(r.reports[0].entries[0].transactionInfos[0].amount.value)
 == 0)
-        assertEquals(CreditDebitIndicator.CRDT, 
r.reports[0].entries[0].transactionInfos[0].creditDebitIndicator)
-        assertEquals("unstructured info one", 
r.reports[0].entries[0].transactionInfos[0].unstructuredRemittanceInformation)
+        assertNotNull(r.reports[0].entries[0].details)
+        assertEquals("unstructured info one", 
r.reports[0].entries[0].details?.unstructuredRemittanceInformation)
 
         // Second Entry
-        assertEquals("unstructured info across lines", 
r.reports[0].entries[1].transactionInfos[0].unstructuredRemittanceInformation)
+        assertEquals("unstructured info across lines", 
r.reports[0].entries[1].details?.unstructuredRemittanceInformation)
 
         // Third Entry
 
         // Make sure that round-tripping of entry CamtBankAccountEntry JSON 
works
         for (entry in r.reports.flatMap { it.entries }) {
             val txStr = 
jacksonObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(entry)
-            println(txStr)
             val tx2 = jacksonObjectMapper().readValue(txStr, 
CamtBankAccountEntry::class.java)
             val tx2Str = 
jacksonObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(tx2)
             assertEquals(jacksonObjectMapper().readTree(txStr), 
jacksonObjectMapper().readTree(tx2Str))
         }
+
+        
println(jacksonObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(r))
     }
 }
diff --git 
a/nexus/src/test/resources/iso20022-samples/camt.053/de.camt.053.001.02.xml 
b/nexus/src/test/resources/iso20022-samples/camt.053/de.camt.053.001.02.xml
index d297f47..14a36eb 100644
--- a/nexus/src/test/resources/iso20022-samples/camt.053/de.camt.053.001.02.xml
+++ b/nexus/src/test/resources/iso20022-samples/camt.053/de.camt.053.001.02.xml
@@ -233,7 +233,7 @@
                                 </Id>
                             </DbtrAcct>
                             <Cdtr>
-                                <Nm>Nonexistant Creditor</Nm>
+                                <Nm>Nonexistent Creditor</Nm>
                             </Cdtr>
                             <CdtrAcct>
                                 <Id>
@@ -330,7 +330,7 @@
                         </Chrgs>
                         <RltdPties>
                             <Dbtr>
-                                <Nm>Some US Bank</Nm>
+                                <Nm>Mr USA</Nm>
                                 <PstlAdr>
                                     <Ctry>US</Ctry>
                                     <AdrLine>42 Some Street</AdrLine>
@@ -359,6 +359,130 @@
                 </NtryDtls>
                 <AddtlNtryInf>AZV-UEBERWEISUNGSGUTSCHRIFT</AddtlNtryInf>
             </Ntry>
+
+            <Ntry>
+                <Amt Ccy="EUR">48.42</Amt>
+                <CdtDbtInd>DBIT</CdtDbtInd>
+                <Sts>BOOK</Sts>
+                <BookgDt>
+                    <Dt>2020-07-07</Dt>
+                </BookgDt>
+                <ValDt>
+                    <Dt>2020-07-07</Dt>
+                </ValDt>
+                <AcctSvcrRef>acctsvcrref-005</AcctSvcrRef>
+                <BkTxCd>
+                    <Domn>
+                        <Cd>PMNT</Cd>
+                        <Fmly>
+                            <Cd>ICDT</Cd>
+                            <SubFmlyCd>ESCT</SubFmlyCd>
+                        </Fmly>
+                    </Domn>
+                </BkTxCd>
+                <AmtDtls>
+                    <TxAmt>
+                        <Amt Ccy="CHF">46.3</Amt>
+                    </TxAmt>
+                </AmtDtls>
+                <NtryDtls>
+                    <Btch>
+                        <MsgId>UXC20070700006</MsgId>
+                        <PmtInfId>UXC20070700006PI00001</PmtInfId>
+                        <NbOfTxs>2</NbOfTxs>
+                        <TtlAmt Ccy="EUR">46.3</TtlAmt>
+                        <CdtDbtInd>DBIT</CdtDbtInd>
+                    </Btch>
+                    <TxDtls>
+                        <AmtDtls>
+                            <TxAmt>
+                                <Amt Ccy="EUR">23.1</Amt>
+                            </TxAmt>
+                        </AmtDtls>
+                        <BkTxCd>
+                            <Domn>
+                                <Cd>PMNT</Cd>
+                                <Fmly>
+                                    <Cd>ICDT</Cd>
+                                    <SubFmlyCd>ESCT</SubFmlyCd>
+                                </Fmly>
+                            </Domn>
+                        </BkTxCd>
+                        <RltdPties>
+                            <Cdtr>
+                                <Nm>Zahlungsempfaenger 23, ZA 5, DE</Nm>
+                                <PstlAdr>
+                                    <Ctry>DE</Ctry>
+                                    <AdrLine>DE Adresszeile 1</AdrLine>
+                                    <AdrLine>DE Adresszeile 2</AdrLine>
+                                </PstlAdr>
+                            </Cdtr>
+                            <CdtrAcct>
+                                <Id>
+                                    <IBAN>DE32733516350012345678</IBAN>
+                                </Id>
+                            </CdtrAcct>
+                        </RltdPties>
+                        <RltdAgts>
+                            <CdtrAgt>
+                                <FinInstnId>
+                                    <BIC>BYLADEM1ALR</BIC>
+                                </FinInstnId>
+                            </CdtrAgt>
+                        </RltdAgts>
+                    </TxDtls>
+                    <TxDtls>
+                        <Refs>
+                            <MsgId>asdfasdf</MsgId>
+                            <AcctSvcrRef>5j3k453k45</AcctSvcrRef>
+                            <PmtInfId>6j564l56</PmtInfId>
+                            <InstrId>6jl5lj65afasdf</InstrId>
+                            <EndToEndId>jh45k34h5l</EndToEndId>
+                        </Refs>
+                        <AmtDtls>
+                            <TxAmt>
+                                <Amt Ccy="EUR">23.2</Amt>
+                            </TxAmt>
+                        </AmtDtls>
+                        <BkTxCd>
+                            <Domn>
+                                <Cd>PMNT</Cd>
+                                <Fmly>
+                                    <Cd>ICDT</Cd>
+                                    <SubFmlyCd>ESCT</SubFmlyCd>
+                                </Fmly>
+                            </Domn>
+                            <Prtry>
+                                <Cd>K25</Cd>
+                            </Prtry>
+                        </BkTxCd>
+                        <RltdPties>
+                            <Cdtr>
+                                <Nm>Zahlungsempfaenger 23, ZA 5, AT</Nm>
+                                <PstlAdr>
+                                    <Ctry>AT</Ctry>
+                                    <AdrLine>AT Adresszeile 1</AdrLine>
+                                    <AdrLine>AT Adresszeile 2</AdrLine>
+                                </PstlAdr>
+                            </Cdtr>
+                            <CdtrAcct>
+                                <Id>
+                                    <IBAN>AT071100000012345678</IBAN>
+                                </Id>
+                            </CdtrAcct>
+                        </RltdPties>
+                        <RltdAgts>
+                            <CdtrAgt>
+                                <FinInstnId>
+                                    <BIC>BKAUATWW</BIC>
+                                </FinInstnId>
+                            </CdtrAgt>
+                        </RltdAgts>
+                    </TxDtls>
+                </NtryDtls>
+                <AddtlNtryInf>Order</AddtlNtryInf>
+            </Ntry>
+
         </Stmt>
     </BkToCstmrStmt>
-</Document>
\ No newline at end of file
+</Document>
diff --git a/util/src/main/kotlin/ISO20022.kt b/util/src/main/kotlin/ISO20022.kt
deleted file mode 100644
index 3f2cc8b..0000000
--- a/util/src/main/kotlin/ISO20022.kt
+++ /dev/null
@@ -1,69 +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.util
-
-import org.w3c.dom.Document
-
-/*
- * This file is part of LibEuFin.
- * Copyright (C) 2019 Stanisci and Dold.
-
- * 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/>
- */
-
-data class CamtData(
-    val bookingDate: Long,
-    val subject: String,
-    val txType: String, /* only "DBIT" / "CRDT" are admitted */
-    val currency: String,
-    val amount: String,
-    val status: String, /* only "BOOK" is admitted */
-    val counterpartIban: String,
-    val counterpartBic: String,
-    val counterpartName: String
-)
-
-fun parseCamt(camtDoc: Document): CamtData {
-    val txType = 
camtDoc.pickString("//*[local-name()='Ntry']//*[local-name()='CdtDbtInd']")
-    val bd = 
parseDashedDate(camtDoc.pickString("//*[local-name()='BookgDt']//*[local-name()='Dt']"))
-    return CamtData(
-        txType = txType,
-        bookingDate = bd.millis(),
-        subject = 
camtDoc.pickString("//*[local-name()='Ntry']//*[local-name()='Ustrd']"),
-        currency = 
camtDoc.pickString("//*[local-name()='Ntry']//*[local-name()='Amt']/@Ccy"),
-        amount = 
camtDoc.pickString("//*[local-name()='Ntry']//*[local-name()='Amt']"),
-        status = 
camtDoc.pickString("//*[local-name()='Ntry']//*[local-name()='Sts']"),
-        counterpartBic = 
camtDoc.pickString("//*[local-name()='RltdAgts']//*[local-name()='BIC']"),
-        counterpartIban = camtDoc.pickString("//*[local-name()='${if (txType 
== "DBIT") "CdtrAcct" else "DbtrAcct"}']//*[local-name()='IBAN']"),
-        counterpartName = 
camtDoc.pickString("//*[local-name()='RltdPties']//*[local-name()='${if (txType 
== "DBIT") "Cdtr" else "Dbtr"}']//*[local-name()='Nm']")
-    )
-}
\ No newline at end of file

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

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