commit ddf1f43c9396d7afa980649555e4ad6e24d42759
parent 997a8dce779b139d89a05c58a9e686aa5d78b086
Author: Antoine A <>
Date: Wed, 25 Sep 2024 01:33:06 +0200
nexus: handle EBICS returns as late failures
Diffstat:
11 files changed, 347 insertions(+), 109 deletions(-)
diff --git a/database-versioning/libeufin-nexus-0007.sql b/database-versioning/libeufin-nexus-0007.sql
@@ -19,6 +19,7 @@ SELECT _v.register_patch('libeufin-nexus-0007', NULL, NULL);
-- Add a new submission state reusing a currently unused slot
ALTER TYPE submission_state RENAME VALUE 'never_heard_back' TO 'pending';
+ALTER TYPE submission_state ADD VALUE 'late_failure';
-- Batch of initiated_outgoing_transactions
CREATE TABLE initiated_outgoing_batches(
diff --git a/database-versioning/libeufin-nexus-procedures.sql b/database-versioning/libeufin-nexus-procedures.sql
@@ -178,12 +178,13 @@ IF NOT out_found THEN
outgoing_transaction_id = out_tx_id
,status = 'success'
,status_msg = null
- WHERE initiated_outgoing_transaction_id = init_id;
+ WHERE initiated_outgoing_transaction_id = init_id
+ AND status != 'late_failure';
-- Reconciles the related initiated batch
UPDATE initiated_outgoing_batches
SET status = 'success', status_msg = null
- WHERE message_id = in_msg_id AND status NOT IN ('success', 'permanent_failure');
+ WHERE message_id = in_msg_id AND status NOT IN ('success', 'permanent_failure', 'late_failure');
END IF;
END IF;
END $$;
@@ -481,4 +482,74 @@ IF (EXISTS(SELECT FROM initiated_outgoing_transactions WHERE initiated_outgoing_
-- Update the batch with the sum of amounts
UPDATE initiated_outgoing_batches SET sum=local_sum WHERE initiated_outgoing_batch_id=batch_id;
END IF;
+END $$;
+
+CREATE FUNCTION batch_status_update(
+ IN in_message_id text,
+ IN in_status submission_state,
+ IN in_status_msg text
+)
+RETURNS void
+LANGUAGE plpgsql AS $$
+DECLARE
+local_batch_id INT8;
+BEGIN
+ -- Check if there is a batch for this message id
+ SELECT initiated_outgoing_batch_id INTO local_batch_id
+ FROM initiated_outgoing_batches
+ WHERE message_id = in_message_id;
+ IF FOUND THEN
+ -- Update unsettled batch status
+ UPDATE initiated_outgoing_batches
+ SET status = in_status, status_msg = in_status_msg
+ WHERE initiated_outgoing_batch_id = local_batch_id
+ AND status NOT IN ('success', 'permanent_failure', 'late_failure');
+
+ -- When a batch succeed it doesn't mean that individual transaction also succeed
+ IF in_status = 'success' THEN
+ in_status = 'pending';
+ END IF;
+
+ -- Update unsettled batch's transaction status
+ UPDATE initiated_outgoing_transactions
+ SET status = in_status, status_msg = in_status_msg
+ WHERE initiated_outgoing_batch_id = local_batch_id
+ AND status NOT IN ('success', 'permanent_failure', 'late_failure');
+ END IF;
+END $$;
+
+CREATE FUNCTION tx_status_update(
+ IN in_end_to_end_id text,
+ IN in_message_id text,
+ IN in_status submission_state,
+ IN in_status_msg text
+)
+RETURNS void
+LANGUAGE plpgsql AS $$
+DECLARE
+local_status submission_state;
+local_tx_id INT8;
+BEGIN
+ -- Check current tx status
+ SELECT initiated_outgoing_transaction_id, status INTO local_tx_id, local_status
+ FROM initiated_outgoing_transactions
+ WHERE end_to_end_id = in_end_to_end_id;
+ IF FOUND THEN
+ -- Update unsettled transaction status
+ IF local_status = 'success' AND in_status = 'permanent_failure' THEN
+ UPDATE initiated_outgoing_transactions
+ SET status = 'late_failure', status_msg = in_status_msg
+ WHERE initiated_outgoing_transaction_id = local_tx_id;
+ ELSIF local_status NOT IN ('success', 'permanent_failure', 'late_failure') THEN
+ UPDATE initiated_outgoing_transactions
+ SET status = in_status, status_msg = in_status_msg
+ WHERE initiated_outgoing_transaction_id = local_tx_id;
+ END IF;
+
+ -- Update unsettled batch status
+ UPDATE initiated_outgoing_batches
+ SET status = 'success', status_msg = NULL
+ WHERE message_id = in_message_id
+ AND status NOT IN ('success', 'permanent_failure', 'late_failure');
+ END IF;
END $$;
\ No newline at end of file
diff --git a/nexus/sample/platform/gls_camt052.xml b/nexus/sample/platform/gls_camt052.xml
@@ -548,6 +548,180 @@
</NtryDtls>
<AddtlNtryInf>Sammelüberweisung</AddtlNtryInf>
</Ntry>
+ <Ntry>
+ <Amt Ccy="EUR">0.42</Amt>
+ <CdtDbtInd>DBIT</CdtDbtInd>
+ <Sts>BOOK</Sts>
+ <BookgDt>
+ <Dt>2024-09-23</Dt>
+ </BookgDt>
+ <ValDt>
+ <Dt>2024-09-23</Dt>
+ </ValDt>
+ <AcctSvcrRef>2024092100252498000</AcctSvcrRef>
+ <BkTxCd>
+ <Domn>
+ <Cd>PMNT</Cd>
+ <Fmly>
+ <Cd>ICDT</Cd>
+ <SubFmlyCd>ESCT</SubFmlyCd>
+ </Fmly>
+ </Domn>
+ <Prtry>
+ <Cd>NTRF+177+08381</Cd>
+ <Issr>DK</Issr>
+ </Prtry>
+ </BkTxCd>
+ <NtryDtls>
+ <TxDtls>
+ <Refs>
+ <MsgId>BATCH_SINGLE_RETURN</MsgId>
+ <PmtInfId>NOTPROVIDED</PmtInfId>
+ <EndToEndId>KLJJ28S1LVNDK1R2HCHLN884M7EKM5XGM5</EndToEndId>
+ <TxId>2024092374955203090200000010000001</TxId>
+ </Refs>
+ <AmtDtls>
+ <TxAmt>
+ <Amt Ccy="EUR">0.42</Amt>
+ </TxAmt>
+ </AmtDtls>
+ <BkTxCd>
+ <Domn>
+ <Cd>PMNT</Cd>
+ <Fmly>
+ <Cd>ICDT</Cd>
+ <SubFmlyCd>ESCT</SubFmlyCd>
+ </Fmly>
+ </Domn>
+ <Prtry>
+ <Cd>NTRF+177+08381</Cd>
+ <Issr>DK</Issr>
+ </Prtry>
+ </BkTxCd>
+ <RltdPties>
+ <Dbtr>
+ <Nm>Florian Dold</Nm>
+ </Dbtr>
+ <DbtrAcct>
+ <Id>
+ <IBAN>DE89500105171325381664</IBAN>
+ </Id>
+ </DbtrAcct>
+ <Cdtr>
+ <Nm>John Smith</Nm>
+ </Cdtr>
+ <CdtrAcct>
+ <Id>
+ <IBAN>DE18500105173385245163</IBAN>
+ </Id>
+ </CdtrAcct>
+ </RltdPties>
+ <RltdAgts>
+ <CdtrAgt>
+ <FinInstnId>
+ <BIC>INGDDEFFXXX</BIC>
+ </FinInstnId>
+ </CdtrAgt>
+ </RltdAgts>
+ <RmtInf>
+ <Ustrd>This should fail because bad iban</Ustrd>
+ </RmtInf>
+ </TxDtls>
+ </NtryDtls>
+ <AddtlNtryInf>Überweisungsauftrag</AddtlNtryInf>
+ </Ntry>
+ <Ntry>
+ <Amt Ccy="EUR">0.42</Amt>
+ <CdtDbtInd>CRDT</CdtDbtInd>
+ <Sts>BOOK</Sts>
+ <BookgDt>
+ <Dt>2024-09-24</Dt>
+ </BookgDt>
+ <ValDt>
+ <Dt>2024-09-24</Dt>
+ </ValDt>
+ <AcctSvcrRef>2024092409341766000</AcctSvcrRef>
+ <BkTxCd>
+ <Domn>
+ <Cd>PMNT</Cd>
+ <Fmly>
+ <Cd>ICDT</Cd>
+ <SubFmlyCd>RRTN</SubFmlyCd>
+ </Fmly>
+ </Domn>
+ <Prtry>
+ <Cd>NRTI+159+00931</Cd>
+ <Issr>DK</Issr>
+ </Prtry>
+ </BkTxCd>
+ <NtryDtls>
+ <TxDtls>
+ <Refs>
+ <EndToEndId>KLJJ28S1LVNDK1R2HCHLN884M7EKM5XGM5</EndToEndId>
+ <TxId>2024092374955203090200000010000001</TxId>
+ </Refs>
+ <AmtDtls>
+ <TxAmt>
+ <Amt Ccy="EUR">0.42</Amt>
+ </TxAmt>
+ </AmtDtls>
+ <BkTxCd>
+ <Domn>
+ <Cd>PMNT</Cd>
+ <Fmly>
+ <Cd>ICDT</Cd>
+ <SubFmlyCd>RRTN</SubFmlyCd>
+ </Fmly>
+ </Domn>
+ <Prtry>
+ <Cd>NRTI+159+00931</Cd>
+ <Issr>DK</Issr>
+ </Prtry>
+ </BkTxCd>
+ <RltdPties>
+ <Dbtr>
+ <Nm>Florian Dold</Nm>
+ </Dbtr>
+ <DbtrAcct>
+ <Id>
+ <IBAN>DE89500105171325381664</IBAN>
+ </Id>
+ </DbtrAcct>
+ <Cdtr>
+ <Nm>John Smith</Nm>
+ </Cdtr>
+ <CdtrAcct>
+ <Id>
+ <IBAN>DE18500105173385245163</IBAN>
+ </Id>
+ </CdtrAcct>
+ </RltdPties>
+ <RmtInf>
+ <Ustrd>Retoure ...</Ustrd>
+ </RmtInf>
+ <RtrInf>
+ <OrgnlBkTxCd>
+ <Prtry>
+ <Cd>116</Cd>
+ <Issr>DK</Issr>
+ </Prtry>
+ </OrgnlBkTxCd>
+ <Orgtr>
+ <Id>
+ <OrgId>
+ <BICOrBEI>GENODEM1GLS</BICOrBEI>
+ </OrgId>
+ </Id>
+ </Orgtr>
+ <Rsn>
+ <Cd>AC01</Cd>
+ </Rsn>
+ <AddtlInf>IBAN fehlerhaft und ungültig</AddtlInf>
+ </RtrInf>
+ </TxDtls>
+ </NtryDtls>
+ <AddtlNtryInf>Retouren</AddtlNtryInf>
+ </Ntry>
</Rpt>
</BkToCstmrAcctRpt>
</Document>
\ No newline at end of file
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/cli/EbicsFetch.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/cli/EbicsFetch.kt
@@ -149,7 +149,7 @@ suspend fun registerTransaction(
is OutgoingBatch -> registerOutgoingBatch(db, tx)
is OutgoingReversal -> {
logger.error("{}", tx)
- db.initiated.txStatusUpdate(tx.endToEndId, tx.msgId, SubmissionState.permanent_failure, "Payment bounced: ${tx.reason}")
+ db.initiated.txStatusUpdate(tx.endToEndId, tx.msgId, StatusUpdate.permanent_failure, "Payment bounced: ${tx.reason}")
}
}
}
@@ -203,12 +203,12 @@ suspend fun registerFile(
if (msgStatus.code != null) {
val msg = msgStatus.msg()
val state = when (msgStatus.code) {
- ExternalPaymentGroupStatusCode.ACSC -> SubmissionState.success
+ ExternalPaymentGroupStatusCode.ACSC -> StatusUpdate.success
ExternalPaymentGroupStatusCode.RJCT -> {
logger.error("Batch ${msgStatus.id} failed: $msg")
- SubmissionState.permanent_failure
+ StatusUpdate.permanent_failure
}
- else -> SubmissionState.pending
+ else -> StatusUpdate.pending
}
db.initiated.batchStatusUpdate(msgStatus.id, state, msg)
}
@@ -218,12 +218,12 @@ suspend fun registerFile(
} else if (pmtStatus.code != null) {
val msg = pmtStatus.msg()
val state = when (pmtStatus.code) {
- ExternalPaymentGroupStatusCode.ACSC -> SubmissionState.success
+ ExternalPaymentGroupStatusCode.ACSC -> StatusUpdate.success
ExternalPaymentGroupStatusCode.RJCT -> {
logger.error("Batch ${msgStatus.id} failed: $msg")
- SubmissionState.permanent_failure
+ StatusUpdate.permanent_failure
}
- else -> SubmissionState.pending
+ else -> StatusUpdate.pending
}
db.initiated.batchStatusUpdate(msgStatus.id, state, msg)
}
@@ -233,9 +233,9 @@ suspend fun registerFile(
ExternalPaymentTransactionStatusCode.RJCT,
ExternalPaymentTransactionStatusCode.BLCK -> {
logger.error("Transaction ${txStatus.endToEndId} failed: $msg")
- SubmissionState.permanent_failure
+ StatusUpdate.permanent_failure
}
- else -> SubmissionState.pending
+ else -> StatusUpdate.pending
}
db.initiated.txStatusUpdate(txStatus.endToEndId, null, state, msg)
}
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/db/Database.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/db/Database.kt
@@ -22,6 +22,7 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import org.slf4j.LoggerFactory
import tech.libeufin.common.TalerAmount
+import tech.libeufin.common.TransferStatusState
import tech.libeufin.common.IbanPayto
import tech.libeufin.common.db.DatabaseConfig
import tech.libeufin.common.db.DbPool
@@ -47,6 +48,13 @@ data class InitiatedPayment(
val endToEndId: String
)
+enum class StatusUpdate {
+ pending,
+ transient_failure,
+ permanent_failure,
+ success
+}
+
/** Outgoing transactions and batches submission status */
enum class SubmissionState {
// Initiated but not yet submitted
@@ -58,12 +66,23 @@ enum class SubmissionState {
// Definitive failure, will never succeed
permanent_failure,
// Definitive success, booked and settled
- success ;
+ success,
+ // Late failure after a success, happens when a payment is returned
+ late_failure;
companion object {
- val SETTLED = listOf(SubmissionState.success, SubmissionState.permanent_failure)
+ val SETTLED = listOf(SubmissionState.success, SubmissionState.permanent_failure, SubmissionState.late_failure)
val PENDING = listOf(SubmissionState.unsubmitted, SubmissionState.pending)
}
+
+ fun toTransferStatus(): TransferStatusState {
+ return when (this) {
+ SubmissionState.unsubmitted, SubmissionState.pending -> TransferStatusState.pending
+ SubmissionState.transient_failure -> TransferStatusState.transient_failure
+ SubmissionState.permanent_failure -> TransferStatusState.permanent_failure
+ SubmissionState.success, SubmissionState.late_failure -> TransferStatusState.success
+ }
+ }
}
/** Collects database connection steps and any operation on the Nexus tables */
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/db/ExchangeDAO.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/db/ExchangeDAO.kt
@@ -159,12 +159,7 @@ class ExchangeDAO(private val db: Database) {
setLong(1, id)
oneOrNull {
TransferStatus(
- status = when (it.getEnum<SubmissionState>("status")) {
- SubmissionState.unsubmitted, SubmissionState.pending -> TransferStatusState.pending
- SubmissionState.transient_failure -> TransferStatusState.transient_failure
- SubmissionState.permanent_failure -> TransferStatusState.permanent_failure
- SubmissionState.success -> TransferStatusState.success
- },
+ status = it.getEnum<SubmissionState>("status").toTransferStatus(),
status_msg = it.getString("status_msg"),
amount = it.getAmount("amount", db.bankCurrency),
origin_exchange_url = it.getString("exchange_base_url"),
@@ -216,12 +211,7 @@ class ExchangeDAO(private val db: Database) {
) {
TransferListStatus(
row_id = it.getLong("initiated_outgoing_transaction_id"),
- status = when (it.getEnum<SubmissionState>("status")) {
- SubmissionState.unsubmitted, SubmissionState.pending -> TransferStatusState.pending
- SubmissionState.transient_failure -> TransferStatusState.transient_failure
- SubmissionState.permanent_failure -> TransferStatusState.permanent_failure
- SubmissionState.success -> TransferStatusState.success
- },
+ status = it.getEnum<SubmissionState>("status").toTransferStatus(),
amount = it.getAmount("amount", db.bankCurrency),
credit_account = it.getString("credit_payto"),
timestamp = it.getTalerTimestamp("initiation_time"),
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/db/InitiatedDAO.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/db/InitiatedDAO.kt
@@ -240,75 +240,27 @@ class InitiatedDAO(private val db: Database) {
}
/** Register payment status [state] with [msg] for batch [msgId] */
- suspend fun batchStatusUpdate(msgId: String, state: SubmissionState, msg: String?) = db.serializableTransaction { tx ->
- // Update unsettled batch status
- val batchId = tx.withStatement(
- """
- UPDATE initiated_outgoing_batches
- SET status = ?::submission_state, status_msg = ?
- WHERE message_id = ? AND $UNSETTLED_FILTER
- RETURNING initiated_outgoing_batch_id
- """
- ) {
- setString(1, state.name)
- setString(2, msg)
- setString(3, msgId)
- oneOrNull { it.getLong("initiated_outgoing_batch_id") }
- }
- // Update unsettled batch's transaction status
- if (batchId != null) {
- // When a batch succeed it doesn't mean that individual transaction also succeed
- val txState = if (state == SubmissionState.success) {
- SubmissionState.pending
- } else {
- state
- }
- tx.withStatement(
- """
- UPDATE initiated_outgoing_transactions
- SET status = ?::submission_state, status_msg = ?
- WHERE initiated_outgoing_batch_id = ? AND $UNSETTLED_FILTER
- """
- ) {
- setString(1, txState.name)
- setString(2, msg)
- setLong(3, batchId)
- execute()
- }
- }
+ suspend fun batchStatusUpdate(msgId: String, state: StatusUpdate, msg: String?) = db.serializable(
+ "SELECT FROM batch_status_update(?,?::submission_state,?)"
+ ) {
+ setString(1, msgId)
+ setString(2, state.name)
+ setString(3, msg)
+ execute()
}
/** Register payment status [state] with [msg] for transaction [endToEndId] in batch [msgId] */
- suspend fun txStatusUpdate(endToEndId: String, msgId: String?, state: SubmissionState, msg: String) = db.serializableTransaction { tx ->
- // Update unsettled transaction status
- tx.withStatement(
- """
- UPDATE initiated_outgoing_transactions
- SET status = ?::submission_state, status_msg = ?
- WHERE end_to_end_id = ? AND $UNSETTLED_FILTER
- """
- ) {
- setString(1, state.name)
- setString(2, msg)
- setString(3, endToEndId)
- execute()
- }
- // Update unsettled batch status
- if (msgId != null) {
- tx.withStatement(
- """
- UPDATE initiated_outgoing_batches
- SET status = 'success', status_msg = NULL
- WHERE message_id = ? AND $UNSETTLED_FILTER
- """
- ) {
- setString(1, msgId)
- execute()
- }
- }
+ suspend fun txStatusUpdate(endToEndId: String, msgId: String?, state: StatusUpdate, msg: String) = db.serializable(
+ "SELECT FROM tx_status_update(?,?,?::submission_state,?)"
+ ) {
+ setString(1, endToEndId)
+ setString(2, msgId)
+ setString(3, state.name)
+ setString(4, msg)
+ execute()
}
- /** Unsettled intiaited payment in batch [msgId] */
+ /** Unsettled initiated payment in batch [msgId] */
suspend fun unsettledTxInBatch(msgId: String, executionTime: Instant) = db.serializable(
"""
SELECT end_to_end_id
diff --git a/nexus/src/test/kotlin/DatabaseTest.kt b/nexus/src/test/kotlin/DatabaseTest.kt
@@ -497,14 +497,16 @@ class PaymentInitiationsTest {
// Payment & batch status
test { batchId ->
checkBatch(batchId, SubmissionState.unsubmitted, null)
- db.initiated.batchStatusUpdate("BATCH", SubmissionState.pending, "progress")
+ db.initiated.batchStatusUpdate("BATCH", StatusUpdate.pending, "progress")
checkBatch(batchId, SubmissionState.pending, "progress")
- db.initiated.txStatusUpdate("TX_SETTLED", null, SubmissionState.success, "success")
+ db.initiated.txStatusUpdate("TX_SETTLED", null, StatusUpdate.success, "success")
checkPart(batchId, SubmissionState.pending, "progress", SubmissionState.pending, "progress", SubmissionState.success, "success")
- db.initiated.batchStatusUpdate("BATCH", SubmissionState.transient_failure, "waiting")
+ db.initiated.batchStatusUpdate("BATCH", StatusUpdate.transient_failure, "waiting")
checkPart(batchId, SubmissionState.transient_failure, "waiting", SubmissionState.transient_failure, "waiting", SubmissionState.success, "success")
- db.initiated.txStatusUpdate("TX", "BATCH", SubmissionState.permanent_failure, "failure")
+ db.initiated.txStatusUpdate("TX", "BATCH", StatusUpdate.permanent_failure, "failure")
checkPart(batchId, SubmissionState.success, null, SubmissionState.permanent_failure, "failure", SubmissionState.success, "success")
+ db.initiated.txStatusUpdate("TX_SETTLED", "BATCH", StatusUpdate.permanent_failure, "late")
+ checkPart(batchId, SubmissionState.success, null, SubmissionState.permanent_failure, "failure", SubmissionState.late_failure, "late")
}
// Registration
@@ -520,8 +522,8 @@ class PaymentInitiationsTest {
db.initiated.batchSubmissionSuccess(42, Instant.now(), "ORDER_X")
db.initiated.batchSubmissionFailure(42, Instant.now(), null)
db.initiated.orderStep("ORDER_X", "msg")
- db.initiated.batchStatusUpdate("BATCH_X", SubmissionState.success, null)
- db.initiated.txStatusUpdate("TX_X", "BATCH_X", SubmissionState.success, "msg")
+ db.initiated.batchStatusUpdate("BATCH_X", StatusUpdate.success, null)
+ db.initiated.txStatusUpdate("TX_X", "BATCH_X", StatusUpdate.success, "msg")
assertNull(db.initiated.orderSuccess("ORDER_X"))
assertNull(db.initiated.orderFailure("ORDER_X"))
}
diff --git a/nexus/src/test/kotlin/Iso20022Test.kt b/nexus/src/test/kotlin/Iso20022Test.kt
@@ -242,7 +242,20 @@ class Iso20022Test {
OutgoingBatch(
msgId = "BATCH_MANY_SUCCESS",
executionTime = dateToInstant("2024-09-20"),
- )
+ ),
+ OutgoingPayment(
+ endToEndId = "KLJJ28S1LVNDK1R2HCHLN884M7EKM5XGM5",
+ msgId = "BATCH_SINGLE_RETURN",
+ amount = TalerAmount("EUR:0.42"),
+ subject = "This should fail because bad iban",
+ executionTime = dateToInstant("2024-09-23"),
+ creditorPayto = ibanPayto("DE18500105173385245163", "John Smith")
+ ),
+ OutgoingReversal(
+ endToEndId = "KLJJ28S1LVNDK1R2HCHLN884M7EKM5XGM5",
+ reason = "IncorrectAccountNumber 'Format of the account number specified is not correct' - 'IBAN fehlerhaft und ungültig'",
+ executionTime = dateToInstant("2024-09-24")
+ ),
)
)
}
diff --git a/nexus/src/test/kotlin/RegistrationTest.kt b/nexus/src/test/kotlin/RegistrationTest.kt
@@ -262,6 +262,9 @@ class RegistrationTest {
"BATCH_SINGLE_FAILURE" to listOf(
genInitPay("DAFC3NEE4T48WVC560T76ABA2C"),
),
+ "BATCH_SINGLE_RETURN" to listOf(
+ genInitPay("KLJJ28S1LVNDK1R2HCHLN884M7EKM5XGM5"),
+ ),
"BATCH_MANY_SUCCESS" to listOf(
genInitPay("IVMIGCUIE7Q7VOF73R8GU3KGRYBZPAYC5V"),
genInitPay("CDFN7I4FVIZ848DGDQ35DZ2K49H9EWXGAW"),
@@ -300,6 +303,9 @@ class RegistrationTest {
"BATCH_SINGLE_FAILURE" to Pair(SubmissionState.pending, mapOf( // TODO success
"DAFC3NEE4T48WVC560T76ABA2C" to SubmissionState.pending, // TODO failure
)),
+ "BATCH_SINGLE_RETURN" to Pair(SubmissionState.success, mapOf(
+ "KLJJ28S1LVNDK1R2HCHLN884M7EKM5XGM5" to SubmissionState.late_failure,
+ )),
"BATCH_MANY_SUCCESS" to Pair(SubmissionState.success, mapOf(
"IVMIGCUIE7Q7VOF73R8GU3KGRYBZPAYC5V" to SubmissionState.success,
"CDFN7I4FVIZ848DGDQ35DZ2K49H9EWXGAW" to SubmissionState.success,
@@ -376,6 +382,13 @@ class RegistrationTest {
creditorPayto = ibanPayto("CH4189144589712575493", "Test")
),
OutgoingPayment(
+ endToEndId = "KLJJ28S1LVNDK1R2HCHLN884M7EKM5XGM5",
+ amount = TalerAmount("EUR:0.42"),
+ subject = "This should fail because bad iban",
+ executionTime = dateToInstant("2024-09-23"),
+ creditorPayto = ibanPayto("DE18500105173385245163", "John Smith")
+ ),
+ OutgoingPayment(
endToEndId = "27SK3166EG36SJ7VP7VFYP0MW8",
amount = TalerAmount("EUR:44"),
subject = "init payment",
diff --git a/testbench/src/main/kotlin/Main.kt b/testbench/src/main/kotlin/Main.kt
@@ -30,7 +30,7 @@ import io.ktor.client.engine.cio.*
import io.ktor.http.*
import kotlinx.coroutines.runBlocking
import kotlinx.serialization.Serializable
-import tech.libeufin.common.ANSI
+import tech.libeufin.common.*
import tech.libeufin.nexus.*
import tech.libeufin.nexus.cli.*
import java.time.Instant
@@ -132,7 +132,7 @@ class Cli : CliktCommand() {
val dummyPaytos = mapOf(
"CHF" to "payto://iban/CH4189144589712575493?receiver-name=John%20Smith",
- "EUR" to "payto://iban/FR6812739000706478491641U13?receiver-name=John%20Smith"
+ "EUR" to "payto://iban/DE18500105173385245162?receiver-name=John%20Smith"
)
val dummyPayto = requireNotNull(dummyPaytos[currency]) {
"Missing dummy payto for $currency"
@@ -142,7 +142,7 @@ class Cli : CliktCommand() {
val recoverDoc = "report statement notification"
runBlocking {
step("Init ${kind.name}")
-
+
assert(nexusCmd.run("dbinit $flags"))
val cmds = buildMap {
@@ -182,13 +182,13 @@ class Cli : CliktCommand() {
Unit
})
put("tx", suspend {
- step("Initiate one new transaction")
+ step("Initiate a new transaction")
val now = Instant.now()
nexusCmd.run("initiate-payment $flags --amount=$currency:0.1 --subject \"single $now\" \"$payto\"")
Unit
})
put("txs", suspend {
- step("Submit many transaction")
+ step("Initiate four new transactions")
val now = Instant.now()
repeat(4) {
nexusCmd.run("initiate-payment $flags --amount=$currency:${(10.0+it)/100} --subject \"multi $it $now\" \"$payto\"")
@@ -197,20 +197,23 @@ class Cli : CliktCommand() {
put("tx-bad-name", suspend {
val badPayto = URLBuilder().takeFrom(payto)
badPayto.parameters["receiver-name"] = "John Smith"
- step("Submit new transaction with a bad name")
- nexusCmd.run("initiate-payment $flags \"$badPayto&amount=$currency:0.21&message=This%20should%20fail%20because%20bad%20name\"")
+ step("Initiate a new transaction with a bad name")
+ val now = Instant.now()
+ nexusCmd.run("initiate-payment $flags --amount=$currency:0.21 --subject \"bad name $now\" \"$badPayto\"")
Unit
})
put("tx-bad-iban", suspend {
- val badPayto = URLBuilder().takeFrom("payto://iban/DE18500105173385245162")
+ val badPayto = URLBuilder().takeFrom("payto://iban/XX18500105173385245165")
badPayto.parameters["receiver-name"] = "John Smith"
- step("Submit new transaction to a bad IBAN")
- nexusCmd.run("initiate-payment $flags \"$badPayto&amount=$currency:0.22&message=This%20should%20fail%20because%20bad%20iban\"")
+ step("Initiate a new transaction to a bad IBAN")
+ val now = Instant.now()
+ nexusCmd.run("initiate-payment $flags --amount=$currency:0.22 --subject \"bad iban $now\" \"$badPayto\"")
Unit
})
- put("tx-dummy", suspend {
- step("Submit new transaction to a dummy IBAN")
- nexusCmd.run("initiate-payment $flags \"$dummyPayto&amount=$currency:0.23&message=This%20should%20fail%20because%20dummy\"")
+ put("tx-dummy-iban", suspend {
+ step("Initiate a new transaction to a dummy IBAN")
+ val now = Instant.now()
+ nexusCmd.run("initiate-payment $flags --amount=$currency:0.23 --subject \"dummy iban $now\" \"$dummyPayto\"")
Unit
})
put("tx-check", "Check transaction semantic", "testing tx-check $flags")