swift_mt_message/messages/
mt202.rs

1use crate::fields::*;
2use serde::{Deserialize, Serialize};
3use swift_mt_message_macros::{SwiftMessage, serde_swift_fields};
4
5/// # MT202: General Financial Institution Transfer (Standard and COV variants)
6///
7/// Unified structure supporting both standard MT202 and MT202 COV variants.
8/// Use `is_cover_message()` to check if the message is a COV variant.
9#[serde_swift_fields]
10#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, SwiftMessage)]
11#[validation_rules(MT202_VALIDATION_RULES)]
12pub struct MT202 {
13    // Mandatory Fields
14    #[field("20", mandatory)]
15    pub field_20: GenericReferenceField,
16
17    #[field("21", mandatory)]
18    pub field_21: GenericReferenceField,
19
20    #[field("32A", mandatory)]
21    pub field_32a: Field32A,
22
23    #[field("58A", mandatory)]
24    pub field_58a: GenericBicField,
25
26    // Optional Fields - Standard MT202 Sequence A
27    #[field("13C", optional)]
28    pub field_13c: Option<Vec<Field13C>>,
29
30    #[field("52A", optional)]
31    pub field_52a: Option<GenericBicField>,
32
33    #[field("52D", optional)]
34    pub field_52d: Option<GenericNameAddressField>,
35
36    #[field("53A", optional)]
37    pub field_53a: Option<GenericBicField>,
38
39    #[field("53B", optional)]
40    pub field_53b: Option<GenericPartyField>,
41
42    #[field("53D", optional)]
43    pub field_53d: Option<GenericNameAddressField>,
44
45    #[field("54A", optional)]
46    pub field_54a: Option<GenericBicField>,
47
48    #[field("54B", optional)]
49    pub field_54b: Option<GenericPartyField>,
50
51    #[field("54D", optional)]
52    pub field_54d: Option<GenericNameAddressField>,
53
54    #[field("56A", optional)]
55    pub field_56a: Option<GenericBicField>,
56
57    #[field("56C", optional)]
58    pub field_56c: Option<GenericAccountField>,
59
60    #[field("56D", optional)]
61    pub field_56d: Option<GenericNameAddressField>,
62
63    #[field("57A", optional)]
64    pub field_57a: Option<GenericBicField>,
65
66    #[field("57B", optional)]
67    pub field_57b: Option<GenericPartyField>,
68
69    #[field("57C", optional)]
70    pub field_57c: Option<GenericAccountField>,
71
72    #[field("57D", optional)]
73    pub field_57d: Option<GenericNameAddressField>,
74
75    #[field("72", optional)]
76    pub field_72: Option<GenericMultiLine6x35>,
77
78    // COV Sequence B Fields - Customer Credit Transfer Details
79    // These fields are present only in MT202 COV messages
80    #[field("50A", optional)]
81    pub field_50a: Option<Field50>,
82
83    #[field("50", optional)]
84    pub field_50: Option<Field50>,
85
86    #[field("59A", optional)]
87    pub field_59a: Option<Field59>,
88
89    #[field("59", optional)]
90    pub field_59: Option<Field59>,
91
92    #[field("70", optional)]
93    pub field_70: Option<GenericMultiLine4x35>,
94
95    #[field("33B", optional)]
96    pub field_33b: Option<GenericCurrencyAmountField>,
97}
98
99impl MT202 {
100    /// Check if this MT202 message contains reject codes
101    ///
102    /// Reject messages are identified by checking:
103    /// 1. Field 20 (Transaction Reference) for "REJT" prefix or content
104    /// 2. Field 72 (Sender to Receiver Information) containing `/REJT/` codes
105    /// 3. Additional structured reject information in field 72
106    pub fn has_reject_codes(&self) -> bool {
107        // Check field 20 (transaction reference)
108        if self.field_20.value.to_uppercase().contains("REJT") {
109            return true;
110        }
111
112        // Check field 72 for structured reject codes
113        if let Some(field_72) = &self.field_72 {
114            let content = field_72.lines.join(" ").to_uppercase();
115            if content.contains("/REJT/") || content.contains("REJT") {
116                return true;
117            }
118        }
119
120        false
121    }
122
123    /// Check if this MT202 message contains return codes
124    ///
125    /// Return messages are identified by checking:
126    /// 1. Field 20 (Transaction Reference) for "RETN" prefix or content
127    /// 2. Field 72 (Sender to Receiver Information) containing `/RETN/` codes
128    /// 3. Additional structured return information in field 72
129    pub fn has_return_codes(&self) -> bool {
130        // Check field 20 (transaction reference)
131        if self.field_20.value.to_uppercase().contains("RETN") {
132            return true;
133        }
134
135        // Check field 72 for structured return codes
136        if let Some(field_72) = &self.field_72 {
137            let content = field_72.lines.join(" ").to_uppercase();
138            if content.contains("/RETN/") || content.contains("RETN") {
139                return true;
140            }
141        }
142
143        false
144    }
145
146    /// Check if this MT202 message is a Cover (COV) message
147    ///
148    /// COV messages are distinguished by:
149    /// - Presence of customer fields (50A/50 and 59A/59) indicating underlying customer details
150    /// - Field 121 (UETR) in Block 3 is typically mandatory for COV messages
151    pub fn is_cover_message(&self) -> bool {
152        // COV messages contain customer fields that indicate underlying customer credit transfer details
153        (self.field_50a.is_some() || self.field_50.is_some())
154            && (self.field_59a.is_some() || self.field_59.is_some())
155    }
156}
157
158const MT202_VALIDATION_RULES: &str = r#"{
159  "rules": [
160    {
161      "id": "C1",
162      "description": "If 56a is present, 57a becomes mandatory",
163      "condition": {
164        "if": [
165          {"or": [
166            {"var": "field_56a.is_some"},
167            {"var": "field_56d.is_some"}
168          ]},
169          {"or": [
170            {"var": "field_57a.is_some"},
171            {"var": "field_57b.is_some"},
172            {"var": "field_57d.is_some"}
173          ]},
174          true
175        ]
176      }
177    }
178  ],
179  "constants": {
180    "VALID_TIME_CODES": ["CLS", "RNC", "SND"],
181    "VALID_INSTRUCTION_CODES": ["/INT/", "/COV/", "/REIMBURSEMENT/", "/SETTLEMENT/", "/SDVA/", "/RETN/", "/REJT/"]
182  }
183}"#;