libeufin

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

commit 404e67e86247bd4ffb784191e137a9db41b4d845
parent 689ae9b19fb5efdf91775123d09e58084246b2a2
Author: Antoine A <>
Date:   Mon, 17 Jun 2024 11:42:34 +0200

Clean code

Diffstat:
Mbank/src/main/kotlin/tech/libeufin/bank/api/CoreBankApi.kt | 12++++++------
Mbank/src/main/kotlin/tech/libeufin/bank/api/WireGatewayApi.kt | 4++--
Mbank/src/main/kotlin/tech/libeufin/bank/auth/Tan.kt | 2+-
Mbank/src/main/kotlin/tech/libeufin/bank/db/AccountDAO.kt | 9++++-----
Mbank/src/main/kotlin/tech/libeufin/bank/db/CashoutDAO.kt | 4++--
Mbank/src/main/kotlin/tech/libeufin/bank/db/ExchangeDAO.kt | 10+++++-----
Mbank/src/main/kotlin/tech/libeufin/bank/db/GcDAO.kt | 8++++----
Mbank/src/main/kotlin/tech/libeufin/bank/db/TanDAO.kt | 16++++++++--------
Mbank/src/main/kotlin/tech/libeufin/bank/db/TokenDAO.kt | 18+++++++-----------
Mbank/src/main/kotlin/tech/libeufin/bank/db/TransactionDAO.kt | 6+++---
Mbank/src/main/kotlin/tech/libeufin/bank/db/WithdrawalDAO.kt | 8++++----
Mbank/src/main/kotlin/tech/libeufin/bank/params.kt | 10+++++-----
Mbank/src/test/kotlin/AmountTest.kt | 2+-
Mbank/src/test/kotlin/DatabaseTest.kt | 16++++++++--------
Mbank/src/test/kotlin/StatsTest.kt | 12++++++------
Mdatabase-versioning/libeufin-bank-procedures.sql | 153+++++++++++++++++++++++++++++++++++++------------------------------------------
Mnexus/src/main/kotlin/tech/libeufin/nexus/db/ExchangeDAO.kt | 5++---
Mnexus/src/main/kotlin/tech/libeufin/nexus/db/InitiatedDAO.kt | 8++++----
Mnexus/src/main/kotlin/tech/libeufin/nexus/db/PaymentDAO.kt | 8+++-----
19 files changed, 146 insertions(+), 165 deletions(-)

