Bug: Add Fiserv for card payments




diff --git a/config/Development.toml b/config/Development.toml
index 06b4d544c0a..fa61e28810c 100644
--- a/config/Development.toml
+++ b/config/Development.toml
@@ -43,7 +43,7 @@ locker_decryption_key2 = ""
 
 [connectors.supported]
 wallets = ["klarna","braintree","applepay"]
-cards = ["stripe","adyen","authorizedotnet","checkout","braintree","aci","shift4","cybersource", "worldpay", "globalpay"]
+cards = ["stripe","adyen","authorizedotnet","checkout","braintree","aci","shift4","cybersource", "worldpay", "globalpay", "fiserv"]
 
 [refund]
 max_attempts = 10
@@ -82,6 +82,9 @@ base_url = "https://apitest.cybersource.com/"
 [connectors.shift4]
 base_url = "https://api.shift4.com/"
 
+[connectors.fiserv]
+base_url = "https://cert.api.fiservapps.com/"
+
 [connectors.worldpay]
 base_url = "http://localhost:9090/"
 
diff --git a/config/config.example.toml b/config/config.example.toml
index cabc652c91e..6bedd98ab23 100644
--- a/config/config.example.toml
+++ b/config/config.example.toml
@@ -133,6 +133,9 @@ base_url = "https://apitest.cybersource.com/"
 [connectors.shift4]
 base_url = "https://api.shift4.com/"
 
+[connectors.fiserv]
+base_url = "https://cert.api.fiservapps.com/"
+
 [connectors.worldpay]
 base_url = "https://try.access.worldpay.com/"
 
diff --git a/config/docker_compose.toml b/config/docker_compose.toml
index 0c026939849..d38380b73c8 100644
--- a/config/docker_compose.toml
+++ b/config/docker_compose.toml
@@ -85,6 +85,9 @@ base_url = "https://apitest.cybersource.com/"
 [connectors.shift4]
 base_url = "https://api.shift4.com/"
 
+[connectors.fiserv]
+base_url = "https://cert.api.fiservapps.com/"
+
 [connectors.worldpay]
 base_url = "https://try.access.worldpay.com/"
 
@@ -93,4 +96,4 @@ base_url = "https://apis.sandbox.globalpay.com/ucp/"
 
 [connectors.supported]
 wallets = ["klarna", "braintree", "applepay"]
