|
|
|
|
@ -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)
|
|
|
|
|
}
|
|
|
|
|
|