commit bd7c4f46f76ea2414b965d163f0e4910c0e27113
parent 059c62f86ac618ee2231141926d8e920b9de0e5f
Author: Antoine A <>
Date: Tue, 17 Oct 2023 16:17:02 +0000
Move all amount computation logic into the database
Diffstat:
9 files changed, 219 insertions(+), 175 deletions(-)
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/Authentication.kt b/bank/src/main/kotlin/tech/libeufin/bank/Authentication.kt
@@ -9,7 +9,6 @@ import java.time.Instant
/** Authenticate admin */
suspend fun ApplicationCall.authAdmin(db: Database, scope: TokenScope) {
- // TODO when all endpoints use this function we can use an optimized database request that only query the customer login
val login = authenticateBankRequest(db, scope) ?: throw unauthorized("Bad login")
if (login != "admin") {
throw unauthorized("Only administrator allowed")
@@ -19,7 +18,6 @@ suspend fun ApplicationCall.authAdmin(db: Database, scope: TokenScope) {
/** Authenticate and check access rights */
suspend fun ApplicationCall.authCheck(db: Database, scope: TokenScope, withAdmin: Boolean = false, requireAdmin: Boolean = false): Pair<String, Boolean> {
- // TODO when all endpoints use this function we can use an optimized database request that only query the customer login
val authLogin = authenticateBankRequest(db, scope) ?: throw unauthorized("Bad login")
val login = accountLogin()
if (requireAdmin && authLogin != "admin") {
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/CoreBankApi.kt b/bank/src/main/kotlin/tech/libeufin/bank/CoreBankApi.kt
@@ -101,32 +101,35 @@ fun Routing.accountsMgmtApi(db: Database, ctx: BankApplicationContext) {
}
// WITHDRAWAL ENDPOINTS
post("/accounts/{USERNAME}/withdrawals") {
- call.authCheck(db, TokenScope.readwrite)
+ val (login, _) = call.authCheck(db, TokenScope.readwrite)
val req = call.receive<BankAccountCreateWithdrawalRequest>() // Checking that the user has enough funds.
+
if (req.amount.currency != ctx.currency)
throw badRequest("Wrong currency: ${req.amount.currency}")
- val b = call.bankAccount(db)
- // TODO balance check only in database
- if (!isBalanceEnough(
- balance = b.expectBalance(), due = req.amount, maxDebt = b.maxDebt, hasBalanceDebt = b.hasDebt
- )
- ) throw forbidden(
- hint = "Insufficient funds to withdraw with Taler",
- talerErrorCode = TalerErrorCode.TALER_EC_BANK_UNALLOWED_DEBIT
- ) // Auth and funds passed, create the operation now!
val opId = UUID.randomUUID()
- if (!db.talerWithdrawalCreate(
- opId, b.expectRowId(), req.amount
+ when (db.talerWithdrawalCreate(login, opId, req.amount)) {
+ WithdrawalCreationResult.ACCOUNT_NOT_FOUND -> throw notFound(
+ "Customer $login not found",
+ TalerErrorCode.TALER_EC_BANK_UNKNOWN_ACCOUNT
)
- ) throw internalServerError("Bank failed at creating the withdraw operation.")
-
- val bankBaseUrl = call.request.getBaseUrl() ?: throw internalServerError("Bank could not find its own base URL")
- call.respond(
- BankAccountCreateWithdrawalResponse(
- withdrawal_id = opId.toString(), taler_withdraw_uri = getTalerWithdrawUri(bankBaseUrl, opId.toString())
+ WithdrawalCreationResult.ACCOUNT_IS_EXCHANGE -> throw conflict(
+ "Exchange account cannot perform withdrawal operation",
+ TalerErrorCode.TALER_EC_BANK_SAME_ACCOUNT
)
- )
+ WithdrawalCreationResult.BALANCE_INSUFFICIENT -> throw conflict(
+ "Insufficient funds to withdraw with Taler",
+ TalerErrorCode.TALER_EC_BANK_UNALLOWED_DEBIT
+ )
+ WithdrawalCreationResult.SUCCESS -> {
+ val bankBaseUrl = call.request.getBaseUrl() ?: throw internalServerError("Bank could not find its own base URL")
+ call.respond(
+ BankAccountCreateWithdrawalResponse(
+ withdrawal_id = opId.toString(), taler_withdraw_uri = getTalerWithdrawUri(bankBaseUrl, opId.toString())
+ )
+ )
+ }
+ }
}
get("/withdrawals/{withdrawal_id}") {
val op = getWithdrawal(db, call.expectUriComponent("withdrawal_id"))
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/Database.kt b/bank/src/main/kotlin/tech/libeufin/bank/Database.kt
@@ -192,7 +192,7 @@ private fun PreparedStatement.executeUpdateViolation(): Boolean {
}
class Database(dbConfig: String, private val bankCurrency: String): java.io.Closeable {
- private val dbPool: HikariDataSource
+ val dbPool: HikariDataSource
private val notifWatcher: NotificationWatcher
init {
@@ -1091,21 +1091,31 @@ class Database(dbConfig: String, private val bankCurrency: String): java.io.Clos
// WITHDRAWALS
suspend fun talerWithdrawalCreate(
+ walletAccountUsername: String,
opUUID: UUID,
- walletBankAccount: Long,
amount: TalerAmount
- ): Boolean = conn { conn ->
+ ): WithdrawalCreationResult = conn { conn ->
val stmt = conn.prepareStatement("""
- INSERT INTO
- taler_withdrawal_operations
- (withdrawal_uuid, wallet_bank_account, amount)
- VALUES (?,?,(?,?)::taler_amount)
- """) // Take all defaults from the SQL.
- stmt.setObject(1, opUUID)
- stmt.setLong(2, walletBankAccount)
+ SELECT
+ out_account_not_found,
+ out_account_is_exchange,
+ out_balance_insufficient
+ FROM create_taler_withdrawal(?, ?, (?,?)::taler_amount);
+ """)
+ stmt.setString(1, walletAccountUsername)
+ stmt.setObject(2, opUUID)
stmt.setLong(3, amount.value)
stmt.setInt(4, amount.frac)
- stmt.executeUpdateViolation()
+ stmt.executeQuery().use {
+ when {
+ !it.next() ->
+ throw internalServerError("No result from DB procedure create_taler_withdrawal")
+ it.getBoolean("out_account_not_found") -> WithdrawalCreationResult.ACCOUNT_NOT_FOUND
+ it.getBoolean("out_account_is_exchange") -> WithdrawalCreationResult.ACCOUNT_IS_EXCHANGE
+ it.getBoolean("out_balance_insufficient") -> WithdrawalCreationResult.BALANCE_INSUFFICIENT
+ else -> WithdrawalCreationResult.SUCCESS
+ }
+ }
}
suspend fun talerWithdrawalGet(opUUID: UUID): TalerWithdrawalOperation? = conn { conn ->
val stmt = conn.prepareStatement("""
@@ -1616,6 +1626,14 @@ enum class TalerAddIncomingResult {
SUCCESS
}
+/** Result status of withdrawal operation creation */
+enum class WithdrawalCreationResult {
+ SUCCESS,
+ ACCOUNT_NOT_FOUND,
+ ACCOUNT_IS_EXCHANGE,
+ BALANCE_INSUFFICIENT
+}
+
/**
* This type communicates the result of a database operation
* to confirm one withdrawal operation.
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/TalerCommon.kt b/bank/src/main/kotlin/tech/libeufin/bank/TalerCommon.kt
@@ -253,34 +253,6 @@ class TalerAmount {
}
}
- fun normalize(): TalerAmount {
- if (frac > FRACTION_BASE) {
- val overflow = frac / FRACTION_BASE
- val normalFrac = frac % FRACTION_BASE
- val normalValue = value + overflow
- if (normalValue < overflow || normalValue > MAX_SAFE_INTEGER)
- throw badRequest("Amount value overflowed")
- return TalerAmount(
- value = normalValue, frac = normalFrac, currency = currency
- )
- }
- return this
- }
-
- operator fun plus(other: TalerAmount): TalerAmount {
- if (currency != other.currency) throw badRequest(
- "Currency mismatch, balance '$currency', price '${other.currency}'",
- TalerErrorCode.TALER_EC_GENERIC_CURRENCY_MISMATCH
- )
- val valueAdd = value + other.value
- if (valueAdd < value || valueAdd > MAX_SAFE_INTEGER) throw badRequest("Amount value overflowed")
- val fracAdd = frac + other.frac
- if (fracAdd < frac) throw badRequest("Amount fraction overflowed")
- return TalerAmount(
- value = valueAdd, frac = fracAdd, currency = currency
- ).normalize()
- }
-
override fun equals(other: Any?): Boolean {
return other is TalerAmount &&
other.value == this.value &&
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/helpers.kt b/bank/src/main/kotlin/tech/libeufin/bank/helpers.kt
@@ -44,15 +44,6 @@ fun ApplicationCall.expectUriComponent(componentName: String) =
typealias ResourceName = String
/**
- * Checks if the input Customer has the rights over ResourceName.
- */
-fun ResourceName.canI(c: Customer, withAdmin: Boolean = true): Boolean {
- if (c.login == this) return true
- if (c.login == "admin" && withAdmin) return true
- return false
-}
-
-/**
* Factors out the retrieval of the resource name from
* the URI. The resource looked for defaults to "USERNAME"
* as this is frequently mentioned resource along the endpoints.
@@ -127,42 +118,6 @@ fun badRequest(
fun genIbanPaytoUri(): String = "payto://iban/SANDBOXX/${getIban()}"
/**
- * Checks whether the balance could cover the due amount. Returns true
- * when it does, false otherwise. Note: this function is only a checker,
- * meaning that no actual business state should change after it runs.
- * The place where business states change is in the SQL that's loaded in
- * the database.
- */
-fun isBalanceEnough(
- balance: TalerAmount, due: TalerAmount, maxDebt: TalerAmount, hasBalanceDebt: Boolean
-): Boolean {
- val normalMaxDebt = maxDebt.normalize() // Very unlikely to be needed.
- if (hasBalanceDebt) {
- val chargedBalance = balance + due
- if (chargedBalance.value > normalMaxDebt.value) return false // max debt surpassed
- if ((chargedBalance.value == normalMaxDebt.value) && (chargedBalance.frac > maxDebt.frac)) return false
- return true
- }
- /**
- * Balance doesn't have debt, but it MIGHT get one. The following
- * block calculates how much debt the balance would get, should a
- * subtraction of 'due' occur.
- */
- if (balance.currency != due.currency) throw badRequest(
- "Currency mismatch, balance '${balance.currency}', due '${due.currency}'",
- TalerErrorCode.TALER_EC_GENERIC_CURRENCY_MISMATCH
- )
- val valueDiff = if (balance.value < due.value) due.value - balance.value else 0L
- val fracDiff = if (balance.frac < due.frac) due.frac - balance.frac else 0
- // Getting the normalized version of such diff.
- val normalDiff = TalerAmount(valueDiff, fracDiff, balance.currency).normalize()
- // Failing if the normalized diff surpasses the max debt.
- if (normalDiff.value > normalMaxDebt.value) return false
- if ((normalDiff.value == normalMaxDebt.value) && (normalDiff.frac > normalMaxDebt.frac)) return false
- return true
-}
-
-/**
* Builds the taler://withdraw-URI. Such URI will serve the requests
* from wallets, when they need to manage the operation. For example,
* a URI like taler://withdraw/$BANK_URL/taler-integration/$WO_ID needs
diff --git a/bank/src/test/kotlin/AmountTest.kt b/bank/src/test/kotlin/AmountTest.kt
@@ -19,49 +19,103 @@
import org.junit.Test
+import org.postgresql.jdbc.PgConnection
import tech.libeufin.bank.*
-import kotlin.test.assertEquals
+import kotlin.test.*
+import java.time.Instant
+import java.util.*
class AmountTest {
+
+ // Test amount computation in database
@Test
- fun amountAdditionTest() {
+ fun computationTest() = bankSetup { db ->
+ val conn = db.dbPool.getConnection().unwrap(PgConnection::class.java)
+ conn.execSQLUpdate("UPDATE libeufin_bank.bank_accounts SET balance.val = 100000 WHERE internal_payto_uri = '${IbanPayTo("payto://iban/EXCHANGE-IBAN-XYZ").canonical}'")
+ val stmt = conn.prepareStatement("""
+ UPDATE libeufin_bank.bank_accounts
+ SET balance = (?, ?)::taler_amount
+ ,has_debt = ?
+ ,max_debt = (?, ?)::taler_amount
+ WHERE internal_payto_uri = '${IbanPayTo("payto://iban/MERCHANT-IBAN-XYZ").canonical}'
+ """)
+ suspend fun routine(balance: TalerAmount, due: TalerAmount, hasBalanceDebt: Boolean, maxDebt: TalerAmount): Boolean {
+ stmt.setLong(1, balance.value)
+ stmt.setInt(2, balance.frac)
+ stmt.setBoolean(3, hasBalanceDebt)
+ stmt.setLong(4, maxDebt.value)
+ stmt.setInt(5, maxDebt.frac)
+
+ // Check bank transaction
+ stmt.executeUpdate()
+ val txRes = db.bankTransaction(
+ creditAccountPayto = IbanPayTo("payto://iban/EXCHANGE-IBAN-XYZ"),
+ debitAccountUsername = "merchant",
+ subject = "test",
+ amount = due,
+ timestamp = Instant.now(),
+ )
+ val txBool = when (txRes) {
+ BankTransactionResult.BALANCE_INSUFFICIENT -> false
+ BankTransactionResult.SUCCESS -> true
+ else -> throw Exception("Unexpected error $txRes")
+ }
+
+ // Check whithdraw
+ stmt.executeUpdate()
+ val wRes = db.talerWithdrawalCreate(
+ walletAccountUsername = "merchant",
+ opUUID = UUID.randomUUID(),
+ amount = due,
+ )
+ val wBool = when (wRes) {
+ WithdrawalCreationResult.BALANCE_INSUFFICIENT -> false
+ WithdrawalCreationResult.SUCCESS -> true
+ else -> throw Exception("Unexpected error $txRes")
+ }
+
+ // Logic must be the same
+ assertEquals(wBool, txBool)
+ return txBool
+ }
+
// Balance enough, assert for true
- assert(isBalanceEnough(
+ assert(routine(
balance = TalerAmount(10, 0, "KUDOS"),
due = TalerAmount(8, 0, "KUDOS"),
hasBalanceDebt = false,
maxDebt = TalerAmount(100, 0, "KUDOS")
))
// Balance still sufficient, thanks for big enough debt permission. Assert true.
- assert(isBalanceEnough(
+ assert(routine(
balance = TalerAmount(10, 0, "KUDOS"),
due = TalerAmount(80, 0, "KUDOS"),
hasBalanceDebt = false,
maxDebt = TalerAmount(100, 0, "KUDOS")
))
// Balance not enough, max debt cannot cover, asserting for false.
- assert(!isBalanceEnough(
+ assert(!routine(
balance = TalerAmount(10, 0, "KUDOS"),
due = TalerAmount(80, 0, "KUDOS"),
hasBalanceDebt = true,
maxDebt = TalerAmount(50, 0, "KUDOS")
))
// Balance becomes enough, due to a larger max debt, asserting for true.
- assert(isBalanceEnough(
+ assert(routine(
balance = TalerAmount(10, 0, "KUDOS"),
due = TalerAmount(80, 0, "KUDOS"),
hasBalanceDebt = false,
maxDebt = TalerAmount(70, 0, "KUDOS")
))
// Max debt not enough for the smallest fraction, asserting for false
- assert(!isBalanceEnough(
+ assert(!routine(
balance = TalerAmount(0, 0, "KUDOS"),
due = TalerAmount(0, 2, "KUDOS"),
hasBalanceDebt = false,
maxDebt = TalerAmount(0, 1, "KUDOS")
))
// Same as above, but already in debt.
- assert(!isBalanceEnough(
+ assert(!routine(
balance = TalerAmount(0, 1, "KUDOS"),
due = TalerAmount(0, 1, "KUDOS"),
hasBalanceDebt = true,
@@ -97,21 +151,4 @@ class AmountTest {
assertEquals(amount, TalerAmount(amount).toString())
}
}
-
- @Test
- fun normalize() {
- assertEquals(TalerAmount("EUR:6"), TalerAmount(4L, 2 * TalerAmount.FRACTION_BASE, "EUR").normalize())
- assertEquals(TalerAmount("EUR:6.00000001"), TalerAmount(4L, 2 * TalerAmount.FRACTION_BASE + 1, "EUR").normalize())
- assertException("Amount value overflowed") { TalerAmount(Long.MAX_VALUE, 2 * TalerAmount.FRACTION_BASE + 1, "EUR").normalize() }
- assertException("Amount value overflowed") { TalerAmount(MAX_SAFE_INTEGER, 2 * TalerAmount.FRACTION_BASE + 1, "EUR").normalize() }
- }
-
- @Test
- fun add() {
- assertEquals(TalerAmount("EUR:6.41") + TalerAmount("EUR:4.69"), TalerAmount("EUR:11.1"))
- assertException("Amount value overflowed") { TalerAmount(MAX_SAFE_INTEGER - 5, 0, "EUR") + TalerAmount(6, 0, "EUR") }
- assertException("Amount value overflowed") { TalerAmount(Long.MAX_VALUE, 0, "EUR") + TalerAmount(1, 0, "EUR") }
- assertException("Amount value overflowed") { TalerAmount(MAX_SAFE_INTEGER - 5, TalerAmount.FRACTION_BASE - 1, "EUR") + TalerAmount(5, 2, "EUR") }
- assertException("Amount fraction overflowed") { TalerAmount(0, Int.MAX_VALUE, "EUR") + TalerAmount(0, 1, "EUR") }
- }
}
\ No newline at end of file
diff --git a/bank/src/test/kotlin/BankIntegrationApiTest.kt b/bank/src/test/kotlin/BankIntegrationApiTest.kt
@@ -13,8 +13,7 @@ import tech.libeufin.util.CryptoUtil
import tech.libeufin.util.stripIbanPayto
import java.util.*
import java.time.Instant
-import kotlin.test.assertEquals
-import kotlin.test.assertNotNull
+import kotlin.test.*
import randHashCode
class BankIntegrationApiTest {
@@ -23,9 +22,9 @@ class BankIntegrationApiTest {
fun intSelect() = bankSetup { db ->
val uuid = UUID.randomUUID()
// insert new.
- assert(db.talerWithdrawalCreate(
+ assertEquals(WithdrawalCreationResult.SUCCESS, db.talerWithdrawalCreate(
opUUID = uuid,
- walletBankAccount = 1L,
+ walletAccountUsername = "merchant",
amount = TalerAmount(1, 0, "KUDOS")
))
@@ -43,9 +42,9 @@ class BankIntegrationApiTest {
fun intGet() = bankSetup { db ->
val uuid = UUID.randomUUID()
// insert new.
- assert(db.talerWithdrawalCreate(
+ assertEquals(WithdrawalCreationResult.SUCCESS, db.talerWithdrawalCreate(
opUUID = uuid,
- walletBankAccount = 1L,
+ walletAccountUsername = "merchant",
amount = TalerAmount(1, 0, "KUDOS")
))
@@ -58,9 +57,9 @@ class BankIntegrationApiTest {
fun withdrawalAbort() = bankSetup { db ->
val uuid = UUID.randomUUID()
// insert new.
- assert(db.talerWithdrawalCreate(
+ assertEquals(WithdrawalCreationResult.SUCCESS, db.talerWithdrawalCreate(
opUUID = uuid,
- walletBankAccount = 1L,
+ walletAccountUsername = "merchant",
amount = TalerAmount(1, 0, "KUDOS")
))
val op = db.talerWithdrawalGet(uuid)
@@ -95,9 +94,9 @@ class BankIntegrationApiTest {
fun withdrawalConfirmation() = bankSetup { db ->
// Artificially making a withdrawal operation for merchant.
val uuid = UUID.randomUUID()
- assert(db.talerWithdrawalCreate(
+ assertEquals(WithdrawalCreationResult.SUCCESS, db.talerWithdrawalCreate(
opUUID = uuid,
- walletBankAccount = 1L,
+ walletAccountUsername = "merchant",
amount = TalerAmount(1, 0, "KUDOS")
))
// Specifying the exchange via its Payto URI.
diff --git a/bank/src/test/kotlin/DatabaseTest.kt b/bank/src/test/kotlin/DatabaseTest.kt
@@ -26,10 +26,7 @@ import java.time.Instant
import java.util.Random
import java.util.UUID
import kotlin.experimental.inv
-import kotlin.test.assertEquals
-import kotlin.test.assertNotEquals
-import kotlin.test.assertNotNull
-import kotlin.test.assertTrue
+import kotlin.test.*
// Foo pays Bar with custom subject.
fun genTx(
@@ -283,14 +280,14 @@ class DatabaseTest {
assert(db.customerCreate(customerBar) != null) // plays the exchange.
assert(db.bankAccountCreate(bankAccountBar) != null)
// insert new.
- assert(db.talerWithdrawalCreate(
+ assertEquals(WithdrawalCreationResult.SUCCESS, db.talerWithdrawalCreate(
+ "bar",
uuid,
- 1L,
TalerAmount(1, 0, currency)
))
// get it.
val op = db.talerWithdrawalGet(uuid)
- assert(op?.walletBankAccount == 1L && op.withdrawalUuid == uuid)
+ assert(op?.walletBankAccount == 2L && op.withdrawalUuid == uuid)
// Setting the details.
assert(db.talerWithdrawalSetDetails(
opUuid = uuid,
diff --git a/database-versioning/procedures.sql b/database-versioning/procedures.sql
@@ -508,6 +508,80 @@ IF out_balance_insufficient THEN
END IF;
END $$;
+CREATE OR REPLACE FUNCTION create_taler_withdrawal(
+ IN in_account_username TEXT,
+ IN in_withdrawal_uuid UUID,
+ IN in_amount taler_amount,
+ -- Error status
+ OUT out_account_not_found BOOLEAN,
+ OUT out_account_is_exchange BOOLEAN,
+ OUT out_balance_insufficient BOOLEAN
+)
+LANGUAGE plpgsql AS $$
+DECLARE
+account_id BIGINT;
+account_has_debt BOOLEAN;
+account_balance taler_amount;
+account_max_debt taler_amount;
+BEGIN
+
+-- check account exists
+SELECT
+ has_debt, bank_account_id,
+ (balance).val, (balance).frac,
+ (max_debt).val, (max_debt).frac,
+ is_taler_exchange
+ INTO
+ account_has_debt, account_id,
+ account_balance.val, account_balance.frac,
+ account_max_debt.val, account_max_debt.frac,
+ out_account_is_exchange
+ FROM bank_accounts
+ JOIN customers ON bank_accounts.owning_customer_id = customers.customer_id
+ WHERE login=in_account_username;
+IF NOT FOUND THEN
+ out_account_not_found=TRUE;
+ RETURN;
+ELSIF out_account_is_exchange THEN
+ RETURN;
+END IF;
+
+-- check enough funds
+IF account_has_debt THEN
+ -- debt case: simply checking against the max debt allowed.
+ CALL amount_add(account_balance, in_amount, account_balance);
+ SELECT NOT ok
+ INTO out_balance_insufficient
+ FROM amount_left_minus_right(account_max_debt, account_balance);
+ IF out_balance_insufficient THEN
+ RETURN;
+ END IF;
+ELSE -- not a debt account
+ SELECT NOT ok
+ INTO out_balance_insufficient
+ FROM amount_left_minus_right(account_balance, in_amount);
+ IF out_balance_insufficient THEN
+ -- debtor will switch to debt: determine their new negative balance.
+ SELECT
+ (diff).val, (diff).frac
+ INTO
+ account_balance.val, account_balance.frac
+ FROM amount_left_minus_right(in_amount, account_balance);
+ SELECT NOT ok
+ INTO out_balance_insufficient
+ FROM amount_left_minus_right(account_max_debt, account_balance);
+ IF out_balance_insufficient THEN
+ RETURN;
+ END IF;
+ END IF;
+END IF;
+
+-- Create withdrawal operation
+INSERT INTO taler_withdrawal_operations
+ (withdrawal_uuid, wallet_bank_account, amount)
+ VALUES (in_withdrawal_uuid, account_id, in_amount);
+END $$;
+
CREATE OR REPLACE FUNCTION confirm_taler_withdrawal(
IN in_withdrawal_uuid uuid,
IN in_confirmation_date BIGINT,
@@ -620,22 +694,19 @@ AS $$
DECLARE
debtor_has_debt BOOLEAN;
debtor_balance taler_amount;
+debtor_max_debt taler_amount;
debtor_payto_uri TEXT;
debtor_name TEXT;
-creditor_payto_uri TEXT;
-creditor_name TEXT;
-debtor_max_debt taler_amount;
creditor_has_debt BOOLEAN;
creditor_balance taler_amount;
+creditor_payto_uri TEXT;
+creditor_name TEXT;
potential_balance taler_amount;
-potential_balance_check BOOLEAN;
new_debtor_balance taler_amount;
new_debtor_balance_ok BOOLEAN;
new_creditor_balance taler_amount;
will_debtor_have_debt BOOLEAN;
will_creditor_have_debt BOOLEAN;
-amount_at_least_debit BOOLEAN;
-potential_balance_ok BOOLEAN;
new_debit_row_id BIGINT;
new_credit_row_id BIGINT;
BEGIN
@@ -695,27 +766,26 @@ IF debtor_has_debt THEN
CALL amount_add(debtor_balance,
in_amount,
potential_balance);
- SELECT ok
- INTO potential_balance_check
+ SELECT NOT ok
+ INTO out_balance_insufficient
FROM amount_left_minus_right(debtor_max_debt,
potential_balance);
- IF NOT potential_balance_check THEN
- out_balance_insufficient=TRUE;
+ IF out_balance_insufficient THEN
RETURN;
END IF;
new_debtor_balance=potential_balance;
will_debtor_have_debt=TRUE;
ELSE -- not a debt account
SELECT
- ok,
+ NOT ok,
(diff).val, (diff).frac
INTO
- potential_balance_ok,
+ out_balance_insufficient,
potential_balance.val,
potential_balance.frac
FROM amount_left_minus_right(debtor_balance,
in_amount);
- IF potential_balance_ok THEN -- debtor has enough funds in the (positive) balance.
+ IF NOT out_balance_insufficient THEN -- debtor has enough funds in the (positive) balance.
new_debtor_balance=potential_balance;
will_debtor_have_debt=FALSE;
ELSE -- debtor will switch to debt: determine their new negative balance.
@@ -726,16 +796,16 @@ ELSE -- not a debt account
FROM amount_left_minus_right(in_amount,
debtor_balance);
will_debtor_have_debt=TRUE;
- SELECT ok
- INTO potential_balance_check
+ SELECT NOT ok
+ INTO out_balance_insufficient
FROM amount_left_minus_right(debtor_max_debt,
new_debtor_balance);
- IF NOT potential_balance_check THEN
- out_balance_insufficient=TRUE;
+ IF out_balance_insufficient THEN
RETURN;
END IF;
END IF;
END IF;
+out_balance_insufficient=FALSE;
-- CREDITOR SIDE.
-- Here we figure out whether the creditor would switch
@@ -747,28 +817,23 @@ IF NOT creditor_has_debt THEN -- easy case.
ELSE -- creditor had debit but MIGHT switch to credit.
SELECT
(diff).val, (diff).frac,
- ok
+ NOT ok
INTO
new_creditor_balance.val, new_creditor_balance.frac,
- amount_at_least_debit
+ will_creditor_have_debt
FROM amount_left_minus_right(in_amount,
creditor_balance);
- IF amount_at_least_debit THEN
- -- the amount is at least as big as the debit, can switch to credit then.
- will_creditor_have_debt=FALSE;
- -- compute new balance.
- ELSE
- -- the amount is not enough to bring the receiver
- -- to a credit state, switch operators to calculate the new balance.
+ IF will_creditor_have_debt THEN
+ -- the amount is not enough to bring the receiver
+ -- to a credit state, switch operators to calculate the new balance.
SELECT
(diff).val, (diff).frac
INTO new_creditor_balance.val, new_creditor_balance.frac
FROM amount_left_minus_right(creditor_balance,
in_amount);
- will_creditor_have_debt=TRUE;
END IF;
END IF;
-out_balance_insufficient=FALSE;
+
-- now actually create the bank transaction.
-- debtor side:
INSERT INTO bank_account_transactions (