From 8a638e4a089c772cd53742fa48f22f4bf8585c79 Mon Sep 17 00:00:00 2001 From: Sai Harsha Vardhan <56996463+sai-harsha-vardhan@users.noreply.github.com> Date: Tue, 1 Aug 2023 12:39:25 +0530 Subject: [PATCH] feat(connector): [Trustpay] unify error_code, error_message and error_reason in error response (#1817) --- crates/router/src/connector/trustpay.rs | 109 ++++++++++++++---- .../src/connector/trustpay/transformers.rs | 62 +++++----- 2 files changed, 115 insertions(+), 56 deletions(-) diff --git a/crates/router/src/connector/trustpay.rs b/crates/router/src/connector/trustpay.rs index 267ed06ab9..a339bc449e 100644 --- a/crates/router/src/connector/trustpay.rs +++ b/crates/router/src/connector/trustpay.rs @@ -8,7 +8,10 @@ use error_stack::{IntoReport, ResultExt}; use masking::PeekInterface; use transformers as trustpay; -use super::utils::collect_and_sort_values_by_removing_signature; +use super::utils::{ + collect_and_sort_values_by_removing_signature, get_error_code_error_message_based_on_priority, + ConnectorErrorType, ConnectorErrorTypeMapping, +}; use crate::{ configs::settings, consts, @@ -113,23 +116,29 @@ impl ConnectorCommon for Trustpay { .response .parse_struct("trustpay ErrorResponse") .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; - let default_error = trustpay::Errors { - code: 0, - description: consts::NO_ERROR_CODE.to_string(), - }; + let error_list = response.errors.clone().unwrap_or(vec![]); + let option_error_code_message = get_error_code_error_message_based_on_priority( + self.clone(), + error_list.into_iter().map(|errors| errors.into()).collect(), + ); + let reason = response.errors.map(|errors| { + errors + .iter() + .map(|error| error.description.clone()) + .collect::>() + .join(" & ") + }); Ok(ErrorResponse { status_code: res.status_code, - code: response.status.to_string(), - message: format!( - "{:?}", - response - .errors - .as_ref() - .unwrap_or(&vec![]) - .first() - .unwrap_or(&default_error) - ), - reason: response.errors.map(|errors| format!("{:?}", errors)), + code: option_error_code_message + .clone() + .map(|error_code_message| error_code_message.error_code) + .unwrap_or(consts::NO_ERROR_CODE.to_string()), + // message vary for the same code, so relying on code alone as it is unique + message: option_error_code_message + .map(|error_code_message| error_code_message.error_code) + .unwrap_or(consts::NO_ERROR_MESSAGE.to_string()), + reason: reason.or(response.description), }) } } @@ -266,11 +275,8 @@ impl ConnectorIntegration ConnectorErrorType { + match (error_code.as_str(), error_message.as_str()) { + // 2xx card api error codes and messages mapping + ("100.100.600", "Empty CVV for VISA, MASTER not allowed") => ConnectorErrorType::UserError, + ("100.350.100", "Referenced session is rejected (no action possible)") => ConnectorErrorType::TechnicalError, + ("100.380.401", "User authentication failed") => ConnectorErrorType::UserError, + ("100.380.501", "Risk management transaction timeout") => ConnectorErrorType::TechnicalError, + ("100.390.103", "PARes validation failed - problem with signature") => ConnectorErrorType::TechnicalError, + ("100.390.111", "Communication error to VISA/Mastercard Directory Server") => ConnectorErrorType::TechnicalError, + ("100.390.112", "Technical error in 3D system") => ConnectorErrorType::TechnicalError, + ("100.390.115", "Authentication failed due to invalid message format") => ConnectorErrorType::TechnicalError, + ("100.390.118", "Authentication failed due to suspected fraud") => ConnectorErrorType::UserError, + ("100.400.304", "Invalid input data") => ConnectorErrorType::UserError, + ("200.300.404", "Invalid or missing parameter") => ConnectorErrorType::UserError, + ("300.100.100", "Transaction declined (additional customer authentication required)") => ConnectorErrorType::UserError, + ("400.001.301", "Card not enrolled in 3DS") => ConnectorErrorType::UserError, + ("400.001.600", "Authentication error") => ConnectorErrorType::UserError, + ("400.001.601", "Transaction declined (auth. declined)") => ConnectorErrorType::UserError, + ("400.001.602", "Invalid transaction") => ConnectorErrorType::UserError, + ("400.001.603", "Invalid transaction") => ConnectorErrorType::UserError, + ("700.400.200", "Cannot refund (refund volume exceeded or tx reversed or invalid workflow)") => ConnectorErrorType::BusinessError, + ("700.500.001", "Referenced session contains too many transactions") => ConnectorErrorType::TechnicalError, + ("700.500.003", "Test accounts not allowed in production") => ConnectorErrorType::UserError, + ("800.100.151", "Transaction declined (invalid card)") => ConnectorErrorType::UserError, + ("800.100.152", "Transaction declined by authorization system") => ConnectorErrorType::UserError, + ("800.100.153", "Transaction declined (invalid CVV)") => ConnectorErrorType::UserError, + ("800.100.155", "Transaction declined (amount exceeds credit)") => ConnectorErrorType::UserError, + ("800.100.157", "Transaction declined (wrong expiry date)") => ConnectorErrorType::UserError, + ("800.100.162", "Transaction declined (limit exceeded)") => ConnectorErrorType::BusinessError, + ("800.100.163", "Transaction declined (maximum transaction frequency exceeded)") => ConnectorErrorType::BusinessError, + ("800.100.168", "Transaction declined (restricted card)") => ConnectorErrorType::UserError, + ("800.100.170", "Transaction declined (transaction not permitted)") => ConnectorErrorType::UserError, + ("800.100.172", "Transaction declined (account blocked)") => ConnectorErrorType::BusinessError, + ("800.100.190", "Transaction declined (invalid configuration data)") => ConnectorErrorType::BusinessError, + ("800.120.100", "Rejected by throttling") => ConnectorErrorType::TechnicalError, + ("800.300.401", "Bin blacklisted") => ConnectorErrorType::BusinessError, + ("800.700.100", "Transaction for the same session is currently being processed, please try again later") => ConnectorErrorType::TechnicalError, + ("900.100.300", "Timeout, uncertain result") => ConnectorErrorType::TechnicalError, + // 4xx error codes for cards api are unique and messages vary, so we are relying only on error code to decide an error type + ("4" | "5" | "6" | "7" | "8" | "9" | "10" | "11" | "12" | "13" | "14" | "15" | "16" | "17" | "18" | "19" | "26" | "34" | "39" | "48" | "52" | "85" | "86", _) => ConnectorErrorType::UserError, + ("21" | "22" | "23" | "30" | "31" | "32" | "35" | "37" | "40" | "41" | "45" | "46" | "49" | "50" | "56" | "60" | "67" | "81" | "82" | "83" | "84" | "87", _) => ConnectorErrorType::BusinessError, + ("59", _) => ConnectorErrorType::TechnicalError, + ("1", _) => ConnectorErrorType::UnknownError, + // Error codes for bank redirects api are unique and messages vary, so we are relying only on error code to decide an error type + ("1112008" | "1132000" | "1152000", _) => ConnectorErrorType::UserError, + ("1112009" | "1122006" | "1132001" | "1132002" | "1132003" | "1132004" | "1132005" | "1132006" | "1132008" | "1132009" | "1132010" | "1132011" | "1132012" | "1132013" | "1133000" | "1133001" | "1133002" | "1133003" | "1133004", _) => ConnectorErrorType::BusinessError, + ("1132014", _) => ConnectorErrorType::TechnicalError, + ("1132007", _) => ConnectorErrorType::UnknownError, + _ => ConnectorErrorType::UnknownError, + } + } +} diff --git a/crates/router/src/connector/trustpay/transformers.rs b/crates/router/src/connector/trustpay/transformers.rs index 0ef4030619..a4cc41b30a 100644 --- a/crates/router/src/connector/trustpay/transformers.rs +++ b/crates/router/src/connector/trustpay/transformers.rs @@ -344,10 +344,10 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for TrustpayPaymentsRequest { fn is_payment_failed(payment_status: &str) -> (bool, &'static str) { match payment_status { "100.100.600" => (true, "Empty CVV for VISA, MASTER not allowed"), - "100.350.100" => (true, "Referenced session is rejected (no action possible)."), - "100.380.401" => (true, "User authentication failed."), - "100.380.501" => (true, "Risk management transaction timeout."), - "100.390.103" => (true, "PARes validation failed - problem with signature."), + "100.350.100" => (true, "Referenced session is rejected (no action possible)"), + "100.380.401" => (true, "User authentication failed"), + "100.380.501" => (true, "Risk management transaction timeout"), + "100.390.103" => (true, "PARes validation failed - problem with signature"), "100.390.111" => ( true, "Communication error to VISA/Mastercard Directory Server", @@ -651,11 +651,8 @@ fn handle_bank_redirects_error_response( let status = enums::AttemptStatus::AuthorizationFailed; let error = Some(types::ErrorResponse { code: response.payment_result_info.result_code.to_string(), - message: response - .payment_result_info - .additional_info - .clone() - .unwrap_or_else(|| consts::NO_ERROR_MESSAGE.to_string()), + // message vary for the same code, so relying on code alone as it is unique + message: response.payment_result_info.result_code.to_string(), reason: response.payment_result_info.additional_info, status_code, }); @@ -688,12 +685,9 @@ fn handle_bank_redirects_sync_response( .status_reason_information .unwrap_or_default(); Some(types::ErrorResponse { - code: reason_info.reason.code, - message: reason_info - .reason - .reject_reason - .clone() - .unwrap_or_else(|| consts::NO_ERROR_MESSAGE.to_string()), + code: reason_info.reason.code.clone(), + // message vary for the same code, so relying on code alone as it is unique + message: reason_info.reason.code, reason: reason_info.reason.reject_reason, status_code, }) @@ -818,12 +812,8 @@ impl TryFrom Ok(Self { response: Err(types::ErrorResponse { code: item.response.result_info.result_code.to_string(), - message: item - .response - .result_info - .additional_info - .clone() - .unwrap_or_else(|| consts::NO_ERROR_MESSAGE.to_string()), + // message vary for the same code, so relying on code alone as it is unique + message: item.response.result_info.result_code.to_string(), reason: item.response.result_info.additional_info, status_code: item.http_code, }), @@ -1331,7 +1321,8 @@ fn handle_bank_redirects_refund_response( let error = if msg.is_some() { Some(types::ErrorResponse { code: response.result_info.result_code.to_string(), - message: msg.unwrap_or(consts::NO_ERROR_MESSAGE).to_owned(), + // message vary for the same code, so relying on code alone as it is unique + message: response.result_info.result_code.to_string(), reason: msg.map(|message| message.to_string()), status_code, }) @@ -1356,12 +1347,9 @@ fn handle_bank_redirects_refund_sync_response( .status_reason_information .unwrap_or_default(); Some(types::ErrorResponse { - code: reason_info.reason.code, - message: reason_info - .reason - .reject_reason - .clone() - .unwrap_or_else(|| consts::NO_ERROR_MESSAGE.to_string()), + code: reason_info.reason.code.clone(), + // message vary for the same code, so relying on code alone as it is unique + message: reason_info.reason.code, reason: reason_info.reason.reject_reason, status_code, }) @@ -1381,11 +1369,8 @@ fn handle_bank_redirects_refund_sync_error_response( ) -> (Option, types::RefundsResponseData) { let error = Some(types::ErrorResponse { code: response.payment_result_info.result_code.to_string(), - message: response - .payment_result_info - .additional_info - .clone() - .unwrap_or_else(|| consts::NO_ERROR_MESSAGE.to_owned()), + // message vary for the same code, so relying on code alone as it is unique + message: response.payment_result_info.result_code.to_string(), reason: response.payment_result_info.additional_info, status_code, }); @@ -1497,7 +1482,7 @@ pub struct TrustpayRedirectResponse { pub status: Option, } -#[derive(Default, Debug, Serialize, Deserialize, PartialEq)] +#[derive(Default, Debug, Serialize, Deserialize, PartialEq, Clone)] pub struct Errors { pub code: i64, pub description: String, @@ -1587,3 +1572,12 @@ pub struct TrustpayWebhookResponse { pub payment_information: WebhookPaymentInformation, pub signature: String, } + +impl From for utils::ErrorCodeAndMessage { + fn from(error: Errors) -> Self { + Self { + error_code: error.code.to_string(), + error_message: error.description, + } + } +}