Skip to content

Commit

Permalink
feat(connector): add webhook support for netcetera (#4382)
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>
  • Loading branch information
hrithikesh026 and hyperswitch-bot[bot] committed Apr 23, 2024
1 parent 4c81a66 commit 776c1a7
Show file tree
Hide file tree
Showing 7 changed files with 91 additions and 10 deletions.
43 changes: 36 additions & 7 deletions crates/router/src/connector/netcetera.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -170,23 +170,52 @@ impl ConnectorIntegration<api::RSync, types::RefundsData, types::RefundsResponse
impl api::IncomingWebhook for Netcetera {
fn get_webhook_object_reference_id(
&self,
_request: &api::IncomingWebhookRequestDetails<'_>,
request: &api::IncomingWebhookRequestDetails<'_>,
) -> CustomResult<api::webhooks::ObjectReferenceId, errors::ConnectorError> {
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<api::IncomingWebhookEvent, errors::ConnectorError> {
Err(report!(errors::ConnectorError::WebhooksNotImplemented))
Ok(api::IncomingWebhookEvent::ExternalAuthenticationARes)
}

fn get_webhook_resource_object(
&self,
_request: &api::IncomingWebhookRequestDetails<'_>,
request: &api::IncomingWebhookRequestDetails<'_>,
) -> CustomResult<Box<dyn masking::ErasedMaskSerialize>, 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<api::ExternalAuthenticationPayload, errors::ConnectorError> {
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,
})
}
}

Expand Down
43 changes: 42 additions & 1 deletion crates/router/src/connector/netcetera/transformers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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<common_enums::TransactionStatus>,

/// 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<String>,

/// Payment System-specific value provided by the ACS to indicate the results of the attempt to authenticate
/// the Cardholder.
pub eci: Option<String>,

/// The received Results Request from the Directory Server.
pub results_request: Option<serde_json::Value>,

/// The sent Results Response to the Directory Server.
pub results_response: Option<serde_json::Value>,

/// Optional object containing error details if any errors occurred during the process.
pub error_details: Option<NetceteraErrorDetails>,
}
2 changes: 2 additions & 0 deletions crates/router/src/core/authentication.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ pub async fn perform_authentication(
sdk_information: Option<payments::SdkInformation>,
threeds_method_comp_ind: api_models::payments::ThreeDsCompletionIndicator,
email: Option<common_utils::pii::Email>,
webhook_url: String,
) -> CustomResult<core_types::api::authentication::AuthenticationResponse, ApiErrorResponse> {
let router_data = transformers::construct_authentication_router_data(
authentication_connector.clone(),
Expand All @@ -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?;
Expand Down
2 changes: 2 additions & 0 deletions crates/router/src/core/authentication/transformers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ pub fn construct_authentication_router_data(
sdk_information: Option<api_models::payments::SdkInformation>,
threeds_method_comp_ind: api_models::payments::ThreeDsCompletionIndicator,
email: Option<common_utils::pii::Email>,
webhook_url: String,
) -> RouterResult<types::authentication::ConnectorAuthenticationRouterData> {
let authentication_details: api_models::admin::AuthenticationConnectorDetails =
business_profile
Expand Down Expand Up @@ -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,
Expand Down
6 changes: 6 additions & 0 deletions crates/router/src/core/payments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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(
Expand Down
4 changes: 2 additions & 2 deletions crates/router/src/types/api/webhooks.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
1 change: 1 addition & 0 deletions crates/router/src/types/authentication.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ pub struct ConnectorAuthenticationRequestData {
pub email: Option<Email>,
pub threeds_method_comp_ind: api_models::payments::ThreeDsCompletionIndicator,
pub three_ds_requestor_url: String,
pub webhook_url: String,
}

#[derive(Clone, Debug)]
Expand Down

0 comments on commit 776c1a7

Please sign in to comment.