diff --git a/crates/hyperswitch_connectors/src/connectors/tokenex.rs b/crates/hyperswitch_connectors/src/connectors/tokenex.rs index 3dbe640beb..1a4d2fc502 100644 --- a/crates/hyperswitch_connectors/src/connectors/tokenex.rs +++ b/crates/hyperswitch_connectors/src/connectors/tokenex.rs @@ -151,11 +151,13 @@ impl ConnectorCommon for Tokenex { event_builder.map(|i| i.set_response_body(&response)); router_env::logger::info!(connector_response=?response); + let (code, message) = response.error.split_once(':').unwrap_or(("", "")); + Ok(ErrorResponse { status_code: res.status_code, - code: response.code, - message: response.message, - reason: response.reason, + code: code.to_string(), + message: message.to_string(), + reason: Some(response.message), attempt_status: None, connector_transaction_id: None, network_advice_code: None, diff --git a/crates/hyperswitch_connectors/src/connectors/tokenex/transformers.rs b/crates/hyperswitch_connectors/src/connectors/tokenex/transformers.rs index 9d7082746b..1904ac1840 100644 --- a/crates/hyperswitch_connectors/src/connectors/tokenex/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/tokenex/transformers.rs @@ -1,10 +1,7 @@ -use common_utils::{ - ext_traits::{Encode, StringExt}, - types::StringMinorUnit, -}; +use common_utils::types::StringMinorUnit; use error_stack::ResultExt; use hyperswitch_domain_models::{ - router_data::{ConnectorAuthType, RouterData}, + router_data::{ConnectorAuthType, ErrorResponse, RouterData}, router_flow_types::{ExternalVaultInsertFlow, ExternalVaultRetrieveFlow}, router_request_types::VaultRequestData, router_response_types::VaultResponseData, @@ -12,7 +9,7 @@ use hyperswitch_domain_models::{ vault::PaymentMethodVaultingData, }; use hyperswitch_interfaces::errors; -use masking::{ExposeInterface, Secret}; +use masking::Secret; use serde::{Deserialize, Serialize}; use crate::types::ResponseRouterData; @@ -24,7 +21,6 @@ pub struct TokenexRouterData { impl From<(StringMinorUnit, T)> for TokenexRouterData { fn from((amount, item): (StringMinorUnit, T)) -> Self { - //Todo : use utils to convert the amount to the type of amount that a connector accepts Self { amount, router_data: item, @@ -34,21 +30,16 @@ impl From<(StringMinorUnit, T)> for TokenexRouterData { #[derive(Default, Debug, Serialize, PartialEq)] pub struct TokenexInsertRequest { - data: Secret, + data: cards::CardNumber, //Currently only card number is tokenized. Data can be stringified and can be tokenized } impl TryFrom<&VaultRouterData> for TokenexInsertRequest { type Error = error_stack::Report; fn try_from(item: &VaultRouterData) -> Result { match item.request.payment_method_vaulting_data.clone() { - Some(PaymentMethodVaultingData::Card(req_card)) => { - let stringified_card = req_card - .encode_to_string_of_json() - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Self { - data: Secret::new(stringified_card), - }) - } + Some(PaymentMethodVaultingData::Card(req_card)) => Ok(Self { + data: req_card.card_number.clone(), + }), _ => Err(errors::ConnectorError::NotImplemented( "Payment method apart from card".to_string(), ) @@ -56,9 +47,6 @@ impl TryFrom<&VaultRouterData> for TokenexInsertRequest { } } } - -//TODO: Fill the struct with respective fields -// Auth Struct pub struct TokenexAuthType { pub(super) api_key: Secret, pub(super) tokenex_id: Secret, @@ -78,10 +66,14 @@ impl TryFrom<&ConnectorAuthType> for TokenexAuthType { } #[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "camelCase")] pub struct TokenexInsertResponse { - token: String, - first_six: String, + token: Option, + first_six: Option, + last_four: Option, success: bool, + error: String, + message: Option, } impl TryFrom< @@ -103,20 +95,49 @@ impl >, ) -> Result { let resp = item.response; + match resp.success && resp.error.is_empty() { + true => { + let token = resp + .token + .clone() + .ok_or(errors::ConnectorError::ResponseDeserializationFailed) + .attach_printable("Token is missing in tokenex response")?; + Ok(Self { + status: common_enums::AttemptStatus::Started, + response: Ok(VaultResponseData::ExternalVaultInsertResponse { + connector_vault_id: token.clone(), + //fingerprint is not provided by tokenex, using token as fingerprint + fingerprint_id: token.clone(), + }), + ..item.data + }) + } + false => { + let (code, message) = resp.error.split_once(':').unwrap_or(("", "")); - Ok(Self { - status: common_enums::AttemptStatus::Started, - response: Ok(VaultResponseData::ExternalVaultInsertResponse { - connector_vault_id: resp.token.clone(), - //fingerprint is not provided by tokenex, using token as fingerprint - fingerprint_id: resp.token.clone(), - }), - ..item.data - }) + let response = Err(ErrorResponse { + code: code.to_string(), + message: message.to_string(), + reason: resp.message, + status_code: item.http_code, + attempt_status: None, + connector_transaction_id: None, + network_decline_code: None, + network_advice_code: None, + network_error_message: None, + connector_metadata: None, + }); + + Ok(Self { + response, + ..item.data + }) + } + } } } - #[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "camelCase")] pub struct TokenexRetrieveRequest { token: Secret, //Currently only card number is tokenized. Data can be stringified and can be tokenized cache_cvv: bool, @@ -124,8 +145,10 @@ pub struct TokenexRetrieveRequest { #[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct TokenexRetrieveResponse { - value: Secret, + value: Option, success: bool, + error: String, + message: Option, } impl TryFrom<&VaultRouterData> for TokenexRetrieveRequest { @@ -164,30 +187,48 @@ impl ) -> Result { let resp = item.response; - let card_detail: api_models::payment_methods::CardDetail = resp - .value - .clone() - .expose() - .parse_struct("CardDetail") - .change_context(errors::ConnectorError::ParsingFailed)?; + match resp.success && resp.error.is_empty() { + true => { + let data = resp + .value + .clone() + .ok_or(errors::ConnectorError::ResponseDeserializationFailed) + .attach_printable("Card number is missing in tokenex response")?; + Ok(Self { + status: common_enums::AttemptStatus::Started, + response: Ok(VaultResponseData::ExternalVaultRetrieveResponse { + vault_data: PaymentMethodVaultingData::CardNumber(data), + }), + ..item.data + }) + } + false => { + let (code, message) = resp.error.split_once(':').unwrap_or(("", "")); - Ok(Self { - status: common_enums::AttemptStatus::Started, - response: Ok(VaultResponseData::ExternalVaultRetrieveResponse { - vault_data: PaymentMethodVaultingData::Card(card_detail), - }), - ..item.data - }) + let response = Err(ErrorResponse { + code: code.to_string(), + message: message.to_string(), + reason: resp.message, + status_code: item.http_code, + attempt_status: None, + connector_transaction_id: None, + network_decline_code: None, + network_advice_code: None, + network_error_message: None, + connector_metadata: None, + }); + + Ok(Self { + response, + ..item.data + }) + } + } } } -#[derive(Default, Debug, Serialize, Deserialize, PartialEq)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] pub struct TokenexErrorResponse { - pub status_code: u16, - pub code: String, + pub error: String, pub message: String, - pub reason: Option, - pub network_advice_code: Option, - pub network_decline_code: Option, - pub network_error_message: Option, } diff --git a/crates/hyperswitch_domain_models/src/payment_method_data.rs b/crates/hyperswitch_domain_models/src/payment_method_data.rs index 932244a986..6bc9062eea 100644 --- a/crates/hyperswitch_domain_models/src/payment_method_data.rs +++ b/crates/hyperswitch_domain_models/src/payment_method_data.rs @@ -7,10 +7,8 @@ use api_models::{ payments::{additional_info as payment_additional_types, ExtendedCardInfo}, }; use common_enums::enums as api_enums; -#[cfg(feature = "v2")] -use common_utils::ext_traits::OptionExt; use common_utils::{ - ext_traits::StringExt, + ext_traits::{OptionExt, StringExt}, id_type, new_type::{ MaskedBankAccount, MaskedIban, MaskedRoutingNumber, MaskedSortCode, MaskedUpiVpaId, @@ -18,7 +16,7 @@ use common_utils::{ payout_method_utils, pii::{self, Email}, }; -use masking::{PeekInterface, Secret}; +use masking::{ExposeInterface, PeekInterface, Secret}; use serde::{Deserialize, Serialize}; use time::Date; @@ -2327,6 +2325,13 @@ impl PaymentMethodsData { Self::BankDetails(_) | Self::WalletDetails(_) | Self::NetworkToken(_) => None, } } + pub fn get_card_details(&self) -> Option { + if let Self::Card(card) = self { + Some(card.clone()) + } else { + None + } + } } #[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] @@ -2593,8 +2598,6 @@ impl // The card_holder_name from locker retrieved card is considered if it is a non-empty string or else card_holder_name is picked let name_on_card = if let Some(name) = card_holder_name.clone() { - use masking::ExposeInterface; - if name.clone().expose().is_empty() { card_token_data .and_then(|token_data| token_data.card_holder_name.clone()) @@ -2626,3 +2629,63 @@ impl } } } + +#[cfg(feature = "v1")] +impl + TryFrom<( + cards::CardNumber, + Option<&CardToken>, + Option, + CardDetailsPaymentMethod, + )> for Card +{ + type Error = error_stack::Report; + fn try_from( + value: ( + cards::CardNumber, + Option<&CardToken>, + Option, + CardDetailsPaymentMethod, + ), + ) -> Result { + let (card_number, card_token_data, co_badged_card_data, card_details) = value; + + // The card_holder_name from locker retrieved card is considered if it is a non-empty string or else card_holder_name is picked + let name_on_card = if let Some(name) = card_details.card_holder_name.clone() { + if name.clone().expose().is_empty() { + card_token_data + .and_then(|token_data| token_data.card_holder_name.clone()) + .or(Some(name)) + } else { + Some(name) + } + } else { + card_token_data.and_then(|token_data| token_data.card_holder_name.clone()) + }; + + Ok(Self { + card_number, + card_exp_month: card_details + .expiry_month + .get_required_value("expiry_month")? + .clone(), + card_exp_year: card_details + .expiry_year + .get_required_value("expiry_year")? + .clone(), + card_holder_name: name_on_card, + card_cvc: card_token_data + .cloned() + .unwrap_or_default() + .card_cvc + .unwrap_or_default(), + card_issuer: card_details.card_issuer, + card_network: card_details.card_network, + card_type: card_details.card_type, + card_issuing_country: card_details.issuer_country, + bank_code: None, + nick_name: card_details.nick_name, + co_badged_card_data, + }) + } +} diff --git a/crates/hyperswitch_domain_models/src/vault.rs b/crates/hyperswitch_domain_models/src/vault.rs index 9ba63f5023..94f23b4fbb 100644 --- a/crates/hyperswitch_domain_models/src/vault.rs +++ b/crates/hyperswitch_domain_models/src/vault.rs @@ -12,6 +12,7 @@ pub enum PaymentMethodVaultingData { Card(payment_methods::CardDetail), #[cfg(feature = "v2")] NetworkToken(payment_method_data::NetworkTokenDetails), + CardNumber(cards::CardNumber), } impl PaymentMethodVaultingData { @@ -20,6 +21,7 @@ impl PaymentMethodVaultingData { Self::Card(card) => Some(card), #[cfg(feature = "v2")] Self::NetworkToken(_) => None, + Self::CardNumber(_) => None, } } pub fn get_payment_methods_data(&self) -> payment_method_data::PaymentMethodsData { @@ -35,6 +37,23 @@ impl PaymentMethodVaultingData { ), ) } + Self::CardNumber(_card_number) => payment_method_data::PaymentMethodsData::Card( + payment_method_data::CardDetailsPaymentMethod { + last4_digits: None, + issuer_country: None, + expiry_month: None, + expiry_year: None, + nick_name: None, + card_holder_name: None, + card_isin: None, + card_issuer: None, + card_network: None, + card_type: None, + saved_to_locker: false, + #[cfg(feature = "v1")] + co_badged_card_data: None, + }, + ), } } } @@ -49,6 +68,7 @@ impl VaultingDataInterface for PaymentMethodVaultingData { Self::Card(card) => card.card_number.to_string(), #[cfg(feature = "v2")] Self::NetworkToken(network_token) => network_token.network_token.to_string(), + Self::CardNumber(card_number) => card_number.to_string(), } } } diff --git a/crates/router/src/core/payment_methods.rs b/crates/router/src/core/payment_methods.rs index b07b89e132..ed78b942e9 100644 --- a/crates/router/src/core/payment_methods.rs +++ b/crates/router/src/core/payment_methods.rs @@ -2201,18 +2201,7 @@ pub async fn create_pm_additional_data_update( external_vault_source: Option, ) -> RouterResult { let encrypted_payment_method_data = pmd - .map( - |payment_method_vaulting_data| match payment_method_vaulting_data { - domain::PaymentMethodVaultingData::Card(card) => domain::PaymentMethodsData::Card( - domain::CardDetailsPaymentMethod::from(card.clone()), - ), - domain::PaymentMethodVaultingData::NetworkToken(network_token) => { - domain::PaymentMethodsData::NetworkToken( - domain::NetworkTokenDetailsPaymentMethod::from(network_token.clone()), - ) - } - }, - ) + .map(|payment_method_vaulting_data| payment_method_vaulting_data.get_payment_methods_data()) .async_map(|payment_method_details| async { let key_manager_state = &(state).into(); diff --git a/crates/router/src/core/payment_methods/vault.rs b/crates/router/src/core/payment_methods/vault.rs index 771df3d97c..98888520d5 100644 --- a/crates/router/src/core/payment_methods/vault.rs +++ b/crates/router/src/core/payment_methods/vault.rs @@ -1964,9 +1964,12 @@ pub fn get_vault_response_for_retrieve_payment_method_data_v1( } }, Err(err) => { - logger::error!("Failed to retrieve payment method: {:?}", err); + logger::error!( + "Failed to retrieve payment method from external vault: {:?}", + err + ); Err(report!(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Failed to retrieve payment method")) + .attach_printable("Failed to retrieve payment method from external vault")) } } } diff --git a/crates/router/src/core/payments/helpers.rs b/crates/router/src/core/payments/helpers.rs index 044466523d..10d72baf28 100644 --- a/crates/router/src/core/payments/helpers.rs +++ b/crates/router/src/core/payments/helpers.rs @@ -2512,10 +2512,30 @@ pub async fn fetch_card_details_from_external_vault( ) .await?; + let payment_methods_data = payment_method_info.get_payment_methods_data(); + match vault_resp { hyperswitch_domain_models::vault::PaymentMethodVaultingData::Card(card) => Ok( domain::Card::from((card, card_token_data, co_badged_card_data)), ), + hyperswitch_domain_models::vault::PaymentMethodVaultingData::CardNumber(card_number) => { + let payment_methods_data = payment_methods_data + .get_required_value("PaymentMethodsData") + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Payment methods data not present")?; + + let card = payment_methods_data + .get_card_details() + .get_required_value("CardDetails") + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Card details not present")?; + + Ok( + domain::Card::try_from((card_number, card_token_data, co_badged_card_data, card)) + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to generate card data")?, + ) + } } } #[cfg(feature = "v1")]