diff --git a/crates/router/src/connector/netcetera.rs b/crates/router/src/connector/netcetera.rs index 393050f8712f..40df0050f47c 100644 --- a/crates/router/src/connector/netcetera.rs +++ b/crates/router/src/connector/netcetera.rs @@ -3,8 +3,8 @@ pub mod transformers; use std::fmt::Debug; -use common_utils::request::RequestContent; -use error_stack::{report, ResultExt}; +use common_utils::{ext_traits::ByteSliceExt, request::RequestContent}; +use error_stack::ResultExt; use masking::ExposeInterface; use transformers as netcetera; @@ -170,23 +170,52 @@ impl ConnectorIntegration, + request: &api::IncomingWebhookRequestDetails<'_>, ) -> CustomResult { - Err(report!(errors::ConnectorError::WebhooksNotImplemented)) + let webhook_body: netcetera::ResultsResponseData = request + .body + .parse_struct("netcetera ResultsResponseData") + .change_context(errors::ConnectorError::WebhookBodyDecodingFailed)?; + Ok(api::webhooks::ObjectReferenceId::ExternalAuthenticationID( + api::webhooks::AuthenticationIdType::ConnectorAuthenticationId( + webhook_body.three_ds_server_trans_id, + ), + )) } fn get_webhook_event_type( &self, _request: &api::IncomingWebhookRequestDetails<'_>, ) -> CustomResult { - Err(report!(errors::ConnectorError::WebhooksNotImplemented)) + Ok(api::IncomingWebhookEvent::ExternalAuthenticationARes) } fn get_webhook_resource_object( &self, - _request: &api::IncomingWebhookRequestDetails<'_>, + request: &api::IncomingWebhookRequestDetails<'_>, ) -> CustomResult, errors::ConnectorError> { - Err(report!(errors::ConnectorError::WebhooksNotImplemented)) + let webhook_body_value: netcetera::ResultsResponseData = request + .body + .parse_struct("netcetera ResultsResponseDatae") + .change_context(errors::ConnectorError::WebhookBodyDecodingFailed)?; + Ok(Box::new(webhook_body_value)) + } + + fn get_external_authentication_details( + &self, + request: &api::IncomingWebhookRequestDetails<'_>, + ) -> CustomResult { + let webhook_body: netcetera::ResultsResponseData = request + .body + .parse_struct("netcetera ResultsResponseData") + .change_context(errors::ConnectorError::WebhookBodyDecodingFailed)?; + Ok(api::ExternalAuthenticationPayload { + trans_status: webhook_body + .trans_status + .unwrap_or(common_enums::TransactionStatus::InformationOnly), + authentication_value: webhook_body.authentication_value, + eci: webhook_body.eci, + }) } } diff --git a/crates/router/src/connector/netcetera/transformers.rs b/crates/router/src/connector/netcetera/transformers.rs index 0f73500ef661..6123b891afba 100644 --- a/crates/router/src/connector/netcetera/transformers.rs +++ b/crates/router/src/connector/netcetera/transformers.rs @@ -548,7 +548,7 @@ impl TryFrom<&NetceteraRouterData<&types::authentication::ConnectorAuthenticatio white_list_status: None, trust_list_status: None, seller_info: None, - results_response_notification_url: request.return_url, + results_response_notification_url: Some(request.webhook_url), }; let browser_information = request.browser_details.map(netcetera_types::Browser::from); let sdk_information = request.sdk_information.map(netcetera_types::Sdk::from); @@ -634,3 +634,44 @@ pub enum ACSChallengeMandatedIndicator { /// Challenge is not mandated N, } + +#[derive(Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ResultsResponseData { + /// Universally unique transaction identifier assigned by the 3DS Server to identify a single transaction. + /// It has the same value as the authentication request and conforms to the format defined in IETF RFC 4122. + #[serde(rename = "threeDSServerTransID")] + pub three_ds_server_trans_id: String, + + /// Indicates the status of a transaction in terms of its authentication. + /// + /// Valid values: + /// - `Y`: Authentication / Account verification successful. + /// - `N`: Not authenticated / Account not verified; Transaction denied. + /// - `U`: Authentication / Account verification could not be performed; technical or other problem. + /// - `C`: A challenge is required to complete the authentication. + /// - `R`: Authentication / Account verification Rejected. Issuer is rejecting authentication/verification + /// and request that authorization not be attempted. + /// - `A`: Attempts processing performed; Not authenticated / verified, but a proof of attempt + /// authentication / verification is provided. + /// - `D`: A challenge is required to complete the authentication. Decoupled Authentication confirmed. + /// - `I`: Informational Only; 3DS Requestor challenge preference acknowledged. + pub trans_status: Option, + + /// Payment System-specific value provided as part of the ACS registration for each supported DS. + /// Authentication Value may be used to provide proof of authentication. + pub authentication_value: Option, + + /// Payment System-specific value provided by the ACS to indicate the results of the attempt to authenticate + /// the Cardholder. + pub eci: Option, + + /// The received Results Request from the Directory Server. + pub results_request: Option, + + /// The sent Results Response to the Directory Server. + pub results_response: Option, + + /// Optional object containing error details if any errors occurred during the process. + pub error_details: Option, +} diff --git a/crates/router/src/core/authentication.rs b/crates/router/src/core/authentication.rs index e095244df830..dd17ae19ddec 100644 --- a/crates/router/src/core/authentication.rs +++ b/crates/router/src/core/authentication.rs @@ -38,6 +38,7 @@ pub async fn perform_authentication( sdk_information: Option, threeds_method_comp_ind: api_models::payments::ThreeDsCompletionIndicator, email: Option, + webhook_url: String, ) -> CustomResult { let router_data = transformers::construct_authentication_router_data( authentication_connector.clone(), @@ -57,6 +58,7 @@ pub async fn perform_authentication( sdk_information, threeds_method_comp_ind, email, + webhook_url, )?; let response = utils::do_auth_connector_call(state, authentication_connector.clone(), router_data).await?; diff --git a/crates/router/src/core/authentication/transformers.rs b/crates/router/src/core/authentication/transformers.rs index 3c19c0630e85..abefd12c77d7 100644 --- a/crates/router/src/core/authentication/transformers.rs +++ b/crates/router/src/core/authentication/transformers.rs @@ -43,6 +43,7 @@ pub fn construct_authentication_router_data( sdk_information: Option, threeds_method_comp_ind: api_models::payments::ThreeDsCompletionIndicator, email: Option, + webhook_url: String, ) -> RouterResult { let authentication_details: api_models::admin::AuthenticationConnectorDetails = business_profile @@ -72,6 +73,7 @@ pub fn construct_authentication_router_data( email, three_ds_requestor_url: authentication_details.three_ds_requestor_url, threeds_method_comp_ind, + webhook_url, }; construct_router_data( authentication_connector, diff --git a/crates/router/src/core/payments.rs b/crates/router/src/core/payments.rs index 1109ce26c030..c255c0debbb2 100644 --- a/crates/router/src/core/payments.rs +++ b/crates/router/src/core/payments.rs @@ -3811,6 +3811,11 @@ pub async fn payment_external_authentication( &payment_attempt.clone(), payment_connector_name, )); + let webhook_url = helpers::create_webhook_url( + &state.conf.server.base_url, + merchant_id, + &authentication_connector, + ); let business_profile = state .store @@ -3844,6 +3849,7 @@ pub async fn payment_external_authentication( req.sdk_information, req.threeds_method_comp_ind, optional_customer.and_then(|customer| customer.email.map(common_utils::pii::Email::from)), + webhook_url, )) .await?; Ok(services::ApplicationResponse::Json( diff --git a/crates/router/src/types/api/webhooks.rs b/crates/router/src/types/api/webhooks.rs index dbc7ea210847..964885cae551 100644 --- a/crates/router/src/types/api/webhooks.rs +++ b/crates/router/src/types/api/webhooks.rs @@ -1,7 +1,7 @@ use api_models::admin::MerchantConnectorWebhookDetails; pub use api_models::webhooks::{ - IncomingWebhookDetails, IncomingWebhookEvent, MerchantWebhookConfig, ObjectReferenceId, - OutgoingWebhook, OutgoingWebhookContent, WebhookFlow, + AuthenticationIdType, IncomingWebhookDetails, IncomingWebhookEvent, MerchantWebhookConfig, + ObjectReferenceId, OutgoingWebhook, OutgoingWebhookContent, WebhookFlow, }; use common_utils::ext_traits::ValueExt; use error_stack::ResultExt; diff --git a/crates/router/src/types/authentication.rs b/crates/router/src/types/authentication.rs index 367b96235abf..7ea937b9ffd8 100644 --- a/crates/router/src/types/authentication.rs +++ b/crates/router/src/types/authentication.rs @@ -114,6 +114,7 @@ pub struct ConnectorAuthenticationRequestData { pub email: Option, pub threeds_method_comp_ind: api_models::payments::ThreeDsCompletionIndicator, pub three_ds_requestor_url: String, + pub webhook_url: String, } #[derive(Clone, Debug)]