From fe43458ddc0fa1cc31f2b326056baea54af57136 Mon Sep 17 00:00:00 2001 From: SamraatBansal <55536657+SamraatBansal@users.noreply.github.com> Date: Wed, 27 Sep 2023 17:35:44 +0530 Subject: [PATCH] refactor(connector): [bluesnap] add refund status and webhooks (#2374) --- crates/router/src/connector/bluesnap.rs | 109 ++++++++--------- .../src/connector/bluesnap/transformers.rs | 111 ++++++++++++------ 2 files changed, 125 insertions(+), 95 deletions(-) diff --git a/crates/router/src/connector/bluesnap.rs b/crates/router/src/connector/bluesnap.rs index 9819b91246ea..7e7af9352267 100644 --- a/crates/router/src/connector/bluesnap.rs +++ b/crates/router/src/connector/bluesnap.rs @@ -903,21 +903,27 @@ impl ConnectorIntegration CustomResult { - let meta_data: CustomResult = - connector_utils::to_connector_meta_from_secret(req.connector_meta_data.clone()); - - match meta_data { - // if merchant_id is present, rsync can be made using merchant_transaction_id - Ok(data) => get_url_with_merchant_transaction_id( - self.base_url(connectors).to_string(), - data.merchant_id, - req.attempt_id.to_owned(), - ), - // otherwise rsync is made using connector_transaction_id - Err(_) => get_rsync_url_with_connector_transaction_id( - req, - self.base_url(connectors).to_string(), - ), + if req.request.payment_amount == req.request.refund_amount { + let meta_data: CustomResult< + bluesnap::BluesnapConnectorMetaData, + errors::ConnectorError, + > = connector_utils::to_connector_meta_from_secret(req.connector_meta_data.clone()); + + match meta_data { + // if merchant_id is present, rsync can be made using merchant_transaction_id + Ok(data) => get_url_with_merchant_transaction_id( + self.base_url(connectors).to_string(), + data.merchant_id, + req.attempt_id.to_owned(), + ), + // otherwise rsync is made using connector_transaction_id + Err(_) => get_rsync_url_with_connector_refund_id( + req, + self.base_url(connectors).to_string(), + ), + } + } else { + get_rsync_url_with_connector_refund_id(req, self.base_url(connectors).to_string()) } } @@ -1002,11 +1008,31 @@ impl api::IncomingWebhook for Bluesnap { serde_urlencoded::from_bytes(request.body) .into_report() .change_context(errors::ConnectorError::WebhookReferenceIdNotFound)?; - Ok(api_models::webhooks::ObjectReferenceId::PaymentId( - api_models::payments::PaymentIdType::PaymentAttemptId( - webhook_body.merchant_transaction_id, - ), - )) + match webhook_body.transaction_type { + bluesnap::BluesnapWebhookEvents::Decline + | bluesnap::BluesnapWebhookEvents::CcChargeFailed + | bluesnap::BluesnapWebhookEvents::Charge + | bluesnap::BluesnapWebhookEvents::Chargeback + | bluesnap::BluesnapWebhookEvents::ChargebackStatusChanged => { + Ok(api_models::webhooks::ObjectReferenceId::PaymentId( + api_models::payments::PaymentIdType::PaymentAttemptId( + webhook_body.merchant_transaction_id, + ), + )) + } + bluesnap::BluesnapWebhookEvents::Refund => { + Ok(api_models::webhooks::ObjectReferenceId::RefundId( + api_models::webhooks::RefundIdType::ConnectorRefundId( + webhook_body + .reversal_ref_num + .ok_or(errors::ConnectorError::WebhookReferenceIdNotFound)?, + ), + )) + } + bluesnap::BluesnapWebhookEvents::Unknown => { + Err(errors::ConnectorError::WebhookReferenceIdNotFound).into_report() + } + } } fn get_webhook_event_type( @@ -1049,49 +1075,12 @@ impl api::IncomingWebhook for Bluesnap { &self, request: &api::IncomingWebhookRequestDetails<'_>, ) -> CustomResult { - let details: bluesnap::BluesnapWebhookObjectResource = + let resource: bluesnap::BluesnapWebhookObjectResource = serde_urlencoded::from_bytes(request.body) .into_report() .change_context(errors::ConnectorError::WebhookResourceObjectNotFound)?; - let (card_transaction_type, processing_status) = match details.transaction_type { - bluesnap::BluesnapWebhookEvents::Decline - | bluesnap::BluesnapWebhookEvents::CcChargeFailed => Ok(( - bluesnap::BluesnapTxnType::Capture, - bluesnap::BluesnapProcessingStatus::Fail, - )), - bluesnap::BluesnapWebhookEvents::Charge => Ok(( - bluesnap::BluesnapTxnType::Capture, - bluesnap::BluesnapProcessingStatus::Success, - )), - bluesnap::BluesnapWebhookEvents::Chargeback - | bluesnap::BluesnapWebhookEvents::ChargebackStatusChanged => { - // returning the complete incoming webhook body, It won't be consumed in dispute flow, so currently does not hold any significance - let res_json = - utils::Encode::::encode_to_value( - &details, - ) - .change_context(errors::ConnectorError::WebhookResourceObjectNotFound)?; - return Ok(res_json); - } - bluesnap::BluesnapWebhookEvents::Unknown => { - Err(errors::ConnectorError::WebhookResourceObjectNotFound) - } - }?; - - let psync_struct = bluesnap::BluesnapPaymentsResponse { - processing_info: bluesnap::ProcessingInfoResponse { - processing_status, - authorization_code: None, - network_transaction_id: None, - }, - transaction_id: details.reference_number, - card_transaction_type, - }; - - let res_json = - utils::Encode::::encode_to_value(&psync_struct) - .change_context(errors::ConnectorError::WebhookResourceObjectNotFound)?; + let res_json = serde_json::Value::try_from(resource)?; Ok(res_json) } @@ -1283,7 +1272,7 @@ fn get_psync_url_with_connector_transaction_id( )) } -fn get_rsync_url_with_connector_transaction_id( +fn get_rsync_url_with_connector_refund_id( req: &types::RefundSyncRouterData, base_url: String, ) -> CustomResult { diff --git a/crates/router/src/connector/bluesnap/transformers.rs b/crates/router/src/connector/bluesnap/transformers.rs index d2e1efa527aa..fc9238f84e87 100644 --- a/crates/router/src/connector/bluesnap/transformers.rs +++ b/crates/router/src/connector/bluesnap/transformers.rs @@ -60,7 +60,6 @@ pub struct BluesnapPaymentsRequest { payment_method: PaymentMethodDetails, currency: enums::Currency, card_transaction_type: BluesnapTxnType, - three_d_secure: Option, transaction_fraud_info: Option, card_holder_info: Option, merchant_transaction_id: Option, @@ -400,7 +399,6 @@ impl TryFrom<&BluesnapRouterData<&types::PaymentsAuthorizeRouterData>> for Blues payment_method, currency: item.router_data.request.currency, card_transaction_type: auth_mode, - three_d_secure: None, transaction_fraud_info: Some(TransactionFraudInfo { fraud_session_id: item.router_data.payment_id.clone(), }), @@ -700,33 +698,6 @@ impl TryFrom<&types::ConnectorAuthType> for BluesnapAuthType { } } -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct BluesnapCustomerResponse { - vaulted_shopper_id: Secret, -} -impl - TryFrom> - for types::RouterData -{ - type Error = error_stack::Report; - fn try_from( - item: types::ResponseRouterData< - F, - BluesnapCustomerResponse, - T, - types::PaymentsResponseData, - >, - ) -> Result { - Ok(Self { - response: Ok(types::PaymentsResponseData::ConnectorCustomerResponse { - connector_customer_id: item.response.vaulted_shopper_id.expose().to_string(), - }), - ..item.data - }) - } -} - // PaymentsResponse #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] @@ -784,6 +755,15 @@ impl From for enums::RefundStatus { } } +impl From for enums::RefundStatus { + fn from(item: BluesnapRefundStatus) -> Self { + match item { + BluesnapRefundStatus::Success => Self::Success, + BluesnapRefundStatus::Pending => Self::Pending, + } + } +} + #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct BluesnapPaymentsResponse { @@ -865,10 +845,18 @@ impl TryFrom<&BluesnapRouterData<&types::RefundsRouterData>> for BluesnapR } } +#[derive(Debug, Clone, Default, Deserialize)] +#[serde(rename_all = "UPPERCASE")] +pub enum BluesnapRefundStatus { + Success, + #[default] + Pending, +} #[derive(Default, Debug, Clone, Deserialize)] #[serde(rename_all = "camelCase")] pub struct RefundResponse { refund_transaction_id: i32, + refund_status: BluesnapRefundStatus, } impl TryFrom> @@ -900,7 +888,7 @@ impl TryFrom> Ok(Self { response: Ok(types::RefundsResponseData { connector_refund_id: item.response.refund_transaction_id.to_string(), - refund_status: enums::RefundStatus::Pending, + refund_status: enums::RefundStatus::from(item.response.refund_status), }), ..item.data }) @@ -911,13 +899,15 @@ impl TryFrom> pub struct BluesnapWebhookBody { pub merchant_transaction_id: String, pub reference_number: String, + pub transaction_type: BluesnapWebhookEvents, + pub reversal_ref_num: Option, } #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct BluesnapWebhookObjectEventType { - pub transaction_type: BluesnapWebhookEvents, - pub cb_status: Option, + transaction_type: BluesnapWebhookEvents, + cb_status: Option, } #[derive(Debug, Deserialize)] @@ -937,12 +927,13 @@ pub enum BluesnapChargebackStatus { CompletedWon, } -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub enum BluesnapWebhookEvents { Decline, CcChargeFailed, Charge, + Refund, Chargeback, ChargebackStatusChanged, #[serde(other)] @@ -957,6 +948,7 @@ impl TryFrom for api::IncomingWebhookEvent { Ok(Self::PaymentIntentFailure) } BluesnapWebhookEvents::Charge => Ok(Self::PaymentIntentSuccess), + BluesnapWebhookEvents::Refund => Ok(Self::RefundSuccess), BluesnapWebhookEvents::Chargeback | BluesnapWebhookEvents::ChargebackStatusChanged => { match details .cb_status @@ -988,8 +980,57 @@ pub struct BluesnapDisputeWebhookBody { #[derive(Debug, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct BluesnapWebhookObjectResource { - pub reference_number: String, - pub transaction_type: BluesnapWebhookEvents, + reference_number: String, + transaction_type: BluesnapWebhookEvents, + reversal_ref_num: Option, +} + +impl TryFrom for serde_json::Value { + type Error = error_stack::Report; + fn try_from(details: BluesnapWebhookObjectResource) -> Result { + let (card_transaction_type, processing_status, transaction_id) = match details + .transaction_type + { + BluesnapWebhookEvents::Decline | BluesnapWebhookEvents::CcChargeFailed => Ok(( + BluesnapTxnType::Capture, + BluesnapProcessingStatus::Fail, + details.reference_number, + )), + BluesnapWebhookEvents::Charge => Ok(( + BluesnapTxnType::Capture, + BluesnapProcessingStatus::Success, + details.reference_number, + )), + BluesnapWebhookEvents::Chargeback | BluesnapWebhookEvents::ChargebackStatusChanged => { + //It won't be consumed in dispute flow, so currently does not hold any significance + return serde_json::to_value(details) + .into_report() + .change_context(errors::ConnectorError::WebhookBodyDecodingFailed); + } + BluesnapWebhookEvents::Refund => Ok(( + BluesnapTxnType::Refund, + BluesnapProcessingStatus::Success, + details + .reversal_ref_num + .ok_or(errors::ConnectorError::WebhookResourceObjectNotFound)?, + )), + BluesnapWebhookEvents::Unknown => { + Err(errors::ConnectorError::WebhookResourceObjectNotFound).into_report() + } + }?; + let sync_struct = BluesnapPaymentsResponse { + processing_info: ProcessingInfoResponse { + processing_status, + authorization_code: None, + network_transaction_id: None, + }, + transaction_id, + card_transaction_type, + }; + serde_json::to_value(sync_struct) + .into_report() + .change_context(errors::ConnectorError::WebhookBodyDecodingFailed) + } } #[derive(Default, Debug, Clone, Serialize, Deserialize)]