Skip to main content

hdm_am/
lib.rs

1//! Client for the Armenian fiscal cash register (HDM) protocol.
2//!
3//! Specification: State Revenue Committee of Armenia (ՀՀՊԵԿ),
4//! "ՀՍԿԻՉ - ԴՐԱՄԱՐԿՂԱՅԻՆ ՄԵՔԵՆԱՅԻ ԻՆՏԵԳՐՈՒՄԸ ԱՐՏԱՔԻՆ (ԱՌԵՎՏՐԱՅԻՆ) ԾՐԱԳՐԵՐԻ ՀԵՏ",
5//! version 0.7.3 (April 2025). The original Armenian PDF and an English translation are
6//! checked in under `docs/`.
7//!
8//! # Example
9//!
10//! ```no_run
11//! use hdm_am::{Client, Decimal, InMemorySeq, PrintMode, PrintReceiptRequest};
12//! use std::net::TcpStream;
13//! use std::time::Duration;
14//!
15//! let mut tcp = TcpStream::connect("192.168.1.50:9000")?;
16//! tcp.set_read_timeout(Some(Duration::from_secs(50)))?;
17//! tcp.set_write_timeout(Some(Duration::from_secs(50)))?;
18//!
19//! let mut client = Client::new(tcp, "hdm-password", InMemorySeq::default());
20//! client.login(1, "1234")?;
21//!
22//! let receipt = client.print_receipt(PrintReceiptRequest {
23//!     mode: PrintMode::Simple,
24//!     paid_amount: Decimal::from(1000),
25//!     paid_amount_card: Decimal::ZERO,
26//!     partial_amount: Decimal::ZERO,
27//!     pre_payment_amount: Decimal::ZERO,
28//!     dep: Some(1),
29//!     partner_tin: None,
30//!     use_ext_pos: false,
31//!     payment_system: None,
32//!     rrn: None,
33//!     terminal_id: None,
34//!     e_marks: vec![],
35//!     items: vec![],
36//! })?;
37//!
38//! println!("fiscal number: {}", receipt.fiscal);
39//! # Ok::<(), Box<dyn std::error::Error>>(())
40//! ```
41//!
42//! # Versions and forward compatibility
43//!
44//! Three version axes are easy to confuse, so the crate keeps them separate:
45//!
46//! - **Wire framing version** — the byte in the request header ([`PROTOCOL_VERSION`]). Frozen
47//!   at `05` since spec v0.5 (2017); one framing covers the whole live protocol.
48//! - **Spec (document) version** — which manual revision the operation/field set follows
49//!   ([`SPEC_VERSION`], currently `0.7.3`). This is what grows over time.
50//! - **Crate version** — ordinary semver; not coupled to the spec version (the mapping lives in the
51//!   changelog, not the version number).
52//!
53//! The crate targets the current `0.7.x` line and is built to tolerate *newer* devices without code
54//! changes:
55//!
56//! - **Responses are forward-compatible.** They are `#[non_exhaustive]`, never use
57//!   `#[serde(deny_unknown_fields)]`, and put `#[serde(default)]` on later-added fields — so a newer
58//!   firmware that returns extra fields still deserialises cleanly. Preserve this when adding ops.
59//! - **Requests track a spec revision exactly.** They are ordinary (exhaustive) structs, so a new
60//!   *request* field introduced by a future spec is a breaking change shipped in a major release —
61//!   a deliberate signal that the targeted [`SPEC_VERSION`] moved.
62//!
63//! `docs/history/` holds the full version lineage (v0.3–v0.7.3) and a runbook for adopting a new
64//! spec.
65
66#![warn(missing_docs)]
67#![cfg_attr(test, allow(clippy::unwrap_used, clippy::expect_used))]
68
69pub mod client;
70pub mod error;
71pub mod format;
72pub mod operations;
73pub mod probe;
74pub mod seq;
75
76// Framing and crypto are implementation details: the genuinely-public items (OperationCode,
77// PROTOCOL_VERSION) are re-exported below; everything else stays internal.
78pub(crate) mod crypto;
79pub(crate) mod wire;
80
81pub use client::Client;
82pub use error::{CryptoError, Error, ServerErrorKind, VendorErrorKind};
83pub use format::{DEFAULT_WIDTH, ReceiptLayout, ReceiptLine, format_receipt};
84pub use operations::{
85    CashInOutRequest, DateTimeRequest, DateTimeResponse, DepartmentInfo, DiscountKind,
86    EmptyResponse, FiscalReportKind, FiscalReportRequest, GetReturnableReceiptRequest,
87    HdmTimeSyncRequest, ListOpsAndDepsRequest, ListOpsAndDepsResponse, Operation, OperatorInfo,
88    OperatorLoginRequest, OperatorLoginResponse, OperatorLogoutRequest, PaymentSystemEntry,
89    PaymentSystemsListRequest, PaymentSystemsListResponse, PrintLastReceiptRequest, PrintMode,
90    PrintReceiptRequest, PrintReturnReceiptRequest, ReceiptItem, ReceiptResponse,
91    ReceiptSampleRequest, ReportFilter, ReturnItem, ReturnReceiptResponse, ReturnableReceiptItem,
92    ReturnableReceiptResponse, SetupHeaderFooterRequest, SetupHeaderLogoRequest,
93    SingleEmarkRequest, TaxationKind, TextAlign, TextLine,
94};
95pub use probe::{HdmIdentity, identify};
96pub use seq::{FileSeq, InMemorySeq, SequenceProvider};
97pub use wire::{OperationCode, PROTOCOL_VERSION};
98
99/// Re-exported so consumers can name monetary/quantity values without depending on `rust_decimal`
100/// directly. All amount and quantity fields use this type.
101///
102/// The wire encoding is a JSON **number** (the HDM format), so values round-trip through `f64` and
103/// are bounded by `f64` precision — about 15–16 significant digits. This is inherent to the protocol,
104/// not the crate; it is far above any realistic AMD receipt magnitude (totals ≪ 10¹³ with 2 decimal
105/// places), but a pathological quantity × price could lose precision.
106pub use rust_decimal::Decimal;
107
108/// HDM specification (document) version this crate targets — the operation and field set follow
109/// this manual revision.
110///
111/// Distinct from the wire framing version [`PROTOCOL_VERSION`] (`05`, frozen since spec v0.5)
112/// and from the crate's own semver. The full version lineage and an upgrade runbook are in
113/// `docs/history/`.
114pub const SPEC_VERSION: &str = "0.7.3";