tap_ivms101/
message.rs

1//! IVMS 101 message data structures
2//!
3//! This module implements the top-level IVMS message structures including
4//! originator, beneficiary, and transaction data.
5
6use crate::error::{Error, Result};
7use crate::person::{LegalPerson, NaturalPerson};
8use crate::types::*;
9use serde::{Deserialize, Serialize};
10use tap_msg::utils::NameHashable;
11
12/// Person type enumeration
13#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
14#[serde(rename_all = "camelCase")]
15pub enum Person {
16    /// Natural person
17    NaturalPerson(NaturalPerson),
18    /// Legal person
19    LegalPerson(LegalPerson),
20}
21
22impl Person {
23    /// Validate the person data
24    pub fn validate(&self) -> Result<()> {
25        match self {
26            Person::NaturalPerson(person) => person.validate(),
27            Person::LegalPerson(person) => person.validate(),
28        }
29    }
30
31    /// Check if this is a natural person
32    pub fn is_natural_person(&self) -> bool {
33        matches!(self, Person::NaturalPerson(_))
34    }
35
36    /// Check if this is a legal person
37    pub fn is_legal_person(&self) -> bool {
38        matches!(self, Person::LegalPerson(_))
39    }
40
41    /// Get the full name for TAIP-12 hashing
42    pub fn get_full_name(&self) -> Option<String> {
43        match self {
44            Person::NaturalPerson(person) => person.name.get_full_name(),
45            Person::LegalPerson(person) => person.name.get_full_name(),
46        }
47    }
48}
49
50// Implement NameHashable for Person
51impl NameHashable for Person {}
52
53/// Originator information
54#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
55#[serde(rename_all = "camelCase")]
56pub struct Originator {
57    /// Originator persons (at least one required)
58    pub originator_persons: Vec<Person>,
59    /// Account numbers (optional)
60    #[serde(skip_serializing_if = "Option::is_none")]
61    pub account_numbers: Option<Vec<String>>,
62    /// BIC (optional)
63    #[serde(skip_serializing_if = "Option::is_none")]
64    pub bic: Option<BicCode>,
65}
66
67impl Originator {
68    /// Create a new originator
69    pub fn new(originator_persons: Vec<Person>) -> Self {
70        Self {
71            originator_persons,
72            account_numbers: None,
73            bic: None,
74        }
75    }
76
77    /// Validate the originator data
78    pub fn validate(&self) -> Result<()> {
79        if self.originator_persons.is_empty() {
80            return Err(Error::MissingRequiredField(
81                "At least one originator person is required".to_string(),
82            ));
83        }
84
85        for person in &self.originator_persons {
86            person.validate()?;
87        }
88
89        if let Some(ref bic) = self.bic {
90            if bic.len() != 8 && bic.len() != 11 {
91                return Err(Error::InvalidBic(format!(
92                    "BIC must be 8 or 11 characters: {}",
93                    bic
94                )));
95            }
96        }
97
98        Ok(())
99    }
100}
101
102/// Beneficiary information
103#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
104#[serde(rename_all = "camelCase")]
105pub struct Beneficiary {
106    /// Beneficiary persons (at least one required)
107    pub beneficiary_persons: Vec<Person>,
108    /// Account numbers (optional)
109    #[serde(skip_serializing_if = "Option::is_none")]
110    pub account_numbers: Option<Vec<String>>,
111}
112
113impl Beneficiary {
114    /// Create a new beneficiary
115    pub fn new(beneficiary_persons: Vec<Person>) -> Self {
116        Self {
117            beneficiary_persons,
118            account_numbers: None,
119        }
120    }
121
122    /// Validate the beneficiary data
123    pub fn validate(&self) -> Result<()> {
124        if self.beneficiary_persons.is_empty() {
125            return Err(Error::MissingRequiredField(
126                "At least one beneficiary person is required".to_string(),
127            ));
128        }
129
130        for person in &self.beneficiary_persons {
131            person.validate()?;
132        }
133
134        Ok(())
135    }
136}
137
138/// Originating VASP information
139#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
140#[serde(rename_all = "camelCase")]
141pub struct OriginatingVasp {
142    /// Originating VASP person
143    pub originating_vasp: Person,
144    /// BIC (optional)
145    #[serde(skip_serializing_if = "Option::is_none")]
146    pub bic: Option<BicCode>,
147}
148
149impl OriginatingVasp {
150    /// Create a new originating VASP
151    pub fn new(originating_vasp: Person) -> Self {
152        Self {
153            originating_vasp,
154            bic: None,
155        }
156    }
157
158    /// Validate the originating VASP data
159    pub fn validate(&self) -> Result<()> {
160        self.originating_vasp.validate()?;
161
162        if let Some(ref bic) = self.bic {
163            if bic.len() != 8 && bic.len() != 11 {
164                return Err(Error::InvalidBic(format!(
165                    "BIC must be 8 or 11 characters: {}",
166                    bic
167                )));
168            }
169        }
170
171        Ok(())
172    }
173}
174
175/// Beneficiary VASP information
176#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
177#[serde(rename_all = "camelCase")]
178pub struct BeneficiaryVasp {
179    /// Beneficiary VASP person
180    pub beneficiary_vasp: Person,
181}
182
183impl BeneficiaryVasp {
184    /// Create a new beneficiary VASP
185    pub fn new(beneficiary_vasp: Person) -> Self {
186        Self { beneficiary_vasp }
187    }
188
189    /// Validate the beneficiary VASP data
190    pub fn validate(&self) -> Result<()> {
191        self.beneficiary_vasp.validate()
192    }
193}
194
195/// Transaction data
196#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
197#[serde(rename_all = "camelCase")]
198pub struct TransactionData {
199    /// Transaction amount
200    pub amount: String,
201    /// Currency code (ISO 4217)
202    pub currency: CurrencyCode,
203    /// Transaction direction
204    pub direction: TransactionDirection,
205    /// Payment type (optional)
206    #[serde(skip_serializing_if = "Option::is_none")]
207    pub payment_type: Option<PaymentType>,
208    /// Transaction identifier
209    pub transaction_identifier: String,
210    /// Transaction datetime (ISO 8601)
211    pub transaction_datetime: String,
212    /// Transaction network (optional)
213    #[serde(skip_serializing_if = "Option::is_none")]
214    pub transaction_network: Option<TransactionNetworkType>,
215    /// Transaction hash (optional)
216    #[serde(skip_serializing_if = "Option::is_none")]
217    pub transaction_hash: Option<String>,
218}
219
220impl TransactionData {
221    /// Validate the transaction data
222    pub fn validate(&self) -> Result<()> {
223        if self.amount.is_empty() {
224            return Err(Error::MissingRequiredField(
225                "Transaction amount is required".to_string(),
226            ));
227        }
228
229        if self.currency.len() != 3 {
230            return Err(Error::InvalidCurrencyCode(format!(
231                "Currency code must be 3 characters: {}",
232                self.currency
233            )));
234        }
235
236        if self.transaction_identifier.is_empty() {
237            return Err(Error::MissingRequiredField(
238                "Transaction identifier is required".to_string(),
239            ));
240        }
241
242        // Validate datetime format
243        if chrono::DateTime::parse_from_rfc3339(&self.transaction_datetime).is_err() {
244            return Err(Error::InvalidDate(format!(
245                "Invalid transaction datetime format: {}",
246                self.transaction_datetime
247            )));
248        }
249
250        Ok(())
251    }
252}
253
254/// Main IVMS 101 message structure
255#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
256#[serde(rename_all = "camelCase")]
257pub struct IvmsMessage {
258    /// Originator information
259    pub originator: Originator,
260    /// Beneficiary information
261    pub beneficiary: Beneficiary,
262    /// Originating VASP information
263    pub originating_vasp: OriginatingVasp,
264    /// Beneficiary VASP information (optional)
265    #[serde(skip_serializing_if = "Option::is_none")]
266    pub beneficiary_vasp: Option<BeneficiaryVasp>,
267    /// Transaction data
268    pub transaction: TransactionData,
269}
270
271impl IvmsMessage {
272    /// Create a new IVMS message
273    pub fn new(
274        originator: Originator,
275        beneficiary: Beneficiary,
276        originating_vasp: OriginatingVasp,
277        transaction: TransactionData,
278    ) -> Self {
279        Self {
280            originator,
281            beneficiary,
282            originating_vasp,
283            beneficiary_vasp: None,
284            transaction,
285        }
286    }
287
288    /// Validate the entire IVMS message
289    pub fn validate(&self) -> Result<()> {
290        let mut issues = Vec::new();
291
292        if let Err(e) = self.originator.validate() {
293            issues.push(format!("Originator: {}", e));
294        }
295
296        if let Err(e) = self.beneficiary.validate() {
297            issues.push(format!("Beneficiary: {}", e));
298        }
299
300        if let Err(e) = self.originating_vasp.validate() {
301            issues.push(format!("Originating VASP: {}", e));
302        }
303
304        if let Some(ref beneficiary_vasp) = self.beneficiary_vasp {
305            if let Err(e) = beneficiary_vasp.validate() {
306                issues.push(format!("Beneficiary VASP: {}", e));
307            }
308        }
309
310        if let Err(e) = self.transaction.validate() {
311            issues.push(format!("Transaction: {}", e));
312        }
313
314        if !issues.is_empty() {
315            return Err(Error::ValidationFailed { issues });
316        }
317
318        Ok(())
319    }
320
321    /// Convert to JSON
322    pub fn to_json(&self) -> Result<String> {
323        serde_json::to_string(self).map_err(Error::from)
324    }
325
326    /// Convert to pretty JSON
327    pub fn to_json_pretty(&self) -> Result<String> {
328        serde_json::to_string_pretty(self).map_err(Error::from)
329    }
330
331    /// Parse from JSON
332    pub fn from_json(json: &str) -> Result<Self> {
333        serde_json::from_str(json).map_err(Error::from)
334    }
335}