feat(core): Add record attempt operation for revenue recovery webhooks (#7236)

Co-authored-by: Chikke Srujan <chikke.srujan@Chikke-Srujan-N7WRTY72X7.local>
Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
This commit is contained in:
chikke srujan
2025-03-07 19:34:47 +05:30
committed by GitHub
parent 3cf529c4dc
commit 24aa00341f
18 changed files with 1024 additions and 63 deletions

View File

@ -483,4 +483,6 @@ pub enum RevenueRecoveryError {
PaymentIntentCreateFailed,
#[error("Source verification failed for billing connector")]
WebhookAuthenticationFailed,
#[error("Payment merchant connector account not found using acccount reference id")]
PaymentMerchantConnectorAccountNotFound,
}

View File

@ -1725,6 +1725,76 @@ where
)
}
#[cfg(feature = "v2")]
#[allow(clippy::too_many_arguments)]
pub async fn record_attempt_core(
state: SessionState,
req_state: ReqState,
merchant_account: domain::MerchantAccount,
profile: domain::Profile,
key_store: domain::MerchantKeyStore,
req: api_models::payments::PaymentsAttemptRecordRequest,
payment_id: id_type::GlobalPaymentId,
header_payload: HeaderPayload,
platform_merchant_account: Option<domain::MerchantAccount>,
) -> RouterResponse<api_models::payments::PaymentAttemptResponse> {
tracing::Span::current().record("merchant_id", merchant_account.get_id().get_string_repr());
let operation: &operations::payment_attempt_record::PaymentAttemptRecord =
&operations::payment_attempt_record::PaymentAttemptRecord;
let boxed_operation: BoxedOperation<
'_,
api::RecordAttempt,
api_models::payments::PaymentsAttemptRecordRequest,
domain_payments::PaymentAttemptRecordData<api::RecordAttempt>,
> = Box::new(operation);
let _validate_result = boxed_operation
.to_validate_request()?
.validate_request(&req, &merchant_account)?;
tracing::Span::current().record("global_payment_id", payment_id.get_string_repr());
let operations::GetTrackerResponse { payment_data } = boxed_operation
.to_get_tracker()?
.get_trackers(
&state,
&payment_id,
&req,
&merchant_account,
&profile,
&key_store,
&header_payload,
platform_merchant_account.as_ref(),
)
.await?;
let (_operation, payment_data) = boxed_operation
.to_update_tracker()?
.update_trackers(
&state,
req_state,
payment_data,
None,
merchant_account.storage_scheme,
None,
&key_store,
None,
header_payload.clone(),
)
.await?;
transformers::GenerateResponse::generate_response(
payment_data,
&state,
None,
None,
header_payload.x_hs_latency,
&merchant_account,
&profile,
)
}
#[cfg(feature = "v2")]
#[allow(clippy::too_many_arguments)]
pub async fn payments_intent_core<F, Res, Req, Op, D>(

View File

@ -30,6 +30,8 @@ pub mod payments_incremental_authorization;
#[cfg(feature = "v1")]
pub mod tax_calculation;
#[cfg(feature = "v2")]
pub mod payment_attempt_record;
#[cfg(feature = "v2")]
pub mod payment_confirm_intent;
#[cfg(feature = "v2")]

View File

@ -0,0 +1,323 @@
use std::marker::PhantomData;
use api_models::{enums::FrmSuggestion, payments::PaymentsAttemptRecordRequest};
use async_trait::async_trait;
use common_utils::{
errors::CustomResult,
ext_traits::{AsyncExt, Encode, ValueExt},
types::keymanager::ToEncryptable,
};
use error_stack::ResultExt;
use hyperswitch_domain_models::payments::PaymentAttemptRecordData;
use masking::PeekInterface;
use router_env::{instrument, tracing};
use super::{BoxedOperation, Domain, GetTracker, Operation, UpdateTracker, ValidateRequest};
use crate::{
core::{
errors::{self, StorageErrorExt},
payments::{
self,
cards::create_encrypted_data,
helpers,
operations::{self, ValidateStatusForOperation},
},
},
db::{domain::types, errors::RouterResult},
routes::{app::ReqState, SessionState},
services,
types::{
api,
domain::{self, types as domain_types},
storage::{self, enums},
},
utils::{self, OptionExt},
};
#[derive(Debug, Clone, Copy)]
pub struct PaymentAttemptRecord;
type PaymentsAttemptRecordOperation<'b, F> =
BoxedOperation<'b, F, PaymentsAttemptRecordRequest, PaymentAttemptRecordData<F>>;
impl<F: Send + Clone + Sync> Operation<F, PaymentsAttemptRecordRequest> for &PaymentAttemptRecord {
type Data = PaymentAttemptRecordData<F>;
fn to_validate_request(
&self,
) -> RouterResult<
&(dyn ValidateRequest<F, PaymentsAttemptRecordRequest, Self::Data> + Send + Sync),
> {
Ok(*self)
}
fn to_get_tracker(
&self,
) -> RouterResult<&(dyn GetTracker<F, Self::Data, PaymentsAttemptRecordRequest> + Send + Sync)>
{
Ok(*self)
}
fn to_domain(
&self,
) -> RouterResult<&(dyn Domain<F, PaymentsAttemptRecordRequest, Self::Data>)> {
Ok(*self)
}
fn to_update_tracker(
&self,
) -> RouterResult<&(dyn UpdateTracker<F, Self::Data, PaymentsAttemptRecordRequest> + Send + Sync)>
{
Ok(*self)
}
}
impl ValidateStatusForOperation for PaymentAttemptRecord {
fn validate_status_for_operation(
&self,
intent_status: common_enums::IntentStatus,
) -> Result<(), errors::ApiErrorResponse> {
// need to verify this?
match intent_status {
// Payment attempt can be recorded for failed payment as well in revenue recovery flow.
common_enums::IntentStatus::RequiresPaymentMethod
| common_enums::IntentStatus::Failed => Ok(()),
common_enums::IntentStatus::Succeeded
| common_enums::IntentStatus::Cancelled
| common_enums::IntentStatus::Processing
| common_enums::IntentStatus::RequiresCustomerAction
| common_enums::IntentStatus::RequiresMerchantAction
| common_enums::IntentStatus::RequiresCapture
| common_enums::IntentStatus::PartiallyCaptured
| common_enums::IntentStatus::RequiresConfirmation
| common_enums::IntentStatus::PartiallyCapturedAndCapturable => {
Err(errors::ApiErrorResponse::PaymentUnexpectedState {
current_flow: format!("{self:?}"),
field_name: "status".to_string(),
current_value: intent_status.to_string(),
states: [
common_enums::IntentStatus::RequiresPaymentMethod,
common_enums::IntentStatus::Failed,
]
.map(|enum_value| enum_value.to_string())
.join(", "),
})
}
}
}
}
#[async_trait]
impl<F: Send + Clone + Sync>
GetTracker<F, PaymentAttemptRecordData<F>, PaymentsAttemptRecordRequest>
for PaymentAttemptRecord
{
#[instrument(skip_all)]
async fn get_trackers<'a>(
&'a self,
state: &'a SessionState,
payment_id: &common_utils::id_type::GlobalPaymentId,
request: &PaymentsAttemptRecordRequest,
merchant_account: &domain::MerchantAccount,
_profile: &domain::Profile,
key_store: &domain::MerchantKeyStore,
_header_payload: &hyperswitch_domain_models::payments::HeaderPayload,
_platform_merchant_account: Option<&domain::MerchantAccount>,
) -> RouterResult<operations::GetTrackerResponse<PaymentAttemptRecordData<F>>> {
let db = &*state.store;
let key_manager_state = &state.into();
let storage_scheme = merchant_account.storage_scheme;
let payment_intent = db
.find_payment_intent_by_id(key_manager_state, payment_id, key_store, storage_scheme)
.await
.to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?;
self.validate_status_for_operation(payment_intent.status)?;
let payment_method_billing_address = request
.payment_method_data
.as_ref()
.and_then(|data| {
data.billing
.as_ref()
.map(|address| address.clone().encode_to_value())
})
.transpose()
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Failed to encode payment_method_billing address")?
.map(masking::Secret::new);
let batch_encrypted_data = domain_types::crypto_operation(
key_manager_state,
common_utils::type_name!(hyperswitch_domain_models::payments::payment_attempt::PaymentAttempt),
domain_types::CryptoOperation::BatchEncrypt(
hyperswitch_domain_models::payments::payment_attempt::FromRequestEncryptablePaymentAttempt::to_encryptable(
hyperswitch_domain_models::payments::payment_attempt::FromRequestEncryptablePaymentAttempt {
payment_method_billing_address,
},
),
),
common_utils::types::keymanager::Identifier::Merchant(merchant_account.get_id().to_owned()),
key_store.key.peek(),
)
.await
.and_then(|val| val.try_into_batchoperation())
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Failed while encrypting payment intent details".to_string())?;
let encrypted_data =
hyperswitch_domain_models::payments::payment_attempt::FromRequestEncryptablePaymentAttempt::from_encryptable(batch_encrypted_data)
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Failed while encrypting payment intent details")?;
let cell_id = state.conf.cell_information.id.clone();
let payment_attempt_domain_model =
hyperswitch_domain_models::payments::payment_attempt::PaymentAttempt::create_domain_model_using_record_request(
&payment_intent,
cell_id,
storage_scheme,
request,
encrypted_data,
)
.await?;
let payment_attempt = db
.insert_payment_attempt(
key_manager_state,
key_store,
payment_attempt_domain_model,
storage_scheme,
)
.await
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Could not insert payment attempt")?;
let revenue_recovery_data = hyperswitch_domain_models::payments::RevenueRecoveryData {
billing_connector_id: request.billing_connector_id.clone(),
processor_payment_method_token: request.processor_payment_method_token.clone(),
connector_customer_id: request.connector_customer_id.clone(),
};
let payment_data = PaymentAttemptRecordData {
flow: PhantomData,
payment_intent,
payment_attempt,
revenue_recovery_data,
};
let get_trackers_response = operations::GetTrackerResponse { payment_data };
Ok(get_trackers_response)
}
}
#[async_trait]
impl<F: Clone + Sync> UpdateTracker<F, PaymentAttemptRecordData<F>, PaymentsAttemptRecordRequest>
for PaymentAttemptRecord
{
#[instrument(skip_all)]
async fn update_trackers<'b>(
&'b self,
state: &'b SessionState,
_req_state: ReqState,
mut payment_data: PaymentAttemptRecordData<F>,
_customer: Option<domain::Customer>,
storage_scheme: enums::MerchantStorageScheme,
_updated_customer: Option<storage::CustomerUpdate>,
key_store: &domain::MerchantKeyStore,
_frm_suggestion: Option<FrmSuggestion>,
_header_payload: hyperswitch_domain_models::payments::HeaderPayload,
) -> RouterResult<(
PaymentsAttemptRecordOperation<'b, F>,
PaymentAttemptRecordData<F>,
)>
where
F: 'b + Send,
{
let feature_metadata = payment_data.get_updated_feature_metadata()?;
let payment_intent_update = hyperswitch_domain_models::payments::payment_intent::PaymentIntentUpdate::RecordUpdate
{
status: common_enums::IntentStatus::from(payment_data.payment_attempt.status),
feature_metadata: Box::new(feature_metadata),
active_attempt_id: payment_data.payment_attempt.id.clone(),
updated_by: storage_scheme.to_string(),
};
payment_data.payment_intent = state
.store
.update_payment_intent(
&state.into(),
payment_data.payment_intent,
payment_intent_update,
key_store,
storage_scheme,
)
.await
.to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?;
Ok((Box::new(self), payment_data))
}
}
impl<F: Send + Clone> ValidateRequest<F, PaymentsAttemptRecordRequest, PaymentAttemptRecordData<F>>
for PaymentAttemptRecord
{
#[instrument(skip_all)]
fn validate_request<'a, 'b>(
&'b self,
_request: &PaymentsAttemptRecordRequest,
merchant_account: &'a domain::MerchantAccount,
) -> RouterResult<operations::ValidateResult> {
Ok(operations::ValidateResult {
merchant_id: merchant_account.get_id().to_owned(),
storage_scheme: merchant_account.storage_scheme,
requeue: false,
})
}
}
#[async_trait]
impl<F: Clone + Send + Sync> Domain<F, PaymentsAttemptRecordRequest, PaymentAttemptRecordData<F>>
for PaymentAttemptRecord
{
#[instrument(skip_all)]
async fn get_customer_details<'a>(
&'a self,
_state: &SessionState,
_payment_data: &mut PaymentAttemptRecordData<F>,
_merchant_key_store: &domain::MerchantKeyStore,
_storage_scheme: enums::MerchantStorageScheme,
) -> CustomResult<
(
BoxedOperation<'a, F, PaymentsAttemptRecordRequest, PaymentAttemptRecordData<F>>,
Option<domain::Customer>,
),
errors::StorageError,
> {
Ok((Box::new(self), None))
}
#[instrument(skip_all)]
async fn make_pm_data<'a>(
&'a self,
_state: &'a SessionState,
_payment_data: &mut PaymentAttemptRecordData<F>,
_storage_scheme: enums::MerchantStorageScheme,
_merchant_key_store: &domain::MerchantKeyStore,
_customer: &Option<domain::Customer>,
_business_profile: &domain::Profile,
_should_retry_with_pan: bool,
) -> RouterResult<(
PaymentsAttemptRecordOperation<'a, F>,
Option<domain::PaymentMethodData>,
Option<String>,
)> {
Ok((Box::new(self), None, None))
}
#[instrument(skip_all)]
async fn perform_routing<'a>(
&'a self,
_merchant_account: &domain::MerchantAccount,
_business_profile: &domain::Profile,
_state: &SessionState,
_payment_data: &mut PaymentAttemptRecordData<F>,
_mechant_key_store: &domain::MerchantKeyStore,
) -> CustomResult<api::ConnectorCallType, errors::ApiErrorResponse> {
Ok(api::ConnectorCallType::Skip)
}
}

