[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[libeufin] 01/02: ISO 20022 amount progress
From: |
gnunet |
Subject: |
[libeufin] 01/02: ISO 20022 amount progress |
Date: |
Tue, 07 Jul 2020 22:15:13 +0200 |
This is an automated email from the git hooks/post-receive script.
dold pushed a commit to branch master
in repository libeufin.
commit f75f5baa9b9672c79275b2d42029a6c614d1c8db
Author: Florian Dold <florian.dold@gmail.com>
AuthorDate: Tue Jul 7 21:28:36 2020 +0530
ISO 20022 amount progress
---
.idea/dictionaries/dold.xml | 1 +
.idea/inspectionProfiles/Project_Default.xml | 8 +
nexus/src/main/kotlin/tech/libeufin/nexus/Taler.kt | 9 +-
.../tech/libeufin/nexus/bankaccount/BankAccount.kt | 6 +-
.../tech/libeufin/nexus/iso20022/Iso20022.kt | 357 +++++++++++++--------
nexus/src/test/kotlin/Iso20022Test.kt | 17 +-
.../camt.053/de.camt.053.001.02.xml | 2 +-
7 files changed, 248 insertions(+), 152 deletions(-)
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/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..4f8beaf 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/iso20022/Iso20022.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/iso20022/Iso20022.kt
@@ -91,14 +91,14 @@ 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 balances: List<Balance>,
+ val reports: List<CamtReport>
)
@JsonInclude(JsonInclude.Include.NON_NULL)
@@ -117,7 +117,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.
*/
@@ -152,10 +152,7 @@ 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?,
@@ -167,14 +164,31 @@ data class TransactionInfo(
val paymentInformationId: String? = null,
val messageId: String? = null,
- val amount: CurrencyAmount,
- val creditDebitIndicator: CreditDebitIndicator,
+ /**
+ * 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 +207,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
+)
+
+
+data class Batch(
+ val batchTransactions: List<BatchTransaction>,
+ val messageId: String?,
+ val paymentInformationId: String?
+)
+
@JsonInclude(JsonInclude.Include.NON_NULL)
data class CamtBankAccountEntry(
- val entryAmount: CurrencyAmount,
+ val amount: CurrencyAmount,
/**
* Is this entry debiting or crediting the account
@@ -212,18 +245,33 @@ 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.
+ */
+ val counterValueAmount: CurrencyAmount?,
+
+ /**
+ * Details of the underlying transaction for type=Simple.
+ */
+ val details: TransactionDetails?,
+ val batches: List<Batch>?
+)
class CamtParsingError(msg: String) : Exception(msg)
@@ -434,10 +482,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,13 +494,13 @@ 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,
@@ -482,7 +530,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 +539,113 @@ private fun
XmlElementDestructor.extractMaybeCurrencyExchange(): CurrencyExchang
}
}
-
-private fun XmlElementDestructor.extractTransactionInfos(
+private fun XmlElementDestructor.extractBatches(
outerAmount: CurrencyAmount,
outerCreditDebitIndicator: CreditDebitIndicator
-): List<TransactionInfo> {
-
- val numTxDtls = requireUniqueChildNamed("NtryDtls") {
- mapEachChildNamed("TxDtls") { Unit }
- }.count()
+): List<Batch> {
+ return mapEachChildNamed("NtryDtls") {
+ val numDtls = mapEachChildNamed("TxDtls") { Unit }.count()
+ var amount = maybeExtractCurrencyAmount()
+ var creditDebitIndicator = maybeExtractCreditDebitIndicator()
+
+ if (amount == null && numDtls == 1) {
+ amount = outerAmount
+ creditDebitIndicator = outerCreditDebitIndicator
+ }
+ 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(txs, null, null)
+ }
+}
- return requireUniqueChildNamed("NtryDtls") {
- mapEachChildNamed("TxDtls") {
+private fun XmlElementDestructor.maybeExtractCreditDebitIndicator():
CreditDebitIndicator? {
+ return maybeUniqueChildNamed("CdtDbtInd") { it.textContent }?.let {
+ CreditDebitIndicator.valueOf(it)
+ }
+}
- val instructedAmount = maybeUniqueChildNamed("AmtDtls") {
- maybeUniqueChildNamed("InstrAmt") { extractCurrencyAmount() }
- }
+private fun XmlElementDestructor.extractTransactionDetails(
+ outerAmount: CurrencyAmount,
+ outerCreditDebitIndicator: CreditDebitIndicator,
+ batch: Boolean
+): TransactionDetails {
+ val instructedAmount = maybeUniqueChildNamed("AmtDtls") {
+ maybeUniqueChildNamed("InstdAmt") { extractCurrencyAmount() }
+ }
- val transactionAmount = maybeUniqueChildNamed("AmtDtls") {
- maybeUniqueChildNamed("TxAmt") { extractCurrencyAmount() }
- }
+ val creditDebitIndicator = maybeExtractCreditDebitIndicator() ?:
outerCreditDebitIndicator
- 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")
- }
- }
- }
+ val currencyExchange = maybeUniqueChildNamed("AmtDtls") {
+ val cxCntrVal = maybeUniqueChildNamed("CntrValAmt") {
extractMaybeCurrencyExchange() }
+ val cxTx = maybeUniqueChildNamed("TxAmt") {
extractMaybeCurrencyExchange() }
+ val cxInstr = maybeUniqueChildNamed("InstdAmt") {
extractMaybeCurrencyExchange() }
+ cxCntrVal ?: cxTx ?: cxInstr
+ }
- if (creditDebitIndicator == null) {
- throw Error("invalid camt, no credit/debit indicator for
transaction details of entry details")
+ 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 = "")
}
-
- 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 }
+ } ?: "",
+ creditorAgent = maybeUniqueChildNamed("RltdAgts") {
maybeUniqueChildNamed("CdtrAgt") { extractAgent() } },
+ debtorAgent = maybeUniqueChildNamed("RltdAgts") {
maybeUniqueChildNamed("DbtrAgt") { extractAgent() } },
+ debtorAccount = maybeUniqueChildNamed("RltdPties") {
maybeUniqueChildNamed("DbtrAgt") { extractAccount() } },
+ creditorAccount = maybeUniqueChildNamed("RltdPties") {
maybeUniqueChildNamed("CdtrAgt") { extractAccount() } },
+ debtor = maybeUniqueChildNamed("RltdPties") {
maybeUniqueChildNamed("Dbtr") { extractParty() } },
+ creditor = maybeUniqueChildNamed("RltdPties") {
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 }
)
}
+ )
+}
+
+
+private fun XmlElementDestructor.extractSingleDetails(
+ outerAmount: CurrencyAmount,
+ outerCreditDebitIndicator: CreditDebitIndicator
+): TransactionDetails {
+ return requireUniqueChildNamed("NtryDtls") {
+ requireUniqueChildNamed("TxDtls") {
+ extractTransactionDetails(outerAmount, outerCreditDebitIndicator,
false)
+ }
}
}
@@ -600,21 +653,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,6 +684,7 @@ private fun
XmlElementDestructor.extractInnerBkTxCd(creditDebitIndicator: Credit
return "XTND-NTAV-NTAV"
}
+
private fun XmlElementDestructor.extractInnerTransactions(): CamtReport {
val account = requireUniqueChildNamed("Acct") { extractAccount() }
val entries = mapEachChildNamed("Ntry") {
@@ -646,14 +700,41 @@ private fun
XmlElementDestructor.extractInnerTransactions(): CamtReport {
}
val acctSvcrRef = maybeUniqueChildNamed("AcctSvcrRef") {
it.textContent }
val entryRef = maybeUniqueChildNamed("NtryRef") { it.textContent }
+
+ val numNtryDtls = mapEachChildNamed("NtryDtls") {
+ Unit
+ }.count()
+
+ 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() }
+ }
+
// 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,
creditDebitIndicator = creditDebitIndicator,
bankTransactionCode = btc,
- transactionInfos = transactionInfos,
+ details = if (numNtryDtls == 1) {
+ extractSingleDetails(amount, creditDebitIndicator)
+ } else {
+ null
+ },
+ batches = if (numNtryDtls > 1) {
+ extractBatches(amount, creditDebitIndicator)
+ } else {
+ null
+ },
bookingDate = maybeUniqueChildNamed("BookgDt") {
extractDateOrDateTime() },
valueDate = maybeUniqueChildNamed("ValDt") {
extractDateOrDateTime() },
accountServicerRef = acctSvcrRef,
@@ -723,7 +804,13 @@ fun parseCamtMessage(doc: Document): CamtParseResult {
}
}
}
- CamtParseResult(reports, balances, messageId, messageType,
creationDateTime)
+ CamtParseResult(
+ reports = reports,
+ balances = balances,
+ 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..dc3511e 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
@@ -361,4 +361,4 @@
</Ntry>
</Stmt>
</BkToCstmrStmt>
-</Document>
\ No newline at end of file
+</Document>
--
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.