Bug: [FEATURE] Add new OLAP endpoint to fetch total count of payment methods of customer for given merchant id.



Add total-payment-method-count api. this is olap api to fetch total count of payment method of customer of given merchant id.

diff --git a/crates/api_models/src/payment_methods.rs b/crates/api_models/src/payment_methods.rs
index ac44f65ee0e..77963ee90df 100644
--- a/crates/api_models/src/payment_methods.rs
+++ b/crates/api_models/src/payment_methods.rs
@@ -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")
diff --git a/crates/diesel_models/src/query/payment_method.rs b/crates/diesel_models/src/query/payment_method.rs
index d4ad52ae146..62f6f37ba52 100644
--- a/crates/diesel_models/src/query/payment_method.rs
+++ b/crates/diesel_models/src/query/payment_method.rs
@@ -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")
+    }
 }
diff --git a/crates/hyperswitch_domain_models/src/payment_methods.rs b/crates/hyperswitch_domain_models/src/payment_methods.rs
index 762ab5bbf86..963ec83e0be 100644
--- a/crates/hyperswitch_domain_models/src/payment_methods.rs
+++ b/crates/hyperswitch_domain_models/src/payment_methods.rs
@@ -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,
diff --git a/crates/router/src/core/payment_methods.rs b/crates/router/src/core/payment_methods.rs
index f9a6f9a9eb9..08968997243 100644
--- a/crates/router/src/core/payment_methods.rs
+++ b/crates/router/src/core/payment_methods.rs
@@ -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(
diff --git a/crates/router/src/db/kafka_store.rs b/crates/router/src/db/kafka_store.rs
index 7c2a9c7498f..d06d13a3123 100644
--- a/crates/router/src/db/kafka_store.rs
+++ b/crates/router/src/db/kafka_store.rs
@@ -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")
diff --git a/crates/router/src/routes/app.rs b/crates/router/src/routes/app.rs
index 7bb42134aa7..c43044fa327 100644
--- a/crates/router/src/routes/app.rs
+++ b/crates/router/src/routes/app.rs
@@ -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"))]
         {
diff --git a/crates/router/src/routes/lock_utils.rs b/crates/router/src/routes/lock_utils.rs
index 880f400ccc4..1cc3442b74b 100644
--- a/crates/router/src/routes/lock_utils.rs
+++ b/crates/router/src/routes/lock_utils.rs
@@ -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,
 
diff --git a/crates/router/src/routes/payment_methods.rs b/crates/router/src/routes/payment_methods.rs
index a32a0268714..92f16b6c4c7 100644
--- a/crates/router/src/routes/payment_methods.rs
+++ b/crates/router/src/routes/payment_methods.rs
@@ -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))]
diff --git a/crates/router/src/types/api/payment_methods.rs b/crates/router/src/types/api/payment_methods.rs
index dc2d7816ffc..87475fcd89a 100644
--- a/crates/router/src/types/api/payment_methods.rs
+++ b/crates/router/src/types/api/payment_methods.rs
@@ -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"),
diff --git a/crates/router_env/src/logger/types.rs b/crates/router_env/src/logger/types.rs
index f287c8b7d50..26f88217e4a 100644
--- a/crates/router_env/src/logger/types.rs
+++ b/crates/router_env/src/logger/types.rs
@@ -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
diff --git a/crates/storage_impl/src/payment_method.rs b/crates/storage_impl/src/payment_method.rs
index 5ed5e36fb3c..4d52ad9d8d3 100644
--- a/crates/storage_impl/src/payment_method.rs
+++ b/crates/storage_impl/src/payment_method.rs
@@ -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,

## Type of Change
<!-- Put an `x` in the boxes that apply -->

- [ ] Bugfix
- [ ] New feature
- [ ] Enhancement
- [ ] Refactoring
- [ ] Dependency updates
- [ ] Documentation
- [ ] CI/CD

## Description
<!-- Describe your changes in detail -->
add total-payment-method-count api. this is olap api to fetch total count of payment method of customer of given merchant id

### Additional Changes

- [ ] This PR modifies the API contract
- [ ] This PR modifies the database schema
- [ ] This PR modifies application configuration/environment variables

<!--
Provide links to the files with corresponding changes.

Following are the paths where you can find config files:
1. `config`
2. `crates/router/src/configs`
3. `loadtest/config`
-->


## Motivation and Context
<!--
Why is this change required? What problem does it solve?
If it fixes an open issue, please link to the issue here.

If you don't have an issue, we'd recommend starting with one first so the PR
can focus on the implementation (unless it is an obvious bug or documentation fix
that will have little conversation).
-->


## How did you test it?
<!--
Did you write an integration/unit/API test to verify the code changes?
Or did you test this change manually (provide relevant screenshots)?
-->
req curl - 
```
curl --location 'http://localhost:8080/v2/customers/total-payment-methods' \
--header 'x-profile-id: pro_i9LGtymazprJz3PG4aam' \
--header 'api-key: dev_QetiSV8rA4A92lmwb52ZJxE7BX8mnLLeQyr6CUoTh745dCfu2A33MVyXKdjKE0Ka'
```
resp - 
```
{
    "total_count": 3
}
```
<img width="1146" alt="Screenshot 2025-03-12 at 11 08 57 AM" src="https://github.com/user-attachments/assets/635f6d97-690e-44dd-971d-5a62b261a4b9" />

verification:-
<img width="1146" alt="Screenshot 2025-03-12 at 11 28 58 AM" src="https://github.com/user-attachments/assets/9702edd1-9a72-4144-adc2-d887afc48a3f" />





## Checklist
<!-- Put an `x` in the boxes that apply -->

- [x] I formatted the code `cargo +nightly fmt --all`
- [ ] I addressed lints thrown by `cargo clippy`
- [ ] I reviewed the submitted code
- [ ] I added unit tests for my changes where possible
