Bug: Add NMI for card payments




diff --git a/Cargo.lock b/Cargo.lock
index a310cff5c1a..c701a355ebb 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1318,6 +1318,7 @@ dependencies = [
  "nanoid",
  "once_cell",
  "proptest",
+ "quick-xml",
  "rand 0.8.5",
  "regex",
  "ring",
@@ -3368,6 +3369,16 @@ version = "2.0.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
 
+[[package]]
+name = "quick-xml"
+version = "0.28.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ce5e73202a820a31f8a0ee32ada5e21029c81fd9e3ebf668a40832e4219d9d1"
+dependencies = [
+ "memchr",
+ "serde",
+]
+
 [[package]]
 name = "quote"
 version = "1.0.26"
diff --git a/config/config.example.toml b/config/config.example.toml
index bda27142478..1f4b3569f95 100644
--- a/config/config.example.toml
+++ b/config/config.example.toml
@@ -165,6 +165,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/"
 nexinets.base_url = "https://apitest.payengine.de/v1"
+nmi.base_url = "https://secure.nmi.com/"
 nuvei.base_url = "https://ppp-test.nuvei.com/"
 opennode.base_url = "https://dev-api.opennode.com"
 payeezy.base_url = "https://api-cert.payeezy.com/"
diff --git a/config/development.toml b/config/development.toml
index b537128d784..0ec43557d92 100644
--- a/config/development.toml
+++ b/config/development.toml
@@ -71,6 +71,7 @@ cards = [
     "mollie",
     "multisafepay",
     "nexinets",
+    "nmi",
     "nuvei",
     "opennode",
     "payeezy",
@@ -119,6 +120,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/"
 nexinets.base_url = "https://apitest.payengine.de/v1"
+nmi.base_url = "https://secure.nmi.com/"
 nuvei.base_url = "https://ppp-test.nuvei.com/"
 opennode.base_url = "https://dev-api.opennode.com"
 payeezy.base_url = "https://api-cert.payeezy.com/"
diff --git a/config/docker_compose.toml b/config/docker_compose.toml
index 10f5129c778..a1eadfe6990 100644
--- a/config/docker_compose.toml
+++ b/config/docker_compose.toml
@@ -89,6 +89,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/"
 nexinets.base_url = "https://apitest.payengine.de/v1"
+nmi.base_url = "https://secure.nmi.com/"
 nuvei.base_url = "https://ppp-test.nuvei.com/"
 opennode.base_url = "https://dev-api.opennode.com"
 payeezy.base_url = "https://api-cert.payeezy.com/"
@@ -127,6 +128,7 @@ cards = [
     "mollie",
     "multisafepay",
     "nexinets",
+    "nmi",
     "nuvei",
     "opennode",
     "payeezy",
diff --git a/crates/api_models/src/enums.rs b/crates/api_models/src/enums.rs
index 2deab45a86d..a849b0a64ed 100644
--- a/crates/api_models/src/enums.rs
+++ b/crates/api_models/src/enums.rs
@@ -608,6 +608,7 @@ pub enum Connector {
     Mollie,
     Multisafepay,
     Nexinets,
+    Nmi,
     Nuvei,
     // Payeezy, As psync and rsync are not supported by this connector, it is added as template code for future usage
     Paypal,
@@ -679,6 +680,7 @@ pub enum RoutableConnectors {
     Mollie,
     Multisafepay,
     Nexinets,
+    Nmi,
     Nuvei,
     Opennode,
     // Payeezy, As psync and rsync are not supported by this connector, it is added as template code for future usage
diff --git a/crates/common_utils/Cargo.toml b/crates/common_utils/Cargo.toml
index ae31147d5b7..aa5ffab69e0 100644
--- a/crates/common_utils/Cargo.toml
+++ b/crates/common_utils/Cargo.toml
@@ -20,6 +20,7 @@ futures = { version = "0.3.28", optional = true }
 hex = "0.4.3"
 nanoid = "0.4.0"
 once_cell = "1.17.1"
+quick-xml = { version = "0.28.2", features = ["serialize"] }
 rand = "0.8.5"
 regex = "1.7.3"
 ring = "0.16.20"
diff --git a/crates/common_utils/src/ext_traits.rs b/crates/common_utils/src/ext_traits.rs
index ac626ff703e..912d859c019 100644
--- a/crates/common_utils/src/ext_traits.rs
+++ b/crates/common_utils/src/ext_traits.rs
@@ -5,6 +5,7 @@
 
 use error_stack::{IntoReport, ResultExt};
 use masking::{ExposeInterface, Secret, Strategy};
+use quick_xml::de;
 use serde::{Deserialize, Serialize};
 
 use crate::errors::{self, CustomResult};
@@ -392,3 +393,22 @@ impl ConfigExt for String {
         self.trim().is_empty()
     }
 }
+
+/// Extension trait for deserializing XML strings using `quick-xml` crate
+pub trait XmlExt {
+    ///
+    /// Deserialize an XML string into the specified type `<T>`.
+    ///
+    fn parse_xml<T>(self) -> Result<T, quick_xml::de::DeError>
+    where
+        T: serde::de::DeserializeOwned;
+}
+
+impl XmlExt for &str {
+    fn parse_xml<T>(self) -> Result<T, quick_xml::de::DeError>
+    where
+        T: serde::de::DeserializeOwned,
+    {
+        de::from_str(self)
+    }
+}
diff --git a/crates/router/src/configs/settings.rs b/crates/router/src/configs/settings.rs
index 7f81bfc6d0a..20f7272f45f 100644
--- a/crates/router/src/configs/settings.rs
+++ b/crates/router/src/configs/settings.rs
@@ -375,6 +375,7 @@ pub struct Connectors {
     pub mollie: ConnectorParams,
     pub multisafepay: ConnectorParams,
     pub nexinets: ConnectorParams,
+    pub nmi: ConnectorParams,
     pub nuvei: ConnectorParams,
     pub opennode: ConnectorParams,
     pub payeezy: ConnectorParams,
diff --git a/crates/router/src/connector.rs b/crates/router/src/connector.rs
index a57ba2998fe..c5a87231252 100644
--- a/crates/router/src/connector.rs
+++ b/crates/router/src/connector.rs
@@ -16,8 +16,10 @@ pub mod forte;
 pub mod globalpay;
 pub mod iatapay;
 pub mod klarna;
+pub mod mollie;
 pub mod multisafepay;
 pub mod nexinets;
+pub mod nmi;
 pub mod nuvei;
 pub mod opennode;
 pub mod payeezy;
@@ -32,8 +34,6 @@ pub mod worldline;
 pub mod worldpay;
 pub mod zen;
 
-pub mod mollie;
-
 #[cfg(feature = "dummy_connector")]
 pub use self::dummyconnector::DummyConnector;
 pub use self::{
@@ -41,7 +41,7 @@ pub use self::{
     bambora::Bambora, bluesnap::Bluesnap, braintree::Braintree, checkout::Checkout,
     coinbase::Coinbase, cybersource::Cybersource, dlocal::Dlocal, fiserv::Fiserv, forte::Forte,
     globalpay::Globalpay, iatapay::Iatapay, klarna::Klarna, mollie::Mollie,
-    multisafepay::Multisafepay, nexinets::Nexinets, nuvei::Nuvei, opennode::Opennode,
+    multisafepay::Multisafepay, nexinets::Nexinets, nmi::Nmi, nuvei::Nuvei, opennode::Opennode,
     payeezy::Payeezy, paypal::Paypal, payu::Payu, rapyd::Rapyd, shift4::Shift4, stripe::Stripe,
     trustpay::Trustpay, worldline::Worldline, worldpay::Worldpay, zen::Zen,
 };
diff --git a/crates/router/src/connector/nmi.rs b/crates/router/src/connector/nmi.rs
new file mode 100644
index 00000000000..be7be395008
--- /dev/null
+++ b/crates/router/src/connector/nmi.rs
@@ -0,0 +1,590 @@
+mod transformers;
+
+use std::fmt::Debug;
+
+use common_utils::ext_traits::ByteSliceExt;
+use error_stack::{IntoReport, ResultExt};
+use transformers as nmi;
+
+use self::transformers::NmiCaptureRequest;
+use crate::{
+    configs::settings,
+    core::errors::{self, CustomResult},
+    services::{self, ConnectorIntegration},
+    types::{
+        self,
+        api::{self, ConnectorCommon, ConnectorCommonExt},
+        ErrorResponse,
+    },
+    utils,
+};
+
+#[derive(Clone, Debug)]
+pub struct Nmi;
+
+impl api::Payment for Nmi {}
+impl api::PaymentSession for Nmi {}
+impl api::ConnectorAccessToken for Nmi {}
+impl api::PreVerify for Nmi {}
+impl api::PaymentAuthorize for Nmi {}
+impl api::PaymentSync for Nmi {}
+impl api::PaymentCapture for Nmi {}
+impl api::PaymentVoid for Nmi {}
+impl api::Refund for Nmi {}
+impl api::RefundExecute for Nmi {}
+impl api::RefundSync for Nmi {}
+impl api::PaymentToken for Nmi {}
+
+impl<Flow, Request, Response> ConnectorCommonExt<Flow, Request, Response> for Nmi
+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> {
+        Ok(vec![(
+            "Content-Type".to_string(),
+            "application/x-www-form-urlencoded".to_string(),
+        )])
+    }
+}
+
+impl ConnectorCommon for Nmi {
+    fn id(&self) -> &'static str {
+        "nmi"
+    }
+
+    fn base_url<'a>(&self, connectors: &'a settings::Connectors) -> &'a str {
+        connectors.nmi.base_url.as_ref()
+    }
+
+    fn build_error_response(
+        &self,
+        res: types::Response,
+    ) -> CustomResult<ErrorResponse, errors::ConnectorError> {
+        let response: nmi::StandardResponse = res
+            .response
+            .parse_struct("StandardResponse")
+            .change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
+        Ok(ErrorResponse {
+            message: response.responsetext,
+            status_code: res.status_code,
+            reason: None,
+            ..Default::default()
+        })
+    }
+}
+
+impl
+    ConnectorIntegration<
+        api::PaymentMethodToken,
+        types::PaymentMethodTokenizationData,
+        types::PaymentsResponseData,
+    > for Nmi
+{
+}
+
+impl ConnectorIntegration<api::Session, types::PaymentsSessionData, types::PaymentsResponseData>
+    for Nmi
+{
+}
+
+impl ConnectorIntegration<api::AccessTokenAuth, types::AccessTokenRequestData, types::AccessToken>
+    for Nmi
+{
+}
+
+impl ConnectorIntegration<api::Verify, types::VerifyRequestData, types::PaymentsResponseData>
+    for Nmi
+{
+    fn get_headers(
+        &self,
+        req: &types::VerifyRouterData,
+        connectors: &settings::Connectors,
+    ) -> CustomResult<Vec<(String, String)>, errors::ConnectorError> {
+        self.build_headers(req, connectors)
+    }
+
+    fn get_url(
+        &self,
+        _req: &types::VerifyRouterData,
+        connectors: &settings::Connectors,
+    ) -> CustomResult<String, errors::ConnectorError> {
+        Ok(format!("{}api/transact.php", self.base_url(connectors)))
+    }
+
+    fn get_request_body(
+        &self,
+        req: &types::VerifyRouterData,
+    ) -> CustomResult<Option<String>, errors::ConnectorError> {
+        let connector_req = nmi::NmiPaymentsRequest::try_from(req)?;
+        let nmi_req = utils::Encode::<nmi::NmiPaymentsRequest>::url_encode(&connector_req)
+            .change_context(errors::ConnectorError::RequestEncodingFailed)?;
+        Ok(Some(nmi_req))
+    }
+
+    fn build_request(
+        &self,
+        req: &types::VerifyRouterData,
+        connectors: &settings::Connectors,
+    ) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
+        Ok(Some(
+            services::RequestBuilder::new()
+                .method(services::Method::Post)
+                .url(&types::PaymentsVerifyType::get_url(self, req, connectors)?)
+                .headers(types::PaymentsVerifyType::get_headers(
+                    self, req, connectors,
+                )?)
+                .body(types::PaymentsVerifyType::get_request_body(self, req)?)
+                .build(),
+        ))
+    }
+
+    fn handle_response(
+        &self,
+        data: &types::VerifyRouterData,
+        res: types::Response,
+    ) -> CustomResult<types::VerifyRouterData, errors::ConnectorError> {
+        let response: nmi::StandardResponse = serde_urlencoded::from_bytes(&res.response)
+            .into_report()
+            .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: types::Response,
+    ) -> CustomResult<ErrorResponse, errors::ConnectorError> {
+        self.build_error_response(res)
+    }
+}
+
+impl ConnectorIntegration<api::Authorize, types::PaymentsAuthorizeData, types::PaymentsResponseData>
+    for Nmi
+{
+    fn get_headers(
+        &self,
+        req: &types::PaymentsAuthorizeRouterData,
+        connectors: &settings::Connectors,
+    ) -> CustomResult<Vec<(String, String)>, errors::ConnectorError> {
+        self.build_headers(req, connectors)
+    }
+
+    fn get_url(
+        &self,
+        _req: &types::PaymentsAuthorizeRouterData,
+        connectors: &settings::Connectors,
+    ) -> CustomResult<String, errors::ConnectorError> {
+        Ok(format!("{}api/transact.php", self.base_url(connectors)))
+    }
+
+    fn get_request_body(
+        &self,
+        req: &types::PaymentsAuthorizeRouterData,
+    ) -> CustomResult<Option<String>, errors::ConnectorError> {
+        let connector_req = nmi::NmiPaymentsRequest::try_from(req)?;
+        let nmi_req = utils::Encode::<nmi::NmiPaymentsRequest>::url_encode(&connector_req)
+            .change_context(errors::ConnectorError::RequestEncodingFailed)?;
+        Ok(Some(nmi_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: types::Response,
+    ) -> CustomResult<types::PaymentsAuthorizeRouterData, errors::ConnectorError> {
+        let response: nmi::StandardResponse = serde_urlencoded::from_bytes(&res.response)
+            .into_report()
+            .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: types::Response,
+    ) -> CustomResult<ErrorResponse, errors::ConnectorError> {
+        self.build_error_response(res)
+    }
+}
+
+impl ConnectorIntegration<api::PSync, types::PaymentsSyncData, types::PaymentsResponseData>
+    for Nmi
+{
+    fn get_headers(
+        &self,
+        req: &types::PaymentsSyncRouterData,
+        connectors: &settings::Connectors,
+    ) -> CustomResult<Vec<(String, String)>, errors::ConnectorError> {
+        self.build_headers(req, connectors)
+    }
+
+    fn get_url(
+        &self,
+        _req: &types::PaymentsSyncRouterData,
+        connectors: &settings::Connectors,
+    ) -> CustomResult<String, errors::ConnectorError> {
+        Ok(format!("{}api/query.php", self.base_url(connectors)))
+    }
+
+    fn get_request_body(
+        &self,
+        req: &types::PaymentsSyncRouterData,
+    ) -> CustomResult<Option<String>, errors::ConnectorError> {
+        let connector_req = nmi::NmiSyncRequest::try_from(req)?;
+        let nmi_req = utils::Encode::<nmi::NmiSyncRequest>::url_encode(&connector_req)
+            .change_context(errors::ConnectorError::RequestEncodingFailed)?;
+        Ok(Some(nmi_req))
+    }
+
+    fn build_request(
+        &self,
+        req: &types::PaymentsSyncRouterData,
+        connectors: &settings::Connectors,
+    ) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
+        Ok(Some(
+            services::RequestBuilder::new()
+                .method(services::Method::Post)
+                .url(&types::PaymentsSyncType::get_url(self, req, connectors)?)
+                .headers(types::PaymentsSyncType::get_headers(self, req, connectors)?)
+                .body(types::PaymentsSyncType::get_request_body(self, req)?)
+                .build(),
+        ))
+    }
+
+    fn handle_response(
+        &self,
+        data: &types::PaymentsSyncRouterData,
+        res: types::Response,
+    ) -> CustomResult<types::PaymentsSyncRouterData, errors::ConnectorError> {
+        types::RouterData::try_from(types::ResponseRouterData {
+            response: res.clone(),
+            data: data.clone(),
+            http_code: res.status_code,
+        })
+    }
+
+    fn get_error_response(
+        &self,
+        res: types::Response,
+    ) -> CustomResult<ErrorResponse, errors::ConnectorError> {
+        self.build_error_response(res)
+    }
+}
+
+impl ConnectorIntegration<api::Capture, types::PaymentsCaptureData, types::PaymentsResponseData>
+    for Nmi
+{
+    fn get_headers(
+        &self,
+        req: &types::PaymentsCaptureRouterData,
+        connectors: &settings::Connectors,
+    ) -> CustomResult<Vec<(String, String)>, errors::ConnectorError> {
+        self.build_headers(req, connectors)
+    }
+
+    fn get_url(
+        &self,
+        _req: &types::PaymentsCaptureRouterData,
+        connectors: &settings::Connectors,
+    ) -> CustomResult<String, errors::ConnectorError> {
+        Ok(format!("{}api/transact.php", self.base_url(connectors)))
+    }
+
+    fn get_request_body(
+        &self,
+        req: &types::PaymentsCaptureRouterData,
+    ) -> CustomResult<Option<String>, errors::ConnectorError> {
+        let connector_req = nmi::NmiCaptureRequest::try_from(req)?;
+        let nmi_req = utils::Encode::<NmiCaptureRequest>::url_encode(&connector_req)
+            .change_context(errors::ConnectorError::RequestEncodingFailed)?;
+        Ok(Some(nmi_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,
+                )?)
+                .body(types::PaymentsCaptureType::get_request_body(self, req)?)
+                .build(),
+        ))
+    }
+
+    fn handle_response(
+        &self,
+        data: &types::PaymentsCaptureRouterData,
+        res: types::Response,
+    ) -> CustomResult<types::PaymentsCaptureRouterData, errors::ConnectorError> {
+        let response: nmi::StandardResponse = serde_urlencoded::from_bytes(&res.response)
+            .into_report()
+            .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: types::Response,
+    ) -> CustomResult<ErrorResponse, errors::ConnectorError> {
+        self.build_error_response(res)
+    }
+}
+
+impl ConnectorIntegration<api::Void, types::PaymentsCancelData, types::PaymentsResponseData>
+    for Nmi
+{
+    fn get_headers(
+        &self,
+        req: &types::PaymentsCancelRouterData,
+        connectors: &settings::Connectors,
+    ) -> CustomResult<Vec<(String, String)>, errors::ConnectorError> {
+        self.build_headers(req, connectors)
+    }
+
+    fn get_url(
+        &self,
+        _req: &types::PaymentsCancelRouterData,
+        connectors: &settings::Connectors,
+    ) -> CustomResult<String, errors::ConnectorError> {
+        Ok(format!("{}api/transact.php", self.base_url(connectors)))
+    }
+
+    fn get_request_body(
+        &self,
+        req: &types::PaymentsCancelRouterData,
+    ) -> CustomResult<Option<String>, errors::ConnectorError> {
+        let connector_req = nmi::NmiCancelRequest::try_from(req)?;
+        let nmi_req = utils::Encode::<nmi::NmiCancelRequest>::url_encode(&connector_req)
+            .change_context(errors::ConnectorError::RequestEncodingFailed)?;
+        Ok(Some(nmi_req))
+    }
+
+    fn build_request(
+        &self,
+        req: &types::PaymentsCancelRouterData,
+        connectors: &settings::Connectors,
+    ) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
+        Ok(Some(
+            services::RequestBuilder::new()
+                .method(services::Method::Post)
+                .url(&types::PaymentsVoidType::get_url(self, req, connectors)?)
+                .headers(types::PaymentsVoidType::get_headers(self, req, connectors)?)
+                .body(types::PaymentsVoidType::get_request_body(self, req)?)
+                .build(),
+        ))
+    }
+
+    fn handle_response(
+        &self,
+        data: &types::PaymentsCancelRouterData,
+        res: types::Response,
+    ) -> CustomResult<types::PaymentsCancelRouterData, errors::ConnectorError> {
+        let response: nmi::StandardResponse = serde_urlencoded::from_bytes(&res.response)
+            .into_report()
+            .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: types::Response,
+    ) -> CustomResult<ErrorResponse, errors::ConnectorError> {
+        self.build_error_response(res)
+    }
+}
+
+impl ConnectorIntegration<api::Execute, types::RefundsData, types::RefundsResponseData> for Nmi {
+    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_url(
+        &self,
+        _req: &types::RefundsRouterData<api::Execute>,
+        connectors: &settings::Connectors,
+    ) -> CustomResult<String, errors::ConnectorError> {
+        Ok(format!("{}api/transact.php", self.base_url(connectors)))
+    }
+
+    fn get_request_body(
+        &self,
+        req: &types::RefundsRouterData<api::Execute>,
+    ) -> CustomResult<Option<String>, errors::ConnectorError> {
+        let connector_req = nmi::NmiRefundRequest::try_from(req)?;
+        let nmi_req = utils::Encode::<nmi::NmiRefundRequest>::url_encode(&connector_req)
+            .change_context(errors::ConnectorError::RequestEncodingFailed)?;
+        Ok(Some(nmi_req))
+    }
+
+    fn build_request(
+        &self,
+        req: &types::RefundsRouterData<api::Execute>,
+        connectors: &settings::Connectors,
+    ) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
+        Ok(Some(
+            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(),
+        ))
+    }
+
+    fn handle_response(
+        &self,
+        data: &types::RefundsRouterData<api::Execute>,
+        res: types::Response,
+    ) -> CustomResult<types::RefundsRouterData<api::Execute>, errors::ConnectorError> {
+        let response: nmi::StandardResponse = serde_urlencoded::from_bytes(&res.response)
+            .into_report()
+            .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: types::Response,
+    ) -> CustomResult<ErrorResponse, errors::ConnectorError> {
+        self.build_error_response(res)
+    }
+}
+
+impl ConnectorIntegration<api::RSync, types::RefundsData, types::RefundsResponseData> for Nmi {
+    fn get_headers(
+        &self,
+        req: &types::RefundsRouterData<api::RSync>,
+        connectors: &settings::Connectors,
+    ) -> CustomResult<Vec<(String, String)>, errors::ConnectorError> {
+        self.build_headers(req, connectors)
+    }
+
+    fn get_url(
+        &self,
+        _req: &types::RefundsRouterData<api::RSync>,
+        connectors: &settings::Connectors,
+    ) -> CustomResult<String, errors::ConnectorError> {
+        Ok(format!("{}api/query.php", self.base_url(connectors)))
+    }
+
+    fn get_request_body(
+        &self,
+        req: &types::RefundsRouterData<api::RSync>,
+    ) -> CustomResult<Option<String>, errors::ConnectorError> {
+        let connector_req = nmi::NmiSyncRequest::try_from(req)?;
+        let nmi_req = utils::Encode::<nmi::NmiSyncRequest>::url_encode(&connector_req)
+            .change_context(errors::ConnectorError::RequestEncodingFailed)?;
+        Ok(Some(nmi_req))
+    }
+
+    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::Post)
+                .url(&types::RefundSyncType::get_url(self, req, connectors)?)
+                .headers(types::RefundSyncType::get_headers(self, req, connectors)?)
+                .body(types::RefundSyncType::get_request_body(self, req)?)
+                .build(),
+        ))
+    }
+
+    fn handle_response(
+        &self,
+        data: &types::RefundsRouterData<api::RSync>,
+        res: types::Response,
+    ) -> CustomResult<types::RefundsRouterData<api::RSync>, errors::ConnectorError> {
+        types::RouterData::try_from(types::ResponseRouterData {
+            response: res.clone(),
+            data: data.clone(),
+            http_code: res.status_code,
+        })
+    }
+
+    fn get_error_response(
+        &self,
+        res: types::Response,
+    ) -> CustomResult<ErrorResponse, errors::ConnectorError> {
+        self.build_error_response(res)
+    }
+}
+
+#[async_trait::async_trait]
+impl api::IncomingWebhook for Nmi {
+    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()
+    }
+}
diff --git a/crates/router/src/connector/nmi/transformers.rs b/crates/router/src/connector/nmi/transformers.rs
new file mode 100644
index 00000000000..eb09497cede
--- /dev/null
+++ b/crates/router/src/connector/nmi/transformers.rs
@@ -0,0 +1,672 @@
+use cards::CardNumber;
+use common_utils::ext_traits::XmlExt;
+use error_stack::{IntoReport, Report, ResultExt};
+use masking::Secret;
+use serde::{Deserialize, Serialize};
+
+use crate::{
+    connector::utils::{self, PaymentsAuthorizeRequestData},
+    core::errors,
+    types::{self, api, storage::enums, transformers::ForeignFrom, ConnectorAuthType},
+};
+
+type Error = Report<errors::ConnectorError>;
+
+#[derive(Debug, Serialize)]
+#[serde(rename_all = "lowercase")]
+pub enum TransactionType {
+    Auth,
+    Capture,
+    Refund,
+    Sale,
+    Validate,
+    Void,
+}
+
+pub struct NmiAuthType {
+    pub(super) api_key: String,
+}
+
+impl TryFrom<&ConnectorAuthType> for NmiAuthType {
+    type Error = Error;
+    fn try_from(auth_type: &ConnectorAuthType) -> Result<Self, Self::Error> {
+        if let types::ConnectorAuthType::HeaderKey { api_key } = auth_type {
+            Ok(Self {
+                api_key: api_key.to_string(),
+            })
+        } else {
+            Err(errors::ConnectorError::FailedToObtainAuthType.into())
+        }
+    }
+}
+
+#[derive(Debug, Serialize)]
+pub struct NmiPaymentsRequest {
+    #[serde(rename = "type")]
+    transaction_type: TransactionType,
+    amount: f64,
+    security_key: Secret<String>,
+    currency: enums::Currency,
+    #[serde(flatten)]
+    payment_method: PaymentMethod,
+}
+
+#[derive(Debug, Serialize)]
+#[serde(untagged)]
+pub enum PaymentMethod {
+    Card(Box<CardData>),
+    GPay(Box<GooglePayData>),
+    ApplePay(Box<ApplePayData>),
+}
+
+#[derive(Debug, Serialize)]
+pub struct CardData {
+    ccnumber: CardNumber,
+    ccexp: Secret<String>,
+    cvv: Secret<String>,
+}
+
+#[derive(Debug, Serialize)]
+pub struct GooglePayData {
+    googlepay_payment_data: String,
+}
+
+#[derive(Debug, Serialize)]
+pub struct ApplePayData {
+    applepay_payment_data: String,
+}
+
+impl TryFrom<&types::PaymentsAuthorizeRouterData> for NmiPaymentsRequest {
+    type Error = Error;
+    fn try_from(item: &types::PaymentsAuthorizeRouterData) -> Result<Self, Self::Error> {
+        let transaction_type = match item.request.is_auto_capture()? {
+            true => TransactionType::Sale,
+            false => TransactionType::Auth,
+        };
+        let auth_type: NmiAuthType = (&item.connector_auth_type).try_into()?;
+        let amount =
+            utils::to_currency_base_unit_asf64(item.request.amount, item.request.currency)?;
+        let payment_method = PaymentMethod::try_from(&item.request.payment_method_data)?;
+
+        Ok(Self {
+            transaction_type,
+            security_key: auth_type.api_key.into(),
+            amount,
+            currency: item.request.currency,
+            payment_method,
+        })
+    }
+}
+
+impl TryFrom<&api_models::payments::PaymentMethodData> for PaymentMethod {
+    type Error = Error;
+    fn try_from(
+        payment_method_data: &api_models::payments::PaymentMethodData,
+    ) -> Result<Self, Self::Error> {
+        match &payment_method_data {
+            api::PaymentMethodData::Card(ref card) => Ok(Self::from(card)),
+            api::PaymentMethodData::Wallet(ref wallet_type) => match wallet_type {
+                api_models::payments::WalletData::GooglePay(ref googlepay_data) => {
+                    Ok(Self::from(googlepay_data))
+                }
+                api_models::payments::WalletData::ApplePay(ref applepay_data) => {
+                    Ok(Self::from(applepay_data))
+                }
+                _ => Err(errors::ConnectorError::NotImplemented(
+                    "Payment Method".to_string(),
+                ))
+                .into_report(),
+            },
+            _ => Err(errors::ConnectorError::NotImplemented(
+                "Payment Method".to_string(),
+            ))
+            .into_report(),
+        }
+    }
+}
+
+impl From<&api_models::payments::Card> for PaymentMethod {
+    fn from(card: &api_models::payments::Card) -> Self {
+        let ccexp = utils::CardData::get_card_expiry_month_year_2_digit_with_delimiter(
+            card,
+            "".to_string(),
+        );
+        let card = CardData {
+            ccnumber: card.card_number.clone(),
+            ccexp,
+            cvv: card.card_cvc.clone(),
+        };
+        Self::Card(Box::new(card))
+    }
+}
+
+impl From<&api_models::payments::GooglePayWalletData> for PaymentMethod {
+    fn from(wallet_data: &api_models::payments::GooglePayWalletData) -> Self {
+        let gpay_data = GooglePayData {
+            googlepay_payment_data: wallet_data.tokenization_data.token.clone(),
+        };
+        Self::GPay(Box::new(gpay_data))
+    }
+}
+
+impl From<&api_models::payments::ApplePayWalletData> for PaymentMethod {
+    fn from(wallet_data: &api_models::payments::ApplePayWalletData) -> Self {
+        let apple_pay_data = ApplePayData {
+            applepay_payment_data: wallet_data.payment_data.clone(),
+        };
+        Self::ApplePay(Box::new(apple_pay_data))
+    }
+}
+
+impl TryFrom<&types::VerifyRouterData> for NmiPaymentsRequest {
+    type Error = Error;
+    fn try_from(item: &types::VerifyRouterData) -> Result<Self, Self::Error> {
+        let auth_type: NmiAuthType = (&item.connector_auth_type).try_into()?;
+        let payment_method = PaymentMethod::try_from(&item.request.payment_method_data)?;
+        Ok(Self {
+            transaction_type: TransactionType::Validate,
+            security_key: auth_type.api_key.into(),
+            amount: 0.0,
+            currency: item.request.currency,
+            payment_method,
+        })
+    }
+}
+
+#[derive(Debug, Serialize)]
+pub struct NmiSyncRequest {
+    pub transaction_id: String,
+    pub security_key: String,
+}
+
+impl TryFrom<&types::PaymentsSyncRouterData> for NmiSyncRequest {
+    type Error = Error;
+    fn try_from(item: &types::PaymentsSyncRouterData) -> Result<Self, Self::Error> {
+        let auth = NmiAuthType::try_from(&item.connector_auth_type)?;
+        Ok(Self {
+            security_key: auth.api_key,
+            transaction_id: item
+                .request
+                .connector_transaction_id
+                .get_connector_transaction_id()
+                .change_context(errors::ConnectorError::MissingConnectorTransactionID)?,
+        })
+    }
+}
+
+#[derive(Debug, Serialize)]
+pub struct NmiCaptureRequest {
+    #[serde(rename = "type")]
+    pub transaction_type: TransactionType,
+    pub security_key: Secret<String>,
+    pub transactionid: String,
+    pub amount: Option<f64>,
+}
+
+impl TryFrom<&types::PaymentsCaptureRouterData> for NmiCaptureRequest {
+    type Error = Error;
+    fn try_from(item: &types::PaymentsCaptureRouterData) -> Result<Self, Self::Error> {
+        let auth = NmiAuthType::try_from(&item.connector_auth_type)?;
+        Ok(Self {
+            transaction_type: TransactionType::Capture,
+            security_key: auth.api_key.into(),
+            transactionid: item.request.connector_transaction_id.clone(),
+            amount: Some(utils::to_currency_base_unit_asf64(
+                item.request.amount_to_capture,
+                item.request.currency,
+            )?),
+        })
+    }
+}
+
+impl
+    TryFrom<
+        types::ResponseRouterData<
+            api::Capture,
+            StandardResponse,
+            types::PaymentsCaptureData,
+            types::PaymentsResponseData,
+        >,
+    > for types::RouterData<api::Capture, types::PaymentsCaptureData, types::PaymentsResponseData>
+{
+    type Error = Error;
+    fn try_from(
+        item: types::ResponseRouterData<
+            api::Capture,
+            StandardResponse,
+            types::PaymentsCaptureData,
+            types::PaymentsResponseData,
+        >,
+    ) -> Result<Self, Self::Error> {
+        let (response, status) = match item.response.response {
+            Response::Approved => (
+                Ok(types::PaymentsResponseData::TransactionResponse {
+                    resource_id: types::ResponseId::ConnectorTransactionId(
+                        item.response.transactionid,
+                    ),
+                    redirection_data: None,
+                    mandate_reference: None,
+                    connector_metadata: None,
+                    network_txn_id: None,
+                }),
+                enums::AttemptStatus::CaptureInitiated,
+            ),
+            Response::Declined | Response::Error => (
+                Err(types::ErrorResponse::foreign_from((
+                    item.response,
+                    item.http_code,
+                ))),
+                enums::AttemptStatus::CaptureFailed,
+            ),
+        };
+        Ok(Self {
+            status,
+            response,
+            ..item.data
+        })
+    }
+}
+
+#[derive(Debug, Serialize)]
+pub struct NmiCancelRequest {
+    #[serde(rename = "type")]
+    pub transaction_type: TransactionType,
+    pub security_key: Secret<String>,
+    pub transactionid: String,
+    pub void_reason: Option<String>,
+}
+
+impl TryFrom<&types::PaymentsCancelRouterData> for NmiCancelRequest {
+    type Error = Error;
+    fn try_from(item: &types::PaymentsCancelRouterData) -> Result<Self, Self::Error> {
+        let auth = NmiAuthType::try_from(&item.connector_auth_type)?;
+        Ok(Self {
+            transaction_type: TransactionType::Void,
+            security_key: auth.api_key.into(),
+            transactionid: item.request.connector_transaction_id.clone(),
+            void_reason: item.request.cancellation_reason.clone(),
+        })
+    }
+}
+
+#[derive(Debug, Deserialize)]
+pub enum Response {
+    #[serde(alias = "1")]
+    Approved,
+    #[serde(alias = "2")]
+    Declined,
+    #[serde(alias = "3")]
+    Error,
+}
+
+#[derive(Debug, Deserialize)]
+pub struct StandardResponse {
+    pub response: Response,
+    pub responsetext: String,
+    pub authcode: Option<String>,
+    pub transactionid: String,
+    pub avsresponse: Option<String>,
+    pub cvvresponse: Option<String>,
+    pub orderid: String,
+    pub response_code: String,
+}
+
+impl<T>
+    TryFrom<
+        types::ResponseRouterData<api::Verify, StandardResponse, T, types::PaymentsResponseData>,
+    > for types::RouterData<api::Verify, T, types::PaymentsResponseData>
+{
+    type Error = Error;
+    fn try_from(
+        item: types::ResponseRouterData<
+            api::Verify,
+            StandardResponse,
+            T,
+            types::PaymentsResponseData,
+        >,
+    ) -> Result<Self, Self::Error> {
+        let (response, status) = match item.response.response {
+            Response::Approved => (
+                Ok(types::PaymentsResponseData::TransactionResponse {
+                    resource_id: types::ResponseId::ConnectorTransactionId(
+                        item.response.transactionid,
+                    ),
+                    redirection_data: None,
+                    mandate_reference: None,
+                    connector_metadata: None,
+                    network_txn_id: None,
+                }),
+                enums::AttemptStatus::Charged,
+            ),
+            Response::Declined | Response::Error => (
+                Err(types::ErrorResponse::foreign_from((
+                    item.response,
+                    item.http_code,
+                ))),
+                enums::AttemptStatus::Failure,
+            ),
+        };
+        Ok(Self {
+            status,
+            response,
+            ..item.data
+        })
+    }
+}
+
+impl ForeignFrom<(StandardResponse, u16)> for types::ErrorResponse {
+    fn foreign_from((response, http_code): (StandardResponse, u16)) -> Self {
+        Self {
+            code: response.response_code,
+            message: response.responsetext,
+            reason: None,
+            status_code: http_code,
+        }
+    }
+}
+
+impl TryFrom<types::PaymentsResponseRouterData<StandardResponse>>
+    for types::RouterData<api::Authorize, types::PaymentsAuthorizeData, types::PaymentsResponseData>
+{
+    type Error = Error;
+    fn try_from(
+        item: types::ResponseRouterData<
+            api::Authorize,
+            StandardResponse,
+            types::PaymentsAuthorizeData,
+            types::PaymentsResponseData,
+        >,
+    ) -> Result<Self, Self::Error> {
+        let (response, status) = match item.response.response {
+            Response::Approved => (
+                Ok(types::PaymentsResponseData::TransactionResponse {
+                    resource_id: types::ResponseId::ConnectorTransactionId(
+                        item.response.transactionid,
+                    ),
+                    redirection_data: None,
+                    mandate_reference: None,
+                    connector_metadata: None,
+                    network_txn_id: None,
+                }),
+                if let Some(storage_models::enums::CaptureMethod::Automatic) =
+                    item.data.request.capture_method
+                {
+                    enums::AttemptStatus::CaptureInitiated
+                } else {
+                    enums::AttemptStatus::Authorizing
+                },
+            ),
+            Response::Declined | Response::Error => (
+                Err(types::ErrorResponse::foreign_from((
+                    item.response,
+                    item.http_code,
+                ))),
+                enums::AttemptStatus::Failure,
+            ),
+        };
+        Ok(Self {
+            status,
+            response,
+            ..item.data
+        })
+    }
+}
+
+impl<T>
+    TryFrom<types::ResponseRouterData<api::Void, StandardResponse, T, types::PaymentsResponseData>>
+    for types::RouterData<api::Void, T, types::PaymentsResponseData>
+{
+    type Error = Error;
+    fn try_from(
+        item: types::ResponseRouterData<
+            api::Void,
+            StandardResponse,
+            T,
+            types::PaymentsResponseData,
+        >,
+    ) -> Result<Self, Self::Error> {
+        let (response, status) = match item.response.response {
+            Response::Approved => (
+                Ok(types::PaymentsResponseData::TransactionResponse {
+                    resource_id: types::ResponseId::ConnectorTransactionId(
+                        item.response.transactionid,
+                    ),
+                    redirection_data: None,
+                    mandate_reference: None,
+                    connector_metadata: None,
+                    network_txn_id: None,
+                }),
+                enums::AttemptStatus::VoidInitiated,
+            ),
+            Response::Declined | Response::Error => (
+                Err(types::ErrorResponse::foreign_from((
+                    item.response,
+                    item.http_code,
+                ))),
+                enums::AttemptStatus::VoidFailed,
+            ),
+        };
+        Ok(Self {
+            status,
+            response,
+            ..item.data
+        })
+    }
+}
+
+#[derive(Debug, Deserialize)]
+#[serde(rename_all = "snake_case")]
+pub enum NmiStatus {
+    Abandoned,
+    Cancelled,
+    Pendingsettlement,
+    Pending,
+    Failed,
+    Complete,
+    InProgress,
+    Unknown,
+}
+
+impl TryFrom<types::PaymentsSyncResponseRouterData<types::Response>>
+    for types::PaymentsSyncRouterData
+{
+    type Error = Error;
+    fn try_from(
+        item: types::PaymentsSyncResponseRouterData<types::Response>,
+    ) -> Result<Self, Self::Error> {
+        let response = SyncResponse::try_from(item.response.response.to_vec())?;
+        Ok(Self {
+            status: enums::AttemptStatus::from(NmiStatus::from(response.transaction.condition)),
+            response: Ok(types::PaymentsResponseData::TransactionResponse {
+                resource_id: types::ResponseId::ConnectorTransactionId(
+                    response.transaction.transaction_id,
+                ),
+                redirection_data: None,
+                mandate_reference: None,
+                connector_metadata: None,
+                network_txn_id: None,
+            }),
+            ..item.data
+        })
+    }
+}
+
+impl TryFrom<Vec<u8>> for SyncResponse {
+    type Error = Error;
+    fn try_from(bytes: Vec<u8>) -> Result<Self, Self::Error> {
+        let query_response = String::from_utf8(bytes)
+            .into_report()
+            .change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
+        query_response
+            .parse_xml::<Self>()
+            .into_report()
+            .change_context(errors::ConnectorError::ResponseDeserializationFailed)
+    }
+}
+
+impl From<NmiStatus> for enums::AttemptStatus {
+    fn from(item: NmiStatus) -> Self {
+        match item {
+            NmiStatus::Abandoned => Self::AuthenticationFailed,
+            NmiStatus::Cancelled => Self::Voided,
+            NmiStatus::Pending => Self::Authorized,
+            NmiStatus::Pendingsettlement => Self::Pending,
+            NmiStatus::Complete => Self::Charged,
+            NmiStatus::InProgress => Self::AuthenticationPending,
+            NmiStatus::Failed | NmiStatus::Unknown => Self::Failure,
+        }
+    }
+}
+
+// REFUND :
+#[derive(Debug, Serialize)]
+pub struct NmiRefundRequest {
+    #[serde(rename = "type")]
+    transaction_type: TransactionType,
+    security_key: String,
+    transactionid: String,
+    amount: f64,
+}
+
+impl<F> TryFrom<&types::RefundsRouterData<F>> for NmiRefundRequest {
+    type Error = Error;
+    fn try_from(item: &types::RefundsRouterData<F>) -> Result<Self, Self::Error> {
+        let auth_type: NmiAuthType = (&item.connector_auth_type).try_into()?;
+        Ok(Self {
+            transaction_type: TransactionType::Refund,
+            security_key: auth_type.api_key,
+            transactionid: item.request.connector_transaction_id.clone(),
+            amount: utils::to_currency_base_unit_asf64(
+                item.request.refund_amount,
+                item.request.currency,
+            )?,
+        })
+    }
+}
+
+impl TryFrom<types::RefundsResponseRouterData<api::Execute, StandardResponse>>
+    for types::RefundsRouterData<api::Execute>
+{
+    type Error = Error;
+    fn try_from(
+        item: types::RefundsResponseRouterData<api::Execute, StandardResponse>,
+    ) -> Result<Self, Self::Error> {
+        let refund_status = enums::RefundStatus::from(item.response.response);
+        Ok(Self {
+            response: Ok(types::RefundsResponseData {
+                connector_refund_id: item.response.transactionid,
+                refund_status,
+            }),
+            ..item.data
+        })
+    }
+}
+
+impl TryFrom<types::RefundsResponseRouterData<api::Capture, StandardResponse>>
+    for types::RefundsRouterData<api::Capture>
+{
+    type Error = Error;
+    fn try_from(
+        item: types::RefundsResponseRouterData<api::Capture, StandardResponse>,
+    ) -> Result<Self, Self::Error> {
+        let refund_status = enums::RefundStatus::from(item.response.response);
+        Ok(Self {
+            response: Ok(types::RefundsResponseData {
+                connector_refund_id: item.response.transactionid,
+                refund_status,
+            }),
+            ..item.data
+        })
+    }
+}
+
+impl From<Response> for enums::RefundStatus {
+    fn from(item: Response) -> Self {
+        match item {
+            Response::Approved => Self::Pending,
+            Response::Declined | Response::Error => Self::Failure,
+        }
+    }
+}
+
+impl TryFrom<&types::RefundSyncRouterData> for NmiSyncRequest {
+    type Error = Error;
+    fn try_from(item: &types::RefundSyncRouterData) -> Result<Self, Self::Error> {
+        let auth = NmiAuthType::try_from(&item.connector_auth_type)?;
+        let transaction_id = item
+            .request
+            .connector_refund_id
+            .clone()
+            .ok_or(errors::ConnectorError::MissingConnectorRefundID)?;
+
+        Ok(Self {
+            security_key: auth.api_key,
+            transaction_id,
+        })
+    }
+}
+
+impl TryFrom<types::RefundsResponseRouterData<api::RSync, types::Response>>
+    for types::RefundsRouterData<api::RSync>
+{
+    type Error = Error;
+    fn try_from(
+        item: types::RefundsResponseRouterData<api::RSync, types::Response>,
+    ) -> Result<Self, Self::Error> {
+        let response = SyncResponse::try_from(item.response.response.to_vec())?;
+        let refund_status =
+            enums::RefundStatus::from(NmiStatus::from(response.transaction.condition));
+        Ok(Self {
+            response: Ok(types::RefundsResponseData {
+                connector_refund_id: response.transaction.transaction_id,
+                refund_status,
+            }),
+            ..item.data
+        })
+    }
+}
+
+impl From<NmiStatus> for enums::RefundStatus {
+    fn from(item: NmiStatus) -> Self {
+        match item {
+            NmiStatus::Abandoned
+            | NmiStatus::Cancelled
+            | NmiStatus::Failed
+            | NmiStatus::Unknown => Self::Failure,
+            NmiStatus::Pendingsettlement | NmiStatus::Pending | NmiStatus::InProgress => {
+                Self::Pending
+            }
+            NmiStatus::Complete => Self::Success,
+        }
+    }
+}
+
+impl From<String> for NmiStatus {
+    fn from(value: String) -> Self {
+        match value.as_str() {
+            "abandoned" => Self::Abandoned,
+            "canceled" => Self::Cancelled,
+            "in_progress" => Self::InProgress,
+            "pendingsettlement" => Self::Pendingsettlement,
+            "complete" => Self::Complete,
+            "failed" => Self::Failed,
+            "unknown" => Self::Unknown,
+            // Other than above values only pending is possible, since value is a string handling this as default
+            _ => Self::Pending,
+        }
+    }
+}
+
+#[derive(Debug, Deserialize)]
+pub struct SyncTransactionResponse {
+    #[serde(rename = "transaction_id")]
+    transaction_id: String,
+    #[serde(rename = "condition")]
+    condition: String,
+}
+
+#[derive(Debug, Deserialize)]
+struct SyncResponse {
+    #[serde(rename = "transaction")]
+    transaction: SyncTransactionResponse,
+}
diff --git a/crates/router/src/core/payments/flows.rs b/crates/router/src/core/payments/flows.rs
index 278a1dd24e2..edf109c0ee0 100644
--- a/crates/router/src/core/payments/flows.rs
+++ b/crates/router/src/core/payments/flows.rs
@@ -118,6 +118,7 @@ default_imp_for_complete_authorize!(
     connector::Klarna,
     connector::Multisafepay,
     connector::Nexinets,
+    connector::Nmi,
     connector::Opennode,
     connector::Payeezy,
     connector::Payu,
@@ -166,6 +167,7 @@ default_imp_for_create_customer!(
     connector::Mollie,
     connector::Multisafepay,
     connector::Nexinets,
+    connector::Nmi,
     connector::Nuvei,
     connector::Opennode,
     connector::Payeezy,
@@ -213,6 +215,7 @@ default_imp_for_connector_redirect_response!(
     connector::Klarna,
     connector::Multisafepay,
     connector::Nexinets,
+    connector::Nmi,
     connector::Opennode,
     connector::Payeezy,
     connector::Payu,
@@ -252,6 +255,7 @@ default_imp_for_connector_request_id!(
     connector::Klarna,
     connector::Mollie,
     connector::Multisafepay,
+    connector::Nmi,
     connector::Nuvei,
     connector::Opennode,
     connector::Payeezy,
@@ -303,6 +307,7 @@ default_imp_for_accept_dispute!(
     connector::Mollie,
     connector::Multisafepay,
     connector::Nexinets,
+    connector::Nmi,
     connector::Nuvei,
     connector::Payeezy,
     connector::Paypal,
@@ -363,6 +368,7 @@ default_imp_for_file_upload!(
     connector::Mollie,
     connector::Multisafepay,
     connector::Nexinets,
+    connector::Nmi,
     connector::Nuvei,
     connector::Payeezy,
     connector::Paypal,
@@ -413,6 +419,7 @@ default_imp_for_submit_evidence!(
     connector::Mollie,
     connector::Multisafepay,
     connector::Nexinets,
+    connector::Nmi,
     connector::Nuvei,
     connector::Payeezy,
     connector::Paypal,
@@ -463,6 +470,7 @@ default_imp_for_defend_dispute!(
     connector::Mollie,
     connector::Multisafepay,
     connector::Nexinets,
+    connector::Nmi,
     connector::Nuvei,
     connector::Payeezy,
     connector::Paypal,
diff --git a/crates/router/src/types.rs b/crates/router/src/types.rs
index 93843f326f2..c462b55912c 100644
--- a/crates/router/src/types.rs
+++ b/crates/router/src/types.rs
@@ -100,7 +100,6 @@ pub type PaymentsSessionType =
     dyn services::ConnectorIntegration<api::Session, PaymentsSessionData, PaymentsResponseData>;
 pub type PaymentsVoidType =
     dyn services::ConnectorIntegration<api::Void, PaymentsCancelData, PaymentsResponseData>;
-
 pub type TokenizationType = dyn services::ConnectorIntegration<
     api::PaymentMethodToken,
     PaymentMethodTokenizationData,
diff --git a/crates/router/src/types/api.rs b/crates/router/src/types/api.rs
index 7af5f624299..541175c5a26 100644
--- a/crates/router/src/types/api.rs
+++ b/crates/router/src/types/api.rs
@@ -217,6 +217,7 @@ impl ConnectorData {
             "iatapay" => Ok(Box::new(&connector::Iatapay)),
             "klarna" => Ok(Box::new(&connector::Klarna)),
             "mollie" => Ok(Box::new(&connector::Mollie)),
+            "nmi" => Ok(Box::new(&connector::Nmi)),
             "nuvei" => Ok(Box::new(&connector::Nuvei)),
             "opennode" => Ok(Box::new(&connector::Opennode)),
             // "payeezy" => Ok(Box::new(&connector::Payeezy)), As psync and rsync are not supported by this connector, it is added as template code for future usage
diff --git a/crates/router/tests/connectors/connector_auth.rs b/crates/router/tests/connectors/connector_auth.rs
index 0613f1a84a7..fbd292a9c22 100644
--- a/crates/router/tests/connectors/connector_auth.rs
+++ b/crates/router/tests/connectors/connector_auth.rs
@@ -23,7 +23,8 @@ pub(crate) struct ConnectorAuthentication {
     pub iatapay: Option<SignatureKey>,
     pub mollie: Option<HeaderKey>,
     pub multisafepay: Option<HeaderKey>,
-    pub nexinets: Option<BodyKey>,
+    pub nexinets: Option<HeaderKey>,
+    pub nmi: Option<HeaderKey>,
     pub nuvei: Option<SignatureKey>,
     pub opennode: Option<HeaderKey>,
     pub payeezy: Option<SignatureKey>,
@@ -41,6 +42,8 @@ pub(crate) struct ConnectorAuthentication {
 impl ConnectorAuthentication {
     #[allow(clippy::expect_used)]
     pub(crate) fn new() -> Self {
+        // Do `export CONNECTOR_AUTH_FILE_PATH="/hyperswitch/crates/router/tests/connectors/sample_auth.toml"`
+        // before running tests
         let path = env::var("CONNECTOR_AUTH_FILE_PATH")
             .expect("connector authentication file path not set");
         toml::from_str(
diff --git a/crates/router/tests/connectors/main.rs b/crates/router/tests/connectors/main.rs
index 766a67029bb..0f2a58e3d63 100644
--- a/crates/router/tests/connectors/main.rs
+++ b/crates/router/tests/connectors/main.rs
@@ -26,6 +26,7 @@ mod iatapay;
 mod mollie;
 mod multisafepay;
 mod nexinets;
+mod nmi;
 mod nuvei;
 mod nuvei_ui;
 mod opennode;
diff --git a/crates/router/tests/connectors/nmi.rs b/crates/router/tests/connectors/nmi.rs
new file mode 100644
index 00000000000..0727ef8da3e
--- /dev/null
+++ b/crates/router/tests/connectors/nmi.rs
@@ -0,0 +1,692 @@
+use std::{str::FromStr, time::Duration};
+
+use router::types::{self, api, storage::enums};
+
+use crate::{
+    connector_auth,
+    utils::{self, ConnectorActions},
+};
+
+struct NmiTest;
+impl ConnectorActions for NmiTest {}
+impl utils::Connector for NmiTest {
+    fn get_data(&self) -> types::api::ConnectorData {
+        use router::connector::Nmi;
+        types::api::ConnectorData {
+            connector: Box::new(&Nmi),
+            connector_name: types::Connector::Nmi,
+            get_token: types::api::GetToken::Connector,
+        }
+    }
+
+    fn get_auth_token(&self) -> types::ConnectorAuthType {
+        types::ConnectorAuthType::from(
+            connector_auth::ConnectorAuthentication::new()
+                .nmi
+                .expect("Missing connector authentication configuration"),
+        )
+    }
+
+    fn get_name(&self) -> String {
+        "nmi".to_string()
+    }
+}
+
+static CONNECTOR: NmiTest = NmiTest {};
+
+fn get_payment_authorize_data() -> Option<types::PaymentsAuthorizeData> {
+    Some(types::PaymentsAuthorizeData {
+        payment_method_data: types::api::PaymentMethodData::Card(api::Card {
+            card_number: cards::CardNumber::from_str("4111111111111111").unwrap(),
+            ..utils::CCardType::default().0
+        }),
+        amount: 2023,
+        ..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_authorize_data(), None)
+        .await
+        .expect("Authorize payment response");
+    let transaction_id = utils::get_connector_transaction_id(response.response).unwrap();
+    let sync_response = CONNECTOR
+        .psync_retry_till_status_matches(
+            enums::AttemptStatus::Authorized,
+            Some(types::PaymentsSyncData {
+                connector_transaction_id: router::types::ResponseId::ConnectorTransactionId(
+                    transaction_id.clone(),
+                ),
+                capture_method: Some(types::storage::enums::CaptureMethod::Manual),
+                ..Default::default()
+            }),
+            None,
+        )
+        .await
+        .unwrap();
+    // 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);
+}
+
+// Captures a payment using the manual capture flow (Non 3DS).
+#[actix_web::test]
+async fn should_capture_authorized_payment() {
+    let response = CONNECTOR
+        .authorize_payment(get_payment_authorize_data(), None)
+        .await
+        .unwrap();
+    assert_eq!(response.status, enums::AttemptStatus::Authorizing);
+    let transaction_id = utils::get_connector_transaction_id(response.response.to_owned()).unwrap();
+    let sync_response = CONNECTOR
+        .psync_retry_till_status_matches(
+            enums::AttemptStatus::Authorized,
+            Some(types::PaymentsSyncData {
+                connector_transaction_id: router::types::ResponseId::ConnectorTransactionId(
+                    transaction_id.clone(),
+                ),
+                capture_method: Some(types::storage::enums::CaptureMethod::Manual),
+                ..Default::default()
+            }),
+            None,
+        )
+        .await
+        .unwrap();
+    assert_eq!(sync_response.status, enums::AttemptStatus::Authorized);
+    let capture_response = CONNECTOR
+        .capture_payment(transaction_id.clone(), None, None)
+        .await
+        .unwrap();
+    assert_eq!(
+        capture_response.status,
+        enums::AttemptStatus::CaptureInitiated
+    );
+    let sync_response = CONNECTOR
+        .psync_retry_till_status_matches(
+            enums::AttemptStatus::Pending,
+            Some(types::PaymentsSyncData {
+                connector_transaction_id: router::types::ResponseId::ConnectorTransactionId(
+                    transaction_id.clone(),
+                ),
+                capture_method: Some(types::storage::enums::CaptureMethod::Manual),
+                ..Default::default()
+            }),
+            None,
+        )
+        .await
+        .unwrap();
+    assert_eq!(sync_response.status, enums::AttemptStatus::Pending);
+}
+
+// Partially captures a payment using the manual capture flow (Non 3DS).
+#[actix_web::test]
+async fn should_partially_capture_authorized_payment() {
+    let response = CONNECTOR
+        .authorize_payment(get_payment_authorize_data(), None)
+        .await
+        .unwrap();
+    assert_eq!(response.status, enums::AttemptStatus::Authorizing);
+    let transaction_id = utils::get_connector_transaction_id(response.response.to_owned()).unwrap();
+    let sync_response = CONNECTOR
+        .psync_retry_till_status_matches(
+            enums::AttemptStatus::Authorized,
+            Some(types::PaymentsSyncData {
+                connector_transaction_id: router::types::ResponseId::ConnectorTransactionId(
+                    transaction_id.clone(),
+                ),
+                capture_method: Some(types::storage::enums::CaptureMethod::Manual),
+                ..Default::default()
+            }),
+            None,
+        )
+        .await
+        .unwrap();
+    assert_eq!(sync_response.status, enums::AttemptStatus::Authorized);
+    let capture_response = CONNECTOR
+        .capture_payment(
+            transaction_id.clone(),
+            Some(types::PaymentsCaptureData {
+                amount_to_capture: 1000,
+                ..utils::PaymentCaptureType::default().0
+            }),
+            None,
+        )
+        .await
+        .unwrap();
+    assert_eq!(
+        capture_response.status,
+        enums::AttemptStatus::CaptureInitiated
+    );
+
+    let sync_response = CONNECTOR
+        .psync_retry_till_status_matches(
+            enums::AttemptStatus::Pending,
+            Some(types::PaymentsSyncData {
+                connector_transaction_id: router::types::ResponseId::ConnectorTransactionId(
+                    transaction_id.clone(),
+                ),
+                capture_method: Some(types::storage::enums::CaptureMethod::Manual),
+                ..Default::default()
+            }),
+            None,
+        )
+        .await
+        .unwrap();
+    assert_eq!(sync_response.status, enums::AttemptStatus::Pending);
+}
+
+// Voids a payment using the manual capture flow (Non 3DS).
+#[actix_web::test]
+async fn should_void_authorized_payment() {
+    let response = CONNECTOR
+        .authorize_payment(get_payment_authorize_data(), None)
+        .await
+        .unwrap();
+    assert_eq!(response.status, enums::AttemptStatus::Authorizing);
+    let transaction_id = utils::get_connector_transaction_id(response.response.to_owned()).unwrap();
+
+    let sync_response = CONNECTOR
+        .psync_retry_till_status_matches(
+            enums::AttemptStatus::Authorized,
+            Some(types::PaymentsSyncData {
+                connector_transaction_id: router::types::ResponseId::ConnectorTransactionId(
+                    transaction_id.clone(),
+                ),
+                capture_method: Some(types::storage::enums::CaptureMethod::Manual),
+                ..Default::default()
+            }),
+            None,
+        )
+        .await
+        .unwrap();
+    assert_eq!(sync_response.status, enums::AttemptStatus::Authorized);
+
+    let void_response = CONNECTOR
+        .void_payment(
+            transaction_id.clone(),
+            Some(types::PaymentsCancelData {
+                connector_transaction_id: String::from(""),
+                cancellation_reason: Some("user_cancel".to_string()),
+                ..Default::default()
+            }),
+            None,
+        )
+        .await
+        .unwrap();
+    assert_eq!(void_response.status, enums::AttemptStatus::VoidInitiated);
+    let sync_response = CONNECTOR
+        .psync_retry_till_status_matches(
+            enums::AttemptStatus::Voided,
+            Some(types::PaymentsSyncData {
+                connector_transaction_id: router::types::ResponseId::ConnectorTransactionId(
+                    transaction_id.clone(),
+                ),
+                capture_method: Some(types::storage::enums::CaptureMethod::Manual),
+                ..Default::default()
+            }),
+            None,
+        )
+        .await
+        .unwrap();
+    assert_eq!(sync_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 response = CONNECTOR
+        .authorize_payment(get_payment_authorize_data(), None)
+        .await
+        .unwrap();
+    assert_eq!(response.status, enums::AttemptStatus::Authorizing);
+    let transaction_id = utils::get_connector_transaction_id(response.response.to_owned()).unwrap();
+
+    let sync_response = CONNECTOR
+        .psync_retry_till_status_matches(
+            enums::AttemptStatus::Authorized,
+            Some(types::PaymentsSyncData {
+                connector_transaction_id: router::types::ResponseId::ConnectorTransactionId(
+                    transaction_id.clone(),
+                ),
+                capture_method: Some(types::storage::enums::CaptureMethod::Manual),
+                ..Default::default()
+            }),
+            None,
+        )
+        .await
+        .unwrap();
+    assert_eq!(sync_response.status, enums::AttemptStatus::Authorized);
+    let capture_response = CONNECTOR
+        .capture_payment(transaction_id.clone(), None, None)
+        .await
+        .unwrap();
+    assert_eq!(
+        capture_response.status,
+        enums::AttemptStatus::CaptureInitiated
+    );
+    let sync_response = CONNECTOR
+        .psync_retry_till_status_matches(
+            enums::AttemptStatus::Pending,
+            Some(types::PaymentsSyncData {
+                connector_transaction_id: router::types::ResponseId::ConnectorTransactionId(
+                    transaction_id.clone(),
+                ),
+                capture_method: Some(types::storage::enums::CaptureMethod::Manual),
+                ..Default::default()
+            }),
+            None,
+        )
+        .await
+        .unwrap();
+    assert_eq!(sync_response.status, enums::AttemptStatus::Pending);
+
+    let refund_response = CONNECTOR
+        .refund_payment(transaction_id.clone(), None, None)
+        .await
+        .unwrap();
+    assert_eq!(refund_response.status, enums::AttemptStatus::Pending);
+    let sync_response = CONNECTOR
+        .rsync_retry_till_status_matches(
+            enums::RefundStatus::Pending,
+            refund_response.response.unwrap().connector_refund_id,
+            None,
+            None,
+        )
+        .await
+        .unwrap();
+    assert_eq!(
+        sync_response.response.unwrap().refund_status,
+        enums::RefundStatus::Pending
+    );
+}
+
+// Partially refunds a payment using the manual capture flow (Non 3DS).
+#[actix_web::test]
+async fn should_partially_refund_manually_captured_payment() {
+    let response = CONNECTOR
+        .authorize_payment(get_payment_authorize_data(), None)
+        .await
+        .unwrap();
+    assert_eq!(response.status, enums::AttemptStatus::Authorizing);
+    let transaction_id = utils::get_connector_transaction_id(response.response.to_owned()).unwrap();
+
+    let sync_response = CONNECTOR
+        .psync_retry_till_status_matches(
+            enums::AttemptStatus::Authorized,
+            Some(types::PaymentsSyncData {
+                connector_transaction_id: router::types::ResponseId::ConnectorTransactionId(
+                    transaction_id.clone(),
+                ),
+                capture_method: Some(types::storage::enums::CaptureMethod::Manual),
+                ..Default::default()
+            }),
+            None,
+        )
+        .await
+        .unwrap();
+    assert_eq!(sync_response.status, enums::AttemptStatus::Authorized);
+    let capture_response = CONNECTOR
+        .capture_payment(
+            transaction_id.clone(),
+            Some(types::PaymentsCaptureData {
+                amount_to_capture: 2023,
+                ..utils::PaymentCaptureType::default().0
+            }),
+            None,
+        )
+        .await
+        .unwrap();
+    assert_eq!(
+        capture_response.status,
+        enums::AttemptStatus::CaptureInitiated
+    );
+    let sync_response = CONNECTOR
+        .psync_retry_till_status_matches(
+            enums::AttemptStatus::Pending,
+            Some(types::PaymentsSyncData {
+                connector_transaction_id: router::types::ResponseId::ConnectorTransactionId(
+                    transaction_id.clone(),
+                ),
+                capture_method: Some(types::storage::enums::CaptureMethod::Manual),
+                ..Default::default()
+            }),
+            None,
+        )
+        .await
+        .unwrap();
+    assert_eq!(sync_response.status, enums::AttemptStatus::Pending);
+
+    let refund_response = CONNECTOR
+        .refund_payment(
+            transaction_id.clone(),
+            Some(types::RefundsData {
+                refund_amount: 1023,
+                ..utils::PaymentRefundType::default().0
+            }),
+            None,
+        )
+        .await
+        .unwrap();
+    assert_eq!(refund_response.status, enums::AttemptStatus::Pending);
+    let sync_response = CONNECTOR
+        .rsync_retry_till_status_matches(
+            enums::RefundStatus::Pending,
+            refund_response.response.unwrap().connector_refund_id,
+            None,
+            None,
+        )
+        .await
+        .unwrap();
+    assert_eq!(
+        sync_response.response.unwrap().refund_status,
+        enums::RefundStatus::Pending
+    );
+}
+
+// Creates a payment using the automatic capture flow (Non 3DS).
+#[actix_web::test]
+async fn should_make_payment() {
+    let response = CONNECTOR
+        .make_payment(get_payment_authorize_data(), None)
+        .await
+        .unwrap();
+    assert_eq!(response.status, enums::AttemptStatus::CaptureInitiated);
+    let transaction_id = utils::get_connector_transaction_id(response.response.to_owned()).unwrap();
+
+    let sync_response = CONNECTOR
+        .psync_retry_till_status_matches(
+            enums::AttemptStatus::Pending,
+            Some(types::PaymentsSyncData {
+                connector_transaction_id: router::types::ResponseId::ConnectorTransactionId(
+                    transaction_id.clone(),
+                ),
+                capture_method: Some(types::storage::enums::CaptureMethod::Automatic),
+                ..Default::default()
+            }),
+            None,
+        )
+        .await
+        .unwrap();
+    assert_eq!(sync_response.status, enums::AttemptStatus::Pending);
+}
+
+// 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(get_payment_authorize_data(), None)
+        .await
+        .unwrap();
+    assert_eq!(response.status, enums::AttemptStatus::CaptureInitiated);
+    let transaction_id = utils::get_connector_transaction_id(response.response.to_owned()).unwrap();
+
+    let sync_response = CONNECTOR
+        .psync_retry_till_status_matches(
+            enums::AttemptStatus::Pending,
+            Some(types::PaymentsSyncData {
+                connector_transaction_id: router::types::ResponseId::ConnectorTransactionId(
+                    transaction_id.clone(),
+                ),
+                capture_method: Some(types::storage::enums::CaptureMethod::Automatic),
+                ..Default::default()
+            }),
+            None,
+        )
+        .await
+        .unwrap();
+    assert_eq!(sync_response.status, enums::AttemptStatus::Pending);
+
+    let refund_response = CONNECTOR
+        .refund_payment(transaction_id.clone(), None, None)
+        .await
+        .unwrap();
+    assert_eq!(refund_response.status, enums::AttemptStatus::Pending);
+    let sync_response = CONNECTOR
+        .rsync_retry_till_status_matches(
+            enums::RefundStatus::Pending,
+            refund_response.response.unwrap().connector_refund_id,
+            None,
+            None,
+        )
+        .await
+        .unwrap();
+    assert_eq!(
+        sync_response.response.unwrap().refund_status,
+        enums::RefundStatus::Pending
+    );
+}
+
+// Partially refunds a payment using the automatic capture flow (Non 3DS).
+#[actix_web::test]
+async fn should_partially_refund_succeeded_payment() {
+    let response = CONNECTOR
+        .make_payment(get_payment_authorize_data(), None)
+        .await
+        .unwrap();
+    assert_eq!(response.status, enums::AttemptStatus::CaptureInitiated);
+    let transaction_id = utils::get_connector_transaction_id(response.response.to_owned()).unwrap();
+
+    let sync_response = CONNECTOR
+        .psync_retry_till_status_matches(
+            enums::AttemptStatus::Pending,
+            Some(types::PaymentsSyncData {
+                connector_transaction_id: router::types::ResponseId::ConnectorTransactionId(
+                    transaction_id.clone(),
+                ),
+                capture_method: Some(types::storage::enums::CaptureMethod::Automatic),
+                ..Default::default()
+            }),
+            None,
+        )
+        .await
+        .unwrap();
+    assert_eq!(sync_response.status, enums::AttemptStatus::Pending);
+
+    let refund_response = CONNECTOR
+        .refund_payment(
+            transaction_id.clone(),
+            Some(types::RefundsData {
+                refund_amount: 1000,
+                ..utils::PaymentRefundType::default().0
+            }),
+            None,
+        )
+        .await
+        .unwrap();
+    assert_eq!(refund_response.status, enums::AttemptStatus::Pending);
+    let sync_response = CONNECTOR
+        .rsync_retry_till_status_matches(
+            enums::RefundStatus::Pending,
+            refund_response.response.unwrap().connector_refund_id,
+            None,
+            None,
+        )
+        .await
+        .unwrap();
+    assert_eq!(
+        sync_response.response.unwrap().refund_status,
+        enums::RefundStatus::Pending
+    );
+}
+
+// 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 response = CONNECTOR
+        .make_payment(get_payment_authorize_data(), None)
+        .await
+        .unwrap();
+    assert_eq!(response.status, enums::AttemptStatus::CaptureInitiated);
+    let transaction_id = utils::get_connector_transaction_id(response.response.to_owned()).unwrap();
+
+    let sync_response = CONNECTOR
+        .psync_retry_till_status_matches(
+            enums::AttemptStatus::Pending,
+            Some(types::PaymentsSyncData {
+                connector_transaction_id: router::types::ResponseId::ConnectorTransactionId(
+                    transaction_id.clone(),
+                ),
+                capture_method: Some(types::storage::enums::CaptureMethod::Automatic),
+                ..Default::default()
+            }),
+            None,
+        )
+        .await
+        .unwrap();
+    assert_eq!(sync_response.status, enums::AttemptStatus::Pending);
+
+    //try refund for previous payment
+    let transaction_id = utils::get_connector_transaction_id(response.response).unwrap();
+    for _x in 0..2 {
+        tokio::time::sleep(Duration::from_secs(5)).await; // to avoid 404 error
+        let refund_response = CONNECTOR
+            .refund_payment(
+                transaction_id.clone(),
+                Some(types::RefundsData {
+                    refund_amount: 50,
+                    ..utils::PaymentRefundType::default().0
+                }),
+                None,
+            )
+            .await
+            .unwrap();
+        let sync_response = CONNECTOR
+            .rsync_retry_till_status_matches(
+                enums::RefundStatus::Pending,
+                refund_response.response.unwrap().connector_refund_id,
+                None,
+                None,
+            )
+            .await
+            .unwrap();
+        assert_eq!(
+            sync_response.response.unwrap().refund_status,
+            enums::RefundStatus::Pending,
+        );
+    }
+}
+
+// Creates a payment with incorrect CVC.
+#[ignore = "Connector returns SUCCESS status in case of invalid CVC"]
+#[actix_web::test]
+async fn should_fail_payment_for_incorrect_cvc() {}
+
+// Creates a payment with incorrect expiry month.
+#[ignore = "Connector returns SUCCESS status in case of expired month."]
+#[actix_web::test]
+async fn should_fail_payment_for_invalid_exp_month() {}
+
+// Creates a payment with incorrect expiry year.
+#[ignore = "Connector returns SUCCESS status in case of expired year."]
+#[actix_web::test]
+async fn should_fail_payment_for_incorrect_expiry_year() {}
+
+// Voids a payment using automatic capture flow (Non 3DS).
+#[actix_web::test]
+async fn should_fail_void_payment_for_auto_capture() {
+    let response = CONNECTOR
+        .make_payment(get_payment_authorize_data(), None)
+        .await
+        .unwrap();
+    assert_eq!(response.status, enums::AttemptStatus::CaptureInitiated);
+    let transaction_id = utils::get_connector_transaction_id(response.response.to_owned()).unwrap();
+
+    let sync_response = CONNECTOR
+        .psync_retry_till_status_matches(
+            enums::AttemptStatus::Pending,
+            Some(types::PaymentsSyncData {
+                connector_transaction_id: router::types::ResponseId::ConnectorTransactionId(
+                    transaction_id.clone(),
+                ),
+                capture_method: Some(types::storage::enums::CaptureMethod::Automatic),
+                ..Default::default()
+            }),
+            None,
+        )
+        .await
+        .unwrap();
+    assert_eq!(sync_response.status, enums::AttemptStatus::Pending);
+
+    let void_response = CONNECTOR
+        .void_payment(transaction_id.clone(), None, None)
+        .await
+        .unwrap();
+    assert_eq!(void_response.status, enums::AttemptStatus::VoidFailed);
+}
+
+// Captures a payment using invalid connector payment id.
+#[actix_web::test]
+async fn should_fail_capture_for_invalid_payment() {
+    let response = CONNECTOR
+        .authorize_payment(get_payment_authorize_data(), None)
+        .await
+        .unwrap();
+    assert_eq!(response.status, enums::AttemptStatus::Authorizing);
+    let transaction_id = utils::get_connector_transaction_id(response.response.to_owned()).unwrap();
+
+    let sync_response = CONNECTOR
+        .psync_retry_till_status_matches(
+            enums::AttemptStatus::Authorized,
+            Some(types::PaymentsSyncData {
+                connector_transaction_id: router::types::ResponseId::ConnectorTransactionId(
+                    transaction_id.clone(),
+                ),
+                capture_method: Some(types::storage::enums::CaptureMethod::Manual),
+                ..Default::default()
+            }),
+            None,
+        )
+        .await
+        .unwrap();
+    assert_eq!(sync_response.status, enums::AttemptStatus::Authorized);
+    let capture_response = CONNECTOR
+        .capture_payment("7899353591".to_string(), None, None)
+        .await
+        .unwrap();
+    assert_eq!(capture_response.status, enums::AttemptStatus::CaptureFailed);
+}
+
+// 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 response = CONNECTOR
+        .make_payment(get_payment_authorize_data(), None)
+        .await
+        .unwrap();
+    assert_eq!(response.status, enums::AttemptStatus::CaptureInitiated);
+    let transaction_id = utils::get_connector_transaction_id(response.response.to_owned()).unwrap();
+
+    let sync_response = CONNECTOR
+        .psync_retry_till_status_matches(
+            enums::AttemptStatus::Pending,
+            Some(types::PaymentsSyncData {
+                connector_transaction_id: router::types::ResponseId::ConnectorTransactionId(
+                    transaction_id.clone(),
+                ),
+                capture_method: Some(types::storage::enums::CaptureMethod::Automatic),
+                ..Default::default()
+            }),
+            None,
+        )
+        .await
+        .unwrap();
+    assert_eq!(sync_response.status, enums::AttemptStatus::Pending);
+    let refund_response = CONNECTOR
+        .refund_payment(
+            transaction_id,
+            Some(types::RefundsData {
+                refund_amount: 3024,
+                ..utils::PaymentRefundType::default().0
+            }),
+            None,
+        )
+        .await
+        .unwrap();
+    assert_eq!(
+        refund_response.response.unwrap().refund_status,
+        enums::RefundStatus::Failure
+    );
+}
diff --git a/crates/router/tests/connectors/sample_auth.toml b/crates/router/tests/connectors/sample_auth.toml
index 262ab819dda..9fc6c6f13a3 100644
--- a/crates/router/tests/connectors/sample_auth.toml
+++ b/crates/router/tests/connectors/sample_auth.toml
@@ -63,6 +63,9 @@ api_secret = "secret"
 api_key = "api_key"
 key1= "key1"
 
+[nmi]
+api_key = "NMI API Key"
+
 [nuvei]
 api_key = "api_key"
 key1 = "key1"
diff --git a/crates/router/tests/connectors/utils.rs b/crates/router/tests/connectors/utils.rs
index fe9c7163ad4..a45038047bf 100644
--- a/crates/router/tests/connectors/utils.rs
+++ b/crates/router/tests/connectors/utils.rs
@@ -37,6 +37,8 @@ pub struct PaymentInfo {
 
 #[async_trait]
 pub trait ConnectorActions: Connector {
+    /// For initiating payments when `CaptureMethod` is set to `Manual`
+    /// This doesn't complete the transaction, `PaymentsCapture` needs to be done manually
     async fn authorize_payment(
         &self,
         payment_data: Option<types::PaymentsAuthorizeData>,
@@ -62,6 +64,8 @@ pub trait ConnectorActions: Connector {
         call_connector(request, integration).await
     }
 
+    /// For initiating payments when `CaptureMethod` is set to `Automatic`
+    /// This does complete the transaction without user intervention to Capture the payment
     async fn make_payment(
         &self,
         payment_data: Option<types::PaymentsAuthorizeData>,
@@ -197,14 +201,14 @@ pub trait ConnectorActions: Connector {
     async fn refund_payment(
         &self,
         transaction_id: String,
-        payment_data: Option<types::RefundsData>,
+        refund_data: Option<types::RefundsData>,
         payment_info: Option<PaymentInfo>,
     ) -> Result<types::RefundExecuteRouterData, Report<ConnectorError>> {
         let integration = self.get_data().connector.get_connector_integration();
         let request = self.generate_data(
             types::RefundsData {
                 connector_transaction_id: transaction_id,
-                ..payment_data.unwrap_or(PaymentRefundType::default().0)
+                ..refund_data.unwrap_or(PaymentRefundType::default().0)
             },
             payment_info,
         );
diff --git a/loadtest/config/development.toml b/loadtest/config/development.toml
index 86571a26b50..1161aa8e318 100644
--- a/loadtest/config/development.toml
+++ b/loadtest/config/development.toml
@@ -78,6 +78,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/"
 nexinets.base_url = "https://apitest.payengine.de/v1"
+nmi.base_url = "https://secure.nmi.com/"
 nuvei.base_url = "https://ppp-test.nuvei.com/"
 opennode.base_url = "https://dev-api.opennode.com"
 payeezy.base_url = "https://api-cert.payeezy.com/"
@@ -115,6 +116,7 @@ cards = [
     "mollie",
     "multisafepay",
     "nexinets",
+    "nmi",
     "nuvei",
     "opennode",
     "payeezy",

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

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

## Description
<!-- Describe your changes in detail -->
Added support for NMI connector that supports the following payment flows:
- Verify
- Authorize
- PSync
- Capture
- Void
- Execute (Refunds)
- RSync

Also, apart from Cards, NMI also support payments via GooglePay and ApplePay.

### Additional Changes

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

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

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


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

If you don't have an issue, we'd recommend starting with one first so the PR
can focus on the implementation (unless its an obvious bug or documentation fix
that will have little conversation).
-->
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)?
-->
update-time: 15 April 2023, Saturday 21:04
<img width="610" alt="image" src="https://user-images.githubusercontent.com/69745008/232234442-e3791a88-3def-4e7d-b28a-fe613aaeb6f6.png">
<img width="1195" alt="Screen Shot 2023-04-26 at 2 32 11 AM" src="https://user-images.githubusercontent.com/20727986/234403252-20d5ac23-995a-4b9d-b795-c4d4581b00e8.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
- [ ] I added unit tests for my changes where possible
- [ ] I added a [CHANGELOG](/CHANGELOG.md) entry if applicable
