mirror of
				https://github.com/juspay/hyperswitch.git
				synced 2025-10-31 18:17:13 +08:00 
			
		
		
		
	feat(router): add external authentication webhooks flow (#4339)
Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
This commit is contained in:
		 Sai Harsha Vardhan
					Sai Harsha Vardhan
				
			
				
					committed by
					
						 GitHub
						GitHub
					
				
			
			
				
	
			
			
			 GitHub
						GitHub
					
				
			
						parent
						
							d4dbaadb06
						
					
				
				
					commit
					00cd96d097
				
			| @ -38,6 +38,7 @@ pub enum IncomingWebhookEvent { | |||||||
|     MandateActive, |     MandateActive, | ||||||
|     MandateRevoked, |     MandateRevoked, | ||||||
|     EndpointVerification, |     EndpointVerification, | ||||||
|  |     ExternalAuthenticationARes, | ||||||
| } | } | ||||||
|  |  | ||||||
| pub enum WebhookFlow { | pub enum WebhookFlow { | ||||||
| @ -48,6 +49,7 @@ pub enum WebhookFlow { | |||||||
|     ReturnResponse, |     ReturnResponse, | ||||||
|     BankTransfer, |     BankTransfer, | ||||||
|     Mandate, |     Mandate, | ||||||
|  |     ExternalAuthentication, | ||||||
| } | } | ||||||
|  |  | ||||||
| #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] | #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] | ||||||
| @ -116,6 +118,7 @@ impl From<IncomingWebhookEvent> for WebhookFlow { | |||||||
|             IncomingWebhookEvent::EndpointVerification => Self::ReturnResponse, |             IncomingWebhookEvent::EndpointVerification => Self::ReturnResponse, | ||||||
|             IncomingWebhookEvent::SourceChargeable |             IncomingWebhookEvent::SourceChargeable | ||||||
|             | IncomingWebhookEvent::SourceTransactionCreated => Self::BankTransfer, |             | IncomingWebhookEvent::SourceTransactionCreated => Self::BankTransfer, | ||||||
|  |             IncomingWebhookEvent::ExternalAuthenticationARes => Self::ExternalAuthentication, | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @ -134,11 +137,18 @@ pub enum MandateIdType { | |||||||
|     ConnectorMandateId(String), |     ConnectorMandateId(String), | ||||||
| } | } | ||||||
|  |  | ||||||
|  | #[derive(Clone)] | ||||||
|  | pub enum AuthenticationIdType { | ||||||
|  |     AuthenticationId(String), | ||||||
|  |     ConnectorAuthenticationId(String), | ||||||
|  | } | ||||||
|  |  | ||||||
| #[derive(Clone)] | #[derive(Clone)] | ||||||
| pub enum ObjectReferenceId { | pub enum ObjectReferenceId { | ||||||
|     PaymentId(payments::PaymentIdType), |     PaymentId(payments::PaymentIdType), | ||||||
|     RefundId(RefundIdType), |     RefundId(RefundIdType), | ||||||
|     MandateId(MandateIdType), |     MandateId(MandateIdType), | ||||||
|  |     ExternalAuthenticationID(AuthenticationIdType), | ||||||
| } | } | ||||||
|  |  | ||||||
| pub struct IncomingWebhookDetails { | pub struct IncomingWebhookDetails { | ||||||
|  | |||||||
| @ -40,6 +40,9 @@ pub struct Authentication { | |||||||
|     pub acs_trans_id: Option<String>, |     pub acs_trans_id: Option<String>, | ||||||
|     pub three_ds_server_trans_id: Option<String>, |     pub three_ds_server_trans_id: Option<String>, | ||||||
|     pub acs_signed_content: Option<String>, |     pub acs_signed_content: Option<String>, | ||||||
|  |     pub profile_id: String, | ||||||
|  |     pub payment_id: Option<String>, | ||||||
|  |     pub merchant_connector_id: String, | ||||||
| } | } | ||||||
|  |  | ||||||
| impl Authentication { | impl Authentication { | ||||||
| @ -82,6 +85,9 @@ pub struct AuthenticationNew { | |||||||
|     pub acs_trans_id: Option<String>, |     pub acs_trans_id: Option<String>, | ||||||
|     pub three_dsserver_trans_id: Option<String>, |     pub three_dsserver_trans_id: Option<String>, | ||||||
|     pub acs_signed_content: Option<String>, |     pub acs_signed_content: Option<String>, | ||||||
|  |     pub profile_id: String, | ||||||
|  |     pub payment_id: Option<String>, | ||||||
|  |     pub merchant_connector_id: String, | ||||||
| } | } | ||||||
|  |  | ||||||
| #[derive(Debug)] | #[derive(Debug)] | ||||||
|  | |||||||
| @ -69,4 +69,18 @@ impl Authentication { | |||||||
|         ) |         ) | ||||||
|         .await |         .await | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     pub async fn find_authentication_by_merchant_id_connector_authentication_id( | ||||||
|  |         conn: &PgPooledConn, | ||||||
|  |         merchant_id: &str, | ||||||
|  |         connector_authentication_id: &str, | ||||||
|  |     ) -> StorageResult<Self> { | ||||||
|  |         generics::generic_find_one::<<Self as HasTable>::Table, _, _>( | ||||||
|  |             conn, | ||||||
|  |             dsl::merchant_id | ||||||
|  |                 .eq(merchant_id.to_owned()) | ||||||
|  |                 .and(dsl::connector_authentication_id.eq(connector_authentication_id.to_owned())), | ||||||
|  |         ) | ||||||
|  |         .await | ||||||
|  |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -111,6 +111,12 @@ diesel::table! { | |||||||
|         acs_trans_id -> Nullable<Varchar>, |         acs_trans_id -> Nullable<Varchar>, | ||||||
|         three_dsserver_trans_id -> Nullable<Varchar>, |         three_dsserver_trans_id -> Nullable<Varchar>, | ||||||
|         acs_signed_content -> Nullable<Varchar>, |         acs_signed_content -> Nullable<Varchar>, | ||||||
|  |         #[max_length = 64] | ||||||
|  |         profile_id -> Varchar, | ||||||
|  |         #[max_length = 255] | ||||||
|  |         payment_id -> Nullable<Varchar>, | ||||||
|  |         #[max_length = 128] | ||||||
|  |         merchant_connector_id -> Varchar, | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | |||||||
| @ -591,6 +591,10 @@ impl From<errors::ApiErrorResponse> for StripeErrorCode { | |||||||
|                 object: "dispute".to_owned(), |                 object: "dispute".to_owned(), | ||||||
|                 id: dispute_id, |                 id: dispute_id, | ||||||
|             }, |             }, | ||||||
|  |             errors::ApiErrorResponse::AuthenticationNotFound { id } => Self::ResourceMissing { | ||||||
|  |                 object: "authentication".to_owned(), | ||||||
|  |                 id, | ||||||
|  |             }, | ||||||
|             errors::ApiErrorResponse::BusinessProfileNotFound { id } => Self::ResourceMissing { |             errors::ApiErrorResponse::BusinessProfileNotFound { id } => Self::ResourceMissing { | ||||||
|                 object: "business_profile".to_owned(), |                 object: "business_profile".to_owned(), | ||||||
|                 id, |                 id, | ||||||
|  | |||||||
| @ -155,6 +155,17 @@ pub async fn perform_post_authentication<F: Clone + Send>( | |||||||
|     Ok(()) |     Ok(()) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | fn get_payment_id_from_pre_authentication_flow_input<F: Clone + Send>( | ||||||
|  |     pre_authentication_flow_input: &types::PreAuthenthenticationFlowInput<'_, F>, | ||||||
|  | ) -> Option<String> { | ||||||
|  |     match pre_authentication_flow_input { | ||||||
|  |         types::PreAuthenthenticationFlowInput::PaymentAuthNFlow { payment_data, .. } => { | ||||||
|  |             Some(payment_data.payment_intent.payment_id.clone()) | ||||||
|  |         } | ||||||
|  |         _ => None, | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
| pub async fn perform_pre_authentication<F: Clone + Send>( | pub async fn perform_pre_authentication<F: Clone + Send>( | ||||||
|     state: &AppState, |     state: &AppState, | ||||||
|     authentication_connector_name: String, |     authentication_connector_name: String, | ||||||
| @ -163,10 +174,17 @@ pub async fn perform_pre_authentication<F: Clone + Send>( | |||||||
|     three_ds_connector_account: payments_core::helpers::MerchantConnectorAccountType, |     three_ds_connector_account: payments_core::helpers::MerchantConnectorAccountType, | ||||||
|     payment_connector_account: payments_core::helpers::MerchantConnectorAccountType, |     payment_connector_account: payments_core::helpers::MerchantConnectorAccountType, | ||||||
| ) -> CustomResult<(), ApiErrorResponse> { | ) -> CustomResult<(), ApiErrorResponse> { | ||||||
|  |     let payment_id = get_payment_id_from_pre_authentication_flow_input(&authentication_flow_input); | ||||||
|     let authentication = utils::create_new_authentication( |     let authentication = utils::create_new_authentication( | ||||||
|         state, |         state, | ||||||
|         business_profile.merchant_id.clone(), |         business_profile.merchant_id.clone(), | ||||||
|         authentication_connector_name.clone(), |         authentication_connector_name.clone(), | ||||||
|  |         business_profile.profile_id.clone(), | ||||||
|  |         payment_id, | ||||||
|  |         three_ds_connector_account | ||||||
|  |             .get_mca_id() | ||||||
|  |             .ok_or(errors::ApiErrorResponse::InternalServerError) | ||||||
|  |             .attach_printable("Error while finding mca_id from merchant_connector_account")?, | ||||||
|     ) |     ) | ||||||
|     .await?; |     .await?; | ||||||
|     match authentication_flow_input { |     match authentication_flow_input { | ||||||
|  | |||||||
| @ -74,3 +74,8 @@ pub struct AcquirerDetails { | |||||||
|     pub acquirer_bin: String, |     pub acquirer_bin: String, | ||||||
|     pub acquirer_merchant_id: String, |     pub acquirer_merchant_id: String, | ||||||
| } | } | ||||||
|  |  | ||||||
|  | #[derive(Clone, Debug, Deserialize)] | ||||||
|  | pub struct ExternalThreeDSConnectorMetadata { | ||||||
|  |     pub pull_mechanism_for_external_3ds_enabled: Option<bool>, | ||||||
|  | } | ||||||
|  | |||||||
| @ -144,6 +144,9 @@ pub async fn create_new_authentication( | |||||||
|     state: &AppState, |     state: &AppState, | ||||||
|     merchant_id: String, |     merchant_id: String, | ||||||
|     authentication_connector: String, |     authentication_connector: String, | ||||||
|  |     profile_id: String, | ||||||
|  |     payment_id: Option<String>, | ||||||
|  |     merchant_connector_id: String, | ||||||
| ) -> RouterResult<storage::Authentication> { | ) -> RouterResult<storage::Authentication> { | ||||||
|     let authentication_id = |     let authentication_id = | ||||||
|         common_utils::generate_id_with_default_len(consts::AUTHENTICATION_ID_PREFIX); |         common_utils::generate_id_with_default_len(consts::AUTHENTICATION_ID_PREFIX); | ||||||
| @ -176,6 +179,9 @@ pub async fn create_new_authentication( | |||||||
|         acs_trans_id: None, |         acs_trans_id: None, | ||||||
|         three_dsserver_trans_id: None, |         three_dsserver_trans_id: None, | ||||||
|         acs_signed_content: None, |         acs_signed_content: None, | ||||||
|  |         profile_id, | ||||||
|  |         payment_id, | ||||||
|  |         merchant_connector_id, | ||||||
|     }; |     }; | ||||||
|     state |     state | ||||||
|         .store |         .store | ||||||
|  | |||||||
| @ -170,6 +170,8 @@ pub enum ApiErrorResponse { | |||||||
|     ResourceIdNotFound, |     ResourceIdNotFound, | ||||||
|     #[error(error_type = ErrorType::ObjectNotFound, code = "HE_02", message = "Mandate does not exist in our records")] |     #[error(error_type = ErrorType::ObjectNotFound, code = "HE_02", message = "Mandate does not exist in our records")] | ||||||
|     MandateNotFound, |     MandateNotFound, | ||||||
|  |     #[error(error_type = ErrorType::ObjectNotFound, code = "HE_02", message = "Authentication does not exist in our records")] | ||||||
|  |     AuthenticationNotFound { id: String }, | ||||||
|     #[error(error_type = ErrorType::ObjectNotFound, code = "HE_02", message = "Failed to update mandate")] |     #[error(error_type = ErrorType::ObjectNotFound, code = "HE_02", message = "Failed to update mandate")] | ||||||
|     MandateUpdateFailed, |     MandateUpdateFailed, | ||||||
|     #[error(error_type = ErrorType::ObjectNotFound, code = "HE_02", message = "API Key does not exist in our records")] |     #[error(error_type = ErrorType::ObjectNotFound, code = "HE_02", message = "API Key does not exist in our records")] | ||||||
|  | |||||||
| @ -229,6 +229,9 @@ impl ErrorSwitch<api_models::errors::types::ApiErrorResponse> for ApiErrorRespon | |||||||
|             Self::DisputeNotFound { .. } => { |             Self::DisputeNotFound { .. } => { | ||||||
|                 AER::NotFound(ApiError::new("HE", 2, "Dispute does not exist in our records", None)) |                 AER::NotFound(ApiError::new("HE", 2, "Dispute does not exist in our records", None)) | ||||||
|             }, |             }, | ||||||
|  |             Self::AuthenticationNotFound { .. } => { | ||||||
|  |                 AER::NotFound(ApiError::new("HE", 2, "Authentication does not exist in our records", None)) | ||||||
|  |             }, | ||||||
|             Self::BusinessProfileNotFound { id } => { |             Self::BusinessProfileNotFound { id } => { | ||||||
|                 AER::NotFound(ApiError::new("HE", 2, format!("Business profile with the given id {id} does not exist"), None)) |                 AER::NotFound(ApiError::new("HE", 2, format!("Business profile with the given id {id} does not exist"), None)) | ||||||
|             } |             } | ||||||
|  | |||||||
| @ -50,7 +50,7 @@ use crate::{ | |||||||
|         api::{self, mandates::MandateResponseExt}, |         api::{self, mandates::MandateResponseExt}, | ||||||
|         domain::{self, types as domain_types}, |         domain::{self, types as domain_types}, | ||||||
|         storage::{self, enums}, |         storage::{self, enums}, | ||||||
|         transformers::{ForeignInto, ForeignTryFrom}, |         transformers::{ForeignFrom, ForeignInto, ForeignTryFrom}, | ||||||
|     }, |     }, | ||||||
|     utils::{self as helper_utils, generate_id, OptionExt, ValueExt}, |     utils::{self as helper_utils, generate_id, OptionExt, ValueExt}, | ||||||
|     workflows::outgoing_webhook_retry, |     workflows::outgoing_webhook_retry, | ||||||
| @ -416,6 +416,162 @@ pub async fn get_or_update_dispute_object( | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | #[allow(clippy::too_many_arguments)] | ||||||
|  | pub async fn external_authentication_incoming_webhook_flow<Ctx: PaymentMethodRetrieve>( | ||||||
|  |     state: AppState, | ||||||
|  |     req_state: ReqState, | ||||||
|  |     merchant_account: domain::MerchantAccount, | ||||||
|  |     key_store: domain::MerchantKeyStore, | ||||||
|  |     source_verified: bool, | ||||||
|  |     event_type: api_models::webhooks::IncomingWebhookEvent, | ||||||
|  |     request_details: &api::IncomingWebhookRequestDetails<'_>, | ||||||
|  |     connector: &(dyn api::Connector + Sync), | ||||||
|  |     object_ref_id: api::ObjectReferenceId, | ||||||
|  |     business_profile: diesel_models::business_profile::BusinessProfile, | ||||||
|  |     merchant_connector_account: domain::MerchantConnectorAccount, | ||||||
|  | ) -> CustomResult<WebhookResponseTracker, errors::ApiErrorResponse> { | ||||||
|  |     if source_verified { | ||||||
|  |         let authentication_details = connector | ||||||
|  |             .get_external_authentication_details(request_details) | ||||||
|  |             .switch()?; | ||||||
|  |         let trans_status = authentication_details.trans_status; | ||||||
|  |         let authentication_update = storage::AuthenticationUpdate::PostAuthenticationUpdate { | ||||||
|  |             authentication_status: common_enums::AuthenticationStatus::foreign_from( | ||||||
|  |                 trans_status.clone(), | ||||||
|  |             ), | ||||||
|  |             trans_status, | ||||||
|  |             authentication_value: authentication_details.authentication_value, | ||||||
|  |             eci: authentication_details.eci, | ||||||
|  |         }; | ||||||
|  |         let authentication = | ||||||
|  |             if let webhooks::ObjectReferenceId::ExternalAuthenticationID(authentication_id_type) = | ||||||
|  |                 object_ref_id | ||||||
|  |             { | ||||||
|  |                 match authentication_id_type { | ||||||
|  |                     webhooks::AuthenticationIdType::AuthenticationId(authentication_id) => state | ||||||
|  |                         .store | ||||||
|  |                         .find_authentication_by_merchant_id_authentication_id( | ||||||
|  |                             merchant_account.merchant_id.clone(), | ||||||
|  |                             authentication_id.clone(), | ||||||
|  |                         ) | ||||||
|  |                         .await | ||||||
|  |                         .to_not_found_response(errors::ApiErrorResponse::AuthenticationNotFound { | ||||||
|  |                             id: authentication_id, | ||||||
|  |                         }) | ||||||
|  |                         .attach_printable("Error while fetching authentication record"), | ||||||
|  |                     webhooks::AuthenticationIdType::ConnectorAuthenticationId( | ||||||
|  |                         connector_authentication_id, | ||||||
|  |                     ) => state | ||||||
|  |                         .store | ||||||
|  |                         .find_authentication_by_merchant_id_connector_authentication_id( | ||||||
|  |                             merchant_account.merchant_id.clone(), | ||||||
|  |                             connector_authentication_id.clone(), | ||||||
|  |                         ) | ||||||
|  |                         .await | ||||||
|  |                         .to_not_found_response(errors::ApiErrorResponse::AuthenticationNotFound { | ||||||
|  |                             id: connector_authentication_id, | ||||||
|  |                         }) | ||||||
|  |                         .attach_printable("Error while fetching authentication record"), | ||||||
|  |                 } | ||||||
|  |             } else { | ||||||
|  |                 Err(errors::ApiErrorResponse::WebhookProcessingFailure).attach_printable( | ||||||
|  |                     "received a non-external-authentication id for retrieving authentication", | ||||||
|  |                 ) | ||||||
|  |             }?; | ||||||
|  |         let updated_authentication = state | ||||||
|  |             .store | ||||||
|  |             .update_authentication_by_merchant_id_authentication_id( | ||||||
|  |                 authentication, | ||||||
|  |                 authentication_update, | ||||||
|  |             ) | ||||||
|  |             .await | ||||||
|  |             .change_context(errors::ApiErrorResponse::InternalServerError) | ||||||
|  |             .attach_printable("Error while updating authentication")?; | ||||||
|  |         // Check if it's a payment authentication flow, payment_id would be there only for payment authentication flows | ||||||
|  |         if let Some(payment_id) = updated_authentication.payment_id { | ||||||
|  |             let is_pull_mechanism_enabled = helper_utils::check_if_pull_mechanism_for_external_3ds_enabled_from_connector_metadata(merchant_connector_account.metadata.map(|metadata| metadata.expose())); | ||||||
|  |             // Merchant doesn't have pull mechanism enabled, so we have to authorize whenever we receive a ARes webhook | ||||||
|  |             if !is_pull_mechanism_enabled | ||||||
|  |                 && event_type == webhooks::IncomingWebhookEvent::ExternalAuthenticationARes | ||||||
|  |             { | ||||||
|  |                 let payment_confirm_req = api::PaymentsRequest { | ||||||
|  |                     payment_id: Some(api_models::payments::PaymentIdType::PaymentIntentId( | ||||||
|  |                         payment_id, | ||||||
|  |                     )), | ||||||
|  |                     merchant_id: Some(merchant_account.merchant_id.clone()), | ||||||
|  |                     ..Default::default() | ||||||
|  |                 }; | ||||||
|  |                 let payments_response = Box::pin(payments::payments_core::< | ||||||
|  |                     api::Authorize, | ||||||
|  |                     api::PaymentsResponse, | ||||||
|  |                     _, | ||||||
|  |                     _, | ||||||
|  |                     _, | ||||||
|  |                     Ctx, | ||||||
|  |                 >( | ||||||
|  |                     state.clone(), | ||||||
|  |                     req_state, | ||||||
|  |                     merchant_account.clone(), | ||||||
|  |                     key_store.clone(), | ||||||
|  |                     payments::PaymentConfirm, | ||||||
|  |                     payment_confirm_req, | ||||||
|  |                     services::api::AuthFlow::Merchant, | ||||||
|  |                     payments::CallConnectorAction::Trigger, | ||||||
|  |                     None, | ||||||
|  |                     HeaderPayload::with_source(enums::PaymentSource::ExternalAuthenticator), | ||||||
|  |                 )) | ||||||
|  |                 .await?; | ||||||
|  |                 match payments_response { | ||||||
|  |                     services::ApplicationResponse::JsonWithHeaders((payments_response, _)) => { | ||||||
|  |                         let payment_id = payments_response | ||||||
|  |                             .payment_id | ||||||
|  |                             .clone() | ||||||
|  |                             .get_required_value("payment_id") | ||||||
|  |                             .change_context(errors::ApiErrorResponse::WebhookProcessingFailure) | ||||||
|  |                             .attach_printable("payment id not received from payments core")?; | ||||||
|  |                         let status = payments_response.status; | ||||||
|  |                         let event_type: Option<enums::EventType> = | ||||||
|  |                             payments_response.status.foreign_into(); | ||||||
|  |                         // If event is NOT an UnsupportedEvent, trigger Outgoing Webhook | ||||||
|  |                         if let Some(outgoing_event_type) = event_type { | ||||||
|  |                             let primary_object_created_at = payments_response.created; | ||||||
|  |                             create_event_and_trigger_outgoing_webhook( | ||||||
|  |                                 state, | ||||||
|  |                                 merchant_account, | ||||||
|  |                                 business_profile, | ||||||
|  |                                 &key_store, | ||||||
|  |                                 outgoing_event_type, | ||||||
|  |                                 enums::EventClass::Payments, | ||||||
|  |                                 payment_id.clone(), | ||||||
|  |                                 enums::EventObjectType::PaymentDetails, | ||||||
|  |                                 api::OutgoingWebhookContent::PaymentDetails(payments_response), | ||||||
|  |                                 primary_object_created_at, | ||||||
|  |                             ) | ||||||
|  |                             .await?; | ||||||
|  |                         }; | ||||||
|  |                         let response = WebhookResponseTracker::Payment { payment_id, status }; | ||||||
|  |                         Ok(response) | ||||||
|  |                     } | ||||||
|  |                     _ => Err(errors::ApiErrorResponse::WebhookProcessingFailure).attach_printable( | ||||||
|  |                         "Did not get payment id as object reference id in webhook payments flow", | ||||||
|  |                     )?, | ||||||
|  |                 } | ||||||
|  |             } else { | ||||||
|  |                 Ok(WebhookResponseTracker::NoEffect) | ||||||
|  |             } | ||||||
|  |         } else { | ||||||
|  |             Ok(WebhookResponseTracker::NoEffect) | ||||||
|  |         } | ||||||
|  |     } else { | ||||||
|  |         logger::error!( | ||||||
|  |             "Webhook source verification failed for external authentication webhook flow" | ||||||
|  |         ); | ||||||
|  |         Err(report!( | ||||||
|  |             errors::ApiErrorResponse::WebhookAuthenticationFailed | ||||||
|  |         )) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
| pub async fn mandates_incoming_webhook_flow( | pub async fn mandates_incoming_webhook_flow( | ||||||
|     state: AppState, |     state: AppState, | ||||||
|     merchant_account: domain::MerchantAccount, |     merchant_account: domain::MerchantAccount, | ||||||
| @ -1365,7 +1521,7 @@ pub async fn webhooks_core<W: types::OutgoingWebhookType, Ctx: PaymentMethodRetr | |||||||
|     // Fetch the merchant connector account to get the webhooks source secret |     // Fetch the merchant connector account to get the webhooks source secret | ||||||
|     // `webhooks source secret` is a secret shared between the merchant and connector |     // `webhooks source secret` is a secret shared between the merchant and connector | ||||||
|     // This is used for source verification and webhooks integrity |     // This is used for source verification and webhooks integrity | ||||||
|     let (merchant_connector_account, connector) = fetch_optional_mca_and_connector( |     let (merchant_connector_account, connector, connector_name) = fetch_optional_mca_and_connector( | ||||||
|         &state, |         &state, | ||||||
|         &merchant_account, |         &merchant_account, | ||||||
|         connector_name_or_mca_id, |         connector_name_or_mca_id, | ||||||
| @ -1373,10 +1529,6 @@ pub async fn webhooks_core<W: types::OutgoingWebhookType, Ctx: PaymentMethodRetr | |||||||
|     ) |     ) | ||||||
|     .await?; |     .await?; | ||||||
|  |  | ||||||
|     let connector_name = connector.connector_name.to_string(); |  | ||||||
|  |  | ||||||
|     let connector = connector.connector; |  | ||||||
|  |  | ||||||
|     let decoded_body = connector |     let decoded_body = connector | ||||||
|         .decode_webhook_body( |         .decode_webhook_body( | ||||||
|             &*state.clone().store, |             &*state.clone().store, | ||||||
| @ -1548,7 +1700,7 @@ pub async fn webhooks_core<W: types::OutgoingWebhookType, Ctx: PaymentMethodRetr | |||||||
|             .attach_printable("Could not find resource object in incoming webhook body")?; |             .attach_printable("Could not find resource object in incoming webhook body")?; | ||||||
|  |  | ||||||
|         let webhook_details = api::IncomingWebhookDetails { |         let webhook_details = api::IncomingWebhookDetails { | ||||||
|             object_reference_id: object_ref_id, |             object_reference_id: object_ref_id.clone(), | ||||||
|             resource_object: serde_json::to_vec(&event_object) |             resource_object: serde_json::to_vec(&event_object) | ||||||
|                 .change_context(errors::ParsingError::EncodeError("byte-vec")) |                 .change_context(errors::ParsingError::EncodeError("byte-vec")) | ||||||
|                 .attach_printable("Unable to convert webhook payload to a value") |                 .attach_printable("Unable to convert webhook payload to a value") | ||||||
| @ -1606,7 +1758,7 @@ pub async fn webhooks_core<W: types::OutgoingWebhookType, Ctx: PaymentMethodRetr | |||||||
|                 key_store, |                 key_store, | ||||||
|                 webhook_details, |                 webhook_details, | ||||||
|                 source_verified, |                 source_verified, | ||||||
|                 *connector, |                 connector, | ||||||
|                 &request_details, |                 &request_details, | ||||||
|                 event_type, |                 event_type, | ||||||
|             )) |             )) | ||||||
| @ -1639,6 +1791,24 @@ pub async fn webhooks_core<W: types::OutgoingWebhookType, Ctx: PaymentMethodRetr | |||||||
|             .await |             .await | ||||||
|             .attach_printable("Incoming webhook flow for mandates failed")?, |             .attach_printable("Incoming webhook flow for mandates failed")?, | ||||||
|  |  | ||||||
|  |             api::WebhookFlow::ExternalAuthentication => { | ||||||
|  |                 Box::pin(external_authentication_incoming_webhook_flow::<Ctx>( | ||||||
|  |                     state.clone(), | ||||||
|  |                     req_state, | ||||||
|  |                     merchant_account, | ||||||
|  |                     key_store, | ||||||
|  |                     source_verified, | ||||||
|  |                     event_type, | ||||||
|  |                     &request_details, | ||||||
|  |                     connector, | ||||||
|  |                     object_ref_id, | ||||||
|  |                     business_profile, | ||||||
|  |                     merchant_connector_account, | ||||||
|  |                 )) | ||||||
|  |                 .await | ||||||
|  |                 .attach_printable("Incoming webhook flow for external authentication failed")? | ||||||
|  |             } | ||||||
|  |  | ||||||
|             _ => Err(errors::ApiErrorResponse::InternalServerError) |             _ => Err(errors::ApiErrorResponse::InternalServerError) | ||||||
|                 .attach_printable("Unsupported Flow Type received in incoming webhooks")?, |                 .attach_printable("Unsupported Flow Type received in incoming webhooks")?, | ||||||
|         } |         } | ||||||
| @ -1704,6 +1874,39 @@ pub async fn get_payment_id( | |||||||
|         .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound) |         .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | fn get_connector_by_connector_name( | ||||||
|  |     state: &AppState, | ||||||
|  |     connector_name: &str, | ||||||
|  |     merchant_connector_id: Option<String>, | ||||||
|  | ) -> CustomResult<(&'static (dyn api::Connector + Sync), String), errors::ApiErrorResponse> { | ||||||
|  |     let authentication_connector = | ||||||
|  |         api_models::enums::convert_authentication_connector(connector_name); | ||||||
|  |     let (connector, connector_name) = if authentication_connector.is_some() { | ||||||
|  |         let authentication_connector_data = | ||||||
|  |             api::AuthenticationConnectorData::get_connector_by_name(connector_name)?; | ||||||
|  |         ( | ||||||
|  |             authentication_connector_data.connector, | ||||||
|  |             authentication_connector_data.connector_name.to_string(), | ||||||
|  |         ) | ||||||
|  |     } else { | ||||||
|  |         let connector_data = api::ConnectorData::get_connector_by_name( | ||||||
|  |             &state.conf.connectors, | ||||||
|  |             connector_name, | ||||||
|  |             api::GetToken::Connector, | ||||||
|  |             merchant_connector_id, | ||||||
|  |         ) | ||||||
|  |         .change_context(errors::ApiErrorResponse::InvalidRequestData { | ||||||
|  |             message: "invalid connector name received".to_string(), | ||||||
|  |         }) | ||||||
|  |         .attach_printable("Failed construction of ConnectorData")?; | ||||||
|  |         ( | ||||||
|  |             connector_data.connector, | ||||||
|  |             connector_data.connector_name.to_string(), | ||||||
|  |         ) | ||||||
|  |     }; | ||||||
|  |     Ok((*connector, connector_name)) | ||||||
|  | } | ||||||
|  |  | ||||||
| /// This function fetches the merchant connector account ( if the url used is /{merchant_connector_id}) | /// This function fetches the merchant connector account ( if the url used is /{merchant_connector_id}) | ||||||
| /// if merchant connector id is not passed in the request, then this will return None for mca | /// if merchant connector id is not passed in the request, then this will return None for mca | ||||||
| async fn fetch_optional_mca_and_connector( | async fn fetch_optional_mca_and_connector( | ||||||
| @ -1712,7 +1915,11 @@ async fn fetch_optional_mca_and_connector( | |||||||
|     connector_name_or_mca_id: &str, |     connector_name_or_mca_id: &str, | ||||||
|     key_store: &domain::MerchantKeyStore, |     key_store: &domain::MerchantKeyStore, | ||||||
| ) -> CustomResult< | ) -> CustomResult< | ||||||
|     (Option<domain::MerchantConnectorAccount>, api::ConnectorData), |     ( | ||||||
|  |         Option<domain::MerchantConnectorAccount>, | ||||||
|  |         &'static (dyn api::Connector + Sync), | ||||||
|  |         String, | ||||||
|  |     ), | ||||||
|     errors::ApiErrorResponse, |     errors::ApiErrorResponse, | ||||||
| > { | > { | ||||||
|     let db = &state.store; |     let db = &state.store; | ||||||
| @ -1730,33 +1937,18 @@ async fn fetch_optional_mca_and_connector( | |||||||
|             .attach_printable( |             .attach_printable( | ||||||
|                 "error while fetching merchant_connector_account from connector_id", |                 "error while fetching merchant_connector_account from connector_id", | ||||||
|             )?; |             )?; | ||||||
|  |         let (connector, connector_name) = get_connector_by_connector_name( | ||||||
|         let connector = api::ConnectorData::get_connector_by_name( |             state, | ||||||
|             &state.conf.connectors, |  | ||||||
|             &mca.connector_name, |             &mca.connector_name, | ||||||
|             api::GetToken::Connector, |  | ||||||
|             Some(mca.merchant_connector_id.clone()), |             Some(mca.merchant_connector_id.clone()), | ||||||
|         ) |         )?; | ||||||
|         .change_context(errors::ApiErrorResponse::InvalidRequestData { |  | ||||||
|             message: "invalid connector name received".to_string(), |  | ||||||
|         }) |  | ||||||
|         .attach_printable("Failed construction of ConnectorData")?; |  | ||||||
|  |  | ||||||
|         Ok((Some(mca), connector)) |         Ok((Some(mca), connector, connector_name)) | ||||||
|     } else { |     } else { | ||||||
|         // Merchant connector account is already being queried, it is safe to set connector id as None |         // Merchant connector account is already being queried, it is safe to set connector id as None | ||||||
|         let connector = api::ConnectorData::get_connector_by_name( |         let (connector, connector_name) = | ||||||
|             &state.conf.connectors, |             get_connector_by_connector_name(state, connector_name_or_mca_id, None)?; | ||||||
|             connector_name_or_mca_id, |         Ok((None, connector, connector_name)) | ||||||
|             api::GetToken::Connector, |  | ||||||
|             None, |  | ||||||
|         ) |  | ||||||
|         .change_context(errors::ApiErrorResponse::InvalidRequestData { |  | ||||||
|             message: "invalid connector name received".to_string(), |  | ||||||
|         }) |  | ||||||
|         .attach_printable("Failed construction of ConnectorData")?; |  | ||||||
|  |  | ||||||
|         Ok((None, connector)) |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | |||||||
| @ -22,6 +22,12 @@ pub trait AuthenticationInterface { | |||||||
|         authentication_id: String, |         authentication_id: String, | ||||||
|     ) -> CustomResult<storage::Authentication, errors::StorageError>; |     ) -> CustomResult<storage::Authentication, errors::StorageError>; | ||||||
|  |  | ||||||
|  |     async fn find_authentication_by_merchant_id_connector_authentication_id( | ||||||
|  |         &self, | ||||||
|  |         merchant_id: String, | ||||||
|  |         connector_authentication_id: String, | ||||||
|  |     ) -> CustomResult<storage::Authentication, errors::StorageError>; | ||||||
|  |  | ||||||
|     async fn update_authentication_by_merchant_id_authentication_id( |     async fn update_authentication_by_merchant_id_authentication_id( | ||||||
|         &self, |         &self, | ||||||
|         previous_state: storage::Authentication, |         previous_state: storage::Authentication, | ||||||
| @ -59,6 +65,21 @@ impl AuthenticationInterface for Store { | |||||||
|         .map_err(|error| report!(errors::StorageError::from(error))) |         .map_err(|error| report!(errors::StorageError::from(error))) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     async fn find_authentication_by_merchant_id_connector_authentication_id( | ||||||
|  |         &self, | ||||||
|  |         merchant_id: String, | ||||||
|  |         connector_authentication_id: String, | ||||||
|  |     ) -> CustomResult<storage::Authentication, errors::StorageError> { | ||||||
|  |         let conn = connection::pg_connection_read(self).await?; | ||||||
|  |         storage::Authentication::find_authentication_by_merchant_id_connector_authentication_id( | ||||||
|  |             &conn, | ||||||
|  |             &merchant_id, | ||||||
|  |             &connector_authentication_id, | ||||||
|  |         ) | ||||||
|  |         .await | ||||||
|  |         .map_err(|error| report!(errors::StorageError::from(error))) | ||||||
|  |     } | ||||||
|  |  | ||||||
|     #[instrument(skip_all)] |     #[instrument(skip_all)] | ||||||
|     async fn update_authentication_by_merchant_id_authentication_id( |     async fn update_authentication_by_merchant_id_authentication_id( | ||||||
|         &self, |         &self, | ||||||
| @ -124,6 +145,9 @@ impl AuthenticationInterface for MockDb { | |||||||
|             acs_trans_id: authentication.acs_trans_id, |             acs_trans_id: authentication.acs_trans_id, | ||||||
|             three_ds_server_trans_id: authentication.three_dsserver_trans_id, |             three_ds_server_trans_id: authentication.three_dsserver_trans_id, | ||||||
|             acs_signed_content: authentication.acs_signed_content, |             acs_signed_content: authentication.acs_signed_content, | ||||||
|  |             profile_id: authentication.profile_id, | ||||||
|  |             payment_id: authentication.payment_id, | ||||||
|  |             merchant_connector_id: authentication.merchant_connector_id, | ||||||
|         }; |         }; | ||||||
|         authentications.push(authentication.clone()); |         authentications.push(authentication.clone()); | ||||||
|         Ok(authentication) |         Ok(authentication) | ||||||
| @ -145,6 +169,14 @@ impl AuthenticationInterface for MockDb { | |||||||
|             ).cloned() |             ).cloned() | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     async fn find_authentication_by_merchant_id_connector_authentication_id( | ||||||
|  |         &self, | ||||||
|  |         _merchant_id: String, | ||||||
|  |         _connector_authentication_id: String, | ||||||
|  |     ) -> CustomResult<storage::Authentication, errors::StorageError> { | ||||||
|  |         Err(errors::StorageError::MockDbError)? | ||||||
|  |     } | ||||||
|  |  | ||||||
|     async fn update_authentication_by_merchant_id_authentication_id( |     async fn update_authentication_by_merchant_id_authentication_id( | ||||||
|         &self, |         &self, | ||||||
|         previous_state: storage::Authentication, |         previous_state: storage::Authentication, | ||||||
|  | |||||||
| @ -2645,6 +2645,19 @@ impl AuthenticationInterface for KafkaStore { | |||||||
|             .await |             .await | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     async fn find_authentication_by_merchant_id_connector_authentication_id( | ||||||
|  |         &self, | ||||||
|  |         merchant_id: String, | ||||||
|  |         connector_authentication_id: String, | ||||||
|  |     ) -> CustomResult<storage::Authentication, errors::StorageError> { | ||||||
|  |         self.diesel_store | ||||||
|  |             .find_authentication_by_merchant_id_connector_authentication_id( | ||||||
|  |                 merchant_id, | ||||||
|  |                 connector_authentication_id, | ||||||
|  |             ) | ||||||
|  |             .await | ||||||
|  |     } | ||||||
|  |  | ||||||
|     async fn update_authentication_by_merchant_id_authentication_id( |     async fn update_authentication_by_merchant_id_authentication_id( | ||||||
|         &self, |         &self, | ||||||
|         previous_state: storage::Authentication, |         previous_state: storage::Authentication, | ||||||
|  | |||||||
| @ -47,6 +47,13 @@ pub enum MessageCategory { | |||||||
|     NonPayment, |     NonPayment, | ||||||
| } | } | ||||||
|  |  | ||||||
|  | #[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: | pub trait ConnectorAuthentication: | ||||||
|     services::ConnectorIntegration< |     services::ConnectorIntegration< | ||||||
|     Authentication, |     Authentication, | ||||||
|  | |||||||
| @ -270,4 +270,14 @@ pub trait IncomingWebhook: ConnectorCommon + Sync { | |||||||
|     ) -> CustomResult<super::disputes::DisputePayload, errors::ConnectorError> { |     ) -> CustomResult<super::disputes::DisputePayload, errors::ConnectorError> { | ||||||
|         Err(errors::ConnectorError::NotImplemented("get_dispute_details method".to_string()).into()) |         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()) | ||||||
|  |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -38,6 +38,7 @@ pub use self::ext_traits::{OptionExt, ValidateCall}; | |||||||
| use crate::{ | use crate::{ | ||||||
|     consts, |     consts, | ||||||
|     core::{ |     core::{ | ||||||
|  |         authentication::types::ExternalThreeDSConnectorMetadata, | ||||||
|         errors::{self, CustomResult, RouterResult, StorageErrorExt}, |         errors::{self, CustomResult, RouterResult, StorageErrorExt}, | ||||||
|         utils, webhooks as webhooks_core, |         utils, webhooks as webhooks_core, | ||||||
|     }, |     }, | ||||||
| @ -323,6 +324,98 @@ pub async fn find_payment_intent_from_mandate_id_type( | |||||||
|     .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound) |     .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | pub async fn find_mca_from_authentication_id_type( | ||||||
|  |     db: &dyn StorageInterface, | ||||||
|  |     authentication_id_type: webhooks::AuthenticationIdType, | ||||||
|  |     merchant_account: &domain::MerchantAccount, | ||||||
|  |     key_store: &domain::MerchantKeyStore, | ||||||
|  | ) -> CustomResult<domain::MerchantConnectorAccount, errors::ApiErrorResponse> { | ||||||
|  |     let authentication = match authentication_id_type { | ||||||
|  |         webhooks::AuthenticationIdType::AuthenticationId(authentication_id) => db | ||||||
|  |             .find_authentication_by_merchant_id_authentication_id( | ||||||
|  |                 merchant_account.merchant_id.clone(), | ||||||
|  |                 authentication_id, | ||||||
|  |             ) | ||||||
|  |             .await | ||||||
|  |             .to_not_found_response(errors::ApiErrorResponse::InternalServerError)?, | ||||||
|  |         webhooks::AuthenticationIdType::ConnectorAuthenticationId(connector_authentication_id) => { | ||||||
|  |             db.find_authentication_by_merchant_id_connector_authentication_id( | ||||||
|  |                 merchant_account.merchant_id.clone(), | ||||||
|  |                 connector_authentication_id, | ||||||
|  |             ) | ||||||
|  |             .await | ||||||
|  |             .to_not_found_response(errors::ApiErrorResponse::InternalServerError)? | ||||||
|  |         } | ||||||
|  |     }; | ||||||
|  |     db.find_by_merchant_connector_account_merchant_id_merchant_connector_id( | ||||||
|  |         &merchant_account.merchant_id, | ||||||
|  |         &authentication.merchant_connector_id, | ||||||
|  |         key_store, | ||||||
|  |     ) | ||||||
|  |     .await | ||||||
|  |     .to_not_found_response(errors::ApiErrorResponse::MerchantConnectorAccountNotFound { | ||||||
|  |         id: authentication.merchant_connector_id.to_string(), | ||||||
|  |     }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | pub async fn get_mca_from_payment_intent( | ||||||
|  |     db: &dyn StorageInterface, | ||||||
|  |     merchant_account: &domain::MerchantAccount, | ||||||
|  |     payment_intent: PaymentIntent, | ||||||
|  |     key_store: &domain::MerchantKeyStore, | ||||||
|  |     connector_name: &str, | ||||||
|  | ) -> CustomResult<domain::MerchantConnectorAccount, errors::ApiErrorResponse> { | ||||||
|  |     let payment_attempt = db | ||||||
|  |         .find_payment_attempt_by_attempt_id_merchant_id( | ||||||
|  |             &payment_intent.active_attempt.get_id(), | ||||||
|  |             &merchant_account.merchant_id, | ||||||
|  |             merchant_account.storage_scheme, | ||||||
|  |         ) | ||||||
|  |         .await | ||||||
|  |         .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?; | ||||||
|  |  | ||||||
|  |     match payment_attempt.merchant_connector_id { | ||||||
|  |         Some(merchant_connector_id) => db | ||||||
|  |             .find_by_merchant_connector_account_merchant_id_merchant_connector_id( | ||||||
|  |                 &merchant_account.merchant_id, | ||||||
|  |                 &merchant_connector_id, | ||||||
|  |                 key_store, | ||||||
|  |             ) | ||||||
|  |             .await | ||||||
|  |             .to_not_found_response(errors::ApiErrorResponse::MerchantConnectorAccountNotFound { | ||||||
|  |                 id: merchant_connector_id, | ||||||
|  |             }), | ||||||
|  |         None => { | ||||||
|  |             let profile_id = match payment_intent.profile_id { | ||||||
|  |                 Some(profile_id) => profile_id, | ||||||
|  |                 None => utils::get_profile_id_from_business_details( | ||||||
|  |                     payment_intent.business_country, | ||||||
|  |                     payment_intent.business_label.as_ref(), | ||||||
|  |                     merchant_account, | ||||||
|  |                     payment_intent.profile_id.as_ref(), | ||||||
|  |                     db, | ||||||
|  |                     false, | ||||||
|  |                 ) | ||||||
|  |                 .await | ||||||
|  |                 .change_context(errors::ApiErrorResponse::InternalServerError) | ||||||
|  |                 .attach_printable("profile_id is not set in payment_intent")?, | ||||||
|  |             }; | ||||||
|  |  | ||||||
|  |             db.find_merchant_connector_account_by_profile_id_connector_name( | ||||||
|  |                 &profile_id, | ||||||
|  |                 connector_name, | ||||||
|  |                 key_store, | ||||||
|  |             ) | ||||||
|  |             .await | ||||||
|  |             .to_not_found_response( | ||||||
|  |                 errors::ApiErrorResponse::MerchantConnectorAccountNotFound { | ||||||
|  |                     id: format!("profile_id {profile_id} and connector_name {connector_name}"), | ||||||
|  |                 }, | ||||||
|  |             ) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
| pub async fn get_mca_from_object_reference_id( | pub async fn get_mca_from_object_reference_id( | ||||||
|     db: &dyn StorageInterface, |     db: &dyn StorageInterface, | ||||||
|     object_reference_id: webhooks::ObjectReferenceId, |     object_reference_id: webhooks::ObjectReferenceId, | ||||||
| @ -330,8 +423,6 @@ pub async fn get_mca_from_object_reference_id( | |||||||
|     connector_name: &str, |     connector_name: &str, | ||||||
|     key_store: &domain::MerchantKeyStore, |     key_store: &domain::MerchantKeyStore, | ||||||
| ) -> CustomResult<domain::MerchantConnectorAccount, errors::ApiErrorResponse> { | ) -> CustomResult<domain::MerchantConnectorAccount, errors::ApiErrorResponse> { | ||||||
|     let merchant_id = merchant_account.merchant_id.clone(); |  | ||||||
|  |  | ||||||
|     match merchant_account.default_profile.as_ref() { |     match merchant_account.default_profile.as_ref() { | ||||||
|         Some(profile_id) => db |         Some(profile_id) => db | ||||||
|             .find_merchant_connector_account_by_profile_id_connector_name( |             .find_merchant_connector_account_by_profile_id_connector_name( | ||||||
| @ -343,78 +434,55 @@ pub async fn get_mca_from_object_reference_id( | |||||||
|             .to_not_found_response(errors::ApiErrorResponse::MerchantConnectorAccountNotFound { |             .to_not_found_response(errors::ApiErrorResponse::MerchantConnectorAccountNotFound { | ||||||
|                 id: format!("profile_id {profile_id} and connector_name {connector_name}"), |                 id: format!("profile_id {profile_id} and connector_name {connector_name}"), | ||||||
|             }), |             }), | ||||||
|         _ => { |         _ => match object_reference_id { | ||||||
|             let payment_intent = match object_reference_id { |  | ||||||
|             webhooks::ObjectReferenceId::PaymentId(payment_id_type) => { |             webhooks::ObjectReferenceId::PaymentId(payment_id_type) => { | ||||||
|  |                 get_mca_from_payment_intent( | ||||||
|  |                     db, | ||||||
|  |                     merchant_account, | ||||||
|                     find_payment_intent_from_payment_id_type(db, payment_id_type, merchant_account) |                     find_payment_intent_from_payment_id_type(db, payment_id_type, merchant_account) | ||||||
|                         .await? |                         .await?, | ||||||
|  |                     key_store, | ||||||
|  |                     connector_name, | ||||||
|  |                 ) | ||||||
|  |                 .await | ||||||
|             } |             } | ||||||
|             webhooks::ObjectReferenceId::RefundId(refund_id_type) => { |             webhooks::ObjectReferenceId::RefundId(refund_id_type) => { | ||||||
|  |                 get_mca_from_payment_intent( | ||||||
|  |                     db, | ||||||
|  |                     merchant_account, | ||||||
|                     find_payment_intent_from_refund_id_type( |                     find_payment_intent_from_refund_id_type( | ||||||
|                         db, |                         db, | ||||||
|                         refund_id_type, |                         refund_id_type, | ||||||
|                         merchant_account, |                         merchant_account, | ||||||
|                         connector_name, |                         connector_name, | ||||||
|                     ) |                     ) | ||||||
|                     .await? |                     .await?, | ||||||
|  |                     key_store, | ||||||
|  |                     connector_name, | ||||||
|  |                 ) | ||||||
|  |                 .await | ||||||
|             } |             } | ||||||
|             webhooks::ObjectReferenceId::MandateId(mandate_id_type) => { |             webhooks::ObjectReferenceId::MandateId(mandate_id_type) => { | ||||||
|                     find_payment_intent_from_mandate_id_type(db, mandate_id_type, merchant_account) |                 get_mca_from_payment_intent( | ||||||
|                         .await? |  | ||||||
|                 } |  | ||||||
|             }; |  | ||||||
|  |  | ||||||
|             let payment_attempt = db |  | ||||||
|                 .find_payment_attempt_by_attempt_id_merchant_id( |  | ||||||
|                     &payment_intent.active_attempt.get_id(), |  | ||||||
|                     &merchant_id, |  | ||||||
|                     merchant_account.storage_scheme, |  | ||||||
|                 ) |  | ||||||
|                 .await |  | ||||||
|                 .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?; |  | ||||||
|  |  | ||||||
|             match payment_attempt.merchant_connector_id { |  | ||||||
|                 Some(merchant_connector_id) => db |  | ||||||
|                     .find_by_merchant_connector_account_merchant_id_merchant_connector_id( |  | ||||||
|                         &merchant_id, |  | ||||||
|                         &merchant_connector_id, |  | ||||||
|                         key_store, |  | ||||||
|                     ) |  | ||||||
|                     .await |  | ||||||
|                     .to_not_found_response( |  | ||||||
|                         errors::ApiErrorResponse::MerchantConnectorAccountNotFound { |  | ||||||
|                             id: merchant_connector_id, |  | ||||||
|                         }, |  | ||||||
|                     ), |  | ||||||
|                 None => { |  | ||||||
|                     let profile_id = utils::get_profile_id_from_business_details( |  | ||||||
|                         payment_intent.business_country, |  | ||||||
|                         payment_intent.business_label.as_ref(), |  | ||||||
|                         merchant_account, |  | ||||||
|                         payment_intent.profile_id.as_ref(), |  | ||||||
|                     db, |                     db, | ||||||
|                         false, |                     merchant_account, | ||||||
|  |                     find_payment_intent_from_mandate_id_type(db, mandate_id_type, merchant_account) | ||||||
|  |                         .await?, | ||||||
|  |                     key_store, | ||||||
|  |                     connector_name, | ||||||
|                 ) |                 ) | ||||||
|                 .await |                 .await | ||||||
|                     .change_context(errors::ApiErrorResponse::InternalServerError) |             } | ||||||
|                     .attach_printable("profile_id is not set in payment_intent")?; |             webhooks::ObjectReferenceId::ExternalAuthenticationID(authentication_id_type) => { | ||||||
|  |                 find_mca_from_authentication_id_type( | ||||||
|                     db.find_merchant_connector_account_by_profile_id_connector_name( |                     db, | ||||||
|                         &profile_id, |                     authentication_id_type, | ||||||
|                         connector_name, |                     merchant_account, | ||||||
|                     key_store, |                     key_store, | ||||||
|                 ) |                 ) | ||||||
|                 .await |                 .await | ||||||
|                     .to_not_found_response( |             } | ||||||
|                         errors::ApiErrorResponse::MerchantConnectorAccountNotFound { |  | ||||||
|                             id: format!( |  | ||||||
|                                 "profile_id {profile_id} and connector_name {connector_name}" |  | ||||||
|                             ), |  | ||||||
|         }, |         }, | ||||||
|                     ) |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| @ -737,6 +805,18 @@ pub fn add_apple_pay_payment_status_metrics( | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | pub fn check_if_pull_mechanism_for_external_3ds_enabled_from_connector_metadata( | ||||||
|  |     metadata: Option<Value>, | ||||||
|  | ) -> bool { | ||||||
|  |     let external_three_ds_connector_metadata: Option<ExternalThreeDSConnectorMetadata> = metadata | ||||||
|  |             .parse_value("ExternalThreeDSConnectorMetadata") | ||||||
|  |             .map_err(|err| logger::warn!(parsing_error=?err,"Error while parsing ExternalThreeDSConnectorMetadata")) | ||||||
|  |             .ok(); | ||||||
|  |     external_three_ds_connector_metadata | ||||||
|  |         .and_then(|metadata| metadata.pull_mechanism_for_external_3ds_enabled) | ||||||
|  |         .unwrap_or(true) | ||||||
|  | } | ||||||
|  |  | ||||||
| #[allow(clippy::too_many_arguments)] | #[allow(clippy::too_many_arguments)] | ||||||
| pub async fn trigger_payments_webhook<F, Op>( | pub async fn trigger_payments_webhook<F, Op>( | ||||||
|     merchant_account: domain::MerchantAccount, |     merchant_account: domain::MerchantAccount, | ||||||
|  | |||||||
| @ -0,0 +1 @@ | |||||||
|  | ALTER TABLE authentication DROP COLUMN IF EXISTS profile_id; | ||||||
| @ -0,0 +1,2 @@ | |||||||
|  | -- Your SQL goes here | ||||||
|  | ALTER TABLE authentication ADD COLUMN profile_id VARCHAR(64) NOT NULL; | ||||||
| @ -0,0 +1 @@ | |||||||
|  | ALTER TABLE authentication DROP COLUMN IF EXISTS payment_id; | ||||||
| @ -0,0 +1,2 @@ | |||||||
|  | -- Your SQL goes here | ||||||
|  | ALTER TABLE authentication ADD COLUMN payment_id VARCHAR(255); | ||||||
| @ -0,0 +1 @@ | |||||||
|  | ALTER TABLE authentication DROP COLUMN IF EXISTS merchant_connector_id; | ||||||
| @ -0,0 +1,2 @@ | |||||||
|  | -- Your SQL goes here | ||||||
|  | ALTER TABLE authentication ADD COLUMN merchant_connector_id VARCHAR(128) NOT NULL; | ||||||
		Reference in New Issue
	
	Block a user