Skip to content

Commit eca6ac4

Browse files
Sayak BhattacharyaSayak Bhattacharya
authored andcommitted
feat(connector): [FISERV] Added Integrity Check Support for all Payment & Refund Flows
1 parent 071b073 commit eca6ac4

File tree

2 files changed

+223
-49
lines changed

2 files changed

+223
-49
lines changed

crates/hyperswitch_connectors/src/connectors/fiserv.rs

Lines changed: 143 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ use uuid::Uuid;
5151
use crate::{
5252
constants::headers,
5353
types::ResponseRouterData,
54+
utils as connector_utils,
5455
utils::{construct_not_implemented_error_report, convert_amount},
5556
};
5657

@@ -168,37 +169,41 @@ impl ConnectorCommon for Fiserv {
168169
event_builder.map(|i| i.set_error_response_body(&response));
169170
router_env::logger::info!(connector_response=?response);
170171

171-
let fiserv::ErrorResponse { error, details } = response;
172-
173-
Ok(error
174-
.or(details)
175-
.and_then(|error_details| {
176-
error_details.first().map(|first_error| ErrorResponse {
177-
code: first_error
178-
.code
179-
.to_owned()
180-
.unwrap_or_else(|| consts::NO_ERROR_CODE.to_string()),
181-
message: first_error.message.to_owned(),
182-
reason: first_error.field.to_owned(),
183-
status_code: res.status_code,
184-
attempt_status: None,
185-
connector_transaction_id: None,
186-
network_advice_code: None,
187-
network_decline_code: None,
188-
network_error_message: None,
189-
})
190-
})
191-
.unwrap_or(ErrorResponse {
192-
code: consts::NO_ERROR_CODE.to_string(),
193-
message: consts::NO_ERROR_MESSAGE.to_string(),
194-
reason: None,
195-
status_code: res.status_code,
196-
attempt_status: None,
197-
connector_transaction_id: None,
198-
network_advice_code: None,
199-
network_decline_code: None,
200-
network_error_message: None,
201-
}))
172+
let error_details_opt = response.error.as_ref().and_then(|v| v.first());
173+
174+
let (code, message, reason) = if let Some(first_error) = error_details_opt {
175+
let code = first_error
176+
.code
177+
.clone()
178+
.unwrap_or_else(|| consts::NO_ERROR_CODE.to_string());
179+
180+
let message = first_error
181+
.message
182+
.clone()
183+
.unwrap_or_else(|| consts::NO_ERROR_CODE.to_string());
184+
185+
let reason = first_error.additional_info.clone();
186+
187+
(code, message, reason)
188+
} else {
189+
(
190+
consts::NO_ERROR_CODE.to_string(),
191+
consts::NO_ERROR_MESSAGE.to_string(),
192+
None,
193+
)
194+
};
195+
196+
Ok(ErrorResponse {
197+
code,
198+
message,
199+
reason,
200+
status_code: res.status_code,
201+
attempt_status: None,
202+
connector_transaction_id: None,
203+
network_advice_code: None,
204+
network_decline_code: None,
205+
network_error_message: None,
206+
})
202207
}
203208
}
204209

