libeufin

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

model.rs (11014B)


      1 /*
      2 * This file is part of LibEuFin.
      3 * Copyright (C) 2026 Taler Systems S.A.
      4 
      5 * LibEuFin is free software; you can redistribute it and/or modify
      6 * it under the terms of the GNU Affero General Public License as
      7 * published by the Free Software Foundation; either version 3, or
      8 * (at your option) any later version.
      9 
     10 * LibEuFin is distributed in the hope that it will be useful, but
     11 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
     12 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General
     13 * Public License for more details.
     14 
     15 * You should have received a copy of the GNU Affero General Public
     16 * License along with LibEuFin; see the file COPYING.  If not, see
     17 * <http://www.gnu.org/licenses/>
     18 */
     19 
     20 use std::fmt::{Display, Write as _};
     21 
     22 use compact_str::{CompactString, ToCompactString as _};
     23 use jiff::Timestamp;
     24 use taler_common::types::{amount::Amount, payto::PaytoURI};
     25 use uuid::Uuid;
     26 
     27 /// ID for incoming transactions
     28 #[derive(Clone, PartialEq, Eq)]
     29 pub struct InId {
     30     /** ISO20022 UETR */
     31     pub uetr: Option<Uuid>,
     32     /// ISO20022 TxID
     33     pub tx_id: Option<CompactString>,
     34     /// ISO20022 AcctSvcrRef
     35     pub sref: Option<CompactString>,
     36 }
     37 
     38 impl InId {
     39     pub fn new(
     40         uetr: Option<Uuid>,
     41         tx_id: Option<CompactString>,
     42         acct_svcr_ref: Option<CompactString>,
     43     ) -> Self {
     44         assert!(uetr.is_some() || tx_id.is_some() || acct_svcr_ref.is_some());
     45         Self {
     46             uetr,
     47             tx_id,
     48             sref: acct_svcr_ref,
     49         }
     50     }
     51 
     52     pub fn r#ref(&self) -> CompactString {
     53         self.uetr
     54             .map(|e| e.to_compact_string())
     55             .or(self.tx_id.clone())
     56             .or(self.sref.clone())
     57             .expect("must be at least one ref")
     58     }
     59 }
     60 
     61 impl std::fmt::Display for InId {
     62     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
     63         f.write_char('(')?;
     64         let mut prepend = false;
     65         if let Some(uetr) = &self.uetr {
     66             write!(f, "uetr={uetr}")?;
     67             prepend = true;
     68         }
     69         if let Some(tx_id) = &self.tx_id {
     70             if prepend {
     71                 f.write_char(' ')?;
     72             }
     73             f.write_str("tx=")?;
     74             f.write_str(tx_id)?;
     75             prepend = true;
     76         }
     77         if let Some(acct_svcr_ref) = &self.sref {
     78             if prepend {
     79                 f.write_char(' ')?;
     80             }
     81             f.write_str("ref=")?;
     82             f.write_str(acct_svcr_ref)?;
     83         }
     84         f.write_char(')')?;
     85         Ok(())
     86     }
     87 }
     88 
     89 impl std::fmt::Debug for InId {
     90     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
     91         Display::fmt(&self, f)
     92     }
     93 }
     94 
     95 /// ID for outgoing transactions
     96 #[derive(Clone, PartialEq, Eq)]
     97 pub struct OutId {
     98     /// Unique msg ID generated by libeufin-nexus
     99     /// ISO20022 MessageId
    100     pub msg_id: Option<CompactString>,
    101     /// Unique end-to-end ID generated by libeufin-nexus
    102     /// ISO20022 EndToEndId or MessageId (retrocompatibility)
    103     pub e2e_id: Option<CompactString>,
    104     /// Unique end-to-end ID generated by the bank
    105     /// ISO20022 AcctSvcrRef
    106     pub sref: Option<CompactString>,
    107 }
    108 
    109 impl OutId {
    110     pub fn new(
    111         msg_id: Option<CompactString>,
    112         e2e_id: Option<CompactString>,
    113         acct_svcr_ref: Option<CompactString>,
    114     ) -> Self {
    115         Self {
    116             msg_id,
    117             e2e_id,
    118             sref: acct_svcr_ref,
    119         }
    120     }
    121 
    122     pub fn r#ref(&self) -> CompactString {
    123         self.e2e_id
    124             .clone()
    125             .or(self.sref.clone())
    126             .or(self.sref.clone())
    127             .expect("must be at least one ref")
    128     }
    129 }
    130 
    131 impl std::fmt::Display for OutId {
    132     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
    133         f.write_char('(')?;
    134         let mut prepend = false;
    135         if let Some(msg_id) = &self.msg_id
    136             && self.msg_id != self.e2e_id
    137         {
    138             f.write_str("msg=")?;
    139             f.write_str(msg_id)?;
    140             prepend = true;
    141         }
    142         if let Some(end_to_end_id) = &self.e2e_id {
    143             if prepend {
    144                 f.write_char(' ')?;
    145             }
    146             f.write_str("e2e=")?;
    147             f.write_str(end_to_end_id)?;
    148             prepend = true;
    149         }
    150         if let Some(acct_svcr_ref) = &self.sref {
    151             if prepend {
    152                 f.write_char(' ')?;
    153             }
    154             f.write_str("ref=")?;
    155             f.write_str(acct_svcr_ref)?;
    156         }
    157         f.write_char(')')?;
    158         Ok(())
    159     }
    160 }
    161 
    162 impl std::fmt::Debug for OutId {
    163     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
    164         Display::fmt(&self, f)
    165     }
    166 }
    167 
    168 /// ID for outgoing batches
    169 #[derive(Clone, PartialEq, Eq)]
    170 pub struct BatchId {
    171     /// Unique msg ID generated by libeufin-nexus
    172     /// ISO20022 MessageId
    173     pub msg_id: CompactString,
    174     /// Unique end-to-end ID generated by the bank
    175     /// ISO20022 AcctSvcrRef
    176     pub sref: Option<CompactString>,
    177 }
    178 
    179 impl BatchId {
    180     pub fn r#ref(&self) -> CompactString {
    181         self.msg_id.clone()
    182     }
    183 }
    184 
    185 impl std::fmt::Display for BatchId {
    186     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
    187         f.write_str("(msg=")?;
    188         f.write_str(&self.msg_id)?;
    189         if let Some(acct_svcr_ref) = &self.sref {
    190             f.write_str("ref=")?;
    191             f.write_str(acct_svcr_ref)?;
    192         }
    193         f.write_char(')')?;
    194         Ok(())
    195     }
    196 }
    197 
    198 impl std::fmt::Debug for BatchId {
    199     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
    200         Display::fmt(&self, f)
    201     }
    202 }
    203 
    204 /// ISO20022 incoming payment
    205 #[derive(Clone, PartialEq, Eq)]
    206 pub struct InTx {
    207     pub id: InId,
    208     pub amount: Amount,
    209     pub credit_fee: Amount,
    210     pub subject: Option<String>,
    211     pub execution_time: Timestamp,
    212     pub debtor: Option<PaytoURI>,
    213 }
    214 
    215 impl InTx {
    216     pub fn with_execution_time(self, execution_time: Timestamp) -> Self {
    217         Self {
    218             execution_time,
    219             ..self
    220         }
    221     }
    222 }
    223 
    224 impl Display for InTx {
    225     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
    226         let Self {
    227             id,
    228             amount,
    229             credit_fee,
    230             subject,
    231             execution_time,
    232             debtor,
    233         } = self;
    234         write!(f, "IN {execution_time} {amount}")?;
    235         if !credit_fee.is_zero() {
    236             write!(f, "-{credit_fee}")?;
    237         }
    238         write!(f, " {id}")?;
    239         if let Some(creditor) = debtor {
    240             write!(f, " creditor={creditor}")?;
    241         }
    242         if let Some(subject) = subject {
    243             write!(f, " subject='{subject}'")?;
    244         }
    245         Ok(())
    246     }
    247 }
    248 
    249 impl std::fmt::Debug for InTx {
    250     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
    251         Display::fmt(&self, f)
    252     }
    253 }
    254 
    255 /// ISO20022 outgoing payment
    256 #[derive(Clone, PartialEq, Eq)]
    257 pub struct OutTx {
    258     pub id: OutId,
    259     pub amount: Amount,
    260     pub debit_fee: Amount,
    261     pub subject: Option<String>,
    262     pub execution_time: Timestamp,
    263     pub creditor: Option<PaytoURI>,
    264 }
    265 
    266 impl OutTx {
    267     pub fn with_execution_time(self, execution_time: Timestamp) -> Self {
    268         Self {
    269             execution_time,
    270             ..self
    271         }
    272     }
    273 
    274     pub fn with_e2e_id(self, end_to_end_id: impl Into<CompactString>) -> Self {
    275         Self {
    276             id: OutId {
    277                 e2e_id: Some(end_to_end_id.into()),
    278                 ..self.id
    279             },
    280             ..self
    281         }
    282     }
    283 
    284     pub fn with_msg_id(self, msg_id: impl Into<CompactString>) -> Self {
    285         Self {
    286             id: OutId {
    287                 msg_id: Some(msg_id.into()),
    288                 ..self.id
    289             },
    290             ..self
    291         }
    292     }
    293 }
    294 
    295 impl Display for OutTx {
    296     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
    297         let Self {
    298             id,
    299             amount,
    300             debit_fee,
    301             subject,
    302             execution_time,
    303             creditor,
    304         } = self;
    305         write!(f, "OUT {execution_time} {amount}")?;
    306         if !debit_fee.is_zero() {
    307             write!(f, "-{debit_fee}")?;
    308         }
    309         write!(f, " {id}")?;
    310         if let Some(creditor) = creditor {
    311             write!(f, " creditor={creditor}")?;
    312         }
    313         if let Some(subject) = subject {
    314             write!(f, " subject='{subject}'")?;
    315         }
    316         Ok(())
    317     }
    318 }
    319 
    320 impl std::fmt::Debug for OutTx {
    321     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
    322         Display::fmt(&self, f)
    323     }
    324 }
    325 
    326 /** ISO20022 outgoing batch */
    327 #[derive(Clone, PartialEq, Eq)]
    328 pub struct OutBatch {
    329     /** ISO20022 MessageId */
    330     pub msg_id: CompactString,
    331     pub execution_time: Timestamp,
    332 }
    333 
    334 impl Display for OutBatch {
    335     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
    336         let Self {
    337             msg_id,
    338             execution_time,
    339         } = self;
    340         // TODO fmt date
    341         write!(f, "BATCH {execution_time} {msg_id}")
    342     }
    343 }
    344 
    345 impl std::fmt::Debug for OutBatch {
    346     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
    347         Display::fmt(&self, f)
    348     }
    349 }
    350 
    351 /** ISO20022 outgoing reversal */
    352 #[derive(Clone, PartialEq, Eq)]
    353 pub struct OutReversal {
    354     /** ISO20022 EndToEndId */
    355     pub e2e_id: CompactString,
    356     /** ISO20022 MessageId */
    357     pub msg_id: Option<CompactString>,
    358     pub reason: String,
    359     pub execution_time: Timestamp,
    360 }
    361 
    362 impl Display for OutReversal {
    363     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
    364         let Self {
    365             e2e_id,
    366             msg_id,
    367             reason,
    368             execution_time,
    369         } = self;
    370         // TODO fmt date
    371         match msg_id {
    372             Some(msg_id) => write!(f, "BATCH {execution_time} {msg_id}.{e2e_id}: {reason}"),
    373             None => write!(f, "BATCH {execution_time} {e2e_id}: {reason}"),
    374         }
    375     }
    376 }
    377 
    378 impl std::fmt::Debug for OutReversal {
    379     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
    380         Display::fmt(&self, f)
    381     }
    382 }
    383 
    384 #[derive(Clone, PartialEq, Eq)]
    385 pub enum Tx {
    386     In(InTx),
    387     Out(OutTx),
    388     Batch(OutBatch),
    389     Reversal(OutReversal),
    390 }
    391 
    392 impl Tx {
    393     pub fn execution_time(&self) -> &Timestamp {
    394         match self {
    395             Tx::In(InTx { execution_time, .. })
    396             | Tx::Out(OutTx { execution_time, .. })
    397             | Tx::Batch(OutBatch { execution_time, .. })
    398             | Tx::Reversal(OutReversal { execution_time, .. }) => execution_time,
    399         }
    400     }
    401 }
    402 
    403 impl Display for Tx {
    404     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
    405         match self {
    406             Tx::In(incoming_payment) => incoming_payment.fmt(f),
    407             Tx::Out(outgoing_payment) => outgoing_payment.fmt(f),
    408             Tx::Batch(outgoing_batch) => outgoing_batch.fmt(f),
    409             Tx::Reversal(outgoing_reversal) => outgoing_reversal.fmt(f),
    410         }
    411     }
    412 }
    413 
    414 impl std::fmt::Debug for Tx {
    415     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
    416         Display::fmt(&self, f)
    417     }
    418 }