Bug: [FEATURE] Add incremental authorization for Paypal, AuthorizeDotNet, Stripe



### Feature Description

Add incremental authorization for Paypal, AuthorizeDotNet, Stripe

### Possible Implementation

Add incremental authorization for Paypal, AuthorizeDotNet, Stripe

### 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?

Yes, I am willing to submit a PR!
diff --git a/crates/hyperswitch_connectors/src/connectors/helcim.rs b/crates/hyperswitch_connectors/src/connectors/helcim.rs
index 05165fbe91b..4845b2cbc7b 100644
--- a/crates/hyperswitch_connectors/src/connectors/helcim.rs
+++ b/crates/hyperswitch_connectors/src/connectors/helcim.rs
@@ -924,7 +924,7 @@ impl ConnectorTransactionId for Helcim {
     #[cfg(feature = "v1")]
     fn connector_transaction_id(
         &self,
-        payment_attempt: PaymentAttempt,
+        payment_attempt: &PaymentAttempt,
     ) -> Result<Option<String>, ApiErrorResponse> {
         if payment_attempt.get_connector_payment_id().is_none() {
             let metadata =
@@ -940,7 +940,7 @@ impl ConnectorTransactionId for Helcim {
     #[cfg(feature = "v2")]
     fn connector_transaction_id(
         &self,
-        payment_attempt: PaymentAttempt,
+        payment_attempt: &PaymentAttempt,
     ) -> Result<Option<String>, ApiErrorResponse> {
         use hyperswitch_domain_models::errors::api_error_response::ApiErrorResponse;
 
diff --git a/crates/hyperswitch_connectors/src/connectors/nexinets.rs b/crates/hyperswitch_connectors/src/connectors/nexinets.rs
index 08084973c50..8733bede9c4 100644
--- a/crates/hyperswitch_connectors/src/connectors/nexinets.rs
+++ b/crates/hyperswitch_connectors/src/connectors/nexinets.rs
@@ -886,7 +886,7 @@ impl ConnectorTransactionId for Nexinets {
     #[cfg(feature = "v1")]
     fn connector_transaction_id(
         &self,
-        payment_attempt: PaymentAttempt,
+        payment_attempt: &PaymentAttempt,
     ) -> Result<Option<String>, ApiErrorResponse> {
         let metadata =
             Self::connector_transaction_id(self, payment_attempt.connector_metadata.as_ref());
@@ -896,7 +896,7 @@ impl ConnectorTransactionId for Nexinets {
     #[cfg(feature = "v2")]
     fn connector_transaction_id(
         &self,
-        payment_attempt: PaymentAttempt,
+        payment_attempt: &PaymentAttempt,
     ) -> Result<Option<String>, ApiErrorResponse> {
         use hyperswitch_domain_models::errors::api_error_response::ApiErrorResponse;
 
diff --git a/crates/hyperswitch_connectors/src/connectors/paypal.rs b/crates/hyperswitch_connectors/src/connectors/paypal.rs
index 578baba88b5..f90ac021fb2 100644
--- a/crates/hyperswitch_connectors/src/connectors/paypal.rs
+++ b/crates/hyperswitch_connectors/src/connectors/paypal.rs
@@ -20,8 +20,8 @@ use hyperswitch_domain_models::{
     router_flow_types::{
         access_token_auth::AccessTokenAuth,
         payments::{
-            Authorize, Capture, PSync, PaymentMethodToken, PostSessionTokens, PreProcessing,
-            SdkSessionUpdate, Session, SetupMandate, Void,
+            Authorize, Capture, IncrementalAuthorization, PSync, PaymentMethodToken,
+            PostSessionTokens, PreProcessing, SdkSessionUpdate, Session, SetupMandate, Void,
         },
         refunds::{Execute, RSync},
         CompleteAuthorize, VerifyWebhookSource,
@@ -29,19 +29,19 @@ use hyperswitch_domain_models::{
     router_request_types::{
         AccessTokenRequestData, CompleteAuthorizeData, PaymentMethodTokenizationData,
         PaymentsAuthorizeData, PaymentsCancelData, PaymentsCaptureData,
-        PaymentsPostSessionTokensData, PaymentsPreProcessingData, PaymentsSessionData,
-        PaymentsSyncData, RefundsData, ResponseId, SdkPaymentsSessionUpdateData,
-        SetupMandateRequestData, VerifyWebhookSourceRequestData,
+        PaymentsIncrementalAuthorizationData, PaymentsPostSessionTokensData,
+        PaymentsPreProcessingData, PaymentsSessionData, PaymentsSyncData, RefundsData, ResponseId,
+        SdkPaymentsSessionUpdateData, SetupMandateRequestData, VerifyWebhookSourceRequestData,
     },
     router_response_types::{
         PaymentsResponseData, RefundsResponseData, VerifyWebhookSourceResponseData,
     },
     types::{
         PaymentsAuthorizeRouterData, PaymentsCancelRouterData, PaymentsCaptureRouterData,
-        PaymentsCompleteAuthorizeRouterData, PaymentsPostSessionTokensRouterData,
-        PaymentsPreProcessingRouterData, PaymentsSyncRouterData, RefreshTokenRouterData,
-        RefundSyncRouterData, RefundsRouterData, SdkSessionUpdateRouterData,
-        SetupMandateRouterData, VerifyWebhookSourceRouterData,
+        PaymentsCompleteAuthorizeRouterData, PaymentsIncrementalAuthorizationRouterData,
+        PaymentsPostSessionTokensRouterData, PaymentsPreProcessingRouterData,
+        PaymentsSyncRouterData, RefreshTokenRouterData, RefundSyncRouterData, RefundsRouterData,
+        SdkSessionUpdateRouterData, SetupMandateRouterData, VerifyWebhookSourceRouterData,
     },
 };
 #[cfg(feature = "payouts")]
@@ -55,17 +55,17 @@ use hyperswitch_interfaces::types::{PayoutFulfillType, PayoutSyncType};
 use hyperswitch_interfaces::{
     api::{
         self, ConnectorCommon, ConnectorCommonExt, ConnectorIntegration, ConnectorRedirectResponse,
-        ConnectorSpecifications, ConnectorValidation,
+        ConnectorSpecifications, ConnectorValidation, PaymentIncrementalAuthorization,
     },
     configs::Connectors,
     consts::{NO_ERROR_CODE, NO_ERROR_MESSAGE},
     disputes, errors,
     events::connector_api_logs::ConnectorEvent,
     types::{
-        PaymentsAuthorizeType, PaymentsCaptureType, PaymentsCompleteAuthorizeType,
-        PaymentsPostSessionTokensType, PaymentsPreProcessingType, PaymentsSyncType,
-        PaymentsVoidType, RefreshTokenType, RefundExecuteType, RefundSyncType, Response,
-        SdkSessionUpdateType, SetupMandateType, VerifyWebhookSourceType,
+        IncrementalAuthorizationType, PaymentsAuthorizeType, PaymentsCaptureType,
+        PaymentsCompleteAuthorizeType, PaymentsPostSessionTokensType, PaymentsPreProcessingType,
+        PaymentsSyncType, PaymentsVoidType, RefreshTokenType, RefundExecuteType, RefundSyncType,
+        Response, SdkSessionUpdateType, SetupMandateType, VerifyWebhookSourceType,
     },
     webhooks::{IncomingWebhook, IncomingWebhookRequestDetails},
 };
@@ -73,7 +73,8 @@ use masking::{ExposeInterface, Mask, Maskable, PeekInterface, Secret};
 #[cfg(feature = "payouts")]
 use router_env::{instrument, tracing};
 use transformers::{
-    self as paypal, auth_headers, PaypalAuthResponse, PaypalMeta, PaypalWebhookEventType,
+    self as paypal, auth_headers, PaypalAuthResponse, PaypalIncrementalAuthResponse, PaypalMeta,
+    PaypalWebhookEventType,
 };
 
 use crate::{
@@ -1105,6 +1106,123 @@ impl ConnectorIntegration<Authorize, PaymentsAuthorizeData, PaymentsResponseData
     }
 }
 
+impl PaymentIncrementalAuthorization for Paypal {}
+
+impl
+    ConnectorIntegration<
+        IncrementalAuthorization,
+        PaymentsIncrementalAuthorizationData,
+        PaymentsResponseData,
+    > for Paypal
+{
+    fn get_headers(
+        &self,
+        req: &PaymentsIncrementalAuthorizationRouterData,
+        connectors: &Connectors,
+    ) -> CustomResult<Vec<(String, Maskable<String>)>, errors::ConnectorError> {
+        self.build_headers(req, connectors)
+    }
+
+    fn get_http_method(&self) -> Method {
+        Method::Post
+    }
+
+    fn get_content_type(&self) -> &'static str {
+        self.common_get_content_type()
+    }
+
+    fn get_url(
+        &self,
+        req: &PaymentsIncrementalAuthorizationRouterData,
+        connectors: &Connectors,
+    ) -> CustomResult<String, errors::ConnectorError> {
+        let paypal_meta: PaypalMeta = to_connector_meta(req.request.connector_meta.clone())?;
+        let incremental_authorization_id = paypal_meta.incremental_authorization_id.ok_or(
+            errors::ConnectorError::RequestEncodingFailedWithReason(
+                "Missing incremental authorization id".to_string(),
+            ),
+        )?;
+        Ok(format!(
+            "{}v2/payments/authorizations/{}/reauthorize",
+            self.base_url(connectors),
+            incremental_authorization_id
+        ))
+    }
+
+    fn get_request_body(
+        &self,
+        req: &PaymentsIncrementalAuthorizationRouterData,
+        _connectors: &Connectors,
+    ) -> CustomResult<RequestContent, errors::ConnectorError> {
+        let amount = connector_utils::convert_amount(
+            self.amount_converter,
+            MinorUnit::new(req.request.total_amount),
+            req.request.currency,
+        )?;
+        let connector_router_data =
+            paypal::PaypalRouterData::try_from((amount, None, None, None, req))?;
+        let connector_req = paypal::PaypalIncrementalAuthRequest::try_from(&connector_router_data)?;
+        Ok(RequestContent::Json(Box::new(connector_req)))
+    }
+
+    fn build_request(
+        &self,
+        req: &PaymentsIncrementalAuthorizationRouterData,
+        connectors: &Connectors,
+    ) -> CustomResult<Option<Request>, errors::ConnectorError> {
+        Ok(Some(
+            RequestBuilder::new()
+                .method(Method::Post)
+                .url(&IncrementalAuthorizationType::get_url(
+                    self, req, connectors,
+                )?)
+                .attach_default_headers()
+                .headers(IncrementalAuthorizationType::get_headers(
+                    self, req, connectors,
+                )?)
+                .set_body(IncrementalAuthorizationType::get_request_body(
+                    self, req, connectors,
+                )?)
+                .build(),
+        ))
+    }
+
+    fn handle_response(
+        &self,
+        data: &PaymentsIncrementalAuthorizationRouterData,
+        event_builder: Option<&mut ConnectorEvent>,
+        res: Response,
+    ) -> CustomResult<
+        RouterData<
+            IncrementalAuthorization,
+            PaymentsIncrementalAuthorizationData,
+            PaymentsResponseData,
+        >,
+        errors::ConnectorError,
+    > {
+        let response: PaypalIncrementalAuthResponse = res
+            .response
+            .parse_struct("Paypal IncrementalAuthResponse")
+            .change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
+        event_builder.map(|i| i.set_response_body(&response));
+        router_env::logger::info!(connector_response=?response);
+        RouterData::try_from(ResponseRouterData {
+            response,
+            data: data.clone(),
+            http_code: res.status_code,
+        })
+        .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::PaymentsPreProcessing for Paypal {}
 
 impl ConnectorIntegration<PreProcessing, PaymentsPreProcessingData, PaymentsResponseData>
diff --git a/crates/hyperswitch_connectors/src/connectors/paypal/transformers.rs b/crates/hyperswitch_connectors/src/connectors/paypal/transformers.rs
index 8cabfcf4663..3814694b08b 100644
--- a/crates/hyperswitch_connectors/src/connectors/paypal/transformers.rs
+++ b/crates/hyperswitch_connectors/src/connectors/paypal/transformers.rs
@@ -19,7 +19,8 @@ use hyperswitch_domain_models::{
         VerifyWebhookSource,
     },
     router_request_types::{
-        PaymentsAuthorizeData, PaymentsPostSessionTokensData, PaymentsSyncData, ResponseId,
+        CompleteAuthorizeData, PaymentsAuthorizeData, PaymentsIncrementalAuthorizationData,
+        PaymentsPostSessionTokensData, PaymentsSyncData, ResponseId,
         VerifyWebhookSourceRequestData,
     },
     router_response_types::{
@@ -28,8 +29,9 @@ use hyperswitch_domain_models::{
     },
     types::{
         PaymentsAuthorizeRouterData, PaymentsCaptureRouterData,
-        PaymentsPostSessionTokensRouterData, RefreshTokenRouterData, RefundsRouterData,
-        SdkSessionUpdateRouterData, SetupMandateRouterData, VerifyWebhookSourceRouterData,
+        PaymentsIncrementalAuthorizationRouterData, PaymentsPostSessionTokensRouterData,
+        RefreshTokenRouterData, RefundsRouterData, SdkSessionUpdateRouterData,
+        SetupMandateRouterData, VerifyWebhookSourceRouterData,
     },
 };
 #[cfg(feature = "payouts")]
@@ -55,6 +57,28 @@ use crate::{
     },
 };
 
+trait GetRequestIncrementalAuthorization {
+    fn get_request_incremental_authorization(&self) -> Option<bool>;
+}
+
+impl GetRequestIncrementalAuthorization for PaymentsAuthorizeData {
+    fn get_request_incremental_authorization(&self) -> Option<bool> {
+        Some(self.request_incremental_authorization)
+    }
+}
+
+impl GetRequestIncrementalAuthorization for CompleteAuthorizeData {
+    fn get_request_incremental_authorization(&self) -> Option<bool> {
+        None
+    }
+}
+
+impl GetRequestIncrementalAuthorization for PaymentsSyncData {
+    fn get_request_incremental_authorization(&self) -> Option<bool> {
+        None
+    }
+}
+
 #[derive(Debug, Serialize)]
 pub struct PaypalRouterData<T> {
     pub amount: StringMajorUnit,
@@ -237,7 +261,7 @@ pub struct PurchaseUnitRequest {
     items: Vec<ItemDetails>,
 }
 
-#[derive(Default, Debug, Serialize, Eq, PartialEq)]
+#[derive(Default, Debug, Deserialize, Serialize, Eq, PartialEq)]
 pub struct Payee {
     merchant_id: Secret<String>,
 }
@@ -1425,6 +1449,119 @@ impl<F, T> TryFrom<ResponseRouterData<F, PaypalAuthUpdateResponse, T, AccessToke
     }
 }
 
+#[derive(Debug, Serialize)]
+pub struct PaypalIncrementalAuthRequest {
+    amount: OrderAmount,
+}
+
+impl TryFrom<&PaypalRouterData<&PaymentsIncrementalAuthorizationRouterData>>
+    for PaypalIncrementalAuthRequest
+{
+    type Error = error_stack::Report<errors::ConnectorError>;
+
+    fn try_from(
+        item: &PaypalRouterData<&PaymentsIncrementalAuthorizationRouterData>,
+    ) -> Result<Self, Self::Error> {
+        Ok(Self {
+            amount: OrderAmount {
+                currency_code: item.router_data.request.currency,
+                value: item.amount.clone(),
+            },
+        })
+    }
+}
+
+#[derive(Debug, Deserialize, Serialize)]
+pub struct PaypalIncrementalAuthResponse {
+    status: PaypalIncrementalStatus,
+    status_details: PaypalIncrementalAuthStatusDetails,
+    id: String,
+    invoice_id: String,
+    custom_id: String,
+    links: Vec<PaypalLinks>,
+    amount: OrderAmount,
+    network_transaction_reference: PaypalNetworkTransactionReference,
+    expiration_time: String,
+    create_time: String,
+    update_time: String,
+    supplementary_data: PaypalSupplementaryData,
+    payee: Payee,
+    name: Option<String>,
+    message: Option<String>,
+}
+
+#[derive(Debug, Deserialize, Serialize)]
+#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
+pub enum PaypalIncrementalStatus {
+    CREATED,
+    CAPTURED,
+    DENIED,
+    PARTIALLYCAPTURED,
+    VOIDED,
+    PENDING,
+}
+
+#[derive(Debug, Deserialize, Serialize)]
+pub struct PaypalNetworkTransactionReference {
+    id: String,
+}
+
+#[derive(Debug, Deserialize, Serialize)]
+pub struct PaypalIncrementalAuthStatusDetails {
+    reason: PaypalStatusPendingReason,
+}
+
+#[derive(Debug, Deserialize, Serialize)]
+#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
+pub enum PaypalStatusPendingReason {
+    PENDINGREVIEW,
+    DECLINEDBYRISKFRAUDFILTERS,
+}
+
+impl From<PaypalIncrementalStatus> for common_enums::AuthorizationStatus {
+    fn from(item: PaypalIncrementalStatus) -> Self {
+        match item {
+            PaypalIncrementalStatus::CREATED
+            | PaypalIncrementalStatus::CAPTURED
+            | PaypalIncrementalStatus::PARTIALLYCAPTURED => Self::Success,
+            PaypalIncrementalStatus::PENDING => Self::Processing,
+            PaypalIncrementalStatus::DENIED | PaypalIncrementalStatus::VOIDED => Self::Failure,
+        }
+    }
+}
+
+impl<F>
+    TryFrom<
+        ResponseRouterData<
+            F,
+            PaypalIncrementalAuthResponse,
+            PaymentsIncrementalAuthorizationData,
+            PaymentsResponseData,
+        >,
+    > for RouterData<F, PaymentsIncrementalAuthorizationData, PaymentsResponseData>
+{
+    type Error = error_stack::Report<errors::ConnectorError>;
+    fn try_from(
+        item: ResponseRouterData<
+            F,
+            PaypalIncrementalAuthResponse,
+            PaymentsIncrementalAuthorizationData,
+            PaymentsResponseData,
+        >,
+    ) -> Result<Self, Self::Error> {
+        let status = common_enums::AuthorizationStatus::from(item.response.status);
+        Ok(Self {
+            response: Ok(PaymentsResponseData::IncrementalAuthorizationResponse {
+                status,
+                error_code: None,
+                error_message: None,
+                connector_authorization_id: Some(item.response.id),
+            }),
+            ..item.data
+        })
+    }
+}
+
 #[derive(Debug)]
 pub enum PaypalAuthType {
     TemporaryAuth,
@@ -1747,6 +1884,7 @@ pub struct CardDetails {
 pub struct PaypalMeta {
     pub authorize_id: Option<String>,
     pub capture_id: Option<String>,
+    pub incremental_authorization_id: Option<String>,
     pub psync_flow: PaypalPaymentIntent,
     pub next_action: Option<api_models::payments::NextActionCall>,
     pub order_id: Option<String>,
@@ -1782,12 +1920,25 @@ fn get_id_based_on_intent(
     .ok_or_else(|| errors::ConnectorError::MissingConnectorTransactionID.into())
 }
 
-impl<F, T> TryFrom<ResponseRouterData<F, PaypalOrdersResponse, T, PaymentsResponseData>>
-    for RouterData<F, T, PaymentsResponseData>
+fn extract_incremental_authorization_id(response: &PaypalOrdersResponse) -> Option<String> {
+    for unit in &response.purchase_units {
+        if let Some(authorizations) = &unit.payments.authorizations {
+            if let Some(first_auth) = authorizations.first() {
+                return Some(first_auth.id.clone());
+            }
+        }
+    }
+    None
+}
+
+impl<F, Req> TryFrom<ResponseRouterData<F, PaypalOrdersResponse, Req, PaymentsResponseData>>
+    for RouterData<F, Req, PaymentsResponseData>
+where
+    Req: GetRequestIncrementalAuthorization,
 {
     type Error = error_stack::Report<errors::ConnectorError>;
     fn try_from(
-        item: ResponseRouterData<F, PaypalOrdersResponse, T, PaymentsResponseData>,
+        item: ResponseRouterData<F, PaypalOrdersResponse, Req, PaymentsResponseData>,
     ) -> Result<Self, Self::Error> {
         let purchase_units = item
             .response
@@ -1801,6 +1952,7 @@ impl<F, T> TryFrom<ResponseRouterData<F, PaypalOrdersResponse, T, PaymentsRespon
                 serde_json::json!(PaypalMeta {
                     authorize_id: None,
                     capture_id: Some(id),
+                    incremental_authorization_id: None,
                     psync_flow: item.response.intent.clone(),
                     next_action: None,
                     order_id: None,
@@ -1812,6 +1964,9 @@ impl<F, T> TryFrom<ResponseRouterData<F, PaypalOrdersResponse, T, PaymentsRespon
                 serde_json::json!(PaypalMeta {
                     authorize_id: Some(id),
                     capture_id: None,
+                    incremental_authorization_id: extract_incremental_authorization_id(
+                        &item.response
+                    ),
                     psync_flow: item.response.intent.clone(),
                     next_action: None,
                     order_id: None,
@@ -1872,7 +2027,10 @@ impl<F, T> TryFrom<ResponseRouterData<F, PaypalOrdersResponse, T, PaymentsRespon
                     .invoice_id
                     .clone()
                     .or(Some(item.response.id)),
-                incremental_authorization_allowed: None,
+                incremental_authorization_allowed: item
+                    .data
+                    .request
+                    .get_request_incremental_authorization(),
                 charges: None,
             }),
             ..item.data
@@ -1965,6 +2123,7 @@ impl<F, T>
         let connector_meta = serde_json::json!(PaypalMeta {
             authorize_id: None,
             capture_id: None,
+            incremental_authorization_id: None,
             psync_flow: item.response.intent,
             next_action,
             order_id: None,
@@ -2017,6 +2176,7 @@ impl
         let connector_meta = serde_json::json!(PaypalMeta {
             authorize_id: None,
             capture_id: None,
+            incremental_authorization_id: None,
             psync_flow: item.response.intent,
             next_action: None,
             order_id: None,
@@ -2072,6 +2232,7 @@ impl
         let connector_meta = serde_json::json!(PaypalMeta {
             authorize_id: None,
             capture_id: None,
+            incremental_authorization_id: None,
             psync_flow: item.response.intent,
             next_action,
             order_id: Some(item.response.id.clone()),
@@ -2144,6 +2305,7 @@ impl<F>
         let connector_meta = serde_json::json!(PaypalMeta {
             authorize_id: None,
             capture_id: None,
+            incremental_authorization_id: None,
             psync_flow: PaypalPaymentIntent::Authenticate, // when there is no capture or auth id present
             next_action: None,
             order_id: None,
@@ -2550,6 +2712,7 @@ impl TryFrom<PaymentsCaptureResponseRouterData<PaypalCaptureResponse>>
                 connector_metadata: Some(serde_json::json!(PaypalMeta {
                     authorize_id: connector_payment_id.authorize_id,
                     capture_id: Some(item.response.id.clone()),
+                    incremental_authorization_id: None,
                     psync_flow: PaypalPaymentIntent::Capture,
                     next_action: None,
                     order_id: None,
diff --git a/crates/hyperswitch_connectors/src/default_implementations.rs b/crates/hyperswitch_connectors/src/default_implementations.rs
index 38e63124029..bdf3ee5604b 100644
--- a/crates/hyperswitch_connectors/src/default_implementations.rs
+++ b/crates/hyperswitch_connectors/src/default_implementations.rs
@@ -1086,7 +1086,6 @@ default_imp_for_incremental_authorization!(
     connectors::Payload,
     connectors::Payme,
     connectors::Payone,
-    connectors::Paypal,
     connectors::Paystack,
     connectors::Payu,
     connectors::Placetopay,
diff --git a/crates/hyperswitch_domain_models/src/router_request_types.rs b/crates/hyperswitch_domain_models/src/router_request_types.rs
index 31879b4cfe3..fd37b30f6f8 100644
--- a/crates/hyperswitch_domain_models/src/router_request_types.rs
+++ b/crates/hyperswitch_domain_models/src/router_request_types.rs
@@ -155,6 +155,7 @@ pub struct PaymentsIncrementalAuthorizationData {
     pub currency: storage_enums::Currency,
     pub reason: Option<String>,
     pub connector_transaction_id: String,
+    pub connector_meta: Option<serde_json::Value>,
 }
 
 #[derive(Debug, Clone, Default)]
diff --git a/crates/hyperswitch_interfaces/src/api.rs b/crates/hyperswitch_interfaces/src/api.rs
index 4162eb29094..70d1bc2c1b6 100644
--- a/crates/hyperswitch_interfaces/src/api.rs
+++ b/crates/hyperswitch_interfaces/src/api.rs
@@ -722,7 +722,7 @@ pub trait ConnectorTransactionId: ConnectorCommon + Sync {
     /// fn connector_transaction_id
     fn connector_transaction_id(
         &self,
-        payment_attempt: hyperswitch_domain_models::payments::payment_attempt::PaymentAttempt,
+        payment_attempt: &hyperswitch_domain_models::payments::payment_attempt::PaymentAttempt,
     ) -> Result<Option<String>, ApiErrorResponse> {
         Ok(payment_attempt
             .get_connector_payment_id()
diff --git a/crates/hyperswitch_interfaces/src/connector_integration_interface.rs b/crates/hyperswitch_interfaces/src/connector_integration_interface.rs
index 998df9ec61c..c40c65ac5f7 100644
--- a/crates/hyperswitch_interfaces/src/connector_integration_interface.rs
+++ b/crates/hyperswitch_interfaces/src/connector_integration_interface.rs
@@ -793,7 +793,7 @@ impl api::ConnectorTransactionId for ConnectorEnum {
     /// A `Result` containing an optional transaction ID or an ApiErrorResponse
     fn connector_transaction_id(
         &self,
-        payment_attempt: hyperswitch_domain_models::payments::payment_attempt::PaymentAttempt,
+        payment_attempt: &hyperswitch_domain_models::payments::payment_attempt::PaymentAttempt,
     ) -> Result<Option<String>, ApiErrorResponse> {
         match self {
             Self::Old(connector) => connector.connector_transaction_id(payment_attempt),
diff --git a/crates/router/src/core/payments/transformers.rs b/crates/router/src/core/payments/transformers.rs
index 2379dddb8fd..1cd67d61f68 100644
--- a/crates/router/src/core/payments/transformers.rs
+++ b/crates/router/src/core/payments/transformers.rs
@@ -478,7 +478,7 @@ pub async fn construct_payment_router_data_for_capture<'a>(
         currency: payment_data.payment_intent.amount_details.currency,
         connector_transaction_id: connector
             .connector
-            .connector_transaction_id(payment_data.payment_attempt.clone())?
+            .connector_transaction_id(&payment_data.payment_attempt)?
             .ok_or(errors::ApiErrorResponse::ResourceIdNotFound)?,
         payment_amount: amount.get_amount_as_i64(), // This should be removed once we start moving to connector module
         minor_payment_amount: amount,
@@ -3756,6 +3756,7 @@ impl<F: Clone> TryFrom<PaymentAdditionalData<'_, F>> for types::PaymentsSyncData
     }
 }
 
+#[cfg(feature = "v1")]
 impl<F: Clone> TryFrom<PaymentAdditionalData<'_, F>>
     for types::PaymentsIncrementalAuthorizationData
 {
@@ -3763,39 +3764,68 @@ impl<F: Clone> TryFrom<PaymentAdditionalData<'_, F>>
 
     fn try_from(additional_data: PaymentAdditionalData<'_, F>) -> Result<Self, Self::Error> {
         let payment_data = additional_data.payment_data;
+        let payment_attempt = &payment_data.payment_attempt;
         let connector = api::ConnectorData::get_connector_by_name(
             &additional_data.state.conf.connectors,
             &additional_data.connector_name,
             api::GetToken::Connector,
-            payment_data.payment_attempt.merchant_connector_id.clone(),
+            payment_attempt.merchant_connector_id.clone(),
         )?;
-        let total_amount = payment_data
+        let incremental_details = payment_data
             .incremental_authorization_details
-            .clone()
-            .map(|details| details.total_amount)
+            .as_ref()
             .ok_or(
                 report!(errors::ApiErrorResponse::InternalServerError)
                     .attach_printable("missing incremental_authorization_details in payment_data"),
             )?;
-        let additional_amount = payment_data
+        Ok(Self {
+            total_amount: incremental_details.total_amount.get_amount_as_i64(),
+            additional_amount: incremental_details.additional_amount.get_amount_as_i64(),
+            reason: incremental_details.reason.clone(),
+            currency: payment_data.currency,
+            connector_transaction_id: connector
+                .connector
+                .connector_transaction_id(payment_attempt)?
+                .ok_or(errors::ApiErrorResponse::ResourceIdNotFound)?,
+            connector_meta: payment_attempt.connector_metadata.clone(),
+        })
+    }
+}
+
+#[cfg(feature = "v2")]
+impl<F: Clone> TryFrom<PaymentAdditionalData<'_, F>>
+    for types::PaymentsIncrementalAuthorizationData
+{
+    type Error = error_stack::Report<errors::ApiErrorResponse>;
+
+    fn try_from(additional_data: PaymentAdditionalData<'_, F>) -> Result<Self, Self::Error> {
+        let payment_data = additional_data.payment_data;
+        let connector = api::ConnectorData::get_connector_by_name(
+            &additional_data.state.conf.connectors,
+            &additional_data.connector_name,
+            api::GetToken::Connector,
+            payment_data.payment_attempt.merchant_connector_id.clone(),
+        )?;
+        let incremental_details = payment_data
             .incremental_authorization_details
-            .clone()
-            .map(|details| details.additional_amount)
+            .as_ref()
             .ok_or(
                 report!(errors::ApiErrorResponse::InternalServerError)
                     .attach_printable("missing incremental_authorization_details in payment_data"),
             )?;
         Ok(Self {
-            total_amount: total_amount.get_amount_as_i64(),
-            additional_amount: additional_amount.get_amount_as_i64(),
-            reason: payment_data
-                .incremental_authorization_details
-                .and_then(|details| details.reason),
+            total_amount: incremental_details.total_amount.get_amount_as_i64(),
+            additional_amount: incremental_details.additional_amount.get_amount_as_i64(),
+            reason: incremental_details.reason.clone(),
             currency: payment_data.currency,
             connector_transaction_id: connector
                 .connector
-                .connector_transaction_id(payment_data.payment_attempt.clone())?
+                .connector_transaction_id(&payment_data.payment_attempt)?
                 .ok_or(errors::ApiErrorResponse::ResourceIdNotFound)?,
+            connector_meta: payment_data
+                .payment_attempt
+                .connector_metadata
+                .map(|secret| secret.expose()),
         })
     }
 }
@@ -3828,7 +3858,7 @@ impl<F: Clone> TryFrom<PaymentAdditionalData<'_, F>> for types::PaymentsCaptureD
             currency: payment_data.currency,
             connector_transaction_id: connector
                 .connector
-                .connector_transaction_id(payment_data.payment_attempt.clone())?
+                .connector_transaction_id(&payment_data.payment_attempt)?
                 .ok_or(errors::ApiErrorResponse::ResourceIdNotFound)?,
             payment_amount: amount.get_amount_as_i64(), // This should be removed once we start moving to connector module
             minor_payment_amount: amount,
@@ -3896,7 +3926,7 @@ impl<F: Clone> TryFrom<PaymentAdditionalData<'_, F>> for types::PaymentsCaptureD
             currency: payment_data.currency,
             connector_transaction_id: connector
                 .connector
-                .connector_transaction_id(payment_data.payment_attempt.clone())?
+                .connector_transaction_id(&payment_data.payment_attempt)?
                 .ok_or(errors::ApiErrorResponse::ResourceIdNotFound)?,
             payment_amount: amount.get_amount_as_i64(), // This should be removed once we start moving to connector module
             minor_payment_amount: amount,
@@ -3973,7 +4003,7 @@ impl<F: Clone> TryFrom<PaymentAdditionalData<'_, F>> for types::PaymentsCancelDa
             currency: Some(payment_data.currency),
             connector_transaction_id: connector
                 .connector
-                .connector_transaction_id(payment_data.payment_attempt.clone())?
+                .connector_transaction_id(&payment_data.payment_attempt)?
                 .ok_or(errors::ApiErrorResponse::ResourceIdNotFound)?,
             cancellation_reason: payment_data.payment_attempt.cancellation_reason,
             connector_meta: payment_data.payment_attempt.connector_metadata,
@@ -4127,7 +4157,7 @@ impl<F: Clone> TryFrom<PaymentAdditionalData<'_, F>> for types::PaymentsUpdateMe
                 .attach_printable("payment_intent.metadata not found")?,
             connector_transaction_id: connector
                 .connector
-                .connector_transaction_id(payment_data.payment_attempt.clone())?
+                .connector_transaction_id(&payment_data.payment_attempt)?
                 .ok_or(errors::ApiErrorResponse::ResourceIdNotFound)?,
         })
     }

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

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

Closes this [issue](https://github.com/juspay/hyperswitch/issues/8516)

## Description
<!-- Describe your changes in detail -->

Added Incremental Authorization flow for Paypal

### 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
<!--
Why is this change required? What problem does it solve?
If it fixes an open issue, please link to the issue here.

If you don't have an issue, we'd recommend starting with one first so the PR
can focus on the implementation (unless it is an obvious bug or documentation fix
that will have little conversation).
-->
https://github.com/juspay/hyperswitch/issues/8516

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

Postman Test

Payments - Create:

Request:

```
curl --location 'http://localhost:8080/payments' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'api-key: dev_1PzpFzUXX4ZALEpOUNRFMeXyRB2WCZHfhd6AXOKifb7fqVRfrUFQLiqS4DchmszP' \
--data-raw '
{
    "amount": 1000,
    "currency": "USD",
    "confirm": true,
    "capture_method": "manual",
    "capture_on": "2022-09-10T10:11:12Z",
    "amount_to_capture": 1000,
    "customer_id": "StripeCustomer",
    "email": "abcdef123@gmail.com",
    "name": "John Doe",
    "phone": "999999999",
    "phone_country_code": "+65",
    "description": "Its my first payment request",
    "authentication_type": "no_three_ds",
    "return_url": "https://duck.com",
    "billing": {
        "address": {
            "first_name": "John",
            "last_name": "Doe",
            "line1": "1467",
            "line2": "Harrison Street",
            "line3": "Harrison Street",
            "city": "San Fransico",
            "state": "California",
            "zip": "94122",
            "country": "AT"
        }
    },
    "payment_method": "card",
    "payment_method_type": "credit",
    "payment_method_data": {
        "card": {
            "card_number": "4111111111111111",
            "card_exp_month": "11",
            "card_exp_year": "2030",
            "card_holder_name": "joseph Doe",
            "card_cvc": "276"
        }
    },
    "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": "nl-NL",
        "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"
    },
    "shipping": {
        "address": {
            "line1": "1467",
            "line2": "Harrison Street",
            "line3": "Harrison Street",
            "city": "San Fransico",
            "state": "California",
            "zip": "94122",
            "country": "US",
            "first_name": "John",
            "last_name": "Doe"
        }
    },
    "statement_descriptor_name": "joseph",
    "statement_descriptor_suffix": "JS",
    "metadata": {
        "udf1": "value1",
        "new_customer": "true",
        "login_date": "2019-09-10T10:11:12Z"
    },
    "request_incremental_authorization": true
}'
```

Response:

```
{
    "payment_id": "pay_1agl8yqCWefEuD7cM9DK",
    "merchant_id": "merchant_1751411099",
    "status": "requires_capture",
    "amount": 1000,
    "net_amount": 1000,
    "shipping_cost": null,
    "amount_capturable": 1000,
    "amount_received": null,
    "connector": "paypal",
    "client_secret": "pay_1agl8yqCWefEuD7cM9DK_secret_iYISlExnoU9wZBlUbA0J",
    "created": "2025-07-01T23:05:08.941Z",
    "currency": "USD",
    "customer_id": "StripeCustomer",
    "customer": {
        "id": "StripeCustomer",
        "name": "John Doe",
        "email": "abcdef123@gmail.com",
        "phone": "999999999",
        "phone_country_code": "+65"
    },
    "description": "Its my first payment request",
    "refunds": null,
    "disputes": null,
    "mandate_id": null,
    "mandate_data": null,
    "setup_future_usage": null,
    "off_session": null,
    "capture_on": null,
    "capture_method": "manual",
    "payment_method": "card",
    "payment_method_data": {
        "card": {
            "last4": "1111",
            "card_type": null,
            "card_network": null,
            "card_issuer": null,
            "card_issuing_country": null,
            "card_isin": "411111",
            "card_extended_bin": null,
            "card_exp_month": "11",
            "card_exp_year": "2030",
            "card_holder_name": "joseph Doe",
            "payment_checks": null,
            "authentication_data": null
        },
        "billing": null
    },
    "payment_token": null,
    "shipping": {
        "address": {
            "city": "San Fransico",
            "country": "US",
            "line1": "1467",
            "line2": "Harrison Street",
            "line3": "Harrison Street",
            "zip": "94122",
            "state": "California",
            "first_name": "John",
            "last_name": "Doe"
        },
        "phone": null,
        "email": null
    },
    "billing": {
        "address": {
            "city": "San Fransico",
            "country": "AT",
            "line1": "1467",
            "line2": "Harrison Street",
            "line3": "Harrison Street",
            "zip": "94122",
            "state": "California",
            "first_name": "John",
            "last_name": "Doe"
        },
        "phone": null,
        "email": null
    },
    "order_details": null,
    "email": "abcdef123@gmail.com",
    "name": "John Doe",
    "phone": "999999999",
    "return_url": "https://duck.com/",
    "authentication_type": "no_three_ds",
    "statement_descriptor_name": "joseph",
    "statement_descriptor_suffix": "JS",
    "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": {
        "customer_id": "StripeCustomer",
        "created_at": 1751411108,
        "expires": 1751414708,
        "secret": "epk_dc7e9d0a449a4c28816179c01355a423"
    },
    "manual_retry_allowed": false,
    "connector_transaction_id": "120066274887",
    "frm_message": null,
    "metadata": {
        "udf1": "value1",
        "login_date": "2019-09-10T10:11:12Z",
        "new_customer": "true"
    },
    "connector_metadata": null,
    "feature_metadata": null,
    "reference_id": "120066274887",
    "payment_link": null,
    "profile_id": "pro_ufQauYhoCJvm3iPPC8mG",
    "surcharge_details": null,
    "attempt_count": 1,
    "merchant_decision": null,
    "merchant_connector_id": "mca_6BJ52EkitWXtjk3xh9jx",
    "incremental_authorization_allowed": true,
    "authorization_count": null,
    "incremental_authorizations": null,
    "external_authentication_details": null,
    "external_3ds_authentication_attempted": false,
    "expires_on": "2025-07-01T23:20:08.940Z",
    "fingerprint": null,
    "browser_info": {
        "language": "nl-NL",
        "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": "2025-07-01T23:05:11.094Z",
    "split_payments": null,
    "frm_metadata": null,
    "extended_authorization_applied": null,
    "capture_before": null,
    "merchant_order_reference_id": null,
    "order_tax_amount": null,
    "connector_mandate_id": null,
    "card_discovery": "manual",
    "force_3ds_challenge": false,
    "force_3ds_challenge_trigger": false,
    "issuer_error_code": null,
    "issuer_error_message": null,
    "is_iframe_redirection_enabled": null,
    "whole_connector_response": null
}
```

Incremental Authorization:

Request:

```
curl --location 'http://localhost:8080/payments/pay_1agl8yqCWefEuD7cM9DK/incremental_authorization' \
--header 'Content-Type: application/json' \
--header 'api-key: dev_1PzpFzUXX4ZALEpOUNRFMeXyRB2WCZHfhd6AXOKifb7fqVRfrUFQLiqS4DchmszP' \
--data '{
  "amount": 6540,
  "reason": "customer gone overbudget"
}'
```

**NOTE**: The response will return failure status if the payment authorization has not yet surpassed the 4-day threshold. As per Paypal's documentation, a payment becomes eligible for incremental authorization only after the initial authorization is older than 4 days.

Response:

```
{
    "payment_id": "pay_KorlaZqtqUdTrpKtGhwj",
    "merchant_id": "merchant_1751412204",
    "status": "requires_capture",
    "amount": 1050,
    "net_amount": 1050,
    "shipping_cost": null,
    "amount_capturable": 1050,
    "amount_received": null,
    "connector": "paypal",
    "client_secret": "pay_KorlaZqtqUdTrpKtGhwj_secret_JvQWos0QgC06DTMUIUod",
    "created": "2025-07-01T23:23:32.564Z",
    "currency": "USD",
    "customer_id": null,
    "customer": {
        "id": null,
        "name": "John Doe",
        "email": "abcdef123@gmail.com",
        "phone": "999999999",
        "phone_country_code": "+65"
    },
    "description": "Its my first payment request",
    "refunds": null,
    "disputes": null,
    "mandate_id": null,
    "mandate_data": null,
    "setup_future_usage": null,
    "off_session": null,
    "capture_on": null,
    "capture_method": "manual",
    "payment_method": "card",
    "payment_method_data": {
        "card": {
            "last4": "1111",
            "card_type": null,
            "card_network": null,
            "card_issuer": null,
            "card_issuing_country": null,
            "card_isin": "411111",
            "card_extended_bin": null,
            "card_exp_month": "11",
            "card_exp_year": "2030",
            "card_holder_name": "joseph Doe",
            "payment_checks": null,
            "authentication_data": null
        },
        "billing": null
    },
    "payment_token": null,
    "shipping": null,
    "billing": null,
    "order_details": null,
    "email": null,
    "name": null,
    "phone": null,
    "return_url": "https://duck.com/",
    "authentication_type": "no_three_ds",
    "statement_descriptor_name": "joseph",
    "statement_descriptor_suffix": "JS",
    "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": "4657001324168371U",
    "frm_message": null,
    "metadata": {
        "udf1": "value1",
        "login_date": "2019-09-10T10:11:12Z",
        "new_customer": "true"
    },
    "connector_metadata": null,
    "feature_metadata": null,
    "reference_id": "pay_KorlaZqtqUdTrpKtGhwj_1",
    "payment_link": null,
    "profile_id": "pro_K0AcXOIDkvcyvNd6AU0K",
    "surcharge_details": null,
    "attempt_count": 1,
    "merchant_decision": null,
    "merchant_connector_id": "mca_frPGdve129UARoghNL0x",
    "incremental_authorization_allowed": true,
    "authorization_count": 2,
    "incremental_authorizations": [
        {
            "authorization_id": "auth_bdKFP2R2zflE7nGe7DSm_1",
            "amount": 6540,
            "status": "failure",
            "error_code": "REAUTHORIZATION_TOO_SOON",
            "error_message": "REAUTHORIZATION_TOO_SOON",
            "previously_authorized_amount": 1000
        },
       {
            "authorization_id": "auth_fkKGl3yaPor09wvHf7KmS_2",
            "amount": 1050,
            "status": "succeeded",
            "error_code": null,
            "error_message": null,
            "previously_authorized_amount": 1000
        },
    ],
    "external_authentication_details": null,
    "external_3ds_authentication_attempted": false,
    "expires_on": "2025-07-01T23:38:32.564Z",
    "fingerprint": null,
    "browser_info": {
        "language": "nl-NL",
        "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": "2025-07-01T23:23:41.215Z",
    "split_payments": null,
    "frm_metadata": null,
    "extended_authorization_applied": null,
    "capture_before": null,
    "merchant_order_reference_id": null,
    "order_tax_amount": null,
    "connector_mandate_id": null,
    "card_discovery": "manual",
    "force_3ds_challenge": false,
    "force_3ds_challenge_trigger": false,
    "issuer_error_code": null,
    "issuer_error_message": null,
    "is_iframe_redirection_enabled": null,
    "whole_connector_response": null
}
```

Cypress Screenshot for Incremental Authorization:

<img width="627" height="416" alt="Screenshot 2025-07-14 at 1 34 28 PM" src="https://github.com/user-attachments/assets/b7917284-2f90-484e-b720-162ae28a1b32" />

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