From e816ccfbdd7b0e24464aa93421e399d63f23b17c Mon Sep 17 00:00:00 2001 From: Sahkal Poddar Date: Thu, 18 Jan 2024 14:24:10 +0530 Subject: [PATCH] fix(connector): Trustpay zen error mapping (#3255) Co-authored-by: Prasunna Soppa Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com> --- .../src/connector/trustpay/transformers.rs | 48 ++++++- .../router/src/connector/zen/transformers.rs | 127 ++++++++++++++---- 2 files changed, 140 insertions(+), 35 deletions(-) diff --git a/crates/router/src/connector/trustpay/transformers.rs b/crates/router/src/connector/trustpay/transformers.rs index 87d98c1b1be..4d8e47ab0dc 100644 --- a/crates/router/src/connector/trustpay/transformers.rs +++ b/crates/router/src/connector/trustpay/transformers.rs @@ -813,7 +813,7 @@ fn handle_bank_redirects_sync_response( errors::ConnectorError, > { let status = enums::AttemptStatus::from(response.payment_information.status); - let error = if status == enums::AttemptStatus::AuthorizationFailed { + let error = if utils::is_payment_failure(status) { let reason_info = response .payment_information .status_reason_information @@ -856,6 +856,7 @@ fn handle_bank_redirects_sync_response( pub fn handle_webhook_response( payment_information: WebhookPaymentInformation, + status_code: u16, ) -> CustomResult< ( enums::AttemptStatus, @@ -865,6 +866,22 @@ pub fn handle_webhook_response( errors::ConnectorError, > { let status = enums::AttemptStatus::try_from(payment_information.status)?; + let error = if utils::is_payment_failure(status) { + let reason_info = payment_information + .status_reason_information + .unwrap_or_default(); + Some(types::ErrorResponse { + 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, + attempt_status: None, + connector_transaction_id: payment_information.references.payment_request_id.clone(), + }) + } else { + None + }; let payment_response_data = types::PaymentsResponseData::TransactionResponse { resource_id: types::ResponseId::NoResponseId, redirection_data: None, @@ -874,7 +891,7 @@ pub fn handle_webhook_response( connector_response_reference_id: None, incremental_authorization_allowed: None, }; - Ok((status, None, payment_response_data)) + Ok((status, error, payment_response_data)) } pub fn get_trustpay_response( @@ -901,7 +918,9 @@ pub fn get_trustpay_response( TrustpayPaymentsResponse::BankRedirectError(response) => { handle_bank_redirects_error_response(*response, status_code) } - TrustpayPaymentsResponse::WebhookResponse(response) => handle_webhook_response(*response), + TrustpayPaymentsResponse::WebhookResponse(response) => { + handle_webhook_response(*response, status_code) + } } } @@ -1452,9 +1471,24 @@ fn handle_cards_refund_response( fn handle_webhooks_refund_response( response: WebhookPaymentInformation, + status_code: u16, ) -> CustomResult<(Option, types::RefundsResponseData), errors::ConnectorError> { let refund_status = diesel_models::enums::RefundStatus::try_from(response.status)?; + let error = if utils::is_refund_failure(refund_status) { + let reason_info = response.status_reason_information.unwrap_or_default(); + Some(types::ErrorResponse { + 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, + attempt_status: None, + connector_transaction_id: response.references.payment_request_id.clone(), + }) + } else { + None + }; let refund_response_data = types::RefundsResponseData { connector_refund_id: response .references @@ -1462,7 +1496,7 @@ fn handle_webhooks_refund_response( .ok_or(errors::ConnectorError::MissingConnectorRefundID)?, refund_status, }; - Ok((None, refund_response_data)) + Ok((error, refund_response_data)) } fn handle_bank_redirects_refund_response( @@ -1495,7 +1529,7 @@ fn handle_bank_redirects_refund_sync_response( status_code: u16, ) -> (Option, types::RefundsResponseData) { let refund_status = enums::RefundStatus::from(response.payment_information.status); - let error = if refund_status == enums::RefundStatus::Failure { + let error = if utils::is_refund_failure(refund_status) { let reason_info = response .payment_information .status_reason_information @@ -1551,7 +1585,9 @@ impl TryFrom> RefundResponse::CardsRefund(response) => { handle_cards_refund_response(*response, item.http_code)? } - RefundResponse::WebhookRefund(response) => handle_webhooks_refund_response(*response)?, + RefundResponse::WebhookRefund(response) => { + handle_webhooks_refund_response(*response, item.http_code)? + } RefundResponse::BankRedirectRefund(response) => { handle_bank_redirects_refund_response(*response, item.http_code) } diff --git a/crates/router/src/connector/zen/transformers.rs b/crates/router/src/connector/zen/transformers.rs index 7ea6953a3f2..0adae0d00bd 100644 --- a/crates/router/src/connector/zen/transformers.rs +++ b/crates/router/src/connector/zen/transformers.rs @@ -11,6 +11,7 @@ use crate::{ connector::utils::{ self, BrowserInformationData, CardData, PaymentsAuthorizeRequestData, RouterData, }, + consts, core::errors::{self, CustomResult}, services::{self, Method}, types::{self, api, storage::enums, transformers::ForeignTryFrom}, @@ -848,12 +849,15 @@ impl ForeignTryFrom<(ZenPaymentStatus, Option)> for enums::AttemptSt } } -#[derive(Debug, Deserialize)] +#[derive(Debug, Clone, Deserialize)] #[serde(rename_all = "camelCase")] pub struct ApiResponse { status: ZenPaymentStatus, id: String, + // merchant_transaction_id: Option, merchant_action: Option, + reject_code: Option, + reject_reason: Option, } #[derive(Debug, Deserialize)] @@ -869,18 +873,18 @@ pub struct CheckoutResponse { redirect_url: url::Url, } -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct ZenMerchantAction { action: ZenActions, data: ZenMerchantActionData, } -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "UPPERCASE")] pub enum ZenActions { Redirect, } -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct ZenMerchantActionData { redirect_url: url::Url, @@ -913,6 +917,57 @@ impl } } +fn get_zen_response( + response: ApiResponse, + status_code: u16, +) -> CustomResult< + ( + enums::AttemptStatus, + Option, + types::PaymentsResponseData, + ), + errors::ConnectorError, +> { + let redirection_data_action = response.merchant_action.map(|merchant_action| { + ( + services::RedirectForm::from((merchant_action.data.redirect_url, Method::Get)), + merchant_action.action, + ) + }); + let (redirection_data, action) = match redirection_data_action { + Some((redirect_form, action)) => (Some(redirect_form), Some(action)), + None => (None, None), + }; + let status = enums::AttemptStatus::foreign_try_from((response.status, action))?; + let error = if utils::is_payment_failure(status) { + Some(types::ErrorResponse { + code: response + .reject_code + .unwrap_or_else(|| consts::NO_ERROR_CODE.to_string()), + message: response + .reject_reason + .clone() + .unwrap_or_else(|| consts::NO_ERROR_MESSAGE.to_string()), + reason: response.reject_reason, + status_code, + attempt_status: Some(status), + connector_transaction_id: Some(response.id.clone()), + }) + } else { + None + }; + let payment_response_data = types::PaymentsResponseData::TransactionResponse { + resource_id: types::ResponseId::ConnectorTransactionId(response.id.clone()), + redirection_data, + mandate_reference: None, + connector_metadata: None, + network_txn_id: None, + connector_response_reference_id: None, + incremental_authorization_allowed: None, + }; + Ok((status, error, payment_response_data)) +} + impl TryFrom> for types::RouterData { @@ -920,28 +975,12 @@ impl TryFrom, ) -> Result { - let redirection_data_action = value.response.merchant_action.map(|merchant_action| { - ( - services::RedirectForm::from((merchant_action.data.redirect_url, Method::Get)), - merchant_action.action, - ) - }); - let (redirection_data, action) = match redirection_data_action { - Some((redirect_form, action)) => (Some(redirect_form), Some(action)), - None => (None, None), - }; + let (status, error, payment_response_data) = + get_zen_response(value.response.clone(), value.http_code)?; Ok(Self { - status: enums::AttemptStatus::foreign_try_from((value.response.status, action))?, - response: Ok(types::PaymentsResponseData::TransactionResponse { - resource_id: types::ResponseId::ConnectorTransactionId(value.response.id), - redirection_data, - mandate_reference: None, - connector_metadata: None, - network_txn_id: None, - connector_response_reference_id: None, - incremental_authorization_allowed: None, - }), + status, + response: error.map_or_else(|| Ok(payment_response_data), Err), ..value.data }) } @@ -1016,9 +1055,12 @@ impl From for enums::RefundStatus { } #[derive(Default, Debug, Deserialize)] +#[serde(rename_all = "camelCase")] pub struct RefundResponse { id: String, status: RefundStatus, + reject_code: Option, + reject_reason: Option, } impl TryFrom> @@ -1028,17 +1070,44 @@ impl TryFrom> fn try_from( item: types::RefundsResponseRouterData, ) -> Result { - let refund_status = enums::RefundStatus::from(item.response.status); + let (error, refund_response_data) = get_zen_refund_response(item.response, item.http_code)?; Ok(Self { - response: Ok(types::RefundsResponseData { - connector_refund_id: item.response.id, - refund_status, - }), + response: error.map_or_else(|| Ok(refund_response_data), Err), ..item.data }) } } +fn get_zen_refund_response( + response: RefundResponse, + status_code: u16, +) -> CustomResult<(Option, types::RefundsResponseData), errors::ConnectorError> +{ + let refund_status = enums::RefundStatus::from(response.status); + let error = if utils::is_refund_failure(refund_status) { + Some(types::ErrorResponse { + code: response + .reject_code + .unwrap_or_else(|| consts::NO_ERROR_CODE.to_string()), + message: response + .reject_reason + .clone() + .unwrap_or_else(|| consts::NO_ERROR_MESSAGE.to_string()), + reason: response.reject_reason, + status_code, + attempt_status: None, + connector_transaction_id: Some(response.id.clone()), + }) + } else { + None + }; + let refund_response_data = types::RefundsResponseData { + connector_refund_id: response.id, + refund_status, + }; + Ok((error, refund_response_data)) +} + impl TryFrom> for types::RefundsRouterData {