View File

@ -1796,6 +1796,30 @@ where
}
}
#[cfg(feature = "v2")]
impl<F> GenerateResponse<api_models::payments::PaymentAttemptResponse>
for hyperswitch_domain_models::payments::PaymentAttemptRecordData<F>
where
F: Clone,
{
fn generate_response(
self,
_state: &SessionState,
_connector_http_status_code: Option<u16>,
_external_latency: Option<u128>,
_is_latency_header_enabled: Option<bool>,
_merchant_account: &domain::MerchantAccount,
_profile: &domain::Profile,
) -> RouterResponse<api_models::payments::PaymentAttemptResponse> {
let payment_attempt = self.payment_attempt;
let response = api_models::payments::PaymentAttemptResponse::foreign_from(&payment_attempt);
Ok(services::ApplicationResponse::JsonWithHeaders((
response,
vec![],
)))
}
}
#[cfg(feature = "v1")]
impl<F, Op, D> ToResponse<F, D, Op> for api::PaymentsPostSessionTokensResponse
where

View File

@ -364,6 +364,7 @@ async fn incoming_webhooks_core<W: types::OutgoingWebhookType>(
&request_details,
event_type,
req_state,
merchant_connector_account,
))
.await
.change_context(errors::ApiErrorResponse::WebhookProcessingFailure)

