From 547d47eaa059a0ca17414c776faee79533b486a6 Mon Sep 17 00:00:00 2001 From: Dima Dorezyuk Date: Thu, 13 Aug 2026 11:49:41 +0200 Subject: [PATCH 1/2] ZvtFeigTerminal: Add card_reading_control to read_card Signed-off-by: Dima Dorezyuk --- zvt_feig_terminal/src/feig.rs | 66 ++++++++++++++++++++++++++++++----- 1 file changed, 57 insertions(+), 9 deletions(-) diff --git a/zvt_feig_terminal/src/feig.rs b/zvt_feig_terminal/src/feig.rs index d95eea1..d07f88d 100644 --- a/zvt_feig_terminal/src/feig.rs +++ b/zvt_feig_terminal/src/feig.rs @@ -17,7 +17,13 @@ use zvt::{constants, feig, packets, sequences}; /// The card information returned from read-card. pub enum CardInfo { /// Indicatates if we've received a bank card. - Bank, + Bank { + /// The card identification item (Tlv tag 0x1f14), identifying the + /// physical card. Only reported by the terminal when reading with + /// [READ_CARD_READING_CONTROL]. Per the ZVT specification the value + /// contains no plain text card data and may be stored. + card_id: Option, + }, /// Indicates if we've received a member ship card. The stirng is our tag-id. MembershipCard(String), @@ -70,10 +76,18 @@ pub enum Error { /// Default card type, which is chip-card, as defined in Table 6. const CARD_TYPE: Option = Some(0x10); -/// Default value for reading control. +/// Card reading control (Tlv tag 0x1f15) using 'Detect Card'. /// -/// See Tlv tag 0x1f15 for the documentation. -const SHORT_CARD_READING_CONTROL: Option = Some(0xd0); +/// The terminal does not send commands to payment cards: fast, but the card +/// identification item (Tlv tag 0x1f14) cannot be computed for them. +pub const DETECT_CARD_READING_CONTROL: u8 = 0xd0; + +/// Card reading control (Tlv tag 0x1f15) fully reading the card. +/// +/// Slower than [DETECT_CARD_READING_CONTROL], but the terminal reports the +/// card identification item (Tlv tag 0x1f14) for payment cards, which +/// identifies the physical card - see [CardInfo::Bank]. +pub const READ_CARD_READING_CONTROL: u8 = 0xc0; /// Default value for allowed card types. /// @@ -533,7 +547,11 @@ impl Feig { /// /// The call will either return some [CardInfo] or [None] - if there is no /// card presented during the specified [config.read_card_timeout]. - pub async fn read_card(&mut self) -> Result { + /// + /// # Arguments + /// * `card_reading_control` - The card reading control (Tlv tag 0x1f15), + /// see [PAYMENT_CARD_READING_CONTROL] and [DETECT_CARD_READING_CONTROL]. + pub async fn read_card(&mut self, card_reading_control: u8) -> Result { if self.end_of_day_last_instant.elapsed() >= self.end_of_day_max_interval && self.transactions.is_empty() { @@ -545,7 +563,7 @@ impl Feig { card_type: CARD_TYPE, dialog_control: DIALOG_CONTROL, tlv: Some(packets::tlv::ReadCard { - card_reading_control: SHORT_CARD_READING_CONTROL, + card_reading_control: Some(card_reading_control), card_type: ALLOWED_CARDS, }), }; @@ -591,8 +609,19 @@ impl Feig { sequences::ReadCardResponse::StatusInformation(data) => { // Retrieve the card information. let tlv = data.tlv.ok_or(zvt::ZVTError::IncompleteData)?; - // Remove the black-listed application_ids. - let application_id = tlv.subs.iter().find(|sub| match &sub.application_id { + // Full reads report the applications on the card under + // `subs_on_card` (Tlv tag 0x62), detect reads under + // `subs` (Tlv tag 0x60). + let subs_on_card = match &tlv.subs_on_card { + Some(subs_on_card) => subs_on_card.subs.as_slice(), + None => &[], + }; + // Remove the black-listed application_ids: fleet cards + // also present application ids but must be treated as + // membership cards. + let application_id = tlv.subs.iter().chain(subs_on_card).find(|sub| match &sub + .application_id + { None => false, Some(application_id) => APPLICATION_ID_DENYLIST_PREFIX .iter() @@ -601,8 +630,27 @@ impl Feig { if let Some(application_id) = application_id { log::info!("Found the application_id {application_id:?}"); - card_info = Some(CardInfo::Bank); + if tlv.card_identification_item.is_none() + && card_reading_control & 0x10 == 0 + { + // A full read should report the identification + // item - without it the card cannot be identified + // on a renewed presentation. + log::warn!("Payment card without a card identification item"); + } + // The per-card identity - the application_id only + // tells us the payment scheme and is shared between + // cards. + card_info = Some(CardInfo::Bank { + card_id: tlv.card_identification_item, + }); } else if let Some(mut uuid) = tlv.uuid { + if tlv.card_identification_item.is_some() { + // The terminal identified the card as payment + // capable but we treat it as a membership card + // (e.g. a fleet card). + log::warn!("Membership card with a card identification item"); + } uuid = uuid.to_uppercase(); if uuid.len() > 14 { uuid = uuid[uuid.len() - 14..].to_string(); From ac1d76e8db387623bb10d4fe7570d1c2bd07aab8 Mon Sep 17 00:00:00 2001 From: Dima Dorezyuk Date: Wed, 19 Aug 2026 13:07:47 +0200 Subject: [PATCH 2/2] reviewer comment Signed-off-by: Dima Dorezyuk --- zvt_feig_terminal/src/feig.rs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/zvt_feig_terminal/src/feig.rs b/zvt_feig_terminal/src/feig.rs index d07f88d..bc8da95 100644 --- a/zvt_feig_terminal/src/feig.rs +++ b/zvt_feig_terminal/src/feig.rs @@ -17,13 +17,13 @@ use zvt::{constants, feig, packets, sequences}; /// The card information returned from read-card. pub enum CardInfo { /// Indicatates if we've received a bank card. - Bank { + Bank( /// The card identification item (Tlv tag 0x1f14), identifying the /// physical card. Only reported by the terminal when reading with /// [READ_CARD_READING_CONTROL]. Per the ZVT specification the value /// contains no plain text card data and may be stored. - card_id: Option, - }, + Option, + ), /// Indicates if we've received a member ship card. The stirng is our tag-id. MembershipCard(String), @@ -641,9 +641,7 @@ impl Feig { // The per-card identity - the application_id only // tells us the payment scheme and is shared between // cards. - card_info = Some(CardInfo::Bank { - card_id: tlv.card_identification_item, - }); + card_info = Some(CardInfo::Bank(tlv.card_identification_item)); } else if let Some(mut uuid) = tlv.uuid { if tlv.card_identification_item.is_some() { // The terminal identified the card as payment