From 0e7ebe417cb41b9e9942616af74dbce6337a6d1a Mon Sep 17 00:00:00 2001 From: MS Date: Mon, 13 Mar 2023 10:13:34 +0100 Subject: Using #7515 constructs. --- .../kotlin/tech/libeufin/sandbox/CircuitApi.kt | 8 +- .../tech/libeufin/sandbox/EbicsProtocolBackend.kt | 6 +- .../src/main/kotlin/tech/libeufin/sandbox/Main.kt | 163 +++++++++++---------- .../kotlin/tech/libeufin/sandbox/bankAccount.kt | 19 +-- 4 files changed, 104 insertions(+), 92 deletions(-) diff --git a/sandbox/src/main/kotlin/tech/libeufin/sandbox/CircuitApi.kt b/sandbox/src/main/kotlin/tech/libeufin/sandbox/CircuitApi.kt index 2c325ccc..8ddbab47 100644 --- a/sandbox/src/main/kotlin/tech/libeufin/sandbox/CircuitApi.kt +++ b/sandbox/src/main/kotlin/tech/libeufin/sandbox/CircuitApi.kt @@ -371,9 +371,9 @@ fun circuitApi(circuitRoute: Route) { val amountCredit = parseAmount(req.amount_credit) // amount after rates, as expected by the client val demobank = ensureDemobank(call) // Currency check of the cash-out's circuit part. - if (amountDebit.currency != demobank.currency) + if (amountDebit.currency != demobank.config.currency) throw badRequest("'${req::amount_debit.name}' (${req.amount_debit})" + - " doesn't match the regional currency (${demobank.currency})" + " doesn't match the regional currency (${demobank.config.currency})" ) // Currency check of the cash-out's fiat part. if (amountCredit.currency != FIAT_CURRENCY) @@ -415,7 +415,7 @@ fun circuitApi(circuitRoute: Route) { // check that the balance is sufficient val balance = getBalance(user, withPending = true) val balanceCheck = balance - amountDebitAsNumber - if (balanceCheck < BigDecimal.ZERO && balanceCheck.abs() > BigDecimal(demobank.usersDebtLimit)) + if (balanceCheck < BigDecimal.ZERO && balanceCheck.abs() > BigDecimal(demobank.config.usersDebtLimit)) throw SandboxError( HttpStatusCode.PreconditionFailed, "Cash-out not possible due to insufficient funds. Balance ${balance.toPlainString()} would reach ${balanceCheck.toPlainString()}" @@ -567,7 +567,7 @@ fun circuitApi(circuitRoute: Route) { val name = it.name val balance = getBalanceForJson( getBalance(it.username), - getDefaultDemobank().currency + getDefaultDemobank().config.currency ) val debitThreshold = getMaxDebitForUser(it.username) }) diff --git a/sandbox/src/main/kotlin/tech/libeufin/sandbox/EbicsProtocolBackend.kt b/sandbox/src/main/kotlin/tech/libeufin/sandbox/EbicsProtocolBackend.kt index 86bb8f18..798a7761 100644 --- a/sandbox/src/main/kotlin/tech/libeufin/sandbox/EbicsProtocolBackend.kt +++ b/sandbox/src/main/kotlin/tech/libeufin/sandbox/EbicsProtocolBackend.kt @@ -300,7 +300,7 @@ fun buildCamtString( val zonedDateTime = camtCreationTime.toZonedString() val creationTimeMillis = camtCreationTime.toInstant().toEpochMilli() val messageId = "sandbox-${creationTimeMillis / 1000}-${getRandomString(10)}" - val currency = getDefaultDemobank().currency + val currency = getDefaultDemobank().config.currency val camtMessage = constructXml(indent = true) { root("Document") { @@ -741,7 +741,7 @@ private fun handleCct(paymentRequest: String, return@transaction } val bankAccount = getBankAccountFromIban(parseResult.debtorIban) - if (parseResult.currency != bankAccount.demoBank.currency) throw EbicsRequestError( + if (parseResult.currency != bankAccount.demoBank.config.currency) throw EbicsRequestError( "[EBICS_PROCESSING_ERROR] Currency (${parseResult.currency}) not supported.", "091116" ) @@ -1034,7 +1034,7 @@ private fun makePartnerInfo(subscriber: EbicsSubscriberEntity): EbicsTypes.Partn this.value = bankAccount.iban } ) - this.currency = bankAccount.demoBank.currency + this.currency = bankAccount.demoBank.config.currency this.description = "Ordinary Bank Account" this.bankCodeList = listOf( EbicsTypes.GeneralBankCode().apply { diff --git a/sandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt b/sandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt index 1f02728c..a266ccdd 100644 --- a/sandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt +++ b/sandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt @@ -61,6 +61,10 @@ import java.math.BigDecimal import java.net.URL import java.security.interfaces.RSAPublicKey import javax.xml.bind.JAXBContext +import kotlin.reflect.KProperty0 +import kotlin.reflect.KProperty1 +import kotlin.reflect.full.declaredMemberProperties +import kotlin.reflect.full.declaredMembers import kotlin.system.exitProcess val logger: Logger = LoggerFactory.getLogger("tech.libeufin.sandbox") @@ -81,11 +85,7 @@ data class SandboxErrorJson(val error: SandboxErrorDetailJson) data class SandboxErrorDetailJson(val type: String, val description: String) class DefaultExchange : CliktCommand("Set default Taler exchange for a demobank.") { - init { - context { - helpFormatter = CliktHelpFormatter(showDefaultValues = true) - } - } + init { context { helpFormatter = CliktHelpFormatter(showDefaultValues = true) } } private val exchangeBaseUrl by argument("EXCHANGE-BASEURL", "base URL of the default exchange") private val exchangePayto by argument("EXCHANGE-PAYTO", "default exchange's payto-address") private val demobank by option("--demobank", help = "Which demobank defaults to EXCHANGE").default("default") @@ -102,20 +102,40 @@ class DefaultExchange : CliktCommand("Set default Taler exchange for a demobank. System.err.println("Error, demobank $demobank not found.") exitProcess(1) } - maybeDemobank.suggestedExchangeBaseUrl = exchangeBaseUrl - maybeDemobank.suggestedExchangePayto = exchangePayto + val config = maybeDemobank.config + /** + * Iterating over the config object's field that hold the exchange + * base URL and Payto. The iteration is only used to retrieve the + * correct names of the DB column 'configKey', because this is named + * after such fields. + */ + listOf( + Pair(config::suggestedExchangeBaseUrl, exchangeBaseUrl), + Pair(config::suggestedExchangePayto, exchangePayto) + ).forEach { + val maybeConfigPair = DemobankConfigPairEntity.find { + DemobankConfigPairsTable.demobankName eq demobank and( + DemobankConfigPairsTable.configKey eq it.first.name) + }.firstOrNull() + /** + * The DB doesn't contain any column to hold the exchange URL + * or Payto, fail. That should never happen, because the DB row + * are created _after_ the DemobankConfig object that _does_ contain + * such fields. + */ + if (maybeConfigPair == null) { + System.err.println("Config key '${it.first.name}' for demobank '$demobank' not found in DB.") + exitProcess(1) + } + maybeConfigPair.configValue = it.second + } } } } } class Config : CliktCommand("Insert one configuration (a.k.a. demobank) into the database.") { - init { - context { - helpFormatter = CliktHelpFormatter(showDefaultValues = true) - } - } - + init { context { helpFormatter = CliktHelpFormatter(showDefaultValues = true) } } private val nameArgument by argument( "NAME", help = "Name of this configuration. Currently, only 'default' is admitted." ) @@ -147,40 +167,40 @@ class Config : CliktCommand("Insert one configuration (a.k.a. demobank) into the } execThrowableOrTerminate { dbCreateTables(dbConnString) - transaction { - val maybeDemobank = BankAccountEntity.find( - BankAccountsTable.label eq "admin" - ).firstOrNull() - if (showOption) { - if (maybeDemobank != null) { - val ret = ObjectMapper() - ret.configure(SerializationFeature.INDENT_OUTPUT, true) - println( - ret.writeValueAsString(object { - val currency = maybeDemobank.demoBank.currency - val bankDebtLimit = maybeDemobank.demoBank.bankDebtLimit - val usersDebtLimit = maybeDemobank.demoBank.usersDebtLimit - val allowRegistrations = maybeDemobank.demoBank.allowRegistrations - val name = maybeDemobank.demoBank.name // always 'default' - val withSignupBonus = maybeDemobank.demoBank.withSignupBonus - val captchaUrl = maybeDemobank.demoBank.captchaUrl - }) - ) - return@transaction - } - println("Nothing to show.") - return@transaction + val maybeDemobank = transaction { getDemobank(nameArgument) } + if (showOption) { + if (maybeDemobank != null) { + printConfig(maybeDemobank) + } else { + println("Demobank: $nameArgument not found.") + System.exit(1) } - if (maybeDemobank == null) { - val demoBank = DemobankConfigEntity.new { - currency = currencyOption - bankDebtLimit = bankDebtLimitOption - usersDebtLimit = usersDebtLimitOption - allowRegistrations = allowRegistrationsOption - name = nameArgument - this.withSignupBonus = withSignupBonusOption - captchaUrl = captchaUrlOption - } + return@execThrowableOrTerminate + } + if (bankDebtLimitOption < 0 || usersDebtLimitOption < 0) { + System.err.println("Debt numbers can't be negative.") + exitProcess(1) + } + // The user asks to _set_ values, regardless of overriding or creating. + val config = DemobankConfig( + currency = currencyOption, + bankDebtLimit = bankDebtLimitOption, + usersDebtLimit = usersDebtLimitOption, + allowRegistrations = allowRegistrationsOption, + demobankName = nameArgument, + withSignupBonus = withSignupBonusOption, + captchaUrl = captchaUrlOption + ) + /** + * The demobank didn't exist. Now: + * 1, Store the config values in the database. + * 2, Store the demobank name in the database. + * 3, Create the admin bank account under this demobank. + */ + if (maybeDemobank == null) { + transaction { + insertConfigPairs(config) + val demoBank = DemobankConfigEntity.new { this.name = nameArgument } BankAccountEntity.new { iban = getIban() label = "admin" @@ -188,16 +208,10 @@ class Config : CliktCommand("Insert one configuration (a.k.a. demobank) into the // For now, the model assumes always one demobank this.demoBank = demoBank } - return@transaction } - maybeDemobank.demoBank.currency = currencyOption - maybeDemobank.demoBank.bankDebtLimit = bankDebtLimitOption - maybeDemobank.demoBank.usersDebtLimit = usersDebtLimitOption - maybeDemobank.demoBank.allowRegistrations = allowRegistrationsOption - maybeDemobank.demoBank.withSignupBonus = withSignupBonusOption - maybeDemobank.demoBank.name = nameArgument - maybeDemobank.demoBank.captchaUrl = captchaUrlOption } + // Demobank exists: update its config values in the database. + else transaction { insertConfigPairs(config, override = true) } } } } @@ -391,10 +405,10 @@ class Serve : CliktCommand("Run sandbox HTTP server") { private fun getJsonFromDemobankConfig(fromDb: DemobankConfigEntity): Demobank { return Demobank( - currency = fromDb.currency, - userDebtLimit = fromDb.usersDebtLimit, - bankDebtLimit = fromDb.bankDebtLimit, - allowRegistrations = fromDb.allowRegistrations, + currency = fromDb.config.currency, + userDebtLimit = fromDb.config.usersDebtLimit, + bankDebtLimit = fromDb.config.bankDebtLimit, + allowRegistrations = fromDb.config.allowRegistrations, name = fromDb.name ) } @@ -710,7 +724,7 @@ val sandboxApp: Application.() -> Unit = { throw unauthorized("'${username}' has no rights over '$label'") val balance = getBalance(bankAccount, withPending = true) object { - val balance = "${bankAccount.demoBank.currency}:${balance}" + val balance = "${bankAccount.demoBank.config.currency}:${balance}" val iban = bankAccount.iban val bic = bankAccount.bic val label = bankAccount.label @@ -755,7 +769,7 @@ val sandboxApp: Application.() -> Unit = { this.account = account direction = "CRDT" this.demobank = demobank - this.currency = demobank.currency + this.currency = demobank.config.currency } } call.respond(object {}) @@ -901,7 +915,7 @@ val sandboxApp: Application.() -> Unit = { this.account = account direction = "CRDT" this.demobank = demobank - currency = demobank.currency + currency = demobank.config.currency } } @@ -922,7 +936,7 @@ val sandboxApp: Application.() -> Unit = { this.account = account direction = "DBIT" this.demobank = demobank - currency = demobank.currency + currency = demobank.config.currency } } } @@ -939,7 +953,7 @@ val sandboxApp: Application.() -> Unit = { val body = call.receive() transaction { // Check the host ID exists. - val maybeHostId = EbicsHostEntity.find { + EbicsHostEntity.find { EbicsHostsTable.hostID eq body.hostID }.firstOrNull() ?: throw notFound("Host ID ${body.hostID} not found.") // Check it exists first. @@ -1086,6 +1100,7 @@ val sandboxApp: Application.() -> Unit = { throw e } catch (e: Exception) { + logger.error(e.stackTraceToString()) throw EbicsProcessingError("Could not map error to EBICS code: $e") } return@post @@ -1168,7 +1183,7 @@ val sandboxApp: Application.() -> Unit = { call.respond(SandboxConfig( name = "taler-bank-integration", version = SANDBOX_VERSION, - currency = demobank.currency + currency = demobank.config.currency )) return@get } @@ -1223,13 +1238,13 @@ val sandboxApp: Application.() -> Unit = { ) } val demobank = ensureDemobank(call) - var captcha_page = demobank.captchaUrl + var captcha_page = demobank.config.captchaUrl if (captcha_page == null) logger.warn("CAPTCHA URL not found") val ret = TalerWithdrawalStatus( selection_done = maybeWithdrawalOp.selectionDone, transfer_done = maybeWithdrawalOp.confirmationDone, amount = maybeWithdrawalOp.amount, - suggested_exchange = demobank.suggestedExchangeBaseUrl, + suggested_exchange = demobank.config.suggestedExchangeBaseUrl, aborted = maybeWithdrawalOp.aborted, confirm_transfer_url = captcha_page ) @@ -1305,8 +1320,8 @@ val sandboxApp: Application.() -> Unit = { val req = call.receive() // Check for currency consistency val amount = parseAmount(req.amount) - if (amount.currency != demobank.currency) - throw badRequest("Currency ${amount.currency} differs from Demobank's: ${demobank.currency}") + if (amount.currency != demobank.config.currency) + throw badRequest("Currency ${amount.currency} differs from Demobank's: ${demobank.config.currency}") // Check funds are sufficient. if (maybeDebit(maybeOwnedAccount.label, BigDecimal(amount.amount))) { logger.error("Account ${maybeOwnedAccount.label} would surpass debit threshold. Not withdrawing") @@ -1373,7 +1388,7 @@ val sandboxApp: Application.() -> Unit = { */ val demobank = ensureDemobank(call) if (wo.selectedExchangePayto == null) { - wo.selectedExchangePayto = demobank.suggestedExchangePayto + wo.selectedExchangePayto = demobank.config.suggestedExchangePayto } val exchangeBankAccount = getBankAccountFromPayto( wo.selectedExchangePayto ?: throw internalServerError( @@ -1422,7 +1437,7 @@ val sandboxApp: Application.() -> Unit = { val balance = getBalance(bankAccount, withPending = true) call.respond(object { val balance = object { - val amount = "${demobank.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( @@ -1524,7 +1539,7 @@ val sandboxApp: Application.() -> Unit = { val balanceIter = getBalance(it, withPending = true) ret.publicAccounts.add( PublicAccountInfo( - balance = "${demobank.currency}:$balanceIter", + balance = "${demobank.config.currency}:$balanceIter", iban = it.iban, accountLabel = it.label ) @@ -1550,7 +1565,7 @@ val sandboxApp: Application.() -> Unit = { post("/testing/register") { // Check demobank was created. val demobank = ensureDemobank(call) - if (!demobank.allowRegistrations) { + if (!demobank.config.allowRegistrations) { throw SandboxError( HttpStatusCode.UnprocessableEntity, "The bank doesn't allow new registrations at the moment." @@ -1566,7 +1581,7 @@ val sandboxApp: Application.() -> Unit = { ) val balance = getBalance(newAccount.bankAccount, withPending = true) call.respond(object { - val balance = getBalanceForJson(balance, demobank.currency) + val balance = getBalanceForJson(balance, demobank.config.currency) val paytoUri = buildIbanPaytoUri( iban = newAccount.bankAccount.iban, bic = newAccount.bankAccount.bic, @@ -1618,4 +1633,4 @@ val sandboxApp: Application.() -> Unit = { } } } -} \ No newline at end of file +} diff --git a/sandbox/src/main/kotlin/tech/libeufin/sandbox/bankAccount.kt b/sandbox/src/main/kotlin/tech/libeufin/sandbox/bankAccount.kt index 5bd45a6f..0e7e64f6 100644 --- a/sandbox/src/main/kotlin/tech/libeufin/sandbox/bankAccount.kt +++ b/sandbox/src/main/kotlin/tech/libeufin/sandbox/bankAccount.kt @@ -21,8 +21,8 @@ fun maybeDebit( ) val balance = getBalance(accountLabel, withPending = true) val maxDebt = if (accountLabel == "admin") { - demobank.bankDebtLimit - } else demobank.usersDebtLimit + demobank.config.bankDebtLimit + } else demobank.config.usersDebtLimit val balanceCheck = balance - requestedAmount if (balanceCheck < BigDecimal.ZERO && balanceCheck.abs() > BigDecimal.valueOf(maxDebt.toLong())) { logger.warn("User '$accountLabel' would surpass the debit" + @@ -34,10 +34,8 @@ fun maybeDebit( fun getMaxDebitForUser(username: String): Int { val bank = getDefaultDemobank() - if (username == "admin") return bank.bankDebtLimit - return bank.usersDebtLimit - - + if (username == "admin") return bank.config.bankDebtLimit + return bank.config.usersDebtLimit } fun getBalanceForJson(value: BigDecimal, currency: String): BalanceJson { @@ -45,7 +43,6 @@ fun getBalanceForJson(value: BigDecimal, currency: String): BalanceJson { amount = "${currency}:${value.abs()}", credit_debit_indicator = if (value < BigDecimal.ZERO) "DBIT" else "CRDT" ) - } /** @@ -147,10 +144,10 @@ fun wireTransfer( val amountAsNumber = BigDecimal(parsedAmount.amount) if (amountAsNumber == BigDecimal.ZERO) throw badRequest("Wire transfers of zero not possible.") - if (parsedAmount.currency != demobank.currency) + if (parsedAmount.currency != demobank.config.currency) throw badRequest( "Won't wire transfer with currency: ${parsedAmount.currency}." + - " Only ${demobank.currency} allowed." + " Only ${demobank.config.currency} allowed." ) // Check funds are sufficient. if (maybeDebit(debitAccount.label, amountAsNumber)) { @@ -169,7 +166,7 @@ fun wireTransfer( debtorName = getPersonNameFromCustomer(debitAccount.owner) this.subject = subject this.amount = parsedAmount.amount - this.currency = demobank.currency + this.currency = demobank.config.currency date = timeStamp accountServicerReference = transactionRef account = creditAccount @@ -186,7 +183,7 @@ fun wireTransfer( debtorName = getPersonNameFromCustomer(debitAccount.owner) this.subject = subject this.amount = parsedAmount.amount - this.currency = demobank.currency + this.currency = demobank.config.currency date = timeStamp accountServicerReference = transactionRef account = debitAccount -- cgit v1.2.3