mirror of
https://github.com/juspay/hyperswitch.git
synced 2026-03-13 09:02:06 +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]]
|
||||
name = "toml_edit"
|
||||
version = "0.19.7"
|
||||
version = "0.19.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc18466501acd8ac6a3f615dd29a3438f8ca6bb3b19537138b3106e575621274"
|
||||
checksum = "239410c8609e8125456927e6707163a3b1fdb40561e4b803bc041f466ccfdc13"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"serde",
|
||||
@@ -5024,9 +5024,9 @@ checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd"
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.3.6"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "23d020b441f92996c80d94ae9166e8501e59c7bb56121189dc9eab3bd8216966"
|
||||
checksum = "ae8970b36c66498d8ff1d66685dc86b91b29db0c7739899012f63a63814b4b28"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
@@ -193,7 +193,7 @@ async fn drainer(
|
||||
}
|
||||
kv::Updateable::PaymentAttemptUpdate(a) => {
|
||||
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,
|
||||
payment_attempt
|
||||
)
|
||||
|
||||
@@ -880,7 +880,7 @@ impl<F, T>
|
||||
.transaction_id
|
||||
.map_or(response.order_id, Some) // For paypal there will be no transaction_id, only order_id will be present
|
||||
.map(types::ResponseId::ConnectorTransactionId)
|
||||
.ok_or_else(|| errors::ConnectorError::MissingConnectorTransactionID)?,
|
||||
.ok_or(errors::ConnectorError::MissingConnectorTransactionID)?,
|
||||
redirection_data,
|
||||
mandate_reference: None,
|
||||
// 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
|
||||
.as_ref()
|
||||
.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.merchant_id,
|
||||
&pi.active_attempt_id,
|
||||
merchant_account.storage_scheme,
|
||||
)
|
||||
.await
|
||||
|
||||
@@ -6,7 +6,6 @@ pub mod transformers;
|
||||
|
||||
use std::{fmt::Debug, marker::PhantomData, time::Instant};
|
||||
|
||||
use common_utils::ext_traits::AsyncExt;
|
||||
use error_stack::{IntoReport, ResultExt};
|
||||
use futures::future::join_all;
|
||||
use router_env::{instrument, tracing};
|
||||
@@ -127,17 +126,29 @@ where
|
||||
|
||||
payment_data = match connector_details {
|
||||
api::ConnectorCallType::Single(connector) => {
|
||||
call_connector_service(
|
||||
let router_data = call_connector_service(
|
||||
state,
|
||||
&merchant_account,
|
||||
&validate_result.payment_id,
|
||||
connector,
|
||||
&operation,
|
||||
payment_data,
|
||||
&payment_data,
|
||||
&customer,
|
||||
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) => {
|
||||
@@ -367,13 +378,12 @@ impl PaymentRedirectFlow for PaymentRedirectSync {
|
||||
pub async fn call_connector_service<F, Op, Req>(
|
||||
state: &AppState,
|
||||
merchant_account: &storage::MerchantAccount,
|
||||
payment_id: &api::PaymentIdType,
|
||||
connector: api::ConnectorData,
|
||||
_operation: &Op,
|
||||
payment_data: PaymentData<F>,
|
||||
payment_data: &PaymentData<F>,
|
||||
customer: &Option<storage::Customer>,
|
||||
call_connector_action: CallConnectorAction,
|
||||
) -> RouterResult<PaymentData<F>>
|
||||
) -> RouterResult<types::RouterData<F, Req, types::PaymentsResponseData>>
|
||||
where
|
||||
Op: Debug + Sync,
|
||||
F: Send + Clone,
|
||||
@@ -388,8 +398,6 @@ where
|
||||
// To perform router related operation for PaymentResponse
|
||||
PaymentResponse: Operation<F, Req>,
|
||||
{
|
||||
let db = &*state.store;
|
||||
|
||||
let stime_connector = Instant::now();
|
||||
|
||||
let mut router_data = payment_data
|
||||
@@ -420,28 +428,11 @@ where
|
||||
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 duration_connector = etime_connector.saturating_duration_since(stime_connector);
|
||||
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>(
|
||||
@@ -668,9 +659,10 @@ pub async fn list_payments(
|
||||
let pi = futures::stream::iter(payment_intents)
|
||||
.filter_map(|pi| async {
|
||||
let pa = db
|
||||
.find_payment_attempt_by_payment_id_merchant_id(
|
||||
.find_payment_attempt_by_payment_id_merchant_id_attempt_id(
|
||||
&pi.payment_id,
|
||||
merchant_id,
|
||||
&pi.active_attempt_id,
|
||||
// since OLAP doesn't have KV. Force to get the data from PSQL.
|
||||
storage_enums::MerchantStorageScheme::PostgresOnly,
|
||||
)
|
||||
|
||||
@@ -56,9 +56,10 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, api::PaymentsCancelRequest>
|
||||
})?;
|
||||
|
||||
let mut payment_attempt = db
|
||||
.find_payment_attempt_by_payment_id_merchant_id(
|
||||
&payment_id,
|
||||
.find_payment_attempt_by_payment_id_merchant_id_attempt_id(
|
||||
payment_intent.payment_id.as_str(),
|
||||
merchant_id,
|
||||
payment_intent.active_attempt_id.as_str(),
|
||||
storage_scheme,
|
||||
)
|
||||
.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();
|
||||
payment_data.payment_attempt = db
|
||||
.update_payment_attempt(
|
||||
.update_payment_attempt_with_attempt_id(
|
||||
payment_data.payment_attempt,
|
||||
storage::PaymentAttemptUpdate::VoidUpdate {
|
||||
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)?;
|
||||
|
||||
payment_attempt = db
|
||||
.find_payment_attempt_by_payment_id_merchant_id(
|
||||
&payment_id,
|
||||
.find_payment_attempt_by_payment_id_merchant_id_attempt_id(
|
||||
payment_intent.payment_id.as_str(),
|
||||
merchant_id,
|
||||
payment_intent.active_attempt_id.as_str(),
|
||||
storage_scheme,
|
||||
)
|
||||
.await
|
||||
|
||||
@@ -94,9 +94,10 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, api::PaymentsRequest> for Co
|
||||
})?;
|
||||
|
||||
payment_attempt = db
|
||||
.find_payment_attempt_by_payment_id_merchant_id(
|
||||
&payment_id,
|
||||
.find_payment_attempt_by_payment_id_merchant_id_attempt_id(
|
||||
&payment_intent.payment_id,
|
||||
merchant_id,
|
||||
&payment_intent.active_attempt_id,
|
||||
storage_scheme,
|
||||
)
|
||||
.await
|
||||
|
||||
@@ -96,9 +96,10 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, api::PaymentsRequest> for Pa
|
||||
})?;
|
||||
|
||||
payment_attempt = db
|
||||
.find_payment_attempt_by_payment_id_merchant_id(
|
||||
&payment_id,
|
||||
.find_payment_attempt_by_payment_id_merchant_id_attempt_id(
|
||||
payment_intent.payment_id.as_str(),
|
||||
merchant_id,
|
||||
payment_intent.active_attempt_id.as_str(),
|
||||
storage_scheme,
|
||||
)
|
||||
.await
|
||||
@@ -338,7 +339,7 @@ impl<F: Clone> UpdateTracker<F, PaymentData<F>, api::PaymentsRequest> for Paymen
|
||||
.attach_printable("Failed to encode additional pm data")?;
|
||||
|
||||
payment_data.payment_attempt = db
|
||||
.update_payment_attempt(
|
||||
.update_payment_attempt_with_attempt_id(
|
||||
payment_data.payment_attempt,
|
||||
storage::PaymentAttemptUpdate::ConfirmUpdate {
|
||||
amount: payment_data.amount.into(),
|
||||
|
||||
@@ -127,6 +127,7 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, api::PaymentsRequest> for Pa
|
||||
request,
|
||||
shipping_address.clone().map(|x| x.address_id),
|
||||
billing_address.clone().map(|x| x.address_id),
|
||||
payment_attempt.attempt_id.to_owned(),
|
||||
)?,
|
||||
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();
|
||||
|
||||
payment_data.payment_attempt = db
|
||||
.update_payment_attempt(
|
||||
.update_payment_attempt_with_attempt_id(
|
||||
payment_data.payment_attempt,
|
||||
storage::PaymentAttemptUpdate::UpdateTrackers {
|
||||
payment_token,
|
||||
@@ -479,6 +480,7 @@ impl PaymentCreate {
|
||||
request: &api::PaymentsRequest,
|
||||
shipping_address_id: Option<String>,
|
||||
billing_address_id: Option<String>,
|
||||
active_attempt_id: String,
|
||||
) -> RouterResult<storage::PaymentIntentNew> {
|
||||
let created_at @ modified_at @ last_synced = Some(common_utils::date_time::now());
|
||||
let status =
|
||||
@@ -512,6 +514,7 @@ impl PaymentCreate {
|
||||
statement_descriptor_name: request.statement_descriptor_name.clone(),
|
||||
statement_descriptor_suffix: request.statement_descriptor_suffix.clone(),
|
||||
metadata: metadata.map(masking::Secret::new),
|
||||
active_attempt_id,
|
||||
..storage::PaymentIntentNew::default()
|
||||
})
|
||||
}
|
||||
|
||||
@@ -106,7 +106,12 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, api::VerifyRequest> for Paym
|
||||
|
||||
payment_intent = match db
|
||||
.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,
|
||||
)
|
||||
.await
|
||||
@@ -312,6 +317,7 @@ impl PaymentMethodValidate {
|
||||
payment_id: &str,
|
||||
merchant_id: &str,
|
||||
request: &api::VerifyRequest,
|
||||
active_attempt_id: String,
|
||||
) -> storage::PaymentIntentNew {
|
||||
let created_at @ modified_at @ last_synced = Some(date_time::now());
|
||||
let status = helpers::payment_intent_status_fsm(&request.payment_method_data, Some(true));
|
||||
@@ -331,6 +337,7 @@ impl PaymentMethodValidate {
|
||||
client_secret: Some(client_secret),
|
||||
setup_future_usage: request.setup_future_usage.map(ForeignInto::foreign_into),
|
||||
off_session: request.off_session,
|
||||
active_attempt_id,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -361,7 +361,7 @@ async fn payment_response_update_tracker<F: Clone, T>(
|
||||
|
||||
payment_data.payment_attempt = match payment_attempt_update {
|
||||
Some(payment_attempt_update) => db
|
||||
.update_payment_attempt(
|
||||
.update_payment_attempt_with_attempt_id(
|
||||
payment_data.payment_attempt,
|
||||
payment_attempt_update,
|
||||
storage_scheme,
|
||||
|
||||
@@ -71,9 +71,10 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, api::PaymentsSessionRequest>
|
||||
)?;
|
||||
|
||||
let mut payment_attempt = db
|
||||
.find_payment_attempt_by_payment_id_merchant_id(
|
||||
&payment_id,
|
||||
.find_payment_attempt_by_payment_id_merchant_id_attempt_id(
|
||||
payment_intent.payment_id.as_str(),
|
||||
merchant_id,
|
||||
payment_intent.active_attempt_id.as_str(),
|
||||
storage_scheme,
|
||||
)
|
||||
.await
|
||||
|
||||
@@ -68,9 +68,10 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, api::PaymentsStartRequest> f
|
||||
)?;
|
||||
|
||||
payment_attempt = db
|
||||
.find_payment_attempt_by_payment_id_merchant_id(
|
||||
&payment_id,
|
||||
.find_payment_attempt_by_payment_id_merchant_id_attempt_id(
|
||||
payment_intent.payment_id.as_str(),
|
||||
merchant_id,
|
||||
payment_intent.active_attempt_id.as_str(),
|
||||
storage_scheme,
|
||||
)
|
||||
.await
|
||||
|
||||
@@ -191,27 +191,11 @@ async fn get_tracker_for_sync<
|
||||
)> {
|
||||
let (payment_intent, payment_attempt, currency, amount);
|
||||
|
||||
payment_attempt = match payment_id {
|
||||
api::PaymentIdType::PaymentIntentId(ref id) => {
|
||||
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))?;
|
||||
(payment_intent, payment_attempt) =
|
||||
get_payment_intent_payment_attempt(db, payment_id, merchant_id, storage_scheme).await?;
|
||||
|
||||
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
|
||||
.find_connector_response_by_payment_id_merchant_id_attempt_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?;
|
||||
|
||||
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
|
||||
.find_payment_attempt_by_payment_id_merchant_id(
|
||||
&payment_id,
|
||||
.find_payment_attempt_by_payment_id_merchant_id_attempt_id(
|
||||
payment_intent.payment_id.as_str(),
|
||||
merchant_id,
|
||||
payment_intent.active_attempt_id.as_str(),
|
||||
storage_scheme,
|
||||
)
|
||||
.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_experience = payment_data.payment_attempt.payment_experience.clone();
|
||||
payment_data.payment_attempt = db
|
||||
.update_payment_attempt(
|
||||
.update_payment_attempt_with_attempt_id(
|
||||
payment_data.payment_attempt,
|
||||
storage::PaymentAttemptUpdate::Update {
|
||||
amount: payment_data.amount.into(),
|
||||
|
||||
@@ -37,26 +37,6 @@ pub async fn refund_create_core(
|
||||
|
||||
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
|
||||
.find_payment_intent_by_payment_id_merchant_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
|
||||
.merchant_connector_details
|
||||
.as_ref()
|
||||
|
||||
@@ -214,9 +214,9 @@ async fn get_payment_attempt_from_object_reference_id(
|
||||
.await
|
||||
.change_context(errors::WebhooksFlowError::ResourceNotFound),
|
||||
api::ObjectReferenceId::PaymentId(api::PaymentIdType::PaymentAttemptId(ref id)) => db
|
||||
.find_payment_attempt_by_merchant_id_attempt_id(
|
||||
&merchant_account.merchant_id,
|
||||
.find_payment_attempt_by_attempt_id_merchant_id(
|
||||
id,
|
||||
&merchant_account.merchant_id,
|
||||
merchant_account.storage_scheme,
|
||||
)
|
||||
.await
|
||||
|
||||
@@ -12,20 +12,13 @@ pub trait PaymentAttemptInterface {
|
||||
storage_scheme: enums::MerchantStorageScheme,
|
||||
) -> CustomResult<types::PaymentAttempt, errors::StorageError>;
|
||||
|
||||
async fn update_payment_attempt(
|
||||
async fn update_payment_attempt_with_attempt_id(
|
||||
&self,
|
||||
this: types::PaymentAttempt,
|
||||
payment_attempt: types::PaymentAttemptUpdate,
|
||||
storage_scheme: enums::MerchantStorageScheme,
|
||||
) -> 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(
|
||||
&self,
|
||||
connector_transaction_id: &str,
|
||||
@@ -48,12 +41,20 @@ pub trait PaymentAttemptInterface {
|
||||
storage_scheme: enums::MerchantStorageScheme,
|
||||
) -> 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,
|
||||
payment_id: &str,
|
||||
merchant_id: &str,
|
||||
attempt_id: &str,
|
||||
storage_scheme: enums::MerchantStorageScheme,
|
||||
) -> 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"))]
|
||||
@@ -83,27 +84,14 @@ mod storage {
|
||||
.into_report()
|
||||
}
|
||||
|
||||
async fn update_payment_attempt(
|
||||
async fn update_payment_attempt_with_attempt_id(
|
||||
&self,
|
||||
this: PaymentAttempt,
|
||||
payment_attempt: PaymentAttemptUpdate,
|
||||
_storage_scheme: enums::MerchantStorageScheme,
|
||||
) -> CustomResult<PaymentAttempt, errors::StorageError> {
|
||||
let conn = connection::pg_connection_write(self).await?;
|
||||
this.update(&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)
|
||||
let conn = connection::pg_connection_write(&self).await?;
|
||||
this.update_with_attempt_id(&conn, payment_attempt)
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
.into_report()
|
||||
@@ -162,7 +150,26 @@ mod storage {
|
||||
.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,
|
||||
merchant_id: &str,
|
||||
attempt_id: &str,
|
||||
@@ -180,8 +187,9 @@ mod storage {
|
||||
|
||||
#[async_trait::async_trait]
|
||||
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,
|
||||
_payment_id: &str,
|
||||
_merchant_id: &str,
|
||||
_attempt_id: &str,
|
||||
_storage_scheme: enums::MerchantStorageScheme,
|
||||
@@ -190,6 +198,16 @@ impl PaymentAttemptInterface for MockDb {
|
||||
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(
|
||||
&self,
|
||||
_merchant_id: &str,
|
||||
@@ -252,7 +270,7 @@ impl PaymentAttemptInterface for MockDb {
|
||||
|
||||
// safety: only used for testing
|
||||
#[allow(clippy::unwrap_used)]
|
||||
async fn update_payment_attempt(
|
||||
async fn update_payment_attempt_with_attempt_id(
|
||||
&self,
|
||||
this: types::PaymentAttempt,
|
||||
payment_attempt: types::PaymentAttemptUpdate,
|
||||
@@ -262,7 +280,7 @@ impl PaymentAttemptInterface for MockDb {
|
||||
|
||||
let item = payment_attempts
|
||||
.iter_mut()
|
||||
.find(|item| item.id == this.id)
|
||||
.find(|item| item.attempt_id == this.attempt_id)
|
||||
.unwrap();
|
||||
|
||||
*item = payment_attempt.apply_changeset(this);
|
||||
@@ -270,16 +288,6 @@ impl PaymentAttemptInterface for MockDb {
|
||||
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(
|
||||
&self,
|
||||
_connector_transaction_id: &str,
|
||||
@@ -317,6 +325,7 @@ mod storage {
|
||||
use common_utils::date_time;
|
||||
use error_stack::{IntoReport, ResultExt};
|
||||
use redis_interface::HsetnxReply;
|
||||
use storage_models::reverse_lookup::ReverseLookup;
|
||||
|
||||
use super::PaymentAttemptInterface;
|
||||
use crate::{
|
||||
@@ -406,9 +415,7 @@ mod storage {
|
||||
ReverseLookupNew {
|
||||
lookup_id: format!(
|
||||
"{}_{}",
|
||||
&created_attempt.merchant_id,
|
||||
// [#439]: Change this to `attempt_id`
|
||||
&created_attempt.payment_id,
|
||||
&created_attempt.merchant_id, &created_attempt.attempt_id,
|
||||
),
|
||||
pk_id: key,
|
||||
sk_id: field,
|
||||
@@ -440,7 +447,7 @@ mod storage {
|
||||
}
|
||||
}
|
||||
|
||||
async fn update_payment_attempt(
|
||||
async fn update_payment_attempt_with_attempt_id(
|
||||
&self,
|
||||
this: PaymentAttempt,
|
||||
payment_attempt: PaymentAttemptUpdate,
|
||||
@@ -449,7 +456,7 @@ mod storage {
|
||||
match storage_scheme {
|
||||
enums::MerchantStorageScheme::PostgresOnly => {
|
||||
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()
|
||||
@@ -465,33 +472,39 @@ mod storage {
|
||||
.change_context(errors::StorageError::KVError)?;
|
||||
let field = format!("pa_{}", updated_attempt.attempt_id);
|
||||
let updated_attempt = self
|
||||
.redis_conn()
|
||||
.map_err(Into::<errors::StorageError>::into)?
|
||||
.redis_conn
|
||||
.set_hash_fields(&key, (&field, &redis_value))
|
||||
.await
|
||||
.map(|_| updated_attempt)
|
||||
.change_context(errors::StorageError::KVError)?;
|
||||
|
||||
let conn = connection::pg_connection_write(self).await?;
|
||||
// Reverse lookup for connector_transaction_id
|
||||
if let (None, Some(connector_transaction_id)) = (
|
||||
match (
|
||||
old_connector_transaction_id,
|
||||
&updated_attempt.connector_transaction_id,
|
||||
) {
|
||||
let field = format!("pa_{}", updated_attempt.attempt_id);
|
||||
ReverseLookupNew {
|
||||
lookup_id: format!(
|
||||
"{}_{}",
|
||||
&updated_attempt.merchant_id, connector_transaction_id
|
||||
),
|
||||
pk_id: key.clone(),
|
||||
sk_id: field.clone(),
|
||||
source: "payment_attempt".to_string(),
|
||||
(None, Some(connector_transaction_id)) => {
|
||||
add_connector_txn_id_to_reverse_lookup(
|
||||
self,
|
||||
key.as_str(),
|
||||
this.merchant_id.as_str(),
|
||||
updated_attempt.attempt_id.as_str(),
|
||||
connector_transaction_id.as_str(),
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
.insert(&conn)
|
||||
.await
|
||||
.map_err(Into::<errors::StorageError>::into)
|
||||
.into_report()?;
|
||||
(Some(old_connector_transaction_id), Some(connector_transaction_id)) => {
|
||||
if old_connector_transaction_id.ne(connector_transaction_id) {
|
||||
add_connector_txn_id_to_reverse_lookup(
|
||||
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 {
|
||||
@@ -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(
|
||||
&self,
|
||||
connector_transaction_id: &str,
|
||||
@@ -594,21 +572,17 @@ mod storage {
|
||||
&self,
|
||||
payment_id: &str,
|
||||
merchant_id: &str,
|
||||
storage_scheme: enums::MerchantStorageScheme,
|
||||
_storage_scheme: enums::MerchantStorageScheme,
|
||||
) -> 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,
|
||||
merchant_id,
|
||||
storage_scheme,
|
||||
)
|
||||
.await
|
||||
.and_then(|attempt| match attempt.status {
|
||||
enums::AttemptStatus::Charged => Ok(attempt),
|
||||
_ => Err(errors::StorageError::ValueNotFound(format!(
|
||||
"Successful payment attempt does not exist for {payment_id}_{merchant_id}"
|
||||
)))
|
||||
.into_report(),
|
||||
})
|
||||
.map_err(Into::into)
|
||||
.into_report()
|
||||
}
|
||||
|
||||
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,
|
||||
merchant_id: &str,
|
||||
attempt_id: &str,
|
||||
merchant_id: &str,
|
||||
storage_scheme: enums::MerchantStorageScheme,
|
||||
) -> CustomResult<PaymentAttempt, errors::StorageError> {
|
||||
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,
|
||||
off_session: new.off_session,
|
||||
client_secret: new.client_secret.clone(),
|
||||
active_attempt_id: new.active_attempt_id.to_owned(),
|
||||
};
|
||||
|
||||
match self
|
||||
@@ -347,6 +348,7 @@ impl PaymentIntentInterface for MockDb {
|
||||
setup_future_usage: new.setup_future_usage,
|
||||
off_session: new.off_session,
|
||||
client_secret: new.client_secret,
|
||||
active_attempt_id: new.active_attempt_id.to_owned(),
|
||||
};
|
||||
payment_intents.push(payment_intent.clone());
|
||||
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 payment_id = Uuid::new_v4().to_string();
|
||||
let attempt_id = Uuid::new_v4().to_string();
|
||||
let merchant_id = Uuid::new_v4().to_string();
|
||||
let connector = types::Connector::Dummy.to_string();
|
||||
|
||||
@@ -106,6 +107,7 @@ mod tests {
|
||||
})),
|
||||
created_at: current_time.into(),
|
||||
modified_at: current_time.into(),
|
||||
attempt_id: attempt_id.clone(),
|
||||
..PaymentAttemptNew::default()
|
||||
};
|
||||
state
|
||||
@@ -116,9 +118,10 @@ mod tests {
|
||||
|
||||
let response = state
|
||||
.store
|
||||
.find_payment_attempt_by_payment_id_merchant_id(
|
||||
.find_payment_attempt_by_payment_id_merchant_id_attempt_id(
|
||||
&payment_id,
|
||||
&merchant_id,
|
||||
&attempt_id,
|
||||
enums::MerchantStorageScheme::PostgresOnly,
|
||||
)
|
||||
.await
|
||||
@@ -150,6 +153,7 @@ mod tests {
|
||||
modified_at: current_time.into(),
|
||||
// Adding a mandate_id
|
||||
mandate_id: Some("man_121212".to_string()),
|
||||
attempt_id: uuid.clone(),
|
||||
..PaymentAttemptNew::default()
|
||||
};
|
||||
state
|
||||
@@ -160,9 +164,10 @@ mod tests {
|
||||
|
||||
let response = state
|
||||
.store
|
||||
.find_payment_attempt_by_payment_id_merchant_id(
|
||||
.find_payment_attempt_by_payment_id_merchant_id_attempt_id(
|
||||
&uuid,
|
||||
"1",
|
||||
&uuid,
|
||||
enums::MerchantStorageScheme::PostgresOnly,
|
||||
)
|
||||
.await
|
||||
|
||||
@@ -33,6 +33,7 @@ pub struct PaymentIntent {
|
||||
pub setup_future_usage: Option<storage_enums::FutureUsage>,
|
||||
pub off_session: Option<bool>,
|
||||
pub client_secret: Option<String>,
|
||||
pub active_attempt_id: String,
|
||||
}
|
||||
|
||||
#[derive(
|
||||
@@ -72,6 +73,7 @@ pub struct PaymentIntentNew {
|
||||
pub client_secret: Option<String>,
|
||||
pub setup_future_usage: Option<storage_enums::FutureUsage>,
|
||||
pub off_session: Option<bool>,
|
||||
pub active_attempt_id: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
@@ -109,6 +111,9 @@ pub enum PaymentIntentUpdate {
|
||||
billing_address_id: Option<String>,
|
||||
return_url: Option<String>,
|
||||
},
|
||||
PaymentAttemptUpdate {
|
||||
active_attempt_id: String,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, AsChangeset, router_derive::DebugAsDisplay)]
|
||||
@@ -128,6 +133,7 @@ pub struct PaymentIntentUpdateInternal {
|
||||
pub billing_address_id: Option<String>,
|
||||
pub shipping_address_id: Option<String>,
|
||||
pub modified_at: Option<PrimitiveDateTime>,
|
||||
pub active_attempt_id: Option<String>,
|
||||
}
|
||||
|
||||
impl PaymentIntentUpdate {
|
||||
@@ -242,6 +248,10 @@ impl From<PaymentIntentUpdate> for PaymentIntentUpdateInternal {
|
||||
modified_at: Some(common_utils::date_time::now()),
|
||||
..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 {
|
||||
#[instrument(skip(conn))]
|
||||
pub async fn update(
|
||||
pub async fn update_with_attempt_id(
|
||||
self,
|
||||
conn: &PgPooledConn,
|
||||
payment_attempt: PaymentAttemptUpdate,
|
||||
) -> 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,
|
||||
dsl::payment_id
|
||||
.eq(self.payment_id.to_owned())
|
||||
dsl::attempt_id
|
||||
.eq(self.attempt_id.to_owned())
|
||||
.and(dsl::merchant_id.eq(self.merchant_id.to_owned())),
|
||||
PaymentAttemptUpdateInternal::from(payment_attempt),
|
||||
)
|
||||
@@ -39,27 +44,10 @@ impl PaymentAttempt {
|
||||
errors::DatabaseError::NoFieldsToUpdate => Ok(self),
|
||||
_ => Err(error),
|
||||
},
|
||||
Ok(mut payment_attempts) => payment_attempts
|
||||
.pop()
|
||||
.ok_or(error_stack::report!(errors::DatabaseError::NotFound)),
|
||||
result => result,
|
||||
}
|
||||
}
|
||||
|
||||
#[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))]
|
||||
pub async fn find_optional_by_payment_id_merchant_id(
|
||||
conn: &PgPooledConn,
|
||||
@@ -118,7 +106,7 @@ impl PaymentAttempt {
|
||||
.fold(
|
||||
Err(errors::DatabaseError::NotFound).into_report(),
|
||||
|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),
|
||||
},
|
||||
)
|
||||
@@ -153,4 +141,22 @@ impl PaymentAttempt {
|
||||
)
|
||||
.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>,
|
||||
off_session -> Nullable<Bool>,
|
||||
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