mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-28 04:04:55 +08:00
feat(refunds_v2): Add Refunds Retrieve and Refunds Sync Core flow (#7835)
Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
This commit is contained in:
@ -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> {
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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")))]
|
||||
|
||||
@ -763,6 +763,10 @@ impl PaymentIntent {
|
||||
})
|
||||
.transpose()
|
||||
}
|
||||
|
||||
pub fn get_currency(&self) -> storage_enums::Currency {
|
||||
self.amount_details.currency
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "v1")]
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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() {}
|
||||
|
||||
@ -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)]
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -1174,7 +1174,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
|
||||
}
|
||||
|
||||
@ -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)
|
||||
///
|
||||
|
||||
@ -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};
|
||||
|
||||
Reference in New Issue
Block a user