mirror of
				https://github.com/juspay/hyperswitch.git
				synced 2025-11-01 02:57:02 +08:00 
			
		
		
		
	feat(core): added multiple payment_attempt support for payment_intent (#439)
Co-authored-by: Nishant Joshi <nishant.joshi@juspay.in>
This commit is contained in:
		
							
								
								
									
										8
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										8
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							| @ -4369,9 +4369,9 @@ dependencies = [ | |||||||
|  |  | ||||||
| [[package]] | [[package]] | ||||||
| name = "toml_edit" | name = "toml_edit" | ||||||
| version = "0.19.7" | version = "0.19.8" | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "dc18466501acd8ac6a3f615dd29a3438f8ca6bb3b19537138b3106e575621274" | checksum = "239410c8609e8125456927e6707163a3b1fdb40561e4b803bc041f466ccfdc13" | ||||||
| dependencies = [ | dependencies = [ | ||||||
|  "indexmap", |  "indexmap", | ||||||
|  "serde", |  "serde", | ||||||
| @ -5024,9 +5024,9 @@ checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd" | |||||||
|  |  | ||||||
| [[package]] | [[package]] | ||||||
| name = "winnow" | name = "winnow" | ||||||
| version = "0.3.6" | version = "0.4.1" | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "23d020b441f92996c80d94ae9166e8501e59c7bb56121189dc9eab3bd8216966" | checksum = "ae8970b36c66498d8ff1d66685dc86b91b29db0c7739899012f63a63814b4b28" | ||||||
| dependencies = [ | dependencies = [ | ||||||
|  "memchr", |  "memchr", | ||||||
| ] | ] | ||||||
|  | |||||||
| @ -193,7 +193,7 @@ async fn drainer( | |||||||
|                         } |                         } | ||||||
|                         kv::Updateable::PaymentAttemptUpdate(a) => { |                         kv::Updateable::PaymentAttemptUpdate(a) => { | ||||||
|                             macro_util::handle_resp!( |                             macro_util::handle_resp!( | ||||||
|                                 a.orig.update(&conn, a.update_data).await, |                                 a.orig.update_with_attempt_id(&conn, a.update_data).await, | ||||||
|                                 update_op, |                                 update_op, | ||||||
|                                 payment_attempt |                                 payment_attempt | ||||||
|                             ) |                             ) | ||||||
|  | |||||||
| @ -880,7 +880,7 @@ impl<F, T> | |||||||
|                             .transaction_id |                             .transaction_id | ||||||
|                             .map_or(response.order_id, Some) // For paypal there will be no transaction_id, only order_id will be present |                             .map_or(response.order_id, Some) // For paypal there will be no transaction_id, only order_id will be present | ||||||
|                             .map(types::ResponseId::ConnectorTransactionId) |                             .map(types::ResponseId::ConnectorTransactionId) | ||||||
|                             .ok_or_else(|| errors::ConnectorError::MissingConnectorTransactionID)?, |                             .ok_or(errors::ConnectorError::MissingConnectorTransactionID)?, | ||||||
|                         redirection_data, |                         redirection_data, | ||||||
|                         mandate_reference: None, |                         mandate_reference: None, | ||||||
|                         // we don't need to save session token for capture, void flow so ignoring if it is not present |                         // we don't need to save session token for capture, void flow so ignoring if it is not present | ||||||
|  | |||||||
| @ -817,9 +817,10 @@ pub async fn list_payment_methods( | |||||||
|     let payment_attempt = payment_intent |     let payment_attempt = payment_intent | ||||||
|         .as_ref() |         .as_ref() | ||||||
|         .async_map(|pi| async { |         .async_map(|pi| async { | ||||||
|             db.find_payment_attempt_by_payment_id_merchant_id( |             db.find_payment_attempt_by_payment_id_merchant_id_attempt_id( | ||||||
|                 &pi.payment_id, |                 &pi.payment_id, | ||||||
|                 &pi.merchant_id, |                 &pi.merchant_id, | ||||||
|  |                 &pi.active_attempt_id, | ||||||
|                 merchant_account.storage_scheme, |                 merchant_account.storage_scheme, | ||||||
|             ) |             ) | ||||||
|             .await |             .await | ||||||
|  | |||||||
| @ -6,7 +6,6 @@ pub mod transformers; | |||||||
|  |  | ||||||
| use std::{fmt::Debug, marker::PhantomData, time::Instant}; | use std::{fmt::Debug, marker::PhantomData, time::Instant}; | ||||||
|  |  | ||||||
| use common_utils::ext_traits::AsyncExt; |  | ||||||
| use error_stack::{IntoReport, ResultExt}; | use error_stack::{IntoReport, ResultExt}; | ||||||
| use futures::future::join_all; | use futures::future::join_all; | ||||||
| use router_env::{instrument, tracing}; | use router_env::{instrument, tracing}; | ||||||
| @ -127,17 +126,29 @@ where | |||||||
|  |  | ||||||
|         payment_data = match connector_details { |         payment_data = match connector_details { | ||||||
|             api::ConnectorCallType::Single(connector) => { |             api::ConnectorCallType::Single(connector) => { | ||||||
|                 call_connector_service( |                 let router_data = call_connector_service( | ||||||
|                     state, |                     state, | ||||||
|                     &merchant_account, |                     &merchant_account, | ||||||
|                     &validate_result.payment_id, |  | ||||||
|                     connector, |                     connector, | ||||||
|                     &operation, |                     &operation, | ||||||
|                     payment_data, |                     &payment_data, | ||||||
|                     &customer, |                     &customer, | ||||||
|                     call_connector_action, |                     call_connector_action, | ||||||
|                 ) |                 ) | ||||||
|                 .await? |                 .await?; | ||||||
|  |  | ||||||
|  |                 let operation = Box::new(PaymentResponse); | ||||||
|  |                 let db = &*state.store; | ||||||
|  |                 operation | ||||||
|  |                     .to_post_update_tracker()? | ||||||
|  |                     .update_tracker( | ||||||
|  |                         db, | ||||||
|  |                         &validate_result.payment_id, | ||||||
|  |                         payment_data, | ||||||
|  |                         router_data, | ||||||
|  |                         merchant_account.storage_scheme, | ||||||
|  |                     ) | ||||||
|  |                     .await? | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             api::ConnectorCallType::Multiple(connectors) => { |             api::ConnectorCallType::Multiple(connectors) => { | ||||||
| @ -367,13 +378,12 @@ impl PaymentRedirectFlow for PaymentRedirectSync { | |||||||
| pub async fn call_connector_service<F, Op, Req>( | pub async fn call_connector_service<F, Op, Req>( | ||||||
|     state: &AppState, |     state: &AppState, | ||||||
|     merchant_account: &storage::MerchantAccount, |     merchant_account: &storage::MerchantAccount, | ||||||
|     payment_id: &api::PaymentIdType, |  | ||||||
|     connector: api::ConnectorData, |     connector: api::ConnectorData, | ||||||
|     _operation: &Op, |     _operation: &Op, | ||||||
|     payment_data: PaymentData<F>, |     payment_data: &PaymentData<F>, | ||||||
|     customer: &Option<storage::Customer>, |     customer: &Option<storage::Customer>, | ||||||
|     call_connector_action: CallConnectorAction, |     call_connector_action: CallConnectorAction, | ||||||
| ) -> RouterResult<PaymentData<F>> | ) -> RouterResult<types::RouterData<F, Req, types::PaymentsResponseData>> | ||||||
| where | where | ||||||
|     Op: Debug + Sync, |     Op: Debug + Sync, | ||||||
|     F: Send + Clone, |     F: Send + Clone, | ||||||
| @ -388,8 +398,6 @@ where | |||||||
|     // To perform router related operation for PaymentResponse |     // To perform router related operation for PaymentResponse | ||||||
|     PaymentResponse: Operation<F, Req>, |     PaymentResponse: Operation<F, Req>, | ||||||
| { | { | ||||||
|     let db = &*state.store; |  | ||||||
|  |  | ||||||
|     let stime_connector = Instant::now(); |     let stime_connector = Instant::now(); | ||||||
|  |  | ||||||
|     let mut router_data = payment_data |     let mut router_data = payment_data | ||||||
| @ -420,28 +428,11 @@ where | |||||||
|         Ok(router_data) |         Ok(router_data) | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     let response = router_data_res |  | ||||||
|         .async_and_then(|response| async { |  | ||||||
|             let operation = helpers::response_operation::<F, Req>(); |  | ||||||
|             let payment_data = operation |  | ||||||
|                 .to_post_update_tracker()? |  | ||||||
|                 .update_tracker( |  | ||||||
|                     db, |  | ||||||
|                     payment_id, |  | ||||||
|                     payment_data, |  | ||||||
|                     response, |  | ||||||
|                     merchant_account.storage_scheme, |  | ||||||
|                 ) |  | ||||||
|                 .await?; |  | ||||||
|             Ok(payment_data) |  | ||||||
|         }) |  | ||||||
|         .await?; |  | ||||||
|  |  | ||||||
|     let etime_connector = Instant::now(); |     let etime_connector = Instant::now(); | ||||||
|     let duration_connector = etime_connector.saturating_duration_since(stime_connector); |     let duration_connector = etime_connector.saturating_duration_since(stime_connector); | ||||||
|     tracing::info!(duration = format!("Duration taken: {}", duration_connector.as_millis())); |     tracing::info!(duration = format!("Duration taken: {}", duration_connector.as_millis())); | ||||||
|  |  | ||||||
|     Ok(response) |     router_data_res | ||||||
| } | } | ||||||
|  |  | ||||||
| pub async fn call_multiple_connectors_service<F, Op, Req>( | pub async fn call_multiple_connectors_service<F, Op, Req>( | ||||||
| @ -668,9 +659,10 @@ pub async fn list_payments( | |||||||
|     let pi = futures::stream::iter(payment_intents) |     let pi = futures::stream::iter(payment_intents) | ||||||
|         .filter_map(|pi| async { |         .filter_map(|pi| async { | ||||||
|             let pa = db |             let pa = db | ||||||
|                 .find_payment_attempt_by_payment_id_merchant_id( |                 .find_payment_attempt_by_payment_id_merchant_id_attempt_id( | ||||||
|                     &pi.payment_id, |                     &pi.payment_id, | ||||||
|                     merchant_id, |                     merchant_id, | ||||||
|  |                     &pi.active_attempt_id, | ||||||
|                     // since OLAP doesn't have KV. Force to get the data from PSQL. |                     // since OLAP doesn't have KV. Force to get the data from PSQL. | ||||||
|                     storage_enums::MerchantStorageScheme::PostgresOnly, |                     storage_enums::MerchantStorageScheme::PostgresOnly, | ||||||
|                 ) |                 ) | ||||||
|  | |||||||
| @ -56,9 +56,10 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, api::PaymentsCancelRequest> | |||||||
|             })?; |             })?; | ||||||
|  |  | ||||||
|         let mut payment_attempt = db |         let mut payment_attempt = db | ||||||
|             .find_payment_attempt_by_payment_id_merchant_id( |             .find_payment_attempt_by_payment_id_merchant_id_attempt_id( | ||||||
|                 &payment_id, |                 payment_intent.payment_id.as_str(), | ||||||
|                 merchant_id, |                 merchant_id, | ||||||
|  |                 payment_intent.active_attempt_id.as_str(), | ||||||
|                 storage_scheme, |                 storage_scheme, | ||||||
|             ) |             ) | ||||||
|             .await |             .await | ||||||
| @ -175,7 +176,7 @@ impl<F: Clone> UpdateTracker<F, PaymentData<F>, api::PaymentsCancelRequest> for | |||||||
|     { |     { | ||||||
|         let cancellation_reason = payment_data.payment_attempt.cancellation_reason.clone(); |         let cancellation_reason = payment_data.payment_attempt.cancellation_reason.clone(); | ||||||
|         payment_data.payment_attempt = db |         payment_data.payment_attempt = db | ||||||
|             .update_payment_attempt( |             .update_payment_attempt_with_attempt_id( | ||||||
|                 payment_data.payment_attempt, |                 payment_data.payment_attempt, | ||||||
|                 storage::PaymentAttemptUpdate::VoidUpdate { |                 storage::PaymentAttemptUpdate::VoidUpdate { | ||||||
|                     status: enums::AttemptStatus::VoidInitiated, |                     status: enums::AttemptStatus::VoidInitiated, | ||||||
|  | |||||||
| @ -63,9 +63,10 @@ impl<F: Send + Clone> GetTracker<F, payments::PaymentData<F>, api::PaymentsCaptu | |||||||
|         helpers::validate_amount_to_capture(payment_intent.amount, request.amount_to_capture)?; |         helpers::validate_amount_to_capture(payment_intent.amount, request.amount_to_capture)?; | ||||||
|  |  | ||||||
|         payment_attempt = db |         payment_attempt = db | ||||||
|             .find_payment_attempt_by_payment_id_merchant_id( |             .find_payment_attempt_by_payment_id_merchant_id_attempt_id( | ||||||
|                 &payment_id, |                 payment_intent.payment_id.as_str(), | ||||||
|                 merchant_id, |                 merchant_id, | ||||||
|  |                 payment_intent.active_attempt_id.as_str(), | ||||||
|                 storage_scheme, |                 storage_scheme, | ||||||
|             ) |             ) | ||||||
|             .await |             .await | ||||||
|  | |||||||
| @ -94,9 +94,10 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, api::PaymentsRequest> for Co | |||||||
|             })?; |             })?; | ||||||
|  |  | ||||||
|         payment_attempt = db |         payment_attempt = db | ||||||
|             .find_payment_attempt_by_payment_id_merchant_id( |             .find_payment_attempt_by_payment_id_merchant_id_attempt_id( | ||||||
|                 &payment_id, |                 &payment_intent.payment_id, | ||||||
|                 merchant_id, |                 merchant_id, | ||||||
|  |                 &payment_intent.active_attempt_id, | ||||||
|                 storage_scheme, |                 storage_scheme, | ||||||
|             ) |             ) | ||||||
|             .await |             .await | ||||||
|  | |||||||
| @ -96,9 +96,10 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, api::PaymentsRequest> for Pa | |||||||
|             })?; |             })?; | ||||||
|  |  | ||||||
|         payment_attempt = db |         payment_attempt = db | ||||||
|             .find_payment_attempt_by_payment_id_merchant_id( |             .find_payment_attempt_by_payment_id_merchant_id_attempt_id( | ||||||
|                 &payment_id, |                 payment_intent.payment_id.as_str(), | ||||||
|                 merchant_id, |                 merchant_id, | ||||||
|  |                 payment_intent.active_attempt_id.as_str(), | ||||||
|                 storage_scheme, |                 storage_scheme, | ||||||
|             ) |             ) | ||||||
|             .await |             .await | ||||||
| @ -338,7 +339,7 @@ impl<F: Clone> UpdateTracker<F, PaymentData<F>, api::PaymentsRequest> for Paymen | |||||||
|             .attach_printable("Failed to encode additional pm data")?; |             .attach_printable("Failed to encode additional pm data")?; | ||||||
|  |  | ||||||
|         payment_data.payment_attempt = db |         payment_data.payment_attempt = db | ||||||
|             .update_payment_attempt( |             .update_payment_attempt_with_attempt_id( | ||||||
|                 payment_data.payment_attempt, |                 payment_data.payment_attempt, | ||||||
|                 storage::PaymentAttemptUpdate::ConfirmUpdate { |                 storage::PaymentAttemptUpdate::ConfirmUpdate { | ||||||
|                     amount: payment_data.amount.into(), |                     amount: payment_data.amount.into(), | ||||||
|  | |||||||
| @ -127,6 +127,7 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, api::PaymentsRequest> for Pa | |||||||
|                     request, |                     request, | ||||||
|                     shipping_address.clone().map(|x| x.address_id), |                     shipping_address.clone().map(|x| x.address_id), | ||||||
|                     billing_address.clone().map(|x| x.address_id), |                     billing_address.clone().map(|x| x.address_id), | ||||||
|  |                     payment_attempt.attempt_id.to_owned(), | ||||||
|                 )?, |                 )?, | ||||||
|                 storage_scheme, |                 storage_scheme, | ||||||
|             ) |             ) | ||||||
| @ -316,7 +317,7 @@ impl<F: Clone> UpdateTracker<F, PaymentData<F>, api::PaymentsRequest> for Paymen | |||||||
|         let connector = payment_data.payment_attempt.connector.clone(); |         let connector = payment_data.payment_attempt.connector.clone(); | ||||||
|  |  | ||||||
|         payment_data.payment_attempt = db |         payment_data.payment_attempt = db | ||||||
|             .update_payment_attempt( |             .update_payment_attempt_with_attempt_id( | ||||||
|                 payment_data.payment_attempt, |                 payment_data.payment_attempt, | ||||||
|                 storage::PaymentAttemptUpdate::UpdateTrackers { |                 storage::PaymentAttemptUpdate::UpdateTrackers { | ||||||
|                     payment_token, |                     payment_token, | ||||||
| @ -479,6 +480,7 @@ impl PaymentCreate { | |||||||
|         request: &api::PaymentsRequest, |         request: &api::PaymentsRequest, | ||||||
|         shipping_address_id: Option<String>, |         shipping_address_id: Option<String>, | ||||||
|         billing_address_id: Option<String>, |         billing_address_id: Option<String>, | ||||||
|  |         active_attempt_id: String, | ||||||
|     ) -> RouterResult<storage::PaymentIntentNew> { |     ) -> RouterResult<storage::PaymentIntentNew> { | ||||||
|         let created_at @ modified_at @ last_synced = Some(common_utils::date_time::now()); |         let created_at @ modified_at @ last_synced = Some(common_utils::date_time::now()); | ||||||
|         let status = |         let status = | ||||||
| @ -512,6 +514,7 @@ impl PaymentCreate { | |||||||
|             statement_descriptor_name: request.statement_descriptor_name.clone(), |             statement_descriptor_name: request.statement_descriptor_name.clone(), | ||||||
|             statement_descriptor_suffix: request.statement_descriptor_suffix.clone(), |             statement_descriptor_suffix: request.statement_descriptor_suffix.clone(), | ||||||
|             metadata: metadata.map(masking::Secret::new), |             metadata: metadata.map(masking::Secret::new), | ||||||
|  |             active_attempt_id, | ||||||
|             ..storage::PaymentIntentNew::default() |             ..storage::PaymentIntentNew::default() | ||||||
|         }) |         }) | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -106,7 +106,12 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, api::VerifyRequest> for Paym | |||||||
|  |  | ||||||
|         payment_intent = match db |         payment_intent = match db | ||||||
|             .insert_payment_intent( |             .insert_payment_intent( | ||||||
|                 Self::make_payment_intent(&payment_id, merchant_id, request), |                 Self::make_payment_intent( | ||||||
|  |                     &payment_id, | ||||||
|  |                     merchant_id, | ||||||
|  |                     request, | ||||||
|  |                     payment_attempt.attempt_id.to_owned(), | ||||||
|  |                 ), | ||||||
|                 storage_scheme, |                 storage_scheme, | ||||||
|             ) |             ) | ||||||
|             .await |             .await | ||||||
| @ -312,6 +317,7 @@ impl PaymentMethodValidate { | |||||||
|         payment_id: &str, |         payment_id: &str, | ||||||
|         merchant_id: &str, |         merchant_id: &str, | ||||||
|         request: &api::VerifyRequest, |         request: &api::VerifyRequest, | ||||||
|  |         active_attempt_id: String, | ||||||
|     ) -> storage::PaymentIntentNew { |     ) -> storage::PaymentIntentNew { | ||||||
|         let created_at @ modified_at @ last_synced = Some(date_time::now()); |         let created_at @ modified_at @ last_synced = Some(date_time::now()); | ||||||
|         let status = helpers::payment_intent_status_fsm(&request.payment_method_data, Some(true)); |         let status = helpers::payment_intent_status_fsm(&request.payment_method_data, Some(true)); | ||||||
| @ -331,6 +337,7 @@ impl PaymentMethodValidate { | |||||||
|             client_secret: Some(client_secret), |             client_secret: Some(client_secret), | ||||||
|             setup_future_usage: request.setup_future_usage.map(ForeignInto::foreign_into), |             setup_future_usage: request.setup_future_usage.map(ForeignInto::foreign_into), | ||||||
|             off_session: request.off_session, |             off_session: request.off_session, | ||||||
|  |             active_attempt_id, | ||||||
|             ..Default::default() |             ..Default::default() | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -361,7 +361,7 @@ async fn payment_response_update_tracker<F: Clone, T>( | |||||||
|  |  | ||||||
|     payment_data.payment_attempt = match payment_attempt_update { |     payment_data.payment_attempt = match payment_attempt_update { | ||||||
|         Some(payment_attempt_update) => db |         Some(payment_attempt_update) => db | ||||||
|             .update_payment_attempt( |             .update_payment_attempt_with_attempt_id( | ||||||
|                 payment_data.payment_attempt, |                 payment_data.payment_attempt, | ||||||
|                 payment_attempt_update, |                 payment_attempt_update, | ||||||
|                 storage_scheme, |                 storage_scheme, | ||||||
|  | |||||||
| @ -71,9 +71,10 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, api::PaymentsSessionRequest> | |||||||
|         )?; |         )?; | ||||||
|  |  | ||||||
|         let mut payment_attempt = db |         let mut payment_attempt = db | ||||||
|             .find_payment_attempt_by_payment_id_merchant_id( |             .find_payment_attempt_by_payment_id_merchant_id_attempt_id( | ||||||
|                 &payment_id, |                 payment_intent.payment_id.as_str(), | ||||||
|                 merchant_id, |                 merchant_id, | ||||||
|  |                 payment_intent.active_attempt_id.as_str(), | ||||||
|                 storage_scheme, |                 storage_scheme, | ||||||
|             ) |             ) | ||||||
|             .await |             .await | ||||||
|  | |||||||
| @ -68,9 +68,10 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, api::PaymentsStartRequest> f | |||||||
|         )?; |         )?; | ||||||
|  |  | ||||||
|         payment_attempt = db |         payment_attempt = db | ||||||
|             .find_payment_attempt_by_payment_id_merchant_id( |             .find_payment_attempt_by_payment_id_merchant_id_attempt_id( | ||||||
|                 &payment_id, |                 payment_intent.payment_id.as_str(), | ||||||
|                 merchant_id, |                 merchant_id, | ||||||
|  |                 payment_intent.active_attempt_id.as_str(), | ||||||
|                 storage_scheme, |                 storage_scheme, | ||||||
|             ) |             ) | ||||||
|             .await |             .await | ||||||
|  | |||||||
| @ -191,27 +191,11 @@ async fn get_tracker_for_sync< | |||||||
| )> { | )> { | ||||||
|     let (payment_intent, payment_attempt, currency, amount); |     let (payment_intent, payment_attempt, currency, amount); | ||||||
|  |  | ||||||
|     payment_attempt = match payment_id { |     (payment_intent, payment_attempt) = | ||||||
|         api::PaymentIdType::PaymentIntentId(ref id) => { |         get_payment_intent_payment_attempt(db, payment_id, merchant_id, storage_scheme).await?; | ||||||
|             db.find_payment_attempt_by_payment_id_merchant_id(id, merchant_id, storage_scheme) |  | ||||||
|         } |  | ||||||
|         api::PaymentIdType::ConnectorTransactionId(ref id) => { |  | ||||||
|             db.find_payment_attempt_by_merchant_id_connector_txn_id(merchant_id, id, storage_scheme) |  | ||||||
|         } |  | ||||||
|         api::PaymentIdType::PaymentAttemptId(ref id) => { |  | ||||||
|             db.find_payment_attempt_by_merchant_id_attempt_id(merchant_id, id, storage_scheme) |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|     .await |  | ||||||
|     .map_err(|error| error.to_not_found_response(errors::ApiErrorResponse::PaymentNotFound))?; |  | ||||||
|  |  | ||||||
|     let payment_id_str = payment_attempt.payment_id.clone(); |     let payment_id_str = payment_attempt.payment_id.clone(); | ||||||
|  |  | ||||||
|     payment_intent = db |  | ||||||
|         .find_payment_intent_by_payment_id_merchant_id(&payment_id_str, merchant_id, storage_scheme) |  | ||||||
|         .await |  | ||||||
|         .map_err(|error| error.to_not_found_response(errors::ApiErrorResponse::PaymentNotFound))?; |  | ||||||
|  |  | ||||||
|     let mut connector_response = db |     let mut connector_response = db | ||||||
|         .find_connector_response_by_payment_id_merchant_id_attempt_id( |         .find_connector_response_by_payment_id_merchant_id_attempt_id( | ||||||
|             &payment_intent.payment_id, |             &payment_intent.payment_id, | ||||||
| @ -319,3 +303,63 @@ impl<F: Send + Clone> ValidateRequest<F, api::PaymentsRetrieveRequest> for Payme | |||||||
|         )) |         )) | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | #[inline] | ||||||
|  | pub async fn get_payment_intent_payment_attempt( | ||||||
|  |     db: &dyn StorageInterface, | ||||||
|  |     payment_id: &api::PaymentIdType, | ||||||
|  |     merchant_id: &str, | ||||||
|  |     storage_scheme: enums::MerchantStorageScheme, | ||||||
|  | ) -> RouterResult<(storage::PaymentIntent, storage::PaymentAttempt)> { | ||||||
|  |     (|| async { | ||||||
|  |         let (pi, pa); | ||||||
|  |         match payment_id { | ||||||
|  |             api_models::payments::PaymentIdType::PaymentIntentId(ref id) => { | ||||||
|  |                 pi = db | ||||||
|  |                     .find_payment_intent_by_payment_id_merchant_id(id, merchant_id, storage_scheme) | ||||||
|  |                     .await?; | ||||||
|  |                 pa = db | ||||||
|  |                     .find_payment_attempt_by_payment_id_merchant_id_attempt_id( | ||||||
|  |                         pi.payment_id.as_str(), | ||||||
|  |                         merchant_id, | ||||||
|  |                         pi.active_attempt_id.as_str(), | ||||||
|  |                         storage_scheme, | ||||||
|  |                     ) | ||||||
|  |                     .await?; | ||||||
|  |             } | ||||||
|  |             api_models::payments::PaymentIdType::ConnectorTransactionId(ref id) => { | ||||||
|  |                 pa = db | ||||||
|  |                     .find_payment_attempt_by_merchant_id_connector_txn_id( | ||||||
|  |                         merchant_id, | ||||||
|  |                         id, | ||||||
|  |                         storage_scheme, | ||||||
|  |                     ) | ||||||
|  |                     .await?; | ||||||
|  |                 pi = db | ||||||
|  |                     .find_payment_intent_by_payment_id_merchant_id( | ||||||
|  |                         pa.payment_id.as_str(), | ||||||
|  |                         merchant_id, | ||||||
|  |                         storage_scheme, | ||||||
|  |                     ) | ||||||
|  |                     .await?; | ||||||
|  |             } | ||||||
|  |             api_models::payments::PaymentIdType::PaymentAttemptId(ref id) => { | ||||||
|  |                 pa = db | ||||||
|  |                     .find_payment_attempt_by_attempt_id_merchant_id(id, merchant_id, storage_scheme) | ||||||
|  |                     .await?; | ||||||
|  |                 pi = db | ||||||
|  |                     .find_payment_intent_by_payment_id_merchant_id( | ||||||
|  |                         pa.payment_id.as_str(), | ||||||
|  |                         merchant_id, | ||||||
|  |                         storage_scheme, | ||||||
|  |                     ) | ||||||
|  |                     .await?; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         Ok((pi, pa)) | ||||||
|  |     })() | ||||||
|  |     .await | ||||||
|  |     .map_err(|error: error_stack::Report<errors::StorageError>| { | ||||||
|  |         error.to_not_found_response(errors::ApiErrorResponse::PaymentNotFound) | ||||||
|  |     }) | ||||||
|  | } | ||||||
|  | |||||||
| @ -87,10 +87,18 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, api::PaymentsRequest> for Pa | |||||||
|             ) |             ) | ||||||
|             .await?; |             .await?; | ||||||
|  |  | ||||||
|  |         payment_intent = db | ||||||
|  |             .find_payment_intent_by_payment_id_merchant_id(&payment_id, merchant_id, storage_scheme) | ||||||
|  |             .await | ||||||
|  |             .map_err(|error| { | ||||||
|  |                 error.to_not_found_response(errors::ApiErrorResponse::PaymentNotFound) | ||||||
|  |             })?; | ||||||
|  |  | ||||||
|         payment_attempt = db |         payment_attempt = db | ||||||
|             .find_payment_attempt_by_payment_id_merchant_id( |             .find_payment_attempt_by_payment_id_merchant_id_attempt_id( | ||||||
|                 &payment_id, |                 payment_intent.payment_id.as_str(), | ||||||
|                 merchant_id, |                 merchant_id, | ||||||
|  |                 payment_intent.active_attempt_id.as_str(), | ||||||
|                 storage_scheme, |                 storage_scheme, | ||||||
|             ) |             ) | ||||||
|             .await |             .await | ||||||
| @ -364,7 +372,7 @@ impl<F: Clone> UpdateTracker<F, PaymentData<F>, api::PaymentsRequest> for Paymen | |||||||
|         let payment_method_type = payment_data.payment_attempt.payment_method_type.clone(); |         let payment_method_type = payment_data.payment_attempt.payment_method_type.clone(); | ||||||
|         let payment_experience = payment_data.payment_attempt.payment_experience.clone(); |         let payment_experience = payment_data.payment_attempt.payment_experience.clone(); | ||||||
|         payment_data.payment_attempt = db |         payment_data.payment_attempt = db | ||||||
|             .update_payment_attempt( |             .update_payment_attempt_with_attempt_id( | ||||||
|                 payment_data.payment_attempt, |                 payment_data.payment_attempt, | ||||||
|                 storage::PaymentAttemptUpdate::Update { |                 storage::PaymentAttemptUpdate::Update { | ||||||
|                     amount: payment_data.amount.into(), |                     amount: payment_data.amount.into(), | ||||||
|  | |||||||
| @ -37,26 +37,6 @@ pub async fn refund_create_core( | |||||||
|  |  | ||||||
|     merchant_id = &merchant_account.merchant_id; |     merchant_id = &merchant_account.merchant_id; | ||||||
|  |  | ||||||
|     payment_attempt = db |  | ||||||
|         .find_payment_attempt_last_successful_attempt_by_payment_id_merchant_id( |  | ||||||
|             &req.payment_id, |  | ||||||
|             merchant_id, |  | ||||||
|             merchant_account.storage_scheme, |  | ||||||
|         ) |  | ||||||
|         .await |  | ||||||
|         .change_context(errors::ApiErrorResponse::SuccessfulPaymentNotFound)?; |  | ||||||
|  |  | ||||||
|     // Amount is not passed in request refer from payment attempt. |  | ||||||
|     amount = req.amount.unwrap_or(payment_attempt.amount); // [#298]: Need to that capture amount |  | ||||||
|                                                            //[#299]: Can we change the flow based on some workflow idea |  | ||||||
|     utils::when(amount <= 0, || { |  | ||||||
|         Err(report!(errors::ApiErrorResponse::InvalidDataFormat { |  | ||||||
|             field_name: "amount".to_string(), |  | ||||||
|             expected_format: "positive integer".to_string() |  | ||||||
|         }) |  | ||||||
|         .attach_printable("amount less than zero")) |  | ||||||
|     })?; |  | ||||||
|  |  | ||||||
|     payment_intent = db |     payment_intent = db | ||||||
|         .find_payment_intent_by_payment_id_merchant_id( |         .find_payment_intent_by_payment_id_merchant_id( | ||||||
|             &req.payment_id, |             &req.payment_id, | ||||||
| @ -74,6 +54,32 @@ pub async fn refund_create_core( | |||||||
|         }, |         }, | ||||||
|     )?; |     )?; | ||||||
|  |  | ||||||
|  |     // Amount is not passed in request refer from payment attempt. | ||||||
|  |     amount = req.amount.unwrap_or( | ||||||
|  |         payment_intent | ||||||
|  |             .amount_captured | ||||||
|  |             .ok_or(errors::ApiErrorResponse::InternalServerError) | ||||||
|  |             .into_report() | ||||||
|  |             .attach_printable("amount captured is none in a successful payment")?, | ||||||
|  |     ); | ||||||
|  |     //[#299]: Can we change the flow based on some workflow idea | ||||||
|  |     utils::when(amount <= 0, || { | ||||||
|  |         Err(report!(errors::ApiErrorResponse::InvalidDataFormat { | ||||||
|  |             field_name: "amount".to_string(), | ||||||
|  |             expected_format: "positive integer".to_string() | ||||||
|  |         }) | ||||||
|  |         .attach_printable("amount less than zero")) | ||||||
|  |     })?; | ||||||
|  |  | ||||||
|  |     payment_attempt = db | ||||||
|  |         .find_payment_attempt_last_successful_attempt_by_payment_id_merchant_id( | ||||||
|  |             &req.payment_id, | ||||||
|  |             merchant_id, | ||||||
|  |             merchant_account.storage_scheme, | ||||||
|  |         ) | ||||||
|  |         .await | ||||||
|  |         .change_context(errors::ApiErrorResponse::SuccessfulPaymentNotFound)?; | ||||||
|  |  | ||||||
|     let creds_identifier = req |     let creds_identifier = req | ||||||
|         .merchant_connector_details |         .merchant_connector_details | ||||||
|         .as_ref() |         .as_ref() | ||||||
|  | |||||||
| @ -214,9 +214,9 @@ async fn get_payment_attempt_from_object_reference_id( | |||||||
|             .await |             .await | ||||||
|             .change_context(errors::WebhooksFlowError::ResourceNotFound), |             .change_context(errors::WebhooksFlowError::ResourceNotFound), | ||||||
|         api::ObjectReferenceId::PaymentId(api::PaymentIdType::PaymentAttemptId(ref id)) => db |         api::ObjectReferenceId::PaymentId(api::PaymentIdType::PaymentAttemptId(ref id)) => db | ||||||
|             .find_payment_attempt_by_merchant_id_attempt_id( |             .find_payment_attempt_by_attempt_id_merchant_id( | ||||||
|                 &merchant_account.merchant_id, |  | ||||||
|                 id, |                 id, | ||||||
|  |                 &merchant_account.merchant_id, | ||||||
|                 merchant_account.storage_scheme, |                 merchant_account.storage_scheme, | ||||||
|             ) |             ) | ||||||
|             .await |             .await | ||||||
|  | |||||||
| @ -12,20 +12,13 @@ pub trait PaymentAttemptInterface { | |||||||
|         storage_scheme: enums::MerchantStorageScheme, |         storage_scheme: enums::MerchantStorageScheme, | ||||||
|     ) -> CustomResult<types::PaymentAttempt, errors::StorageError>; |     ) -> CustomResult<types::PaymentAttempt, errors::StorageError>; | ||||||
|  |  | ||||||
|     async fn update_payment_attempt( |     async fn update_payment_attempt_with_attempt_id( | ||||||
|         &self, |         &self, | ||||||
|         this: types::PaymentAttempt, |         this: types::PaymentAttempt, | ||||||
|         payment_attempt: types::PaymentAttemptUpdate, |         payment_attempt: types::PaymentAttemptUpdate, | ||||||
|         storage_scheme: enums::MerchantStorageScheme, |         storage_scheme: enums::MerchantStorageScheme, | ||||||
|     ) -> CustomResult<types::PaymentAttempt, errors::StorageError>; |     ) -> CustomResult<types::PaymentAttempt, errors::StorageError>; | ||||||
|  |  | ||||||
|     async fn find_payment_attempt_by_payment_id_merchant_id( |  | ||||||
|         &self, |  | ||||||
|         payment_id: &str, |  | ||||||
|         merchant_id: &str, |  | ||||||
|         storage_scheme: enums::MerchantStorageScheme, |  | ||||||
|     ) -> CustomResult<types::PaymentAttempt, errors::StorageError>; |  | ||||||
|  |  | ||||||
|     async fn find_payment_attempt_by_connector_transaction_id_payment_id_merchant_id( |     async fn find_payment_attempt_by_connector_transaction_id_payment_id_merchant_id( | ||||||
|         &self, |         &self, | ||||||
|         connector_transaction_id: &str, |         connector_transaction_id: &str, | ||||||
| @ -48,12 +41,20 @@ pub trait PaymentAttemptInterface { | |||||||
|         storage_scheme: enums::MerchantStorageScheme, |         storage_scheme: enums::MerchantStorageScheme, | ||||||
|     ) -> CustomResult<types::PaymentAttempt, errors::StorageError>; |     ) -> CustomResult<types::PaymentAttempt, errors::StorageError>; | ||||||
|  |  | ||||||
|     async fn find_payment_attempt_by_merchant_id_attempt_id( |     async fn find_payment_attempt_by_payment_id_merchant_id_attempt_id( | ||||||
|         &self, |         &self, | ||||||
|  |         payment_id: &str, | ||||||
|         merchant_id: &str, |         merchant_id: &str, | ||||||
|         attempt_id: &str, |         attempt_id: &str, | ||||||
|         storage_scheme: enums::MerchantStorageScheme, |         storage_scheme: enums::MerchantStorageScheme, | ||||||
|     ) -> CustomResult<types::PaymentAttempt, errors::StorageError>; |     ) -> CustomResult<types::PaymentAttempt, errors::StorageError>; | ||||||
|  |  | ||||||
|  |     async fn find_payment_attempt_by_attempt_id_merchant_id( | ||||||
|  |         &self, | ||||||
|  |         attempt_id: &str, | ||||||
|  |         merchant_id: &str, | ||||||
|  |         storage_scheme: enums::MerchantStorageScheme, | ||||||
|  |     ) -> CustomResult<types::PaymentAttempt, errors::StorageError>; | ||||||
| } | } | ||||||
|  |  | ||||||
| #[cfg(not(feature = "kv_store"))] | #[cfg(not(feature = "kv_store"))] | ||||||
| @ -83,27 +84,14 @@ mod storage { | |||||||
|                 .into_report() |                 .into_report() | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         async fn update_payment_attempt( |         async fn update_payment_attempt_with_attempt_id( | ||||||
|             &self, |             &self, | ||||||
|             this: PaymentAttempt, |             this: PaymentAttempt, | ||||||
|             payment_attempt: PaymentAttemptUpdate, |             payment_attempt: PaymentAttemptUpdate, | ||||||
|             _storage_scheme: enums::MerchantStorageScheme, |             _storage_scheme: enums::MerchantStorageScheme, | ||||||
|         ) -> CustomResult<PaymentAttempt, errors::StorageError> { |         ) -> CustomResult<PaymentAttempt, errors::StorageError> { | ||||||
|             let conn = connection::pg_connection_write(self).await?; |             let conn = connection::pg_connection_write(&self).await?; | ||||||
|             this.update(&conn, payment_attempt) |             this.update_with_attempt_id(&conn, payment_attempt) | ||||||
|                 .await |  | ||||||
|                 .map_err(Into::into) |  | ||||||
|                 .into_report() |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         async fn find_payment_attempt_by_payment_id_merchant_id( |  | ||||||
|             &self, |  | ||||||
|             payment_id: &str, |  | ||||||
|             merchant_id: &str, |  | ||||||
|             _storage_scheme: enums::MerchantStorageScheme, |  | ||||||
|         ) -> CustomResult<PaymentAttempt, errors::StorageError> { |  | ||||||
|             let conn = connection::pg_connection_read(self).await?; |  | ||||||
|             PaymentAttempt::find_by_payment_id_merchant_id(&conn, payment_id, merchant_id) |  | ||||||
|                 .await |                 .await | ||||||
|                 .map_err(Into::into) |                 .map_err(Into::into) | ||||||
|                 .into_report() |                 .into_report() | ||||||
| @ -162,7 +150,26 @@ mod storage { | |||||||
|             .into_report() |             .into_report() | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         async fn find_payment_attempt_by_merchant_id_attempt_id( |         async fn find_payment_attempt_by_payment_id_merchant_id_attempt_id( | ||||||
|  |             &self, | ||||||
|  |             payment_id: &str, | ||||||
|  |             merchant_id: &str, | ||||||
|  |             attempt_id: &str, | ||||||
|  |             _storage_scheme: enums::MerchantStorageScheme, | ||||||
|  |         ) -> CustomResult<PaymentAttempt, errors::StorageError> { | ||||||
|  |             let conn = connection::pg_connection_read(self).await?; | ||||||
|  |  | ||||||
|  |             PaymentAttempt::find_by_payment_id_merchant_id_attempt_id( | ||||||
|  |                 &conn, | ||||||
|  |                 payment_id, | ||||||
|  |                 merchant_id, | ||||||
|  |                 attempt_id, | ||||||
|  |             ) | ||||||
|  |             .await | ||||||
|  |             .map_err(Into::into) | ||||||
|  |             .into_report() | ||||||
|  |         } | ||||||
|  |         async fn find_payment_attempt_by_attempt_id_merchant_id( | ||||||
|             &self, |             &self, | ||||||
|             merchant_id: &str, |             merchant_id: &str, | ||||||
|             attempt_id: &str, |             attempt_id: &str, | ||||||
| @ -180,8 +187,9 @@ mod storage { | |||||||
|  |  | ||||||
| #[async_trait::async_trait] | #[async_trait::async_trait] | ||||||
| impl PaymentAttemptInterface for MockDb { | impl PaymentAttemptInterface for MockDb { | ||||||
|     async fn find_payment_attempt_by_merchant_id_attempt_id( |     async fn find_payment_attempt_by_payment_id_merchant_id_attempt_id( | ||||||
|         &self, |         &self, | ||||||
|  |         _payment_id: &str, | ||||||
|         _merchant_id: &str, |         _merchant_id: &str, | ||||||
|         _attempt_id: &str, |         _attempt_id: &str, | ||||||
|         _storage_scheme: enums::MerchantStorageScheme, |         _storage_scheme: enums::MerchantStorageScheme, | ||||||
| @ -190,6 +198,16 @@ impl PaymentAttemptInterface for MockDb { | |||||||
|         Err(errors::StorageError::MockDbError)? |         Err(errors::StorageError::MockDbError)? | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     async fn find_payment_attempt_by_attempt_id_merchant_id( | ||||||
|  |         &self, | ||||||
|  |         _attempt_id: &str, | ||||||
|  |         _merchant_id: &str, | ||||||
|  |         _storage_scheme: enums::MerchantStorageScheme, | ||||||
|  |     ) -> CustomResult<types::PaymentAttempt, errors::StorageError> { | ||||||
|  |         // [#172]: Implement function for `MockDb` | ||||||
|  |         Err(errors::StorageError::MockDbError)? | ||||||
|  |     } | ||||||
|  |  | ||||||
|     async fn find_payment_attempt_by_merchant_id_connector_txn_id( |     async fn find_payment_attempt_by_merchant_id_connector_txn_id( | ||||||
|         &self, |         &self, | ||||||
|         _merchant_id: &str, |         _merchant_id: &str, | ||||||
| @ -252,7 +270,7 @@ impl PaymentAttemptInterface for MockDb { | |||||||
|  |  | ||||||
|     // safety: only used for testing |     // safety: only used for testing | ||||||
|     #[allow(clippy::unwrap_used)] |     #[allow(clippy::unwrap_used)] | ||||||
|     async fn update_payment_attempt( |     async fn update_payment_attempt_with_attempt_id( | ||||||
|         &self, |         &self, | ||||||
|         this: types::PaymentAttempt, |         this: types::PaymentAttempt, | ||||||
|         payment_attempt: types::PaymentAttemptUpdate, |         payment_attempt: types::PaymentAttemptUpdate, | ||||||
| @ -262,7 +280,7 @@ impl PaymentAttemptInterface for MockDb { | |||||||
|  |  | ||||||
|         let item = payment_attempts |         let item = payment_attempts | ||||||
|             .iter_mut() |             .iter_mut() | ||||||
|             .find(|item| item.id == this.id) |             .find(|item| item.attempt_id == this.attempt_id) | ||||||
|             .unwrap(); |             .unwrap(); | ||||||
|  |  | ||||||
|         *item = payment_attempt.apply_changeset(this); |         *item = payment_attempt.apply_changeset(this); | ||||||
| @ -270,16 +288,6 @@ impl PaymentAttemptInterface for MockDb { | |||||||
|         Ok(item.clone()) |         Ok(item.clone()) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     async fn find_payment_attempt_by_payment_id_merchant_id( |  | ||||||
|         &self, |  | ||||||
|         _payment_id: &str, |  | ||||||
|         _merchant_id: &str, |  | ||||||
|         _storage_scheme: enums::MerchantStorageScheme, |  | ||||||
|     ) -> CustomResult<types::PaymentAttempt, errors::StorageError> { |  | ||||||
|         // [#172]: Implement function for `MockDb` |  | ||||||
|         Err(errors::StorageError::MockDbError)? |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     async fn find_payment_attempt_by_connector_transaction_id_payment_id_merchant_id( |     async fn find_payment_attempt_by_connector_transaction_id_payment_id_merchant_id( | ||||||
|         &self, |         &self, | ||||||
|         _connector_transaction_id: &str, |         _connector_transaction_id: &str, | ||||||
| @ -317,6 +325,7 @@ mod storage { | |||||||
|     use common_utils::date_time; |     use common_utils::date_time; | ||||||
|     use error_stack::{IntoReport, ResultExt}; |     use error_stack::{IntoReport, ResultExt}; | ||||||
|     use redis_interface::HsetnxReply; |     use redis_interface::HsetnxReply; | ||||||
|  |     use storage_models::reverse_lookup::ReverseLookup; | ||||||
|  |  | ||||||
|     use super::PaymentAttemptInterface; |     use super::PaymentAttemptInterface; | ||||||
|     use crate::{ |     use crate::{ | ||||||
| @ -406,9 +415,7 @@ mod storage { | |||||||
|                             ReverseLookupNew { |                             ReverseLookupNew { | ||||||
|                                 lookup_id: format!( |                                 lookup_id: format!( | ||||||
|                                     "{}_{}", |                                     "{}_{}", | ||||||
|                                     &created_attempt.merchant_id, |                                     &created_attempt.merchant_id, &created_attempt.attempt_id, | ||||||
|                                     // [#439]: Change this to `attempt_id` |  | ||||||
|                                     &created_attempt.payment_id, |  | ||||||
|                                 ), |                                 ), | ||||||
|                                 pk_id: key, |                                 pk_id: key, | ||||||
|                                 sk_id: field, |                                 sk_id: field, | ||||||
| @ -440,7 +447,7 @@ mod storage { | |||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         async fn update_payment_attempt( |         async fn update_payment_attempt_with_attempt_id( | ||||||
|             &self, |             &self, | ||||||
|             this: PaymentAttempt, |             this: PaymentAttempt, | ||||||
|             payment_attempt: PaymentAttemptUpdate, |             payment_attempt: PaymentAttemptUpdate, | ||||||
| @ -449,7 +456,7 @@ mod storage { | |||||||
|             match storage_scheme { |             match storage_scheme { | ||||||
|                 enums::MerchantStorageScheme::PostgresOnly => { |                 enums::MerchantStorageScheme::PostgresOnly => { | ||||||
|                     let conn = connection::pg_connection_write(self).await?; |                     let conn = connection::pg_connection_write(self).await?; | ||||||
|                     this.update(&conn, payment_attempt) |                     this.update_with_attempt_id(&conn, payment_attempt) | ||||||
|                         .await |                         .await | ||||||
|                         .map_err(Into::into) |                         .map_err(Into::into) | ||||||
|                         .into_report() |                         .into_report() | ||||||
| @ -465,33 +472,39 @@ mod storage { | |||||||
|                         .change_context(errors::StorageError::KVError)?; |                         .change_context(errors::StorageError::KVError)?; | ||||||
|                     let field = format!("pa_{}", updated_attempt.attempt_id); |                     let field = format!("pa_{}", updated_attempt.attempt_id); | ||||||
|                     let updated_attempt = self |                     let updated_attempt = self | ||||||
|                         .redis_conn() |                         .redis_conn | ||||||
|                         .map_err(Into::<errors::StorageError>::into)? |  | ||||||
|                         .set_hash_fields(&key, (&field, &redis_value)) |                         .set_hash_fields(&key, (&field, &redis_value)) | ||||||
|                         .await |                         .await | ||||||
|                         .map(|_| updated_attempt) |                         .map(|_| updated_attempt) | ||||||
|                         .change_context(errors::StorageError::KVError)?; |                         .change_context(errors::StorageError::KVError)?; | ||||||
|  |  | ||||||
|                     let conn = connection::pg_connection_write(self).await?; |                     match ( | ||||||
|                     // Reverse lookup for connector_transaction_id |  | ||||||
|                     if let (None, Some(connector_transaction_id)) = ( |  | ||||||
|                         old_connector_transaction_id, |                         old_connector_transaction_id, | ||||||
|                         &updated_attempt.connector_transaction_id, |                         &updated_attempt.connector_transaction_id, | ||||||
|                     ) { |                     ) { | ||||||
|                         let field = format!("pa_{}", updated_attempt.attempt_id); |                         (None, Some(connector_transaction_id)) => { | ||||||
|                         ReverseLookupNew { |                             add_connector_txn_id_to_reverse_lookup( | ||||||
|                             lookup_id: format!( |                                 self, | ||||||
|                                 "{}_{}", |                                 key.as_str(), | ||||||
|                                 &updated_attempt.merchant_id, connector_transaction_id |                                 this.merchant_id.as_str(), | ||||||
|                             ), |                                 updated_attempt.attempt_id.as_str(), | ||||||
|                             pk_id: key.clone(), |                                 connector_transaction_id.as_str(), | ||||||
|                             sk_id: field.clone(), |                             ) | ||||||
|                             source: "payment_attempt".to_string(), |                             .await?; | ||||||
|                         } |                         } | ||||||
|                         .insert(&conn) |                         (Some(old_connector_transaction_id), Some(connector_transaction_id)) => { | ||||||
|                         .await |                             if old_connector_transaction_id.ne(connector_transaction_id) { | ||||||
|                         .map_err(Into::<errors::StorageError>::into) |                                 add_connector_txn_id_to_reverse_lookup( | ||||||
|                         .into_report()?; |                                     self, | ||||||
|  |                                     key.as_str(), | ||||||
|  |                                     this.merchant_id.as_str(), | ||||||
|  |                                     updated_attempt.attempt_id.as_str(), | ||||||
|  |                                     connector_transaction_id.as_str(), | ||||||
|  |                                 ) | ||||||
|  |                                 .await?; | ||||||
|  |                             } | ||||||
|  |                         } | ||||||
|  |                         (_, _) => {} | ||||||
|                     } |                     } | ||||||
|  |  | ||||||
|                     let redis_entry = kv::TypedSql { |                     let redis_entry = kv::TypedSql { | ||||||
| @ -517,41 +530,6 @@ mod storage { | |||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         async fn find_payment_attempt_by_payment_id_merchant_id( |  | ||||||
|             &self, |  | ||||||
|             payment_id: &str, |  | ||||||
|             merchant_id: &str, |  | ||||||
|             storage_scheme: enums::MerchantStorageScheme, |  | ||||||
|         ) -> CustomResult<PaymentAttempt, errors::StorageError> { |  | ||||||
|             let database_call = || async { |  | ||||||
|                 let conn = connection::pg_connection_read(self).await?; |  | ||||||
|                 PaymentAttempt::find_by_payment_id_merchant_id(&conn, payment_id, merchant_id) |  | ||||||
|                     .await |  | ||||||
|                     .map_err(Into::into) |  | ||||||
|                     .into_report() |  | ||||||
|             }; |  | ||||||
|             match storage_scheme { |  | ||||||
|                 enums::MerchantStorageScheme::PostgresOnly => database_call().await, |  | ||||||
|                 enums::MerchantStorageScheme::RedisKv => { |  | ||||||
|                     // [#439]: get the attempt_id from payment_intent |  | ||||||
|                     let key = format!("{merchant_id}_{payment_id}"); |  | ||||||
|                     let lookup = self.get_lookup_by_lookup_id(&key).await?; |  | ||||||
|  |  | ||||||
|                     db_utils::try_redis_get_else_try_database_get( |  | ||||||
|                         self.redis_conn() |  | ||||||
|                             .map_err(Into::<errors::StorageError>::into)? |  | ||||||
|                             .get_hash_field_and_deserialize( |  | ||||||
|                                 &lookup.pk_id, |  | ||||||
|                                 &lookup.sk_id, |  | ||||||
|                                 "PaymentAttempt", |  | ||||||
|                             ), |  | ||||||
|                         database_call, |  | ||||||
|                     ) |  | ||||||
|                     .await |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         async fn find_payment_attempt_by_connector_transaction_id_payment_id_merchant_id( |         async fn find_payment_attempt_by_connector_transaction_id_payment_id_merchant_id( | ||||||
|             &self, |             &self, | ||||||
|             connector_transaction_id: &str, |             connector_transaction_id: &str, | ||||||
| @ -594,21 +572,17 @@ mod storage { | |||||||
|             &self, |             &self, | ||||||
|             payment_id: &str, |             payment_id: &str, | ||||||
|             merchant_id: &str, |             merchant_id: &str, | ||||||
|             storage_scheme: enums::MerchantStorageScheme, |             _storage_scheme: enums::MerchantStorageScheme, | ||||||
|         ) -> CustomResult<PaymentAttempt, errors::StorageError> { |         ) -> CustomResult<PaymentAttempt, errors::StorageError> { | ||||||
|             self.find_payment_attempt_by_payment_id_merchant_id( |             let conn = connection::pg_connection_read(self).await?; | ||||||
|  |             PaymentAttempt::find_last_successful_attempt_by_payment_id_merchant_id( | ||||||
|  |                 &conn, | ||||||
|                 payment_id, |                 payment_id, | ||||||
|                 merchant_id, |                 merchant_id, | ||||||
|                 storage_scheme, |  | ||||||
|             ) |             ) | ||||||
|             .await |             .await | ||||||
|             .and_then(|attempt| match attempt.status { |             .map_err(Into::into) | ||||||
|                 enums::AttemptStatus::Charged => Ok(attempt), |             .into_report() | ||||||
|                 _ => Err(errors::StorageError::ValueNotFound(format!( |  | ||||||
|                     "Successful payment attempt does not exist for {payment_id}_{merchant_id}" |  | ||||||
|                 ))) |  | ||||||
|                 .into_report(), |  | ||||||
|             }) |  | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         async fn find_payment_attempt_by_merchant_id_connector_txn_id( |         async fn find_payment_attempt_by_merchant_id_connector_txn_id( | ||||||
| @ -647,10 +621,10 @@ mod storage { | |||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         async fn find_payment_attempt_by_merchant_id_attempt_id( |         async fn find_payment_attempt_by_attempt_id_merchant_id( | ||||||
|             &self, |             &self, | ||||||
|             merchant_id: &str, |  | ||||||
|             attempt_id: &str, |             attempt_id: &str, | ||||||
|  |             merchant_id: &str, | ||||||
|             storage_scheme: enums::MerchantStorageScheme, |             storage_scheme: enums::MerchantStorageScheme, | ||||||
|         ) -> CustomResult<PaymentAttempt, errors::StorageError> { |         ) -> CustomResult<PaymentAttempt, errors::StorageError> { | ||||||
|             let database_call = || async { |             let database_call = || async { | ||||||
| @ -677,5 +651,64 @@ mod storage { | |||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         async fn find_payment_attempt_by_payment_id_merchant_id_attempt_id( | ||||||
|  |             &self, | ||||||
|  |             payment_id: &str, | ||||||
|  |             merchant_id: &str, | ||||||
|  |             attempt_id: &str, | ||||||
|  |             storage_scheme: enums::MerchantStorageScheme, | ||||||
|  |         ) -> CustomResult<PaymentAttempt, errors::StorageError> { | ||||||
|  |             let database_call = || async { | ||||||
|  |                 let conn = connection::pg_connection_read(self).await?; | ||||||
|  |                 PaymentAttempt::find_by_payment_id_merchant_id_attempt_id( | ||||||
|  |                     &conn, | ||||||
|  |                     payment_id, | ||||||
|  |                     merchant_id, | ||||||
|  |                     attempt_id, | ||||||
|  |                 ) | ||||||
|  |                 .await | ||||||
|  |                 .map_err(Into::into) | ||||||
|  |                 .into_report() | ||||||
|  |             }; | ||||||
|  |             match storage_scheme { | ||||||
|  |                 enums::MerchantStorageScheme::PostgresOnly => database_call().await, | ||||||
|  |  | ||||||
|  |                 enums::MerchantStorageScheme::RedisKv => { | ||||||
|  |                     let lookup_id = format!("{merchant_id}_{attempt_id}"); | ||||||
|  |                     let lookup = self.get_lookup_by_lookup_id(&lookup_id).await?; | ||||||
|  |                     let key = &lookup.pk_id; | ||||||
|  |                     db_utils::try_redis_get_else_try_database_get( | ||||||
|  |                         self.redis_conn() | ||||||
|  |                             .map_err(Into::<errors::StorageError>::into)? | ||||||
|  |                             .get_hash_field_and_deserialize(key, &lookup.sk_id, "PaymentAttempt"), | ||||||
|  |                         database_call, | ||||||
|  |                     ) | ||||||
|  |                     .await | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     #[inline] | ||||||
|  |     async fn add_connector_txn_id_to_reverse_lookup( | ||||||
|  |         store: &Store, | ||||||
|  |         key: &str, | ||||||
|  |         merchant_id: &str, | ||||||
|  |         updated_attempt_attempt_id: &str, | ||||||
|  |         connector_transaction_id: &str, | ||||||
|  |     ) -> CustomResult<ReverseLookup, errors::StorageError> { | ||||||
|  |         let conn = connection::pg_connection_write(store).await?; | ||||||
|  |         let field = format!("pa_{}", updated_attempt_attempt_id); | ||||||
|  |         ReverseLookupNew { | ||||||
|  |             lookup_id: format!("{}_{}", merchant_id, connector_transaction_id), | ||||||
|  |             pk_id: key.to_owned(), | ||||||
|  |             sk_id: field.clone(), | ||||||
|  |             source: "payment_attempt".to_string(), | ||||||
|  |         } | ||||||
|  |         .insert(&conn) | ||||||
|  |         .await | ||||||
|  |         .map_err(Into::<errors::StorageError>::into) | ||||||
|  |         .into_report() | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -92,6 +92,7 @@ mod storage { | |||||||
|                         setup_future_usage: new.setup_future_usage, |                         setup_future_usage: new.setup_future_usage, | ||||||
|                         off_session: new.off_session, |                         off_session: new.off_session, | ||||||
|                         client_secret: new.client_secret.clone(), |                         client_secret: new.client_secret.clone(), | ||||||
|  |                         active_attempt_id: new.active_attempt_id.to_owned(), | ||||||
|                     }; |                     }; | ||||||
|  |  | ||||||
|                     match self |                     match self | ||||||
| @ -347,6 +348,7 @@ impl PaymentIntentInterface for MockDb { | |||||||
|             setup_future_usage: new.setup_future_usage, |             setup_future_usage: new.setup_future_usage, | ||||||
|             off_session: new.off_session, |             off_session: new.off_session, | ||||||
|             client_secret: new.client_secret, |             client_secret: new.client_secret, | ||||||
|  |             active_attempt_id: new.active_attempt_id.to_owned(), | ||||||
|         }; |         }; | ||||||
|         payment_intents.push(payment_intent.clone()); |         payment_intents.push(payment_intent.clone()); | ||||||
|         Ok(payment_intent) |         Ok(payment_intent) | ||||||
|  | |||||||
| @ -1,279 +0,0 @@ | |||||||
| use error_stack::{FutureExt, IntoReport, ResultExt}; |  | ||||||
| use futures::{ |  | ||||||
|     future::{join_all, try_join, try_join_all}, |  | ||||||
|     join, |  | ||||||
| }; |  | ||||||
| use router_derive::Setter; |  | ||||||
| use storage_models::enums::MerchantStorageScheme; |  | ||||||
|  |  | ||||||
| use crate::{ |  | ||||||
|     core::errors::{self, RouterResult, StorageErrorExt}, |  | ||||||
|     db::StorageInterface, |  | ||||||
|     types::storage::{self as storage_types}, |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| pub enum PaymentAttemptDbCall { |  | ||||||
|     Query { |  | ||||||
|         merchant_id: String, |  | ||||||
|         payment_id: String, |  | ||||||
|     }, |  | ||||||
|     Insert(storage_types::PaymentAttemptNew), |  | ||||||
|     Update { |  | ||||||
|         current_payment_attempt: storage_types::PaymentAttempt, |  | ||||||
|         updated_payment_attempt: storage_types::PaymentAttemptUpdate, |  | ||||||
|     }, |  | ||||||
| } |  | ||||||
|  |  | ||||||
| impl PaymentAttemptDbCall { |  | ||||||
|     async fn get_db_call( |  | ||||||
|         self, |  | ||||||
|         db: &dyn StorageInterface, |  | ||||||
|         storage_scheme: MerchantStorageScheme, |  | ||||||
|     ) -> Result<storage_types::PaymentAttempt, error_stack::Report<errors::ApiErrorResponse>> { |  | ||||||
|         match self { |  | ||||||
|             PaymentAttemptDbCall::Query { |  | ||||||
|                 merchant_id, |  | ||||||
|                 payment_id, |  | ||||||
|             } => db |  | ||||||
|                 .find_payment_attempt_by_payment_id_merchant_id( |  | ||||||
|                     &payment_id, |  | ||||||
|                     &merchant_id, |  | ||||||
|                     storage_scheme, |  | ||||||
|                 ) |  | ||||||
|                 .await |  | ||||||
|                 .change_context(errors::ApiErrorResponse::PaymentNotFound), |  | ||||||
|             PaymentAttemptDbCall::Insert(payment_attempt_new) => { |  | ||||||
|                 let payment_id = payment_attempt_new.payment_id.clone(); |  | ||||||
|                 db.insert_payment_attempt(payment_attempt_new, storage_scheme) |  | ||||||
|                     .await |  | ||||||
|                     .change_context(errors::ApiErrorResponse::DuplicatePayment { payment_id }) |  | ||||||
|             } |  | ||||||
|             PaymentAttemptDbCall::Update { |  | ||||||
|                 current_payment_attempt, |  | ||||||
|                 updated_payment_attempt, |  | ||||||
|             } => db |  | ||||||
|                 .update_payment_attempt( |  | ||||||
|                     current_payment_attempt, |  | ||||||
|                     updated_payment_attempt, |  | ||||||
|                     storage_scheme, |  | ||||||
|                 ) |  | ||||||
|                 .await |  | ||||||
|                 .change_context(errors::ApiErrorResponse::InternalServerError) |  | ||||||
|                 .attach_printable("Failed to update payment attempt"), |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| pub enum PaymentIntentDbCall { |  | ||||||
|     Insert(storage_types::PaymentIntentNew), |  | ||||||
| } |  | ||||||
|  |  | ||||||
| impl PaymentIntentDbCall { |  | ||||||
|     async fn get_db_call( |  | ||||||
|         self, |  | ||||||
|         db: &dyn StorageInterface, |  | ||||||
|         storage_scheme: MerchantStorageScheme, |  | ||||||
|     ) -> Result<storage_types::PaymentIntent, error_stack::Report<errors::ApiErrorResponse>> { |  | ||||||
|         match self { |  | ||||||
|             PaymentIntentDbCall::Insert(payment_intent_new) => { |  | ||||||
|                 let payment_id = payment_intent_new.payment_id.clone(); |  | ||||||
|                 db.insert_payment_intent(payment_intent_new, storage_scheme) |  | ||||||
|                     .await |  | ||||||
|                     .change_context(errors::ApiErrorResponse::DuplicatePayment { payment_id }) |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| pub enum ConnectorResponseDbCall { |  | ||||||
|     Query { |  | ||||||
|         merchant_id: String, |  | ||||||
|         payment_id: String, |  | ||||||
|         attempt_id: String, |  | ||||||
|     }, |  | ||||||
|     Insert(storage_types::ConnectorResponseNew), |  | ||||||
|     Update(storage_types::ConnectorResponseUpdate), |  | ||||||
| } |  | ||||||
|  |  | ||||||
| pub enum AddressDbCall { |  | ||||||
|     Query { address_id: String }, |  | ||||||
|     Insert(storage_types::AddressNew), |  | ||||||
|     Update(storage_types::AddressUpdate), |  | ||||||
| } |  | ||||||
|  |  | ||||||
| pub struct DbCall { |  | ||||||
|     payment_intent: PaymentIntentDbCall, |  | ||||||
|     payment_attempt: PaymentAttemptDbCall, |  | ||||||
|     connector_response: ConnectorResponseDbCall, |  | ||||||
|     shipping_address: Option<AddressDbCall>, |  | ||||||
|     billing_address: Option<AddressDbCall>, |  | ||||||
| } |  | ||||||
|  |  | ||||||
| pub enum EntityRequest { |  | ||||||
|     PaymentIntent {}, |  | ||||||
|     PaymentAttempt { |  | ||||||
|         payment_id: String, |  | ||||||
|         merchant_id: String, |  | ||||||
|     }, |  | ||||||
|     Address { |  | ||||||
|         address_id: String, |  | ||||||
|     }, |  | ||||||
|     ConnectorResponse { |  | ||||||
|         payment_id: String, |  | ||||||
|         attempt_id: String, |  | ||||||
|         merchant_id: String, |  | ||||||
|     }, |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #[derive(Debug)] |  | ||||||
| pub enum Entity { |  | ||||||
|     PaymentIntent( |  | ||||||
|         Result<storage_types::PaymentIntent, error_stack::Report<errors::ApiErrorResponse>>, |  | ||||||
|     ), |  | ||||||
|     PaymentAttempt( |  | ||||||
|         Result<storage_types::PaymentAttempt, error_stack::Report<errors::ApiErrorResponse>>, |  | ||||||
|     ), |  | ||||||
|     Address(Result<storage_types::Address, error_stack::Report<errors::ApiErrorResponse>>), |  | ||||||
|     ConnectorResponse( |  | ||||||
|         Result<storage_types::ConnectorResponse, error_stack::Report<errors::ApiErrorResponse>>, |  | ||||||
|     ), |  | ||||||
|     None, //FIXME: for testing purposes only |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #[derive(Setter)] |  | ||||||
| pub struct EntityResult { |  | ||||||
|     pub payment_intent: |  | ||||||
|         Result<storage_types::PaymentIntent, error_stack::Report<errors::ApiErrorResponse>>, |  | ||||||
|     pub payment_attempt: |  | ||||||
|         Result<storage_types::PaymentAttempt, error_stack::Report<errors::ApiErrorResponse>>, |  | ||||||
|     pub connector_response: |  | ||||||
|         Result<storage_types::ConnectorResponse, error_stack::Report<errors::ApiErrorResponse>>, |  | ||||||
|     pub billing_address: |  | ||||||
|         Result<Option<storage_types::Address>, error_stack::Report<errors::ApiErrorResponse>>, |  | ||||||
|     pub shipping_address: |  | ||||||
|         Result<Option<storage_types::Address>, error_stack::Report<errors::ApiErrorResponse>>, |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // #[derive(Setter)] |  | ||||||
| // pub struct DbCallRequest<T, F: Fn() -> Result<T, errors::ApiErrorResponse>> { |  | ||||||
| //     pub payment_attempt: F, |  | ||||||
| //     pub connector_response: |  | ||||||
| //         Result<storage_types::ConnectorResponse, error_stack::Report<errors::ApiErrorResponse>>, |  | ||||||
| //     pub billing_address: |  | ||||||
| //         Result<Option<storage_types::Address>, error_stack::Report<errors::ApiErrorResponse>>, |  | ||||||
| //     pub shipping_address: |  | ||||||
| //         Result<Option<storage_types::Address>, error_stack::Report<errors::ApiErrorResponse>>, |  | ||||||
| //     pub mandate: |  | ||||||
| //         Result<Option<storage_types::Mandate>, error_stack::Report<errors::ApiErrorResponse>>, |  | ||||||
| // } |  | ||||||
|  |  | ||||||
| impl EntityResult { |  | ||||||
|     fn new() -> Self { |  | ||||||
|         Self { |  | ||||||
|             payment_intent: Err(error_stack::report!( |  | ||||||
|                 errors::ApiErrorResponse::PaymentNotFound |  | ||||||
|             )), |  | ||||||
|             payment_attempt: Err(error_stack::report!( |  | ||||||
|                 errors::ApiErrorResponse::PaymentNotFound |  | ||||||
|             )), |  | ||||||
|             connector_response: Err(error_stack::report!( |  | ||||||
|                 errors::ApiErrorResponse::PaymentNotFound |  | ||||||
|             )), |  | ||||||
|             billing_address: Ok(None), |  | ||||||
|             shipping_address: Ok(None), |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| impl Default for EntityResult { |  | ||||||
|     fn default() -> Self { |  | ||||||
|         Self::new() |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #[async_trait::async_trait] |  | ||||||
| pub trait QueryEntity { |  | ||||||
|     async fn query_entity( |  | ||||||
|         &self, |  | ||||||
|         db: &dyn StorageInterface, |  | ||||||
|         storage_scheme: MerchantStorageScheme, |  | ||||||
|     ) -> Entity; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #[async_trait::async_trait] |  | ||||||
| impl QueryEntity for EntityRequest { |  | ||||||
|     async fn query_entity( |  | ||||||
|         &self, |  | ||||||
|         db: &dyn StorageInterface, |  | ||||||
|         storage_scheme: MerchantStorageScheme, |  | ||||||
|     ) -> Entity { |  | ||||||
|         match self { |  | ||||||
|             EntityRequest::PaymentIntent { |  | ||||||
|                 payment_id, |  | ||||||
|                 merchant_id, |  | ||||||
|             } => Entity::PaymentIntent( |  | ||||||
|                 db.find_payment_intent_by_payment_id_merchant_id( |  | ||||||
|                     payment_id, |  | ||||||
|                     merchant_id, |  | ||||||
|                     storage_scheme, |  | ||||||
|                 ) |  | ||||||
|                 .await |  | ||||||
|                 .change_context(errors::ApiErrorResponse::PaymentNotFound), |  | ||||||
|             ), |  | ||||||
|             EntityRequest::PaymentAttempt { |  | ||||||
|                 payment_id, |  | ||||||
|                 merchant_id, |  | ||||||
|             } => Entity::PaymentAttempt( |  | ||||||
|                 db.find_payment_attempt_by_payment_id_merchant_id( |  | ||||||
|                     payment_id, |  | ||||||
|                     merchant_id, |  | ||||||
|                     storage_scheme, |  | ||||||
|                 ) |  | ||||||
|                 .await |  | ||||||
|                 .change_context(errors::ApiErrorResponse::PaymentNotFound), |  | ||||||
|             ), |  | ||||||
|             EntityRequest::Address { address_id } => Entity::Address( |  | ||||||
|                 db.find_address(address_id) |  | ||||||
|                     .await |  | ||||||
|                     .change_context(errors::ApiErrorResponse::AddressNotFound), //FIXME: do not change context |  | ||||||
|             ), |  | ||||||
|             EntityRequest::ConnectorResponse { |  | ||||||
|                 payment_id, |  | ||||||
|                 attempt_id, |  | ||||||
|                 merchant_id, |  | ||||||
|             } => Entity::ConnectorResponse( |  | ||||||
|                 db.find_connector_response_by_payment_id_merchant_id_attempt_id( |  | ||||||
|                     &payment_id, |  | ||||||
|                     &merchant_id, |  | ||||||
|                     &attempt_id, |  | ||||||
|                     storage_scheme, |  | ||||||
|                 ) |  | ||||||
|                 .await |  | ||||||
|                 .change_context(errors::ApiErrorResponse::PaymentNotFound), |  | ||||||
|             ), |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| pub async fn make_parallel_db_call( |  | ||||||
|     db: &dyn StorageInterface, |  | ||||||
|     db_calls: DbCall, |  | ||||||
|     storage_scheme: MerchantStorageScheme, |  | ||||||
| ) -> EntityResult { |  | ||||||
|     let (payment_intent_res) = join!( |  | ||||||
|         db_calls.payment_attempt.get_db_call(db, storage_scheme), |  | ||||||
|         db_calls.payment_intent.get_db_call(db, storage_scheme) |  | ||||||
|     ); |  | ||||||
|  |  | ||||||
|     let mut entities_result = EntityResult::new(); |  | ||||||
|  |  | ||||||
|     for entity in combined_res { |  | ||||||
|         match entity { |  | ||||||
|             Entity::PaymentIntent(pi_res) => entities_result.set_payment_intent(pi_res), |  | ||||||
|             Entity::PaymentAttempt(pa_res) => entities_result.set_payment_attempt(pa_res), |  | ||||||
|             _ => &mut entities_result, |  | ||||||
|         }; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     entities_result |  | ||||||
| } |  | ||||||
| @ -95,6 +95,7 @@ mod tests { | |||||||
|  |  | ||||||
|         let current_time = common_utils::date_time::now(); |         let current_time = common_utils::date_time::now(); | ||||||
|         let payment_id = Uuid::new_v4().to_string(); |         let payment_id = Uuid::new_v4().to_string(); | ||||||
|  |         let attempt_id = Uuid::new_v4().to_string(); | ||||||
|         let merchant_id = Uuid::new_v4().to_string(); |         let merchant_id = Uuid::new_v4().to_string(); | ||||||
|         let connector = types::Connector::Dummy.to_string(); |         let connector = types::Connector::Dummy.to_string(); | ||||||
|  |  | ||||||
| @ -106,6 +107,7 @@ mod tests { | |||||||
|             })), |             })), | ||||||
|             created_at: current_time.into(), |             created_at: current_time.into(), | ||||||
|             modified_at: current_time.into(), |             modified_at: current_time.into(), | ||||||
|  |             attempt_id: attempt_id.clone(), | ||||||
|             ..PaymentAttemptNew::default() |             ..PaymentAttemptNew::default() | ||||||
|         }; |         }; | ||||||
|         state |         state | ||||||
| @ -116,9 +118,10 @@ mod tests { | |||||||
|  |  | ||||||
|         let response = state |         let response = state | ||||||
|             .store |             .store | ||||||
|             .find_payment_attempt_by_payment_id_merchant_id( |             .find_payment_attempt_by_payment_id_merchant_id_attempt_id( | ||||||
|                 &payment_id, |                 &payment_id, | ||||||
|                 &merchant_id, |                 &merchant_id, | ||||||
|  |                 &attempt_id, | ||||||
|                 enums::MerchantStorageScheme::PostgresOnly, |                 enums::MerchantStorageScheme::PostgresOnly, | ||||||
|             ) |             ) | ||||||
|             .await |             .await | ||||||
| @ -150,6 +153,7 @@ mod tests { | |||||||
|             modified_at: current_time.into(), |             modified_at: current_time.into(), | ||||||
|             // Adding a mandate_id |             // Adding a mandate_id | ||||||
|             mandate_id: Some("man_121212".to_string()), |             mandate_id: Some("man_121212".to_string()), | ||||||
|  |             attempt_id: uuid.clone(), | ||||||
|             ..PaymentAttemptNew::default() |             ..PaymentAttemptNew::default() | ||||||
|         }; |         }; | ||||||
|         state |         state | ||||||
| @ -160,9 +164,10 @@ mod tests { | |||||||
|  |  | ||||||
|         let response = state |         let response = state | ||||||
|             .store |             .store | ||||||
|             .find_payment_attempt_by_payment_id_merchant_id( |             .find_payment_attempt_by_payment_id_merchant_id_attempt_id( | ||||||
|                 &uuid, |                 &uuid, | ||||||
|                 "1", |                 "1", | ||||||
|  |                 &uuid, | ||||||
|                 enums::MerchantStorageScheme::PostgresOnly, |                 enums::MerchantStorageScheme::PostgresOnly, | ||||||
|             ) |             ) | ||||||
|             .await |             .await | ||||||
|  | |||||||
| @ -33,6 +33,7 @@ pub struct PaymentIntent { | |||||||
|     pub setup_future_usage: Option<storage_enums::FutureUsage>, |     pub setup_future_usage: Option<storage_enums::FutureUsage>, | ||||||
|     pub off_session: Option<bool>, |     pub off_session: Option<bool>, | ||||||
|     pub client_secret: Option<String>, |     pub client_secret: Option<String>, | ||||||
|  |     pub active_attempt_id: String, | ||||||
| } | } | ||||||
|  |  | ||||||
| #[derive( | #[derive( | ||||||
| @ -72,6 +73,7 @@ pub struct PaymentIntentNew { | |||||||
|     pub client_secret: Option<String>, |     pub client_secret: Option<String>, | ||||||
|     pub setup_future_usage: Option<storage_enums::FutureUsage>, |     pub setup_future_usage: Option<storage_enums::FutureUsage>, | ||||||
|     pub off_session: Option<bool>, |     pub off_session: Option<bool>, | ||||||
|  |     pub active_attempt_id: String, | ||||||
| } | } | ||||||
|  |  | ||||||
| #[derive(Debug, Clone, Serialize, Deserialize)] | #[derive(Debug, Clone, Serialize, Deserialize)] | ||||||
| @ -109,6 +111,9 @@ pub enum PaymentIntentUpdate { | |||||||
|         billing_address_id: Option<String>, |         billing_address_id: Option<String>, | ||||||
|         return_url: Option<String>, |         return_url: Option<String>, | ||||||
|     }, |     }, | ||||||
|  |     PaymentAttemptUpdate { | ||||||
|  |         active_attempt_id: String, | ||||||
|  |     }, | ||||||
| } | } | ||||||
|  |  | ||||||
| #[derive(Clone, Debug, Default, AsChangeset, router_derive::DebugAsDisplay)] | #[derive(Clone, Debug, Default, AsChangeset, router_derive::DebugAsDisplay)] | ||||||
| @ -128,6 +133,7 @@ pub struct PaymentIntentUpdateInternal { | |||||||
|     pub billing_address_id: Option<String>, |     pub billing_address_id: Option<String>, | ||||||
|     pub shipping_address_id: Option<String>, |     pub shipping_address_id: Option<String>, | ||||||
|     pub modified_at: Option<PrimitiveDateTime>, |     pub modified_at: Option<PrimitiveDateTime>, | ||||||
|  |     pub active_attempt_id: Option<String>, | ||||||
| } | } | ||||||
|  |  | ||||||
| impl PaymentIntentUpdate { | impl PaymentIntentUpdate { | ||||||
| @ -242,6 +248,10 @@ impl From<PaymentIntentUpdate> for PaymentIntentUpdateInternal { | |||||||
|                 modified_at: Some(common_utils::date_time::now()), |                 modified_at: Some(common_utils::date_time::now()), | ||||||
|                 ..Default::default() |                 ..Default::default() | ||||||
|             }, |             }, | ||||||
|  |             PaymentIntentUpdate::PaymentAttemptUpdate { active_attempt_id } => Self { | ||||||
|  |                 active_attempt_id: Some(active_attempt_id), | ||||||
|  |                 ..Default::default() | ||||||
|  |             }, | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -21,15 +21,20 @@ impl PaymentAttemptNew { | |||||||
|  |  | ||||||
| impl PaymentAttempt { | impl PaymentAttempt { | ||||||
|     #[instrument(skip(conn))] |     #[instrument(skip(conn))] | ||||||
|     pub async fn update( |     pub async fn update_with_attempt_id( | ||||||
|         self, |         self, | ||||||
|         conn: &PgPooledConn, |         conn: &PgPooledConn, | ||||||
|         payment_attempt: PaymentAttemptUpdate, |         payment_attempt: PaymentAttemptUpdate, | ||||||
|     ) -> StorageResult<Self> { |     ) -> StorageResult<Self> { | ||||||
|         match generics::generic_update_with_results::<<Self as HasTable>::Table, _, _, _>( |         match generics::generic_update_with_unique_predicate_get_result::< | ||||||
|  |             <Self as HasTable>::Table, | ||||||
|  |             _, | ||||||
|  |             _, | ||||||
|  |             _, | ||||||
|  |         >( | ||||||
|             conn, |             conn, | ||||||
|             dsl::payment_id |             dsl::attempt_id | ||||||
|                 .eq(self.payment_id.to_owned()) |                 .eq(self.attempt_id.to_owned()) | ||||||
|                 .and(dsl::merchant_id.eq(self.merchant_id.to_owned())), |                 .and(dsl::merchant_id.eq(self.merchant_id.to_owned())), | ||||||
|             PaymentAttemptUpdateInternal::from(payment_attempt), |             PaymentAttemptUpdateInternal::from(payment_attempt), | ||||||
|         ) |         ) | ||||||
| @ -39,27 +44,10 @@ impl PaymentAttempt { | |||||||
|                 errors::DatabaseError::NoFieldsToUpdate => Ok(self), |                 errors::DatabaseError::NoFieldsToUpdate => Ok(self), | ||||||
|                 _ => Err(error), |                 _ => Err(error), | ||||||
|             }, |             }, | ||||||
|             Ok(mut payment_attempts) => payment_attempts |             result => result, | ||||||
|                 .pop() |  | ||||||
|                 .ok_or(error_stack::report!(errors::DatabaseError::NotFound)), |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     #[instrument(skip(conn))] |  | ||||||
|     pub async fn find_by_payment_id_merchant_id( |  | ||||||
|         conn: &PgPooledConn, |  | ||||||
|         payment_id: &str, |  | ||||||
|         merchant_id: &str, |  | ||||||
|     ) -> StorageResult<Self> { |  | ||||||
|         generics::generic_find_one::<<Self as HasTable>::Table, _, _>( |  | ||||||
|             conn, |  | ||||||
|             dsl::merchant_id |  | ||||||
|                 .eq(merchant_id.to_owned()) |  | ||||||
|                 .and(dsl::payment_id.eq(payment_id.to_owned())), |  | ||||||
|         ) |  | ||||||
|         .await |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     #[instrument(skip(conn))] |     #[instrument(skip(conn))] | ||||||
|     pub async fn find_optional_by_payment_id_merchant_id( |     pub async fn find_optional_by_payment_id_merchant_id( | ||||||
|         conn: &PgPooledConn, |         conn: &PgPooledConn, | ||||||
| @ -118,7 +106,7 @@ impl PaymentAttempt { | |||||||
|         .fold( |         .fold( | ||||||
|             Err(errors::DatabaseError::NotFound).into_report(), |             Err(errors::DatabaseError::NotFound).into_report(), | ||||||
|             |acc, cur| match acc { |             |acc, cur| match acc { | ||||||
|                 Ok(value) if value.created_at > cur.created_at => Ok(value), |                 Ok(value) if value.modified_at > cur.modified_at => Ok(value), | ||||||
|                 _ => Ok(cur), |                 _ => Ok(cur), | ||||||
|             }, |             }, | ||||||
|         ) |         ) | ||||||
| @ -153,4 +141,22 @@ impl PaymentAttempt { | |||||||
|         ) |         ) | ||||||
|         .await |         .await | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     #[instrument(skip(conn))] | ||||||
|  |     pub async fn find_by_payment_id_merchant_id_attempt_id( | ||||||
|  |         conn: &PgPooledConn, | ||||||
|  |         payment_id: &str, | ||||||
|  |         merchant_id: &str, | ||||||
|  |         attempt_id: &str, | ||||||
|  |     ) -> StorageResult<Self> { | ||||||
|  |         generics::generic_find_one::<<Self as HasTable>::Table, _, _>( | ||||||
|  |             conn, | ||||||
|  |             dsl::payment_id.eq(payment_id.to_owned()).and( | ||||||
|  |                 dsl::merchant_id | ||||||
|  |                     .eq(merchant_id.to_owned()) | ||||||
|  |                     .and(dsl::attempt_id.eq(attempt_id.to_owned())), | ||||||
|  |             ), | ||||||
|  |         ) | ||||||
|  |         .await | ||||||
|  |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -312,6 +312,7 @@ diesel::table! { | |||||||
|         setup_future_usage -> Nullable<FutureUsage>, |         setup_future_usage -> Nullable<FutureUsage>, | ||||||
|         off_session -> Nullable<Bool>, |         off_session -> Nullable<Bool>, | ||||||
|         client_secret -> Nullable<Varchar>, |         client_secret -> Nullable<Varchar>, | ||||||
|  |         active_attempt_id -> Varchar, | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | |||||||
| @ -0,0 +1,5 @@ | |||||||
|  | -- This file should undo anything in `up.sql` | ||||||
|  | DROP INDEX payment_attempt_payment_id_merchant_id_index; | ||||||
|  | CREATE UNIQUE INDEX payment_attempt_payment_id_merchant_id_index ON payment_attempt (payment_id, merchant_id); | ||||||
|  | DROP INDEX payment_attempt_payment_id_merchant_id_attempt_id_index; | ||||||
|  | ALTER TABLE PAYMENT_INTENT DROP COLUMN attempt_id; | ||||||
| @ -0,0 +1,11 @@ | |||||||
|  | -- Your SQL goes here | ||||||
|  | ALTER TABLE payment_intent ADD COLUMN active_attempt_id VARCHAR(64) NOT NULL DEFAULT 'xxx'; | ||||||
|  |  | ||||||
|  | UPDATE payment_intent SET active_attempt_id = payment_attempt.attempt_id from payment_attempt where payment_intent.active_attempt_id = payment_attempt.payment_id; | ||||||
|  |  | ||||||
|  | CREATE UNIQUE INDEX payment_attempt_payment_id_merchant_id_attempt_id_index ON payment_attempt (payment_id, merchant_id, attempt_id); | ||||||
|  |  | ||||||
|  | -- Because payment_attempt table can have rows with same payment_id and merchant_id, this index is dropped. | ||||||
|  | DROP index payment_attempt_payment_id_merchant_id_index; | ||||||
|  |  | ||||||
|  | CREATE INDEX payment_attempt_payment_id_merchant_id_index ON payment_attempt (payment_id, merchant_id); | ||||||
| @ -0,0 +1,2 @@ | |||||||
|  | -- This file should undo anything in `up.sql` | ||||||
|  | DROP INDEX payment_attempt_connector_transaction_id_merchant_id_index; | ||||||
| @ -0,0 +1,2 @@ | |||||||
|  | -- Your SQL goes here | ||||||
|  | CREATE INDEX payment_attempt_connector_transaction_id_merchant_id_index ON payment_attempt (connector_transaction_id, merchant_id); | ||||||
		Reference in New Issue
	
	Block a user
	 Abhishek
					Abhishek