Bug: Add Paypal for card payments




diff --git a/config/Development.toml b/config/Development.toml
index 2989e035c65..25110882c8d 100644
--- a/config/Development.toml
+++ b/config/Development.toml
@@ -67,6 +67,7 @@ cards = [
     "mollie",
     "multisafepay",
     "nuvei",
+    "paypal",
     "payu",
     "shift4",
     "stripe",
@@ -106,6 +107,7 @@ klarna.base_url = "https://api-na.playground.klarna.com/"
 mollie.base_url = "https://api.mollie.com/v2/"
 multisafepay.base_url = "https://testapi.multisafepay.com/"
 nuvei.base_url = "https://ppp-test.nuvei.com/"
+paypal.base_url = "https://www.sandbox.paypal.com/"
 payu.base_url = "https://secure.snd.payu.com/"
 rapyd.base_url = "https://sandboxapi.rapyd.net"
 shift4.base_url = "https://api.shift4.com/"
diff --git a/config/config.example.toml b/config/config.example.toml
index 9f6652bc3c4..597d1181da3 100644
--- a/config/config.example.toml
+++ b/config/config.example.toml
@@ -141,6 +141,7 @@ klarna.base_url = "https://api-na.playground.klarna.com/"
 mollie.base_url = "https://api.mollie.com/v2/"
 multisafepay.base_url = "https://testapi.multisafepay.com/"
 nuvei.base_url = "https://ppp-test.nuvei.com/"
+paypal.base_url = "https://www.sandbox.paypal.com/" 
 payu.base_url = "https://secure.snd.payu.com/"
 rapyd.base_url = "https://sandboxapi.rapyd.net"
 shift4.base_url = "https://api.shift4.com/"
@@ -154,16 +155,16 @@ trustpay.base_url_bank_redirects = "https://aapi.trustpay.eu/"
 [connectors.supported]
 wallets = ["klarna", "braintree", "applepay"]
 cards = [
-    "stripe",
     "adyen",
     "authorizedotnet",
-    "checkout",
     "braintree",
+    "checkout",
     "cybersource",
     "mollie",
+    "paypal",
     "shift4",
+    "stripe",
     "worldpay",
-    "globalpay",
 ]
 
 # Scheduler settings provides a point to modify the behaviour of scheduler flow.
diff --git a/config/docker_compose.toml b/config/docker_compose.toml
index fdcb63d2a0c..50de78bf669 100644
--- a/config/docker_compose.toml
+++ b/config/docker_compose.toml
@@ -86,6 +86,7 @@ klarna.base_url = "https://api-na.playground.klarna.com/"
 mollie.base_url = "https://api.mollie.com/v2/"
 multisafepay.base_url = "https://testapi.multisafepay.com/"
 nuvei.base_url = "https://ppp-test.nuvei.com/"
+paypal.base_url = "https://www.sandbox.paypal.com/"
 payu.base_url = "https://secure.snd.payu.com/"
 rapyd.base_url = "https://sandboxapi.rapyd.net"
 shift4.base_url = "https://api.shift4.com/"
@@ -95,6 +96,7 @@ worldpay.base_url = "https://try.access.worldpay.com/"
 trustpay.base_url = "https://test-tpgw.trustpay.eu/"
 trustpay.base_url_bank_redirects = "https://aapi.trustpay.eu/"
 
+
 [connectors.supported]
 wallets = ["klarna", "braintree", "applepay"]
 cards = [
@@ -113,6 +115,7 @@ cards = [
     "mollie",
     "multisafepay",
     "nuvei",
+    "paypal",
     "payu",
     "shift4",
     "stripe",
diff --git a/crates/api_models/src/enums.rs b/crates/api_models/src/enums.rs
index 3b4fce23d2a..20a77790965 100644
--- a/crates/api_models/src/enums.rs
+++ b/crates/api_models/src/enums.rs
@@ -572,6 +572,7 @@ pub enum Connector {
     Mollie,
     Multisafepay,
     Nuvei,
+    Paypal,
     Payu,
     Rapyd,
     Shift4,
@@ -587,6 +588,7 @@ impl Connector {
             (self, payment_method),
             (Self::Airwallex, _)
                 | (Self::Globalpay, _)
+                | (Self::Paypal, _)
                 | (Self::Payu, _)
                 | (Self::Trustpay, PaymentMethod::BankRedirect)
         )
@@ -624,6 +626,7 @@ pub enum RoutableConnectors {
     Mollie,
     Multisafepay,
     Nuvei,
+    Paypal,
     Payu,
     Rapyd,
     Shift4,
diff --git a/crates/router/src/configs/settings.rs b/crates/router/src/configs/settings.rs
index eb88a48c9de..70833e20cbd 100644
--- a/crates/router/src/configs/settings.rs
+++ b/crates/router/src/configs/settings.rs
@@ -263,6 +263,7 @@ pub struct Connectors {
     pub mollie: ConnectorParams,
     pub multisafepay: ConnectorParams,
     pub nuvei: ConnectorParams,
+    pub paypal: ConnectorParams,
     pub payu: ConnectorParams,
     pub rapyd: ConnectorParams,
     pub shift4: ConnectorParams,
diff --git a/crates/router/src/connector.rs b/crates/router/src/connector.rs
index 9f5211d0530..4a3299daffe 100644
--- a/crates/router/src/connector.rs
+++ b/crates/router/src/connector.rs
@@ -14,6 +14,7 @@ pub mod globalpay;
 pub mod klarna;
 pub mod multisafepay;
 pub mod nuvei;
+pub mod paypal;
 pub mod payu;
 pub mod rapyd;
 pub mod shift4;
@@ -30,6 +31,6 @@ pub use self::{
     authorizedotnet::Authorizedotnet, bambora::Bambora, bluesnap::Bluesnap, braintree::Braintree,
     checkout::Checkout, cybersource::Cybersource, dlocal::Dlocal, fiserv::Fiserv,
     globalpay::Globalpay, klarna::Klarna, mollie::Mollie, multisafepay::Multisafepay, nuvei::Nuvei,
-    payu::Payu, rapyd::Rapyd, shift4::Shift4, stripe::Stripe, trustpay::Trustpay,
+    paypal::Paypal, payu::Payu, rapyd::Rapyd, shift4::Shift4, stripe::Stripe, trustpay::Trustpay,
     worldline::Worldline, worldpay::Worldpay,
 };
diff --git a/crates/router/src/connector/paypal.rs b/crates/router/src/connector/paypal.rs
new file mode 100644
index 00000000000..d1f15c12f9f
--- /dev/null
+++ b/crates/router/src/connector/paypal.rs
@@ -0,0 +1,781 @@
+mod transformers;
+use std::fmt::Debug;
+
+use base64::Engine;
+use error_stack::{IntoReport, ResultExt};
+use transformers as paypal;
+
+use self::transformers::PaypalMeta;
+use crate::{
+    configs::settings,
+    connector::utils::{to_connector_meta, RefundsRequestData},
+    consts,
+    core::{
+        errors::{self, CustomResult},
+        payments,
+    },
+    headers,
+    services::{self, ConnectorIntegration, PaymentAction},
+    types::{
+        self,
+        api::{self, CompleteAuthorize, ConnectorCommon, ConnectorCommonExt},
+        ErrorResponse, Response,
+    },
+    utils::{self, BytesExt},
+};
+
+#[derive(Debug, Clone)]
+pub struct Paypal;
+
+impl api::Payment for Paypal {}
+impl api::PaymentSession for Paypal {}
+impl api::ConnectorAccessToken for Paypal {}
+impl api::PreVerify for Paypal {}
+impl api::PaymentAuthorize for Paypal {}
+impl api::PaymentsCompleteAuthorize for Paypal {}
+impl api::PaymentSync for Paypal {}
+impl api::PaymentCapture for Paypal {}
+impl api::PaymentVoid for Paypal {}
+impl api::Refund for Paypal {}
+impl api::RefundExecute for Paypal {}
+impl api::RefundSync for Paypal {}
+
+impl Paypal {
+    pub fn connector_transaction_id(
+        &self,
+        connector_meta: &Option<serde_json::Value>,
+    ) -> CustomResult<Option<String>, errors::ConnectorError> {
+        let meta: PaypalMeta = to_connector_meta(connector_meta.clone())?;
+        Ok(meta.authorize_id)
+    }
+
+    pub fn get_order_error_response(
+        &self,
+        res: Response,
+    ) -> CustomResult<ErrorResponse, errors::ConnectorError> {
+        //Handled error response separately for Orders as the end point is different for Orders - (Authorize) and Payments - (Capture, void, refund, rsync).
+        //Error response have different fields for Orders and Payments.
+        let response: paypal::PaypalOrderErrorResponse = res
+            .response
+            .parse_struct("Paypal ErrorResponse")
+            .change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
+
+        let message = match response.details {
+            Some(mes) => {
+                let mut des = "".to_owned();
+                for item in mes.iter() {
+                    let mut description = format!("description - {}", item.to_owned().description);
+
+                    if let Some(data) = &item.value {
+                        description.push_str(format!(", value - {}", data.to_owned()).as_str());
+                    }
+
+                    if let Some(data) = &item.field {
+                        let field = data
+                            .clone()
+                            .split('/')
+                            .last()
+                            .unwrap_or_default()
+                            .to_owned();
+
+                        description.push_str(format!(", field - {};", field).as_str());
+                    }
+                    des.push_str(description.as_str())
+                }
+                des
+            }
+            None => consts::NO_ERROR_MESSAGE.to_string(),
+        };
+        Ok(ErrorResponse {
+            status_code: res.status_code,
+            code: response.name,
+            message,
+            reason: None,
+        })
+    }
+}
+
+impl<Flow, Request, Response> ConnectorCommonExt<Flow, Request, Response> for Paypal
+where
+    Self: ConnectorIntegration<Flow, Request, Response>,
+{
+    fn build_headers(
+        &self,
+        req: &types::RouterData<Flow, Request, Response>,
+        _connectors: &settings::Connectors,
+    ) -> CustomResult<Vec<(String, String)>, errors::ConnectorError> {
+        let access_token = req
+            .access_token
+            .clone()
+            .ok_or(errors::ConnectorError::FailedToObtainAuthType)?;
+        let key = &req.attempt_id;
+
+        Ok(vec![
+            (
+                headers::CONTENT_TYPE.to_string(),
+                self.get_content_type().to_string(),
+            ),
+            (
+                headers::AUTHORIZATION.to_string(),
+                format!("Bearer {}", access_token.token),
+            ),
+            ("Prefer".to_string(), "return=representation".to_string()),
+            ("PayPal-Request-Id".to_string(), key.to_string()),
+        ])
+    }
+}
+
+impl ConnectorCommon for Paypal {
+    fn id(&self) -> &'static str {
+        "paypal"
+    }
+
+    fn common_get_content_type(&self) -> &'static str {
+        "application/json"
+    }
+
+    fn base_url<'a>(&self, connectors: &'a settings::Connectors) -> &'a str {
+        connectors.paypal.base_url.as_ref()
+    }
+
+    fn get_auth_header(
+        &self,
+        auth_type: &types::ConnectorAuthType,
+    ) -> CustomResult<Vec<(String, String)>, errors::ConnectorError> {
+        let auth: paypal::PaypalAuthType = auth_type
+            .try_into()
+            .change_context(errors::ConnectorError::FailedToObtainAuthType)?;
+        Ok(vec![(headers::AUTHORIZATION.to_string(), auth.api_key)])
+    }
+
+    fn build_error_response(
+        &self,
+        res: Response,
+    ) -> CustomResult<ErrorResponse, errors::ConnectorError> {
+        let response: paypal::PaypalPaymentErrorResponse = res
+            .response
+            .parse_struct("Paypal ErrorResponse")
+            .change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
+
+        let message = match response.details {
+            Some(mes) => {
+                let mut des = "".to_owned();
+                for item in mes.iter() {
+                    let x = item.clone().description;
+                    let st = format!("description - {} ; ", x);
+                    des.push_str(&st);
+                }
+                des
+            }
+            None => consts::NO_ERROR_MESSAGE.to_string(),
+        };
+
+        Ok(ErrorResponse {
+            status_code: res.status_code,
+            code: response.name,
+            message,
+            reason: None,
+        })
+    }
+}
+
+impl ConnectorIntegration<api::Session, types::PaymentsSessionData, types::PaymentsResponseData>
+    for Paypal
+{
+    //TODO: implement sessions flow
+}
+
+impl ConnectorIntegration<api::AccessTokenAuth, types::AccessTokenRequestData, types::AccessToken>
+    for Paypal
+{
+    fn get_url(
+        &self,
+        _req: &types::RefreshTokenRouterData,
+        connectors: &settings::Connectors,
+    ) -> CustomResult<String, errors::ConnectorError> {
+        Ok(format!("{}v1/oauth2/token", self.base_url(connectors)))
+    }
+    fn get_content_type(&self) -> &'static str {
+        "application/x-www-form-urlencoded"
+    }
+    fn get_headers(
+        &self,
+        req: &types::RefreshTokenRouterData,
+        _connectors: &settings::Connectors,
+    ) -> CustomResult<Vec<(String, String)>, errors::ConnectorError> {
+        let auth: paypal::PaypalAuthType = (&req.connector_auth_type)
+            .try_into()
+            .change_context(errors::ConnectorError::FailedToObtainAuthType)?;
+
+        let auth_id = format!("{}:{}", auth.key1, auth.api_key);
+        let auth_val = format!("Basic {}", consts::BASE64_ENGINE.encode(auth_id));
+
+        Ok(vec![
+            (
+                headers::CONTENT_TYPE.to_string(),
+                types::RefreshTokenType::get_content_type(self).to_string(),
+            ),
+            (headers::AUTHORIZATION.to_string(), auth_val),
+        ])
+    }
+    fn get_request_body(
+        &self,
+        req: &types::RefreshTokenRouterData,
+    ) -> CustomResult<Option<String>, errors::ConnectorError> {
+        let paypal_req =
+            utils::Encode::<paypal::PaypalAuthUpdateRequest>::convert_and_url_encode(req)
+                .change_context(errors::ConnectorError::RequestEncodingFailed)?;
+
+        Ok(Some(paypal_req))
+    }
+
+    fn build_request(
+        &self,
+        req: &types::RefreshTokenRouterData,
+        connectors: &settings::Connectors,
+    ) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
+        let req = Some(
+            services::RequestBuilder::new()
+                .method(services::Method::Post)
+                .headers(types::RefreshTokenType::get_headers(self, req, connectors)?)
+                .url(&types::RefreshTokenType::get_url(self, req, connectors)?)
+                .body(types::RefreshTokenType::get_request_body(self, req)?)
+                .build(),
+        );
+
+        Ok(req)
+    }
+
+    fn handle_response(
+        &self,
+        data: &types::RefreshTokenRouterData,
+        res: Response,
+    ) -> CustomResult<types::RefreshTokenRouterData, errors::ConnectorError> {
+        let response: paypal::PaypalAuthUpdateResponse = res
+            .response
+            .parse_struct("Paypal PaypalAuthUpdateResponse")
+            .change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
+
+        types::RouterData::try_from(types::ResponseRouterData {
+            response,
+            data: data.clone(),
+            http_code: res.status_code,
+        })
+    }
+
+    fn get_error_response(
+        &self,
+        res: Response,
+    ) -> CustomResult<ErrorResponse, errors::ConnectorError> {
+        let response: paypal::PaypalAccessTokenErrorResponse = res
+            .response
+            .parse_struct("Paypal AccessTokenErrorResponse")
+            .change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
+
+        Ok(ErrorResponse {
+            status_code: res.status_code,
+            code: response.error,
+            message: response.error_description,
+            reason: None,
+        })
+    }
+}
+
+impl ConnectorIntegration<api::Verify, types::VerifyRequestData, types::PaymentsResponseData>
+    for Paypal
+{
+}
+
+impl ConnectorIntegration<api::Authorize, types::PaymentsAuthorizeData, types::PaymentsResponseData>
+    for Paypal
+{
+    fn get_headers(
+        &self,
+        req: &types::PaymentsAuthorizeRouterData,
+        connectors: &settings::Connectors,
+    ) -> CustomResult<Vec<(String, String)>, errors::ConnectorError> {
+        self.build_headers(req, connectors)
+    }
+
+    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!("{}v2/checkout/orders", self.base_url(connectors),))
+    }
+
+    fn get_request_body(
+        &self,
+        req: &types::PaymentsAuthorizeRouterData,
+    ) -> CustomResult<Option<String>, errors::ConnectorError> {
+        let req_obj = paypal::PaypalPaymentsRequest::try_from(req)?;
+        let paypal_req =
+            utils::Encode::<paypal::PaypalPaymentsRequest>::encode_to_string_of_json(&req_obj)
+                .change_context(errors::ConnectorError::RequestEncodingFailed)?;
+        Ok(Some(paypal_req))
+    }
+
+    fn build_request(
+        &self,
+        req: &types::PaymentsAuthorizeRouterData,
+        connectors: &settings::Connectors,
+    ) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
+        Ok(Some(
+            services::RequestBuilder::new()
+                .method(services::Method::Post)
+                .url(&types::PaymentsAuthorizeType::get_url(
+                    self, req, connectors,
+                )?)
+                .headers(types::PaymentsAuthorizeType::get_headers(
+                    self, req, connectors,
+                )?)
+                .body(types::PaymentsAuthorizeType::get_request_body(self, req)?)
+                .build(),
+        ))
+    }
+
+    fn handle_response(
+        &self,
+        data: &types::PaymentsAuthorizeRouterData,
+        res: Response,
+    ) -> CustomResult<types::PaymentsAuthorizeRouterData, errors::ConnectorError> {
+        let response: paypal::PaypalOrdersResponse = res
+            .response
+            .parse_struct("Paypal PaymentsAuthorizeResponse")
+            .change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
+        types::RouterData::try_from(types::ResponseRouterData {
+            response,
+            data: data.clone(),
+            http_code: res.status_code,
+        })
+    }
+
+    fn get_error_response(
+        &self,
+        res: Response,
+    ) -> CustomResult<ErrorResponse, errors::ConnectorError> {
+        self.get_order_error_response(res)
+    }
+}
+
+impl
+    ConnectorIntegration<
+        CompleteAuthorize,
+        types::CompleteAuthorizeData,
+        types::PaymentsResponseData,
+    > for Paypal
+{
+}
+
+impl ConnectorIntegration<api::PSync, types::PaymentsSyncData, types::PaymentsResponseData>
+    for Paypal
+{
+    fn get_headers(
+        &self,
+        req: &types::PaymentsSyncRouterData,
+        connectors: &settings::Connectors,
+    ) -> CustomResult<Vec<(String, String)>, errors::ConnectorError> {
+        self.build_headers(req, connectors)
+    }
+
+    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> {
+        let capture_id = req
+            .request
+            .connector_transaction_id
+            .get_connector_transaction_id()
+            .change_context(errors::ConnectorError::MissingConnectorTransactionID)?;
+        let paypal_meta: PaypalMeta = to_connector_meta(req.request.connector_meta.clone())?;
+        let psync_url = match paypal_meta.psync_flow {
+            transformers::PaypalPaymentIntent::Authorize => format!(
+                "v2/payments/authorizations/{}",
+                paypal_meta.authorize_id.unwrap_or_default()
+            ),
+            transformers::PaypalPaymentIntent::Capture => {
+                format!("v2/payments/captures/{}", capture_id)
+            }
+        };
+        Ok(format!("{}{}", self.base_url(connectors), psync_url,))
+    }
+
+    fn build_request(
+        &self,
+        req: &types::PaymentsSyncRouterData,
+        connectors: &settings::Connectors,
+    ) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
+        Ok(Some(
+            services::RequestBuilder::new()
+                .method(services::Method::Get)
+                .url(&types::PaymentsSyncType::get_url(self, req, connectors)?)
+                .headers(types::PaymentsSyncType::get_headers(self, req, connectors)?)
+                .build(),
+        ))
+    }
+
+    fn handle_response(
+        &self,
+        data: &types::PaymentsSyncRouterData,
+        res: Response,
+    ) -> CustomResult<types::PaymentsSyncRouterData, errors::ConnectorError> {
+        let response: paypal::PaypalPaymentsSyncResponse = res
+            .response
+            .parse_struct("paypal PaymentsSyncResponse")
+            .change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
+        types::RouterData::try_from(types::ResponseRouterData {
+            response,
+            data: data.clone(),
+            http_code: res.status_code,
+        })
+    }
+
+    fn get_error_response(
+        &self,
+        res: Response,
+    ) -> CustomResult<ErrorResponse, errors::ConnectorError> {
+        self.build_error_response(res)
+    }
+}
+
+impl ConnectorIntegration<api::Capture, types::PaymentsCaptureData, types::PaymentsResponseData>
+    for Paypal
+{
+    fn get_headers(
+        &self,
+        req: &types::PaymentsCaptureRouterData,
+        connectors: &settings::Connectors,
+    ) -> CustomResult<Vec<(String, String)>, errors::ConnectorError> {
+        self.build_headers(req, connectors)
+    }
+
+    fn get_content_type(&self) -> &'static str {
+        self.common_get_content_type()
+    }
+
+    fn get_url(
+        &self,
+        req: &types::PaymentsCaptureRouterData,
+        connectors: &settings::Connectors,
+    ) -> CustomResult<String, errors::ConnectorError> {
+        let paypal_meta: PaypalMeta = to_connector_meta(req.request.connector_meta.clone())?;
+        let authorize_id = paypal_meta.authorize_id.ok_or(
+            errors::ConnectorError::RequestEncodingFailedWithReason(
+                "Missing Authorize id".to_string(),
+            ),
+        )?;
+        Ok(format!(
+            "{}v2/payments/authorizations/{}/capture",
+            self.base_url(connectors),
+            authorize_id
+        ))
+    }
+
+    fn get_request_body(
+        &self,
+        req: &types::PaymentsCaptureRouterData,
+    ) -> CustomResult<Option<String>, errors::ConnectorError> {
+        let connector_req = paypal::PaypalPaymentsCaptureRequest::try_from(req)?;
+        let paypal_req =
+            utils::Encode::<paypal::PaypalPaymentsCaptureRequest>::encode_to_string_of_json(
+                &connector_req,
+            )
+            .change_context(errors::ConnectorError::RequestEncodingFailed)?;
+        Ok(Some(paypal_req))
+    }
+
+    fn build_request(
+        &self,
+        req: &types::PaymentsCaptureRouterData,
+        connectors: &settings::Connectors,
+    ) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
+        Ok(Some(
+            services::RequestBuilder::new()
+                .method(services::Method::Post)
+                .url(&types::PaymentsCaptureType::get_url(self, req, connectors)?)
+                .headers(types::PaymentsCaptureType::get_headers(
+                    self, req, connectors,
+                )?)
+                .build(),
+        ))
+    }
+
+    fn handle_response(
+        &self,
+        data: &types::PaymentsCaptureRouterData,
+        res: Response,
+    ) -> CustomResult<types::PaymentsCaptureRouterData, errors::ConnectorError> {
+        let response: paypal::PaymentCaptureResponse = res
+            .response
+            .parse_struct("Paypal PaymentsCaptureResponse")
+            .change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
+        types::RouterData::try_from(types::ResponseRouterData {
+            response,
+            data: data.clone(),
+            http_code: res.status_code,
+        })
+    }
+
+    fn get_error_response(
+        &self,
+        res: Response,
+    ) -> CustomResult<ErrorResponse, errors::ConnectorError> {
+        self.build_error_response(res)
+    }
+}
+
+impl ConnectorIntegration<api::Void, types::PaymentsCancelData, types::PaymentsResponseData>
+    for Paypal
+{
+    fn get_headers(
+        &self,
+        req: &types::PaymentsCancelRouterData,
+        connectors: &settings::Connectors,
+    ) -> CustomResult<Vec<(String, String)>, errors::ConnectorError> {
+        self.build_headers(req, connectors)
+    }
+
+    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> {
+        let paypal_meta: PaypalMeta = to_connector_meta(req.request.connector_meta.clone())?;
+        let authorize_id = paypal_meta.authorize_id.ok_or(
+            errors::ConnectorError::RequestEncodingFailedWithReason(
+                "Missing Authorize id".to_string(),
+            ),
+        )?;
+        Ok(format!(
+            "{}v2/payments/authorizations/{}/void",
+            self.base_url(connectors),
+            authorize_id,
+        ))
+    }
+
+    fn build_request(
+        &self,
+        req: &types::PaymentsCancelRouterData,
+        connectors: &settings::Connectors,
+    ) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
+        let request = services::RequestBuilder::new()
+            .method(services::Method::Post)
+            .url(&types::PaymentsVoidType::get_url(self, req, connectors)?)
+            .headers(types::PaymentsVoidType::get_headers(self, req, connectors)?)
+            .build();
+
+        Ok(Some(request))
+    }
+
+    fn handle_response(
+        &self,
+        data: &types::PaymentsCancelRouterData,
+        res: Response,
+    ) -> CustomResult<types::PaymentsCancelRouterData, errors::ConnectorError> {
+        let response: paypal::PaypalPaymentsCancelResponse = res
+            .response
+            .parse_struct("PaymentCancelResponse")
+            .change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
+        types::RouterData::try_from(types::ResponseRouterData {
+            response,
+            data: data.clone(),
+            http_code: res.status_code,
+        })
+    }
+    fn get_error_response(
+        &self,
+        res: Response,
+    ) -> CustomResult<ErrorResponse, errors::ConnectorError> {
+        self.build_error_response(res)
+    }
+}
+
+impl ConnectorIntegration<api::Execute, types::RefundsData, types::RefundsResponseData> for Paypal {
+    fn get_headers(
+        &self,
+        req: &types::RefundsRouterData<api::Execute>,
+        connectors: &settings::Connectors,
+    ) -> CustomResult<Vec<(String, String)>, errors::ConnectorError> {
+        self.build_headers(req, connectors)
+    }
+
+    fn get_content_type(&self) -> &'static str {
+        self.common_get_content_type()
+    }
+
+    fn get_url(
+        &self,
+        req: &types::RefundsRouterData<api::Execute>,
+        connectors: &settings::Connectors,
+    ) -> CustomResult<String, errors::ConnectorError> {
+        let id = req.request.connector_transaction_id.clone();
+        Ok(format!(
+            "{}v2/payments/captures/{}/refund",
+            self.base_url(connectors),
+            id,
+        ))
+    }
+
+    fn get_request_body(
+        &self,
+        req: &types::RefundsRouterData<api::Execute>,
+    ) -> CustomResult<Option<String>, errors::ConnectorError> {
+        let req_obj = paypal::PaypalRefundRequest::try_from(req)?;
+        let paypal_req =
+            utils::Encode::<paypal::PaypalRefundRequest>::encode_to_string_of_json(&req_obj)
+                .change_context(errors::ConnectorError::RequestEncodingFailed)?;
+        Ok(Some(paypal_req))
+    }
+
+    fn build_request(
+        &self,
+        req: &types::RefundsRouterData<api::Execute>,
+        connectors: &settings::Connectors,
+    ) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
+        let request = services::RequestBuilder::new()
+            .method(services::Method::Post)
+            .url(&types::RefundExecuteType::get_url(self, req, connectors)?)
+            .headers(types::RefundExecuteType::get_headers(
+                self, req, connectors,
+            )?)
+            .body(types::RefundExecuteType::get_request_body(self, req)?)
+            .build();
+
+        Ok(Some(request))
+    }
+
+    fn handle_response(
+        &self,
+        data: &types::RefundsRouterData<api::Execute>,
+        res: Response,
+    ) -> CustomResult<types::RefundsRouterData<api::Execute>, errors::ConnectorError> {
+        let response: paypal::RefundResponse =
+            res.response
+                .parse_struct("paypal RefundResponse")
+                .change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
+        types::RouterData::try_from(types::ResponseRouterData {
+            response,
+            data: data.clone(),
+            http_code: res.status_code,
+        })
+    }
+
+    fn get_error_response(
+        &self,
+        res: Response,
+    ) -> CustomResult<ErrorResponse, errors::ConnectorError> {
+        self.build_error_response(res)
+    }
+}
+
+impl ConnectorIntegration<api::RSync, types::RefundsData, types::RefundsResponseData> for Paypal {
+    fn get_headers(
+        &self,
+        req: &types::RefundSyncRouterData,
+        connectors: &settings::Connectors,
+    ) -> CustomResult<Vec<(String, String)>, errors::ConnectorError> {
+        self.build_headers(req, connectors)
+    }
+
+    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> {
+        Ok(format!(
+            "{}v2/payments/refunds/{}",
+            self.base_url(connectors),
+            req.request.get_connector_refund_id()?
+        ))
+    }
+
+    fn build_request(
+        &self,
+        req: &types::RefundSyncRouterData,
+        connectors: &settings::Connectors,
+    ) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
+        Ok(Some(
+            services::RequestBuilder::new()
+                .method(services::Method::Get)
+                .url(&types::RefundSyncType::get_url(self, req, connectors)?)
+                .headers(types::RefundSyncType::get_headers(self, req, connectors)?)
+                .build(),
+        ))
+    }
+
+    fn handle_response(
+        &self,
+        data: &types::RefundSyncRouterData,
+        res: Response,
+    ) -> CustomResult<types::RefundSyncRouterData, errors::ConnectorError> {
+        let response: paypal::RefundSyncResponse = res
+            .response
+            .parse_struct("paypal RefundSyncResponse")
+            .change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
+        types::RouterData::try_from(types::ResponseRouterData {
+            response,
+            data: data.clone(),
+            http_code: res.status_code,
+        })
+    }
+
+    fn get_error_response(
+        &self,
+        res: Response,
+    ) -> CustomResult<ErrorResponse, errors::ConnectorError> {
+        self.build_error_response(res)
+    }
+}
+
+#[async_trait::async_trait]
+impl api::IncomingWebhook for Paypal {
+    fn get_webhook_object_reference_id(
+        &self,
+        _request: &api::IncomingWebhookRequestDetails<'_>,
+    ) -> CustomResult<api_models::webhooks::ObjectReferenceId, errors::ConnectorError> {
+        Err(errors::ConnectorError::WebhooksNotImplemented).into_report()
+    }
+
+    fn get_webhook_event_type(
+        &self,
+        _request: &api::IncomingWebhookRequestDetails<'_>,
+    ) -> CustomResult<api::IncomingWebhookEvent, errors::ConnectorError> {
+        Err(errors::ConnectorError::WebhooksNotImplemented).into_report()
+    }
+
+    fn get_webhook_resource_object(
+        &self,
+        _request: &api::IncomingWebhookRequestDetails<'_>,
+    ) -> CustomResult<serde_json::Value, errors::ConnectorError> {
+        Err(errors::ConnectorError::WebhooksNotImplemented).into_report()
+    }
+}
+
+impl services::ConnectorRedirectResponse for Paypal {
+    fn get_flow_type(
+        &self,
+        _query_params: &str,
+        _json_payload: Option<serde_json::Value>,
+        _action: PaymentAction,
+    ) -> CustomResult<payments::CallConnectorAction, errors::ConnectorError> {
+        Ok(payments::CallConnectorAction::Trigger)
+    }
+}
diff --git a/crates/router/src/connector/paypal/transformers.rs b/crates/router/src/connector/paypal/transformers.rs
new file mode 100644
index 00000000000..8a4ce0d3a3a
--- /dev/null
+++ b/crates/router/src/connector/paypal/transformers.rs
@@ -0,0 +1,608 @@
+use common_utils::errors::CustomResult;
+use masking::Secret;
+use serde::{Deserialize, Serialize};
+
+use crate::{
+    connector::utils::{
+        to_connector_meta, AccessTokenRequestInfo, AddressDetailsData, CardData,
+        PaymentsAuthorizeRequestData,
+    },
+    core::errors,
+    pii,
+    types::{self, api, storage::enums as storage_enums, transformers::ForeignFrom},
+};
+
+#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)]
+#[serde(rename_all = "UPPERCASE")]
+pub enum PaypalPaymentIntent {
+    Capture,
+    Authorize,
+}
+
+#[derive(Default, Debug, Clone, Serialize, Eq, PartialEq, Deserialize)]
+pub struct OrderAmount {
+    currency_code: storage_enums::Currency,
+    value: String,
+}
+
+#[derive(Default, Debug, Serialize, Eq, PartialEq)]
+pub struct PurchaseUnitRequest {
+    reference_id: String,
+    amount: OrderAmount,
+}
+
+#[derive(Debug, Serialize)]
+pub struct Address {
+    address_line_1: Option<Secret<String>>,
+    postal_code: Option<Secret<String>>,
+    country_code: api_models::enums::CountryCode,
+}
+
+#[derive(Debug, Serialize)]
+pub struct CardRequest {
+    billing_address: Option<Address>,
+    expiry: Option<Secret<String>>,
+    name: Secret<String>,
+    number: Option<Secret<String, pii::CardNumber>>,
+    security_code: Option<Secret<String>>,
+}
+
+#[derive(Debug, Serialize)]
+#[serde(rename_all = "lowercase")]
+pub enum PaymentSourceItem {
+    Card(CardRequest),
+}
+
+#[derive(Debug, Serialize)]
+pub struct PaypalPaymentsRequest {
+    intent: PaypalPaymentIntent,
+    purchase_units: Vec<PurchaseUnitRequest>,
+    payment_source: Option<PaymentSourceItem>,
+}
+
+fn get_address_info(
+    payment_address: Option<&api_models::payments::Address>,
+) -> Result<Option<Address>, error_stack::Report<errors::ConnectorError>> {
+    let address = payment_address.and_then(|payment_address| payment_address.address.as_ref());
+    let address = match address {
+        Some(address) => Some(Address {
+            country_code: address.get_country()?.to_owned(),
+            address_line_1: address.line1.clone(),
+            postal_code: address.zip.clone(),
+        }),
+        None => None,
+    };
+    Ok(address)
+}
+
+impl TryFrom<&types::PaymentsAuthorizeRouterData> for PaypalPaymentsRequest {
+    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::PaymentMethodData::Card(ref ccard) => {
+                let intent = match item.request.is_auto_capture() {
+                    true => PaypalPaymentIntent::Capture,
+                    false => PaypalPaymentIntent::Authorize,
+                };
+                let amount = OrderAmount {
+                    currency_code: item.request.currency,
+                    value: item.request.amount.to_string(),
+                };
+                let reference_id = item.attempt_id.clone();
+
+                let purchase_units = vec![PurchaseUnitRequest {
+                    reference_id,
+                    amount,
+                }];
+                let card = item.request.get_card()?;
+                let expiry = Some(card.get_expiry_date_as_yyyymm("-"));
+
+                let payment_source = Some(PaymentSourceItem::Card(CardRequest {
+                    billing_address: get_address_info(item.address.billing.as_ref())?,
+                    expiry,
+                    name: ccard.card_holder_name.clone(),
+                    number: Some(ccard.card_number.clone()),
+                    security_code: Some(ccard.card_cvc.clone()),
+                }));
+
+                Ok(Self {
+                    intent,
+                    purchase_units,
+                    payment_source,
+                })
+            }
+            _ => Err(errors::ConnectorError::NotImplemented("Payment Method".to_string()).into()),
+        }
+    }
+}
+
+#[derive(Debug, Clone, Serialize, PartialEq)]
+pub struct PaypalAuthUpdateRequest {
+    grant_type: String,
+    client_id: String,
+    client_secret: String,
+}
+impl TryFrom<&types::RefreshTokenRouterData> for PaypalAuthUpdateRequest {
+    type Error = error_stack::Report<errors::ConnectorError>;
+    fn try_from(item: &types::RefreshTokenRouterData) -> Result<Self, Self::Error> {
+        Ok(Self {
+            grant_type: "client_credentials".to_string(),
+            client_id: item.get_request_id()?,
+            client_secret: item.request.app_id.clone(),
+        })
+    }
+}
+
+#[derive(Default, Debug, Clone, Deserialize, PartialEq)]
+pub struct PaypalAuthUpdateResponse {
+    pub access_token: String,
+    pub token_type: String,
+    pub expires_in: i64,
+}
+
+impl<F, T> TryFrom<types::ResponseRouterData<F, PaypalAuthUpdateResponse, T, types::AccessToken>>
+    for types::RouterData<F, T, types::AccessToken>
+{
+    type Error = error_stack::Report<errors::ConnectorError>;
+    fn try_from(
+        item: types::ResponseRouterData<F, PaypalAuthUpdateResponse, T, types::AccessToken>,
+    ) -> Result<Self, Self::Error> {
+        Ok(Self {
+            response: Ok(types::AccessToken {
+                token: item.response.access_token,
+                expires: item.response.expires_in,
+            }),
+            ..item.data
+        })
+    }
+}
+
+#[derive(Debug)]
+pub struct PaypalAuthType {
+    pub(super) api_key: String,
+    pub(super) key1: String,
+}
+
+impl TryFrom<&types::ConnectorAuthType> for PaypalAuthType {
+    type Error = error_stack::Report<errors::ConnectorError>;
+    fn try_from(auth_type: &types::ConnectorAuthType) -> Result<Self, Self::Error> {
+        match auth_type {
+            types::ConnectorAuthType::BodyKey { api_key, key1 } => Ok(Self {
+                api_key: api_key.to_string(),
+                key1: key1.to_string(),
+            }),
+            _ => Err(errors::ConnectorError::FailedToObtainAuthType)?,
+        }
+    }
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
+#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
+pub enum PaypalOrderStatus {
+    Completed,
+    Voided,
+    Created,
+    Saved,
+    PayerActionRequired,
+    Approved,
+}
+
+impl ForeignFrom<(PaypalOrderStatus, PaypalPaymentIntent)> for storage_enums::AttemptStatus {
+    fn foreign_from(item: (PaypalOrderStatus, PaypalPaymentIntent)) -> Self {
+        match item.0 {
+            PaypalOrderStatus::Completed => {
+                if item.1 == PaypalPaymentIntent::Authorize {
+                    Self::Authorized
+                } else {
+                    Self::Charged
+                }
+            }
+            PaypalOrderStatus::Voided => Self::Voided,
+            PaypalOrderStatus::Created | PaypalOrderStatus::Saved | PaypalOrderStatus::Approved => {
+                Self::Pending
+            }
+            PaypalOrderStatus::PayerActionRequired => Self::Authorizing,
+        }
+    }
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub struct PaymentsCollectionItem {
+    amount: OrderAmount,
+    expiration_time: Option<String>,
+    id: String,
+    final_capture: Option<bool>,
+    status: PaypalOrderStatus,
+}
+
+#[derive(Default, Debug, Clone, Serialize, Deserialize)]
+pub struct PaymentsCollection {
+    authorizations: Option<Vec<PaymentsCollectionItem>>,
+    captures: Option<Vec<PaymentsCollectionItem>>,
+}
+
+#[derive(Default, Debug, Clone, Serialize, Deserialize)]
+pub struct PurchaseUnitItem {
+    reference_id: String,
+    payments: PaymentsCollection,
+}
+
+#[derive(Debug, Serialize, Deserialize)]
+pub struct PaypalOrdersResponse {
+    id: String,
+    intent: PaypalPaymentIntent,
+    status: PaypalOrderStatus,
+    purchase_units: Vec<PurchaseUnitItem>,
+}
+
+#[derive(Debug, Serialize, Deserialize)]
+pub struct PaypalPaymentsSyncResponse {
+    id: String,
+    status: PaypalPaymentStatus,
+    amount: OrderAmount,
+}
+
+#[derive(Debug, Serialize, Deserialize)]
+pub struct PaypalMeta {
+    pub authorize_id: Option<String>,
+    pub order_id: String,
+    pub psync_flow: PaypalPaymentIntent,
+}
+
+fn get_id_based_on_intent(
+    intent: &PaypalPaymentIntent,
+    purchase_unit: &PurchaseUnitItem,
+) -> CustomResult<String, errors::ConnectorError> {
+    || -> _ {
+        match intent {
+            PaypalPaymentIntent::Capture => Some(
+                purchase_unit
+                    .payments
+                    .captures
+                    .clone()?
+                    .into_iter()
+                    .next()?
+                    .id,
+            ),
+            PaypalPaymentIntent::Authorize => Some(
+                purchase_unit
+                    .payments
+                    .authorizations
+                    .clone()?
+                    .into_iter()
+                    .next()?
+                    .id,
+            ),
+        }
+    }()
+    .ok_or(errors::ConnectorError::MissingConnectorTransactionID.into())
+}
+
+impl<F, T>
+    TryFrom<types::ResponseRouterData<F, PaypalOrdersResponse, T, types::PaymentsResponseData>>
+    for types::RouterData<F, T, types::PaymentsResponseData>
+{
+    type Error = error_stack::Report<errors::ConnectorError>;
+    fn try_from(
+        item: types::ResponseRouterData<F, PaypalOrdersResponse, T, types::PaymentsResponseData>,
+    ) -> Result<Self, Self::Error> {
+        let purchase_units = item
+            .response
+            .purchase_units
+            .first()
+            .ok_or(errors::ConnectorError::MissingConnectorTransactionID)?;
+
+        let id = get_id_based_on_intent(&item.response.intent, purchase_units)?;
+        let (connector_meta, capture_id) = match item.response.intent.clone() {
+            PaypalPaymentIntent::Capture => (
+                serde_json::json!(PaypalMeta {
+                    authorize_id: None,
+                    order_id: item.response.id,
+                    psync_flow: item.response.intent.clone()
+                }),
+                types::ResponseId::ConnectorTransactionId(id),
+            ),
+
+            PaypalPaymentIntent::Authorize => (
+                serde_json::json!(PaypalMeta {
+                    authorize_id: Some(id),
+                    order_id: item.response.id,
+                    psync_flow: item.response.intent.clone()
+                }),
+                types::ResponseId::NoResponseId,
+            ),
+        };
+        Ok(Self {
+            status: storage_enums::AttemptStatus::foreign_from((
+                item.response.status,
+                item.response.intent,
+            )),
+            response: Ok(types::PaymentsResponseData::TransactionResponse {
+                resource_id: capture_id,
+                redirection_data: None,
+                mandate_reference: None,
+                connector_metadata: Some(connector_meta),
+            }),
+            ..item.data
+        })
+    }
+}
+
+impl<F, T>
+    TryFrom<
+        types::ResponseRouterData<F, PaypalPaymentsSyncResponse, T, types::PaymentsResponseData>,
+    > for types::RouterData<F, T, types::PaymentsResponseData>
+{
+    type Error = error_stack::Report<errors::ConnectorError>;
+    fn try_from(
+        item: types::ResponseRouterData<
+            F,
+            PaypalPaymentsSyncResponse,
+            T,
+            types::PaymentsResponseData,
+        >,
+    ) -> Result<Self, Self::Error> {
+        Ok(Self {
+            status: storage_enums::AttemptStatus::from(item.response.status),
+            response: Ok(types::PaymentsResponseData::TransactionResponse {
+                resource_id: types::ResponseId::ConnectorTransactionId(item.response.id),
+                redirection_data: None,
+                mandate_reference: None,
+                connector_metadata: None,
+            }),
+            ..item.data
+        })
+    }
+}
+
+#[derive(Debug, Serialize)]
+pub struct PaypalPaymentsCaptureRequest {
+    amount: OrderAmount,
+    final_capture: bool,
+}
+
+impl TryFrom<&types::PaymentsCaptureRouterData> for PaypalPaymentsCaptureRequest {
+    type Error = error_stack::Report<errors::ConnectorError>;
+    fn try_from(item: &types::PaymentsCaptureRouterData) -> Result<Self, Self::Error> {
+        let amount = OrderAmount {
+            currency_code: item.request.currency,
+            value: item.request.amount_to_capture.to_string(),
+        };
+        Ok(Self {
+            amount,
+            final_capture: true,
+        })
+    }
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
+#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
+pub enum PaypalPaymentStatus {
+    Created,
+    Captured,
+    Completed,
+    Declined,
+    Failed,
+    Pending,
+    Denied,
+    Expired,
+    PartiallyCaptured,
+    Refunded,
+}
+
+#[derive(Debug, Serialize, Deserialize)]
+pub struct PaymentCaptureResponse {
+    id: String,
+    status: PaypalPaymentStatus,
+    amount: Option<OrderAmount>,
+    final_capture: bool,
+}
+
+impl From<PaypalPaymentStatus> for storage_enums::AttemptStatus {
+    fn from(item: PaypalPaymentStatus) -> Self {
+        match item {
+            PaypalPaymentStatus::Created => Self::Authorized,
+            PaypalPaymentStatus::Completed
+            | PaypalPaymentStatus::Captured
+            | PaypalPaymentStatus::Refunded => Self::Charged,
+            PaypalPaymentStatus::Declined => Self::Failure,
+            PaypalPaymentStatus::Failed => Self::CaptureFailed,
+            PaypalPaymentStatus::Pending => Self::Pending,
+            PaypalPaymentStatus::Denied | PaypalPaymentStatus::Expired => Self::Failure,
+            PaypalPaymentStatus::PartiallyCaptured => Self::PartialCharged,
+        }
+    }
+}
+
+impl TryFrom<types::PaymentsCaptureResponseRouterData<PaymentCaptureResponse>>
+    for types::PaymentsCaptureRouterData
+{
+    type Error = error_stack::Report<errors::ConnectorError>;
+    fn try_from(
+        item: types::PaymentsCaptureResponseRouterData<PaymentCaptureResponse>,
+    ) -> Result<Self, Self::Error> {
+        let amount_captured = item.data.request.amount_to_capture;
+        let status = storage_enums::AttemptStatus::from(item.response.status);
+        let connector_payment_id: PaypalMeta =
+            to_connector_meta(item.data.request.connector_meta.clone())?;
+        Ok(Self {
+            status,
+            response: Ok(types::PaymentsResponseData::TransactionResponse {
+                resource_id: types::ResponseId::ConnectorTransactionId(item.response.id),
+                redirection_data: None,
+                mandate_reference: None,
+                connector_metadata: Some(serde_json::json!(PaypalMeta {
+                    authorize_id: connector_payment_id.authorize_id,
+                    order_id: item.data.request.connector_transaction_id.clone(),
+                    psync_flow: PaypalPaymentIntent::Capture
+                })),
+            }),
+            amount_captured: Some(amount_captured),
+            ..item.data
+        })
+    }
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
+#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
+pub enum PaypalCancelStatus {
+    Voided,
+}
+
+#[derive(Debug, Serialize, Deserialize, PartialEq)]
+#[serde(rename_all = "camelCase")]
+pub struct PaypalPaymentsCancelResponse {
+    id: String,
+    status: PaypalCancelStatus,
+    amount: Option<OrderAmount>,
+}
+
+impl<F, T>
+    TryFrom<
+        types::ResponseRouterData<F, PaypalPaymentsCancelResponse, T, types::PaymentsResponseData>,
+    > for types::RouterData<F, T, types::PaymentsResponseData>
+{
+    type Error = error_stack::Report<errors::ConnectorError>;
+    fn try_from(
+        item: types::ResponseRouterData<
+            F,
+            PaypalPaymentsCancelResponse,
+            T,
+            types::PaymentsResponseData,
+        >,
+    ) -> Result<Self, Self::Error> {
+        let status = match item.response.status {
+            PaypalCancelStatus::Voided => storage_enums::AttemptStatus::Voided,
+        };
+        Ok(Self {
+            status,
+            response: Ok(types::PaymentsResponseData::TransactionResponse {
+                resource_id: types::ResponseId::ConnectorTransactionId(item.response.id),
+                redirection_data: None,
+                mandate_reference: None,
+                connector_metadata: None,
+            }),
+            ..item.data
+        })
+    }
+}
+
+#[derive(Default, Debug, Serialize)]
+pub struct PaypalRefundRequest {
+    pub amount: OrderAmount,
+}
+
+impl<F> TryFrom<&types::RefundsRouterData<F>> for PaypalRefundRequest {
+    type Error = error_stack::Report<errors::ConnectorError>;
+    fn try_from(item: &types::RefundsRouterData<F>) -> Result<Self, Self::Error> {
+        Ok(Self {
+            amount: OrderAmount {
+                currency_code: item.request.currency,
+                value: item.request.refund_amount.to_string(),
+            },
+        })
+    }
+}
+
+#[allow(dead_code)]
+#[derive(Debug, Serialize, Deserialize, Clone)]
+#[serde(rename_all = "UPPERCASE")]
+pub enum RefundStatus {
+    Completed,
+    Failed,
+    Cancelled,
+    Pending,
+}
+
+impl From<RefundStatus> for storage_enums::RefundStatus {
+    fn from(item: RefundStatus) -> Self {
+        match item {
+            RefundStatus::Completed => Self::Success,
+            RefundStatus::Failed | RefundStatus::Cancelled => Self::Failure,
+            RefundStatus::Pending => Self::Pending,
+        }
+    }
+}
+
+#[derive(Debug, Serialize, Deserialize)]
+pub struct RefundResponse {
+    id: String,
+    status: RefundStatus,
+    amount: Option<OrderAmount>,
+}
+
+impl TryFrom<types::RefundsResponseRouterData<api::Execute, RefundResponse>>
+    for types::RefundsRouterData<api::Execute>
+{
+    type Error = error_stack::Report<errors::ConnectorError>;
+    fn try_from(
+        item: types::RefundsResponseRouterData<api::Execute, RefundResponse>,
+    ) -> Result<Self, Self::Error> {
+        Ok(Self {
+            response: Ok(types::RefundsResponseData {
+                connector_refund_id: item.response.id,
+                refund_status: storage_enums::RefundStatus::from(item.response.status),
+            }),
+            ..item.data
+        })
+    }
+}
+
+#[derive(Debug, Clone, Deserialize)]
+pub struct RefundSyncResponse {
+    id: String,
+    status: RefundStatus,
+}
+
+impl TryFrom<types::RefundsResponseRouterData<api::RSync, RefundSyncResponse>>
+    for types::RefundsRouterData<api::RSync>
+{
+    type Error = error_stack::Report<errors::ConnectorError>;
+    fn try_from(
+        item: types::RefundsResponseRouterData<api::RSync, RefundSyncResponse>,
+    ) -> Result<Self, Self::Error> {
+        Ok(Self {
+            response: Ok(types::RefundsResponseData {
+                connector_refund_id: item.response.id,
+                refund_status: storage_enums::RefundStatus::from(item.response.status),
+            }),
+            ..item.data
+        })
+    }
+}
+
+#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq)]
+pub struct OrderErrorDetails {
+    pub issue: String,
+    pub description: String,
+    pub value: Option<String>,
+    pub field: Option<String>,
+}
+
+#[derive(Default, Debug, Serialize, Deserialize, PartialEq)]
+pub struct PaypalOrderErrorResponse {
+    pub name: String,
+    pub message: String,
+    pub debug_id: Option<String>,
+    pub details: Option<Vec<OrderErrorDetails>>,
+}
+
+#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq)]
+pub struct ErrorDetails {
+    pub issue: String,
+    pub description: String,
+}
+
+#[derive(Default, Debug, Serialize, Deserialize, PartialEq)]
+pub struct PaypalPaymentErrorResponse {
+    pub name: String,
+    pub message: String,
+    pub debug_id: Option<String>,
+    pub details: Option<Vec<ErrorDetails>>,
+}
+
+#[derive(Deserialize, Debug)]
+pub struct PaypalAccessTokenErrorResponse {
+    pub error: String,
+    pub error_description: String,
+}
diff --git a/crates/router/src/connector/trustpay/transformers.rs b/crates/router/src/connector/trustpay/transformers.rs
index 9f7030c5dcd..a28c6f2bcc1 100644
--- a/crates/router/src/connector/trustpay/transformers.rs
+++ b/crates/router/src/connector/trustpay/transformers.rs
@@ -114,7 +114,7 @@ pub struct PaymentRequestCards {
     pub pan: Secret<String, pii::CardNumber>,
     pub cvv: Secret<String>,
     #[serde(rename = "exp")]
-    pub expiry_date: String,
+    pub expiry_date: Secret<String>,
     pub cardholder: Secret<String>,
     pub reference: String,
     #[serde(rename = "redirectUrl")]
diff --git a/crates/router/src/connector/utils.rs b/crates/router/src/connector/utils.rs
index cc57764eea7..f7eda87d089 100644
--- a/crates/router/src/connector/utils.rs
+++ b/crates/router/src/connector/utils.rs
@@ -247,7 +247,11 @@ pub enum CardIssuer {
 pub trait CardData {
     fn get_card_expiry_year_2_digit(&self) -> Secret<String>;
     fn get_card_issuer(&self) -> Result<CardIssuer, Error>;
-    fn get_card_expiry_month_year_2_digit_with_delimiter(&self, delimiter: String) -> String;
+    fn get_card_expiry_month_year_2_digit_with_delimiter(
+        &self,
+        delimiter: String,
+    ) -> Secret<String>;
+    fn get_expiry_date_as_yyyymm(&self, delimiter: &str) -> Secret<String>;
 }
 
 impl CardData for api::Card {
@@ -263,14 +267,29 @@ impl CardData for api::Card {
             .map(|card| card.split_whitespace().collect());
         get_card_issuer(card.peek().clone().as_str())
     }
-    fn get_card_expiry_month_year_2_digit_with_delimiter(&self, delimiter: String) -> String {
+    fn get_card_expiry_month_year_2_digit_with_delimiter(
+        &self,
+        delimiter: String,
+    ) -> Secret<String> {
         let year = self.get_card_expiry_year_2_digit();
-        format!(
+        Secret::new(format!(
             "{}{}{}",
             self.card_exp_month.peek().clone(),
             delimiter,
             year.peek()
-        )
+        ))
+    }
+    fn get_expiry_date_as_yyyymm(&self, delimiter: &str) -> Secret<String> {
+        let mut x = self.card_exp_year.peek().clone();
+        if x.len() == 2 {
+            x = format!("20{}", x);
+        }
+        Secret::new(format!(
+            "{}{}{}",
+            x,
+            delimiter,
+            self.card_exp_month.peek().clone()
+        ))
     }
 }
 
diff --git a/crates/router/src/core/payments/flows.rs b/crates/router/src/core/payments/flows.rs
index 6a51588e072..1fba3783b0d 100644
--- a/crates/router/src/core/payments/flows.rs
+++ b/crates/router/src/core/payments/flows.rs
@@ -133,3 +133,38 @@ default_imp_for_connector_redirect_response!(
     connector::Worldline,
     connector::Worldpay
 );
+
+macro_rules! default_imp_for_connector_request_id{
+    ($($path:ident::$connector:ident),*)=> {
+        $(
+            impl api::ConnectorTransactionId for $path::$connector {}
+    )*
+    };
+}
+
+default_imp_for_connector_request_id!(
+    connector::Aci,
+    connector::Adyen,
+    connector::Airwallex,
+    connector::Applepay,
+    connector::Authorizedotnet,
+    connector::Bambora,
+    connector::Bluesnap,
+    connector::Braintree,
+    connector::Checkout,
+    connector::Cybersource,
+    connector::Dlocal,
+    connector::Fiserv,
+    connector::Globalpay,
+    connector::Klarna,
+    connector::Mollie,
+    connector::Multisafepay,
+    connector::Nuvei,
+    connector::Payu,
+    connector::Rapyd,
+    connector::Shift4,
+    connector::Stripe,
+    connector::Trustpay,
+    connector::Worldline,
+    connector::Worldpay
+);
diff --git a/crates/router/src/core/payments/transformers.rs b/crates/router/src/core/payments/transformers.rs
index 5fa7a235cea..96c22dd57eb 100644
--- a/crates/router/src/core/payments/transformers.rs
+++ b/crates/router/src/core/payments/transformers.rs
@@ -6,6 +6,7 @@ use router_env::{instrument, tracing};
 use super::{flows::Feature, PaymentAddress, PaymentData};
 use crate::{
     configs::settings::Server,
+    connector::Paypal,
     core::{
         errors::{self, RouterResponse, RouterResult},
         payments::{self, helpers},
@@ -28,11 +29,11 @@ pub async fn construct_payment_router_data<'a, F, T>(
     merchant_account: &storage::MerchantAccount,
 ) -> RouterResult<types::RouterData<F, T, types::PaymentsResponseData>>
 where
-    T: TryFrom<PaymentAdditionalData<F>>,
+    T: TryFrom<PaymentAdditionalData<'a, F>>,
     types::RouterData<F, T, types::PaymentsResponseData>: Feature<F, T>,
     F: Clone,
     error_stack::Report<errors::ApiErrorResponse>:
-        From<<T as TryFrom<PaymentAdditionalData<F>>>::Error>,
+        From<<T as TryFrom<PaymentAdditionalData<'a, F>>>::Error>,
 {
     let (merchant_connector_account, payment_method, router_data);
     let db = &*state.store;
@@ -72,6 +73,7 @@ where
         router_base_url: state.conf.server.base_url.clone(),
         connector_name: connector_id.to_string(),
         payment_data: payment_data.clone(),
+        state,
     };
 
     router_data = types::RouterData {
@@ -423,18 +425,19 @@ impl ForeignTryFrom<(storage::PaymentIntent, storage::PaymentAttempt)> for api::
 }
 
 #[derive(Clone)]
-pub struct PaymentAdditionalData<F>
+pub struct PaymentAdditionalData<'a, F>
 where
     F: Clone,
 {
     router_base_url: String,
     connector_name: String,
     payment_data: PaymentData<F>,
+    state: &'a AppState,
 }
-impl<F: Clone> TryFrom<PaymentAdditionalData<F>> for types::PaymentsAuthorizeData {
+impl<F: Clone> TryFrom<PaymentAdditionalData<'_, F>> for types::PaymentsAuthorizeData {
     type Error = error_stack::Report<errors::ApiErrorResponse>;
 
-    fn try_from(additional_data: PaymentAdditionalData<F>) -> Result<Self, Self::Error> {
+    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;
@@ -509,10 +512,10 @@ impl<F: Clone> TryFrom<PaymentAdditionalData<F>> for types::PaymentsAuthorizeDat
     }
 }
 
-impl<F: Clone> TryFrom<PaymentAdditionalData<F>> for types::PaymentsSyncData {
+impl<F: Clone> TryFrom<PaymentAdditionalData<'_, F>> for types::PaymentsSyncData {
     type Error = errors::ApiErrorResponse;
 
-    fn try_from(additional_data: PaymentAdditionalData<F>) -> Result<Self, Self::Error> {
+    fn try_from(additional_data: PaymentAdditionalData<'_, F>) -> Result<Self, Self::Error> {
         let payment_data = additional_data.payment_data;
         Ok(Self {
             connector_transaction_id: match payment_data.payment_attempt.connector_transaction_id {
@@ -528,11 +531,34 @@ impl<F: Clone> TryFrom<PaymentAdditionalData<F>> for types::PaymentsSyncData {
     }
 }
 
-impl<F: Clone> TryFrom<PaymentAdditionalData<F>> for types::PaymentsCaptureData {
+impl api::ConnectorTransactionId for Paypal {
+    fn connector_transaction_id(
+        &self,
+        payment_attempt: storage::PaymentAttempt,
+    ) -> Result<Option<String>, errors::ApiErrorResponse> {
+        let metadata = Self::connector_transaction_id(self, &payment_attempt.connector_metadata);
+        match metadata {
+            Ok(data) => Ok(data),
+            _ => Err(errors::ApiErrorResponse::ResourceIdNotFound),
+        }
+    }
+}
+
+impl<F: Clone> TryFrom<PaymentAdditionalData<'_, F>> for types::PaymentsCaptureData {
     type Error = errors::ApiErrorResponse;
 
-    fn try_from(additional_data: PaymentAdditionalData<F>) -> Result<Self, Self::Error> {
+    fn try_from(additional_data: PaymentAdditionalData<'_, F>) -> Result<Self, Self::Error> {
         let payment_data = additional_data.payment_data;
+        let connector = api::ConnectorData::get_connector_by_name(
+            &additional_data.state.conf.connectors,
+            &additional_data.connector_name,
+            api::GetToken::Connector,
+        );
+        let connectors = match connector {
+            Ok(conn) => *conn.connector,
+            _ => Err(errors::ApiErrorResponse::ResourceIdNotFound)?,
+        };
+
         let amount_to_capture: i64 = payment_data
             .payment_attempt
             .amount_to_capture
@@ -540,40 +566,45 @@ impl<F: Clone> TryFrom<PaymentAdditionalData<F>> for types::PaymentsCaptureData
         Ok(Self {
             amount_to_capture,
             currency: payment_data.currency,
-            connector_transaction_id: payment_data
-                .payment_attempt
-                .connector_transaction_id
-                .ok_or(errors::ApiErrorResponse::MerchantConnectorAccountNotFound)?,
+            connector_transaction_id: connectors
+                .connector_transaction_id(payment_data.payment_attempt.clone())?
+                .ok_or(errors::ApiErrorResponse::ResourceIdNotFound)?,
             payment_amount: payment_data.amount.into(),
             connector_meta: payment_data.payment_attempt.connector_metadata,
         })
     }
 }
 
-impl<F: Clone> TryFrom<PaymentAdditionalData<F>> for types::PaymentsCancelData {
+impl<F: Clone> TryFrom<PaymentAdditionalData<'_, F>> for types::PaymentsCancelData {
     type Error = errors::ApiErrorResponse;
 
-    fn try_from(additional_data: PaymentAdditionalData<F>) -> Result<Self, Self::Error> {
+    fn try_from(additional_data: PaymentAdditionalData<'_, F>) -> Result<Self, Self::Error> {
         let payment_data = additional_data.payment_data;
+        let connector = api::ConnectorData::get_connector_by_name(
+            &additional_data.state.conf.connectors,
+            &additional_data.connector_name,
+            api::GetToken::Connector,
+        );
+        let connectors = match connector {
+            Ok(conn) => *conn.connector,
+            _ => Err(errors::ApiErrorResponse::ResourceIdNotFound)?,
+        };
         Ok(Self {
             amount: Some(payment_data.amount.into()),
             currency: Some(payment_data.currency),
-            connector_transaction_id: payment_data
-                .payment_attempt
-                .connector_transaction_id
-                .ok_or(errors::ApiErrorResponse::MissingRequiredField {
-                    field_name: "connector_transaction_id",
-                })?,
+            connector_transaction_id: connectors
+                .connector_transaction_id(payment_data.payment_attempt.clone())?
+                .ok_or(errors::ApiErrorResponse::ResourceIdNotFound)?,
             cancellation_reason: payment_data.payment_attempt.cancellation_reason,
             connector_meta: payment_data.payment_attempt.connector_metadata,
         })
     }
 }
 
-impl<F: Clone> TryFrom<PaymentAdditionalData<F>> for types::PaymentsSessionData {
+impl<F: Clone> TryFrom<PaymentAdditionalData<'_, F>> for types::PaymentsSessionData {
     type Error = error_stack::Report<errors::ApiErrorResponse>;
 
-    fn try_from(additional_data: PaymentAdditionalData<F>) -> Result<Self, Self::Error> {
+    fn try_from(additional_data: PaymentAdditionalData<'_, F>) -> Result<Self, Self::Error> {
         let payment_data = additional_data.payment_data;
         let parsed_metadata: Option<api_models::payments::Metadata> = payment_data
             .payment_intent
@@ -602,10 +633,10 @@ impl<F: Clone> TryFrom<PaymentAdditionalData<F>> for types::PaymentsSessionData
     }
 }
 
-impl<F: Clone> TryFrom<PaymentAdditionalData<F>> for types::VerifyRequestData {
+impl<F: Clone> TryFrom<PaymentAdditionalData<'_, F>> for types::VerifyRequestData {
     type Error = error_stack::Report<errors::ApiErrorResponse>;
 
-    fn try_from(additional_data: PaymentAdditionalData<F>) -> Result<Self, Self::Error> {
+    fn try_from(additional_data: PaymentAdditionalData<'_, F>) -> Result<Self, Self::Error> {
         let payment_data = additional_data.payment_data;
         Ok(Self {
             currency: payment_data.currency,
@@ -622,10 +653,10 @@ impl<F: Clone> TryFrom<PaymentAdditionalData<F>> for types::VerifyRequestData {
     }
 }
 
-impl<F: Clone> TryFrom<PaymentAdditionalData<F>> for types::CompleteAuthorizeData {
+impl<F: Clone> TryFrom<PaymentAdditionalData<'_, F>> for types::CompleteAuthorizeData {
     type Error = error_stack::Report<errors::ApiErrorResponse>;
 
-    fn try_from(additional_data: PaymentAdditionalData<F>) -> Result<Self, Self::Error> {
+    fn try_from(additional_data: PaymentAdditionalData<'_, F>) -> Result<Self, Self::Error> {
         let payment_data = additional_data.payment_data;
         let browser_info: Option<types::BrowserInformation> = payment_data
             .payment_attempt
diff --git a/crates/router/src/types/api.rs b/crates/router/src/types/api.rs
index 654800c3c87..0eea03f2a7e 100644
--- a/crates/router/src/types/api.rs
+++ b/crates/router/src/types/api.rs
@@ -35,6 +35,15 @@ pub trait ConnectorAccessToken:
 {
 }
 
+pub trait ConnectorTransactionId: ConnectorCommon + Sync {
+    fn connector_transaction_id(
+        &self,
+        payment_attempt: storage_models::payment_attempt::PaymentAttempt,
+    ) -> Result<Option<String>, errors::ApiErrorResponse> {
+        Ok(payment_attempt.connector_transaction_id)
+    }
+}
+
 pub trait ConnectorCommon {
     /// Name of the connector (in lowercase).
     fn id(&self) -> &'static str;
@@ -90,7 +99,14 @@ pub trait ConnectorCommonExt<Flow, Req, Resp>:
 pub trait Router {}
 
 pub trait Connector:
-    Send + Refund + Payment + Debug + ConnectorRedirectResponse + IncomingWebhook + ConnectorAccessToken
+    Send
+    + Refund
+    + Payment
+    + Debug
+    + ConnectorRedirectResponse
+    + IncomingWebhook
+    + ConnectorAccessToken
+    + ConnectorTransactionId
 {
 }
 
@@ -105,7 +121,8 @@ impl<
             + ConnectorRedirectResponse
             + Send
             + IncomingWebhook
-            + ConnectorAccessToken,
+            + ConnectorAccessToken
+            + ConnectorTransactionId,
     > Connector for T
 {
 }
@@ -189,6 +206,7 @@ impl ConnectorData {
             "worldline" => Ok(Box::new(&connector::Worldline)),
             "worldpay" => Ok(Box::new(&connector::Worldpay)),
             "multisafepay" => Ok(Box::new(&connector::Multisafepay)),
+            "paypal" => Ok(Box::new(&connector::Paypal)),
             "trustpay" => Ok(Box::new(&connector::Trustpay)),
             _ => Err(report!(errors::ConnectorError::InvalidConnectorName)
                 .attach_printable(format!("invalid connector name: {connector_name}")))
diff --git a/crates/router/tests/connectors/connector_auth.rs b/crates/router/tests/connectors/connector_auth.rs
index 15d75ecd53b..daa17722986 100644
--- a/crates/router/tests/connectors/connector_auth.rs
+++ b/crates/router/tests/connectors/connector_auth.rs
@@ -19,6 +19,7 @@ pub(crate) struct ConnectorAuthentication {
     pub mollie: Option<HeaderKey>,
     pub multisafepay: Option<HeaderKey>,
     pub nuvei: Option<SignatureKey>,
+    pub paypal: Option<BodyKey>,
     pub payu: Option<BodyKey>,
     pub rapyd: Option<BodyKey>,
     pub shift4: Option<HeaderKey>,
diff --git a/crates/router/tests/connectors/main.rs b/crates/router/tests/connectors/main.rs
index 62b29ffe989..e314408125f 100644
--- a/crates/router/tests/connectors/main.rs
+++ b/crates/router/tests/connectors/main.rs
@@ -15,6 +15,7 @@ mod globalpay;
 mod mollie;
 mod multisafepay;
 mod nuvei;
+mod paypal;
 mod payu;
 mod rapyd;
 mod shift4;
diff --git a/crates/router/tests/connectors/paypal.rs b/crates/router/tests/connectors/paypal.rs
new file mode 100644
index 00000000000..15b52c9d349
--- /dev/null
+++ b/crates/router/tests/connectors/paypal.rs
@@ -0,0 +1,627 @@
+use masking::Secret;
+use router::types::{self, api, storage::enums, AccessToken, ConnectorAuthType};
+
+use crate::{
+    connector_auth,
+    utils::{self, Connector, ConnectorActions},
+};
+
+struct PaypalTest;
+impl ConnectorActions for PaypalTest {}
+impl Connector for PaypalTest {
+    fn get_data(&self) -> types::api::ConnectorData {
+        use router::connector::Paypal;
+        types::api::ConnectorData {
+            connector: Box::new(&Paypal),
+            connector_name: types::Connector::Paypal,
+            get_token: types::api::GetToken::Connector,
+        }
+    }
+
+    fn get_auth_token(&self) -> ConnectorAuthType {
+        types::ConnectorAuthType::from(
+            connector_auth::ConnectorAuthentication::new()
+                .paypal
+                .expect("Missing connector authentication configuration"),
+        )
+    }
+
+    fn get_name(&self) -> String {
+        "paypal".to_string()
+    }
+}
+static CONNECTOR: PaypalTest = PaypalTest {};
+
+fn get_access_token() -> Option<AccessToken> {
+    let connector = PaypalTest {};
+
+    match connector.get_auth_token() {
+        ConnectorAuthType::BodyKey { api_key, key1: _ } => Some(AccessToken {
+            token: api_key,
+            expires: 18600,
+        }),
+        _ => None,
+    }
+}
+fn get_default_payment_info() -> Option<utils::PaymentInfo> {
+    Some(utils::PaymentInfo {
+        access_token: get_access_token(),
+        ..Default::default()
+    })
+}
+
+fn get_payment_data() -> Option<types::PaymentsAuthorizeData> {
+    Some(types::PaymentsAuthorizeData {
+        payment_method_data: types::api::PaymentMethodData::Card(api::Card {
+            card_number: Secret::new(String::from("4000020000000000")),
+            ..utils::CCardType::default().0
+        }),
+        ..utils::PaymentAuthorizeType::default().0
+    })
+}
+
+// Cards Positive Tests
+// Creates a payment using the manual capture flow (Non 3DS).
+#[actix_web::test]
+async fn should_only_authorize_payment() {
+    let response = CONNECTOR
+        .authorize_payment(get_payment_data(), get_default_payment_info())
+        .await
+        .expect("Authorize payment response");
+    assert_eq!(response.status, enums::AttemptStatus::Authorized);
+}
+
+// Captures a payment using the manual capture flow (Non 3DS).
+#[actix_web::test]
+async fn should_capture_authorized_payment() {
+    let authorize_response = CONNECTOR
+        .authorize_payment(get_payment_data(), get_default_payment_info())
+        .await
+        .expect("Authorize payment response");
+    let txn_id = "".to_string();
+    let connector_meta = utils::get_connector_metadata(authorize_response.response);
+    let response = CONNECTOR
+        .capture_payment(
+            txn_id,
+            Some(types::PaymentsCaptureData {
+                connector_meta,
+                ..utils::PaymentCaptureType::default().0
+            }),
+            get_default_payment_info(),
+        )
+        .await
+        .expect("Capture payment response");
+    assert_eq!(response.status, enums::AttemptStatus::Charged);
+}
+
+// Partially captures a payment using the manual capture flow (Non 3DS).
+#[actix_web::test]
+async fn should_partially_capture_authorized_payment() {
+    let authorize_response = CONNECTOR
+        .authorize_payment(get_payment_data(), get_default_payment_info())
+        .await
+        .expect("Authorize payment response");
+    let txn_id = "".to_string();
+    let connector_meta = utils::get_connector_metadata(authorize_response.response);
+    let response = CONNECTOR
+        .capture_payment(
+            txn_id,
+            Some(types::PaymentsCaptureData {
+                connector_meta,
+                amount_to_capture: 50,
+                ..utils::PaymentCaptureType::default().0
+            }),
+            get_default_payment_info(),
+        )
+        .await
+        .expect("Capture payment response");
+    assert_eq!(response.status, enums::AttemptStatus::Charged);
+}
+
+// Synchronizes a payment using the manual capture flow (Non 3DS).
+#[actix_web::test]
+async fn should_sync_authorized_payment() {
+    let authorize_response = CONNECTOR
+        .authorize_payment(get_payment_data(), get_default_payment_info())
+        .await
+        .expect("Authorize payment response");
+    let txn_id = "".to_string();
+    let connector_meta = utils::get_connector_metadata(authorize_response.response);
+    let response = CONNECTOR
+        .psync_retry_till_status_matches(
+            enums::AttemptStatus::Authorized,
+            Some(types::PaymentsSyncData {
+                connector_transaction_id: router::types::ResponseId::ConnectorTransactionId(txn_id),
+                encoded_data: None,
+                capture_method: None,
+                connector_meta,
+            }),
+            get_default_payment_info(),
+        )
+        .await
+        .expect("PSync response");
+    assert_eq!(response.status, enums::AttemptStatus::Authorized,);
+}
+
+// Voids a payment using the manual capture flow (Non 3DS).
+#[actix_web::test]
+async fn should_void_authorized_payment() {
+    let authorize_response = CONNECTOR
+        .authorize_payment(get_payment_data(), get_default_payment_info())
+        .await
+        .expect("Authorize payment response");
+    let txn_id = "".to_string();
+    let connector_meta = utils::get_connector_metadata(authorize_response.response);
+    let response = CONNECTOR
+        .void_payment(
+            txn_id,
+            Some(types::PaymentsCancelData {
+                connector_transaction_id: String::from(""),
+                cancellation_reason: Some("requested_by_customer".to_string()),
+                connector_meta,
+                ..Default::default()
+            }),
+            get_default_payment_info(),
+        )
+        .await
+        .expect("Void payment response");
+    assert_eq!(response.status, enums::AttemptStatus::Voided);
+}
+
+// Refunds a payment using the manual capture flow (Non 3DS).
+#[actix_web::test]
+async fn should_refund_manually_captured_payment() {
+    let authorize_response = CONNECTOR
+        .authorize_payment(get_payment_data(), get_default_payment_info())
+        .await
+        .expect("Authorize payment response");
+    let txn_id = "".to_string();
+    let capture_connector_meta = utils::get_connector_metadata(authorize_response.response);
+    let capture_response = CONNECTOR
+        .capture_payment(
+            txn_id,
+            Some(types::PaymentsCaptureData {
+                connector_meta: capture_connector_meta,
+                ..utils::PaymentCaptureType::default().0
+            }),
+            get_default_payment_info(),
+        )
+        .await
+        .expect("Capture payment response");
+    let refund_txn_id =
+        utils::get_connector_transaction_id(capture_response.response.clone()).unwrap();
+    let response = CONNECTOR
+        .refund_payment(
+            refund_txn_id,
+            Some(types::RefundsData {
+                ..utils::PaymentRefundType::default().0
+            }),
+            get_default_payment_info(),
+        )
+        .await
+        .unwrap();
+    assert_eq!(
+        response.response.unwrap().refund_status,
+        enums::RefundStatus::Success,
+    );
+}
+
+// Partially refunds a payment using the manual capture flow (Non 3DS).
+#[actix_web::test]
+async fn should_partially_refund_manually_captured_payment() {
+    let authorize_response = CONNECTOR
+        .authorize_payment(get_payment_data(), get_default_payment_info())
+        .await
+        .expect("Authorize payment response");
+    let txn_id = "".to_string();
+    let capture_connector_meta = utils::get_connector_metadata(authorize_response.response);
+    let capture_response = CONNECTOR
+        .capture_payment(
+            txn_id,
+            Some(types::PaymentsCaptureData {
+                connector_meta: capture_connector_meta,
+                ..utils::PaymentCaptureType::default().0
+            }),
+            get_default_payment_info(),
+        )
+        .await
+        .expect("Capture payment response");
+    let refund_txn_id =
+        utils::get_connector_transaction_id(capture_response.response.clone()).unwrap();
+    let response = CONNECTOR
+        .refund_payment(
+            refund_txn_id,
+            Some(types::RefundsData {
+                refund_amount: 50,
+                ..utils::PaymentRefundType::default().0
+            }),
+            get_default_payment_info(),
+        )
+        .await
+        .unwrap();
+    assert_eq!(
+        response.response.unwrap().refund_status,
+        enums::RefundStatus::Success,
+    );
+}
+
+// Synchronizes a refund using the manual capture flow (Non 3DS).
+#[actix_web::test]
+async fn should_sync_manually_captured_refund() {
+    let authorize_response = CONNECTOR
+        .authorize_payment(get_payment_data(), get_default_payment_info())
+        .await
+        .expect("Authorize payment response");
+    let txn_id = "".to_string();
+    let capture_connector_meta = utils::get_connector_metadata(authorize_response.response);
+    let capture_response = CONNECTOR
+        .capture_payment(
+            txn_id,
+            Some(types::PaymentsCaptureData {
+                connector_meta: capture_connector_meta,
+                ..utils::PaymentCaptureType::default().0
+            }),
+            get_default_payment_info(),
+        )
+        .await
+        .expect("Capture payment response");
+    let refund_txn_id =
+        utils::get_connector_transaction_id(capture_response.response.clone()).unwrap();
+    let refund_response = CONNECTOR
+        .refund_payment(
+            refund_txn_id,
+            Some(types::RefundsData {
+                ..utils::PaymentRefundType::default().0
+            }),
+            get_default_payment_info(),
+        )
+        .await
+        .unwrap();
+    let response = CONNECTOR
+        .rsync_retry_till_status_matches(
+            enums::RefundStatus::Success,
+            refund_response.response.unwrap().connector_refund_id,
+            None,
+            get_default_payment_info(),
+        )
+        .await
+        .unwrap();
+    assert_eq!(
+        response.response.unwrap().refund_status,
+        enums::RefundStatus::Success,
+    );
+}
+
+// Creates a payment using the automatic capture flow (Non 3DS).
+#[actix_web::test]
+async fn should_make_payment() {
+    let authorize_response = CONNECTOR
+        .make_payment(get_payment_data(), get_default_payment_info())
+        .await
+        .unwrap();
+    assert_eq!(authorize_response.status, enums::AttemptStatus::Charged);
+}
+
+// Synchronizes a payment using the automatic capture flow (Non 3DS).
+#[actix_web::test]
+async fn should_sync_auto_captured_payment() {
+    let authorize_response = CONNECTOR
+        .make_payment(get_payment_data(), get_default_payment_info())
+        .await
+        .unwrap();
+    assert_eq!(
+        authorize_response.status.clone(),
+        enums::AttemptStatus::Charged
+    );
+    let txn_id = utils::get_connector_transaction_id(authorize_response.response.clone());
+    assert_ne!(txn_id, None, "Empty connector transaction id");
+    let connector_meta = utils::get_connector_metadata(authorize_response.response);
+    let response = CONNECTOR
+        .psync_retry_till_status_matches(
+            enums::AttemptStatus::Charged,
+            Some(types::PaymentsSyncData {
+                connector_transaction_id: router::types::ResponseId::ConnectorTransactionId(
+                    txn_id.unwrap(),
+                ),
+                encoded_data: None,
+                capture_method: Some(enums::CaptureMethod::Automatic),
+                connector_meta,
+            }),
+            get_default_payment_info(),
+        )
+        .await
+        .unwrap();
+    assert_eq!(response.status, enums::AttemptStatus::Charged,);
+}
+
+// Refunds a payment using the automatic capture flow (Non 3DS).
+#[actix_web::test]
+async fn should_refund_auto_captured_payment() {
+    let response = CONNECTOR
+        .make_payment_and_refund(get_payment_data(), None, get_default_payment_info())
+        .await
+        .unwrap();
+    assert_eq!(
+        response.response.unwrap().refund_status,
+        enums::RefundStatus::Success,
+    );
+}
+
+// Partially refunds a payment using the automatic capture flow (Non 3DS).
+#[actix_web::test]
+async fn should_partially_refund_succeeded_payment() {
+    let authorize_response = CONNECTOR
+        .make_payment(get_payment_data(), get_default_payment_info())
+        .await
+        .unwrap();
+
+    let txn_id = utils::get_connector_transaction_id(authorize_response.response.clone()).unwrap();
+    let refund_response = CONNECTOR
+        .refund_payment(
+            txn_id,
+            Some(types::RefundsData {
+                refund_amount: 50,
+                ..utils::PaymentRefundType::default().0
+            }),
+            get_default_payment_info(),
+        )
+        .await
+        .unwrap();
+    assert_eq!(
+        refund_response.response.unwrap().refund_status,
+        enums::RefundStatus::Success,
+    );
+}
+
+// Creates multiple refunds against a payment using the automatic capture flow (Non 3DS).
+#[actix_web::test]
+async fn should_refund_succeeded_payment_multiple_times() {
+    let authorize_response = CONNECTOR
+        .make_payment(get_payment_data(), get_default_payment_info())
+        .await
+        .unwrap();
+
+    let txn_id = utils::get_connector_transaction_id(authorize_response.response.clone()).unwrap();
+    for _x in 0..2 {
+        let refund_response = CONNECTOR
+            .refund_payment(
+                txn_id.clone(),
+                Some(types::RefundsData {
+                    refund_amount: 50,
+                    ..utils::PaymentRefundType::default().0
+                }),
+                get_default_payment_info(),
+            )
+            .await
+            .unwrap();
+        assert_eq!(
+            refund_response.response.unwrap().refund_status,
+            enums::RefundStatus::Success,
+        );
+    }
+}
+
+// Synchronizes a refund using the automatic capture flow (Non 3DS).
+#[actix_web::test]
+async fn should_sync_refund() {
+    let refund_response = CONNECTOR
+        .make_payment_and_refund(get_payment_data(), None, get_default_payment_info())
+        .await
+        .unwrap();
+    let response = CONNECTOR
+        .rsync_retry_till_status_matches(
+            enums::RefundStatus::Success,
+            refund_response.response.unwrap().connector_refund_id,
+            None,
+            get_default_payment_info(),
+        )
+        .await
+        .unwrap();
+    assert_eq!(
+        response.response.unwrap().refund_status,
+        enums::RefundStatus::Success,
+    );
+}
+
+// Cards Negative scenerios
+// Creates a payment with incorrect card number.
+#[actix_web::test]
+async fn should_fail_payment_for_incorrect_card_number() {
+    let response = CONNECTOR
+        .make_payment(
+            Some(types::PaymentsAuthorizeData {
+                payment_method_data: types::api::PaymentMethodData::Card(api::Card {
+                    card_number: Secret::new("1234567891011".to_string()),
+                    ..utils::CCardType::default().0
+                }),
+                ..utils::PaymentAuthorizeType::default().0
+            }),
+            get_default_payment_info(),
+        )
+        .await
+        .unwrap();
+    assert_eq!(
+        response.response.unwrap_err().message,
+        "description - UNPROCESSABLE_ENTITY",
+    );
+}
+
+// Creates a payment with empty card number.
+#[actix_web::test]
+async fn should_fail_payment_for_empty_card_number() {
+    let response = CONNECTOR
+        .make_payment(
+            Some(types::PaymentsAuthorizeData {
+                payment_method_data: types::api::PaymentMethodData::Card(api::Card {
+                    card_number: Secret::new(String::from("")),
+                    ..utils::CCardType::default().0
+                }),
+                ..utils::PaymentAuthorizeType::default().0
+            }),
+            get_default_payment_info(),
+        )
+        .await
+        .unwrap();
+    let x = response.response.unwrap_err();
+    assert_eq!(
+        x.message,
+        "description - The card number is required when attempting to process payment with card., field - number;",
+    );
+}
+
+// Creates a payment with incorrect CVC.
+#[actix_web::test]
+async fn should_fail_payment_for_incorrect_cvc() {
+    let response = CONNECTOR
+        .make_payment(
+            Some(types::PaymentsAuthorizeData {
+                payment_method_data: types::api::PaymentMethodData::Card(api::Card {
+                    card_cvc: Secret::new("12345".to_string()),
+                    ..utils::CCardType::default().0
+                }),
+                ..utils::PaymentAuthorizeType::default().0
+            }),
+            get_default_payment_info(),
+        )
+        .await
+        .unwrap();
+    assert_eq!(
+        response.response.unwrap_err().message,
+        "description - The value of a field does not conform to the expected format., value - 12345, field - security_code;",
+    );
+}
+
+// Creates a payment with incorrect expiry month.
+#[actix_web::test]
+async fn should_fail_payment_for_invalid_exp_month() {
+    let response = CONNECTOR
+        .make_payment(
+            Some(types::PaymentsAuthorizeData {
+                payment_method_data: types::api::PaymentMethodData::Card(api::Card {
+                    card_exp_month: Secret::new("20".to_string()),
+                    ..utils::CCardType::default().0
+                }),
+                ..utils::PaymentAuthorizeType::default().0
+            }),
+            get_default_payment_info(),
+        )
+        .await
+        .unwrap();
+    assert_eq!(
+        response.response.unwrap_err().message,
+        "description - The value of a field does not conform to the expected format., value - 2025-20, field - expiry;",
+    );
+}
+
+// Creates a payment with incorrect expiry year.
+#[actix_web::test]
+async fn should_fail_payment_for_incorrect_expiry_year() {
+    let response = CONNECTOR
+        .make_payment(
+            Some(types::PaymentsAuthorizeData {
+                payment_method_data: types::api::PaymentMethodData::Card(api::Card {
+                    card_exp_year: Secret::new("2000".to_string()),
+                    ..utils::CCardType::default().0
+                }),
+                ..utils::PaymentAuthorizeType::default().0
+            }),
+            get_default_payment_info(),
+        )
+        .await
+        .unwrap();
+    assert_eq!(
+        response.response.unwrap_err().message,
+        "description - The card is expired., field - expiry;",
+    );
+}
+
+// Voids a payment using automatic capture flow (Non 3DS).
+#[actix_web::test]
+async fn should_fail_void_payment_for_auto_capture() {
+    let authorize_response = CONNECTOR
+        .authorize_payment(get_payment_data(), get_default_payment_info())
+        .await
+        .expect("Authorize payment response");
+    let txn_id = "".to_string();
+    let capture_connector_meta = utils::get_connector_metadata(authorize_response.response);
+    let capture_response = CONNECTOR
+        .capture_payment(
+            txn_id,
+            Some(types::PaymentsCaptureData {
+                connector_meta: capture_connector_meta,
+                ..utils::PaymentCaptureType::default().0
+            }),
+            get_default_payment_info(),
+        )
+        .await
+        .expect("Capture payment response");
+    let txn_id = utils::get_connector_transaction_id(capture_response.clone().response).unwrap();
+    let connector_meta = utils::get_connector_metadata(capture_response.response);
+    let void_response = CONNECTOR
+        .void_payment(
+            txn_id,
+            Some(types::PaymentsCancelData {
+                cancellation_reason: Some("requested_by_customer".to_string()),
+                connector_meta,
+                ..Default::default()
+            }),
+            get_default_payment_info(),
+        )
+        .await
+        .expect("Void payment response");
+    assert_eq!(
+        void_response.response.unwrap_err().message,
+        "description - Authorization has been previously captured and hence cannot be voided. ; "
+    );
+}
+
+// Captures a payment using invalid connector payment id.
+#[actix_web::test]
+async fn should_fail_capture_for_invalid_payment() {
+    let connector_meta = Some(serde_json::json!({
+        "authorize_id": "56YH8TZ",
+        "order_id":"02569315XM5003146",
+        "psync_flow":"AUTHORIZE",
+    }));
+    let capture_response = CONNECTOR
+        .capture_payment(
+            "".to_string(),
+            Some(types::PaymentsCaptureData {
+                connector_meta,
+                ..utils::PaymentCaptureType::default().0
+            }),
+            get_default_payment_info(),
+        )
+        .await
+        .unwrap();
+    assert_eq!(
+        capture_response.response.unwrap_err().message,
+        "description - Specified resource ID does not exist. Please check the resource ID and try again. ; ",
+    );
+}
+
+// Refunds a payment with refund amount higher than payment amount.
+#[actix_web::test]
+async fn should_fail_for_refund_amount_higher_than_payment_amount() {
+    let authorize_response = CONNECTOR
+        .make_payment(get_payment_data(), get_default_payment_info())
+        .await
+        .unwrap();
+    let txn_id = utils::get_connector_transaction_id(authorize_response.response.clone()).unwrap();
+    let response = CONNECTOR
+        .refund_payment(
+            txn_id,
+            Some(types::RefundsData {
+                refund_amount: 150,
+                ..utils::PaymentRefundType::default().0
+            }),
+            get_default_payment_info(),
+        )
+        .await
+        .unwrap();
+    assert_eq!(&response.response.unwrap_err().message, "description - The refund amount must be less than or equal to the capture amount that has not yet been refunded. ; ");
+}
+
+// Connector dependent test cases goes here
+
+// [#478]: add unit tests for non 3DS, wallets & webhooks in connector tests
diff --git a/crates/router/tests/connectors/sample_auth.toml b/crates/router/tests/connectors/sample_auth.toml
index f12c63851bb..2a2ad2d8ed5 100644
--- a/crates/router/tests/connectors/sample_auth.toml
+++ b/crates/router/tests/connectors/sample_auth.toml
@@ -67,5 +67,9 @@ api_key = "api_key"
 key1 = "key1"
 api_secret = "secret"
 
+[paypal]
+api_key = "api_key"
+key1 = "key1"
+
 [mollie]
 api_key = "API Key"
\ No newline at end of file
diff --git a/crates/router/tests/connectors/utils.rs b/crates/router/tests/connectors/utils.rs
index 78688bf1a72..db92f2ead26 100644
--- a/crates/router/tests/connectors/utils.rs
+++ b/crates/router/tests/connectors/utils.rs
@@ -579,3 +579,17 @@ pub fn get_connector_transaction_id(
         Err(_) => None,
     }
 }
+
+pub fn get_connector_metadata(
+    response: Result<types::PaymentsResponseData, types::ErrorResponse>,
+) -> Option<serde_json::Value> {
+    match response {
+        Ok(types::PaymentsResponseData::TransactionResponse {
+            resource_id: _,
+            redirection_data: _,
+            mandate_reference: _,
+            connector_metadata,
+        }) => connector_metadata,
+        _ => None,
+    }
+}
diff --git a/loadtest/config/Development.toml b/loadtest/config/Development.toml
index d5c88c13f55..7406f43639b 100644
--- a/loadtest/config/Development.toml
+++ b/loadtest/config/Development.toml
@@ -72,6 +72,7 @@ klarna.base_url = "https://api-na.playground.klarna.com/"
 mollie.base_url = "https://api.mollie.com/v2/"
 multisafepay.base_url = "https://testapi.multisafepay.com/"
 nuvei.base_url = "https://ppp-test.nuvei.com/"
+paypal.base_url = "https://www.sandbox.paypal.com/"
 payu.base_url = "https://secure.snd.payu.com/"
 rapyd.base_url = "https://sandboxapi.rapyd.net"
 shift4.base_url = "https://api.shift4.com/"
@@ -99,6 +100,7 @@ cards = [
     "mollie",
     "multisafepay",
     "nuvei",
+    "paypal",
     "payu",
     "shift4",
     "stripe",

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

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

## Description
Updating support for [Paypal](https://www.paypal.com/) payment gateway
Payment Method: card
Supported payment flows:

Authorize
Capture
Void
PSync
Refund
RSync


### Additional Changes

- [ ] This PR modifies the database schema
- [x] 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).
-->
New connector integration.


## 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="972" alt="Screenshot 2023-03-15 at 4 02 29 AM" src="https://user-images.githubusercontent.com/70575890/225219741-08e2be3c-e126-4098-b528-b578fbc0e452.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
- [ ] I added a [CHANGELOG](/CHANGELOG.md) entry if applicable
