fix: amount_captured goes to 0 for 3ds payments (#2954)

This commit is contained in:
Hrithikesh
2023-11-23 13:03:42 +05:30
committed by GitHub
parent e721b06c70
commit 75eea7e817
7 changed files with 65 additions and 36 deletions

View File

@ -312,6 +312,18 @@ pub struct PaymentsRequest {
pub payment_type: Option<api_enums::PaymentType>, pub payment_type: Option<api_enums::PaymentType>,
} }
impl PaymentsRequest {
pub fn get_total_capturable_amount(&self) -> Option<i64> {
let surcharge_amount = self
.surcharge_details
.map(|surcharge_details| {
surcharge_details.surcharge_amount + surcharge_details.tax_amount.unwrap_or(0)
})
.unwrap_or(0);
self.amount
.map(|amount| i64::from(amount) + surcharge_amount)
}
}
#[derive( #[derive(
Default, Debug, Clone, serde::Serialize, serde::Deserialize, Copy, ToSchema, PartialEq, Default, Debug, Clone, serde::Serialize, serde::Deserialize, Copy, ToSchema, PartialEq,
)] )]

View File

@ -111,14 +111,8 @@ where
} }
} }
enums::AttemptStatus::Charged => { enums::AttemptStatus::Charged => {
let captured_amount = if self.request.is_psync() { let captured_amount =
payment_data types::Capturable::get_capture_amount(&self.request, payment_data);
.payment_attempt
.amount_to_capture
.or(Some(payment_data.payment_attempt.get_total_amount()))
} else {
types::Capturable::get_capture_amount(&self.request)
};
if Some(payment_data.payment_attempt.get_total_amount()) == captured_amount { if Some(payment_data.payment_attempt.get_total_amount()) == captured_amount {
enums::AttemptStatus::Charged enums::AttemptStatus::Charged
} else if captured_amount.is_some() { } else if captured_amount.is_some() {

View File

@ -601,19 +601,19 @@ pub fn validate_request_amount_and_amount_to_capture(
} }
} }
/// if confirm = true and capture method = automatic, amount_to_capture(if provided) must be equal to amount /// if capture method = automatic, amount_to_capture(if provided) must be equal to amount
#[instrument(skip_all)] #[instrument(skip_all)]
pub fn validate_amount_to_capture_in_create_call_request( pub fn validate_amount_to_capture_in_create_call_request(
request: &api_models::payments::PaymentsRequest, request: &api_models::payments::PaymentsRequest,
) -> CustomResult<(), errors::ApiErrorResponse> { ) -> CustomResult<(), errors::ApiErrorResponse> {
if request.capture_method.unwrap_or_default() == api_enums::CaptureMethod::Automatic if request.capture_method.unwrap_or_default() == api_enums::CaptureMethod::Automatic {
&& request.confirm.unwrap_or(false) let total_capturable_amount = request.get_total_capturable_amount();
{ if let Some((amount_to_capture, total_capturable_amount)) =
if let Some((amount_to_capture, amount)) = request.amount_to_capture.zip(request.amount) { request.amount_to_capture.zip(total_capturable_amount)
let amount_int: i64 = amount.into(); {
utils::when(amount_to_capture != amount_int, || { utils::when(amount_to_capture != total_capturable_amount, || {
Err(report!(errors::ApiErrorResponse::PreconditionFailed { Err(report!(errors::ApiErrorResponse::PreconditionFailed {
message: "amount_to_capture must be equal to amount when confirm = true and capture_method = automatic".into() message: "amount_to_capture must be equal to total_capturable_amount when capture_method = automatic".into()
})) }))
}) })
} else { } else {

View File

@ -751,7 +751,7 @@ fn get_total_amount_captured<F: Clone, T: types::Capturable>(
} }
None => { None => {
//Non multiple capture //Non multiple capture
let amount = request.get_capture_amount(); let amount = request.get_capture_amount(payment_data);
amount_captured.or_else(|| { amount_captured.or_else(|| {
if router_data_status == enums::AttemptStatus::Charged { if router_data_status == enums::AttemptStatus::Charged {
amount amount

View File

@ -58,13 +58,12 @@ pub async fn refund_create_core(
)?; )?;
// Amount is not passed in request refer from payment intent. // Amount is not passed in request refer from payment intent.
amount = req.amount.unwrap_or( amount = req
payment_intent .amount
.amount_captured .or(payment_intent.amount_captured)
.ok_or(errors::ApiErrorResponse::InternalServerError) .ok_or(errors::ApiErrorResponse::InternalServerError)
.into_report() .into_report()
.attach_printable("amount captured is none in a successful payment")?, .attach_printable("amount captured is none in a successful payment")?;
);
//[#299]: Can we change the flow based on some workflow idea //[#299]: Can we change the flow based on some workflow idea
utils::when(amount <= 0, || { utils::when(amount <= 0, || {

View File

@ -30,9 +30,10 @@ use crate::core::utils::IRRELEVANT_CONNECTOR_REQUEST_REFERENCE_ID_IN_DISPUTE_FLO
use crate::{ use crate::{
core::{ core::{
errors::{self, RouterResult}, errors::{self, RouterResult},
payments::RecurringMandatePaymentData, payments::{PaymentData, RecurringMandatePaymentData},
}, },
services, services,
types::storage::payment_attempt::PaymentAttemptExt,
utils::OptionExt, utils::OptionExt,
}; };
@ -544,7 +545,10 @@ pub struct AccessTokenRequestData {
} }
pub trait Capturable { pub trait Capturable {
fn get_capture_amount(&self) -> Option<i64> { fn get_capture_amount<F>(&self, _payment_data: &PaymentData<F>) -> Option<i64>
where
F: Clone,
{
None None
} }
fn get_surcharge_amount(&self) -> Option<i64> { fn get_surcharge_amount(&self) -> Option<i64> {
@ -553,13 +557,13 @@ pub trait Capturable {
fn get_tax_on_surcharge_amount(&self) -> Option<i64> { fn get_tax_on_surcharge_amount(&self) -> Option<i64> {
None None
} }
fn is_psync(&self) -> bool {
false
}
} }
impl Capturable for PaymentsAuthorizeData { impl Capturable for PaymentsAuthorizeData {
fn get_capture_amount(&self) -> Option<i64> { fn get_capture_amount<F>(&self, _payment_data: &PaymentData<F>) -> Option<i64>
where
F: Clone,
{
let final_amount = self let final_amount = self
.surcharge_details .surcharge_details
.as_ref() .as_ref()
@ -579,24 +583,44 @@ impl Capturable for PaymentsAuthorizeData {
} }
impl Capturable for PaymentsCaptureData { impl Capturable for PaymentsCaptureData {
fn get_capture_amount(&self) -> Option<i64> { fn get_capture_amount<F>(&self, _payment_data: &PaymentData<F>) -> Option<i64>
where
F: Clone,
{
Some(self.amount_to_capture) Some(self.amount_to_capture)
} }
} }
impl Capturable for CompleteAuthorizeData { impl Capturable for CompleteAuthorizeData {
fn get_capture_amount(&self) -> Option<i64> { fn get_capture_amount<F>(&self, _payment_data: &PaymentData<F>) -> Option<i64>
where
F: Clone,
{
Some(self.amount) Some(self.amount)
} }
} }
impl Capturable for SetupMandateRequestData {} impl Capturable for SetupMandateRequestData {}
impl Capturable for PaymentsCancelData {} impl Capturable for PaymentsCancelData {
fn get_capture_amount<F>(&self, payment_data: &PaymentData<F>) -> Option<i64>
where
F: Clone,
{
// return previously captured amount
payment_data.payment_intent.amount_captured
}
}
impl Capturable for PaymentsApproveData {} impl Capturable for PaymentsApproveData {}
impl Capturable for PaymentsRejectData {} impl Capturable for PaymentsRejectData {}
impl Capturable for PaymentsSessionData {} impl Capturable for PaymentsSessionData {}
impl Capturable for PaymentsSyncData { impl Capturable for PaymentsSyncData {
fn is_psync(&self) -> bool { fn get_capture_amount<F>(&self, payment_data: &PaymentData<F>) -> Option<i64>
true where
F: Clone,
{
payment_data
.payment_attempt
.amount_to_capture
.or(Some(payment_data.payment_attempt.get_total_amount()))
} }
} }

View File

@ -23,7 +23,7 @@
"confirm": false, "confirm": false,
"capture_method": "automatic", "capture_method": "automatic",
"capture_on": "2022-09-10T10:11:12Z", "capture_on": "2022-09-10T10:11:12Z",
"amount_to_capture": 6540, "amount_to_capture": 8000,
"customer_id": "StripeCustomer", "customer_id": "StripeCustomer",
"email": "guest@example.com", "email": "guest@example.com",
"name": "John Doe", "name": "John Doe",