1use std::fmt;
7
8#[allow(missing_docs)]
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
11pub struct ResponseCode(pub u8, pub u8);
12
13#[allow(missing_docs)]
14impl ResponseCode {
15 pub const APPROVED: Self = Self(0, 0);
17 pub const APPROVED_WITH_ID: Self = Self(0, 1);
18 pub const APPROVED_PARTIAL: Self = Self(0, 2);
19
20 pub const REFER_TO_ISSUER: Self = Self(0, 1);
22 pub const REFER_SPECIAL: Self = Self(0, 2);
23 pub const INVALID_MERCHANT: Self = Self(0, 3);
24 pub const PICK_UP_CARD: Self = Self(0, 4);
25
26 pub const DO_NOT_HONOR: Self = Self(0, 5);
28 pub const ERROR: Self = Self(0, 6);
29 pub const PICK_UP_SPECIAL: Self = Self(0, 7);
30 pub const HONOR_WITH_ID: Self = Self(0, 8);
31
32 pub const INVALID_TRANSACTION: Self = Self(1, 2);
34 pub const INVALID_AMOUNT: Self = Self(1, 3);
35 pub const INVALID_CARD_NUMBER: Self = Self(1, 4);
36 pub const NO_SUCH_ISSUER: Self = Self(1, 5);
37
38 pub const CUSTOMER_CANCELLATION: Self = Self(1, 7);
40 pub const DUPLICATE_TRANSACTION: Self = Self(1, 8);
41 pub const RE_ENTER_TRANSACTION: Self = Self(1, 9);
42 pub const INVALID_RESPONSE: Self = Self(2, 0);
43 pub const NO_ACTION_TAKEN: Self = Self(2, 1);
44 pub const SUSPECTED_MALFUNCTION: Self = Self(2, 2);
45 pub const UNACCEPTABLE_TRANSACTION_FEE: Self = Self(2, 3);
46 pub const FILE_UPDATE_NOT_SUPPORTED: Self = Self(2, 4);
47 pub const UNABLE_TO_LOCATE_RECORD: Self = Self(2, 5);
48 pub const DUPLICATE_RECORD: Self = Self(2, 6);
49 pub const FILE_UPDATE_EDIT_ERROR: Self = Self(2, 7);
50 pub const FILE_UPDATE_FILE_LOCKED: Self = Self(2, 8);
51 pub const FILE_UPDATE_FAILED: Self = Self(2, 9);
52 pub const FORMAT_ERROR: Self = Self(3, 0);
53
54 pub const BANK_NOT_SUPPORTED: Self = Self(3, 1);
56 pub const COMPLETED_PARTIALLY: Self = Self(3, 2);
57 pub const EXPIRED_CARD_PICKUP: Self = Self(3, 3);
58 pub const SUSPECTED_FRAUD: Self = Self(3, 4);
59 pub const RESTRICTED_CARD: Self = Self(3, 6);
60 pub const CONTACT_ACQUIRER_SECURITY: Self = Self(3, 7);
61 pub const LOST_CARD: Self = Self(4, 1);
62 pub const STOLEN_CARD: Self = Self(4, 3);
63
64 pub const INSUFFICIENT_FUNDS: Self = Self(5, 1);
66 pub const NO_CHECKING_ACCOUNT: Self = Self(5, 2);
67 pub const NO_SAVINGS_ACCOUNT: Self = Self(5, 3);
68 pub const EXPIRED_CARD: Self = Self(5, 4);
69 pub const INCORRECT_PIN: Self = Self(5, 5);
70 pub const NO_CARD_RECORD: Self = Self(5, 6);
71 pub const TRANSACTION_NOT_PERMITTED: Self = Self(5, 7);
72 pub const TRANSACTION_NOT_PERMITTED_TERMINAL: Self = Self(5, 8);
73 pub const SUSPECTED_FRAUD_DECLINE: Self = Self(5, 9);
74 pub const CONTACT_ACQUIRER: Self = Self(6, 0);
75 pub const EXCEEDS_WITHDRAWAL_LIMIT: Self = Self(6, 1);
76 pub const RESTRICTED_CARD_DECLINE: Self = Self(6, 2);
77 pub const SECURITY_VIOLATION: Self = Self(6, 3);
78 pub const EXCEEDS_WITHDRAWAL_FREQUENCY: Self = Self(6, 5);
79
80 pub const PIN_REQUIRED: Self = Self(7, 5);
82 pub const PIN_VALIDATION_NOT_POSSIBLE: Self = Self(7, 6);
83 pub const PIN_TRIES_EXCEEDED: Self = Self(7, 7);
84
85 pub const CRYPTOGRAPHIC_FAILURE: Self = Self(8, 0);
87 pub const CRYPTOGRAPHIC_KEY_SYNC_ERROR: Self = Self(8, 1);
88 pub const CVV_FAILURE: Self = Self(8, 2);
89 pub const CANT_VERIFY_PIN: Self = Self(8, 3);
90 pub const MESSAGE_FLOW_ERROR: Self = Self(8, 5);
91 pub const CUTOVER_IN_PROGRESS: Self = Self(9, 0);
92 pub const ISSUER_UNAVAILABLE: Self = Self(9, 1);
93 pub const ROUTING_ERROR: Self = Self(9, 2);
94 pub const DUPLICATE_TRANSMISSION: Self = Self(9, 4);
95 pub const RECONCILE_ERROR: Self = Self(9, 5);
96 pub const SYSTEM_MALFUNCTION: Self = Self(9, 6);
97
98 pub const MAC_ERROR: Self = Self(9, 7);
100 pub const FAILED_SECURITY_CHECK: Self = Self(9, 8);
101
102 pub fn new(first: u8, second: u8) -> Self {
104 Self(first, second)
105 }
106
107 pub fn description(&self) -> &'static str {
109 match (self.0, self.1) {
110 (0, 0) => "Approved or completed successfully",
111 (0, 1) => "Refer to card issuer",
112 (0, 2) => "Refer to card issuer, special condition",
113 (0, 3) => "Invalid merchant",
114 (0, 4) => "Pick up card",
115 (0, 5) => "Do not honor",
116 (0, 6) => "Error",
117 (0, 7) => "Pick up card, special condition",
118 (1, 2) => "Invalid transaction",
119 (1, 3) => "Invalid amount",
120 (1, 4) => "Invalid card number",
121 (1, 5) => "No such issuer",
122 (3, 0) => "Format error",
123 (4, 1) => "Lost card, pick up",
124 (4, 3) => "Stolen card, pick up",
125 (5, 1) => "Insufficient funds",
126 (5, 4) => "Expired card",
127 (5, 5) => "Incorrect PIN",
128 (5, 7) => "Transaction not permitted to cardholder",
129 (5, 8) => "Transaction not permitted to terminal",
130 (6, 1) => "Exceeds withdrawal amount limit",
131 (7, 5) => "PIN required",
132 (7, 7) => "PIN tries exceeded",
133 (9, 1) => "Issuer or switch inoperative",
134 (9, 6) => "System malfunction",
135 _ => "Unknown response code",
136 }
137 }
138
139 pub fn is_approved(&self) -> bool {
141 self.0 == 0 && self.1 == 0
142 }
143
144 pub fn is_declined(&self) -> bool {
146 !self.is_approved() && !self.is_referral() && !self.is_system_error()
147 }
148
149 pub fn is_referral(&self) -> bool {
151 matches!((self.0, self.1), (0, 1) | (0, 2))
152 }
153
154 pub fn is_system_error(&self) -> bool {
156 matches!(self.0, 9 | 8) || matches!((self.0, self.1), (3, 0) | (0, 6))
157 }
158
159 pub fn should_retain_card(&self) -> bool {
161 matches!((self.0, self.1), (0, 4) | (0, 7) | (4, 1) | (4, 3))
162 }
163
164 pub fn category(&self) -> ResponseCategory {
166 match (self.0, self.1) {
167 (0, 0..=2) => ResponseCategory::Approved,
168 (0, 1..=4) | (0, 7) => ResponseCategory::Referral,
169 (4, 1) | (4, 3) => ResponseCategory::CardRetention,
170 (5, 1) | (6, 1) | (6, 5) => ResponseCategory::InsufficientFunds,
171 (5, 4) => ResponseCategory::ExpiredCard,
172 (5, 5) | (7, 5) | (7, 7) => ResponseCategory::PINError,
173 (9, _) | (8, _) => ResponseCategory::SystemError,
174 _ => ResponseCategory::Declined,
175 }
176 }
177}
178
179impl std::str::FromStr for ResponseCode {
180 type Err = ();
181
182 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
183 if s.len() != 2 {
184 return Err(());
185 }
186
187 let first = s.chars().nth(0).ok_or(())?.to_digit(10).ok_or(())? as u8;
188 let second = s.chars().nth(1).ok_or(())?.to_digit(10).ok_or(())? as u8;
189
190 Ok(Self(first, second))
191 }
192}
193
194#[allow(missing_docs)]
196#[derive(Debug, Clone, Copy, PartialEq, Eq)]
197pub enum ResponseCategory {
198 Approved,
199 Declined,
200 Referral,
201 CardRetention,
202 InsufficientFunds,
203 ExpiredCard,
204 PINError,
205 SystemError,
206}
207
208impl fmt::Display for ResponseCode {
209 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
210 write!(f, "{:02}", self.0 * 10 + self.1)
211 }
212}
213
214impl fmt::Display for ResponseCategory {
215 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
216 match self {
217 Self::Approved => write!(f, "Approved"),
218 Self::Declined => write!(f, "Declined"),
219 Self::Referral => write!(f, "Referral"),
220 Self::CardRetention => write!(f, "Card Retention"),
221 Self::InsufficientFunds => write!(f, "Insufficient Funds"),
222 Self::ExpiredCard => write!(f, "Expired Card"),
223 Self::PINError => write!(f, "PIN Error"),
224 Self::SystemError => write!(f, "System Error"),
225 }
226 }
227}
228
229#[cfg(test)]
230mod tests {
231 use super::*;
232
233 #[test]
234 fn test_response_codes() {
235 assert!(ResponseCode::APPROVED.is_approved());
236 assert!(!ResponseCode::DO_NOT_HONOR.is_approved());
237 assert!(ResponseCode::INSUFFICIENT_FUNDS.is_declined());
238 assert!(ResponseCode::REFER_TO_ISSUER.is_referral());
239 assert!(ResponseCode::ISSUER_UNAVAILABLE.is_system_error());
240 }
241
242 #[test]
243 fn test_card_retention() {
244 assert!(ResponseCode::LOST_CARD.should_retain_card());
245 assert!(ResponseCode::STOLEN_CARD.should_retain_card());
246 assert!(!ResponseCode::INSUFFICIENT_FUNDS.should_retain_card());
247 }
248
249 #[test]
250 fn test_response_categories() {
251 assert_eq!(
252 ResponseCode::APPROVED.category(),
253 ResponseCategory::Approved
254 );
255 assert_eq!(
256 ResponseCode::INSUFFICIENT_FUNDS.category(),
257 ResponseCategory::InsufficientFunds
258 );
259 assert_eq!(
260 ResponseCode::INCORRECT_PIN.category(),
261 ResponseCategory::PINError
262 );
263 }
264
265 #[test]
266 fn test_from_string() {
267 let code = "00".parse::<ResponseCode>().unwrap();
268 assert_eq!(code, ResponseCode::APPROVED);
269
270 let code = "51".parse::<ResponseCode>().unwrap();
271 assert_eq!(code, ResponseCode::INSUFFICIENT_FUNDS);
272 }
273
274 #[test]
275 fn test_to_string() {
276 assert_eq!(ResponseCode::APPROVED.to_string(), "00");
277 assert_eq!(ResponseCode::INSUFFICIENT_FUNDS.to_string(), "51");
278 }
279}