Bug: Implement verify flow for Adyen



Implement Verify flow for the connectors. Verify flow makes sure if the card is valid or not for future payments like mandate etc.
diff --git a/config/development.toml b/config/development.toml
index ad0b5891d23..3ef6509f46f 100644
--- a/config/development.toml
+++ b/config/development.toml
@@ -229,7 +229,7 @@ stripe = { long_lived_token = false, payment_method = "wallet", payment_method_t
 checkout = { long_lived_token = false, payment_method = "wallet"}
 
 [connector_customer]
-connector_list = "bluesnap, stripe"
+connector_list = "bluesnap,stripe"
 
 [dummy_connector]
 payment_ttl = 172800
diff --git a/crates/router/src/compatibility/stripe/errors.rs b/crates/router/src/compatibility/stripe/errors.rs
index f1099a09da8..57a466b66c2 100644
--- a/crates/router/src/compatibility/stripe/errors.rs
+++ b/crates/router/src/compatibility/stripe/errors.rs
@@ -400,7 +400,8 @@ impl From<errors::ApiErrorResponse> for StripeErrorCode {
             errors::ApiErrorResponse::RefundNotPossible { connector } => Self::RefundFailed,
             errors::ApiErrorResponse::RefundFailed { data } => Self::RefundFailed, // Nothing at stripe to map
 
-            errors::ApiErrorResponse::InternalServerError => Self::InternalServerError, // not a stripe code
+            errors::ApiErrorResponse::MandateUpdateFailed
+            | errors::ApiErrorResponse::InternalServerError => Self::InternalServerError, // not a stripe code
             errors::ApiErrorResponse::ExternalConnectorError {
                 code,
                 message,
diff --git a/crates/router/src/connector/adyen.rs b/crates/router/src/connector/adyen.rs
index 7f47e198248..2221f5c7582 100644
--- a/crates/router/src/connector/adyen.rs
+++ b/crates/router/src/connector/adyen.rs
@@ -81,7 +81,101 @@ impl
         types::PaymentsResponseData,
     > for Adyen
 {
-    // Issue: #173
+    fn get_headers(
+        &self,
+        req: &types::VerifyRouterData,
+        _connectors: &settings::Connectors,
+    ) -> CustomResult<Vec<(String, String)>, errors::ConnectorError> {
+        let mut header = vec![(
+            headers::CONTENT_TYPE.to_string(),
+            types::PaymentsVerifyType::get_content_type(self).to_string(),
+        )];
+        let mut api_key = self.get_auth_header(&req.connector_auth_type)?;
+        header.append(&mut api_key);
+        Ok(header)
+    }
+
+    fn get_url(
+        &self,
+        _req: &types::VerifyRouterData,
+        connectors: &settings::Connectors,
+    ) -> CustomResult<String, errors::ConnectorError> {
+        Ok(format!("{}{}", self.base_url(connectors), "v68/payments"))
+    }
+    fn get_request_body(
+        &self,
+        req: &types::VerifyRouterData,
+    ) -> CustomResult<Option<String>, errors::ConnectorError> {
+        let authorize_req = types::PaymentsAuthorizeRouterData::from((
+            req,
+            types::PaymentsAuthorizeData::from(req),
+        ));
+        let connector_req = adyen::AdyenPaymentRequest::try_from(&authorize_req)?;
+        let adyen_req = utils::Encode::<adyen::AdyenPaymentRequest<'_>>::encode_to_string_of_json(
+            &connector_req,
+        )
+        .change_context(errors::ConnectorError::RequestEncodingFailed)?;
+        Ok(Some(adyen_req))
+    }
+    fn build_request(
+        &self,
+        req: &types::VerifyRouterData,
+        connectors: &settings::Connectors,
+    ) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
+        Ok(Some(
+            services::RequestBuilder::new()
+                .method(services::Method::Post)
+                .url(&types::PaymentsVerifyType::get_url(self, req, connectors)?)
+                .attach_default_headers()
+                .headers(types::PaymentsVerifyType::get_headers(
+                    self, req, connectors,
+                )?)
+                .body(types::PaymentsVerifyType::get_request_body(self, req)?)
+                .build(),
+        ))
+    }
+    fn handle_response(
+        &self,
+        data: &types::VerifyRouterData,
+        res: types::Response,
+    ) -> CustomResult<
+        types::RouterData<api::Verify, types::VerifyRequestData, types::PaymentsResponseData>,
+        errors::ConnectorError,
+    >
+    where
+        api::Verify: Clone,
+        types::VerifyRequestData: Clone,
+        types::PaymentsResponseData: Clone,
+    {
+        let response: adyen::AdyenPaymentResponse = res
+            .response
+            .parse_struct("AdyenPaymentResponse")
+            .change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
+        types::RouterData::try_from((
+            types::ResponseRouterData {
+                response,
+                data: data.clone(),
+                http_code: res.status_code,
+            },
+            false,
+        ))
+        .change_context(errors::ConnectorError::ResponseHandlingFailed)
+    }
+    fn get_error_response(
+        &self,
+        res: types::Response,
+    ) -> CustomResult<types::ErrorResponse, errors::ConnectorError> {
+        let response: adyen::ErrorResponse = res
+            .response
+            .parse_struct("ErrorResponse")
+            .change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
+        Ok(types::ErrorResponse {
+            status_code: res.status_code,
+            code: response.error_code,
+            message: response.message,
+            reason: None,
+        })
+    }
 }
 
 impl api::PaymentSession for Adyen {}
diff --git a/crates/router/src/connector/adyen/transformers.rs b/crates/router/src/connector/adyen/transformers.rs
index 0d07eb72779..199ab239a3a 100644
--- a/crates/router/src/connector/adyen/transformers.rs
+++ b/crates/router/src/connector/adyen/transformers.rs
@@ -37,8 +37,8 @@ pub enum AdyenShopperInteraction {
     Pos,
 }
 
-#[derive(Debug, Serialize, Deserialize)]
-#[serde(rename_all = "camelCase")]
+#[derive(Debug, Clone, Serialize, Deserialize)]
+#[serde(rename_all = "PascalCase")]
 pub enum AdyenRecurringModel {
     UnscheduledCardOnFile,
     CardOnFile,
@@ -54,6 +54,8 @@ pub enum AuthType {
 pub struct AdditionalData {
     authorisation_type: Option<AuthType>,
     manual_capture: Option<bool>,
+    pub recurring_processing_model: Option<AdyenRecurringModel>,
+    /// Enable recurring details in dashboard to receive this ID, https://docs.adyen.com/online-payments/tokenization/create-and-use-tokens#test-and-go-live
     #[serde(rename = "recurring.recurringDetailReference")]
     recurring_detail_reference: Option<String>,
     #[serde(rename = "recurring.shopperReference")]
@@ -90,6 +92,7 @@ pub struct LineItem {
     quantity: Option<u16>,
 }
 
+#[serde_with::skip_serializing_none]
 #[derive(Debug, Serialize)]
 #[serde(rename_all = "camelCase")]
 pub struct AdyenPaymentRequest<'a> {
@@ -100,7 +103,6 @@ pub struct AdyenPaymentRequest<'a> {
     return_url: String,
     browser_info: Option<AdyenBrowserInfo>,
     shopper_interaction: AdyenShopperInteraction,
-    #[serde(skip_serializing_if = "Option::is_none")]
     recurring_processing_model: Option<AdyenRecurringModel>,
     additional_data: Option<AdditionalData>,
     shopper_reference: Option<String>,
@@ -279,6 +281,14 @@ pub enum AdyenPaymentMethod<'a> {
     WeChatPayWeb(Box<WeChatPayWebData>),
 }
 
+#[derive(Debug, Clone, Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct MandateData {
+    #[serde(rename = "type")]
+    payment_type: PaymentType,
+    stored_payment_method_id: String,
+}
+
 #[derive(Debug, Clone, Serialize)]
 pub struct WeChatPayWebData {
     #[serde(rename = "type")]
@@ -768,16 +778,22 @@ type RecurringDetails = (Option<AdyenRecurringModel>, Option<bool>, Option<Strin
 fn get_recurring_processing_model(
     item: &types::PaymentsAuthorizeRouterData,
 ) -> Result<RecurringDetails, Error> {
-    match item.request.setup_future_usage {
-        Some(storage_enums::FutureUsage::OffSession) => {
+    match (item.request.setup_future_usage, item.request.off_session) {
+        (Some(storage_enums::FutureUsage::OffSession), _) => {
             let customer_id = item.get_customer_id()?;
             let shopper_reference = format!("{}_{}", item.merchant_id, customer_id);
+            let store_payment_method = item.request.is_mandate_payment();
             Ok((
                 Some(AdyenRecurringModel::UnscheduledCardOnFile),
-                Some(true),
+                Some(store_payment_method),
                 Some(shopper_reference),
             ))
         }
+        (_, Some(true)) => Ok((
+            Some(AdyenRecurringModel::UnscheduledCardOnFile),
+            None,
+            Some(format!("{}_{}", item.merchant_id, item.get_customer_id()?)),
+        )),
         _ => Ok((None, None, None)),
     }
 }
@@ -812,6 +828,7 @@ fn get_additional_data(item: &types::PaymentsAuthorizeRouterData) -> Option<Addi
             network_tx_reference: None,
             recurring_detail_reference: None,
             recurring_shopper_reference: None,
+            recurring_processing_model: Some(AdyenRecurringModel::UnscheduledCardOnFile),
         }),
         _ => None,
     }
@@ -1163,7 +1180,6 @@ impl<'a> TryFrom<(&types::PaymentsAuthorizeRouterData, MandateReferenceId)>
         })
     }
 }
