Skip to content

Commit 3f18c94

Browse files
feat(connectors): [Redsys] add Psync and Rsync support (#7586)
Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
1 parent d59c2fa commit 3f18c94

File tree

7 files changed

+613
-29
lines changed

7 files changed

+613
-29
lines changed

Cargo.lock

+16
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/common_utils/src/consts.rs

+3
Original file line numberDiff line numberDiff line change
@@ -173,3 +173,6 @@ pub const DEFAULT_CUSTOMER_ID_BLOCKING_THRESHOLD: i32 = 5;
173173

174174
/// Default Card Testing Guard Redis Expiry in seconds
175175
pub const DEFAULT_CARD_TESTING_GUARD_EXPIRY_IN_SECS: i32 = 3600;
176+
177+
/// SOAP 1.1 Envelope Namespace
178+
pub const SOAP_ENV_NAMESPACE: &str = "http://schemas.xmlsoap.org/soap/envelope/";

crates/hyperswitch_connectors/Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ urlencoding = "2.1.3"
4646
uuid = { version = "1.8.0", features = ["v4"] }
4747
lazy_static = "1.4.0"
4848
unicode-normalization = "0.1.21"
49-
49+
html-escape = "0.2"
5050
# First party crates
5151
api_models = { version = "0.1.0", path = "../api_models", features = ["errors"], default-features = false }
5252
cards = { version = "0.1.0", path = "../cards" }

crates/hyperswitch_connectors/src/connectors/redsys.rs

+180-11
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use std::sync::LazyLock;
44

55
use common_utils::{
66
errors::CustomResult,
7-
ext_traits::BytesExt,
7+
ext_traits::{BytesExt, XmlExt},
88
request::{Method, Request, RequestBuilder, RequestContent},
99
types::{AmountConvertor, StringMinorUnit, StringMinorUnitForConnector},
1010
};
@@ -29,7 +29,7 @@ use hyperswitch_domain_models::{
2929
types::{
3030
PaymentsAuthorizeRouterData, PaymentsCancelRouterData, PaymentsCaptureRouterData,
3131
PaymentsCompleteAuthorizeRouterData, PaymentsPreProcessingRouterData,
32-
RefundExecuteRouterData,
32+
PaymentsSyncRouterData, RefundExecuteRouterData, RefundSyncRouterData,
3333
},
3434
};
3535
use hyperswitch_interfaces::{
@@ -42,13 +42,14 @@ use hyperswitch_interfaces::{
4242
events::connector_api_logs::ConnectorEvent,
4343
types::{
4444
PaymentsAuthorizeType, PaymentsCaptureType, PaymentsCompleteAuthorizeType,
45-
PaymentsPreProcessingType, PaymentsVoidType, RefundExecuteType, Response,
45+
PaymentsPreProcessingType, PaymentsSyncType, PaymentsVoidType, RefundExecuteType,
46+
RefundSyncType, Response,
4647
},
4748
webhooks,
4849
};
4950
use transformers as redsys;
5051

51-
use crate::{types::ResponseRouterData, utils as connector_utils};
52+
use crate::{constants::headers, types::ResponseRouterData, utils as connector_utils};
5253

5354
#[derive(Clone)]
5455
pub struct Redsys {
@@ -78,11 +79,6 @@ impl api::PaymentToken for Redsys {}
7879
impl api::PaymentsPreProcessing for Redsys {}
7980
impl api::PaymentsCompleteAuthorize for Redsys {}
8081

81-
impl<Flow, Request, Response> ConnectorCommonExt<Flow, Request, Response> for Redsys where
82-
Self: ConnectorIntegration<Flow, Request, Response>
83-
{
84-
}
85-
8682
impl ConnectorCommon for Redsys {
8783
fn id(&self) -> &'static str {
8884
"redsys"
@@ -143,6 +139,29 @@ impl ConnectorIntegration<SetupMandate, SetupMandateRequestData, PaymentsRespons
143139
}
144140
}
145141

142+
impl<Flow, Request, Response> ConnectorCommonExt<Flow, Request, Response> for Redsys
143+
where
144+
Self: ConnectorIntegration<Flow, Request, Response>,
145+
{
146+
fn build_headers(
147+
&self,
148+
_req: &RouterData<Flow, Request, Response>,
149+
_connectors: &Connectors,
150+
) -> CustomResult<Vec<(String, masking::Maskable<String>)>, errors::ConnectorError> {
151+
let headers = vec![
152+
(
153+
headers::CONTENT_TYPE.to_string(),
154+
"application/xml".to_string().into(),
155+
),
156+
(
157+
headers::SOAP_ACTION.to_string(),
158+
redsys::REDSYS_SOAP_ACTION.to_string().into(),
159+
),
160+
];
161+
Ok(headers)
162+
}
163+
}
164+
146165
impl ConnectorIntegration<PreProcessing, PaymentsPreProcessingData, PaymentsResponseData>
147166
for Redsys
148167
{
@@ -621,9 +640,159 @@ impl ConnectorIntegration<Execute, RefundsData, RefundsResponseData> for Redsys
621640
}
622641
}
623642

624-
impl ConnectorIntegration<PSync, PaymentsSyncData, PaymentsResponseData> for Redsys {}
643+
impl ConnectorIntegration<PSync, PaymentsSyncData, PaymentsResponseData> for Redsys {
644+
fn get_headers(
645+
&self,
646+
req: &PaymentsSyncRouterData,
647+
connectors: &Connectors,
648+
) -> CustomResult<Vec<(String, masking::Maskable<String>)>, errors::ConnectorError> {
649+
self.build_headers(req, connectors)
650+
}
651+
652+
fn get_url(
653+
&self,
654+
_req: &PaymentsSyncRouterData,
655+
connectors: &Connectors,
656+
) -> CustomResult<String, errors::ConnectorError> {
657+
Ok(format!(
658+
"{}/apl02/services/SerClsWSConsulta",
659+
self.base_url(connectors)
660+
))
661+
}
662+
663+
fn get_content_type(&self) -> &'static str {
664+
self.common_get_content_type()
665+
}
666+
667+
fn get_request_body(
668+
&self,
669+
req: &PaymentsSyncRouterData,
670+
_connectors: &Connectors,
671+
) -> CustomResult<RequestContent, errors::ConnectorError> {
672+
let connector_req = redsys::build_payment_sync_request(req)?;
673+
Ok(RequestContent::RawBytes(connector_req))
674+
}
675+
676+
fn build_request(
677+
&self,
678+
req: &PaymentsSyncRouterData,
679+
connectors: &Connectors,
680+
) -> CustomResult<Option<Request>, errors::ConnectorError> {
681+
Ok(Some(
682+
RequestBuilder::new()
683+
.method(Method::Post)
684+
.url(&PaymentsSyncType::get_url(self, req, connectors)?)
685+
.attach_default_headers()
686+
.headers(PaymentsSyncType::get_headers(self, req, connectors)?)
687+
.set_body(self.get_request_body(req, connectors)?)
688+
.build(),
689+
))
690+
}
691+
fn handle_response(
692+
&self,
693+
data: &PaymentsSyncRouterData,
694+
event_builder: Option<&mut ConnectorEvent>,
695+
res: Response,
696+
) -> CustomResult<PaymentsSyncRouterData, errors::ConnectorError> {
697+
let response = String::from_utf8(res.response.to_vec())
698+
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
699+
let response_data = html_escape::decode_html_entities(&response).to_ascii_lowercase();
700+
let response = response_data
701+
.parse_xml::<redsys::RedsysSyncResponse>()
702+
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
703+
704+
event_builder.map(|i| i.set_response_body(&response));
705+
router_env::logger::info!(connector_response=?response);
706+
RouterData::try_from(ResponseRouterData {
707+
response,
708+
data: data.clone(),
709+
http_code: res.status_code,
710+
})
711+
}
712+
fn get_error_response(
713+
&self,
714+
res: Response,
715+
event_builder: Option<&mut ConnectorEvent>,
716+
) -> CustomResult<ErrorResponse, errors::ConnectorError> {
717+
self.build_error_response(res, event_builder)
718+
}
719+
}
720+
721+
impl ConnectorIntegration<RSync, RefundsData, RefundsResponseData> for Redsys {
722+
fn get_headers(
723+
&self,
724+
req: &RefundSyncRouterData,
725+
connectors: &Connectors,
726+
) -> CustomResult<Vec<(String, masking::Maskable<String>)>, errors::ConnectorError> {
727+
self.build_headers(req, connectors)
728+
}
729+
fn get_content_type(&self) -> &'static str {
730+
self.common_get_content_type()
731+
}
732+
fn get_url(
733+
&self,
734+
_req: &RefundSyncRouterData,
735+
connectors: &Connectors,
736+
) -> CustomResult<String, errors::ConnectorError> {
737+
Ok(format!(
738+
"{}/apl02/services/SerClsWSConsulta",
739+
self.base_url(connectors)
740+
))
741+
}
742+
743+
fn get_request_body(
744+
&self,
745+
req: &RefundSyncRouterData,
746+
_connectors: &Connectors,
747+
) -> CustomResult<RequestContent, errors::ConnectorError> {
748+
let connector_req = redsys::build_refund_sync_request(req)?;
749+
Ok(RequestContent::RawBytes(connector_req))
750+
}
751+
752+
fn build_request(
753+
&self,
754+
req: &RefundSyncRouterData,
755+
connectors: &Connectors,
756+
) -> CustomResult<Option<Request>, errors::ConnectorError> {
757+
Ok(Some(
758+
RequestBuilder::new()
759+
.method(Method::Post)
760+
.url(&RefundSyncType::get_url(self, req, connectors)?)
761+
.attach_default_headers()
762+
.headers(RefundSyncType::get_headers(self, req, connectors)?)
763+
.set_body(self.get_request_body(req, connectors)?)
764+
.build(),
765+
))
766+
}
767+
fn handle_response(
768+
&self,
769+
data: &RefundSyncRouterData,
770+
event_builder: Option<&mut ConnectorEvent>,
771+
res: Response,
772+
) -> CustomResult<RefundSyncRouterData, errors::ConnectorError> {
773+
let response = String::from_utf8(res.response.to_vec())
774+
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
775+
let response_data = html_escape::decode_html_entities(&response).to_ascii_lowercase();
776+
let response = response_data
777+
.parse_xml::<redsys::RedsysSyncResponse>()
778+
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
625779

626-
impl ConnectorIntegration<RSync, RefundsData, RefundsResponseData> for Redsys {}
780+
event_builder.map(|i| i.set_response_body(&response));
781+
router_env::logger::info!(connector_response=?response);
782+
RouterData::try_from(ResponseRouterData {
783+
response,
784+
data: data.clone(),
785+
http_code: res.status_code,
786+
})
787+
}
788+
fn get_error_response(
789+
&self,
790+
res: Response,
791+
event_builder: Option<&mut ConnectorEvent>,
792+
) -> CustomResult<ErrorResponse, errors::ConnectorError> {
793+
self.build_error_response(res, event_builder)
794+
}
795+
}
627796

628797
impl ConnectorIntegration<PaymentMethodToken, PaymentMethodTokenizationData, PaymentsResponseData>
629798
for Redsys

0 commit comments

Comments
 (0)