mirror of
				https://github.com/juspay/hyperswitch.git
				synced 2025-10-31 01:57:45 +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 serde::{Deserialize, Serialize}; | ||||
| @ -342,6 +342,19 @@ impl Currency { | ||||
|         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 | ||||
|     /// 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/ | ||||
|  | ||||
| @ -1042,7 +1042,7 @@ impl api::IncomingWebhook for Bluesnap { | ||||
|         let webhook_body: bluesnap::BluesnapWebhookBody = | ||||
|             serde_urlencoded::from_bytes(request.body) | ||||
|                 .into_report() | ||||
|                 .change_context(errors::ConnectorError::WebhookSignatureNotFound)?; | ||||
|                 .change_context(errors::ConnectorError::WebhookReferenceIdNotFound)?; | ||||
|         Ok(api_models::webhooks::ObjectReferenceId::PaymentId( | ||||
|             api_models::payments::PaymentIdType::PaymentAttemptId( | ||||
|                 webhook_body.merchant_transaction_id, | ||||
| @ -1058,18 +1058,31 @@ impl api::IncomingWebhook for Bluesnap { | ||||
|             serde_urlencoded::from_bytes(request.body) | ||||
|                 .into_report() | ||||
|                 .change_context(errors::ConnectorError::WebhookEventTypeNotFound)?; | ||||
|         api::IncomingWebhookEvent::try_from(details) | ||||
|     } | ||||
|  | ||||
|         Ok(match details.transaction_type { | ||||
|             bluesnap::BluesnapWebhookEvents::Decline | ||||
|             | bluesnap::BluesnapWebhookEvents::CcChargeFailed => { | ||||
|                 api::IncomingWebhookEvent::PaymentIntentFailure | ||||
|             } | ||||
|             bluesnap::BluesnapWebhookEvents::Charge => { | ||||
|                 api::IncomingWebhookEvent::PaymentIntentSuccess | ||||
|             } | ||||
|             bluesnap::BluesnapWebhookEvents::Unknown => { | ||||
|                 api::IncomingWebhookEvent::EventNotSupported | ||||
|             } | ||||
|     fn get_dispute_details( | ||||
|         &self, | ||||
|         request: &api::IncomingWebhookRequestDetails<'_>, | ||||
|     ) -> CustomResult<api::disputes::DisputePayload, errors::ConnectorError> { | ||||
|         let dispute_details: bluesnap::BluesnapDisputeWebhookBody = | ||||
|             serde_urlencoded::from_bytes(request.body) | ||||
|                 .into_report() | ||||
|                 .change_context(errors::ConnectorError::WebhookBodyDecodingFailed)?; | ||||
|         Ok(api::disputes::DisputePayload { | ||||
|             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::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 => { | ||||
|                 Err(errors::ConnectorError::WebhookEventTypeNotFound) | ||||
|                 Err(errors::ConnectorError::WebhookResourceObjectNotFound) | ||||
|             } | ||||
|         }?; | ||||
|  | ||||
|  | ||||
| @ -818,19 +818,76 @@ pub struct BluesnapWebhookBody { | ||||
| #[serde(rename_all = "camelCase")] | ||||
| pub struct BluesnapWebhookObjectEventType { | ||||
|     pub transaction_type: BluesnapWebhookEvents, | ||||
|     pub cb_status: Option<BluesnapChargebackStatus>, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Deserialize)] | ||||
| #[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 { | ||||
|     Decline, | ||||
|     CcChargeFailed, | ||||
|     Charge, | ||||
|     Chargeback, | ||||
|     ChargebackStatusChanged, | ||||
|     #[serde(other)] | ||||
|     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)] | ||||
| #[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 reference_number: String, | ||||
|     pub transaction_type: BluesnapWebhookEvents, | ||||
|  | ||||
| @ -1000,6 +1000,16 @@ pub fn to_currency_base_unit( | ||||
|         .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( | ||||
|     capture_method: enums::CaptureMethod, | ||||
|     connector_name: &str, | ||||
|  | ||||
		Reference in New Issue
	
	Block a user
	 SamraatBansal
					SamraatBansal