mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-30 09:38:33 +08:00
feat(core): enable payments void for multiple partial capture (#2048)
This commit is contained in:
@ -1483,6 +1483,7 @@ pub fn should_call_connector<Op: Debug, F: Clone>(
|
||||
"PaymentCancel" => matches!(
|
||||
payment_data.payment_intent.status,
|
||||
storage_enums::IntentStatus::RequiresCapture
|
||||
| storage_enums::IntentStatus::PartiallyCaptured
|
||||
),
|
||||
"PaymentCapture" => {
|
||||
matches!(
|
||||
|
||||
@ -2696,6 +2696,7 @@ impl AttemptType {
|
||||
error_reason: None,
|
||||
multiple_capture_count: None,
|
||||
connector_response_reference_id: None,
|
||||
amount_capturable: old_payment_attempt.amount,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -91,6 +91,12 @@ impl<F: Send + Clone> GetTracker<F, payments::PaymentData<F>, api::PaymentsCaptu
|
||||
let amount_to_capture = request
|
||||
.amount_to_capture
|
||||
.get_required_value("amount_to_capture")?;
|
||||
|
||||
helpers::validate_amount_to_capture(
|
||||
payment_attempt.amount_capturable,
|
||||
Some(amount_to_capture),
|
||||
)?;
|
||||
|
||||
let previous_captures = db
|
||||
.find_all_captures_by_merchant_id_payment_id_authorized_attempt_id(
|
||||
&payment_attempt.merchant_id,
|
||||
@ -100,20 +106,6 @@ impl<F: Send + Clone> GetTracker<F, payments::PaymentData<F>, api::PaymentsCaptu
|
||||
)
|
||||
.await
|
||||
.to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?;
|
||||
let previously_blocked_amount =
|
||||
previous_captures.iter().fold(0, |accumulator, capture| {
|
||||
accumulator
|
||||
+ match capture.status {
|
||||
enums::CaptureStatus::Charged | enums::CaptureStatus::Pending => {
|
||||
capture.amount
|
||||
}
|
||||
enums::CaptureStatus::Started | enums::CaptureStatus::Failed => 0,
|
||||
}
|
||||
});
|
||||
helpers::validate_amount_to_capture(
|
||||
payment_attempt.amount - previously_blocked_amount,
|
||||
Some(amount_to_capture),
|
||||
)?;
|
||||
|
||||
let capture = db
|
||||
.insert_capture(
|
||||
|
||||
@ -17,8 +17,8 @@ use crate::{
|
||||
services::RedirectForm,
|
||||
types::{
|
||||
self, api,
|
||||
storage::{self, enums},
|
||||
transformers::{ForeignFrom, ForeignTryFrom},
|
||||
storage::{self, enums, payment_attempt::PaymentAttemptExt},
|
||||
transformers::ForeignTryFrom,
|
||||
CaptureSyncResponse,
|
||||
},
|
||||
utils,
|
||||
@ -305,19 +305,27 @@ async fn payment_response_update_tracker<F: Clone, T: types::Capturable>(
|
||||
)];
|
||||
(Some((multiple_capture_data, capture_update_list)), None)
|
||||
}
|
||||
None => (
|
||||
None,
|
||||
Some(storage::PaymentAttemptUpdate::ErrorUpdate {
|
||||
connector: None,
|
||||
status: match err.status_code {
|
||||
500..=511 => storage::enums::AttemptStatus::Pending,
|
||||
_ => storage::enums::AttemptStatus::Failure,
|
||||
},
|
||||
error_message: Some(Some(err.message)),
|
||||
error_code: Some(Some(err.code)),
|
||||
error_reason: Some(err.reason),
|
||||
}),
|
||||
),
|
||||
None => {
|
||||
let status = match err.status_code {
|
||||
500..=511 => storage::enums::AttemptStatus::Pending,
|
||||
_ => storage::enums::AttemptStatus::Failure,
|
||||
};
|
||||
(
|
||||
None,
|
||||
Some(storage::PaymentAttemptUpdate::ErrorUpdate {
|
||||
connector: None,
|
||||
status,
|
||||
error_message: Some(Some(err.message)),
|
||||
error_code: Some(Some(err.code)),
|
||||
error_reason: Some(err.reason),
|
||||
amount_capturable: if status.is_terminal_status() {
|
||||
Some(0)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
}),
|
||||
)
|
||||
}
|
||||
};
|
||||
(
|
||||
capture_update,
|
||||
@ -422,6 +430,11 @@ async fn payment_response_update_tracker<F: Clone, T: types::Capturable>(
|
||||
error_message: error_status.clone(),
|
||||
error_reason: error_status,
|
||||
connector_response_reference_id,
|
||||
amount_capturable: if router_data.status.is_terminal_status() {
|
||||
Some(0)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
}),
|
||||
),
|
||||
};
|
||||
@ -499,8 +512,10 @@ async fn payment_response_update_tracker<F: Clone, T: types::Capturable>(
|
||||
|
||||
let authorized_amount = payment_data.payment_attempt.amount;
|
||||
|
||||
payment_attempt_update = Some(storage::PaymentAttemptUpdate::StatusUpdate {
|
||||
payment_attempt_update = Some(storage::PaymentAttemptUpdate::AmountToCaptureUpdate {
|
||||
status: multiple_capture_data.get_attempt_status(authorized_amount),
|
||||
amount_capturable: payment_data.payment_attempt.amount
|
||||
- multiple_capture_data.get_total_blocked_amount(),
|
||||
});
|
||||
Some(multiple_capture_data)
|
||||
}
|
||||
@ -553,10 +568,14 @@ async fn payment_response_update_tracker<F: Clone, T: types::Capturable>(
|
||||
);
|
||||
let payment_intent_update = match &router_data.response {
|
||||
Err(_) => storage::PaymentIntentUpdate::PGStatusUpdate {
|
||||
status: enums::IntentStatus::foreign_from(payment_data.payment_attempt.status),
|
||||
status: payment_data
|
||||
.payment_attempt
|
||||
.get_intent_status(payment_data.payment_intent.amount_captured),
|
||||
},
|
||||
Ok(_) => storage::PaymentIntentUpdate::ResponseUpdate {
|
||||
status: enums::IntentStatus::foreign_from(payment_data.payment_attempt.status),
|
||||
status: payment_data
|
||||
.payment_attempt
|
||||
.get_intent_status(payment_data.payment_intent.amount_captured),
|
||||
return_url: router_data.return_url.clone(),
|
||||
amount_captured,
|
||||
},
|
||||
|
||||
@ -519,16 +519,13 @@ where
|
||||
connector_name,
|
||||
)
|
||||
});
|
||||
|
||||
let amount_captured = payment_intent.amount_captured.unwrap_or_default();
|
||||
let amount_capturable = Some(payment_attempt.amount - amount_captured);
|
||||
services::ApplicationResponse::JsonWithHeaders((
|
||||
response
|
||||
.set_payment_id(Some(payment_attempt.payment_id))
|
||||
.set_merchant_id(Some(payment_attempt.merchant_id))
|
||||
.set_status(payment_intent.status)
|
||||
.set_amount(payment_attempt.amount)
|
||||
.set_amount_capturable(amount_capturable)
|
||||
.set_amount_capturable(Some(payment_attempt.amount_capturable))
|
||||
.set_amount_received(payment_intent.amount_captured)
|
||||
.set_connector(routed_through)
|
||||
.set_client_secret(payment_intent.client_secret.map(masking::Secret::new))
|
||||
|
||||
@ -4,7 +4,9 @@ pub use data_models::payments::payment_attempt::{
|
||||
use diesel_models::{capture::CaptureNew, enums};
|
||||
use error_stack::ResultExt;
|
||||
|
||||
use crate::{core::errors, errors::RouterResult, utils::OptionExt};
|
||||
use crate::{
|
||||
core::errors, errors::RouterResult, types::transformers::ForeignFrom, utils::OptionExt,
|
||||
};
|
||||
|
||||
pub trait PaymentAttemptExt {
|
||||
fn make_new_capture(
|
||||
@ -14,6 +16,7 @@ pub trait PaymentAttemptExt {
|
||||
) -> RouterResult<CaptureNew>;
|
||||
|
||||
fn get_next_capture_id(&self) -> String;
|
||||
fn get_intent_status(&self, amount_captured: Option<i64>) -> enums::IntentStatus;
|
||||
}
|
||||
|
||||
impl PaymentAttemptExt for PaymentAttempt {
|
||||
@ -55,6 +58,15 @@ impl PaymentAttemptExt for PaymentAttempt {
|
||||
let next_sequence_number = self.multiple_capture_count.unwrap_or_default() + 1;
|
||||
format!("{}_{}", self.attempt_id.clone(), next_sequence_number)
|
||||
}
|
||||
|
||||
fn get_intent_status(&self, amount_captured: Option<i64>) -> enums::IntentStatus {
|
||||
let intent_status = enums::IntentStatus::foreign_from(self.status);
|
||||
if intent_status == enums::IntentStatus::Cancelled && amount_captured > Some(0) {
|
||||
enums::IntentStatus::Succeeded
|
||||
} else {
|
||||
intent_status
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
Reference in New Issue
Block a user