Bug: Add Rapyd for card payments




diff --git a/config/Development.toml b/config/Development.toml
index e0315b066fc..761156a9a37 100644
--- a/config/Development.toml
+++ b/config/Development.toml
@@ -82,6 +82,9 @@ base_url = "https://apitest.cybersource.com/"
 [connectors.shift4]
 base_url = "https://api.shift4.com/"
 
+[connectors.rapyd]
+base_url = "https://sandboxapi.rapyd.net"
+
 [connectors.fiserv]
 base_url = "https://cert.api.fiservapps.com/"
 
@@ -90,6 +93,7 @@ base_url = "http://localhost:9090/"
 
 [connectors.payu]
 base_url = "https://secure.snd.payu.com/api/"
+
 [connectors.globalpay]
 base_url = "https://apis.sandbox.globalpay.com/ucp/"
 
diff --git a/config/config.example.toml b/config/config.example.toml
index 6bedd98ab23..ed90f403192 100644
--- a/config/config.example.toml
+++ b/config/config.example.toml
@@ -133,6 +133,9 @@ base_url = "https://apitest.cybersource.com/"
 [connectors.shift4]
 base_url = "https://api.shift4.com/"
 
+[connectors.rapyd]
+base_url = "https://sandboxapi.rapyd.net"
+
 [connectors.fiserv]
 base_url = "https://cert.api.fiservapps.com/"
 
diff --git a/config/docker_compose.toml b/config/docker_compose.toml
index d38380b73c8..bf6ec07b306 100644
--- a/config/docker_compose.toml
+++ b/config/docker_compose.toml
@@ -85,6 +85,9 @@ base_url = "https://apitest.cybersource.com/"
 [connectors.shift4]
 base_url = "https://api.shift4.com/"
 
+[connectors.rapyd]
+base_url = "https://sandboxapi.rapyd.net"
+
 [connectors.fiserv]
 base_url = "https://cert.api.fiservapps.com/"
 
