1use serde::{Deserialize, Serialize};
8
9use crate::lrc::LrcMode;
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
17#[serde(rename_all = "lowercase")]
18pub enum ConnectionState {
19 #[default]
21 Disconnected,
22 Connecting,
24 Connected,
26}
27
28#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
31#[serde(rename_all = "camelCase")]
32pub enum TransactionOutcome {
33 Ok,
35 Ko,
37 CardNotPresent,
39 UnknownTag,
41 #[default]
43 Unknown,
44}
45
46#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
48#[serde(rename_all = "lowercase")]
49pub enum CardType {
50 Debit,
52 Credit,
54 Other,
56 #[default]
58 Unknown,
59}
60
61#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
63#[serde(rename_all = "camelCase")]
64pub enum TransactionEntryMode {
65 Icc,
67 Mag,
69 Manual,
71 ClessMag,
73 ClessIcc,
75 #[default]
77 Unknown,
78}
79
80#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
82#[serde(rename_all = "lowercase")]
83pub enum PaymentCardType {
84 #[default]
86 Auto,
87 Debit,
89 Credit,
91 Other,
93}
94
95impl PaymentCardType {
96 #[must_use]
98 pub fn as_digit(self) -> char {
99 match self {
100 PaymentCardType::Auto => '0',
101 PaymentCardType::Debit => '1',
102 PaymentCardType::Credit => '2',
103 PaymentCardType::Other => '3',
104 }
105 }
106}
107
108#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
110#[serde(rename_all = "camelCase")]
111pub enum TokenizationService {
112 Recurring,
114 UnscheduledOrOneClick,
116}
117
118impl TokenizationService {
119 #[must_use]
121 pub fn is_recurring(self) -> bool {
122 matches!(self, TokenizationService::Recurring)
123 }
124}
125
126#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
128#[serde(rename_all = "camelCase")]
129pub struct TokenizationRequest {
130 pub service: TokenizationService,
132 pub contract_code: String,
134}
135
136#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
142#[serde(rename_all = "camelCase")]
143pub struct Ecr17Config {
144 pub host: String,
146 pub port: Option<u16>,
148
149 pub terminal_id: String,
151 pub cash_register_id: String,
153
154 pub lrc_mode: Option<LrcMode>,
156
157 pub keep_alive: Option<bool>,
159 pub auto_reconnect: Option<bool>,
162
163 pub connection_timeout_ms: Option<u32>,
165 pub response_timeout_ms: Option<u32>,
167 pub ack_timeout_ms: Option<u32>,
169
170 pub receipt_drain_ms: Option<u32>,
173
174 pub retry_count: Option<u32>,
176 pub retry_delay_ms: Option<u32>,
178
179 pub debug: Option<bool>,
181}
182
183pub type PosTerminalStatus = i32;
186
187#[must_use]
189pub fn pos_terminal_status_message(status: PosTerminalStatus) -> &'static str {
190 match status {
191 0 => "Terminal not configured",
192 1 => "Terminal configured, no DLL",
193 2 => "Terminal operative (after a DLL)",
194 3 => "Terminal not aligned (first DLL requested)",
195 4 => "KMPB/KPOS key corrupted (first DLL requested)",
196 5 => "DLL solicited by GT pending",
197 6 => "Remote SW updated request pending",
198 _ => "Unknown",
199 }
200}
201
202#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
204#[serde(rename_all = "camelCase")]
205pub struct PosStatusResponse {
206 pub terminal_id: String,
208 pub terminal_date_time: String,
213 pub status: PosTerminalStatus,
216 pub software_release: String,
218}
219
220#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
226#[serde(rename_all = "camelCase")]
227pub struct PaymentRequest {
228 pub amount_cents: i64,
230 pub cash_register_id: Option<String>,
232 pub payment_type: Option<PaymentCardType>,
234 pub card_already_present: Option<bool>,
236 pub receipt_text: Option<String>,
238 pub tokenization: Option<TokenizationRequest>,
240}
241
242#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
244#[serde(rename_all = "camelCase")]
245pub struct ReversalRequest {
246 pub cash_register_id: Option<String>,
248 pub stan: Option<String>,
251}
252
253#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
255#[serde(rename_all = "camelCase")]
256pub struct PreAuthRequest {
257 pub amount_cents: i64,
259 pub cash_register_id: Option<String>,
261 pub payment_type: Option<PaymentCardType>,
263 pub card_already_present: Option<bool>,
265 pub receipt_text: Option<String>,
267 pub tokenization: Option<TokenizationRequest>,
269}
270
271#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
273#[serde(rename_all = "camelCase")]
274pub struct IncrementalAuthRequest {
275 pub amount_cents: i64,
277 pub original_pre_auth_code: String,
279 pub cash_register_id: Option<String>,
281 pub receipt_text: Option<String>,
283}
284
285#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
287#[serde(rename_all = "camelCase")]
288pub struct PreAuthClosureRequest {
289 pub amount_cents: i64,
291 pub original_pre_auth_code: String,
293 pub cash_register_id: Option<String>,
295 pub receipt_text: Option<String>,
297}
298
299#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
301#[serde(rename_all = "camelCase")]
302pub struct CardVerificationRequest {
303 pub cash_register_id: Option<String>,
305 pub payment_type: Option<PaymentCardType>,
307 pub tokenization: Option<TokenizationRequest>,
309}
310
311#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
317#[serde(rename_all = "camelCase")]
318pub struct CurrencyExchange {
319 pub applied: bool,
321 pub rate: Option<f64>,
323 pub currency_code: Option<String>,
325 pub amount_cents: Option<i64>,
327 pub precision: Option<i32>,
329}
330
331#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
333#[serde(rename_all = "camelCase")]
334pub struct PaymentResult {
335 pub outcome: TransactionOutcome,
337 pub result_code: String,
339 pub pan: Option<String>,
341 pub entry_mode: Option<TransactionEntryMode>,
343 pub auth_code: Option<String>,
345 pub host_date_time: Option<String>,
347 pub card_type: Option<CardType>,
349 pub acquirer_id: Option<String>,
351 pub stan: Option<String>,
353 pub online_id: Option<String>,
355 pub error_description: Option<String>,
357 pub currency_exchange: Option<CurrencyExchange>,
359}
360
361#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
363#[serde(rename_all = "camelCase")]
364pub struct ReversalResult {
365 pub outcome: TransactionOutcome,
367 pub result_code: String,
369 pub pan: Option<String>,
371 pub entry_mode: Option<TransactionEntryMode>,
373 pub host_date_time: Option<String>,
375 pub card_type: Option<CardType>,
377 pub acquirer_id: Option<String>,
379 pub stan: Option<String>,
381 pub online_id: Option<String>,
383 pub action_code: Option<String>,
385 pub error_description: Option<String>,
387}
388
389#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
391#[serde(rename_all = "camelCase")]
392pub struct PreAuthResult {
393 pub outcome: TransactionOutcome,
395 pub result_code: String,
397 pub pan: Option<String>,
399 pub entry_mode: Option<TransactionEntryMode>,
401 pub auth_code: Option<String>,
403 pub pre_authorized_amount_cents: Option<i64>,
405 pub pre_auth_code: Option<String>,
407 pub action_code: Option<String>,
409 pub host_date_time: Option<String>,
411 pub card_type: Option<CardType>,
413 pub acquirer_id: Option<String>,
415 pub stan: Option<String>,
417 pub online_id: Option<String>,
419 pub error_description: Option<String>,
421}
422
423#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
425#[serde(rename_all = "camelCase")]
426pub struct CardVerificationResult {
427 pub outcome: TransactionOutcome,
429 pub result_code: String,
431 pub pan: Option<String>,
433 pub entry_mode: Option<TransactionEntryMode>,
435 pub auth_code: Option<String>,
437 pub host_date_time: Option<String>,
439 pub card_type: Option<CardType>,
441 pub acquirer_id: Option<String>,
443 pub stan: Option<String>,
445 pub online_id: Option<String>,
447 pub action_code: Option<String>,
449 pub error_description: Option<String>,
451}
452
453#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
455#[serde(rename_all = "camelCase")]
456pub struct TotalsResult {
457 pub outcome: TransactionOutcome,
459 pub result_code: String,
461 pub pos_total_cents: i64,
463}
464
465#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
467#[serde(rename_all = "camelCase")]
468pub struct CloseSessionResult {
469 pub outcome: TransactionOutcome,
471 pub result_code: String,
473 pub pos_total_cents: Option<i64>,
475 pub host_total_cents: Option<i64>,
477 pub action_code: Option<String>,
479 pub error_description: Option<String>,
481}
482
483#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
485#[serde(rename_all = "camelCase")]
486pub struct VasResult {
487 pub response_id: String,
489 pub response_message: String,
491 pub order_id: Option<String>,
493 pub raw_xml: String,
495}
496
497#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
503#[serde(rename_all = "camelCase")]
504pub struct ProgressEvent {
505 pub message: String,
507}
508
509#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
511#[serde(rename_all = "camelCase")]
512pub struct ReceiptLine {
513 pub text: String,
515}
516
517#[cfg(test)]
518mod tests {
519 use super::*;
520
521 #[test]
522 fn enums_serialize_to_ts_string_unions() {
523 assert_eq!(
524 serde_json::to_string(&ConnectionState::Disconnected).unwrap(),
525 "\"disconnected\""
526 );
527 assert_eq!(
528 serde_json::to_string(&TransactionOutcome::CardNotPresent).unwrap(),
529 "\"cardNotPresent\""
530 );
531 assert_eq!(
532 serde_json::to_string(&TransactionOutcome::UnknownTag).unwrap(),
533 "\"unknownTag\""
534 );
535 assert_eq!(
536 serde_json::to_string(&CardType::Debit).unwrap(),
537 "\"debit\""
538 );
539 assert_eq!(
540 serde_json::to_string(&TransactionEntryMode::ClessMag).unwrap(),
541 "\"clessMag\""
542 );
543 assert_eq!(
544 serde_json::to_string(&PaymentCardType::Auto).unwrap(),
545 "\"auto\""
546 );
547 assert_eq!(
548 serde_json::to_string(&TokenizationService::UnscheduledOrOneClick).unwrap(),
549 "\"unscheduledOrOneClick\""
550 );
551 }
552
553 #[test]
554 fn payment_card_type_digit_mapping() {
555 assert_eq!(PaymentCardType::Auto.as_digit(), '0');
556 assert_eq!(PaymentCardType::Debit.as_digit(), '1');
557 assert_eq!(PaymentCardType::Credit.as_digit(), '2');
558 assert_eq!(PaymentCardType::Other.as_digit(), '3');
559 }
560
561 #[test]
562 fn request_deserializes_camel_case_and_defaults_missing_options() {
563 let r: PaymentRequest = serde_json::from_str(r#"{"amountCents":650}"#).unwrap();
565 assert_eq!(r.amount_cents, 650);
566 assert_eq!(r.payment_type, None);
567 assert_eq!(r.card_already_present, None);
568
569 let full: PaymentRequest = serde_json::from_str(
570 r#"{"amountCents":100,"paymentType":"credit","cardAlreadyPresent":true,"receiptText":"x"}"#,
571 )
572 .unwrap();
573 assert_eq!(full.payment_type, Some(PaymentCardType::Credit));
574 assert_eq!(full.card_already_present, Some(true));
575 assert_eq!(full.receipt_text.as_deref(), Some("x"));
576 }
577
578 #[test]
579 fn tokenization_request_round_trip() {
580 let t = TokenizationRequest {
581 service: TokenizationService::Recurring,
582 contract_code: "ABC".into(),
583 };
584 let json = serde_json::to_string(&t).unwrap();
585 assert_eq!(json, r#"{"service":"recurring","contractCode":"ABC"}"#);
586 assert_eq!(
587 serde_json::from_str::<TokenizationRequest>(&json).unwrap(),
588 t
589 );
590 assert!(t.service.is_recurring());
591 }
592
593 #[test]
594 fn status_message_lookup() {
595 assert_eq!(
596 pos_terminal_status_message(2),
597 "Terminal operative (after a DLL)"
598 );
599 assert_eq!(pos_terminal_status_message(-1), "Unknown");
600 assert_eq!(pos_terminal_status_message(99), "Unknown");
601 }
602
603 #[test]
604 fn config_round_trip_camel_case() {
605 let json =
606 r#"{"host":"10.0.0.5","terminalId":"12345678","cashRegisterId":"1","lrcMode":"stx"}"#;
607 let cfg: Ecr17Config = serde_json::from_str(json).unwrap();
608 assert_eq!(cfg.host, "10.0.0.5");
609 assert_eq!(cfg.lrc_mode, Some(LrcMode::Stx));
610 assert_eq!(cfg.port, None);
611 }
612}