summaryrefslogtreecommitdiff
path: root/sandbox/src/main/kotlin/tech/libeufin/sandbox/bankAccount.kt
diff options
context:
space:
mode:
Diffstat (limited to 'sandbox/src/main/kotlin/tech/libeufin/sandbox/bankAccount.kt')
-rw-r--r--sandbox/src/main/kotlin/tech/libeufin/sandbox/bankAccount.kt276
1 files changed, 0 insertions, 276 deletions
diff --git a/sandbox/src/main/kotlin/tech/libeufin/sandbox/bankAccount.kt b/sandbox/src/main/kotlin/tech/libeufin/sandbox/bankAccount.kt
deleted file mode 100644
index d82a0eb4..00000000
--- a/sandbox/src/main/kotlin/tech/libeufin/sandbox/bankAccount.kt
+++ /dev/null
@@ -1,276 +0,0 @@
-package tech.libeufin.sandbox
-
-import io.ktor.http.*
-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.
- * Returns true when the debit WOULD be surpassed. */
-fun maybeDebit(
- accountLabel: String,
- requestedAmount: BigDecimal,
- demobankName: String = "default"
-): Boolean {
- val demobank = getDemobank(demobankName) ?: throw notFound(
- "Demobank '${demobankName}' not found when trying to check the debit threshold" +
- " for user $accountLabel"
- )
- val balance = getBalance(accountLabel, demobankName)
- val maxDebt = if (accountLabel == "admin") {
- 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" +
- " threshold of $maxDebt, given the requested amount of ${requestedAmount.toPlainString()}")
- return true
- }
- return false
-}
-
-fun getMaxDebitForUser(
- username: String,
- demobankName: String = "default"
-): Int {
- val bank = getDemobank(demobankName) ?: throw internalServerError(
- "demobank $demobankName not found"
- )
- if (username == "admin") return bank.config.bankDebtLimit
- return bank.config.usersDebtLimit
-}
-
-fun getBalanceForJson(value: BigDecimal, currency: String): BalanceJson {
- return BalanceJson(
- amount = "${currency}:${value.abs()}",
- credit_debit_indicator = if (value < BigDecimal.ZERO) "debit" else "credit"
- )
-}
-
-fun getBalance(bankAccount: BankAccountEntity): BigDecimal {
- return BigDecimal(bankAccount.balance)
-}
-
-/**
- * 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 getBalanceForStatement(
- bankAccount: BankAccountEntity,
- withPending: Boolean = true
-): BigDecimal {
- val lastStatement = transaction {
- BankAccountStatementEntity.find {
- BankAccountStatementsTable.bankAccount eq bankAccount.id
- }.lastOrNull()
- }
- var lastBalance = if (lastStatement == null) {
- BigDecimal.ZERO
- } else { BigDecimal(lastStatement.balanceClbd) }
- if (!withPending) return lastBalance
- /**
- * Caller asks to include the pending transactions in the
- * balance. The block below gets the transactions happened
- * later than the last statement and adds them to the balance
- * that was calculated so far.
- */
- transaction {
- val pendingTransactions = BankAccountTransactionEntity.find {
- BankAccountTransactionsTable.account eq bankAccount.id and (
- BankAccountTransactionsTable.date.greater(lastStatement?.creationTime ?: 0L))
- }
- pendingTransactions.forEach { tx ->
- when (tx.direction) {
- "DBIT" -> lastBalance -= parseDecimal(tx.amount)
- "CRDT" -> lastBalance += parseDecimal(tx.amount)
- else -> {
- logger.error("Transaction ${tx.id} is neither debit nor credit.")
- throw SandboxError(
- HttpStatusCode.InternalServerError,
- "Error in transactions state."
- )
- }
- }
- }
- }
- return lastBalance
-}
-
-// Gets the balance of 'accountLabel', which is hosted at 'demobankName'.
-fun getBalance(accountLabel: String,
- demobankName: String = "default"
-): BigDecimal {
- val demobank = getDemobank(demobankName) ?: throw SandboxError(
- HttpStatusCode.InternalServerError,
- "Demobank '$demobankName' not found"
- )
-
- /**
- * Setting withBankFault to true for the following reason:
- * when asking for a balance, the bank should have made sure
- * that the user has a bank account (together with a customer profile).
- * If that's not the case, it's bank's fault, since it didn't check
- * earlier.
- */
- val account = getBankAccountFromLabel(
- accountLabel,
- demobank,
- withBankFault = true
- )
- return getBalance(account)
-}
-
-/**
- * 'debitAccount' and 'creditAccount' are customer usernames
- * and ALSO labels of the bank accounts owned by them. They are
- * used to both resort a bank account and the legal name owning
- * the bank accounts.
- */
-fun wireTransfer(
- debitAccount: String,
- creditAccount: String,
- demobank: String = "default",
- subject: String,
- amount: String, // $currency:x.y
- pmtInfId: String? = null,
- endToEndId: String? = null
-): String {
- logger.debug("Maybe wire transfer (endToEndId: $endToEndId): $debitAccount -> $creditAccount, $subject, $amount")
- return transaction {
- val demobankDb = ensureDemobank(demobank)
- val debitAccountDb = getBankAccountFromLabel(debitAccount, demobankDb)
- val creditAccountDb = getBankAccountFromLabel(creditAccount, demobankDb)
- val parsedAmount = parseAmount(amount)
- // Potential amount to transfer.
- val amountAsNumber = BigDecimal(parsedAmount.amount)
- if (amountAsNumber == BigDecimal.ZERO)
- throw badRequest("Wire transfers of zero not possible.")
- if (parsedAmount.currency != demobankDb.config.currency)
- throw badRequest(
- "Won't wire transfer with currency: ${parsedAmount.currency}." +
- " Only ${demobankDb.config.currency} allowed."
- )
- // Check funds are sufficient.
- if (
- maybeDebit(
- debitAccountDb.label,
- amountAsNumber,
- demobankDb.name
- )) {
- logger.error("Account ${debitAccountDb.label} would surpass debit threshold. Rollback wire transfer")
- throw SandboxError(HttpStatusCode.Conflict, "Insufficient funds")
- }
- val timeStamp = getNowMillis()
- val transactionRef = getRandomString(8)
- BankAccountTransactionEntity.new {
- creditorIban = creditAccountDb.iban
- creditorBic = creditAccountDb.bic
- this.creditorName = getPersonNameFromCustomer(creditAccountDb.owner)
- debtorIban = debitAccountDb.iban
- debtorBic = debitAccountDb.bic
- debtorName = getPersonNameFromCustomer(debitAccountDb.owner)
- this.subject = subject
- this.amount = parsedAmount.amount
- this.currency = demobankDb.config.currency
- date = timeStamp
- accountServicerReference = transactionRef
- account = creditAccountDb
- direction = "CRDT"
- this.demobank = demobankDb
- this.pmtInfId = pmtInfId
- }
- BankAccountTransactionEntity.new {
- creditorIban = creditAccountDb.iban
- creditorBic = creditAccountDb.bic
- this.creditorName = getPersonNameFromCustomer(creditAccountDb.owner)
- debtorIban = debitAccountDb.iban
- debtorBic = debitAccountDb.bic
- debtorName = getPersonNameFromCustomer(debitAccountDb.owner)
- this.subject = subject
- this.amount = parsedAmount.amount
- this.currency = demobankDb.config.currency
- date = timeStamp
- accountServicerReference = transactionRef
- account = debitAccountDb
- direction = "DBIT"
- this.demobank = demobankDb
- this.pmtInfId = pmtInfId
- this.endToEndId = endToEndId
- }
-
- // Adjusting the balances (acceptable debit conditions checked before).
- // Debit:
- val newDebitBalance = (BigDecimal(debitAccountDb.balance) - amountAsNumber).roundToTwoDigits()
- debitAccountDb.balance = newDebitBalance.toPlainString()
- // Credit:
- val newCreditBalance = (BigDecimal(creditAccountDb.balance) + amountAsNumber).roundToTwoDigits()
- creditAccountDb.balance = newCreditBalance.toPlainString()
-
- // Signaling this wire transfer's event.
- if (this.isPostgres()) {
- val creditChannel = buildChannelName(
- NotificationsChannelDomains.LIBEUFIN_REGIO_TX,
- creditAccountDb.label
- )
- this.postgresNotify(creditChannel, "CRDT")
- val debitChannel = buildChannelName(
- NotificationsChannelDomains.LIBEUFIN_REGIO_TX,
- debitAccountDb.label
- )
- this.postgresNotify(debitChannel, "DBIT")
- }
- transactionRef
- }
-}
-
-/**
- * Helper that constructs a transactions history page
- * according to the URI parameters passed to Access API's
- * GET /transactions.
- */
-data class HistoryParams(
- val pageNumber: Int,
- val pageSize: Int,
- val fromMs: Long,
- val untilMs: Long,
- val bankAccount: BankAccountEntity
-)
-
-fun extractTxHistory(params: HistoryParams): List<XLibeufinBankTransaction> {
- val ret = mutableListOf<XLibeufinBankTransaction>()
-
- /**
- * Helper that gets transactions earlier than the 'firstElementId'
- * transaction AND that match the URI parameters.
- */
- fun getPage(firstElementId: Long): Iterable<BankAccountTransactionEntity> {
- return BankAccountTransactionEntity.find {
- (BankAccountTransactionsTable.id lessEq firstElementId) and
- (BankAccountTransactionsTable.account eq params.bankAccount.id) and
- (BankAccountTransactionsTable.date.between(params.fromMs, params.untilMs))
- }.sortedByDescending { it.id.value }.take(params.pageSize)
- }
- // Gets a pointer to the last transaction of this bank account.
- val lastTransaction: BankAccountTransactionEntity? = params.bankAccount.lastTransaction
- if (lastTransaction == null) return ret
- var nextPageIdUpperLimit: Long = lastTransaction.id.value
-
- // This loop fetches (and discards) pages until the desired one is found.
- for (i in 1..(params.pageNumber)) {
- val pageBuf = getPage(nextPageIdUpperLimit)
- logger.debug("pageBuf #$i follows. Request wants #${params.pageNumber}:")
- pageBuf.forEach { logger.debug("ID: ${it.id}, subject: ${it.subject}, amount: ${it.currency}:${it.amount}") }
- if (pageBuf.none()) return ret
- nextPageIdUpperLimit = pageBuf.last().id.value - 1
- if (i == params.pageNumber) pageBuf.forEach {
- ret.add(getHistoryElementFromTransactionRow(it))
- }
- }
- return ret
-} \ No newline at end of file