commit 20dc138f25b2790edc07e4902791615d3cff4413
parent 21aee9fc8810296ccf5cbebb8d28e11030fa7d5d
Author: Antoine A <>
Date: Wed, 17 Jun 2026 12:57:53 +0200
nexus: update to match kotlin behavior with QR IBAN
Diffstat:
9 files changed, 119 insertions(+), 42 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
@@ -1312,7 +1312,7 @@ dependencies = [
[[package]]
name = "http-client"
version = "1.5.0"
-source = "git+git://git.taler.net/taler-rust.git/#9db73672548899f1626c9c8ab854f4b0b26a02bc"
+source = "git+git://git.taler.net/taler-rust.git/#20d95dafe4abacee0dc9f1ec1400958de632ef88"
dependencies = [
"compact_str",
"futures-util",
@@ -3463,7 +3463,7 @@ dependencies = [
[[package]]
name = "taler-api"
version = "1.5.0"
-source = "git+git://git.taler.net/taler-rust.git/#9db73672548899f1626c9c8ab854f4b0b26a02bc"
+source = "git+git://git.taler.net/taler-rust.git/#20d95dafe4abacee0dc9f1ec1400958de632ef88"
dependencies = [
"aws-lc-rs",
"axum",
@@ -3490,12 +3490,12 @@ dependencies = [
[[package]]
name = "taler-build"
version = "1.5.0"
-source = "git+git://git.taler.net/taler-rust.git/#9db73672548899f1626c9c8ab854f4b0b26a02bc"
+source = "git+git://git.taler.net/taler-rust.git/#20d95dafe4abacee0dc9f1ec1400958de632ef88"
[[package]]
name = "taler-common"
version = "1.5.0"
-source = "git+git://git.taler.net/taler-rust.git/#9db73672548899f1626c9c8ab854f4b0b26a02bc"
+source = "git+git://git.taler.net/taler-rust.git/#20d95dafe4abacee0dc9f1ec1400958de632ef88"
dependencies = [
"anyhow",
"aws-lc-rs",
@@ -3524,7 +3524,7 @@ dependencies = [
[[package]]
name = "taler-macros"
version = "1.5.0"
-source = "git+git://git.taler.net/taler-rust.git/#9db73672548899f1626c9c8ab854f4b0b26a02bc"
+source = "git+git://git.taler.net/taler-rust.git/#20d95dafe4abacee0dc9f1ec1400958de632ef88"
dependencies = [
"proc-macro2",
"quote",
@@ -3534,7 +3534,7 @@ dependencies = [
[[package]]
name = "taler-test-utils"
version = "1.5.0"
-source = "git+git://git.taler.net/taler-rust.git/#9db73672548899f1626c9c8ab854f4b0b26a02bc"
+source = "git+git://git.taler.net/taler-rust.git/#20d95dafe4abacee0dc9f1ec1400958de632ef88"
dependencies = [
"aws-lc-rs",
"axum",
diff --git a/libeufin-nexus/src/api.rs b/libeufin-nexus/src/api.rs
@@ -50,6 +50,7 @@ use taler_common::{
error_code::ErrorCode,
types::{
amount::{Amount, Currency},
+ iban::IBAN,
payto::{FullIbanPayto, IbanPayto, PaytoURI},
timestamp::TalerTimestamp,
},
@@ -70,19 +71,26 @@ pub struct NexusApi {
pub pool: sqlx::PgPool,
pub currency: Currency,
pub payto: FullIbanPayto,
+ pub qr_iban: Option<IBAN>,
pub in_channel: Sender<i64>,
pub taler_in_channel: Sender<i64>,
pub taler_out_channel: Sender<i64>,
}
impl NexusApi {
- pub async fn start(pool: sqlx::PgPool, payto: FullIbanPayto, currency: Currency) -> Self {
+ pub async fn start(
+ pool: sqlx::PgPool,
+ currency: Currency,
+ payto: FullIbanPayto,
+ qr_iban: Option<IBAN>,
+ ) -> Self {
let in_channel = Sender::new(0);
let taler_in_channel = Sender::new(0);
let taler_out_channel = Sender::new(0);
let tmp = Self {
pool: pool.clone(),
payto,
+ qr_iban,
currency,
in_channel: in_channel.clone(),
taler_in_channel: taler_in_channel.clone(),
@@ -261,10 +269,13 @@ impl PreparedTransfer for NexusApi {
async fn registration(&self, req: RegistrationRequest) -> ApiResult<RegistrationResponse> {
let creditor = IbanPayto::try_from(&req.credit_account)?;
- if *creditor != *self.payto {
+ let reference_number = if creditor.iban == self.payto.iban {
+ None
+ } else if Some(creditor.iban) == self.qr_iban {
+ Some(subject_fmt_qr_bill(req.authorization_pub.as_ref()))
+ } else {
return Err(failure_code(ErrorCode::BANK_UNKNOWN_CREDITOR));
- }
- let reference_number = subject_fmt_qr_bill(req.authorization_pub.as_ref());
+ };
match transfer_register(
&self.pool,
req.r#type.into(),
@@ -272,19 +283,20 @@ impl PreparedTransfer for NexusApi {
&req.authorization_pub,
&req.authorization_sig,
req.recurrent,
- &reference_number,
+ reference_number.as_deref(),
&Timestamp::now(),
)
.await?
{
RegistrationResult::Success => ApiResult::Ok(RegistrationResponse {
- subjects: vec![
+ subjects: vec![if let Some(qr_reference_number) = reference_number {
TransferSubject::QrBill {
credit_amount: req.credit_amount,
- qr_reference_number: reference_number,
- },
- simple_subject(req),
- ],
+ qr_reference_number,
+ }
+ } else {
+ simple_subject(req)
+ }],
expiration: TalerTimestamp::Never,
}),
RegistrationResult::ReservePubReuse => {
@@ -332,7 +344,7 @@ pub mod test {
pub async fn api_setup() -> (Router, PgPool) {
let (_, pool) = db_test_setup(CONFIG_SOURCE).await;
- let api = Arc::new(NexusApi::start(pool.clone(), EXCHANGE.clone(), CURR).await);
+ let api = Arc::new(NexusApi::start(pool.clone(), CURR, EXCHANGE.clone(), None).await);
let server = Router::new()
.wire_gateway(api.clone(), AuthMethod::None)
.prepared_transfer(api.clone())
diff --git a/libeufin-nexus/src/config.rs b/libeufin-nexus/src/config.rs
@@ -38,6 +38,7 @@ use taler_common::{
config::{Config, ValueErr},
types::{
amount::{Amount, Currency},
+ iban::IBAN,
payto::{BankID, FullIbanPayto},
utils::date_to_utc_ts,
},
@@ -188,6 +189,7 @@ impl NexusSubmitCfg {
pub struct NexusEbicsConfig {
pub account: FullIbanPayto,
+ pub qr_iban: Option<IBAN>,
pub dialect: Dialect,
}
@@ -203,6 +205,7 @@ impl NexusEbicsConfig {
&s.str("name").require()?,
),
dialect: s.parse("bank dialect", "bank_dialect").require()?,
+ qr_iban: s.parse("IBAN", "qr_iban").opt()?,
})
}
}
diff --git a/libeufin-nexus/src/db/payment.rs b/libeufin-nexus/src/db/payment.rs
@@ -732,7 +732,7 @@ mod test {
&auth_pub,
&auth_sig,
false,
- &reference_number,
+ None,
&Timestamp::now()
)
.await
@@ -780,7 +780,7 @@ mod test {
&auth_pub,
&auth_sig,
true,
- &reference_number,
+ Some(&reference_number),
&Timestamp::now()
)
.await
@@ -818,7 +818,7 @@ mod test {
&auth_pub,
&auth_sig,
true,
- &reference_number,
+ Some(&reference_number),
&Timestamp::now()
)
.await
@@ -856,7 +856,7 @@ mod test {
&auth_pub,
&auth_sig,
false,
- &reference_number,
+ Some(&reference_number),
&Timestamp::now()
)
.await
@@ -904,7 +904,7 @@ mod test {
&auth_pub,
&auth_sig,
true,
- &reference_number,
+ Some(&reference_number),
&Timestamp::now()
)
.await
@@ -942,7 +942,7 @@ mod test {
&auth_pub,
&auth_sig,
true,
- &reference_number,
+ Some(&reference_number),
&Timestamp::now()
)
.await
diff --git a/libeufin-nexus/src/db/transfer.rs b/libeufin-nexus/src/db/transfer.rs
@@ -39,7 +39,7 @@ pub async fn transfer_register(
auth_pub: &EddsaPublicKey,
auth_sig: &EddsaSignature,
recurrent: bool,
- reference_number: &str,
+ reference_number: Option<&str>,
timestamp: &Timestamp,
) -> sqlx::Result<RegistrationResult> {
serialized!(
diff --git a/libeufin-nexus/src/lib.rs b/libeufin-nexus/src/lib.rs
@@ -562,8 +562,16 @@ pub async fn run(cfg: &Config, cmd: Cmd) -> anyhow::Result<()> {
}
} else {
let pool = pool(&cfg.db_cfg).await?;
- let payto = &cfg.ebics()?.account;
- let api = Arc::new(NexusApi::start(pool, payto.clone(), cfg.currency).await);
+ let ebics_cfg = cfg.ebics()?;
+ let api = Arc::new(
+ NexusApi::start(
+ pool,
+ cfg.currency,
+ ebics_cfg.account.clone(),
+ ebics_cfg.qr_iban,
+ )
+ .await,
+ );
let mut server = Router::new();
if let Some(it) = cfg.wire_cfg {
server = server
diff --git a/libeufin-nexus/src/test.rs b/libeufin-nexus/src/test.rs
@@ -131,7 +131,7 @@ async fn prepare(db: &PgPool) -> String {
&key,
&sig,
false,
- &reference_number,
+ Some(&reference_number),
&Timestamp::now()
)
.await
diff --git a/libeufin-nexus/src/testing.rs b/libeufin-nexus/src/testing.rs
@@ -34,7 +34,7 @@ use taler_common::{
types::{
amount::Amount,
iban::{Country, IBAN},
- payto::TransferIbanPayto,
+ payto::{BankID, ParsedPayto, PaytoImpl},
},
};
use tracing::debug;
@@ -81,8 +81,20 @@ pub enum TestingCmd {
#[arg(long)]
subject: Option<CompactString>,
+ /// The payment reference, credit-payto 'ch-qrr' parameter takes precedence
+ #[arg(long)]
+ ch_qrr: Option<CompactString>,
+
+ /// The credited account IBAN payto URI
+ #[arg(long)]
+ credit_payto: Option<ParsedPayto<BankID>>,
+
/// The debited account IBAN payto URI
- payto: TransferIbanPayto,
+ #[arg(long)]
+ debit_payto: Option<ParsedPayto<BankID>>,
+
+ /// The debited account IBAN payto URI
+ payto: Option<ParsedPayto<BankID>>,
},
#[clap(subcommand)]
List(ListCmd),
@@ -133,20 +145,30 @@ impl TestingCmd {
amount,
credit_fee,
subject,
+ ch_qrr,
+ credit_payto,
+ debit_payto,
payto,
} => {
let cfg = NexusCfg::parse(cfg)?;
+ let ebics_cfg = cfg.ebics()?;
let db = pool(&cfg.db_cfg).await?;
- let subject = payto
- .subject
+ let amount = credit_payto
.as_ref()
- .or(subject.as_ref())
- .ok_or(anyhow!("Missing subject"))?;
- let amount = payto
- .amount
- .as_ref()
- .or(amount.as_ref())
+ .and_then(|p| p.amount)
+ .or_else(|| payto.as_ref().and_then(|p| p.amount))
+ .or(amount)
.ok_or(anyhow!("Missing amount"))?;
+ let subject = credit_payto
+ .as_ref()
+ .and_then(|p| p.subject.clone())
+ .or_else(|| payto.as_ref().and_then(|p| p.subject.clone()))
+ .or_else(|| subject.clone());
+ let reference = credit_payto
+ .as_ref()
+ .and_then(|p| p.ch_qrr.clone())
+ .or_else(|| payto.as_ref().and_then(|p| p.ch_qrr.clone()))
+ .or_else(|| ch_qrr.clone());
if cfg.currency != amount.currency {
bail!(
@@ -155,16 +177,48 @@ impl TestingCmd {
amount.currency
);
}
+
+ if let Some(ref credit_payto) = credit_payto {
+ if reference.is_some() {
+ if let Some(qr_iban) = ebics_cfg.qr_iban {
+ if credit_payto.iban != qr_iban {
+ bail!(
+ "Creditor must be the exchange QRR account expected {qr_iban} got {}",
+ credit_payto.iban
+ )
+ }
+ } else {
+ bail!("QR reference is not supported without a configured QR IBAN")
+ }
+ } else {
+ if credit_payto.iban != ebics_cfg.account.iban {
+ bail!(
+ "Creditor must be the exchange expected {} got {}",
+ ebics_cfg.account.iban,
+ credit_payto.iban
+ )
+ }
+ }
+ }
+
register_incoming(
&db,
&cfg.ingest()?,
&InTx {
id: InId::new(None, Some(rand_ebics_id()), None),
- amount: *amount,
+ amount,
credit_fee: credit_fee.unwrap_or(Amount::zero(&cfg.currency)),
- subject: Some(subject.clone().into_string()),
+ subject: reference.or(subject).map(|it| it.into_string()),
execution_time: Timestamp::now(),
- debtor: Some(payto.as_uri()),
+ debtor: Some(
+ debit_payto
+ .map(|it| it.as_uri())
+ .or(payto.map(|it| it.as_uri()))
+ .unwrap_or_else(|| {
+ IBAN::random(ebics_cfg.account.iban.country())
+ .as_full_uri("Testing Account")
+ }),
+ ),
},
)
.await?;
diff --git a/testbench/conf/integration.conf b/testbench/conf/integration.conf
@@ -24,8 +24,8 @@ USER_ID = myuser
PARTNER_ID = myorg
# Account information
-IBAN = myiban
-BIC = mybic
+IBAN = CH7789144474425692816
+BIC = SANDBOXX
NAME = myname
[libeufin-bankdb-postgres]