commit 4b3cf170c669079f644c33b809c528e780b1abd7
parent b490ddbec8a471d97370d2a20b92cdfd558533a0
Author: Antoine A <>
Date: Tue, 27 May 2025 17:33:50 +0200
nexus: add bounce reason to the bounce subject
Diffstat:
8 files changed, 34 insertions(+), 25 deletions(-)
diff --git a/common/src/main/kotlin/Subject.kt b/common/src/main/kotlin/Subject.kt
@@ -88,7 +88,7 @@ private val ALPHA_NUMBERIC_PATTERN = Regex("[0-9a-zA-Z]*")
**/
fun parseIncomingSubject(subject: String?): IncomingSubject {
if (subject == null || subject.isEmpty()) {
- throw Exception("Missing subject")
+ throw Exception("missing subject")
}
/** Parse an incoming subject */
@@ -150,7 +150,7 @@ fun parseIncomingSubject(subject: String?): IncomingSubject {
if (best != null) {
if (best.subject is IncomingSubject.AdminBalanceAdjust) {
if (other.subject !is IncomingSubject.AdminBalanceAdjust) {
- throw Exception("Found multiple subject kind")
+ throw Exception("found multiple subject kind")
}
} else if (other.quality > best.quality // We prefer high quality keys
|| ( // We prefer prefixed keys over reserve keys
@@ -166,7 +166,7 @@ fun parseIncomingSubject(subject: String?): IncomingSubject {
other.subject.type == IncomingType.reserve
))
{
- throw Exception("Found multiple reserve public key")
+ throw Exception("found multiple reserve public key")
}
} else {
best = other
@@ -175,11 +175,11 @@ fun parseIncomingSubject(subject: String?): IncomingSubject {
}
}
- return best?.subject ?: throw Exception("Missing reserve public key")
+ return best?.subject ?: throw Exception("missing reserve public key")
}
/** Extract the reserve public key from an incoming Taler transaction subject */
fun parseOutgoingSubject(subject: String): Pair<ShortHashCode, BaseURL> {
- val (wtid, baseUrl) = subject.splitOnce(" ") ?: throw Exception("Malformed outgoing subject")
+ val (wtid, baseUrl) = subject.splitOnce(" ") ?: throw Exception("malformed outgoing subject")
return Pair(EddsaPublicKey(wtid), BaseURL.parse(baseUrl))
}
\ No newline at end of file
diff --git a/common/src/test/kotlin/SubjectTest.kt b/common/src/test/kotlin/SubjectTest.kt
@@ -91,7 +91,7 @@ class SubjectTest {
"$standard $other_standard",
"$mixed $other_mixed",
)) {
- assertFailsMsg("Found multiple reserve public key") {
+ assertFailsMsg("found multiple reserve public key") {
parseIncomingSubject(case)
}
}
@@ -111,7 +111,7 @@ class SubjectTest {
standard.substring(1), // Check fail if missing char
"2MZT6RS3RVB3B0E2RDMYW0YRA3Y0VPHYV0CYDE6XBB0YMPFXCEG0" // Check fail if not a valid key
)) {
- assertFailsMsg("Missing reserve public key") {
+ assertFailsMsg("missing reserve public key") {
parseIncomingSubject(case)
}
}
diff --git a/database-versioning/libeufin-conversion-setup.sql b/database-versioning/libeufin-conversion-setup.sql
@@ -95,6 +95,7 @@ LANGUAGE plpgsql AS $$
-- end with 34 random chars which is valid for EBICS (max 35 chars)
,upper(replace(gen_random_uuid()::text, '-', ''))
,now_date
+ ,'amount too small to be converted'
);
RETURN NULL;
END IF;
diff --git a/database-versioning/libeufin-nexus-procedures.sql b/database-versioning/libeufin-nexus-procedures.sql
@@ -332,6 +332,7 @@ CREATE FUNCTION register_and_bounce_incoming(
,IN in_bounce_amount taler_amount
,IN in_now_date INT8
,IN in_bounce_id TEXT
+ ,IN in_cause TEXT
-- Error status
,OUT out_talerable BOOLEAN
-- Success return
@@ -354,7 +355,7 @@ IF out_talerable THEN
RETURN;
END IF;
-- Bounce incoming transaction
-SELECT bounce.out_bounce_id INTO out_bounce_id FROM bounce_incoming(out_tx_id, in_bounce_amount, in_bounce_id, in_now_date) AS bounce;
+SELECT bounce.out_bounce_id INTO out_bounce_id FROM bounce_incoming(out_tx_id, in_bounce_amount, in_bounce_id, in_now_date, in_cause) AS bounce;
END $$;
CREATE FUNCTION bounce_incoming(
@@ -362,6 +363,7 @@ CREATE FUNCTION bounce_incoming(
,IN in_bounce_amount taler_amount
,IN in_bounce_id TEXT
,IN in_now_date INT8
+ ,IN in_cause TEXT
,OUT out_bounce_id TEXT
)
LANGUAGE plpgsql AS $$
@@ -393,7 +395,7 @@ IF NOT FOUND THEN
,end_to_end_id
) VALUES (
in_bounce_amount
- ,'bounce: ' || local_bank_id
+ ,'bounce ' || local_bank_id || ': ' || in_cause
,payto_uri
,in_now_date
,in_bounce_id
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/cli/EbicsFetch.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/cli/EbicsFetch.kt
@@ -115,7 +115,7 @@ suspend fun registerIncomingPayment(
logger.debug(fmt)
}
}
- suspend fun bounce(msg: String) {
+ suspend fun bounce(cause: String) {
if (payment.id == null) {
logger.debug("{} ignored: missing bank ID", payment)
return
@@ -124,20 +124,20 @@ suspend fun registerIncomingPayment(
AccountType.exchange -> {
if (payment.executionTime < cfg.ignoreBouncesBefore) {
val res = db.payment.registerIncoming(payment)
- logRes(res, suffix = "ignored bounce: $msg")
+ logRes(res, suffix = "ignored bounce: $cause")
} else {
var bounceAmount = payment.amount
if (payment.creditFee != null && cfg.bounceDeduceFee) {
if (payment.creditFee > bounceAmount) {
val res = db.payment.registerIncoming(payment)
- logRes(res, suffix = "skip bounce (transfer fee higher than amount): $msg")
+ logRes(res, suffix = "skip bounce (transfer fee higher than amount): $cause")
return
}
bounceAmount -= payment.creditFee
}
if (cfg.bounceFee > bounceAmount) {
val res = db.payment.registerIncoming(payment)
- logRes(res, suffix = "skip bounce (bounce fee higher than amount): $msg")
+ logRes(res, suffix = "skip bounce (bounce fee higher than amount): $cause")
return
}
bounceAmount -= cfg.bounceFee
@@ -145,13 +145,14 @@ suspend fun registerIncomingPayment(
payment,
bounceAmount,
randEbicsId(),
- Instant.now()
+ Instant.now(),
+ cause
)
when (res) {
IncomingBounceRegistrationResult.Talerable ->
logger.warn("{} tried to bounce a talerable transaction", payment)
is IncomingBounceRegistrationResult.Success ->
- logRes(res, suffix=": $msg")
+ logRes(res, suffix=": $cause")
}
}
}
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/db/PaymentDAO.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/db/PaymentDAO.kt
@@ -84,11 +84,12 @@ class PaymentDAO(private val db: Database) {
payment: IncomingPayment,
bounceAmount: TalerAmount,
bounceEndToEndId: String,
- timestamp: Instant
+ timestamp: Instant,
+ cause: String
): IncomingBounceRegistrationResult = db.serializable(
"""
SELECT out_found, out_tx_id, out_completed, out_bounce_id, out_talerable
- FROM register_and_bounce_incoming((?,?)::taler_amount,(?,?)::taler_amount,?,?,?,?,?,?,(?,?)::taler_amount,?,?)
+ FROM register_and_bounce_incoming((?,?)::taler_amount,(?,?)::taler_amount,?,?,?,?,?,?,(?,?)::taler_amount,?,?, ?)
"""
) {
bind(payment.amount)
@@ -102,6 +103,7 @@ class PaymentDAO(private val db: Database) {
bind(bounceAmount)
bind(timestamp)
bind(bounceEndToEndId)
+ bind(cause)
one {
if (it.getBoolean("out_talerable")) {
IncomingBounceRegistrationResult.Talerable
diff --git a/nexus/src/test/kotlin/DatabaseTest.kt b/nexus/src/test/kotlin/DatabaseTest.kt
@@ -155,7 +155,8 @@ class IncomingPaymentsTest {
payment,
TalerAmount("KUDOS:2.53"),
id,
- Instant.now()
+ Instant.now(),
+ "manual bounce"
).run {
assertIs<IncomingBounceRegistrationResult.Success>(this)
assertTrue(new)
@@ -165,7 +166,8 @@ class IncomingPaymentsTest {
payment,
TalerAmount("KUDOS:2.53"),
randEbicsId(),
- Instant.now()
+ Instant.now(),
+ "manual bounce"
).run {
assertIs<IncomingBounceRegistrationResult.Success>(this)
assertFalse(new)
@@ -385,7 +387,8 @@ class IncomingPaymentsTest {
talerablePayment,
TalerAmount("KUDOS:2.53"),
randEbicsId(),
- Instant.now()
+ Instant.now(),
+ "manual bounce"
).run {
assertEquals(IncomingBounceRegistrationResult.Talerable, this)
}
diff --git a/nexus/src/test/kotlin/RegistrationTest.kt b/nexus/src/test/kotlin/RegistrationTest.kt
@@ -564,35 +564,35 @@ class RegistrationTest {
OutgoingPayment(
id = OutgoingId(null, null, null),
amount = TalerAmount("CHF:1"),
- subject = "bounce: 7371795e-62fa-42dd-93b7-da89cc120faa",
+ subject = "bounce 7371795e-62fa-42dd-93b7-da89cc120faa: missing reserve public key",
executionTime = Instant.EPOCH,
creditor = ibanPayto("CH7389144832588726658", "Mr Test")
),
OutgoingPayment(
id = OutgoingId(null, null, null),
amount = TalerAmount("CHF:0.5"),
- subject = "bounce: 50523424675.0001",
+ subject = "bounce 50523424675.0001: missing subject",
executionTime = Instant.EPOCH,
creditor = ibanPayto("CH7389144832588726658", "Grothoff Hans")
),
OutgoingPayment(
id = OutgoingId(null, null, null),
amount = TalerAmount("CHF:0.15"),
- subject = "bounce: f203fbb4-6e13-4c78-9b2a-d852fea6374a",
+ subject = "bounce f203fbb4-6e13-4c78-9b2a-d852fea6374a: missing reserve public key",
executionTime = Instant.EPOCH,
creditor = ibanPayto("CH7389144832588726658", "Grothoff Hans")
),
OutgoingPayment(
id = OutgoingId(null, null, null),
amount = TalerAmount("CHF:0.1"),
- subject = "bounce: 81b0d8c6-a677-4577-b75e-a639dcc03681",
+ subject = "bounce 81b0d8c6-a677-4577-b75e-a639dcc03681: missing reserve public key",
executionTime = Instant.EPOCH,
creditor = ibanPayto("CH7389144832588726658", "Grothoff Hans")
),
OutgoingPayment(
id = OutgoingId(null, null, null),
amount = TalerAmount("CHF:10"),
- subject = "bounce: F000787951230001",
+ subject = "bounce F000787951230001: restricted account",
executionTime = Instant.EPOCH,
creditor = ibanPayto("DE20500105172419259181", "Mr German")
)