mod payment_cancel; mod payment_capture; mod payment_confirm; mod payment_create; mod payment_method_validate; mod payment_response; mod payment_session; mod payment_start; mod payment_status; mod payment_update; use async_trait::async_trait; use error_stack::{report, IntoReport, ResultExt}; pub use payment_cancel::PaymentCancel; pub use payment_capture::PaymentCapture; pub use payment_confirm::PaymentConfirm; pub use payment_create::PaymentCreate; pub use payment_method_validate::PaymentMethodValidate; pub use payment_response::PaymentResponse; pub use payment_session::PaymentSession; pub use payment_start::PaymentStart; pub use payment_status::PaymentStatus; pub use payment_update::PaymentUpdate; use router_env::{instrument, tracing}; use storage::Customer; use super::{helpers, CustomerDetails, PaymentData}; use crate::{ core::errors::{self, CustomResult, RouterResult}, db::StorageInterface, routes::AppState, scheduler::{metrics, workflows::payment_sync}, types::{ self, api, storage::{self, enums}, PaymentsResponseData, }, }; pub type BoxedOperation<'a, F, T> = Box + Send + Sync + 'a>; pub trait Operation: Send + std::fmt::Debug { fn to_validate_request(&self) -> RouterResult<&(dyn ValidateRequest + Send + Sync)> { Err(report!(errors::ApiErrorResponse::InternalServerError)) .attach_printable_lazy(|| format!("validate request interface not found for {self:?}")) } fn to_get_tracker( &self, ) -> RouterResult<&(dyn GetTracker, T> + Send + Sync)> { Err(report!(errors::ApiErrorResponse::InternalServerError)) .attach_printable_lazy(|| format!("get tracker interface not found for {self:?}")) } fn to_domain(&self) -> RouterResult<&dyn Domain> { Err(report!(errors::ApiErrorResponse::InternalServerError)) .attach_printable_lazy(|| format!("domain interface not found for {self:?}")) } fn to_update_tracker( &self, ) -> RouterResult<&(dyn UpdateTracker, T> + Send + Sync)> { Err(report!(errors::ApiErrorResponse::InternalServerError)) .attach_printable_lazy(|| format!("update tracker interface not found for {self:?}")) } fn to_post_update_tracker( &self, ) -> RouterResult<&(dyn PostUpdateTracker, T> + Send + Sync)> { Err(report!(errors::ApiErrorResponse::InternalServerError)).attach_printable_lazy(|| { format!("post connector update tracker not found for {self:?}") }) } } pub struct ValidateResult<'a> { pub merchant_id: &'a str, pub payment_id: api::PaymentIdType, pub mandate_type: Option, pub storage_scheme: enums::MerchantStorageScheme, } #[allow(clippy::type_complexity)] pub trait ValidateRequest { fn validate_request<'a, 'b>( &'b self, request: &R, merchant_account: &'a storage::MerchantAccount, ) -> RouterResult<(BoxedOperation<'b, F, R>, ValidateResult<'a>)>; } #[async_trait] pub trait GetTracker: Send { #[allow(clippy::too_many_arguments)] async fn get_trackers<'a>( &'a self, state: &'a AppState, payment_id: &api::PaymentIdType, merchant_id: &str, connector: types::Connector, request: &R, mandate_type: Option, storage_scheme: enums::MerchantStorageScheme, ) -> RouterResult<(BoxedOperation<'a, F, R>, D, Option)>; } #[async_trait] pub trait Domain: Send + Sync { /// This will fetch customer details, (this operation is flow specific) async fn get_or_create_customer_details<'a>( &'a self, db: &dyn StorageInterface, payment_data: &mut PaymentData, request: Option, merchant_id: &str, ) -> CustomResult<(BoxedOperation<'a, F, R>, Option), errors::StorageError>; #[allow(clippy::too_many_arguments)] async fn make_pm_data<'a>( &'a self, state: &'a AppState, _payment_method: Option, txn_id: &str, payment_attempt: &storage::PaymentAttempt, request: &Option, token: &Option, storage_scheme: enums::MerchantStorageScheme, ) -> RouterResult<(BoxedOperation<'a, F, R>, Option)>; async fn add_task_to_process_tracker<'a>( &'a self, _db: &'a AppState, _payment_attempt: &storage::PaymentAttempt, ) -> CustomResult<(), errors::ApiErrorResponse> { Ok(()) } } #[async_trait] pub trait UpdateTracker: Send { async fn update_trackers<'b>( &'b self, db: &dyn StorageInterface, payment_id: &api::PaymentIdType, payment_data: D, customer: Option, storage_scheme: enums::MerchantStorageScheme, ) -> RouterResult<(BoxedOperation<'b, F, R>, D)> where F: 'b + Send; } #[async_trait] pub trait PostUpdateTracker: Send { async fn update_tracker<'b>( &'b self, db: &dyn StorageInterface, payment_id: &api::PaymentIdType, payment_data: D, response: Option>, storage_scheme: enums::MerchantStorageScheme, ) -> RouterResult where F: 'b + Send; } #[async_trait] impl> Domain for Op where for<'a> &'a Op: Operation + std::fmt::Debug, { #[instrument(skip_all)] async fn get_or_create_customer_details<'a>( &'a self, db: &dyn StorageInterface, payment_data: &mut PaymentData, request: Option, merchant_id: &str, ) -> CustomResult< ( BoxedOperation<'a, F, api::PaymentsRequest>, Option, ), errors::StorageError, > { helpers::create_customer_if_not_exist( Box::new(self), db, payment_data, request, merchant_id, ) .await } #[instrument(skip_all)] async fn make_pm_data<'a>( &'a self, state: &'a AppState, payment_method: Option, txn_id: &str, payment_attempt: &storage::PaymentAttempt, request: &Option, token: &Option, _storage_scheme: enums::MerchantStorageScheme, ) -> RouterResult<( BoxedOperation<'a, F, api::PaymentsRequest>, Option, )> { helpers::make_pm_data( Box::new(self), state, payment_method, txn_id, payment_attempt, request, token, ) .await } #[instrument(skip_all)] async fn add_task_to_process_tracker<'a>( &'a self, state: &'a AppState, payment_attempt: &storage::PaymentAttempt, ) -> CustomResult<(), errors::ApiErrorResponse> { if helpers::check_if_operation_confirm(self) { metrics::TASKS_ADDED_COUNT.add(&metrics::CONTEXT, 1, &[]); // Metrics let schedule_time = payment_sync::get_sync_process_schedule_time( &*state.store, &payment_attempt.connector, &payment_attempt.merchant_id, 0, ) .await .into_report() .change_context(errors::ApiErrorResponse::InternalServerError)?; match schedule_time { Some(stime) => super::add_process_sync_task(&*state.store, payment_attempt, stime) .await .into_report() .change_context(errors::ApiErrorResponse::InternalServerError), None => Ok(()), } } else { Ok(()) } } } #[async_trait] impl> Domain for Op where for<'a> &'a Op: Operation, { #[instrument(skip_all)] async fn get_or_create_customer_details<'a>( &'a self, db: &dyn StorageInterface, payment_data: &mut PaymentData, _request: Option, merchant_id: &str, ) -> CustomResult< ( BoxedOperation<'a, F, api::PaymentsRetrieveRequest>, Option, ), errors::StorageError, > { Ok(( Box::new(self), helpers::get_customer_from_details( db, payment_data.payment_intent.customer_id.clone(), merchant_id, ) .await?, )) } #[instrument(skip_all)] async fn make_pm_data<'a>( &'a self, state: &'a AppState, payment_method: Option, txn_id: &str, payment_attempt: &storage::PaymentAttempt, request: &Option, token: &Option, _storage_scheme: enums::MerchantStorageScheme, ) -> RouterResult<( BoxedOperation<'a, F, api::PaymentsRetrieveRequest>, Option, )> { helpers::make_pm_data( Box::new(self), state, payment_method, txn_id, payment_attempt, request, token, ) .await } } #[async_trait] impl> Domain for Op where for<'a> &'a Op: Operation, { #[instrument(skip_all)] async fn get_or_create_customer_details<'a>( &'a self, db: &dyn StorageInterface, payment_data: &mut PaymentData, _request: Option, merchant_id: &str, ) -> CustomResult< ( BoxedOperation<'a, F, api::PaymentsCaptureRequest>, Option, ), errors::StorageError, > { Ok(( Box::new(self), helpers::get_customer_from_details( db, payment_data.payment_intent.customer_id.clone(), merchant_id, ) .await?, )) } #[instrument(skip_all)] async fn make_pm_data<'a>( &'a self, _state: &'a AppState, _payment_method: Option, _txn_id: &str, _payment_attempt: &storage::PaymentAttempt, _request: &Option, _token: &Option, _storage_scheme: enums::MerchantStorageScheme, ) -> RouterResult<( BoxedOperation<'a, F, api::PaymentsCaptureRequest>, Option, )> { Ok((Box::new(self), None)) } } #[async_trait] impl> Domain for Op where for<'a> &'a Op: Operation, { #[instrument(skip_all)] async fn get_or_create_customer_details<'a>( &'a self, db: &dyn StorageInterface, payment_data: &mut PaymentData, _request: Option, merchant_id: &str, ) -> CustomResult< ( BoxedOperation<'a, F, api::PaymentsCancelRequest>, Option, ), errors::StorageError, > { Ok(( Box::new(self), helpers::get_customer_from_details( db, payment_data.payment_intent.customer_id.clone(), merchant_id, ) .await?, )) } #[instrument(skip_all)] async fn make_pm_data<'a>( &'a self, _state: &'a AppState, _payment_method: Option, _txn_id: &str, _payment_attempt: &storage::PaymentAttempt, _request: &Option, _token: &Option, _storage_scheme: enums::MerchantStorageScheme, ) -> RouterResult<( BoxedOperation<'a, F, api::PaymentsCancelRequest>, Option, )> { Ok((Box::new(self), None)) } }