diff --git a/crates/diesel_models/src/query/merchant_connector_account.rs b/crates/diesel_models/src/query/merchant_connector_account.rs index 1b921c59dc..4f11e3c13e 100644 --- a/crates/diesel_models/src/query/merchant_connector_account.rs +++ b/crates/diesel_models/src/query/merchant_connector_account.rs @@ -70,6 +70,29 @@ impl MerchantConnectorAccount { .await } + #[instrument(skip(conn))] + pub async fn find_by_merchant_id_connector_name( + conn: &PgPooledConn, + merchant_id: &str, + connector_name: &str, + ) -> StorageResult> { + generics::generic_filter::< + ::Table, + _, + <::Table as Table>::PrimaryKey, + _, + >( + conn, + dsl::merchant_id + .eq(merchant_id.to_owned()) + .and(dsl::connector_name.eq(connector_name.to_owned())), + None, + None, + None, + ) + .await + } + #[instrument(skip(conn))] pub async fn find_by_merchant_id_merchant_connector_id( conn: &PgPooledConn, diff --git a/crates/router/src/compatibility/stripe/app.rs b/crates/router/src/compatibility/stripe/app.rs index dd687deb70..5b4be4dde0 100644 --- a/crates/router/src/compatibility/stripe/app.rs +++ b/crates/router/src/compatibility/stripe/app.rs @@ -101,7 +101,7 @@ impl Webhooks { web::scope("/webhooks") .app_data(web::Data::new(config)) .service( - web::resource("/{merchant_id}/{connector_label}") + web::resource("/{merchant_id}/{connector_name}") .route( web::post().to(webhooks::receive_incoming_webhook::), ) diff --git a/crates/router/src/core/webhooks.rs b/crates/router/src/core/webhooks.rs index 6ab62632e0..af4e1a2be4 100644 --- a/crates/router/src/core/webhooks.rs +++ b/crates/router/src/core/webhooks.rs @@ -630,18 +630,9 @@ pub async fn webhooks_core( req: &actix_web::HttpRequest, merchant_account: domain::MerchantAccount, key_store: domain::MerchantKeyStore, - connector_label: &str, + connector_name: &str, body: actix_web::web::Bytes, ) -> RouterResponse { - let connector_name = connector_label - .split('_') //connector_name will be the first string after splitting connector_label - .next() - .ok_or(errors::ApiErrorResponse::InvalidDataValue { - field_name: "connector_label", - }) - .into_report() - .attach_printable("Failed to infer connector_name from connector_label")?; - metrics::WEBHOOK_INCOMING_COUNT.add( &metrics::CONTEXT, 1, @@ -688,7 +679,7 @@ pub async fn webhooks_core( let process_webhook_further = utils::lookup_webhook_event( &*state.store, - connector_label, + connector_name, &merchant_account.merchant_id, &event_type, ) @@ -704,7 +695,7 @@ pub async fn webhooks_core( &*state.store, &request_details, &merchant_account.merchant_id, - connector_label, + connector_name, &key_store, ) .await diff --git a/crates/router/src/db/merchant_connector_account.rs b/crates/router/src/db/merchant_connector_account.rs index 18358ae955..973b18de88 100644 --- a/crates/router/src/db/merchant_connector_account.rs +++ b/crates/router/src/db/merchant_connector_account.rs @@ -1,4 +1,7 @@ +use std::cmp::Ordering; + use common_utils::ext_traits::{AsyncExt, ByteSliceExt, Encode}; +use diesel_models::errors as storage_errors; use error_stack::{IntoReport, ResultExt}; use super::{MockDb, Store}; @@ -119,6 +122,13 @@ where key_store: &domain::MerchantKeyStore, ) -> CustomResult; + async fn find_merchant_connector_account_by_merchant_id_connector_name( + &self, + merchant_id: &str, + connector_name: &str, + key_store: &domain::MerchantKeyStore, + ) -> CustomResult; + async fn insert_merchant_connector_account( &self, t: domain::MerchantConnectorAccount, @@ -200,6 +210,53 @@ impl MerchantConnectorAccountInterface for Store { } } + async fn find_merchant_connector_account_by_merchant_id_connector_name( + &self, + merchant_id: &str, + connector_name: &str, + key_store: &domain::MerchantKeyStore, + ) -> CustomResult { + let find_call = || async { + let conn = connection::pg_connection_read(self).await?; + storage::MerchantConnectorAccount::find_by_merchant_id_connector_name( + &conn, + merchant_id, + connector_name, + ) + .await + .map_err(Into::into) + .into_report() + }; + let mca_list = find_call().await?; + match mca_list.len().cmp(&1) { + Ordering::Less => { + Err(errors::StorageError::ValueNotFound("MerchantConnectorAccount".into()).into()) + .attach_printable(format!( + "No records found for {} and {}", + merchant_id, connector_name + )) + } + Ordering::Greater => Err(errors::StorageError::DatabaseError( + storage_errors::DatabaseError::Others.into(), + )) + .into_report() + .attach_printable(format!( + "Found multiple records for {} and {}", + merchant_id, connector_name + )), + Ordering::Equal => match mca_list.first() { + Some(mca) => mca + .to_owned() + .convert(key_store.key.get_inner()) + .await + .change_context(errors::StorageError::DeserializationFailed), + None => Err( + errors::StorageError::ValueNotFound("MerchantConnectorAccount".into()).into(), + ), + }, + } + } + async fn find_by_merchant_connector_account_merchant_id_merchant_connector_id( &self, merchant_id: &str, @@ -377,7 +434,7 @@ impl MerchantConnectorAccountInterface for MockDb { .await .iter() .find(|account| { - account.merchant_id == merchant_id && account.connector_name == connector + account.merchant_id == merchant_id && account.connector_label == connector }) .cloned() .async_map(|account| async { @@ -398,6 +455,51 @@ impl MerchantConnectorAccountInterface for MockDb { } } + async fn find_merchant_connector_account_by_merchant_id_connector_name( + &self, + merchant_id: &str, + connector_name: &str, + key_store: &domain::MerchantKeyStore, + ) -> CustomResult { + let mca_list = self + .merchant_connector_accounts + .lock() + .await + .iter() + .filter(|account| { + account.merchant_id == merchant_id && account.connector_name == connector_name + }) + .cloned() + .collect::>(); + match mca_list.len().cmp(&1) { + Ordering::Less => { + Err(errors::StorageError::ValueNotFound("MerchantConnectorAccount".into()).into()) + .attach_printable(format!( + "No records found for {} and {}", + merchant_id, connector_name + )) + } + Ordering::Greater => Err(errors::StorageError::DatabaseError( + storage_errors::DatabaseError::Others.into(), + )) + .into_report() + .attach_printable(format!( + "Found multiple records for {} and {}", + merchant_id, connector_name + )), + Ordering::Equal => match mca_list.first() { + Some(mca) => mca + .to_owned() + .convert(key_store.key.get_inner()) + .await + .change_context(errors::StorageError::DeserializationFailed), + None => Err( + errors::StorageError::ValueNotFound("MerchantConnectorAccount".into()).into(), + ), + }, + } + } + async fn find_by_merchant_connector_account_merchant_id_merchant_connector_id( &self, merchant_id: &str, diff --git a/crates/router/src/routes/app.rs b/crates/router/src/routes/app.rs index 75d0ed2fba..5109d1159c 100644 --- a/crates/router/src/routes/app.rs +++ b/crates/router/src/routes/app.rs @@ -410,7 +410,7 @@ impl Webhooks { web::scope("/webhooks") .app_data(web::Data::new(config)) .service( - web::resource("/{merchant_id}/{connector_label}") + web::resource("/{merchant_id}/{connector_name}") .route( web::post().to(receive_incoming_webhook::), ) diff --git a/crates/router/src/routes/webhooks.rs b/crates/router/src/routes/webhooks.rs index 188fd0883b..6338347935 100644 --- a/crates/router/src/routes/webhooks.rs +++ b/crates/router/src/routes/webhooks.rs @@ -15,7 +15,7 @@ pub async fn receive_incoming_webhook( path: web::Path<(String, String)>, ) -> impl Responder { let flow = Flow::IncomingWebhookReceive; - let (merchant_id, connector_label) = path.into_inner(); + let (merchant_id, connector_name) = path.into_inner(); api::server_wrap( flow, @@ -28,7 +28,7 @@ pub async fn receive_incoming_webhook( &req, auth.merchant_account, auth.key_store, - &connector_label, + &connector_name, body, ) }, diff --git a/crates/router/src/types/api/webhooks.rs b/crates/router/src/types/api/webhooks.rs index 5fd5621ef9..a2d82d412c 100644 --- a/crates/router/src/types/api/webhooks.rs +++ b/crates/router/src/types/api/webhooks.rs @@ -11,7 +11,7 @@ use super::ConnectorCommon; use crate::{ core::errors::{self, CustomResult}, db::StorageInterface, - services, + logger, services, types::domain, utils::crypto, }; @@ -79,43 +79,49 @@ pub trait IncomingWebhook: ConnectorCommon + Sync { &self, db: &dyn StorageInterface, merchant_id: &str, - connector_label: &str, + connector_name: &str, key_store: &domain::MerchantKeyStore, ) -> CustomResult, errors::ConnectorError> { let debug_suffix = format!( - "For merchant_id: {}, and connector_label: {}", - merchant_id, connector_label + "For merchant_id: {}, and connector_name: {}", + merchant_id, connector_name ); - let merchant_connector_webhook_details = db - .find_merchant_connector_account_by_merchant_id_connector_label( + let default_secret = "default_secret".to_string(); + let merchant_connector_account_result = db + .find_merchant_connector_account_by_merchant_id_connector_name( merchant_id, - connector_label, + connector_name, key_store, ) - .await - .change_context(errors::ConnectorError::WebhookSourceVerificationFailed) - .attach_printable_lazy(|| { - format!( - "Fetch merchant_webhook_secret from MCA table failed {}", - debug_suffix - ) - })? - .connector_webhook_details; + .await; - let merchant_secret = match merchant_connector_webhook_details { - Some(merchant_connector_webhook_details) => merchant_connector_webhook_details - .parse_value::("MerchantConnectorWebhookDetails") - .change_context_lazy(|| errors::ConnectorError::WebhookSourceVerificationFailed) - .attach_printable_lazy(|| { - format!( - "Deserializing MerchantConnectorWebhookDetails failed {}", - debug_suffix + let merchant_secret = match merchant_connector_account_result { + Ok(mca) => match mca.connector_webhook_details { + Some(merchant_connector_webhook_details) => merchant_connector_webhook_details + .parse_value::( + "MerchantConnectorWebhookDetails", ) - })? - .merchant_secret - .expose(), - None => "default_secret".to_string(), + .change_context_lazy(|| errors::ConnectorError::WebhookSourceVerificationFailed) + .attach_printable_lazy(|| { + format!( + "Deserializing MerchantConnectorWebhookDetails failed {}", + debug_suffix + ) + })? + .merchant_secret + .expose(), + None => default_secret, + }, + Err(err) => { + logger::error!( + "Failed to fetch merchant_secret for source verification {}", + debug_suffix + ); + logger::error!("DB error = {:?}", err); + default_secret + } }; + //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.