feat(payment_methods_v2): add total-payment-method-count api (#7479)

Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
This commit is contained in:
Prasunna Soppa
2025-03-13 00:41:12 +05:30
committed by GitHub
parent d1f53036c7
commit 4f6174d1bf
11 changed files with 188 additions and 21 deletions

View File

@ -1884,6 +1884,16 @@ pub struct CustomerPaymentMethodsListResponse {
pub customer_payment_methods: Vec<CustomerPaymentMethod>,
}
#[cfg(all(feature = "v2", feature = "payment_methods_v2"))]
#[derive(Debug, serde::Serialize, ToSchema)]
pub struct TotalPaymentMethodCountResponse {
/// total count of payment methods under the merchant
pub total_count: i64,
}
#[cfg(all(feature = "v2", feature = "payment_methods_v2"))]
impl common_utils::events::ApiEventMetric for TotalPaymentMethodCountResponse {}
#[cfg(all(
any(feature = "v2", feature = "v1"),
not(feature = "payment_methods_v2")

View File

@ -1,23 +1,8 @@
#[cfg(all(
any(feature = "v1", feature = "v2"),
not(feature = "payment_methods_v2")
))]
use async_bb8_diesel::AsyncRunQueryDsl;
#[cfg(all(
any(feature = "v1", feature = "v2"),
not(feature = "payment_methods_v2")
))]
use diesel::Table;
use diesel::{associations::HasTable, BoolExpressionMethods, ExpressionMethods};
#[cfg(all(
any(feature = "v1", feature = "v2"),
not(feature = "payment_methods_v2")
))]
use diesel::{debug_query, pg::Pg, QueryDsl};
#[cfg(all(
any(feature = "v1", feature = "v2"),
not(feature = "payment_methods_v2")
))]
use diesel::{
associations::HasTable, debug_query, pg::Pg, BoolExpressionMethods, ExpressionMethods,
QueryDsl, Table,
};
use error_stack::ResultExt;
use super::generics;
@ -153,6 +138,28 @@ impl PaymentMethod {
.attach_printable("Failed to get a count of payment methods")
}
pub async fn get_count_by_merchant_id_status(
conn: &PgPooledConn,
merchant_id: &common_utils::id_type::MerchantId,
status: common_enums::PaymentMethodStatus,
) -> StorageResult<i64> {
let query = <Self as HasTable>::table().count().filter(
dsl::merchant_id
.eq(merchant_id.to_owned())
.and(dsl::status.eq(status.to_owned())),
);
router_env::logger::debug!(query = %debug_query::<Pg, _>(&query).to_string());
generics::db_metrics::track_database_call::<<Self as HasTable>::Table, _, _>(
query.get_result_async::<i64>(conn),
generics::db_metrics::DatabaseOperation::Count,
)
.await
.change_context(errors::DatabaseError::Others)
.attach_printable("Failed to get a count of payment methods")
}
pub async fn find_by_customer_id_merchant_id_status(
conn: &PgPooledConn,
customer_id: &common_utils::id_type::CustomerId,
@ -275,4 +282,26 @@ impl PaymentMethod {
)
.await
}
pub async fn get_count_by_merchant_id_status(
conn: &PgPooledConn,
merchant_id: &common_utils::id_type::MerchantId,
status: common_enums::PaymentMethodStatus,
) -> StorageResult<i64> {
let query = <Self as HasTable>::table().count().filter(
dsl::merchant_id
.eq(merchant_id.to_owned())
.and(dsl::status.eq(status.to_owned())),
);
router_env::logger::debug!(query = %debug_query::<Pg, _>(&query).to_string());
generics::db_metrics::track_database_call::<<Self as HasTable>::Table, _, _>(
query.get_result_async::<i64>(conn),
generics::db_metrics::DatabaseOperation::Count,
)
.await
.change_context(errors::DatabaseError::Others)
.attach_printable("Failed to get a count of payment methods")
}
}

View File

