commit b95b95b5d15eee3204c9c754835b5231ed0a3c3e
parent 746756605b41d22e49454e95b38338b6fdb07059
Author: Antoine A <>
Date: Mon, 27 Nov 2023 13:52:05 +0000
Fix cashin
Diffstat:
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