mirror of
				https://github.com/juspay/hyperswitch.git
				synced 2025-10-31 18:17:13 +08:00 
			
		
		
		
	refactor(relay): add trait based implementation for relay (#7264)
Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
This commit is contained in:
		| @ -1,7 +1,14 @@ | ||||
| use api_models::relay as relay_models; | ||||
| use std::marker::PhantomData; | ||||
|  | ||||
| use api_models::relay as relay_api_models; | ||||
| use async_trait::async_trait; | ||||
| use common_enums::RelayStatus; | ||||
| use common_utils::{self, id_type}; | ||||
| use common_utils::{ | ||||
|     self, fp_utils, | ||||
|     id_type::{self, GenerateId}, | ||||
| }; | ||||
| use error_stack::ResultExt; | ||||
| use hyperswitch_domain_models::relay; | ||||
|  | ||||
| use super::errors::{self, ConnectorErrorExt, RouterResponse, RouterResult, StorageErrorExt}; | ||||
| use crate::{ | ||||
| @ -17,13 +24,208 @@ use crate::{ | ||||
|  | ||||
| pub mod utils; | ||||
|  | ||||
| pub async fn relay( | ||||
| pub trait Validate { | ||||
|     type Error: error_stack::Context; | ||||
|     fn validate(&self) -> Result<(), Self::Error>; | ||||
| } | ||||
|  | ||||
| impl Validate for relay_api_models::RelayRefundRequestData { | ||||
|     type Error = errors::ApiErrorResponse; | ||||
|     fn validate(&self) -> Result<(), Self::Error> { | ||||
|         fp_utils::when(self.amount.get_amount_as_i64() <= 0, || { | ||||
|             Err(errors::ApiErrorResponse::PreconditionFailed { | ||||
|                 message: "Amount should be greater than 0".to_string(), | ||||
|             }) | ||||
|         })?; | ||||
|         Ok(()) | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[async_trait] | ||||
| pub trait RelayInterface { | ||||
|     type Request: Validate; | ||||
|     fn validate_relay_request(req: &Self::Request) -> RouterResult<()> { | ||||
|         req.validate() | ||||
|             .change_context(errors::ApiErrorResponse::PreconditionFailed { | ||||
|                 message: "Invalid relay request".to_string(), | ||||
|             }) | ||||
|     } | ||||
|  | ||||
|     fn get_domain_models( | ||||
|         relay_request: RelayRequestInner<Self>, | ||||
|         merchant_id: &id_type::MerchantId, | ||||
|         profile_id: &id_type::ProfileId, | ||||
|     ) -> relay::Relay; | ||||
|  | ||||
|     async fn process_relay( | ||||
|         state: &SessionState, | ||||
|         merchant_account: domain::MerchantAccount, | ||||
|         connector_account: domain::MerchantConnectorAccount, | ||||
|         relay_record: &relay::Relay, | ||||
|     ) -> RouterResult<relay::RelayUpdate>; | ||||
|  | ||||
|     fn generate_response(value: relay::Relay) -> RouterResult<api_models::relay::RelayResponse>; | ||||
| } | ||||
|  | ||||
| pub struct RelayRequestInner<T: RelayInterface + ?Sized> { | ||||
|     pub connector_resource_id: String, | ||||
|     pub connector_id: id_type::MerchantConnectorAccountId, | ||||
|     pub relay_type: PhantomData<T>, | ||||
|     pub data: T::Request, | ||||
| } | ||||
|  | ||||
| impl RelayRequestInner<RelayRefund> { | ||||
|     pub fn from_relay_request(relay_request: relay_api_models::RelayRequest) -> RouterResult<Self> { | ||||
|         match relay_request.data { | ||||
|             Some(relay_api_models::RelayData::Refund(ref_data)) => Ok(Self { | ||||
|                 connector_resource_id: relay_request.connector_resource_id, | ||||
|                 connector_id: relay_request.connector_id, | ||||
|                 relay_type: PhantomData, | ||||
|                 data: ref_data, | ||||
|             }), | ||||
|             None => Err(errors::ApiErrorResponse::InvalidRequestData { | ||||
|                 message: "Relay data is required for relay type refund".to_string(), | ||||
|             })?, | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| pub struct RelayRefund; | ||||
|  | ||||
| #[async_trait] | ||||
| impl RelayInterface for RelayRefund { | ||||
|     type Request = relay_api_models::RelayRefundRequestData; | ||||
|  | ||||
|     fn get_domain_models( | ||||
|         relay_request: RelayRequestInner<Self>, | ||||
|         merchant_id: &id_type::MerchantId, | ||||
|         profile_id: &id_type::ProfileId, | ||||
|     ) -> relay::Relay { | ||||
|         let relay_id = id_type::RelayId::generate(); | ||||
|         let relay_refund: relay::RelayRefundData = relay_request.data.into(); | ||||
|         relay::Relay { | ||||
|             id: relay_id.clone(), | ||||
|             connector_resource_id: relay_request.connector_resource_id.clone(), | ||||
|             connector_id: relay_request.connector_id.clone(), | ||||
|             profile_id: profile_id.clone(), | ||||
|             merchant_id: merchant_id.clone(), | ||||
|             relay_type: common_enums::RelayType::Refund, | ||||
|             request_data: Some(relay::RelayData::Refund(relay_refund)), | ||||
|             status: RelayStatus::Created, | ||||
|             connector_reference_id: None, | ||||
|             error_code: None, | ||||
|             error_message: None, | ||||
|             created_at: common_utils::date_time::now(), | ||||
|             modified_at: common_utils::date_time::now(), | ||||
|             response_data: None, | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     async fn process_relay( | ||||
|         state: &SessionState, | ||||
|         merchant_account: domain::MerchantAccount, | ||||
|         connector_account: domain::MerchantConnectorAccount, | ||||
|         relay_record: &relay::Relay, | ||||
|     ) -> RouterResult<relay::RelayUpdate> { | ||||
|         let connector_id = &relay_record.connector_id; | ||||
|  | ||||
|         let merchant_id = merchant_account.get_id(); | ||||
|  | ||||
|         let connector_name = &connector_account.get_connector_name_as_string(); | ||||
|  | ||||
|         let connector_data = api::ConnectorData::get_connector_by_name( | ||||
|             &state.conf.connectors, | ||||
|             connector_name, | ||||
|             api::GetToken::Connector, | ||||
|             Some(connector_id.clone()), | ||||
|         )?; | ||||
|  | ||||
|         let connector_integration: services::BoxedRefundConnectorIntegrationInterface< | ||||
|             api::Execute, | ||||
|             hyperswitch_domain_models::router_request_types::RefundsData, | ||||
|             hyperswitch_domain_models::router_response_types::RefundsResponseData, | ||||
|         > = connector_data.connector.get_connector_integration(); | ||||
|  | ||||
|         let router_data = utils::construct_relay_refund_router_data( | ||||
|             state, | ||||
|             merchant_id, | ||||
|             &connector_account, | ||||
|             relay_record, | ||||
|         ) | ||||
|         .await?; | ||||
|  | ||||
|         let router_data_res = services::execute_connector_processing_step( | ||||
|             state, | ||||
|             connector_integration, | ||||
|             &router_data, | ||||
|             payments::CallConnectorAction::Trigger, | ||||
|             None, | ||||
|         ) | ||||
|         .await | ||||
|         .to_refund_failed_response()?; | ||||
|  | ||||
|         let relay_update = relay::RelayUpdate::from(router_data_res.response); | ||||
|  | ||||
|         Ok(relay_update) | ||||
|     } | ||||
|  | ||||
|     fn generate_response(value: relay::Relay) -> RouterResult<api_models::relay::RelayResponse> { | ||||
|         let error = value | ||||
|             .error_code | ||||
|             .zip(value.error_message) | ||||
|             .map( | ||||
|                 |(error_code, error_message)| api_models::relay::RelayError { | ||||
|                     code: error_code, | ||||
|                     message: error_message, | ||||
|                 }, | ||||
|             ); | ||||
|  | ||||
|         let data = | ||||
|             api_models::relay::RelayData::from(value.request_data.get_required_value("RelayData")?); | ||||
|  | ||||
|         Ok(api_models::relay::RelayResponse { | ||||
|             id: value.id, | ||||
|             status: value.status, | ||||
|             error, | ||||
|             connector_resource_id: value.connector_resource_id, | ||||
|             connector_id: value.connector_id, | ||||
|             profile_id: value.profile_id, | ||||
|             relay_type: value.relay_type, | ||||
|             data: Some(data), | ||||
|             connector_reference_id: value.connector_reference_id, | ||||
|         }) | ||||
|     } | ||||
| } | ||||
|  | ||||
| pub async fn relay_flow_decider( | ||||
|     state: SessionState, | ||||
|     merchant_account: domain::MerchantAccount, | ||||
|     profile_id_optional: Option<id_type::ProfileId>, | ||||
|     key_store: domain::MerchantKeyStore, | ||||
|     req: relay_models::RelayRequest, | ||||
| ) -> RouterResponse<relay_models::RelayResponse> { | ||||
|     request: relay_api_models::RelayRequest, | ||||
| ) -> RouterResponse<relay_api_models::RelayResponse> { | ||||
|     let relay_flow_request = match request.relay_type { | ||||
|         common_enums::RelayType::Refund => { | ||||
|             RelayRequestInner::<RelayRefund>::from_relay_request(request)? | ||||
|         } | ||||
|     }; | ||||
|     relay( | ||||
|         state, | ||||
|         merchant_account, | ||||
|         profile_id_optional, | ||||
|         key_store, | ||||
|         relay_flow_request, | ||||
|     ) | ||||
|     .await | ||||
| } | ||||
|  | ||||
| pub async fn relay<T: RelayInterface>( | ||||
|     state: SessionState, | ||||
|     merchant_account: domain::MerchantAccount, | ||||
|     profile_id_optional: Option<id_type::ProfileId>, | ||||
|     key_store: domain::MerchantKeyStore, | ||||
|     req: RelayRequestInner<T>, | ||||
| ) -> RouterResponse<relay_api_models::RelayResponse> { | ||||
|     let db = state.store.as_ref(); | ||||
|     let key_manager_state = &(&state).into(); | ||||
|     let merchant_id = merchant_account.get_id(); | ||||
| @ -64,10 +266,9 @@ pub async fn relay( | ||||
|             id: connector_id.get_string_repr().to_string(), | ||||
|         })?; | ||||
|  | ||||
|     validate_relay_refund_request(&req).attach_printable("Invalid relay refund request")?; | ||||
|     T::validate_relay_request(&req.data)?; | ||||
|  | ||||
|     let relay_domain = | ||||
|         hyperswitch_domain_models::relay::Relay::new(&req, merchant_id, profile.get_id()); | ||||
|     let relay_domain = T::get_domain_models(req, merchant_id, profile.get_id()); | ||||
|  | ||||
|     let relay_record = db | ||||
|         .insert_relay(key_manager_state, &key_store, relay_domain) | ||||
| @ -75,117 +276,31 @@ pub async fn relay( | ||||
|         .change_context(errors::ApiErrorResponse::InternalServerError) | ||||
|         .attach_printable("Failed to insert a relay record in db")?; | ||||
|  | ||||
|     let relay_response = match req.relay_type { | ||||
|         common_enums::RelayType::Refund => { | ||||
|             Box::pin(relay_refund( | ||||
|                 &state, | ||||
|                 merchant_account, | ||||
|                 connector_account, | ||||
|                 &relay_record, | ||||
|             )) | ||||
|             .await? | ||||
|         } | ||||
|     }; | ||||
|     let relay_response = | ||||
|         T::process_relay(&state, merchant_account, connector_account, &relay_record) | ||||
|             .await | ||||
|             .attach_printable("Failed to process relay")?; | ||||
|  | ||||
|     let relay_update_record = db | ||||
|         .update_relay(key_manager_state, &key_store, relay_record, relay_response) | ||||
|         .await | ||||
|         .change_context(errors::ApiErrorResponse::InternalServerError)?; | ||||
|  | ||||
|     let response = relay_models::RelayResponse::from(relay_update_record); | ||||
|     let response = T::generate_response(relay_update_record) | ||||
|         .attach_printable("Failed to generate relay response")?; | ||||
|  | ||||
|     Ok(hyperswitch_domain_models::api::ApplicationResponse::Json( | ||||
|         response, | ||||
|     )) | ||||
| } | ||||
|  | ||||
| pub async fn relay_refund( | ||||
|     state: &SessionState, | ||||
|     merchant_account: domain::MerchantAccount, | ||||
|     connector_account: domain::MerchantConnectorAccount, | ||||
|     relay_record: &hyperswitch_domain_models::relay::Relay, | ||||
| ) -> RouterResult<hyperswitch_domain_models::relay::RelayUpdate> { | ||||
|     let connector_id = &relay_record.connector_id; | ||||
|  | ||||
|     let merchant_id = merchant_account.get_id(); | ||||
|  | ||||
|     #[cfg(feature = "v1")] | ||||
|     let connector_name = &connector_account.connector_name; | ||||
|  | ||||
|     #[cfg(feature = "v2")] | ||||
|     let connector_name = &connector_account.connector_name.to_string(); | ||||
|  | ||||
|     let connector_data = api::ConnectorData::get_connector_by_name( | ||||
|         &state.conf.connectors, | ||||
|         connector_name, | ||||
|         api::GetToken::Connector, | ||||
|         Some(connector_id.clone()), | ||||
|     )?; | ||||
|  | ||||
|     let connector_integration: services::BoxedRefundConnectorIntegrationInterface< | ||||
|         api::Execute, | ||||
|         hyperswitch_domain_models::router_request_types::RefundsData, | ||||
|         hyperswitch_domain_models::router_response_types::RefundsResponseData, | ||||
|     > = connector_data.connector.get_connector_integration(); | ||||
|  | ||||
|     let router_data = utils::construct_relay_refund_router_data( | ||||
|         state, | ||||
|         merchant_id, | ||||
|         &connector_account, | ||||
|         relay_record, | ||||
|     ) | ||||
|     .await?; | ||||
|  | ||||
|     let router_data_res = services::execute_connector_processing_step( | ||||
|         state, | ||||
|         connector_integration, | ||||
|         &router_data, | ||||
|         payments::CallConnectorAction::Trigger, | ||||
|         None, | ||||
|     ) | ||||
|     .await | ||||
|     .to_refund_failed_response()?; | ||||
|  | ||||
|     let relay_response = | ||||
|         hyperswitch_domain_models::relay::RelayUpdate::from(router_data_res.response); | ||||
|  | ||||
|     Ok(relay_response) | ||||
| } | ||||
|  | ||||
| // validate relay request | ||||
| pub fn validate_relay_refund_request( | ||||
|     relay_request: &relay_models::RelayRequest, | ||||
| ) -> RouterResult<()> { | ||||
|     match (relay_request.relay_type, &relay_request.data) { | ||||
|         (common_enums::RelayType::Refund, Some(relay_models::RelayData::Refund(ref_data))) => { | ||||
|             validate_relay_refund_data(ref_data) | ||||
|         } | ||||
|         (common_enums::RelayType::Refund, None) => { | ||||
|             Err(errors::ApiErrorResponse::PreconditionFailed { | ||||
|                 message: "Relay data is required for refund relay".to_string(), | ||||
|             })? | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| pub fn validate_relay_refund_data( | ||||
|     refund_data: &relay_models::RelayRefundRequest, | ||||
| ) -> RouterResult<()> { | ||||
|     if refund_data.amount.get_amount_as_i64() <= 0 { | ||||
|         Err(errors::ApiErrorResponse::PreconditionFailed { | ||||
|             message: "Amount should be greater than 0".to_string(), | ||||
|         })? | ||||
|     } | ||||
|     Ok(()) | ||||
| } | ||||
|  | ||||
| pub async fn relay_retrieve( | ||||
|     state: SessionState, | ||||
|     merchant_account: domain::MerchantAccount, | ||||
|     profile_id_optional: Option<id_type::ProfileId>, | ||||
|     key_store: domain::MerchantKeyStore, | ||||
|     req: relay_models::RelayRetrieveRequest, | ||||
| ) -> RouterResponse<relay_models::RelayResponse> { | ||||
|     req: relay_api_models::RelayRetrieveRequest, | ||||
| ) -> RouterResponse<relay_api_models::RelayResponse> { | ||||
|     let db = state.store.as_ref(); | ||||
|     let key_manager_state = &(&state).into(); | ||||
|     let merchant_id = merchant_account.get_id(); | ||||
| @ -269,17 +384,14 @@ pub async fn relay_retrieve( | ||||
|         } | ||||
|     }; | ||||
|  | ||||
|     let response = relay_models::RelayResponse::from(relay_response); | ||||
|     let response = relay_api_models::RelayResponse::from(relay_response); | ||||
|  | ||||
|     Ok(hyperswitch_domain_models::api::ApplicationResponse::Json( | ||||
|         response, | ||||
|     )) | ||||
| } | ||||
|  | ||||
| fn should_call_connector_for_relay_refund_status( | ||||
|     relay: &hyperswitch_domain_models::relay::Relay, | ||||
|     force_sync: bool, | ||||
| ) -> bool { | ||||
| fn should_call_connector_for_relay_refund_status(relay: &relay::Relay, force_sync: bool) -> bool { | ||||
|     // This allows refund sync at connector level if force_sync is enabled, or | ||||
|     // check if the refund is in terminal state | ||||
|     !matches!(relay.status, RelayStatus::Failure | RelayStatus::Success) && force_sync | ||||
| @ -288,9 +400,9 @@ fn should_call_connector_for_relay_refund_status( | ||||
| pub async fn sync_relay_refund_with_gateway( | ||||
|     state: &SessionState, | ||||
|     merchant_account: &domain::MerchantAccount, | ||||
|     relay_record: &hyperswitch_domain_models::relay::Relay, | ||||
|     relay_record: &relay::Relay, | ||||
|     connector_account: domain::MerchantConnectorAccount, | ||||
| ) -> RouterResult<hyperswitch_domain_models::relay::RelayUpdate> { | ||||
| ) -> RouterResult<relay::RelayUpdate> { | ||||
|     let connector_id = &relay_record.connector_id; | ||||
|     let merchant_id = merchant_account.get_id(); | ||||
|  | ||||
| @ -333,8 +445,7 @@ pub async fn sync_relay_refund_with_gateway( | ||||
|     .await | ||||
|     .to_refund_failed_response()?; | ||||
|  | ||||
|     let relay_response = | ||||
|         hyperswitch_domain_models::relay::RelayUpdate::from(router_data_res.response); | ||||
|     let relay_response = relay::RelayUpdate::from(router_data_res.response); | ||||
|  | ||||
|     Ok(relay_response) | ||||
| } | ||||
|  | ||||
| @ -22,7 +22,7 @@ pub async fn relay( | ||||
|         &req, | ||||
|         payload, | ||||
|         |state, auth: auth::AuthenticationData, req, _| { | ||||
|             relay::relay( | ||||
|             relay::relay_flow_decider( | ||||
|                 state, | ||||
|                 auth.merchant_account, | ||||
|                 #[cfg(feature = "v1")] | ||||
|  | ||||
		Reference in New Issue
	
	Block a user
	 Shankar Singh C
					Shankar Singh C