Bug: [FEATURE] integrate 3DS for Worldpay connector



### Feature Description

WorldPay offers 3DS capabilities for a card txn. Same needs to be integrated in HyperSwitch.

Flow - https://developer.worldpay.com/products/access/3ds/web

### Possible Implementation

Implementing 3DS for WorldPay requires one additional step of running DDC flows. Flow can be looked at in WorldPay's dev docs

1. Redirect to Worldpay's DDC
2. Redirect to Wordlpay's 3DS redirection form
3. Capture details in HyperSwitch's `/redirect/complete` flow
4. Perform a PSync (optional)

Reference
https://developer.worldpay.com/products/access/3ds/web

### Have you spent some time checking if this feature request has been raised before?

- [X] I checked and didn't find a similar issue

### Have you read the Contributing Guidelines?

- [X] I have read the [Contributing Guidelines](https://github.com/juspay/hyperswitch/blob/main/docs/CONTRIBUTING.md)

### Are you willing to submit a PR?

None
diff --git a/crates/diesel_models/src/query/payment_attempt.rs b/crates/diesel_models/src/query/payment_attempt.rs
index 85c69824892..0627fa0048a 100644
--- a/crates/diesel_models/src/query/payment_attempt.rs
+++ b/crates/diesel_models/src/query/payment_attempt.rs
@@ -171,11 +171,23 @@ impl PaymentAttempt {
         merchant_id: &common_utils::id_type::MerchantId,
         connector_txn_id: &str,
     ) -> StorageResult<Self> {
+        let (txn_id, txn_data) = common_utils::types::ConnectorTransactionId::form_id_and_data(
+            connector_txn_id.to_string(),
+        );
+        let connector_transaction_id = txn_id
+            .get_txn_id(txn_data.as_ref())
+            .change_context(DatabaseError::Others)
+            .attach_printable_lazy(|| {
+                format!(
+                    "Failed to retrieve txn_id for ({:?}, {:?})",
+                    txn_id, txn_data
+                )
+            })?;
         generics::generic_find_one::<<Self as HasTable>::Table, _, _>(
             conn,
             dsl::merchant_id
                 .eq(merchant_id.to_owned())
-                .and(dsl::connector_transaction_id.eq(connector_txn_id.to_owned())),
+                .and(dsl::connector_transaction_id.eq(connector_transaction_id.to_owned())),
         )
         .await
     }
diff --git a/crates/hyperswitch_domain_models/src/router_response_types.rs b/crates/hyperswitch_domain_models/src/router_response_types.rs
index eca56b8c866..6682ac1ad44 100644
--- a/crates/hyperswitch_domain_models/src/router_response_types.rs
+++ b/crates/hyperswitch_domain_models/src/router_response_types.rs
@@ -163,6 +163,12 @@ pub enum RedirectForm {
     Mifinity {
         initialization_token: String,
     },
+    WorldpayDDCForm {
+        endpoint: url::Url,
+        method: Method,
+        form_fields: HashMap<String, String>,
+        collection_id: Option<String>,
+    },
 }
 
 impl From<(url::Url, Method)> for RedirectForm {
diff --git a/crates/router/src/connector/worldpay.rs b/crates/router/src/connector/worldpay.rs
index 7a4c4c5da97..97da36f39b3 100644
--- a/crates/router/src/connector/worldpay.rs
+++ b/crates/router/src/connector/worldpay.rs
@@ -624,6 +624,113 @@ impl ConnectorIntegration<api::Authorize, types::PaymentsAuthorizeData, types::P
     }
 }
 
+impl api::PaymentsCompleteAuthorize for Worldpay {}
+impl
+    ConnectorIntegration<
+        api::CompleteAuthorize,
+        types::CompleteAuthorizeData,
+        types::PaymentsResponseData,
+    > for Worldpay
+{
+    fn get_headers(
+        &self,
+        req: &types::PaymentsCompleteAuthorizeRouterData,
+        connectors: &settings::Connectors,
+    ) -> CustomResult<Vec<(String, request::Maskable<String>)>, errors::ConnectorError> {
+        self.build_headers(req, connectors)
+    }
+
+    fn get_content_type(&self) -> &'static str {
+        self.common_get_content_type()
+    }
+
+    fn get_url(
+        &self,
+        req: &types::PaymentsCompleteAuthorizeRouterData,
+        connectors: &settings::Connectors,
+    ) -> CustomResult<String, errors::ConnectorError> {
+        let connector_payment_id = req
+            .request
+            .connector_transaction_id
+            .clone()
+            .ok_or(errors::ConnectorError::MissingConnectorTransactionID)?;
+        let stage = match req.status {
+            enums::AttemptStatus::DeviceDataCollectionPending => "3dsDeviceData".to_string(),
+            _ => "3dsChallenges".to_string(),
+        };
+        Ok(format!(
+            "{}api/payments/{connector_payment_id}/{stage}",
+            self.base_url(connectors),
+        ))
+    }
+
+    fn get_request_body(
+        &self,
+        req: &types::PaymentsCompleteAuthorizeRouterData,
+        _connectors: &settings::Connectors,
+    ) -> CustomResult<RequestContent, errors::ConnectorError> {
+        let req_obj = WorldpayCompleteAuthorizationRequest::try_from(req)?;
+        Ok(RequestContent::Json(Box::new(req_obj)))
+    }
+
+    fn build_request(
+        &self,
+        req: &types::PaymentsCompleteAuthorizeRouterData,
+        connectors: &settings::Connectors,
+    ) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
+        let request = services::RequestBuilder::new()
+            .method(services::Method::Post)
+            .url(&types::PaymentsCompleteAuthorizeType::get_url(
+                self, req, connectors,
+            )?)
+            .headers(types::PaymentsCompleteAuthorizeType::get_headers(
+                self, req, connectors,
+            )?)
+            .set_body(types::PaymentsCompleteAuthorizeType::get_request_body(
+                self, req, connectors,
+            )?)
+            .build();
+        Ok(Some(request))
+    }
+
+    fn handle_response(
+        &self,
+        data: &types::PaymentsCompleteAuthorizeRouterData,
+        event_builder: Option<&mut ConnectorEvent>,
+        res: Response,
+    ) -> CustomResult<types::PaymentsCompleteAuthorizeRouterData, errors::ConnectorError> {
+        let response: WorldpayPaymentsResponse = res
+            .response
+            .parse_struct("WorldpayPaymentsResponse")
+            .change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
+        event_builder.map(|i| i.set_response_body(&response));
+        router_env::logger::info!(connector_response=?response);
+        let optional_correlation_id = res.headers.and_then(|headers| {
+            headers
+                .get("WP-CorrelationId")
+                .and_then(|header_value| header_value.to_str().ok())
+                .map(|id| id.to_string())
+        });
+        types::RouterData::foreign_try_from((
+            types::ResponseRouterData {
+                response,
+                data: data.clone(),
+                http_code: res.status_code,
+            },
+            optional_correlation_id,
+        ))
+        .change_context(errors::ConnectorError::ResponseHandlingFailed)
+    }
+
+    fn get_error_response(
+        &self,
+        res: Response,
+        event_builder: Option<&mut ConnectorEvent>,
+    ) -> CustomResult<ErrorResponse, errors::ConnectorError> {
+        self.build_error_response(res, event_builder)
+    }
+}
+
 impl api::Refund for Worldpay {}
 impl api::RefundExecute for Worldpay {}
 impl api::RefundSync for Worldpay {}
@@ -900,3 +1007,20 @@ impl api::IncomingWebhook for Worldpay {
         Ok(Box::new(psync_body))
     }
 }
