mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-11-03 21:37:41 +08:00
feat(core): add psync for multiple partial captures (#1934)
This commit is contained in:
@ -5,8 +5,9 @@ pub mod helpers;
|
||||
pub mod operations;
|
||||
pub mod tokenization;
|
||||
pub mod transformers;
|
||||
pub mod types;
|
||||
|
||||
use std::{collections::HashMap, fmt::Debug, marker::PhantomData, ops::Deref, time::Instant};
|
||||
use std::{fmt::Debug, marker::PhantomData, ops::Deref, time::Instant};
|
||||
|
||||
use api_models::payments::FrmMessage;
|
||||
use common_utils::{ext_traits::AsyncExt, pii};
|
||||
@ -36,7 +37,7 @@ use crate::{
|
||||
scheduler::{utils as pt_utils, workflows::payment_sync},
|
||||
services::{self, api::Authenticate},
|
||||
types::{
|
||||
self, api, domain,
|
||||
self as router_types, api, domain,
|
||||
storage::{self, enums as storage_enums, ProcessTrackerExt},
|
||||
},
|
||||
utils::{add_connector_http_status_code_metrics, Encode, OptionExt, ValueExt},
|
||||
@ -58,12 +59,12 @@ where
|
||||
Op: Operation<F, Req> + Send + Sync,
|
||||
|
||||
// To create connector flow specific interface data
|
||||
PaymentData<F>: ConstructFlowSpecificData<F, FData, types::PaymentsResponseData>,
|
||||
types::RouterData<F, FData, types::PaymentsResponseData>: Feature<F, FData>,
|
||||
PaymentData<F>: ConstructFlowSpecificData<F, FData, router_types::PaymentsResponseData>,
|
||||
router_types::RouterData<F, FData, router_types::PaymentsResponseData>: Feature<F, FData>,
|
||||
|
||||
// To construct connector flow specific api
|
||||
dyn types::api::Connector:
|
||||
services::api::ConnectorIntegration<F, FData, types::PaymentsResponseData>,
|
||||
dyn router_types::api::Connector:
|
||||
services::api::ConnectorIntegration<F, FData, router_types::PaymentsResponseData>,
|
||||
|
||||
// To perform router related operation for PaymentResponse
|
||||
PaymentResponse: Operation<F, FData>,
|
||||
@ -251,12 +252,12 @@ where
|
||||
Req: Debug + Authenticate,
|
||||
Res: transformers::ToResponse<Req, PaymentData<F>, Op>,
|
||||
// To create connector flow specific interface data
|
||||
PaymentData<F>: ConstructFlowSpecificData<F, FData, types::PaymentsResponseData>,
|
||||
types::RouterData<F, FData, types::PaymentsResponseData>: Feature<F, FData>,
|
||||
PaymentData<F>: ConstructFlowSpecificData<F, FData, router_types::PaymentsResponseData>,
|
||||
router_types::RouterData<F, FData, router_types::PaymentsResponseData>: Feature<F, FData>,
|
||||
|
||||
// To construct connector flow specific api
|
||||
dyn types::api::Connector:
|
||||
services::api::ConnectorIntegration<F, FData, types::PaymentsResponseData>,
|
||||
dyn router_types::api::Connector:
|
||||
services::api::ConnectorIntegration<F, FData, router_types::PaymentsResponseData>,
|
||||
|
||||
// To perform router related operation for PaymentResponse
|
||||
PaymentResponse: Operation<F, FData>,
|
||||
@ -315,7 +316,7 @@ pub trait PaymentRedirectFlow: Sync {
|
||||
fn generate_response(
|
||||
&self,
|
||||
payments_response: api_models::payments::PaymentsResponse,
|
||||
merchant_account: types::domain::MerchantAccount,
|
||||
merchant_account: router_types::domain::MerchantAccount,
|
||||
payment_id: String,
|
||||
connector: String,
|
||||
) -> RouterResult<api::RedirectionResponse>;
|
||||
@ -435,7 +436,7 @@ impl PaymentRedirectFlow for PaymentRedirectCompleteAuthorize {
|
||||
fn generate_response(
|
||||
&self,
|
||||
payments_response: api_models::payments::PaymentsResponse,
|
||||
merchant_account: types::domain::MerchantAccount,
|
||||
merchant_account: router_types::domain::MerchantAccount,
|
||||
payment_id: String,
|
||||
connector: String,
|
||||
) -> RouterResult<api::RedirectionResponse> {
|
||||
@ -524,7 +525,7 @@ impl PaymentRedirectFlow for PaymentRedirectSync {
|
||||
fn generate_response(
|
||||
&self,
|
||||
payments_response: api_models::payments::PaymentsResponse,
|
||||
merchant_account: types::domain::MerchantAccount,
|
||||
merchant_account: router_types::domain::MerchantAccount,
|
||||
payment_id: String,
|
||||
connector: String,
|
||||
) -> RouterResult<api::RedirectionResponse> {
|
||||
@ -555,18 +556,19 @@ pub async fn call_connector_service<F, RouterDReq, ApiRequest>(
|
||||
updated_customer: Option<storage::CustomerUpdate>,
|
||||
requeue: bool,
|
||||
schedule_time: Option<time::PrimitiveDateTime>,
|
||||
) -> RouterResult<types::RouterData<F, RouterDReq, types::PaymentsResponseData>>
|
||||
) -> RouterResult<router_types::RouterData<F, RouterDReq, router_types::PaymentsResponseData>>
|
||||
where
|
||||
F: Send + Clone + Sync,
|
||||
RouterDReq: Send + Sync,
|
||||
|
||||
// To create connector flow specific interface data
|
||||
PaymentData<F>: ConstructFlowSpecificData<F, RouterDReq, types::PaymentsResponseData>,
|
||||
types::RouterData<F, RouterDReq, types::PaymentsResponseData>: Feature<F, RouterDReq> + Send,
|
||||
PaymentData<F>: ConstructFlowSpecificData<F, RouterDReq, router_types::PaymentsResponseData>,
|
||||
router_types::RouterData<F, RouterDReq, router_types::PaymentsResponseData>:
|
||||
Feature<F, RouterDReq> + Send,
|
||||
|
||||
// To construct connector flow specific api
|
||||
dyn api::Connector:
|
||||
services::api::ConnectorIntegration<F, RouterDReq, types::PaymentsResponseData>,
|
||||
services::api::ConnectorIntegration<F, RouterDReq, router_types::PaymentsResponseData>,
|
||||
{
|
||||
let stime_connector = Instant::now();
|
||||
|
||||
@ -607,7 +609,7 @@ where
|
||||
)
|
||||
.await?;
|
||||
|
||||
if let Ok(types::PaymentsResponseData::PreProcessingResponse {
|
||||
if let Ok(router_types::PaymentsResponseData::PreProcessingResponse {
|
||||
session_token: Some(session_token),
|
||||
..
|
||||
}) = router_data.response.to_owned()
|
||||
@ -697,11 +699,12 @@ where
|
||||
F: Send + Clone,
|
||||
|
||||
// To create connector flow specific interface data
|
||||
PaymentData<F>: ConstructFlowSpecificData<F, Req, types::PaymentsResponseData>,
|
||||
types::RouterData<F, Req, types::PaymentsResponseData>: Feature<F, Req>,
|
||||
PaymentData<F>: ConstructFlowSpecificData<F, Req, router_types::PaymentsResponseData>,
|
||||
router_types::RouterData<F, Req, router_types::PaymentsResponseData>: Feature<F, Req>,
|
||||
|
||||
// To construct connector flow specific api
|
||||
dyn api::Connector: services::api::ConnectorIntegration<F, Req, types::PaymentsResponseData>,
|
||||
dyn api::Connector:
|
||||
services::api::ConnectorIntegration<F, Req, router_types::PaymentsResponseData>,
|
||||
|
||||
// To perform router related operation for PaymentResponse
|
||||
PaymentResponse: Operation<F, Req>,
|
||||
@ -733,8 +736,10 @@ where
|
||||
let connector_name = session_connector.connector.connector_name.to_string();
|
||||
match connector_res {
|
||||
Ok(connector_response) => {
|
||||
if let Ok(types::PaymentsResponseData::SessionResponse { session_token, .. }) =
|
||||
connector_response.response
|
||||
if let Ok(router_types::PaymentsResponseData::SessionResponse {
|
||||
session_token,
|
||||
..
|
||||
}) = connector_response.response
|
||||
{
|
||||
// If session token is NoSessionTokenReceived, it is not pushed into the sessions_token as there is no response or there can be some error
|
||||
// In case of error, that error is already logged
|
||||
@ -776,11 +781,12 @@ where
|
||||
Req: Send + Sync,
|
||||
|
||||
// To create connector flow specific interface data
|
||||
PaymentData<F>: ConstructFlowSpecificData<F, Req, types::PaymentsResponseData>,
|
||||
types::RouterData<F, Req, types::PaymentsResponseData>: Feature<F, Req> + Send,
|
||||
PaymentData<F>: ConstructFlowSpecificData<F, Req, router_types::PaymentsResponseData>,
|
||||
router_types::RouterData<F, Req, router_types::PaymentsResponseData>: Feature<F, Req> + Send,
|
||||
|
||||
// To construct connector flow specific api
|
||||
dyn api::Connector: services::api::ConnectorIntegration<F, Req, types::PaymentsResponseData>,
|
||||
dyn api::Connector:
|
||||
services::api::ConnectorIntegration<F, Req, router_types::PaymentsResponseData>,
|
||||
|
||||
// To perform router related operation for PaymentResponse
|
||||
PaymentResponse: Operation<F, Req>,
|
||||
@ -850,21 +856,25 @@ async fn complete_preprocessing_steps_if_required<F, Req>(
|
||||
state: &AppState,
|
||||
connector: &api::ConnectorData,
|
||||
payment_data: &PaymentData<F>,
|
||||
mut router_data: types::RouterData<F, Req, types::PaymentsResponseData>,
|
||||
mut router_data: router_types::RouterData<F, Req, router_types::PaymentsResponseData>,
|
||||
should_continue_payment: bool,
|
||||
) -> RouterResult<(types::RouterData<F, Req, types::PaymentsResponseData>, bool)>
|
||||
) -> RouterResult<(
|
||||
router_types::RouterData<F, Req, router_types::PaymentsResponseData>,
|
||||
bool,
|
||||
)>
|
||||
where
|
||||
F: Send + Clone + Sync,
|
||||
Req: Send + Sync,
|
||||
types::RouterData<F, Req, types::PaymentsResponseData>: Feature<F, Req> + Send,
|
||||
dyn api::Connector: services::api::ConnectorIntegration<F, Req, types::PaymentsResponseData>,
|
||||
router_types::RouterData<F, Req, router_types::PaymentsResponseData>: Feature<F, Req> + Send,
|
||||
dyn api::Connector:
|
||||
services::api::ConnectorIntegration<F, Req, router_types::PaymentsResponseData>,
|
||||
{
|
||||
//TODO: For ACH transfers, if preprocessing_step is not required for connectors encountered in future, add the check
|
||||
let router_data_and_should_continue_payment = match payment_data.payment_method_data.clone() {
|
||||
Some(api_models::payments::PaymentMethodData::BankTransfer(data)) => match data.deref() {
|
||||
api_models::payments::BankTransferData::AchBankTransfer { .. }
|
||||
| api_models::payments::BankTransferData::MultibancoBankTransfer { .. }
|
||||
if connector.connector_name == types::Connector::Stripe =>
|
||||
if connector.connector_name == router_types::Connector::Stripe =>
|
||||
{
|
||||
if payment_data.payment_attempt.preprocessing_step_id.is_none() {
|
||||
(
|
||||
@ -888,7 +898,7 @@ where
|
||||
}
|
||||
}
|
||||
Some(api_models::payments::PaymentMethodData::Card(_)) => {
|
||||
if connector.connector_name == types::Connector::Payme {
|
||||
if connector.connector_name == router_types::Connector::Payme {
|
||||
router_data = router_data.preprocessing_steps(state, connector).await?;
|
||||
|
||||
let is_error_in_response = router_data.response.is_err();
|
||||
@ -1118,7 +1128,7 @@ where
|
||||
pub flow: PhantomData<F>,
|
||||
pub payment_intent: storage::PaymentIntent,
|
||||
pub payment_attempt: storage::PaymentAttempt,
|
||||
pub multiple_capture_data: Option<MultipleCaptureData>,
|
||||
pub multiple_capture_data: Option<types::MultipleCaptureData>,
|
||||
pub connector_response: storage::ConnectorResponse,
|
||||
pub amount: api::Amount,
|
||||
pub mandate_id: Option<api_models::payments::MandateIds>,
|
||||
@ -1145,96 +1155,6 @@ where
|
||||
pub frm_message: Option<FrmMessage>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct MultipleCaptureData {
|
||||
previous_captures: Vec<storage::Capture>,
|
||||
current_capture: storage::Capture,
|
||||
}
|
||||
|
||||
impl MultipleCaptureData {
|
||||
fn get_previously_blocked_amount(&self) -> i64 {
|
||||
self.previous_captures
|
||||
.iter()
|
||||
.fold(0, |accumulator, capture| {
|
||||
accumulator
|
||||
+ match capture.status {
|
||||
storage_enums::CaptureStatus::Charged
|
||||
| storage_enums::CaptureStatus::Pending => capture.amount,
|
||||
storage_enums::CaptureStatus::Started
|
||||
| storage_enums::CaptureStatus::Failed => 0,
|
||||
}
|
||||
})
|
||||
}
|
||||
fn get_total_blocked_amount(&self) -> i64 {
|
||||
self.get_previously_blocked_amount()
|
||||
+ match self.current_capture.status {
|
||||
api_models::enums::CaptureStatus::Charged
|
||||
| api_models::enums::CaptureStatus::Pending => self.current_capture.amount,
|
||||
api_models::enums::CaptureStatus::Failed
|
||||
| api_models::enums::CaptureStatus::Started => 0,
|
||||
}
|
||||
}
|
||||
fn get_previously_charged_amount(&self) -> i64 {
|
||||
self.previous_captures
|
||||
.iter()
|
||||
.fold(0, |accumulator, capture| {
|
||||
accumulator
|
||||
+ match capture.status {
|
||||
storage_enums::CaptureStatus::Charged => capture.amount,
|
||||
storage_enums::CaptureStatus::Pending
|
||||
| storage_enums::CaptureStatus::Started
|
||||
| storage_enums::CaptureStatus::Failed => 0,
|
||||
}
|
||||
})
|
||||
}
|
||||
fn get_total_charged_amount(&self) -> i64 {
|
||||
self.get_previously_charged_amount()
|
||||
+ match self.current_capture.status {
|
||||
storage_enums::CaptureStatus::Charged => self.current_capture.amount,
|
||||
storage_enums::CaptureStatus::Pending
|
||||
| storage_enums::CaptureStatus::Started
|
||||
| storage_enums::CaptureStatus::Failed => 0,
|
||||
}
|
||||
}
|
||||
fn get_captures_count(&self) -> RouterResult<i16> {
|
||||
i16::try_from(1 + self.previous_captures.len())
|
||||
.into_report()
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable("Error while converting from usize to i16")
|
||||
}
|
||||
fn get_status_count(&self) -> HashMap<storage_enums::CaptureStatus, i16> {
|
||||
let mut hash_map: HashMap<storage_enums::CaptureStatus, i16> = HashMap::new();
|
||||
hash_map.insert(storage_enums::CaptureStatus::Charged, 0);
|
||||
hash_map.insert(storage_enums::CaptureStatus::Pending, 0);
|
||||
hash_map.insert(storage_enums::CaptureStatus::Started, 0);
|
||||
hash_map.insert(storage_enums::CaptureStatus::Failed, 0);
|
||||
hash_map
|
||||
.entry(self.current_capture.status)
|
||||
.and_modify(|count| *count += 1);
|
||||
self.previous_captures
|
||||
.iter()
|
||||
.fold(hash_map, |mut accumulator, capture| {
|
||||
let current_capture_status = capture.status;
|
||||
accumulator
|
||||
.entry(current_capture_status)
|
||||
.and_modify(|count| *count += 1);
|
||||
accumulator
|
||||
})
|
||||
}
|
||||
fn get_attempt_status(&self, authorized_amount: i64) -> storage_enums::AttemptStatus {
|
||||
let total_captured_amount = self.get_total_charged_amount();
|
||||
if authorized_amount == total_captured_amount {
|
||||
return storage_enums::AttemptStatus::Charged;
|
||||
}
|
||||
let status_count_map = self.get_status_count();
|
||||
if status_count_map.get(&storage_enums::CaptureStatus::Charged) > Some(&0) {
|
||||
storage_enums::AttemptStatus::PartialCharged
|
||||
} else {
|
||||
storage_enums::AttemptStatus::CaptureInitiated
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct RecurringMandatePaymentData {
|
||||
pub payment_method_type: Option<storage_enums::PaymentMethodType>, //required for making recurring payment using saved payment method through stripe
|
||||
|
||||
Reference in New Issue
Block a user