-
 impl<'a> TryFrom<(&types::PaymentsAuthorizeRouterData, &api::Card)> for AdyenPaymentRequest<'a> {
     type Error = Error;
     fn try_from(
diff --git a/crates/router/src/connector/airwallex.rs b/crates/router/src/connector/airwallex.rs
index d6636d162dd..3f7d4734b4c 100644
--- a/crates/router/src/connector/airwallex.rs
+++ b/crates/router/src/connector/airwallex.rs
@@ -288,8 +288,10 @@ impl ConnectorIntegration<api::Authorize, types::PaymentsAuthorizeData, types::P
                   + Sync
                   + 'static),
         > = Box::new(&Self);
-        let authorize_data =
-            &types::PaymentsInitRouterData::from((&router_data, router_data.request.clone()));
+        let authorize_data = &types::PaymentsInitRouterData::from((
+            &router_data.to_owned(),
+            router_data.request.clone(),
+        ));
         let resp = services::execute_connector_processing_step(
             app_state,
             integ,
diff --git a/crates/router/src/connector/nuvei.rs b/crates/router/src/connector/nuvei.rs
index 576c8cece64..97f26f6c970 100644
--- a/crates/router/src/connector/nuvei.rs
+++ b/crates/router/src/connector/nuvei.rs
@@ -475,7 +475,7 @@ impl ConnectorIntegration<api::Authorize, types::PaymentsAuthorizeData, types::P
                   + 'static),
         > = Box::new(&Self);
         let authorize_data = &types::PaymentsAuthorizeSessionTokenRouterData::from((
-            &router_data,
+            &router_data.to_owned(),
             types::AuthorizeSessionTokenData::from(&router_data),
         ));
         let resp = services::execute_connector_processing_step(
@@ -502,7 +502,7 @@ impl ConnectorIntegration<api::Authorize, types::PaymentsAuthorizeData, types::P
                               + 'static),
                     > = Box::new(&Self);
                     let init_data = &types::PaymentsInitRouterData::from((
-                        &router_data,
+                        &router_data.to_owned(),
                         router_data.request.clone(),
                     ));
                     let init_resp = services::execute_connector_processing_step(
diff --git a/crates/router/src/connector/shift4.rs b/crates/router/src/connector/shift4.rs
index 3f9ac055737..c189535d372 100644
--- a/crates/router/src/connector/shift4.rs
+++ b/crates/router/src/connector/shift4.rs
@@ -183,7 +183,7 @@ impl ConnectorIntegration<api::Authorize, types::PaymentsAuthorizeData, types::P
                           + 'static),
                 > = Box::new(&Self);
                 let init_data = &types::PaymentsInitRouterData::from((
-                    &router_data,
+                    &router_data.to_owned(),
                     router_data.request.clone(),
                 ));
                 let init_resp = services::execute_connector_processing_step(
diff --git a/crates/router/src/core/errors/api_error_response.rs b/crates/router/src/core/errors/api_error_response.rs
index 729ba183169..0da5cebc302 100644
--- a/crates/router/src/core/errors/api_error_response.rs
+++ b/crates/router/src/core/errors/api_error_response.rs
@@ -142,6 +142,8 @@ pub enum ApiErrorResponse {
     ResourceIdNotFound,
     #[error(error_type = ErrorType::ObjectNotFound, code = "HE_02", message = "Mandate does not exist in our records")]
     MandateNotFound,
+    #[error(error_type = ErrorType::ObjectNotFound, code = "HE_02", message = "Failed to update mandate")]
+    MandateUpdateFailed,
     #[error(error_type = ErrorType::ObjectNotFound, code = "HE_02", message = "API Key does not exist in our records")]
     ApiKeyNotFound,
     #[error(error_type = ErrorType::ValidationError, code = "HE_03", message = "Return URL is not configured and not passed in payments request")]
@@ -246,7 +248,9 @@ impl actix_web::ResponseError for ApiErrorResponse {
             | Self::PaymentUnexpectedState { .. }
             | Self::MandateValidationFailed { .. } => StatusCode::BAD_REQUEST, // 400
 
-            Self::InternalServerError => StatusCode::INTERNAL_SERVER_ERROR, // 500
+            Self::MandateUpdateFailed | Self::InternalServerError => {
+                StatusCode::INTERNAL_SERVER_ERROR
+            } // 500
             Self::DuplicateRefundRequest | Self::DuplicatePayment { .. } => StatusCode::BAD_REQUEST, // 400
             Self::RefundNotFound
             | Self::CustomerNotFound
@@ -402,8 +406,8 @@ impl common_utils::errors::ErrorSwitch<api_models::errors::types::ApiErrorRespon
             Self::RefundFailed { data } => AER::BadRequest(ApiError::new("CE", 6, "Refund failed while processing with connector. Retry refund", Some(Extra { data: data.clone(), ..Default::default()}))),
             Self::VerificationFailed { data } => {
                 AER::BadRequest(ApiError::new("CE", 7, "Verification failed while processing with connector. Retry operation", Some(Extra { data: data.clone(), ..Default::default()})))
-            }
-            Self::InternalServerError => {
+            },
+            Self::MandateUpdateFailed | Self::InternalServerError => {
                 AER::InternalServerError(ApiError::new("HE", 0, "Something went wrong", None))
             }
             Self::DuplicateRefundRequest => AER::BadRequest(ApiError::new("HE", 1, "Duplicate refund request. Refund already attempted with the refund ID", None)),
diff --git a/crates/router/src/core/errors/utils.rs b/crates/router/src/core/errors/utils.rs
index 87d9e38a2c0..b4c7f10ccfd 100644
--- a/crates/router/src/core/errors/utils.rs
+++ b/crates/router/src/core/errors/utils.rs
@@ -123,7 +123,7 @@ impl ConnectorErrorExt for error_stack::Report<errors::ConnectorError> {
         let data = match error {
             errors::ConnectorError::ProcessingStepFailed(Some(bytes)) => {
                 let response_str = std::str::from_utf8(bytes);
-                match response_str {
+                let error_response = match response_str {
                     Ok(s) => serde_json::from_str(s)
                         .map_err(|err| logger::error!(%err, "Failed to convert response to JSON"))
                         .ok(),
@@ -131,14 +131,20 @@ impl ConnectorErrorExt for error_stack::Report<errors::ConnectorError> {
                         logger::error!(%err, "Failed to convert response to UTF8 string");
                         None
                     }
+                };
+                errors::ApiErrorResponse::PaymentAuthorizationFailed {
+                    data: error_response,
                 }
             }
+            errors::ConnectorError::MissingRequiredField { field_name } => {
+                errors::ApiErrorResponse::MissingRequiredField { field_name }
+            }
             _ => {
                 logger::error!(%error,"Verify flow failed");
-                None
+                errors::ApiErrorResponse::PaymentAuthorizationFailed { data: None }
             }
         };
-        self.change_context(errors::ApiErrorResponse::PaymentAuthorizationFailed { data })
+        self.change_context(data)
     }
 }
 
diff --git a/crates/router/src/core/mandate.rs b/crates/router/src/core/mandate.rs
index 02269d6707c..a5afa04d348 100644
--- a/crates/router/src/core/mandate.rs
+++ b/crates/router/src/core/mandate.rs
@@ -1,4 +1,4 @@
-use common_utils::ext_traits::Encode;
+use common_utils::{ext_traits::Encode, pii};
 use error_stack::{report, ResultExt};
 use router_env::{instrument, logger, tracing};
 use storage_models::enums as storage_enums;
@@ -16,7 +16,7 @@ use crate::{
             mandates::{self, MandateResponseExt},
         },
         storage,
-        transformers::ForeignInto,
+        transformers::{ForeignInto, ForeignTryFrom},
     },
     utils::OptionExt,
 };
@@ -62,6 +62,37 @@ pub async fn revoke_mandate(
     ))
 }
 
+#[instrument(skip(db))]
+pub async fn update_connector_mandate_id(
+    db: &dyn StorageInterface,
+    merchant_account: String,
+    mandate_ids_opt: Option<api_models::payments::MandateIds>,
+    resp: Result<types::PaymentsResponseData, types::ErrorResponse>,
+) -> RouterResponse<mandates::MandateResponse> {
+    let connector_mandate_id = Option::foreign_try_from(resp)?;
+    //Ignore updation if the payment_attempt mandate_id or connector_mandate_id is not present
+    if let Some((mandate_ids, connector_id)) = mandate_ids_opt.zip(connector_mandate_id) {
+        let mandate_id = &mandate_ids.mandate_id;
+        let mandate = db
+            .find_mandate_by_merchant_id_mandate_id(&merchant_account, mandate_id)
+            .await
+            .change_context(errors::ApiErrorResponse::MandateNotFound)?;
+        // only update the connector_mandate_id if existing is none
+        if mandate.connector_mandate_id.is_none() {
+            db.update_mandate_by_merchant_id_mandate_id(
+                &merchant_account,
+                mandate_id,
+                storage::MandateUpdate::ConnectorReferenceUpdate {
+                    connector_mandate_ids: Some(connector_id),
+                },
+            )
+            .await
+            .change_context(errors::ApiErrorResponse::MandateUpdateFailed)?;
+        }
+    }
+    Ok(services::ApplicationResponse::StatusOk)
+}
+
 #[instrument(skip(state))]
 pub async fn get_customer_mandates(
     state: &AppState,
@@ -122,7 +153,7 @@ where
                         },
                     )
                     .await
-                    .change_context(errors::ApiErrorResponse::MandateNotFound),
+                    .change_context(errors::ApiErrorResponse::MandateUpdateFailed),
                 storage_enums::MandateType::MultiUse => state
                     .store
                     .update_mandate_by_merchant_id_mandate_id(
@@ -135,7 +166,7 @@ where
                         },
                     )
                     .await
-                    .change_context(errors::ApiErrorResponse::MandateNotFound),
+                    .change_context(errors::ApiErrorResponse::MandateUpdateFailed),
             }?;
             metrics::SUBSEQUENT_MANDATE_PAYMENT.add(
                 &metrics::CONTEXT,
@@ -223,6 +254,30 @@ where
     Ok(resp)
 }
 
+impl ForeignTryFrom<Result<types::PaymentsResponseData, types::ErrorResponse>>
+    for Option<pii::SecretSerdeValue>
+{
+    type Error = error_stack::Report<errors::ApiErrorResponse>;
+    fn foreign_try_from(
+        resp: Result<types::PaymentsResponseData, types::ErrorResponse>,
+    ) -> errors::RouterResult<Self> {
+        let mandate_details = match resp {
+            Ok(types::PaymentsResponseData::TransactionResponse {
+                mandate_reference, ..
+            }) => mandate_reference,
+            _ => None,
+        };
+
+        mandate_details
+            .map(|md| {
+                Encode::<types::MandateReference>::encode_to_value(&md)
+                    .change_context(errors::ApiErrorResponse::MandateNotFound)
+                    .map(masking::Secret::new)
+            })
+            .transpose()
+    }
+}
+
 pub trait MandateBehaviour {
     fn get_amount(&self) -> i64;
     fn get_setup_future_usage(&self) -> Option<storage_models::enums::FutureUsage>;
diff --git a/crates/router/src/core/payments/operations/payment_response.rs b/crates/router/src/core/payments/operations/payment_response.rs
index 6af82659fe7..e7ed3384d90 100644
--- a/crates/router/src/core/payments/operations/payment_response.rs
+++ b/crates/router/src/core/payments/operations/payment_response.rs
@@ -7,6 +7,7 @@ use super::{Operation, PostUpdateTracker};
 use crate::{
     core::{
         errors::{self, RouterResult, StorageErrorExt},
+        mandate,
         payments::PaymentData,
     },
     db::StorageInterface,
@@ -443,5 +444,14 @@ async fn payment_response_update_tracker<F: Clone, T>(
         .await
         .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?;
 
+    // When connector requires redirection for mandate creation it can update the connector mandate_id during Psync
+    mandate::update_connector_mandate_id(
+        db,
+        router_data.merchant_id,
+        payment_data.mandate_id.clone(),
+        router_data.response.clone(),
+    )
+    .await?;
+
     Ok(payment_data)
 }
diff --git a/crates/router/src/core/payments/operations/payment_status.rs b/crates/router/src/core/payments/operations/payment_status.rs
index ec25f5995e6..78bab09e006 100644
--- a/crates/router/src/core/payments/operations/payment_status.rs
+++ b/crates/router/src/core/payments/operations/payment_status.rs
@@ -252,7 +252,12 @@ async fn get_tracker_for_sync<
             currency,
             amount,
             email: None,
-            mandate_id: None,
+            mandate_id: payment_attempt.mandate_id.clone().map(|id| {
+                api_models::payments::MandateIds {
+                    mandate_id: id,
+                    mandate_reference_id: None,
+                }
+            }),
             setup_mandate: None,
             token: None,
             address: PaymentAddress {
diff --git a/crates/router/src/core/payments/transformers.rs b/crates/router/src/core/payments/transformers.rs
index 04ab21228cb..7945ef1d537 100644
--- a/crates/router/src/core/payments/transformers.rs
+++ b/crates/router/src/core/payments/transformers.rs
@@ -570,6 +570,7 @@ impl<F: Clone> TryFrom<PaymentAdditionalData<'_, F>> for types::PaymentsSyncData
     fn try_from(additional_data: PaymentAdditionalData<'_, F>) -> Result<Self, Self::Error> {
         let payment_data = additional_data.payment_data;
         Ok(Self {
+            mandate_id: payment_data.mandate_id.clone(),
             connector_transaction_id: match payment_data.payment_attempt.connector_transaction_id {
                 Some(connector_txn_id) => {
                     types::ResponseId::ConnectorTransactionId(connector_txn_id)
@@ -698,6 +699,15 @@ impl<F: Clone> TryFrom<PaymentAdditionalData<'_, F>> for types::VerifyRequestDat
 
     fn try_from(additional_data: PaymentAdditionalData<'_, F>) -> Result<Self, Self::Error> {
         let payment_data = additional_data.payment_data;
+        let router_base_url = &additional_data.router_base_url;
+        let connector_name = &additional_data.connector_name;
+        let attempt = &payment_data.payment_attempt;
+        let router_return_url = Some(helpers::create_redirect_url(
+            router_base_url,
+            attempt,
+            connector_name,
+            payment_data.creds_identifier.as_deref(),
+        ));
         Ok(Self {
             currency: payment_data.currency,
             confirm: true,
@@ -709,6 +719,7 @@ impl<F: Clone> TryFrom<PaymentAdditionalData<'_, F>> for types::VerifyRequestDat
             off_session: payment_data.mandate_id.as_ref().map(|_| true),
             mandate_id: payment_data.mandate_id.clone(),
             setup_mandate_details: payment_data.setup_mandate,
+            router_return_url,
             email: payment_data.email,
             return_url: payment_data.payment_intent.return_url,
         })
diff --git a/crates/router/src/types.rs b/crates/router/src/types.rs
index 34a705d1228..d0750db413b 100644
--- a/crates/router/src/types.rs
+++ b/crates/router/src/types.rs
@@ -75,6 +75,8 @@ pub type RefundsResponseRouterData<F, R> =
 
 pub type PaymentsAuthorizeType =
     dyn services::ConnectorIntegration<api::Authorize, PaymentsAuthorizeData, PaymentsResponseData>;
+pub type PaymentsVerifyType =
+    dyn services::ConnectorIntegration<api::Verify, VerifyRequestData, PaymentsResponseData>;
 pub type PaymentsCompleteAuthorizeType = dyn services::ConnectorIntegration<
     api::CompleteAuthorize,
     CompleteAuthorizeData,
@@ -270,6 +272,7 @@ pub struct PaymentsSyncData {
     pub encoded_data: Option<String>,
     pub capture_method: Option<storage_enums::CaptureMethod>,
     pub connector_meta: Option<serde_json::Value>,
+    pub mandate_id: Option<api_models::payments::MandateIds>,
 }
 
 #[derive(Debug, Default, Clone)]
@@ -299,6 +302,7 @@ pub struct VerifyRequestData {
     pub setup_future_usage: Option<storage_enums::FutureUsage>,
     pub off_session: Option<bool>,
     pub setup_mandate_details: Option<payments::MandateData>,
+    pub router_return_url: Option<String>,
     pub email: Option<Email>,
     pub return_url: Option<String>,
 }
@@ -646,10 +650,39 @@ impl From<&&mut PaymentsAuthorizeRouterData> for AuthorizeSessionTokenData {
     }
 }
 
-impl<F1, F2, T1, T2> From<(&&mut RouterData<F1, T1, PaymentsResponseData>, T2)>
+impl From<&VerifyRouterData> for PaymentsAuthorizeData {
+    fn from(data: &VerifyRouterData) -> Self {
+        Self {
+            currency: data.request.currency,
+            payment_method_data: data.request.payment_method_data.clone(),
+            confirm: data.request.confirm,
+            statement_descriptor_suffix: data.request.statement_descriptor_suffix.clone(),
+            mandate_id: data.request.mandate_id.clone(),
+            setup_future_usage: data.request.setup_future_usage,
+            off_session: data.request.off_session,
+            setup_mandate_details: data.request.setup_mandate_details.clone(),
+            router_return_url: data.request.router_return_url.clone(),
+            email: data.request.email.clone(),
+            amount: 0,
+            statement_descriptor: None,
+            capture_method: None,
+            webhook_url: None,
+            complete_authorize_url: None,
+            browser_info: None,
+            order_details: None,
+            session_token: None,
+            enrolled_for_3ds: true,
+            related_transaction_id: None,
+            payment_experience: None,
+            payment_method_type: None,
+        }
+    }
+}
+
+impl<F1, F2, T1, T2> From<(&RouterData<F1, T1, PaymentsResponseData>, T2)>
     for RouterData<F2, T2, PaymentsResponseData>
 {
-    fn from(item: (&&mut RouterData<F1, T1, PaymentsResponseData>, T2)) -> Self {
+    fn from(item: (&RouterData<F1, T1, PaymentsResponseData>, T2)) -> Self {
         let data = item.0;
         let request = item.1;
         Self {
diff --git a/crates/router/tests/connectors/adyen_ui.rs b/crates/router/tests/connectors/adyen_ui.rs
new file mode 100644
index 00000000000..4a2b840f68f
--- /dev/null
+++ b/crates/router/tests/connectors/adyen_ui.rs
@@ -0,0 +1,104 @@
+use serial_test::serial;
+use thirtyfour::{prelude::*, WebDriver};
+
+use crate::{selenium::*, tester};
+
+struct AdyenSeleniumTest;
+
+impl SeleniumTest for AdyenSeleniumTest {}
+
+async fn should_make_adyen_gpay_payment(c: WebDriver) -> Result<(), WebDriverError> {
+    let conn = AdyenSeleniumTest {};
+    conn.make_gpay_payment(c,
+        &format!("{CHEKOUT_BASE_URL}/gpay?gatewayname=adyen&gatewaymerchantid=JuspayDEECOM&amount=70.00&country=US&currency=USD"),
+        vec![
+        Event::Assert(Assert::IsPresent("succeeded")),
+    ]).await?;
+    Ok(())
+}
+
+async fn should_make_adyen_gpay_mandate_payment(c: WebDriver) -> Result<(), WebDriverError> {
+    let conn = AdyenSeleniumTest {};
+    conn.make_gpay_payment(c,
+        &format!("{CHEKOUT_BASE_URL}/gpay?gatewayname=adyen&gatewaymerchantid=JuspayDEECOM&amount=70.00&country=US&currency=USD&mandate_data[customer_acceptance][acceptance_type]=offline&mandate_data[customer_acceptance][accepted_at]=1963-05-03T04:07:52.723Z&mandate_data[customer_acceptance][online][ip_address]=127.0.0.1&mandate_data[customer_acceptance][online][user_agent]=amet%20irure%20esse&mandate_data[mandate_type][multi_use][amount]=7000&mandate_data[mandate_type][multi_use][currency]=USD"),
+        vec![
+        Event::Assert(Assert::IsPresent("succeeded")),
+        Event::Assert(Assert::IsPresent("Mandate ID")),
+        Event::Assert(Assert::IsPresent("man_")),// mandate id starting with man_
+        Event::Trigger(Trigger::Click(By::Id("pm-mandate-btn"))),
+        Event::Trigger(Trigger::Click(By::Id("pay-with-mandate-btn"))),
+        Event::Assert(Assert::IsPresent("succeeded")),
+    ]).await?;
+    Ok(())
+}
+
+async fn should_make_adyen_gpay_zero_dollar_mandate_payment(
+    c: WebDriver,
+) -> Result<(), WebDriverError> {
+    let conn = AdyenSeleniumTest {};
+    conn.make_gpay_payment(c,
+        &format!("{CHEKOUT_BASE_URL}/gpay?gatewayname=adyen&gatewaymerchantid=JuspayDEECOM&amount=0.00&country=US&currency=USD&mandate_data[customer_acceptance][acceptance_type]=offline&mandate_data[customer_acceptance][accepted_at]=1963-05-03T04:07:52.723Z&mandate_data[customer_acceptance][online][ip_address]=127.0.0.1&mandate_data[customer_acceptance][online][user_agent]=amet%20irure%20esse&mandate_data[mandate_type][multi_use][amount]=700&mandate_data[mandate_type][multi_use][currency]=USD"),
+        vec![
+        Event::Assert(Assert::IsPresent("succeeded")),
+        Event::Assert(Assert::IsPresent("Mandate ID")),
+        Event::Assert(Assert::IsPresent("man_")),// mandate id starting with man_
+        Event::Trigger(Trigger::Click(By::Id("pm-mandate-btn"))),
+        Event::Trigger(Trigger::Click(By::Id("pay-with-mandate-btn"))),
+        Event::Assert(Assert::IsPresent("succeeded")),
+    ]).await?;
+    Ok(())
+}
+
+async fn should_make_adyen_klarna_mandate_payment(c: WebDriver) -> Result<(), WebDriverError> {
+    let conn = AdyenSeleniumTest {};
+    conn.make_redirection_payment(c,
+        vec![
+            Event::Trigger(Trigger::Goto(&format!("{CHEKOUT_BASE_URL}/klarna-redirect?amount=70.00&country=SE&currency=SEK&mandate_data[customer_acceptance][acceptance_type]=offline&mandate_data[customer_acceptance][accepted_at]=1963-05-03T04:07:52.723Z&mandate_data[customer_acceptance][online][ip_address]=127.0.0.1&mandate_data[customer_acceptance][online][user_agent]=amet%20irure%20esse&mandate_data[mandate_type][multi_use][amount]=700&mandate_data[mandate_type][multi_use][currency]=SEK&return_url={CHEKOUT_BASE_URL}/payments"))),
+            Event::Trigger(Trigger::Click(By::Id("klarna-redirect-btn"))),
+            Event::Trigger(Trigger::SwitchFrame(By::Id("klarna-apf-iframe"))),
+            Event::Trigger(Trigger::Click(By::Id("signInWithBankId"))),
+            Event::Assert(Assert::IsPresent("Klart att betala")),
+            Event::EitherOr(Assert::IsPresent("Klart att betala"), vec![
+                Event::Trigger(Trigger::Click(By::Css("button[data-testid='confirm-and-pay']"))),
+            ],
+            vec![
+                Event::Trigger(Trigger::Click(By::Css("button[data-testid='SmoothCheckoutPopUp:skip']"))),
+                Event::Trigger(Trigger::Click(By::Css("button[data-testid='confirm-and-pay']"))),
+            ]
+            ),
+            Event::Trigger(Trigger::SwitchTab(Position::Prev)),
+            Event::Assert(Assert::IsPresent("succeeded")),
+            Event::Assert(Assert::IsPresent("Mandate ID")),
+            Event::Assert(Assert::IsPresent("man_")),// mandate id starting with man_
+            Event::Trigger(Trigger::Click(By::Id("pm-mandate-btn"))),
+            Event::Trigger(Trigger::Click(By::Id("pay-with-mandate-btn"))),
+            Event::Assert(Assert::IsPresent("succeeded")),
+    ]).await?;
+    Ok(())
+}
+
+#[test]
+#[serial]
+fn should_make_adyen_gpay_payment_test() {
+    tester!(should_make_adyen_gpay_payment);
+}
+
+#[test]
+#[serial]
+fn should_make_adyen_gpay_mandate_payment_test() {
+    tester!(should_make_adyen_gpay_mandate_payment);
+}
+
+#[test]
+#[serial]
+fn should_make_adyen_gpay_zero_dollar_mandate_payment_test() {
+    tester!(should_make_adyen_gpay_zero_dollar_mandate_payment);
+}
+
+#[test]
+#[serial]
+fn should_make_adyen_klarna_mandate_payment_test() {
+    tester!(should_make_adyen_klarna_mandate_payment);
+}
+
+// https://hs-payments-test.netlify.app/paypal-redirect?amount=70.00&country=US&currency=USD&mandate_data[customer_acceptance][acceptance_type]=offline&mandate_data[customer_acceptance][accepted_at]=1963-05-03T04:07:52.723Z&mandate_data[customer_acceptance][online][ip_address]=127.0.0.1&mandate_data[customer_acceptance][online][user_agent]=amet%20irure%20esse&mandate_data[mandate_type][multi_use][amount]=700&mandate_data[mandate_type][multi_use][currency]=USD&apikey=dev_uFpxA0r6jjbVaxHSY3X0BZLL3erDUzvg3i51abwB1Bknu3fdiPxw475DQgnByn1z
diff --git a/crates/router/tests/connectors/bambora.rs b/crates/router/tests/connectors/bambora.rs
index 83a327aaf85..b27480825db 100644
--- a/crates/router/tests/connectors/bambora.rs
+++ b/crates/router/tests/connectors/bambora.rs
@@ -97,6 +97,7 @@ async fn should_sync_authorized_payment() {
         .psync_retry_till_status_matches(
             enums::AttemptStatus::Authorized,
             Some(types::PaymentsSyncData {
+                mandate_id: None,
                 connector_transaction_id: router::types::ResponseId::ConnectorTransactionId(
                     txn_id.unwrap(),
                 ),
@@ -209,6 +210,7 @@ async fn should_sync_auto_captured_payment() {
         .psync_retry_till_status_matches(
             enums::AttemptStatus::Charged,
             Some(types::PaymentsSyncData {
+                mandate_id: None,
                 connector_transaction_id: router::types::ResponseId::ConnectorTransactionId(
                     txn_id.unwrap(),
                 ),
diff --git a/crates/router/tests/connectors/forte.rs b/crates/router/tests/connectors/forte.rs
index 57fa0175000..7fdebd2eb44 100644
--- a/crates/router/tests/connectors/forte.rs
+++ b/crates/router/tests/connectors/forte.rs
@@ -149,6 +149,7 @@ async fn should_sync_authorized_payment() {
                 encoded_data: None,
                 capture_method: None,
                 connector_meta: None,
+                mandate_id: None,
             }),
             get_default_payment_info(),
         )
diff --git a/crates/router/tests/connectors/main.rs b/crates/router/tests/connectors/main.rs
index 95b79c6166d..6bda1098fe4 100644
--- a/crates/router/tests/connectors/main.rs
+++ b/crates/router/tests/connectors/main.rs
@@ -2,6 +2,7 @@
 
 mod aci;
 mod adyen;
+mod adyen_ui;
 mod airwallex;
 mod authorizedotnet;
 mod bambora;
diff --git a/crates/router/tests/connectors/nexinets.rs b/crates/router/tests/connectors/nexinets.rs
index 86817c2e588..2f3641fa992 100644
--- a/crates/router/tests/connectors/nexinets.rs
+++ b/crates/router/tests/connectors/nexinets.rs
@@ -117,6 +117,7 @@ async fn should_sync_authorized_payment() {
                 encoded_data: None,
                 capture_method: None,
                 connector_meta,
+                mandate_id: None,
             }),
             None,
         )
diff --git a/crates/router/tests/connectors/paypal.rs b/crates/router/tests/connectors/paypal.rs
index 15b52c9d349..ba00e06744e 100644
--- a/crates/router/tests/connectors/paypal.rs
+++ b/crates/router/tests/connectors/paypal.rs
@@ -131,6 +131,7 @@ async fn should_sync_authorized_payment() {
         .psync_retry_till_status_matches(
             enums::AttemptStatus::Authorized,
             Some(types::PaymentsSyncData {
+                mandate_id: None,
                 connector_transaction_id: router::types::ResponseId::ConnectorTransactionId(txn_id),
                 encoded_data: None,
                 capture_method: None,
@@ -320,6 +321,7 @@ async fn should_sync_auto_captured_payment() {
         .psync_retry_till_status_matches(
             enums::AttemptStatus::Charged,
             Some(types::PaymentsSyncData {
+                mandate_id: None,
                 connector_transaction_id: router::types::ResponseId::ConnectorTransactionId(
                     txn_id.unwrap(),
                 ),
diff --git a/crates/router/tests/connectors/selenium.rs b/crates/router/tests/connectors/selenium.rs
index c599965feff..fd71cc531fd 100644
--- a/crates/router/tests/connectors/selenium.rs
+++ b/crates/router/tests/connectors/selenium.rs
@@ -39,6 +39,7 @@ pub enum Assert<'a> {
     Eq(Selector, &'a str),
     Contains(Selector, &'a str),
     IsPresent(&'a str),
+    IsPresentNow(&'a str),
 }
 
 pub static CHEKOUT_BASE_URL: &str = "https://hs-payments-test.netlify.app";
@@ -64,6 +65,9 @@ pub trait SeleniumTest {
                     Assert::IsPresent(text) => {
                         assert!(is_text_present(driver, text).await?)
                     }
+                    Assert::IsPresentNow(text) => {
+                        assert!(is_text_present_now(driver, text).await?)
+                    }
                 },
                 Event::RunIf(con_event, events) => match con_event {
                     Assert::Contains(selector, text) => match selector {
@@ -85,6 +89,11 @@ pub trait SeleniumTest {
                             self.complete_actions(driver, events).await?;
                         }
                     }
+                    Assert::IsPresentNow(text) => {
+                        if is_text_present_now(driver, text).await.is_ok() {
+                            self.complete_actions(driver, events).await?;
+                        }
+                    }
                 },
                 Event::EitherOr(con_event, success, failure) => match con_event {
                     Assert::Contains(selector, text) => match selector {
@@ -124,6 +133,17 @@ pub trait SeleniumTest {
                         )
                         .await?;
                     }
+                    Assert::IsPresentNow(text) => {
+                        self.complete_actions(
+                            driver,
+                            if is_text_present_now(driver, text).await.is_ok() {
+                                success
+                            } else {
+                                failure
+                            },
+                        )
+                        .await?;
+                    }
                 },
                 Event::Trigger(trigger) => match trigger {
                     Trigger::Goto(url) => {
@@ -143,12 +163,14 @@ pub trait SeleniumTest {
                         let ele = driver.query(by).first().await?;
                         ele.wait_until().displayed().await?;
                         ele.wait_until().clickable().await?;
+                        ele.wait_until().enabled().await?;
                         ele.click().await?;
                     }
                     Trigger::ClickNth(by, n) => {
                         let ele = driver.query(by).all().await?.into_iter().nth(n).unwrap();
                         ele.wait_until().displayed().await?;
                         ele.wait_until().clickable().await?;
+                        ele.wait_until().enabled().await?;
                         ele.click().await?;
                     }
                     Trigger::Find(by) => {
@@ -226,8 +248,9 @@ pub trait SeleniumTest {
             Event::Trigger(Trigger::Goto(url)),
             Event::Trigger(Trigger::Click(By::Css(".gpay-button"))),
             Event::Trigger(Trigger::SwitchTab(Position::Next)),
+            Event::Trigger(Trigger::Sleep(5)),
             Event::RunIf(
-                Assert::IsPresent("Sign in"),
+                Assert::IsPresentNow("Sign in"),
                 vec![
                     Event::Trigger(Trigger::SendKeys(By::Id("identifierId"), email)),
                     Event::Trigger(Trigger::ClickNth(By::Tag("button"), 2)),
@@ -248,7 +271,6 @@ pub trait SeleniumTest {
                     ),
                 ],
             ),
-            Event::Trigger(Trigger::Sleep(5)),
             Event::Trigger(Trigger::SwitchFrame(By::Id("sM432dIframe"))),
             Event::Assert(Assert::IsPresent("Gpay Tester")),
             Event::Trigger(Trigger::Click(By::ClassName("jfk-button-action"))),
@@ -295,6 +317,13 @@ pub trait SeleniumTest {
         self.complete_actions(&c, pypl_actions).await
     }
 }
+async fn is_text_present_now(driver: &WebDriver, key: &str) -> WebDriverResult<bool> {
+    let mut xpath = "//*[contains(text(),'".to_owned();
+    xpath.push_str(key);
+    xpath.push_str("')]");
+    let result = driver.find(By::XPath(&xpath)).await?;
+    result.is_present().await
+}
 async fn is_text_present(driver: &WebDriver, key: &str) -> WebDriverResult<bool> {
     let mut xpath = "//*[contains(text(),'".to_owned();
     xpath.push_str(key);
diff --git a/crates/router/tests/connectors/utils.rs b/crates/router/tests/connectors/utils.rs
index c5815819e9f..8b3f2d51090 100644
--- a/crates/router/tests/connectors/utils.rs
+++ b/crates/router/tests/connectors/utils.rs
@@ -557,6 +557,7 @@ impl Default for BrowserInfoType {
 impl Default for PaymentSyncType {
     fn default() -> Self {
         let data = types::PaymentsSyncData {
+            mandate_id: None,
             connector_transaction_id: types::ResponseId::ConnectorTransactionId(
                 "12345".to_string(),
             ),
diff --git a/crates/router/tests/connectors/zen.rs b/crates/router/tests/connectors/zen.rs
index 4130a73990c..58df15a052f 100644
--- a/crates/router/tests/connectors/zen.rs
+++ b/crates/router/tests/connectors/zen.rs
@@ -98,6 +98,7 @@ async fn should_sync_authorized_payment() {
                 encoded_data: None,
                 capture_method: None,
                 connector_meta: None,
+                mandate_id: None,
             }),
             None,
         )
@@ -210,6 +211,7 @@ async fn should_sync_auto_captured_payment() {
                 encoded_data: None,
                 capture_method: Some(enums::CaptureMethod::Automatic),
                 connector_meta: None,
+                mandate_id: None,
             }),
             None,
         )

## Type of Change
<!-- Put an `x` in the boxes that apply -->

- [ ] Bugfix
- [ ] New feature
- [X] Enhancement
- [ ] Refactoring
- [ ] Dependency updates
- [ ] Documentation
- [ ] CI/CD

## Description
<!-- Describe your changes in detail -->
Add mandate support for gpay, apple_pay and klarna
Closes #1045 

### Additional Changes

- [ ] This PR modifies the API contract
- [ ] This PR modifies the database schema
- [ ] This PR modifies application configuration/environment variables

<!-- 
Provide links to the files with corresponding changes.

Following are the paths where you can find config files:
1. `config`
2. `crates/router/src/configs`
3. `loadtest/config`
-->


## Motivation and Context
<!--
Why is this change required? What problem does it solve?
If it fixes an open issue, please link to the issue here.

If you don't have an issue, we'd recommend starting with one first so the PR
can focus on the implementation (unless its an obvious bug or documentation fix
that will have little conversation).
-->
1. As part of enabling mandate payments for major connectors, we are adding support for gpay, apple_pay and klarna mandates in adyen
2. Core changes required as mandates for wallets and BNPL are not supported as of now in core
    Eg: mandate id need to updated during PSync when redirection is required for mandate creation, So after the /redirect call happened it will call the /sync internally and it should be updating the mandate id

## How did you test it?
<!--
Did you write an integration/unit/API test to verify the code changes?
Or did you test this change manually (provide relevant screenshots)?
-->
<img width="1123" alt="Screen Shot 2023-05-04 at 1 27 01 AM" src="https://user-images.githubusercontent.com/20727598/236036746-0811f3e0-99c3-4096-9a9d-17e4305d7346.png">


## Checklist
<!-- Put an `x` in the boxes that apply -->

- [X] I formatted the code `cargo +nightly fmt --all`
- [X] I addressed lints thrown by `cargo clippy`
- [X] I reviewed submitted code
- [X] I added unit tests for my changes where possible
