Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add feig payment terminal code #15

Merged
merged 6 commits into from
Feb 7, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
894 changes: 876 additions & 18 deletions Cargo.lock

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ members = [
"zvt_builder",
"zvt_cli",
"zvt_derive",
"zvt_feig_terminal",
]

[workspace.package]
Expand Down
1 change: 1 addition & 0 deletions WORKSPACE
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ crates_repository(
"//zvt_builder:Cargo.toml",
"//zvt_cli:Cargo.toml",
"//zvt_derive:Cargo.toml",
"//zvt_feig_terminal:Cargo.toml",
],
)

Expand Down
17 changes: 17 additions & 0 deletions zvt_feig_terminal/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
load("@rules_rust//rust:defs.bzl", "rust_binary", "rust_library", "rust_test")
load("@crate_index//:defs.bzl", "all_crate_deps")

rust_library(
name = "zvt_feig_terminal",
srcs = glob(["src/*.rs"]),
deps = all_crate_deps() + ["//zvt"],
proc_macro_deps = all_crate_deps(proc_macro = True),
edition = "2021",
visibility = ["//visibility:public"],
)

rust_test(
name = "zvt_feig_terminal_test",
deps = all_crate_deps(normal_dev = True),
crate = ":zvt_feig_terminal",
)
31 changes: 31 additions & 0 deletions zvt_feig_terminal/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
[package]
name = "zvt_feig_terminal"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
zvt = { version = "0.1.0", path = "../zvt" }
anyhow = "1.0.75"
tokio = { version = "1.32.0", features = ["macros", "rt-multi-thread", "net", "sync"] }
log = "0.4.20"
trust-dns-client = "0.23.0"
trust-dns-proto = "0.23.0"
async-trait = "0.1.73"
env_logger = "0.10.0"
tonic = "0.8.3"
tokio-stream = "0.1.14"
serde = { version = "1.0.188", features = ["derive"] }
thiserror = "1.0.48"
time = { version = "0.3.28", features = ["macros"] }
time-macros = "0.2.14"
pin-project = "1.1.3"
futures = "0.3.28"
async-stream = "0.3.5"
mockall = "0.11.4"
mockall_double = "0.3.0"
num-traits = "0.2.17"

[dev-dependencies]
serde_json = "1.0.105"
137 changes: 137 additions & 0 deletions zvt_feig_terminal/src/config.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
use anyhow::{bail, Result};
use serde::Deserialize;
use std::net::Ipv4Addr;

/// The config for the Feig terminal included in the
/// [PoleConfiguration::configuration].
#[derive(serde::Deserialize, PartialEq, Debug, Clone)]
pub struct FeigConfig {
/// The currency code as defined by ISO 4217. See
/// https://en.wikipedia.org/wiki/ISO_4217.
///
/// The input is the string representation of the currency as defined by
/// ISO 4217, e.x. `EUR` or `GBP`.
#[serde(default = "currency")]
#[serde(deserialize_with = "deserialize_iso_4217")]
pub currency: usize,

/// The pre-authorization amount in the smallest currency unit (e.x. Cent).
#[serde(default = "pre_authorization_amount")]
pub pre_authorization_amount: usize,

/// The default time to wait for reading a card in seconds. While a card is being read, the
/// payment terminal cannot do anything else (like refunding a transaction for example).
#[serde(default = "read_card_timeout")]
pub read_card_timeout: u8,

/// The password to the payment terminal.
#[serde(default)]
pub password: usize,
}

/// Deserializer which consumes a string code and returns the numerical code.
fn deserialize_iso_4217<'de, D>(deserializer: D) -> std::result::Result<usize, D::Error>
where
D: serde::Deserializer<'de>,
{
let code = String::deserialize(deserializer)?;
iso_4217(&code).map_err(serde::de::Error::custom)
}

/// The default currency (returns the EUR code).
const fn currency() -> usize {
978
}

/// The default read card timeout in seconds
const fn read_card_timeout() -> u8 {
15
}

/// The default pre-authorization amount in Cent (returns 25 EUR).
const fn pre_authorization_amount() -> usize {
2500
}

impl Default for FeigConfig {
fn default() -> Self {
Self {
currency: currency(),
pre_authorization_amount: pre_authorization_amount(),
read_card_timeout: read_card_timeout(),
password: 0,
}
}
}

/// Maps the currency code (three letters) to a numeric value.
///
/// The mapping is defined under the ISO 4217. See
/// https://en.wikipedia.org/wiki/ISO_4217
fn iso_4217(code: &str) -> Result<usize> {
match code.to_uppercase().as_str() {
// Keep the list sorted by the numeric value.
"SEK" => Ok(752),
"GBP" => Ok(826),
"EUR" => Ok(978),
_ => bail!("Unknown currency code {code}"),
}
}

/// The configuration needed for the entire payment terminal, which contains
/// parsed data.
#[derive(Clone, Debug)]
pub struct Config {
pub terminal_id: String,
/// We only use feig_serial to make sure we are connected to the proper
/// terminal.
pub feig_serial: String,
pub ip_address: Ipv4Addr,
/// Parsed from [PoleConfiguration::configuration].
pub feig_config: FeigConfig,
/// Maximum number of concurrent transactions.
pub transactions_max_num: usize,
}

impl Default for Config {
fn default() -> Self {
Self {
terminal_id: String::default(),
feig_serial: String::default(),
ip_address: Ipv4Addr::new(0, 0, 0, 0),
feig_config: FeigConfig::default(),
transactions_max_num: 1,
}
}
}

#[cfg(test)]
mod tests {

use super::*;

#[test]
fn test_feig_config() {
// Valid inputs.
let empty = serde_json::from_str::<FeigConfig>("{}").unwrap();
assert_eq!(empty, FeigConfig::default());

let with_currency = serde_json::from_str::<FeigConfig>("{\"currency\": \"GBP\"}").unwrap();
assert_eq!(with_currency.currency, 826);
assert_eq!(with_currency.pre_authorization_amount, 2500);

let with_all = serde_json::from_str::<FeigConfig>(
"{\"currency\": \"GBP\", \"pre_authorization_amount\": 10}",
)
.unwrap();
assert_eq!(with_all.currency, 826);
assert_eq!(with_all.pre_authorization_amount, 10);

// Invalid inputs.
assert!(serde_json::from_str::<FeigConfig>("{\"currency\": \"ABC\"}").is_err());
assert!(serde_json::from_str::<FeigConfig>("{\"currency\": 123}").is_err());
assert!(
serde_json::from_str::<FeigConfig>("{\"pre_authorization_amount\": \"AB\"}").is_err()
);
}
}
Loading