@@ -404,14 +409,38 @@ impl ConnectorIntegration<PSync, PaymentsSyncData, PaymentsResponseData> for Fis
404409
.response
405410
.parse_struct("Fiserv PaymentSyncResponse")
406411
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
412+
413+
let p_sync_response = response.sync_responses.first().ok_or(
414+
errors::ConnectorError::MissingRequiredField {
415+
field_name: "P_Sync_Responses[0]",
416+
},
417+
)?;
418+
419+
let response_integrity_object = connector_utils::get_sync_integrity_object(
420+
self.amount_converter,
421+
p_sync_response.payment_receipt.approved_amount.total,
422+
p_sync_response
423+
.payment_receipt
424+
.approved_amount
425+
.currency
426+
.to_string()
427+
.clone(),
428+
)?;
429+
407430
event_builder.map(|i| i.set_response_body(&response));
408431
router_env::logger::info!(connector_response=?response);
409-
RouterData::try_from(ResponseRouterData {
432+
433+
let new_router_data = RouterData::try_from(ResponseRouterData {
410434
response,
411435
data: data.clone(),
412436
http_code: res.status_code,
413437
})
414-
.change_context(errors::ConnectorError::ResponseHandlingFailed)
438+
.change_context(errors::ConnectorError::ResponseHandlingFailed);
439+
440+
new_router_data.map(|mut router_data| {
441+
router_data.request.integrity_object = Some(response_integrity_object);
442+
router_data
443+
})
415444
}
416445

417446
fn get_error_response(
@@ -483,16 +512,30 @@ impl ConnectorIntegration<Capture, PaymentsCaptureData, PaymentsResponseData> fo
483512
.response
484513
.parse_struct("Fiserv Payment Response")
485514
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
486-
515+
let response_integrity_object = connector_utils::get_capture_integrity_object(
516+
self.amount_converter,
517+
Some(response.payment_receipt.approved_amount.total),
518+
response
519+
.payment_receipt
520+
.approved_amount
521+
.currency
522+
.to_string()
523+
.clone(),
524+
)?;
487525
event_builder.map(|i| i.set_response_body(&response));
488526
router_env::logger::info!(connector_response=?response);
489527

490-
RouterData::try_from(ResponseRouterData {
528+
let new_router_data = RouterData::try_from(ResponseRouterData {
491529
response,
492530
data: data.clone(),
493531
http_code: res.status_code,
494532
})
495-
.change_context(errors::ConnectorError::ResponseHandlingFailed)
533+
.change_context(errors::ConnectorError::ResponseHandlingFailed);
534+
535+
new_router_data.map(|mut router_data| {
536+
router_data.request.integrity_object = Some(response_integrity_object);
537+
router_data
538+
})
496539
}
497540

498541
fn get_url(
@@ -595,14 +638,32 @@ impl ConnectorIntegration<Authorize, PaymentsAuthorizeData, PaymentsResponseData
595638
.response
596639
.parse_struct("Fiserv PaymentResponse")
597640
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
641+
642+
let response_integrity_object = connector_utils::get_authorise_integrity_object(
643+
self.amount_converter,
644+
response.payment_receipt.approved_amount.total,
645+
response
646+
.payment_receipt
647+
.approved_amount
648+
.currency
649+
.to_string()
650+
.clone(),
651+
)?;
652+
598653
event_builder.map(|i| i.set_response_body(&response));
599654
router_env::logger::info!(connector_response=?response);
600-
RouterData::try_from(ResponseRouterData {
655+
656+
let new_router_data = RouterData::try_from(ResponseRouterData {
601657
response,
602658
data: data.clone(),
603659
http_code: res.status_code,
604660
})
605-
.change_context(errors::ConnectorError::ResponseHandlingFailed)
661+
.change_context(errors::ConnectorError::ResponseHandlingFailed);
662+
663+
new_router_data.map(|mut router_data| {
664+
router_data.request.integrity_object = Some(response_integrity_object);
665+
router_data
666+
})
606667
}
607668

608669
fn get_error_response(
@@ -684,14 +745,31 @@ impl ConnectorIntegration<Execute, RefundsData, RefundsResponseData> for Fiserv
684745
res.response
685746
.parse_struct("fiserv RefundResponse")
686747
.change_context(errors::ConnectorError::RequestEncodingFailed)?;
748+
749+
let response_integrity_object = connector_utils::get_refund_integrity_object(
750+
self.amount_converter,
751+
response.payment_receipt.approved_amount.total,
752+
response
753+
.payment_receipt
754+
.approved_amount
755+
.currency
756+
.to_string()
757+
.clone(),
758+
)?;
759+
687760
event_builder.map(|i| i.set_response_body(&response));
688761
router_env::logger::info!(connector_response=?response);
689-
RouterData::try_from(ResponseRouterData {
762+
let new_router_data = RouterData::try_from(ResponseRouterData {
690763
response,
691764
data: data.clone(),
692765
http_code: res.status_code,
693766
})
694-
.change_context(errors::ConnectorError::ResponseHandlingFailed)
767+
.change_context(errors::ConnectorError::ResponseHandlingFailed);
768+
769+
new_router_data.map(|mut router_data| {
770+
router_data.request.integrity_object = Some(response_integrity_object);
771+
router_data
772+
})
695773
}
696774
fn get_error_response(
697775
&self,
@@ -767,14 +845,37 @@ impl ConnectorIntegration<RSync, RefundsData, RefundsResponseData> for Fiserv {
767845
.response
768846
.parse_struct("Fiserv Refund Response")
769847
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
848+
849+
let r_sync_response = response.sync_responses.first().ok_or(
850+
errors::ConnectorError::MissingRequiredField {
851+
field_name: "R_Sync_Responses[0]",
852+
},
853+
)?;
854+
855+
let response_integrity_object = connector_utils::get_refund_integrity_object(
856+
self.amount_converter,
857+
r_sync_response.payment_receipt.approved_amount.total,
858+
r_sync_response
859+
.payment_receipt
860+
.approved_amount
861+
.currency
862+
.to_string()
863+
.clone(),
864+
)?;
865+
770866
event_builder.map(|i| i.set_response_body(&response));
771867
router_env::logger::info!(connector_response=?response);
772-
RouterData::try_from(ResponseRouterData {
868+
let new_router_data = RouterData::try_from(ResponseRouterData {
773869
response,
774870
data: data.clone(),
775871
http_code: res.status_code,
776872
})
777-
.change_context(errors::ConnectorError::ResponseHandlingFailed)
873+
.change_context(errors::ConnectorError::ResponseHandlingFailed);
874+
875+
new_router_data.map(|mut router_data| {
876+
router_data.request.integrity_object = Some(response_integrity_object);
877+
router_data
878+
})
778879
}
779880

780881
fn get_error_response(

crates/hyperswitch_connectors/src/connectors/fiserv/transformers.rs

Lines changed: 80 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use common_enums::enums;
1+
use common_enums::{enums, Currency};
22
use common_utils::{ext_traits::ValueExt, pii, types::FloatMajorUnit};
33
use error_stack::ResultExt;
44
use hyperswitch_domain_models::{
@@ -274,18 +274,18 @@ impl TryFrom<&types::PaymentsCancelRouterData> for FiservCancelRequest {
274274
#[derive(Debug, Default, Deserialize, Serialize)]
275275
#[serde(rename_all = "camelCase")]
276276
pub struct ErrorResponse {
277-
pub details: Option<Vec<ErrorDetails>>,
278277
pub error: Option<Vec<ErrorDetails>>,
279278
}
280279

281280
#[derive(Debug, Default, Deserialize, Serialize)]
282281
#[serde(rename_all = "camelCase")]
283282
pub struct ErrorDetails {
284283
#[serde(rename = "type")]
285-
pub error_type: String,
284+
pub error_type: Option<String>,
286285
pub code: Option<String>,
287-
pub message: String,
288286
pub field: Option<String>,
287+
pub message: Option<String>,
288+
pub additional_info: Option<String>,
289289
}
290290

291291
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
@@ -325,17 +325,89 @@ impl From<FiservPaymentStatus> for enums::RefundStatus {
325325
}
326326
}
327327

328+
#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)]
329+
#[serde(rename_all = "camelCase")]
330+
pub struct ProcessorResponseDetails {
331+
pub approval_status: Option<String>,
332+
pub approval_code: Option<String>,
333+
pub reference_number: Option<String>,
334+
pub processor: Option<String>,
335+
pub host: Option<String>,
336+
pub network_routed: Option<String>,
337+
pub network_international_id: Option<String>,
338+
pub response_code: Option<String>,
339+
pub response_message: Option<String>,
340+
pub host_response_code: Option<String>,
341+
pub host_response_message: Option<String>,
342+
pub additional_info: Option<Vec<AdditionalInfo>>,
343+
pub bank_association_details: Option<BankAssociationDetails>,
344+
pub response_indicators: Option<ResponseIndicators>,
345+
}
346+
347+
#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)]
348+
#[serde(rename_all = "camelCase")]
349+
pub struct AdditionalInfo {
350+
pub name: Option<String>,
351+
pub value: Option<String>,
352+
}
353+
354+
#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)]
355+
#[serde(rename_all = "camelCase")]
356+
pub struct BankAssociationDetails {
357+
pub association_response_code: Option<String>,
358+
pub avs_security_code_response: Option<AvsSecurityCodeResponse>,
359+
}
360+
361+
#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)]
362+
#[serde(rename_all = "camelCase")]
363+
pub struct AvsSecurityCodeResponse {
364+
pub street_match: Option<String>,
365+
pub postal_code_match: Option<String>,
366+
pub security_code_match: Option<String>,
367+
pub association: Option<Association>,
368+
}
369+
370+
#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)]
371+
#[serde(rename_all = "camelCase")]
372+
pub struct Association {
373+
pub avs_code: Option<String>,
374+
pub security_code_response: Option<String>,
375+
}
376+
377+
#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)]
378+
#[serde(rename_all = "camelCase")]
379+
pub struct ResponseIndicators {
380+
pub alternate_route_debit_indicator: Option<bool>,
381+
pub signature_line_indicator: Option<bool>,
382+
pub signature_debit_route_indicator: Option<bool>,
383+
}
384+
328385
#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)]
329386
#[serde(rename_all = "camelCase")]
330387
pub struct FiservPaymentsResponse {
331-
gateway_response: GatewayResponse,
388+
pub gateway_response: GatewayResponse,
389+
pub payment_receipt: PaymentReceipt,
390+
}
391+
392+
#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)]
393+
#[serde(rename_all = "camelCase")]
394+
pub struct PaymentReceipt {
395+
pub approved_amount: ApprovedAmount,
396+
pub processor_response_details: Option<ProcessorResponseDetails>,
397+
}
398+
399+
#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)]
400+
#[serde(rename_all = "camelCase")]
401+
pub struct ApprovedAmount {
402+
pub total: FloatMajorUnit,
403+
pub currency: Currency,
332404
}
333405

334406
#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)]
335407
#[serde(rename_all = "camelCase")]
336408
#[serde(transparent)]
337409
pub struct FiservSyncResponse {
338-
sync_responses: Vec<FiservPaymentsResponse>,
410+
pub sync_responses: Vec<FiservPaymentsResponse>,
339411
}
340412

341413
#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)]
@@ -591,7 +663,8 @@ impl<F> TryFrom<&FiservRouterData<&types::RefundsRouterData<F>>> for FiservRefun
591663
#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)]
592664
#[serde(rename_all = "camelCase")]
593665
pub struct RefundResponse {
594-
gateway_response: GatewayResponse,
666+
pub gateway_response: GatewayResponse,
667+
pub payment_receipt: PaymentReceipt,
595668
}
596669

597670
impl TryFrom<RefundsResponseRouterData<Execute, RefundResponse>>

0 commit comments

Comments
 (0)