swift_mt_message/messages/
mt202.rs

1use crate::errors::{ParseError, SwiftValidationError};
2use crate::fields::*;
3use crate::parser::MessageParser;
4use crate::parser::utils::*;
5use serde::{Deserialize, Serialize};
6
7/// Sequence B - Cover Payment Details (MT202 COV)
8#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
9pub struct MT202SequenceB {
10    /// Ordering Customer (Field 50)
11    #[serde(flatten, skip_serializing_if = "Option::is_none")]
12    pub ordering_customer: Option<Field50OrderingCustomerAFK>,
13
14    /// Ordering Institution (Field 52)
15    #[serde(flatten, skip_serializing_if = "Option::is_none")]
16    pub ordering_institution: Option<Field52OrderingInstitution>,
17
18    /// Intermediary (Field 56)
19    #[serde(flatten, skip_serializing_if = "Option::is_none")]
20    pub intermediary: Option<Field56Intermediary>,
21
22    /// Account With Institution (Field 57)
23    #[serde(flatten, skip_serializing_if = "Option::is_none")]
24    pub account_with_institution: Option<Field57AccountWithInstitution>,
25
26    /// Beneficiary Customer (Field 59)
27    #[serde(flatten, skip_serializing_if = "Option::is_none")]
28    pub beneficiary_customer: Option<Field59>,
29
30    /// Remittance Information (Field 70)
31    #[serde(rename = "70", skip_serializing_if = "Option::is_none")]
32    pub remittance_information: Option<Field70>,
33
34    /// Sender to Receiver Information (Field 72)
35    #[serde(rename = "72", skip_serializing_if = "Option::is_none")]
36    pub sender_to_receiver_information: Option<Field72>,
37
38    /// Currency/Instructed Amount (Field 33B)
39    #[serde(rename = "33B", skip_serializing_if = "Option::is_none")]
40    pub currency_amount: Option<Field33B>,
41}
42
43/// **MT202: General Financial Institution Transfer**
44///
45/// Bank-to-bank transfer on behalf of customer or financial institution.
46/// Supports both direct transfers and cover payments (MT202 COV).
47///
48/// **Usage:** Interbank transfers, cover payments, correspondent banking
49/// **Category:** Category 2 (Financial Institution Transfers)
50#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
51pub struct MT202 {
52    /// Transaction Reference Number (Field 20)
53    #[serde(rename = "20")]
54    pub field_20: Field20,
55
56    /// Related Reference (Field 21)
57    #[serde(rename = "21")]
58    pub field_21: Field21NoOption,
59
60    /// Time Indication (Field 13C)
61    #[serde(rename = "13C", skip_serializing_if = "Option::is_none")]
62    pub field_13c: Option<Vec<Field13C>>,
63
64    /// Value Date, Currency Code, Amount (Field 32A)
65    #[serde(rename = "32A")]
66    pub field_32a: Field32A,
67
68    /// Ordering Institution (Field 52)
69    #[serde(flatten, skip_serializing_if = "Option::is_none")]
70    pub field_52: Option<Field52OrderingInstitution>,
71
72    /// Sender's Correspondent (Field 53)
73    #[serde(flatten, skip_serializing_if = "Option::is_none")]
74    pub field_53: Option<Field53SenderCorrespondent>,
75
76    /// Receiver's Correspondent (Field 54)
77    #[serde(flatten, skip_serializing_if = "Option::is_none")]
78    pub field_54: Option<Field54ReceiverCorrespondent>,
79
80    /// Intermediary Institution (Field 56)
81    #[serde(flatten, skip_serializing_if = "Option::is_none")]
82    pub field_56: Option<Field56Intermediary>,
83
84    /// Account With Institution (Field 57)
85    #[serde(flatten, skip_serializing_if = "Option::is_none")]
86    pub field_57: Option<Field57AccountWithInstitution>,
87
88    /// Beneficiary Institution (Field 58)
89    #[serde(flatten)]
90    pub field_58: Field58,
91
92    /// Sender to Receiver Information (Field 72)
93    #[serde(rename = "72", skip_serializing_if = "Option::is_none")]
94    pub field_72: Option<Field72>,
95
96    /// Sequence B - Cover Payment Details
97    #[serde(rename = "#", skip_serializing_if = "Option::is_none")]
98    pub sequence_b: Option<MT202SequenceB>,
99}
100
101impl MT202 {
102    /// Parse MT202 from a raw SWIFT message string
103    pub fn parse_from_block4(block4: &str) -> Result<Self, ParseError> {
104        let mut parser = MessageParser::new(block4, "202");
105
106        // Sequence A - Parse mandatory fields
107        let field_20 = parser.parse_field::<Field20>("20")?;
108        let field_21 = parser.parse_field::<Field21NoOption>("21")?;
109
110        // Parse optional Field 13C (can be repeated) - enable duplicates mode
111        parser = parser.with_duplicates(true);
112        let mut time_indications = Vec::new();
113        while let Ok(field) = parser.parse_field::<Field13C>("13C") {
114            time_indications.push(field);
115        }
116        parser = parser.with_duplicates(false);
117
118        let field_13c = if time_indications.is_empty() {
119            None
120        } else {
121            Some(time_indications)
122        };
123
124        // Parse mandatory Field 32A
125        let field_32a = parser.parse_field::<Field32A>("32A")?;
126
127        // Parse optional Sequence A fields
128        let field_52 = parser.parse_optional_variant_field::<Field52OrderingInstitution>("52")?;
129        let field_53 = parser.parse_optional_variant_field::<Field53SenderCorrespondent>("53")?;
130        let field_54 = parser.parse_optional_variant_field::<Field54ReceiverCorrespondent>("54")?;
131        let field_56 = parser.parse_optional_variant_field::<Field56Intermediary>("56")?;
132        let field_57 =
133            parser.parse_optional_variant_field::<Field57AccountWithInstitution>("57")?;
134
135        // Parse mandatory Field 58 - Beneficiary Institution
136        let field_58 = parser.parse_variant_field::<Field58>("58")?;
137
138        // Parse optional Field 72
139        let field_72 = parser.parse_optional_field::<Field72>("72")?;
140
141        // Sequence B - Parse COV fields (optional, for MT202 COV variant)
142        // Enable duplicates for Sequence B as it may have fields 52, 56, 57, 72 again
143        parser = parser.with_duplicates(true);
144
145        let ordering_customer =
146            parser.parse_optional_variant_field::<Field50OrderingCustomerAFK>("50")?;
147        let ordering_institution =
148            parser.parse_optional_variant_field::<Field52OrderingInstitution>("52")?;
149        let intermediary = parser.parse_optional_variant_field::<Field56Intermediary>("56")?;
150        let account_with_institution =
151            parser.parse_optional_variant_field::<Field57AccountWithInstitution>("57")?;
152        let beneficiary_customer = parser.parse_optional_variant_field::<Field59>("59")?;
153        let remittance_information = parser.parse_optional_field::<Field70>("70")?;
154        let sender_to_receiver_information = parser.parse_optional_field::<Field72>("72")?;
155        let currency_amount = parser.parse_optional_field::<Field33B>("33B")?;
156
157        // Build Sequence B only if any COV fields are present
158        let sequence_b = if ordering_customer.is_some()
159            || ordering_institution.is_some()
160            || intermediary.is_some()
161            || account_with_institution.is_some()
162            || beneficiary_customer.is_some()
163            || remittance_information.is_some()
164            || sender_to_receiver_information.is_some()
165            || currency_amount.is_some()
166        {
167            Some(MT202SequenceB {
168                ordering_customer,
169                ordering_institution,
170                intermediary,
171                account_with_institution,
172                beneficiary_customer,
173                remittance_information,
174                sender_to_receiver_information,
175                currency_amount,
176            })
177        } else {
178            None
179        };
180
181        Ok(MT202 {
182            field_20,
183            field_21,
184            field_13c,
185            field_32a,
186            field_52,
187            field_53,
188            field_54,
189            field_56,
190            field_57,
191            field_58,
192            field_72,
193            sequence_b,
194        })
195    }
196
197    // ========================================================================
198    // NETWORK VALIDATION RULES (SR 2025 MT202)
199    // ========================================================================
200
201    // ========================================================================
202    // HELPER METHODS
203    // ========================================================================
204
205    /// Check if intermediary (56a) is present in Sequence A
206    fn has_intermediary_in_seq_a(&self) -> bool {
207        self.field_56.is_some()
208    }
209
210    /// Check if account with institution (57a) is present in Sequence A
211    fn has_account_with_in_seq_a(&self) -> bool {
212        self.field_57.is_some()
213    }
214
215    /// Check if intermediary (56a) is present in Sequence B (COV)
216    fn has_intermediary_in_seq_b(&self) -> bool {
217        self.sequence_b
218            .as_ref()
219            .map(|seq_b| seq_b.intermediary.is_some())
220            .unwrap_or(false)
221    }
222
223    /// Check if account with institution (57a) is present in Sequence B (COV)
224    fn has_account_with_in_seq_b(&self) -> bool {
225        self.sequence_b
226            .as_ref()
227            .map(|seq_b| seq_b.account_with_institution.is_some())
228            .unwrap_or(false)
229    }
230
231    // ========================================================================
232    // VALIDATION RULES (C1-C2)
233    // ========================================================================
234
235    /// C1: Intermediary and Account With Institution (Sequence A) (Error code: C81)
236    /// If field 56a is present in sequence A, then field 57a must also be present in sequence A
237    fn validate_c1_intermediary_seq_a(&self) -> Option<SwiftValidationError> {
238        if self.has_intermediary_in_seq_a() && !self.has_account_with_in_seq_a() {
239            return Some(SwiftValidationError::business_error(
240                "C81",
241                "57a",
242                vec!["56a".to_string()],
243                "Field 57a (Account With Institution) is mandatory when field 56a (Intermediary) is present in Sequence A",
244                "If field 56a is present in sequence A, then field 57a must also be present in sequence A",
245            ));
246        }
247        None
248    }
249
250    /// C2: Intermediary and Account With Institution (Sequence B) (Error code: C68)
251    /// If field 56a is present in sequence B, then field 57a must also be present in sequence B
252    fn validate_c2_intermediary_seq_b(&self) -> Option<SwiftValidationError> {
253        if self.has_intermediary_in_seq_b() && !self.has_account_with_in_seq_b() {
254            return Some(SwiftValidationError::business_error(
255                "C68",
256                "57a",
257                vec!["56a".to_string()],
258                "Field 57a (Account With Institution) is mandatory when field 56a (Intermediary) is present in Sequence B",
259                "If field 56a is present in sequence B, then field 57a must also be present in sequence B",
260            ));
261        }
262        None
263    }
264
265    /// Main validation method - validates all network rules
266    /// Returns array of validation errors, respects stop_on_first_error flag
267    pub fn validate_network_rules(&self, stop_on_first_error: bool) -> Vec<SwiftValidationError> {
268        let mut all_errors = Vec::new();
269
270        // C1: Intermediary and Account With Institution (Sequence A)
271        if let Some(error) = self.validate_c1_intermediary_seq_a() {
272            all_errors.push(error);
273            if stop_on_first_error {
274                return all_errors;
275            }
276        }
277
278        // C2: Intermediary and Account With Institution (Sequence B)
279        if let Some(error) = self.validate_c2_intermediary_seq_b() {
280            all_errors.push(error);
281            if stop_on_first_error {
282                return all_errors;
283            }
284        }
285
286        all_errors
287    }
288
289    /// Check if this message has reject codes
290    pub fn has_reject_codes(&self) -> bool {
291        if let Some(ref info) = self.field_72 {
292            info.information
293                .iter()
294                .any(|line| line.contains("/REJT/") || line.contains("/RJT/"))
295        } else {
296            false
297        }
298    }
299
300    /// Check if this message has return codes
301    pub fn has_return_codes(&self) -> bool {
302        if let Some(ref info) = self.field_72 {
303            info.information
304                .iter()
305                .any(|line| line.contains("/RETN/") || line.contains("/RET/"))
306        } else {
307            false
308        }
309    }
310
311    /// Check if this is a cover message (MT202 COV)
312    pub fn is_cover_message(&self) -> bool {
313        // Check if Sequence B is present with COV fields
314        self.sequence_b
315            .as_ref()
316            .map(|seq_b| seq_b.ordering_customer.is_some() || seq_b.beneficiary_customer.is_some())
317            .unwrap_or(false)
318    }
319}
320
321impl crate::traits::SwiftMessageBody for MT202 {
322    fn message_type() -> &'static str {
323        "202"
324    }
325
326    fn parse_from_block4(block4: &str) -> Result<Self, crate::errors::ParseError> {
327        Self::parse_from_block4(block4)
328    }
329
330    fn to_mt_string(&self) -> String {
331        let mut result = String::new();
332
333        // Sequence A - Basic Transfer Details
334        append_field(&mut result, &self.field_20);
335        append_field(&mut result, &self.field_21);
336        append_vec_field(&mut result, &self.field_13c);
337        append_field(&mut result, &self.field_32a);
338        append_optional_field(&mut result, &self.field_52);
339        append_optional_field(&mut result, &self.field_53);
340        append_optional_field(&mut result, &self.field_54);
341        append_optional_field(&mut result, &self.field_56);
342        append_optional_field(&mut result, &self.field_57);
343        append_field(&mut result, &self.field_58);
344        append_optional_field(&mut result, &self.field_72);
345
346        // Sequence B - Cover Payment Details (MT202 COV)
347        if let Some(ref seq_b) = self.sequence_b {
348            append_optional_field(&mut result, &seq_b.ordering_customer);
349            append_optional_field(&mut result, &seq_b.ordering_institution);
350            append_optional_field(&mut result, &seq_b.intermediary);
351            append_optional_field(&mut result, &seq_b.account_with_institution);
352            append_optional_field(&mut result, &seq_b.beneficiary_customer);
353            append_optional_field(&mut result, &seq_b.remittance_information);
354            append_optional_field(&mut result, &seq_b.sender_to_receiver_information);
355            append_optional_field(&mut result, &seq_b.currency_amount);
356        }
357
358        finalize_mt_string(result, false)
359    }
360
361    fn validate_network_rules(&self, stop_on_first_error: bool) -> Vec<SwiftValidationError> {
362        // Call the existing public method implementation
363        MT202::validate_network_rules(self, stop_on_first_error)
364    }
365}