diff --git a/crates/api_models/src/enums.rs b/crates/api_models/src/enums.rs
index 0c77ca85bff..c24b915e324 100644
--- a/crates/api_models/src/enums.rs
+++ b/crates/api_models/src/enums.rs
@@ -507,6 +507,7 @@ pub enum Connector {
     Globalpay,
     Klarna,
     Payu,
+    Rapyd,
     Shift4,
     Stripe,
     Worldline,
diff --git a/crates/router/src/configs/defaults.toml b/crates/router/src/configs/defaults.toml
index 5e447f2775d..437f78eed23 100644
--- a/crates/router/src/configs/defaults.toml
+++ b/crates/router/src/configs/defaults.toml
@@ -63,4 +63,4 @@ max_read_count = 100
 
 [connectors.supported]
 wallets = ["klarna","braintree"]
-cards = ["stripe","adyen","authorizedotnet","checkout","braintree", "cybersource", "fiserv"]
+cards = ["stripe","adyen","authorizedotnet","checkout","braintree", "cybersource", "fiserv", "rapyd"]
diff --git a/crates/router/src/configs/settings.rs b/crates/router/src/configs/settings.rs
index 1f66cb22ff2..72b641f9ae8 100644
--- a/crates/router/src/configs/settings.rs
+++ b/crates/router/src/configs/settings.rs
@@ -131,6 +131,7 @@ pub struct Connectors {
     pub globalpay: ConnectorParams,
     pub klarna: ConnectorParams,
     pub payu: ConnectorParams,
+    pub rapyd: ConnectorParams,
     pub shift4: ConnectorParams,
     pub stripe: ConnectorParams,
     pub supported: SupportedConnectors,
diff --git a/crates/router/src/connector.rs b/crates/router/src/connector.rs
index 4c218454d99..a692e1e48c5 100644
--- a/crates/router/src/connector.rs
+++ b/crates/router/src/connector.rs
@@ -9,6 +9,7 @@ pub mod fiserv;
 pub mod globalpay;
 pub mod klarna;
 pub mod payu;
+pub mod rapyd;
 pub mod shift4;
 pub mod stripe;
 pub mod utils;
@@ -18,6 +19,6 @@ pub mod worldpay;
 pub use self::{
     aci::Aci, adyen::Adyen, applepay::Applepay, authorizedotnet::Authorizedotnet,
     braintree::Braintree, checkout::Checkout, cybersource::Cybersource, fiserv::Fiserv,
-    globalpay::Globalpay, klarna::Klarna, payu::Payu, shift4::Shift4, stripe::Stripe,
+    globalpay::Globalpay, klarna::Klarna, payu::Payu, rapyd::Rapyd, shift4::Shift4, stripe::Stripe,
     worldline::Worldline, worldpay::Worldpay,
 };
diff --git a/crates/router/src/connector/rapyd.rs b/crates/router/src/connector/rapyd.rs
new file mode 100644
index 00000000000..b6f35e1a93d
--- /dev/null
+++ b/crates/router/src/connector/rapyd.rs
@@ -0,0 +1,656 @@
+mod transformers;
+use std::fmt::Debug;
+
+use base64::Engine;
+use bytes::Bytes;
+use common_utils::date_time;
+use error_stack::{IntoReport, ResultExt};
+use rand::distributions::{Alphanumeric, DistString};
+use ring::hmac;
+use transformers as rapyd;
+
+use crate::{
+    configs::settings,
+    consts,
+    core::{
+        errors::{self, CustomResult},
+        payments,
+    },
+    headers, logger, services,
+    types::{
+        self,
+        api::{self, ConnectorCommon},
+        ErrorResponse, Response,
+    },
+    utils::{self, BytesExt},
+};
+
+#[derive(Debug, Clone)]
+pub struct Rapyd;
+
+impl Rapyd {
+    pub fn generate_signature(
+        &self,
+        auth: &rapyd::RapydAuthType,
+        http_method: &str,
+        url_path: &str,
+        body: &str,
+        timestamp: &i64,
+        salt: &str,
+    ) -> CustomResult<String, errors::ConnectorError> {
+        let rapyd::RapydAuthType {
+            access_key,
+            secret_key,
+        } = auth;
+        let to_sign =
+            format!("{http_method}{url_path}{salt}{timestamp}{access_key}{secret_key}{body}");
+        let key = hmac::Key::new(hmac::HMAC_SHA256, secret_key.as_bytes());
+        let tag = hmac::sign(&key, to_sign.as_bytes());
+        let hmac_sign = hex::encode(tag);
+        let signature_value = consts::BASE64_ENGINE_URL_SAFE.encode(hmac_sign);
+        Ok(signature_value)
+    }
+}
+
+impl ConnectorCommon for Rapyd {
+    fn id(&self) -> &'static str {
+        "rapyd"
+    }
+
+    fn common_get_content_type(&self) -> &'static str {
+        "application/json"
+    }
+
+    fn base_url<'a>(&self, connectors: &'a settings::Connectors) -> &'a str {
+        connectors.rapyd.base_url.as_ref()
+    }
+
+    fn get_auth_header(
+        &self,
+        _auth_type: &types::ConnectorAuthType,
+    ) -> CustomResult<Vec<(String, String)>, errors::ConnectorError> {
+        Ok(vec![])
+    }
+}
+
+impl api::PaymentAuthorize for Rapyd {}
+
+impl
+    services::ConnectorIntegration<
+        api::Authorize,
+        types::PaymentsAuthorizeData,
+        types::PaymentsResponseData,
+    > for Rapyd
+{
+    fn get_headers(
+        &self,
+        _req: &types::PaymentsAuthorizeRouterData,
+        _connectors: &settings::Connectors,
+    ) -> CustomResult<Vec<(String, String)>, errors::ConnectorError> {
+        Ok(vec![(
+            headers::CONTENT_TYPE.to_string(),
+            types::PaymentsAuthorizeType::get_content_type(self).to_string(),
+        )])
+    }
+
+    fn get_content_type(&self) -> &'static str {
+        self.common_get_content_type()
+    }
+
+    fn get_url(
+        &self,
+        _req: &types::PaymentsAuthorizeRouterData,
+        connectors: &settings::Connectors,
+    ) -> CustomResult<String, errors::ConnectorError> {
+        Ok(format!("{}/v1/payments", self.base_url(connectors)))
+    }
+
+    fn build_request(
+        &self,
+        req: &types::RouterData<
+            api::Authorize,
+            types::PaymentsAuthorizeData,
+            types::PaymentsResponseData,
+        >,
+        connectors: &settings::Connectors,
+    ) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
+        let timestamp = date_time::now_unix_timestamp();
+        let salt = Alphanumeric.sample_string(&mut rand::thread_rng(), 12);
+
+        let rapyd_req = utils::Encode::<rapyd::RapydPaymentsRequest>::convert_and_encode(req)
+            .change_context(errors::ConnectorError::RequestEncodingFailed)?;
+
+        let auth: rapyd::RapydAuthType = rapyd::RapydAuthType::try_from(&req.connector_auth_type)?;
+        let signature =
+            self.generate_signature(&auth, "post", "/v1/payments", &rapyd_req, &timestamp, &salt)?;
+        let headers = vec![
+            ("access_key".to_string(), auth.access_key),
+            ("salt".to_string(), salt),
+            ("timestamp".to_string(), timestamp.to_string()),
+            ("signature".to_string(), signature),
+        ];
+        let request = services::RequestBuilder::new()
+            .method(services::Method::Post)
+            .url(&types::PaymentsAuthorizeType::get_url(
+                self, req, connectors,
+            )?)
+            .headers(types::PaymentsAuthorizeType::get_headers(
+                self, req, connectors,
+            )?)
+            .headers(headers)
+            .body(Some(rapyd_req))
+            .build();
+        Ok(Some(request))
+    }
+
+    fn get_request_body(
+        &self,
+        req: &types::PaymentsAuthorizeRouterData,
+    ) -> CustomResult<Option<String>, errors::ConnectorError> {
+        let rapyd_req = utils::Encode::<rapyd::RapydPaymentsRequest>::convert_and_url_encode(req)
+            .change_context(errors::ConnectorError::RequestEncodingFailed)?;
+        Ok(Some(rapyd_req))
+    }
+
+    fn handle_response(
+        &self,
+        data: &types::PaymentsAuthorizeRouterData,
+        res: Response,
+    ) -> CustomResult<types::PaymentsAuthorizeRouterData, errors::ConnectorError> {
+        let response: rapyd::RapydPaymentsResponse = res
+            .response
+            .parse_struct("Rapyd PaymentResponse")
+            .change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
+        logger::debug!(rapydpayments_create_response=?response);
+        types::ResponseRouterData {
+            response,
+            data: data.clone(),
+            http_code: res.status_code,
+        }
+        .try_into()
+        .change_context(errors::ConnectorError::ResponseHandlingFailed)
+    }
+
+    fn get_error_response(
+        &self,
+        res: Bytes,
+    ) -> CustomResult<ErrorResponse, errors::ConnectorError> {
+        let response: rapyd::RapydPaymentsResponse = res
+            .parse_struct("Rapyd ErrorResponse")
+            .change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
+        Ok(ErrorResponse {
+            code: response.status.error_code,
+            message: response.status.status,
+            reason: response.status.message,
+        })
+    }
+}
+
+impl api::Payment for Rapyd {}
+
+impl api::PreVerify for Rapyd {}
+impl
+    services::ConnectorIntegration<
+        api::Verify,
+        types::VerifyRequestData,
+        types::PaymentsResponseData,
+    > for Rapyd
+{
+}
+
+impl api::PaymentVoid for Rapyd {}
+
+impl
+    services::ConnectorIntegration<
+        api::Void,
+        types::PaymentsCancelData,
+        types::PaymentsResponseData,
+    > for Rapyd
+{
+    fn get_headers(
+        &self,
+        _req: &types::PaymentsCancelRouterData,
+        _connectors: &settings::Connectors,
+    ) -> CustomResult<Vec<(String, String)>, errors::ConnectorError> {
+        Ok(vec![(
+            headers::CONTENT_TYPE.to_string(),
+            types::PaymentsVoidType::get_content_type(self).to_string(),
+        )])
+    }
+
+    fn get_content_type(&self) -> &'static str {
+        self.common_get_content_type()
+    }
+
+    fn get_url(
+        &self,
+        req: &types::PaymentsCancelRouterData,
+        connectors: &settings::Connectors,
+    ) -> CustomResult<String, errors::ConnectorError> {
+        Ok(format!(
+            "{}/v1/payments/{}",
+            self.base_url(connectors),
+            req.request.connector_transaction_id
+        ))
+    }
+
+    fn build_request(
+        &self,
+        req: &types::PaymentsCancelRouterData,
+        connectors: &settings::Connectors,
+    ) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
+        let timestamp = date_time::now_unix_timestamp();
+        let salt = Alphanumeric.sample_string(&mut rand::thread_rng(), 12);
+
+        let auth: rapyd::RapydAuthType = rapyd::RapydAuthType::try_from(&req.connector_auth_type)?;
+        let url_path = format!("/v1/payments/{}", req.request.connector_transaction_id);
+        let signature =
+            self.generate_signature(&auth, "delete", &url_path, "", &timestamp, &salt)?;
+
+        let headers = vec![
+            ("access_key".to_string(), auth.access_key),
+            ("salt".to_string(), salt),
+            ("timestamp".to_string(), timestamp.to_string()),
+            ("signature".to_string(), signature),
+        ];
+        let request = services::RequestBuilder::new()
+            .method(services::Method::Delete)
+            .url(&types::PaymentsVoidType::get_url(self, req, connectors)?)
+            .headers(types::PaymentsVoidType::get_headers(self, req, connectors)?)
+            .headers(headers)
+            .build();
+        Ok(Some(request))
+    }
+
+    fn handle_response(
+        &self,
+        data: &types::PaymentsCancelRouterData,
+        res: Response,
+    ) -> CustomResult<types::PaymentsCancelRouterData, errors::ConnectorError> {
+        let response: rapyd::RapydPaymentsResponse = res
+            .response
+            .parse_struct("Rapyd PaymentResponse")
+            .change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
+        logger::debug!(rapydpayments_create_response=?response);
+        types::ResponseRouterData {
+            response,
+            data: data.clone(),
+            http_code: res.status_code,
+        }
+        .try_into()
+        .change_context(errors::ConnectorError::ResponseHandlingFailed)
+    }
+
+    fn get_error_response(
+        &self,
+        res: Bytes,
+    ) -> CustomResult<ErrorResponse, errors::ConnectorError> {
+        let response: rapyd::RapydPaymentsResponse = res
+            .parse_struct("Rapyd ErrorResponse")
+            .change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
+        Ok(ErrorResponse {
+            code: response.status.error_code,
+            message: response.status.status,
+            reason: response.status.message,
+        })
+    }
+}
+
+impl api::PaymentSync for Rapyd {}
+impl
+    services::ConnectorIntegration<api::PSync, types::PaymentsSyncData, types::PaymentsResponseData>
+    for Rapyd
+{
+    fn get_headers(
+        &self,
+        _req: &types::PaymentsSyncRouterData,
+        _connectors: &settings::Connectors,
+    ) -> CustomResult<Vec<(String, String)>, errors::ConnectorError> {
+        Ok(vec![])
+    }
+
+    fn get_content_type(&self) -> &'static str {
+        self.common_get_content_type()
+    }
+
+    fn get_url(
+        &self,
+        _req: &types::PaymentsSyncRouterData,
+        _connectors: &settings::Connectors,
+    ) -> CustomResult<String, errors::ConnectorError> {
+        Err(errors::ConnectorError::NotImplemented("PSync".to_string()).into())
+    }
+
+    fn build_request(
+        &self,
+        _req: &types::PaymentsSyncRouterData,
+        _connectors: &settings::Connectors,
+    ) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
+        Ok(None)
+    }
+
+    fn get_error_response(
+        &self,
+        _res: Bytes,
+    ) -> CustomResult<ErrorResponse, errors::ConnectorError> {
+        Err(errors::ConnectorError::NotImplemented("PSync".to_string()).into())
+    }
+
+    fn handle_response(
+        &self,
+        _data: &types::PaymentsSyncRouterData,
+        _res: Response,
+    ) -> CustomResult<types::PaymentsSyncRouterData, errors::ConnectorError> {
+        Err(errors::ConnectorError::NotImplemented("PSync".to_string()).into())
+    }
+}
+
+impl api::PaymentCapture for Rapyd {}
+impl
+    services::ConnectorIntegration<
+        api::Capture,
+        types::PaymentsCaptureData,
+        types::PaymentsResponseData,
+    > for Rapyd
+{
+    fn get_headers(
+        &self,
+        _req: &types::PaymentsCaptureRouterData,
+        _connectors: &settings::Connectors,
+    ) -> CustomResult<Vec<(String, String)>, errors::ConnectorError> {
+        Ok(vec![(
+            headers::CONTENT_TYPE.to_string(),
+            types::PaymentsCaptureType::get_content_type(self).to_string(),
+        )])
+    }
+
+    fn get_content_type(&self) -> &'static str {
+        self.common_get_content_type()
+    }
+
+    fn get_request_body(
+        &self,
+        req: &types::PaymentsCaptureRouterData,
+    ) -> CustomResult<Option<String>, errors::ConnectorError> {
+        let rapyd_req = utils::Encode::<rapyd::CaptureRequest>::convert_and_encode(req)
+            .change_context(errors::ConnectorError::RequestEncodingFailed)?;
+        Ok(Some(rapyd_req))
+    }
+
+    fn build_request(
+        &self,
+        req: &types::PaymentsCaptureRouterData,
+        connectors: &settings::Connectors,
+    ) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
+        let timestamp = date_time::now_unix_timestamp();
+        let salt = Alphanumeric.sample_string(&mut rand::thread_rng(), 12);
+
+        let rapyd_req = utils::Encode::<rapyd::CaptureRequest>::convert_and_encode(req)
+            .change_context(errors::ConnectorError::RequestEncodingFailed)?;
+
+        let auth: rapyd::RapydAuthType = rapyd::RapydAuthType::try_from(&req.connector_auth_type)?;
+        let url_path = format!(
+            "/v1/payments/{}/capture",
+            req.request.connector_transaction_id
+        );
+        let signature =
+            self.generate_signature(&auth, "post", &url_path, &rapyd_req, &timestamp, &salt)?;
+        let headers = vec![
+            ("access_key".to_string(), auth.access_key),
+            ("salt".to_string(), salt),
+            ("timestamp".to_string(), timestamp.to_string()),
+            ("signature".to_string(), signature),
+        ];
+        let request = services::RequestBuilder::new()
+            .method(services::Method::Post)
+            .url(&types::PaymentsCaptureType::get_url(self, req, connectors)?)
+            .headers(types::PaymentsCaptureType::get_headers(
+                self, req, connectors,
+            )?)
+            .headers(headers)
+            .body(Some(rapyd_req))
+            .build();
+        Ok(Some(request))
+    }
+
+    fn handle_response(
+        &self,
+        data: &types::PaymentsCaptureRouterData,
+        res: Response,
+    ) -> CustomResult<types::PaymentsCaptureRouterData, errors::ConnectorError> {
+        let response: rapyd::RapydPaymentsResponse = res
+            .response
+            .parse_struct("RapydPaymentResponse")
+            .change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
+
+        types::RouterData::try_from(types::ResponseRouterData {
+            response,
+            data: data.clone(),
+            http_code: res.status_code,
+        })
+        .change_context(errors::ConnectorError::ResponseHandlingFailed)
+    }
+
+    fn get_url(
+        &self,
+        req: &types::PaymentsCaptureRouterData,
+        connectors: &settings::Connectors,
+    ) -> CustomResult<String, errors::ConnectorError> {
+        Ok(format!(
+            "{}/v1/payments/{}/capture",
+            self.base_url(connectors),
+            req.request.connector_transaction_id
+        ))
+    }
+
+    fn get_error_response(
+        &self,
+        res: Bytes,
+    ) -> CustomResult<ErrorResponse, errors::ConnectorError> {
+        let response: rapyd::RapydPaymentsResponse = res
+            .parse_struct("Rapyd ErrorResponse")
+            .change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
+        Ok(ErrorResponse {
+            code: response.status.error_code,
+            message: response.status.status,
+            reason: response.status.message,
+        })
+    }
+}
+
+impl api::PaymentSession for Rapyd {}
+
+impl
+    services::ConnectorIntegration<
+        api::Session,
+        types::PaymentsSessionData,
+        types::PaymentsResponseData,
+    > for Rapyd
+{
+    //TODO: implement sessions flow
+}
+
+impl api::Refund for Rapyd {}
+impl api::RefundExecute for Rapyd {}
+impl api::RefundSync for Rapyd {}
+
+impl services::ConnectorIntegration<api::Execute, types::RefundsData, types::RefundsResponseData>
+    for Rapyd
+{
+    fn get_headers(
+        &self,
+        _req: &types::RefundsRouterData<api::Execute>,
+        _connectors: &settings::Connectors,
+    ) -> CustomResult<Vec<(String, String)>, errors::ConnectorError> {
+        Ok(vec![(
+            headers::CONTENT_TYPE.to_string(),
+            types::RefundExecuteType::get_content_type(self).to_string(),
+        )])
+    }
+
+    fn get_content_type(&self) -> &'static str {
+        api::ConnectorCommon::common_get_content_type(self)
+    }
+
+    fn get_url(
+        &self,
+        _req: &types::RefundsRouterData<api::Execute>,
+        connectors: &settings::Connectors,
+    ) -> CustomResult<String, errors::ConnectorError> {
+        Ok(format!("{}/v1/refunds", self.base_url(connectors)))
+    }
+
+    fn get_request_body(
+        &self,
+        req: &types::RefundsRouterData<api::Execute>,
+    ) -> CustomResult<Option<String>, errors::ConnectorError> {
+        let rapyd_req = utils::Encode::<rapyd::RapydRefundRequest>::convert_and_url_encode(req)
+            .change_context(errors::ConnectorError::RequestEncodingFailed)?;
+        Ok(Some(rapyd_req))
+    }
+
+    fn build_request(
+        &self,
+        req: &types::RefundsRouterData<api::Execute>,
+        connectors: &settings::Connectors,
+    ) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
+        let timestamp = date_time::now_unix_timestamp();
+        let salt = Alphanumeric.sample_string(&mut rand::thread_rng(), 12);
+
+        let rapyd_req = utils::Encode::<rapyd::RapydRefundRequest>::convert_and_encode(req)
+            .change_context(errors::ConnectorError::RequestEncodingFailed)?;
+
+        let auth: rapyd::RapydAuthType = rapyd::RapydAuthType::try_from(&req.connector_auth_type)?;
+        let signature =
+            self.generate_signature(&auth, "post", "/v1/refunds", &rapyd_req, &timestamp, &salt)?;
+        let headers = vec![
+            ("access_key".to_string(), auth.access_key),
+            ("salt".to_string(), salt),
+            ("timestamp".to_string(), timestamp.to_string()),
+            ("signature".to_string(), signature),
+        ];
+        let request = services::RequestBuilder::new()
+            .method(services::Method::Post)
+            .url(&types::RefundExecuteType::get_url(self, req, connectors)?)
+            .headers(headers)
+            .body(Some(rapyd_req))
+            .build();
+        Ok(Some(request))
+    }
+
+    fn handle_response(
+        &self,
+        data: &types::RefundsRouterData<api::Execute>,
+        res: Response,
+    ) -> CustomResult<types::RefundsRouterData<api::Execute>, errors::ConnectorError> {
+        logger::debug!(target: "router::connector::rapyd", response=?res);
+        let response: rapyd::RefundResponse = res
+            .response
+            .parse_struct("rapyd RefundResponse")
+            .change_context(errors::ConnectorError::RequestEncodingFailed)?;
+        types::ResponseRouterData {
+            response,
+            data: data.clone(),
+            http_code: res.status_code,
+        }
+        .try_into()
+        .change_context(errors::ConnectorError::ResponseHandlingFailed)
+    }
+
+    fn get_error_response(
+        &self,
+        res: Bytes,
+    ) -> CustomResult<ErrorResponse, errors::ConnectorError> {
+        let response: rapyd::RapydPaymentsResponse = res
+            .parse_struct("Rapyd ErrorResponse")
+            .change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
+        Ok(ErrorResponse {
+            code: response.status.error_code,
+            message: response.status.status,
+            reason: response.status.message,
+        })
+    }
+}
+
+impl services::ConnectorIntegration<api::RSync, types::RefundsData, types::RefundsResponseData>
+    for Rapyd
+{
+    fn get_headers(
+        &self,
+        _req: &types::RefundSyncRouterData,
+        _connectors: &settings::Connectors,
+    ) -> CustomResult<Vec<(String, String)>, errors::ConnectorError> {
+        Ok(vec![])
+    }
+
+    fn get_content_type(&self) -> &'static str {
+        self.common_get_content_type()
+    }
+
+    fn get_url(
+        &self,
+        _req: &types::RefundSyncRouterData,
+        _connectors: &settings::Connectors,
+    ) -> CustomResult<String, errors::ConnectorError> {
+        Err(errors::ConnectorError::NotImplemented("RSync".to_string()).into())
+    }
+
+    fn handle_response(
+        &self,
+        data: &types::RefundSyncRouterData,
+        res: Response,
+    ) -> CustomResult<types::RefundSyncRouterData, errors::ConnectorError> {
+        logger::debug!(target: "router::connector::rapyd", response=?res);
+        let response: rapyd::RefundResponse = res
+            .response
+            .parse_struct("rapyd RefundResponse")
+            .change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
+        types::ResponseRouterData {
+            response,
+            data: data.clone(),
+            http_code: res.status_code,
+        }
+        .try_into()
+        .change_context(errors::ConnectorError::ResponseHandlingFailed)
+    }
+
+    fn get_error_response(
+        &self,
+        _res: Bytes,
+    ) -> CustomResult<ErrorResponse, errors::ConnectorError> {
+        Err(errors::ConnectorError::NotImplemented("RSync".to_string()).into())
+    }
+}
+
+#[async_trait::async_trait]
+impl api::IncomingWebhook for Rapyd {
+    fn get_webhook_object_reference_id(
+        &self,
+        _body: &[u8],
+    ) -> CustomResult<String, errors::ConnectorError> {
+        Err(errors::ConnectorError::WebhooksNotImplemented).into_report()
+    }
+
+    fn get_webhook_event_type(
+        &self,
+        _body: &[u8],
+    ) -> CustomResult<api::IncomingWebhookEvent, errors::ConnectorError> {
+        Err(errors::ConnectorError::WebhooksNotImplemented).into_report()
+    }
+
+    fn get_webhook_resource_object(
+        &self,
+        _body: &[u8],
+    ) -> CustomResult<serde_json::Value, errors::ConnectorError> {
+        Err(errors::ConnectorError::WebhooksNotImplemented).into_report()
+    }
+}
+
+impl services::ConnectorRedirectResponse for Rapyd {
+    fn get_flow_type(
+        &self,
+        _query_params: &str,
+    ) -> CustomResult<payments::CallConnectorAction, errors::ConnectorError> {
+        Ok(payments::CallConnectorAction::Trigger)
+    }
+}
diff --git a/crates/router/src/connector/rapyd/transformers.rs b/crates/router/src/connector/rapyd/transformers.rs
new file mode 100644
index 00000000000..f2bd8315253
--- /dev/null
+++ b/crates/router/src/connector/rapyd/transformers.rs
@@ -0,0 +1,481 @@
+use error_stack::{IntoReport, ResultExt};
+use serde::{Deserialize, Serialize};
+use url::Url;
+
+use crate::{
+    core::errors,
+    pii::{self, Secret},
+    services,
+    types::{
+        self, api,
+        storage::enums,
+        transformers::{self, ForeignFrom},
+    },
+};
+
+#[derive(Default, Debug, Serialize)]
+pub struct RapydPaymentsRequest {
+    pub amount: i64,
+    pub currency: enums::Currency,
+    pub payment_method: PaymentMethod,
+    pub payment_method_options: PaymentMethodOptions,
+    pub capture: bool,
+}
+
+#[derive(Default, Debug, Serialize)]
+pub struct PaymentMethodOptions {
+    #[serde(rename = "3d_required")]
+    pub three_ds: bool,
+}
+#[derive(Default, Debug, Serialize)]
+pub struct PaymentMethod {
+    #[serde(rename = "type")]
+    pub pm_type: String,
+    pub fields: PaymentFields,
+}
+
+#[derive(Default, Debug, Serialize)]
+pub struct PaymentFields {
+    pub number: Secret<String, pii::CardNumber>,
+    pub expiration_month: Secret<String>,
+    pub expiration_year: Secret<String>,
+    pub name: Secret<String>,
+    pub cvv: Secret<String>,
+}
+
+impl TryFrom<&types::PaymentsAuthorizeRouterData> for RapydPaymentsRequest {
+    type Error = error_stack::Report<errors::ConnectorError>;
+    fn try_from(item: &types::PaymentsAuthorizeRouterData) -> Result<Self, Self::Error> {
+        match item.request.payment_method_data {
+            api_models::payments::PaymentMethod::Card(ref ccard) => {
+                let payment_method = PaymentMethod {
+                    pm_type: "in_amex_card".to_owned(), //[#369] Map payment method type based on country
+                    fields: PaymentFields {
+                        number: ccard.card_number.to_owned(),
+                        expiration_month: ccard.card_exp_month.to_owned(),
+                        expiration_year: ccard.card_exp_year.to_owned(),
+                        name: ccard.card_holder_name.to_owned(),
+                        cvv: ccard.card_cvc.to_owned(),
+                    },
+                };
+                let three_ds_enabled = matches!(item.auth_type, enums::AuthenticationType::ThreeDs);
+                let payment_method_options = PaymentMethodOptions {
+                    three_ds: three_ds_enabled,
+                };
+                Ok(Self {
+                    amount: item.request.amount,
+                    currency: item.request.currency,
+                    payment_method,
+                    capture: matches!(
+                        item.request.capture_method,
+                        Some(enums::CaptureMethod::Automatic) | None
+                    ),
+                    payment_method_options,
+                })
+            }
+            _ => Err(errors::ConnectorError::NotImplemented("Payment methods".to_string()).into()),
+        }
+    }
+}
+
+pub struct RapydAuthType {
+    pub access_key: String,
+    pub secret_key: String,
+}
+
+impl TryFrom<&types::ConnectorAuthType> for RapydAuthType {
+    type Error = error_stack::Report<errors::ConnectorError>;
+    fn try_from(auth_type: &types::ConnectorAuthType) -> Result<Self, Self::Error> {
+        if let types::ConnectorAuthType::BodyKey { api_key, key1 } = auth_type {
+            Ok(Self {
+                access_key: api_key.to_string(),
+                secret_key: key1.to_string(),
+            })
+        } else {
+            Err(errors::ConnectorError::FailedToObtainAuthType)?
+        }
+    }
+}
+
+#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
+#[allow(clippy::upper_case_acronyms)]
+pub enum RapydPaymentStatus {
+    #[serde(rename = "ACT")]
+    Active,
+    #[serde(rename = "CAN")]
+    CanceledByClientOrBank,
+    #[serde(rename = "CLO")]
+    Closed,
+    #[serde(rename = "ERR")]
+    Error,
+    #[serde(rename = "EXP")]
+    Expired,
+    #[serde(rename = "REV")]
+    ReversedByRapyd,
+    #[default]
+    #[serde(rename = "NEW")]
+    New,
+}
+
+impl From<transformers::Foreign<(RapydPaymentStatus, String)>>
+    for transformers::Foreign<enums::AttemptStatus>
+{
+    fn from(item: transformers::Foreign<(RapydPaymentStatus, String)>) -> Self {
+        let (status, next_action) = item.0;
+        match status {
+            RapydPaymentStatus::Closed => enums::AttemptStatus::Charged,
+            RapydPaymentStatus::Active => {
+                if next_action == "3d_verification" {
+                    enums::AttemptStatus::AuthenticationPending
+                } else if next_action == "pending_capture" {
+                    enums::AttemptStatus::Authorized
+                } else {
+                    enums::AttemptStatus::Pending
+                }
+            }
+            RapydPaymentStatus::CanceledByClientOrBank
+            | RapydPaymentStatus::Error
+            | RapydPaymentStatus::Expired
+            | RapydPaymentStatus::ReversedByRapyd => enums::AttemptStatus::Failure,
+            RapydPaymentStatus::New => enums::AttemptStatus::Authorizing,
+        }
+        .into()
+    }
+}
+
+#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)]
+pub struct RapydPaymentsResponse {
+    pub status: Status,
+    pub data: Option<ResponseData>,
+}
+
+#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)]
+pub struct Status {
+    pub error_code: String,
+    pub status: String,
+    pub message: Option<String>,
+    pub response_code: Option<String>,
+    pub operation_id: String,
+}
+
+#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)]
+pub struct ResponseData {
+    pub id: String,
+    pub amount: i64,
+    pub status: RapydPaymentStatus,
+    pub next_action: String,
+    pub redirect_url: Option<String>,
+    pub original_amount: Option<i64>,
+    pub is_partial: Option<bool>,
+    pub currency_code: Option<enums::Currency>,
+    pub country_code: Option<String>,
+    pub captured: Option<bool>,
+    pub transaction_id: String,
+    pub paid: Option<bool>,
+    pub failure_code: Option<String>,
+    pub failure_message: Option<String>,
+}
+
+impl TryFrom<types::PaymentsResponseRouterData<RapydPaymentsResponse>>
+    for types::PaymentsAuthorizeRouterData
+{
+    type Error = error_stack::Report<errors::ParsingError>;
+    fn try_from(
+        item: types::PaymentsResponseRouterData<RapydPaymentsResponse>,
+    ) -> Result<Self, Self::Error> {
+        let (status, response) = match item.response.status.status.as_str() {
+            "SUCCESS" => match item.response.data {
+                Some(data) => {
+                    let redirection_data = match (data.next_action.as_str(), data.redirect_url) {
+                        ("3d_verification", Some(url)) => {
+                            let url = Url::parse(&url)
+                                .into_report()
+                                .change_context(errors::ParsingError)?;
+                            let mut base_url = url.clone();
+                            base_url.set_query(None);
+                            Some(services::RedirectForm {
+                                url: base_url.to_string(),
+                                method: services::Method::Get,
+                                form_fields: std::collections::HashMap::from_iter(
+                                    url.query_pairs()
+                                        .map(|(k, v)| (k.to_string(), v.to_string())),
+                                ),
+                            })
+                        }
+                        (_, _) => None,
+                    };
+                    (
+                        enums::AttemptStatus::foreign_from((data.status, data.next_action)),
+                        Ok(types::PaymentsResponseData::TransactionResponse {
+                            resource_id: types::ResponseId::ConnectorTransactionId(data.id), //transaction_id is also the field but this id is used to initiate a refund
+                            redirect: redirection_data.is_some(),
+                            redirection_data,
+                            mandate_reference: None,
+                            connector_metadata: None,
+                        }),
+                    )
+                }
+                None => (
+                    enums::AttemptStatus::Failure,
+                    Err(types::ErrorResponse {
+                        code: item.response.status.error_code,
+                        message: item.response.status.status,
+                        reason: item.response.status.message,
+                    }),
+                ),
+            },
+            "ERROR" => (
+                enums::AttemptStatus::Failure,
+                Err(types::ErrorResponse {
+                    code: item.response.status.error_code,
+                    message: item.response.status.status,
+                    reason: item.response.status.message,
+                }),
+            ),
+            _ => (
+                enums::AttemptStatus::Failure,
+                Err(types::ErrorResponse {
+                    code: item.response.status.error_code,
+                    message: item.response.status.status,
+                    reason: item.response.status.message,
+                }),
+            ),
+        };
+
+        Ok(Self {
+            status,
+            response,
+            ..item.data
+        })
+    }
+}
+
+#[derive(Default, Debug, Serialize)]
+pub struct RapydRefundRequest {
+    pub payment: String,
+    pub amount: Option<i64>,
+    pub currency: Option<enums::Currency>,
+}
+
+impl<F> TryFrom<&types::RefundsRouterData<F>> for RapydRefundRequest {
+    type Error = error_stack::Report<errors::ParsingError>;
+    fn try_from(item: &types::RefundsRouterData<F>) -> Result<Self, Self::Error> {
+        Ok(Self {
+            payment: item.request.connector_transaction_id.to_string(),
+            amount: Some(item.request.amount),
+            currency: Some(item.request.currency),
+        })
+    }
+}
+
+#[allow(dead_code)]
+#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
+pub enum RefundStatus {
+    Completed,
+    Error,
+    Rejected,
+    #[default]
+    Pending,
+}
+
+impl From<RefundStatus> for enums::RefundStatus {
+    fn from(item: RefundStatus) -> Self {
+        match item {
+            RefundStatus::Completed => Self::Success,
+            RefundStatus::Error | RefundStatus::Rejected => Self::Failure,
+            RefundStatus::Pending => Self::Pending,
+        }
+    }
+}
+
+#[derive(Default, Debug, Clone, Serialize, Deserialize)]
+pub struct RefundResponse {
+    pub status: Status,
+    pub data: Option<RefundResponseData>,
+}
+
+#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)]
+pub struct RefundResponseData {
+    //Some field related to forign exchange and split payment can be added as and when implemented
+    pub id: String,
+    pub payment: String,
+    pub amount: i64,
+    pub currency: enums::Currency,
+    pub status: RefundStatus,
+    pub created_at: Option<i64>,
+    pub failure_reason: Option<String>,
+}
+
+impl TryFrom<types::RefundsResponseRouterData<api::Execute, RefundResponse>>
+    for types::RefundsRouterData<api::Execute>
+{
+    type Error = error_stack::Report<errors::ParsingError>;
+    fn try_from(
+        item: types::RefundsResponseRouterData<api::Execute, RefundResponse>,
+    ) -> Result<Self, Self::Error> {
+        let (connector_refund_id, refund_status) = match item.response.data {
+            Some(data) => (data.id, enums::RefundStatus::from(data.status)),
+            None => (
+                item.response.status.error_code,
+                enums::RefundStatus::Failure,
+            ),
+        };
+        Ok(Self {
+            response: Ok(types::RefundsResponseData {
+                connector_refund_id,
+                refund_status,
+            }),
+            ..item.data
+        })
+    }
+}
+
+impl TryFrom<types::RefundsResponseRouterData<api::RSync, RefundResponse>>
+    for types::RefundsRouterData<api::RSync>
+{
+    type Error = error_stack::Report<errors::ParsingError>;
+    fn try_from(
+        item: types::RefundsResponseRouterData<api::RSync, RefundResponse>,
+    ) -> Result<Self, Self::Error> {
+        let (connector_refund_id, refund_status) = match item.response.data {
+            Some(data) => (data.id, enums::RefundStatus::from(data.status)),
+            None => (
+                item.response.status.error_code,
+                enums::RefundStatus::Failure,
+            ),
+        };
+        Ok(Self {
+            response: Ok(types::RefundsResponseData {
+                connector_refund_id,
+                refund_status,
+            }),
+            ..item.data
+        })
+    }
+}
+
+#[derive(Debug, Serialize, Clone)]
+pub struct CaptureRequest {
+    amount: Option<i64>,
+    receipt_email: Option<String>,
+    statement_descriptor: Option<String>,
+}
+
+impl TryFrom<&types::PaymentsCaptureRouterData> for CaptureRequest {
+    type Error = error_stack::Report<errors::ParsingError>;
+    fn try_from(item: &types::PaymentsCaptureRouterData) -> Result<Self, Self::Error> {
+        Ok(Self {
+            amount: item.request.amount_to_capture,
+            receipt_email: None,
+            statement_descriptor: None,
+        })
+    }
+}
+
+impl TryFrom<types::PaymentsCaptureResponseRouterData<RapydPaymentsResponse>>
+    for types::PaymentsCaptureRouterData
+{
+    type Error = error_stack::Report<errors::ParsingError>;
+    fn try_from(
+        item: types::PaymentsCaptureResponseRouterData<RapydPaymentsResponse>,
+    ) -> Result<Self, Self::Error> {
+        let (status, response) = match item.response.status.status.as_str() {
+            "SUCCESS" => match item.response.data {
+                Some(data) => (
+                    enums::AttemptStatus::foreign_from((data.status, data.next_action)),
+                    Ok(types::PaymentsResponseData::TransactionResponse {
+                        resource_id: types::ResponseId::ConnectorTransactionId(data.id), //transaction_id is also the field but this id is used to initiate a refund
+                        redirection_data: None,
+                        redirect: false,
+                        mandate_reference: None,
+                        connector_metadata: None,
+                    }),
+                ),
+                None => (
+                    enums::AttemptStatus::Failure,
+                    Err(types::ErrorResponse {
+                        code: item.response.status.error_code,
+                        message: item.response.status.status,
+                        reason: item.response.status.message,
+                    }),
+                ),
+            },
+            "ERROR" => (
+                enums::AttemptStatus::Failure,
+                Err(types::ErrorResponse {
+                    code: item.response.status.error_code,
+                    message: item.response.status.status,
+                    reason: item.response.status.message,
+                }),
+            ),
+            _ => (
+                enums::AttemptStatus::Failure,
+                Err(types::ErrorResponse {
+                    code: item.response.status.error_code,
+                    message: item.response.status.status,
+                    reason: item.response.status.message,
+                }),
+            ),
+        };
+
+        Ok(Self {
+            status,
+            response,
+            ..item.data
+        })
+    }
+}
+
+impl TryFrom<types::PaymentsCancelResponseRouterData<RapydPaymentsResponse>>
+    for types::PaymentsCancelRouterData
+{
+    type Error = error_stack::Report<errors::ParsingError>;
+    fn try_from(
+        item: types::PaymentsCancelResponseRouterData<RapydPaymentsResponse>,
+    ) -> Result<Self, Self::Error> {
+        let (status, response) = match item.response.status.status.as_str() {
+            "SUCCESS" => match item.response.data {
+                Some(data) => (
+                    enums::AttemptStatus::foreign_from((data.status, data.next_action)),
+                    Ok(types::PaymentsResponseData::TransactionResponse {
+                        resource_id: types::ResponseId::ConnectorTransactionId(data.id), //transaction_id is also the field but this id is used to initiate a refund
+                        redirection_data: None,
+                        redirect: false,
+                        mandate_reference: None,
+                        connector_metadata: None,
+                    }),
+                ),
+                None => (
+                    enums::AttemptStatus::Failure,
+                    Err(types::ErrorResponse {
+                        code: item.response.status.error_code,
+                        message: item.response.status.status,
+                        reason: item.response.status.message,
+                    }),
+                ),
+            },
+            "ERROR" => (
+                enums::AttemptStatus::Failure,
+                Err(types::ErrorResponse {
+                    code: item.response.status.error_code,
+                    message: item.response.status.status,
+                    reason: item.response.status.message,
+                }),
+            ),
+            _ => (
+                enums::AttemptStatus::Failure,
+                Err(types::ErrorResponse {
+                    code: item.response.status.error_code,
+                    message: item.response.status.status,
+                    reason: item.response.status.message,
+                }),
+            ),
+        };
+
+        Ok(Self {
+            status,
+            response,
+            ..item.data
+        })
+    }
+}
diff --git a/crates/router/src/consts.rs b/crates/router/src/consts.rs
index 687d7e6ac0e..3d30069981e 100644
--- a/crates/router/src/consts.rs
+++ b/crates/router/src/consts.rs
@@ -19,3 +19,6 @@ pub(crate) const NO_ERROR_CODE: &str = "No error code";
 // General purpose base64 engine
 pub(crate) const BASE64_ENGINE: base64::engine::GeneralPurpose =
     base64::engine::general_purpose::STANDARD;
