libeufin

Integration and sandbox testing for FinTech APIs and data formats
Log | Files | Refs | Submodules | README | LICENSE

commit 3e3912bfc1e7af67756046379bc62ba4e292e374
parent a63f1ea71e22956cf9a0cd6b905626e254cc9b3f
Author: Florian Dold <florian.dold@gmail.com>
Date:   Tue,  7 Jul 2020 13:58:36 +0530

amount format

Diffstat:
Mnexus/src/main/kotlin/tech/libeufin/nexus/bankaccount/BankAccount.kt | 2+-
Mnexus/src/main/kotlin/tech/libeufin/nexus/iso20022/Iso20022.kt | 14+++++++-------
Mnexus/src/main/kotlin/tech/libeufin/nexus/server/JSON.kt | 41+++++++++++++++++++++++++++++++++++++++--
Mnexus/src/test/kotlin/Iso20022Test.kt | 6++++--
4 files changed, 51 insertions(+), 12 deletions(-)

diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/bankaccount/BankAccount.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/bankaccount/BankAccount.kt @@ -156,7 +156,7 @@ fun processCamtMessage( val rawEntity = NexusBankTransactionEntity.new { bankAccount = acct accountTransactionId = "AcctSvcrRef:$acctSvcrRef" - amount = tx.entryAmount.amount + amount = tx.entryAmount.value.toPlainString() currency = tx.entryAmount.currency transactionJson = jacksonObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(tx) creditDebitIndicator = tx.creditDebitIndicator.name diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/iso20022/Iso20022.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/iso20022/Iso20022.kt @@ -27,6 +27,7 @@ import com.fasterxml.jackson.annotation.JsonValue import org.w3c.dom.Document import tech.libeufin.nexus.server.CurrencyAmount import tech.libeufin.util.* +import java.math.BigDecimal import java.time.Instant import java.time.ZoneId import java.time.ZonedDateTime @@ -471,7 +472,7 @@ private fun XmlElementDestructor.extractParty(): PartyIdentification { private fun XmlElementDestructor.extractCurrencyAmount(): CurrencyAmount { return CurrencyAmount( - amount = requireUniqueChildNamed("Amt") { it.textContent }, + value = BigDecimal(requireUniqueChildNamed("Amt") { it.textContent }), currency = requireUniqueChildNamed("Amt") { it.getAttribute("Ccy") } ) } @@ -479,8 +480,8 @@ private fun XmlElementDestructor.extractCurrencyAmount(): CurrencyAmount { private fun XmlElementDestructor.maybeExtractCurrencyAmount(): CurrencyAmount? { return maybeUniqueChildNamed("Amt") { CurrencyAmount( - it.textContent, - it.getAttribute("Ccy") + it.getAttribute("Ccy"), + BigDecimal(it.textContent) ) } } @@ -624,8 +625,7 @@ private fun XmlElementDestructor.extractInnerBkTxCd(): BankTransactionCode { private fun XmlElementDestructor.extractInnerTransactions(): CamtReport { val account = requireUniqueChildNamed("Acct") { extractAccount() } val entries = mapEachChildNamed("Ntry") { - val amount = requireUniqueChildNamed("Amt") { it.textContent } - val currency = requireUniqueChildNamed("Amt") { it.getAttribute("Ccy") } + val amount = extractCurrencyAmount() val status = requireUniqueChildNamed("Sts") { it.textContent }.let { EntryStatus.valueOf(it) } @@ -638,9 +638,9 @@ private fun XmlElementDestructor.extractInnerTransactions(): CamtReport { val acctSvcrRef = maybeUniqueChildNamed("AcctSvcrRef") { it.textContent } val entryRef = maybeUniqueChildNamed("NtryRef") { it.textContent } // For now, only support account servicer reference as id - val transactionInfos = extractTransactionInfos(CurrencyAmount(currency, amount), creditDebitIndicator) + val transactionInfos = extractTransactionInfos(amount, creditDebitIndicator) CamtBankAccountEntry( - entryAmount = CurrencyAmount(currency, amount), + entryAmount = amount, status = status, creditDebitIndicator = creditDebitIndicator, bankTransactionCode = btc, diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/server/JSON.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/server/JSON.kt @@ -23,11 +23,21 @@ import com.fasterxml.jackson.annotation.JsonSubTypes import com.fasterxml.jackson.annotation.JsonTypeInfo import com.fasterxml.jackson.annotation.JsonTypeName import com.fasterxml.jackson.annotation.JsonValue +import com.fasterxml.jackson.core.JsonGenerator +import com.fasterxml.jackson.core.JsonParser +import com.fasterxml.jackson.databind.DeserializationContext import com.fasterxml.jackson.databind.JsonNode +import com.fasterxml.jackson.databind.SerializerProvider +import com.fasterxml.jackson.databind.annotation.JsonDeserialize +import com.fasterxml.jackson.databind.annotation.JsonSerialize +import com.fasterxml.jackson.databind.deser.std.StdDeserializer +import com.fasterxml.jackson.databind.ser.std.StdSerializer import tech.libeufin.nexus.iso20022.CamtBankAccountEntry import tech.libeufin.nexus.iso20022.CreditDebitIndicator import tech.libeufin.nexus.iso20022.EntryStatus import tech.libeufin.util.* +import java.lang.UnsupportedOperationException +import java.math.BigDecimal import java.time.Instant import java.time.ZoneId import java.time.ZonedDateTime @@ -342,12 +352,39 @@ data class ImportBankAccount( val nexusBankAccountId: String ) + +class CurrencyAmountDeserializer(jc: Class<*> = CurrencyAmount::class.java) : StdDeserializer<CurrencyAmount>(jc) { + override fun deserialize(p: JsonParser?, ctxt: DeserializationContext?): CurrencyAmount { + if (p == null) { + throw UnsupportedOperationException(); + } + val s = p.valueAsString + val components = s.split(":") + // FIXME: error handling! + return CurrencyAmount(components[0], BigDecimal(components[1])) + } +} + +class CurrencyAmountSerializer(jc: Class<CurrencyAmount> = CurrencyAmount::class.java) : StdSerializer<CurrencyAmount>(jc) { + override fun serialize(value: CurrencyAmount?, gen: JsonGenerator?, provider: SerializerProvider?) { + if (gen == null) { + throw UnsupportedOperationException() + } + if (value == null) { + gen.writeNull() + } else { + gen.writeString("${value.currency}:${value.value.toPlainString()}") + } + } +} + +@JsonDeserialize(using = CurrencyAmountDeserializer::class) +@JsonSerialize(using = CurrencyAmountSerializer::class) data class CurrencyAmount( val currency: String, - val amount: String + val value: BigDecimal ) - /** * Account entry item as returned by the /bank-accounts/{acctId}/transactions API. */ diff --git a/nexus/src/test/kotlin/Iso20022Test.kt b/nexus/src/test/kotlin/Iso20022Test.kt @@ -4,7 +4,9 @@ import org.junit.Test import org.w3c.dom.Document import tech.libeufin.nexus.iso20022.* import tech.libeufin.util.XMLUtil +import java.math.BigDecimal import kotlin.test.assertEquals +import kotlin.test.assertTrue fun loadXmlResource(name: String): Document { val classLoader = ClassLoader.getSystemClassLoader() @@ -26,7 +28,7 @@ class Iso20022Test { assertEquals(1, r.reports.size) // First Entry - assertEquals("100.00", r.reports[0].entries[0].entryAmount.amount) + assertTrue(BigDecimal(100).compareTo(r.reports[0].entries[0].entryAmount.value) == 0) assertEquals("EUR", r.reports[0].entries[0].entryAmount.currency) assertEquals(CreditDebitIndicator.CRDT, r.reports[0].entries[0].creditDebitIndicator) assertEquals(EntryStatus.BOOK, r.reports[0].entries[0].status) @@ -39,7 +41,7 @@ class Iso20022Test { assertEquals("DK", r.reports[0].entries[0].bankTransactionCode.proprietaryIssuer) assertEquals(1, r.reports[0].entries[0].transactionInfos.size) assertEquals("EUR", r.reports[0].entries[0].transactionInfos[0].amount.currency) - assertEquals("100.00", r.reports[0].entries[0].transactionInfos[0].amount.amount) + 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)