refactor: Move trait IncomingWebhook to hyperswitch_interfaces (#5191)

This commit is contained in:
DEEPANSHU BANSAL
2024-07-11 16:01:31 +05:30
committed by GitHub
parent 3312e787f9
commit 35d502e3da
27 changed files with 612 additions and 573 deletions

View File

@ -7,7 +7,7 @@ use base64::Engine;
use common_utils::request::RequestContent;
use diesel_models::{enums as storage_enums, enums};
use error_stack::{report, ResultExt};
use masking::ExposeInterface;
use masking::{ExposeInterface, Secret};
use ring::hmac;
use router_env::{instrument, tracing};
@ -1754,15 +1754,16 @@ impl api::IncomingWebhook for Adyen {
async fn verify_webhook_source(
&self,
request: &api::IncomingWebhookRequestDetails<'_>,
merchant_account: &domain::MerchantAccount,
merchant_connector_account: domain::MerchantConnectorAccount,
merchant_id: &str,
connector_webhook_details: Option<common_utils::pii::SecretSerdeValue>,
_connector_account_details: crypto::Encryptable<Secret<serde_json::Value>>,
connector_label: &str,
) -> CustomResult<bool, errors::ConnectorError> {
let connector_webhook_secrets = self
.get_webhook_source_verification_merchant_secret(
merchant_account,
merchant_id,
connector_label,
merchant_connector_account,
connector_webhook_details,
)
.await
.change_context(errors::ConnectorError::WebhookSourceVerificationFailed)?;
@ -1774,7 +1775,7 @@ impl api::IncomingWebhook for Adyen {
let message = self
.get_webhook_source_verification_message(
request,
&merchant_account.merchant_id,
merchant_id,
&connector_webhook_secrets,
)
.change_context(errors::ConnectorError::WebhookSourceVerificationFailed)?;

View File

@ -7,7 +7,7 @@ use base64::Engine;
use common_utils::{crypto, ext_traits::XmlExt, request::RequestContent};
use diesel_models::enums;
use error_stack::{report, Report, ResultExt};
use masking::{ExposeInterface, PeekInterface};
use masking::{ExposeInterface, PeekInterface, Secret};
use ring::hmac;
use sha1::{Digest, Sha1};
@ -31,7 +31,6 @@ use crate::{
types::{
self,
api::{self, ConnectorCommon, ConnectorCommonExt},
domain,
transformers::ForeignFrom,
ErrorResponse,
},
@ -180,7 +179,7 @@ impl ConnectorValidation for Braintree {
fn validate_mandate_payment(
&self,
pm_type: Option<types::storage::enums::PaymentMethodType>,
pm_data: domain::payments::PaymentMethodData,
pm_data: hyperswitch_domain_models::payment_method_data::PaymentMethodData,
) -> CustomResult<(), errors::ConnectorError> {
let mandate_supported_pmd = std::collections::HashSet::from([PaymentMethodDataType::Card]);
connector_utils::is_mandate_supported(pm_data, pm_type, mandate_supported_pmd, self.id())
@ -1377,15 +1376,16 @@ impl api::IncomingWebhook for Braintree {
async fn verify_webhook_source(
&self,
request: &api::IncomingWebhookRequestDetails<'_>,
merchant_account: &domain::MerchantAccount,
merchant_connector_account: domain::MerchantConnectorAccount,
merchant_id: &str,
connector_webhook_details: Option<common_utils::pii::SecretSerdeValue>,
_connector_account_details: crypto::Encryptable<Secret<serde_json::Value>>,
connector_label: &str,
) -> CustomResult<bool, errors::ConnectorError> {
let connector_webhook_secrets = self
.get_webhook_source_verification_merchant_secret(
merchant_account,
merchant_id,
connector_label,
merchant_connector_account,
connector_webhook_details,
)
.await
.change_context(errors::ConnectorError::WebhookSourceVerificationFailed)?;
@ -1397,7 +1397,7 @@ impl api::IncomingWebhook for Braintree {
let message = self
.get_webhook_source_verification_message(
request,
&merchant_account.merchant_id,
merchant_id,
&connector_webhook_secrets,
)
.change_context(errors::ConnectorError::WebhookSourceVerificationFailed)?;

View File

@ -24,7 +24,7 @@ use crate::{
types::{
self,
api::{self, ConnectorCommon, ConnectorCommonExt},
domain, storage, ErrorResponse, Response,
storage, ErrorResponse, Response,
},
utils::{ByteSliceExt, BytesExt},
};
@ -383,15 +383,16 @@ impl api::IncomingWebhook for Cashtocode {
async fn verify_webhook_source(
&self,
request: &api::IncomingWebhookRequestDetails<'_>,
merchant_account: &domain::MerchantAccount,
merchant_connector_account: domain::MerchantConnectorAccount,
merchant_id: &str,
connector_webhook_details: Option<common_utils::pii::SecretSerdeValue>,
_connector_account_details: common_utils::crypto::Encryptable<Secret<serde_json::Value>>,
connector_label: &str,
) -> CustomResult<bool, errors::ConnectorError> {
let connector_webhook_secrets = self
.get_webhook_source_verification_merchant_secret(
merchant_account,
merchant_id,
connector_label,
merchant_connector_account,
connector_webhook_details,
)
.await
.change_context(errors::ConnectorError::WebhookSourceVerificationFailed)?;

View File

@ -5,6 +5,7 @@ use std::fmt::Debug;
use common_utils::{ext_traits::ByteSliceExt, request::RequestContent};
use error_stack::ResultExt;
use hyperswitch_interfaces::authentication::ExternalAuthenticationPayload;
use transformers as netcetera;
use crate::{
@ -203,12 +204,12 @@ impl api::IncomingWebhook for Netcetera {
fn get_external_authentication_details(
&self,
request: &api::IncomingWebhookRequestDetails<'_>,
) -> CustomResult<api::ExternalAuthenticationPayload, errors::ConnectorError> {
) -> CustomResult<ExternalAuthenticationPayload, errors::ConnectorError> {
let webhook_body: netcetera::ResultsResponseData = request
.body
.parse_struct("netcetera ResultsResponseData")
.change_context(errors::ConnectorError::WebhookBodyDecodingFailed)?;
Ok(api::ExternalAuthenticationPayload {
Ok(ExternalAuthenticationPayload {
trans_status: webhook_body
.trans_status
.unwrap_or(common_enums::TransactionStatus::InformationOnly),

View File

@ -11,7 +11,7 @@ use common_utils::{
};
use diesel_models::enums;
use error_stack::{Report, ResultExt};
use masking::ExposeInterface;
use masking::{ExposeInterface, Secret};
use transformers as payme;
use crate::{
@ -1160,8 +1160,9 @@ impl api::IncomingWebhook for Payme {
async fn verify_webhook_source(
&self,
request: &api::IncomingWebhookRequestDetails<'_>,
merchant_account: &domain::MerchantAccount,
merchant_connector_account: domain::MerchantConnectorAccount,
merchant_id: &str,
connector_webhook_details: Option<common_utils::pii::SecretSerdeValue>,
_connector_account_details: crypto::Encryptable<Secret<serde_json::Value>>,
connector_label: &str,
) -> CustomResult<bool, errors::ConnectorError> {
let algorithm = self
@ -1170,9 +1171,9 @@ impl api::IncomingWebhook for Payme {
let connector_webhook_secrets = self
.get_webhook_source_verification_merchant_secret(
merchant_account,
merchant_id,
connector_label,
merchant_connector_account,
connector_webhook_details,
)
.await
.change_context(errors::ConnectorError::WebhookSourceVerificationFailed)?;
@ -1184,7 +1185,7 @@ impl api::IncomingWebhook for Payme {
let mut message = self
.get_webhook_source_verification_message(
request,
&merchant_account.merchant_id,
merchant_id,
&connector_webhook_secrets,
)
.change_context(errors::ConnectorError::WebhookSourceVerificationFailed)?;

View File

@ -9,7 +9,7 @@ use common_utils::{
};
use diesel_models::enums;
use error_stack::{Report, ResultExt};
use masking::{ExposeInterface, PeekInterface};
use masking::{ExposeInterface, PeekInterface, Secret};
use rand::distributions::{Alphanumeric, DistString};
use ring::hmac;
use transformers as rapyd;
@ -29,7 +29,7 @@ use crate::{
types::{
self,
api::{self, ConnectorCommon},
domain, ErrorResponse,
ErrorResponse,
},
utils::{self, crypto, ByteSliceExt, BytesExt},
};
@ -819,15 +819,16 @@ impl api::IncomingWebhook for Rapyd {
async fn verify_webhook_source(
&self,
request: &api::IncomingWebhookRequestDetails<'_>,
merchant_account: &domain::MerchantAccount,
merchant_connector_account: domain::MerchantConnectorAccount,
merchant_id: &str,
connector_webhook_details: Option<common_utils::pii::SecretSerdeValue>,
_connector_account_details: crypto::Encryptable<Secret<serde_json::Value>>,
connector_label: &str,
) -> CustomResult<bool, errors::ConnectorError> {
let connector_webhook_secrets = self
.get_webhook_source_verification_merchant_secret(
merchant_account,
merchant_id,
connector_label,
merchant_connector_account,
connector_webhook_details,
)
.await
.change_context(errors::ConnectorError::WebhookSourceVerificationFailed)?;
@ -837,7 +838,7 @@ impl api::IncomingWebhook for Rapyd {
let message = self
.get_webhook_source_verification_message(
request,
&merchant_account.merchant_id,
merchant_id,
&connector_webhook_secrets,
)
.change_context(errors::ConnectorError::WebhookSourceVerificationFailed)?;

View File

@ -7,7 +7,6 @@ use common_utils::{
use error_stack::{Report, ResultExt};
use masking::ExposeInterface;
use transformers as razorpay;
use types::domain;
use super::utils::{self as connector_utils};
use crate::{
@ -644,8 +643,11 @@ impl api::IncomingWebhook for Razorpay {
async fn verify_webhook_source(
&self,
_request: &api::IncomingWebhookRequestDetails<'_>,
_merchant_account: &domain::MerchantAccount,
_merchant_connector_account: domain::MerchantConnectorAccount,
_merchant_id: &str,
_connector_webhook_details: Option<common_utils::pii::SecretSerdeValue>,
_connector_account_details: common_utils::crypto::Encryptable<
masking::Secret<serde_json::Value>,
>,
_connector_label: &str,
) -> CustomResult<bool, errors::ConnectorError> {
Ok(false)

View File

@ -8,7 +8,7 @@ use common_utils::{crypto, ext_traits::ByteSliceExt, request::RequestContent};
#[cfg(feature = "frm")]
use error_stack::ResultExt;
#[cfg(feature = "frm")]
use masking::{ExposeInterface, PeekInterface};
use masking::{ExposeInterface, PeekInterface, Secret};
#[cfg(feature = "frm")]
use ring::hmac;
#[cfg(feature = "frm")]
@ -31,9 +31,7 @@ use crate::{
events::connector_api_logs::ConnectorEvent,
headers,
services::request,
types::{
api::fraud_check as frm_api, domain, fraud_check as frm_types, ErrorResponse, Response,
},
types::{api::fraud_check as frm_api, fraud_check as frm_types, ErrorResponse, Response},
utils::BytesExt,
};
@ -572,15 +570,16 @@ impl api::IncomingWebhook for Riskified {
async fn verify_webhook_source(
&self,
request: &api::IncomingWebhookRequestDetails<'_>,
merchant_account: &domain::MerchantAccount,
merchant_connector_account: domain::MerchantConnectorAccount,
merchant_id: &str,
connector_webhook_details: Option<common_utils::pii::SecretSerdeValue>,
_connector_account_details: crypto::Encryptable<Secret<serde_json::Value>>,
connector_label: &str,
) -> CustomResult<bool, errors::ConnectorError> {
let connector_webhook_secrets = self
.get_webhook_source_verification_merchant_secret(
merchant_account,
merchant_id,
connector_label,
merchant_connector_account,
connector_webhook_details,
)
.await
.change_context(errors::ConnectorError::WebhookSourceVerificationFailed)?;
@ -592,7 +591,7 @@ impl api::IncomingWebhook for Riskified {
let message = self
.get_webhook_source_verification_message(
request,
&merchant_account.merchant_id,
merchant_id,
&connector_webhook_secrets,
)
.change_context(errors::ConnectorError::WebhookSourceVerificationFailed)?;

View File

@ -8,7 +8,7 @@ use common_utils::{crypto, ext_traits::ByteSliceExt, request::RequestContent};
#[cfg(feature = "frm")]
use error_stack::ResultExt;
#[cfg(feature = "frm")]
use masking::PeekInterface;
use masking::{PeekInterface, Secret};
#[cfg(feature = "frm")]
use ring::hmac;
#[cfg(feature = "frm")]
@ -30,9 +30,7 @@ use crate::{
use crate::{
consts,
events::connector_api_logs::ConnectorEvent,
types::{
api::fraud_check as frm_api, domain, fraud_check as frm_types, ErrorResponse, Response,
},
types::{api::fraud_check as frm_api, fraud_check as frm_types, ErrorResponse, Response},
utils::BytesExt,
};
@ -689,15 +687,16 @@ impl api::IncomingWebhook for Signifyd {
async fn verify_webhook_source(
&self,
request: &api::IncomingWebhookRequestDetails<'_>,
merchant_account: &domain::MerchantAccount,
merchant_connector_account: domain::MerchantConnectorAccount,
merchant_id: &str,
connector_webhook_details: Option<common_utils::pii::SecretSerdeValue>,
_connector_account_details: crypto::Encryptable<Secret<serde_json::Value>>,
connector_label: &str,
) -> CustomResult<bool, errors::ConnectorError> {
let connector_webhook_secrets = self
.get_webhook_source_verification_merchant_secret(
merchant_account,
merchant_id,
connector_label,
merchant_connector_account,
connector_webhook_details,
)
.await
.change_context(errors::ConnectorError::WebhookSourceVerificationFailed)?;
@ -709,7 +708,7 @@ impl api::IncomingWebhook for Signifyd {
let message = self
.get_webhook_source_verification_message(
request,
&merchant_account.merchant_id,
merchant_id,
&connector_webhook_secrets,
)
.change_context(errors::ConnectorError::WebhookSourceVerificationFailed)?;

View File

@ -5,7 +5,7 @@ use std::fmt::Debug;
use common_utils::{ext_traits::ByteSliceExt, request::RequestContent};
use diesel_models::enums;
use error_stack::ResultExt;
use masking::PeekInterface;
use masking::{PeekInterface, Secret};
use transformers as stax;
use self::stax::StaxWebhookEventType;
@ -24,7 +24,7 @@ use crate::{
types::{
self,
api::{self, ConnectorCommon, ConnectorCommonExt},
domain, ErrorResponse, Response,
ErrorResponse, Response,
},
utils::BytesExt,
};
@ -852,8 +852,9 @@ impl api::IncomingWebhook for Stax {
async fn verify_webhook_source(
&self,
_request: &api::IncomingWebhookRequestDetails<'_>,
_merchant_account: &domain::MerchantAccount,
_merchant_connector_account: domain::MerchantConnectorAccount,
_merchant_id: &str,
_connector_webhook_details: Option<common_utils::pii::SecretSerdeValue>,
_connector_account_details: common_utils::crypto::Encryptable<Secret<serde_json::Value>>,
_connector_label: &str,
) -> CustomResult<bool, errors::ConnectorError> {
Ok(false)

View File

@ -4,7 +4,7 @@ use std::fmt::Debug;
use common_utils::{crypto, ext_traits::ByteSliceExt, request::RequestContent};
use error_stack::ResultExt;
use masking::PeekInterface;
use masking::{PeekInterface, Secret};
use transformers as zen;
use uuid::Uuid;
@ -608,16 +608,17 @@ impl api::IncomingWebhook for Zen {
async fn verify_webhook_source(
&self,
request: &api::IncomingWebhookRequestDetails<'_>,
merchant_account: &domain::MerchantAccount,
merchant_connector_account: domain::MerchantConnectorAccount,
merchant_id: &str,
connector_webhook_details: Option<common_utils::pii::SecretSerdeValue>,
_connector_account_details: crypto::Encryptable<Secret<serde_json::Value>>,
connector_label: &str,
) -> CustomResult<bool, errors::ConnectorError> {
let algorithm = self.get_webhook_source_verification_algorithm(request)?;
let connector_webhook_secrets = self
.get_webhook_source_verification_merchant_secret(
merchant_account,
merchant_id,
connector_label,
merchant_connector_account,
connector_webhook_details,
)
.await?;
let signature =
@ -625,7 +626,7 @@ impl api::IncomingWebhook for Zen {
let mut message = self.get_webhook_source_verification_message(
request,
&merchant_account.merchant_id,
merchant_id,
&connector_webhook_secrets,
)?;
let mut secret = connector_webhook_secrets.secret;

View File

@ -5,7 +5,7 @@ use std::fmt::Debug;
use common_utils::ext_traits::ValueExt;
use diesel_models::enums;
use error_stack::ResultExt;
use masking::ExposeInterface;
use masking::{ExposeInterface, Secret};
use transformers as zsl;
use crate::{
@ -418,12 +418,12 @@ impl api::IncomingWebhook for Zsl {
async fn verify_webhook_source(
&self,
request: &api::IncomingWebhookRequestDetails<'_>,
_merchant_account: &types::domain::MerchantAccount,
merchant_connector_account: types::domain::MerchantConnectorAccount,
_merchant_id: &str,
_connector_webhook_details: Option<common_utils::pii::SecretSerdeValue>,
connector_account_details: common_utils::crypto::Encryptable<Secret<serde_json::Value>>,
_connector_label: &str,
) -> CustomResult<bool, errors::ConnectorError> {
let connector_account_details = merchant_connector_account
.connector_account_details
let connector_account_details = connector_account_details
.parse_value::<types::ConnectorAuthType>("ConnectorAuthType")
.change_context_lazy(|| errors::ConnectorError::WebhookSourceVerificationFailed)?;
let auth_type = zsl::ZslAuthType::try_from(&connector_account_details)?;

View File

@ -9,6 +9,11 @@ use api_models::{
};
use common_utils::{errors::ReportSwitchExt, events::ApiEventsType};
use error_stack::{report, ResultExt};
use hyperswitch_domain_models::{
router_request_types::VerifyWebhookSourceRequestData,
router_response_types::{VerifyWebhookSourceResponseData, VerifyWebhookStatus},
};
use hyperswitch_interfaces::webhooks::IncomingWebhookRequestDetails;
use masking::ExposeInterface;
use router_env::{instrument, metrics::add_attributes, tracing, tracing_actix_web::RequestId};
@ -19,6 +24,7 @@ use crate::{
api_locking,
errors::{self, ConnectorErrorExt, CustomResult, RouterResponse, StorageErrorExt},
metrics, payments, refunds, utils as core_utils,
webhooks::utils::construct_webhook_router_data,
},
db::StorageInterface,
events::api_logs::ApiEvent,
@ -32,7 +38,10 @@ use crate::{
ConnectorValidation,
},
types::{
api::{self, mandates::MandateResponseExt, ConnectorCommon, IncomingWebhook},
api::{
self, mandates::MandateResponseExt, ConnectorCommon, ConnectorData, GetToken,
IncomingWebhook,
},
domain,
storage::{self, enums},
transformers::{ForeignFrom, ForeignInto, ForeignTryFrom},
@ -129,7 +138,7 @@ async fn incoming_webhooks_core<W: types::OutgoingWebhookType>(
merchant_account.merchant_id.clone(),
)],
);
let mut request_details = api::IncomingWebhookRequestDetails {
let mut request_details = IncomingWebhookRequestDetails {
method: req.method().clone(),
uri: req.uri().clone(),
headers: req.headers(),
@ -150,9 +159,14 @@ async fn incoming_webhooks_core<W: types::OutgoingWebhookType>(
let decoded_body = connector
.decode_webhook_body(
&*state.clone().store,
&request_details,
&merchant_account.merchant_id,
merchant_connector_account
.clone()
.and_then(|merchant_connector_account| {
merchant_connector_account.connector_webhook_details
}),
connector_name.as_str(),
)
.await
.switch()
@ -258,30 +272,32 @@ async fn incoming_webhooks_core<W: types::OutgoingWebhookType>(
.connectors_with_webhook_source_verification_call
.contains(&connector_enum)
{
connector
.verify_webhook_source_verification_call(
&state,
&merchant_account,
merchant_connector_account.clone(),
&connector_name,
&request_details,
)
.await
.or_else(|error| match error.current_context() {
errors::ConnectorError::WebhookSourceVerificationFailed => {
logger::error!(?error, "Source Verification Failed");
Ok(false)
}
_ => Err(error),
})
.switch()
.attach_printable("There was an issue in incoming webhook source verification")?
verify_webhook_source_verification_call(
connector.clone(),
&state,
&merchant_account,
merchant_connector_account.clone(),
&connector_name,
&request_details,
)
.await
.or_else(|error| match error.current_context() {
errors::ConnectorError::WebhookSourceVerificationFailed => {
logger::error!(?error, "Source Verification Failed");
Ok(false)
}
_ => Err(error),
})
.switch()
.attach_printable("There was an issue in incoming webhook source verification")?
} else {
connector
.clone()
.verify_webhook_source(
&request_details,
&merchant_account,
merchant_connector_account.clone(),
&merchant_account.merchant_id,
merchant_connector_account.connector_webhook_details.clone(),
merchant_connector_account.connector_account_details.clone(),
connector_name.as_str(),
)
.await
@ -970,7 +986,7 @@ async fn external_authentication_incoming_webhook_flow(
key_store: domain::MerchantKeyStore,
source_verified: bool,
event_type: webhooks::IncomingWebhookEvent,
request_details: &api::IncomingWebhookRequestDetails<'_>,
request_details: &IncomingWebhookRequestDetails<'_>,
connector: &ConnectorEnum,
object_ref_id: api::ObjectReferenceId,
business_profile: diesel_models::business_profile::BusinessProfile,
@ -1346,7 +1362,7 @@ async fn disputes_incoming_webhook_flow(
webhook_details: api::IncomingWebhookDetails,
source_verified: bool,
connector: &ConnectorEnum,
request_details: &api::IncomingWebhookRequestDetails<'_>,
request_details: &IncomingWebhookRequestDetails<'_>,
event_type: webhooks::IncomingWebhookEvent,
) -> CustomResult<WebhookResponseTracker, errors::ApiErrorResponse> {
metrics::INCOMING_DISPUTE_WEBHOOK_METRIC.add(&metrics::CONTEXT, 1, &[]);
@ -1534,6 +1550,66 @@ async fn get_payment_id(
.to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)
}
#[inline]
async fn verify_webhook_source_verification_call(
connector: ConnectorEnum,
state: &SessionState,
merchant_account: &domain::MerchantAccount,
merchant_connector_account: domain::MerchantConnectorAccount,
connector_name: &str,
request_details: &IncomingWebhookRequestDetails<'_>,
) -> CustomResult<bool, errors::ConnectorError> {
let connector_data = ConnectorData::get_connector_by_name(
&state.conf.connectors,
connector_name,
GetToken::Connector,
None,
)
.change_context(errors::ConnectorError::WebhookSourceVerificationFailed)
.attach_printable("invalid connector name received in payment attempt")?;
let connector_integration: services::BoxedWebhookSourceVerificationConnectorIntegrationInterface<
hyperswitch_domain_models::router_flow_types::VerifyWebhookSource,
VerifyWebhookSourceRequestData,
VerifyWebhookSourceResponseData,
> = connector_data.connector.get_connector_integration();
let connector_webhook_secrets = connector
.get_webhook_source_verification_merchant_secret(
&merchant_account.merchant_id,
connector_name,
merchant_connector_account.connector_webhook_details.clone(),
)
.await
.change_context(errors::ConnectorError::WebhookSourceVerificationFailed)?;
let router_data = construct_webhook_router_data(
connector_name,
merchant_connector_account,
merchant_account,
&connector_webhook_secrets,
request_details,
)
.await
.change_context(errors::ConnectorError::WebhookSourceVerificationFailed)
.attach_printable("Failed while constructing webhook router data")?;
let response = services::execute_connector_processing_step(
state,
connector_integration,
&router_data,
payments::CallConnectorAction::Trigger,
None,
)
.await?;
let verification_result = response
.response
.map(|response| response.verify_webhook_status);
match verification_result {
Ok(VerifyWebhookStatus::SourceVerified) => Ok(true),
_ => Ok(false),
}
}
fn get_connector_by_connector_name(
state: &SessionState,
connector_name: &str,
@ -1562,10 +1638,10 @@ fn get_connector_by_connector_name(
authentication_connector_data.connector_name.to_string(),
)
} else {
let connector_data = api::ConnectorData::get_connector_by_name(
let connector_data = ConnectorData::get_connector_by_name(
&state.conf.connectors,
connector_name,
api::GetToken::Connector,
GetToken::Connector,
merchant_connector_id,
)
.change_context(errors::ApiErrorResponse::InvalidRequestData {

View File

@ -15,10 +15,7 @@ use crate::routes::dummy_connector::types::{
};
use crate::{
core::payments::PaymentsRedirectResponseData,
services::{
authentication::AuthenticationType, kafka::KafkaMessage, ApplicationResponse,
GenericLinkFormData, PaymentLinkFormData,
},
services::{authentication::AuthenticationType, kafka::KafkaMessage},
types::api::{
AttachEvidenceRequest, Config, ConfigUpdate, CreateFileRequest, DisputeId, FileId, PollId,
},
@ -101,22 +98,11 @@ impl KafkaMessage for ApiEvent {
}
}
impl<T: ApiEventMetric> ApiEventMetric for ApplicationResponse<T> {
fn get_api_event_type(&self) -> Option<ApiEventsType> {
match self {
Self::Json(r) => r.get_api_event_type(),
Self::JsonWithHeaders((r, _)) => r.get_api_event_type(),
_ => None,
}
}
}
impl_misc_api_event_type!(
Config,
CreateFileRequest,
FileId,
AttachEvidenceRequest,
PaymentLinkFormData,
GenericLinkFormData,
ConfigUpdate
);

View File

@ -4,7 +4,7 @@ pub mod request;
use std::{
collections::{HashMap, HashSet},
error::Error,
fmt::{Debug, Display},
fmt::Debug,
future::Future,
str,
sync::Arc,
@ -26,7 +26,14 @@ use common_utils::{
};
use error_stack::{report, Report, ResultExt};
use hyperswitch_domain_models::router_data_v2::flow_common_types as common_types;
pub use hyperswitch_domain_models::router_response_types::RedirectForm;
pub use hyperswitch_domain_models::{
api::{
ApplicationResponse, GenericExpiredLinkData, GenericLinkFormData, GenericLinkStatusData,
GenericLinks, PaymentLinkAction, PaymentLinkFormData, PaymentLinkStatusData,
RedirectionFormData,
},
router_response_types::RedirectForm,
};
pub use hyperswitch_interfaces::{
api::{
BoxedConnectorIntegration, CaptureSyncMethod, ConnectorIntegration, ConnectorIntegrationAny,
@ -713,93 +720,6 @@ async fn handle_response(
.await
}
#[derive(Debug, Eq, PartialEq)]
pub enum ApplicationResponse<R> {
Json(R),
StatusOk,
TextPlain(String),
JsonForRedirection(api::RedirectionResponse),
Form(Box<RedirectionFormData>),
PaymentLinkForm(Box<PaymentLinkAction>),
FileData((Vec<u8>, mime::Mime)),
JsonWithHeaders((R, Vec<(String, Maskable<String>)>)),
GenericLinkForm(Box<GenericLinks>),
}
#[derive(Debug, Eq, PartialEq)]
pub enum GenericLinks {
ExpiredLink(GenericExpiredLinkData),
PaymentMethodCollect(GenericLinkFormData),
PayoutLink(GenericLinkFormData),
PayoutLinkStatus(GenericLinkStatusData),
PaymentMethodCollectStatus(GenericLinkStatusData),
}
impl Display for Box<GenericLinks> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
match **self {
GenericLinks::ExpiredLink(_) => "ExpiredLink",
GenericLinks::PaymentMethodCollect(_) => "PaymentMethodCollect",
GenericLinks::PayoutLink(_) => "PayoutLink",
GenericLinks::PayoutLinkStatus(_) => "PayoutLinkStatus",
GenericLinks::PaymentMethodCollectStatus(_) => "PaymentMethodCollectStatus",
}
)
}
}
#[derive(Debug, Eq, PartialEq, Clone, serde::Serialize, serde::Deserialize)]
pub struct GenericLinkFormData {
pub js_data: String,
pub css_data: String,
pub sdk_url: String,
pub html_meta_tags: String,
}
#[derive(Debug, Eq, PartialEq, Clone, serde::Serialize, serde::Deserialize)]
pub struct GenericExpiredLinkData {
pub title: String,
pub message: String,
pub theme: String,
}
#[derive(Debug, Eq, PartialEq, Clone, serde::Serialize, serde::Deserialize)]
pub struct GenericLinkStatusData {
pub js_data: String,
pub css_data: String,
}
#[derive(Debug, Eq, PartialEq)]
pub enum PaymentLinkAction {
PaymentLinkFormData(PaymentLinkFormData),
PaymentLinkStatus(PaymentLinkStatusData),
}
#[derive(Debug, Eq, PartialEq, Clone, serde::Serialize, serde::Deserialize)]
pub struct PaymentLinkFormData {
pub js_script: String,
pub css_script: String,
pub sdk_url: String,
pub html_meta_tags: String,
}
#[derive(Debug, Eq, PartialEq, Clone, serde::Serialize, serde::Deserialize)]
pub struct PaymentLinkStatusData {
pub js_script: String,
pub css_script: String,
}
#[derive(Debug, Eq, PartialEq)]
pub struct RedirectionFormData {
pub redirect_form: RedirectForm,
pub payment_method_data: Option<api::PaymentMethodData>,
pub amount: String,
pub currency: String,
}
#[derive(Debug, Eq, PartialEq)]
pub enum PaymentAction {
PSync,

View File

@ -1,13 +1,14 @@
use common_utils::{crypto, errors::CustomResult, request::Request};
use hyperswitch_domain_models::{router_data::RouterData, router_data_v2::RouterDataV2};
use hyperswitch_interfaces::connector_integration_v2::ConnectorIntegrationV2;
use hyperswitch_interfaces::{
authentication::ExternalAuthenticationPayload, connector_integration_v2::ConnectorIntegrationV2,
};
use super::{BoxedConnectorIntegrationV2, ConnectorValidation};
use crate::{
core::payments,
errors,
events::connector_api_logs::ConnectorEvent,
routes::app::StorageInterface,
services::{
api as services_api, BoxedConnectorIntegration, CaptureSyncMethod, ConnectorIntegration,
ConnectorRedirectResponse, PaymentAction,
@ -16,8 +17,8 @@ use crate::{
types::{
self,
api::{
self, disputes, Connector, ConnectorV2, CurrencyUnit, ExternalAuthenticationPayload,
IncomingWebhookEvent, IncomingWebhookRequestDetails, ObjectReferenceId,
self, disputes, Connector, ConnectorV2, CurrencyUnit, IncomingWebhookEvent,
IncomingWebhookRequestDetails, ObjectReferenceId,
},
domain,
},
@ -99,25 +100,6 @@ impl api::IncomingWebhook for ConnectorEnum {
}
}
async fn get_webhook_body_decoding_merchant_secret(
&self,
db: &dyn StorageInterface,
merchant_id: &str,
) -> CustomResult<Vec<u8>, errors::ConnectorError> {
match self {
Self::Old(connector) => {
connector
.get_webhook_body_decoding_merchant_secret(db, merchant_id)
.await
}
Self::New(connector) => {
connector
.get_webhook_body_decoding_merchant_secret(db, merchant_id)
.await
}
}
}
fn get_webhook_body_decoding_message(
&self,
request: &IncomingWebhookRequestDetails<'_>,
@ -130,19 +112,30 @@ impl api::IncomingWebhook for ConnectorEnum {
async fn decode_webhook_body(
&self,
db: &dyn StorageInterface,
request: &IncomingWebhookRequestDetails<'_>,
merchant_id: &str,
connector_webhook_details: Option<common_utils::pii::SecretSerdeValue>,
connector_name: &str,
) -> CustomResult<Vec<u8>, errors::ConnectorError> {
match self {
Self::Old(connector) => {
connector
.decode_webhook_body(db, request, merchant_id)
.decode_webhook_body(
request,
merchant_id,
connector_webhook_details,
connector_name,
)
.await
}
Self::New(connector) => {
connector
.decode_webhook_body(db, request, merchant_id)
.decode_webhook_body(
request,
merchant_id,
connector_webhook_details,
connector_name,
)
.await
}
}
@ -160,26 +153,26 @@ impl api::IncomingWebhook for ConnectorEnum {
async fn get_webhook_source_verification_merchant_secret(
&self,
merchant_account: &domain::MerchantAccount,
merchant_id: &str,
connector_name: &str,
merchant_connector_account: domain::MerchantConnectorAccount,
connector_webhook_details: Option<common_utils::pii::SecretSerdeValue>,
) -> CustomResult<api_models::webhooks::ConnectorWebhookSecrets, errors::ConnectorError> {
match self {
Self::Old(connector) => {
connector
.get_webhook_source_verification_merchant_secret(
merchant_account,
merchant_id,
connector_name,
merchant_connector_account,
connector_webhook_details,
)
.await
}
Self::New(connector) => {
connector
.get_webhook_source_verification_merchant_secret(
merchant_account,
merchant_id,
connector_name,
merchant_connector_account,
connector_webhook_details,
)
.await
}
@ -219,45 +212,12 @@ impl api::IncomingWebhook for ConnectorEnum {
}
}
async fn verify_webhook_source_verification_call(
&self,
state: &crate::routes::SessionState,
merchant_account: &domain::MerchantAccount,
merchant_connector_account: domain::MerchantConnectorAccount,
connector_name: &str,
request_details: &IncomingWebhookRequestDetails<'_>,
) -> CustomResult<bool, errors::ConnectorError> {
match self {
Self::Old(connector) => {
connector
.verify_webhook_source_verification_call(
state,
merchant_account,
merchant_connector_account,
connector_name,
request_details,
)
.await
}
Self::New(connector) => {
connector
.verify_webhook_source_verification_call(
state,
merchant_account,
merchant_connector_account,
connector_name,
request_details,
)
.await
}
}
}
async fn verify_webhook_source(
&self,
request: &IncomingWebhookRequestDetails<'_>,
merchant_account: &domain::MerchantAccount,
merchant_connector_account: domain::MerchantConnectorAccount,
merchant_id: &str,
connector_webhook_details: Option<common_utils::pii::SecretSerdeValue>,
connector_account_details: crypto::Encryptable<masking::Secret<serde_json::Value>>,
connector_name: &str,
) -> CustomResult<bool, errors::ConnectorError> {
match self {
@ -265,8 +225,9 @@ impl api::IncomingWebhook for ConnectorEnum {
connector
.verify_webhook_source(
request,
merchant_account,
merchant_connector_account,
merchant_id,
connector_webhook_details,
connector_account_details,
connector_name,
)
.await
@ -275,8 +236,9 @@ impl api::IncomingWebhook for ConnectorEnum {
connector
.verify_webhook_source(
request,
merchant_account,
merchant_connector_account,
merchant_id,
connector_webhook_details,
connector_account_details,
connector_name,
)
.await

View File

@ -74,13 +74,6 @@ pub struct PostAuthenticationResponse {
pub eci: Option<String>,
}
#[derive(Clone, serde::Deserialize, Debug, serde::Serialize, PartialEq, Eq)]
pub struct ExternalAuthenticationPayload {
pub trans_status: common_enums::TransactionStatus,
pub authentication_value: Option<String>,
pub eci: Option<String>,
}
pub trait ConnectorAuthentication:
services::ConnectorIntegration<
Authentication,

View File

@ -1,5 +1,5 @@
pub use hyperswitch_interfaces::disputes::DisputePayload;
use masking::{Deserialize, Serialize};
use time::PrimitiveDateTime;
use crate::{services, types};
@ -12,20 +12,6 @@ pub use hyperswitch_domain_models::router_flow_types::dispute::{Accept, Defend,
pub use super::disputes_v2::{AcceptDisputeV2, DefendDisputeV2, DisputeV2, SubmitEvidenceV2};
#[derive(Default, Debug)]
pub struct DisputePayload {
pub amount: String,
pub currency: String,
pub dispute_stage: api_models::enums::DisputeStage,
pub connector_status: String,
pub connector_dispute_id: String,
pub connector_reason: Option<String>,
pub connector_reason_code: Option<String>,
pub challenge_required_by: Option<PrimitiveDateTime>,
pub created_at: Option<PrimitiveDateTime>,
pub updated_at: Option<PrimitiveDateTime>,
}
#[derive(Default, Debug, Deserialize, Serialize)]
pub struct DisputeEvidence {
pub cancellation_policy: Option<String>,

View File

@ -1,282 +1,5 @@
use api_models::admin::MerchantConnectorWebhookDetails;
pub use api_models::webhooks::{
AuthenticationIdType, IncomingWebhookDetails, IncomingWebhookEvent, MerchantWebhookConfig,
ObjectReferenceId, OutgoingWebhook, OutgoingWebhookContent, WebhookFlow,
};
use common_utils::ext_traits::ValueExt;
use error_stack::ResultExt;
use masking::ExposeInterface;
use super::ConnectorCommon;
use crate::{
core::{
errors::{self, CustomResult},
payments,
webhooks::utils::construct_webhook_router_data,
},
db::StorageInterface,
services::{self},
types::{self, domain},
utils::crypto,
};
pub struct IncomingWebhookRequestDetails<'a> {
pub method: actix_web::http::Method,
pub uri: actix_web::http::Uri,
pub headers: &'a actix_web::http::header::HeaderMap,
pub body: &'a [u8],
pub query_params: String,
}
#[async_trait::async_trait]
pub trait IncomingWebhook: ConnectorCommon + Sync {
fn get_webhook_body_decoding_algorithm(
&self,
_request: &IncomingWebhookRequestDetails<'_>,
) -> CustomResult<Box<dyn crypto::DecodeMessage + Send>, errors::ConnectorError> {
Ok(Box::new(crypto::NoAlgorithm))
}
async fn get_webhook_body_decoding_merchant_secret(
&self,
_db: &dyn StorageInterface,
_merchant_id: &str,
) -> CustomResult<Vec<u8>, errors::ConnectorError> {
Ok(Vec::new())
}
fn get_webhook_body_decoding_message(
&self,
request: &IncomingWebhookRequestDetails<'_>,
) -> CustomResult<Vec<u8>, errors::ConnectorError> {
Ok(request.body.to_vec())
}
async fn decode_webhook_body(
&self,
db: &dyn StorageInterface,
request: &IncomingWebhookRequestDetails<'_>,
merchant_id: &str,
) -> CustomResult<Vec<u8>, errors::ConnectorError> {
let algorithm = self.get_webhook_body_decoding_algorithm(request)?;
let message = self
.get_webhook_body_decoding_message(request)
.change_context(errors::ConnectorError::WebhookBodyDecodingFailed)?;
let secret = self
.get_webhook_body_decoding_merchant_secret(db, merchant_id)
.await
.change_context(errors::ConnectorError::WebhookBodyDecodingFailed)?;
algorithm
.decode_message(&secret, message.into())
.change_context(errors::ConnectorError::WebhookBodyDecodingFailed)
}
fn get_webhook_source_verification_algorithm(
&self,
_request: &IncomingWebhookRequestDetails<'_>,
) -> CustomResult<Box<dyn crypto::VerifySignature + Send>, errors::ConnectorError> {
Ok(Box::new(crypto::NoAlgorithm))
}
async fn get_webhook_source_verification_merchant_secret(
&self,
merchant_account: &domain::MerchantAccount,
connector_name: &str,
merchant_connector_account: domain::MerchantConnectorAccount,
) -> CustomResult<api_models::webhooks::ConnectorWebhookSecrets, errors::ConnectorError> {
let merchant_id = merchant_account.merchant_id.as_str();
let debug_suffix = format!(
"For merchant_id: {}, and connector_name: {}",
merchant_id, connector_name
);
let default_secret = "default_secret".to_string();
let merchant_secret = match merchant_connector_account.connector_webhook_details {
Some(merchant_connector_webhook_details) => {
let connector_webhook_details = merchant_connector_webhook_details
.parse_value::<MerchantConnectorWebhookDetails>(
"MerchantConnectorWebhookDetails",
)
.change_context_lazy(|| errors::ConnectorError::WebhookSourceVerificationFailed)
.attach_printable_lazy(|| {
format!(
"Deserializing MerchantConnectorWebhookDetails failed {}",
debug_suffix
)
})?;
api_models::webhooks::ConnectorWebhookSecrets {
secret: connector_webhook_details
.merchant_secret
.expose()
.into_bytes(),
additional_secret: connector_webhook_details.additional_secret,
}
}
None => api_models::webhooks::ConnectorWebhookSecrets {
secret: default_secret.into_bytes(),
additional_secret: None,
},
};
//need to fetch merchant secret from config table with caching in future for enhanced performance
//If merchant has not set the secret for webhook source verification, "default_secret" is returned.
//So it will fail during verification step and goes to psync flow.
Ok(merchant_secret)
}
fn get_webhook_source_verification_signature(
&self,
_request: &IncomingWebhookRequestDetails<'_>,
_connector_webhook_secrets: &api_models::webhooks::ConnectorWebhookSecrets,
) -> CustomResult<Vec<u8>, errors::ConnectorError> {
Ok(Vec::new())
}
fn get_webhook_source_verification_message(
&self,
_request: &IncomingWebhookRequestDetails<'_>,
_merchant_id: &str,
_connector_webhook_secrets: &api_models::webhooks::ConnectorWebhookSecrets,
) -> CustomResult<Vec<u8>, errors::ConnectorError> {
Ok(Vec::new())
}
async fn verify_webhook_source_verification_call(
&self,
state: &crate::routes::SessionState,
merchant_account: &domain::MerchantAccount,
merchant_connector_account: domain::MerchantConnectorAccount,
connector_name: &str,
request_details: &IncomingWebhookRequestDetails<'_>,
) -> CustomResult<bool, errors::ConnectorError> {
let connector_data = types::api::ConnectorData::get_connector_by_name(
&state.conf.connectors,
connector_name,
types::api::GetToken::Connector,
None,
)
.change_context(errors::ConnectorError::WebhookSourceVerificationFailed)
.attach_printable("invalid connector name received in payment attempt")?;
let connector_integration: services::BoxedWebhookSourceVerificationConnectorIntegrationInterface<
types::api::VerifyWebhookSource,
types::VerifyWebhookSourceRequestData,
types::VerifyWebhookSourceResponseData,
> = connector_data.connector.get_connector_integration();
let connector_webhook_secrets = self
.get_webhook_source_verification_merchant_secret(
merchant_account,
connector_name,
merchant_connector_account.clone(),
)
.await
.change_context(errors::ConnectorError::WebhookSourceVerificationFailed)?;
let router_data = construct_webhook_router_data(
connector_name,
merchant_connector_account,
merchant_account,
&connector_webhook_secrets,
request_details,
)
.await
.change_context(errors::ConnectorError::WebhookSourceVerificationFailed)
.attach_printable("Failed while constructing webhook router data")?;
let response = services::execute_connector_processing_step(
state,
connector_integration,
&router_data,
payments::CallConnectorAction::Trigger,
None,
)
.await?;
let verification_result = response
.response
.map(|response| response.verify_webhook_status);
match verification_result {
Ok(types::VerifyWebhookStatus::SourceVerified) => Ok(true),
_ => Ok(false),
}
}
async fn verify_webhook_source(
&self,
request: &IncomingWebhookRequestDetails<'_>,
merchant_account: &domain::MerchantAccount,
merchant_connector_account: domain::MerchantConnectorAccount,
connector_name: &str,
) -> CustomResult<bool, errors::ConnectorError> {
let algorithm = self
.get_webhook_source_verification_algorithm(request)
.change_context(errors::ConnectorError::WebhookSourceVerificationFailed)?;
let connector_webhook_secrets = self
.get_webhook_source_verification_merchant_secret(
merchant_account,
connector_name,
merchant_connector_account,
)
.await
.change_context(errors::ConnectorError::WebhookSourceVerificationFailed)?;
let signature = self
.get_webhook_source_verification_signature(request, &connector_webhook_secrets)
.change_context(errors::ConnectorError::WebhookSourceVerificationFailed)?;
let message = self
.get_webhook_source_verification_message(
request,
&merchant_account.merchant_id,
&connector_webhook_secrets,
)
.change_context(errors::ConnectorError::WebhookSourceVerificationFailed)?;
algorithm
.verify_signature(&connector_webhook_secrets.secret, &signature, &message)
.change_context(errors::ConnectorError::WebhookSourceVerificationFailed)
}
fn get_webhook_object_reference_id(
&self,
_request: &IncomingWebhookRequestDetails<'_>,
) -> CustomResult<ObjectReferenceId, errors::ConnectorError>;
fn get_webhook_event_type(
&self,
_request: &IncomingWebhookRequestDetails<'_>,
) -> CustomResult<IncomingWebhookEvent, errors::ConnectorError>;
fn get_webhook_resource_object(
&self,
_request: &IncomingWebhookRequestDetails<'_>,
) -> CustomResult<Box<dyn masking::ErasedMaskSerialize>, errors::ConnectorError>;
fn get_webhook_api_response(
&self,
_request: &IncomingWebhookRequestDetails<'_>,
) -> CustomResult<services::api::ApplicationResponse<serde_json::Value>, errors::ConnectorError>
{
Ok(services::api::ApplicationResponse::StatusOk)
}
fn get_dispute_details(
&self,
_request: &IncomingWebhookRequestDetails<'_>,
) -> CustomResult<super::disputes::DisputePayload, errors::ConnectorError> {
Err(errors::ConnectorError::NotImplemented("get_dispute_details method".to_string()).into())
}
fn get_external_authentication_details(
&self,
_request: &IncomingWebhookRequestDetails<'_>,
) -> CustomResult<super::ExternalAuthenticationPayload, errors::ConnectorError> {
Err(errors::ConnectorError::NotImplemented(
"get_external_authentication_details method".to_string(),
)
.into())
}
}
pub use hyperswitch_interfaces::webhooks::{IncomingWebhook, IncomingWebhookRequestDetails};