libeufin

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

commit 210f2cb5e61fcd796262d84a0dc1306605d76c70
parent a6aaf2085ae70a4a8ce6949e52d044e09d80b55d
Author: Antoine A <>
Date:   Tue,  8 Apr 2025 17:58:47 +0200

nexus: complete outgoing transactions with new ids

Diffstat:
Mdatabase-versioning/libeufin-nexus-procedures.sql | 35++++++++++++++---------------------
Mnexus/src/test/kotlin/DatabaseTest.kt | 58++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 72 insertions(+), 21 deletions(-)

diff --git a/database-versioning/libeufin-nexus-procedures.sql b/database-versioning/libeufin-nexus-procedures.sql @@ -233,7 +233,6 @@ local_ref TEXT; local_amount taler_amount; local_subject TEXT; local_debit_payto TEXT; -need_completion BOOLEAN; BEGIN IF in_credit_fee = (0, 0)::taler_amount THEN in_credit_fee = NULL; @@ -248,31 +247,25 @@ out_found=FOUND; IF out_found THEN local_ref=COALESCE(in_uetr::text, in_tx_id, in_acct_svcr_ref); -- Check metadata - IF in_subject IS NOT NULL THEN - IF local_subject IS NULL THEN - need_completion=TRUE; - ELSIF local_subject != in_subject THEN - RAISE NOTICE 'incoming tx %: stored subject is ''%'' got ''%''', local_ref, local_subject, in_subject; - END IF; + IF in_subject != local_subject THEN + RAISE NOTICE 'incoming tx %: stored subject is ''%'' got ''%''', local_ref, local_subject, in_subject; END IF; - IF in_debit_payto IS NOT NULL THEN - IF local_debit_payto IS NULL THEN - need_completion=TRUE; - ELSIF local_debit_payto != in_debit_payto THEN - RAISE NOTICE 'incoming tx %: stored subject debit payto is % got %', local_ref, local_debit_payto, in_debit_payto; - END IF; + IF in_debit_payto != local_debit_payto THEN + RAISE NOTICE 'incoming tx %: stored subject debit payto is % got %', local_ref, local_debit_payto, in_debit_payto; END IF; IF local_amount != in_amount THEN RAISE NOTICE 'incoming tx %: stored amount is % got %', local_ref, local_amount, in_amount; END IF; - IF need_completion THEN - UPDATE incoming_transactions - SET subject=COALESCE(subject, in_subject), debit_payto=COALESCE(debit_payto, in_debit_payto) - WHERE incoming_transaction_id = out_tx_id; - out_completed=COALESCE(in_subject, local_subject) IS NOT NULL AND COALESCE(in_debit_payto, local_debit_payto) IS NOT NULL; - IF out_completed THEN - PERFORM pg_notify('nexus_revenue_tx', out_tx_id::text); - END IF; + UPDATE incoming_transactions + SET subject=COALESCE(subject, in_subject), + debit_payto=COALESCE(debit_payto, in_debit_payto), + uetr=COALESCE(uetr, in_uetr), + tx_id=COALESCE(tx_id, in_tx_id), + acct_svcr_ref=COALESCE(acct_svcr_ref, in_acct_svcr_ref) + WHERE incoming_transaction_id = out_tx_id; + out_completed=COALESCE(in_subject, local_subject) IS NOT NULL AND COALESCE(in_debit_payto, local_debit_payto) IS NOT NULL; + IF out_completed THEN + PERFORM pg_notify('nexus_revenue_tx', out_tx_id::text); END IF; ELSE out_reserve_pub_reuse=in_type = 'reserve' AND EXISTS(SELECT FROM talerable_incoming_transactions WHERE metadata = in_metadata AND type = 'reserve'); diff --git a/nexus/src/test/kotlin/DatabaseTest.kt b/nexus/src/test/kotlin/DatabaseTest.kt @@ -29,6 +29,7 @@ import tech.libeufin.nexus.db.* import tech.libeufin.nexus.db.PaymentDAO.OutgoingRegistrationResult import tech.libeufin.nexus.db.InitiatedDAO.PaymentInitiationResult import java.time.Instant +import java.util.UUID; import kotlin.test.* suspend fun Database.checkInCount(nbIncoming: Int, nbBounce: Int, nbTalerable: Int) = serializable( @@ -280,6 +281,63 @@ class IncomingPaymentsTest { registerIncomingPayment(db, cfg, original) db.checkInCount(5, 2, 2) } + + @Test + fun recoverInfo() = setup { db, _ -> + val cfg = NexusIngestConfig.default(AccountType.exchange) + + suspend fun Database.checkContent(payment: IncomingPayment) = serializable( + """ + SELECT + uetr IS NOT DISTINCT FROM ? AND + tx_id IS NOT DISTINCT FROM ? AND + acct_svcr_ref IS NOT DISTINCT FROM ? AND + subject IS NOT DISTINCT FROM ? AND + debit_payto IS NOT DISTINCT FROM ? + FROM incoming_transactions ORDER BY incoming_transaction_id DESC LIMIT 1 + """ + ) { + setObject(1, payment.id.uetr) + setString(2, payment.id.txId) + setString(3, payment.id.acctSvcrRef) + setString(4, payment.subject) + setString(5, payment.debtor?.toString()) + one { + assertTrue(it.getBoolean(1)) + } + } + + for ((index, partialId) in sequenceOf( + IncomingId(UUID.randomUUID(), null, null), + IncomingId(null, randEbicsId(), null), + IncomingId(null, null, randEbicsId()), + ).withIndex()) { + val payment = genInPay("subject") + + // Register minimal + val partialPayment = payment.copy(id = partialId, subject = null, debtor = null) + registerIncomingPayment(db, cfg, partialPayment) + db.checkContent(partialPayment) + db.checkInCount(index + 1, index, 0) + + // Recover ID + val fullId = IncomingId( + partialId.uetr ?: UUID.randomUUID(), + partialId.txId ?: randEbicsId(), + partialId.acctSvcrRef ?: randEbicsId() + ) + val idPayment = partialPayment.copy(id = fullId) + registerIncomingPayment(db, cfg, idPayment) + db.checkContent(idPayment) + db.checkInCount(index + 1, index, 0) + + // Recover subject & debtor + val fullPayment = payment.copy(id = fullId) + registerIncomingPayment(db, cfg, fullPayment) + db.checkContent(fullPayment) + db.checkInCount(index + 1, index + 1, 0) + } + } } class PaymentInitiationsTest {