mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-11-03 05:17:02 +08:00
feat(router): add straight-through routing connector selection in payments (#153)
This commit is contained in:
@ -123,6 +123,8 @@ pub enum ApiErrorResponse {
|
||||
PaymentNotSucceeded,
|
||||
#[error(error_type= ErrorType::ObjectNotFound, code = "RE_05", message = "Successful payment not found for the given payment id")]
|
||||
SuccessfulPaymentNotFound,
|
||||
#[error(error_type = ErrorType::ObjectNotFound, code = "RE_05", message = "The connector provided in the request is incorrect or not available")]
|
||||
IncorrectConnectorNameGiven,
|
||||
#[error(error_type = ErrorType::ObjectNotFound, code = "RE_05", message = "Address does not exist in our records.")]
|
||||
AddressNotFound,
|
||||
#[error(error_type = ErrorType::ValidationError, code = "RE_03", message = "Mandate Validation Failed" )]
|
||||
@ -183,6 +185,7 @@ impl actix_web::ResponseError for ApiErrorResponse {
|
||||
| ApiErrorResponse::ClientSecretNotGiven
|
||||
| ApiErrorResponse::ClientSecretInvalid
|
||||
| ApiErrorResponse::SuccessfulPaymentNotFound
|
||||
| ApiErrorResponse::IncorrectConnectorNameGiven
|
||||
| ApiErrorResponse::ResourceIdNotFound
|
||||
| ApiErrorResponse::AddressNotFound => StatusCode::BAD_REQUEST, // 400
|
||||
ApiErrorResponse::DuplicateMerchantAccount
|
||||
|
||||
@ -25,15 +25,14 @@ use crate::{
|
||||
payments,
|
||||
},
|
||||
db::StorageInterface,
|
||||
logger,
|
||||
pii::{Email, Secret},
|
||||
logger, pii,
|
||||
routes::AppState,
|
||||
scheduler::utils as pt_utils,
|
||||
services,
|
||||
types::{
|
||||
self,
|
||||
api::{self, PaymentIdTypeExt, PaymentsResponse, PaymentsRetrieveRequest},
|
||||
storage::{self, enums, ProcessTrackerExt},
|
||||
api::{self, enums as api_enums},
|
||||
storage::{self, enums as storage_enums},
|
||||
transformers::ForeignInto,
|
||||
},
|
||||
utils::{self, OptionExt},
|
||||
@ -45,6 +44,7 @@ pub async fn payments_operation_core<F, Req, Op, FData>(
|
||||
merchant_account: storage::MerchantAccount,
|
||||
operation: Op,
|
||||
req: Req,
|
||||
use_connector: Option<api_enums::Connector>,
|
||||
call_connector_action: CallConnectorAction,
|
||||
) -> RouterResult<(PaymentData<F>, Req, Option<storage::Customer>)>
|
||||
where
|
||||
@ -106,6 +106,7 @@ where
|
||||
validate_result.storage_scheme,
|
||||
)
|
||||
.await?;
|
||||
|
||||
payment_data.payment_method_data = payment_method_data;
|
||||
if let Some(token) = payment_token {
|
||||
payment_data.token = Some(token)
|
||||
@ -113,7 +114,7 @@ where
|
||||
|
||||
let connector_details = operation
|
||||
.to_domain()?
|
||||
.get_connector(&merchant_account, state)
|
||||
.get_connector(&merchant_account, state, use_connector)
|
||||
.await?;
|
||||
|
||||
if let api::ConnectorCallType::Single(ref connector) = connector_details {
|
||||
@ -175,6 +176,7 @@ pub async fn payments_core<F, Res, Req, Op, FData>(
|
||||
operation: Op,
|
||||
req: Req,
|
||||
auth_flow: services::AuthFlow,
|
||||
use_connector: Option<api_enums::Connector>,
|
||||
call_connector_action: CallConnectorAction,
|
||||
) -> RouterResponse<Res>
|
||||
where
|
||||
@ -199,9 +201,11 @@ where
|
||||
merchant_account,
|
||||
operation.clone(),
|
||||
req,
|
||||
use_connector,
|
||||
call_connector_action,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Res::generate_response(
|
||||
Some(req),
|
||||
payment_data,
|
||||
@ -220,7 +224,7 @@ fn is_start_pay<Op: Debug>(operation: &Op) -> bool {
|
||||
pub async fn handle_payments_redirect_response<'a, F>(
|
||||
state: &AppState,
|
||||
merchant_account: storage::MerchantAccount,
|
||||
req: PaymentsRetrieveRequest,
|
||||
req: api::PaymentsRetrieveRequest,
|
||||
) -> RouterResponse<api::RedirectionResponse>
|
||||
where
|
||||
F: Send + Clone + 'a,
|
||||
@ -229,11 +233,10 @@ where
|
||||
|
||||
let query_params = req.param.clone().get_required_value("param")?;
|
||||
|
||||
let resource_id = req.resource_id.get_payment_intent_id().change_context(
|
||||
errors::ApiErrorResponse::MissingRequiredField {
|
||||
let resource_id = api::PaymentIdTypeExt::get_payment_intent_id(&req.resource_id)
|
||||
.change_context(errors::ApiErrorResponse::MissingRequiredField {
|
||||
field_name: "payment_id".to_string(),
|
||||
},
|
||||
)?;
|
||||
})?;
|
||||
|
||||
let connector_data = api::ConnectorData::get_connector_by_name(
|
||||
&state.conf.connectors,
|
||||
@ -277,15 +280,16 @@ where
|
||||
pub async fn payments_response_for_redirection_flows<'a>(
|
||||
state: &AppState,
|
||||
merchant_account: storage::MerchantAccount,
|
||||
req: PaymentsRetrieveRequest,
|
||||
req: api::PaymentsRetrieveRequest,
|
||||
flow_type: CallConnectorAction,
|
||||
) -> RouterResponse<PaymentsResponse> {
|
||||
) -> RouterResponse<api::PaymentsResponse> {
|
||||
payments_core::<api::PSync, api::PaymentsResponse, _, _, _>(
|
||||
state,
|
||||
merchant_account,
|
||||
payments::PaymentStatus,
|
||||
req,
|
||||
services::api::AuthFlow::Merchant,
|
||||
None,
|
||||
flow_type,
|
||||
)
|
||||
.await
|
||||
@ -433,7 +437,7 @@ where
|
||||
pub enum CallConnectorAction {
|
||||
Trigger,
|
||||
Avoid,
|
||||
StatusUpdate(enums::AttemptStatus),
|
||||
StatusUpdate(storage_enums::AttemptStatus),
|
||||
HandleResponse(Vec<u8>),
|
||||
}
|
||||
|
||||
@ -453,7 +457,7 @@ where
|
||||
pub payment_attempt: storage::PaymentAttempt,
|
||||
pub connector_response: storage::ConnectorResponse,
|
||||
pub amount: api::Amount,
|
||||
pub currency: enums::Currency,
|
||||
pub currency: storage_enums::Currency,
|
||||
pub mandate_id: Option<String>,
|
||||
pub setup_mandate: Option<api::MandateData>,
|
||||
pub address: PaymentAddress,
|
||||
@ -463,21 +467,21 @@ where
|
||||
pub payment_method_data: Option<api::PaymentMethod>,
|
||||
pub refunds: Vec<storage::Refund>,
|
||||
pub sessions_token: Vec<api::SessionToken>,
|
||||
pub card_cvc: Option<Secret<String>>,
|
||||
pub card_cvc: Option<pii::Secret<String>>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct CustomerDetails {
|
||||
pub customer_id: Option<String>,
|
||||
pub name: Option<masking::Secret<String, masking::WithType>>,
|
||||
pub email: Option<masking::Secret<String, Email>>,
|
||||
pub email: Option<masking::Secret<String, pii::Email>>,
|
||||
pub phone: Option<masking::Secret<String, masking::WithType>>,
|
||||
pub phone_country_code: Option<String>,
|
||||
}
|
||||
|
||||
pub fn if_not_create_change_operation<'a, Op, F>(
|
||||
is_update: bool,
|
||||
status: enums::IntentStatus,
|
||||
status: storage_enums::IntentStatus,
|
||||
current: &'a Op,
|
||||
) -> BoxedOperation<F, api::PaymentsRequest>
|
||||
where
|
||||
@ -486,9 +490,9 @@ where
|
||||
&'a Op: Operation<F, api::PaymentsRequest>,
|
||||
{
|
||||
match status {
|
||||
enums::IntentStatus::RequiresConfirmation
|
||||
| enums::IntentStatus::RequiresCustomerAction
|
||||
| enums::IntentStatus::RequiresPaymentMethod => {
|
||||
storage_enums::IntentStatus::RequiresConfirmation
|
||||
| storage_enums::IntentStatus::RequiresCustomerAction
|
||||
| storage_enums::IntentStatus::RequiresPaymentMethod => {
|
||||
if is_update {
|
||||
Box::new(&PaymentUpdate)
|
||||
} else {
|
||||
@ -525,7 +529,7 @@ pub fn should_call_connector<Op: Debug, F: Clone>(
|
||||
"PaymentStart" => {
|
||||
!matches!(
|
||||
payment_data.payment_intent.status,
|
||||
enums::IntentStatus::Failed | enums::IntentStatus::Succeeded
|
||||
storage_enums::IntentStatus::Failed | storage_enums::IntentStatus::Succeeded
|
||||
) && payment_data
|
||||
.connector_response
|
||||
.authentication_data
|
||||
@ -534,20 +538,20 @@ pub fn should_call_connector<Op: Debug, F: Clone>(
|
||||
"PaymentStatus" => {
|
||||
matches!(
|
||||
payment_data.payment_intent.status,
|
||||
enums::IntentStatus::Failed
|
||||
| enums::IntentStatus::Processing
|
||||
| enums::IntentStatus::Succeeded
|
||||
| enums::IntentStatus::RequiresCustomerAction
|
||||
storage_enums::IntentStatus::Failed
|
||||
| storage_enums::IntentStatus::Processing
|
||||
| storage_enums::IntentStatus::Succeeded
|
||||
| storage_enums::IntentStatus::RequiresCustomerAction
|
||||
) && payment_data.force_sync.unwrap_or(false)
|
||||
}
|
||||
"PaymentCancel" => matches!(
|
||||
payment_data.payment_intent.status,
|
||||
enums::IntentStatus::RequiresCapture
|
||||
storage_enums::IntentStatus::RequiresCapture
|
||||
),
|
||||
"PaymentCapture" => {
|
||||
matches!(
|
||||
payment_data.payment_intent.status,
|
||||
enums::IntentStatus::RequiresCapture
|
||||
storage_enums::IntentStatus::RequiresCapture
|
||||
)
|
||||
}
|
||||
"PaymentSession" => true,
|
||||
@ -602,13 +606,14 @@ pub async fn add_process_sync_task(
|
||||
&payment_attempt.txn_id,
|
||||
&payment_attempt.merchant_id,
|
||||
);
|
||||
let process_tracker_entry = storage::ProcessTracker::make_process_tracker_new(
|
||||
process_tracker_id,
|
||||
task,
|
||||
runner,
|
||||
tracking_data,
|
||||
schedule_time,
|
||||
)?;
|
||||
let process_tracker_entry =
|
||||
<storage::ProcessTracker as storage::ProcessTrackerExt>::make_process_tracker_new(
|
||||
process_tracker_id,
|
||||
task,
|
||||
runner,
|
||||
tracking_data,
|
||||
schedule_time,
|
||||
)?;
|
||||
|
||||
db.insert_process(process_tracker_entry).await?;
|
||||
Ok(())
|
||||
|
||||
@ -620,43 +620,52 @@ pub async fn get_customer_from_details(
|
||||
pub async fn get_connector_default(
|
||||
merchant_account: &storage::MerchantAccount,
|
||||
state: &AppState,
|
||||
request_connector: Option<api_enums::Connector>,
|
||||
) -> CustomResult<api::ConnectorCallType, errors::ApiErrorResponse> {
|
||||
let connectors = &state.conf.connectors;
|
||||
let vec_val: Vec<serde_json::Value> = merchant_account
|
||||
.custom_routing_rules
|
||||
.clone()
|
||||
.parse_value("CustomRoutingRulesVec")
|
||||
.change_context(errors::ConnectorError::RoutingRulesParsingError)
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)?;
|
||||
let custom_routing_rules: api::CustomRoutingRules = vec_val[0]
|
||||
.clone()
|
||||
.parse_value("CustomRoutingRules")
|
||||
.change_context(errors::ConnectorError::RoutingRulesParsingError)
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)?;
|
||||
let connector_names = custom_routing_rules
|
||||
.connectors_pecking_order
|
||||
.unwrap_or_else(|| vec!["stripe".to_string()]);
|
||||
if let Some(connector) = request_connector {
|
||||
let connector_data = api::ConnectorData::get_connector_by_name(
|
||||
connectors,
|
||||
&connector.to_string(),
|
||||
api::GetToken::Connector,
|
||||
)?;
|
||||
Ok(api::ConnectorCallType::Single(connector_data))
|
||||
} else {
|
||||
let vec_val: Vec<serde_json::Value> = merchant_account
|
||||
.custom_routing_rules
|
||||
.clone()
|
||||
.parse_value("CustomRoutingRulesVec")
|
||||
.change_context(errors::ConnectorError::RoutingRulesParsingError)
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)?;
|
||||
let custom_routing_rules: api::CustomRoutingRules = vec_val[0]
|
||||
.clone()
|
||||
.parse_value("CustomRoutingRules")
|
||||
.change_context(errors::ConnectorError::RoutingRulesParsingError)
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)?;
|
||||
let connector_names = custom_routing_rules
|
||||
.connectors_pecking_order
|
||||
.unwrap_or_else(|| vec!["stripe".to_string()]);
|
||||
|
||||
//use routing rules if configured by merchant else query MCA as per PM
|
||||
let connector_list: types::ConnectorsList = types::ConnectorsList {
|
||||
connectors: connector_names,
|
||||
};
|
||||
//use routing rules if configured by merchant else query MCA as per PM
|
||||
let connector_list: types::ConnectorsList = types::ConnectorsList {
|
||||
connectors: connector_names,
|
||||
};
|
||||
|
||||
let connector_name = connector_list
|
||||
.connectors
|
||||
.first()
|
||||
.get_required_value("connectors")
|
||||
.change_context(errors::ConnectorError::FailedToObtainPreferredConnector)
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)?
|
||||
.as_str();
|
||||
let connector_name = connector_list
|
||||
.connectors
|
||||
.first()
|
||||
.get_required_value("connectors")
|
||||
.change_context(errors::ConnectorError::FailedToObtainPreferredConnector)
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)?
|
||||
.as_str();
|
||||
|
||||
let connector_data = api::ConnectorData::get_connector_by_name(
|
||||
connectors,
|
||||
connector_name,
|
||||
api::GetToken::Connector,
|
||||
)?;
|
||||
|
||||
Ok(api::ConnectorCallType::Single(connector_data))
|
||||
let connector_data = api::ConnectorData::get_connector_by_name(
|
||||
connectors,
|
||||
connector_name,
|
||||
api::GetToken::Connector,
|
||||
)?;
|
||||
Ok(api::ConnectorCallType::Single(connector_data))
|
||||
}
|
||||
}
|
||||
|
||||
#[instrument(skip_all)]
|
||||
|
||||
@ -30,7 +30,8 @@ use crate::{
|
||||
pii::Secret,
|
||||
routes::AppState,
|
||||
types::{
|
||||
self, api,
|
||||
self,
|
||||
api::{self, enums as api_enums},
|
||||
storage::{self, enums},
|
||||
PaymentsResponseData,
|
||||
},
|
||||
@ -138,6 +139,7 @@ pub trait Domain<F: Clone, R>: Send + Sync {
|
||||
&'a self,
|
||||
merchant_account: &storage::MerchantAccount,
|
||||
state: &AppState,
|
||||
request_connector: Option<api_enums::Connector>,
|
||||
) -> CustomResult<api::ConnectorCallType, errors::ApiErrorResponse>;
|
||||
}
|
||||
|
||||
@ -204,8 +206,9 @@ where
|
||||
&'a self,
|
||||
merchant_account: &storage::MerchantAccount,
|
||||
state: &AppState,
|
||||
request_connector: Option<api_enums::Connector>,
|
||||
) -> CustomResult<api::ConnectorCallType, errors::ApiErrorResponse> {
|
||||
helpers::get_connector_default(merchant_account, state).await
|
||||
helpers::get_connector_default(merchant_account, state, request_connector).await
|
||||
}
|
||||
|
||||
#[instrument(skip_all)]
|
||||
@ -291,8 +294,9 @@ where
|
||||
&'a self,
|
||||
merchant_account: &storage::MerchantAccount,
|
||||
state: &AppState,
|
||||
request_connector: Option<api_enums::Connector>,
|
||||
) -> CustomResult<api::ConnectorCallType, errors::ApiErrorResponse> {
|
||||
helpers::get_connector_default(merchant_account, state).await
|
||||
helpers::get_connector_default(merchant_account, state, request_connector).await
|
||||
}
|
||||
}
|
||||
|
||||
@ -350,7 +354,8 @@ where
|
||||
&'a self,
|
||||
merchant_account: &storage::MerchantAccount,
|
||||
state: &AppState,
|
||||
request_connector: Option<api_enums::Connector>,
|
||||
) -> CustomResult<api::ConnectorCallType, errors::ApiErrorResponse> {
|
||||
helpers::get_connector_default(merchant_account, state).await
|
||||
helpers::get_connector_default(merchant_account, state, request_connector).await
|
||||
}
|
||||
}
|
||||
|
||||
@ -17,7 +17,7 @@ use crate::{
|
||||
routes::AppState,
|
||||
types::{
|
||||
self,
|
||||
api::{self, PaymentIdTypeExt},
|
||||
api::{self, enums as api_enums, PaymentIdTypeExt},
|
||||
storage::{self, enums},
|
||||
transformers::ForeignInto,
|
||||
},
|
||||
@ -243,8 +243,9 @@ impl<F: Clone + Send> Domain<F, api::PaymentsRequest> for PaymentConfirm {
|
||||
&'a self,
|
||||
merchant_account: &storage::MerchantAccount,
|
||||
state: &AppState,
|
||||
request_connector: Option<api_enums::Connector>,
|
||||
) -> CustomResult<api::ConnectorCallType, errors::ApiErrorResponse> {
|
||||
helpers::get_connector_default(merchant_account, state).await
|
||||
helpers::get_connector_default(merchant_account, state, request_connector).await
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -19,7 +19,7 @@ use crate::{
|
||||
routes::AppState,
|
||||
types::{
|
||||
self,
|
||||
api::{self, PaymentIdTypeExt},
|
||||
api::{self, enums as api_enums, PaymentIdTypeExt},
|
||||
storage::{
|
||||
self,
|
||||
enums::{self, IntentStatus},
|
||||
@ -290,8 +290,9 @@ impl<F: Clone + Send> Domain<F, api::PaymentsRequest> for PaymentCreate {
|
||||
&'a self,
|
||||
merchant_account: &storage::MerchantAccount,
|
||||
state: &AppState,
|
||||
request_connector: Option<api_enums::Connector>,
|
||||
) -> CustomResult<api::ConnectorCallType, errors::ApiErrorResponse> {
|
||||
helpers::get_connector_default(merchant_account, state).await
|
||||
helpers::get_connector_default(merchant_account, state, request_connector).await
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -265,8 +265,9 @@ where
|
||||
&'a self,
|
||||
merchant_account: &storage::MerchantAccount,
|
||||
state: &AppState,
|
||||
request_connector: Option<api_enums::Connector>,
|
||||
) -> CustomResult<api::ConnectorCallType, errors::ApiErrorResponse> {
|
||||
helpers::get_connector_default(merchant_account, state).await
|
||||
helpers::get_connector_default(merchant_account, state, request_connector).await
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -16,7 +16,7 @@ use crate::{
|
||||
pii::Secret,
|
||||
routes::AppState,
|
||||
types::{
|
||||
api::{self, PaymentIdTypeExt},
|
||||
api::{self, enums as api_enums, PaymentIdTypeExt},
|
||||
storage::{self, enums},
|
||||
transformers::ForeignInto,
|
||||
},
|
||||
@ -257,6 +257,7 @@ where
|
||||
&'a self,
|
||||
merchant_account: &storage::MerchantAccount,
|
||||
state: &AppState,
|
||||
_request_connector: Option<api_enums::Connector>,
|
||||
) -> RouterResult<api::ConnectorCallType> {
|
||||
let connectors = &state.conf.connectors;
|
||||
let db = &state.store;
|
||||
|
||||
@ -15,7 +15,7 @@ use crate::{
|
||||
pii::Secret,
|
||||
routes::AppState,
|
||||
types::{
|
||||
api::{self, PaymentIdTypeExt},
|
||||
api::{self, enums as api_enums, PaymentIdTypeExt},
|
||||
storage::{self, enums, Customer},
|
||||
transformers::ForeignInto,
|
||||
},
|
||||
@ -265,7 +265,8 @@ where
|
||||
&'a self,
|
||||
merchant_account: &storage::MerchantAccount,
|
||||
state: &AppState,
|
||||
request_connector: Option<api_enums::Connector>,
|
||||
) -> CustomResult<api::ConnectorCallType, errors::ApiErrorResponse> {
|
||||
helpers::get_connector_default(merchant_account, state).await
|
||||
helpers::get_connector_default(merchant_account, state, request_connector).await
|
||||
}
|
||||
}
|
||||
|
||||
@ -15,7 +15,7 @@ use crate::{
|
||||
db::StorageInterface,
|
||||
routes::AppState,
|
||||
types::{
|
||||
api,
|
||||
api::{self, enums as api_enums},
|
||||
storage::{self, enums},
|
||||
transformers::ForeignInto,
|
||||
},
|
||||
@ -117,8 +117,9 @@ impl<F: Clone + Send> Domain<F, api::PaymentsRequest> for PaymentStatus {
|
||||
&'a self,
|
||||
merchant_account: &storage::MerchantAccount,
|
||||
state: &AppState,
|
||||
request_connector: Option<api_enums::Connector>,
|
||||
) -> CustomResult<api::ConnectorCallType, errors::ApiErrorResponse> {
|
||||
helpers::get_connector_default(merchant_account, state).await
|
||||
helpers::get_connector_default(merchant_account, state, request_connector).await
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -16,7 +16,7 @@ use crate::{
|
||||
db::StorageInterface,
|
||||
routes::AppState,
|
||||
types::{
|
||||
api::{self, PaymentIdTypeExt},
|
||||
api::{self, enums as api_enums, PaymentIdTypeExt},
|
||||
storage::{self, enums},
|
||||
transformers::ForeignInto,
|
||||
},
|
||||
@ -237,8 +237,9 @@ impl<F: Clone + Send> Domain<F, api::PaymentsRequest> for PaymentUpdate {
|
||||
&'a self,
|
||||
merchant_account: &storage::MerchantAccount,
|
||||
state: &AppState,
|
||||
request_connector: Option<api_enums::Connector>,
|
||||
) -> CustomResult<api::ConnectorCallType, errors::ApiErrorResponse> {
|
||||
helpers::get_connector_default(merchant_account, state).await
|
||||
helpers::get_connector_default(merchant_account, state, request_connector).await
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -52,6 +52,7 @@ async fn payments_incoming_webhook_flow(
|
||||
param: None,
|
||||
},
|
||||
services::AuthFlow::Merchant,
|
||||
None,
|
||||
consume_or_trigger_flow,
|
||||
)
|
||||
.await
|
||||
|
||||
Reference in New Issue
Block a user