+
+impl services::ConnectorRedirectResponse for Worldpay {
+    fn get_flow_type(
+        &self,
+        _query_params: &str,
+        _json_payload: Option<serde_json::Value>,
+        action: services::PaymentAction,
+    ) -> CustomResult<enums::CallConnectorAction, errors::ConnectorError> {
+        match action {
+            services::PaymentAction::CompleteAuthorize => Ok(enums::CallConnectorAction::Trigger),
+            services::PaymentAction::PSync
+            | services::PaymentAction::PaymentAuthenticateCompleteAuthorize => {
+                Ok(enums::CallConnectorAction::Avoid)
+            }
+        }
+    }
+}
diff --git a/crates/router/src/connector/worldpay/requests.rs b/crates/router/src/connector/worldpay/requests.rs
index 3d0be891ebb..b0fa85a64c3 100644
--- a/crates/router/src/connector/worldpay/requests.rs
+++ b/crates/router/src/connector/worldpay/requests.rs
@@ -31,6 +31,8 @@ pub struct Instruction {
     pub value: PaymentValue,
     #[serde(skip_serializing_if = "Option::is_none")]
     pub debt_repayment: Option<bool>,
+    #[serde(rename = "threeDS")]
+    pub three_ds: Option<ThreeDSRequest>,
 }
 
 #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
@@ -187,6 +189,44 @@ pub struct AutoSettlement {
     pub auto: bool,
 }
 
+#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct ThreeDSRequest {
+    #[serde(rename = "type")]
+    pub three_ds_type: String,
+    pub mode: String,
+    pub device_data: ThreeDSRequestDeviceData,
+    pub challenge: ThreeDSRequestChallenge,
+}
+
+#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct ThreeDSRequestDeviceData {
+    pub accept_header: String,
+    pub user_agent_header: String,
+    pub browser_language: Option<String>,
+    pub browser_screen_width: Option<u32>,
+    pub browser_screen_height: Option<u32>,
+    pub browser_color_depth: Option<String>,
+    pub time_zone: Option<String>,
+    pub browser_java_enabled: Option<bool>,
+    pub browser_javascript_enabled: Option<bool>,
+    pub channel: Option<ThreeDSRequestChannel>,
+}
+
+#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
+#[serde(rename_all = "lowercase")]
+pub enum ThreeDSRequestChannel {
+    Browser,
+    Native,
+}
+
+#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct ThreeDSRequestChallenge {
+    pub return_url: String,
+}
+
 #[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)]
 #[serde(rename_all = "lowercase")]
 pub enum PaymentMethod {
@@ -237,3 +277,10 @@ pub struct WorldpayPartialRequest {
     pub value: PaymentValue,
     pub reference: String,
 }
+
+#[derive(Clone, Debug, Default, Serialize, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct WorldpayCompleteAuthorizationRequest {
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub collection_reference: Option<String>,
+}
diff --git a/crates/router/src/connector/worldpay/response.rs b/crates/router/src/connector/worldpay/response.rs
index 0a7f690c3aa..edc3c26948f 100644
--- a/crates/router/src/connector/worldpay/response.rs
+++ b/crates/router/src/connector/worldpay/response.rs
@@ -1,6 +1,7 @@
 use error_stack::ResultExt;
 use masking::Secret;
 use serde::{Deserialize, Serialize};
+use url::Url;
 
 use super::requests::*;
 use crate::core::errors;
@@ -10,7 +11,7 @@ pub struct WorldpayPaymentsResponse {
     pub outcome: PaymentOutcome,
     pub transaction_reference: Option<String>,
     #[serde(flatten)]
-    pub other_fields: WorldpayPaymentResponseFields,
+    pub other_fields: Option<WorldpayPaymentResponseFields>,
 }
 
 #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
