1pub mod bitmap;
2pub mod codec;
3pub mod error;
4pub mod field_spec;
5pub mod iso8583;
6pub mod mti;
7pub mod validation;
8
9pub use error::{Iso8583Error, Result};
10pub use field_spec::LoadedSpec;
11pub use validation::{ValidationError, ValidationResult};
12
13pub fn parse(hex_message: &str, spec: &LoadedSpec) -> Result<serde_json::Value> {
18 iso8583::parse(hex_message, spec)
19}
20
21pub fn publish(message: &serde_json::Value, spec: &LoadedSpec) -> Result<String> {
25 iso8583::publish(message, spec)
26}
27
28pub fn load_spec(json: &str) -> Result<LoadedSpec> {
30 LoadedSpec::from_json(json)
31}
32
33pub fn validate(message: &serde_json::Value, spec: &LoadedSpec) -> ValidationResult {
38 validation::validate(message, spec)
39}
40
41pub fn lookup_response_code(code: &str) -> Option<&'static str> {
45 validation::lookup_response_code(code)
46}
47
48#[cfg(test)]
49mod tests {
50 use super::*;
51 use serde_json::json;
52
53 fn default_spec() -> LoadedSpec {
54 let spec_json = include_str!("../specifications/fields.json");
55 load_spec(spec_json).expect("failed to load spec")
56 }
57
58 #[test]
59 fn test_load_spec() {
60 let spec = default_spec();
61 assert_eq!(spec.version, "1987");
62 assert!(spec.get_field(2).is_some());
63 assert!(spec.get_field(3).is_some());
64 assert!(spec.get_field(128).is_some());
65 }
66
67 #[test]
68 fn test_simple_round_trip() {
69 let spec = default_spec();
70
71 let message = json!({
72 "mti": "0200",
73 "fields": {
74 "de002_primary_account_number": "4111111111111111",
75 "de003_processing_code": "000000",
76 "de004_amount_transaction": "000000001000",
77 "de011_system_trace_audit_number": "123456",
78 "de041_card_acceptor_terminal_id": "TERM0001"
79 }
80 });
81
82 let hex_encoded = publish(&message, &spec).expect("publish failed");
83 let parsed = parse(&hex_encoded, &spec).expect("parse failed");
84
85 assert_eq!(parsed["mti"], "0200");
86 assert_eq!(
87 parsed["fields"]["de002_primary_account_number"],
88 "4111111111111111"
89 );
90 assert_eq!(parsed["fields"]["de003_processing_code"], "000000");
91 assert_eq!(parsed["fields"]["de004_amount_transaction"], "000000001000");
92 assert_eq!(
93 parsed["fields"]["de011_system_trace_audit_number"],
94 "123456"
95 );
96 assert_eq!(
97 parsed["fields"]["de041_card_acceptor_terminal_id"],
98 "TERM0001"
99 );
100 }
101
102 #[test]
103 fn test_round_trip_with_llvar_fields() {
104 let spec = default_spec();
105
106 let message = json!({
107 "mti": "0200",
108 "fields": {
109 "de002_primary_account_number": "4111111111111111",
110 "de003_processing_code": "000000",
111 "de004_amount_transaction": "000000001000",
112 "de007_transmission_date_time": "0725143052",
113 "de011_system_trace_audit_number": "123456",
114 "de032_acquiring_institution_id_code": "123456",
115 "de035_track_2_data": "4111111111111111D2512101123400001",
116 "de037_retrieval_reference_number": "425612000001",
117 "de041_card_acceptor_terminal_id": "TERM0001",
118 "de043_card_acceptor_name_location": "ACME STORE NEW YORK NY US"
119 }
120 });
121
122 let hex_encoded = publish(&message, &spec).expect("publish failed");
123 let parsed = parse(&hex_encoded, &spec).expect("parse failed");
124
125 assert_eq!(parsed["mti"], "0200");
126 assert_eq!(
127 parsed["fields"]["de002_primary_account_number"],
128 "4111111111111111"
129 );
130 assert_eq!(
131 parsed["fields"]["de035_track_2_data"],
132 "4111111111111111D2512101123400001"
133 );
134 assert_eq!(
135 parsed["fields"]["de037_retrieval_reference_number"],
136 "425612000001"
137 );
138 let name_loc = parsed["fields"]["de043_card_acceptor_name_location"]
140 .as_str()
141 .unwrap();
142 assert_eq!(name_loc.len(), 40);
143 }
144
145 #[test]
146 fn test_composite_ordered_field() {
147 let spec = default_spec();
148
149 let message = json!({
150 "mti": "0200",
151 "fields": {
152 "de003_processing_code": "000000",
153 "de022_pos_entry_mode": {
154 "pan_entry_mode": "05",
155 "pin_entry_capability": "1"
156 }
157 }
158 });
159
160 let hex_encoded = publish(&message, &spec).expect("publish failed");
161 let parsed = parse(&hex_encoded, &spec).expect("parse failed");
162
163 assert_eq!(parsed["mti"], "0200");
164 let pos = &parsed["fields"]["de022_pos_entry_mode"];
165 assert_eq!(pos["pan_entry_mode"], "05");
166 assert_eq!(pos["pin_entry_capability"], "1");
167 }
168
169 #[test]
170 fn test_composite_tlv_field() {
171 let spec = default_spec();
172
173 let message = json!({
174 "mti": "0200",
175 "fields": {
176 "de003_processing_code": "000000",
177 "de055_emv_data": {
178 "9F26": "AABBCCDD11223344",
179 "9F27": "80",
180 "9F10": "0102030405"
181 }
182 }
183 });
184
185 let hex_encoded = publish(&message, &spec).expect("publish failed");
186 let parsed = parse(&hex_encoded, &spec).expect("parse failed");
187
188 assert_eq!(parsed["mti"], "0200");
189 let emv = &parsed["fields"]["de055_emv_data"];
190 assert_eq!(emv["9F26"], "AABBCCDD11223344");
191 assert_eq!(emv["9F27"], "80");
192 assert_eq!(emv["9F10"], "0102030405");
193 }
194
195 #[test]
196 fn test_secondary_bitmap_fields() {
197 let spec = default_spec();
198
199 let message = json!({
200 "mti": "0200",
201 "fields": {
202 "de003_processing_code": "000000",
203 "de070_network_management_information_code": "301"
204 }
205 });
206
207 let hex_encoded = publish(&message, &spec).expect("publish failed");
208 let parsed = parse(&hex_encoded, &spec).expect("parse failed");
209
210 assert_eq!(parsed["mti"], "0200");
211 assert_eq!(
212 parsed["fields"]["de070_network_management_information_code"],
213 "301"
214 );
215 }
216
217 #[test]
218 fn test_binary_field_round_trip() {
219 let spec = default_spec();
220
221 let message = json!({
222 "mti": "0200",
223 "fields": {
224 "de003_processing_code": "000000",
225 "de052_pin_data": "1234567890ABCDEF"
226 }
227 });
228
229 let hex_encoded = publish(&message, &spec).expect("publish failed");
230 let parsed = parse(&hex_encoded, &spec).expect("parse failed");
231
232 assert_eq!(parsed["fields"]["de052_pin_data"], "1234567890ABCDEF");
233 }
234
235 #[test]
236 fn test_parse_invalid_hex() {
237 let spec = default_spec();
238 let result = parse("ZZZZ", &spec);
239 assert!(result.is_err());
240 }
241
242 #[test]
243 fn test_parse_too_short() {
244 let spec = default_spec();
245 let result = parse("30", &spec);
247 assert!(result.is_err());
248 }
249
250 fn visa_spec() -> LoadedSpec {
255 let spec_json = include_str!("../specifications/visa_base1.json");
256 load_spec(spec_json).expect("failed to load Visa BASE I spec")
257 }
258
259 fn mastercard_spec() -> LoadedSpec {
260 let spec_json = include_str!("../specifications/mastercard_mip.json");
261 load_spec(spec_json).expect("failed to load Mastercard MIP spec")
262 }
263
264 #[test]
265 fn test_load_visa_spec() {
266 let spec = visa_spec();
267 assert_eq!(spec.version, "2003");
268 assert_eq!(spec.defaults.encoding, "ebcdic");
269 assert_eq!(spec.defaults.prefix_encoding, "bcd");
270 assert_eq!(spec.defaults.bitmap_encoding, "binary");
271 assert_eq!(spec.defaults.mti_encoding, "bcd");
272 assert_eq!(spec.defaults.max_bitmaps, 3);
273 assert!(spec.get_field(2).is_some());
274 assert!(spec.get_field(48).is_some());
275 assert!(spec.get_field(62).is_some());
276 assert!(spec.get_field(128).is_some());
277
278 let de48 = spec.get_field(48).unwrap();
280 assert_eq!(de48.field_type, field_spec::FieldType::Composite);
281 assert_eq!(de48.composite_type, Some(field_spec::CompositeType::Bitmap));
282 assert!(de48.bitmap.is_some());
283 let bm = de48.bitmap.as_ref().unwrap();
284 assert_eq!(bm.length, 4);
285 assert_eq!(bm.encoding, "binary");
286 }
287
288 #[test]
289 fn test_load_mastercard_spec() {
290 let spec = mastercard_spec();
291 assert_eq!(spec.version, "2003");
292 assert_eq!(spec.defaults.encoding, "ebcdic");
293 assert_eq!(spec.defaults.prefix_encoding, "bcd");
294 assert_eq!(spec.defaults.bitmap_encoding, "binary");
295 assert_eq!(spec.defaults.mti_encoding, "ascii");
296 assert_eq!(spec.defaults.max_bitmaps, 2);
297 assert!(spec.get_field(2).is_some());
298 assert!(spec.get_field(61).is_some());
299 assert!(spec.get_field(128).is_some());
300
301 let de61 = spec.get_field(61).unwrap();
303 assert_eq!(de61.field_type, field_spec::FieldType::Composite);
304 assert_eq!(
305 de61.composite_type,
306 Some(field_spec::CompositeType::Ordered)
307 );
308 assert!(de61.subfields.is_some());
309 assert_eq!(de61.subfields.as_ref().unwrap().len(), 12);
310 }
311
312 #[test]
313 fn test_visa_bcd_mti_round_trip() {
314 let spec = visa_spec();
315
316 let message = json!({
317 "mti": "0100",
318 "fields": {
319 "de003_processing_code": "000000",
320 "de004_amount_transaction": "000000001000",
321 "de011_system_trace_audit_number": "123456",
322 "de025_pos_condition_code": "00"
323 }
324 });
325
326 let hex_encoded = publish(&message, &spec).expect("visa publish failed");
327 let parsed = parse(&hex_encoded, &spec).expect("visa parse failed");
328
329 assert_eq!(parsed["mti"], "0100");
330 assert_eq!(parsed["fields"]["de003_processing_code"], "000000");
331 assert_eq!(parsed["fields"]["de004_amount_transaction"], "000000001000");
332 assert_eq!(
333 parsed["fields"]["de011_system_trace_audit_number"],
334 "123456"
335 );
336 assert_eq!(parsed["fields"]["de025_pos_condition_code"], "00");
337 }
338
339 #[test]
340 fn test_visa_ebcdic_string_fields() {
341 let spec = visa_spec();
342
343 let message = json!({
344 "mti": "0100",
345 "fields": {
346 "de003_processing_code": "000000",
347 "de037_retrieval_reference_number": "425612000001",
348 "de038_authorization_id_response": "ABC123",
349 "de039_response_code": "00",
350 "de041_card_acceptor_terminal_id": "TERM0001"
351 }
352 });
353
354 let hex_encoded = publish(&message, &spec).expect("visa publish failed");
355 let parsed = parse(&hex_encoded, &spec).expect("visa parse failed");
356
357 assert_eq!(
358 parsed["fields"]["de037_retrieval_reference_number"],
359 "425612000001"
360 );
361 assert_eq!(
362 parsed["fields"]["de038_authorization_id_response"],
363 "ABC123"
364 );
365 assert_eq!(parsed["fields"]["de039_response_code"], "00");
366 assert_eq!(
367 parsed["fields"]["de041_card_acceptor_terminal_id"],
368 "TERM0001"
369 );
370 }
371
372 #[test]
373 fn test_visa_llvar_bcd_prefix() {
374 let spec = visa_spec();
375
376 let message = json!({
377 "mti": "0100",
378 "fields": {
379 "de002_primary_account_number": "4111111111111111",
380 "de003_processing_code": "000000"
381 }
382 });
383
384 let hex_encoded = publish(&message, &spec).expect("visa publish failed");
385 let parsed = parse(&hex_encoded, &spec).expect("visa parse failed");
386
387 assert_eq!(
388 parsed["fields"]["de002_primary_account_number"],
389 "4111111111111111"
390 );
391 }
392
393 #[test]
394 fn test_visa_bitmap_composite_round_trip() {
395 let spec = visa_spec();
396
397 let message = json!({
398 "mti": "0100",
399 "fields": {
400 "de003_processing_code": "000000",
401 "de062_custom_payment_service": {
402 "authorization_characteristics": "A",
403 "transaction_id": "123456789012345"
404 }
405 }
406 });
407
408 let hex_encoded = publish(&message, &spec).expect("visa publish failed");
409 let parsed = parse(&hex_encoded, &spec).expect("visa parse failed");
410
411 let de62 = &parsed["fields"]["de062_custom_payment_service"];
412 assert_eq!(de62["authorization_characteristics"], "A");
413 assert_eq!(de62["transaction_id"], "123456789012345");
414 }
415
416 #[test]
417 fn test_mastercard_ascii_mti_round_trip() {
418 let spec = mastercard_spec();
419
420 let message = json!({
421 "mti": "0100",
422 "fields": {
423 "de003_processing_code": "000000",
424 "de004_amount_transaction": "000000001000",
425 "de011_system_trace_audit_number": "123456",
426 "de025_pos_condition_code": "00"
427 }
428 });
429
430 let hex_encoded = publish(&message, &spec).expect("mc publish failed");
431 let parsed = parse(&hex_encoded, &spec).expect("mc parse failed");
432
433 assert_eq!(parsed["mti"], "0100");
434 assert_eq!(parsed["fields"]["de003_processing_code"], "000000");
435 assert_eq!(parsed["fields"]["de004_amount_transaction"], "000000001000");
436 }
437
438 #[test]
439 fn test_mastercard_ordered_composite_de61() {
440 let spec = mastercard_spec();
441
442 let message = json!({
443 "mti": "0100",
444 "fields": {
445 "de003_processing_code": "000000",
446 "de061_pos_data": {
447 "pos_terminal_attendance": "1",
448 "reserved": "0",
449 "pos_terminal_location": "0",
450 "pos_cardholder_presence": "0",
451 "pos_card_presence": "0",
452 "pos_card_capture": "0",
453 "pos_transaction_status": "0",
454 "pos_transaction_security": "0",
455 "reserved2": "0",
456 "cardholder_activated_terminal": "2",
457 "pos_card_data_terminal_input": "1",
458 "pos_authorization_life_cycle": "00"
459 }
460 }
461 });
462
463 let hex_encoded = publish(&message, &spec).expect("mc publish failed");
464 let parsed = parse(&hex_encoded, &spec).expect("mc parse failed");
465
466 let de61 = &parsed["fields"]["de061_pos_data"];
467 assert_eq!(de61["pos_terminal_attendance"], "1");
468 assert_eq!(de61["cardholder_activated_terminal"], "2");
469 assert_eq!(de61["pos_card_data_terminal_input"], "1");
470 }
471
472 #[test]
473 fn test_mastercard_ebcdic_string_fields() {
474 let spec = mastercard_spec();
475
476 let message = json!({
477 "mti": "0100",
478 "fields": {
479 "de003_processing_code": "000000",
480 "de037_retrieval_reference_number": "425612000001",
481 "de041_card_acceptor_terminal_id": "TERM0001",
482 "de042_card_acceptor_id_code": "MERCHANT000001 "
483 }
484 });
485
486 let hex_encoded = publish(&message, &spec).expect("mc publish failed");
487 let parsed = parse(&hex_encoded, &spec).expect("mc parse failed");
488
489 assert_eq!(
490 parsed["fields"]["de037_retrieval_reference_number"],
491 "425612000001"
492 );
493 assert_eq!(
494 parsed["fields"]["de041_card_acceptor_terminal_id"],
495 "TERM0001"
496 );
497 }
498
499 #[test]
500 fn test_visa_full_auth_round_trip() {
501 let spec = visa_spec();
502
503 let message = json!({
504 "mti": "0100",
505 "fields": {
506 "de002_primary_account_number": "4111111111111111",
507 "de003_processing_code": "000000",
508 "de004_amount_transaction": "000000005000",
509 "de007_transmission_date_time": "0101120000",
510 "de011_system_trace_audit_number": "000001",
511 "de014_expiration_date": "2512",
512 "de022_pos_entry_mode": {
513 "pan_entry_mode": "05",
514 "pin_entry_capability": "1"
515 },
516 "de025_pos_condition_code": "00",
517 "de037_retrieval_reference_number": "000000000001",
518 "de041_card_acceptor_terminal_id": "TERM0001",
519 "de042_card_acceptor_id_code": "MERCHANT000001 ",
520 "de049_currency_code_transaction": "840"
521 }
522 });
523
524 let hex_encoded = publish(&message, &spec).expect("visa full auth publish failed");
525 let parsed = parse(&hex_encoded, &spec).expect("visa full auth parse failed");
526
527 assert_eq!(parsed["mti"], "0100");
528 assert_eq!(
529 parsed["fields"]["de002_primary_account_number"],
530 "4111111111111111"
531 );
532 assert_eq!(parsed["fields"]["de004_amount_transaction"], "000000005000");
533 assert_eq!(parsed["fields"]["de049_currency_code_transaction"], "840");
534 let pos = &parsed["fields"]["de022_pos_entry_mode"];
535 assert_eq!(pos["pan_entry_mode"], "05");
536 assert_eq!(pos["pin_entry_capability"], "1");
537 }
538
539 #[test]
540 fn test_mastercard_full_auth_round_trip() {
541 let spec = mastercard_spec();
542
543 let message = json!({
544 "mti": "0100",
545 "fields": {
546 "de002_primary_account_number": "5500000000000004",
547 "de003_processing_code": "000000",
548 "de004_amount_transaction": "000000002500",
549 "de007_transmission_date_time": "0315093000",
550 "de011_system_trace_audit_number": "000042",
551 "de014_expiration_date": "2612",
552 "de022_pos_entry_mode": {
553 "pan_entry_mode": "05",
554 "pin_entry_capability": "1"
555 },
556 "de025_pos_condition_code": "00",
557 "de037_retrieval_reference_number": "000000000042",
558 "de041_card_acceptor_terminal_id": "TERM0002",
559 "de042_card_acceptor_id_code": "MC_MERCHANT001 ",
560 "de049_currency_code_transaction": "840"
561 }
562 });
563
564 let hex_encoded = publish(&message, &spec).expect("mc full auth publish failed");
565 let parsed = parse(&hex_encoded, &spec).expect("mc full auth parse failed");
566
567 assert_eq!(parsed["mti"], "0100");
568 assert_eq!(
569 parsed["fields"]["de002_primary_account_number"],
570 "5500000000000004"
571 );
572 assert_eq!(parsed["fields"]["de004_amount_transaction"], "000000002500");
573 assert_eq!(parsed["fields"]["de049_currency_code_transaction"], "840");
574 }
575
576 #[test]
585 fn test_validate_valid_message() {
586 let spec = default_spec();
587 let message = json!({
588 "mti": "0100",
589 "fields": {
590 "de002_primary_account_number": "4111111111111111",
591 "de003_processing_code": "000000",
592 "de004_amount_transaction": "000000005000",
593 "de007_transmission_date_time": "0725143052",
594 "de011_system_trace_audit_number": "000001",
595 "de014_expiration_date": "2512",
596 "de022_pos_entry_mode": { "pan_entry_mode": "05", "pin_entry_capability": "1" },
597 "de025_pos_condition_code": "00",
598 "de041_card_acceptor_terminal_id": "TERM0001",
599 "de042_card_acceptor_id_code": "MERCHANT000001 ",
600 "de049_currency_code_transaction": "840"
601 }
602 });
603 let result = validate(&message, &spec);
604 assert!(
605 result.is_valid(),
606 "expected valid but got errors: {:?}",
607 result.errors
608 );
609 }
610
611 #[test]
612 fn test_validate_missing_mandatory_fields() {
613 let spec = default_spec();
614 let message = json!({
615 "mti": "0100",
616 "fields": {
617 "de003_processing_code": "000000",
618 "de007_transmission_date_time": "0725143052",
619 "de011_system_trace_audit_number": "000001",
620 "de014_expiration_date": "2512",
621 "de022_pos_entry_mode": { "pan_entry_mode": "05", "pin_entry_capability": "1" },
622 "de025_pos_condition_code": "00",
623 "de041_card_acceptor_terminal_id": "TERM0001",
624 "de042_card_acceptor_id_code": "MERCHANT000001 ",
625 "de049_currency_code_transaction": "840"
626 }
627 });
628 let result = validate(&message, &spec);
629 let mandatory_errors: Vec<_> = result
630 .errors
631 .iter()
632 .filter(|e| e.rule == "mandatory")
633 .collect();
634 assert_eq!(mandatory_errors.len(), 2); assert!(mandatory_errors.iter().any(|e| e.de == 2));
636 assert!(mandatory_errors.iter().any(|e| e.de == 4));
637 }
638
639 #[test]
640 fn test_validate_numeric_format_error() {
641 let spec = default_spec();
642 let message = json!({
643 "mti": "0200",
644 "fields": {
645 "de002_primary_account_number": "4111111111111111",
646 "de003_processing_code": "000000",
647 "de004_amount_transaction": "00ABC0001000",
648 "de007_transmission_date_time": "0725143052",
649 "de011_system_trace_audit_number": "123456",
650 "de022_pos_entry_mode": { "pan_entry_mode": "05", "pin_entry_capability": "1" },
651 "de025_pos_condition_code": "00",
652 "de041_card_acceptor_terminal_id": "TERM0001",
653 "de042_card_acceptor_id_code": "MERCHANT000001 ",
654 "de049_currency_code_transaction": "840"
655 }
656 });
657 let result = validate(&message, &spec);
658 let format_errors: Vec<_> = result
659 .errors
660 .iter()
661 .filter(|e| e.rule == "format" && e.de == 4)
662 .collect();
663 assert_eq!(format_errors.len(), 1);
664 }
665
666 #[test]
667 fn test_validate_length_exceeded() {
668 let spec = default_spec();
669 let message = json!({
670 "mti": "0200",
671 "fields": {
672 "de002_primary_account_number": "41111111111111111111",
673 "de003_processing_code": "000000",
674 "de004_amount_transaction": "000000001000",
675 "de007_transmission_date_time": "0725143052",
676 "de011_system_trace_audit_number": "123456",
677 "de022_pos_entry_mode": { "pan_entry_mode": "05", "pin_entry_capability": "1" },
678 "de025_pos_condition_code": "00",
679 "de041_card_acceptor_terminal_id": "TERM0001",
680 "de042_card_acceptor_id_code": "MERCHANT000001 ",
681 "de049_currency_code_transaction": "840"
682 }
683 });
684 let result = validate(&message, &spec);
685 let len_errors: Vec<_> = result
686 .errors
687 .iter()
688 .filter(|e| e.rule == "length" && e.de == 2)
689 .collect();
690 assert_eq!(len_errors.len(), 1);
691 }
692
693 #[test]
694 fn test_validate_fixed_length_mismatch() {
695 let spec = default_spec();
696 let message = json!({
697 "mti": "0200",
698 "fields": {
699 "de003_processing_code": "00"
700 }
701 });
702 let result = validate(&message, &spec);
703 let len_errors: Vec<_> = result
704 .errors
705 .iter()
706 .filter(|e| e.rule == "length" && e.de == 3)
707 .collect();
708 assert_eq!(len_errors.len(), 1);
709 }
710
711 #[test]
712 fn test_validate_date_pattern_invalid() {
713 let spec = default_spec();
714 let message = json!({
715 "mti": "0200",
716 "fields": {
717 "de007_transmission_date_time": "1325143052"
718 }
719 });
720 let result = validate(&message, &spec);
721 let pattern_errors: Vec<_> = result
722 .errors
723 .iter()
724 .filter(|e| e.rule == "pattern" && e.de == 7)
725 .collect();
726 assert_eq!(pattern_errors.len(), 1);
727 }
728
729 #[test]
730 fn test_validate_date_pattern_valid() {
731 let spec = default_spec();
732 let message = json!({
733 "mti": "0200",
734 "fields": {
735 "de007_transmission_date_time": "0725143052"
736 }
737 });
738 let result = validate(&message, &spec);
739 let pattern_errors: Vec<_> = result
740 .errors
741 .iter()
742 .filter(|e| e.rule == "pattern" && e.de == 7)
743 .collect();
744 assert_eq!(pattern_errors.len(), 0);
745 }
746
747 #[test]
748 fn test_validate_response_code_valid() {
749 let spec = default_spec();
750 let message = json!({
751 "mti": "0210",
752 "fields": {
753 "de003_processing_code": "000000",
754 "de004_amount_transaction": "000000001000",
755 "de007_transmission_date_time": "0725143052",
756 "de011_system_trace_audit_number": "123456",
757 "de039_response_code": "00"
758 }
759 });
760 let result = validate(&message, &spec);
761 let rc_errors: Vec<_> = result
762 .errors
763 .iter()
764 .filter(|e| e.rule == "response_code")
765 .collect();
766 assert_eq!(rc_errors.len(), 0);
767 }
768
769 #[test]
770 fn test_validate_response_code_unknown() {
771 let spec = default_spec();
772 let message = json!({
773 "mti": "0210",
774 "fields": {
775 "de039_response_code": "ZZ"
776 }
777 });
778 let result = validate(&message, &spec);
779 let rc_errors: Vec<_> = result
780 .errors
781 .iter()
782 .filter(|e| e.rule == "response_code")
783 .collect();
784 assert_eq!(rc_errors.len(), 1);
785 }
786
787 #[test]
788 fn test_validate_invalid_mti() {
789 let spec = default_spec();
790 let message = json!({
791 "mti": "0090",
792 "fields": {}
793 });
794 let result = validate(&message, &spec);
795 let mti_errors: Vec<_> = result.errors.iter().filter(|e| e.rule == "mti").collect();
796 assert!(!mti_errors.is_empty());
797 }
798
799 #[test]
800 fn test_validate_missing_mti() {
801 let spec = default_spec();
802 let message = json!({
803 "fields": {}
804 });
805 let result = validate(&message, &spec);
806 assert!(!result.is_valid());
807 assert_eq!(result.errors[0].rule, "mti");
808 }
809
810 #[test]
811 fn test_validate_multiple_errors() {
812 let spec = default_spec();
813 let message = json!({
814 "mti": "0200",
815 "fields": {
816 "de003_processing_code": "00",
817 "de004_amount_transaction": "00ABC0001000",
818 "de007_transmission_date_time": "1325143052"
819 }
820 });
821 let result = validate(&message, &spec);
822 assert!(result.errors.len() >= 3);
823 }
824
825 #[test]
826 fn test_validate_composite_skipped() {
827 let spec = default_spec();
828 let message = json!({
829 "mti": "0200",
830 "fields": {
831 "de022_pos_entry_mode": { "pan_entry_mode": "05", "pin_entry_capability": "1" }
832 }
833 });
834 let result = validate(&message, &spec);
835 let de22_errors: Vec<_> = result.errors.iter().filter(|e| e.de == 22).collect();
836 assert_eq!(de22_errors.len(), 0);
837 }
838
839 #[test]
840 fn test_lookup_response_code() {
841 assert_eq!(lookup_response_code("00"), Some("Approved"));
842 assert_eq!(lookup_response_code("51"), Some("Insufficient Funds"));
843 assert_eq!(lookup_response_code("ZZ"), None);
844 }
845
846 #[test]
847 fn test_validate_no_mti_rules() {
848 let spec = default_spec();
849 let message = json!({
850 "mti": "0300",
851 "fields": {
852 "de003_processing_code": "000000"
853 }
854 });
855 let result = validate(&message, &spec);
856 let mandatory_errors: Vec<_> = result
857 .errors
858 .iter()
859 .filter(|e| e.rule == "mandatory")
860 .collect();
861 assert_eq!(mandatory_errors.len(), 0);
862 }
863
864 #[test]
865 fn test_validate_yymm_pattern() {
866 let spec = default_spec();
867 let message = json!({
868 "mti": "0200",
869 "fields": {
870 "de014_expiration_date": "2513"
871 }
872 });
873 let result = validate(&message, &spec);
874 let pattern_errors: Vec<_> = result
875 .errors
876 .iter()
877 .filter(|e| e.rule == "pattern" && e.de == 14)
878 .collect();
879 assert_eq!(pattern_errors.len(), 1);
880 }
881
882 #[test]
891 fn test_bitmap_composite_unknown_subfield_with_default() {
892 let spec = visa_spec();
893 let message = json!({
895 "mti": "0100",
896 "fields": {
897 "de003_processing_code": "000000",
898 "de048_additional_data_private": {
899 "additional_pos_data": "12345678901234567890123456",
900 "merchant_category_override": "5411"
901 }
902 }
903 });
904
905 let hex_encoded = publish(&message, &spec).expect("visa publish failed");
906 let parsed = parse(&hex_encoded, &spec).expect("visa parse failed");
907
908 let de48 = &parsed["fields"]["de048_additional_data_private"];
909 assert_eq!(de48["additional_pos_data"], "12345678901234567890123456");
910 assert_eq!(de48["merchant_category_override"], "5411");
911 }
912
913 #[test]
918 fn test_visa_expanded_de62_subfields() {
919 let spec = visa_spec();
920
921 let message = json!({
922 "mti": "0100",
923 "fields": {
924 "de003_processing_code": "000000",
925 "de062_custom_payment_service": {
926 "authorization_characteristics": "A",
927 "transaction_id": "123456789012345",
928 "validation_code": "ABCD",
929 "market_specific_data": "H"
930 }
931 }
932 });
933
934 let hex_encoded = publish(&message, &spec).expect("visa publish failed");
935 let parsed = parse(&hex_encoded, &spec).expect("visa parse failed");
936
937 let de62 = &parsed["fields"]["de062_custom_payment_service"];
938 assert_eq!(de62["authorization_characteristics"], "A");
939 assert_eq!(de62["transaction_id"], "123456789012345");
940 assert_eq!(de62["validation_code"], "ABCD");
941 assert_eq!(de62["market_specific_data"], "H");
942 }
943
944 #[test]
949 fn test_xn_field_round_trip() {
950 let spec = default_spec();
951
952 let message = json!({
953 "mti": "0200",
954 "fields": {
955 "de003_processing_code": "000000",
956 "de028_amount_transaction_fee": "C00000150"
957 }
958 });
959
960 let hex_encoded = publish(&message, &spec).expect("publish failed");
961 let parsed = parse(&hex_encoded, &spec).expect("parse failed");
962
963 assert_eq!(
964 parsed["fields"]["de028_amount_transaction_fee"],
965 "C00000150"
966 );
967 }
968
969 #[test]
970 fn test_xn_field_debit_round_trip() {
971 let spec = default_spec();
972
973 let message = json!({
974 "mti": "0200",
975 "fields": {
976 "de003_processing_code": "000000",
977 "de028_amount_transaction_fee": "D00000200"
978 }
979 });
980
981 let hex_encoded = publish(&message, &spec).expect("publish failed");
982 let parsed = parse(&hex_encoded, &spec).expect("parse failed");
983
984 assert_eq!(
985 parsed["fields"]["de028_amount_transaction_fee"],
986 "D00000200"
987 );
988 }
989
990 #[test]
991 fn test_validate_xn_format() {
992 let spec = default_spec();
993
994 let message = json!({
996 "mti": "0200",
997 "fields": {
998 "de028_amount_transaction_fee": "C00000150"
999 }
1000 });
1001 let result = validate(&message, &spec);
1002 let xn_errors: Vec<_> = result
1003 .errors
1004 .iter()
1005 .filter(|e| e.de == 28 && e.rule == "format")
1006 .collect();
1007 assert_eq!(xn_errors.len(), 0);
1008
1009 let message_bad = json!({
1011 "mti": "0200",
1012 "fields": {
1013 "de028_amount_transaction_fee": "X00000150"
1014 }
1015 });
1016 let result_bad = validate(&message_bad, &spec);
1017 let xn_errors_bad: Vec<_> = result_bad
1018 .errors
1019 .iter()
1020 .filter(|e| e.de == 28 && e.rule == "format")
1021 .collect();
1022 assert!(xn_errors_bad.len() >= 1);
1023 }
1024
1025 #[test]
1030 fn test_mti_version_9() {
1031 let spec = default_spec();
1032
1033 let message = json!({
1034 "mti": "9100",
1035 "fields": {
1036 "de003_processing_code": "000000"
1037 }
1038 });
1039
1040 let hex_encoded = publish(&message, &spec).expect("publish failed");
1041 let parsed = parse(&hex_encoded, &spec).expect("parse failed");
1042 assert_eq!(parsed["mti"], "9100");
1043
1044 let result = validate(&message, &spec);
1046 let mti_errors: Vec<_> = result.errors.iter().filter(|e| e.rule == "mti").collect();
1047 assert_eq!(mti_errors.len(), 0);
1048 }
1049
1050 #[test]
1051 fn test_classify_mti_version_9() {
1052 let c = mti::classify_mti("9100").unwrap();
1053 assert_eq!(c.version, "Private/Test");
1054 assert_eq!(c.message_class, "Authorization");
1055 assert_eq!(c.function, "Request");
1056 }
1057
1058 #[test]
1063 fn test_tlv_3byte_tag_round_trip() {
1064 let spec = default_spec();
1065
1066 let message = json!({
1068 "mti": "0200",
1069 "fields": {
1070 "de003_processing_code": "000000",
1071 "de055_emv_data": {
1072 "9F26": "AABBCCDD11223344",
1073 "9F8101": "FF"
1074 }
1075 }
1076 });
1077
1078 let hex_encoded = publish(&message, &spec).expect("publish failed");
1079 let parsed = parse(&hex_encoded, &spec).expect("parse failed");
1080
1081 let emv = &parsed["fields"]["de055_emv_data"];
1082 assert_eq!(emv["9F26"], "AABBCCDD11223344");
1083 assert_eq!(emv["9F8101"], "FF");
1084 }
1085
1086 #[test]
1087 fn test_mastercard_pds_round_trip() {
1088 let spec = mastercard_spec();
1089
1090 let message = json!({
1091 "mti": "0100",
1092 "fields": {
1093 "de003_processing_code": "000000",
1094 "de048_additional_data_private": {
1095 "0043": "12345",
1096 "0023": "ABCDE"
1097 }
1098 }
1099 });
1100
1101 let hex_encoded = publish(&message, &spec).expect("mc pds publish failed");
1102 let parsed = parse(&hex_encoded, &spec).expect("mc pds parse failed");
1103
1104 let de48 = &parsed["fields"]["de048_additional_data_private"];
1105 assert_eq!(de48["0023"], "ABCDE");
1106 assert_eq!(de48["0043"], "12345");
1107 }
1108
1109 #[test]
1114 fn test_new_response_codes() {
1115 assert_eq!(lookup_response_code("16"), Some("Approved, Update Track 3"));
1116 assert_eq!(lookup_response_code("17"), Some("Customer Cancellation"));
1117 assert_eq!(lookup_response_code("20"), Some("Invalid Response"));
1118 assert_eq!(lookup_response_code("22"), Some("Suspected Malfunction"));
1119 assert_eq!(
1120 lookup_response_code("35"),
1121 Some("Contact Acquirer, Pick Up")
1122 );
1123 assert_eq!(
1124 lookup_response_code("37"),
1125 Some("Call Acquirer Security, Pick Up")
1126 );
1127 assert_eq!(lookup_response_code("42"), Some("No Universal Account"));
1128 assert_eq!(lookup_response_code("50"), Some("Do Not Renew"));
1129 assert_eq!(lookup_response_code("60"), Some("Contact Acquirer"));
1130 assert_eq!(lookup_response_code("66"), Some("Acquirer Response Error"));
1131 assert_eq!(lookup_response_code("67"), Some("Hard Capture, Pick Up"));
1132 assert_eq!(lookup_response_code("90"), Some("Cutoff in Progress"));
1133 assert_eq!(lookup_response_code("99"), Some("Reserved for Private Use"));
1134 }
1135
1136 #[test]
1137 fn test_visa_tertiary_bitmap_field_round_trip() {
1138 let spec = visa_spec();
1139
1140 let message = json!({
1141 "mti": "0100",
1142 "fields": {
1143 "de003_processing_code": "000000",
1144 "de130_reserved_private_130": "TESTDATA"
1145 }
1146 });
1147
1148 let hex_encoded = publish(&message, &spec).expect("visa tertiary publish failed");
1149 let parsed = parse(&hex_encoded, &spec).expect("visa tertiary parse failed");
1150
1151 assert_eq!(parsed["fields"]["de130_reserved_private_130"], "TESTDATA");
1152 }
1153
1154 #[test]
1155 fn test_authorization_request_response_pattern() {
1156 let spec = default_spec();
1157
1158 let request = json!({
1160 "mti": "0100",
1161 "fields": {
1162 "de002_primary_account_number": "4111111111111111",
1163 "de003_processing_code": "000000",
1164 "de004_amount_transaction": "000000005000",
1165 "de007_transmission_date_time": "0101120000",
1166 "de011_system_trace_audit_number": "000001",
1167 "de014_expiration_date": "2512",
1168 "de022_pos_entry_mode": {
1169 "pan_entry_mode": "05",
1170 "pin_entry_capability": "1"
1171 },
1172 "de025_pos_condition_code": "00",
1173 "de026_pos_capture_code": "12",
1174 "de037_retrieval_reference_number": "000000000001",
1175 "de041_card_acceptor_terminal_id": "TERM0001",
1176 "de042_card_acceptor_id_code": "MERCHANT000001 ",
1177 "de043_card_acceptor_name_location": "ACME STORE NEW YORK NY US ",
1178 "de049_currency_code_transaction": "840"
1179 }
1180 });
1181
1182 let hex_req = publish(&request, &spec).expect("publish request failed");
1183 let parsed_req = parse(&hex_req, &spec).expect("parse request failed");
1184 assert_eq!(parsed_req["mti"], "0100");
1185 assert_eq!(
1186 parsed_req["fields"]["de002_primary_account_number"],
1187 "4111111111111111"
1188 );
1189 }
1190}