diff --git a/crates/hyperswitch_connectors/src/connectors/fiserv.rs b/crates/hyperswitch_connectors/src/connectors/fiserv.rs index 123abdd076..2dc4360b14 100644 --- a/crates/hyperswitch_connectors/src/connectors/fiserv.rs +++ b/crates/hyperswitch_connectors/src/connectors/fiserv.rs @@ -51,6 +51,7 @@ use uuid::Uuid; use crate::{ constants::headers, types::ResponseRouterData, + utils as connector_utils, utils::{construct_not_implemented_error_report, convert_amount}, }; @@ -168,37 +169,41 @@ impl ConnectorCommon for Fiserv { event_builder.map(|i| i.set_error_response_body(&response)); router_env::logger::info!(connector_response=?response); - let fiserv::ErrorResponse { error, details } = response; + let error_details_opt = response.error.as_ref().and_then(|v| v.first()); - Ok(error - .or(details) - .and_then(|error_details| { - error_details.first().map(|first_error| ErrorResponse { - code: first_error - .code - .to_owned() - .unwrap_or_else(|| consts::NO_ERROR_CODE.to_string()), - message: first_error.message.to_owned(), - reason: first_error.field.to_owned(), - status_code: res.status_code, - attempt_status: None, - connector_transaction_id: None, - network_advice_code: None, - network_decline_code: None, - network_error_message: None, - }) - }) - .unwrap_or(ErrorResponse { - code: consts::NO_ERROR_CODE.to_string(), - message: consts::NO_ERROR_MESSAGE.to_string(), - reason: None, - status_code: res.status_code, - attempt_status: None, - connector_transaction_id: None, - network_advice_code: None, - network_decline_code: None, - network_error_message: None, - })) + let (code, message, reason) = if let Some(first_error) = error_details_opt { + let code = first_error + .code + .clone() + .unwrap_or_else(|| consts::NO_ERROR_CODE.to_string()); + + let message = first_error + .message + .clone() + .unwrap_or_else(|| consts::NO_ERROR_CODE.to_string()); + + let reason = first_error.additional_info.clone(); + + (code, message, reason) + } else { + ( + consts::NO_ERROR_CODE.to_string(), + consts::NO_ERROR_MESSAGE.to_string(), + None, + ) + }; + + Ok(ErrorResponse { + code, + message, + reason, + status_code: res.status_code, + attempt_status: None, + connector_transaction_id: None, + network_advice_code: None, + network_decline_code: None, + network_error_message: None, + }) } } @@ -404,14 +409,38 @@ impl ConnectorIntegration for Fis .response .parse_struct("Fiserv PaymentSyncResponse") .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + + let p_sync_response = response.sync_responses.first().ok_or( + errors::ConnectorError::MissingRequiredField { + field_name: "P_Sync_Responses[0]", + }, + )?; + + let response_integrity_object = connector_utils::get_sync_integrity_object( + self.amount_converter, + p_sync_response.payment_receipt.approved_amount.total, + p_sync_response + .payment_receipt + .approved_amount + .currency + .to_string() + .clone(), + )?; + event_builder.map(|i| i.set_response_body(&response)); router_env::logger::info!(connector_response=?response); - RouterData::try_from(ResponseRouterData { + + let new_router_data = RouterData::try_from(ResponseRouterData { response, data: data.clone(), http_code: res.status_code, }) - .change_context(errors::ConnectorError::ResponseHandlingFailed) + .change_context(errors::ConnectorError::ResponseHandlingFailed); + + new_router_data.map(|mut router_data| { + router_data.request.integrity_object = Some(response_integrity_object); + router_data + }) } fn get_error_response( @@ -483,16 +512,30 @@ impl ConnectorIntegration fo .response .parse_struct("Fiserv Payment Response") .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; - + let response_integrity_object = connector_utils::get_capture_integrity_object( + self.amount_converter, + Some(response.payment_receipt.approved_amount.total), + response + .payment_receipt + .approved_amount + .currency + .to_string() + .clone(), + )?; event_builder.map(|i| i.set_response_body(&response)); router_env::logger::info!(connector_response=?response); - RouterData::try_from(ResponseRouterData { + let new_router_data = RouterData::try_from(ResponseRouterData { response, data: data.clone(), http_code: res.status_code, }) - .change_context(errors::ConnectorError::ResponseHandlingFailed) + .change_context(errors::ConnectorError::ResponseHandlingFailed); + + new_router_data.map(|mut router_data| { + router_data.request.integrity_object = Some(response_integrity_object); + router_data + }) } fn get_url( @@ -595,14 +638,32 @@ impl ConnectorIntegration for Fiserv res.response .parse_struct("fiserv RefundResponse") .change_context(errors::ConnectorError::RequestEncodingFailed)?; + + let response_integrity_object = connector_utils::get_refund_integrity_object( + self.amount_converter, + response.payment_receipt.approved_amount.total, + response + .payment_receipt + .approved_amount + .currency + .to_string() + .clone(), + )?; + event_builder.map(|i| i.set_response_body(&response)); router_env::logger::info!(connector_response=?response); - RouterData::try_from(ResponseRouterData { + let new_router_data = RouterData::try_from(ResponseRouterData { response, data: data.clone(), http_code: res.status_code, }) - .change_context(errors::ConnectorError::ResponseHandlingFailed) + .change_context(errors::ConnectorError::ResponseHandlingFailed); + + new_router_data.map(|mut router_data| { + router_data.request.integrity_object = Some(response_integrity_object); + router_data + }) } fn get_error_response( &self, @@ -767,14 +845,37 @@ impl ConnectorIntegration for Fiserv { .response .parse_struct("Fiserv Refund Response") .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + + let r_sync_response = response.sync_responses.first().ok_or( + errors::ConnectorError::MissingRequiredField { + field_name: "R_Sync_Responses[0]", + }, + )?; + + let response_integrity_object = connector_utils::get_refund_integrity_object( + self.amount_converter, + r_sync_response.payment_receipt.approved_amount.total, + r_sync_response + .payment_receipt + .approved_amount + .currency + .to_string() + .clone(), + )?; + event_builder.map(|i| i.set_response_body(&response)); router_env::logger::info!(connector_response=?response); - RouterData::try_from(ResponseRouterData { + let new_router_data = RouterData::try_from(ResponseRouterData { response, data: data.clone(), http_code: res.status_code, }) - .change_context(errors::ConnectorError::ResponseHandlingFailed) + .change_context(errors::ConnectorError::ResponseHandlingFailed); + + new_router_data.map(|mut router_data| { + router_data.request.integrity_object = Some(response_integrity_object); + router_data + }) } fn get_error_response( diff --git a/crates/hyperswitch_connectors/src/connectors/fiserv/transformers.rs b/crates/hyperswitch_connectors/src/connectors/fiserv/transformers.rs index 8f9a15e49c..f4eea57905 100644 --- a/crates/hyperswitch_connectors/src/connectors/fiserv/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/fiserv/transformers.rs @@ -1,4 +1,4 @@ -use common_enums::enums; +use common_enums::{enums, Currency}; use common_utils::{ext_traits::ValueExt, pii, types::FloatMajorUnit}; use error_stack::ResultExt; use hyperswitch_domain_models::{ @@ -274,7 +274,6 @@ impl TryFrom<&types::PaymentsCancelRouterData> for FiservCancelRequest { #[derive(Debug, Default, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct ErrorResponse { - pub details: Option>, pub error: Option>, } @@ -282,10 +281,11 @@ pub struct ErrorResponse { #[serde(rename_all = "camelCase")] pub struct ErrorDetails { #[serde(rename = "type")] - pub error_type: String, + pub error_type: Option, pub code: Option, - pub message: String, pub field: Option, + pub message: Option, + pub additional_info: Option, } #[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)] @@ -325,17 +325,89 @@ impl From for enums::RefundStatus { } } +#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct ProcessorResponseDetails { + pub approval_status: Option, + pub approval_code: Option, + pub reference_number: Option, + pub processor: Option, + pub host: Option, + pub network_routed: Option, + pub network_international_id: Option, + pub response_code: Option, + pub response_message: Option, + pub host_response_code: Option, + pub host_response_message: Option, + pub additional_info: Option>, + pub bank_association_details: Option, + pub response_indicators: Option, +} + +#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct AdditionalInfo { + pub name: Option, + pub value: Option, +} + +#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct BankAssociationDetails { + pub association_response_code: Option, + pub avs_security_code_response: Option, +} + +#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct AvsSecurityCodeResponse { + pub street_match: Option, + pub postal_code_match: Option, + pub security_code_match: Option, + pub association: Option, +} + +#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct Association { + pub avs_code: Option, + pub security_code_response: Option, +} + +#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct ResponseIndicators { + pub alternate_route_debit_indicator: Option, + pub signature_line_indicator: Option, + pub signature_debit_route_indicator: Option, +} + #[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)] #[serde(rename_all = "camelCase")] pub struct FiservPaymentsResponse { - gateway_response: GatewayResponse, + pub gateway_response: GatewayResponse, + pub payment_receipt: PaymentReceipt, +} + +#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct PaymentReceipt { + pub approved_amount: ApprovedAmount, + pub processor_response_details: Option, +} + +#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct ApprovedAmount { + pub total: FloatMajorUnit, + pub currency: Currency, } #[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)] #[serde(rename_all = "camelCase")] #[serde(transparent)] pub struct FiservSyncResponse { - sync_responses: Vec, + pub sync_responses: Vec, } #[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)] @@ -591,7 +663,8 @@ impl TryFrom<&FiservRouterData<&types::RefundsRouterData>> for FiservRefun #[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)] #[serde(rename_all = "camelCase")] pub struct RefundResponse { - gateway_response: GatewayResponse, + pub gateway_response: GatewayResponse, + pub payment_receipt: PaymentReceipt, } impl TryFrom>