Bug: [FEATURE]: Add Refunds Retrieve and Rsync Flow in V2 apis.



Add Refunds Retrieve and Rsync Flow in V2 apis.
diff --git a/api-reference-v2/openapi_spec.json b/api-reference-v2/openapi_spec.json
index 8b27fde3201..b6d6fb87e27 100644
--- a/api-reference-v2/openapi_spec.json
+++ b/api-reference-v2/openapi_spec.json
@@ -3265,6 +3265,47 @@
         ]
       }
     },
+    "/v2/refunds/{id}": {
+      "get": {
+        "tags": [
+          "Refunds"
+        ],
+        "summary": "Refunds - Retrieve",
+        "description": "Retrieves a Refund. This may be used to get the status of a previously initiated refund",
+        "operationId": "Retrieve a Refund",
+        "parameters": [
+          {
+            "name": "id",
+            "in": "path",
+            "description": "The identifier for refund",
+            "required": true,
+            "schema": {
+              "type": "string"
+            }
+          }
+        ],
+        "responses": {
+          "200": {
+            "description": "Refund retrieved",
+            "content": {
+              "application/json": {
+                "schema": {
+                  "$ref": "#/components/schemas/RefundResponse"
+                }
+              }
+            }
+          },
+          "404": {
+            "description": "Refund does not exist in our records"
+          }
+        },
+        "security": [
+          {
+            "api_key": []
+          }
+        ]
+      }
+    },
     "/v2/process_tracker/revenue_recovery_workflow/{revenue_recovery_id}": {
       "get": {
         "tags": [
diff --git a/crates/api_models/src/events/refund.rs b/crates/api_models/src/events/refund.rs
index 14e1984a760..08c6dd52c39 100644
--- a/crates/api_models/src/events/refund.rs
+++ b/crates/api_models/src/events/refund.rs
@@ -59,6 +59,13 @@ impl ApiEventMetric for RefundsRetrieveRequest {
     }
 }
 
+#[cfg(feature = "v2")]
+impl ApiEventMetric for refunds::RefundsRetrieveRequest {
+    fn get_api_event_type(&self) -> Option<ApiEventsType> {
+        None
+    }
+}
+
 #[cfg(feature = "v1")]
 impl ApiEventMetric for RefundUpdateRequest {
     fn get_api_event_type(&self) -> Option<ApiEventsType> {
diff --git a/crates/api_models/src/refunds.rs b/crates/api_models/src/refunds.rs
index e8fcf39ca4e..775e0dfeef7 100644
--- a/crates/api_models/src/refunds.rs
+++ b/crates/api_models/src/refunds.rs
@@ -105,11 +105,19 @@ pub struct RefundsCreateRequest {
     pub metadata: Option<pii::SecretSerdeValue>,
 }
 
+#[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "refunds_v2")))]
+#[derive(Default, Debug, Clone, Deserialize)]
+pub struct RefundsRetrieveBody {
+    pub force_sync: Option<bool>,
+}
+
+#[cfg(all(feature = "v2", feature = "refunds_v2"))]
 #[derive(Default, Debug, Clone, Deserialize)]
 pub struct RefundsRetrieveBody {
     pub force_sync: Option<bool>,
 }
 
+#[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "refunds_v2")))]
 #[derive(Default, Debug, ToSchema, Clone, Deserialize, Serialize)]
 pub struct RefundsRetrieveRequest {
     /// Unique Identifier for the Refund. This is to ensure idempotency for multiple partial refund initiated against the same payment. If the identifiers is not defined by the merchant, this filed shall be auto generated and provide in the API response. It is recommended to generate uuid(v4) as the refund_id.
@@ -128,6 +136,22 @@ pub struct RefundsRetrieveRequest {
     pub merchant_connector_details: Option<admin::MerchantConnectorDetailsWrap>,
 }
 
+#[cfg(all(feature = "v2", feature = "refunds_v2"))]
+#[derive(Debug, ToSchema, Clone, Deserialize, Serialize)]
+pub struct RefundsRetrieveRequest {
+    /// Unique Identifier for the Refund. This is to ensure idempotency for multiple partial refund initiated against the same payment. If the identifiers is not defined by the merchant, this filed shall be auto generated and provide in the API response. It is recommended to generate uuid(v4) as the refund_id.
+    #[schema(
+        max_length = 30,
+        min_length = 30,
+        example = "ref_mbabizu24mvu3mela5njyhpit4"
+    )]
+    pub refund_id: common_utils::id_type::GlobalRefundId,
+
+    /// `force_sync` with the connector to get refund details
+    /// (defaults to false)
+    pub force_sync: Option<bool>,
+}
+
 #[derive(Default, Debug, ToSchema, Clone, Deserialize, Serialize)]
 #[serde(deny_unknown_fields)]
 pub struct RefundUpdateRequest {
diff --git a/crates/diesel_models/src/refund.rs b/crates/diesel_models/src/refund.rs
index e197bc3c211..927965a5338 100644
--- a/crates/diesel_models/src/refund.rs
+++ b/crates/diesel_models/src/refund.rs
@@ -757,6 +757,24 @@ impl RefundUpdate {
             processor_refund_data: connector_refund_id.extract_hashed_data(),
         }
     }
+
+    pub fn build_error_update_for_refund_failure(
+        refund_status: Option<storage_enums::RefundStatus>,
+        refund_error_message: Option<String>,
+        refund_error_code: Option<String>,
+        storage_scheme: &storage_enums::MerchantStorageScheme,
+    ) -> Self {
+        Self::ErrorUpdate {
+            refund_status,
+            refund_error_message,
+            refund_error_code,
+            updated_by: storage_scheme.to_string(),
+            connector_refund_id: None,
+            processor_refund_data: None,
+            unified_code: None,
+            unified_message: None,
+        }
+    }
 }
 
 #[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "refunds_v2")))]
diff --git a/crates/hyperswitch_domain_models/src/payments.rs b/crates/hyperswitch_domain_models/src/payments.rs
index dea3d5f7128..3cb9cf766d6 100644
--- a/crates/hyperswitch_domain_models/src/payments.rs
+++ b/crates/hyperswitch_domain_models/src/payments.rs
@@ -763,6 +763,10 @@ impl PaymentIntent {
             })
             .transpose()
     }
