mirror of
				https://github.com/juspay/hyperswitch.git
				synced 2025-10-31 10:06:32 +08:00 
			
		
		
		
	fix: handle 5xx during multiple capture call (#2148)
This commit is contained in:
		| @ -278,6 +278,7 @@ pub struct Response { | |||||||
|     refusal_reason: Option<String>, |     refusal_reason: Option<String>, | ||||||
|     refusal_reason_code: Option<String>, |     refusal_reason_code: Option<String>, | ||||||
|     additional_data: Option<AdditionalData>, |     additional_data: Option<AdditionalData>, | ||||||
|  |     // event_code will be available only in webhook body | ||||||
|     event_code: Option<WebhookEventCode>, |     event_code: Option<WebhookEventCode>, | ||||||
| } | } | ||||||
|  |  | ||||||
| @ -3710,6 +3711,12 @@ impl utils::MultipleCaptureSyncResponse for Response { | |||||||
|     fn get_connector_reference_id(&self) -> Option<String> { |     fn get_connector_reference_id(&self) -> Option<String> { | ||||||
|         Some(self.merchant_reference.clone()) |         Some(self.merchant_reference.clone()) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     fn get_amount_captured(&self) -> Option<i64> { | ||||||
|  |         self.amount | ||||||
|  |             .as_ref() | ||||||
|  |             .map(|amount_struct| amount_struct.value) | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| // Payouts | // Payouts | ||||||
|  | |||||||
| @ -872,6 +872,10 @@ impl utils::MultipleCaptureSyncResponse for ActionResponse { | |||||||
|     fn is_capture_response(&self) -> bool { |     fn is_capture_response(&self) -> bool { | ||||||
|         self.action_type == ActionType::Capture |         self.action_type == ActionType::Capture | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     fn get_amount_captured(&self) -> Option<i64> { | ||||||
|  |         Some(self.amount) | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| impl utils::MultipleCaptureSyncResponse for Box<PaymentsResponse> { | impl utils::MultipleCaptureSyncResponse for Box<PaymentsResponse> { | ||||||
| @ -890,6 +894,12 @@ impl utils::MultipleCaptureSyncResponse for Box<PaymentsResponse> { | |||||||
|     fn is_capture_response(&self) -> bool { |     fn is_capture_response(&self) -> bool { | ||||||
|         self.status == CheckoutPaymentStatus::Captured |         self.status == CheckoutPaymentStatus::Captured | ||||||
|     } |     } | ||||||
|  |     fn get_amount_captured(&self) -> Option<i64> { | ||||||
|  |         match self.amount { | ||||||
|  |             Some(amount) => amount.try_into().ok(), | ||||||
|  |             None => None, | ||||||
|  |         } | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| #[derive(Debug, Clone, serde::Deserialize, Eq, PartialEq)] | #[derive(Debug, Clone, serde::Deserialize, Eq, PartialEq)] | ||||||
|  | |||||||
| @ -506,6 +506,12 @@ impl utils::MultipleCaptureSyncResponse for GlobalpayPaymentsResponse { | |||||||
|         true |         true | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     fn get_amount_captured(&self) -> Option<i64> { | ||||||
|  |         match self.amount.clone() { | ||||||
|  |             Some(amount) => amount.parse().ok(), | ||||||
|  |             None => None, | ||||||
|  |         } | ||||||
|  |     } | ||||||
|     fn get_connector_reference_id(&self) -> Option<String> { |     fn get_connector_reference_id(&self) -> Option<String> { | ||||||
|         self.reference.clone() |         self.reference.clone() | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -1380,6 +1380,7 @@ pub trait MultipleCaptureSyncResponse { | |||||||
|     fn get_connector_reference_id(&self) -> Option<String> { |     fn get_connector_reference_id(&self) -> Option<String> { | ||||||
|         None |         None | ||||||
|     } |     } | ||||||
|  |     fn get_amount_captured(&self) -> Option<i64>; | ||||||
| } | } | ||||||
|  |  | ||||||
| pub fn construct_captures_response_hashmap<T>( | pub fn construct_captures_response_hashmap<T>( | ||||||
| @ -1401,6 +1402,7 @@ where | |||||||
|                         status: capture_sync_response.get_capture_attempt_status(), |                         status: capture_sync_response.get_capture_attempt_status(), | ||||||
|                         connector_response_reference_id: capture_sync_response |                         connector_response_reference_id: capture_sync_response | ||||||
|                             .get_connector_reference_id(), |                             .get_connector_reference_id(), | ||||||
|  |                         amount: capture_sync_response.get_amount_captured(), | ||||||
|                     }, |                     }, | ||||||
|                 ); |                 ); | ||||||
|             } |             } | ||||||
|  | |||||||
| @ -191,6 +191,7 @@ impl types::RouterData<api::PSync, types::PaymentsSyncData, types::PaymentsRespo | |||||||
|                             message: err.message, |                             message: err.message, | ||||||
|                             reason: err.reason, |                             reason: err.reason, | ||||||
|                             status_code: err.status_code, |                             status_code: err.status_code, | ||||||
|  |                             amount: None, | ||||||
|                         }); |                         }); | ||||||
|                     }, |                     }, | ||||||
|                     Ok(types::PaymentsResponseData::MultipleCaptureResponse { capture_sync_response_list })=> { |                     Ok(types::PaymentsResponseData::MultipleCaptureResponse { capture_sync_response_list })=> { | ||||||
|  | |||||||
| @ -621,16 +621,54 @@ fn response_to_capture_update( | |||||||
|     response_list: HashMap<String, CaptureSyncResponse>, |     response_list: HashMap<String, CaptureSyncResponse>, | ||||||
| ) -> RouterResult<Vec<(storage::Capture, storage::CaptureUpdate)>> { | ) -> RouterResult<Vec<(storage::Capture, storage::CaptureUpdate)>> { | ||||||
|     let mut capture_update_list = vec![]; |     let mut capture_update_list = vec![]; | ||||||
|  |     let mut unmapped_captures = vec![]; | ||||||
|     for (connector_capture_id, capture_sync_response) in response_list { |     for (connector_capture_id, capture_sync_response) in response_list { | ||||||
|         let capture = |         let capture = | ||||||
|             multiple_capture_data.get_capture_by_connector_capture_id(connector_capture_id); |             multiple_capture_data.get_capture_by_connector_capture_id(connector_capture_id); | ||||||
|         if let Some(capture) = capture { |         if let Some(capture) = capture { | ||||||
|             capture_update_list.push((capture.clone(), capture_sync_response.try_into()?)) |             capture_update_list.push((capture.clone(), capture_sync_response.try_into()?)) | ||||||
|  |         } else { | ||||||
|  |             // connector_capture_id may not be populated in the captures table in some case | ||||||
|  |             // if so, we try to map the unmapped capture response and captures in DB. | ||||||
|  |             unmapped_captures.push(capture_sync_response) | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |     capture_update_list.extend(get_capture_update_for_unmapped_capture_responses( | ||||||
|  |         unmapped_captures, | ||||||
|  |         multiple_capture_data, | ||||||
|  |     )?); | ||||||
|  |  | ||||||
|     Ok(capture_update_list) |     Ok(capture_update_list) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | fn get_capture_update_for_unmapped_capture_responses( | ||||||
|  |     unmapped_capture_sync_response_list: Vec<CaptureSyncResponse>, | ||||||
|  |     multiple_capture_data: &MultipleCaptureData, | ||||||
|  | ) -> RouterResult<Vec<(storage::Capture, storage::CaptureUpdate)>> { | ||||||
|  |     let mut result = Vec::new(); | ||||||
|  |     let captures_without_connector_capture_id: Vec<_> = multiple_capture_data | ||||||
|  |         .get_pending_captures_without_connector_capture_id() | ||||||
|  |         .into_iter() | ||||||
|  |         .cloned() | ||||||
|  |         .collect(); | ||||||
|  |     for capture_sync_response in unmapped_capture_sync_response_list { | ||||||
|  |         if let Some(capture) = captures_without_connector_capture_id | ||||||
|  |             .iter() | ||||||
|  |             .find(|capture| { | ||||||
|  |                 capture_sync_response.get_connector_response_reference_id() | ||||||
|  |                     == Some(capture.capture_id.clone()) | ||||||
|  |                     || capture_sync_response.get_amount_captured() == Some(capture.amount) | ||||||
|  |             }) | ||||||
|  |         { | ||||||
|  |             result.push(( | ||||||
|  |                 capture.clone(), | ||||||
|  |                 storage::CaptureUpdate::try_from(capture_sync_response)?, | ||||||
|  |             )) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     Ok(result) | ||||||
|  | } | ||||||
|  |  | ||||||
| fn get_total_amount_captured<F: Clone, T: types::Capturable>( | fn get_total_amount_captured<F: Clone, T: types::Capturable>( | ||||||
|     request: T, |     request: T, | ||||||
|     amount_captured: Option<i64>, |     amount_captured: Option<i64>, | ||||||
|  | |||||||
| @ -1206,6 +1206,7 @@ impl TryFrom<types::CaptureSyncResponse> for storage::CaptureUpdate { | |||||||
|                 resource_id, |                 resource_id, | ||||||
|                 status, |                 status, | ||||||
|                 connector_response_reference_id, |                 connector_response_reference_id, | ||||||
|  |                 .. | ||||||
|             } => { |             } => { | ||||||
|                 let connector_capture_id = match resource_id { |                 let connector_capture_id = match resource_id { | ||||||
|                     types::ResponseId::ConnectorTransactionId(id) => Some(id), |                     types::ResponseId::ConnectorTransactionId(id) => Some(id), | ||||||
| @ -1222,6 +1223,7 @@ impl TryFrom<types::CaptureSyncResponse> for storage::CaptureUpdate { | |||||||
|                 message, |                 message, | ||||||
|                 reason, |                 reason, | ||||||
|                 status_code, |                 status_code, | ||||||
|  |                 .. | ||||||
|             } => Ok(Self::ErrorUpdate { |             } => Ok(Self::ErrorUpdate { | ||||||
|                 status: match status_code { |                 status: match status_code { | ||||||
|                     500..=511 => storage::enums::CaptureStatus::Pending, |                     500..=511 => storage::enums::CaptureStatus::Pending, | ||||||
|  | |||||||
| @ -157,4 +157,10 @@ impl MultipleCaptureData { | |||||||
|             .collect(); |             .collect(); | ||||||
|         pending_connector_capture_ids |         pending_connector_capture_ids | ||||||
|     } |     } | ||||||
|  |     pub fn get_pending_captures_without_connector_capture_id(&self) -> Vec<&storage::Capture> { | ||||||
|  |         self.get_pending_captures() | ||||||
|  |             .into_iter() | ||||||
|  |             .filter(|capture| capture.connector_capture_id.is_none()) | ||||||
|  |             .collect() | ||||||
|  |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -572,15 +572,34 @@ pub enum CaptureSyncResponse { | |||||||
|         resource_id: ResponseId, |         resource_id: ResponseId, | ||||||
|         status: storage_enums::AttemptStatus, |         status: storage_enums::AttemptStatus, | ||||||
|         connector_response_reference_id: Option<String>, |         connector_response_reference_id: Option<String>, | ||||||
|  |         amount: Option<i64>, | ||||||
|     }, |     }, | ||||||
|     Error { |     Error { | ||||||
|         code: String, |         code: String, | ||||||
|         message: String, |         message: String, | ||||||
|         reason: Option<String>, |         reason: Option<String>, | ||||||
|         status_code: u16, |         status_code: u16, | ||||||
|  |         amount: Option<i64>, | ||||||
|     }, |     }, | ||||||
| } | } | ||||||
|  |  | ||||||
|  | impl CaptureSyncResponse { | ||||||
|  |     pub fn get_amount_captured(&self) -> Option<i64> { | ||||||
|  |         match self { | ||||||
|  |             Self::Success { amount, .. } | Self::Error { amount, .. } => *amount, | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     pub fn get_connector_response_reference_id(&self) -> Option<String> { | ||||||
|  |         match self { | ||||||
|  |             Self::Success { | ||||||
|  |                 connector_response_reference_id, | ||||||
|  |                 .. | ||||||
|  |             } => connector_response_reference_id.clone(), | ||||||
|  |             Self::Error { .. } => None, | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
| #[derive(Debug, Clone)] | #[derive(Debug, Clone)] | ||||||
| pub enum PaymentsResponseData { | pub enum PaymentsResponseData { | ||||||
|     TransactionResponse { |     TransactionResponse { | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user
	 Hrithikesh
					Hrithikesh