fix(connector): Trustpay zen error mapping (#3255)

Co-authored-by: Prasunna Soppa <prasunna.soppa@juspay.in>
Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
This commit is contained in:
Sahkal Poddar
2024-01-18 14:24:10 +05:30
committed by GitHub
parent 2f693ad1fd
commit e816ccfbdd
2 changed files with 140 additions and 35 deletions

View File

@ -813,7 +813,7 @@ fn handle_bank_redirects_sync_response(
errors::ConnectorError, errors::ConnectorError,
> { > {
let status = enums::AttemptStatus::from(response.payment_information.status); 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 let reason_info = response
.payment_information .payment_information
.status_reason_information .status_reason_information
@ -856,6 +856,7 @@ fn handle_bank_redirects_sync_response(
pub fn handle_webhook_response( pub fn handle_webhook_response(
payment_information: WebhookPaymentInformation, payment_information: WebhookPaymentInformation,
status_code: u16,
) -> CustomResult< ) -> CustomResult<
( (
enums::AttemptStatus, enums::AttemptStatus,
@ -865,6 +866,22 @@ pub fn handle_webhook_response(
errors::ConnectorError, errors::ConnectorError,
> { > {
let status = enums::AttemptStatus::try_from(payment_information.status)?; 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 { let payment_response_data = types::PaymentsResponseData::TransactionResponse {
resource_id: types::ResponseId::NoResponseId, resource_id: types::ResponseId::NoResponseId,
redirection_data: None, redirection_data: None,
@ -874,7 +891,7 @@ pub fn handle_webhook_response(
connector_response_reference_id: None, connector_response_reference_id: None,
incremental_authorization_allowed: None, incremental_authorization_allowed: None,
}; };
Ok((status, None, payment_response_data)) Ok((status, error, payment_response_data))
} }
pub fn get_trustpay_response( pub fn get_trustpay_response(
@ -901,7 +918,9 @@ pub fn get_trustpay_response(
TrustpayPaymentsResponse::BankRedirectError(response) => { TrustpayPaymentsResponse::BankRedirectError(response) => {
handle_bank_redirects_error_response(*response, status_code) 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( fn handle_webhooks_refund_response(
response: WebhookPaymentInformation, response: WebhookPaymentInformation,
status_code: u16,
) -> CustomResult<(Option<types::ErrorResponse>, types::RefundsResponseData), errors::ConnectorError> ) -> CustomResult<(Option<types::ErrorResponse>, types::RefundsResponseData), errors::ConnectorError>
{ {
let refund_status = diesel_models::enums::RefundStatus::try_from(response.status)?; 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 { let refund_response_data = types::RefundsResponseData {
connector_refund_id: response connector_refund_id: response
.references .references
@ -1462,7 +1496,7 @@ fn handle_webhooks_refund_response(
.ok_or(errors::ConnectorError::MissingConnectorRefundID)?, .ok_or(errors::ConnectorError::MissingConnectorRefundID)?,
refund_status, refund_status,
}; };
Ok((None, refund_response_data)) Ok((error, refund_response_data))
} }
fn handle_bank_redirects_refund_response( fn handle_bank_redirects_refund_response(
@ -1495,7 +1529,7 @@ fn handle_bank_redirects_refund_sync_response(
status_code: u16, status_code: u16,
) -> (Option<types::ErrorResponse>, types::RefundsResponseData) { ) -> (Option<types::ErrorResponse>, types::RefundsResponseData) {
let refund_status = enums::RefundStatus::from(response.payment_information.status); 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 let reason_info = response
.payment_information .payment_information
.status_reason_information .status_reason_information
@ -1551,7 +1585,9 @@ impl<F> TryFrom<types::RefundsResponseRouterData<F, RefundResponse>>
RefundResponse::CardsRefund(response) => { RefundResponse::CardsRefund(response) => {
handle_cards_refund_response(*response, item.http_code)? 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) => { RefundResponse::BankRedirectRefund(response) => {
handle_bank_redirects_refund_response(*response, item.http_code) handle_bank_redirects_refund_response(*response, item.http_code)
} }

View File

@ -11,6 +11,7 @@ use crate::{
connector::utils::{ connector::utils::{
self, BrowserInformationData, CardData, PaymentsAuthorizeRequestData, RouterData, self, BrowserInformationData, CardData, PaymentsAuthorizeRequestData, RouterData,
}, },
consts,
core::errors::{self, CustomResult}, core::errors::{self, CustomResult},
services::{self, Method}, services::{self, Method},
types::{self, api, storage::enums, transformers::ForeignTryFrom}, types::{self, api, storage::enums, transformers::ForeignTryFrom},
@ -848,12 +849,15 @@ impl ForeignTryFrom<(ZenPaymentStatus, Option<ZenActions>)> for enums::AttemptSt
} }
} }
#[derive(Debug, Deserialize)] #[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct ApiResponse { pub struct ApiResponse {
status: ZenPaymentStatus, status: ZenPaymentStatus,
id: String, id: String,
// merchant_transaction_id: Option<String>,
merchant_action: Option<ZenMerchantAction>, merchant_action: Option<ZenMerchantAction>,
reject_code: Option<String>,
reject_reason: Option<String>,
} }
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
@ -869,18 +873,18 @@ pub struct CheckoutResponse {
redirect_url: url::Url, redirect_url: url::Url,
} }
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct ZenMerchantAction { pub struct ZenMerchantAction {
action: ZenActions, action: ZenActions,
data: ZenMerchantActionData, data: ZenMerchantActionData,
} }
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "UPPERCASE")] #[serde(rename_all = "UPPERCASE")]
pub enum ZenActions { pub enum ZenActions {
Redirect, Redirect,
} }
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct ZenMerchantActionData { pub struct ZenMerchantActionData {
redirect_url: url::Url, redirect_url: url::Url,
@ -913,6 +917,57 @@ impl<F, T>
} }
} }
fn get_zen_response(
response: ApiResponse,
status_code: u16,
) -> CustomResult<
(
enums::AttemptStatus,
Option<types::ErrorResponse>,
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<F, T> TryFrom<types::ResponseRouterData<F, ApiResponse, T, types::PaymentsResponseData>> impl<F, T> TryFrom<types::ResponseRouterData<F, ApiResponse, T, types::PaymentsResponseData>>
for types::RouterData<F, T, types::PaymentsResponseData> for types::RouterData<F, T, types::PaymentsResponseData>
{ {
@ -920,28 +975,12 @@ impl<F, T> TryFrom<types::ResponseRouterData<F, ApiResponse, T, types::PaymentsR
fn try_from( fn try_from(
value: types::ResponseRouterData<F, ApiResponse, T, types::PaymentsResponseData>, value: types::ResponseRouterData<F, ApiResponse, T, types::PaymentsResponseData>,
) -> Result<Self, Self::Error> { ) -> Result<Self, Self::Error> {
let redirection_data_action = value.response.merchant_action.map(|merchant_action| { let (status, error, payment_response_data) =
( get_zen_response(value.response.clone(), value.http_code)?;
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),
};
Ok(Self { Ok(Self {
status: enums::AttemptStatus::foreign_try_from((value.response.status, action))?, status,
response: Ok(types::PaymentsResponseData::TransactionResponse { response: error.map_or_else(|| Ok(payment_response_data), Err),
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,
}),
..value.data ..value.data
}) })
} }
@ -1016,9 +1055,12 @@ impl From<RefundStatus> for enums::RefundStatus {
} }
#[derive(Default, Debug, Deserialize)] #[derive(Default, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RefundResponse { pub struct RefundResponse {
id: String, id: String,
status: RefundStatus, status: RefundStatus,
reject_code: Option<String>,
reject_reason: Option<String>,
} }
impl TryFrom<types::RefundsResponseRouterData<api::Execute, RefundResponse>> impl TryFrom<types::RefundsResponseRouterData<api::Execute, RefundResponse>>
@ -1028,17 +1070,44 @@ impl TryFrom<types::RefundsResponseRouterData<api::Execute, RefundResponse>>
fn try_from( fn try_from(
item: types::RefundsResponseRouterData<api::Execute, RefundResponse>, item: types::RefundsResponseRouterData<api::Execute, RefundResponse>,
) -> Result<Self, Self::Error> { ) -> Result<Self, Self::Error> {
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 { Ok(Self {
response: Ok(types::RefundsResponseData { response: error.map_or_else(|| Ok(refund_response_data), Err),
connector_refund_id: item.response.id,
refund_status,
}),
..item.data ..item.data
}) })
} }
} }
fn get_zen_refund_response(
response: RefundResponse,
status_code: u16,
) -> CustomResult<(Option<types::ErrorResponse>, 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<types::RefundsResponseRouterData<api::RSync, RefundResponse>> impl TryFrom<types::RefundsResponseRouterData<api::RSync, RefundResponse>>
for types::RefundsRouterData<api::RSync> for types::RefundsRouterData<api::RSync>
{ {