@ -786,6 +786,12 @@ pub trait PaymentMethodInterface {
status: common_enums::PaymentMethodStatus,
) -> CustomResult<i64, errors::StorageError>;
async fn get_payment_method_count_by_merchant_id_status(
&self,
merchant_id: &id_type::MerchantId,
status: common_enums::PaymentMethodStatus,
) -> CustomResult<i64, errors::StorageError>;
async fn insert_payment_method(
&self,
state: &keymanager::KeyManagerState,

View File

@ -1308,6 +1308,20 @@ pub async fn list_saved_payment_methods_for_customer(
))
}
#[cfg(all(feature = "v2", feature = "olap"))]
#[instrument(skip_all)]
pub async fn get_total_saved_payment_methods_for_merchant(
state: SessionState,
merchant_account: domain::MerchantAccount,
) -> RouterResponse<api::TotalPaymentMethodCountResponse> {
let total_payment_method_count =
get_total_payment_method_count_core(&state, &merchant_account).await?;
Ok(hyperswitch_domain_models::api::ApplicationResponse::Json(
total_payment_method_count,
))
}
#[cfg(feature = "v2")]
/// Container for the inputs required for the required fields
struct RequiredFieldsInput {
@ -1778,6 +1792,27 @@ pub async fn list_customer_payment_method_core(
Ok(response)
}
#[cfg(all(feature = "v2", feature = "olap"))]
pub async fn get_total_payment_method_count_core(
state: &SessionState,
merchant_account: &domain::MerchantAccount,
) -> RouterResult<api::TotalPaymentMethodCountResponse> {
let db = &*state.store;
let total_count = db
.get_payment_method_count_by_merchant_id_status(
merchant_account.get_id(),
common_enums::PaymentMethodStatus::Active,
)
.await
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Unable to get total payment method count")?;
let response = api::TotalPaymentMethodCountResponse { total_count };
Ok(response)
}
#[cfg(all(feature = "v2", feature = "payment_methods_v2"))]
#[instrument(skip_all)]
pub async fn retrieve_payment_method(

View File

@ -2176,6 +2176,16 @@ impl PaymentMethodInterface for KafkaStore {
.await
}
async fn get_payment_method_count_by_merchant_id_status(
&self,
merchant_id: &id_type::MerchantId,
status: common_enums::PaymentMethodStatus,
) -> CustomResult<i64, errors::DataStorageError> {
self.diesel_store
.get_payment_method_count_by_merchant_id_status(merchant_id, status)
.await
}
#[cfg(all(
any(feature = "v1", feature = "v2"),
not(feature = "payment_methods_v2")

View File

@ -1039,6 +1039,10 @@ impl Customers {
{
route = route
.service(web::resource("/list").route(web::get().to(customers::customers_list)))
.service(
web::resource("/total-payment-methods")
.route(web::get().to(payment_methods::get_total_payment_method_count)),
)
}
#[cfg(all(feature = "oltp", feature = "v2", feature = "customer_v2"))]
{

View File

@ -118,7 +118,8 @@ impl From<Flow> for ApiIdentifier {
| Flow::ValidatePaymentMethod
| Flow::ListCountriesCurrencies
| Flow::DefaultPaymentMethodsSet
| Flow::PaymentMethodSave => Self::PaymentMethods,
| Flow::PaymentMethodSave
| Flow::TotalPaymentMethodCount => Self::PaymentMethods,
Flow::PmAuthLinkTokenCreate | Flow::PmAuthExchangeToken => Self::PaymentMethodAuth,

View File

@ -624,6 +624,37 @@ pub async fn list_customer_payment_method_api(
.await
}
#[cfg(all(feature = "v2", feature = "olap"))]
#[instrument(skip_all, fields(flow = ?Flow::TotalPaymentMethodCount))]
pub async fn get_total_payment_method_count(
state: web::Data<AppState>,
req: HttpRequest,
) -> HttpResponse {
let flow = Flow::TotalPaymentMethodCount;
Box::pin(api::server_wrap(
flow,
state,
&req,
(),
|state, auth: auth::AuthenticationData, _, _| {
payment_methods_routes::get_total_saved_payment_methods_for_merchant(
state,
auth.merchant_account,
)
},
auth::auth_type(
&auth::V2ApiKeyAuth,
&auth::JWTAuth {
permission: Permission::MerchantCustomerRead,
},
req.headers(),
),
api_locking::LockAction::NotApplicable,
))
.await
}
#[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "customer_v2")))]
/// Generate a form link for collecting payment methods for a customer
#[instrument(skip_all, fields(flow = ?Flow::PaymentMethodCollectLink))]

View File

@ -11,7 +11,7 @@ pub use api_models::payment_methods::{
PaymentMethodMigrateResponse, PaymentMethodResponse, PaymentMethodResponseData,
PaymentMethodUpdate, PaymentMethodUpdateData, PaymentMethodsData, TokenizePayloadEncrypted,
TokenizePayloadRequest, TokenizedCardValue1, TokenizedCardValue2, TokenizedWalletValue1,
TokenizedWalletValue2,
TokenizedWalletValue2, TotalPaymentMethodCountResponse,
};
#[cfg(all(
any(feature = "v2", feature = "v1"),

View File

@ -574,6 +574,8 @@ pub enum Flow {
CardsInfoUpdate,
/// Cards Info migrate flow
CardsInfoMigrate,
///Total payment method count for merchant
TotalPaymentMethodCount,
}
/// Trait for providing generic behaviour to flow metric

View File

@ -119,6 +119,17 @@ impl<T: DatabaseStore> PaymentMethodInterface for KVRouterStore<T> {
.await
}
#[instrument(skip_all)]
async fn get_payment_method_count_by_merchant_id_status(
&self,
merchant_id: &id_type::MerchantId,
status: common_enums::PaymentMethodStatus,
) -> CustomResult<i64, errors::StorageError> {
self.router_store
.get_payment_method_count_by_merchant_id_status(merchant_id, status)
.await
}
#[cfg(all(feature = "v2", feature = "payment_methods_v2"))]
#[instrument(skip_all)]
async fn insert_payment_method(
@ -496,6 +507,21 @@ impl<T: DatabaseStore> PaymentMethodInterface for RouterStore<T> {
})
}
#[instrument(skip_all)]
async fn get_payment_method_count_by_merchant_id_status(
&self,
merchant_id: &id_type::MerchantId,
status: common_enums::PaymentMethodStatus,
) -> CustomResult<i64, errors::StorageError> {
let conn = pg_connection_read(self).await?;
PaymentMethod::get_count_by_merchant_id_status(&conn, merchant_id, status)
.await
.map_err(|error| {
let new_err = diesel_error_to_data_error(*error.current_context());
error.change_context(new_err)
})
}
#[instrument(skip_all)]
async fn insert_payment_method(
&self,
@ -810,6 +836,19 @@ impl PaymentMethodInterface for MockDb {
i64::try_from(count).change_context(errors::StorageError::MockDbError)
}
async fn get_payment_method_count_by_merchant_id_status(
&self,
merchant_id: &id_type::MerchantId,
status: common_enums::PaymentMethodStatus,
) -> CustomResult<i64, errors::StorageError> {
let payment_methods = self.payment_methods.lock().await;
let count = payment_methods
.iter()
.filter(|pm| pm.merchant_id == *merchant_id && pm.status == status)
.count();
i64::try_from(count).change_context(errors::StorageError::MockDbError)
}
async fn insert_payment_method(
&self,
_state: &KeyManagerState,