libeufin

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

commit 2d4f778e4c143020e5ada9876134e54df9bd4f47
parent e8490074b3d174acb7863a36c3529792c83158e9
Author: Antoine A <>
Date:   Mon, 29 Apr 2024 18:05:31 +0900

nexus: clean wire gateway implementation using relaxed API documentation

Diffstat:
Mdatabase-versioning/libeufin-bank-procedures.sql | 2+-
Mdatabase-versioning/libeufin-nexus-0001.sql | 14+++++++-------
Mdatabase-versioning/libeufin-nexus-0003.sql | 22++++++++++------------
Mdatabase-versioning/libeufin-nexus-procedures.sql | 28++++++++++++----------------
Mnexus/src/main/kotlin/tech/libeufin/nexus/db/ExchangeDAO.kt | 30+++++++++++++-----------------
Mnexus/src/test/kotlin/WireGatewayApiTest.kt | 7++++---
6 files changed, 47 insertions(+), 56 deletions(-)

diff --git a/database-versioning/libeufin-bank-procedures.sql b/database-versioning/libeufin-bank-procedures.sql @@ -1056,7 +1056,7 @@ SELECT bank_account_id, is_taler_exchange, cashout_payto IS NULL, (NOT in_is_tan AND tan_channel IS NOT NULL) INTO account_id, out_account_is_exchange, out_no_cashout_payto, out_tan_required FROM bank_accounts - JOIN customers ON bank_accounts.owning_customer_id = customers.customer_id + JOIN customers ON owning_customer_id=customer_id WHERE login=in_login; IF NOT FOUND THEN out_account_not_found=TRUE; diff --git a/database-versioning/libeufin-nexus-0001.sql b/database-versioning/libeufin-nexus-0001.sql @@ -41,7 +41,7 @@ COMMENT ON TYPE submission_state never_heard_back is a fallback state, in case one successful submission did never get confirmed via camt.5x or pain.002.'; -CREATE TABLE IF NOT EXISTS incoming_transactions +CREATE TABLE incoming_transactions (incoming_transaction_id INT8 GENERATED BY DEFAULT AS IDENTITY UNIQUE ,amount taler_amount NOT NULL ,wire_transfer_subject TEXT NOT NULL @@ -53,12 +53,12 @@ COMMENT ON COLUMN incoming_transactions.bank_id IS 'ISO20022 AccountServicerReference'; -- only active in exchange mode. Note: duplicate keys are another reason to bounce. -CREATE TABLE IF NOT EXISTS talerable_incoming_transactions +CREATE TABLE talerable_incoming_transactions (incoming_transaction_id INT8 NOT NULL UNIQUE REFERENCES incoming_transactions(incoming_transaction_id) ON DELETE CASCADE ,reserve_public_key BYTEA NOT NULL UNIQUE CHECK (LENGTH(reserve_public_key)=32) ); -CREATE TABLE IF NOT EXISTS outgoing_transactions +CREATE TABLE outgoing_transactions (outgoing_transaction_id INT8 GENERATED BY DEFAULT AS IDENTITY UNIQUE ,amount taler_amount NOT NULL ,wire_transfer_subject TEXT @@ -69,7 +69,7 @@ CREATE TABLE IF NOT EXISTS outgoing_transactions COMMENT ON COLUMN outgoing_transactions.message_id IS 'ISO20022 MessageIdentification'; -CREATE TABLE IF NOT EXISTS initiated_outgoing_transactions +CREATE TABLE initiated_outgoing_transactions (initiated_outgoing_transaction_id INT8 GENERATED BY DEFAULT AS IDENTITY UNIQUE ,amount taler_amount NOT NULL ,wire_transfer_subject TEXT NOT NULL @@ -93,15 +93,15 @@ value will be used as a unique identifier for its related pain.001 document. For this reason, it must have at most 35 characters'; -- only active in exchange mode. -CREATE TABLE IF NOT EXISTS bounced_transactions +CREATE TABLE bounced_transactions (incoming_transaction_id INT8 NOT NULL UNIQUE REFERENCES incoming_transactions(incoming_transaction_id) ON DELETE CASCADE ,initiated_outgoing_transaction_id INT8 NOT NULL UNIQUE REFERENCES initiated_outgoing_transactions(initiated_outgoing_transaction_id) ON DELETE CASCADE ); -CREATE INDEX IF NOT EXISTS incoming_transaction_timestamp +CREATE INDEX incoming_transaction_timestamp ON incoming_transactions (execution_time); -CREATE INDEX IF NOT EXISTS outgoing_transaction_timestamp +CREATE INDEX outgoing_transaction_timestamp ON outgoing_transactions (execution_time); COMMIT; diff --git a/database-versioning/libeufin-nexus-0003.sql b/database-versioning/libeufin-nexus-0003.sql @@ -19,20 +19,18 @@ SELECT _v.register_patch('libeufin-nexus-0003', NULL, NULL); SET search_path TO libeufin_nexus; -CREATE TABLE IF NOT EXISTS talerable_outgoing_transactions - (talerable_outgoing_transaction_id INT8 GENERATED BY DEFAULT AS IDENTITY UNIQUE - ,initiated_outgoing_transaction_id INT8 UNIQUE REFERENCES initiated_outgoing_transactions(initiated_outgoing_transaction_id) ON DELETE CASCADE - ,outgoing_transaction_id INT8 UNIQUE REFERENCES outgoing_transactions(outgoing_transaction_id) ON DELETE CASCADE - ,CONSTRAINT tx_link CHECK (initiated_outgoing_transaction_id IS NOT NULL OR outgoing_transaction_id IS NOT NULL) - ,request_uid BYTEA UNIQUE CHECK (LENGTH(request_uid)=64) +CREATE TABLE talerable_outgoing_transactions + ( outgoing_transaction_id INT8 UNIQUE NOT NULL REFERENCES outgoing_transactions(outgoing_transaction_id) ON DELETE CASCADE ,wtid BYTEA NOT NULL UNIQUE CHECK (LENGTH(wtid)=32) ,exchange_base_url TEXT NOT NULL ); -COMMENT ON COLUMN talerable_outgoing_transactions.initiated_outgoing_transaction_id - IS 'If the transaction have been initiated'; -COMMENT ON COLUMN talerable_outgoing_transactions.outgoing_transaction_id - IS 'If the transaction have been recovered'; -COMMENT ON CONSTRAINT tx_link ON talerable_outgoing_transactions - IS 'A transaction is either initiated or recovered'; +CREATE TABLE transfer_operations + ( initiated_outgoing_transaction_id INT8 UNIQUE NOT NULL REFERENCES initiated_outgoing_transactions(initiated_outgoing_transaction_id) ON DELETE CASCADE + ,request_uid BYTEA UNIQUE NOT NULL CHECK (LENGTH(request_uid)=64) + ,wtid BYTEA UNIQUE NOT NULL CHECK (LENGTH(wtid)=32) + ,exchange_base_url TEXT NOT NULL + ); +COMMENT ON TABLE transfer_operations + IS 'Operation table for idempotent wire gateway transfers.'; COMMIT; diff --git a/database-versioning/libeufin-nexus-procedures.sql b/database-versioning/libeufin-nexus-procedures.sql @@ -42,7 +42,6 @@ CREATE FUNCTION register_outgoing( LANGUAGE plpgsql AS $$ DECLARE init_id INT8; -talerable_id INT8; BEGIN -- Check if already registered SELECT outgoing_transaction_id INTO out_tx_id @@ -90,10 +89,9 @@ IF in_wtid IS NOT NULL OR in_exchange_url IS NOT NULL THEN wtid, exchange_base_url ) VALUES (out_tx_id, in_wtid, in_exchange_url) - ON CONFLICT (wtid) DO NOTHING - RETURNING talerable_outgoing_transaction_id INTO talerable_id; - IF talerable_id IS NOT NULL THEN - PERFORM pg_notify('outgoing_tx', talerable_id::text); + ON CONFLICT (wtid) DO NOTHING; + IF FOUND THEN + PERFORM pg_notify('outgoing_tx', out_tx_id::text); END IF; END IF; END $$; @@ -233,7 +231,7 @@ BEGIN -- Check conflict IF EXISTS ( SELECT FROM talerable_incoming_transactions - JOIN incoming_transactions ON talerable_incoming_transactions.incoming_transaction_id=incoming_transactions.incoming_transaction_id + JOIN incoming_transactions USING(incoming_transaction_id) WHERE reserve_public_key = in_reserve_public_key AND bank_id != in_bank_id ) THEN @@ -280,20 +278,18 @@ CREATE FUNCTION taler_transfer( OUT out_timestamp INT8 ) LANGUAGE plpgsql AS $$ -DECLARE - initiated_id INT8; BEGIN -- Check for idempotence and conflict SELECT (amount != in_amount OR credit_payto_uri != in_credit_account_payto OR exchange_base_url != in_exchange_base_url OR wtid != in_wtid) - ,talerable_outgoing_transaction_id, initiation_time + ,transfer_operations.initiated_outgoing_transaction_id, initiation_time INTO out_request_uid_reuse, out_tx_row_id, out_timestamp - FROM talerable_outgoing_transactions + FROM transfer_operations JOIN initiated_outgoing_transactions - ON talerable_outgoing_transactions.initiated_outgoing_transaction_id=initiated_outgoing_transactions.initiated_outgoing_transaction_id - WHERE talerable_outgoing_transactions.request_uid = in_request_uid; + ON transfer_operations.initiated_outgoing_transaction_id=initiated_outgoing_transactions.initiated_outgoing_transaction_id + WHERE transfer_operations.request_uid = in_request_uid; IF FOUND THEN RETURN; END IF; @@ -310,19 +306,19 @@ INSERT INTO initiated_outgoing_transactions ( ,in_credit_account_payto ,in_timestamp ,in_bank_id -) RETURNING initiated_outgoing_transaction_id INTO initiated_id; +) RETURNING initiated_outgoing_transaction_id INTO out_tx_row_id; -- Register outgoing transaction -INSERT INTO talerable_outgoing_transactions( +INSERT INTO transfer_operations( initiated_outgoing_transaction_id ,request_uid ,wtid ,exchange_base_url ) VALUES ( - initiated_id + out_tx_row_id ,in_request_uid ,in_wtid ,in_exchange_base_url -) RETURNING talerable_outgoing_transaction_id INTO out_tx_row_id; +); out_timestamp = in_timestamp; PERFORM pg_notify('outgoing_tx', out_tx_row_id::text); END $$; diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/db/ExchangeDAO.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/db/ExchangeDAO.kt @@ -32,17 +32,16 @@ class ExchangeDAO(private val db: Database) { ): List<IncomingReserveTransaction> = db.poolHistoryGlobal(params, db::listenIncoming, """ SELECT - tit.incoming_transaction_id + incoming_transaction_id ,execution_time ,(amount).val AS amount_val ,(amount).frac AS amount_frac ,debit_payto_uri ,reserve_public_key - FROM talerable_incoming_transactions AS tit - JOIN incoming_transactions AS it - ON tit.incoming_transaction_id=it.incoming_transaction_id + FROM talerable_incoming_transactions + JOIN incoming_transactions USING(incoming_transaction_id) WHERE - """, "tit.incoming_transaction_id") { + """, "incoming_transaction_id") { IncomingReserveTransaction( row_id = it.getLong("incoming_transaction_id"), date = it.getTalerTimestamp("execution_time"), @@ -60,22 +59,19 @@ class ExchangeDAO(private val db: Database) { // reach database : the initiation first else the recovered transaction. = db.poolHistoryGlobal(params, db::listenOutgoing, """ SELECT - talerable_outgoing_transaction_id - ,COALESCE(iot.initiation_time, ot.execution_time) AS execution_time - ,(COALESCE(iot.amount, ot.amount)).val AS amount_val - ,(COALESCE(iot.amount, ot.amount)).frac AS amount_frac - ,COALESCE(iot.credit_payto_uri, ot.credit_payto_uri) AS credit_payto_uri + outgoing_transaction_id + ,execution_time AS execution_time + ,(amount).val AS amount_val + ,(amount).frac AS amount_frac + ,credit_payto_uri AS credit_payto_uri ,wtid ,exchange_base_url - FROM talerable_outgoing_transactions AS tot - LEFT OUTER JOIN outgoing_transactions AS ot - ON tot.outgoing_transaction_id=ot.outgoing_transaction_id - LEFT OUTER JOIN initiated_outgoing_transactions AS iot - ON tot.initiated_outgoing_transaction_id=iot.initiated_outgoing_transaction_id + FROM talerable_outgoing_transactions + JOIN outgoing_transactions USING(outgoing_transaction_id) WHERE - """, "talerable_outgoing_transaction_id") { + """, "outgoing_transaction_id") { OutgoingTransaction( - row_id = it.getLong("talerable_outgoing_transaction_id"), + row_id = it.getLong("outgoing_transaction_id"), date = it.getTalerTimestamp("execution_time"), amount = it.getAmount("amount", db.bankCurrency), credit_account = it.getString("credit_payto_uri"), diff --git a/nexus/src/test/kotlin/WireGatewayApiTest.kt b/nexus/src/test/kotlin/WireGatewayApiTest.kt @@ -149,15 +149,16 @@ class WireGatewayApiTest { url = "/taler-wire-gateway/history/outgoing", ids = { it.outgoing_transactions.map { it.row_id } }, registered = listOf( - { - transfer() - }, { talerableOut(db) } ), ignored = listOf( { + // Ignore pending transfers + transfer() + }, + { // Ignore manual incoming transaction talerableIn(db) },