mirror of
				https://github.com/juspay/hyperswitch.git
				synced 2025-10-31 18:17:13 +08:00 
			
		
		
		
	feat(connector): [Bluesnap] Add dispute webhooks support (#2053)
This commit is contained in:
		| @ -1,4 +1,4 @@ | |||||||
| use std::num::TryFromIntError; | use std::num::{ParseFloatError, TryFromIntError}; | ||||||
|  |  | ||||||
| use router_derive; | use router_derive; | ||||||
| use serde::{Deserialize, Serialize}; | use serde::{Deserialize, Serialize}; | ||||||
| @ -342,6 +342,19 @@ impl Currency { | |||||||
|         Ok(amount) |         Ok(amount) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     ///Convert the higher decimal amount to its base absolute units | ||||||
|  |     pub fn to_currency_lower_unit(&self, amount: String) -> Result<String, ParseFloatError> { | ||||||
|  |         let amount_f64 = amount.parse::<f64>()?; | ||||||
|  |         let amount_string = if self.is_zero_decimal_currency() { | ||||||
|  |             amount_f64 | ||||||
|  |         } else if self.is_three_decimal_currency() { | ||||||
|  |             amount_f64 * 1000.00 | ||||||
|  |         } else { | ||||||
|  |             amount_f64 * 100.00 | ||||||
|  |         }; | ||||||
|  |         Ok(amount_string.to_string()) | ||||||
|  |     } | ||||||
|  |  | ||||||
|     /// Convert the amount to its base denomination based on Currency and check for zero decimal currency and return String |     /// Convert the amount to its base denomination based on Currency and check for zero decimal currency and return String | ||||||
|     /// Paypal Connector accepts Zero and Two decimal currency but not three decimal and it should be updated as required for 3 decimal currencies. |     /// Paypal Connector accepts Zero and Two decimal currency but not three decimal and it should be updated as required for 3 decimal currencies. | ||||||
|     /// Paypal Ref - https://developer.paypal.com/docs/reports/reference/paypal-supported-currencies/ |     /// Paypal Ref - https://developer.paypal.com/docs/reports/reference/paypal-supported-currencies/ | ||||||
|  | |||||||
| @ -1042,7 +1042,7 @@ impl api::IncomingWebhook for Bluesnap { | |||||||
|         let webhook_body: bluesnap::BluesnapWebhookBody = |         let webhook_body: bluesnap::BluesnapWebhookBody = | ||||||
|             serde_urlencoded::from_bytes(request.body) |             serde_urlencoded::from_bytes(request.body) | ||||||
|                 .into_report() |                 .into_report() | ||||||
|                 .change_context(errors::ConnectorError::WebhookSignatureNotFound)?; |                 .change_context(errors::ConnectorError::WebhookReferenceIdNotFound)?; | ||||||
|         Ok(api_models::webhooks::ObjectReferenceId::PaymentId( |         Ok(api_models::webhooks::ObjectReferenceId::PaymentId( | ||||||
|             api_models::payments::PaymentIdType::PaymentAttemptId( |             api_models::payments::PaymentIdType::PaymentAttemptId( | ||||||
|                 webhook_body.merchant_transaction_id, |                 webhook_body.merchant_transaction_id, | ||||||
| @ -1058,18 +1058,31 @@ impl api::IncomingWebhook for Bluesnap { | |||||||
|             serde_urlencoded::from_bytes(request.body) |             serde_urlencoded::from_bytes(request.body) | ||||||
|                 .into_report() |                 .into_report() | ||||||
|                 .change_context(errors::ConnectorError::WebhookEventTypeNotFound)?; |                 .change_context(errors::ConnectorError::WebhookEventTypeNotFound)?; | ||||||
|  |         api::IncomingWebhookEvent::try_from(details) | ||||||
|  |     } | ||||||
|  |  | ||||||
|         Ok(match details.transaction_type { |     fn get_dispute_details( | ||||||
|             bluesnap::BluesnapWebhookEvents::Decline |         &self, | ||||||
|             | bluesnap::BluesnapWebhookEvents::CcChargeFailed => { |         request: &api::IncomingWebhookRequestDetails<'_>, | ||||||
|                 api::IncomingWebhookEvent::PaymentIntentFailure |     ) -> CustomResult<api::disputes::DisputePayload, errors::ConnectorError> { | ||||||
|             } |         let dispute_details: bluesnap::BluesnapDisputeWebhookBody = | ||||||
|             bluesnap::BluesnapWebhookEvents::Charge => { |             serde_urlencoded::from_bytes(request.body) | ||||||
|                 api::IncomingWebhookEvent::PaymentIntentSuccess |                 .into_report() | ||||||
|             } |                 .change_context(errors::ConnectorError::WebhookBodyDecodingFailed)?; | ||||||
|             bluesnap::BluesnapWebhookEvents::Unknown => { |         Ok(api::disputes::DisputePayload { | ||||||
|                 api::IncomingWebhookEvent::EventNotSupported |             amount: connector_utils::to_currency_lower_unit( | ||||||
|             } |                 dispute_details.invoice_charge_amount.abs().to_string(), | ||||||
|  |                 dispute_details.currency, | ||||||
|  |             )?, | ||||||
|  |             currency: dispute_details.currency.to_string(), | ||||||
|  |             dispute_stage: api_models::enums::DisputeStage::Dispute, | ||||||
|  |             connector_dispute_id: dispute_details.reversal_ref_num, | ||||||
|  |             connector_reason: dispute_details.reversal_reason, | ||||||
|  |             connector_reason_code: None, | ||||||
|  |             challenge_required_by: None, | ||||||
|  |             connector_status: dispute_details.cb_status, | ||||||
|  |             created_at: None, | ||||||
|  |             updated_at: None, | ||||||
|         }) |         }) | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @ -1092,8 +1105,18 @@ impl api::IncomingWebhook for Bluesnap { | |||||||
|                 bluesnap::BluesnapTxnType::Capture, |                 bluesnap::BluesnapTxnType::Capture, | ||||||
|                 bluesnap::BluesnapProcessingStatus::Success, |                 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::<bluesnap::BluesnapWebhookObjectResource>::encode_to_value( | ||||||
|  |                         &details, | ||||||
|  |                     ) | ||||||
|  |                     .change_context(errors::ConnectorError::WebhookResourceObjectNotFound)?; | ||||||
|  |                 return Ok(res_json); | ||||||
|  |             } | ||||||
|             bluesnap::BluesnapWebhookEvents::Unknown => { |             bluesnap::BluesnapWebhookEvents::Unknown => { | ||||||
|                 Err(errors::ConnectorError::WebhookEventTypeNotFound) |                 Err(errors::ConnectorError::WebhookResourceObjectNotFound) | ||||||
|             } |             } | ||||||
|         }?; |         }?; | ||||||
|  |  | ||||||
|  | |||||||
| @ -818,19 +818,76 @@ pub struct BluesnapWebhookBody { | |||||||
| #[serde(rename_all = "camelCase")] | #[serde(rename_all = "camelCase")] | ||||||
| pub struct BluesnapWebhookObjectEventType { | pub struct BluesnapWebhookObjectEventType { | ||||||
|     pub transaction_type: BluesnapWebhookEvents, |     pub transaction_type: BluesnapWebhookEvents, | ||||||
|  |     pub cb_status: Option<BluesnapChargebackStatus>, | ||||||
| } | } | ||||||
|  |  | ||||||
| #[derive(Debug, Deserialize)] | #[derive(Debug, Deserialize)] | ||||||
| #[serde(rename_all = "SCREAMING_SNAKE_CASE")] | #[serde(rename_all = "SCREAMING_SNAKE_CASE")] | ||||||
|  | pub enum BluesnapChargebackStatus { | ||||||
|  |     #[serde(alias = "New")] | ||||||
|  |     New, | ||||||
|  |     #[serde(alias = "Working")] | ||||||
|  |     Working, | ||||||
|  |     #[serde(alias = "Closed")] | ||||||
|  |     Closed, | ||||||
|  |     #[serde(alias = "Completed_Lost")] | ||||||
|  |     CompletedLost, | ||||||
|  |     #[serde(alias = "Completed_Pending")] | ||||||
|  |     CompletedPending, | ||||||
|  |     #[serde(alias = "Completed_Won")] | ||||||
|  |     CompletedWon, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[derive(Debug, Serialize, Deserialize)] | ||||||
|  | #[serde(rename_all = "SCREAMING_SNAKE_CASE")] | ||||||
| pub enum BluesnapWebhookEvents { | pub enum BluesnapWebhookEvents { | ||||||
|     Decline, |     Decline, | ||||||
|     CcChargeFailed, |     CcChargeFailed, | ||||||
|     Charge, |     Charge, | ||||||
|  |     Chargeback, | ||||||
|  |     ChargebackStatusChanged, | ||||||
|     #[serde(other)] |     #[serde(other)] | ||||||
|     Unknown, |     Unknown, | ||||||
| } | } | ||||||
|  |  | ||||||
|  | impl TryFrom<BluesnapWebhookObjectEventType> for api::IncomingWebhookEvent { | ||||||
|  |     type Error = error_stack::Report<errors::ConnectorError>; | ||||||
|  |     fn try_from(details: BluesnapWebhookObjectEventType) -> Result<Self, Self::Error> { | ||||||
|  |         match details.transaction_type { | ||||||
|  |             BluesnapWebhookEvents::Decline | BluesnapWebhookEvents::CcChargeFailed => { | ||||||
|  |                 Ok(Self::PaymentIntentFailure) | ||||||
|  |             } | ||||||
|  |             BluesnapWebhookEvents::Charge => Ok(Self::PaymentIntentSuccess), | ||||||
|  |             BluesnapWebhookEvents::Chargeback | BluesnapWebhookEvents::ChargebackStatusChanged => { | ||||||
|  |                 match details | ||||||
|  |                     .cb_status | ||||||
|  |                     .ok_or(errors::ConnectorError::WebhookEventTypeNotFound)? | ||||||
|  |                 { | ||||||
|  |                     BluesnapChargebackStatus::New | BluesnapChargebackStatus::Working => { | ||||||
|  |                         Ok(Self::DisputeOpened) | ||||||
|  |                     } | ||||||
|  |                     BluesnapChargebackStatus::Closed => Ok(Self::DisputeExpired), | ||||||
|  |                     BluesnapChargebackStatus::CompletedLost => Ok(Self::DisputeLost), | ||||||
|  |                     BluesnapChargebackStatus::CompletedPending => Ok(Self::DisputeChallenged), | ||||||
|  |                     BluesnapChargebackStatus::CompletedWon => Ok(Self::DisputeWon), | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             BluesnapWebhookEvents::Unknown => Ok(Self::EventNotSupported), | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
| #[derive(Debug, Deserialize)] | #[derive(Debug, Deserialize)] | ||||||
| #[serde(rename_all = "camelCase")] | #[serde(rename_all = "camelCase")] | ||||||
|  | pub struct BluesnapDisputeWebhookBody { | ||||||
|  |     pub invoice_charge_amount: f64, | ||||||
|  |     pub currency: diesel_models::enums::Currency, | ||||||
|  |     pub reversal_reason: Option<String>, | ||||||
|  |     pub reversal_ref_num: String, | ||||||
|  |     pub cb_status: String, | ||||||
|  | } | ||||||
|  | #[derive(Debug, Serialize, Deserialize)] | ||||||
|  | #[serde(rename_all = "camelCase")] | ||||||
| pub struct BluesnapWebhookObjectResource { | pub struct BluesnapWebhookObjectResource { | ||||||
|     pub reference_number: String, |     pub reference_number: String, | ||||||
|     pub transaction_type: BluesnapWebhookEvents, |     pub transaction_type: BluesnapWebhookEvents, | ||||||
|  | |||||||
| @ -1000,6 +1000,16 @@ pub fn to_currency_base_unit( | |||||||
|         .change_context(errors::ConnectorError::RequestEncodingFailed) |         .change_context(errors::ConnectorError::RequestEncodingFailed) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | pub fn to_currency_lower_unit( | ||||||
|  |     amount: String, | ||||||
|  |     currency: diesel_models::enums::Currency, | ||||||
|  | ) -> Result<String, error_stack::Report<errors::ConnectorError>> { | ||||||
|  |     currency | ||||||
|  |         .to_currency_lower_unit(amount) | ||||||
|  |         .into_report() | ||||||
|  |         .change_context(errors::ConnectorError::ResponseHandlingFailed) | ||||||
|  | } | ||||||
|  |  | ||||||
| pub fn construct_not_implemented_error_report( | pub fn construct_not_implemented_error_report( | ||||||
|     capture_method: enums::CaptureMethod, |     capture_method: enums::CaptureMethod, | ||||||
|     connector_name: &str, |     connector_name: &str, | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user
	 SamraatBansal
					SamraatBansal