mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-27 19:46:48 +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::{
|
use crate::{
|
||||||
constants::headers,
|
constants::headers,
|
||||||
types::ResponseRouterData,
|
types::ResponseRouterData,
|
||||||
|
utils as connector_utils,
|
||||||
utils::{construct_not_implemented_error_report, convert_amount},
|
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));
|
event_builder.map(|i| i.set_error_response_body(&response));
|
||||||
router_env::logger::info!(connector_response=?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
|
let (code, message, reason) = if let Some(first_error) = error_details_opt {
|
||||||
.or(details)
|
let code = first_error
|
||||||
.and_then(|error_details| {
|
|
||||||
error_details.first().map(|first_error| ErrorResponse {
|
|
||||||
code: first_error
|
|
||||||
.code
|
.code
|
||||||
.to_owned()
|
.clone()
|
||||||
.unwrap_or_else(|| consts::NO_ERROR_CODE.to_string()),
|
.unwrap_or_else(|| consts::NO_ERROR_CODE.to_string());
|
||||||
message: first_error.message.to_owned(),
|
|
||||||
reason: first_error.field.to_owned(),
|
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,
|
status_code: res.status_code,
|
||||||
attempt_status: None,
|
attempt_status: None,
|
||||||
connector_transaction_id: None,
|
connector_transaction_id: None,
|
||||||
@ -187,18 +204,6 @@ impl ConnectorCommon for Fiserv {
|
|||||||
network_decline_code: None,
|
network_decline_code: None,
|
||||||
network_error_message: 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
|
.response
|
||||||
.parse_struct("Fiserv PaymentSyncResponse")
|
.parse_struct("Fiserv PaymentSyncResponse")
|
||||||
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
|
.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));
|
event_builder.map(|i| i.set_response_body(&response));
|
||||||
router_env::logger::info!(connector_response=?response);
|
router_env::logger::info!(connector_response=?response);
|
||||||
RouterData::try_from(ResponseRouterData {
|
|
||||||
|
let new_router_data = RouterData::try_from(ResponseRouterData {
|
||||||
response,
|
response,
|
||||||
data: data.clone(),
|
data: data.clone(),
|
||||||
http_code: res.status_code,
|
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(
|
fn get_error_response(
|
||||||
@ -483,16 +512,30 @@ impl ConnectorIntegration<Capture, PaymentsCaptureData, PaymentsResponseData> fo
|
|||||||
.response
|
.response
|
||||||
.parse_struct("Fiserv Payment Response")
|
.parse_struct("Fiserv Payment Response")
|
||||||
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
|
.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));
|
event_builder.map(|i| i.set_response_body(&response));
|
||||||
router_env::logger::info!(connector_response=?response);
|
router_env::logger::info!(connector_response=?response);
|
||||||
|
|
||||||
RouterData::try_from(ResponseRouterData {
|
let new_router_data = RouterData::try_from(ResponseRouterData {
|
||||||
response,
|
response,
|
||||||
data: data.clone(),
|
data: data.clone(),
|
||||||
http_code: res.status_code,
|
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(
|
fn get_url(
|
||||||
@ -595,14 +638,32 @@ impl ConnectorIntegration<Authorize, PaymentsAuthorizeData, PaymentsResponseData
|
|||||||
.response
|
.response
|
||||||
.parse_struct("Fiserv PaymentResponse")
|
.parse_struct("Fiserv PaymentResponse")
|
||||||
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
|
.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));
|
event_builder.map(|i| i.set_response_body(&response));
|
||||||
router_env::logger::info!(connector_response=?response);
|
router_env::logger::info!(connector_response=?response);
|
||||||
RouterData::try_from(ResponseRouterData {
|
|
||||||
|
let new_router_data = RouterData::try_from(ResponseRouterData {
|
||||||
response,
|
response,
|
||||||
data: data.clone(),
|
data: data.clone(),
|
||||||
http_code: res.status_code,
|
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(
|
fn get_error_response(
|
||||||
@ -684,14 +745,31 @@ impl ConnectorIntegration<Execute, RefundsData, RefundsResponseData> for Fiserv
|
|||||||
res.response
|
res.response
|
||||||
.parse_struct("fiserv RefundResponse")
|
.parse_struct("fiserv RefundResponse")
|
||||||
.change_context(errors::ConnectorError::RequestEncodingFailed)?;
|
.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));
|
event_builder.map(|i| i.set_response_body(&response));
|
||||||
router_env::logger::info!(connector_response=?response);
|
router_env::logger::info!(connector_response=?response);
|
||||||
RouterData::try_from(ResponseRouterData {
|
let new_router_data = RouterData::try_from(ResponseRouterData {
|
||||||
response,
|
response,
|
||||||
data: data.clone(),
|
data: data.clone(),
|
||||||
http_code: res.status_code,
|
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(
|
fn get_error_response(
|
||||||
&self,
|
&self,
|
||||||
@ -767,14 +845,37 @@ impl ConnectorIntegration<RSync, RefundsData, RefundsResponseData> for Fiserv {
|
|||||||
.response
|
.response
|
||||||
.parse_struct("Fiserv Refund Response")
|
.parse_struct("Fiserv Refund Response")
|
||||||
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
|
.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));
|
event_builder.map(|i| i.set_response_body(&response));
|
||||||
router_env::logger::info!(connector_response=?response);
|
router_env::logger::info!(connector_response=?response);
|
||||||
RouterData::try_from(ResponseRouterData {
|
let new_router_data = RouterData::try_from(ResponseRouterData {
|
||||||
response,
|
response,
|
||||||
data: data.clone(),
|
data: data.clone(),
|
||||||
http_code: res.status_code,
|
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(
|
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 common_utils::{ext_traits::ValueExt, pii, types::FloatMajorUnit};
|
||||||
use error_stack::ResultExt;
|
use error_stack::ResultExt;
|
||||||
use hyperswitch_domain_models::{
|
use hyperswitch_domain_models::{
|
||||||
@ -274,7 +274,6 @@ impl TryFrom<&types::PaymentsCancelRouterData> for FiservCancelRequest {
|
|||||||
#[derive(Debug, Default, Deserialize, Serialize)]
|
#[derive(Debug, Default, Deserialize, Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct ErrorResponse {
|
pub struct ErrorResponse {
|
||||||
pub details: Option<Vec<ErrorDetails>>,
|
|
||||||
pub error: Option<Vec<ErrorDetails>>,
|
pub error: Option<Vec<ErrorDetails>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -282,10 +281,11 @@ pub struct ErrorResponse {
|
|||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct ErrorDetails {
|
pub struct ErrorDetails {
|
||||||
#[serde(rename = "type")]
|
#[serde(rename = "type")]
|
||||||
pub error_type: String,
|
pub error_type: Option<String>,
|
||||||
pub code: Option<String>,
|
pub code: Option<String>,
|
||||||
pub message: String,
|
|
||||||
pub field: Option<String>,
|
pub field: Option<String>,
|
||||||
|
pub message: Option<String>,
|
||||||
|
pub additional_info: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
|
#[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)]
|
#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct FiservPaymentsResponse {
|
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)]
|
#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
#[serde(transparent)]
|
#[serde(transparent)]
|
||||||
pub struct FiservSyncResponse {
|
pub struct FiservSyncResponse {
|
||||||
sync_responses: Vec<FiservPaymentsResponse>,
|
pub sync_responses: Vec<FiservPaymentsResponse>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)]
|
#[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)]
|
#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct RefundResponse {
|
pub struct RefundResponse {
|
||||||
gateway_response: GatewayResponse,
|
pub gateway_response: GatewayResponse,
|
||||||
|
pub payment_receipt: PaymentReceipt,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<RefundsResponseRouterData<Execute, RefundResponse>>
|
impl TryFrom<RefundsResponseRouterData<Execute, RefundResponse>>
|
||||||
|
|||||||
Reference in New Issue
Block a user