View File

@ -1,4 +1,4 @@
use api_models::webhooks;
use api_models::{payments as api_payments, webhooks};
use common_utils::ext_traits::AsyncExt;
use error_stack::{report, ResultExt};
use hyperswitch_domain_models::revenue_recovery;
@ -29,6 +29,7 @@ pub async fn recovery_incoming_webhook_flow(
request_details: &hyperswitch_interfaces::webhooks::IncomingWebhookRequestDetails<'_>,
event_type: webhooks::IncomingWebhookEvent,
req_state: ReqState,
billing_connector_account: domain::MerchantConnectorAccount,
) -> CustomResult<webhooks::WebhookResponseTracker, errors::RevenueRecoveryError> {
// Source verification is necessary for revenue recovery webhooks flow since We don't have payment intent/attempt object created before in our system.
@ -79,17 +80,43 @@ pub async fn recovery_incoming_webhook_flow(
)
.change_context(errors::RevenueRecoveryError::TransactionWebhookProcessingFailed)?,
);
invoice_transaction_details
.get_payment_attempt(
// Find the payment merchant connector ID at the top level to avoid multiple DB calls.
let payment_merchant_connector_account = invoice_transaction_details
.find_payment_merchant_connector_account(
&state,
&req_state,
&merchant_account,
&business_profile,
&key_store,
payment_intent.payment_id.clone(),
&billing_connector_account,
)
.await?
.await?;
Some(
invoice_transaction_details
.get_payment_attempt(
&state,
&req_state,
&merchant_account,
&business_profile,
&key_store,
payment_intent.payment_id.clone(),
)
.await
.transpose()
.async_unwrap_or_else(|| async {
invoice_transaction_details
.record_payment_attempt(
&state,
&req_state,
&merchant_account,
&business_profile,
&key_store,
payment_intent.payment_id.clone(),
&billing_connector_account.id,
payment_merchant_connector_account,
)
.await
})
.await?,
)
}
false => None,
};
@ -189,13 +216,13 @@ impl RevenueRecoveryInvoice {
profile: &domain::Profile,
key_store: &domain::MerchantKeyStore,
) -> CustomResult<revenue_recovery::RecoveryPaymentIntent, errors::RevenueRecoveryError> {
let payload = api_models::payments::PaymentsCreateIntentRequest::from(&self.0);
let payload = api_payments::PaymentsCreateIntentRequest::from(&self.0);
let global_payment_id =
common_utils::id_type::GlobalPaymentId::generate(&state.conf.cell_information.id);
let create_intent_response = Box::pin(payments::payments_intent_core::<
hyperswitch_domain_models::router_flow_types::payments::PaymentCreateIntent,
api_models::payments::PaymentsIntentResponse,
api_payments::PaymentsIntentResponse,
_,
_,
hyperswitch_domain_models::payments::PaymentIntentData<
@ -242,7 +269,7 @@ impl RevenueRecoveryAttempt {
{
let attempt_response = Box::pin(payments::payments_core::<
hyperswitch_domain_models::router_flow_types::payments::PSync,
api_models::payments::PaymentsResponse,
api_payments::PaymentsResponse,
_,
_,
_,
@ -256,7 +283,7 @@ impl RevenueRecoveryAttempt {
profile.clone(),
key_store.clone(),
payments::operations::PaymentGet,
api_models::payments::PaymentsRetrieveRequest {
api_payments::PaymentsRetrieveRequest {
force_sync: false,
expand_attempts: true,
param: None,
@ -286,25 +313,122 @@ impl RevenueRecoveryAttempt {
});
Ok(payment_attempt)
}
Ok(_) => Err(errors::RevenueRecoveryError::PaymentIntentFetchFailed)
Ok(_) => Err(errors::RevenueRecoveryError::PaymentAttemptFetchFailed)
.attach_printable("Unexpected response from payment intent core"),
error @ Err(_) => {
router_env::logger::error!(?error);
Err(errors::RevenueRecoveryError::PaymentIntentFetchFailed)
.attach_printable("failed to fetch payment intent recovery webhook flow")
Err(errors::RevenueRecoveryError::PaymentAttemptFetchFailed)
.attach_printable("failed to fetch payment attempt in recovery webhook flow")
}
}?;
Ok(response)
}
#[allow(clippy::too_many_arguments)]
async fn record_payment_attempt(
&self,
_state: &SessionState,
_req_state: &ReqState,
_merchant_account: &domain::MerchantAccount,
_profile: &domain::Profile,
_key_store: &domain::MerchantKeyStore,
_payment_id: common_utils::id_type::GlobalPaymentId,
state: &SessionState,
req_state: &ReqState,
merchant_account: &domain::MerchantAccount,
profile: &domain::Profile,
key_store: &domain::MerchantKeyStore,
payment_id: common_utils::id_type::GlobalPaymentId,
billing_connector_account_id: &common_utils::id_type::MerchantConnectorAccountId,
payment_connector_account: Option<domain::MerchantConnectorAccount>,
) -> CustomResult<revenue_recovery::RecoveryPaymentAttempt, errors::RevenueRecoveryError> {
todo!()
let request_payload = self
.create_payment_record_request(billing_connector_account_id, payment_connector_account);
let attempt_response = Box::pin(payments::record_attempt_core(
state.clone(),
req_state.clone(),
merchant_account.clone(),
profile.clone(),
key_store.clone(),
request_payload,
payment_id.clone(),
hyperswitch_domain_models::payments::HeaderPayload::default(),
None,
))
.await;
let response = match attempt_response {
Ok(services::ApplicationResponse::JsonWithHeaders((attempt_response, _))) => {
Ok(revenue_recovery::RecoveryPaymentAttempt {
attempt_id: attempt_response.id,
attempt_status: attempt_response.status,
feature_metadata: attempt_response.feature_metadata,
})
}
Ok(_) => Err(errors::RevenueRecoveryError::PaymentAttemptFetchFailed)
.attach_printable("Unexpected response from record attempt core"),
error @ Err(_) => {
router_env::logger::error!(?error);
Err(errors::RevenueRecoveryError::PaymentAttemptFetchFailed)
.attach_printable("failed to record attempt in recovery webhook flow")
}
}?;
Ok(response)
}
pub fn create_payment_record_request(
&self,
billing_merchant_connector_account_id: &common_utils::id_type::MerchantConnectorAccountId,
payment_merchant_connector_account: Option<domain::MerchantConnectorAccount>,
) -> api_payments::PaymentsAttemptRecordRequest {
let amount_details = api_payments::PaymentAttemptAmountDetails::from(&self.0);
let feature_metadata = api_payments::PaymentAttemptFeatureMetadata {
revenue_recovery: Some(api_payments::PaymentAttemptRevenueRecoveryData {
// Since we are recording the external paymenmt attempt, this is hardcoded to External
attempt_triggered_by: common_enums::TriggeredBy::External,
}),
};
let error = Option::<api_payments::RecordAttemptErrorDetails>::from(&self.0);
api_payments::PaymentsAttemptRecordRequest {
amount_details,
status: self.0.status,
billing: None,
shipping: None,
connector : payment_merchant_connector_account.as_ref().map(|account| account.connector_name),
payment_merchant_connector_id: payment_merchant_connector_account.as_ref().map(|account: &hyperswitch_domain_models::merchant_connector_account::MerchantConnectorAccount| account.id.clone()),
error,
description: None,
connector_transaction_id: self.0.connector_transaction_id.clone(),
payment_method_type: self.0.payment_method_type,
billing_connector_id: billing_merchant_connector_account_id.clone(),
payment_method_subtype: self.0.payment_method_sub_type,
payment_method_data: None,
metadata: None,
feature_metadata: Some(feature_metadata),
transaction_created_at: self.0.transaction_created_at,
processor_payment_method_token: self.0.processor_payment_method_token.clone(),
connector_customer_id: self.0.connector_customer_id.clone(),
}
}
pub async fn find_payment_merchant_connector_account(
&self,
state: &SessionState,
key_store: &domain::MerchantKeyStore,
billing_connector_account: &domain::MerchantConnectorAccount,
) -> CustomResult<Option<domain::MerchantConnectorAccount>, errors::RevenueRecoveryError> {
let payment_merchant_connector_account_id = billing_connector_account
.get_payment_merchant_connector_account_id_using_account_reference_id(
self.0.connector_account_reference_id.clone(),
);
let db = &*state.store;
let key_manager_state = &(state).into();
let payment_merchant_connector_account = payment_merchant_connector_account_id
.as_ref()
.async_map(|mca_id| async move {
db.find_merchant_connector_account_by_id(key_manager_state, mca_id, key_store)
.await
})
.await
.transpose()
.change_context(errors::RevenueRecoveryError::PaymentMerchantConnectorAccountNotFound)
.attach_printable(
"failed to fetch payment merchant connector id using account reference id",
)?;
Ok(payment_merchant_connector_account)
}
}

View File

@ -36,7 +36,7 @@ pub use hyperswitch_domain_models::router_flow_types::payments::{
Approve, Authorize, AuthorizeSessionToken, Balance, CalculateTax, Capture, CompleteAuthorize,
CreateConnectorCustomer, IncrementalAuthorization, InitPayment, PSync, PaymentCreateIntent,
PaymentGetIntent, PaymentMethodToken, PaymentUpdateIntent, PostProcessing, PostSessionTokens,
PreProcessing, Reject, SdkSessionUpdate, Session, SetupMandate, Void,
PreProcessing, RecordAttempt, Reject, SdkSessionUpdate, Session, SetupMandate, Void,
};
pub use hyperswitch_interfaces::api::payments::{
ConnectorCustomer, MandateSetup, Payment, PaymentApprove, PaymentAuthorize,