libeufin

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

commit b95b95b5d15eee3204c9c754835b5231ed0a3c3e
parent 746756605b41d22e49454e95b38338b6fdb07059
Author: Antoine A <>
Date:   Mon, 27 Nov 2023 13:52:05 +0000

Fix cashin

Diffstat:
Mbank/src/main/kotlin/tech/libeufin/bank/Main.kt | 1+
Mbank/src/test/kotlin/StatsTest.kt | 7+++++--
Mdatabase-versioning/libeufin-bank-procedures.sql | 53+++++++++++++++++++++++++++++++++--------------------
Mdatabase-versioning/libeufin-conversion-setup.sql | 24++++++++++++------------
Mintegration/test/IntegrationTest.kt | 23++++++++++++++++++++---
5 files changed, 71 insertions(+), 37 deletions(-)

diff --git a/bank/src/main/kotlin/tech/libeufin/bank/Main.kt b/bank/src/main/kotlin/tech/libeufin/bank/Main.kt @@ -338,6 +338,7 @@ class ServeBank : CliktCommand("Run libeufin-bank HTTP server", name = "serve") val db = Database(dbCfg.dbConnStr, ctx.regionalCurrency, ctx.fiatCurrency) runBlocking { if (ctx.allowConversion) { + // TODO check exchange account logger.info("ensure conversion is enabled") val sqlProcedures = File("${dbCfg.sqlDir}/libeufin-conversion-setup.sql") if (!sqlProcedures.exists()) { diff --git a/bank/src/test/kotlin/StatsTest.kt b/bank/src/test/kotlin/StatsTest.kt @@ -41,7 +41,7 @@ class StatsTest { db.conn { conn -> val stmt = conn.prepareStatement("SELECT 0 FROM cashin(?, ?, (?, ?)::taler_amount, ?)") stmt.setLong(1, Instant.now().toDbMicros()!!) - stmt.setString(2, customerPayto.canonical) + stmt.setBytes(2, randShortHashCode().raw) val amount = TalerAmount(amount) stmt.setLong(3, amount.value) stmt.setInt(4, amount.frac) @@ -98,10 +98,13 @@ class StatsTest { cashin("EUR:10") monitorCashin(1, "KUDOS:7.98", "EUR:10") + monitorTalerIn(4, "KUDOS:30.88") cashin("EUR:20") monitorCashin(2, "KUDOS:23.96", "EUR:30") + monitorTalerIn(5, "KUDOS:46.86") cashin("EUR:40") monitorCashin(3, "KUDOS:55.94", "EUR:70") + monitorTalerIn(6, "KUDOS:78.84") cashout("KUDOS:3") monitorCashout(1, "KUDOS:3", "EUR:3.747") @@ -110,7 +113,7 @@ class StatsTest { cashout("KUDOS:12.3") monitorCashout(3, "KUDOS:22.9", "EUR:28.616") - monitorTalerIn(3, "KUDOS:22.9") + monitorTalerIn(6, "KUDOS:78.84") monitorTalerOut(3, "KUDOS:82.5") monitorCashin(3, "KUDOS:55.94", "EUR:70") monitorCashout(3, "KUDOS:22.9", "EUR:28.616") diff --git a/database-versioning/libeufin-bank-procedures.sql b/database-versioning/libeufin-bank-procedures.sql @@ -1001,7 +1001,7 @@ END $$; CREATE OR REPLACE FUNCTION cashin( IN in_now_date BIGINT, - IN in_payto_uri TEXT, + IN in_reserve_pub BYTEA, IN in_amount taler_amount, IN in_subject TEXT, -- Error status @@ -1014,13 +1014,18 @@ LANGUAGE plpgsql AS $$ DECLARE converted_amount taler_amount; admin_account_id BIGINT; - wallet_account_id BIGINT; + exchange_account_id BIGINT; + tx_row_id BIGINT; BEGIN --- Recover account info +-- TODO check reserve_pub reuse ? + +-- Recover exchange account info SELECT bank_account_id - INTO wallet_account_id + INTO exchange_account_id FROM bank_accounts - WHERE internal_payto_uri = in_payto_uri; + JOIN customers + ON customer_id=owning_customer_id + WHERE login = 'exchange'; IF NOT FOUND THEN out_no_account = true; RETURN; @@ -1043,24 +1048,32 @@ IF out_too_small OR out_no_config THEN END IF; -- Perform bank wire transfer -SELECT transfer.out_balance_insufficient -INTO out_balance_insufficient -FROM bank_wire_transfer( - wallet_account_id, - admin_account_id, - in_subject, - converted_amount, - in_now_date, - 'not-used', - 'not-used', - 'not-used' -) as transfer; +SELECT + transfer.out_balance_insufficient, + transfer.out_credit_row_id + INTO + out_balance_insufficient, + tx_row_id + FROM bank_wire_transfer( + exchange_account_id, + admin_account_id, + in_subject, + converted_amount, + in_now_date, + NULL, + NULL, + NULL + ) as transfer; IF out_balance_insufficient THEN RETURN; END IF; +-- Register incoming transaction +CALL register_incoming(in_reserve_pub, tx_row_id); + -- update stats CALL stats_register_payment('cashin', now()::TIMESTAMP, converted_amount, in_amount); + END $$; COMMENT ON FUNCTION cashin IS 'Perform a cashin operation'; @@ -1246,9 +1259,9 @@ FROM bank_wire_transfer( subject_local, amount_debit_local, in_now_date, - 'not-used', - 'not-used', - 'not-used' + NULL, + NULL, + NULL ) as transfer; IF out_balance_insufficient THEN RETURN; diff --git a/database-versioning/libeufin-conversion-setup.sql b/database-versioning/libeufin-conversion-setup.sql @@ -43,7 +43,6 @@ RETURNS trigger LANGUAGE plpgsql AS $$ DECLARE now_date BIGINT; - payto_uri TEXT; local_amount libeufin_bank.taler_amount; subject TEXT; too_small BOOLEAN; @@ -51,28 +50,29 @@ LANGUAGE plpgsql AS $$ no_account BOOLEAN; no_config BOOLEAN; BEGIN - SELECT (amount).val, (amount).frac, wire_transfer_subject, execution_time, debit_payto_uri - INTO local_amount.val, local_amount.frac, subject, now_date, payto_uri + SELECT (amount).val, (amount).frac, wire_transfer_subject, execution_time + INTO local_amount.val, local_amount.frac, subject, now_date FROM libeufin_nexus.incoming_transactions WHERE incoming_transaction_id = NEW.incoming_transaction_id; SET search_path TO libeufin_bank; SELECT out_too_small, out_balance_insufficient, out_no_account, out_no_config INTO too_small, balance_insufficient, no_account, no_config - FROM libeufin_bank.cashin(now_date, payto_uri, local_amount, subject); + FROM libeufin_bank.cashin(now_date, NEW.reserve_public_key, local_amount, subject); SET search_path TO libeufin_nexus; - IF no_account THEN - RAISE EXCEPTION 'TODO soft error bounce: unknown account'; - END IF; IF too_small THEN - RAISE EXCEPTION 'TODO soft error bounce: too small amount'; - END IF; - IF balance_insufficient THEN - RAISE EXCEPTION 'TODO hard error bounce: admin balance insufficient'; + RAISE EXCEPTION 'cashin currency conversion failed: too small amount'; END IF; IF no_config THEN - RAISE EXCEPTION 'TODO hard error bounce: missing conversion rate config'; + RAISE EXCEPTION 'cashin currency conversion failed: missing conversion rates'; END IF; + IF no_account THEN + RAISE EXCEPTION 'cashin failed: missing exchange account'; + END IF; + IF balance_insufficient THEN + RAISE EXCEPTION 'cashin failed: admin balance insufficient'; + END IF; + RETURN NEW; END; $$; diff --git a/integration/test/IntegrationTest.kt b/integration/test/IntegrationTest.kt @@ -28,6 +28,7 @@ import tech.libeufin.bank.AccountDAO.* import tech.libeufin.util.* import java.io.File import java.time.Instant +import java.util.Arrays import kotlinx.coroutines.runBlocking import com.github.ajalt.clikt.testing.test import com.github.ajalt.clikt.core.CliktCommand @@ -99,6 +100,16 @@ class IntegrationTest { } }.assertCreated() + // Create exchange + client.post("http://0.0.0.0:8080/accounts") { + json { + "username" to "exchange" + "password" to "password" + "name" to "Mr Money" + "is_taler_exchange" to true + } + }.assertCreated() + // Set conversion rates client.post("http://0.0.0.0:8080/conversion-info/conversion-rate") { basicAuth("admin", "password") @@ -129,14 +140,20 @@ class IntegrationTest { reservePub) val converted = client.get("http://0.0.0.0:8080/conversion-info/cashin-rate?amount_debit=EUR:${20 + i}") .assertOkJson<ConversionResponse>().amount_credit - client.get("http://0.0.0.0:8080/accounts/customer/transactions") { - basicAuth("customer", "password") + client.get("http://0.0.0.0:8080/accounts/exchange/transactions") { + basicAuth("exchange", "password") }.assertOkJson<BankAccountTransactionsResponse> { val tx = it.transactions.first() - assertEquals(userPayTo.canonical, tx.creditor_payto_uri) assertEquals("cashin test $i", tx.subject) assertEquals(converted, tx.amount) } + client.get("http://0.0.0.0:8080/accounts/exchange/taler-wire-gateway/history/incoming") { + basicAuth("exchange", "password") + }.assertOkJson<IncomingHistory> { + val tx = it.incoming_transactions.first() + assertEquals(converted, tx.amount) + assert(Arrays.equals(reservePub, tx.reserve_pub.raw)) + } } // Cashout