-cards = ["stripe", "adyen", "authorizedotnet", "checkout", "braintree", "shift4", "cybersource", "worldpay", "globalpay"]
+cards = ["stripe", "adyen", "authorizedotnet", "checkout", "braintree", "shift4", "cybersource", "worldpay", "globalpay", "fiserv"]
diff --git a/crates/api_models/src/enums.rs b/crates/api_models/src/enums.rs
index cdb2f70d1fe..83ead25c354 100644
--- a/crates/api_models/src/enums.rs
+++ b/crates/api_models/src/enums.rs
@@ -503,6 +503,7 @@ pub enum Connector {
     Cybersource,
     #[default]
     Dummy,
+    Fiserv,
     Globalpay,
     Klarna,
     Payu,
diff --git a/crates/common_utils/src/lib.rs b/crates/common_utils/src/lib.rs
index 8ba8aa195a5..fd730414808 100644
--- a/crates/common_utils/src/lib.rs
+++ b/crates/common_utils/src/lib.rs
@@ -27,6 +27,11 @@ pub mod date_time {
     pub fn convert_to_pdt(offset_time: OffsetDateTime) -> PrimitiveDateTime {
         PrimitiveDateTime::new(offset_time.date(), offset_time.time())
     }
+
+    /// Return the UNIX timestamp of the current date and time in UTC
+    pub fn now_unix_timestamp() -> i64 {
+        OffsetDateTime::now_utc().unix_timestamp()
+    }
 }
 
 /// Generate a nanoid with the given prefix and length
diff --git a/crates/router/src/configs/defaults.toml b/crates/router/src/configs/defaults.toml
index fe24060d3c6..5e447f2775d 100644
--- a/crates/router/src/configs/defaults.toml
+++ b/crates/router/src/configs/defaults.toml
@@ -63,4 +63,4 @@ max_read_count = 100
 
 [connectors.supported]
 wallets = ["klarna","braintree"]
-cards = ["stripe","adyen","authorizedotnet","checkout","braintree", "cybersource"]
+cards = ["stripe","adyen","authorizedotnet","checkout","braintree", "cybersource", "fiserv"]
diff --git a/crates/router/src/configs/settings.rs b/crates/router/src/configs/settings.rs
index 0707dfb1dbf..3aeb0fc0410 100644
--- a/crates/router/src/configs/settings.rs
+++ b/crates/router/src/configs/settings.rs
@@ -127,6 +127,7 @@ pub struct Connectors {
     pub braintree: ConnectorParams,
     pub checkout: ConnectorParams,
     pub cybersource: ConnectorParams,
+    pub fiserv: ConnectorParams,
     pub globalpay: ConnectorParams,
     pub klarna: ConnectorParams,
     pub payu: ConnectorParams,
diff --git a/crates/router/src/connector.rs b/crates/router/src/connector.rs
index a790601e5e4..2b9ef18559f 100644
--- a/crates/router/src/connector.rs
+++ b/crates/router/src/connector.rs
@@ -5,6 +5,7 @@ pub mod authorizedotnet;
 pub mod braintree;
 pub mod checkout;
 pub mod cybersource;
+pub mod fiserv;
 pub mod globalpay;
 pub mod klarna;
 pub mod payu;
@@ -15,6 +16,7 @@ pub mod worldpay;
 
 pub use self::{
     aci::Aci, adyen::Adyen, applepay::Applepay, authorizedotnet::Authorizedotnet,
-    braintree::Braintree, checkout::Checkout, cybersource::Cybersource, globalpay::Globalpay,
-    klarna::Klarna, payu::Payu, shift4::Shift4, stripe::Stripe, worldpay::Worldpay,
+    braintree::Braintree, checkout::Checkout, cybersource::Cybersource, fiserv::Fiserv,
+    globalpay::Globalpay, klarna::Klarna, payu::Payu, shift4::Shift4, stripe::Stripe,
+    worldpay::Worldpay,
 };
diff --git a/crates/router/src/connector/fiserv.rs b/crates/router/src/connector/fiserv.rs
new file mode 100644
index 00000000000..b4b78f84168
--- /dev/null
+++ b/crates/router/src/connector/fiserv.rs
@@ -0,0 +1,426 @@
+mod transformers;
+
+use std::fmt::Debug;
+
+use base64::Engine;
+use bytes::Bytes;
+use error_stack::ResultExt;
+use ring::hmac;
+use time::OffsetDateTime;
+use transformers as fiserv;
+use uuid::Uuid;
+
+use crate::{
+    configs::settings,
+    consts,
+    core::{
+        errors::{self, CustomResult},
+        payments,
+    },
+    headers, services,
+    types::{
+        self,
+        api::{self},
+    },
+    utils::{self, BytesExt},
+};
+
+#[derive(Debug, Clone)]
+pub struct Fiserv;
+
+impl Fiserv {
+    pub fn generate_authorization_signature(
+        &self,
+        auth: fiserv::FiservAuthType,
+        request_id: &str,
+        payload: &str,
+        timestamp: i128,
+    ) -> CustomResult<String, errors::ConnectorError> {
+        let fiserv::FiservAuthType {
+            api_key,
+            api_secret,
+            ..
+        } = auth;
+        let raw_signature = format!("{api_key}{request_id}{timestamp}{payload}");
+
+        let key = hmac::Key::new(hmac::HMAC_SHA256, api_secret.as_bytes());
+        let signature_value =
+            consts::BASE64_ENGINE.encode(hmac::sign(&key, raw_signature.as_bytes()).as_ref());
+        Ok(signature_value)
+    }
+}
+
+impl api::ConnectorCommon for Fiserv {
+    fn id(&self) -> &'static str {
+        "fiserv"
+    }
+
+    fn common_get_content_type(&self) -> &'static str {
+        "application/json"
+    }
+
+    fn base_url<'a>(&self, connectors: &'a settings::Connectors) -> &'a str {
+        connectors.fiserv.base_url.as_ref()
+    }
+}
+
+impl api::Payment for Fiserv {}
+
+impl api::PreVerify for Fiserv {}
+
+#[allow(dead_code)]
+impl
+    services::ConnectorIntegration<
+        api::Verify,
+        types::VerifyRequestData,
+        types::PaymentsResponseData,
+    > for Fiserv
+{
+}
+
+impl api::PaymentVoid for Fiserv {}
+
+#[allow(dead_code)]
+impl
+    services::ConnectorIntegration<
+        api::Void,
+        types::PaymentsCancelData,
+        types::PaymentsResponseData,
+    > for Fiserv
+{
+}
+
+impl api::PaymentSync for Fiserv {}
+
+#[allow(dead_code)]
+impl
+    services::ConnectorIntegration<api::PSync, types::PaymentsSyncData, types::PaymentsResponseData>
+    for Fiserv
+{
+}
+
+impl api::PaymentCapture for Fiserv {}
+impl
+    services::ConnectorIntegration<
+        api::Capture,
+        types::PaymentsCaptureData,
+        types::PaymentsResponseData,
+    > for Fiserv
+{
+    fn get_headers(
+        &self,
+        req: &types::PaymentsCaptureRouterData,
+        _connectors: &settings::Connectors,
+    ) -> CustomResult<Vec<(String, String)>, errors::ConnectorError> {
+        let timestamp = OffsetDateTime::now_utc().unix_timestamp_nanos() / 1_000_000;
+        let auth: fiserv::FiservAuthType =
+            fiserv::FiservAuthType::try_from(&req.connector_auth_type)?;
+        let api_key = auth.api_key.clone();
+
+        let fiserv_req = self
+            .get_request_body(req)?
+            .ok_or(errors::ConnectorError::RequestEncodingFailed)?;
+        let client_request_id = Uuid::new_v4().to_string();
+        let hmac = self
+            .generate_authorization_signature(auth, &client_request_id, &fiserv_req, timestamp)
+            .change_context(errors::ConnectorError::RequestEncodingFailed)?;
+        let headers = vec![
+            (
+                headers::CONTENT_TYPE.to_string(),
+                types::PaymentsAuthorizeType::get_content_type(self).to_string(),
+            ),
+            ("Client-Request-Id".to_string(), client_request_id),
+            ("Auth-Token-Type".to_string(), "HMAC".to_string()),
+            ("Api-Key".to_string(), api_key),
+            ("Timestamp".to_string(), timestamp.to_string()),
+            ("Authorization".to_string(), hmac),
+        ];
+        Ok(headers)
+    }
+
+    fn get_content_type(&self) -> &'static str {
+        "application/json"
+    }
+
+    fn get_request_body(
+        &self,
+        req: &types::PaymentsCaptureRouterData,
+    ) -> CustomResult<Option<String>, errors::ConnectorError> {
+        let fiserv_req = utils::Encode::<fiserv::FiservCaptureRequest>::convert_and_encode(req)
+            .change_context(errors::ConnectorError::RequestEncodingFailed)?;
+        Ok(Some(fiserv_req))
+    }
+
+    fn build_request(
+        &self,
+        req: &types::PaymentsCaptureRouterData,
+        connectors: &settings::Connectors,
+    ) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
+        let request = 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(),
+        );
+        Ok(request)
+    }
+
+    fn handle_response(
+        &self,
+        data: &types::PaymentsCaptureRouterData,
+        res: types::Response,
+    ) -> CustomResult<types::PaymentsCaptureRouterData, errors::ConnectorError> {
+        let response: fiserv::FiservPaymentsResponse = res
+            .response
+            .parse_struct("Fiserv Payment Response")
+            .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_url(
+        &self,
+        _req: &types::PaymentsCaptureRouterData,
+        connectors: &settings::Connectors,
+    ) -> CustomResult<String, errors::ConnectorError> {
+        Ok(format!(
+            "{}ch/payments/v1/charges",
+            connectors.fiserv.base_url
+        ))
+    }
+
+    fn get_error_response(
+        &self,
+        res: Bytes,
+    ) -> CustomResult<types::ErrorResponse, errors::ConnectorError> {
+        let response: fiserv::ErrorResponse = res
+            .parse_struct("Fiserv ErrorResponse")
+            .change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
+
+        let fiserv::ErrorResponse { error, details } = response;
+
+        let message = match (error, details) {
+            (Some(err), _) => err
+                .iter()
+                .map(|v| v.message.clone())
+                .collect::<Vec<String>>()
+                .join(""),
+            (None, Some(err_details)) => err_details
+                .iter()
+                .map(|v| v.message.clone())
+                .collect::<Vec<String>>()
+                .join(""),
+            (None, None) => consts::NO_ERROR_MESSAGE.to_string(),
+        };
+
+        Ok(types::ErrorResponse {
+            code: consts::NO_ERROR_CODE.to_string(),
+            message,
+            reason: None,
+        })
+    }
+}
+
+impl api::PaymentSession for Fiserv {}
+
+#[allow(dead_code)]
+impl
+    services::ConnectorIntegration<
+        api::Session,
+        types::PaymentsSessionData,
+        types::PaymentsResponseData,
+    > for Fiserv
+{
+}
+
+impl api::PaymentAuthorize for Fiserv {}
+
+impl
+    services::ConnectorIntegration<
+        api::Authorize,
+        types::PaymentsAuthorizeData,
+        types::PaymentsResponseData,
+    > for Fiserv
+{
+    fn get_headers(
+        &self,
+        req: &types::PaymentsAuthorizeRouterData,
+        _connectors: &settings::Connectors,
+    ) -> CustomResult<Vec<(String, String)>, errors::ConnectorError> {
+        let timestamp = OffsetDateTime::now_utc().unix_timestamp_nanos() / 1_000_000;
+        let auth: fiserv::FiservAuthType =
+            fiserv::FiservAuthType::try_from(&req.connector_auth_type)?;
+        let api_key = auth.api_key.clone();
+
+        let fiserv_req = self
+            .get_request_body(req)?
+            .ok_or(errors::ConnectorError::RequestEncodingFailed)?;
+        let client_request_id = Uuid::new_v4().to_string();
+        let hmac = self
+            .generate_authorization_signature(auth, &client_request_id, &fiserv_req, timestamp)
+            .change_context(errors::ConnectorError::RequestEncodingFailed)?;
+        let headers = vec![
+            (
+                headers::CONTENT_TYPE.to_string(),
+                types::PaymentsAuthorizeType::get_content_type(self).to_string(),
+            ),
+            ("Client-Request-Id".to_string(), client_request_id),
+            ("Auth-Token-Type".to_string(), "HMAC".to_string()),
+            ("Api-Key".to_string(), api_key),
+            ("Timestamp".to_string(), timestamp.to_string()),
+            ("Authorization".to_string(), hmac),
+        ];
+        Ok(headers)
+    }
+
+    fn get_content_type(&self) -> &'static str {
+        "application/json"
+    }
+
+    fn get_url(
+        &self,
+        _req: &types::PaymentsAuthorizeRouterData,
+        connectors: &settings::Connectors,
+    ) -> CustomResult<String, errors::ConnectorError> {
+        Ok(format!(
+            "{}ch/payments/v1/charges",
+            connectors.fiserv.base_url
+        ))
+    }
+
+    fn get_request_body(
+        &self,
+        req: &types::PaymentsAuthorizeRouterData,
+    ) -> CustomResult<Option<String>, errors::ConnectorError> {
+        let fiserv_req = utils::Encode::<fiserv::FiservPaymentsRequest>::convert_and_encode(req)
+            .change_context(errors::ConnectorError::RequestEncodingFailed)?;
+        Ok(Some(fiserv_req))
+    }
+
+    fn build_request(
+        &self,
+        req: &types::PaymentsAuthorizeRouterData,
+        connectors: &settings::Connectors,
+    ) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
+        let request = 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(),
+        );
+
+        Ok(request)
+    }
+
+    fn handle_response(
+        &self,
+        data: &types::PaymentsAuthorizeRouterData,
+        res: types::Response,
+    ) -> CustomResult<types::PaymentsAuthorizeRouterData, errors::ConnectorError> {
+        let response: fiserv::FiservPaymentsResponse = res
+            .response
+            .parse_struct("Fiserv PaymentResponse")
+            .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<types::ErrorResponse, errors::ConnectorError> {
+        let response: fiserv::ErrorResponse = res
+            .parse_struct("Fiserv ErrorResponse")
+            .change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
+
+        let fiserv::ErrorResponse { error, details } = response;
+
+        let message = match (error, details) {
+            (Some(err), _) => err
+                .iter()
+                .map(|v| v.message.clone())
+                .collect::<Vec<String>>()
+                .join(""),
+            (None, Some(err_details)) => err_details
+                .iter()
+                .map(|v| v.message.clone())
+                .collect::<Vec<String>>()
+                .join(""),
+            (None, None) => consts::NO_ERROR_MESSAGE.to_string(),
+        };
+        Ok(types::ErrorResponse {
+            code: consts::NO_ERROR_CODE.to_string(),
+            message,
+            reason: None,
+        })
+    }
+}
+
+impl api::Refund for Fiserv {}
+impl api::RefundExecute for Fiserv {}
+impl api::RefundSync for Fiserv {}
+
+#[allow(dead_code)]
+impl services::ConnectorIntegration<api::Execute, types::RefundsData, types::RefundsResponseData>
+    for Fiserv
+{
+}
+
+#[allow(dead_code)]
+impl services::ConnectorIntegration<api::RSync, types::RefundsData, types::RefundsResponseData>
+    for Fiserv
+{
+}
+
+#[async_trait::async_trait]
+impl api::IncomingWebhook for Fiserv {
+    fn get_webhook_object_reference_id(
+        &self,
+        _body: &[u8],
+    ) -> CustomResult<String, errors::ConnectorError> {
+        Err(errors::ConnectorError::NotImplemented("fiserv".to_string()).into())
+    }
+
+    fn get_webhook_event_type(
+        &self,
+        _body: &[u8],
+    ) -> CustomResult<api::IncomingWebhookEvent, errors::ConnectorError> {
+        Err(errors::ConnectorError::NotImplemented("fiserv".to_string()).into())
+    }
+
+    fn get_webhook_resource_object(
+        &self,
+        _body: &[u8],
+    ) -> CustomResult<serde_json::Value, errors::ConnectorError> {
+        Err(errors::ConnectorError::NotImplemented("fiserv".to_string()).into())
+    }
+}
+
+impl services::ConnectorRedirectResponse for Fiserv {
+    fn get_flow_type(
+        &self,
+        _query_params: &str,
+    ) -> CustomResult<payments::CallConnectorAction, errors::ConnectorError> {
+        Ok(payments::CallConnectorAction::Trigger)
+    }
+}
diff --git a/crates/router/src/connector/fiserv/transformers.rs b/crates/router/src/connector/fiserv/transformers.rs
new file mode 100644
index 00000000000..a16fdea007a
--- /dev/null
+++ b/crates/router/src/connector/fiserv/transformers.rs
@@ -0,0 +1,343 @@
+use common_utils::ext_traits::ValueExt;
+use error_stack::ResultExt;
+use serde::{Deserialize, Serialize};
+
+use crate::{
+    core::errors,
+    pii::{self, Secret},
+    types::{self, api, storage::enums},
+};
+
+#[derive(Default, Debug, Serialize, Eq, PartialEq)]
+#[serde(rename_all = "camelCase")]
+pub struct FiservPaymentsRequest {
+    amount: Amount,
+    source: Source,
+    transaction_details: TransactionDetails,
+    merchant_details: MerchantDetails,
+    transaction_interaction: TransactionInteraction,
+}
+
+#[derive(Default, Debug, Serialize, Eq, PartialEq)]
+#[serde(rename_all = "camelCase")]
+pub struct Source {
+    source_type: String,
+    card: CardData,
+}
+
+#[derive(Default, Debug, Serialize, Eq, PartialEq)]
+#[serde(rename_all = "camelCase")]
+pub struct CardData {
+    card_data: Secret<String, pii::CardNumber>,
+    expiration_month: Secret<String>,
+    expiration_year: Secret<String>,
+    security_code: Secret<String>,
+}
+
+#[derive(Default, Debug, Serialize, Eq, PartialEq)]
+pub struct Amount {
+    total: i64,
+    currency: String,
+}
+
+#[derive(Default, Debug, Serialize, Eq, PartialEq)]
+#[serde(rename_all = "camelCase")]
+pub struct TransactionDetails {
+    capture_flag: bool,
+}
+
+#[derive(Default, Debug, Serialize, Eq, PartialEq)]
+#[serde(rename_all = "camelCase")]
+pub struct MerchantDetails {
+    merchant_id: String,
+    terminal_id: String,
+}
+
+#[derive(Default, Debug, Serialize, Eq, PartialEq)]
+#[serde(rename_all = "camelCase")]
+pub struct TransactionInteraction {
+    origin: String,
+    eci_indicator: String,
+    pos_condition_code: String,
+}
+
+impl TryFrom<&types::PaymentsAuthorizeRouterData> for FiservPaymentsRequest {
+    type Error = error_stack::Report<errors::ConnectorError>;
+    fn try_from(item: &types::PaymentsAuthorizeRouterData) -> Result<Self, Self::Error> {
+        match item.request.payment_method_data {
+            api::PaymentMethod::Card(ref ccard) => {
+                let auth: FiservAuthType = FiservAuthType::try_from(&item.connector_auth_type)?;
+                let amount = Amount {
+                    total: item.request.amount,
+                    currency: item.request.currency.to_string(),
+                };
+
+                let card = CardData {
+                    card_data: ccard.card_number.clone(),
+                    expiration_month: ccard.card_exp_month.clone(),
+                    expiration_year: ccard.card_exp_year.clone(),
+                    security_code: ccard.card_cvc.clone(),
+                };
+                let source = Source {
+                    source_type: "PaymentCard".to_string(),
+                    card,
+                };
+                let transaction_details = TransactionDetails {
+                    capture_flag: matches!(
+                        item.request.capture_method,
+                        Some(enums::CaptureMethod::Automatic) | None
+                    ),
+                };
+                let metadata = item
+                    .connector_meta_data
+                    .clone()
+                    .ok_or(errors::ConnectorError::RequestEncodingFailed)?;
+                let session: SessionObject = metadata
+                    .parse_value("SessionObject")
+                    .change_context(errors::ConnectorError::RequestEncodingFailed)?;
+
+                let merchant_details = MerchantDetails {
+                    merchant_id: auth.merchant_account,
+                    terminal_id: session.terminal_id,
+                };
+
+                let transaction_interaction = TransactionInteraction {
+                    origin: "ECOM".to_string(), //Payment is being made in online mode, card not present
+                    eci_indicator: "CHANNEL_ENCRYPTED".to_string(), // transaction encryption such as SSL/TLS, but authentication was not performed
+                    pos_condition_code: "CARD_NOT_PRESENT_ECOM".to_string(), //card not present in online transaction
+                };
+                Ok(Self {
+                    amount,
+                    source,
+                    transaction_details,
+                    merchant_details,
+                    transaction_interaction,
+                })
+            }
+            _ => Err(errors::ConnectorError::NotImplemented(
+                "Payment Methods".to_string(),
+            ))?,
+        }
+    }
+}
+
+pub struct FiservAuthType {
+    pub(super) api_key: String,
+    pub(super) merchant_account: String,
+    pub(super) api_secret: String,
+}
+
+impl TryFrom<&types::ConnectorAuthType> for FiservAuthType {
+    type Error = error_stack::Report<errors::ConnectorError>;
+    fn try_from(auth_type: &types::ConnectorAuthType) -> Result<Self, Self::Error> {
+        if let types::ConnectorAuthType::SignatureKey {
+            api_key,
+            key1,
+            api_secret,
+        } = auth_type
+        {
+            Ok(Self {
+                api_key: api_key.to_string(),
+                merchant_account: key1.to_string(),
+                api_secret: api_secret.to_string(),
+            })
+        } else {
+            Err(errors::ConnectorError::FailedToObtainAuthType)?
+        }
+    }
+}
+
+#[derive(Debug, Default, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct ErrorResponse {
+    pub details: Option<Vec<ErrorDetails>>,
+    pub error: Option<Vec<ErrorDetails>>,
+}
+
+#[derive(Debug, Default, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct ErrorDetails {
+    #[serde(rename = "type")]
+    pub error_type: String,
+    pub code: Option<String>,
+    pub message: String,
+}
+
+#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
+#[serde(rename_all = "UPPERCASE")]
+pub enum FiservPaymentStatus {
+    Succeeded,
+    Failed,
+    Captured,
+    Declined,
+    Voided,
+    Authorized,
+    #[default]
+    Processing,
+}
+
+impl From<FiservPaymentStatus> for enums::AttemptStatus {
+    fn from(item: FiservPaymentStatus) -> Self {
+        match item {
+            FiservPaymentStatus::Captured | FiservPaymentStatus::Succeeded => Self::Charged,
+            FiservPaymentStatus::Declined | FiservPaymentStatus::Failed => Self::Failure,
+            FiservPaymentStatus::Processing => Self::Authorizing,
+            FiservPaymentStatus::Voided => Self::Voided,
+            FiservPaymentStatus::Authorized => Self::Authorized,
+        }
+    }
+}
+
+#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)]
+#[serde(rename_all = "camelCase")]
+pub struct FiservPaymentsResponse {
+    gateway_response: GatewayResponse,
+}
+
+#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)]
+#[serde(rename_all = "camelCase")]
+pub struct GatewayResponse {
+    gateway_transaction_id: String,
+    transaction_state: FiservPaymentStatus,
+    transaction_processing_details: TransactionProcessingDetails,
+}
+
+#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)]
+#[serde(rename_all = "camelCase")]
+pub struct TransactionProcessingDetails {
+    order_id: String,
+    transaction_id: String,
+}
+
+impl<F, T>
+    TryFrom<types::ResponseRouterData<F, FiservPaymentsResponse, T, types::PaymentsResponseData>>
+    for types::RouterData<F, T, types::PaymentsResponseData>
+{
+    type Error = error_stack::Report<errors::ConnectorError>;
+    fn try_from(
+        item: types::ResponseRouterData<F, FiservPaymentsResponse, T, types::PaymentsResponseData>,
+    ) -> Result<Self, Self::Error> {
+        let gateway_resp = item.response.gateway_response;
+
+        Ok(Self {
+            status: gateway_resp.transaction_state.into(),
+            response: Ok(types::PaymentsResponseData::TransactionResponse {
+                resource_id: types::ResponseId::ConnectorTransactionId(
+                    gateway_resp.transaction_processing_details.transaction_id,
+                ),
+                redirection_data: None,
+                redirect: false,
+                mandate_reference: None,
+                connector_metadata: None,
+            }),
+            ..item.data
+        })
+    }
+}
+
+#[derive(Default, Debug, Serialize, Eq, PartialEq)]
+#[serde(rename_all = "camelCase")]
+pub struct FiservCaptureRequest {
+    amount: Amount,
+    transaction_details: TransactionDetails,
+    merchant_details: MerchantDetails,
+    reference_transaction_details: ReferenceTransactionDetails,
+}
+
+#[derive(Default, Debug, Serialize, Eq, PartialEq)]
+#[serde(rename_all = "camelCase")]
+pub struct ReferenceTransactionDetails {
+    reference_transaction_id: String,
+}
+
+#[derive(Debug, Default, Serialize, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct SessionObject {
+    pub terminal_id: String,
+}
+
+impl TryFrom<&types::PaymentsCaptureRouterData> for FiservCaptureRequest {
+    type Error = error_stack::Report<errors::ConnectorError>;
+    fn try_from(item: &types::PaymentsCaptureRouterData) -> Result<Self, Self::Error> {
+        let auth: FiservAuthType = FiservAuthType::try_from(&item.connector_auth_type)?;
+        let metadata = item
+            .connector_meta_data
+            .clone()
+            .ok_or(errors::ConnectorError::RequestEncodingFailed)?;
+        let session: SessionObject = metadata
+            .parse_value("SessionObject")
+            .change_context(errors::ConnectorError::RequestEncodingFailed)?;
+        let amount = item
+            .request
+            .amount_to_capture
+            .ok_or(errors::ConnectorError::RequestEncodingFailed)?;
+        Ok(Self {
+            amount: Amount {
+                total: amount,
+                currency: item.request.currency.to_string(),
+            },
+            transaction_details: TransactionDetails { capture_flag: true },
+            merchant_details: MerchantDetails {
+                merchant_id: auth.merchant_account,
+                terminal_id: session.terminal_id,
+            },
+            reference_transaction_details: ReferenceTransactionDetails {
+                reference_transaction_id: item.request.connector_transaction_id.to_string(),
+            },
+        })
+    }
+}
+
+#[derive(Default, Debug, Serialize)]
+pub struct FiservRefundRequest {}
+
+impl<F> TryFrom<&types::RefundsRouterData<F>> for FiservRefundRequest {
+    type Error = error_stack::Report<errors::ConnectorError>;
+    fn try_from(_item: &types::RefundsRouterData<F>) -> Result<Self, Self::Error> {
+        Err(errors::ConnectorError::NotImplemented("fiserv".to_string()).into())
+    }
+}
+
+#[allow(dead_code)]
+#[derive(Debug, Serialize, Default, Deserialize, Clone)]
+pub enum RefundStatus {
+    Succeeded,
+    Failed,
+    #[default]
+    Processing,
+}
+
+impl From<RefundStatus> for enums::RefundStatus {
+    fn from(item: RefundStatus) -> Self {
+        match item {
+            RefundStatus::Succeeded => Self::Success,
+            RefundStatus::Failed => Self::Failure,
+            RefundStatus::Processing => Self::Pending,
+        }
+    }
+}
+
+#[derive(Default, Debug, Clone, Serialize, Deserialize)]
+pub struct RefundResponse {}
+
+impl TryFrom<types::RefundsResponseRouterData<api::Execute, RefundResponse>>
+    for types::RefundsRouterData<api::Execute>
+{
+    type Error = error_stack::Report<errors::ConnectorError>;
+    fn try_from(
+        _item: types::RefundsResponseRouterData<api::Execute, RefundResponse>,
+    ) -> Result<Self, Self::Error> {
+        Err(errors::ConnectorError::NotImplemented("fiserv".to_string()).into())
+    }
+}
+
+impl TryFrom<types::RefundsResponseRouterData<api::RSync, RefundResponse>>
+    for types::RefundsRouterData<api::RSync>
+{
+    type Error = error_stack::Report<errors::ConnectorError>;
+    fn try_from(
+        _item: types::RefundsResponseRouterData<api::RSync, RefundResponse>,
+    ) -> Result<Self, Self::Error> {
+        Err(errors::ConnectorError::NotImplemented("fiserv".to_string()).into())
+    }
+}
diff --git a/crates/router/src/core/payments/transformers.rs b/crates/router/src/core/payments/transformers.rs
index 89059f6b95f..5845bdaded8 100644
--- a/crates/router/src/core/payments/transformers.rs
+++ b/crates/router/src/core/payments/transformers.rs
@@ -455,11 +455,11 @@ impl<F: Clone> TryFrom<PaymentData<F>> for types::PaymentsCaptureData {
     fn try_from(payment_data: PaymentData<F>) -> Result<Self, Self::Error> {
         Ok(Self {
             amount_to_capture: payment_data.payment_attempt.amount_to_capture,
+            currency: payment_data.currency,
             connector_transaction_id: payment_data
                 .payment_attempt
                 .connector_transaction_id
                 .ok_or(errors::ApiErrorResponse::MerchantConnectorAccountNotFound)?,
-            currency: payment_data.currency,
             amount: payment_data.amount.into(),
         })
     }
diff --git a/crates/router/src/types.rs b/crates/router/src/types.rs
index d4c1cc0e3bd..37143542a0c 100644
--- a/crates/router/src/types.rs
+++ b/crates/router/src/types.rs
@@ -112,8 +112,8 @@ pub struct PaymentsAuthorizeData {
 #[derive(Debug, Clone)]
 pub struct PaymentsCaptureData {
     pub amount_to_capture: Option<i64>,
-    pub connector_transaction_id: String,
     pub currency: storage_enums::Currency,
+    pub connector_transaction_id: String,
     pub amount: i64,
 }
 
diff --git a/crates/router/src/types/api.rs b/crates/router/src/types/api.rs
index f9feb7e9442..a14ad31c1d6 100644
--- a/crates/router/src/types/api.rs
+++ b/crates/router/src/types/api.rs
@@ -139,19 +139,20 @@ impl ConnectorData {
         connector_name: &str,
     ) -> CustomResult<BoxedConnector, errors::ApiErrorResponse> {
         match connector_name {
-            "stripe" => Ok(Box::new(&connector::Stripe)),
-            "adyen" => Ok(Box::new(&connector::Adyen)),
             "aci" => Ok(Box::new(&connector::Aci)),
-            "checkout" => Ok(Box::new(&connector::Checkout)),
+            "adyen" => Ok(Box::new(&connector::Adyen)),
+            "applepay" => Ok(Box::new(&connector::Applepay)),
             "authorizedotnet" => Ok(Box::new(&connector::Authorizedotnet)),
             "braintree" => Ok(Box::new(&connector::Braintree)),
-            "klarna" => Ok(Box::new(&connector::Klarna)),
-            "applepay" => Ok(Box::new(&connector::Applepay)),
+            "checkout" => Ok(Box::new(&connector::Checkout)),
             "cybersource" => Ok(Box::new(&connector::Cybersource)),
+            "fiserv" => Ok(Box::new(&connector::Fiserv)),
+            "globalpay" => Ok(Box::new(&connector::Globalpay)),
+            "klarna" => Ok(Box::new(&connector::Klarna)),
+            "payu" => Ok(Box::new(&connector::Payu)),
             "shift4" => Ok(Box::new(&connector::Shift4)),
+            "stripe" => Ok(Box::new(&connector::Stripe)),
             "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}")))
             .change_context(errors::ApiErrorResponse::InternalServerError),
diff --git a/crates/router/tests/connectors/connector_auth.rs b/crates/router/tests/connectors/connector_auth.rs
index 86683b0f7a6..2237f0994d2 100644
--- a/crates/router/tests/connectors/connector_auth.rs
+++ b/crates/router/tests/connectors/connector_auth.rs
@@ -6,6 +6,7 @@ pub(crate) struct ConnectorAuthentication {
     pub aci: Option<BodyKey>,
     pub authorizedotnet: Option<BodyKey>,
     pub checkout: Option<BodyKey>,
+    pub fiserv: Option<SignatureKey>,
     pub globalpay: Option<HeaderKey>,
     pub payu: Option<BodyKey>,
     pub shift4: Option<HeaderKey>,
@@ -50,3 +51,20 @@ impl From<BodyKey> for ConnectorAuthType {
         }
     }
 }
