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 }