mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-27 11:24:45 +08:00
feat(connector): [FISERV] Added Integrity Check support for all Payment & Refund Flows (#8075)
Co-authored-by: Sayak Bhattacharya <sayak.b@Sayak-Bhattacharya-G092THXJ34.local>
This commit is contained in:
committed by
GitHub
parent
b5011296ab
commit
dbca363f44
@ -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,18 +169,34 @@ 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
|
||||
let (code, message, reason) = if let Some(first_error) = error_details_opt {
|
||||
let 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(),
|
||||
.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,
|
||||
@ -187,18 +204,6 @@ impl ConnectorCommon for Fiserv {
|
||||
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,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
@ -404,14 +409,38 @@ impl ConnectorIntegration<PSync, PaymentsSyncData, PaymentsResponseData> 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<Capture, PaymentsCaptureData, PaymentsResponseData> 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<Authorize, PaymentsAuthorizeData, PaymentsResponseData
|
||||
.response
|
||||
.parse_struct("Fiserv PaymentResponse")
|
||||
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
|
||||
|
||||
let response_integrity_object = connector_utils::get_authorise_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(
|
||||
@ -684,14 +745,31 @@ impl ConnectorIntegration<Execute, RefundsData, RefundsResponseData> 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<RSync, RefundsData, RefundsResponseData> 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(
|
||||
|
||||
@ -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<Vec<ErrorDetails>>,
|
||||
pub error: Option<Vec<ErrorDetails>>,
|
||||
}
|
||||
|
||||
@ -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<String>,
|
||||
pub code: Option<String>,
|
||||
pub message: String,
|
||||
pub field: Option<String>,
|
||||
pub message: Option<String>,
|
||||
pub additional_info: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
|
||||
@ -325,17 +325,89 @@ impl From<FiservPaymentStatus> for enums::RefundStatus {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ProcessorResponseDetails {
|
||||
pub approval_status: Option<String>,
|
||||
pub approval_code: Option<String>,
|
||||
pub reference_number: Option<String>,
|
||||
pub processor: Option<String>,
|
||||
pub host: Option<String>,
|
||||
pub network_routed: Option<String>,
|
||||
pub network_international_id: Option<String>,
|
||||
pub response_code: Option<String>,
|
||||
pub response_message: Option<String>,
|
||||
pub host_response_code: Option<String>,
|
||||
pub host_response_message: Option<String>,
|
||||
pub additional_info: Option<Vec<AdditionalInfo>>,
|
||||
pub bank_association_details: Option<BankAssociationDetails>,
|
||||
pub response_indicators: Option<ResponseIndicators>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct AdditionalInfo {
|
||||
pub name: Option<String>,
|
||||
pub value: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct BankAssociationDetails {
|
||||
pub association_response_code: Option<String>,
|
||||
pub avs_security_code_response: Option<AvsSecurityCodeResponse>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct AvsSecurityCodeResponse {
|
||||
pub street_match: Option<String>,
|
||||
pub postal_code_match: Option<String>,
|
||||
pub security_code_match: Option<String>,
|
||||
pub association: Option<Association>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Association {
|
||||
pub avs_code: Option<String>,
|
||||
pub security_code_response: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ResponseIndicators {
|
||||
pub alternate_route_debit_indicator: Option<bool>,
|
||||
pub signature_line_indicator: Option<bool>,
|
||||
pub signature_debit_route_indicator: Option<bool>,
|
||||
}
|
||||
|
||||
#[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<ProcessorResponseDetails>,
|
||||
}
|
||||
|
||||
#[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<FiservPaymentsResponse>,
|
||||
pub sync_responses: Vec<FiservPaymentsResponse>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
@ -591,7 +663,8 @@ impl<F> TryFrom<&FiservRouterData<&types::RefundsRouterData<F>>> 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<RefundsResponseRouterData<Execute, RefundResponse>>
|
||||
|
||||
Reference in New Issue
Block a user