feat(router): Add Payments - List endpoint for v2 (#7191)

Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
This commit is contained in:
Anurag Thakur
2025-02-20 16:39:38 +05:30
committed by GitHub
parent 74bbf4bf27
commit d1f537e229
20 changed files with 1814 additions and 89 deletions

View File

@ -5252,6 +5252,82 @@ pub async fn list_payments(
))
}
#[cfg(all(feature = "v2", feature = "olap"))]
pub async fn list_payments(
state: SessionState,
merchant: domain::MerchantAccount,
key_store: domain::MerchantKeyStore,
constraints: api::PaymentListConstraints,
) -> RouterResponse<payments_api::PaymentListResponse> {
common_utils::metrics::utils::record_operation_time(
async {
let limit = &constraints.limit;
helpers::validate_payment_list_request_for_joins(*limit)?;
let db: &dyn StorageInterface = state.store.as_ref();
let fetch_constraints = constraints.clone().into();
let list: Vec<(storage::PaymentIntent, Option<storage::PaymentAttempt>)> = db
.get_filtered_payment_intents_attempt(
&(&state).into(),
merchant.get_id(),
&fetch_constraints,
&key_store,
merchant.storage_scheme,
)
.await
.to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?;
let data: Vec<api_models::payments::PaymentsListResponseItem> =
list.into_iter().map(ForeignFrom::foreign_from).collect();
let active_attempt_ids = db
.get_filtered_active_attempt_ids_for_total_count(
merchant.get_id(),
&fetch_constraints,
merchant.storage_scheme,
)
.await
.to_not_found_response(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Error while retrieving active_attempt_ids for merchant")?;
let total_count = if constraints.has_no_attempt_filters() {
i64::try_from(active_attempt_ids.len())
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Error while converting from usize to i64")
} else {
let active_attempt_ids = active_attempt_ids
.into_iter()
.flatten()
.collect::<Vec<String>>();
db.get_total_count_of_filtered_payment_attempts(
merchant.get_id(),
&active_attempt_ids,
constraints.connector,
constraints.payment_method_type,
constraints.payment_method_subtype,
constraints.authentication_type,
constraints.merchant_connector_id,
constraints.card_network,
merchant.storage_scheme,
)
.await
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Error while retrieving total count of payment attempts")
}?;
Ok(services::ApplicationResponse::Json(
api_models::payments::PaymentListResponse {
count: data.len(),
total_count,
data,
},
))
},
&metrics::PAYMENT_LIST_LATENCY,
router_env::metric_attributes!(("merchant_id", merchant.get_id().clone())),
)
.await
}
#[cfg(all(feature = "olap", feature = "v1"))]
pub async fn apply_filters_on_payments(
state: SessionState,

View File

@ -2774,6 +2774,54 @@ impl ForeignFrom<(storage::PaymentIntent, storage::PaymentAttempt)> for api::Pay
}
}
#[cfg(feature = "v2")]
impl ForeignFrom<(storage::PaymentIntent, Option<storage::PaymentAttempt>)>
for api_models::payments::PaymentsListResponseItem
{
fn foreign_from((pi, pa): (storage::PaymentIntent, Option<storage::PaymentAttempt>)) -> Self {
Self {
id: pi.id,
merchant_id: pi.merchant_id,
profile_id: pi.profile_id,
customer_id: pi.customer_id,
payment_method_id: pa.as_ref().and_then(|p| p.payment_method_id.clone()),
status: pi.status,
amount: api_models::payments::PaymentAmountDetailsResponse::foreign_from((
&pi.amount_details,
pa.as_ref().map(|p| &p.amount_details),
)),
created: pi.created_at,
payment_method_type: pa.as_ref().and_then(|p| p.payment_method_type.into()),
payment_method_subtype: pa.as_ref().and_then(|p| p.payment_method_subtype.into()),
connector: pa.as_ref().and_then(|p| p.connector.clone()),
merchant_connector_id: pa.as_ref().and_then(|p| p.merchant_connector_id.clone()),
customer: None,
merchant_reference_id: pi.merchant_reference_id,
connector_payment_id: pa.as_ref().and_then(|p| p.connector_payment_id.clone()),
connector_response_reference_id: pa
.as_ref()
.and_then(|p| p.connector_response_reference_id.clone()),
metadata: pi.metadata,
description: pi.description.map(|val| val.get_string_repr().to_string()),
authentication_type: pi.authentication_type,
capture_method: Some(pi.capture_method),
setup_future_usage: Some(pi.setup_future_usage),
attempt_count: pi.attempt_count,
error: pa
.as_ref()
.and_then(|p| p.error.as_ref())
.map(|e| api_models::payments::ErrorDetails::foreign_from(e.clone())),
cancellation_reason: pa.as_ref().and_then(|p| p.cancellation_reason.clone()),
order_details: None,
return_url: pi.return_url,
statement_descriptor: pi.statement_descriptor,
allowed_payment_method_types: pi.allowed_payment_method_types,
authorization_count: pi.authorization_count,
modified_at: pa.as_ref().map(|p| p.modified_at),
}
}
}
#[cfg(feature = "v1")]
impl ForeignFrom<ephemeral_key::EphemeralKey> for api::ephemeral_key::EphemeralKeyCreateResponse {
fn foreign_from(from: ephemeral_key::EphemeralKey) -> Self {

View File

@ -1735,6 +1735,34 @@ impl PaymentAttemptInterface for KafkaStore {
.await
}
#[cfg(feature = "v2")]
async fn get_total_count_of_filtered_payment_attempts(
&self,
merchant_id: &id_type::MerchantId,
active_attempt_ids: &[String],
connector: Option<api_models::enums::Connector>,
payment_method_type: Option<common_enums::PaymentMethod>,
payment_method_subtype: Option<common_enums::PaymentMethodType>,
authentication_type: Option<common_enums::AuthenticationType>,
merchant_connector_id: Option<id_type::MerchantConnectorAccountId>,
card_network: Option<common_enums::CardNetwork>,
storage_scheme: MerchantStorageScheme,
) -> CustomResult<i64, errors::DataStorageError> {
self.diesel_store
.get_total_count_of_filtered_payment_attempts(
merchant_id,
active_attempt_ids,
connector,
payment_method_type,
payment_method_subtype,
authentication_type,
merchant_connector_id,
card_network,
storage_scheme,
)
.await
}
#[cfg(feature = "v1")]
async fn find_attempts_by_merchant_id_payment_id(
&self,
@ -1914,6 +1942,32 @@ impl PaymentIntentInterface for KafkaStore {
.await
}
#[cfg(all(feature = "olap", feature = "v2"))]
async fn get_filtered_payment_intents_attempt(
&self,
state: &KeyManagerState,
merchant_id: &id_type::MerchantId,
constraints: &hyperswitch_domain_models::payments::payment_intent::PaymentIntentFetchConstraints,
key_store: &domain::MerchantKeyStore,
storage_scheme: MerchantStorageScheme,
) -> CustomResult<
Vec<(
hyperswitch_domain_models::payments::PaymentIntent,
Option<hyperswitch_domain_models::payments::payment_attempt::PaymentAttempt>,
)>,
errors::DataStorageError,
> {
self.diesel_store
.get_filtered_payment_intents_attempt(
state,
merchant_id,
constraints,
key_store,
storage_scheme,
)
.await
}
#[cfg(all(feature = "olap", feature = "v1"))]
async fn get_filtered_active_attempt_ids_for_total_count(
&self,
@ -1952,6 +2006,21 @@ impl PaymentIntentInterface for KafkaStore {
)
.await
}
#[cfg(all(feature = "olap", feature = "v2"))]
async fn get_filtered_active_attempt_ids_for_total_count(
&self,
merchant_id: &id_type::MerchantId,
constraints: &hyperswitch_domain_models::payments::payment_intent::PaymentIntentFetchConstraints,
storage_scheme: MerchantStorageScheme,
) -> CustomResult<Vec<Option<String>>, errors::DataStorageError> {
self.diesel_store
.get_filtered_active_attempt_ids_for_total_count(
merchant_id,
constraints,
storage_scheme,
)
.await
}
}
#[async_trait::async_trait]

View File

@ -573,6 +573,7 @@ impl Payments {
web::resource("/create-intent")
.route(web::post().to(payments::payments_create_intent)),
)
.service(web::resource("/list").route(web::get().to(payments::payments_list)))
.service(
web::resource("/aggregate").route(web::get().to(payments::get_payments_aggregates)),
)

View File

@ -1234,6 +1234,35 @@ pub async fn payments_list(
.await
}
#[instrument(skip_all, fields(flow = ?Flow::PaymentsList))]
#[cfg(all(feature = "olap", feature = "v2"))]
pub async fn payments_list(
state: web::Data<app::AppState>,
req: actix_web::HttpRequest,
payload: web::Query<payment_types::PaymentListConstraints>,
) -> impl Responder {
let flow = Flow::PaymentsList;
let payload = payload.into_inner();
Box::pin(api::server_wrap(
flow,
state,
&req,
payload,
|state, auth: auth::AuthenticationData, req, _| {
payments::list_payments(state, auth.merchant_account, auth.key_store, req)
},
auth::auth_type(
&auth::HeaderAuth(auth::ApiKeyAuth),
&auth::JWTAuth {
permission: Permission::MerchantPaymentRead,
},
req.headers(),
),
api_locking::LockAction::NotApplicable,
))
.await
}
#[instrument(skip_all, fields(flow = ?Flow::PaymentsList))]
#[cfg(all(feature = "olap", feature = "v1"))]
pub async fn profile_payments_list(

View File

@ -1,5 +1,7 @@
#[cfg(feature = "v1")]
pub use api_models::payments::{PaymentListResponse, PaymentListResponseV2};
pub use api_models::payments::{
PaymentListFilterConstraints, PaymentListResponse, PaymentListResponseV2,
};
#[cfg(feature = "v2")]
pub use api_models::payments::{
PaymentsConfirmIntentRequest, PaymentsCreateIntentRequest, PaymentsIntentResponse,
@ -14,9 +16,8 @@ pub use api_models::{
CryptoData, CustomerAcceptance, CustomerDetailsResponse, MandateAmountData, MandateData,
MandateTransactionType, MandateType, MandateValidationFields, NextActionType,
OnlineMandate, OpenBankingSessionToken, PayLaterData, PaymentIdType,
PaymentListConstraints, PaymentListFilterConstraints, PaymentListFilters,
PaymentListFiltersV2, PaymentMethodData, PaymentMethodDataRequest,
PaymentMethodDataResponse, PaymentOp, PaymentRetrieveBody,
PaymentListConstraints, PaymentListFilters, PaymentListFiltersV2, PaymentMethodData,
PaymentMethodDataRequest, PaymentMethodDataResponse, PaymentOp, PaymentRetrieveBody,
PaymentRetrieveBodyWithCredentials, PaymentsAggregateResponse, PaymentsApproveRequest,
PaymentsCancelRequest, PaymentsCaptureRequest, PaymentsCompleteAuthorizeRequest,
PaymentsDynamicTaxCalculationRequest, PaymentsDynamicTaxCalculationResponse,