mirror of
				https://github.com/juspay/hyperswitch.git
				synced 2025-11-01 02:57:02 +08:00 
			
		
		
		
	fix: update amount_capturable based on intent_status and payment flow (#3278)
This commit is contained in:
		| @ -117,7 +117,7 @@ where | ||||
|             } | ||||
|             enums::AttemptStatus::Charged => { | ||||
|                 let captured_amount = | ||||
|                     types::Capturable::get_capture_amount(&self.request, payment_data); | ||||
|                     types::Capturable::get_captured_amount(&self.request, payment_data); | ||||
|                 let total_capturable_amount = payment_data.payment_attempt.get_total_amount(); | ||||
|                 if Some(total_capturable_amount) == captured_amount { | ||||
|                     enums::AttemptStatus::Charged | ||||
|  | ||||
| @ -24,7 +24,7 @@ use crate::{ | ||||
|     services::RedirectForm, | ||||
|     types::{ | ||||
|         self, api, | ||||
|         storage::{self, enums, payment_attempt::AttemptStatusExt}, | ||||
|         storage::{self, enums}, | ||||
|         transformers::{ForeignFrom, ForeignTryFrom}, | ||||
|         CaptureSyncResponse, | ||||
|     }, | ||||
| @ -499,15 +499,9 @@ async fn payment_response_update_tracker<F: Clone, T: types::Capturable>( | ||||
|                             error_message: Some(Some(err.message)), | ||||
|                             error_code: Some(Some(err.code)), | ||||
|                             error_reason: Some(err.reason), | ||||
|                             amount_capturable: if status.is_terminal_status() | ||||
|                                 || router_data | ||||
|                                     .status | ||||
|                                     .maps_to_intent_status(enums::IntentStatus::Processing) | ||||
|                             { | ||||
|                                 Some(0) | ||||
|                             } else { | ||||
|                                 None | ||||
|                             }, | ||||
|                             amount_capturable: router_data | ||||
|                                 .request | ||||
|                                 .get_amount_capturable(&payment_data, status), | ||||
|                             updated_by: storage_scheme.to_string(), | ||||
|                             unified_code: option_gsm.clone().map(|gsm| gsm.unified_code), | ||||
|                             unified_message: option_gsm.map(|gsm| gsm.unified_message), | ||||
| @ -598,27 +592,33 @@ async fn payment_response_update_tracker<F: Clone, T: types::Capturable>( | ||||
|                     payment_data.payment_attempt.merchant_id.clone(), | ||||
|                 ); | ||||
|  | ||||
|                 let (capture_updates, payment_attempt_update) = | ||||
|                     match payment_data.multiple_capture_data { | ||||
|                         Some(multiple_capture_data) => { | ||||
|                             let capture_update = storage::CaptureUpdate::ResponseUpdate { | ||||
|                                 status: enums::CaptureStatus::foreign_try_from(router_data.status)?, | ||||
|                                 connector_capture_id: connector_transaction_id.clone(), | ||||
|                                 connector_response_reference_id, | ||||
|                             }; | ||||
|                             let capture_update_list = vec![( | ||||
|                                 multiple_capture_data.get_latest_capture().clone(), | ||||
|                                 capture_update, | ||||
|                             )]; | ||||
|                             (Some((multiple_capture_data, capture_update_list)), None) | ||||
|                         } | ||||
|                         None => ( | ||||
|                 let (capture_updates, payment_attempt_update) = match payment_data | ||||
|                     .multiple_capture_data | ||||
|                 { | ||||
|                     Some(multiple_capture_data) => { | ||||
|                         let capture_update = storage::CaptureUpdate::ResponseUpdate { | ||||
|                             status: enums::CaptureStatus::foreign_try_from(router_data.status)?, | ||||
|                             connector_capture_id: connector_transaction_id.clone(), | ||||
|                             connector_response_reference_id, | ||||
|                         }; | ||||
|                         let capture_update_list = vec![( | ||||
|                             multiple_capture_data.get_latest_capture().clone(), | ||||
|                             capture_update, | ||||
|                         )]; | ||||
|                         (Some((multiple_capture_data, capture_update_list)), None) | ||||
|                     } | ||||
|                     None => { | ||||
|                         let status = router_data.get_attempt_status_for_db_update(&payment_data); | ||||
|                         ( | ||||
|                             None, | ||||
|                             Some(storage::PaymentAttemptUpdate::ResponseUpdate { | ||||
|                                 status: router_data.get_attempt_status_for_db_update(&payment_data), | ||||
|                                 status, | ||||
|                                 connector: None, | ||||
|                                 connector_transaction_id: connector_transaction_id.clone(), | ||||
|                                 authentication_type: None, | ||||
|                                 amount_capturable: router_data | ||||
|                                     .request | ||||
|                                     .get_amount_capturable(&payment_data, status), | ||||
|                                 payment_method_id: Some(router_data.payment_method_id), | ||||
|                                 mandate_id: payment_data | ||||
|                                     .mandate_id | ||||
| @ -632,21 +632,13 @@ async fn payment_response_update_tracker<F: Clone, T: types::Capturable>( | ||||
|                                 unified_code: error_status.clone(), | ||||
|                                 unified_message: error_status, | ||||
|                                 connector_response_reference_id, | ||||
|                                 amount_capturable: if router_data.status.is_terminal_status() | ||||
|                                     || router_data | ||||
|                                         .status | ||||
|                                         .maps_to_intent_status(enums::IntentStatus::Processing) | ||||
|                                 { | ||||
|                                     Some(0) | ||||
|                                 } else { | ||||
|                                     None | ||||
|                                 }, | ||||
|                                 updated_by: storage_scheme.to_string(), | ||||
|                                 authentication_data, | ||||
|                                 encoded_data, | ||||
|                             }), | ||||
|                         ), | ||||
|                     }; | ||||
|                         ) | ||||
|                     } | ||||
|                 }; | ||||
|  | ||||
|                 (capture_updates, payment_attempt_update) | ||||
|             } | ||||
| @ -900,7 +892,7 @@ fn get_total_amount_captured<F: Clone, T: types::Capturable>( | ||||
|         } | ||||
|         None => { | ||||
|             //Non multiple capture | ||||
|             let amount = request.get_capture_amount(payment_data); | ||||
|             let amount = request.get_captured_amount(payment_data); | ||||
|             amount_captured.or_else(|| { | ||||
|                 if router_data_status == enums::AttemptStatus::Charged { | ||||
|                     amount | ||||
|  | ||||
| @ -599,7 +599,17 @@ pub struct AccessTokenRequestData { | ||||
| } | ||||
|  | ||||
| pub trait Capturable { | ||||
|     fn get_capture_amount<F>(&self, _payment_data: &PaymentData<F>) -> Option<i64> | ||||
|     fn get_captured_amount<F>(&self, _payment_data: &PaymentData<F>) -> Option<i64> | ||||
|     where | ||||
|         F: Clone, | ||||
|     { | ||||
|         None | ||||
|     } | ||||
|     fn get_amount_capturable<F>( | ||||
|         &self, | ||||
|         _payment_data: &PaymentData<F>, | ||||
|         _attempt_status: common_enums::AttemptStatus, | ||||
|     ) -> Option<i64> | ||||
|     where | ||||
|         F: Clone, | ||||
|     { | ||||
| @ -608,7 +618,7 @@ pub trait Capturable { | ||||
| } | ||||
|  | ||||
| impl Capturable for PaymentsAuthorizeData { | ||||
|     fn get_capture_amount<F>(&self, _payment_data: &PaymentData<F>) -> Option<i64> | ||||
|     fn get_captured_amount<F>(&self, _payment_data: &PaymentData<F>) -> Option<i64> | ||||
|     where | ||||
|         F: Clone, | ||||
|     { | ||||
| @ -618,41 +628,171 @@ impl Capturable for PaymentsAuthorizeData { | ||||
|             .map(|surcharge_details| surcharge_details.final_amount); | ||||
|         final_amount.or(Some(self.amount)) | ||||
|     } | ||||
|  | ||||
|     fn get_amount_capturable<F>( | ||||
|         &self, | ||||
|         payment_data: &PaymentData<F>, | ||||
|         attempt_status: common_enums::AttemptStatus, | ||||
|     ) -> Option<i64> | ||||
|     where | ||||
|         F: Clone, | ||||
|     { | ||||
|         match payment_data | ||||
|             .payment_attempt | ||||
|             .capture_method | ||||
|             .unwrap_or_default() | ||||
|         { | ||||
|             common_enums::CaptureMethod::Automatic => { | ||||
|                 let intent_status = common_enums::IntentStatus::foreign_from(attempt_status); | ||||
|                 match intent_status { | ||||
|                     common_enums::IntentStatus::Succeeded | ||||
|                     | common_enums::IntentStatus::Failed | ||||
|                     | common_enums::IntentStatus::Processing => Some(0), | ||||
|                     common_enums::IntentStatus::Cancelled | ||||
|                     | common_enums::IntentStatus::PartiallyCaptured | ||||
|                     | common_enums::IntentStatus::RequiresCustomerAction | ||||
|                     | common_enums::IntentStatus::RequiresMerchantAction | ||||
|                     | common_enums::IntentStatus::RequiresPaymentMethod | ||||
|                     | common_enums::IntentStatus::RequiresConfirmation | ||||
|                     | common_enums::IntentStatus::RequiresCapture | ||||
|                     | common_enums::IntentStatus::PartiallyCapturedAndCapturable => None, | ||||
|                 } | ||||
|             }, | ||||
|             common_enums::CaptureMethod::Manual => Some(payment_data.payment_attempt.get_total_amount()), | ||||
|             // In case of manual multiple, amount capturable must be inferred from all captures. | ||||
|             common_enums::CaptureMethod::ManualMultiple | | ||||
|             // Scheduled capture is not supported as of now | ||||
|             common_enums::CaptureMethod::Scheduled => None, | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl Capturable for PaymentsCaptureData { | ||||
|     fn get_capture_amount<F>(&self, _payment_data: &PaymentData<F>) -> Option<i64> | ||||
|     fn get_captured_amount<F>(&self, _payment_data: &PaymentData<F>) -> Option<i64> | ||||
|     where | ||||
|         F: Clone, | ||||
|     { | ||||
|         Some(self.amount_to_capture) | ||||
|     } | ||||
|     fn get_amount_capturable<F>( | ||||
|         &self, | ||||
|         _payment_data: &PaymentData<F>, | ||||
|         attempt_status: common_enums::AttemptStatus, | ||||
|     ) -> Option<i64> | ||||
|     where | ||||
|         F: Clone, | ||||
|     { | ||||
|         let intent_status = common_enums::IntentStatus::foreign_from(attempt_status); | ||||
|         match intent_status { | ||||
|             common_enums::IntentStatus::Succeeded | ||||
|             | common_enums::IntentStatus::PartiallyCaptured | ||||
|             | common_enums::IntentStatus::Processing => Some(0), | ||||
|             common_enums::IntentStatus::Cancelled | ||||
|             | common_enums::IntentStatus::Failed | ||||
|             | common_enums::IntentStatus::RequiresCustomerAction | ||||
|             | common_enums::IntentStatus::RequiresMerchantAction | ||||
|             | common_enums::IntentStatus::RequiresPaymentMethod | ||||
|             | common_enums::IntentStatus::RequiresConfirmation | ||||
|             | common_enums::IntentStatus::RequiresCapture | ||||
|             | common_enums::IntentStatus::PartiallyCapturedAndCapturable => None, | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl Capturable for CompleteAuthorizeData { | ||||
|     fn get_capture_amount<F>(&self, _payment_data: &PaymentData<F>) -> Option<i64> | ||||
|     fn get_captured_amount<F>(&self, _payment_data: &PaymentData<F>) -> Option<i64> | ||||
|     where | ||||
|         F: Clone, | ||||
|     { | ||||
|         Some(self.amount) | ||||
|     } | ||||
|     fn get_amount_capturable<F>( | ||||
|         &self, | ||||
|         payment_data: &PaymentData<F>, | ||||
|         attempt_status: common_enums::AttemptStatus, | ||||
|     ) -> Option<i64> | ||||
|     where | ||||
|         F: Clone, | ||||
|     { | ||||
|         match payment_data | ||||
|             .payment_attempt | ||||
|             .capture_method | ||||
|             .unwrap_or_default() | ||||
|         { | ||||
|             common_enums::CaptureMethod::Automatic => { | ||||
|                 let intent_status = common_enums::IntentStatus::foreign_from(attempt_status); | ||||
|                 match intent_status { | ||||
|                     common_enums::IntentStatus::Succeeded| | ||||
|                     common_enums::IntentStatus::Failed| | ||||
|                     common_enums::IntentStatus::Processing => Some(0), | ||||
|                     common_enums::IntentStatus::Cancelled | ||||
|                     | common_enums::IntentStatus::PartiallyCaptured | ||||
|                     | common_enums::IntentStatus::RequiresCustomerAction | ||||
|                     | common_enums::IntentStatus::RequiresMerchantAction | ||||
|                     | common_enums::IntentStatus::RequiresPaymentMethod | ||||
|                     | common_enums::IntentStatus::RequiresConfirmation | ||||
|                     | common_enums::IntentStatus::RequiresCapture | ||||
|                     | common_enums::IntentStatus::PartiallyCapturedAndCapturable => None, | ||||
|                 } | ||||
|             }, | ||||
|             common_enums::CaptureMethod::Manual => Some(payment_data.payment_attempt.get_total_amount()), | ||||
|             // In case of manual multiple, amount capturable must be inferred from all captures. | ||||
|             common_enums::CaptureMethod::ManualMultiple | | ||||
|             // Scheduled capture is not supported as of now | ||||
|             common_enums::CaptureMethod::Scheduled => None, | ||||
|         } | ||||
|     } | ||||
| } | ||||
| impl Capturable for SetupMandateRequestData {} | ||||
| impl Capturable for PaymentsCancelData { | ||||
|     fn get_capture_amount<F>(&self, payment_data: &PaymentData<F>) -> Option<i64> | ||||
|     fn get_captured_amount<F>(&self, payment_data: &PaymentData<F>) -> Option<i64> | ||||
|     where | ||||
|         F: Clone, | ||||
|     { | ||||
|         // return previously captured amount | ||||
|         payment_data.payment_intent.amount_captured | ||||
|     } | ||||
|     fn get_amount_capturable<F>( | ||||
|         &self, | ||||
|         _payment_data: &PaymentData<F>, | ||||
|         attempt_status: common_enums::AttemptStatus, | ||||
|     ) -> Option<i64> | ||||
|     where | ||||
|         F: Clone, | ||||
|     { | ||||
|         let intent_status = common_enums::IntentStatus::foreign_from(attempt_status); | ||||
|         match intent_status { | ||||
|             common_enums::IntentStatus::Cancelled | ||||
|             | common_enums::IntentStatus::Processing | ||||
|             | common_enums::IntentStatus::PartiallyCaptured => Some(0), | ||||
|             common_enums::IntentStatus::Succeeded | ||||
|             | common_enums::IntentStatus::Failed | ||||
|             | common_enums::IntentStatus::RequiresCustomerAction | ||||
|             | common_enums::IntentStatus::RequiresMerchantAction | ||||
|             | common_enums::IntentStatus::RequiresPaymentMethod | ||||
|             | common_enums::IntentStatus::RequiresConfirmation | ||||
|             | common_enums::IntentStatus::RequiresCapture | ||||
|             | common_enums::IntentStatus::PartiallyCapturedAndCapturable => None, | ||||
|         } | ||||
|     } | ||||
| } | ||||
| impl Capturable for PaymentsApproveData {} | ||||
| impl Capturable for PaymentsRejectData {} | ||||
| impl Capturable for PaymentsSessionData {} | ||||
| impl Capturable for PaymentsIncrementalAuthorizationData {} | ||||
| impl Capturable for PaymentsIncrementalAuthorizationData { | ||||
|     fn get_amount_capturable<F>( | ||||
|         &self, | ||||
|         _payment_data: &PaymentData<F>, | ||||
|         _attempt_status: common_enums::AttemptStatus, | ||||
|     ) -> Option<i64> | ||||
|     where | ||||
|         F: Clone, | ||||
|     { | ||||
|         Some(self.total_amount) | ||||
|     } | ||||
| } | ||||
| impl Capturable for PaymentsSyncData { | ||||
|     fn get_capture_amount<F>(&self, payment_data: &PaymentData<F>) -> Option<i64> | ||||
|     fn get_captured_amount<F>(&self, payment_data: &PaymentData<F>) -> Option<i64> | ||||
|     where | ||||
|         F: Clone, | ||||
|     { | ||||
| @ -661,6 +801,20 @@ impl Capturable for PaymentsSyncData { | ||||
|             .amount_to_capture | ||||
|             .or_else(|| Some(payment_data.payment_attempt.get_total_amount())) | ||||
|     } | ||||
|     fn get_amount_capturable<F>( | ||||
|         &self, | ||||
|         _payment_data: &PaymentData<F>, | ||||
|         attempt_status: common_enums::AttemptStatus, | ||||
|     ) -> Option<i64> | ||||
|     where | ||||
|         F: Clone, | ||||
|     { | ||||
|         if attempt_status.is_terminal_status() { | ||||
|             Some(0) | ||||
|         } else { | ||||
|             None | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| pub struct AddAccessTokenResult { | ||||
|  | ||||
		Reference in New Issue
	
	Block a user
	 Hrithikesh
					Hrithikesh