+
+    pub fn get_currency(&self) -> storage_enums::Currency {
+        self.amount_details.currency
+    }
 }
 
 #[cfg(feature = "v1")]
diff --git a/crates/openapi/src/openapi_v2.rs b/crates/openapi/src/openapi_v2.rs
index b7f88345484..bb48871190e 100644
--- a/crates/openapi/src/openapi_v2.rs
+++ b/crates/openapi/src/openapi_v2.rs
@@ -148,6 +148,7 @@ Never share your secret api keys. Keep them guarded and secure.
 
         //Routes for refunds
         routes::refunds::refunds_create,
+        routes::refunds::refunds_retrieve,
 
         // Routes for Revenue Recovery flow under Process Tracker
         routes::revenue_recovery::revenue_recovery_pt_retrieve_api
diff --git a/crates/openapi/src/routes/refunds.rs b/crates/openapi/src/routes/refunds.rs
index 0ff0891ee54..5c55de2d177 100644
--- a/crates/openapi/src/routes/refunds.rs
+++ b/crates/openapi/src/routes/refunds.rs
@@ -64,6 +64,7 @@ pub async fn refunds_create() {}
     operation_id = "Retrieve a Refund",
     security(("api_key" = []))
 )]
+#[cfg(feature = "v1")]
 pub async fn refunds_retrieve() {}
 
 /// Refunds - Retrieve (POST)
@@ -212,3 +213,23 @@ pub async fn refunds_filter_list() {}
 )]
 #[cfg(feature = "v2")]
 pub async fn refunds_create() {}
+
+/// Refunds - Retrieve
+///
+/// Retrieves a Refund. This may be used to get the status of a previously initiated refund
+#[utoipa::path(
+    get,
+    path = "/v2/refunds/{id}",
+    params(
+        ("id" = String, Path, description = "The identifier for refund")
+    ),
+    responses(
+        (status = 200, description = "Refund retrieved", body = RefundResponse),
+        (status = 404, description = "Refund does not exist in our records")
+    ),
+    tag = "Refunds",
+    operation_id = "Retrieve a Refund",
+    security(("api_key" = []))
+)]
+#[cfg(feature = "v2")]
+pub async fn refunds_retrieve() {}
diff --git a/crates/router/src/core/refunds_v2.rs b/crates/router/src/core/refunds_v2.rs
index 89315006dc6..b6ccfe8376b 100644
--- a/crates/router/src/core/refunds_v2.rs
+++ b/crates/router/src/core/refunds_v2.rs
@@ -1,10 +1,17 @@
-use std::str::FromStr;
+use std::{fmt::Debug, str::FromStr};
 
 use api_models::{enums::Connector, refunds::RefundErrorDetails};
 use common_utils::{id_type, types as common_utils_types};
 use error_stack::{report, ResultExt};
