diff --git a/zvt/src/packets.rs b/zvt/src/packets.rs index 3da248f..7718fe0 100644 --- a/zvt/src/packets.rs +++ b/zvt/src/packets.rs @@ -144,6 +144,47 @@ pub struct Registration { pub tlv: Option, } +#[derive(Debug, Default, PartialEq, Zvt)] +#[zvt_control_field(class = 0x06, instr = 0x01)] +pub struct Authorization { + #[zvt_bmp(number = 0x04, length = length::Fixed<6>, encoding = encoding::Bcd)] + pub amount: Option, + + #[zvt_bmp(number = 0x49, length = length::Fixed<2>, encoding = encoding::Bcd)] + pub currency: Option, + + #[zvt_bmp(number = 0x19)] + pub payment_type: Option, + + #[zvt_bmp(number = 0x0e, length = length::Fixed<2>, encoding = encoding::Bcd)] + pub expiry_date: Option, + + #[zvt_bmp(number = 0x22, length = length::Llv, encoding = encoding::Bcd)] + pub card_number: Option, + + #[zvt_bmp(number = 0x23, length = length::Llv, encoding= encoding::Hex)] + pub track_2_data: Option, + + // Unclear how to interpret this. + #[zvt_bmp(number = 0x01)] + pub timeout: Option, + + #[zvt_bmp(number = 0x02)] + pub maximum_no_of_status_info: Option, + + #[zvt_bmp(number = 0x05)] + pub pump_no: Option, + + #[zvt_bmp(number = 0x3c, length = length::Lllv)] + pub additional_text: Option, + + #[zvt_bmp(number = 0x8a)] + pub zvt_card_type: Option, + + #[zvt_bmp(number = 0x06, length = length::Tlv)] + pub tlv: Option, +} + #[derive(Debug, Default, PartialEq, Eq, Zvt)] #[zvt_control_field(class = 0x06, instr = 0x0f)] pub struct CompletionData { diff --git a/zvt/src/packets/tlv.rs b/zvt/src/packets/tlv.rs index 512f910..8520c21 100644 --- a/zvt/src/packets/tlv.rs +++ b/zvt/src/packets/tlv.rs @@ -110,6 +110,18 @@ pub struct PreAuthData { pub bmp_data: Option, } +#[derive(Debug, Default, PartialEq, Zvt)] +pub struct AuthorizationData { + #[zvt_tlv(tag = 0x41, encoding = encoding::Hex)] + pub card_type: Option, + + #[zvt_tlv(tag = 0x43, encoding = encoding::Hex)] + pub application_id: Option, + + #[zvt_tlv(tag = 0x1F15)] + pub card_reading_control: Option, +} + #[derive(Debug, PartialEq, Zvt)] pub struct Diagnosis { #[zvt_tlv(tag = 0x1b)] diff --git a/zvt/src/sequences.rs b/zvt/src/sequences.rs index a33fd27..8ceac90 100644 --- a/zvt/src/sequences.rs +++ b/zvt/src/sequences.rs @@ -68,6 +68,65 @@ impl Sequence for Registration { type Output = RegistrationResponse; } +/// Authorization sequence as defined under 2.1. +/// +/// Using the command Authorization the ECR initiates a payment transaction. +pub struct Authorization; + +/// Response to [packets::Reservation] message, as defined under 2.8. +/// +/// The response is the same as for Authorization, defined in chapter 2.1. +#[derive(Debug, ZvtEnum)] +#[allow(clippy::large_enum_variant)] +pub enum AuthorizationResponse { + /// 2.2.4 + IntermediateStatusInformation(packets::IntermediateStatusInformation), + // 2.2.5 produces no message. + /// 2.2.6 + StatusInformation(packets::StatusInformation), + /// 2.2.7 + PrintLine(packets::PrintLine), + /// 2.2.7 + PrintTextBlock(packets::PrintTextBlock), + // 2.2.8 produces no message. + /// 2.2.9 + CompletionData(packets::CompletionData), + /// 2.2.9 + Abort(packets::Abort), +} + +impl Sequence for Authorization { + type Input = packets::Authorization; + type Output = AuthorizationResponse; + + fn into_stream<'a, Source>( + input: &'a Self::Input, + src: &'a mut PacketTransport, + ) -> Pin> + Send + 'a>> + where + Source: AsyncReadExt + AsyncWriteExt + Unpin + Send, + Self: 'a, + { + let s = try_stream! { + // 2.1 + src.write_packet_with_ack(input).await?; + + loop { + let packet = src.read_packet().await?; + src.write_packet(&packets::Ack {}).await?; + match packet { + AuthorizationResponse::CompletionData(_) | AuthorizationResponse::Abort(_) => { + yield packet; + break; + } + _ => yield packet, + } + } + }; + Box::pin(s) + } +} + /// Read-card sequence as defined under 2.21. /// /// With this command the PT reads a chip-card/magnet-card and transmits the @@ -348,28 +407,6 @@ impl Sequence for EndOfDay { /// amount via a [PartialReversal] or Book Total (06 24, not implemented). pub struct Reservation; -/// Response to [packets::Reservation] message, as defined under 2.8. -/// -/// The response is the same as for Authorization, defined in chapter 2.1. -#[derive(Debug, ZvtEnum)] -#[allow(clippy::large_enum_variant)] -pub enum AuthorizationResponse { - /// 2.2.4 - IntermediateStatusInformation(packets::IntermediateStatusInformation), - // 2.2.5 produces no message. - /// 2.2.6 - StatusInformation(packets::StatusInformation), - /// 2.2.7 - PrintLine(packets::PrintLine), - /// 2.2.7 - PrintTextBlock(packets::PrintTextBlock), - // 2.2.8 produces no message. - /// 2.2.9 - CompletionData(packets::CompletionData), - /// 2.2.9 - Abort(packets::Abort), -} - impl Sequence for Reservation { type Input = packets::Reservation; type Output = AuthorizationResponse; diff --git a/zvt_cli/src/main.rs b/zvt_cli/src/main.rs index 920e0af..5f8fdab 100644 --- a/zvt_cli/src/main.rs +++ b/zvt_cli/src/main.rs @@ -16,6 +16,7 @@ enum SubCommands { Status(StatusArgs), FactoryReset(FactoryResetArgs), Registration(RegistrationArgs), + Authorization(AuthorizationArgs), SetTerminalId(SetTerminalIdArgs), Initialization(InitializationArgs), Diagnosis(DiagnosisArgs), @@ -50,6 +51,23 @@ struct RegistrationArgs { config_byte: u8, } +#[derive(FromArgs, PartialEq, Debug)] +/// Do payment using authorization command. +#[argh(subcommand, name = "pay")] +struct AuthorizationArgs { + /// amount to pay (in cents). + #[argh(positional)] + amount: usize, + + /// currency code. Defauls to 978 (= EUR). + #[argh(option, default = "978")] + currency_code: usize, + + /// payment type. Defaults to 0 . + #[argh(option, default = "0")] + payment_type: u8, +} + #[derive(FromArgs, PartialEq, Debug)] /// Sets the terminal id. #[argh(subcommand, name = "set_terminal_id")] @@ -294,6 +312,31 @@ async fn registration( Ok(()) } +async fn authorization( + socket: &mut PacketTransport, + args: &AuthorizationArgs, +) -> Result<()> { + let request = packets::Authorization { + amount: Some(args.amount), + currency: Some(args.currency_code), + payment_type: Some(args.payment_type), + ..Default::default() + }; + + let mut stream = sequences::Authorization::into_stream(&request, socket); + use sequences::AuthorizationResponse::*; + while let Some(response) = stream.next().await { + match response? { + IntermediateStatusInformation(_) | CompletionData(_) => (), + PrintLine(data) => log::info!("{}", data.text), + PrintTextBlock(data) => log::info!("{data:#?}"), + Abort(data) => bail!("Received Abort: {:?}", data), + StatusInformation(data) => log::info!("StatusInformation: {:#?}", data), + } + } + Ok(()) +} + async fn set_terminal_id( socket: &mut PacketTransport, password: usize, @@ -545,6 +588,7 @@ async fn main() -> Result<()> { SubCommands::Status(_) => status(&mut socket).await?, SubCommands::FactoryReset(_) => factory_reset(&mut socket, args.password).await?, SubCommands::Registration(a) => registration(&mut socket, args.password, &a).await?, + SubCommands::Authorization(a) => authorization(&mut socket, &a).await?, SubCommands::SetTerminalId(a) => set_terminal_id(&mut socket, args.password, &a).await?, SubCommands::Initialization(_) => initialization(&mut socket, args.password).await?, SubCommands::Diagnosis(a) => diagnosis(&mut socket, &a).await?,