+
+#[derive(Debug, Deserialize, Clone)]
+pub(crate) struct SignatureKey {
+    pub api_key: String,
+    pub key1: String,
+    pub api_secret: String,
+}
+
+impl From<SignatureKey> for ConnectorAuthType {
+    fn from(key: SignatureKey) -> Self {
+        Self::SignatureKey {
+            api_key: key.api_key,
+            key1: key.key1,
+            api_secret: key.api_secret,
+        }
+    }
+}
diff --git a/crates/router/tests/connectors/fiserv.rs b/crates/router/tests/connectors/fiserv.rs
new file mode 100644
index 00000000000..56092b8687b
--- /dev/null
+++ b/crates/router/tests/connectors/fiserv.rs
@@ -0,0 +1,129 @@
+use masking::Secret;
+use router::types::{self, api, storage::enums};
+use serde_json::json;
+
+use crate::{
+    connector_auth,
+    utils::{self, ConnectorActions},
+};
+
+struct Fiserv;
+impl ConnectorActions for Fiserv {}
+
+impl utils::Connector for Fiserv {
+    fn get_data(&self) -> types::api::ConnectorData {
+        use router::connector::Fiserv;
+        types::api::ConnectorData {
+            connector: Box::new(&Fiserv),
+            connector_name: types::Connector::Fiserv,
+            get_token: types::api::GetToken::Connector,
+        }
+    }
+
+    fn get_auth_token(&self) -> types::ConnectorAuthType {
+        types::ConnectorAuthType::from(
+            connector_auth::ConnectorAuthentication::new()
+                .fiserv
+                .expect("Missing connector authentication configuration"),
+        )
+    }
+
+    fn get_name(&self) -> String {
+        "fiserv".to_string()
+    }
+
+    fn get_connector_meta(&self) -> Option<serde_json::Value> {
+        Some(json!({"terminalId": "10000001"}))
+    }
+}
+
+#[actix_web::test]
+async fn should_only_authorize_payment() {
+    let response = Fiserv {}
+        .authorize_payment(
+            Some(types::PaymentsAuthorizeData {
+                payment_method_data: types::api::PaymentMethod::Card(api::CCard {
+                    card_number: Secret::new("4005550000000019".to_string()),
+                    card_exp_month: Secret::new("02".to_string()),
+                    card_exp_year: Secret::new("2035".to_string()),
+                    card_holder_name: Secret::new("John Doe".to_string()),
+                    card_cvc: Secret::new("123".to_string()),
+                }),
+                capture_method: Some(storage_models::enums::CaptureMethod::Manual),
+                ..utils::PaymentAuthorizeType::default().0
+            }),
+            None,
+        )
+        .await;
+    assert_eq!(response.status, enums::AttemptStatus::Authorized);
+}
+
+#[actix_web::test]
+async fn should_authorize_and_capture_payment() {
+    let response = Fiserv {}
+        .make_payment(
+            Some(types::PaymentsAuthorizeData {
+                payment_method_data: types::api::PaymentMethod::Card(api::CCard {
+                    card_number: Secret::new("4005550000000019".to_string()),
+                    card_exp_month: Secret::new("02".to_string()),
+                    card_exp_year: Secret::new("2035".to_string()),
+                    card_holder_name: Secret::new("John Doe".to_string()),
+                    card_cvc: Secret::new("123".to_string()),
+                }),
+                ..utils::PaymentAuthorizeType::default().0
+            }),
+            None,
+        )
+        .await;
+    assert_eq!(response.status, enums::AttemptStatus::Charged);
+}
+
+// You get a service declined for Payment Capture, look into it from merchant dashboard
+/*
+#[actix_web::test]
+async fn should_capture_already_authorized_payment() {
+    let connector = Fiserv {};
+    let authorize_response = connector.authorize_payment(None, None).await;
+    assert_eq!(authorize_response.status, enums::AttemptStatus::Authorized);
+    let txn_id = utils::get_connector_transaction_id(authorize_response);
+    let response: OptionFuture<_> = txn_id
+        .map(|transaction_id| async move {
+            connector.capture_payment(transaction_id, None, None).await.status
+        })
+        .into();
+    assert_eq!(response.await, Some(enums::AttemptStatus::Charged));
+}
+
+#[actix_web::test]
+async fn should_fail_payment_for_incorrect_cvc() {
+    let response = Fiserv {}.make_payment(Some(types::PaymentsAuthorizeData {
+            payment_method_data: types::api::PaymentMethod::Card(api::CCard {
+                card_number: Secret::new("4024007134364842".to_string()),
+                ..utils::CCardType::default().0
+            }),
+            ..utils::PaymentAuthorizeType::default().0
+        }), None)
+        .await;
+    let x = response.response.unwrap_err();
+    assert_eq!(
+        x.message,
+        "The card's security code failed verification.".to_string(),
+    );
+}
+
+#[actix_web::test]
+async fn should_refund_succeeded_payment() {
+    let connector = Fiserv {};
+    //make a successful payment
+    let response = connector.make_payment(None, None).await;
+
+    //try refund for previous payment
+    if let Some(transaction_id) = utils::get_connector_transaction_id(response) {
+        let response = connector.refund_payment(transaction_id, None, None).await;
+        assert_eq!(
+            response.response.unwrap().refund_status,
+            enums::RefundStatus::Success,
+        );
+    }
+}
+*/
diff --git a/crates/router/tests/connectors/main.rs b/crates/router/tests/connectors/main.rs
index b0b44c89cba..3d5af329ed7 100644
--- a/crates/router/tests/connectors/main.rs
+++ b/crates/router/tests/connectors/main.rs
@@ -4,6 +4,7 @@ mod aci;
 mod authorizedotnet;
 mod checkout;
 mod connector_auth;
