feat(router): add straight-through routing connector selection in payments (#153)

This commit is contained in:
ItsMeShashank
2022-12-19 18:43:04 +05:30
committed by GitHub
parent a5f0c98eb7
commit d6a3e508e2
26 changed files with 192 additions and 100 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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;

View File

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

View File

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

View File

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

View File

@ -52,6 +52,7 @@ async fn payments_incoming_webhook_flow(
param: None,
},
services::AuthFlow::Merchant,
None,
consume_or_trigger_flow,
)
.await