From 4fec003502556e1b09cbcb6a397078d89283638b Mon Sep 17 00:00:00 2001 From: Wojciech Kula Date: Wed, 18 Dec 2024 09:59:20 +0100 Subject: [PATCH] Accept bank cards only for specific connectors Signed-off-by: Wojciech Kula --- modules/RsPaymentTerminal/manifest.yaml | 12 ++ modules/RsPaymentTerminal/src/main.rs | 235 +++++++++++++++++++++--- 2 files changed, 222 insertions(+), 25 deletions(-) diff --git a/modules/RsPaymentTerminal/manifest.yaml b/modules/RsPaymentTerminal/manifest.yaml index 53e766e7a..f433b99a3 100644 --- a/modules/RsPaymentTerminal/manifest.yaml +++ b/modules/RsPaymentTerminal/manifest.yaml @@ -40,6 +40,18 @@ config: password: description: Password for the Feig terminal. type: integer + accept_credit_cards: + description: Indicates whether credit cards should be accepted for all connectors + type: boolean + default: true + credit_card_connectors: + description: >- + If the `accept_credit_cards` is set to true credit cards + will only provide authentication to the given connectors. + If list is empty, authentication will happen to all the connectors. + type: string + default: "" + requires: session: interface: session_cost diff --git a/modules/RsPaymentTerminal/src/main.rs b/modules/RsPaymentTerminal/src/main.rs index ed75f09e8..7b329c58a 100644 --- a/modules/RsPaymentTerminal/src/main.rs +++ b/modules/RsPaymentTerminal/src/main.rs @@ -127,18 +127,28 @@ mod sync_feig { #[mockall_double::double] use sync_feig::SyncFeig; +fn string_to_vec(s: &String) -> Vec { + s.split(',') + .filter_map(|s| s.trim().parse::().ok()) + .collect() +} + impl ProvidedIdToken { - fn new(id_token: String, authorization_type: AuthorizationType) -> Self { + fn new( + id_token: String, + authorization_type: AuthorizationType, + connectors: Option>, + ) -> Self { Self { parent_id_token: None, id_token: IdToken { value: id_token, r#type: IdTokenType::Local, - additional_info: None + additional_info: None, }, authorization_type, certificate: None, - connectors: None, + connectors: connectors, iso_15118_certificate_hash_data: None, prevalidated: None, request_id: None, @@ -154,6 +164,12 @@ pub struct PaymentTerminalModule { /// The Feig interface. feig: SyncFeig, + + /// For which connectors credit cards should be accepted + credit_cards_connectors: Vec, + + /// If credit cards should be accepted + accept_credit_cards: bool, } impl PaymentTerminalModule { @@ -188,26 +204,43 @@ impl PaymentTerminalModule { }; let card_info = read_card_loop()?; - let provided_token = match card_info { + if let Some(provided_token) = match card_info { CardInfo::Bank => { - self.feig.begin_transaction(&token)?; - - // Reuse the bank token as invoice token so we can use the - // invoice token later on to commit our transactions. - ProvidedIdToken::new(token, AuthorizationType::BankCard) - } - CardInfo::MembershipCard(id_token) => { - ProvidedIdToken::new(id_token, AuthorizationType::RFID) + if !self.accept_credit_cards { + None + } else { + self.feig.begin_transaction(&token)?; + let credit_cards_connectors = if self.credit_cards_connectors.is_empty() { + None + } else { + Some(self.credit_cards_connectors.clone()) + }; + // Reuse the bank token as invoice token so we can use the + // invoice token later on to commit our transactions. + Some(ProvidedIdToken::new( + token, + AuthorizationType::BankCard, + credit_cards_connectors, + )) + } } - }; - publishers.token_provider.provided_token(provided_token)?; + CardInfo::MembershipCard(id_token) => Some(ProvidedIdToken::new( + id_token, + AuthorizationType::RFID, + None, + )), + } { + publishers.token_provider.provided_token(provided_token)?; + } Ok(()) } /// The implementation of the `SessionCostClientSubscriber::on_session_cost`, /// but here we can return errors. fn on_session_cost_impl(&self, context: &Context, value: SessionCost) -> Result<()> { - let Some(id_tag) = value.id_tag else { return Ok(()) }; + let Some(id_tag) = value.id_tag else { + return Ok(()); + }; // We only care about bank cards. match id_tag.authorization_type { @@ -292,6 +325,8 @@ fn main() -> Result<()> { let pt_module = Arc::new(PaymentTerminalModule { tx, feig: SyncFeig::new(pt_config), + credit_cards_connectors: string_to_vec(&config.credit_card_connectors), + accept_credit_cards: config.accept_credit_cards, }); let _module = Module::new( @@ -347,7 +382,12 @@ mod tests { let feig = SyncFeig::default(); let (tx, _) = channel(); - let pt_module = PaymentTerminalModule { tx, feig }; + let pt_module = PaymentTerminalModule { + tx, + feig, + credit_cards_connectors: vec![], + accept_credit_cards: true, + }; assert!(pt_module.begin_transaction(&everest_mock).is_err()); } @@ -401,6 +441,113 @@ mod tests { let pt_module = PaymentTerminalModule { tx, feig: feig_mock, + credit_cards_connectors: vec![], + accept_credit_cards: true, + }; + + assert!(pt_module.begin_transaction(&everest_mock).is_ok()); + } + } + #[test] + /// Unit tests for the `PaymentTerminalModule::begin_transaction` with credit card acceptance. + fn payment_terminal_module__begin_transaction_credit_cards_accepted() { + // Now test the successful execution. + let parameters = [ + ( + CardInfo::Bank, + "my bank token", + true, + true, + String::from(""), + None, + ), + ( + CardInfo::Bank, + "my bank token", + false, + false, + String::from("1,2"), + None, + ), + ( + CardInfo::Bank, + "my bank token", + true, + true, + String::from("1"), + Some(vec![1]), + ), + ( + CardInfo::Bank, + "my bank token", + true, + true, + String::from("1,2"), + Some(vec![1, 2]), + ), + ( + CardInfo::Bank, + "my bank token", + true, + true, + String::from(""), + None, + ), + ]; + + for ( + card_info, + expected_token, + expected_transaction, + accept_credit_cards, + credit_card_connectors, + expected_connectors, + ) in parameters + { + let mut everest_mock = ModulePublisher::default(); + let mut feig_mock = SyncFeig::default(); + + everest_mock + .bank_session_token + .expect_get_bank_session_token() + .times(1) + .return_once(|| { + Ok(BankSessionToken { + token: Some("my bank token".to_string()), + }) + }); + if expected_transaction { + everest_mock + .token_provider + .expect_provided_token() + .times(1) + .withf(move |arg| { + arg.id_token.value == expected_token.to_string() + && arg.connectors == expected_connectors + }) + .return_once(|_| Ok(())); + } + + feig_mock + .expect_read_card() + .times(1) + .return_once(|| Ok(card_info)); + + if expected_transaction { + feig_mock + .expect_begin_transaction() + .times(1) + .with(eq("my bank token")) + .return_once(|_| Ok(())); + } + + let (tx, _) = channel(); + + let pt_module = PaymentTerminalModule { + tx, + feig: feig_mock, + credit_cards_connectors: string_to_vec(&credit_card_connectors), + accept_credit_cards, }; assert!(pt_module.begin_transaction(&everest_mock).is_ok()); @@ -417,7 +564,11 @@ mod tests { code: Some(CurrencyCode::EUR), decimals: None, }, - id_tag: Some(ProvidedIdToken::new(String::new(), AuthorizationType::OCPP)), + id_tag: Some(ProvidedIdToken::new( + String::new(), + AuthorizationType::OCPP, + None, + )), status: SessionStatus::Finished, session_id: String::new(), idle_price: None, @@ -432,7 +583,11 @@ mod tests { code: Some(CurrencyCode::EUR), decimals: None, }, - id_tag: Some(ProvidedIdToken::new(String::new(), AuthorizationType::RFID)), + id_tag: Some(ProvidedIdToken::new( + String::new(), + AuthorizationType::RFID, + None, + )), status: SessionStatus::Finished, session_id: String::new(), idle_price: None, @@ -447,7 +602,11 @@ mod tests { code: Some(CurrencyCode::EUR), decimals: None, }, - id_tag: Some(ProvidedIdToken::new(String::new(), AuthorizationType::BankCard)), + id_tag: Some(ProvidedIdToken::new( + String::new(), + AuthorizationType::BankCard, + None, + )), status: SessionStatus::Running, session_id: String::new(), idle_price: None, @@ -468,7 +627,12 @@ mod tests { let feig = SyncFeig::default(); let (tx, _) = channel(); - let pt_module = PaymentTerminalModule { tx, feig }; + let pt_module = PaymentTerminalModule { + tx, + feig, + credit_cards_connectors: vec![], + accept_credit_cards: true, + }; assert!(pt_module .on_session_cost_impl(&context, session_cost) .is_ok()); @@ -487,7 +651,11 @@ mod tests { code: Some(CurrencyCode::EUR), decimals: None, }, - id_tag: Some(ProvidedIdToken::new("token".to_string(), AuthorizationType::BankCard)), + id_tag: Some(ProvidedIdToken::new( + "token".to_string(), + AuthorizationType::BankCard, + None, + )), status: SessionStatus::Finished, session_id: String::new(), idle_price: None, @@ -505,7 +673,11 @@ mod tests { code: Some(CurrencyCode::EUR), decimals: None, }, - id_tag: Some(ProvidedIdToken::new("token".to_string(), AuthorizationType::BankCard)), + id_tag: Some(ProvidedIdToken::new( + "token".to_string(), + AuthorizationType::BankCard, + None, + )), status: SessionStatus::Finished, session_id: String::new(), idle_price: None, @@ -530,7 +702,11 @@ mod tests { code: Some(CurrencyCode::EUR), decimals: None, }, - id_tag: Some(ProvidedIdToken::new("token".to_string(), AuthorizationType::BankCard)), + id_tag: Some(ProvidedIdToken::new( + "token".to_string(), + AuthorizationType::BankCard, + None, + )), status: SessionStatus::Finished, session_id: String::new(), idle_price: None, @@ -565,7 +741,11 @@ mod tests { code: Some(CurrencyCode::EUR), decimals: None, }, - id_tag: Some(ProvidedIdToken::new("token".to_string(), AuthorizationType::BankCard)), + id_tag: Some(ProvidedIdToken::new( + "token".to_string(), + AuthorizationType::BankCard, + None, + )), status: SessionStatus::Finished, session_id: String::new(), idle_price: None, @@ -606,7 +786,12 @@ mod tests { }); let (tx, _) = channel(); - let pt_module = PaymentTerminalModule { tx, feig }; + let pt_module = PaymentTerminalModule { + tx, + feig, + credit_cards_connectors: vec![], + accept_credit_cards: true, + }; assert!(pt_module .on_session_cost_impl(&context, session_cost) .is_ok());