refactor: raise appropriate errors instead of ValidateError (#71)

This commit is contained in:
Sanchith Hegde
2022-12-06 12:34:07 +05:30
committed by GitHub
parent 8c5e907cc8
commit ab5988e6ba
12 changed files with 136 additions and 135 deletions

View File

@ -4,21 +4,24 @@ use crate::core::errors::ApiErrorResponse;
#[derive(Debug, router_derive::ApiError)] #[derive(Debug, router_derive::ApiError)]
#[error(error_type_enum = StripeErrorType)] #[error(error_type_enum = StripeErrorType)]
pub(crate) enum ErrorCode { pub(crate) enum ErrorCode {
/*
"error": {
"message": "Invalid API Key provided: sk_jkjgs****nlgs",
"type": "invalid_request_error"
}
*/
#[error( #[error(
error_type = StripeErrorType::InvalidRequestError, code = "IR_01", error_type = StripeErrorType::InvalidRequestError, code = "IR_01",
message = "Invalid API Key provided" message = "Invalid API Key provided"
)] )]
Unauthorized, Unauthorized,
/*
"error": {
"message": "Invalid API Key provided: sk_jkjgs****nlgs",
"type": "invalid_request_error"
} */
#[error(error_type = StripeErrorType::InvalidRequestError, code = "IR_02", message = "Unrecognized request URL.")] #[error(error_type = StripeErrorType::InvalidRequestError, code = "IR_02", message = "Unrecognized request URL.")]
InvalidRequestUrl, InvalidRequestUrl,
#[error(error_type = StripeErrorType::InvalidRequestError, code = "parameter_missing", message = "Missing required param: {field_name}.")] #[error(error_type = StripeErrorType::InvalidRequestError, code = "parameter_missing", message = "Missing required param: {field_name}.")]
ParameterMissing { field_name: String, param: String }, ParameterMissing { field_name: String, param: String },
#[error( #[error(
error_type = StripeErrorType::InvalidRequestError, code = "parameter_unknown", error_type = StripeErrorType::InvalidRequestError, code = "parameter_unknown",
message = "{field_name} contains invalid data. Expected format is {expected_format}." message = "{field_name} contains invalid data. Expected format is {expected_format}."
@ -81,8 +84,10 @@ pub(crate) enum ErrorCode {
#[error(error_type = StripeErrorType::InvalidRequestError, code = "token_already_used", message = "duplicate merchant account")] #[error(error_type = StripeErrorType::InvalidRequestError, code = "token_already_used", message = "duplicate merchant account")]
DuplicateMerchantAccount, DuplicateMerchantAccount,
#[error(error_type = StripeErrorType::InvalidRequestError, code = "token_already_used", message = "duplicate merchant_connector_account")] #[error(error_type = StripeErrorType::InvalidRequestError, code = "token_already_used", message = "duplicate merchant_connector_account")]
DuplicateMerchantConnectorAccount, DuplicateMerchantConnectorAccount,
#[error(error_type = StripeErrorType::InvalidRequestError, code = "token_already_used", message = "duplicate payment method")] #[error(error_type = StripeErrorType::InvalidRequestError, code = "token_already_used", message = "duplicate payment method")]
DuplicatePaymentMethod, DuplicatePaymentMethod,
@ -96,11 +101,17 @@ pub(crate) enum ErrorCode {
PaymentIntentInvalidParameter { param: String }, PaymentIntentInvalidParameter { param: String },
#[error( #[error(
error_type = StripeErrorType::InvalidRequestError, code = "IR-05", error_type = StripeErrorType::InvalidRequestError, code = "IR_05",
message = "{message}" message = "{message}"
)] )]
InvalidRequestData { message: String }, InvalidRequestData { message: String },
#[error(
error_type = StripeErrorType::InvalidRequestError, code = "IR_10",
message = "{message}"
)]
PreconditionFailed { message: String },
#[error( #[error(
error_type = StripeErrorType::InvalidRequestError, code = "", error_type = StripeErrorType::InvalidRequestError, code = "",
message = "The payment has not succeeded yet" message = "The payment has not succeeded yet"
@ -205,17 +216,13 @@ pub(crate) enum ErrorCode {
// ParameterInvalidInteger, // ParameterInvalidInteger,
// ParameterInvalidStringBlank, // ParameterInvalidStringBlank,
// ParameterInvalidStringEmpty, // ParameterInvalidStringEmpty,
// ParametersExclusive, // ParametersExclusive,
// PaymentIntentActionRequired, // PaymentIntentActionRequired,
// PaymentIntentIncompatiblePaymentMethod, // PaymentIntentIncompatiblePaymentMethod,
// PaymentIntentInvalidParameter, // PaymentIntentInvalidParameter,
// PaymentIntentKonbiniRejectedConfirmationNumber, // PaymentIntentKonbiniRejectedConfirmationNumber,
// PaymentIntentMandateInvalid, // PaymentIntentMandateInvalid,
// PaymentIntentPaymentAttemptExpired, // PaymentIntentPaymentAttemptExpired,
// PaymentIntentUnexpectedState, // PaymentIntentUnexpectedState,
// PaymentMethodBankAccountAlreadyVerified, // PaymentMethodBankAccountAlreadyVerified,
// PaymentMethodBankAccountBlocked, // PaymentMethodBankAccountBlocked,
@ -263,7 +270,6 @@ pub(crate) enum ErrorCode {
// TerminalLocationCountryUnsupported, // TerminalLocationCountryUnsupported,
// TestmodeChargesOnly, // TestmodeChargesOnly,
// TlsVersionUnsupported, // TlsVersionUnsupported,
// ,
// TokenInUse, // TokenInUse,
// TransferSourceBalanceParametersMismatch, // TransferSourceBalanceParametersMismatch,
// TransfersNotAllowed, // TransfersNotAllowed,
@ -299,7 +305,7 @@ impl From<ApiErrorResponse> for ErrorCode {
field_name: field_name.to_owned(), field_name: field_name.to_owned(),
param: field_name, param: field_name,
}, },
// parameter unknown, invalid request error // actually if we type worng values in addrees we get this error. Stripe throws parameter unknown. I don't know if stripe is validating email and stuff // parameter unknown, invalid request error // actually if we type wrong values in address we get this error. Stripe throws parameter unknown. I don't know if stripe is validating email and stuff
ApiErrorResponse::InvalidDataFormat { ApiErrorResponse::InvalidDataFormat {
field_name, field_name,
expected_format, expected_format,
@ -319,7 +325,7 @@ impl From<ApiErrorResponse> for ErrorCode {
ApiErrorResponse::PaymentCaptureFailed { data } => { ApiErrorResponse::PaymentCaptureFailed { data } => {
ErrorCode::PaymentIntentPaymentAttemptFailed { data } ErrorCode::PaymentIntentPaymentAttemptFailed { data }
} }
ApiErrorResponse::InvalidCardData { data } => ErrorCode::InvalidCardType, // Maybe it is better to de generalise this router error ApiErrorResponse::InvalidCardData { data } => ErrorCode::InvalidCardType, // Maybe it is better to de generalize this router error
ApiErrorResponse::CardExpired { data } => ErrorCode::ExpiredCard, ApiErrorResponse::CardExpired { data } => ErrorCode::ExpiredCard,
ApiErrorResponse::RefundFailed { data } => ErrorCode::RefundFailed, // Nothing at stripe to map ApiErrorResponse::RefundFailed { data } => ErrorCode::RefundFailed, // Nothing at stripe to map
@ -347,12 +353,15 @@ impl From<ApiErrorResponse> for ErrorCode {
ApiErrorResponse::InvalidRequestData { message } => { ApiErrorResponse::InvalidRequestData { message } => {
ErrorCode::InvalidRequestData { message } ErrorCode::InvalidRequestData { message }
} }
ApiErrorResponse::PreconditionFailed { message } => {
ErrorCode::PreconditionFailed { message }
}
ApiErrorResponse::BadCredentials => ErrorCode::Unauthorized, ApiErrorResponse::BadCredentials => ErrorCode::Unauthorized,
ApiErrorResponse::InvalidDataValue { field_name } => ErrorCode::ParameterMissing { ApiErrorResponse::InvalidDataValue { field_name } => ErrorCode::ParameterMissing {
field_name: field_name.to_owned(), field_name: field_name.to_owned(),
param: field_name.to_owned(), param: field_name.to_owned(),
}, },
ApiErrorResponse::MaxiumumRefundCount => ErrorCode::MaximumRefundCount, ApiErrorResponse::MaximumRefundCount => ErrorCode::MaximumRefundCount,
ApiErrorResponse::PaymentNotSucceeded => ErrorCode::PaymentFailed, ApiErrorResponse::PaymentNotSucceeded => ErrorCode::PaymentFailed,
ApiErrorResponse::DuplicateMandate => ErrorCode::DuplicateMandate, ApiErrorResponse::DuplicateMandate => ErrorCode::DuplicateMandate,
ApiErrorResponse::SuccessfulPaymentNotFound => ErrorCode::SuccessfulPaymentNotFound, ApiErrorResponse::SuccessfulPaymentNotFound => ErrorCode::SuccessfulPaymentNotFound,
@ -403,6 +412,7 @@ impl actix_web::ResponseError for ErrorCode {
| ErrorCode::PaymentIntentInvalidParameter { .. } | ErrorCode::PaymentIntentInvalidParameter { .. }
| ErrorCode::SerdeQsError { .. } | ErrorCode::SerdeQsError { .. }
| ErrorCode::InvalidRequestData { .. } | ErrorCode::InvalidRequestData { .. }
| ErrorCode::PreconditionFailed { .. }
| ErrorCode::DuplicateMandate | ErrorCode::DuplicateMandate
| ErrorCode::SuccessfulPaymentNotFound | ErrorCode::SuccessfulPaymentNotFound
| ErrorCode::AddressNotFound | ErrorCode::AddressNotFound

View File

@ -56,7 +56,7 @@ pub struct CheckoutThreeDS {
} }
impl TryFrom<&types::ConnectorAuthType> for CheckoutAuthType { impl TryFrom<&types::ConnectorAuthType> for CheckoutAuthType {
type Error = error_stack::Report<errors::ValidateError>; type Error = error_stack::Report<errors::ConnectorError>;
fn try_from(auth_type: &types::ConnectorAuthType) -> Result<Self, Self::Error> { fn try_from(auth_type: &types::ConnectorAuthType) -> Result<Self, Self::Error> {
if let types::ConnectorAuthType::BodyKey { api_key, key1 } = auth_type { if let types::ConnectorAuthType::BodyKey { api_key, key1 } = auth_type {
Ok(Self { Ok(Self {
@ -64,12 +64,12 @@ impl TryFrom<&types::ConnectorAuthType> for CheckoutAuthType {
processing_channel_id: key1.to_string(), processing_channel_id: key1.to_string(),
}) })
} else { } else {
Err(errors::ValidateError.into()) Err(errors::ConnectorError::FailedToObtainAuthType.into())
} }
} }
} }
impl TryFrom<&types::PaymentsAuthorizeRouterData> for PaymentsRequest { impl TryFrom<&types::PaymentsAuthorizeRouterData> for PaymentsRequest {
type Error = error_stack::Report<errors::ValidateError>; type Error = error_stack::Report<errors::ConnectorError>;
fn try_from(item: &types::PaymentsAuthorizeRouterData) -> Result<Self, Self::Error> { fn try_from(item: &types::PaymentsAuthorizeRouterData) -> Result<Self, Self::Error> {
let ccard = match item.request.payment_method_data { let ccard = match item.request.payment_method_data {
api::PaymentMethod::Card(ref ccard) => Some(ccard), api::PaymentMethod::Card(ref ccard) => Some(ccard),
@ -261,7 +261,7 @@ impl From<&PaymentVoidResponse> for enums::AttemptStatus {
impl TryFrom<types::PaymentsCancelResponseRouterData<PaymentVoidResponse>> impl TryFrom<types::PaymentsCancelResponseRouterData<PaymentVoidResponse>>
for types::PaymentsCancelRouterData for types::PaymentsCancelRouterData
{ {
type Error = error_stack::Report<errors::ValidateError>; type Error = error_stack::Report<errors::ParsingError>;
fn try_from( fn try_from(
item: types::PaymentsCancelResponseRouterData<PaymentVoidResponse>, item: types::PaymentsCancelResponseRouterData<PaymentVoidResponse>,
) -> Result<Self, Self::Error> { ) -> Result<Self, Self::Error> {
@ -294,7 +294,7 @@ pub struct RefundRequest {
} }
impl<F> TryFrom<&types::RefundsRouterData<F>> for RefundRequest { impl<F> TryFrom<&types::RefundsRouterData<F>> for RefundRequest {
type Error = error_stack::Report<errors::ValidateError>; type Error = error_stack::Report<errors::ParsingError>;
fn try_from(item: &types::RefundsRouterData<F>) -> Result<Self, Self::Error> { fn try_from(item: &types::RefundsRouterData<F>) -> Result<Self, Self::Error> {
let amount = item.request.refund_amount; let amount = item.request.refund_amount;
let reference = item.request.refund_id.clone(); let reference = item.request.refund_id.clone();
@ -330,7 +330,7 @@ impl From<&CheckoutRefundResponse> for enums::RefundStatus {
impl TryFrom<types::RefundsResponseRouterData<api::Execute, CheckoutRefundResponse>> impl TryFrom<types::RefundsResponseRouterData<api::Execute, CheckoutRefundResponse>>
for types::RefundsRouterData<api::Execute> for types::RefundsRouterData<api::Execute>
{ {
type Error = error_stack::Report<errors::ValidateError>; type Error = error_stack::Report<errors::ParsingError>;
fn try_from( fn try_from(
item: types::RefundsResponseRouterData<api::Execute, CheckoutRefundResponse>, item: types::RefundsResponseRouterData<api::Execute, CheckoutRefundResponse>,
) -> Result<Self, Self::Error> { ) -> Result<Self, Self::Error> {
@ -348,7 +348,7 @@ impl TryFrom<types::RefundsResponseRouterData<api::Execute, CheckoutRefundRespon
impl TryFrom<types::RefundsResponseRouterData<api::RSync, CheckoutRefundResponse>> impl TryFrom<types::RefundsResponseRouterData<api::RSync, CheckoutRefundResponse>>
for types::RefundsRouterData<api::RSync> for types::RefundsRouterData<api::RSync>
{ {
type Error = error_stack::Report<errors::ValidateError>; type Error = error_stack::Report<errors::ParsingError>;
fn try_from( fn try_from(
item: types::RefundsResponseRouterData<api::RSync, CheckoutRefundResponse>, item: types::RefundsResponseRouterData<api::RSync, CheckoutRefundResponse>,
) -> Result<Self, Self::Error> { ) -> Result<Self, Self::Error> {
@ -421,7 +421,7 @@ pub struct CheckoutRedirectResponse {
impl TryFrom<types::RefundsResponseRouterData<api::Execute, &ActionResponse>> impl TryFrom<types::RefundsResponseRouterData<api::Execute, &ActionResponse>>
for types::RefundsRouterData<api::Execute> for types::RefundsRouterData<api::Execute>
{ {
type Error = error_stack::Report<errors::ValidateError>; type Error = error_stack::Report<errors::ParsingError>;
fn try_from( fn try_from(
item: types::RefundsResponseRouterData<api::Execute, &ActionResponse>, item: types::RefundsResponseRouterData<api::Execute, &ActionResponse>,
) -> Result<Self, Self::Error> { ) -> Result<Self, Self::Error> {
@ -439,7 +439,7 @@ impl TryFrom<types::RefundsResponseRouterData<api::Execute, &ActionResponse>>
impl TryFrom<types::RefundsResponseRouterData<api::RSync, &ActionResponse>> impl TryFrom<types::RefundsResponseRouterData<api::RSync, &ActionResponse>>
for types::RefundsRouterData<api::RSync> for types::RefundsRouterData<api::RSync>
{ {
type Error = error_stack::Report<errors::ValidateError>; type Error = error_stack::Report<errors::ParsingError>;
fn try_from( fn try_from(
item: types::RefundsResponseRouterData<api::RSync, &ActionResponse>, item: types::RefundsResponseRouterData<api::RSync, &ActionResponse>,
) -> Result<Self, Self::Error> { ) -> Result<Self, Self::Error> {

View File

@ -18,14 +18,14 @@ pub struct StripeAuthType {
} }
impl TryFrom<&types::ConnectorAuthType> for StripeAuthType { impl TryFrom<&types::ConnectorAuthType> for StripeAuthType {
type Error = error_stack::Report<errors::ValidateError>; type Error = error_stack::Report<errors::ConnectorError>;
fn try_from(item: &types::ConnectorAuthType) -> Result<Self, Self::Error> { fn try_from(item: &types::ConnectorAuthType) -> Result<Self, Self::Error> {
if let types::ConnectorAuthType::HeaderKey { api_key } = item { if let types::ConnectorAuthType::HeaderKey { api_key } = item {
Ok(Self { Ok(Self {
api_key: api_key.to_string(), api_key: api_key.to_string(),
}) })
} else { } else {
Err(errors::ValidateError.into()) Err(errors::ConnectorError::FailedToObtainAuthType.into())
} }
} }
} }

View File

@ -99,7 +99,6 @@ impl_error_type!(AuthenticationError, "Authentication error");
impl_error_type!(AuthorisationError, "Authorisation error"); impl_error_type!(AuthorisationError, "Authorisation error");
impl_error_type!(EncryptionError, "Encryption error"); impl_error_type!(EncryptionError, "Encryption error");
impl_error_type!(UnexpectedError, "Unexpected error"); impl_error_type!(UnexpectedError, "Unexpected error");
impl_error_type!(ValidateError, "validation failed");
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]
pub enum BachError { pub enum BachError {
@ -123,9 +122,6 @@ pub enum BachError {
#[error("Environment configuration error: {0}")] #[error("Environment configuration error: {0}")]
ConfigurationError(ConfigError), 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} }}")] #[error("{{ error_description: Database operation failed, error_message: {0} }}")]
EDatabaseError(error_stack::Report<DatabaseError>), EDatabaseError(error_stack::Report<DatabaseError>),
@ -139,10 +135,6 @@ pub enum BachError {
EIo(std::io::Error), EIo(std::io::Error),
} }
router_error_error_stack_specific!(
error_stack::Report<ValidateError>,
BachError::EValidationError(error_stack::Report<ValidateError>)
);
router_error_error_stack_specific!( router_error_error_stack_specific!(
error_stack::Report<DatabaseError>, error_stack::Report<DatabaseError>,
BachError::EDatabaseError(error_stack::Report<DatabaseError>) BachError::EDatabaseError(error_stack::Report<DatabaseError>)
@ -202,8 +194,7 @@ impl ResponseError for BachError {
match self { match self {
BachError::EParsingError(_) BachError::EParsingError(_)
| BachError::EAuthenticationError(_) | BachError::EAuthenticationError(_)
| BachError::EAuthorisationError(_) | BachError::EAuthorisationError(_) => StatusCode::BAD_REQUEST,
| BachError::EValidationError(_) => StatusCode::BAD_REQUEST,
BachError::EDatabaseError(_) BachError::EDatabaseError(_)
| BachError::NotImplementedByConnector(_) | BachError::NotImplementedByConnector(_)
@ -424,6 +415,8 @@ pub enum ValidationError {
MissingRequiredField { field_name: String }, MissingRequiredField { field_name: String },
#[error("Incorrect value provided for field: {field_name}")] #[error("Incorrect value provided for field: {field_name}")]
IncorrectValueProvided { field_name: &'static str }, IncorrectValueProvided { field_name: &'static str },
#[error("{message}")]
InvalidValue { message: String },
} }
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]

View File

@ -42,17 +42,16 @@ pub enum ApiErrorResponse {
field_name: String, field_name: String,
expected_format: String, expected_format: String,
}, },
#[error( #[error(error_type = ErrorType::InvalidRequestError, code = "IR_07", message = "{message}")]
error_type = ErrorType::InvalidRequestError, code = "IR_07",
message = "{message}"
)]
InvalidRequestData { message: String }, InvalidRequestData { message: String },
#[error(error_type = ErrorType::InvalidRequestError, code = "IR_08", message = "Refund amount exceeds the payment amount.")] #[error(error_type = ErrorType::InvalidRequestError, code = "IR_08", message = "Refund amount exceeds the payment amount.")]
RefundAmountExceedsPaymentAmount, 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}.")] #[error(error_type = ErrorType::InvalidRequestError, code = "IR_07", message = "Invalid value provided: {field_name}.")]
InvalidDataValue { field_name: &'static str }, InvalidDataValue { field_name: &'static str },
#[error(error_type = ErrorType::InvalidRequestError, code = "IR_08", message = "Reached maximum refund attempts")] #[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}.")] #[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 { PaymentUnexpectedState {
current_flow: String, current_flow: String,
@ -60,12 +59,16 @@ pub enum ApiErrorResponse {
current_value: String, current_value: String,
states: 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.")] #[error(error_type = ErrorType::InvalidRequestError, code = "IR_07", message = "The client_secret provided does not match the client_secret associated with the Payment.")]
ClientSecretInvalid, ClientSecretInvalid,
#[error(error_type = ErrorType::ProcessingError, code = "CE_01", message = "Payment failed while processing with connector. Retry payment.")] #[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.")] #[error(error_type = ErrorType::ProcessingError, code = "CE_02", message = "Payment failed while processing with connector. Retry payment.")]
PaymentAuthenticationFailed { data: Option<serde_json::Value> }, PaymentAuthenticationFailed { data: Option<serde_json::Value> },
#[error(error_type = ErrorType::ProcessingError, code = "CE_03", message = "Capture attempt failed while processing with connector.")] #[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::InvalidDataFormat { .. }
| ApiErrorResponse::InvalidRequestData { .. } => StatusCode::UNPROCESSABLE_ENTITY, // 422 | ApiErrorResponse::InvalidRequestData { .. } => StatusCode::UNPROCESSABLE_ENTITY, // 422
ApiErrorResponse::RefundAmountExceedsPaymentAmount => StatusCode::BAD_REQUEST, // 400 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::PaymentAuthorizationFailed { .. }
| ApiErrorResponse::PaymentAuthenticationFailed { .. } | ApiErrorResponse::PaymentAuthenticationFailed { .. }
@ -170,8 +174,8 @@ impl actix_web::ResponseError for ApiErrorResponse {
| ApiErrorResponse::DuplicatePaymentMethod | ApiErrorResponse::DuplicatePaymentMethod
| ApiErrorResponse::DuplicateMandate => StatusCode::BAD_REQUEST, // 400 | ApiErrorResponse::DuplicateMandate => StatusCode::BAD_REQUEST, // 400
ApiErrorResponse::ReturnUrlUnavailable => StatusCode::SERVICE_UNAVAILABLE, // 503 ApiErrorResponse::ReturnUrlUnavailable => StatusCode::SERVICE_UNAVAILABLE, // 503
ApiErrorResponse::PaymentNotSucceeded => StatusCode::BAD_REQUEST, ApiErrorResponse::PaymentNotSucceeded => StatusCode::BAD_REQUEST, // 400
ApiErrorResponse::NotImplemented => StatusCode::NOT_IMPLEMENTED, ApiErrorResponse::NotImplemented => StatusCode::NOT_IMPLEMENTED, // 501
} }
} }

View File

@ -35,7 +35,7 @@ pub async fn get_address_for_payment_request(
req_address: Option<&api::Address>, req_address: Option<&api::Address>,
address_id: Option<&str>, address_id: Option<&str>,
) -> CustomResult<Option<storage::Address>, errors::ApiErrorResponse> { ) -> 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 { Ok(match req_address {
Some(address) => { Some(address) => {
match address_id { match address_id {
@ -143,20 +143,14 @@ pub async fn get_token_for_recurring_mandate(
let payment_method_id = { let payment_method_id = {
if mandate.customer_id != customer { if mandate.customer_id != customer {
Err(report!(errors::ValidateError) Err(report!(errors::ApiErrorResponse::PreconditionFailed {
.attach_printable("Invalid Mandate ID") message: "customer_id must match mandate customer_id".into()
.change_context(errors::ApiErrorResponse::InvalidDataFormat { }))?
field_name: "customer_id".to_string(),
expected_format: "customer_id must match mandate customer_id".to_string(),
}))?
} }
if mandate.mandate_status != enums::MandateStatus::Active { if mandate.mandate_status != enums::MandateStatus::Active {
Err(report!(errors::ValidateError) Err(report!(errors::ApiErrorResponse::PreconditionFailed {
.attach_printable("Mandate is not active") message: "mandate is not active".into()
.change_context(errors::ApiErrorResponse::InvalidDataFormat { }))?
field_name: "mandate_id".to_string(),
expected_format: "mandate_id of an active mandate".to_string(),
}))?
}; };
mandate.payment_method_id 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 let Some(payment_method_from_request) = req.payment_method {
if payment_method_from_request != payment_method.payment_method { if payment_method_from_request != payment_method.payment_method {
Err(report!(errors::ValidateError) Err(report!(errors::ApiErrorResponse::PreconditionFailed {
.attach_printable("Invalid Mandate ID") message: "payment method in request does not match previously provided payment \
.change_context(errors::ApiErrorResponse::InvalidDataFormat { method information"
field_name: "payment_method".to_string(), .into()
expected_format: "valid payment method information".to_string(), }))?
}))?
} }
}; };
@ -192,7 +185,7 @@ pub async fn get_token_for_recurring_mandate(
pub fn validate_merchant_id( pub fn validate_merchant_id(
merchant_id: &str, merchant_id: &str,
request_merchant_id: Option<&str>, request_merchant_id: Option<&str>,
) -> CustomResult<(), errors::ValidateError> { ) -> CustomResult<(), errors::ApiErrorResponse> {
// Get Merchant Id from the merchant // Get Merchant Id from the merchant
// or get from merchant account // or get from merchant account
@ -200,9 +193,11 @@ pub fn validate_merchant_id(
utils::when( utils::when(
merchant_id.ne(request_merchant_id), merchant_id.ne(request_merchant_id),
Err(report!(errors::ValidateError).attach_printable(format!( Err(report!(errors::ApiErrorResponse::PreconditionFailed {
"Invalid merchant_id: {request_merchant_id} not found in merchant account" 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( pub fn validate_request_amount_and_amount_to_capture(
op_amount: Option<i32>, op_amount: Option<i32>,
op_amount_to_capture: Option<i32>, op_amount_to_capture: Option<i32>,
) -> CustomResult<(), errors::ValidateError> { ) -> CustomResult<(), errors::ApiErrorResponse> {
// If both amount and amount to capture is present // If both amount and amount to capture is present
// then amount to be capture should be less than or equal to request amount // 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( utils::when(
!is_capture_amount_valid, !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: {:?}", "amount_to_capture is greater than amount capture_amount: {:?} request_amount: {:?}",
op_amount_to_capture, op_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")?; let confirm = req.confirm.get_required_value("confirm")?;
if !confirm { if !confirm {
Err(report!(errors::ValidateError) Err(report!(errors::ApiErrorResponse::PreconditionFailed {
.attach_printable("Confirm should be true for mandates") message: "`confirm` must be `true` for mandates".into()
.change_context(errors::ApiErrorResponse::InvalidDataFormat { }))?
field_name: "confirm".to_string(),
expected_format: "confirm must be true for mandates".to_string(),
}))?
} }
let _ = req.customer_id.as_ref().get_required_value("customer_id")?; 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 .setup_future_usage
.get_required_value("setup_future_usage")? .get_required_value("setup_future_usage")?
{ {
Err(report!(errors::ValidateError) Err(report!(errors::ApiErrorResponse::PreconditionFailed {
.attach_printable("Key 'setup_future_usage' should be 'off_session' for mandates") message: "`setup_future_usage` must be `off_session` for mandates".into()
.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(),
}))?
}; };
if (mandate_data.customer_acceptance.acceptance_type == api::AcceptanceType::Online) if (mandate_data.customer_acceptance.acceptance_type == api::AcceptanceType::Online)
&& mandate_data.customer_acceptance.online.is_none() && mandate_data.customer_acceptance.online.is_none()
{ {
Err(report!(errors::ValidateError) Err(report!(errors::ApiErrorResponse::PreconditionFailed {
.attach_printable("Key 'mandate_data.customer_acceptance.online' is required when 'mandate_data.customer_acceptance.acceptance_type' is 'online'") message: "`mandate_data.customer_acceptance.online` is required when \
.change_context(errors::ApiErrorResponse::MissingRequiredField { field_name: "mandate_data.customer_acceptance.online".to_string() }))? `mandate_data.customer_acceptance.acceptance_type` is `online`"
.into()
}))?
} }
Ok(()) Ok(())
@ -309,6 +302,7 @@ pub fn create_redirect_url(server: &Server, payment_attempt: &storage::PaymentAt
payment_attempt.connector payment_attempt.connector
) )
} }
fn validate_recurring_mandate(req: &api::PaymentsRequest) -> RouterResult<()> { fn validate_recurring_mandate(req: &api::PaymentsRequest) -> RouterResult<()> {
req.mandate_id.check_value_present("mandate_id")?; 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")?; let confirm = req.confirm.get_required_value("confirm")?;
if !confirm { if !confirm {
Err(report!(errors::ValidateError) Err(report!(errors::ApiErrorResponse::PreconditionFailed {
.attach_printable("Confirm should be true for mandates") message: "`confirm` must be `true` for mandates".into()
.change_context(errors::ApiErrorResponse::InvalidDataFormat { }))?
field_name: "confirm".to_string(),
expected_format: "confirm must be true for mandates".to_string(),
}))?
} }
let off_session = req.off_session.get_required_value("off_session")?; let off_session = req.off_session.get_required_value("off_session")?;
if !off_session { if !off_session {
Err(report!(errors::ValidateError) Err(report!(errors::ApiErrorResponse::PreconditionFailed {
.attach_printable("off_session should be true for mandates") message: "`off_session` should be `true` for mandates".into()
.change_context(errors::ApiErrorResponse::InvalidDataFormat { }))?
field_name: "off_session".to_string(),
expected_format: "off_session must be true for mandates".to_string(),
}))?
} }
Ok(()) Ok(())

View File

@ -119,9 +119,11 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, api::PaymentsRequest> for Pa
match payment_intent.status { match payment_intent.status {
enums::IntentStatus::Succeeded | enums::IntentStatus::Failed => { enums::IntentStatus::Succeeded | enums::IntentStatus::Failed => {
Err(report!(errors::ValidateError) Err(report!(errors::ApiErrorResponse::PreconditionFailed {
.attach_printable("You cannot confirm this Payment because it has already succeeded after being previously confirmed.") message: "You cannot confirm this Payment because it has already succeeded \
.change_context(errors::ApiErrorResponse::InvalidDataFormat { field_name: "payment_id".to_string(), expected_format: "payment_id of pending payment".to_string() })) after being previously confirmed."
.into()
}))
} }
_ => Ok(( _ => Ok((
Box::new(self), 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(), payment_method_data: request.payment_method_data.clone(),
force_sync: None, force_sync: None,
refunds: vec![], refunds: vec![],
}, },
Some(CustomerDetails { Some(CustomerDetails {
customer_id: request.customer_id.clone(), customer_id: request.customer_id.clone(),
name: request.name.clone(), name: request.name.clone(),
email: request.email.clone(), email: request.email.clone(),
phone: request.phone.clone(), phone: request.phone.clone(),
phone_country_code: request.phone_country_code.clone(), phone_country_code: request.phone_country_code.clone(),
}) }),
)), )),
} }
} }

View File

@ -105,9 +105,11 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, api::PaymentsStartRequest> f
match payment_intent.status { match payment_intent.status {
enums::IntentStatus::Succeeded | enums::IntentStatus::Failed => { enums::IntentStatus::Succeeded | enums::IntentStatus::Failed => {
Err(report!(errors::ValidateError) Err(report!(errors::ApiErrorResponse::PreconditionFailed {
.attach_printable("You cannot confirm this Payment because it has already succeeded after being previously confirmed.") message: "You cannot confirm this Payment because it has already succeeded \
.change_context(errors::ApiErrorResponse::InvalidDataFormat { field_name: "payment_id".to_string(), expected_format: "payment_id of pending payment".to_string() })) after being previously confirmed."
.into()
}))
} }
_ => Ok(( _ => Ok((
Box::new(self), 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()), billing: billing_address.as_ref().map(|a| a.into()),
}, },
confirm: Some(payment_attempt.confirm), confirm: Some(payment_attempt.confirm),
payment_attempt, payment_attempt,
payment_method_data: None, payment_method_data: None,
force_sync: None, force_sync: None,
refunds: vec![] refunds: vec![],
}, },
Some(customer_details) Some(customer_details),
)), )),
} }
} }

View File

@ -124,14 +124,11 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, api::PaymentsRequest> for Pa
match payment_intent.status { match payment_intent.status {
enums::IntentStatus::Succeeded | enums::IntentStatus::Failed => { enums::IntentStatus::Succeeded | enums::IntentStatus::Failed => {
Err(report!(errors::ValidateError) Err(report!(errors::ApiErrorResponse::PreconditionFailed {
.attach_printable( message:
"You cannot update this Payment because it has already succeeded/failed.", "You cannot update this Payment because it has already succeeded/failed."
) .into()
.change_context(errors::ApiErrorResponse::InvalidDataFormat { }))
field_name: "payment_id".to_string(),
expected_format: "payment_id of pending payment".to_string(),
}))
} }
_ => Ok(( _ => Ok((
Box::new(self), Box::new(self),

View File

@ -359,7 +359,7 @@ pub async fn validate_and_create_refund(
.change_context(errors::ApiErrorResponse::RefundAmountExceedsPaymentAmount)?; .change_context(errors::ApiErrorResponse::RefundAmountExceedsPaymentAmount)?;
validator::validate_maximum_refund_against_payment_attempt(&all_refunds) 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( refund_create_req = mk_new_refund(
req, req,

View File

@ -534,11 +534,10 @@ pub async fn authenticate_merchant<'a>(
let admin_api_key = let admin_api_key =
get_api_key(request).change_context(errors::ApiErrorResponse::Unauthorized)?; get_api_key(request).change_context(errors::ApiErrorResponse::Unauthorized)?;
if admin_api_key != "test_admin" { if admin_api_key != "test_admin" {
Err(report!(errors::ValidateError) // TODO: The BadCredentials error is too specific for api keys, and inappropriate
.attach_printable("Admin Authentication Failure") // for AdminApiKey/MerchantID
// TODO: The BadCredentials error is too specific for api keys, and inappropriate for AdminApiKey/MerchantID Err(report!(errors::ApiErrorResponse::BadCredentials)
// https://juspay.atlassian.net/browse/ORCA-366 .attach_printable("Admin Authentication Failure"))?;
.change_context(errors::ApiErrorResponse::BadCredentials))?;
} }
Ok(storage::MerchantAccount { Ok(storage::MerchantAccount {

View File

@ -4,7 +4,7 @@ use once_cell::sync::Lazy;
use regex::Regex; use regex::Regex;
use crate::{ use crate::{
core::errors::{self, ApiErrorResponse, CustomResult, RouterResult, ValidateError}, core::errors::{self, ApiErrorResponse, CustomResult, RouterResult},
logger, logger,
types::api::AddressDetails, types::api::AddressDetails,
utils::when, utils::when,
@ -111,20 +111,20 @@ pub(crate) fn merge_json_values(a: &mut serde_json::Value, b: &serde_json::Value
// TODO: change Name // TODO: change Name
pub trait ValidateVar { pub trait ValidateVar {
fn validate(self) -> CustomResult<Self, ValidateError> fn validate(self) -> CustomResult<Self, errors::ValidationError>
where where
Self: std::marker::Sized; Self: std::marker::Sized;
} }
pub trait ValidateCall<T, F> { pub trait ValidateCall<T, F> {
fn validate_opt(self, func: F) -> CustomResult<(), ValidateError>; fn validate_opt(self, func: F) -> CustomResult<(), errors::ValidationError>;
} }
impl<T, F> ValidateCall<T, F> for Option<&T> impl<T, F> ValidateCall<T, F> for Option<&T>
where where
F: Fn(&T) -> CustomResult<(), ValidateError>, F: Fn(&T) -> CustomResult<(), errors::ValidationError>,
{ {
fn validate_opt(self, func: F) -> CustomResult<(), ValidateError> { fn validate_opt(self, func: F) -> CustomResult<(), errors::ValidationError> {
match self { match self {
Some(val) => func(val), Some(val) => func(val),
None => Ok(()), None => Ok(()),
@ -132,7 +132,7 @@ where
} }
} }
pub fn validate_email(email: &str) -> CustomResult<(), ValidateError> { pub fn validate_email(email: &str) -> CustomResult<(), errors::ValidationError> {
#[deny(clippy::invalid_regex)] #[deny(clippy::invalid_regex)]
static EMAIL_REGEX: Lazy<Option<Regex>> = Lazy::new(|| { static EMAIL_REGEX: Lazy<Option<Regex>> = Lazy::new(|| {
match Regex::new( match Regex::new(
@ -147,26 +147,32 @@ pub fn validate_email(email: &str) -> CustomResult<(), ValidateError> {
}); });
let email_regex = match EMAIL_REGEX.as_ref() { let email_regex = match EMAIL_REGEX.as_ref() {
Some(regex) => Ok(regex), Some(regex) => Ok(regex),
None => Err(report!(ValidateError).attach_printable("Invalid regex expression")), None => Err(report!(errors::ValidationError::InvalidValue {
message: "Invalid regex expression".into()
})),
}?; }?;
const EMAIL_MAX_LENGTH: usize = 319; const EMAIL_MAX_LENGTH: usize = 319;
if email.is_empty() || email.chars().count() > EMAIL_MAX_LENGTH { if email.is_empty() || email.chars().count() > EMAIL_MAX_LENGTH {
return Err(report!(ValidateError).attach_printable("Invalid email address length")); return Err(report!(errors::ValidationError::InvalidValue {
message: "Email address is either empty or exceeds maximum allowed length".into()
}));
} }
if !email_regex.is_match(email) { if !email_regex.is_match(email) {
return Err(report!(ValidateError).attach_printable("Invalid email format")); return Err(report!(errors::ValidationError::InvalidValue {
message: "Invalid email address format".into()
}));
} }
Ok(()) Ok(())
} }
pub fn validate_address(address: &serde_json::Value) -> CustomResult<(), ValidateError> { pub fn validate_address(address: &serde_json::Value) -> CustomResult<(), errors::ValidationError> {
if let Err(err) = serde_json::from_value::<AddressDetails>(address.clone()) { if let Err(err) = serde_json::from_value::<AddressDetails>(address.clone()) {
return Err( return Err(report!(errors::ValidationError::InvalidValue {
report!(ValidateError).attach_printable(format!("Address is invalid {:?}", err)) message: format!("Invalid address: {err}")
); }));
} }
Ok(()) Ok(())
} }