feat(core): add psync for multiple partial captures (#1934)

This commit is contained in:
Hrithikesh
2023-08-23 10:13:54 +05:30
committed by GitHub
parent 1b346fcf56
commit 5657ad6933
30 changed files with 1219 additions and 1020 deletions

View File

@ -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