Bug: Add PayU for card payments




diff --git a/config/Development.toml b/config/Development.toml
index c5b1911913f..06b4d544c0a 100644
--- a/config/Development.toml
+++ b/config/Development.toml
@@ -85,6 +85,8 @@ base_url = "https://api.shift4.com/"
 [connectors.worldpay]
 base_url = "http://localhost:9090/"
 
+[connectors.payu]
+base_url = "https://secure.snd.payu.com/api/"
 [connectors.globalpay]
 base_url = "https://apis.sandbox.globalpay.com/ucp/"
 
diff --git a/crates/api_models/src/enums.rs b/crates/api_models/src/enums.rs
index 79eee90f401..cdb2f70d1fe 100644
--- a/crates/api_models/src/enums.rs
+++ b/crates/api_models/src/enums.rs
@@ -505,6 +505,7 @@ pub enum Connector {
     Dummy,
     Globalpay,
     Klarna,
+    Payu,
     Shift4,
     Stripe,
     Worldpay,
diff --git a/crates/router/src/configs/settings.rs b/crates/router/src/configs/settings.rs
index 474298ff074..0707dfb1dbf 100644
--- a/crates/router/src/configs/settings.rs
+++ b/crates/router/src/configs/settings.rs
@@ -122,17 +122,18 @@ pub struct SupportedConnectors {
 pub struct Connectors {
     pub aci: ConnectorParams,
     pub adyen: ConnectorParams,
+    pub applepay: ConnectorParams,
     pub authorizedotnet: ConnectorParams,
     pub braintree: ConnectorParams,
     pub checkout: ConnectorParams,
-    pub klarna: ConnectorParams,
     pub cybersource: ConnectorParams,
+    pub globalpay: ConnectorParams,
+    pub klarna: ConnectorParams,
+    pub payu: ConnectorParams,
     pub shift4: ConnectorParams,
     pub stripe: ConnectorParams,
     pub supported: SupportedConnectors,
-    pub globalpay: ConnectorParams,
     pub worldpay: ConnectorParams,
-    pub applepay: ConnectorParams,
 }
 
 #[derive(Debug, Deserialize, Clone)]
diff --git a/crates/router/src/connector.rs b/crates/router/src/connector.rs
index f6428ed6d9e..a790601e5e4 100644
--- a/crates/router/src/connector.rs
+++ b/crates/router/src/connector.rs
@@ -7,6 +7,7 @@ pub mod checkout;
 pub mod cybersource;
 pub mod globalpay;
 pub mod klarna;
+pub mod payu;
 pub mod shift4;
 pub mod stripe;
 pub mod utils;
@@ -15,5 +16,5 @@ pub mod worldpay;
 pub use self::{
     aci::Aci, adyen::Adyen, applepay::Applepay, authorizedotnet::Authorizedotnet,
     braintree::Braintree, checkout::Checkout, cybersource::Cybersource, globalpay::Globalpay,
-    klarna::Klarna, shift4::Shift4, stripe::Stripe, worldpay::Worldpay,
+    klarna::Klarna, payu::Payu, shift4::Shift4, stripe::Stripe, worldpay::Worldpay,
 };
diff --git a/crates/router/src/connector/payu.rs b/crates/router/src/connector/payu.rs
new file mode 100644
index 00000000000..da46caa247b
--- /dev/null
+++ b/crates/router/src/connector/payu.rs
@@ -0,0 +1,615 @@
+mod transformers;
+
+use std::fmt::Debug;
+
+use bytes::Bytes;
+use error_stack::{IntoReport, ResultExt};
+use transformers as payu;
+
+use crate::{
+    configs::settings,
+    core::{
+        errors::{self, CustomResult},
+        payments,
+    },
+    headers, logger, services,
+    types::{
+        self,
+        api::{self, ConnectorCommon, ConnectorCommonExt},
+        ErrorResponse, Response,
+    },
+    utils::{self, BytesExt},
+};
+
+#[derive(Debug, Clone)]
+pub struct Payu;
+
+impl<Flow, Request, Response> ConnectorCommonExt<Flow, Request, Response> for Payu
+where
+    Self: services::ConnectorIntegration<Flow, Request, Response>,
+{
+    fn build_headers(
+        &self,
+        req: &types::RouterData<Flow, Request, Response>,
+        _connectors: &settings::Connectors,
+    ) -> CustomResult<Vec<(String, String)>, errors::ConnectorError> {
+        let mut header = vec![(
+            headers::CONTENT_TYPE.to_string(),
+            types::PaymentsAuthorizeType::get_content_type(self).to_string(),
+        )];
+        let mut api_key = self.get_auth_header(&req.connector_auth_type)?;
+        header.append(&mut api_key);
+        Ok(header)
+    }
+}
+
+impl ConnectorCommon for Payu {
+    fn id(&self) -> &'static str {
+        "payu"
+    }
+
+    fn common_get_content_type(&self) -> &'static str {
+        "application/json"
+    }
+
+    fn base_url<'a>(&self, connectors: &'a settings::Connectors) -> &'a str {
+        connectors.payu.base_url.as_ref()
+    }
+
+    fn get_auth_header(
+        &self,
+        auth_type: &types::ConnectorAuthType,
+    ) -> CustomResult<Vec<(String, String)>, errors::ConnectorError> {
+        let auth: payu::PayuAuthType = auth_type
+            .try_into()
+            .change_context(errors::ConnectorError::FailedToObtainAuthType)?;
+        Ok(vec![(headers::AUTHORIZATION.to_string(), auth.api_key)])
+    }
+    fn build_error_response(
+        &self,
+        res: Bytes,
+    ) -> CustomResult<ErrorResponse, errors::ConnectorError> {
+        let response: payu::PayuErrorResponse = res
+            .parse_struct("Payu ErrorResponse")
+            .change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
+
+        Ok(ErrorResponse {
+            code: response.status.status_code,
+            message: response.status.status_desc,
+            reason: response.status.code_literal,
+        })
+    }
+}
+
+impl api::Payment for Payu {}
+
+impl api::PreVerify for Payu {}
+impl
+    services::ConnectorIntegration<
+        api::Verify,
+        types::VerifyRequestData,
+        types::PaymentsResponseData,
+    > for Payu
+{
+}
+
+impl api::PaymentVoid for Payu {}
+
+impl
+    services::ConnectorIntegration<
+        api::Void,
+        types::PaymentsCancelData,
+        types::PaymentsResponseData,
+    > for Payu
+{
+    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 connector_payment_id = &req.request.connector_transaction_id;
+        Ok(format!(
+            "{}{}{}",
+            self.base_url(connectors),
+            "v2_1/orders/",
+            connector_payment_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::Delete)
+            .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: payu::PayuPaymentsCancelResponse = res
+            .response
+            .parse_struct("PaymentCancelResponse")
+            .change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
+        logger::debug!(payments_create_response=?response);
+        types::RouterData::try_from(types::ResponseRouterData {
+            response,
+            data: data.clone(),
+            http_code: res.status_code,
+        })
+        .change_context(errors::ConnectorError::ResponseHandlingFailed)
+    }
+    fn get_error_response(
+        &self,
+        res: Bytes,
+    ) -> CustomResult<ErrorResponse, errors::ConnectorError> {
+        self.build_error_response(res)
+    }
+}
+
+impl api::PaymentSync for Payu {}
+impl
+    services::ConnectorIntegration<api::PSync, types::PaymentsSyncData, types::PaymentsResponseData>
+    for Payu
+{
+    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 connector_payment_id = req
+            .request
+            .connector_transaction_id
+            .get_connector_transaction_id()
+            .change_context(errors::ConnectorError::MissingConnectorTransactionID)?;
+        Ok(format!(
+            "{}{}{}",
+            self.base_url(connectors),
+            "v2_1/orders/",
+            connector_payment_id
+        ))
+    }
+
+    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> {
+        logger::debug!(target: "router::connector::payu", response=?res);
+        let response: payu::PayuPaymentsSyncResponse = res
+            .response
+            .parse_struct("payu OrderResponse")
+            .change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
+        types::ResponseRouterData {
+            response,
+            data: data.clone(),
+            http_code: res.status_code,
+        }
+        .try_into()
+        .change_context(errors::ConnectorError::ResponseHandlingFailed)
+    }
+
+    fn get_error_response(
+        &self,
+        res: Bytes,
+    ) -> CustomResult<ErrorResponse, errors::ConnectorError> {
+        self.build_error_response(res)
+    }
+}
+
+impl api::PaymentCapture for Payu {}
+impl
+    services::ConnectorIntegration<
+        api::Capture,
+        types::PaymentsCaptureData,
+        types::PaymentsResponseData,
+    > for Payu
+{
+    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> {
+        Ok(format!(
+            "{}{}{}{}",
+            self.base_url(connectors),
+            "v2_1/orders/",
+            req.request.connector_transaction_id,
+            "/status"
+        ))
+    }
+
+    fn get_request_body(
+        &self,
+        req: &types::PaymentsCaptureRouterData,
+    ) -> CustomResult<Option<String>, errors::ConnectorError> {
+        let payu_req = utils::Encode::<payu::PayuPaymentsCaptureRequest>::convert_and_encode(req)
+            .change_context(errors::ConnectorError::RequestEncodingFailed)?;
+        Ok(Some(payu_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::Put)
+                .url(&types::PaymentsCaptureType::get_url(self, req, connectors)?)
+                .headers(types::PaymentsCaptureType::get_headers(
+                    self, req, connectors,
+                )?)
+                .body(types::PaymentsCaptureType::get_request_body(self, req)?)
+                .build(),
+        ))
+    }
+
+    fn handle_response(
+        &self,
+        data: &types::PaymentsCaptureRouterData,
+        res: Response,
+    ) -> CustomResult<types::PaymentsCaptureRouterData, errors::ConnectorError> {
+        let response: payu::PayuPaymentsCaptureResponse = res
+            .response
+            .parse_struct("payu CaptureResponse")
+            .change_context(errors::ConnectorError::RequestEncodingFailed)?;
+        types::ResponseRouterData {
+            response,
+            data: data.clone(),
+            http_code: res.status_code,
+        }
+        .try_into()
+        .change_context(errors::ConnectorError::ResponseHandlingFailed)
+    }
+
+    fn get_error_response(
+        &self,
+        res: Bytes,
+    ) -> CustomResult<ErrorResponse, errors::ConnectorError> {
+        self.build_error_response(res)
+    }
+}
+
+impl api::PaymentSession for Payu {}
+
+impl
+    services::ConnectorIntegration<
+        api::Session,
+        types::PaymentsSessionData,
+        types::PaymentsResponseData,
+    > for Payu
+{
+    //TODO: implement sessions flow
+}
+
+impl api::PaymentAuthorize for Payu {}
+
+impl
+    services::ConnectorIntegration<
+        api::Authorize,
+        types::PaymentsAuthorizeData,
+        types::PaymentsResponseData,
+    > for Payu
+{
+    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!("{}{}", self.base_url(connectors), "v2_1/orders"))
+    }
+
+    fn get_request_body(
+        &self,
+        req: &types::PaymentsAuthorizeRouterData,
+    ) -> CustomResult<Option<String>, errors::ConnectorError> {
+        let payu_req = utils::Encode::<payu::PayuPaymentsRequest>::convert_and_encode(req)
+            .change_context(errors::ConnectorError::RequestEncodingFailed)?;
+        Ok(Some(payu_req))
+    }
+
+    fn build_request(
+        &self,
+        req: &types::RouterData<
+            api::Authorize,
+            types::PaymentsAuthorizeData,
+            types::PaymentsResponseData,
+        >,
+        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: payu::PayuPaymentsResponse = res
+            .response
+            .parse_struct("PayuPaymentsResponse")
+            .change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
+        logger::debug!(payupayments_create_response=?response);
+        types::ResponseRouterData {
+            response,
+            data: data.clone(),
+            http_code: res.status_code,
+        }
+        .try_into()
+        .change_context(errors::ConnectorError::ResponseHandlingFailed)
+    }
+
+    fn get_error_response(
+        &self,
+        res: Bytes,
+    ) -> CustomResult<ErrorResponse, errors::ConnectorError> {
+        self.build_error_response(res)
+    }
+}
+
+impl api::Refund for Payu {}
+impl api::RefundExecute for Payu {}
+impl api::RefundSync for Payu {}
+
+impl services::ConnectorIntegration<api::Execute, types::RefundsData, types::RefundsResponseData>
+    for Payu
+{
+    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> {
+        Ok(format!(
+            "{}{}{}{}",
+            self.base_url(connectors),
+            "v2_1/orders/",
+            req.request.connector_transaction_id,
+            "/refund"
+        ))
+    }
+
+    fn get_request_body(
+        &self,
+        req: &types::RefundsRouterData<api::Execute>,
+    ) -> CustomResult<Option<String>, errors::ConnectorError> {
+        let payu_req = utils::Encode::<payu::PayuRefundRequest>::convert_and_encode(req)
+            .change_context(errors::ConnectorError::RequestEncodingFailed)?;
+        Ok(Some(payu_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> {
+        logger::debug!(target: "router::connector::payu", response=?res);
+        let response: payu::RefundResponse = res
+            .response
+            .parse_struct("payu RefundResponse")
+            .change_context(errors::ConnectorError::RequestEncodingFailed)?;
+        types::ResponseRouterData {
+            response,
+            data: data.clone(),
+            http_code: res.status_code,
+        }
+        .try_into()
+        .change_context(errors::ConnectorError::ResponseHandlingFailed)
+    }
+
+    fn get_error_response(
+        &self,
+        res: Bytes,
+    ) -> CustomResult<ErrorResponse, errors::ConnectorError> {
+        self.build_error_response(res)
+    }
+}
+
+impl services::ConnectorIntegration<api::RSync, types::RefundsData, types::RefundsResponseData>
+    for Payu
+{
+    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!(
+            "{}{}{}{}",
+            self.base_url(connectors),
+            "v2_1/orders/",
+            req.request.connector_transaction_id,
+            "/refunds"
+        ))
+    }
+
+    fn build_request(
+        &self,
+        req: &types::RefundsRouterData<api::RSync>,
+        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> {
+        logger::debug!(target: "router::connector::payu", response=?res);
+        let response: payu::RefundSyncResponse =
+            res.response
+                .parse_struct("payu RefundResponse")
+                .change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
+        types::ResponseRouterData {
+            response,
+            data: data.clone(),
+            http_code: res.status_code,
+        }
+        .try_into()
+        .change_context(errors::ConnectorError::ResponseHandlingFailed)
+    }
+
+    fn get_error_response(
+        &self,
+        res: Bytes,
+    ) -> CustomResult<ErrorResponse, errors::ConnectorError> {
+        self.build_error_response(res)
+    }
+}
+
+#[async_trait::async_trait]
+impl api::IncomingWebhook for Payu {
+    fn get_webhook_object_reference_id(
+        &self,
+        _body: &[u8],
+    ) -> CustomResult<String, errors::ConnectorError> {
+        Err(errors::ConnectorError::WebhooksNotImplemented).into_report()
+    }
+
+    fn get_webhook_event_type(
+        &self,
+        _body: &[u8],
+    ) -> CustomResult<api::IncomingWebhookEvent, errors::ConnectorError> {
+        Err(errors::ConnectorError::WebhooksNotImplemented).into_report()
+    }
+
+    fn get_webhook_resource_object(
+        &self,
+        _body: &[u8],
+    ) -> CustomResult<serde_json::Value, errors::ConnectorError> {
+        Err(errors::ConnectorError::WebhooksNotImplemented).into_report()
+    }
+}
+
+impl services::ConnectorRedirectResponse for Payu {
+    fn get_flow_type(
+        &self,
+        _query_params: &str,
+    ) -> CustomResult<payments::CallConnectorAction, errors::ConnectorError> {
+        Ok(payments::CallConnectorAction::Trigger)
+    }
+}
diff --git a/crates/router/src/connector/payu/transformers.rs b/crates/router/src/connector/payu/transformers.rs
new file mode 100644
index 00000000000..1a526ef373d
--- /dev/null
+++ b/crates/router/src/connector/payu/transformers.rs
@@ -0,0 +1,564 @@
+use base64::Engine;
+use error_stack::{IntoReport, ResultExt};
+use serde::{Deserialize, Serialize};
+
+use crate::{
+    consts,
+    core::errors,
+    pii::{self, Secret},
+    types::{self, api, storage::enums},
+    utils::OptionExt,
+};
+
+const WALLET_IDENTIFIER: &str = "PBL";
+
+#[derive(Debug, Serialize, Eq, PartialEq)]
+#[serde(rename_all = "camelCase")]
+pub struct PayuPaymentsRequest {
+    customer_ip: std::net::IpAddr,
+    merchant_pos_id: String,
+    total_amount: i64,
+    currency_code: enums::Currency,
+    description: String,
+    pay_methods: PayuPaymentMethod,
+    continue_url: Option<String>,
+}
+
+#[derive(Debug, Eq, PartialEq, Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct PayuPaymentMethod {
+    pay_method: PayuPaymentMethodData,
+}
+
+#[derive(Debug, Eq, PartialEq, Serialize)]
+#[serde(untagged)]
+pub enum PayuPaymentMethodData {
+    Card(PayuCard),
+    Wallet(PayuWallet),
+}
+
+#[derive(Debug, Eq, PartialEq, Serialize)]
+#[serde(rename_all = "camelCase")]
+pub enum PayuCard {
+    #[serde(rename_all = "camelCase")]
+    Card {
+        number: Secret<String, pii::CardNumber>,
+        expiration_month: Secret<String>,
+        expiration_year: Secret<String>,
+        cvv: Secret<String>,
+    },
+}
+
+#[derive(Debug, Eq, PartialEq, Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct PayuWallet {
+    pub value: PayuWalletCode,
+    #[serde(rename = "type")]
+    pub wallet_type: String,
+    pub authorization_code: String,
+}
+#[derive(Debug, Eq, PartialEq, Serialize)]
+#[serde(rename_all = "lowercase")]
+pub enum PayuWalletCode {
+    Ap,
+    Jp,
+}
+
+impl TryFrom<&types::PaymentsAuthorizeRouterData> for PayuPaymentsRequest {
+    type Error = error_stack::Report<errors::ConnectorError>;
+    fn try_from(item: &types::PaymentsAuthorizeRouterData) -> Result<Self, Self::Error> {
+        let auth_type = PayuAuthType::try_from(&item.connector_auth_type)?;
+        let payment_method = match item.request.payment_method_data.clone() {
+            api::PaymentMethod::Card(ccard) => Ok(PayuPaymentMethod {
+                pay_method: PayuPaymentMethodData::Card(PayuCard::Card {
+                    number: ccard.card_number,
+                    expiration_month: ccard.card_exp_month,
+                    expiration_year: ccard.card_exp_year,
+                    cvv: ccard.card_cvc,
+                }),
+            }),
+            api::PaymentMethod::Wallet(wallet_data) => match wallet_data.issuer_name {
+                api_models::enums::WalletIssuer::GooglePay => Ok(PayuPaymentMethod {
+                    pay_method: PayuPaymentMethodData::Wallet({
+                        PayuWallet {
+                            value: PayuWalletCode::Ap,
+                            wallet_type: WALLET_IDENTIFIER.to_string(),
+                            authorization_code: consts::BASE64_ENGINE.encode(
+                                wallet_data
+                                    .token
+                                    .get_required_value("token")
+                                    .change_context(errors::ConnectorError::RequestEncodingFailed)
+                                    .attach_printable("No token passed")?,
+                            ),
+                        }
+                    }),
+                }),
+                api_models::enums::WalletIssuer::ApplePay => Ok(PayuPaymentMethod {
+                    pay_method: PayuPaymentMethodData::Wallet({
+                        PayuWallet {
+                            value: PayuWalletCode::Jp,
+                            wallet_type: WALLET_IDENTIFIER.to_string(),
+                            authorization_code: consts::BASE64_ENGINE.encode(
+                                wallet_data
+                                    .token
+                                    .get_required_value("token")
+                                    .change_context(errors::ConnectorError::RequestEncodingFailed)
+                                    .attach_printable("No token passed")?,
+                            ),
+                        }
+                    }),
+                }),
+                _ => Err(errors::ConnectorError::NotImplemented(
+                    "Unknown Wallet in Payment Method".to_string(),
+                )),
+            },
+            _ => Err(errors::ConnectorError::NotImplemented(
+                "Unknown payment method".to_string(),
+            )),
+        }?;
+        let browser_info = item.request.browser_info.clone().ok_or(
+            errors::ConnectorError::MissingRequiredField {
+                field_name: "browser_info".to_string(),
+            },
+        )?;
+        Ok(Self {
+            customer_ip: browser_info.ip_address.ok_or(
+                errors::ConnectorError::MissingRequiredField {
+                    field_name: "browser_info.ip_address".to_string(),
+                },
+            )?,
+            merchant_pos_id: auth_type.merchant_pos_id,
+            total_amount: item.request.amount,
+            currency_code: item.request.currency,
+            description: item.description.clone().ok_or(
+                errors::ConnectorError::MissingRequiredField {
+                    field_name: "item.description".to_string(),
+                },
+            )?,
+            pay_methods: payment_method,
+            continue_url: None,
+        })
+    }
+}
+
+pub struct PayuAuthType {
+    pub(super) api_key: String,
+    pub(super) merchant_pos_id: String,
+}
+
+impl TryFrom<&types::ConnectorAuthType> for PayuAuthType {
+    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(),
+                merchant_pos_id: key1.to_string(),
+            }),
+            _ => Err(errors::ConnectorError::FailedToObtainAuthType)?,
+        }
+    }
+}
+
+#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
+#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
+pub enum PayuPaymentStatus {
+    Success,
+    WarningContinueRedirect,
+    #[serde(rename = "WARNING_CONTINUE_3DS")]
+    WarningContinue3ds,
+    WarningContinueCvv,
+    #[default]
+    Pending,
+}
+
+impl From<PayuPaymentStatus> for enums::AttemptStatus {
+    fn from(item: PayuPaymentStatus) -> Self {
+        match item {
+            PayuPaymentStatus::Success => Self::Pending,
+            PayuPaymentStatus::WarningContinue3ds => Self::Pending,
+            PayuPaymentStatus::WarningContinueCvv => Self::Pending,
+            PayuPaymentStatus::WarningContinueRedirect => Self::Pending,
+            PayuPaymentStatus::Pending => Self::Pending,
+        }
+    }
+}
+
+#[derive(Debug, Clone, Deserialize, PartialEq)]
+#[serde(rename_all = "camelCase")]
+pub struct PayuPaymentsResponse {
+    pub status: PayuPaymentStatusData,
+    pub redirect_uri: String,
+    pub iframe_allowed: Option<bool>,
+    pub three_ds_protocol_version: Option<String>,
+    pub order_id: String,
+    pub ext_order_id: Option<String>,
+}
+
+impl<F, T>
+    TryFrom<types::ResponseRouterData<F, PayuPaymentsResponse, T, types::PaymentsResponseData>>
+    for types::RouterData<F, T, types::PaymentsResponseData>
+{
+    type Error = error_stack::Report<errors::ParsingError>;
+    fn try_from(
+        item: types::ResponseRouterData<F, PayuPaymentsResponse, T, types::PaymentsResponseData>,
+    ) -> Result<Self, Self::Error> {
+        Ok(Self {
+            status: enums::AttemptStatus::from(item.response.status.status_code),
+            response: Ok(types::PaymentsResponseData::TransactionResponse {
+                resource_id: types::ResponseId::ConnectorTransactionId(item.response.order_id),
+                redirect: false,
+                redirection_data: None,
+                mandate_reference: None,
+                connector_metadata: None,
+            }),
+            amount_captured: None,
+            ..item.data
+        })
+    }
+}
+
+#[derive(Default, Debug, Clone, Serialize, PartialEq)]
+#[serde(rename_all = "camelCase")]
+pub struct PayuPaymentsCaptureRequest {
+    order_id: String,
+    order_status: OrderStatus,
+}
+
+impl TryFrom<&types::PaymentsCaptureRouterData> for PayuPaymentsCaptureRequest {
+    type Error = error_stack::Report<errors::ParsingError>;
+    fn try_from(item: &types::PaymentsCaptureRouterData) -> Result<Self, Self::Error> {
+        Ok(Self {
+            order_id: item.request.connector_transaction_id.clone(),
+            order_status: OrderStatus::Completed,
+        })
+    }
+}
+
+#[derive(Default, Debug, Clone, Deserialize, PartialEq)]
+pub struct PayuPaymentsCaptureResponse {
+    status: PayuPaymentStatusData,
+}
+
+impl<F, T>
+    TryFrom<
+        types::ResponseRouterData<F, PayuPaymentsCaptureResponse, T, types::PaymentsResponseData>,
+    > for types::RouterData<F, T, types::PaymentsResponseData>
+{
+    type Error = error_stack::Report<errors::ParsingError>;
+    fn try_from(
+        item: types::ResponseRouterData<
+            F,
+            PayuPaymentsCaptureResponse,
+            T,
+            types::PaymentsResponseData,
+        >,
+    ) -> Result<Self, Self::Error> {
+        Ok(Self {
+            status: enums::AttemptStatus::from(item.response.status.status_code.clone()),
+            response: Ok(types::PaymentsResponseData::TransactionResponse {
+                resource_id: types::ResponseId::NoResponseId,
+                redirect: false,
+                redirection_data: None,
+                mandate_reference: None,
+                connector_metadata: None,
+            }),
+            amount_captured: None,
+            ..item.data
+        })
+    }
+}
+
+#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)]
+#[serde(rename_all = "camelCase")]
+pub struct PayuPaymentsCancelResponse {
+    pub order_id: String,
+    pub ext_order_id: Option<String>,
+    pub status: PayuPaymentStatusData,
+}
+
+impl<F, T>
+    TryFrom<
+        types::ResponseRouterData<F, PayuPaymentsCancelResponse, T, types::PaymentsResponseData>,
+    > for types::RouterData<F, T, types::PaymentsResponseData>
+{
+    type Error = error_stack::Report<errors::ParsingError>;
+    fn try_from(
+        item: types::ResponseRouterData<
+            F,
+            PayuPaymentsCancelResponse,
+            T,
+            types::PaymentsResponseData,
+        >,
+    ) -> Result<Self, Self::Error> {
+        Ok(Self {
+            status: enums::AttemptStatus::from(item.response.status.status_code.clone()),
+            response: Ok(types::PaymentsResponseData::TransactionResponse {
+                resource_id: types::ResponseId::ConnectorTransactionId(item.response.order_id),
+                redirect: false,
+                redirection_data: None,
+                mandate_reference: None,
+                connector_metadata: None,
+            }),
+            amount_captured: None,
+            ..item.data
+        })
+    }
+}
+
+#[allow(dead_code)]
+#[derive(Debug, Serialize, Eq, PartialEq, Default, Deserialize, Clone)]
+#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
+pub enum OrderStatus {
+    New,
+    Canceled,
+    Completed,
+    WaitingForConfirmation,
+    #[default]
+    Pending,
+}
+
+impl From<OrderStatus> for enums::AttemptStatus {
+    fn from(item: OrderStatus) -> Self {
+        match item {
+            OrderStatus::New => Self::PaymentMethodAwaited,
+            OrderStatus::Canceled => Self::Voided,
+            OrderStatus::Completed => Self::Charged,
+            OrderStatus::Pending => Self::Pending,
+            OrderStatus::WaitingForConfirmation => Self::Authorized,
+        }
+    }
+}
+
+#[derive(Debug, Serialize, Default, Deserialize, Clone, PartialEq)]
+#[serde(rename_all = "camelCase")]
+pub struct PayuPaymentStatusData {
+    status_code: PayuPaymentStatus,
+    severity: Option<String>,
+    status_desc: Option<String>,
+}
+#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)]
+#[serde(rename_all = "camelCase")]
+pub struct PayuProductData {
+    name: String,
+    unit_price: String,
+    quantity: String,
+    #[serde(rename = "virtual")]
+    virtually: Option<bool>,
+    listing_date: Option<String>,
+}
+
+#[derive(Debug, Clone, Deserialize, PartialEq)]
+#[serde(rename_all = "camelCase")]
+pub struct PayuOrderResponseData {
+    order_id: String,
+    ext_order_id: Option<String>,
+    order_create_date: String,
+    notify_url: Option<String>,
+    customer_ip: std::net::IpAddr,
+    merchant_pos_id: String,
+    description: String,
+    validity_time: Option<String>,
+    currency_code: enums::Currency,
+    total_amount: String,
+    buyer: Option<PayuOrderResponseBuyerData>,
+    pay_method: Option<PayuOrderResponsePayMethod>,
+    products: Option<Vec<PayuProductData>>,
+    status: OrderStatus,
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
+#[serde(rename_all = "camelCase")]
+pub struct PayuOrderResponseBuyerData {
+    ext_customer_id: Option<String>,
+    email: Option<String>,
+    phone: Option<String>,
+    first_name: Option<String>,
+    last_name: Option<String>,
+    nin: Option<String>,
+    language: Option<String>,
+    delivery: Option<String>,
+    customer_id: Option<String>,
+}
+
+#[derive(Debug, Clone, Deserialize, PartialEq)]
+#[serde(tag = "type", rename_all = "SCREAMING_SNAKE_CASE")]
+pub enum PayuOrderResponsePayMethod {
+    CardToken,
+    Pbl,
+    Installemnts,
+}
+
+#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)]
+pub struct PayuOrderResponseProperty {
+    name: String,
+    value: String,
+}
+
+#[derive(Default, Debug, Clone, Deserialize, PartialEq)]
+pub struct PayuPaymentsSyncResponse {
+    orders: Vec<PayuOrderResponseData>,
+    status: PayuPaymentStatusData,
+    properties: Option<Vec<PayuOrderResponseProperty>>,
+}
+
+impl<F, T>
+    TryFrom<types::ResponseRouterData<F, PayuPaymentsSyncResponse, T, types::PaymentsResponseData>>
+    for types::RouterData<F, T, types::PaymentsResponseData>
+{
+    type Error = error_stack::Report<errors::ConnectorError>;
+    fn try_from(
+        item: types::ResponseRouterData<
+            F,
+            PayuPaymentsSyncResponse,
+            T,
+            types::PaymentsResponseData,
+        >,
+    ) -> Result<Self, Self::Error> {
+        let order = match item.response.orders.first() {
+            Some(order) => order,
+            _ => Err(errors::ConnectorError::ResponseHandlingFailed)?,
+        };
+        Ok(Self {
+            status: enums::AttemptStatus::from(order.status.clone()),
+            response: Ok(types::PaymentsResponseData::TransactionResponse {
+                resource_id: types::ResponseId::ConnectorTransactionId(order.order_id.clone()),
+                redirect: false,
+                redirection_data: None,
+                mandate_reference: None,
+                connector_metadata: None,
+            }),
+            amount_captured: Some(
+                order
+                    .total_amount
+                    .parse::<i64>()
+                    .into_report()
+                    .change_context(errors::ConnectorError::ResponseDeserializationFailed)?,
+            ),
+            ..item.data
+        })
+    }
+}
+
+#[derive(Default, Debug, Eq, PartialEq, Serialize)]
+pub struct PayuRefundRequestData {
+    description: String,
+    amount: Option<i64>,
+}
+
+#[derive(Default, Debug, Serialize)]
+pub struct PayuRefundRequest {
+    refund: PayuRefundRequestData,
+}
+
+impl<F> TryFrom<&types::RefundsRouterData<F>> for PayuRefundRequest {
+    type Error = error_stack::Report<errors::ConnectorError>;
+    fn try_from(item: &types::RefundsRouterData<F>) -> Result<Self, Self::Error> {
+        Ok(Self {
+            refund: PayuRefundRequestData {
+                description: item.description.clone().ok_or(
+                    errors::ConnectorError::MissingRequiredField {
+                        field_name: "item.description".to_string(),
+                    },
+                )?,
+                amount: None,
+            },
+        })
+    }
+}
+
+// Type definition for Refund Response
+
+#[allow(dead_code)]
+#[derive(Debug, Serialize, Eq, PartialEq, Default, Deserialize, Clone)]
+#[serde(rename_all = "UPPERCASE")]
+pub enum RefundStatus {
+    Finalized,
+    Canceled,
+    #[default]
+    Pending,
+}
+
+impl From<RefundStatus> for enums::RefundStatus {
+    fn from(item: RefundStatus) -> Self {
+        match item {
+            RefundStatus::Finalized => Self::Success,
+            RefundStatus::Canceled => Self::Failure,
+            RefundStatus::Pending => Self::Pending,
+        }
+    }
+}
+
+#[derive(Default, Debug, Clone, Eq, PartialEq, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct PayuRefundResponseData {
+    refund_id: String,
+    ext_refund_id: String,
+    amount: String,
+    currency_code: enums::Currency,
+    description: String,
+    creation_date_time: String,
+    status: RefundStatus,
+    status_date_time: Option<String>,
+}
+
+#[derive(Default, Debug, Clone, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct RefundResponse {
+    refund: PayuRefundResponseData,
+}
+
+impl TryFrom<types::RefundsResponseRouterData<api::Execute, RefundResponse>>
+    for types::RefundsRouterData<api::Execute>
+{
+    type Error = error_stack::Report<errors::ParsingError>;
+    fn try_from(
+        item: types::RefundsResponseRouterData<api::Execute, RefundResponse>,
+    ) -> Result<Self, Self::Error> {
+        let refund_status = enums::RefundStatus::from(item.response.refund.status);
+        Ok(Self {
+            response: Ok(types::RefundsResponseData {
+                connector_refund_id: item.response.refund.refund_id,
+                refund_status,
+            }),
+            ..item.data
+        })
+    }
+}
+
+#[derive(Default, Debug, Clone, Deserialize)]
+pub struct RefundSyncResponse {
+    refunds: Vec<PayuRefundResponseData>,
+}
+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> {
+        let refund = match item.response.refunds.first() {
+            Some(refund) => refund,
+            _ => Err(errors::ConnectorError::ResponseHandlingFailed)?,
+        };
+        Ok(Self {
+            response: Ok(types::RefundsResponseData {
+                connector_refund_id: refund.refund_id.clone(),
+                refund_status: enums::RefundStatus::from(refund.status.clone()),
+            }),
+            ..item.data
+        })
+    }
+}
+
+#[derive(Default, Debug, Serialize, Deserialize, PartialEq)]
+#[serde(rename_all = "camelCase")]
+pub struct PayuErrorData {
+    pub status_code: String,
+    pub code: Option<String>,
+    pub code_literal: Option<String>,
+    pub status_desc: String,
+}
+#[derive(Default, Debug, Serialize, Deserialize, PartialEq)]
+pub struct PayuErrorResponse {
+    pub status: PayuErrorData,
+}
diff --git a/crates/router/src/services/api.rs b/crates/router/src/services/api.rs
index 8cfc61670d6..1cb9c7a4e5e 100644
--- a/crates/router/src/services/api.rs
+++ b/crates/router/src/services/api.rs
@@ -233,6 +233,7 @@ async fn send_request(
                     logger::debug!(?url_encoded_payload);
                     client.body(url_encoded_payload).send()
                 }
