mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-11-03 05:17:02 +08:00
refactor: raise appropriate errors instead of ValidateError (#71)
This commit is contained in:
@ -99,7 +99,6 @@ impl_error_type!(AuthenticationError, "Authentication error");
|
||||
impl_error_type!(AuthorisationError, "Authorisation error");
|
||||
impl_error_type!(EncryptionError, "Encryption error");
|
||||
impl_error_type!(UnexpectedError, "Unexpected error");
|
||||
impl_error_type!(ValidateError, "validation failed");
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum BachError {
|
||||
@ -123,9 +122,6 @@ pub enum BachError {
|
||||
#[error("Environment configuration error: {0}")]
|
||||
ConfigurationError(ConfigError),
|
||||
|
||||
#[error("{{ error_description: Error while validating, error_message: {0} }}")]
|
||||
EValidationError(error_stack::Report<ValidateError>), // Parsing error actually
|
||||
|
||||
#[error("{{ error_description: Database operation failed, error_message: {0} }}")]
|
||||
EDatabaseError(error_stack::Report<DatabaseError>),
|
||||
|
||||
@ -139,10 +135,6 @@ pub enum BachError {
|
||||
EIo(std::io::Error),
|
||||
}
|
||||
|
||||
router_error_error_stack_specific!(
|
||||
error_stack::Report<ValidateError>,
|
||||
BachError::EValidationError(error_stack::Report<ValidateError>)
|
||||
);
|
||||
router_error_error_stack_specific!(
|
||||
error_stack::Report<DatabaseError>,
|
||||
BachError::EDatabaseError(error_stack::Report<DatabaseError>)
|
||||
@ -202,8 +194,7 @@ impl ResponseError for BachError {
|
||||
match self {
|
||||
BachError::EParsingError(_)
|
||||
| BachError::EAuthenticationError(_)
|
||||
| BachError::EAuthorisationError(_)
|
||||
| BachError::EValidationError(_) => StatusCode::BAD_REQUEST,
|
||||
| BachError::EAuthorisationError(_) => StatusCode::BAD_REQUEST,
|
||||
|
||||
BachError::EDatabaseError(_)
|
||||
| BachError::NotImplementedByConnector(_)
|
||||
@ -424,6 +415,8 @@ pub enum ValidationError {
|
||||
MissingRequiredField { field_name: String },
|
||||
#[error("Incorrect value provided for field: {field_name}")]
|
||||
IncorrectValueProvided { field_name: &'static str },
|
||||
#[error("{message}")]
|
||||
InvalidValue { message: String },
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
|
||||
@ -42,17 +42,16 @@ pub enum ApiErrorResponse {
|
||||
field_name: String,
|
||||
expected_format: String,
|
||||
},
|
||||
#[error(
|
||||
error_type = ErrorType::InvalidRequestError, code = "IR_07",
|
||||
message = "{message}"
|
||||
)]
|
||||
#[error(error_type = ErrorType::InvalidRequestError, code = "IR_07", message = "{message}")]
|
||||
InvalidRequestData { message: String },
|
||||
#[error(error_type = ErrorType::InvalidRequestError, code = "IR_08", message = "Refund amount exceeds the payment amount.")]
|
||||
RefundAmountExceedsPaymentAmount,
|
||||
/// Typically used when a field has invalid value, or deserialization of the value contained in
|
||||
/// a field fails.
|
||||
#[error(error_type = ErrorType::InvalidRequestError, code = "IR_07", message = "Invalid value provided: {field_name}.")]
|
||||
InvalidDataValue { field_name: &'static str },
|
||||
#[error(error_type = ErrorType::InvalidRequestError, code = "IR_08", message = "Reached maximum refund attempts")]
|
||||
MaxiumumRefundCount,
|
||||
MaximumRefundCount,
|
||||
#[error(error_type = ErrorType::InvalidRequestError, code = "IR_09", message = "This PaymentIntent could not be {current_flow} because it has a {field_name} of {current_value}. The expected state is {states}.")]
|
||||
PaymentUnexpectedState {
|
||||
current_flow: String,
|
||||
@ -60,12 +59,16 @@ pub enum ApiErrorResponse {
|
||||
current_value: String,
|
||||
states: String,
|
||||
},
|
||||
/// Typically used when information involving multiple fields or previously provided
|
||||
/// information doesn't satisfy a condition.
|
||||
#[error(error_type = ErrorType::InvalidRequestError, code = "IR_10", message = "{message}")]
|
||||
PreconditionFailed { message: String },
|
||||
|
||||
#[error(error_type = ErrorType::InvalidRequestError, code = "IR_07", message = "The client_secret provided does not match the client_secret associated with the Payment.")]
|
||||
ClientSecretInvalid,
|
||||
|
||||
#[error(error_type = ErrorType::ProcessingError, code = "CE_01", message = "Payment failed while processing with connector. Retry payment.")]
|
||||
PaymentAuthorizationFailed { data: Option<serde_json::Value> }, //
|
||||
PaymentAuthorizationFailed { data: Option<serde_json::Value> },
|
||||
#[error(error_type = ErrorType::ProcessingError, code = "CE_02", message = "Payment failed while processing with connector. Retry payment.")]
|
||||
PaymentAuthenticationFailed { data: Option<serde_json::Value> },
|
||||
#[error(error_type = ErrorType::ProcessingError, code = "CE_03", message = "Capture attempt failed while processing with connector.")]
|
||||
@ -142,7 +145,8 @@ impl actix_web::ResponseError for ApiErrorResponse {
|
||||
ApiErrorResponse::InvalidDataFormat { .. }
|
||||
| ApiErrorResponse::InvalidRequestData { .. } => StatusCode::UNPROCESSABLE_ENTITY, // 422
|
||||
ApiErrorResponse::RefundAmountExceedsPaymentAmount => StatusCode::BAD_REQUEST, // 400
|
||||
ApiErrorResponse::MaxiumumRefundCount => StatusCode::BAD_REQUEST,
|
||||
ApiErrorResponse::MaximumRefundCount => StatusCode::BAD_REQUEST, // 400
|
||||
ApiErrorResponse::PreconditionFailed { .. } => StatusCode::BAD_REQUEST, // 400
|
||||
|
||||
ApiErrorResponse::PaymentAuthorizationFailed { .. }
|
||||
| ApiErrorResponse::PaymentAuthenticationFailed { .. }
|
||||
@ -170,8 +174,8 @@ impl actix_web::ResponseError for ApiErrorResponse {
|
||||
| ApiErrorResponse::DuplicatePaymentMethod
|
||||
| ApiErrorResponse::DuplicateMandate => StatusCode::BAD_REQUEST, // 400
|
||||
ApiErrorResponse::ReturnUrlUnavailable => StatusCode::SERVICE_UNAVAILABLE, // 503
|
||||
ApiErrorResponse::PaymentNotSucceeded => StatusCode::BAD_REQUEST,
|
||||
ApiErrorResponse::NotImplemented => StatusCode::NOT_IMPLEMENTED,
|
||||
ApiErrorResponse::PaymentNotSucceeded => StatusCode::BAD_REQUEST, // 400
|
||||
ApiErrorResponse::NotImplemented => StatusCode::NOT_IMPLEMENTED, // 501
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -35,7 +35,7 @@ pub async fn get_address_for_payment_request(
|
||||
req_address: Option<&api::Address>,
|
||||
address_id: Option<&str>,
|
||||
) -> CustomResult<Option<storage::Address>, errors::ApiErrorResponse> {
|
||||
// TODO: Refactoring this function for more redability (TryFrom)
|
||||
// TODO: Refactor this function for more readability (TryFrom)
|
||||
Ok(match req_address {
|
||||
Some(address) => {
|
||||
match address_id {
|
||||
@ -143,20 +143,14 @@ pub async fn get_token_for_recurring_mandate(
|
||||
|
||||
let payment_method_id = {
|
||||
if mandate.customer_id != customer {
|
||||
Err(report!(errors::ValidateError)
|
||||
.attach_printable("Invalid Mandate ID")
|
||||
.change_context(errors::ApiErrorResponse::InvalidDataFormat {
|
||||
field_name: "customer_id".to_string(),
|
||||
expected_format: "customer_id must match mandate customer_id".to_string(),
|
||||
}))?
|
||||
Err(report!(errors::ApiErrorResponse::PreconditionFailed {
|
||||
message: "customer_id must match mandate customer_id".into()
|
||||
}))?
|
||||
}
|
||||
if mandate.mandate_status != enums::MandateStatus::Active {
|
||||
Err(report!(errors::ValidateError)
|
||||
.attach_printable("Mandate is not active")
|
||||
.change_context(errors::ApiErrorResponse::InvalidDataFormat {
|
||||
field_name: "mandate_id".to_string(),
|
||||
expected_format: "mandate_id of an active mandate".to_string(),
|
||||
}))?
|
||||
Err(report!(errors::ApiErrorResponse::PreconditionFailed {
|
||||
message: "mandate is not active".into()
|
||||
}))?
|
||||
};
|
||||
mandate.payment_method_id
|
||||
};
|
||||
@ -174,12 +168,11 @@ pub async fn get_token_for_recurring_mandate(
|
||||
|
||||
if let Some(payment_method_from_request) = req.payment_method {
|
||||
if payment_method_from_request != payment_method.payment_method {
|
||||
Err(report!(errors::ValidateError)
|
||||
.attach_printable("Invalid Mandate ID")
|
||||
.change_context(errors::ApiErrorResponse::InvalidDataFormat {
|
||||
field_name: "payment_method".to_string(),
|
||||
expected_format: "valid payment method information".to_string(),
|
||||
}))?
|
||||
Err(report!(errors::ApiErrorResponse::PreconditionFailed {
|
||||
message: "payment method in request does not match previously provided payment \
|
||||
method information"
|
||||
.into()
|
||||
}))?
|
||||
}
|
||||
};
|
||||
|
||||
@ -192,7 +185,7 @@ pub async fn get_token_for_recurring_mandate(
|
||||
pub fn validate_merchant_id(
|
||||
merchant_id: &str,
|
||||
request_merchant_id: Option<&str>,
|
||||
) -> CustomResult<(), errors::ValidateError> {
|
||||
) -> CustomResult<(), errors::ApiErrorResponse> {
|
||||
// Get Merchant Id from the merchant
|
||||
// or get from merchant account
|
||||
|
||||
@ -200,9 +193,11 @@ pub fn validate_merchant_id(
|
||||
|
||||
utils::when(
|
||||
merchant_id.ne(request_merchant_id),
|
||||
Err(report!(errors::ValidateError).attach_printable(format!(
|
||||
"Invalid merchant_id: {request_merchant_id} not found in merchant account"
|
||||
))),
|
||||
Err(report!(errors::ApiErrorResponse::PreconditionFailed {
|
||||
message: format!(
|
||||
"Invalid `merchant_id`: {request_merchant_id} not found in merchant account"
|
||||
)
|
||||
})),
|
||||
)
|
||||
}
|
||||
|
||||
@ -210,7 +205,7 @@ pub fn validate_merchant_id(
|
||||
pub fn validate_request_amount_and_amount_to_capture(
|
||||
op_amount: Option<i32>,
|
||||
op_amount_to_capture: Option<i32>,
|
||||
) -> CustomResult<(), errors::ValidateError> {
|
||||
) -> CustomResult<(), errors::ApiErrorResponse> {
|
||||
// If both amount and amount to capture is present
|
||||
// then amount to be capture should be less than or equal to request amount
|
||||
|
||||
@ -222,10 +217,12 @@ pub fn validate_request_amount_and_amount_to_capture(
|
||||
|
||||
utils::when(
|
||||
!is_capture_amount_valid,
|
||||
Err(report!(errors::ValidateError).attach_printable(format!(
|
||||
Err(report!(errors::ApiErrorResponse::PreconditionFailed {
|
||||
message: format!(
|
||||
"amount_to_capture is greater than amount capture_amount: {:?} request_amount: {:?}",
|
||||
op_amount_to_capture, op_amount
|
||||
))),
|
||||
)
|
||||
})),
|
||||
)
|
||||
}
|
||||
|
||||
@ -247,12 +244,9 @@ fn validate_new_mandate_request(req: &api::PaymentsRequest) -> RouterResult<()>
|
||||
let confirm = req.confirm.get_required_value("confirm")?;
|
||||
|
||||
if !confirm {
|
||||
Err(report!(errors::ValidateError)
|
||||
.attach_printable("Confirm should be true for mandates")
|
||||
.change_context(errors::ApiErrorResponse::InvalidDataFormat {
|
||||
field_name: "confirm".to_string(),
|
||||
expected_format: "confirm must be true for mandates".to_string(),
|
||||
}))?
|
||||
Err(report!(errors::ApiErrorResponse::PreconditionFailed {
|
||||
message: "`confirm` must be `true` for mandates".into()
|
||||
}))?
|
||||
}
|
||||
|
||||
let _ = req.customer_id.as_ref().get_required_value("customer_id")?;
|
||||
@ -267,20 +261,19 @@ fn validate_new_mandate_request(req: &api::PaymentsRequest) -> RouterResult<()>
|
||||
.setup_future_usage
|
||||
.get_required_value("setup_future_usage")?
|
||||
{
|
||||
Err(report!(errors::ValidateError)
|
||||
.attach_printable("Key 'setup_future_usage' should be 'off_session' for mandates")
|
||||
.change_context(errors::ApiErrorResponse::InvalidDataFormat {
|
||||
field_name: "setup_future_usage".to_string(),
|
||||
expected_format: "setup_future_usage must be off_session for mandates".to_string(),
|
||||
}))?
|
||||
Err(report!(errors::ApiErrorResponse::PreconditionFailed {
|
||||
message: "`setup_future_usage` must be `off_session` for mandates".into()
|
||||
}))?
|
||||
};
|
||||
|
||||
if (mandate_data.customer_acceptance.acceptance_type == api::AcceptanceType::Online)
|
||||
&& mandate_data.customer_acceptance.online.is_none()
|
||||
{
|
||||
Err(report!(errors::ValidateError)
|
||||
.attach_printable("Key 'mandate_data.customer_acceptance.online' is required when 'mandate_data.customer_acceptance.acceptance_type' is 'online'")
|
||||
.change_context(errors::ApiErrorResponse::MissingRequiredField { field_name: "mandate_data.customer_acceptance.online".to_string() }))?
|
||||
Err(report!(errors::ApiErrorResponse::PreconditionFailed {
|
||||
message: "`mandate_data.customer_acceptance.online` is required when \
|
||||
`mandate_data.customer_acceptance.acceptance_type` is `online`"
|
||||
.into()
|
||||
}))?
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@ -309,6 +302,7 @@ pub fn create_redirect_url(server: &Server, payment_attempt: &storage::PaymentAt
|
||||
payment_attempt.connector
|
||||
)
|
||||
}
|
||||
|
||||
fn validate_recurring_mandate(req: &api::PaymentsRequest) -> RouterResult<()> {
|
||||
req.mandate_id.check_value_present("mandate_id")?;
|
||||
|
||||
@ -316,22 +310,16 @@ fn validate_recurring_mandate(req: &api::PaymentsRequest) -> RouterResult<()> {
|
||||
|
||||
let confirm = req.confirm.get_required_value("confirm")?;
|
||||
if !confirm {
|
||||
Err(report!(errors::ValidateError)
|
||||
.attach_printable("Confirm should be true for mandates")
|
||||
.change_context(errors::ApiErrorResponse::InvalidDataFormat {
|
||||
field_name: "confirm".to_string(),
|
||||
expected_format: "confirm must be true for mandates".to_string(),
|
||||
}))?
|
||||
Err(report!(errors::ApiErrorResponse::PreconditionFailed {
|
||||
message: "`confirm` must be `true` for mandates".into()
|
||||
}))?
|
||||
}
|
||||
|
||||
let off_session = req.off_session.get_required_value("off_session")?;
|
||||
if !off_session {
|
||||
Err(report!(errors::ValidateError)
|
||||
.attach_printable("off_session should be true for mandates")
|
||||
.change_context(errors::ApiErrorResponse::InvalidDataFormat {
|
||||
field_name: "off_session".to_string(),
|
||||
expected_format: "off_session must be true for mandates".to_string(),
|
||||
}))?
|
||||
Err(report!(errors::ApiErrorResponse::PreconditionFailed {
|
||||
message: "`off_session` should be `true` for mandates".into()
|
||||
}))?
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
@ -119,9 +119,11 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, api::PaymentsRequest> for Pa
|
||||
|
||||
match payment_intent.status {
|
||||
enums::IntentStatus::Succeeded | enums::IntentStatus::Failed => {
|
||||
Err(report!(errors::ValidateError)
|
||||
.attach_printable("You cannot confirm this Payment because it has already succeeded after being previously confirmed.")
|
||||
.change_context(errors::ApiErrorResponse::InvalidDataFormat { field_name: "payment_id".to_string(), expected_format: "payment_id of pending payment".to_string() }))
|
||||
Err(report!(errors::ApiErrorResponse::PreconditionFailed {
|
||||
message: "You cannot confirm this Payment because it has already succeeded \
|
||||
after being previously confirmed."
|
||||
.into()
|
||||
}))
|
||||
}
|
||||
_ => Ok((
|
||||
Box::new(self),
|
||||
@ -143,14 +145,14 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, api::PaymentsRequest> for Pa
|
||||
payment_method_data: request.payment_method_data.clone(),
|
||||
force_sync: None,
|
||||
refunds: vec![],
|
||||
},
|
||||
},
|
||||
Some(CustomerDetails {
|
||||
customer_id: request.customer_id.clone(),
|
||||
name: request.name.clone(),
|
||||
email: request.email.clone(),
|
||||
phone: request.phone.clone(),
|
||||
phone_country_code: request.phone_country_code.clone(),
|
||||
})
|
||||
}),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
@ -105,9 +105,11 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, api::PaymentsStartRequest> f
|
||||
|
||||
match payment_intent.status {
|
||||
enums::IntentStatus::Succeeded | enums::IntentStatus::Failed => {
|
||||
Err(report!(errors::ValidateError)
|
||||
.attach_printable("You cannot confirm this Payment because it has already succeeded after being previously confirmed.")
|
||||
.change_context(errors::ApiErrorResponse::InvalidDataFormat { field_name: "payment_id".to_string(), expected_format: "payment_id of pending payment".to_string() }))
|
||||
Err(report!(errors::ApiErrorResponse::PreconditionFailed {
|
||||
message: "You cannot confirm this Payment because it has already succeeded \
|
||||
after being previously confirmed."
|
||||
.into()
|
||||
}))
|
||||
}
|
||||
_ => Ok((
|
||||
Box::new(self),
|
||||
@ -125,12 +127,12 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, api::PaymentsStartRequest> f
|
||||
billing: billing_address.as_ref().map(|a| a.into()),
|
||||
},
|
||||
confirm: Some(payment_attempt.confirm),
|
||||
payment_attempt,
|
||||
payment_attempt,
|
||||
payment_method_data: None,
|
||||
force_sync: None,
|
||||
refunds: vec![]
|
||||
refunds: vec![],
|
||||
},
|
||||
Some(customer_details)
|
||||
Some(customer_details),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
@ -124,14 +124,11 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, api::PaymentsRequest> for Pa
|
||||
|
||||
match payment_intent.status {
|
||||
enums::IntentStatus::Succeeded | enums::IntentStatus::Failed => {
|
||||
Err(report!(errors::ValidateError)
|
||||
.attach_printable(
|
||||
"You cannot update this Payment because it has already succeeded/failed.",
|
||||
)
|
||||
.change_context(errors::ApiErrorResponse::InvalidDataFormat {
|
||||
field_name: "payment_id".to_string(),
|
||||
expected_format: "payment_id of pending payment".to_string(),
|
||||
}))
|
||||
Err(report!(errors::ApiErrorResponse::PreconditionFailed {
|
||||
message:
|
||||
"You cannot update this Payment because it has already succeeded/failed."
|
||||
.into()
|
||||
}))
|
||||
}
|
||||
_ => Ok((
|
||||
Box::new(self),
|
||||
|
||||
@ -359,7 +359,7 @@ pub async fn validate_and_create_refund(
|
||||
.change_context(errors::ApiErrorResponse::RefundAmountExceedsPaymentAmount)?;
|
||||
|
||||
validator::validate_maximum_refund_against_payment_attempt(&all_refunds)
|
||||
.change_context(errors::ApiErrorResponse::MaxiumumRefundCount)?;
|
||||
.change_context(errors::ApiErrorResponse::MaximumRefundCount)?;
|
||||
|
||||
refund_create_req = mk_new_refund(
|
||||
req,
|
||||
|
||||
Reference in New Issue
Block a user