Bug: Add Worldline for card payments




diff --git a/Cargo.lock b/Cargo.lock
index 3812b1bb856..b14223062dc 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -3015,6 +3015,7 @@ dependencies = [
  "once_cell",
  "rand 0.8.5",
  "redis_interface",
+ "regex",
  "reqwest",
  "ring",
  "router_derive",
diff --git a/config/Development.toml b/config/Development.toml
index fa61e28810c..e0315b066fc 100644
--- a/config/Development.toml
+++ b/config/Development.toml
@@ -93,6 +93,9 @@ base_url = "https://secure.snd.payu.com/api/"
 [connectors.globalpay]
 base_url = "https://apis.sandbox.globalpay.com/ucp/"
 
+[connectors.worldline]
+base_url = "https://eu.sandbox.api-ingenico.com/"
+
 [scheduler]
 stream = "SCHEDULER_STREAM"
 consumer_group = "SCHEDULER_GROUP"
diff --git a/crates/api_models/src/enums.rs b/crates/api_models/src/enums.rs
index 83ead25c354..0c77ca85bff 100644
--- a/crates/api_models/src/enums.rs
+++ b/crates/api_models/src/enums.rs
@@ -509,6 +509,7 @@ pub enum Connector {
     Payu,
     Shift4,
     Stripe,
+    Worldline,
     Worldpay,
 }
 
diff --git a/crates/router/Cargo.toml b/crates/router/Cargo.toml
index 9c95e070ab0..45aeb8d377d 100644
--- a/crates/router/Cargo.toml
+++ b/crates/router/Cargo.toml
@@ -53,6 +53,7 @@ nanoid = "0.4.0"
 num_cpus = "1.15.0"
 once_cell = "1.17.0"
 rand = "0.8.5"
+regex = "1.7.1"
 reqwest = { version = "0.11.13", features = ["json", "native-tls", "gzip"] }
 ring = "0.16.20"
 serde = { version = "1.0.152", features = ["derive"] }
diff --git a/crates/router/src/configs/settings.rs b/crates/router/src/configs/settings.rs
index 3aeb0fc0410..1f66cb22ff2 100644
--- a/crates/router/src/configs/settings.rs
+++ b/crates/router/src/configs/settings.rs
@@ -134,6 +134,7 @@ pub struct Connectors {
     pub shift4: ConnectorParams,
     pub stripe: ConnectorParams,
     pub supported: SupportedConnectors,
+    pub worldline: ConnectorParams,
     pub worldpay: ConnectorParams,
 }
 
diff --git a/crates/router/src/connector.rs b/crates/router/src/connector.rs
index 2b9ef18559f..4c218454d99 100644
--- a/crates/router/src/connector.rs
+++ b/crates/router/src/connector.rs
@@ -12,11 +12,12 @@ pub mod payu;
 pub mod shift4;
 pub mod stripe;
 pub mod utils;
+pub mod worldline;
 pub mod worldpay;
 
 pub use self::{
     aci::Aci, adyen::Adyen, applepay::Applepay, authorizedotnet::Authorizedotnet,
     braintree::Braintree, checkout::Checkout, cybersource::Cybersource, fiserv::Fiserv,
     globalpay::Globalpay, klarna::Klarna, payu::Payu, shift4::Shift4, stripe::Stripe,
-    worldpay::Worldpay,
+    worldline::Worldline, worldpay::Worldpay,
 };
diff --git a/crates/router/src/connector/worldline.rs b/crates/router/src/connector/worldline.rs
new file mode 100644
index 00000000000..50419ee2b9c
--- /dev/null
+++ b/crates/router/src/connector/worldline.rs
@@ -0,0 +1,606 @@
+mod transformers;
+
+use std::fmt::Debug;
+
+use base64::Engine;
+use bytes::Bytes;
+use error_stack::{IntoReport, ResultExt};
+use ring::hmac;
+use time::{format_description, OffsetDateTime};
+use transformers as worldline;
+
+use crate::{
+    configs::settings::Connectors,
+    consts,
+    core::errors::{self, CustomResult},
+    headers, logger,
+    services::{self, ConnectorIntegration},
+    types::{
+        self,
+        api::{self, ConnectorCommon},
+        ErrorResponse, Response,
+    },
+    utils::{self, BytesExt, OptionExt},
+};
+
+#[derive(Debug, Clone)]
+pub struct Worldline;
+
+impl Worldline {
+    pub fn generate_authorization_token(
+        &self,
+        auth: worldline::AuthType,
+        http_method: &services::Method,
+        content_type: &str,
+        date: &str,
+        endpoint: &str,
+    ) -> CustomResult<String, errors::ConnectorError> {
+        let signature_data: String = format!(
+            "{}\n{}\n{}\n/{}\n",
+            http_method,
+            content_type.trim(),
+            date.trim(),
+            endpoint.trim()
+        );
+        let worldline::AuthType {
+            api_key,
+            api_secret,
+            ..
+        } = auth;
+        let key = hmac::Key::new(hmac::HMAC_SHA256, api_secret.as_bytes());
+        let signed_data = consts::BASE64_ENGINE.encode(hmac::sign(&key, signature_data.as_bytes()));
+
+        Ok(format!("GCS v1HMAC:{api_key}:{signed_data}"))
+    }
+
+    pub fn get_current_date_time() -> CustomResult<String, errors::ConnectorError> {
+        let format = format_description::parse(
+            "[weekday repr:short], [day] [month repr:short] [year] [hour]:[minute]:[second] GMT",
+        )
+        .into_report()
+        .change_context(errors::ConnectorError::InvalidDateFormat)?;
+        OffsetDateTime::now_utc()
+            .format(&format)
+            .into_report()
+            .change_context(errors::ConnectorError::InvalidDateFormat)
+    }
+}
+
+impl ConnectorCommon for Worldline {
+    fn id(&self) -> &'static str {
+        "worldline"
+    }
+
+    fn base_url<'a>(&self, connectors: &'a Connectors) -> &'a str {
+        connectors.worldline.base_url.as_ref()
+    }
+
+    fn build_error_response(
+        &self,
+        res: Bytes,
+    ) -> CustomResult<ErrorResponse, errors::ConnectorError> {
+        let response: worldline::ErrorResponse = res
+            .parse_struct("Worldline ErrorResponse")
+            .change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
+        let error = response.errors.into_iter().next().unwrap_or_default();
+        Ok(ErrorResponse {
+            code: error
+                .code
+                .unwrap_or_else(|| consts::NO_ERROR_CODE.to_string()),
+            message: error
+                .message
+                .unwrap_or_else(|| consts::NO_ERROR_MESSAGE.to_string()),
+            ..Default::default()
+        })
+    }
+}
+
+impl api::Payment for Worldline {}
+
+impl api::PreVerify for Worldline {}
+impl ConnectorIntegration<api::Verify, types::VerifyRequestData, types::PaymentsResponseData>
+    for Worldline
+{
+}
+
+impl api::PaymentVoid for Worldline {}
+
+impl ConnectorIntegration<api::Void, types::PaymentsCancelData, types::PaymentsResponseData>
+    for Worldline
+{
+    fn get_headers(
+        &self,
+        req: &types::RouterData<api::Void, types::PaymentsCancelData, types::PaymentsResponseData>,
+        connectors: &Connectors,
+    ) -> CustomResult<Vec<(String, String)>, errors::ConnectorError> {
+        let base_url = self.base_url(connectors);
+        let url = &types::PaymentsVoidType::get_url(self, req, connectors)?;
+        let endpoint = url.clone().replace(base_url, "");
+        let http_method = services::Method::Post;
+        let auth = worldline::AuthType::try_from(&req.connector_auth_type)?;
+        let date = Self::get_current_date_time()?;
+        let content_type = types::PaymentsAuthorizeType::get_content_type(self);
+        let signed_data: String =
+            self.generate_authorization_token(auth, &http_method, content_type, &date, &endpoint)?;
+
+        Ok(vec![
+            (headers::DATE.to_string(), date),
+            (headers::AUTHORIZATION.to_string(), signed_data),
+            (headers::CONTENT_TYPE.to_string(), content_type.to_string()),
+        ])
+    }
+
+    fn get_content_type(&self) -> &'static str {
+        "application/json"
+    }
+
+    fn get_url(
+        &self,
+        req: &types::PaymentsCancelRouterData,
+        connectors: &Connectors,
+    ) -> CustomResult<String, errors::ConnectorError> {
+        let base_url = self.base_url(connectors);
+        let auth: worldline::AuthType = worldline::AuthType::try_from(&req.connector_auth_type)?;
+        let merchat_account_id = auth.merchant_account_id;
+        let payment_id: &str = req.request.connector_transaction_id.as_ref();
+        Ok(format!(
+            "{base_url}v1/{merchat_account_id}/payments/{payment_id}/cancel"
+        ))
+    }
+
+    fn build_request(
+        &self,
+        req: &types::RouterData<api::Void, types::PaymentsCancelData, types::PaymentsResponseData>,
+        connectors: &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)?)
+                .build(),
+        ))
+    }
+
+    fn handle_response(
+        &self,
+        data: &types::PaymentsCancelRouterData,
+        res: Response,
+    ) -> CustomResult<types::PaymentsCancelRouterData, errors::ConnectorError> {
+        let response: worldline::PaymentResponse = res
+            .response
+            .parse_struct("PaymentResponse")
+            .change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
+        logger::debug!(payments_cancel_response=?response);
+        types::RouterData::try_from(types::ResponseRouterData {
+            response,
+            data: data.clone(),
+            http_code: res.status_code,
+        })
+        .change_context(errors::ConnectorError::ResponseHandlingFailed)
+    }
+
+    fn get_error_response(
+        &self,
+        res: Bytes,
+    ) -> CustomResult<ErrorResponse, errors::ConnectorError> {
+        self.build_error_response(res)
+    }
+}
+
+impl api::PaymentSync for Worldline {}
+impl ConnectorIntegration<api::PSync, types::PaymentsSyncData, types::PaymentsResponseData>
+    for Worldline
+{
+    fn get_headers(
+        &self,
+        req: &types::RouterData<api::PSync, types::PaymentsSyncData, types::PaymentsResponseData>,
+        connectors: &Connectors,
+    ) -> CustomResult<Vec<(String, String)>, errors::ConnectorError> {
+        let base_url = self.base_url(connectors);
+        let url = &types::PaymentsSyncType::get_url(self, req, connectors)?;
+        let endpoint = url.clone().replace(base_url, "");
+        let auth = worldline::AuthType::try_from(&req.connector_auth_type)?;
+        let date = Self::get_current_date_time()?;
+        let signed_data: String =
+            self.generate_authorization_token(auth, &services::Method::Get, "", &date, &endpoint)?;
+        Ok(vec![
+            (headers::DATE.to_string(), date),
+            (headers::AUTHORIZATION.to_string(), signed_data),
+        ])
+    }
+
+    fn get_url(
+        &self,
+        req: &types::PaymentsSyncRouterData,
+        connectors: &Connectors,
+    ) -> CustomResult<String, errors::ConnectorError> {
+        let payment_id = req
+            .request
+            .connector_transaction_id
+            .get_connector_transaction_id()
+            .change_context(errors::ConnectorError::MissingConnectorTransactionID)?;
+        let base_url = self.base_url(connectors);
+        let auth = worldline::AuthType::try_from(&req.connector_auth_type)?;
+        let merchat_account_id = auth.merchant_account_id;
+        Ok(format!(
+            "{base_url}v1/{merchat_account_id}/payments/{payment_id}"
+        ))
+    }
+
+    fn build_request(
+        &self,
+        req: &types::PaymentsSyncRouterData,
+        connectors: &Connectors,
+    ) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
+        Ok(Some(
+            services::RequestBuilder::new()
+                .method(services::Method::Get)
+                .url(&types::PaymentsSyncType::get_url(self, req, connectors)?)
+                .headers(types::PaymentsSyncType::get_headers(self, req, connectors)?)
+                .build(),
+        ))
+    }
+
+    fn get_error_response(
+        &self,
+        res: Bytes,
+    ) -> CustomResult<ErrorResponse, errors::ConnectorError> {
+        self.build_error_response(res)
+    }
+
+    fn handle_response(
+        &self,
+        data: &types::PaymentsSyncRouterData,
+        res: Response,
+    ) -> CustomResult<types::PaymentsSyncRouterData, errors::ConnectorError> {
+        logger::debug!(payment_sync_response=?res);
+        let response: worldline::Payment = res
+            .response
+            .parse_struct("Payment")
+            .change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
+        types::RouterData::try_from(types::ResponseRouterData {
+            response,
+            data: data.clone(),
+            http_code: res.status_code,
+        })
+        .change_context(errors::ConnectorError::ResponseHandlingFailed)
+    }
+}
+
+impl api::PaymentCapture for Worldline {}
+impl ConnectorIntegration<api::Capture, types::PaymentsCaptureData, types::PaymentsResponseData>
+    for Worldline
+{
+    // Not Implemented
+}
+
+impl api::PaymentSession for Worldline {}
+
+impl ConnectorIntegration<api::Session, types::PaymentsSessionData, types::PaymentsResponseData>
+    for Worldline
+{
+    // Not Implemented
+}
+
+impl api::PaymentAuthorize for Worldline {}
+
+impl ConnectorIntegration<api::Authorize, types::PaymentsAuthorizeData, types::PaymentsResponseData>
+    for Worldline
+{
+    fn get_headers(
+        &self,
+        req: &types::RouterData<
+            api::Authorize,
+            types::PaymentsAuthorizeData,
+            types::PaymentsResponseData,
+        >,
+        connectors: &Connectors,
+    ) -> CustomResult<Vec<(String, String)>, errors::ConnectorError> {
+        let base_url = self.base_url(connectors);
+        let url = &types::PaymentsAuthorizeType::get_url(self, req, connectors)?;
+        let endpoint = url.clone().replace(base_url, "");
+        let auth = worldline::AuthType::try_from(&req.connector_auth_type)?;
+        let date = Self::get_current_date_time()?;
+        let content_type = types::PaymentsAuthorizeType::get_content_type(self);
+        let signed_data: String = self.generate_authorization_token(
+            auth,
+            &services::Method::Post,
+            content_type,
+            &date,
+            &endpoint,
+        )?;
+
+        Ok(vec![
+            (headers::DATE.to_string(), date),
+            (headers::AUTHORIZATION.to_string(), signed_data),
+            (headers::CONTENT_TYPE.to_string(), content_type.to_string()),
+        ])
+    }
+
+    fn get_content_type(&self) -> &'static str {
+        "application/json"
+    }
+
+    fn get_url(
+        &self,
+        req: &types::PaymentsAuthorizeRouterData,
+        connectors: &Connectors,
+    ) -> CustomResult<String, errors::ConnectorError> {
+        let base_url = self.base_url(connectors);
+        let auth = worldline::AuthType::try_from(&req.connector_auth_type)?;
+        let merchat_account_id = auth.merchant_account_id;
+        Ok(format!("{base_url}v1/{merchat_account_id}/payments"))
+    }
+
+    fn get_request_body(
+        &self,
+        req: &types::PaymentsAuthorizeRouterData,
+    ) -> CustomResult<Option<String>, errors::ConnectorError> {
+        let worldline_req = utils::Encode::<worldline::PaymentsRequest>::convert_and_encode(req)
+            .change_context(errors::ConnectorError::RequestEncodingFailed)?;
+        Ok(Some(worldline_req))
+    }
+
+    fn build_request(
+        &self,
+        req: &types::RouterData<
+            api::Authorize,
+            types::PaymentsAuthorizeData,
+            types::PaymentsResponseData,
+        >,
+        connectors: &Connectors,
+    ) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
+        Ok(Some(
+            services::RequestBuilder::new()
+                .method(services::Method::Post)
+                .url(&types::PaymentsAuthorizeType::get_url(
+                    self, req, connectors,
+                )?)
+                .headers(types::PaymentsAuthorizeType::get_headers(
+                    self, req, connectors,
+                )?)
+                .body(types::PaymentsAuthorizeType::get_request_body(self, req)?)
+                .build(),
+        ))
+    }
+    fn handle_response(
+        &self,
+        data: &types::PaymentsAuthorizeRouterData,
+        res: Response,
+    ) -> CustomResult<types::PaymentsAuthorizeRouterData, errors::ConnectorError> {
+        let response: worldline::PaymentResponse = res
+            .response
+            .parse_struct("PaymentIntentResponse")
+            .change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
+        logger::debug!(worldlinepayments_create_response=?response);
+        types::ResponseRouterData {
+            response,
+            data: data.clone(),
+            http_code: res.status_code,
+        }
+        .try_into()
+        .change_context(errors::ConnectorError::ResponseHandlingFailed)
+    }
+
+    fn get_error_response(
+        &self,
+        res: Bytes,
+    ) -> CustomResult<ErrorResponse, errors::ConnectorError> {
+        self.build_error_response(res)
+    }
+}
+
+impl api::Refund for Worldline {}
+impl api::RefundExecute for Worldline {}
+impl api::RefundSync for Worldline {}
+
+impl ConnectorIntegration<api::Execute, types::RefundsData, types::RefundsResponseData>
+    for Worldline
+{
+    fn get_headers(
+        &self,
+        req: &types::RefundsRouterData<api::Execute>,
+        connectors: &Connectors,
+    ) -> CustomResult<Vec<(String, String)>, errors::ConnectorError> {
+        let base_url = self.base_url(connectors);
+        let url = &types::RefundExecuteType::get_url(self, req, connectors)?;
+        let endpoint = url.clone().replace(base_url, "");
+        let auth = worldline::AuthType::try_from(&req.connector_auth_type)?;
+        let date = Self::get_current_date_time()?;
+        let content_type = types::RefundExecuteType::get_content_type(self);
+        let signed_data: String = self.generate_authorization_token(
+            auth,
+            &services::Method::Post,
+            content_type,
+            &date,
+            &endpoint,
+        )?;
+
+        Ok(vec![
+            (headers::DATE.to_string(), date),
+            (headers::AUTHORIZATION.to_string(), signed_data),
+            (headers::CONTENT_TYPE.to_string(), content_type.to_string()),
+        ])
+    }
+
+    fn get_content_type(&self) -> &'static str {
+        "application/json"
+    }
+
+    fn get_url(
+        &self,
+        req: &types::RefundsRouterData<api::Execute>,
+        connectors: &Connectors,
+    ) -> CustomResult<String, errors::ConnectorError> {
+        let payment_id = req.request.connector_transaction_id.clone();
+        let base_url = self.base_url(connectors);
+        let auth = worldline::AuthType::try_from(&req.connector_auth_type)?;
+        let merchat_account_id = auth.merchant_account_id;
+        Ok(format!(
+            "{base_url}v1/{merchat_account_id}/payments/{payment_id}/refund"
+        ))
+    }
+
+    fn get_request_body(
+        &self,
+        req: &types::RefundsRouterData<api::Execute>,
+    ) -> CustomResult<Option<String>, errors::ConnectorError> {
+        let refund_req =
+            utils::Encode::<worldline::WorldlineRefundRequest>::convert_and_encode(req)
+                .change_context(errors::ConnectorError::RequestEncodingFailed)?;
+        Ok(Some(refund_req))
+    }
+
+    fn build_request(
+        &self,
+        req: &types::RefundsRouterData<api::Execute>,
+        connectors: &Connectors,
+    ) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
+        let request = services::RequestBuilder::new()
+            .method(services::Method::Post)
+            .url(&types::RefundExecuteType::get_url(self, req, connectors)?)
+            .headers(types::RefundExecuteType::get_headers(
+                self, req, connectors,
+            )?)
+            .body(types::RefundExecuteType::get_request_body(self, req)?)
+            .build();
+        Ok(Some(request))
+    }
+
+    fn handle_response(
+        &self,
+        data: &types::RefundsRouterData<api::Execute>,
+        res: Response,
+    ) -> CustomResult<types::RefundsRouterData<api::Execute>, errors::ConnectorError> {
+        logger::debug!(target: "router::connector::worldline", response=?res);
+        let response: worldline::RefundResponse = res
+            .response
+            .parse_struct("worldline RefundResponse")
+            .change_context(errors::ConnectorError::RequestEncodingFailed)?;
+        types::ResponseRouterData {
+            response,
+            data: data.clone(),
+            http_code: res.status_code,
+        }
+        .try_into()
+        .change_context(errors::ConnectorError::ResponseHandlingFailed)
+    }
+
+    fn get_error_response(
+        &self,
+        res: Bytes,
+    ) -> CustomResult<ErrorResponse, errors::ConnectorError> {
+        self.build_error_response(res)
+    }
+}
+
+impl ConnectorIntegration<api::RSync, types::RefundsData, types::RefundsResponseData>
+    for Worldline
+{
+    fn get_headers(
+        &self,
+        req: &types::RefundSyncRouterData,
+        connectors: &Connectors,
+    ) -> CustomResult<Vec<(String, String)>, errors::ConnectorError> {
+        let base_url = self.base_url(connectors);
+        let url = &types::RefundSyncType::get_url(self, req, connectors)?;
+        let endpoint = url.clone().replace(base_url, "");
+        let auth = worldline::AuthType::try_from(&req.connector_auth_type)?;
+        let date = Self::get_current_date_time()?;
+        let signed_data: String =
+            self.generate_authorization_token(auth, &services::Method::Get, "", &date, &endpoint)?;
+
+        Ok(vec![
+            (headers::DATE.to_string(), date),
+            (headers::AUTHORIZATION.to_string(), signed_data),
+        ])
+    }
+
+    fn get_url(
+        &self,
+        req: &types::RefundSyncRouterData,
+        connectors: &Connectors,
+    ) -> CustomResult<String, errors::ConnectorError> {
+        let refund_id = req
+            .response
+            .as_ref()
+            .ok()
+            .get_required_value("response")
+            .change_context(errors::ConnectorError::FailedToObtainIntegrationUrl)?
+            .connector_refund_id
+            .clone();
+        let base_url = self.base_url(connectors);
+        let auth: worldline::AuthType = worldline::AuthType::try_from(&req.connector_auth_type)?;
+        let merchat_account_id = auth.merchant_account_id;
+        Ok(format!(
+            "{base_url}v1/{merchat_account_id}/refunds/{refund_id}/"
+        ))
+    }
+
+    fn build_request(
+        &self,
+        req: &types::RefundSyncRouterData,
+        connectors: &Connectors,
+    ) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
+        Ok(Some(
+            services::RequestBuilder::new()
+                .method(services::Method::Get)
+                .url(&types::RefundSyncType::get_url(self, req, connectors)?)
+                .headers(types::RefundSyncType::get_headers(self, req, connectors)?)
+                .build(),
+        ))
+    }
+
+    fn handle_response(
+        &self,
+        data: &types::RefundSyncRouterData,
+        res: Response,
+    ) -> CustomResult<types::RefundSyncRouterData, errors::ConnectorError> {
+        logger::debug!(target: "router::connector::worldline", response=?res);
+        let response: worldline::RefundResponse = res
+            .response
+            .parse_struct("worldline RefundResponse")
+            .change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
+        types::ResponseRouterData {
+            response,
+            data: data.clone(),
+            http_code: res.status_code,
+        }
+        .try_into()
+        .change_context(errors::ConnectorError::ResponseHandlingFailed)
+    }
+
+    fn get_error_response(
+        &self,
+        res: Bytes,
+    ) -> CustomResult<ErrorResponse, errors::ConnectorError> {
+        self.build_error_response(res)
+    }
+}
+
+#[async_trait::async_trait]
+impl api::IncomingWebhook for Worldline {
+    fn get_webhook_object_reference_id(
+        &self,
+        _body: &[u8],
+    ) -> CustomResult<String, errors::ConnectorError> {
+        Err(errors::ConnectorError::WebhooksNotImplemented).into_report()
+    }
+
+    fn get_webhook_event_type(
+        &self,
+        _body: &[u8],
+    ) -> CustomResult<api::IncomingWebhookEvent, errors::ConnectorError> {
+        Err(errors::ConnectorError::WebhooksNotImplemented).into_report()
+    }
+
+    fn get_webhook_resource_object(
+        &self,
+        _body: &[u8],
+    ) -> CustomResult<serde_json::Value, errors::ConnectorError> {
+        Err(errors::ConnectorError::WebhooksNotImplemented).into_report()
+    }
+}
+
+impl services::ConnectorRedirectResponse for Worldline {}
diff --git a/crates/router/src/connector/worldline/transformers.rs b/crates/router/src/connector/worldline/transformers.rs
new file mode 100644
index 00000000000..0c958a4ffea
--- /dev/null
+++ b/crates/router/src/connector/worldline/transformers.rs
@@ -0,0 +1,487 @@
+use std::collections::HashMap;
+
+use api_models::payments as api_models;
+use common_utils::pii::{self, Email};
+use error_stack::{IntoReport, ResultExt};
+use masking::{PeekInterface, Secret};
+use once_cell::sync::Lazy;
+use regex::Regex;
+use serde::{Deserialize, Serialize};
+
+use crate::{
+    core::errors,
+    types::{self, api, storage::enums},
+};
+
+static CARD_REGEX: Lazy<HashMap<CardProduct, Result<Regex, regex::Error>>> = Lazy::new(|| {
+    let mut map = HashMap::new();
+    // Reference: https://gist.github.com/michaelkeevildown/9096cd3aac9029c4e6e05588448a8841
+    // [#379]: Determine card issuer from card BIN number
+    map.insert(CardProduct::Master, Regex::new(r"^5[1-5][0-9]{14}$"));
+    map.insert(
+        CardProduct::AmericanExpress,
+        Regex::new(r"^3[47][0-9]{13}$"),
+    );
+    map.insert(CardProduct::Visa, Regex::new(r"^4[0-9]{12}(?:[0-9]{3})?$"));
+    map.insert(CardProduct::Discover, Regex::new(r"^65[4-9][0-9]{13}|64[4-9][0-9]{13}|6011[0-9]{12}|(622(?:12[6-9]|1[3-9][0-9]|[2-8][0-9][0-9]|9[01][0-9]|92[0-5])[0-9]{10})$"));
+    map
+});
+
+#[derive(Default, Debug, Serialize, Eq, PartialEq)]
+#[serde(rename_all = "camelCase")]
+pub struct Card {
+    pub card_number: Secret<String, pii::CardNumber>,
+    pub cardholder_name: Secret<String>,
+    pub cvv: Secret<String>,
+    pub expiry_date: Secret<String>,
+}
+
+#[derive(Default, Debug, Serialize, Eq, PartialEq)]
+#[serde(rename_all = "camelCase")]
+pub struct CardPaymentMethod {
+    pub card: Card,
+    pub requires_approval: bool,
+    pub payment_product_id: u16,
+}
+
+#[derive(Default, Debug, Serialize, Eq, PartialEq)]
+#[serde(rename_all = "camelCase")]
+pub struct AmountOfMoney {
+    pub amount: i64,
+    pub currency_code: String,
+}
+
+#[derive(Default, Debug, Serialize, Eq, PartialEq)]
+#[serde(rename_all = "camelCase")]
+pub struct Order {
+    pub amount_of_money: AmountOfMoney,
+    pub customer: Customer,
+}
+
+#[derive(Default, Debug, Serialize, Eq, PartialEq)]
+#[serde(rename_all = "camelCase")]
+pub struct BillingAddress {
+    pub city: Option<String>,
+    pub country_code: Option<String>,
+    pub house_number: Option<String>,
+    pub state: Option<Secret<String>>,
+    pub state_code: Option<String>,
+    pub street: Option<String>,
+    pub zip: Option<Secret<String>>,
+}
+
+#[derive(Default, Debug, Serialize, Eq, PartialEq)]
+#[serde(rename_all = "camelCase")]
+pub struct ContactDetails {
+    pub email_address: Option<Secret<String, Email>>,
+    pub mobile_phone_number: Option<Secret<String>>,
+}
+
+#[derive(Default, Debug, Serialize, Eq, PartialEq)]
+#[serde(rename_all = "camelCase")]
+pub struct Customer {
+    pub billing_address: BillingAddress,
+    pub contact_details: Option<ContactDetails>,
+}
+
+#[derive(Default, Debug, Serialize, Eq, PartialEq)]
+#[serde(rename_all = "camelCase")]
+pub struct Name {
+    pub first_name: Option<Secret<String>>,
+    pub surname: Option<Secret<String>>,
+    pub surname_prefix: Option<Secret<String>>,
+    pub title: Option<Secret<String>>,
+}
+
+#[derive(Default, Debug, Serialize, Eq, PartialEq)]
+#[serde(rename_all = "camelCase")]
+pub struct Shipping {
+    pub city: Option<String>,
+    pub country_code: Option<String>,
+    pub house_number: Option<String>,
+    pub name: Option<Name>,
+    pub state: Option<Secret<String>>,
+    pub state_code: Option<String>,
+    pub street: Option<String>,
+    pub zip: Option<Secret<String>>,
+}
+
+#[derive(Default, Debug, Serialize, Eq, PartialEq)]
+#[serde(rename_all = "camelCase")]
+pub struct PaymentsRequest {
+    pub card_payment_method_specific_input: CardPaymentMethod,
+    pub order: Order,
+    pub shipping: Option<Shipping>,
+}
+
+impl TryFrom<&types::PaymentsAuthorizeRouterData> for PaymentsRequest {
+    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 card) => {
+                make_card_request(&item.address, &item.request, card)
+            }
+            _ => Err(errors::ConnectorError::NotImplemented("Payment methods".to_string()).into()),
+        }
+    }
+}
+
+fn make_card_request(
+    address: &types::PaymentAddress,
+    req: &types::PaymentsAuthorizeData,
+    ccard: &api_models::CCard,
+) -> Result<PaymentsRequest, error_stack::Report<errors::ConnectorError>> {
+    let card_number = ccard.card_number.peek().as_ref();
+    let expiry_year = ccard.card_exp_year.peek().clone();
+    let secret_value = format!("{}{}", ccard.card_exp_month.peek(), &expiry_year[2..]);
+    let expiry_date: Secret<String> = Secret::new(secret_value);
+    let card = Card {
+        card_number: ccard.card_number.clone(),
+        cardholder_name: ccard.card_holder_name.clone(),
+        cvv: ccard.card_cvc.clone(),
+        expiry_date,
+    };
+    let payment_product_id = get_card_product_id(card_number)?;
+    let card_payment_method_specific_input = CardPaymentMethod {
+        card,
+        requires_approval: matches!(req.capture_method, Some(enums::CaptureMethod::Manual)),
+        payment_product_id,
+    };
+
+    let customer = build_customer_info(address, &req.email)?;
+
+    let order = Order {
+        amount_of_money: AmountOfMoney {
+            amount: req.amount,
+            currency_code: req.currency.to_string().to_uppercase(),
+        },
+        customer,
+    };
+
+    let shipping = address
+        .shipping
+        .as_ref()
+        .and_then(|shipping| shipping.address.clone())
+        .map(|address| Shipping { ..address.into() });
+
+    Ok(PaymentsRequest {
+        card_payment_method_specific_input,
+        order,
+        shipping,
+    })
+}
+
+fn get_card_product_id(
+    card_number: &str,
+) -> Result<u16, error_stack::Report<errors::ConnectorError>> {
+    for (k, v) in CARD_REGEX.iter() {
+        let regex: Regex = v
+            .clone()
+            .into_report()
+            .change_context(errors::ConnectorError::RequestEncodingFailed)?;
+        if regex.is_match(card_number) {
+            return Ok(k.product_id());
+        }
+    }
+    Err(error_stack::Report::new(
+        errors::ConnectorError::RequestEncodingFailed,
+    ))
+}
+
+fn get_address(
+    payment_address: &types::PaymentAddress,
+) -> Option<(&api_models::Address, &api_models::AddressDetails)> {
+    let billing = payment_address.billing.as_ref()?;
+    let address = billing.address.as_ref()?;
+    address.country.as_ref()?;
+    Some((billing, address))
+}
+
+fn build_customer_info(
+    payment_address: &types::PaymentAddress,
+    email: &Option<Secret<String, Email>>,
+) -> Result<Customer, error_stack::Report<errors::ConnectorError>> {
+    let (billing, address) =
+        get_address(payment_address).ok_or(errors::ConnectorError::RequestEncodingFailed)?;
+
+    let number_with_country_code = billing.phone.as_ref().and_then(|phone| {
+        phone.number.as_ref().and_then(|number| {
+            phone
+                .country_code
+                .as_ref()
+                .map(|cc| Secret::new(format!("{}{}", cc, number.peek())))
+        })
+    });
+
+    Ok(Customer {
+        billing_address: BillingAddress {
+            ..address.clone().into()
+        },
+        contact_details: Some(ContactDetails {
+            mobile_phone_number: number_with_country_code,
+            email_address: email.clone(),
+        }),
+    })
+}
+
+impl From<api_models::AddressDetails> for BillingAddress {
+    fn from(value: api_models::AddressDetails) -> Self {
+        Self {
+            city: value.city,
+            country_code: value.country,
+            state: value.state,
+            zip: value.zip,
+            ..Default::default()
+        }
+    }
+}
+
+impl From<api_models::AddressDetails> for Shipping {
+    fn from(value: api_models::AddressDetails) -> Self {
+        Self {
+            city: value.city,
+            country_code: value.country,
+            name: Some(Name {
+                first_name: value.first_name,
+                surname: value.last_name,
+                ..Default::default()
+            }),
+            state: value.state,
+            zip: value.zip,
+            ..Default::default()
+        }
+    }
+}
+
+pub struct AuthType {
+    pub api_key: String,
+    pub api_secret: String,
+    pub merchant_account_id: String,
+}
+
+impl TryFrom<&types::ConnectorAuthType> for AuthType {
+    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(),
+                api_secret: api_secret.to_string(),
+                merchant_account_id: key1.to_string(),
+            })
+        } else {
+            Err(errors::ConnectorError::FailedToObtainAuthType)?
+        }
+    }
+}
+
+#[derive(Debug, Clone, Default, Deserialize, PartialEq)]
+#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
+pub enum PaymentStatus {
+    Captured,
+    Paid,
+    ChargebackNotification,
+    Cancelled,
+    Rejected,
+    RejectedCapture,
+    PendingApproval,
+    CaptureRequested,
+    #[default]
+    Processing,
+}
+
+impl From<PaymentStatus> for enums::AttemptStatus {
+    fn from(item: PaymentStatus) -> Self {
+        match item {
+            PaymentStatus::Captured
+            | PaymentStatus::Paid
+            | PaymentStatus::ChargebackNotification => Self::Charged,
+            PaymentStatus::Cancelled => Self::Voided,
+            PaymentStatus::Rejected | PaymentStatus::RejectedCapture => Self::Failure,
+            PaymentStatus::CaptureRequested => Self::CaptureInitiated,
+            PaymentStatus::PendingApproval => Self::Authorizing,
+            _ => Self::Pending,
+        }
+    }
+}
+
+#[derive(Default, Debug, Clone, Deserialize, PartialEq)]
+pub struct Payment {
+    id: String,
+    status: PaymentStatus,
+}
+
+impl<F, T> TryFrom<types::ResponseRouterData<F, Payment, T, types::PaymentsResponseData>>
+    for types::RouterData<F, T, types::PaymentsResponseData>
+{
+    type Error = error_stack::Report<errors::ConnectorError>;
+    fn try_from(
+        item: types::ResponseRouterData<F, Payment, T, types::PaymentsResponseData>,
+    ) -> Result<Self, Self::Error> {
+        Ok(Self {
+            status: enums::AttemptStatus::from(item.response.status.clone()),
+            response: Ok(types::PaymentsResponseData::TransactionResponse {
+                resource_id: types::ResponseId::ConnectorTransactionId(item.response.id),
+                redirection_data: None,
+                redirect: false,
+                mandate_reference: None,
+                connector_metadata: None,
+            }),
+            ..item.data
+        })
+    }
+}
+
+#[derive(Default, Debug, Clone, Deserialize, PartialEq)]
+pub struct PaymentResponse {
+    payment: Payment,
+}
+
+impl<F, T> TryFrom<types::ResponseRouterData<F, PaymentResponse, T, types::PaymentsResponseData>>
+    for types::RouterData<F, T, types::PaymentsResponseData>
+{
+    type Error = error_stack::Report<errors::ConnectorError>;
+    fn try_from(
+        item: types::ResponseRouterData<F, PaymentResponse, T, types::PaymentsResponseData>,
+    ) -> Result<Self, Self::Error> {
+        Ok(Self {
+            status: enums::AttemptStatus::from(item.response.payment.status.clone()),
+            response: Ok(types::PaymentsResponseData::TransactionResponse {
+                resource_id: types::ResponseId::ConnectorTransactionId(item.response.payment.id),
+                redirection_data: None,
+                redirect: false,
+                mandate_reference: None,
+                connector_metadata: None,
+            }),
+            ..item.data
+        })
+    }
+}
+
+#[derive(Default, Debug, Serialize)]
+pub struct WorldlineRefundRequest {
+    amount_of_money: AmountOfMoney,
+}
+
+impl<F> TryFrom<&types::RefundsRouterData<F>> for WorldlineRefundRequest {
+    type Error = error_stack::Report<errors::ParsingError>;
+    fn try_from(item: &types::RefundsRouterData<F>) -> Result<Self, Self::Error> {
+        Ok(Self {
+            amount_of_money: AmountOfMoney {
+                amount: item.request.refund_amount,
+                currency_code: item.request.currency.to_string(),
+            },
+        })
+    }
+}
+
+#[allow(dead_code)]
+#[derive(Debug, Default, Deserialize, Clone)]
+#[serde(rename_all = "UPPERCASE")]
+pub enum RefundStatus {
+    Cancelled,
+    Rejected,
+    Refunded,
+    #[default]
+    Processing,
+}
+
+impl From<RefundStatus> for enums::RefundStatus {
+    fn from(item: RefundStatus) -> Self {
+        match item {
+            RefundStatus::Refunded => Self::Success,
+            RefundStatus::Cancelled | RefundStatus::Rejected => Self::Failure,
+            RefundStatus::Processing => Self::Pending,
+        }
+    }
+}
+
+#[derive(Default, Debug, Clone, Deserialize)]
+pub struct RefundResponse {
+    id: String,
+    status: RefundStatus,
+}
+
+impl TryFrom<types::RefundsResponseRouterData<api::Execute, RefundResponse>>
+    for types::RefundsRouterData<api::Execute>
+{
+    type Error = error_stack::Report<errors::ParsingError>;
+    fn try_from(
+        item: types::RefundsResponseRouterData<api::Execute, RefundResponse>,
+    ) -> Result<Self, Self::Error> {
+        let refund_status = enums::RefundStatus::from(item.response.status);
+        Ok(Self {
+            response: Ok(types::RefundsResponseData {
+                connector_refund_id: item.response.id.clone(),
+                refund_status,
+            }),
+            ..item.data
+        })
+    }
+}
+
+impl TryFrom<types::RefundsResponseRouterData<api::RSync, RefundResponse>>
+    for types::RefundsRouterData<api::RSync>
+{
+    type Error = error_stack::Report<errors::ParsingError>;
+    fn try_from(
+        item: types::RefundsResponseRouterData<api::RSync, RefundResponse>,
+    ) -> Result<Self, Self::Error> {
+        let refund_status = enums::RefundStatus::from(item.response.status);
+        Ok(Self {
+            response: Ok(types::RefundsResponseData {
+                connector_refund_id: item.response.id.clone(),
+                refund_status,
+            }),
+            ..item.data
+        })
+    }
+}
+
+impl From<&PaymentResponse> for enums::AttemptStatus {
+    fn from(item: &PaymentResponse) -> Self {
+        if item.payment.status == PaymentStatus::Cancelled {
+            Self::Voided
+        } else {
+            Self::VoidFailed
+        }
+    }
+}
+
+#[derive(Default, Debug, Deserialize, PartialEq)]
+#[serde(rename_all = "camelCase")]
+pub struct Error {
+    pub code: Option<String>,
+    pub property_name: Option<String>,
+    pub message: Option<String>,
+}
+
+#[derive(Default, Debug, Deserialize, PartialEq)]
+#[serde(rename_all = "camelCase")]
+pub struct ErrorResponse {
+    pub error_id: Option<String>,
+    pub errors: Vec<Error>,
+}
+
+#[derive(Debug, Eq, Hash, PartialEq)]
+pub enum CardProduct {
+    AmericanExpress,
+    Master,
+    Visa,
+    Discover,
+}
+
+impl CardProduct {
+    fn product_id(&self) -> u16 {
+        match *self {
+            Self::AmericanExpress => 2,
+            Self::Master => 3,
+            Self::Visa => 1,
+            Self::Discover => 128,
+        }
+    }
+}
diff --git a/crates/router/src/core/errors.rs b/crates/router/src/core/errors.rs
index 899969d55d4..f506f242dc9 100644
--- a/crates/router/src/core/errors.rs
+++ b/crates/router/src/core/errors.rs
@@ -242,6 +242,8 @@ pub enum ConnectorError {
     WebhookEventTypeNotFound,
     #[error("Incoming webhook event resource object not found")]
     WebhookResourceObjectNotFound,
+    #[error("Invalid Date/time format")]
+    InvalidDateFormat,
 }
 
 #[derive(Debug, thiserror::Error)]