diff --git a/bank/src/main/kotlin/tech/libeufin/bank/api/CoreBankApi.kt b/bank/src/main/kotlin/tech/libeufin/bank/api/CoreBankApi.kt @@ -257,7 +257,7 @@ suspend fun patchAccount( db: Database, cfg: BankConfig, req: AccountReconfiguration, - username: String, + login: String, isAdmin: Boolean, is2fa: Boolean, channel: TanChannel? = null, @@ -265,7 +265,7 @@ suspend fun patchAccount( ): AccountPatchResult { req.debit_threshold?.run { cfg.checkRegionalCurrency(this) } - if (username == "admin" && req.is_public == true) + if (login == "admin" && req.is_public == true) throw conflict( "'admin' account cannot be public", TalerErrorCode.END @@ -276,7 +276,7 @@ suspend fun patchAccount( } return db.account.reconfig( - login = username, + login = login, name = req.name, cashoutPayto = req.cashout_payto_uri, email = req.contact_data?.email ?: Option.None, @@ -601,7 +601,7 @@ private fun Routing.coreBankCashoutApi(db: Database, ctx: BankConfig) = conditio amountDebit = req.amount_debit, amountCredit = req.amount_credit, subject = req.subject ?: "", // TODO default subject - now = Instant.now(), + timestamp = Instant.now(), is2fa = challenge != null ) when (res) { @@ -677,7 +677,7 @@ private fun Routing.coreBankTanApi(db: Database, ctx: BankConfig) { id = id, login = username, code = Tan.genCode(), - now = Instant.now(), + timestamp = Instant.now(), retryCounter = TAN_RETRY_COUNTER, validityPeriod = TAN_VALIDITY_PERIOD ) @@ -741,7 +741,7 @@ private fun Routing.coreBankTanApi(db: Database, ctx: BankConfig) { id = id, login = username, code = code, - now = Instant.now() + timestamp = Instant.now() ) when (res) { TanSolveResult.NotFound -> throw notFound( diff --git a/bank/src/main/kotlin/tech/libeufin/bank/api/WireGatewayApi.kt b/bank/src/main/kotlin/tech/libeufin/bank/api/WireGatewayApi.kt @@ -52,7 +52,7 @@ fun Routing.wireGatewayApi(db: Database, ctx: BankConfig) { val res = db.exchange.transfer( req = req, login = username, - now = Instant.now() + timestamp = Instant.now() ) when (res) { is TransferResult.UnknownExchange -> throw unknownAccount(username) @@ -119,7 +119,7 @@ fun Routing.wireGatewayApi(db: Database, ctx: BankConfig) { val res = db.exchange.addIncoming( req = req, login = username, - now = timestamp + timestamp = timestamp ) when (res) { is AddIncomingResult.UnknownExchange -> throw unknownAccount(username) diff --git a/bank/src/main/kotlin/tech/libeufin/bank/auth/Tan.kt b/bank/src/main/kotlin/tech/libeufin/bank/auth/Tan.kt @@ -51,7 +51,7 @@ suspend inline fun <reified B> ApplicationCall.respondChallenge( op = op, body = json, code = code, - now = Instant.now(), + timestamp = Instant.now(), retryCounter = TAN_RETRY_COUNTER, validityPeriod = TAN_VALIDITY_PERIOD, channel = channel, diff --git a/bank/src/main/kotlin/tech/libeufin/bank/db/AccountDAO.kt b/bank/src/main/kotlin/tech/libeufin/bank/db/AccountDAO.kt @@ -54,7 +54,7 @@ class AccountDAO(private val db: Database) { checkPaytoIdempotent: Boolean, ctx: BankPaytoCtx ): AccountCreationResult = db.serializable { it -> - val now = Instant.now().micros() + val timestamp = Instant.now().micros() it.transaction { conn -> val idempotent = conn.prepareStatement(""" SELECT password_hash, name=? @@ -105,7 +105,7 @@ class AccountDAO(private val db: Database) { ) VALUES (?, ?) """).run { setString(1, internalPayto.iban.value) - setLong(2, now) + setLong(2, timestamp) if (!executeUpdateViolation()) { conn.rollback() return@transaction AccountCreationResult.PayToReuse @@ -169,7 +169,7 @@ class AccountDAO(private val db: Database) { setString(1, internalPayto.canonical) setLong(2, bonus.value) setInt(3, bonus.frac) - setLong(4, now) + setLong(4, timestamp) executeQuery().use { when { !it.next() -> throw internalServerError("Bank transaction didn't properly return") @@ -201,7 +201,6 @@ class AccountDAO(private val db: Database) { login: String, is2fa: Boolean ): AccountDeletionResult = db.serializable { conn -> - val now = Instant.now().micros() val stmt = conn.prepareStatement(""" SELECT out_not_found, @@ -210,7 +209,7 @@ class AccountDAO(private val db: Database) { FROM account_delete(?,?,?) """) stmt.setString(1, login) - stmt.setLong(2, now) + stmt.setLong(2, Instant.now().micros()) stmt.setBoolean(3, is2fa) stmt.executeQuery().use { when { diff --git a/bank/src/main/kotlin/tech/libeufin/bank/db/CashoutDAO.kt b/bank/src/main/kotlin/tech/libeufin/bank/db/CashoutDAO.kt @@ -46,7 +46,7 @@ class CashoutDAO(private val db: Database) { amountDebit: TalerAmount, amountCredit: TalerAmount, subject: String, - now: Instant, + timestamp: Instant, is2fa: Boolean ): CashoutCreationResult = db.serializable { conn -> val stmt = conn.prepareStatement(""" @@ -69,7 +69,7 @@ class CashoutDAO(private val db: Database) { stmt.setLong(5, amountCredit.value) stmt.setInt(6, amountCredit.frac) stmt.setString(7, subject) - stmt.setLong(8, now.micros()) + stmt.setLong(8, timestamp.micros()) stmt.setBoolean(9, is2fa) stmt.executeQuery().use { when { diff --git a/bank/src/main/kotlin/tech/libeufin/bank/db/ExchangeDAO.kt b/bank/src/main/kotlin/tech/libeufin/bank/db/ExchangeDAO.kt @@ -102,7 +102,7 @@ class ExchangeDAO(private val db: Database) { suspend fun transfer( req: TransferRequest, login: String, - now: Instant + timestamp: Instant ): TransferResult = db.serializable { conn -> val subject = "${req.wtid} ${req.exchange_base_url.url}" val stmt = conn.prepareStatement(""" @@ -131,7 +131,7 @@ class ExchangeDAO(private val db: Database) { stmt.setString(6, req.exchange_base_url.url) stmt.setString(7, req.credit_account.canonical) stmt.setString(8, login) - stmt.setLong(9, now.micros()) + stmt.setLong(9, timestamp.micros()) stmt.executeQuery().use { when { @@ -167,7 +167,7 @@ class ExchangeDAO(private val db: Database) { suspend fun addIncoming( req: AddIncomingRequest, login: String, - now: Instant + timestamp: Instant ): AddIncomingResult = db.serializable { conn -> val stmt = conn.prepareStatement(""" SELECT @@ -192,7 +192,7 @@ class ExchangeDAO(private val db: Database) { stmt.setInt(4, req.amount.frac) stmt.setString(5, req.debit_account.canonical) stmt.setString(6, login) - stmt.setLong(7, now.micros()) + stmt.setLong(7, timestamp.micros()) stmt.one { when { @@ -204,7 +204,7 @@ class ExchangeDAO(private val db: Database) { it.getBoolean("out_reserve_pub_reuse") -> AddIncomingResult.ReservePubReuse else -> AddIncomingResult.Success( id = it.getLong("out_tx_row_id"), - timestamp = TalerProtocolTimestamp(now) + timestamp = TalerProtocolTimestamp(timestamp) ) } } diff --git a/bank/src/main/kotlin/tech/libeufin/bank/db/GcDAO.kt b/bank/src/main/kotlin/tech/libeufin/bank/db/GcDAO.kt @@ -27,14 +27,14 @@ import java.time.Instant class GcDAO(private val db: Database) { /** Run garbage collection */ suspend fun collect( - now: Instant, + timestamp: Instant, abortAfter: Duration, cleanAfter: Duration, deleteAfter: Duration ) = db.conn { conn -> - val abortAfterMicro = now.minus(abortAfter).micros() - val cleanAfterMicro = now.minus(cleanAfter).micros() - val deleteAfterMicro = now.minus(deleteAfter).micros() + val abortAfterMicro = timestamp.minus(abortAfter).micros() + val cleanAfterMicro = timestamp.minus(cleanAfter).micros() + val deleteAfterMicro = timestamp.minus(deleteAfter).micros() // Abort pending operations conn.prepareStatement( diff --git a/bank/src/main/kotlin/tech/libeufin/bank/db/TanDAO.kt b/bank/src/main/kotlin/tech/libeufin/bank/db/TanDAO.kt @@ -35,7 +35,7 @@ class TanDAO(private val db: Database) { op: Operation, body: String, code: String, - now: Instant, + timestamp: Instant, retryCounter: Int, validityPeriod: Duration, channel: TanChannel? = null, @@ -45,7 +45,7 @@ class TanDAO(private val db: Database) { stmt.setString(1, body) stmt.setString(2, op.name) stmt.setString(3, code) - stmt.setLong(4, now.micros()) + stmt.setLong(4, timestamp.micros()) stmt.setLong(5, TimeUnit.MICROSECONDS.convert(validityPeriod)) stmt.setInt(6, retryCounter) stmt.setString(7, login) @@ -67,7 +67,7 @@ class TanDAO(private val db: Database) { id: Long, login: String, code: String, - now: Instant, + timestamp: Instant, retryCounter: Int, validityPeriod: Duration ) = db.serializable { conn -> @@ -75,7 +75,7 @@ class TanDAO(private val db: Database) { stmt.setLong(1, id) stmt.setString(2, login) stmt.setString(3, code) - stmt.setLong(4, now.micros()) + stmt.setLong(4, timestamp.micros()) stmt.setLong(5, TimeUnit.MICROSECONDS.convert(validityPeriod)) stmt.setInt(6, retryCounter) stmt.executeQuery().use { @@ -94,12 +94,12 @@ class TanDAO(private val db: Database) { /** Mark TAN challenge transmission */ suspend fun markSent( id: Long, - now: Instant, + timestamp: Instant, retransmissionPeriod: Duration ) = db.serializable { conn -> val stmt = conn.prepareStatement("SELECT tan_challenge_mark_sent(?,?,?)") stmt.setLong(1, id) - stmt.setLong(2, now.micros()) + stmt.setLong(2, timestamp.micros()) stmt.setLong(3, TimeUnit.MICROSECONDS.convert(retransmissionPeriod)) stmt.executeQuery() } @@ -118,7 +118,7 @@ class TanDAO(private val db: Database) { id: Long, login: String, code: String, - now: Instant + timestamp: Instant ) = db.serializable { conn -> val stmt = conn.prepareStatement(""" SELECT @@ -128,7 +128,7 @@ class TanDAO(private val db: Database) { stmt.setLong(1, id) stmt.setString(2, login) stmt.setString(3, code) - stmt.setLong(4, now.micros()) + stmt.setLong(4, timestamp.micros()) stmt.executeQuery().use { when { !it.next() -> throw internalServerError("TAN try returned nothing") diff --git a/bank/src/main/kotlin/tech/libeufin/bank/db/TokenDAO.kt b/bank/src/main/kotlin/tech/libeufin/bank/db/TokenDAO.kt @@ -36,13 +36,6 @@ class TokenDAO(private val db: Database) { isRefreshable: Boolean, description: String? ): Boolean = db.serializable { conn -> - // TODO single query - val bankCustomer = conn.prepareStatement(""" - SELECT customer_id FROM customers WHERE login=? AND deleted_at IS NULL - """).run { - setString(1, login) - oneOrNull { it.getLong(1) }!! - } val stmt = conn.prepareStatement(""" INSERT INTO bearer_tokens ( content, @@ -53,13 +46,16 @@ class TokenDAO(private val db: Database) { is_refreshable, description, last_access - ) VALUES (?,?,?,?::token_scope_enum,?,?,?,?) + ) VALUES ( + ?,?,?,?::token_scope_enum, + (SELECT customer_id FROM customers WHERE login=? AND deleted_at IS NULL), + ?,?,?) """) stmt.setBytes(1, content) stmt.setLong(2, creationTime.micros()) stmt.setLong(3, expirationTime.micros()) stmt.setString(4, scope.name) - stmt.setLong(5, bankCustomer) + stmt.setString(5, login) stmt.setBoolean(6, isRefreshable) stmt.setString(7, description) stmt.setLong(8, creationTime.micros()) @@ -81,8 +77,8 @@ class TokenDAO(private val db: Database) { is_refreshable """) stmt.setLong(1, accessTime.micros()) - stmt.setBytes(2, token) - stmt.oneOrNull { + stmt.setBytes(2, token) + stmt.oneOrNull { BearerToken( creationTime = it.getLong("creation_time").asInstant(), expirationTime = it.getLong("expiration_time").asInstant(), diff --git a/bank/src/main/kotlin/tech/libeufin/bank/db/TransactionDAO.kt b/bank/src/main/kotlin/tech/libeufin/bank/db/TransactionDAO.kt @@ -53,7 +53,7 @@ class TransactionDAO(private val db: Database) { requestUid: ShortHashCode?, wireTransferFees: TalerAmount ): BankTransactionResult = db.serializable { conn -> - val now = timestamp.micros() + val timestamp = timestamp.micros() conn.transaction { val stmt = conn.prepareStatement(""" SELECT @@ -79,7 +79,7 @@ class TransactionDAO(private val db: Database) { stmt.setString(3, subject) stmt.setLong(4, amount.value) stmt.setInt(5, amount.frac) - stmt.setLong(6, now) + stmt.setLong(6, timestamp) stmt.setBoolean(7, is2fa) stmt.setBytes(8, requestUid?.raw) stmt.setLong(9, wireTransferFees.value) @@ -137,7 +137,7 @@ class TransactionDAO(private val db: Database) { setString(3, "Bounce $creditRowId: $bounceCause") setLong(4, amount.value) setInt(5, amount.frac) - setLong(6, now) + setLong(6, timestamp) executeQuery() } } diff --git a/bank/src/main/kotlin/tech/libeufin/bank/db/WithdrawalDAO.kt b/bank/src/main/kotlin/tech/libeufin/bank/db/WithdrawalDAO.kt @@ -45,7 +45,7 @@ class WithdrawalDAO(private val db: Database) { uuid: UUID, amount: TalerAmount?, suggested_amount: TalerAmount?, - now: Instant, + timestamp: Instant, wireTransferFees: TalerAmount, ): WithdrawalCreationResult = db.serializable { conn -> val stmt = conn.prepareStatement(""" @@ -73,7 +73,7 @@ class WithdrawalDAO(private val db: Database) { stmt.setInt(id+1, suggested_amount.frac) id += 2 } - stmt.setLong(id, now.micros()) + stmt.setLong(id, timestamp.micros()) stmt.setLong(id+1, wireTransferFees.value) stmt.setInt(id+2, wireTransferFees.frac) stmt.executeQuery().use { @@ -192,7 +192,7 @@ class WithdrawalDAO(private val db: Database) { login: String, uuid: UUID, wireTransferFees: TalerAmount, - now: Instant, + timestamp: Instant, is2fa: Boolean ): WithdrawalConfirmationResult = db.serializable { conn -> val stmt = conn.prepareStatement(""" @@ -208,7 +208,7 @@ class WithdrawalDAO(private val db: Database) { ) stmt.setString(1, login) stmt.setObject(2, uuid) - stmt.setLong(3, now.micros()) + stmt.setLong(3, timestamp.micros()) stmt.setBoolean(4, is2fa) stmt.setLong(5, wireTransferFees.value) stmt.setInt(6, wireTransferFees.frac) diff --git a/bank/src/main/kotlin/tech/libeufin/bank/params.kt b/bank/src/main/kotlin/tech/libeufin/bank/params.kt @@ -31,13 +31,13 @@ data class MonitorParams( val timeframe: Timeframe, val date: LocalDateTime ) { - constructor(timeframe: Timeframe, now: LocalDateTime, which: Int) : this( + constructor(timeframe: Timeframe, timestamp: LocalDateTime, which: Int) : this( timeframe, when (timeframe) { - Timeframe.hour -> now.withHour(which) - Timeframe.day -> now.withDayOfMonth(which) - Timeframe.month -> now.withMonth(which) - Timeframe.year -> now.withYear(which) + Timeframe.hour -> timestamp.withHour(which) + Timeframe.day -> timestamp.withDayOfMonth(which) + Timeframe.month -> timestamp.withMonth(which) + Timeframe.year -> timestamp.withYear(which) } ) constructor(timeframe: Timeframe, secs: Long) : this( diff --git a/bank/src/test/kotlin/AmountTest.kt b/bank/src/test/kotlin/AmountTest.kt @@ -71,7 +71,7 @@ class AmountTest { uuid = UUID.randomUUID(), amount = due, suggested_amount = null, - now = Instant.now(), + timestamp = Instant.now(), wireTransferFees = TalerAmount.zero("KUDOS") ) val wBool = when (wRes) { diff --git a/bank/src/test/kotlin/DatabaseTest.kt b/bank/src/test/kotlin/DatabaseTest.kt @@ -79,34 +79,34 @@ class DatabaseTest { val retransmissionPeriod: Duration = Duration.ofMinutes(1) val retryCounter = 3 - fun create(code: String, now: Instant): Long { + fun create(code: String, timestamp: Instant): Long { createStmt.setString(1, code) - createStmt.setLong(2, ChronoUnit.MICROS.between(Instant.EPOCH, now)) + createStmt.setLong(2, ChronoUnit.MICROS.between(Instant.EPOCH, timestamp)) createStmt.setLong(3, TimeUnit.MICROSECONDS.convert(validityPeriod)) createStmt.setInt(4, retryCounter) return createStmt.oneOrNull { it.getLong(1) }!! } - fun markSent(id: Long, now: Instant) { + fun markSent(id: Long, timestamp: Instant) { markSentStmt.setLong(1, id) - markSentStmt.setLong(2, ChronoUnit.MICROS.between(Instant.EPOCH, now)) + markSentStmt.setLong(2, ChronoUnit.MICROS.between(Instant.EPOCH, timestamp)) markSentStmt.setLong(3, TimeUnit.MICROSECONDS.convert(retransmissionPeriod)) return markSentStmt.oneOrNull { }!! } - fun cTry(id: Long, code: String, now: Instant): Triple<Boolean, Boolean, Boolean> { + fun cTry(id: Long, code: String, timestamp: Instant): Triple<Boolean, Boolean, Boolean> { tryStmt.setLong(1, id) tryStmt.setString(2, code) - tryStmt.setLong(3, ChronoUnit.MICROS.between(Instant.EPOCH, now)) + tryStmt.setLong(3, ChronoUnit.MICROS.between(Instant.EPOCH, timestamp)) return tryStmt.oneOrNull { Triple(it.getBoolean(1), it.getBoolean(2), it.getBoolean(3)) }!! } - fun send(id: Long, code: String, now: Instant): String? { + fun send(id: Long, code: String, timestamp: Instant): String? { sendStmt.setLong(1, id) sendStmt.setString(2, code) - sendStmt.setLong(3, ChronoUnit.MICROS.between(Instant.EPOCH, now)) + sendStmt.setLong(3, ChronoUnit.MICROS.between(Instant.EPOCH, timestamp)) sendStmt.setLong(4, TimeUnit.MICROSECONDS.convert(validityPeriod)) sendStmt.setInt(5, retryCounter) return sendStmt.oneOrNull { diff --git a/bank/src/test/kotlin/StatsTest.kt b/bank/src/test/kotlin/StatsTest.kt @@ -123,11 +123,11 @@ class StatsTest { @Test fun timeframe() = bankSetup { db -> db.conn { conn -> - suspend fun register(now: LocalDateTime, amount: TalerAmount) { + suspend fun register(timestamp: LocalDateTime, amount: TalerAmount) { val stmt = conn.prepareStatement( "CALL stats_register_payment('taler_out', ?::timestamp, (?, ?)::taler_amount, null)" ) - stmt.setObject(1, now) + stmt.setObject(1, timestamp) stmt.setLong(2, amount.value) stmt.setInt(3, amount.frac) stmt.executeUpdate() @@ -144,18 +144,18 @@ class StatsTest { } suspend fun checkSimple( - now: LocalDateTime, + timestamp: LocalDateTime, timeframe: Timeframe, count: Long, amount: TalerAmount - ) = check(MonitorParams(timeframe, now), count, amount) + ) = check(MonitorParams(timeframe, timestamp), count, amount) suspend fun checkWhich( - now: LocalDateTime, + timestamp: LocalDateTime, timeframe: Timeframe, which: Int, count: Long, amount: TalerAmount - ) = check(MonitorParams(timeframe, now, which), count, amount) + ) = check(MonitorParams(timeframe, timestamp, which), count, amount) suspend fun checkDate( secs: Long, timeframe: Timeframe, diff --git a/database-versioning/libeufin-bank-procedures.sql b/database-versioning/libeufin-bank-procedures.sql @@ -42,13 +42,13 @@ COMMENT ON FUNCTION amount_normalize 'It raises an exception when the resulting .val is larger than 2^52'; CREATE FUNCTION amount_add( - IN a taler_amount - ,IN b taler_amount + IN l taler_amount + ,IN r taler_amount ,OUT sum taler_amount ) LANGUAGE plpgsql AS $$ BEGIN - sum = (a.val + b.val, a.frac + b.frac); + sum = (l.val + r.val, l.frac + r.frac); SELECT normalized.val, normalized.frac INTO sum.val, sum.frac FROM amount_normalize(sum) as normalized; END $$; COMMENT ON FUNCTION amount_add @@ -87,6 +87,7 @@ COMMENT ON FUNCTION amount_left_minus_right CREATE FUNCTION account_balance_is_sufficient( IN in_account_id INT8, IN in_amount taler_amount, + IN in_wire_transfer_fees taler_amount, OUT out_balance_insufficient BOOLEAN ) LANGUAGE plpgsql AS $$ @@ -94,8 +95,14 @@ DECLARE account_has_debt BOOLEAN; account_balance taler_amount; account_max_debt taler_amount; +amount_with_fee taler_amount; BEGIN --- get account info, we expect the account to exist +-- Add fees to the amount +SELECT sum.val, sum.frac + INTO amount_with_fee.val, amount_with_fee.frac + FROM amount_add(in_amount, in_wire_transfer_fees) as sum; + +-- Get account info, we expect the account to exist SELECT has_debt, (balance).val, (balance).frac, @@ -106,12 +113,12 @@ SELECT account_max_debt.val, account_max_debt.frac FROM bank_accounts WHERE bank_account_id=in_account_id; --- check enough funds +-- Check enough funds IF account_has_debt THEN -- debt case: simply checking against the max debt allowed. SELECT sum.val, sum.frac INTO account_balance.val, account_balance.frac - FROM amount_add(account_balance, in_amount) as sum; + FROM amount_add(account_balance, amount_with_fee) as sum; SELECT NOT ok INTO out_balance_insufficient FROM amount_left_minus_right(account_max_debt, account_balance); @@ -121,14 +128,14 @@ IF account_has_debt THEN ELSE -- not a debt account SELECT NOT ok INTO out_balance_insufficient - FROM amount_left_minus_right(account_balance, in_amount); + FROM amount_left_minus_right(account_balance, amount_with_fee); 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); + FROM amount_left_minus_right(amount_with_fee, account_balance); SELECT NOT ok INTO out_balance_insufficient FROM amount_left_minus_right(account_max_debt, account_balance); @@ -142,7 +149,7 @@ COMMENT ON FUNCTION account_balance_is_sufficient IS 'Check if an account have e CREATE FUNCTION account_delete( IN in_login TEXT, - IN in_now INT8, + IN in_timestamp INT8, IN in_is_tan BOOLEAN, OUT out_not_found BOOLEAN, OUT out_balance_not_zero BOOLEAN, @@ -170,7 +177,7 @@ IF NOT FOUND OR out_balance_not_zero OR out_tan_required THEN END IF; -- actual deletion -UPDATE customers SET deleted_at = in_now WHERE customer_id = my_customer_id; +UPDATE customers SET deleted_at = in_timestamp WHERE customer_id = my_customer_id; END $$; COMMENT ON FUNCTION account_delete IS 'Deletes an account if the balance is zero'; @@ -505,7 +512,7 @@ CREATE FUNCTION create_taler_withdrawal( IN in_withdrawal_uuid UUID, IN in_amount taler_amount, IN in_suggested_amount taler_amount, - IN in_now_date INT8, + IN in_timestamp INT8, IN in_wire_transfer_fees taler_amount, -- Error status OUT out_account_not_found BOOLEAN, @@ -529,29 +536,15 @@ IF NOT FOUND OR out_account_is_exchange THEN END IF; -- Check enough funds -IF in_amount IS NOT NULL THEN - SELECT sum.val, sum.frac - INTO amount_with_fee.val, amount_with_fee.frac - FROM amount_add(in_amount, in_wire_transfer_fees) as sum; - SELECT account_balance_is_sufficient(account_id, amount_with_fee) INTO out_balance_insufficient; - IF out_balance_insufficient THEN - RETURN; - END IF; -END IF; -IF in_suggested_amount IS NOT NULL THEN - SELECT sum.val, sum.frac - INTO amount_with_fee.val, amount_with_fee.frac - FROM amount_add(in_suggested_amount, in_wire_transfer_fees) as sum; - SELECT account_balance_is_sufficient(account_id, amount_with_fee) INTO out_balance_insufficient; - IF out_balance_insufficient THEN - RETURN; - END IF; +SELECT account_balance_is_sufficient(account_id, COALESCE(in_amount, in_suggested_amount), in_wire_transfer_fees) INTO out_balance_insufficient; +IF out_balance_insufficient THEN + RETURN; END IF; -- Create withdrawal operation INSERT INTO taler_withdrawal_operations (withdrawal_uuid, wallet_bank_account, amount, suggested_amount, creation_date) - VALUES (in_withdrawal_uuid, account_id, in_amount, in_suggested_amount, in_now_date); + VALUES (in_withdrawal_uuid, account_id, in_amount, in_suggested_amount, in_timestamp); END $$; COMMENT ON FUNCTION create_taler_withdrawal IS 'Create a new withdrawal operation'; @@ -622,11 +615,7 @@ IF not_selected THEN END IF; IF in_amount IS NOT NULL THEN - -- Check enough funds - SELECT sum.val, sum.frac - INTO amount_with_fee.val, amount_with_fee.frac - FROM amount_add(in_amount, in_wire_transfer_fees) as sum; - SELECT account_balance_is_sufficient(account_id, amount_with_fee) INTO out_balance_insufficient; + SELECT account_balance_is_sufficient(account_id, in_amount, in_wire_transfer_fees) INTO out_balance_insufficient; IF out_balance_insufficient THEN RETURN; END IF; @@ -672,7 +661,7 @@ COMMENT ON FUNCTION abort_taler_withdrawal IS 'Abort a withdrawal operation.'; CREATE FUNCTION confirm_taler_withdrawal( IN in_login TEXT, IN in_withdrawal_uuid uuid, - IN in_confirmation_date INT8, + IN in_timestamp INT8, IN in_is_tan BOOLEAN, IN in_wire_transfer_fees taler_amount, OUT out_no_op BOOLEAN, @@ -740,7 +729,7 @@ FROM bank_wire_transfer( wallet_bank_account_local, subject_local, amount_local, - in_confirmation_date, + in_timestamp, in_wire_transfer_fees ) as transfer; IF out_balance_insufficient THEN @@ -766,8 +755,8 @@ CREATE FUNCTION bank_wire_transfer( IN in_debtor_account_id INT8, IN in_subject TEXT, IN in_amount taler_amount, - IN in_transaction_date INT8, - IN in_fee taler_amount, + IN in_timestamp INT8, + IN in_wire_transfer_fees taler_amount, -- Error status OUT out_balance_insufficient BOOLEAN, -- Success return @@ -780,16 +769,16 @@ amount_with_fee taler_amount; admin_account_id INT8; admin_has_debt BOOLEAN; admin_balance taler_amount; -admin_payto_uri TEXT; +admin_payto TEXT; admin_name TEXT; debtor_has_debt BOOLEAN; debtor_balance taler_amount; debtor_max_debt taler_amount; -debtor_payto_uri TEXT; +debtor_payto TEXT; debtor_name TEXT; creditor_has_debt BOOLEAN; creditor_balance taler_amount; -creditor_payto_uri TEXT; +creditor_payto TEXT; creditor_name TEXT; tmp_balance taler_amount; BEGIN @@ -803,7 +792,7 @@ SELECT debtor_has_debt, debtor_balance.val, debtor_balance.frac, debtor_max_debt.val, debtor_max_debt.frac, - debtor_payto_uri, debtor_name + debtor_payto, debtor_name FROM bank_accounts JOIN customers ON customer_id=owning_customer_id WHERE bank_account_id=in_debtor_account_id; @@ -818,7 +807,7 @@ SELECT INTO creditor_has_debt, creditor_balance.val, creditor_balance.frac, - creditor_payto_uri, creditor_name + creditor_payto, creditor_name FROM bank_accounts JOIN customers ON customer_id=owning_customer_id WHERE bank_account_id=in_creditor_account_id; @@ -833,7 +822,7 @@ SELECT INTO admin_account_id, admin_has_debt, admin_balance.val, admin_balance.frac, - admin_payto_uri, admin_name + admin_payto, admin_name FROM bank_accounts JOIN customers ON customer_id=owning_customer_id WHERE login = 'admin'; @@ -841,10 +830,10 @@ IF NOT FOUND THEN RAISE EXCEPTION 'No admin'; END IF; -IF in_fee != (0, 0)::taler_amount AND admin_account_id != in_debtor_account_id THEN +IF in_wire_transfer_fees != (0, 0)::taler_amount AND admin_account_id != in_debtor_account_id THEN SELECT sum.val, sum.frac INTO amount_with_fee.val, amount_with_fee.frac - FROM amount_add(in_amount, in_fee) as sum; + FROM amount_add(in_amount, in_wire_transfer_fees) as sum; ELSE amount_with_fee = in_amount; END IF; @@ -931,13 +920,13 @@ IF amount_with_fee != in_amount THEN IF NOT admin_has_debt THEN -- easy case. SELECT sum.val, sum.frac INTO admin_balance.val, admin_balance.frac - FROM amount_add(admin_balance, in_fee) as sum; + FROM amount_add(admin_balance, in_wire_transfer_fees) as sum; ELSE -- creditor had debit but MIGHT switch to credit. SELECT (diff).val, (diff).frac, NOT ok INTO tmp_balance.val, tmp_balance.frac, admin_has_debt - FROM amount_left_minus_right(in_fee, admin_balance); + FROM amount_left_minus_right(in_wire_transfer_fees, admin_balance); IF NOT admin_has_debt THEN admin_balance=tmp_balance; ELSE @@ -945,7 +934,7 @@ IF amount_with_fee != in_amount THEN -- to a credit state, switch operators to calculate the new balance. SELECT (diff).val, (diff).frac INTO admin_balance.val, admin_balance.frac - FROM amount_left_minus_right(admin_balance, in_fee); + FROM amount_left_minus_right(admin_balance, in_wire_transfer_fees); END IF; END IF; END IF; @@ -964,13 +953,13 @@ INSERT INTO bank_account_transactions ( ,bank_account_id ) VALUES ( - creditor_payto_uri, + creditor_payto, creditor_name, - debtor_payto_uri, + debtor_payto, debtor_name, in_subject, in_amount, - in_transaction_date, + in_timestamp, 'debit', in_debtor_account_id ) RETURNING bank_transaction_id INTO out_debit_row_id; @@ -988,13 +977,13 @@ INSERT INTO bank_account_transactions ( ,bank_account_id ) VALUES ( - creditor_payto_uri, + creditor_payto, creditor_name, - debtor_payto_uri, + debtor_payto, debtor_name, in_subject, in_amount, - in_transaction_date, + in_timestamp, 'credit', in_creditor_account_id ) RETURNING bank_transaction_id INTO out_credit_row_id; @@ -1026,23 +1015,23 @@ IF amount_with_fee != in_amount THEN ,bank_account_id ) VALUES ( - admin_payto_uri, + admin_payto, admin_name, - debtor_payto_uri, + debtor_payto, debtor_name, - 'wire transfer fee for tx ' || out_debit_row_id, - in_fee, - in_transaction_date, + 'wire transfer fees for tx ' || out_debit_row_id, + in_wire_transfer_fees, + in_timestamp, 'debit', in_debtor_account_id ), ( - admin_payto_uri, + admin_payto, admin_name, - debtor_payto_uri, + debtor_payto, debtor_name, - 'wire transfer fee for tx ' || out_debit_row_id, + 'wire transfer fees for tx ' || out_debit_row_id, in_amount, - in_transaction_date, + in_timestamp, 'credit', admin_account_id ); @@ -1059,7 +1048,7 @@ PERFORM pg_notify('bank_tx', in_debtor_account_id || ' ' || in_creditor_account_ END $$; CREATE FUNCTION cashin( - IN in_now_date INT8, + IN in_timestamp INT8, IN in_reserve_pub BYTEA, IN in_amount taler_amount, IN in_subject TEXT, @@ -1118,7 +1107,7 @@ SELECT admin_account_id, in_subject, converted_amount, - in_now_date, + in_timestamp, (0, 0)::taler_amount ) as transfer; IF out_balance_insufficient THEN @@ -1141,7 +1130,7 @@ CREATE FUNCTION cashout_create( IN in_amount_debit taler_amount, IN in_amount_credit taler_amount, IN in_subject TEXT, - IN in_now_date INT8, + IN in_timestamp INT8, IN in_is_tan BOOLEAN, -- Error status OUT out_bad_conversion BOOLEAN, @@ -1182,7 +1171,7 @@ ELSIF out_account_is_exchange OR out_no_cashout_payto THEN RETURN; END IF; --- check conversion TODO use custom min +-- check conversion IF custom_min_cashout.val IS NULL THEN custom_min_cashout = NULL; END IF; @@ -1221,7 +1210,7 @@ FROM bank_wire_transfer( account_id, in_subject, in_amount_debit, - in_now_date, + in_timestamp, (0, 0)::taler_amount ) as transfer; IF out_balance_insufficient THEN @@ -1241,7 +1230,7 @@ INSERT INTO cashout_operations ( in_request_uid ,in_amount_debit ,in_amount_credit - ,in_now_date + ,in_timestamp ,account_id ,in_subject ,tx_id @@ -1255,7 +1244,7 @@ CREATE FUNCTION tan_challenge_create ( IN in_body TEXT, IN in_op op_enum, IN in_code TEXT, - IN in_now_date INT8, + IN in_timestamp INT8, IN in_validity_period INT8, IN in_retry_counter INT4, IN in_login TEXT, @@ -1284,8 +1273,8 @@ INSERT INTO tan_challenges ( in_body, in_op, in_code, - in_now_date, - in_now_date + in_validity_period, + in_timestamp, + in_timestamp + in_validity_period, in_retry_counter, account_id, in_tan_channel, @@ -1298,7 +1287,7 @@ CREATE FUNCTION tan_challenge_send ( IN in_challenge_id INT8, IN in_login TEXT, IN in_code TEXT, -- New code to use if the old code expired - IN in_now_date INT8, + IN in_timestamp INT8, IN in_validity_period INT8, IN in_retry_counter INT4, -- Error status @@ -1324,8 +1313,8 @@ FROM customers WHERE login = in_login AND deleted_at IS NULL; -- Recover expiration date SELECT - (in_now_date >= expiration_date OR retry_counter <= 0) AND confirmation_date IS NULL - ,in_now_date >= retransmission_date AND confirmation_date IS NULL + (in_timestamp >= expiration_date OR retry_counter <= 0) AND confirmation_date IS NULL + ,in_timestamp >= retransmission_date AND confirmation_date IS NULL ,code, COALESCE(tan_channel, out_tan_channel), COALESCE(tan_info, out_tan_info) INTO expired, retransmit, out_tan_code, out_tan_channel, out_tan_info FROM tan_challenges WHERE challenge_id = in_challenge_id AND customer = account_id; @@ -1337,7 +1326,7 @@ END IF; IF expired THEN UPDATE tan_challenges SET code = in_code - ,expiration_date = in_now_date + in_validity_period + ,expiration_date = in_timestamp + in_validity_period ,retry_counter = in_retry_counter WHERE challenge_id = in_challenge_id; out_tan_code = in_code; @@ -1349,12 +1338,12 @@ COMMENT ON FUNCTION tan_challenge_send IS 'Get the challenge to send, return NUL CREATE FUNCTION tan_challenge_mark_sent ( IN in_challenge_id INT8, - IN in_now_date INT8, + IN in_timestamp INT8, IN in_retransmission_period INT8 ) RETURNS void LANGUAGE sql AS $$ UPDATE tan_challenges SET - retransmission_date = in_now_date + in_retransmission_period + retransmission_date = in_timestamp + in_retransmission_period WHERE challenge_id = in_challenge_id; $$; COMMENT ON FUNCTION tan_challenge_mark_sent IS 'Register a challenge as successfully sent'; @@ -1363,7 +1352,7 @@ CREATE FUNCTION tan_challenge_try ( IN in_challenge_id INT8, IN in_login TEXT, IN in_code TEXT, - IN in_now_date INT8, + IN in_timestamp INT8, -- Error status OUT out_ok BOOLEAN, OUT out_no_op BOOLEAN, @@ -1384,7 +1373,7 @@ SELECT customer_id INTO account_id FROM customers WHERE login = in_login AND del -- Check challenge UPDATE tan_challenges SET confirmation_date = CASE - WHEN (retry_counter > 0 AND in_now_date < expiration_date AND code = in_code) THEN in_now_date + WHEN (retry_counter > 0 AND in_timestamp < expiration_date AND code = in_code) THEN in_timestamp ELSE confirmation_date END, retry_counter = retry_counter - 1 @@ -1392,7 +1381,7 @@ WHERE challenge_id = in_challenge_id AND customer = account_id RETURNING confirmation_date IS NOT NULL, retry_counter <= 0 AND confirmation_date IS NULL, - in_now_date >= expiration_date AND confirmation_date IS NULL + in_timestamp >= expiration_date AND confirmation_date IS NULL INTO out_ok, out_no_retry, out_expired; IF NOT FOUND OR NOT out_ok OR out_no_retry OR out_expired THEN out_no_op = NOT FOUND; diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/db/ExchangeDAO.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/db/ExchangeDAO.kt @@ -89,7 +89,7 @@ class ExchangeDAO(private val db: Database) { suspend fun transfer( req: TransferRequest, bankId: String, - now: Instant + timestamp: Instant ): TransferResult = db.serializable { conn -> val subject = "${req.wtid} ${req.exchange_base_url.url}" val stmt = conn.prepareStatement(""" @@ -113,8 +113,7 @@ class ExchangeDAO(private val db: Database) { stmt.setString(6, req.exchange_base_url.url) stmt.setString(7, req.credit_account.canonical) stmt.setString(8, bankId) - stmt.setLong(9, now.micros()) - + stmt.setLong(9, timestamp.micros()) stmt.one { when { it.getBoolean("out_request_uid_reuse") -> TransferResult.RequestUidReuse diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/db/InitiatedDAO.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/db/InitiatedDAO.kt @@ -65,7 +65,7 @@ class InitiatedDAO(private val db: Database) { /** Register EBICS submission success */ suspend fun submissionSuccess( id: Long, - now: Instant, + timestamp: Instant, orderId: String ) = db.conn { conn -> val stmt = conn.prepareStatement(""" @@ -77,7 +77,7 @@ class InitiatedDAO(private val db: Database) { ,submission_counter = submission_counter + 1 WHERE initiated_outgoing_transaction_id = ? """) - stmt.setLong(1, now.micros()) + stmt.setLong(1, timestamp.micros()) stmt.setString(2, orderId) stmt.setLong(3, id) stmt.execute() @@ -86,7 +86,7 @@ class InitiatedDAO(private val db: Database) { /** Register EBICS submission failure */ suspend fun submissionFailure( id: Long, - now: Instant, + timestamp: Instant, msg: String? ) = db.conn { conn -> val stmt = conn.prepareStatement(""" @@ -97,7 +97,7 @@ class InitiatedDAO(private val db: Database) { ,submission_counter = submission_counter + 1 WHERE initiated_outgoing_transaction_id = ? """) - stmt.setLong(1, now.micros()) + stmt.setLong(1, timestamp.micros()) stmt.setString(2, msg) stmt.setLong(3, id) stmt.execute() diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/db/PaymentDAO.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/db/PaymentDAO.kt @@ -82,23 +82,21 @@ class PaymentDAO(private val db: Database) { suspend fun registerMalformedIncoming( paymentData: IncomingPayment, bounceAmount: TalerAmount, - now: Instant + timestamp: Instant ): IncomingBounceRegistrationResult = db.conn { val stmt = it.prepareStatement(""" SELECT out_found, out_tx_id, out_bounce_id FROM register_incoming_and_bounce((?,?)::taler_amount,?,?,?,?,(?,?)::taler_amount,?) """) - val refundTimestamp = now.micros() - val executionTime = paymentData.executionTime.micros() stmt.setLong(1, paymentData.amount.value) stmt.setInt(2, paymentData.amount.frac) stmt.setString(3, paymentData.wireTransferSubject) - stmt.setLong(4, executionTime) + stmt.setLong(4, paymentData.executionTime.micros()) stmt.setString(5, paymentData.debitPaytoUri) stmt.setString(6, paymentData.bankId) stmt.setLong(7, bounceAmount.value) stmt.setInt(8, bounceAmount.frac) - stmt.setLong(9, refundTimestamp) + stmt.setLong(9, timestamp.micros()) stmt.one { IncomingBounceRegistrationResult( it.getLong("out_tx_id"),