use serde::{Deserialize, Serialize};
use crate::lrc::LrcMode;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "lowercase")]
pub enum ConnectionState {
#[default]
Disconnected,
Connecting,
Connected,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "camelCase")]
pub enum TransactionOutcome {
Ok,
Ko,
CardNotPresent,
UnknownTag,
#[default]
Unknown,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "lowercase")]
pub enum CardType {
Debit,
Credit,
Other,
#[default]
Unknown,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "camelCase")]
pub enum TransactionEntryMode {
Icc,
Mag,
Manual,
ClessMag,
ClessIcc,
#[default]
Unknown,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "lowercase")]
pub enum PaymentCardType {
#[default]
Auto,
Debit,
Credit,
Other,
}
impl PaymentCardType {
#[must_use]
pub fn as_digit(self) -> char {
match self {
PaymentCardType::Auto => '0',
PaymentCardType::Debit => '1',
PaymentCardType::Credit => '2',
PaymentCardType::Other => '3',
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum TokenizationService {
Recurring,
UnscheduledOrOneClick,
}
impl TokenizationService {
#[must_use]
pub fn is_recurring(self) -> bool {
matches!(self, TokenizationService::Recurring)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TokenizationRequest {
pub service: TokenizationService,
pub contract_code: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Ecr17Config {
pub host: String,
pub port: Option<u16>,
pub terminal_id: String,
pub cash_register_id: String,
pub lrc_mode: Option<LrcMode>,
pub keep_alive: Option<bool>,
pub auto_reconnect: Option<bool>,
pub connection_timeout_ms: Option<u32>,
pub response_timeout_ms: Option<u32>,
pub ack_timeout_ms: Option<u32>,
pub receipt_drain_ms: Option<u32>,
pub retry_count: Option<u32>,
pub retry_delay_ms: Option<u32>,
pub debug: Option<bool>,
}
pub type PosTerminalStatus = i32;
#[must_use]
pub fn pos_terminal_status_message(status: PosTerminalStatus) -> &'static str {
match status {
0 => "Terminal not configured",
1 => "Terminal configured, no DLL",
2 => "Terminal operative (after a DLL)",
3 => "Terminal not aligned (first DLL requested)",
4 => "KMPB/KPOS key corrupted (first DLL requested)",
5 => "DLL solicited by GT pending",
6 => "Remote SW updated request pending",
_ => "Unknown",
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PosStatusResponse {
pub terminal_id: String,
pub terminal_date_time: String,
pub status: PosTerminalStatus,
pub software_release: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PaymentRequest {
pub amount_cents: i64,
pub cash_register_id: Option<String>,
pub payment_type: Option<PaymentCardType>,
pub card_already_present: Option<bool>,
pub receipt_text: Option<String>,
pub tokenization: Option<TokenizationRequest>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "camelCase")]
pub struct ReversalRequest {
pub cash_register_id: Option<String>,
pub stan: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PreAuthRequest {
pub amount_cents: i64,
pub cash_register_id: Option<String>,
pub payment_type: Option<PaymentCardType>,
pub card_already_present: Option<bool>,
pub receipt_text: Option<String>,
pub tokenization: Option<TokenizationRequest>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct IncrementalAuthRequest {
pub amount_cents: i64,
pub original_pre_auth_code: String,
pub cash_register_id: Option<String>,
pub receipt_text: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PreAuthClosureRequest {
pub amount_cents: i64,
pub original_pre_auth_code: String,
pub cash_register_id: Option<String>,
pub receipt_text: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "camelCase")]
pub struct CardVerificationRequest {
pub cash_register_id: Option<String>,
pub payment_type: Option<PaymentCardType>,
pub tokenization: Option<TokenizationRequest>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
#[serde(rename_all = "camelCase")]
pub struct CurrencyExchange {
pub applied: bool,
pub rate: Option<f64>,
pub currency_code: Option<String>,
pub amount_cents: Option<i64>,
pub precision: Option<i32>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
#[serde(rename_all = "camelCase")]
pub struct PaymentResult {
pub outcome: TransactionOutcome,
pub result_code: String,
pub pan: Option<String>,
pub entry_mode: Option<TransactionEntryMode>,
pub auth_code: Option<String>,
pub host_date_time: Option<String>,
pub card_type: Option<CardType>,
pub acquirer_id: Option<String>,
pub stan: Option<String>,
pub online_id: Option<String>,
pub error_description: Option<String>,
pub currency_exchange: Option<CurrencyExchange>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "camelCase")]
pub struct ReversalResult {
pub outcome: TransactionOutcome,
pub result_code: String,
pub pan: Option<String>,
pub entry_mode: Option<TransactionEntryMode>,
pub host_date_time: Option<String>,
pub card_type: Option<CardType>,
pub acquirer_id: Option<String>,
pub stan: Option<String>,
pub online_id: Option<String>,
pub action_code: Option<String>,
pub error_description: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
#[serde(rename_all = "camelCase")]
pub struct PreAuthResult {
pub outcome: TransactionOutcome,
pub result_code: String,
pub pan: Option<String>,
pub entry_mode: Option<TransactionEntryMode>,
pub auth_code: Option<String>,
pub pre_authorized_amount_cents: Option<i64>,
pub pre_auth_code: Option<String>,
pub action_code: Option<String>,
pub host_date_time: Option<String>,
pub card_type: Option<CardType>,
pub acquirer_id: Option<String>,
pub stan: Option<String>,
pub online_id: Option<String>,
pub error_description: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "camelCase")]
pub struct CardVerificationResult {
pub outcome: TransactionOutcome,
pub result_code: String,
pub pan: Option<String>,
pub entry_mode: Option<TransactionEntryMode>,
pub auth_code: Option<String>,
pub host_date_time: Option<String>,
pub card_type: Option<CardType>,
pub acquirer_id: Option<String>,
pub stan: Option<String>,
pub online_id: Option<String>,
pub action_code: Option<String>,
pub error_description: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "camelCase")]
pub struct TotalsResult {
pub outcome: TransactionOutcome,
pub result_code: String,
pub pos_total_cents: i64,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "camelCase")]
pub struct CloseSessionResult {
pub outcome: TransactionOutcome,
pub result_code: String,
pub pos_total_cents: Option<i64>,
pub host_total_cents: Option<i64>,
pub action_code: Option<String>,
pub error_description: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "camelCase")]
pub struct VasResult {
pub response_id: String,
pub response_message: String,
pub order_id: Option<String>,
pub raw_xml: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ProgressEvent {
pub message: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ReceiptLine {
pub text: String,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn enums_serialize_to_ts_string_unions() {
assert_eq!(
serde_json::to_string(&ConnectionState::Disconnected).unwrap(),
"\"disconnected\""
);
assert_eq!(
serde_json::to_string(&TransactionOutcome::CardNotPresent).unwrap(),
"\"cardNotPresent\""
);
assert_eq!(
serde_json::to_string(&TransactionOutcome::UnknownTag).unwrap(),
"\"unknownTag\""
);
assert_eq!(
serde_json::to_string(&CardType::Debit).unwrap(),
"\"debit\""
);
assert_eq!(
serde_json::to_string(&TransactionEntryMode::ClessMag).unwrap(),
"\"clessMag\""
);
assert_eq!(
serde_json::to_string(&PaymentCardType::Auto).unwrap(),
"\"auto\""
);
assert_eq!(
serde_json::to_string(&TokenizationService::UnscheduledOrOneClick).unwrap(),
"\"unscheduledOrOneClick\""
);
}
#[test]
fn payment_card_type_digit_mapping() {
assert_eq!(PaymentCardType::Auto.as_digit(), '0');
assert_eq!(PaymentCardType::Debit.as_digit(), '1');
assert_eq!(PaymentCardType::Credit.as_digit(), '2');
assert_eq!(PaymentCardType::Other.as_digit(), '3');
}
#[test]
fn request_deserializes_camel_case_and_defaults_missing_options() {
let r: PaymentRequest = serde_json::from_str(r#"{"amountCents":650}"#).unwrap();
assert_eq!(r.amount_cents, 650);
assert_eq!(r.payment_type, None);
assert_eq!(r.card_already_present, None);
let full: PaymentRequest = serde_json::from_str(
r#"{"amountCents":100,"paymentType":"credit","cardAlreadyPresent":true,"receiptText":"x"}"#,
)
.unwrap();
assert_eq!(full.payment_type, Some(PaymentCardType::Credit));
assert_eq!(full.card_already_present, Some(true));
assert_eq!(full.receipt_text.as_deref(), Some("x"));
}
#[test]
fn tokenization_request_round_trip() {
let t = TokenizationRequest {
service: TokenizationService::Recurring,
contract_code: "ABC".into(),
};
let json = serde_json::to_string(&t).unwrap();
assert_eq!(json, r#"{"service":"recurring","contractCode":"ABC"}"#);
assert_eq!(
serde_json::from_str::<TokenizationRequest>(&json).unwrap(),
t
);
assert!(t.service.is_recurring());
}
#[test]
fn status_message_lookup() {
assert_eq!(
pos_terminal_status_message(2),
"Terminal operative (after a DLL)"
);
assert_eq!(pos_terminal_status_message(-1), "Unknown");
assert_eq!(pos_terminal_status_message(99), "Unknown");
}
#[test]
fn config_round_trip_camel_case() {
let json =
r#"{"host":"10.0.0.5","terminalId":"12345678","cashRegisterId":"1","lrcMode":"stx"}"#;
let cfg: Ecr17Config = serde_json::from_str(json).unwrap();
assert_eq!(cfg.host, "10.0.0.5");
assert_eq!(cfg.lrc_mode, Some(LrcMode::Stx));
assert_eq!(cfg.port, None);
}
}