diff --git a/crates/router/src/lib.rs b/crates/router/src/lib.rs
index e130a70707c..becbecdaeb5 100644
--- a/crates/router/src/lib.rs
+++ b/crates/router/src/lib.rs
@@ -47,6 +47,7 @@ pub mod headers {
     pub const AUTHORIZATION: &str = "Authorization";
     pub const ACCEPT: &str = "Accept";
     pub const X_API_VERSION: &str = "X-ApiVersion";
+    pub const DATE: &str = "Date";
 }
 
 pub mod pii {
diff --git a/crates/router/src/types/api.rs b/crates/router/src/types/api.rs
index a14ad31c1d6..0c21a8a45ca 100644
--- a/crates/router/src/types/api.rs
+++ b/crates/router/src/types/api.rs
@@ -152,6 +152,7 @@ impl ConnectorData {
             "payu" => Ok(Box::new(&connector::Payu)),
             "shift4" => Ok(Box::new(&connector::Shift4)),
             "stripe" => Ok(Box::new(&connector::Stripe)),
+            "worldline" => Ok(Box::new(&connector::Worldline)),
             "worldpay" => Ok(Box::new(&connector::Worldpay)),
             _ => Err(report!(errors::ConnectorError::InvalidConnectorName)
                 .attach_printable(format!("invalid connector name: {connector_name}")))
diff --git a/crates/router/tests/connectors/connector_auth.rs b/crates/router/tests/connectors/connector_auth.rs
index 2237f0994d2..9bb2cabdcc3 100644
--- a/crates/router/tests/connectors/connector_auth.rs
+++ b/crates/router/tests/connectors/connector_auth.rs
@@ -11,6 +11,7 @@ pub(crate) struct ConnectorAuthentication {
     pub payu: Option<BodyKey>,
     pub shift4: Option<HeaderKey>,
     pub worldpay: Option<HeaderKey>,
+    pub worldline: Option<SignatureKey>,
 }
 
 impl ConnectorAuthentication {
diff --git a/crates/router/tests/connectors/main.rs b/crates/router/tests/connectors/main.rs
index 3d5af329ed7..39bb16e7a4b 100644
--- a/crates/router/tests/connectors/main.rs
+++ b/crates/router/tests/connectors/main.rs
@@ -9,4 +9,5 @@ mod globalpay;
 mod payu;
 mod shift4;
 mod utils;
+mod worldline;
 mod worldpay;
diff --git a/crates/router/tests/connectors/sample_auth.toml b/crates/router/tests/connectors/sample_auth.toml
index 463e6460b54..a161b71c078 100644
--- a/crates/router/tests/connectors/sample_auth.toml
+++ b/crates/router/tests/connectors/sample_auth.toml
@@ -31,3 +31,7 @@ api_key = "MyApiKey"
 key1 = "MerchantID"
 api_secret = "MySecretKey"
 
+[worldline]
+key1 = "Merchant Id"
+api_key = "API Key"
+api_secret = "API Secret Key"
\ No newline at end of file
diff --git a/crates/router/tests/connectors/worldline.rs b/crates/router/tests/connectors/worldline.rs
new file mode 100644
index 00000000000..c8aa01549e1
--- /dev/null
+++ b/crates/router/tests/connectors/worldline.rs
@@ -0,0 +1,278 @@
+use api_models::payments::{Address, AddressDetails};
+use masking::Secret;
+use router::{
+    connector::Worldline,
+    types::{self, storage::enums, PaymentAddress},
+};
+
+use crate::{
+    connector_auth::ConnectorAuthentication,
+    utils::{self, ConnectorActions, PaymentInfo},
+};
+
+struct WorldlineTest;
+
+impl ConnectorActions for WorldlineTest {}
+impl utils::Connector for WorldlineTest {
+    fn get_data(&self) -> types::api::ConnectorData {
+        types::api::ConnectorData {
+            connector: Box::new(&Worldline),
+            connector_name: types::Connector::Worldline,
+            get_token: types::api::GetToken::Connector,
+        }
+    }
+
+    fn get_auth_token(&self) -> types::ConnectorAuthType {
+        types::ConnectorAuthType::from(
+            ConnectorAuthentication::new()
+                .worldline
+                .expect("Missing connector authentication configuration"),
+        )
+    }
+
+    fn get_name(&self) -> String {
+        String::from("worldline")
+    }
+}
+
+impl WorldlineTest {
+    fn get_payment_info() -> Option<PaymentInfo> {
+        Some(PaymentInfo {
+            address: Some(PaymentAddress {
+                billing: Some(Address {
+                    address: Some(AddressDetails {
+                        country: Some("US".to_string()),
+                        ..Default::default()
+                    }),
+                    phone: None,
+                }),
+                ..Default::default()
+            }),
+            auth_type: None,
+        })
+    }
+
+    fn get_payment_authorize_data(
+        card_number: &str,
+        card_exp_month: &str,
+        card_exp_year: &str,
+        card_cvc: &str,
+        capture_method: enums::CaptureMethod,
+    ) -> Option<types::PaymentsAuthorizeData> {
+        Some(types::PaymentsAuthorizeData {
+            amount: 3500,
+            currency: enums::Currency::USD,
+            payment_method_data: types::api::PaymentMethod::Card(types::api::CCard {
+                card_number: Secret::new(card_number.to_string()),
+                card_exp_month: Secret::new(card_exp_month.to_string()),
+                card_exp_year: Secret::new(card_exp_year.to_string()),
+                card_holder_name: Secret::new("John Doe".to_string()),
+                card_cvc: Secret::new(card_cvc.to_string()),
+            }),
+            confirm: true,
+            statement_descriptor_suffix: None,
+            setup_future_usage: None,
+            mandate_id: None,
+            off_session: None,
+            setup_mandate_details: None,
+            capture_method: Some(capture_method),
+            browser_info: None,
+            order_details: None,
+            email: None,
+        })
+    }
+}
+
+#[actix_web::test]
+async fn should_requires_manual_authorization() {
+    let authorize_data = WorldlineTest::get_payment_authorize_data(
+        "4012000033330026",
+        "10",
+        "2025",
+        "123",
+        enums::CaptureMethod::Manual,
+    );
+    let response = WorldlineTest {}
+        .make_payment(authorize_data, WorldlineTest::get_payment_info())
+        .await;
+    assert_eq!(response.status, enums::AttemptStatus::Authorizing);
+}
+
+#[actix_web::test]
+async fn should_auto_authorize_and_request_capture() {
+    let authorize_data = WorldlineTest::get_payment_authorize_data(
+        "4012000033330026",
+        "10",
+        "2025",
+        "123",
+        enums::CaptureMethod::Automatic,
+    );
+    let response = WorldlineTest {}
+        .make_payment(authorize_data, WorldlineTest::get_payment_info())
+        .await;
+    assert_eq!(response.status, enums::AttemptStatus::CaptureInitiated);
+}
+
+#[actix_web::test]
+async fn should_fail_payment_for_invalid_cvc() {
+    let authorize_data = WorldlineTest::get_payment_authorize_data(
+        "4012000033330026",
+        "10",
+        "2025",
+        "",
+        enums::CaptureMethod::Automatic,
+    );
+    let response = WorldlineTest {}
+        .make_payment(authorize_data, WorldlineTest::get_payment_info())
+        .await;
+    assert_eq!(
+        response.response.unwrap_err().message,
+        "NULL VALUE NOT ALLOWED FOR cardPaymentMethodSpecificInput.card.cvv".to_string(),
+    );
+}
+
+#[actix_web::test]
+async fn should_sync_manual_auth_payment() {
+    let connector = WorldlineTest {};
+    let authorize_data = WorldlineTest::get_payment_authorize_data(
+        "4012000033330026",
+        "10",
+        "2025",
+        "123",
+        enums::CaptureMethod::Manual,
+    );
+    let response = connector
+        .make_payment(authorize_data, WorldlineTest::get_payment_info())
+        .await;
+    assert_eq!(response.status, enums::AttemptStatus::Authorizing);
+    let connector_payment_id = utils::get_connector_transaction_id(response).unwrap_or_default();
+    let sync_response = connector
+        .sync_payment(
+            Some(types::PaymentsSyncData {
+                connector_transaction_id: router::types::ResponseId::ConnectorTransactionId(
+                    connector_payment_id,
+                ),
+                encoded_data: None,
+            }),
+            None,
+        )
+        .await;
+    assert_eq!(sync_response.status, enums::AttemptStatus::Authorizing);
+}
+
+#[actix_web::test]
+async fn should_sync_auto_auth_payment() {
+    let connector = WorldlineTest {};
+    let authorize_data = WorldlineTest::get_payment_authorize_data(
+        "4012000033330026",
+        "10",
+        "2025",
+        "123",
+        enums::CaptureMethod::Automatic,
+    );
+    let response = connector
+        .make_payment(authorize_data, WorldlineTest::get_payment_info())
+        .await;
+    assert_eq!(response.status, enums::AttemptStatus::CaptureInitiated);
+    let connector_payment_id = utils::get_connector_transaction_id(response).unwrap_or_default();
+    let sync_response = connector
+        .sync_payment(
+            Some(types::PaymentsSyncData {
+                connector_transaction_id: router::types::ResponseId::ConnectorTransactionId(
+                    connector_payment_id,
+                ),
+                encoded_data: None,
+            }),
+            None,
+        )
+        .await;
+    assert_eq!(sync_response.status, enums::AttemptStatus::CaptureInitiated);
+}
+
+#[actix_web::test]
+async fn should_fail_capture_payment() {
+    let capture_response = WorldlineTest {}
+        .capture_payment("123456789".to_string(), None, None)
+        .await;
+    assert_eq!(
+        capture_response.response.unwrap_err().message,
+        "Something went wrong.".to_string()
+    );
+}
+
+#[actix_web::test]
+async fn should_cancel_unauthorized_payment() {
+    let connector = WorldlineTest {};
+    let authorize_data = WorldlineTest::get_payment_authorize_data(
+        "4012000033330026",
+        "10",
+        "2025",
+        "123",
+        enums::CaptureMethod::Manual,
+    );
+    let response = connector
+        .make_payment(authorize_data, WorldlineTest::get_payment_info())
+        .await;
+    assert_eq!(response.status, enums::AttemptStatus::Authorizing);
+    let connector_payment_id = utils::get_connector_transaction_id(response).unwrap_or_default();
+    let cancel_response = connector
+        .void_payment(connector_payment_id, None, None)
+        .await;
+    assert_eq!(cancel_response.status, enums::AttemptStatus::Voided);
+}
+
+#[actix_web::test]
+async fn should_cancel_uncaptured_payment() {
+    let connector = WorldlineTest {};
+    let authorize_data = WorldlineTest::get_payment_authorize_data(
+        "4012000033330026",
+        "10",
+        "2025",
+        "123",
+        enums::CaptureMethod::Automatic,
+    );
+    let response = connector
+        .make_payment(authorize_data, WorldlineTest::get_payment_info())
+        .await;
+    assert_eq!(response.status, enums::AttemptStatus::CaptureInitiated);
+    let connector_payment_id = utils::get_connector_transaction_id(response).unwrap_or_default();
+    let cancel_response = connector
+        .void_payment(connector_payment_id, None, None)
+        .await;
+    assert_eq!(cancel_response.status, enums::AttemptStatus::Voided);
+}
+
+#[actix_web::test]
+async fn should_fail_cancel_with_invalid_payment_id() {
+    let response = WorldlineTest {}
+        .void_payment("123456789".to_string(), None, None)
+        .await;
+    assert_eq!(
+        response.response.unwrap_err().message,
+        "UNKNOWN_PAYMENT_ID".to_string(),
+    );
+}
+
+#[actix_web::test]
+async fn should_fail_refund_with_invalid_payment_status() {
+    let connector = WorldlineTest {};
+    let authorize_data = WorldlineTest::get_payment_authorize_data(
+        "4012000033330026",
+        "10",
+        "2025",
+        "123",
+        enums::CaptureMethod::Manual,
+    );
+    let response = connector
+        .make_payment(authorize_data, WorldlineTest::get_payment_info())
+        .await;
+    assert_eq!(response.status, enums::AttemptStatus::Authorizing);
+    let connector_payment_id = utils::get_connector_transaction_id(response).unwrap_or_default();
+    let refund_response = connector
+        .refund_payment(connector_payment_id, None, None)
+        .await;
+    assert_eq!(
+        refund_response.response.unwrap_err().message,
+        "ORDER WITHOUT REFUNDABLE PAYMENTS".to_string(),
+    );
+}

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

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

## Description
Adding support for [worldline](https://epayments-api.developer-ingenico.com/?paymentPlatform=GLOBALCOLLECT) payment gateway

Payment Method: card
Supported payment flows

[Authorize](https://epayments-api.developer-ingenico.com/s2sapi/v1/en_US/java/payments/create.html?paymentPlatform=ALL#payments-create)
[PSync](https://epayments-api.developer-ingenico.com/s2sapi/v1/en_US/java/payments/get.html?paymentPlatform=ALL#payments-get)
[Refund](https://epayments-api.developer-ingenico.com/s2sapi/v1/en_US/java/payments/refund.html?paymentPlatform=ALL#payments-refund)
[RSync](https://epayments-api.developer-ingenico.com/s2sapi/v1/en_US/java/refunds/get.html?paymentPlatform=ALL#refunds-get-response-200)
[Cancel](https://epayments-api.developer-ingenico.com/s2sapi/v1/en_US/java/payments/cancel.html?paymentPlatform=ALL#payments-cancel)


### 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 new connector [worldline](https://epayments-api.developer-ingenico.com/?paymentPlatform=GLOBALCOLLECT)

Resolves #233.

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

<img width="1128" alt="Screenshot 2023-01-13 at 4 58 59 PM" src="https://user-images.githubusercontent.com/20727986/212310910-f840a6fd-4704-4aa7-9464-a13edf9b615c.png">

<img width="866" alt="Screenshot 2023-01-16 at 1 07 51 PM" src="https://user-images.githubusercontent.com/20727986/212622915-8ebe92bd-7cc1-4878-856d-523d0a530ce0.png">


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

- [x] I formatted the code `cargo +nightly fmt --all`
- [x] I addressed lints thrown by `cargo clippy`
- [x] I reviewed submitted code
- [x] I added unit tests for my changes where possible
- [ ] I added a [CHANGELOG](/CHANGELOG.md) entry if applicable
