mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-29 17:19:15 +08:00
fix: status goes from pending to partially captured in psync (#2915)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
This commit is contained in:
@ -321,12 +321,15 @@ pub enum PaymentAttemptUpdate {
|
|||||||
error_message: Option<Option<String>>,
|
error_message: Option<Option<String>>,
|
||||||
error_reason: Option<Option<String>>,
|
error_reason: Option<Option<String>>,
|
||||||
amount_capturable: Option<i64>,
|
amount_capturable: Option<i64>,
|
||||||
|
surcharge_amount: Option<i64>,
|
||||||
|
tax_amount: Option<i64>,
|
||||||
updated_by: String,
|
updated_by: String,
|
||||||
unified_code: Option<Option<String>>,
|
unified_code: Option<Option<String>>,
|
||||||
unified_message: Option<Option<String>>,
|
unified_message: Option<Option<String>>,
|
||||||
},
|
},
|
||||||
MultipleCaptureCountUpdate {
|
CaptureUpdate {
|
||||||
multiple_capture_count: i16,
|
amount_to_capture: Option<i64>,
|
||||||
|
multiple_capture_count: Option<i16>,
|
||||||
updated_by: String,
|
updated_by: String,
|
||||||
},
|
},
|
||||||
AmountToCaptureUpdate {
|
AmountToCaptureUpdate {
|
||||||
|
|||||||
@ -238,12 +238,15 @@ pub enum PaymentAttemptUpdate {
|
|||||||
error_message: Option<Option<String>>,
|
error_message: Option<Option<String>>,
|
||||||
error_reason: Option<Option<String>>,
|
error_reason: Option<Option<String>>,
|
||||||
amount_capturable: Option<i64>,
|
amount_capturable: Option<i64>,
|
||||||
|
surcharge_amount: Option<i64>,
|
||||||
|
tax_amount: Option<i64>,
|
||||||
updated_by: String,
|
updated_by: String,
|
||||||
unified_code: Option<Option<String>>,
|
unified_code: Option<Option<String>>,
|
||||||
unified_message: Option<Option<String>>,
|
unified_message: Option<Option<String>>,
|
||||||
},
|
},
|
||||||
MultipleCaptureCountUpdate {
|
CaptureUpdate {
|
||||||
multiple_capture_count: i16,
|
amount_to_capture: Option<i64>,
|
||||||
|
multiple_capture_count: Option<i16>,
|
||||||
updated_by: String,
|
updated_by: String,
|
||||||
},
|
},
|
||||||
AmountToCaptureUpdate {
|
AmountToCaptureUpdate {
|
||||||
@ -535,6 +538,8 @@ impl From<PaymentAttemptUpdate> for PaymentAttemptUpdateInternal {
|
|||||||
error_message,
|
error_message,
|
||||||
error_reason,
|
error_reason,
|
||||||
amount_capturable,
|
amount_capturable,
|
||||||
|
surcharge_amount,
|
||||||
|
tax_amount,
|
||||||
updated_by,
|
updated_by,
|
||||||
unified_code,
|
unified_code,
|
||||||
unified_message,
|
unified_message,
|
||||||
@ -547,6 +552,8 @@ impl From<PaymentAttemptUpdate> for PaymentAttemptUpdateInternal {
|
|||||||
error_reason,
|
error_reason,
|
||||||
amount_capturable,
|
amount_capturable,
|
||||||
updated_by,
|
updated_by,
|
||||||
|
surcharge_amount,
|
||||||
|
tax_amount,
|
||||||
unified_code,
|
unified_code,
|
||||||
unified_message,
|
unified_message,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
@ -618,12 +625,14 @@ impl From<PaymentAttemptUpdate> for PaymentAttemptUpdateInternal {
|
|||||||
updated_by,
|
updated_by,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
PaymentAttemptUpdate::MultipleCaptureCountUpdate {
|
PaymentAttemptUpdate::CaptureUpdate {
|
||||||
multiple_capture_count,
|
multiple_capture_count,
|
||||||
updated_by,
|
updated_by,
|
||||||
|
amount_to_capture,
|
||||||
} => Self {
|
} => Self {
|
||||||
multiple_capture_count: Some(multiple_capture_count),
|
multiple_capture_count,
|
||||||
updated_by,
|
updated_by,
|
||||||
|
amount_to_capture,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
PaymentAttemptUpdate::AmountToCaptureUpdate {
|
PaymentAttemptUpdate::AmountToCaptureUpdate {
|
||||||
|
|||||||
@ -24,7 +24,10 @@ use crate::{
|
|||||||
payments::PaymentData,
|
payments::PaymentData,
|
||||||
},
|
},
|
||||||
pii::PeekInterface,
|
pii::PeekInterface,
|
||||||
types::{self, api, transformers::ForeignTryFrom, PaymentsCancelData, ResponseId},
|
types::{
|
||||||
|
self, api, storage::payment_attempt::PaymentAttemptExt, transformers::ForeignTryFrom,
|
||||||
|
PaymentsCancelData, ResponseId,
|
||||||
|
},
|
||||||
utils::{OptionExt, ValueExt},
|
utils::{OptionExt, ValueExt},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -108,11 +111,20 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
enums::AttemptStatus::Charged => {
|
enums::AttemptStatus::Charged => {
|
||||||
let captured_amount = types::Capturable::get_capture_amount(&self.request);
|
let captured_amount = if self.request.is_psync() {
|
||||||
if Some(payment_data.payment_intent.amount) == captured_amount {
|
payment_data
|
||||||
enums::AttemptStatus::Charged
|
.payment_attempt
|
||||||
|
.amount_to_capture
|
||||||
|
.or(Some(payment_data.payment_attempt.get_total_amount()))
|
||||||
} else {
|
} else {
|
||||||
|
types::Capturable::get_capture_amount(&self.request)
|
||||||
|
};
|
||||||
|
if Some(payment_data.payment_attempt.get_total_amount()) == captured_amount {
|
||||||
|
enums::AttemptStatus::Charged
|
||||||
|
} else if captured_amount.is_some() {
|
||||||
enums::AttemptStatus::PartialCharged
|
enums::AttemptStatus::PartialCharged
|
||||||
|
} else {
|
||||||
|
self.status
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => self.status,
|
_ => self.status,
|
||||||
|
|||||||
@ -600,6 +600,29 @@ 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
|
||||||
|
#[instrument(skip_all)]
|
||||||
|
pub fn validate_amount_to_capture_in_create_call_request(
|
||||||
|
request: &api_models::payments::PaymentsRequest,
|
||||||
|
) -> CustomResult<(), errors::ApiErrorResponse> {
|
||||||
|
if request.capture_method.unwrap_or_default() == api_enums::CaptureMethod::Automatic
|
||||||
|
&& request.confirm.unwrap_or(false)
|
||||||
|
{
|
||||||
|
if let Some((amount_to_capture, amount)) = request.amount_to_capture.zip(request.amount) {
|
||||||
|
let amount_int: i64 = amount.into();
|
||||||
|
utils::when(amount_to_capture != amount_int, || {
|
||||||
|
Err(report!(errors::ApiErrorResponse::PreconditionFailed {
|
||||||
|
message: "amount_to_capture must be equal to amount when confirm = true and capture_method = automatic".into()
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[instrument(skip_all)]
|
#[instrument(skip_all)]
|
||||||
pub fn validate_card_data(
|
pub fn validate_card_data(
|
||||||
payment_method_data: Option<api::PaymentMethodData>,
|
payment_method_data: Option<api::PaymentMethodData>,
|
||||||
|
|||||||
@ -251,20 +251,29 @@ impl<F: Clone, Ctx: PaymentMethodRetrieve>
|
|||||||
where
|
where
|
||||||
F: 'b + Send,
|
F: 'b + Send,
|
||||||
{
|
{
|
||||||
payment_data.payment_attempt = match &payment_data.multiple_capture_data {
|
payment_data.payment_attempt = if payment_data.multiple_capture_data.is_some()
|
||||||
Some(multiple_capture_data) => db
|
|| payment_data.payment_attempt.amount_to_capture.is_some()
|
||||||
.store
|
{
|
||||||
|
let multiple_capture_count = payment_data
|
||||||
|
.multiple_capture_data
|
||||||
|
.as_ref()
|
||||||
|
.map(|multiple_capture_data| multiple_capture_data.get_captures_count())
|
||||||
|
.transpose()?;
|
||||||
|
let amount_to_capture = payment_data.payment_attempt.amount_to_capture;
|
||||||
|
db.store
|
||||||
.update_payment_attempt_with_attempt_id(
|
.update_payment_attempt_with_attempt_id(
|
||||||
payment_data.payment_attempt,
|
payment_data.payment_attempt,
|
||||||
storage::PaymentAttemptUpdate::MultipleCaptureCountUpdate {
|
storage::PaymentAttemptUpdate::CaptureUpdate {
|
||||||
multiple_capture_count: multiple_capture_data.get_captures_count()?,
|
amount_to_capture,
|
||||||
|
multiple_capture_count,
|
||||||
updated_by: storage_scheme.to_string(),
|
updated_by: storage_scheme.to_string(),
|
||||||
},
|
},
|
||||||
storage_scheme,
|
storage_scheme,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?,
|
.to_not_found_response(errors::ApiErrorResponse::InternalServerError)?
|
||||||
None => payment_data.payment_attempt,
|
} else {
|
||||||
|
payment_data.payment_attempt
|
||||||
};
|
};
|
||||||
Ok((Box::new(self), payment_data))
|
Ok((Box::new(self), payment_data))
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
use api_models::{enums::FrmSuggestion, payment_methods};
|
use api_models::enums::FrmSuggestion;
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use common_utils::ext_traits::{AsyncExt, Encode, ValueExt};
|
use common_utils::ext_traits::{AsyncExt, Encode, ValueExt};
|
||||||
use data_models::{mandates::MandateData, payments::payment_attempt::PaymentAttempt};
|
use data_models::{mandates::MandateData, payments::payment_attempt::PaymentAttempt};
|
||||||
@ -279,15 +279,7 @@ impl<F: Send + Clone, Ctx: PaymentMethodRetrieve>
|
|||||||
let setup_mandate = setup_mandate.map(MandateData::from);
|
let setup_mandate = setup_mandate.map(MandateData::from);
|
||||||
|
|
||||||
let surcharge_details = request.surcharge_details.map(|surcharge_details| {
|
let surcharge_details = request.surcharge_details.map(|surcharge_details| {
|
||||||
payment_methods::SurchargeDetailsResponse {
|
surcharge_details.get_surcharge_details_object(payment_attempt.amount)
|
||||||
surcharge: payment_methods::Surcharge::Fixed(surcharge_details.surcharge_amount),
|
|
||||||
tax_on_surcharge: None,
|
|
||||||
surcharge_amount: surcharge_details.surcharge_amount,
|
|
||||||
tax_on_surcharge_amount: surcharge_details.tax_amount.unwrap_or(0),
|
|
||||||
final_amount: payment_attempt.amount
|
|
||||||
+ surcharge_details.surcharge_amount
|
|
||||||
+ surcharge_details.tax_amount.unwrap_or(0),
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
let payment_data = PaymentData {
|
let payment_data = PaymentData {
|
||||||
@ -546,6 +538,8 @@ impl<F: Send + Clone, Ctx: PaymentMethodRetrieve> ValidateRequest<F, api::Paymen
|
|||||||
expected_format: "amount_to_capture lesser than amount".to_string(),
|
expected_format: "amount_to_capture lesser than amount".to_string(),
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
helpers::validate_amount_to_capture_in_create_call_request(request)?;
|
||||||
|
|
||||||
helpers::validate_card_data(request.payment_method_data.clone())?;
|
helpers::validate_card_data(request.payment_method_data.clone())?;
|
||||||
|
|
||||||
helpers::validate_payment_method_fields_present(request)?;
|
helpers::validate_payment_method_fields_present(request)?;
|
||||||
|
|||||||
@ -372,6 +372,8 @@ async fn payment_response_update_tracker<F: Clone, T: types::Capturable>(
|
|||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
},
|
},
|
||||||
|
surcharge_amount: router_data.request.get_surcharge_amount(),
|
||||||
|
tax_amount: router_data.request.get_tax_on_surcharge_amount(),
|
||||||
updated_by: storage_scheme.to_string(),
|
updated_by: storage_scheme.to_string(),
|
||||||
unified_code: option_gsm.clone().map(|gsm| gsm.unified_code),
|
unified_code: option_gsm.clone().map(|gsm| gsm.unified_code),
|
||||||
unified_message: option_gsm.map(|gsm| gsm.unified_message),
|
unified_message: option_gsm.map(|gsm| gsm.unified_message),
|
||||||
|
|||||||
@ -410,6 +410,8 @@ where
|
|||||||
status: storage_enums::AttemptStatus::Failure,
|
status: storage_enums::AttemptStatus::Failure,
|
||||||
error_reason: Some(error_response.reason.clone()),
|
error_reason: Some(error_response.reason.clone()),
|
||||||
amount_capturable: Some(0),
|
amount_capturable: Some(0),
|
||||||
|
surcharge_amount: None,
|
||||||
|
tax_amount: None,
|
||||||
updated_by: storage_scheme.to_string(),
|
updated_by: storage_scheme.to_string(),
|
||||||
unified_code: option_gsm.clone().map(|gsm| gsm.unified_code),
|
unified_code: option_gsm.clone().map(|gsm| gsm.unified_code),
|
||||||
unified_message: option_gsm.map(|gsm| gsm.unified_message),
|
unified_message: option_gsm.map(|gsm| gsm.unified_message),
|
||||||
|
|||||||
@ -545,7 +545,7 @@ pub struct AccessTokenRequestData {
|
|||||||
|
|
||||||
pub trait Capturable {
|
pub trait Capturable {
|
||||||
fn get_capture_amount(&self) -> Option<i64> {
|
fn get_capture_amount(&self) -> Option<i64> {
|
||||||
Some(0)
|
None
|
||||||
}
|
}
|
||||||
fn get_surcharge_amount(&self) -> Option<i64> {
|
fn get_surcharge_amount(&self) -> Option<i64> {
|
||||||
None
|
None
|
||||||
@ -553,6 +553,9 @@ 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 {
|
||||||
@ -591,7 +594,11 @@ impl Capturable for PaymentsCancelData {}
|
|||||||
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 {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct AddAccessTokenResult {
|
pub struct AddAccessTokenResult {
|
||||||
pub access_token_result: Result<Option<AccessToken>, ErrorResponse>,
|
pub access_token_result: Result<Option<AccessToken>, ErrorResponse>,
|
||||||
|
|||||||
@ -135,6 +135,8 @@ impl ProcessTrackerWorkflow<AppState> for PaymentsSyncWorkflow {
|
|||||||
consts::REQUEST_TIMEOUT_ERROR_MESSAGE_FROM_PSYNC.to_string(),
|
consts::REQUEST_TIMEOUT_ERROR_MESSAGE_FROM_PSYNC.to_string(),
|
||||||
)),
|
)),
|
||||||
amount_capturable: Some(0),
|
amount_capturable: Some(0),
|
||||||
|
surcharge_amount: None,
|
||||||
|
tax_amount: None,
|
||||||
updated_by: merchant_account.storage_scheme.to_string(),
|
updated_by: merchant_account.storage_scheme.to_string(),
|
||||||
unified_code: None,
|
unified_code: None,
|
||||||
unified_message: None,
|
unified_message: None,
|
||||||
|
|||||||
@ -1320,6 +1320,8 @@ impl DataModelExt for PaymentAttemptUpdate {
|
|||||||
error_message,
|
error_message,
|
||||||
error_reason,
|
error_reason,
|
||||||
amount_capturable,
|
amount_capturable,
|
||||||
|
tax_amount,
|
||||||
|
surcharge_amount,
|
||||||
updated_by,
|
updated_by,
|
||||||
unified_code,
|
unified_code,
|
||||||
unified_message,
|
unified_message,
|
||||||
@ -1330,16 +1332,20 @@ impl DataModelExt for PaymentAttemptUpdate {
|
|||||||
error_message,
|
error_message,
|
||||||
error_reason,
|
error_reason,
|
||||||
amount_capturable,
|
amount_capturable,
|
||||||
|
surcharge_amount,
|
||||||
|
tax_amount,
|
||||||
updated_by,
|
updated_by,
|
||||||
unified_code,
|
unified_code,
|
||||||
unified_message,
|
unified_message,
|
||||||
},
|
},
|
||||||
Self::MultipleCaptureCountUpdate {
|
Self::CaptureUpdate {
|
||||||
multiple_capture_count,
|
multiple_capture_count,
|
||||||
updated_by,
|
updated_by,
|
||||||
} => DieselPaymentAttemptUpdate::MultipleCaptureCountUpdate {
|
amount_to_capture,
|
||||||
|
} => DieselPaymentAttemptUpdate::CaptureUpdate {
|
||||||
multiple_capture_count,
|
multiple_capture_count,
|
||||||
updated_by,
|
updated_by,
|
||||||
|
amount_to_capture,
|
||||||
},
|
},
|
||||||
Self::PreprocessingUpdate {
|
Self::PreprocessingUpdate {
|
||||||
status,
|
status,
|
||||||
@ -1577,6 +1583,8 @@ impl DataModelExt for PaymentAttemptUpdate {
|
|||||||
error_message,
|
error_message,
|
||||||
error_reason,
|
error_reason,
|
||||||
amount_capturable,
|
amount_capturable,
|
||||||
|
surcharge_amount,
|
||||||
|
tax_amount,
|
||||||
updated_by,
|
updated_by,
|
||||||
unified_code,
|
unified_code,
|
||||||
unified_message,
|
unified_message,
|
||||||
@ -1588,13 +1596,17 @@ impl DataModelExt for PaymentAttemptUpdate {
|
|||||||
error_reason,
|
error_reason,
|
||||||
amount_capturable,
|
amount_capturable,
|
||||||
updated_by,
|
updated_by,
|
||||||
|
surcharge_amount,
|
||||||
|
tax_amount,
|
||||||
unified_code,
|
unified_code,
|
||||||
unified_message,
|
unified_message,
|
||||||
},
|
},
|
||||||
DieselPaymentAttemptUpdate::MultipleCaptureCountUpdate {
|
DieselPaymentAttemptUpdate::CaptureUpdate {
|
||||||
|
amount_to_capture,
|
||||||
multiple_capture_count,
|
multiple_capture_count,
|
||||||
updated_by,
|
updated_by,
|
||||||
} => Self::MultipleCaptureCountUpdate {
|
} => Self::CaptureUpdate {
|
||||||
|
amount_to_capture,
|
||||||
multiple_capture_count,
|
multiple_capture_count,
|
||||||
updated_by,
|
updated_by,
|
||||||
},
|
},
|
||||||
|
|||||||
@ -25,7 +25,7 @@
|
|||||||
"business_label": "default",
|
"business_label": "default",
|
||||||
"capture_method": "automatic",
|
"capture_method": "automatic",
|
||||||
"capture_on": "2022-09-10T10:11:12Z",
|
"capture_on": "2022-09-10T10:11:12Z",
|
||||||
"amount_to_capture": 1,
|
"amount_to_capture": 6540,
|
||||||
"customer_id": "bernard123",
|
"customer_id": "bernard123",
|
||||||
"email": "guest@example.com",
|
"email": "guest@example.com",
|
||||||
"name": "John Doe",
|
"name": "John Doe",
|
||||||
|
|||||||
@ -23,7 +23,7 @@
|
|||||||
"confirm": true,
|
"confirm": true,
|
||||||
"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": 6570,
|
||||||
"customer_id": "StripeCustomer",
|
"customer_id": "StripeCustomer",
|
||||||
"email": "guest@example.com",
|
"email": "guest@example.com",
|
||||||
"name": "John Doe",
|
"name": "John Doe",
|
||||||
|
|||||||
@ -25,7 +25,7 @@
|
|||||||
"business_label": "default",
|
"business_label": "default",
|
||||||
"capture_method": "automatic",
|
"capture_method": "automatic",
|
||||||
"capture_on": "2022-09-10T10:11:12Z",
|
"capture_on": "2022-09-10T10:11:12Z",
|
||||||
"amount_to_capture": 1,
|
"amount_to_capture": 6540,
|
||||||
"customer_id": "bernard123",
|
"customer_id": "bernard123",
|
||||||
"email": "guest@example.com",
|
"email": "guest@example.com",
|
||||||
"name": "John Doe",
|
"name": "John Doe",
|
||||||
|
|||||||
@ -25,7 +25,7 @@
|
|||||||
"business_label": "default",
|
"business_label": "default",
|
||||||
"capture_method": "automatic",
|
"capture_method": "automatic",
|
||||||
"capture_on": "2022-09-10T10:11:12Z",
|
"capture_on": "2022-09-10T10:11:12Z",
|
||||||
"amount_to_capture": 1,
|
"amount_to_capture": 6540,
|
||||||
"customer_id": "bernard123",
|
"customer_id": "bernard123",
|
||||||
"email": "guest@example.com",
|
"email": "guest@example.com",
|
||||||
"name": "John Doe",
|
"name": "John Doe",
|
||||||
|
|||||||
Reference in New Issue
Block a user