+mod fiserv;
 mod globalpay;
 mod payu;
 mod shift4;
diff --git a/crates/router/tests/connectors/sample_auth.toml b/crates/router/tests/connectors/sample_auth.toml
index a8fe5dffe2e..463e6460b54 100644
--- a/crates/router/tests/connectors/sample_auth.toml
+++ b/crates/router/tests/connectors/sample_auth.toml
@@ -25,3 +25,9 @@ key1 = "MerchantPosId"
 
 [globalpay]
 api_key = "Bearer MyApiKey"
+
+[fiserv]
+api_key = "MyApiKey"
+key1 = "MerchantID"
+api_secret = "MySecretKey"
+
diff --git a/crates/router/tests/connectors/utils.rs b/crates/router/tests/connectors/utils.rs
index 650eafd7bf0..99c14b65df0 100644
--- a/crates/router/tests/connectors/utils.rs
+++ b/crates/router/tests/connectors/utils.rs
@@ -78,8 +78,8 @@ pub trait ConnectorActions: Connector {
         let request = self.generate_data(
             payment_data.unwrap_or(types::PaymentsCaptureData {
                 amount_to_capture: Some(100),
-                connector_transaction_id: transaction_id,
                 currency: enums::Currency::USD,
+                connector_transaction_id: transaction_id,
                 amount: 100,
             }),
             payment_info,

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

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

## Description
Authorize and capture flow
Todo: Issue for optimising the encoding process in header and body #370 

### 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
Adding a new connector `Fiserv`
<!--
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).
-->
Closes #229.

## How did you test it?
Added unit tests for mentioned payment flows.

<img width="784" alt="Screenshot 2023-01-13 at 7 43 38 PM" src="https://user-images.githubusercontent.com/55536657/212340057-35e12934-a1a8-4ca6-b411-729d27d6e357.png">

<!--
Did you write an integration/unit/API test to verify the code changes?
Or did you test this change manually (provide relevant screenshots)?
-->


## 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
- [ ] I added unit tests for my changes where possible
- [ ] I added a [CHANGELOG](/CHANGELOG.md) entry if applicable
