summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMS <ms@taler.net>2023-07-18 13:49:55 +0200
committerMS <ms@taler.net>2023-07-18 13:49:55 +0200
commit5b56ad9ed803b3a1105e6333716dcfca7593a3f1 (patch)
tree05ab91aada2cb5cb496461519d28aee5fd9fd2d0
parentf1b7567a31e2a3496fe9d1a6887d3ea599414895 (diff)
downloadlibeufin-5b56ad9ed803b3a1105e6333716dcfca7593a3f1.tar.gz
libeufin-5b56ad9ed803b3a1105e6333716dcfca7593a3f1.tar.bz2
libeufin-5b56ad9ed803b3a1105e6333716dcfca7593a3f1.zip
Getting the balance in constant time.
-rw-r--r--build.gradle2
-rw-r--r--database-versioning/sandbox-0001.sql3
-rw-r--r--nexus/src/main/kotlin/tech/libeufin/nexus/Taler.kt5
-rw-r--r--nexus/src/test/kotlin/SandboxAccessApiTest.kt7
-rw-r--r--nexus/src/test/kotlin/SandboxBankAccountTest.kt9
-rw-r--r--nexus/src/test/kotlin/SandboxCircuitApiTest.kt7
-rw-r--r--sandbox/src/main/kotlin/tech/libeufin/sandbox/CircuitApi.kt9
-rw-r--r--sandbox/src/main/kotlin/tech/libeufin/sandbox/DB.kt5
-rw-r--r--sandbox/src/main/kotlin/tech/libeufin/sandbox/EbicsProtocolBackend.kt69
-rw-r--r--sandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt19
-rw-r--r--sandbox/src/main/kotlin/tech/libeufin/sandbox/bankAccount.kt39
-rw-r--r--sandbox/src/test/kotlin/BalanceTest.kt23
-rw-r--r--util/src/main/kotlin/amounts.kt9
-rw-r--r--util/src/test/kotlin/AmountTest.kt1
14 files changed, 87 insertions, 120 deletions
diff --git a/build.gradle b/build.gradle
index bdcf07d0..6f944e01 100644
--- a/build.gradle
+++ b/build.gradle
@@ -23,7 +23,7 @@ if (!JavaVersion.current().isJava11Compatible()){
allprojects {
ext.set("ktor_version", "2.2.1")
ext.set("ktor_auth_version", "1.6.8")
- ext.set("exposed_version", "0.32.1")
+ ext.set("exposed_version", "0.41.1")
repositories {
mavenCentral()
diff --git a/database-versioning/sandbox-0001.sql b/database-versioning/sandbox-0001.sql
index b6ed53ef..7fa9bbd7 100644
--- a/database-versioning/sandbox-0001.sql
+++ b/database-versioning/sandbox-0001.sql
@@ -25,6 +25,9 @@ CREATE TABLE IF NOT EXISTS bankaccounts
ALTER TABLE
bankaccounts ADD CONSTRAINT accountLabelIndex UNIQUE ("label");
+ALTER TABLE
+ bankaccounts ADD COLUMN balance TEXT DEFAULT ('0');
+
CREATE TABLE IF NOT EXISTS bankaccounttransactions
(id BIGSERIAL PRIMARY KEY
,"creditorIban" TEXT NOT NULL
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/Taler.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/Taler.kt
index 5a59401a..cebb250d 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/Taler.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/Taler.kt
@@ -638,7 +638,10 @@ private suspend fun addIncoming(call: ApplicationCall) {
* the ingested ones.
*/
val lastIncomingPayment = transaction {
- val lastRecord = TalerIncomingPaymentEntity.all().last()
+ val allIncomingPayments = TalerIncomingPaymentEntity.all()
+ if (allIncomingPayments.empty())
+ throw internalServerError("Incoming payment(s) not found AFTER /add-incoming")
+ val lastRecord = allIncomingPayments.last()
return@transaction Pair(lastRecord.id.value, lastRecord.timestampMs)
}
call.respond(object {
diff --git a/nexus/src/test/kotlin/SandboxAccessApiTest.kt b/nexus/src/test/kotlin/SandboxAccessApiTest.kt
index 175e479a..4ac26ab6 100644
--- a/nexus/src/test/kotlin/SandboxAccessApiTest.kt
+++ b/nexus/src/test/kotlin/SandboxAccessApiTest.kt
@@ -120,14 +120,17 @@ class SandboxAccessApiTest {
}
val mapper = ObjectMapper()
var j = mapper.readTree(R.readBytes())
- assert(j.get("balance").get("amount").asText() == "TESTKUDOS:20")
+ val expectDebitOf20 = j.get("balance").get("amount").asText()
+ println("Expect debit of 20: $expectDebitOf20")
+ val testkudos20regex = "^TESTKUDOS:20(.00)?$".toRegex()
+ assert(testkudos20regex.matches(expectDebitOf20))
assert(j.get("balance").get("credit_debit_indicator").asText().lowercase() == "debit")
// Bar checks its balance: 20
R = client.get("/demobanks/default/access-api/accounts/bar") {
basicAuth("bar", "bar")
}
j = mapper.readTree(R.readBytes())
- assert(j.get("balance").get("amount").asText() == "TESTKUDOS:20")
+ assert(testkudos20regex.matches(j.get("balance").get("amount").asText()))
assert(j.get("balance").get("credit_debit_indicator").asText().lowercase() == "credit")
// Foo tries with an invalid amount
R = client.post("/demobanks/default/access-api/accounts/foo/transactions") {
diff --git a/nexus/src/test/kotlin/SandboxBankAccountTest.kt b/nexus/src/test/kotlin/SandboxBankAccountTest.kt
index 799f3435..d2e3197a 100644
--- a/nexus/src/test/kotlin/SandboxBankAccountTest.kt
+++ b/nexus/src/test/kotlin/SandboxBankAccountTest.kt
@@ -6,6 +6,7 @@ import tech.libeufin.sandbox.sandboxApp
import tech.libeufin.sandbox.wireTransfer
import tech.libeufin.util.buildBasicAuthLine
import tech.libeufin.util.parseDecimal
+import tech.libeufin.util.roundToTwoDigits
class SandboxBankAccountTest {
// Check if the balance shows debit.
@@ -26,7 +27,7 @@ class SandboxBankAccountTest {
* transactions must be included in the calculation.
*/
var bankBalance = getBalance("admin")
- assert(bankBalance == parseDecimal("-1"))
+ assert(bankBalance.roundToTwoDigits() == parseDecimal("-1").roundToTwoDigits())
wireTransfer(
"foo",
"admin",
@@ -35,7 +36,7 @@ class SandboxBankAccountTest {
"TESTKUDOS:5"
)
bankBalance = getBalance("admin")
- assert(bankBalance == parseDecimal("4"))
+ assert(bankBalance.roundToTwoDigits() == parseDecimal("4").roundToTwoDigits())
// Trigger Insufficient funds case for users.
try {
wireTransfer(
@@ -64,9 +65,9 @@ class SandboxBankAccountTest {
}
// Check balance didn't change for both parties.
bankBalance = getBalance("admin")
- assert(bankBalance == parseDecimal("4"))
+ assert(bankBalance.roundToTwoDigits() == parseDecimal("4").roundToTwoDigits())
val fooBalance = getBalance("foo")
- assert(fooBalance == parseDecimal("-4"))
+ assert(fooBalance.roundToTwoDigits() == parseDecimal("-4").roundToTwoDigits())
}
}
} \ No newline at end of file
diff --git a/nexus/src/test/kotlin/SandboxCircuitApiTest.kt b/nexus/src/test/kotlin/SandboxCircuitApiTest.kt
index 72a942e3..bbfffd1b 100644
--- a/nexus/src/test/kotlin/SandboxCircuitApiTest.kt
+++ b/nexus/src/test/kotlin/SandboxCircuitApiTest.kt
@@ -13,6 +13,7 @@ import org.junit.Test
import tech.libeufin.sandbox.*
import tech.libeufin.util.getIban
import tech.libeufin.util.parseAmount
+import tech.libeufin.util.roundToTwoDigits
import java.io.File
import java.math.BigDecimal
import java.util.*
@@ -617,7 +618,7 @@ class SandboxCircuitApiTest {
amount = "TESTKUDOS:100"
)
val fooBalance = getBalance("foo")
- assert(fooBalance == BigDecimal("100"))
+ assert(fooBalance.roundToTwoDigits() == BigDecimal("100").roundToTwoDigits())
// Foo pays 3 to bar.
wireTransfer(
"foo",
@@ -626,7 +627,7 @@ class SandboxCircuitApiTest {
amount = "TESTKUDOS:3"
)
val barBalance = getBalance("bar")
- assert(barBalance == BigDecimal("3"))
+ assert(barBalance.roundToTwoDigits() == BigDecimal("3").roundToTwoDigits())
// Deleting foo from the system.
transaction {
val uBankAccount = getBankAccountFromLabel("foo")
@@ -635,7 +636,7 @@ class SandboxCircuitApiTest {
uCustomerProfile.delete()
}
val barBalanceUpdate = getBalance("bar")
- assert(barBalanceUpdate == BigDecimal("3"))
+ assert(barBalanceUpdate.roundToTwoDigits() == BigDecimal("3").roundToTwoDigits())
}
}
}
diff --git a/sandbox/src/main/kotlin/tech/libeufin/sandbox/CircuitApi.kt b/sandbox/src/main/kotlin/tech/libeufin/sandbox/CircuitApi.kt
index 956ccba7..ebd72d3c 100644
--- a/sandbox/src/main/kotlin/tech/libeufin/sandbox/CircuitApi.kt
+++ b/sandbox/src/main/kotlin/tech/libeufin/sandbox/CircuitApi.kt
@@ -146,12 +146,6 @@ fun generateCashoutSubject(
" to ${amountCredit.currency}:${amountCredit.amount}"
}
-fun BigDecimal.roundToTwoDigits(): BigDecimal {
- // val twoDigitsRounding = MathContext(2)
- // return this.round(twoDigitsRounding)
- return this.setScale(2, RoundingMode.HALF_UP)
-}
-
/**
* By default, it takes the amount in the regional currency
* and applies ratio and fees to convert it to fiat. If the
@@ -522,8 +516,7 @@ fun circuitApi(circuitRoute: Route) {
// check that the balance is sufficient
val balance = getBalance(
user,
- demobank.name,
- withPending = true
+ demobank.name
)
val balanceCheck = balance - amountDebitAsNumber
if (balanceCheck < BigDecimal.ZERO && balanceCheck.abs() > BigDecimal(demobank.config.usersDebtLimit))
diff --git a/sandbox/src/main/kotlin/tech/libeufin/sandbox/DB.kt b/sandbox/src/main/kotlin/tech/libeufin/sandbox/DB.kt
index c71d1ee7..87db263e 100644
--- a/sandbox/src/main/kotlin/tech/libeufin/sandbox/DB.kt
+++ b/sandbox/src/main/kotlin/tech/libeufin/sandbox/DB.kt
@@ -34,6 +34,7 @@ import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.transactions.TransactionManager
import org.jetbrains.exposed.sql.transactions.transaction
import org.jetbrains.exposed.sql.transactions.transactionManager
+import tech.libeufin.sandbox.CashoutSubmissionsTable.nullable
import tech.libeufin.util.*
import java.sql.Connection
import kotlin.reflect.*
@@ -498,6 +499,7 @@ class BankAccountTransactionEntity(id: EntityID<Long>) : LongEntity(id) {
* own multiple bank accounts.
*/
object BankAccountsTable : IntIdTable() {
+ val balance = text("balance").default("0")
val iban = text("iban")
val bic = text("bic").default("SANDBOXX")
val label = text("label").uniqueIndex("accountLabelIndex")
@@ -538,6 +540,7 @@ object BankAccountsTable : IntIdTable() {
class BankAccountEntity(id: EntityID<Int>) : IntEntity(id) {
companion object : IntEntityClass<BankAccountEntity>(BankAccountsTable)
+ var balance by BankAccountsTable.balance
var iban by BankAccountsTable.iban
var bic by BankAccountsTable.bic
var label by BankAccountsTable.label
@@ -555,7 +558,7 @@ object BankAccountStatementsTable : IntIdTable() {
val xmlMessage = text("xmlMessage")
val bankAccount = reference("bankAccount", BankAccountsTable)
// Signed BigDecimal representing a Camt.053 CLBD field.
- val balanceClbd = text("balanceClbd")
+ val balanceClbd = text("balanceClbd").nullable()
}
class BankAccountStatementEntity(id: EntityID<Int>) : IntEntity(id) {
diff --git a/sandbox/src/main/kotlin/tech/libeufin/sandbox/EbicsProtocolBackend.kt b/sandbox/src/main/kotlin/tech/libeufin/sandbox/EbicsProtocolBackend.kt
index fc240963..658d6373 100644
--- a/sandbox/src/main/kotlin/tech/libeufin/sandbox/EbicsProtocolBackend.kt
+++ b/sandbox/src/main/kotlin/tech/libeufin/sandbox/EbicsProtocolBackend.kt
@@ -282,8 +282,6 @@ fun buildCamtString(
type: Int,
subscriberIban: String,
history: MutableList<XLibeufinBankTransaction>,
- balancePrcd: BigDecimal, // Balance up to freshHistory (excluded).
- balanceClbd: BigDecimal,
currency: String
): SandboxCamt {
/**
@@ -359,45 +357,6 @@ fun buildCamtString(
}
}
}
- element("Bal") {
- element("Tp/CdOrPrtry/Cd") {
- /* Balance type, in a coded format. PRCD stands
- for "Previously closed booked" and shows the
- balance at the time _before_ all the entries
- reported in this document were posted to the
- involved bank account. */
- text("PRCD")
- }
- element("Amt") {
- attribute("Ccy", currency)
- text(balancePrcd.abs().toPlainString())
- }
- element("CdtDbtInd") {
- text(getCreditDebitInd(balancePrcd))
- }
- element("Dt/Dt") {
- // date of this balance
- text(dashedDate)
- }
- }
- element("Bal") {
- element("Tp/CdOrPrtry/Cd") {
- /* CLBD stands for "Closing booked balance", and it
- is calculated by summing the PRCD with all the
- entries reported in this document */
- text("CLBD")
- }
- element("Amt") {
- attribute("Ccy", currency)
- text(balanceClbd.abs().toPlainString())
- }
- element("CdtDbtInd") {
- text(getCreditDebitInd(balanceClbd))
- }
- element("Dt/Dt") {
- text(dashedDate)
- }
- }
history.forEach {
this.element("Ntry") {
element("Amt") {
@@ -532,38 +491,10 @@ private fun constructCamtResponse(
}
}
if (history.size == 0) throw EbicsNoDownloadDataAvailable()
-
- /**
- * PRCD balance: balance mentioned in the last statement. This
- * will be normally zero, because statements need to be explicitly created.
- *
- * CLBD balance: PRCD + transactions accounted in the current C52.
- * Alternatively, that could be changed into: PRCD + all the pending
- * transactions. This way, the CLBD balance would closer reflect the
- * latest (pending) activities.
- */
- val prcdBalance = getBalance(bankAccount, withPending = false)
- val clbdBalance = run {
- var base = prcdBalance
- history.forEach { tx ->
- when (tx.direction) {
- XLibeufinBankDirection.DEBIT -> base -= parseDecimal(tx.amount)
- XLibeufinBankDirection.CREDIT -> base += parseDecimal(tx.amount)
- else -> {
- logger.error("Transaction with subject '${tx.subject}' is " +
- "inconsistent: neither DBIT nor CRDT")
- throw internalServerError("Transactions internal error.")
- }
- }
- }
- base
- }
val camtData = buildCamtString(
type,
bankAccount.iban,
history,
- balancePrcd = prcdBalance,
- balanceClbd = clbdBalance,
bankAccount.demoBank.config.currency
)
val paymentsList: String = if (logger.isDebugEnabled) {
diff --git a/sandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt b/sandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt
index ad9417f1..fab1fca6 100644
--- a/sandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt
+++ b/sandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt
@@ -264,14 +264,10 @@ class Camt053Tick : CliktCommand(
* Resorting the closing (CLBD) balance of the last statement; will
* become the PRCD balance of the _new_ one.
*/
- val lastBalance = getBalance(accountIter, withPending = false)
- val balanceClbd = getBalance(accountIter, withPending = true)
val camtData = buildCamtString(
53,
accountIter.iban,
newStatements[accountIter.label]!!,
- balanceClbd = balanceClbd,
- balancePrcd = lastBalance,
currency = accountIter.demoBank.config.currency
)
BankAccountStatementEntity.new {
@@ -279,7 +275,6 @@ class Camt053Tick : CliktCommand(
creationTime = getUTCnow().toInstant().epochSecond
xmlMessage = camtData.camtMessage
bankAccount = accountIter
- this.balanceClbd = balanceClbd.toPlainString()
}
}
BankAccountFreshTransactionsTable.deleteAll()
@@ -802,7 +797,7 @@ val sandboxApp: Application.() -> Unit = {
val bankAccount = getBankAccountFromLabel(label, demobank)
if (!allowOwnerOrAdmin(username, label))
throw unauthorized("'${username}' has no rights over '$label'")
- val balance = getBalance(bankAccount, withPending = true)
+ val balance = getBalance(bankAccount)
object {
val balance = "${bankAccount.demoBank.config.currency}:${balance}"
val iban = bankAccount.iban
@@ -1453,10 +1448,11 @@ val sandboxApp: Application.() -> Unit = {
val authGranted = !WITH_AUTH || bankAccount.isPublic || username == "admin"
if (!authGranted && bankAccount.owner != username)
throw forbidden("Customer '$username' cannot access bank account '$accountAccessed'")
- val balance = getBalance(bankAccount, withPending = true)
+ val balance = getBalance(bankAccount)
+ logger.debug("Balance of '$username': ${balance.toPlainString()}")
call.respond(object {
val balance = object {
- val amount = "${demobank.config.currency}:${balance.abs(). toPlainString()}"
+ val amount = "${demobank.config.currency}:${balance.abs().toPlainString()}"
val credit_debit_indicator = if (balance < BigDecimal.ZERO) "debit" else "credit"
}
val paytoUri = buildIbanPaytoUri(
@@ -1574,10 +1570,7 @@ val sandboxApp: Application.() -> Unit = {
BankAccountsTable.demoBank eq demobank.id
)
}.forEach {
- val balanceIter = getBalance(
- it,
- withPending = true,
- )
+ val balanceIter = getBalance(it)
ret.publicAccounts.add(
PublicAccountInfo(
balance = "${demobank.config.currency}:$balanceIter",
@@ -1632,7 +1625,7 @@ val sandboxApp: Application.() -> Unit = {
demobank = demobank.name,
isPublic = req.isPublic
)
- val balance = getBalance(newAccount.bankAccount, withPending = true)
+ val balance = getBalance(newAccount.bankAccount)
call.respond(object {
val balance = getBalanceForJson(balance, demobank.config.currency)
val paytoUri = buildIbanPaytoUri(
diff --git a/sandbox/src/main/kotlin/tech/libeufin/sandbox/bankAccount.kt b/sandbox/src/main/kotlin/tech/libeufin/sandbox/bankAccount.kt
index 748962d5..23c24dbf 100644
--- a/sandbox/src/main/kotlin/tech/libeufin/sandbox/bankAccount.kt
+++ b/sandbox/src/main/kotlin/tech/libeufin/sandbox/bankAccount.kt
@@ -1,13 +1,13 @@
package tech.libeufin.sandbox
import io.ktor.http.*
+import org.jetbrains.exposed.sql.StdOutSqlLogger
+import org.jetbrains.exposed.sql.addLogger
import org.jetbrains.exposed.sql.and
import org.jetbrains.exposed.sql.transactions.transaction
import tech.libeufin.util.*
import java.math.BigDecimal
-
-
/**
* Check whether the given bank account would surpass the
* debit threshold, in case the potential amount gets transferred.
@@ -21,7 +21,7 @@ fun maybeDebit(
"Demobank '${demobankName}' not found when trying to check the debit threshold" +
" for user $accountLabel"
)
- val balance = getBalance(accountLabel, demobankName, withPending = true)
+ val balance = getBalance(accountLabel, demobankName)
val maxDebt = if (accountLabel == "admin") {
demobank.config.bankDebtLimit
} else demobank.config.usersDebtLimit
@@ -52,16 +52,18 @@ fun getBalanceForJson(value: BigDecimal, currency: String): BalanceJson {
)
}
+fun getBalance(bankAccount: BankAccountEntity): BigDecimal {
+ return BigDecimal(bankAccount.balance)
+}
+
/**
- * The last balance is the one mentioned in the bank account's
- * last statement. If the bank account does not have any statement
- * yet, then zero is returned. When 'withPending' is true, it adds
- * the pending transactions to it.
- *
- * Note: because transactions are searched after the bank accounts
- * (numeric) id, the research in the database is not ambiguous.
+ * This function balances _in bank account statements_. A statement
+ * witnesses the bank account after a given business time slot. Therefore
+ * _this_ type of balance is not guaranteed to hold the _actual_ and
+ * more up-to-date bank account. It'll be used when Sandbox will support
+ * the issuing of bank statement.
*/
-fun getBalance(
+fun getBalanceForStatement(
bankAccount: BankAccountEntity,
withPending: Boolean = true
): BigDecimal {
@@ -104,8 +106,7 @@ fun getBalance(
// Gets the balance of 'accountLabel', which is hosted at 'demobankName'.
fun getBalance(accountLabel: String,
- demobankName: String = "default",
- withPending: Boolean = true
+ demobankName: String = "default"
): BigDecimal {
val demobank = getDemobank(demobankName) ?: throw SandboxError(
HttpStatusCode.InternalServerError,
@@ -124,7 +125,7 @@ fun getBalance(accountLabel: String,
demobank,
withBankFault = true
)
- return getBalance(account, withPending)
+ return getBalance(account)
}
/**
@@ -190,6 +191,7 @@ fun wireTransfer(
val timeStamp = getUTCnow().toInstant().toEpochMilli()
val transactionRef = getRandomString(8)
transaction {
+ // addLogger(StdOutSqlLogger)
BankAccountTransactionEntity.new {
creditorIban = creditAccount.iban
creditorBic = creditAccount.bic
@@ -224,6 +226,15 @@ fun wireTransfer(
this.demobank = demobank
this.pmtInfId = pmtInfId
}
+
+ // Adjusting the balances (acceptable debit conditions checked before).
+ // Debit:
+ val newDebitBalance = (BigDecimal(debitAccount.balance) - amountAsNumber).roundToTwoDigits()
+ debitAccount.balance = newDebitBalance.toPlainString() // FIXME: that's ignored!
+ // Credit:
+ val newCreditBalance = (BigDecimal(creditAccount.balance) + amountAsNumber).roundToTwoDigits()
+ creditAccount.balance = newCreditBalance.toPlainString()
+
// Signaling this wire transfer's event.
if (this.isPostgres()) {
val creditChannel = buildChannelName(
diff --git a/sandbox/src/test/kotlin/BalanceTest.kt b/sandbox/src/test/kotlin/BalanceTest.kt
index cf9f3918..aabf2051 100644
--- a/sandbox/src/test/kotlin/BalanceTest.kt
+++ b/sandbox/src/test/kotlin/BalanceTest.kt
@@ -4,6 +4,7 @@ import org.jetbrains.exposed.sql.transactions.transaction
import org.junit.Test
import tech.libeufin.sandbox.*
import tech.libeufin.util.millis
+import tech.libeufin.util.roundToTwoDigits
import java.math.BigDecimal
import java.time.LocalDateTime
@@ -28,7 +29,14 @@ class BalanceTest {
iban = "IBAN 1"
bic = "BIC"
label = "label 1"
- owner = "test"
+ owner = "admin"
+ this.demoBank = demobank
+ }
+ val other = BankAccountEntity.new {
+ iban = "IBAN 2"
+ bic = "BIC"
+ label = "label 2"
+ owner = "admin"
this.demoBank = demobank
}
BankAccountTransactionEntity.new {
@@ -82,7 +90,18 @@ class BalanceTest {
accountServicerReference = "test-account-servicer-reference"
this.demobank = demobank
}
- assert(BigDecimal.ONE == getBalance(one, withPending = true))
+ wireTransfer(
+ other, one, demobank, "one gets 1", "EUR:1"
+ )
+ wireTransfer(
+ other, one, demobank, "one gets another 1", "EUR:1"
+ )
+ wireTransfer(
+ one, other, demobank, "one gives 1", "EUR:1"
+ )
+ val maybeOneBalance: BigDecimal = getBalance(one)
+ println(maybeOneBalance)
+ assert(BigDecimal.ONE.roundToTwoDigits() == maybeOneBalance.roundToTwoDigits())
}
}
}
diff --git a/util/src/main/kotlin/amounts.kt b/util/src/main/kotlin/amounts.kt
index 043e9af3..671dfbd3 100644
--- a/util/src/main/kotlin/amounts.kt
+++ b/util/src/main/kotlin/amounts.kt
@@ -3,6 +3,7 @@ package tech.libeufin.util
import UtilError
import io.ktor.http.*
import java.math.BigDecimal
+import java.math.RoundingMode
/*
* This file is part of LibEuFin.
@@ -48,4 +49,10 @@ fun isAmountZero(a: BigDecimal): Boolean {
return false
}
return true
-} \ No newline at end of file
+}
+
+fun BigDecimal.roundToTwoDigits(): BigDecimal {
+ // val twoDigitsRounding = MathContext(2)
+ // return this.round(twoDigitsRounding)
+ return this.setScale(2, RoundingMode.HALF_UP)
+}
diff --git a/util/src/test/kotlin/AmountTest.kt b/util/src/test/kotlin/AmountTest.kt
index eb7d8493..636c9060 100644
--- a/util/src/test/kotlin/AmountTest.kt
+++ b/util/src/test/kotlin/AmountTest.kt
@@ -1,6 +1,5 @@
import io.ktor.util.reflect.*
import org.junit.Test
-import tech.libeufin.sandbox.roundToTwoDigits
import tech.libeufin.util.isAmountZero
import tech.libeufin.util.parseAmount
import tech.libeufin.util.validatePlainAmount