+                // If payload needs processing the body cannot have default
                 None => client
                     .body(request.payload.expose_option().unwrap_or_default())
                     .send(),
@@ -240,7 +241,14 @@ async fn send_request(
             .await
         }
 
-        Method::Put => client.put(url).add_headers(headers).send().await,
+        Method::Put => {
+            client
+                .put(url)
+                .add_headers(headers)
+                .body(request.payload.expose_option().unwrap_or_default()) // If payload needs processing the body cannot have default
+                .send()
+                .await
+        }
         Method::Delete => client.delete(url).add_headers(headers).send().await,
     }
     .map_err(|error| match error {
@@ -260,7 +268,7 @@ async fn handle_response(
             logger::info!(?response);
             let status_code = response.status().as_u16();
             match status_code {
-                200..=202 => {
+                200..=202 | 302 => {
                     logger::debug!(response=?response);
                     // If needed add log line
                     // logger:: error!( error_parsing_response=?err);
diff --git a/crates/router/src/services/api/client.rs b/crates/router/src/services/api/client.rs
index 6d2f01b8522..e8b8409f021 100644
--- a/crates/router/src/services/api/client.rs
+++ b/crates/router/src/services/api/client.rs
@@ -41,7 +41,7 @@ pub(super) fn create_client(
     client_certificate: Option<String>,
     client_certificate_key: Option<String>,
 ) -> CustomResult<reqwest::Client, errors::ApiClientError> {
-    let mut client_builder = reqwest::Client::builder();
+    let mut client_builder = reqwest::Client::builder().redirect(reqwest::redirect::Policy::none());
 
     if !should_bypass_proxy {
         if let Some(url) = ProxyType::Http.get_proxy_url(proxy) {
diff --git a/crates/router/src/types/api.rs b/crates/router/src/types/api.rs
index 87b32f24c55..f9feb7e9442 100644
--- a/crates/router/src/types/api.rs
+++ b/crates/router/src/types/api.rs
@@ -150,6 +150,7 @@ impl ConnectorData {
             "cybersource" => Ok(Box::new(&connector::Cybersource)),
             "shift4" => Ok(Box::new(&connector::Shift4)),
             "worldpay" => Ok(Box::new(&connector::Worldpay)),
+            "payu" => Ok(Box::new(&connector::Payu)),
             "globalpay" => Ok(Box::new(&connector::Globalpay)),
             _ => 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 24916e65829..86683b0f7a6 100644
--- a/crates/router/tests/connectors/connector_auth.rs
+++ b/crates/router/tests/connectors/connector_auth.rs
@@ -7,6 +7,7 @@ pub(crate) struct ConnectorAuthentication {
     pub authorizedotnet: Option<BodyKey>,
     pub checkout: Option<BodyKey>,
     pub globalpay: Option<HeaderKey>,
+    pub payu: Option<BodyKey>,
     pub shift4: Option<HeaderKey>,
     pub worldpay: Option<HeaderKey>,
 }
diff --git a/crates/router/tests/connectors/main.rs b/crates/router/tests/connectors/main.rs
index c3f35179958..b0b44c89cba 100644
--- a/crates/router/tests/connectors/main.rs
+++ b/crates/router/tests/connectors/main.rs
@@ -5,6 +5,7 @@ mod authorizedotnet;
 mod checkout;
 mod connector_auth;
 mod globalpay;
+mod payu;
 mod shift4;
 mod utils;
 mod worldpay;
diff --git a/crates/router/tests/connectors/payu.rs b/crates/router/tests/connectors/payu.rs
new file mode 100644
index 00000000000..ecfd0426e71
--- /dev/null
+++ b/crates/router/tests/connectors/payu.rs
@@ -0,0 +1,277 @@
+use router::types::{self, api, storage::enums};
+
+use crate::{
+    connector_auth,
+    utils::{self, ConnectorActions, PaymentAuthorizeType},
+};
+
+struct Payu;
+impl ConnectorActions for Payu {}
+impl utils::Connector for Payu {
+    fn get_data(&self) -> types::api::ConnectorData {
+        use router::connector::Payu;
+        types::api::ConnectorData {
+            connector: Box::new(&Payu),
+            connector_name: types::Connector::Payu,
+            get_token: types::api::GetToken::Connector,
+        }
+    }
+
+    fn get_auth_token(&self) -> types::ConnectorAuthType {
+        types::ConnectorAuthType::from(
+            connector_auth::ConnectorAuthentication::new()
+                .payu
+                .expect("Missing connector authentication configuration"),
+        )
+    }
+
+    fn get_name(&self) -> String {
+        "payu".to_string()
+    }
+}
+
+#[actix_web::test]
+async fn should_authorize_card_payment() {
+    //Authorize Card Payment in PLN currenct
+    let authorize_response = Payu {}
+        .authorize_payment(
+            Some(types::PaymentsAuthorizeData {
+                currency: enums::Currency::PLN,
+                ..PaymentAuthorizeType::default().0
+            }),
+            None,
+        )
+        .await;
+    // in Payu need Psync to get status therfore set to pending
+    assert_eq!(authorize_response.status, enums::AttemptStatus::Pending);
+    if let Some(transaction_id) = utils::get_connector_transaction_id(authorize_response) {
+        let sync_response = Payu {}
+            .sync_payment(
+                Some(types::PaymentsSyncData {
+                    connector_transaction_id: router::types::ResponseId::ConnectorTransactionId(
+                        transaction_id.clone(),
+                    ),
+                    encoded_data: None,
+                }),
+                None,
+            )
+            .await;
+        // Assert the sync response, it will be authorized in case of manual capture, for automatic it will be Completed Success
+        assert_eq!(sync_response.status, enums::AttemptStatus::Authorized);
+    }
+}
+
+#[actix_web::test]
+async fn should_authorize_gpay_payment() {
+    let authorize_response = Payu {}.authorize_payment(Some(types::PaymentsAuthorizeData{
+            payment_method_data: types::api::PaymentMethod::Wallet(api::WalletData{
+                    issuer_name: api_models::enums::WalletIssuer::GooglePay,
+                    token: Some(r#"{"signature":"MEUCIQD7Ta+d9+buesrH2KKkF+03AqTen+eHHN8KFleHoKaiVAIgGvAXyI0Vg3ws8KlF7agW/gmXJhpJOOPkqiNVbn/4f0Y\u003d","protocolVersion":"ECv1","signedMessage":"{\"encryptedMessage\":\"UcdGP9F/1loU0aXvVj6VqGRPA5EAjHYfJrXD0N+5O13RnaJXKWIjch1zzjpy9ONOZHqEGAqYKIcKcpe5ppN4Fpd0dtbm1H4u+lA+SotCff3euPV6sne22/Pl/MNgbz5QvDWR0UjcXvIKSPNwkds1Ib7QMmH4GfZ3vvn6s534hxAmcv/LlkeM4FFf6py9crJK5fDIxtxRJncfLuuPeAXkyy+u4zE33HmT34Oe5MSW/kYZVz31eWqFy2YCIjbJcC9ElMluoOKSZ305UG7tYGB1LCFGQLtLxphrhPu1lEmGEZE1t2cVDoCzjr3rm1OcfENc7eNC4S+ko6yrXh1ZX06c/F9kunyLn0dAz8K5JLIwLdjw3wPADVSd3L0eM7jkzhH80I6nWkutO0x8BFltxWl+OtzrnAe093OUncH6/DK1pCxtJaHdw1WUWrzULcdaMZmPfA\\u003d\\u003d\",\"ephemeralPublicKey\":\"BH7A1FUBWiePkjh/EYmsjY/63D/6wU+4UmkLh7WW6v7PnoqQkjrFpc4kEP5a1Op4FkIlM9LlEs3wGdFB8xIy9cM\\u003d\",\"tag\":\"e/EOsw2Y2wYpJngNWQqH7J62Fhg/tzmgDl6UFGuAN+A\\u003d\"}"}"#
+                    .to_string()) //Generate new GooglePay token this is bound to expire
+                }),
+            currency: enums::Currency::PLN,
+            ..PaymentAuthorizeType::default().0
+    }), None).await;
+    assert_eq!(authorize_response.status, enums::AttemptStatus::Pending);
+    if let Some(transaction_id) = utils::get_connector_transaction_id(authorize_response) {
+        let sync_response = Payu {}
+            .sync_payment(
+                Some(types::PaymentsSyncData {
+                    connector_transaction_id: router::types::ResponseId::ConnectorTransactionId(
+                        transaction_id.clone(),
+                    ),
+                    encoded_data: None,
+                }),
+                None,
+            )
+            .await;
+        assert_eq!(sync_response.status, enums::AttemptStatus::Authorized);
+    }
+}
+
+#[actix_web::test]
+async fn should_capture_already_authorized_payment() {
+    let connector = Payu {};
+    let authorize_response = connector
+        .authorize_payment(
+            Some(types::PaymentsAuthorizeData {
+                currency: enums::Currency::PLN,
+                ..PaymentAuthorizeType::default().0
+            }),
+            None,
+        )
+        .await;
+    assert_eq!(authorize_response.status, enums::AttemptStatus::Pending);
+
+    if let Some(transaction_id) = utils::get_connector_transaction_id(authorize_response) {
+        let sync_response = connector
+            .sync_payment(
+                Some(types::PaymentsSyncData {
+                    connector_transaction_id: router::types::ResponseId::ConnectorTransactionId(
+                        transaction_id.clone(),
+                    ),
+                    encoded_data: None,
+                }),
+                None,
+            )
+            .await;
+        assert_eq!(sync_response.status, enums::AttemptStatus::Authorized);
+        let capture_response = connector
+            .capture_payment(transaction_id.clone(), None, None)
+            .await;
+        assert_eq!(capture_response.status, enums::AttemptStatus::Pending);
+        let response = connector
+            .sync_payment(
+                Some(types::PaymentsSyncData {
+                    connector_transaction_id: router::types::ResponseId::ConnectorTransactionId(
+                        transaction_id,
+                    ),
+                    encoded_data: None,
+                }),
+                None,
+            )
+            .await;
+        assert_eq!(response.status, enums::AttemptStatus::Charged,);
+    }
+}
+
+#[actix_web::test]
+async fn should_sync_payment() {
+    let connector = Payu {};
+    // Authorize the payment for manual capture
+    let authorize_response = connector
+        .authorize_payment(
+            Some(types::PaymentsAuthorizeData {
+                currency: enums::Currency::PLN,
+                ..PaymentAuthorizeType::default().0
+            }),
+            None,
+        )
+        .await;
+    assert_eq!(authorize_response.status, enums::AttemptStatus::Pending);
+
+    if let Some(transaction_id) = utils::get_connector_transaction_id(authorize_response) {
+        // Sync the Payment Data
+        let response = connector
+            .sync_payment(
+                Some(types::PaymentsSyncData {
+                    connector_transaction_id: router::types::ResponseId::ConnectorTransactionId(
+                        transaction_id,
+                    ),
+                    encoded_data: None,
+                }),
+                None,
+            )
+            .await;
+
+        assert_eq!(response.status, enums::AttemptStatus::Authorized);
+    }
+}
+
+#[actix_web::test]
+async fn should_void_already_authorized_payment() {
+    let connector = Payu {};
+    //make a successful payment
+    let authorize_response = connector
+        .make_payment(
+            Some(types::PaymentsAuthorizeData {
+                currency: enums::Currency::PLN,
+                ..PaymentAuthorizeType::default().0
+            }),
+            None,
+        )
+        .await;
+    assert_eq!(authorize_response.status, enums::AttemptStatus::Pending);
+
+    //try CANCEL for previous payment
+    if let Some(transaction_id) = utils::get_connector_transaction_id(authorize_response) {
+        let void_response = connector
+            .void_payment(transaction_id.clone(), None, None)
+            .await;
+        assert_eq!(void_response.status, enums::AttemptStatus::Pending);
+
+        let sync_response = connector
+            .sync_payment(
+                Some(types::PaymentsSyncData {
+                    connector_transaction_id: router::types::ResponseId::ConnectorTransactionId(
+                        transaction_id,
+                    ),
+                    encoded_data: None,
+                }),
+                None,
+            )
+            .await;
+        assert_eq!(sync_response.status, enums::AttemptStatus::Voided,);
+    }
+}
+
+#[actix_web::test]
+async fn should_refund_succeeded_payment() {
+    let connector = Payu {};
+    //make a successful payment
+    let authorize_response = connector
+        .make_payment(
+            Some(types::PaymentsAuthorizeData {
+                currency: enums::Currency::PLN,
+                ..PaymentAuthorizeType::default().0
+            }),
+            None,
+        )
+        .await;
+    assert_eq!(authorize_response.status, enums::AttemptStatus::Pending);
+
+    if let Some(transaction_id) = utils::get_connector_transaction_id(authorize_response) {
+        //Capture the payment in case of Manual Capture
+        let capture_response = connector
+            .capture_payment(transaction_id.clone(), None, None)
+            .await;
+        assert_eq!(capture_response.status, enums::AttemptStatus::Pending);
+
+        let sync_response = connector
+            .sync_payment(
+                Some(types::PaymentsSyncData {
+                    connector_transaction_id: router::types::ResponseId::ConnectorTransactionId(
+                        transaction_id.clone(),
+                    ),
+                    encoded_data: None,
+                }),
+                None,
+            )
+            .await;
+        assert_eq!(sync_response.status, enums::AttemptStatus::Charged);
+
+        //Refund the payment
+        let refund_response = connector
+            .refund_payment(transaction_id.clone(), None, None)
+            .await;
+        assert_eq!(
+            refund_response.response.unwrap().connector_refund_id.len(),
+            10
+        );
+    }
+}
+
+#[actix_web::test]
+async fn should_sync_succeeded_refund_payment() {
+    let connector = Payu {};
+
+    //Currently hardcoding the order_id because RSync is not instant, change it accordingly
+    let sync_refund_response = connector
+        .sync_refund("6DHQQN3T57230110GUEST000P01".to_string(), None, None)
+        .await;
+    assert_eq!(
+        sync_refund_response.response.unwrap().refund_status,
+        enums::RefundStatus::Success
+    );
+}
+
+#[actix_web::test]
+async fn should_fail_already_refunded_payment() {
+    let connector = Payu {};
+    //Currently hardcoding the order_id, change it accordingly
+    let response = connector
+        .refund_payment("5H1SVX6P7W230112GUEST000P01".to_string(), None, None)
+        .await;
+    let x = response.response.unwrap_err();
+    assert_eq!(x.reason.unwrap(), "PAID".to_string());
+}
diff --git a/crates/router/tests/connectors/sample_auth.toml b/crates/router/tests/connectors/sample_auth.toml
index 3dcf495e9ad..a8fe5dffe2e 100644
--- a/crates/router/tests/connectors/sample_auth.toml
+++ b/crates/router/tests/connectors/sample_auth.toml
@@ -19,5 +19,9 @@ api_key = "Bearer MyApiKey"
 [worldpay]
 api_key = "Bearer MyApiKey"
 
+[payu]
+api_key = "Bearer MyApiKey"
+key1 = "MerchantPosId"
+
 [globalpay]
 api_key = "Bearer MyApiKey"
diff --git a/crates/router/tests/connectors/utils.rs b/crates/router/tests/connectors/utils.rs
index 70576c37c09..650eafd7bf0 100644
--- a/crates/router/tests/connectors/utils.rs
+++ b/crates/router/tests/connectors/utils.rs
@@ -135,7 +135,7 @@ pub trait ConnectorActions: Connector {
         let integration = self.get_data().connector.get_connector_integration();
         let request = self.generate_data(
             payment_data.unwrap_or_else(|| types::RefundsData {
-                amount: 100,
+                amount: 1000,
                 currency: enums::Currency::USD,
                 refund_id: uuid::Uuid::new_v4().to_string(),
                 connector_transaction_id: transaction_id,
@@ -230,6 +230,7 @@ pub struct PaymentAuthorizeType(pub types::PaymentsAuthorizeData);
 pub struct PaymentSyncType(pub types::PaymentsSyncData);
 pub struct PaymentRefundType(pub types::RefundsData);
 pub struct CCardType(pub api::CCard);
+pub struct BrowserInfoType(pub types::BrowserInformation);
 
 impl Default for CCardType {
     fn default() -> Self {
@@ -256,7 +257,7 @@ impl Default for PaymentAuthorizeType {
             mandate_id: None,
             off_session: None,
             setup_mandate_details: None,
-            browser_info: None,
+            browser_info: Some(BrowserInfoType::default().0),
             order_details: None,
             email: None,
         };
@@ -264,6 +265,24 @@ impl Default for PaymentAuthorizeType {
     }
 }
 
+impl Default for BrowserInfoType {
+    fn default() -> Self {
+        let data = types::BrowserInformation {
+            user_agent: "".to_string(),
+            accept_header: "".to_string(),
+            language: "nl-NL".to_string(),
+            color_depth: 24,
+            screen_height: 723,
+            screen_width: 1536,
+            time_zone: 0,
+            java_enabled: true,
+            java_script_enabled: true,
+            ip_address: Some("127.0.0.1".parse().unwrap()),
+        };
+        Self(data)
+    }
+}
+
 impl Default for PaymentSyncType {
     fn default() -> Self {
         let data = types::PaymentsSyncData {

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

- [X] New feature

## Description
<!-- Describe your changes in detail -->
- Adding support for [payU](https://developers.payu.com/en/overview.html) payment gateway
- Updated connector creation script

**Payment Method:** card, GooglePay, ApplePay
**Supported payment flows**

- [Authorize](https://developers.payu.com/en/restapi.html#creating_new_order_api)
- [Capture](https://developers.payu.com/en/restapi.html#status_update)
- [PSync](https://developers.payu.com/en/restapi.html#order_data_retrieve)
- [Refund](https://developers.payu.com/en/restapi.html#refunds_create)
- [RSync](https://developers.payu.com/en/restapi.html#refunds_retrieve)
- [Cancel](https://developers.payu.com/en/restapi.html#cancellation)


### 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).
-->

Adding new connector [payU](https://developers.payu.com/en/overview.html)
#237 


## 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)?
-->
Added unit tests for mentioned payment flows.
<img width="786" alt="Screenshot 2023-01-11 at 3 34 52 PM" src="https://user-images.githubusercontent.com/55536657/211777225-0f0b47bc-edb5-4b59-ae65-6d6fd6188baa.png">

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

- [X] I formatted the code `cargo +nightly fmt`
- [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