-use hyperswitch_domain_models::router_data::{ErrorResponse, RouterData};
-use hyperswitch_interfaces::integrity::{CheckIntegrity, FlowIntegrity, GetIntegrityObject};
+use hyperswitch_domain_models::{
+    router_data::{ErrorResponse, RouterData},
+    router_data_v2::RefundFlowData,
+};
+use hyperswitch_interfaces::{
+    api::{Connector as ConnectorTrait, ConnectorIntegration},
+    connector_integration_v2::{ConnectorIntegrationV2, ConnectorV2},
+    integrity::{CheckIntegrity, FlowIntegrity, GetIntegrityObject},
+};
 use router_env::{instrument, tracing};
 
 use crate::{
@@ -202,20 +209,27 @@ pub async fn trigger_refund_to_gateway(
     Ok(response)
 }
 
-async fn call_connector_service(
+async fn call_connector_service<F>(
     state: &SessionState,
     connector: &api::ConnectorData,
     add_access_token_result: types::AddAccessTokenResult,
-    router_data: RouterData<api::Execute, types::RefundsData, types::RefundsResponseData>,
+    router_data: RouterData<F, types::RefundsData, types::RefundsResponseData>,
 ) -> Result<
-    RouterData<api::Execute, types::RefundsData, types::RefundsResponseData>,
+    RouterData<F, types::RefundsData, types::RefundsResponseData>,
     error_stack::Report<errors::ConnectorError>,
-> {
+>
+where
+    F: Debug + Clone + 'static,
+    dyn ConnectorTrait + Sync:
+        ConnectorIntegration<F, types::RefundsData, types::RefundsResponseData>,
+    dyn ConnectorV2 + Sync:
+        ConnectorIntegrationV2<F, RefundFlowData, types::RefundsData, types::RefundsResponseData>,
+{
     if !(add_access_token_result.connector_supports_access_token
         && router_data.access_token.is_none())
     {
         let connector_integration: services::BoxedRefundConnectorIntegrationInterface<
-            api::Execute,
+            F,
             types::RefundsData,
             types::RefundsResponseData,
         > = connector.connector.get_connector_integration();
@@ -382,9 +396,12 @@ pub fn get_refund_update_for_refund_response_data(
     }
 }
 
-pub fn perform_integrity_check(
-    mut router_data: RouterData<api::Execute, types::RefundsData, types::RefundsResponseData>,
-) -> RouterData<api::Execute, types::RefundsData, types::RefundsResponseData> {
+pub fn perform_integrity_check<F>(
+    mut router_data: RouterData<F, types::RefundsData, types::RefundsResponseData>,
+) -> RouterData<F, types::RefundsData, types::RefundsResponseData>
+where
+    F: Debug + Clone + 'static,
+{
     // Initiating Integrity check
     let integrity_result = check_refund_integrity(&router_data.request, &router_data.response);
     router_data.integrity_check = integrity_result;
@@ -447,6 +464,276 @@ where
     request.check_integrity(request, connector_refund_id.to_owned())
 }
 
+// ********************************************** REFUND SYNC **********************************************
+
+#[instrument(skip_all)]
+pub async fn refund_retrieve_core_with_refund_id(
+    state: SessionState,
+    merchant_context: domain::MerchantContext,
+    profile: domain::Profile,
+    request: refunds::RefundsRetrieveRequest,
+) -> errors::RouterResponse<refunds::RefundResponse> {
+    let refund_id = request.refund_id.clone();
+    let db = &*state.store;
+    let profile_id = profile.get_id().to_owned();
+    let refund = db
+        .find_refund_by_id(
+            &refund_id,
+            merchant_context.get_merchant_account().storage_scheme,
+        )
+        .await
+        .to_not_found_response(errors::ApiErrorResponse::RefundNotFound)?;
+
+    let response = Box::pin(refund_retrieve_core(
+        state.clone(),
+        merchant_context,
+        Some(profile_id),
+        request,
+        refund,
+    ))
+    .await?;
+
+    api::RefundResponse::foreign_try_from(response).map(services::ApplicationResponse::Json)
+}
+
+#[instrument(skip_all)]
+pub async fn refund_retrieve_core(
+    state: SessionState,
+    merchant_context: domain::MerchantContext,
+    profile_id: Option<id_type::ProfileId>,
+    request: refunds::RefundsRetrieveRequest,
+    refund: storage::Refund,
+) -> errors::RouterResult<storage::Refund> {
+    let db = &*state.store;
+
+    let key_manager_state = &(&state).into();
+    core_utils::validate_profile_id_from_auth_layer(profile_id, &refund)?;
+    let payment_id = &refund.payment_id;
+    let payment_intent = db
+        .find_payment_intent_by_id(
+            key_manager_state,
+            payment_id,
+            merchant_context.get_merchant_key_store(),
+            merchant_context.get_merchant_account().storage_scheme,
+        )
+        .await
+        .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?;
+
+    let active_attempt_id = payment_intent
+        .active_attempt_id
+        .clone()
+        .ok_or(errors::ApiErrorResponse::InternalServerError)
+        .attach_printable("Active attempt id not found")?;
+
+    let payment_attempt = db
+        .find_payment_attempt_by_id(
+            key_manager_state,
+            merchant_context.get_merchant_key_store(),
+            &active_attempt_id,
+            merchant_context.get_merchant_account().storage_scheme,
+        )
+        .await
+        .to_not_found_response(errors::ApiErrorResponse::InternalServerError)?;
+
+    let unified_translated_message = if let (Some(unified_code), Some(unified_message)) =
+        (refund.unified_code.clone(), refund.unified_message.clone())
+    {
+        helpers::get_unified_translation(
+            &state,
+            unified_code,
+            unified_message.clone(),
+            state.locale.to_string(),
+        )
+        .await
+        .or(Some(unified_message))
+    } else {
+        refund.unified_message
+    };
+
+    let refund = storage::Refund {
+        unified_message: unified_translated_message,
+        ..refund
+    };
+
+    let response = if should_call_refund(&refund, request.force_sync.unwrap_or(false)) {
+        Box::pin(sync_refund_with_gateway(
+            &state,
+            &merchant_context,
+            &payment_attempt,
+            &payment_intent,
+            &refund,
+        ))
+        .await
+    } else {
+        Ok(refund)
+    }?;
+    Ok(response)
+}
+
+fn should_call_refund(refund: &diesel_models::refund::Refund, force_sync: bool) -> bool {
+    // This implies, we cannot perform a refund sync & `the connector_refund_id`
+    // doesn't exist
+    let predicate1 = refund.connector_refund_id.is_some();
+
+    // This allows refund sync at connector level if force_sync is enabled, or
+    // checks if the refund has failed
+    let predicate2 = force_sync
+        || !matches!(
+            refund.refund_status,
+            diesel_models::enums::RefundStatus::Failure
+                | diesel_models::enums::RefundStatus::Success
+        );
+
+    predicate1 && predicate2
+}
+
+#[allow(clippy::too_many_arguments)]
+#[instrument(skip_all)]
+pub async fn sync_refund_with_gateway(
+    state: &SessionState,
+    merchant_context: &domain::MerchantContext,
+    payment_attempt: &storage::PaymentAttempt,
+    payment_intent: &storage::PaymentIntent,
+    refund: &storage::Refund,
+) -> errors::RouterResult<storage::Refund> {
+    let db = &*state.store;
+
+    let connector_id = refund.connector.to_string();
+    let connector: api::ConnectorData = api::ConnectorData::get_connector_by_name(
+        &state.conf.connectors,
+        &connector_id,
+        api::GetToken::Connector,
+        payment_attempt.merchant_connector_id.clone(),
+    )
+    .change_context(errors::ApiErrorResponse::InternalServerError)
+    .attach_printable("Failed to get the connector")?;
+
+    let mca_id = payment_attempt.get_attempt_merchant_connector_account_id()?;
+
+    let mca = db
+        .find_merchant_connector_account_by_id(
+            &state.into(),
+            &mca_id,
+            merchant_context.get_merchant_key_store(),
+        )
+        .await
+        .change_context(errors::ApiErrorResponse::InternalServerError)
+        .attach_printable("Failed to fetch merchant connector account")?;
+
+    let connector_enum = mca.connector_name;
+
+    let mut router_data = core_utils::construct_refund_router_data::<api::RSync>(
+        state,
+        connector_enum,
+        merchant_context,
+        payment_intent,
+        payment_attempt,
+        refund,
+        &mca,
+    )
+    .await?;
+
+    let add_access_token_result =
+        access_token::add_access_token(state, &connector, merchant_context, &router_data, None)
+            .await?;
+
+    logger::debug!(refund_retrieve_router_data=?router_data);
+
+    access_token::update_router_data_with_access_token_result(
+        &add_access_token_result,
+        &mut router_data,
+        &payments::CallConnectorAction::Trigger,
+    );
+
+    let connector_response =
+        call_connector_service(state, &connector, add_access_token_result, router_data)
+            .await
+            .to_refund_failed_response()?;
+
+    let connector_response = perform_integrity_check(connector_response);
+
+    let refund_update =
+        build_refund_update_for_rsync(&connector, merchant_context, connector_response);
+
+    let response = state
+        .store
+        .update_refund(
+            refund.to_owned(),
+            refund_update,
+            merchant_context.get_merchant_account().storage_scheme,
+        )
+        .await
+        .to_not_found_response(errors::ApiErrorResponse::RefundNotFound)
+        .attach_printable_lazy(|| {
+            format!(
+                "Unable to update refund with refund_id: {}",
+                refund.id.get_string_repr()
+            )
+        })?;
+
+    // Implement outgoing webhook here
+    Ok(response)
+}
+
+pub fn build_refund_update_for_rsync(
+    connector: &api::ConnectorData,
+    merchant_context: &domain::MerchantContext,
+    router_data_response: RouterData<api::RSync, types::RefundsData, types::RefundsResponseData>,
+) -> storage::RefundUpdate {
+    let merchant_account = merchant_context.get_merchant_account();
+    let storage_scheme = &merchant_context.get_merchant_account().storage_scheme;
+
+    match router_data_response.response {
+        Err(error_message) => {
+            let refund_status = match error_message.status_code {
+                // marking failure for 2xx because this is genuine refund failure
+                200..=299 => Some(enums::RefundStatus::Failure),
+                _ => None,
+            };
+            let refund_error_message = error_message.reason.or(Some(error_message.message));
+            let refund_error_code = Some(error_message.code);
+
+            storage::RefundUpdate::build_error_update_for_refund_failure(
+                refund_status,
+                refund_error_message,
+                refund_error_code,
+                storage_scheme,
+            )
+        }
+        Ok(response) => match router_data_response.integrity_check.clone() {
+            Err(err) => {
+                metrics::INTEGRITY_CHECK_FAILED.add(
+                    1,
+                    router_env::metric_attributes!(
+                        ("connector", connector.connector_name.to_string()),
+                        ("merchant_id", merchant_account.get_id().clone()),
+                    ),
+                );
+
+                let connector_refund_id = err
+                    .connector_transaction_id
+                    .map(common_utils_types::ConnectorTransactionId::from);
+
+                storage::RefundUpdate::build_error_update_for_integrity_check_failure(
+                    err.field_names,
+                    connector_refund_id,
+                    storage_scheme,
+                )
+            }
+            Ok(()) => {
+                let connector_refund_id =
+                    common_utils_types::ConnectorTransactionId::from(response.connector_refund_id);
+
+                storage::RefundUpdate::build_refund_update(
+                    connector_refund_id,
+                    response.refund_status,
+                    storage_scheme,
+                )
+            }
+        },
+    }
+}
+
 // ********************************************** VALIDATIONS **********************************************
 
 #[instrument(skip_all)]
diff --git a/crates/router/src/core/utils.rs b/crates/router/src/core/utils.rs
index e123b792e75..d8b01bfd23e 100644
--- a/crates/router/src/core/utils.rs
+++ b/crates/router/src/core/utils.rs
@@ -252,7 +252,7 @@ pub async fn construct_refund_router_data<'a, F>(
     let status = payment_attempt.status;
 
     let payment_amount = payment_attempt.get_total_amount();
-    let currency = payment_intent.amount_details.currency;
+    let currency = payment_intent.get_currency();
 
     let payment_method_type = payment_attempt.payment_method_type;
 
diff --git a/crates/router/src/routes/app.rs b/crates/router/src/routes/app.rs
index 9dc67be1d2d..68c88af3fd4 100644
--- a/crates/router/src/routes/app.rs
+++ b/crates/router/src/routes/app.rs
@@ -1170,7 +1170,9 @@ impl Refunds {
     pub fn server(state: AppState) -> Scope {
         let mut route = web::scope("/v2/refunds").app_data(web::Data::new(state));
 
-        route = route.service(web::resource("").route(web::post().to(refunds::refunds_create)));
+        route = route
+            .service(web::resource("").route(web::post().to(refunds::refunds_create)))
+            .service(web::resource("/{id}").route(web::get().to(refunds::refunds_retrieve)));
 
         route
     }
diff --git a/crates/router/src/routes/refunds.rs b/crates/router/src/routes/refunds.rs
index a7a4e5dddfb..65ea8720f99 100644
--- a/crates/router/src/routes/refunds.rs
+++ b/crates/router/src/routes/refunds.rs
@@ -168,6 +168,56 @@ pub async fn refunds_retrieve(
     .await
 }
 
+#[cfg(all(feature = "v2", feature = "refunds_v2"))]
+#[instrument(skip_all, fields(flow))]
+pub async fn refunds_retrieve(
+    state: web::Data<AppState>,
+    req: HttpRequest,
+    path: web::Path<common_utils::id_type::GlobalRefundId>,
+    query_params: web::Query<api_models::refunds::RefundsRetrieveBody>,
+) -> HttpResponse {
+    let refund_request = refunds::RefundsRetrieveRequest {
+        refund_id: path.into_inner(),
+        force_sync: query_params.force_sync,
+    };
+    let flow = match query_params.force_sync {
+        Some(true) => Flow::RefundsRetrieveForceSync,
+        _ => Flow::RefundsRetrieve,
+    };
+
+    tracing::Span::current().record("flow", flow.to_string());
+
+    Box::pin(api::server_wrap(
+        flow,
+        state,
+        &req,
+        refund_request,
+        |state, auth: auth::AuthenticationData, refund_request, _| {
+            let merchant_context = domain::MerchantContext::NormalMerchant(Box::new(
+                domain::Context(auth.merchant_account, auth.key_store),
+            ));
+            refund_retrieve_core_with_refund_id(
+                state,
+                merchant_context,
+                auth.profile,
+                refund_request,
+            )
+        },
+        auth::auth_type(
+            &auth::V2ApiKeyAuth {
+                is_connected_allowed: false,
+                is_platform_allowed: false,
+            },
+            &auth::JWTAuth {
+                permission: Permission::ProfileRefundRead,
+            },
+            req.headers(),
+        ),
+        api_locking::LockAction::NotApplicable,
+    ))
+    .await
+}
+
 #[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "refunds_v2")))]
 /// Refunds - Retrieve (POST)
 ///
diff --git a/crates/router/src/types/api/refunds.rs b/crates/router/src/types/api/refunds.rs
index ff1f192fd45..8beaf7010f9 100644
--- a/crates/router/src/types/api/refunds.rs
+++ b/crates/router/src/types/api/refunds.rs
@@ -3,7 +3,8 @@ pub use api_models::refunds::RefundRequest;
 #[cfg(all(feature = "v2", feature = "refunds_v2"))]
 pub use api_models::refunds::RefundsCreateRequest;
 pub use api_models::refunds::{
-    RefundResponse, RefundStatus, RefundType, RefundUpdateRequest, RefundsRetrieveRequest,
+    RefundResponse, RefundStatus, RefundType, RefundUpdateRequest, RefundsRetrieveBody,
+    RefundsRetrieveRequest,
 };
 pub use hyperswitch_domain_models::router_flow_types::refunds::{Execute, RSync};
 pub use hyperswitch_interfaces::api::refunds::{Refund, RefundExecute, RefundSync};

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

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

## Description
<!-- Describe your changes in detail -->
This PR introduces support for Refunds Retrieve flow and Refunds Sync flow (Rsync) in V2 apis.

### Additional Changes

- [x] 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).
-->
Upon merging this PR, users can be able to fetch a particular refund. Whether to check its status or fetch it from the gateway using force_sync query parameter.

## 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)?
-->
- Create Payment Intent API call
```
curl --location 'http://localhost:8080/v2/payments/create-intent' \
--header 'api-key: dev_Oce2SOCaD6B7eTuE6o7fAoS0dJIOucG97Y1sS0O94JCzHg1Qh1emDSasTiFvVkIp' \
--header 'Content-Type: application/json' \
--header 'x-profile-id: pro_OIQz3QuZDrhyUteOS41r' \
--header 'Authorization: api-key=dev_Oce2SOCaD6B7eTuE6o7fAoS0dJIOucG97Y1sS0O94JCzHg1Qh1emDSasTiFvVkIp' \
--data-raw '{
    "amount_details": {
        "order_amount": 100,
        "currency": "USD"
    },
    "capture_method":"automatic",
    "authentication_type": "no_three_ds",
    "billing": {
        "address": {
            "first_name": "John",
            "last_name": "Dough"
        },
        "email": "example@example.com"
    },
    "shipping": {
        "address": {
            "first_name": "John",
            "last_name": "Dough",
            "city": "Karwar",
            "zip": "581301",
            "state": "Karnataka"
        },
        "email": "example@example.com"
    }
}'
```
- Response from the above call
```
{
   "id":"12345_pay_019642a2b1bb7653ae08d3a9c5559177",
   "status":"requires_payment_method",
   "amount_details":{
      "order_amount":100,
      "currency":"USD",
      "shipping_cost":null,
      "order_tax_amount":null,
      "external_tax_calculation":"skip",
      "surcharge_calculation":"skip",
      "surcharge_amount":null,
      "tax_on_surcharge":null
   },
   "client_secret":"cs_019642a2b25a7301962eaf25dbdb22fd",
   "profile_id":"pro_OIQz3QuZDrhyUteOS41r",
   "merchant_reference_id":null,
   "routing_algorithm_id":null,
   "capture_method":"automatic",
   "authentication_type":"no_three_ds",
   "billing":{
      "address":{
         "city":null,
         "country":null,
         "line1":null,
         "line2":null,
         "line3":null,
         "zip":null,
         "state":null,
         "first_name":"John",
         "last_name":"Dough"
      },
      "phone":null,
      "email":"example@example.com"
   },
   "shipping":{
      "address":{
         "city":"Karwar",
         "country":null,
         "line1":null,
         "line2":null,
         "line3":null,
         "zip":"581301",
         "state":"Karnataka",
         "first_name":"John",
         "last_name":"Dough"
      },
      "phone":null,
      "email":"example@example.com"
   },
   "customer_id":null,
   "customer_present":"present",
   "description":null,
   "return_url":null,
   "setup_future_usage":"on_session",
   "apply_mit_exemption":"Skip",
   "statement_descriptor":null,
   "order_details":null,
   "allowed_payment_method_types":null,
   "metadata":null,
   "connector_metadata":null,
   "feature_metadata":null,
   "payment_link_enabled":"Skip",
   "payment_link_config":null,
   "request_incremental_authorization":"default",
   "expires_on":"2025-04-17T07:39:40.883Z",
   "frm_metadata":null,
   "request_external_three_ds_authentication":"Skip"
}
```
- Confirm Intent API Call
```
curl --location 'http://localhost:8080/v2/payments/12345_pay_019642a2b1bb7653ae08d3a9c5559177/confirm-intent' \
--header 'x-client-secret: cs_019642a2b25a7301962eaf25dbdb22fd' \
--header 'x-profile-id: pro_OIQz3QuZDrhyUteOS41r' \
--header 'Content-Type: application/json' \
--header 'Authorization: publishable-key=pk_dev_ed28381f289249908a9fc946f11bf871,client-secret=cs_01963eaad7087700a02f86c215139747' \
--data '{
    "payment_method_data": {
        "card": {
            "card_number": "4242424242424242",
            "card_exp_month": "05",
            "card_exp_year": "25",
            "card_holder_name": "John Doe",
            "card_cvc": "100"
        }
    },
    "payment_method_type": "card",
    "payment_method_subtype": "credit",
    "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": "1x.x.x.31"
    }
}'
```
- Response from the above call
```
{
   "id":"12345_pay_019642a2b1bb7653ae08d3a9c5559177",
   "status":"succeeded",
   "amount":{
      "order_amount":100,
      "currency":"USD",
      "shipping_cost":null,
      "order_tax_amount":null,
      "external_tax_calculation":"skip",
      "surcharge_calculation":"skip",
      "surcharge_amount":null,
      "tax_on_surcharge":null,
      "net_amount":100,
      "amount_to_capture":null,
      "amount_capturable":0,
      "amount_captured":100
   },
   "customer_id":null,
   "connector":"stripe",
   "created":"2025-04-17T07:24:40.884Z",
   "payment_method_data":{
      "billing":null
   },
   "payment_method_type":"card",
   "payment_method_subtype":"credit",
   "connector_transaction_id":"pi_3REmpOD5R7gDAGff0MLPnmeN",
   "connector_reference_id":null,
   "merchant_connector_id":"mca_sank3uwIE62b8Fzr90a6",
   "browser_info":null,
   "error":null,
   "shipping":null,
   "billing":null,
   "attempts":null,
   "connector_token_details":{
      "token":"pm_1REmpOD5R7gDAGffAT3f1rIN",
      "connector_token_request_reference_id":"Ow8Jd6RrV0u6t2qyST"
   },
   "payment_method_id":null,
   "next_action":null,
   "return_url":"https://google.com/success",
   "authentication_type":"no_three_ds",
   "authentication_type_applied":"no_three_ds"
}
```
- Refunds Create API call
```
curl --location 'http://localhost:8080/v2/refunds' \
--header 'X-Profile-Id: pro_OIQz3QuZDrhyUteOS41r' \
--header 'Content-Type: application/json' \
--header 'api-key: dev_Oce2SOCaD6B7eTuE6o7fAoS0dJIOucG97Y1sS0O94JCzHg1Qh1emDSasTiFvVkIp' \
--data '{
    "payment_id":"12345_pay_019642a2b1bb7653ae08d3a9c5559177",
    "merchant_reference_id":"1744874778",
    "reason":"Paid by mistake",
    "metadata":{
        "foo":"bar"
    }
}'
```
- Response from the above call
```
{
    "id": "12345_ref_019642a487767f20bb8fa4a34d70da4a",
    "payment_id": "12345_pay_019642a2b1bb7653ae08d3a9c5559177",
    "merchant_reference_id": "1744874801",
    "amount": 100,
    "currency": "USD",
    "status": "succeeded",
    "reason": "Paid by mistake",
    "metadata": {
        "foo": "bar"
    },
    "error_details": {
        "code": "",
        "message": ""
    },
    "created_at": "2025-04-17T07:26:41.030Z",
    "updated_at": "2025-04-17T07:26:42.057Z",
    "connector": "stripe",
    "profile_id": "pro_OIQz3QuZDrhyUteOS41r",
    "merchant_connector_id": "mca_sank3uwIE62b8Fzr90a6",
    "connector_refund_reference_id": null
}
```
- Refunds Retrieve API call
```
curl --location 'http://localhost:8080/v2/refunds/12345_ref_019642a487767f20bb8fa4a34d70da4a' \
--header 'x-profile-id: pro_OIQz3QuZDrhyUteOS41r' \
--header 'api-key: dev_Oce2SOCaD6B7eTuE6o7fAoS0dJIOucG97Y1sS0O94JCzHg1Qh1emDSasTiFvVkIp'
```
- Response from the above call
```
{
    "id": "12345_ref_019642a487767f20bb8fa4a34d70da4a",
    "payment_id": "12345_pay_019642a2b1bb7653ae08d3a9c5559177",
    "merchant_reference_id": "1744874801",
    "amount": 100,
    "currency": "USD",
    "status": "succeeded",
    "reason": "Paid by mistake",
    "metadata": {
        "foo": "bar"
    },
    "error_details": {
        "code": "",
        "message": ""
    },
    "created_at": "2025-04-17T07:26:41.030Z",
    "updated_at": "2025-04-17T07:26:42.057Z",
    "connector": "stripe",
    "profile_id": "pro_OIQz3QuZDrhyUteOS41r",
    "merchant_connector_id": "mca_sank3uwIE62b8Fzr90a6",
    "connector_refund_reference_id": null
}
```
- Refunds Retrieve api call with `force_sync=True` in query
```
curl --location 'http://localhost:8080/v2/refunds/12345_ref_019642a487767f20bb8fa4a34d70da4a?force_sync=true' \
--header 'x-profile-id: pro_OIQz3QuZDrhyUteOS41r' \
--header 'api-key: dev_Oce2SOCaD6B7eTuE6o7fAoS0dJIOucG97Y1sS0O94JCzHg1Qh1emDSasTiFvVkIp' \
--data ''
```
- Response from the above call
```
{
    "id": "12345_ref_019642a487767f20bb8fa4a34d70da4a",
    "payment_id": "12345_pay_019642a2b1bb7653ae08d3a9c5559177",
    "merchant_reference_id": "1744874801",
    "amount": 100,
    "currency": "USD",
    "status": "succeeded",
    "reason": "Paid by mistake",
    "metadata": {
        "foo": "bar"
    },
    "error_details": {
        "code": "",
        "message": ""
    },
    "created_at": "2025-04-17T07:26:41.030Z",
    "updated_at": "2025-04-17T08:38:46.639Z",
    "connector": "stripe",
    "profile_id": "pro_OIQz3QuZDrhyUteOS41r",
    "merchant_connector_id": "mca_sank3uwIE62b8Fzr90a6",
    "connector_refund_reference_id": null
}
```

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