Skip to content

Commit

Permalink
feat(core): add support for confirmation flow for click to pay (#6982)
Browse files Browse the repository at this point in the history
Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
Co-authored-by: sai-harsha-vardhan <[email protected]>
Co-authored-by: Sai Harsha Vardhan <[email protected]>
  • Loading branch information
4 people authored Feb 20, 2025
1 parent eea4d6f commit 74bbf4b
Show file tree
Hide file tree
Showing 25 changed files with 906 additions and 287 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1270,13 +1270,13 @@ impl
ucaf_collection_indicator: None,
cavv,
ucaf_authentication_data,
xid: Some(authn_data.threeds_server_transaction_id.clone()),
xid: authn_data.threeds_server_transaction_id.clone(),
directory_server_transaction_id: authn_data
.ds_trans_id
.clone()
.map(Secret::new),
specification_version: None,
pa_specification_version: Some(authn_data.message_version.clone()),
pa_specification_version: authn_data.message_version.clone(),
veres_enrolled: Some("Y".to_string()),
}
});
Expand Down Expand Up @@ -1353,13 +1353,13 @@ impl
ucaf_collection_indicator: None,
cavv,
ucaf_authentication_data,
xid: Some(authn_data.threeds_server_transaction_id.clone()),
xid: authn_data.threeds_server_transaction_id.clone(),
directory_server_transaction_id: authn_data
.ds_trans_id
.clone()
.map(Secret::new),
specification_version: None,
pa_specification_version: Some(authn_data.message_version.clone()),
pa_specification_version: authn_data.message_version.clone(),
veres_enrolled: Some("Y".to_string()),
}
});
Expand Down Expand Up @@ -1434,13 +1434,13 @@ impl
ucaf_collection_indicator: None,
cavv,
ucaf_authentication_data,
xid: Some(authn_data.threeds_server_transaction_id.clone()),
xid: authn_data.threeds_server_transaction_id.clone(),
directory_server_transaction_id: authn_data
.ds_trans_id
.clone()
.map(Secret::new),
specification_version: None,
pa_specification_version: Some(authn_data.message_version.clone()),
pa_specification_version: authn_data.message_version.clone(),
veres_enrolled: Some("Y".to_string()),
}
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -189,12 +189,12 @@ pub enum ThreeDSecureData {
#[serde(rename_all = "camelCase")]
pub struct ThreeDSData {
#[serde(rename = "threeDSTransactionId")]
pub three_ds_transaction_id: Secret<String>,
pub three_ds_transaction_id: Option<Secret<String>>,
pub cavv: Secret<String>,
pub eci: Option<String>,
pub xid: Option<Secret<String>>,
#[serde(rename = "threeDSVersion")]
pub three_ds_version: String,
pub three_ds_version: Option<String>,
#[serde(rename = "authenticationResponse")]
pub authentication_response: String,
}
Expand Down Expand Up @@ -452,11 +452,17 @@ fn create_card_details(

if let Some(auth_data) = &item.router_data.request.authentication_data {
details.three_ds = Some(ThreeDSecureData::Authentication(ThreeDSData {
three_ds_transaction_id: Secret::new(auth_data.threeds_server_transaction_id.clone()),
three_ds_transaction_id: auth_data
.threeds_server_transaction_id
.clone()
.map(Secret::new),
cavv: Secret::new(auth_data.cavv.clone()),
eci: auth_data.eci.clone(),
xid: auth_data.ds_trans_id.clone().map(Secret::new),
three_ds_version: auth_data.message_version.to_string(),
three_ds_version: auth_data
.message_version
.clone()
.map(|version| version.to_string()),
authentication_response: "Y".to_string(),
}));
} else if item.router_data.is_three_ds() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,23 @@ use hyperswitch_domain_models::{
access_token_auth::AccessTokenAuth,
payments::{Authorize, Capture, PSync, PaymentMethodToken, Session, SetupMandate, Void},
refunds::{Execute, RSync},
Authenticate, PostAuthenticate, PreAuthenticate,
Authenticate, AuthenticationConfirmation, PostAuthenticate, PreAuthenticate,
},
router_request_types::{
unified_authentication_service::{
UasAuthenticationRequestData, UasAuthenticationResponseData,
UasPostAuthenticationRequestData, UasPreAuthenticationRequestData,
UasConfirmationRequestData, UasPostAuthenticationRequestData,
UasPreAuthenticationRequestData,
},
AccessTokenRequestData, PaymentMethodTokenizationData, PaymentsAuthorizeData,
PaymentsCancelData, PaymentsCaptureData, PaymentsSessionData, PaymentsSyncData,
RefundsData, SetupMandateRequestData,
},
router_response_types::{PaymentsResponseData, RefundsResponseData},
types::{UasPostAuthenticationRouterData, UasPreAuthenticationRouterData},
types::{
UasAuthenticationConfirmationRouterData, UasPostAuthenticationRouterData,
UasPreAuthenticationRouterData,
},
};
use hyperswitch_interfaces::{
api::{
Expand Down Expand Up @@ -71,6 +75,7 @@ impl api::PaymentToken for UnifiedAuthenticationService {}
impl api::UnifiedAuthenticationService for UnifiedAuthenticationService {}
impl api::UasPreAuthentication for UnifiedAuthenticationService {}
impl api::UasPostAuthentication for UnifiedAuthenticationService {}
impl api::UasAuthenticationConfirmation for UnifiedAuthenticationService {}
impl api::UasAuthentication for UnifiedAuthenticationService {}

impl ConnectorIntegration<PaymentMethodToken, PaymentMethodTokenizationData, PaymentsResponseData>
Expand Down Expand Up @@ -168,6 +173,105 @@ impl ConnectorIntegration<SetupMandate, SetupMandateRequestData, PaymentsRespons
{
}

impl
ConnectorIntegration<
AuthenticationConfirmation,
UasConfirmationRequestData,
UasAuthenticationResponseData,
> for UnifiedAuthenticationService
{
fn get_headers(
&self,
req: &UasAuthenticationConfirmationRouterData,
connectors: &Connectors,
) -> CustomResult<Vec<(String, masking::Maskable<String>)>, errors::ConnectorError> {
self.build_headers(req, connectors)
}

fn get_content_type(&self) -> &'static str {
self.common_get_content_type()
}

fn get_url(
&self,
_req: &UasAuthenticationConfirmationRouterData,
connectors: &Connectors,
) -> CustomResult<String, errors::ConnectorError> {
Ok(format!("{}confirmation", self.base_url(connectors)))
}

fn get_request_body(
&self,
req: &UasAuthenticationConfirmationRouterData,
_connectors: &Connectors,
) -> CustomResult<RequestContent, errors::ConnectorError> {
let amount = utils::convert_amount(
self.amount_converter,
req.request.transaction_amount,
req.request.transaction_currency,
)?;

let connector_router_data =
unified_authentication_service::UnifiedAuthenticationServiceRouterData::from((
amount, req,
));
let connector_req =
unified_authentication_service::UnifiedAuthenticationServiceAuthenticateConfirmationRequest::try_from(
&connector_router_data,
)?;
Ok(RequestContent::Json(Box::new(connector_req)))
}

fn build_request(
&self,
req: &UasAuthenticationConfirmationRouterData,
connectors: &Connectors,
) -> CustomResult<Option<Request>, errors::ConnectorError> {
Ok(Some(
RequestBuilder::new()
.method(Method::Post)
.url(&types::UasAuthenticationConfirmationType::get_url(
self, req, connectors,
)?)
.attach_default_headers()
.headers(types::UasAuthenticationConfirmationType::get_headers(
self, req, connectors,
)?)
.set_body(types::UasAuthenticationConfirmationType::get_request_body(
self, req, connectors,
)?)
.build(),
))
}

fn handle_response(
&self,
data: &UasAuthenticationConfirmationRouterData,
event_builder: Option<&mut ConnectorEvent>,
res: Response,
) -> CustomResult<UasAuthenticationConfirmationRouterData, errors::ConnectorError> {
let response: unified_authentication_service::UnifiedAuthenticationServiceAuthenticateConfirmationResponse =
res.response
.parse_struct("UnifiedAuthenticationService UnifiedAuthenticationServiceAuthenticateConfirmationResponse")
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
event_builder.map(|i| i.set_response_body(&response));
router_env::logger::info!(connector_response=?response);
RouterData::try_from(ResponseRouterData {
response,
data: data.clone(),
http_code: res.status_code,
})
}

fn get_error_response(
&self,
res: Response,
event_builder: Option<&mut ConnectorEvent>,
) -> CustomResult<ErrorResponse, errors::ConnectorError> {
self.build_error_response(res, event_builder)
}
}

impl
ConnectorIntegration<
PreAuthenticate,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@ use hyperswitch_domain_models::{
DynamicData, PostAuthenticationDetails, PreAuthenticationDetails, TokenDetails,
UasAuthenticationResponseData,
},
types::{UasPostAuthenticationRouterData, UasPreAuthenticationRouterData},
types::{
UasAuthenticationConfirmationRouterData, UasPostAuthenticationRouterData,
UasPreAuthenticationRouterData,
},
};
use hyperswitch_interfaces::errors;
use masking::Secret;
Expand Down Expand Up @@ -46,6 +49,56 @@ pub struct UnifiedAuthenticationServicePreAuthenticateRequest {
pub transaction_details: Option<TransactionDetails>,
}

#[derive(Debug, Serialize, PartialEq)]
pub struct UnifiedAuthenticationServiceAuthenticateConfirmationRequest {
pub authenticate_by: String,
pub source_authentication_id: String,
pub auth_creds: AuthType,
pub x_src_flow_id: Option<String>,
pub transaction_amount: Option<FloatMajorUnit>,
pub transaction_currency: Option<enums::Currency>,
pub checkout_event_type: Option<String>,
pub checkout_event_status: Option<String>,
pub confirmation_status: Option<String>,
pub confirmation_reason: Option<String>,
pub confirmation_timestamp: Option<PrimitiveDateTime>,
pub network_authorization_code: Option<String>,
pub network_transaction_identifier: Option<String>,
pub correlation_id: Option<String>,
pub merchant_transaction_id: Option<String>,
}

#[derive(Debug, Serialize, PartialEq, Deserialize)]
pub struct UnifiedAuthenticationServiceAuthenticateConfirmationResponse {
status: String,
}

impl<F, T>
TryFrom<
ResponseRouterData<
F,
UnifiedAuthenticationServiceAuthenticateConfirmationResponse,
T,
UasAuthenticationResponseData,
>,
> for RouterData<F, T, UasAuthenticationResponseData>
{
type Error = error_stack::Report<errors::ConnectorError>;
fn try_from(
item: ResponseRouterData<
F,
UnifiedAuthenticationServiceAuthenticateConfirmationResponse,
T,
UasAuthenticationResponseData,
>,
) -> Result<Self, Self::Error> {
Ok(Self {
response: Ok(UasAuthenticationResponseData::Confirmation {}),
..item.data
})
}
}

#[derive(Debug, Serialize, PartialEq)]
#[serde(tag = "auth_type")]
pub enum AuthType {
Expand Down Expand Up @@ -443,3 +496,43 @@ impl<F, T>
pub struct UnifiedAuthenticationServiceErrorResponse {
pub error: String,
}

impl TryFrom<&UnifiedAuthenticationServiceRouterData<&UasAuthenticationConfirmationRouterData>>
for UnifiedAuthenticationServiceAuthenticateConfirmationRequest
{
type Error = error_stack::Report<errors::ConnectorError>;
fn try_from(
item: &UnifiedAuthenticationServiceRouterData<&UasAuthenticationConfirmationRouterData>,
) -> Result<Self, Self::Error> {
let auth_type =
UnifiedAuthenticationServiceAuthType::try_from(&item.router_data.connector_auth_type)?;
let authentication_id = item.router_data.authentication_id.clone().ok_or(
errors::ConnectorError::MissingRequiredField {
field_name: "authentication_id",
},
)?;
Ok(Self {
authenticate_by: item.router_data.connector.clone(),
auth_creds: AuthType::HeaderKey {
api_key: auth_type.api_key,
},
source_authentication_id: authentication_id,
x_src_flow_id: item.router_data.request.x_src_flow_id.clone(),
transaction_amount: Some(item.amount),
transaction_currency: Some(item.router_data.request.transaction_currency),
checkout_event_type: item.router_data.request.checkout_event_type.clone(),
checkout_event_status: item.router_data.request.checkout_event_status.clone(),
confirmation_status: item.router_data.request.confirmation_status.clone(),
confirmation_reason: item.router_data.request.confirmation_reason.clone(),
confirmation_timestamp: item.router_data.request.confirmation_timestamp,
network_authorization_code: item.router_data.request.network_authorization_code.clone(),
network_transaction_identifier: item
.router_data
.request
.network_transaction_identifier
.clone(),
correlation_id: item.router_data.request.correlation_id.clone(),
merchant_transaction_id: item.router_data.request.merchant_transaction_id.clone(),
})
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -941,13 +941,13 @@ impl
ucaf_collection_indicator: None,
cavv,
ucaf_authentication_data,
xid: Some(authn_data.threeds_server_transaction_id.clone()),
xid: authn_data.threeds_server_transaction_id.clone(),
directory_server_transaction_id: authn_data
.ds_trans_id
.clone()
.map(Secret::new),
specification_version: None,
pa_specification_version: Some(authn_data.message_version.clone()),
pa_specification_version: authn_data.message_version.clone(),
veres_enrolled: Some("Y".to_string()),
}
});
Expand Down
Loading

0 comments on commit 74bbf4b

Please sign in to comment.