+
+pub(crate) const BASE64_ENGINE_URL_SAFE: base64::engine::GeneralPurpose =
+    base64::engine::general_purpose::URL_SAFE;
diff --git a/crates/router/src/types/api.rs b/crates/router/src/types/api.rs
index 0c21a8a45ca..d8baef0eab5 100644
--- a/crates/router/src/types/api.rs
+++ b/crates/router/src/types/api.rs
@@ -150,6 +150,7 @@ impl ConnectorData {
             "globalpay" => Ok(Box::new(&connector::Globalpay)),
             "klarna" => Ok(Box::new(&connector::Klarna)),
             "payu" => Ok(Box::new(&connector::Payu)),
+            "rapyd" => Ok(Box::new(&connector::Rapyd)),
             "shift4" => Ok(Box::new(&connector::Shift4)),
             "stripe" => Ok(Box::new(&connector::Stripe)),
             "worldline" => Ok(Box::new(&connector::Worldline)),
diff --git a/crates/router/tests/connectors/connector_auth.rs b/crates/router/tests/connectors/connector_auth.rs
index 9bb2cabdcc3..0e74e936fde 100644
--- a/crates/router/tests/connectors/connector_auth.rs
+++ b/crates/router/tests/connectors/connector_auth.rs
@@ -9,6 +9,7 @@ pub(crate) struct ConnectorAuthentication {
     pub fiserv: Option<SignatureKey>,
     pub globalpay: Option<HeaderKey>,
     pub payu: Option<BodyKey>,
+    pub rapyd: Option<BodyKey>,
     pub shift4: Option<HeaderKey>,
     pub worldpay: Option<HeaderKey>,
     pub worldline: Option<SignatureKey>,
diff --git a/crates/router/tests/connectors/main.rs b/crates/router/tests/connectors/main.rs
index 39bb16e7a4b..2cd9aa7d240 100644
--- a/crates/router/tests/connectors/main.rs
+++ b/crates/router/tests/connectors/main.rs
@@ -7,6 +7,7 @@ mod connector_auth;
 mod fiserv;
 mod globalpay;
 mod payu;
+mod rapyd;
 mod shift4;
 mod utils;
 mod worldline;
diff --git a/crates/router/tests/connectors/rapyd.rs b/crates/router/tests/connectors/rapyd.rs
new file mode 100644
index 00000000000..0625d3b08d3
--- /dev/null
+++ b/crates/router/tests/connectors/rapyd.rs
@@ -0,0 +1,144 @@
+use futures::future::OptionFuture;
+use masking::Secret;
+use router::types::{self, api, storage::enums};
+use serial_test::serial;
+
+use crate::{
+    connector_auth,
+    utils::{self, ConnectorActions},
+};
+
+struct Rapyd;
+impl ConnectorActions for Rapyd {}
+impl utils::Connector for Rapyd {
+    fn get_data(&self) -> types::api::ConnectorData {
+        use router::connector::Rapyd;
+        types::api::ConnectorData {
+            connector: Box::new(&Rapyd),
+            connector_name: types::Connector::Rapyd,
+            get_token: types::api::GetToken::Connector,
+        }
+    }
+
+    fn get_auth_token(&self) -> types::ConnectorAuthType {
+        types::ConnectorAuthType::from(
+            connector_auth::ConnectorAuthentication::new()
+                .rapyd
+                .expect("Missing connector authentication configuration"),
+        )
+    }
+
+    fn get_name(&self) -> String {
+        "rapyd".to_string()
+    }
+}
+
+#[actix_web::test]
+async fn should_only_authorize_payment() {
+    let response = Rapyd {}
+        .authorize_payment(
+            Some(types::PaymentsAuthorizeData {
+                payment_method_data: types::api::PaymentMethod::Card(api::CCard {
+                    card_number: Secret::new("4111111111111111".to_string()),
+                    card_exp_month: Secret::new("02".to_string()),
+                    card_exp_year: Secret::new("2024".to_string()),
+                    card_holder_name: Secret::new("John Doe".to_string()),
+                    card_cvc: Secret::new("123".to_string()),
+                }),
+                capture_method: Some(storage_models::enums::CaptureMethod::Manual),
+                ..utils::PaymentAuthorizeType::default().0
+            }),
+            None,
+        )
+        .await;
+    assert_eq!(response.status, enums::AttemptStatus::Authorized);
+}
+
+#[actix_web::test]
+async fn should_authorize_and_capture_payment() {
+    let response = Rapyd {}
+        .make_payment(
+            Some(types::PaymentsAuthorizeData {
+                payment_method_data: types::api::PaymentMethod::Card(api::CCard {
+                    card_number: Secret::new("4111111111111111".to_string()),
+                    card_exp_month: Secret::new("02".to_string()),
+                    card_exp_year: Secret::new("2024".to_string()),
+                    card_holder_name: Secret::new("John Doe".to_string()),
+                    card_cvc: Secret::new("123".to_string()),
+                }),
+                ..utils::PaymentAuthorizeType::default().0
+            }),
+            None,
+        )
+        .await;
+    assert_eq!(response.status, enums::AttemptStatus::Charged);
+}
+
+#[actix_web::test]
+async fn should_capture_already_authorized_payment() {
+    let connector = Rapyd {};
+    let authorize_response = connector.authorize_payment(None, None).await;
+    assert_eq!(authorize_response.status, enums::AttemptStatus::Authorized);
+    let txn_id = utils::get_connector_transaction_id(authorize_response);
+    let response: OptionFuture<_> = txn_id
+        .map(|transaction_id| async move {
+            connector
+                .capture_payment(transaction_id, None, None)
+                .await
+                .status
+        })
+        .into();
+    assert_eq!(response.await, Some(enums::AttemptStatus::Charged));
+}
+
+#[actix_web::test]
+#[serial]
+async fn voiding_already_authorized_payment_fails() {
+    let connector = Rapyd {};
+    let authorize_response = connector.authorize_payment(None, None).await;
+    assert_eq!(authorize_response.status, enums::AttemptStatus::Authorized);
+    let txn_id = utils::get_connector_transaction_id(authorize_response);
+    let response: OptionFuture<_> = txn_id
+        .map(|transaction_id| async move {
+            connector
+                .void_payment(transaction_id, None, None)
+                .await
+                .status
+        })
+        .into();
+    assert_eq!(response.await, Some(enums::AttemptStatus::Failure)); //rapyd doesn't allow authorize transaction to be voided
+}
+
+#[actix_web::test]
+async fn should_refund_succeeded_payment() {
+    let connector = Rapyd {};
+    //make a successful payment
+    let response = connector.make_payment(None, None).await;
+
+    //try refund for previous payment
+    if let Some(transaction_id) = utils::get_connector_transaction_id(response) {
+        let response = connector.refund_payment(transaction_id, None, None).await;
+        assert_eq!(
+            response.response.unwrap().refund_status,
+            enums::RefundStatus::Success,
+        );
+    }
+}
+
+#[actix_web::test]
+async fn should_fail_payment_for_incorrect_card_number() {
+    let response = Rapyd {}
+        .make_payment(
+            Some(types::PaymentsAuthorizeData {
+                payment_method_data: types::api::PaymentMethod::Card(api::CCard {
+                    card_number: Secret::new("0000000000000000".to_string()),
+                    ..utils::CCardType::default().0
+                }),
+                ..utils::PaymentAuthorizeType::default().0
+            }),
+            None,
+        )
+        .await;
+
+    assert!(response.response.is_err(), "The Payment pass");
+}
diff --git a/crates/router/tests/connectors/sample_auth.toml b/crates/router/tests/connectors/sample_auth.toml
index a161b71c078..fcb5c75df2f 100644
--- a/crates/router/tests/connectors/sample_auth.toml
+++ b/crates/router/tests/connectors/sample_auth.toml
@@ -26,6 +26,10 @@ key1 = "MerchantPosId"
 [globalpay]
 api_key = "Bearer MyApiKey"
 
+[rapyd]
+api_key = "access_key"
+key1 = "secret_key"
+
 [fiserv]
 api_key = "MyApiKey"
 key1 = "MerchantID"

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

- [ ] Bugfix
- [x] New feature
- [ ] Enhancement
- [ ] Refactoring
- [ ] Dependency updates

## Description
Integrating Rapyd Card transaction flow (no_three_ds, refund)


### Additional Changes

- [ ] 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).
-->


## How did you test it?
Doing No-3ds, Separate authorize + capture transaction with rapyd
<img width="1533" alt="Screenshot 2023-01-16 at 9 05 12 PM" src="https://user-images.githubusercontent.com/118727120/212717259-38015cfc-52ac-437c-bb7d-9f9008cae12b.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