@@ -20,13 +21,13 @@ pub enum WorldpayPaymentResponseFields {
     DDCResponse(DDCResponse),
     FraudHighRisk(FraudHighRiskResponse),
     RefusedResponse(RefusedResponse),
+    ThreeDsChallenged(ThreeDsChallengedResponse),
 }
 
 #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
 #[serde(rename_all = "camelCase")]
 pub struct AuthorizedResponse {
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub payment_instrument: Option<PaymentsResPaymentInstrument>,
+    pub payment_instrument: PaymentsResPaymentInstrument,
     #[serde(skip_serializing_if = "Option::is_none")]
     pub issuer: Option<Issuer>,
     #[serde(skip_serializing_if = "Option::is_none")]
@@ -67,6 +68,34 @@ pub struct ThreeDsResponse {
 
 #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
 #[serde(rename_all = "camelCase")]
+pub struct ThreeDsChallengedResponse {
+    pub authentication: AuthenticationResponse,
+    pub challenge: ThreeDsChallenge,
+    #[serde(rename = "_actions")]
+    pub actions: CompleteThreeDsActionLink,
+}
+
+#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
+pub struct AuthenticationResponse {
+    pub version: String,
+}
+
+#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
+pub struct ThreeDsChallenge {
+    pub reference: String,
+    pub url: Url,
+    pub jwt: Secret<String>,
+    pub payload: Secret<String>,
+}
+
+#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
+pub struct CompleteThreeDsActionLink {
+    #[serde(rename = "complete3dsChallenge")]
+    pub complete_three_ds_challenge: ActionLink,
+}
+
+#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
+#[serde(rename_all = "lowercase")]
 pub enum IssuerResponse {
     Challenged,
     Frictionless,
@@ -82,16 +111,15 @@ pub struct DDCResponse {
 
 #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
 pub struct DDCToken {
-    pub jwt: String,
-    pub url: String,
-    pub bin: String,
+    pub jwt: Secret<String>,
+    pub url: Url,
+    pub bin: Secret<String>,
 }
 
 #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
 pub struct DDCActionLink {
     #[serde(rename = "supply3dsDeviceData")]
     supply_ddc_data: ActionLink,
-    method: String,
 }
 
 #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
@@ -105,11 +133,32 @@ pub enum PaymentOutcome {
     FraudHighRisk,
     #[serde(alias = "3dsDeviceDataRequired")]
     ThreeDsDeviceDataRequired,
-    ThreeDsChallenged,
     SentForCancellation,
     #[serde(alias = "3dsAuthenticationFailed")]
     ThreeDsAuthenticationFailed,
     SentForPartialRefund,
+    #[serde(alias = "3dsChallenged")]
+    ThreeDsChallenged,
+    #[serde(alias = "3dsUnavailable")]
+    ThreeDsUnavailable,
+}
+
+impl std::fmt::Display for PaymentOutcome {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        match self {
+            Self::Authorized => write!(f, "authorized"),
+            Self::Refused => write!(f, "refused"),
+            Self::SentForSettlement => write!(f, "sentForSettlement"),
+            Self::SentForRefund => write!(f, "sentForRefund"),
+            Self::FraudHighRisk => write!(f, "fraudHighRisk"),
+            Self::ThreeDsDeviceDataRequired => write!(f, "3dsDeviceDataRequired"),
+            Self::SentForCancellation => write!(f, "sentForCancellation"),
+            Self::ThreeDsAuthenticationFailed => write!(f, "3dsAuthenticationFailed"),
+            Self::SentForPartialRefund => write!(f, "sentForPartialRefund"),
+            Self::ThreeDsChallenged => write!(f, "3dsChallenged"),
+            Self::ThreeDsUnavailable => write!(f, "3dsUnavailable"),
+        }
+    }
 }
 
 #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
@@ -202,30 +251,33 @@ pub fn get_resource_id<T, F>(
 where
     F: Fn(String) -> T,
 {
-    let reference_id = match response.other_fields {
-        WorldpayPaymentResponseFields::AuthorizedResponse(res) => res
-            .links
-            .as_ref()
-            .and_then(|link| link.self_link.href.rsplit_once('/'))
-            .map(|(_, h)| urlencoding::decode(h))
-            .transpose()
-            .change_context(errors::ConnectorError::ResponseHandlingFailed)?
-            .map(|s| transform_fn(s.into_owned())),
-        WorldpayPaymentResponseFields::DDCResponse(res) => res
-            .actions
-            .supply_ddc_data
-            .href
-            .split('/')
-            .rev()
-            .nth(1)
-            .map(urlencoding::decode)
-            .transpose()
-            .change_context(errors::ConnectorError::ResponseHandlingFailed)?
-            .map(|s| transform_fn(s.into_owned())),
-        WorldpayPaymentResponseFields::FraudHighRisk(_) => None,
-        WorldpayPaymentResponseFields::RefusedResponse(_) => None,
-    };
-    reference_id
+    let optional_reference_id = response
+        .other_fields
+        .as_ref()
+        .and_then(|other_fields| match other_fields {
+            WorldpayPaymentResponseFields::AuthorizedResponse(res) => res
+                .links
+                .as_ref()
+                .and_then(|link| link.self_link.href.rsplit_once('/').map(|(_, h)| h)),
+            WorldpayPaymentResponseFields::DDCResponse(res) => {
+                res.actions.supply_ddc_data.href.split('/').nth_back(1)
+            }
+            WorldpayPaymentResponseFields::ThreeDsChallenged(res) => res
+                .actions
+                .complete_three_ds_challenge
+                .href
+                .split('/')
+                .nth_back(1),
+            WorldpayPaymentResponseFields::FraudHighRisk(_)
+            | WorldpayPaymentResponseFields::RefusedResponse(_) => None,
+        })
+        .map(|href| {
+            urlencoding::decode(href)
+                .map(|s| transform_fn(s.into_owned()))
+                .change_context(errors::ConnectorError::ResponseHandlingFailed)
+        })
+        .transpose()?;
+    optional_reference_id
         .or_else(|| connector_transaction_id.map(transform_fn))
         .ok_or_else(|| {
             errors::ConnectorError::MissingRequiredField {
@@ -256,8 +308,8 @@ impl Issuer {
 #[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)]
 #[serde(rename_all = "camelCase")]
 pub struct PaymentsResPaymentInstrument {
-    #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
-    pub payment_instrument_type: Option<String>,
+    #[serde(rename = "type")]
+    pub payment_instrument_type: String,
     pub card_bin: Option<String>,
     pub last_four: Option<String>,
     pub expiry_date: Option<ExpiryDate>,
@@ -268,22 +320,6 @@ pub struct PaymentsResPaymentInstrument {
     pub payment_account_reference: Option<String>,
 }
 
-impl PaymentsResPaymentInstrument {
-    pub fn new() -> Self {
-        Self {
-            payment_instrument_type: None,
-            card_bin: None,
-            last_four: None,
-            category: None,
-            expiry_date: None,
-            card_brand: None,
-            funding_type: None,
-            issuer_name: None,
-            payment_account_reference: None,
-        }
-    }
-}
-
 #[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)]
 #[serde(rename_all = "camelCase")]
 pub struct RiskFactorsInner {
diff --git a/crates/router/src/connector/worldpay/transformers.rs b/crates/router/src/connector/worldpay/transformers.rs
index a0f2bfd2508..a28d3bff7ed 100644
--- a/crates/router/src/connector/worldpay/transformers.rs
+++ b/crates/router/src/connector/worldpay/transformers.rs
@@ -1,17 +1,20 @@
+use std::collections::HashMap;
+
 use api_models::payments::Address;
 use base64::Engine;
 use common_utils::{errors::CustomResult, ext_traits::OptionExt, pii, types::MinorUnit};
 use diesel_models::enums;
 use error_stack::ResultExt;
-use hyperswitch_connectors::utils::RouterData;
+use hyperswitch_connectors::utils::{PaymentsAuthorizeRequestData, RouterData};
 use masking::{ExposeInterface, PeekInterface, Secret};
 use serde::{Deserialize, Serialize};
 
 use super::{requests::*, response::*};
 use crate::{
-    connector::utils,
+    connector::utils::{self, AddressData},
     consts,
     core::errors,
+    services,
     types::{
         self, domain, transformers::ForeignTryFrom, PaymentsAuthorizeData, PaymentsResponseData,
     },
@@ -65,49 +68,40 @@ impl TryFrom<&Option<pii::SecretSerdeValue>> for WorldpayConnectorMetadataObject
 fn fetch_payment_instrument(
     payment_method: domain::PaymentMethodData,
     billing_address: Option<&Address>,
-    auth_type: enums::AuthenticationType,
 ) -> CustomResult<PaymentInstrument, errors::ConnectorError> {
     match payment_method {
-        domain::PaymentMethodData::Card(card) => {
-            if auth_type == enums::AuthenticationType::ThreeDs {
-                return Err(errors::ConnectorError::NotImplemented(
-                    "ThreeDS flow through worldpay".to_string(),
-                )
-                .into());
-            }
-            Ok(PaymentInstrument::Card(CardPayment {
-                payment_type: PaymentType::Plain,
-                expiry_date: ExpiryDate {
-                    month: utils::CardData::get_expiry_month_as_i8(&card)?,
-                    year: utils::CardData::get_expiry_year_as_i32(&card)?,
-                },
-                card_number: card.card_number,
-                cvc: card.card_cvc,
-                card_holder_name: card.nick_name,
-                billing_address: if let Some(address) =
-                    billing_address.and_then(|addr| addr.address.clone())
-                {
-                    Some(BillingAddress {
-                        address1: address.line1,
-                        address2: address.line2,
-                        address3: address.line3,
-                        city: address.city,
-                        state: address.state,
-                        postal_code: address.zip.get_required_value("zip").change_context(
-                            errors::ConnectorError::MissingRequiredField { field_name: "zip" },
-                        )?,
-                        country_code: address
-                            .country
-                            .get_required_value("country_code")
-                            .change_context(errors::ConnectorError::MissingRequiredField {
-                                field_name: "country_code",
-                            })?,
-                    })
-                } else {
-                    None
-                },
-            }))
-        }
+        domain::PaymentMethodData::Card(card) => Ok(PaymentInstrument::Card(CardPayment {
+            payment_type: PaymentType::Plain,
+            expiry_date: ExpiryDate {
+                month: utils::CardData::get_expiry_month_as_i8(&card)?,
+                year: utils::CardData::get_expiry_year_as_i32(&card)?,
+            },
+            card_number: card.card_number,
+            cvc: card.card_cvc,
+            card_holder_name: billing_address.and_then(|address| address.get_optional_full_name()),
+            billing_address: if let Some(address) =
+                billing_address.and_then(|addr| addr.address.clone())
+            {
+                Some(BillingAddress {
+                    address1: address.line1,
+                    address2: address.line2,
+                    address3: address.line3,
+                    city: address.city,
+                    state: address.state,
+                    postal_code: address.zip.get_required_value("zip").change_context(
+                        errors::ConnectorError::MissingRequiredField { field_name: "zip" },
+                    )?,
+                    country_code: address
+                        .country
+                        .get_required_value("country_code")
+                        .change_context(errors::ConnectorError::MissingRequiredField {
+                            field_name: "country_code",
+                        })?,
+                })
+            } else {
+                None
+            },
+        })),
         domain::PaymentMethodData::Wallet(wallet) => match wallet {
             domain::WalletData::GooglePay(data) => {
                 Ok(PaymentInstrument::Googlepay(WalletPayment {
@@ -230,6 +224,53 @@ impl
                 config: "metadata.merchant_name",
             },
         )?;
+        let three_ds = match item.router_data.auth_type {
+            enums::AuthenticationType::ThreeDs => {
+                let browser_info = item
+                    .router_data
+                    .request
+                    .browser_info
+                    .clone()
+                    .get_required_value("browser_info")
+                    .change_context(errors::ConnectorError::MissingRequiredField {
+                        field_name: "browser_info",
+                    })?;
+                let accept_header = browser_info
+                    .accept_header
+                    .get_required_value("accept_header")
+                    .change_context(errors::ConnectorError::MissingRequiredField {
+                        field_name: "accept_header",
+                    })?;
+                let user_agent_header = browser_info
+                    .user_agent
+                    .get_required_value("user_agent")
+                    .change_context(errors::ConnectorError::MissingRequiredField {
+                        field_name: "user_agent",
+                    })?;
+                Some(ThreeDSRequest {
+                    three_ds_type: "integrated".to_string(),
+                    mode: "always".to_string(),
+                    device_data: ThreeDSRequestDeviceData {
+                        accept_header,
+                        user_agent_header,
+                        browser_language: browser_info.language.clone(),
+                        browser_screen_width: browser_info.screen_width,
+                        browser_screen_height: browser_info.screen_height,
+                        browser_color_depth: browser_info
+                            .color_depth
+                            .map(|depth| depth.to_string()),
+                        time_zone: browser_info.time_zone.map(|tz| tz.to_string()),
+                        browser_java_enabled: browser_info.java_enabled,
+                        browser_javascript_enabled: browser_info.java_script_enabled,
+                        channel: Some(ThreeDSRequestChannel::Browser),
+                    },
+                    challenge: ThreeDSRequestChallenge {
+                        return_url: item.router_data.request.get_complete_authorize_url()?,
+                    },
+                })
+            }
+            _ => None,
+        };
         Ok(Self {
             instruction: Instruction {
                 settlement: item
@@ -252,7 +293,6 @@ impl
                 payment_instrument: fetch_payment_instrument(
                     item.router_data.request.payment_method_data.clone(),
                     item.router_data.get_optional_billing(),
-                    item.router_data.auth_type,
                 )?,
                 narrative: InstructionNarrative {
                     line1: merchant_name.expose(),
@@ -262,6 +302,7 @@ impl
                     currency: item.router_data.request.currency,
                 },
                 debt_repayment: None,
+                three_ds,
             },
             merchant: Merchant {
                 entity: entity_id.clone(),
@@ -321,6 +362,7 @@ impl From<PaymentOutcome> for enums::AttemptStatus {
                 Self::AutoRefunded
             }
             PaymentOutcome::Refused | PaymentOutcome::FraudHighRisk => Self::Failure,
+            PaymentOutcome::ThreeDsUnavailable => Self::AuthenticationFailed,
         }
     }
 }
@@ -363,42 +405,105 @@ impl From<EventType> for enums::RefundStatus {
     }
 }
 
-impl
+impl<F, T>
     ForeignTryFrom<(
-        types::PaymentsResponseRouterData<WorldpayPaymentsResponse>,
+        types::ResponseRouterData<F, WorldpayPaymentsResponse, T, PaymentsResponseData>,
         Option<String>,
-    )> for types::PaymentsAuthorizeRouterData
+    )> for types::RouterData<F, T, PaymentsResponseData>
 {
     type Error = error_stack::Report<errors::ConnectorError>;
     fn foreign_try_from(
         item: (
-            types::PaymentsResponseRouterData<WorldpayPaymentsResponse>,
+            types::ResponseRouterData<F, WorldpayPaymentsResponse, T, PaymentsResponseData>,
             Option<String>,
         ),
     ) -> Result<Self, Self::Error> {
         let (router_data, optional_correlation_id) = item;
-        let description = match router_data.response.other_fields {
-            WorldpayPaymentResponseFields::AuthorizedResponse(ref res) => res.description.clone(),
-            WorldpayPaymentResponseFields::DDCResponse(_)
-            | WorldpayPaymentResponseFields::FraudHighRisk(_)
-            | WorldpayPaymentResponseFields::RefusedResponse(_) => None,
+        let (description, redirection_data) = router_data
+            .response
+            .other_fields
+            .as_ref()
+            .map(|other_fields| match other_fields {
+                WorldpayPaymentResponseFields::AuthorizedResponse(res) => {
+                    (res.description.clone(), None)
+                }
+                WorldpayPaymentResponseFields::DDCResponse(res) => (
+                    None,
+                    Some(services::RedirectForm::WorldpayDDCForm {
+                        endpoint: res.device_data_collection.url.clone(),
+                        method: common_utils::request::Method::Post,
+                        collection_id: Some("SessionId".to_string()),
+                        form_fields: HashMap::from([
+                            (
+                                "Bin".to_string(),
+                                res.device_data_collection.bin.clone().expose(),
+                            ),
+                            (
+                                "JWT".to_string(),
+                                res.device_data_collection.jwt.clone().expose(),
+                            ),
+                        ]),
+                    }),
+                ),
+                WorldpayPaymentResponseFields::ThreeDsChallenged(res) => (
+                    None,
+                    Some(services::RedirectForm::Form {
+                        endpoint: res.challenge.url.to_string(),
+                        method: common_utils::request::Method::Post,
+                        form_fields: HashMap::from([(
+                            "JWT".to_string(),
+                            res.challenge.jwt.clone().expose(),
+                        )]),
+                    }),
+                ),
+                WorldpayPaymentResponseFields::FraudHighRisk(_)
+                | WorldpayPaymentResponseFields::RefusedResponse(_) => (None, None),
+            })
+            .unwrap_or((None, None));
+        let worldpay_status = router_data.response.outcome.clone();
+        let optional_reason = match worldpay_status {
+            PaymentOutcome::ThreeDsAuthenticationFailed => {
+                Some("3DS authentication failed from issuer".to_string())
+            }
+            PaymentOutcome::ThreeDsUnavailable => {
+                Some("3DS authentication unavailable from issuer".to_string())
+            }
+            PaymentOutcome::FraudHighRisk => {
+                Some("Transaction marked as high risk by Worldpay".to_string())
+            }
+            PaymentOutcome::Refused => Some("Transaction refused by issuer".to_string()),
+            _ => None,
         };
-        Ok(Self {
-            status: enums::AttemptStatus::from(router_data.response.outcome.clone()),
-            description,
-            response: Ok(PaymentsResponseData::TransactionResponse {
+        let status = enums::AttemptStatus::from(worldpay_status.clone());
+        let response = optional_reason.map_or(
+            Ok(PaymentsResponseData::TransactionResponse {
                 resource_id: types::ResponseId::foreign_try_from((
                     router_data.response,
                     optional_correlation_id.clone(),
                 ))?,
-                redirection_data: None,
+                redirection_data,
                 mandate_reference: None,
                 connector_metadata: None,
                 network_txn_id: None,
-                connector_response_reference_id: optional_correlation_id,
+                connector_response_reference_id: optional_correlation_id.clone(),
                 incremental_authorization_allowed: None,
                 charge_id: None,
             }),
+            |reason| {
+                Err(types::ErrorResponse {
+                    code: worldpay_status.to_string(),
+                    message: reason.clone(),
+                    reason: Some(reason),
+                    status_code: router_data.http_code,
+                    attempt_status: Some(status),
+                    connector_transaction_id: optional_correlation_id,
+                })
+            },
+        );
+        Ok(Self {
+            status,
+            description,
+            response,
             ..router_data.data
         })
     }
@@ -459,3 +564,17 @@ impl ForeignTryFrom<(WorldpayPaymentsResponse, Option<String>)> for types::Respo
         get_resource_id(item.0, item.1, Self::ConnectorTransactionId)
     }
 }
+
+impl TryFrom<&types::PaymentsCompleteAuthorizeRouterData> for WorldpayCompleteAuthorizationRequest {
+    type Error = error_stack::Report<errors::ConnectorError>;
+    fn try_from(item: &types::PaymentsCompleteAuthorizeRouterData) -> Result<Self, Self::Error> {
+        let params = item
+            .request
+            .redirect_response
+            .as_ref()
+            .and_then(|redirect_response| redirect_response.params.as_ref())
+            .ok_or(errors::ConnectorError::ResponseDeserializationFailed)?;
+        serde_urlencoded::from_str::<Self>(params.peek())
+            .change_context(errors::ConnectorError::ResponseDeserializationFailed)
+    }
+}
diff --git a/crates/router/src/core/payments/flows.rs b/crates/router/src/core/payments/flows.rs
index babf393bb3a..700fff16cf5 100644
--- a/crates/router/src/core/payments/flows.rs
+++ b/crates/router/src/core/payments/flows.rs
@@ -240,7 +240,6 @@ default_imp_for_complete_authorize!(
     connector::Wise,
     connector::Wellsfargo,
     connector::Wellsfargopayout,
-    connector::Worldpay,
     connector::Zen,
     connector::Zsl
 );
@@ -472,7 +471,6 @@ default_imp_for_connector_redirect_response!(
     connector::Wellsfargo,
     connector::Wellsfargopayout,
     connector::Wise,
-    connector::Worldpay,
     connector::Zsl
 );
 
diff --git a/crates/router/src/services/api.rs b/crates/router/src/services/api.rs
index 579a4ac4ffd..0d84ba9c30f 100644
--- a/crates/router/src/services/api.rs
+++ b/crates/router/src/services/api.rs
@@ -1809,6 +1809,135 @@ pub fn build_redirection_form(
 
             }
         }
+        RedirectForm::WorldpayDDCForm {
+            endpoint,
+            method,
+            form_fields,
+            collection_id,
+        } => maud::html! {
+            (maud::DOCTYPE)
+            html {
+                meta name="viewport" content="width=device-width, initial-scale=1";
+                head {
+                    (PreEscaped(r##"
+                            <style>
+                                #loader1 {
+                                    width: 500px;
+                                }
+                                @media max-width: 600px {
+                                    #loader1 {
+                                        width: 200px;
+                                    }
+                                }
+                            </style>
+                        "##))
+                }
+
+                body style="background-color: #ffffff; padding: 20px; font-family: Arial, Helvetica, Sans-Serif;" {
+                    div id="loader1" class="lottie" style="height: 150px; display: block; position: relative; margin-left: auto; margin-right: auto;" { "" }
+                    (PreEscaped(r#"<script src="https://cdnjs.cloudflare.com/ajax/libs/bodymovin/5.7.4/lottie.min.js"></script>"#))
+                    (PreEscaped(r#"
+                        <script>
+                            var anime = bodymovin.loadAnimation({
+                                container: document.getElementById('loader1'),
+                                renderer: 'svg',
+                                loop: true,
+                                autoplay: true,
+                                name: 'hyperswitch loader',
+                                animationData: {"v":"4.8.0","meta":{"g":"LottieFiles AE 3.1.1","a":"","k":"","d":"","tc":""},"fr":29.9700012207031,"ip":0,"op":31.0000012626559,"w":400,"h":250,"nm":"loader_shape","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"circle 2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[278.25,202.671,0],"ix":2},"a":{"a":0,"k":[23.72,23.72,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[12.935,0],[0,-12.936],[-12.935,0],[0,12.935]],"o":[[-12.952,0],[0,12.935],[12.935,0],[0,-12.936]],"v":[[0,-23.471],[-23.47,0.001],[0,23.471],[23.47,0.001]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0.427451010311,0.976470648074,1],"ix":4},"o":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":10,"s":[10]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":19.99,"s":[100]},{"t":29.9800012211104,"s":[10]}],"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[23.72,23.721],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":48.0000019550801,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"square 2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[196.25,201.271,0],"ix":2},"a":{"a":0,"k":[22.028,22.03,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[1.914,0],[0,0],[0,-1.914],[0,0],[-1.914,0],[0,0],[0,1.914],[0,0]],"o":[[0,0],[-1.914,0],[0,0],[0,1.914],[0,0],[1.914,0],[0,0],[0,-1.914]],"v":[[18.313,-21.779],[-18.312,-21.779],[-21.779,-18.313],[-21.779,18.314],[-18.312,21.779],[18.313,21.779],[21.779,18.314],[21.779,-18.313]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0.427451010311,0.976470648074,1],"ix":4},"o":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":5,"s":[10]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":14.99,"s":[100]},{"t":24.9800010174563,"s":[10]}],"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[22.028,22.029],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":47.0000019143492,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Triangle 2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[116.25,200.703,0],"ix":2},"a":{"a":0,"k":[27.11,21.243,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0.558,-0.879],[0,0],[-1.133,0],[0,0],[0.609,0.947],[0,0]],"o":[[-0.558,-0.879],[0,0],[-0.609,0.947],[0,0],[1.133,0],[0,0],[0,0]],"v":[[1.209,-20.114],[-1.192,-20.114],[-26.251,18.795],[-25.051,20.993],[25.051,20.993],[26.251,18.795],[1.192,-20.114]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0.427451010311,0.976470648074,1],"ix":4},"o":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[10]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":9.99,"s":[100]},{"t":19.9800008138021,"s":[10]}],"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[27.11,21.243],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":48.0000019550801,"st":0,"bm":0}],"markers":[]}
+                            })
+                        </script>
+                    "#))
+                    h3 style="text-align: center;" { "Please wait while we process your payment..." }
+
+                    script {
+                        (PreEscaped(format!(
+                            r#"
+                                function submitCollectionReference(collectionReference) {{
+                                    var redirectPathname = window.location.pathname.replace(/payments\/redirect\/(\w+)\/(\w+)\/\w+/, "payments/$1/$2/redirect/complete/worldpay");
+                                    var redirectUrl = window.location.origin + redirectPathname;
+                                    try {{
+                                        if (typeof collectionReference === "string" && collectionReference.length > 0) {{
+                                            var form = document.createElement("form");
+                                            form.action = redirectPathname;
+                                            form.method = "GET";
+                                            var input = document.createElement("input");
+                                            input.type = "hidden";
+                                            input.name = "collectionReference";
+                                            input.value = collectionReference;
+                                            form.appendChild(input);
+                                            document.body.appendChild(form);
+                                            form.submit();;
+                                        }} else {{
+                                            window.location.replace(redirectUrl);
+                                        }}
+                                    }} catch (error) {{
+                                        window.location.replace(redirectUrl);
+                                    }}
+                                }}
+                                var allowedHost = "{}";
+                                var collectionField = "{}";
+                                window.addEventListener("message", function(event) {{
+                                    if (event.origin === allowedHost) {{
+                                        try {{
+                                            var data = JSON.parse(event.data);
+                                            if (collectionField.length > 0) {{
+                                                var collectionReference = data[collectionField];
+                                                return submitCollectionReference(collectionReference);
+                                            }} else {{
+                                                console.error("Collection field not found in event data (" + collectionField + ")"); 
+                                            }}
+                                        }} catch (error) {{
+                                            console.error("Error parsing event data: ", error);
+                                        }}
+                                    }} else {{
+                                        console.error("Invalid origin: " + event.origin, "Expected origin: " + allowedHost);
+                                    }}
+
+                                    submitCollectionReference("");
+                                }});
+
+                                // Redirect within 8 seconds if no collection reference is received
+                                window.setTimeout(submitCollectionReference, 8000);
+                            "#,
+                            endpoint.host_str().map_or(endpoint.as_ref().split('/').take(3).collect::<Vec<&str>>().join("/"), |host| format!("{}://{}", endpoint.scheme(), host)),
+                            collection_id.clone().unwrap_or("".to_string())))
+                        )
+                    }
+
+                    iframe
+                        style="display: none;"
+                        srcdoc=(
+                            maud::html! {
+                                (maud::DOCTYPE)
+                                html {
+                                    body {
+                                        form action=(PreEscaped(endpoint.to_string())) method=(method.to_string()) #payment_form {
+                                            @for (field, value) in form_fields {
+                                                input type="hidden" name=(field) value=(value);
+                                            }
+                                        }
+                                        (PreEscaped(format!(r#"
+                                            <script type="text/javascript"> {logging_template}
+                                                var form = document.getElementById("payment_form"); 
+                                                var formFields = form.querySelectorAll("input");
+                                                window.setTimeout(function () {{
+                                                    if (form.method.toUpperCase() === "GET" && formFields.length === 0) {{
+                                                        window.location.href = form.action;
+                                                    }} else {{
+                                                        form.submit();
+                                                    }}
+                                                }}, 300);
+                                            </script>
+                                        "#)))
+                                    }
+                                }
+                            }.into_string()
+                        )
+                        {}
+                }
+            }
+        },
     }
 }
 

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

- [ ] Bugfix
- [x] New feature
- [ ] Enhancement
- [ ] Refactoring
- [ ] Dependency updates
- [ ] Documentation
- [ ] CI/CD

## Description
Described in #6177 

### Additional Changes

- [ ] This PR modifies the API contract
- [ ] 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
Integrates Worldpay's 3DS flow

## How did you test it?
Things to test

1. 3DS payments using automatic capture
2. No 3DS payment using manual capture (full amount captures)
3. Payments Sync

<details>
    <summary>1. Create and capture</summary>

cURL

    curl --location --request POST 'http://localhost:8080/payments' \
        --header 'Content-Type: application/json' \
        --header 'Accept: application/json' \
        --header 'api-key: dev_sMl3D9efytjSoXznSOKCv5PHyql50M1BMhU8e7qsa5m6mXxHvyROVg7cER7AWue9' \
        --data-raw '{"authentication_type":"three_ds","capture_method":"automatic","customer_id":"cus_sH6s3hf1uukqvYlAGkj5","profile_id":"pro_OodaMoJ1Y9nlnegU4soR","amount":100,"currency":"USD","confirm":true,"connector":["worldpay"],"billing":{"address":{"line1":"1467","line2":"Harrison Street","line3":"Harrison Street","city":"San Fransico","state":"CA","zip":"94122","country":"US","first_name":"John","last_name":"Doe"},"phone":{"number":"8056594427","country_code":"+91"}},"email":"john.doe@example.com","session_expiry":10000,"return_url":"https://example.com","off_session":true,"payment_method":"card","payment_method_type":"credit","payment_method_data":{"card":{"card_number":"4000000000001091","card_exp_month":"12","card_exp_year":"2030","card_holder_name":"John Doe","card_cvc":"123"}},"browser_info":{"user_agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36","accept_header":"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8","language":"en-US","color_depth":24,"screen_height":723,"screen_width":1536,"time_zone":0,"java_enabled":true,"java_script_enabled":true,"ip_address":"127.0.0.1"},"statement_descriptor_name":"John","statement_descriptor_suffix":"JD","metadata":{"udf1":"value1","new_customer":"true","login_date":"2019-09-10T10:11:12Z"}}'

Response

    {"payment_id":"pay_OjJGWEhFHNpycXbZmDNS","merchant_id":"merchant_1728979001","status":"requires_customer_action","amount":100,"net_amount":100,"shipping_cost":null,"amount_capturable":100,"amount_received":null,"connector":"worldpay","client_secret":"pay_OjJGWEhFHNpycXbZmDNS_secret_vMQEnVxtwo0xKYXtDTqh","created":"2024-10-21T10:59:11.767Z","currency":"USD","customer_id":"cus_sH6s3hf1uukqvYlAGkj5","customer":{"id":"cus_sH6s3hf1uukqvYlAGkj5","name":"John Doe","email":"john.doe@example.com","phone":"999999999","phone_country_code":"+65"},"description":null,"refunds":null,"disputes":null,"mandate_id":null,"mandate_data":null,"setup_future_usage":null,"off_session":true,"capture_on":null,"capture_method":"automatic","payment_method":"card","payment_method_data":{"card":{"last4":"1091","card_type":null,"card_network":null,"card_issuer":null,"card_issuing_country":null,"card_isin":"400000","card_extended_bin":null,"card_exp_month":"12","card_exp_year":"2030","card_holder_name":null,"payment_checks":null,"authentication_data":null},"billing":null},"payment_token":null,"shipping":null,"billing":{"address":{"city":"San Fransico","country":"US","line1":"1467","line2":"Harrison Street","line3":"Harrison Street","zip":"94122","state":"CA","first_name":"John","last_name":"Doe"},"phone":{"number":"8056594427","country_code":"+91"},"email":null},"order_details":null,"email":"john.doe@example.com","name":"John Doe","phone":"999999999","return_url":"https://example.com/","authentication_type":"three_ds","statement_descriptor_name":"John","statement_descriptor_suffix":"JD","next_action":{"type":"redirect_to_url","redirect_to_url":"http://localhost:8080/payments/redirect/pay_OjJGWEhFHNpycXbZmDNS/merchant_1728979001/pay_OjJGWEhFHNpycXbZmDNS_1"},"cancellation_reason":null,"error_code":null,"error_message":null,"unified_code":null,"unified_message":null,"payment_experience":null,"payment_method_type":"credit","connector_label":null,"business_country":null,"business_label":"default","business_sub_label":null,"allowed_payment_method_types":null,"ephemeral_key":{"customer_id":"cus_sH6s3hf1uukqvYlAGkj5","created_at":1729508351,"expires":1729511951,"secret":"epk_dfac7bbac2014a18ae323ba08bfe7e9b"},"manual_retry_allowed":null,"connector_transaction_id":"eyJrIjoxLCJkIjoibVJjNnpNRE45cys3RmIxWjRRbWF2SWd4akc2VC9KdHZiVlgrTTRvTURQdzJJRGpBeDR3ekZaWWVqNDJzOTJkWiJ9","frm_message":null,"metadata":{"udf1":"value1","login_date":"2019-09-10T10:11:12Z","new_customer":"true"},"connector_metadata":null,"feature_metadata":null,"reference_id":"3fbccf2f-0ab5-494c-9909-1c8d2be40049","payment_link":null,"profile_id":"pro_OodaMoJ1Y9nlnegU4soR","surcharge_details":null,"attempt_count":1,"merchant_decision":null,"merchant_connector_id":"mca_omabWO9B7CSmaCMuT6eh","incremental_authorization_allowed":null,"authorization_count":null,"incremental_authorizations":null,"external_authentication_details":null,"external_3ds_authentication_attempted":false,"expires_on":"2024-10-21T13:45:51.767Z","fingerprint":null,"browser_info":{"language":"en-US","time_zone":0,"ip_address":"127.0.0.1","user_agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36","color_depth":24,"java_enabled":true,"screen_width":1536,"accept_header":"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8","screen_height":723,"java_script_enabled":true},"payment_method_id":null,"payment_method_status":null,"updated":"2024-10-21T10:59:12.308Z","charges":null,"frm_metadata":null,"merchant_order_reference_id":null,"order_tax_amount":null,"connector_mandate_id":null}

Open the link and continue

https://github.com/user-attachments/assets/52239cd6-fa40-45e3-88a9-231115ab8b20

</details>

<details>
    <summary>2. Create a payment using manual capture</summary>

cURL

    curl --location --request POST 'http://localhost:8080/payments' \
        --header 'Content-Type: application/json' \
        --header 'Accept: application/json' \
        --header 'api-key: dev_sMl3D9efytjSoXznSOKCv5PHyql50M1BMhU8e7qsa5m6mXxHvyROVg7cER7AWue9' \
        --data-raw '{"authentication_type":"three_ds","capture_method":"manual","customer_id":"cus_sH6s3hf1uukqvYlAGkj5","profile_id":"pro_OodaMoJ1Y9nlnegU4soR","amount":100,"currency":"USD","confirm":true,"connector":["worldpay"],"billing":{"address":{"line1":"1467","line2":"Harrison Street","line3":"Harrison Street","city":"San Fransico","state":"CA","zip":"94122","country":"US","first_name":"John","last_name":"Doe"},"phone":{"number":"8056594427","country_code":"+91"}},"email":"john.doe@example.com","session_expiry":10000,"return_url":"https://example.com","off_session":true,"payment_method":"card","payment_method_type":"credit","payment_method_data":{"card":{"card_number":"4000000000001091","card_exp_month":"12","card_exp_year":"2030","card_holder_name":"John Doe","card_cvc":"123"}},"browser_info":{"user_agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36","accept_header":"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8","language":"en-US","color_depth":24,"screen_height":723,"screen_width":1536,"time_zone":0,"java_enabled":true,"java_script_enabled":true,"ip_address":"127.0.0.1"},"statement_descriptor_name":"John","statement_descriptor_suffix":"JD","metadata":{"udf1":"value1","new_customer":"true","login_date":"2019-09-10T10:11:12Z"}}'

Response

    {"payment_id":"pay_ff5lUcN6X1lg5f2PBtyv","merchant_id":"merchant_1728979001","status":"requires_customer_action","amount":100,"net_amount":100,"shipping_cost":null,"amount_capturable":100,"amount_received":null,"connector":"worldpay","client_secret":"pay_ff5lUcN6X1lg5f2PBtyv_secret_6iktmiNQwi8DAsIRNn4i","created":"2024-10-21T11:03:31.227Z","currency":"USD","customer_id":"cus_sH6s3hf1uukqvYlAGkj5","customer":{"id":"cus_sH6s3hf1uukqvYlAGkj5","name":"John Doe","email":"john.doe@example.com","phone":"999999999","phone_country_code":"+65"},"description":null,"refunds":null,"disputes":null,"mandate_id":null,"mandate_data":null,"setup_future_usage":null,"off_session":true,"capture_on":null,"capture_method":"manual","payment_method":"card","payment_method_data":{"card":{"last4":"1096","card_type":null,"card_network":null,"card_issuer":null,"card_issuing_country":null,"card_isin":"520000","card_extended_bin":null,"card_exp_month":"12","card_exp_year":"2030","card_holder_name":null,"payment_checks":null,"authentication_data":null},"billing":null},"payment_token":null,"shipping":null,"billing":{"address":{"city":"San Fransico","country":"US","line1":"1467","line2":"Harrison Street","line3":"Harrison Street","zip":"94122","state":"CA","first_name":"John","last_name":"Doe"},"phone":{"number":"8056594427","country_code":"+91"},"email":null},"order_details":null,"email":"john.doe@example.com","name":"John Doe","phone":"999999999","return_url":"https://example.com/","authentication_type":"three_ds","statement_descriptor_name":"John","statement_descriptor_suffix":"JD","next_action":{"type":"redirect_to_url","redirect_to_url":"http://localhost:8080/payments/redirect/pay_ff5lUcN6X1lg5f2PBtyv/merchant_1728979001/pay_ff5lUcN6X1lg5f2PBtyv_1"},"cancellation_reason":null,"error_code":null,"error_message":null,"unified_code":null,"unified_message":null,"payment_experience":null,"payment_method_type":"credit","connector_label":null,"business_country":null,"business_label":"default","business_sub_label":null,"allowed_payment_method_types":null,"ephemeral_key":{"customer_id":"cus_sH6s3hf1uukqvYlAGkj5","created_at":1729508611,"expires":1729512211,"secret":"epk_739c58c7f7384fac89c7099e1cdae436"},"manual_retry_allowed":null,"connector_transaction_id":"eyJrIjoxLCJkIjoiQTRKMWZFZXlONnFqdGhHTWlmWDdYY2RzUVlIUzI2dEdLcEFPak9GRzNaczJJRGpBeDR3ekZaWWVqNDJzOTJkWiJ9","frm_message":null,"metadata":{"udf1":"value1","login_date":"2019-09-10T10:11:12Z","new_customer":"true"},"connector_metadata":null,"feature_metadata":null,"reference_id":"5b1cdf1c-26ea-42b1-b6e0-a02453ebf21f","payment_link":null,"profile_id":"pro_OodaMoJ1Y9nlnegU4soR","surcharge_details":null,"attempt_count":1,"merchant_decision":null,"merchant_connector_id":"mca_omabWO9B7CSmaCMuT6eh","incremental_authorization_allowed":null,"authorization_count":null,"incremental_authorizations":null,"external_authentication_details":null,"external_3ds_authentication_attempted":false,"expires_on":"2024-10-21T13:50:11.227Z","fingerprint":null,"browser_info":{"language":"en-US","time_zone":0,"ip_address":"127.0.0.1","user_agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36","color_depth":24,"java_enabled":true,"screen_width":1536,"accept_header":"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8","screen_height":723,"java_script_enabled":true},"payment_method_id":null,"payment_method_status":null,"updated":"2024-10-21T11:03:31.677Z","charges":null,"frm_metadata":null,"merchant_order_reference_id":null,"order_tax_amount":null,"connector_mandate_id":null}

cURL (capture)

    curl --location --request POST 'http://localhost:8080/payments/pay_ff5lUcN6X1lg5f2PBtyv/capture' \
        --header 'Content-Type: application/json' \
        --header 'Accept: application/json' \
        --header 'api-key: dev_sMl3D9efytjSoXznSOKCv5PHyql50M1BMhU8e7qsa5m6mXxHvyROVg7cER7AWue9' \
        --data '{}'

Response

    {"payment_id":"pay_ff5lUcN6X1lg5f2PBtyv","merchant_id":"merchant_1728979001","status":"processing","amount":100,"net_amount":100,"shipping_cost":null,"amount_capturable":100,"amount_received":null,"connector":"worldpay","client_secret":"pay_ff5lUcN6X1lg5f2PBtyv_secret_6iktmiNQwi8DAsIRNn4i","created":"2024-10-21T11:03:31.227Z","currency":"USD","customer_id":"cus_sH6s3hf1uukqvYlAGkj5","customer":{"id":"cus_sH6s3hf1uukqvYlAGkj5","name":"John Doe","email":"john.doe@example.com","phone":"999999999","phone_country_code":"+65"},"description":null,"refunds":null,"disputes":null,"mandate_id":null,"mandate_data":null,"setup_future_usage":null,"off_session":true,"capture_on":null,"capture_method":"manual","payment_method":"card","payment_method_data":{"card":{"last4":"1096","card_type":null,"card_network":null,"card_issuer":null,"card_issuing_country":null,"card_isin":"520000","card_extended_bin":null,"card_exp_month":"12","card_exp_year":"2030","card_holder_name":null,"payment_checks":null,"authentication_data":null},"billing":null},"payment_token":null,"shipping":null,"billing":{"address":{"city":"San Fransico","country":"US","line1":"1467","line2":"Harrison Street","line3":"Harrison Street","zip":"94122","state":"CA","first_name":"John","last_name":"Doe"},"phone":{"number":"8056594427","country_code":"+91"},"email":null},"order_details":null,"email":"john.doe@example.com","name":"John Doe","phone":"999999999","return_url":"https://example.com/","authentication_type":"three_ds","statement_descriptor_name":"John","statement_descriptor_suffix":"JD","next_action":null,"cancellation_reason":null,"error_code":null,"error_message":null,"unified_code":null,"unified_message":null,"payment_experience":null,"payment_method_type":"credit","connector_label":null,"business_country":null,"business_label":"default","business_sub_label":null,"allowed_payment_method_types":null,"ephemeral_key":null,"manual_retry_allowed":false,"connector_transaction_id":"eyJrIjoiazNhYjYzMiIsImxpbmtWZXJzaW9uIjoiNS4wLjAifQ==.sN:g8wd64bwkbrp0md+bPxcanBnk2zLdsIqSa1pR99GGg8fCNQpPLoWNslSzWNPFBM5Tpa8tW7EFI5onKINsgChMHeJVoeH2lrBWCRyjZYT6h+lbqfJa+1BSoKFSY8HLZqKn+Z0amZtgDGJNxxTouoBznrTCkCd2i5TQsznCawECpZY8cZeCeMgt1ol:GABqQG6u+T:u3sfHv3ezSOJxioRTixsThPEhzFW4ZZ6mObj3CK0rnFndcM0swvZMqgQwSEj5tBsydfzM4XKX20O6Nme96ha9twqxTSQIfr1rtl9V3q7fw3w5O9UZT4vQi9BMyCcaHkWSD:RbCWCcmiQqa:C6PZmXicLsKiJilUv0dIUwRuaBJaSeFhCq3fDWQC7:n7drI2adm1BXX7w39afnJw==","frm_message":null,"metadata":{"udf1":"value1","login_date":"2019-09-10T10:11:12Z","new_customer":"true"},"connector_metadata":null,"feature_metadata":null,"reference_id":"f7cb203a-aa4d-4fbc-84e7-9f6c1a9273a5","payment_link":null,"profile_id":"pro_OodaMoJ1Y9nlnegU4soR","surcharge_details":null,"attempt_count":1,"merchant_decision":null,"merchant_connector_id":"mca_omabWO9B7CSmaCMuT6eh","incremental_authorization_allowed":false,"authorization_count":null,"incremental_authorizations":null,"external_authentication_details":null,"external_3ds_authentication_attempted":false,"expires_on":"2024-10-21T13:50:11.227Z","fingerprint":null,"browser_info":{"language":"en-US","time_zone":0,"ip_address":"127.0.0.1","user_agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36","color_depth":24,"java_enabled":true,"screen_width":1536,"accept_header":"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8","screen_height":723,"java_script_enabled":true},"payment_method_id":null,"payment_method_status":null,"updated":"2024-10-21T11:04:47.760Z","charges":null,"frm_metadata":null,"merchant_order_reference_id":null,"order_tax_amount":null,"connector_mandate_id":null}

</details>

<details>
    <summary>3. Payments Sync</summary>

cURL

    curl --location --request GET 'http://localhost:8080/payments/pay_ff5lUcN6X1lg5f2PBtyv?force_sync=true' \
        --header 'Accept: application/json' \
        --header 'api-key: dev_sMl3D9efytjSoXznSOKCv5PHyql50M1BMhU8e7qsa5m6mXxHvyROVg7cER7AWue9'

Response

    {"payment_id":"pay_ff5lUcN6X1lg5f2PBtyv","merchant_id":"merchant_1728979001","status":"processing","amount":100,"net_amount":100,"shipping_cost":null,"amount_capturable":100,"amount_received":null,"connector":"worldpay","client_secret":"pay_ff5lUcN6X1lg5f2PBtyv_secret_6iktmiNQwi8DAsIRNn4i","created":"2024-10-21T11:03:31.227Z","currency":"USD","customer_id":"cus_sH6s3hf1uukqvYlAGkj5","customer":{"id":"cus_sH6s3hf1uukqvYlAGkj5","name":"John Doe","email":"john.doe@example.com","phone":"999999999","phone_country_code":"+65"},"description":null,"refunds":null,"disputes":null,"mandate_id":null,"mandate_data":null,"setup_future_usage":null,"off_session":true,"capture_on":null,"capture_method":"manual","payment_method":"card","payment_method_data":{"card":{"last4":"1096","card_type":null,"card_network":null,"card_issuer":null,"card_issuing_country":null,"card_isin":"520000","card_extended_bin":null,"card_exp_month":"12","card_exp_year":"2030","card_holder_name":null,"payment_checks":null,"authentication_data":null},"billing":null},"payment_token":null,"shipping":null,"billing":{"address":{"city":"San Fransico","country":"US","line1":"1467","line2":"Harrison Street","line3":"Harrison Street","zip":"94122","state":"CA","first_name":"John","last_name":"Doe"},"phone":{"number":"8056594427","country_code":"+91"},"email":null},"order_details":null,"email":"john.doe@example.com","name":"John Doe","phone":"999999999","return_url":"https://example.com/","authentication_type":"three_ds","statement_descriptor_name":"John","statement_descriptor_suffix":"JD","next_action":null,"cancellation_reason":null,"error_code":null,"error_message":null,"unified_code":null,"unified_message":null,"payment_experience":null,"payment_method_type":"credit","connector_label":null,"business_country":null,"business_label":"default","business_sub_label":null,"allowed_payment_method_types":null,"ephemeral_key":null,"manual_retry_allowed":false,"connector_transaction_id":"eyJrIjoiazNhYjYzMiIsImxpbmtWZXJzaW9uIjoiNS4wLjAifQ==.sN:g8wd64bwkbrp0md+bPxcanBnk2zLdsIqSa1pR99GGg8fCNQpPLoWNslSzWNPFBM5Tpa8tW7EFI5onKINsgChMHeJVoeH2lrBWCRyjZYT6h+lbqfJa+1BSoKFSY8HLZqKn+Z0amZtgDGJNxxTouoBznrTCkCd2i5TQsznCawECpZY8cZeCeMgt1ol:GABqQG6u+T:u3sfHv3ezSOJxioRTixsThPEhzFW4ZZ6mObj3CK0rnFndcM0swvZMqgQwSEj5tBsydfzM4XKX20O6Nme96ha9twqxTSQIfr1rtl9V3q7fw3w5O9UZT4vQi9BMyCcaHkWSD:RbCWCcmiQqa:C6PZmXicLsKiJilUv0dIUwRuaBJaSeFhCq3fDWQC7:n7drI2adm1BXX7w39afnJw==","frm_message":null,"metadata":{"udf1":"value1","login_date":"2019-09-10T10:11:12Z","new_customer":"true"},"connector_metadata":null,"feature_metadata":null,"reference_id":"eaf9a1d5-031a-4b3b-b70b-ed9b7ec05406","payment_link":null,"profile_id":"pro_OodaMoJ1Y9nlnegU4soR","surcharge_details":null,"attempt_count":1,"merchant_decision":null,"merchant_connector_id":"mca_omabWO9B7CSmaCMuT6eh","incremental_authorization_allowed":false,"authorization_count":null,"incremental_authorizations":null,"external_authentication_details":null,"external_3ds_authentication_attempted":false,"expires_on":"2024-10-21T13:50:11.227Z","fingerprint":null,"browser_info":{"language":"en-US","time_zone":0,"ip_address":"127.0.0.1","user_agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36","color_depth":24,"java_enabled":true,"screen_width":1536,"accept_header":"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8","screen_height":723,"java_script_enabled":true},"payment_method_id":null,"payment_method_status":null,"updated":"2024-10-21T11:06:18.222Z","charges":null,"frm_metadata":null,"merchant_order_reference_id":null,"order_tax_amount":null,"connector_mandate_id":null}

</details>



## 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 the submitted code
- [ ] I added unit tests for my changes where possible
