Bug: [FEAT]: [Customer] Add search support to Customer API by Customer_ID



### Feature Description

Currently, the Customer List API (/api/customers/list?limit=20&offset=0) does not support search functionality, which forces the dashboard to display customers without the ability to search dynamically.

We want to enhance this API to support search across multiple fields so that users can quickly locate a customer.
Supported fields for full text match:

customer_id: "cust_abcdefgh"

name: "venu"

email: "venu.b@juspay.in"

phone: "+91234567890"


### Possible Implementation

Add a search bar in the Dashboard UI for the Customer List page.

The search bar should allow merchants to search using customer_id (partial or full match).

Pass the search query to the backend API via query parameter, for example:
``` bash
GET: /api/customers/list?limit=20&offset=0&customer_id=cus_12345
```

### Have you spent some time checking if this feature request has been raised before?

- [x] I checked and didn't find a similar issue

### Have you read the Contributing Guidelines?

- [x] I have read the [Contributing Guidelines](https://github.com/juspay/hyperswitch/blob/main/docs/CONTRIBUTING.md)

### Are you willing to submit a PR?

None
diff --git a/crates/api_models/src/customers.rs b/crates/api_models/src/customers.rs
index 19016e292a8..62fd000ddb8 100644
--- a/crates/api_models/src/customers.rs
+++ b/crates/api_models/src/customers.rs
@@ -52,6 +52,8 @@ pub struct CustomerListRequest {
     /// Limit
     #[schema(example = 32)]
     pub limit: Option<u16>,
+    /// Unique identifier for a customer
+    pub customer_id: Option<id_type::CustomerId>,
 }
 
 #[cfg(feature = "v1")]
diff --git a/crates/diesel_models/src/query/customers.rs b/crates/diesel_models/src/query/customers.rs
index def8d77fe7a..a6121d408c2 100644
--- a/crates/diesel_models/src/query/customers.rs
+++ b/crates/diesel_models/src/query/customers.rs
@@ -20,6 +20,7 @@ impl CustomerNew {
 pub struct CustomerListConstraints {
     pub limit: i64,
     pub offset: Option<i64>,
+    pub customer_id: Option<id_type::CustomerId>,
 }
 
 impl Customer {
@@ -54,19 +55,66 @@ impl Customer {
         generics::generic_find_by_id::<<Self as HasTable>::Table, _, _>(conn, id.to_owned()).await
     }
 
+    #[cfg(feature = "v1")]
     pub async fn list_by_merchant_id(
         conn: &PgPooledConn,
         merchant_id: &id_type::MerchantId,
         constraints: CustomerListConstraints,
     ) -> StorageResult<Vec<Self>> {
-        generics::generic_filter::<<Self as HasTable>::Table, _, _, _>(
-            conn,
-            dsl::merchant_id.eq(merchant_id.to_owned()),
-            Some(constraints.limit),
-            constraints.offset,
-            Some(dsl::created_at),
-        )
-        .await
+        if let Some(customer_id) = constraints.customer_id {
+            let predicate = dsl::merchant_id
+                .eq(merchant_id.clone())
+                .and(dsl::customer_id.eq(customer_id));
+            generics::generic_filter::<<Self as HasTable>::Table, _, _, Self>(
+                conn,
+                predicate,
+                Some(constraints.limit),
+                constraints.offset,
+                Some(dsl::created_at),
+            )
+            .await
+        } else {
+            let predicate = dsl::merchant_id.eq(merchant_id.clone());
+            generics::generic_filter::<<Self as HasTable>::Table, _, _, Self>(
+                conn,
+                predicate,
+                Some(constraints.limit),
+                constraints.offset,
+                Some(dsl::created_at),
+            )
+            .await
+        }
+    }
+
+    #[cfg(feature = "v2")]
+    pub async fn list_by_merchant_id(
+        conn: &PgPooledConn,
+        merchant_id: &id_type::MerchantId,
+        constraints: CustomerListConstraints,
+    ) -> StorageResult<Vec<Self>> {
+        if let Some(customer_id) = constraints.customer_id {
+            let predicate = dsl::merchant_id
+                .eq(merchant_id.clone())
+                .and(dsl::merchant_reference_id.eq(customer_id));
+            generics::generic_filter::<<Self as HasTable>::Table, _, _, Self>(
+                conn,
+                predicate,
+                Some(constraints.limit),
+                constraints.offset,
+                Some(dsl::created_at),
+            )
+            .await
+        } else {
+            let predicate = dsl::merchant_id.eq(merchant_id.clone());
+            generics::generic_filter::<<Self as HasTable>::Table, _, _, Self>(
+                conn,
+                predicate,
+                Some(constraints.limit),
+                constraints.offset,
+                Some(dsl::created_at),
+            )
+            .await
+        }
     }
 
     #[cfg(feature = "v2")]
diff --git a/crates/hyperswitch_domain_models/src/customer.rs b/crates/hyperswitch_domain_models/src/customer.rs
index 5a88cb6cd48..720296cdcac 100644
--- a/crates/hyperswitch_domain_models/src/customer.rs
+++ b/crates/hyperswitch_domain_models/src/customer.rs
@@ -538,6 +538,7 @@ impl From<CustomerUpdate> for CustomerUpdateInternal {
 pub struct CustomerListConstraints {
     pub limit: u16,
     pub offset: Option<u32>,
+    pub customer_id: Option<id_type::CustomerId>,
 }
 
 impl From<CustomerListConstraints> for query::CustomerListConstraints {
@@ -545,6 +546,7 @@ impl From<CustomerListConstraints> for query::CustomerListConstraints {
         Self {
             limit: i64::from(value.limit),
             offset: value.offset.map(i64::from),
+            customer_id: value.customer_id,
         }
     }
 }
diff --git a/crates/router/src/core/customers.rs b/crates/router/src/core/customers.rs
index ffc44781037..09f12d62ba0 100644
--- a/crates/router/src/core/customers.rs
+++ b/crates/router/src/core/customers.rs
@@ -590,6 +590,7 @@ pub async fn list_customers(
             .limit
             .unwrap_or(crate::consts::DEFAULT_LIST_API_LIMIT),
         offset: request.offset,
+        customer_id: request.customer_id,
     };
 
     let domain_customers = db
diff --git a/crates/router/src/core/locker_migration.rs b/crates/router/src/core/locker_migration.rs
index e2680666e4d..bea3ff4e58e 100644
--- a/crates/router/src/core/locker_migration.rs
+++ b/crates/router/src/core/locker_migration.rs
@@ -54,6 +54,7 @@ pub async fn rust_locker_migration(
     let constraints = CustomerListConstraints {
         limit: u16::MAX,
         offset: None,
+        customer_id: None,
     };
 
     let domain_customers = db

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

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

## Description
<!-- Describe your changes in detail -->

This PR introduces improvements to the **Customer page API**:

Added **search bar functionality** to filter customers by `customer_id`.

These changes enhance API usability and improve performance when handling large customer datasets.

### 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).
-->
- Search functionality allows **direct lookup by `customer_id`**, making customer retrieval faster and more user-friendly.

## 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)?
-->
Executed the following API call to fetch the list of roles:
Before: No search bar for the searching Items.
```bash
curl --location 'http://localhost:8080/customers/list?limit=50&offset=0' \
--header 'Accept: application/json' \
--header 'api-key: <YOUR_API_KEY>' \
--header 'Content-Type: application/json' \
--data-binary '@'

```

Response:

```json
[
    {
        "customer_id": "cus_123",
        "name": "John Doe",
        "email": "guest@example.com",
        "phone": "999999999",
        "phone_country_code": "+65",
        "description": "First customer",
        "address": null,
        "created_at": "2025-09-24T08:05:42.489Z",
        "metadata": {
            "udf1": "value1",
            "new_customer": "true",
            "login_date": "2019-09-10T10:11:12Z"
        },
        "default_payment_method_id": null,
        "tax_registration_id": null
    },
    {
        "customer_id": "cus_124",
        "name": "Venu Doe",
        "email": "guest@example.com",
        "phone": "999999999",
        "phone_country_code": "+65",
        "description": "First customer",
        "address": null,
        "created_at": "2025-09-24T09:34:15.146Z",
        "metadata": {
            "udf1": "value1",
            "new_customer": "true",
            "login_date": "2019-09-10T10:11:12Z"
        },
        "default_payment_method_id": null,
        "tax_registration_id": null
    },
    {
        "customer_id": "cus_125",
        "name": "Venu Madhav",
        "email": "guest123@example.com",
        "phone": "999999999",
        "phone_country_code": "+65",
        "description": "First customer",
        "address": null,
        "created_at": "2025-09-24T09:34:23.527Z",
        "metadata": {
            "udf1": "value1",
            "new_customer": "true",
            "login_date": "2019-09-10T10:11:12Z"
        },
        "default_payment_method_id": null,
        "tax_registration_id": null
    },
    {
        "customer_id": "cus_126",
        "name": "Bandarupalli",
        "email": "guest1@example.com",
        "phone": "999999999",
        "phone_country_code": "+65",
        "description": "First customer",
        "address": null,
        "created_at": "2025-09-24T09:34:39.328Z",
        "metadata": {
            "udf1": "value1",
            "new_customer": "true",
            "login_date": "2019-09-10T10:11:12Z"
        },
        "default_payment_method_id": null,
        "tax_registration_id": null
    },
    {
        "customer_id": "cus_127",
        "name": "venu 2",
        "email": "guest1@example.com",
        "phone": "999999999",
        "phone_country_code": "+65",
        "description": "First customer",
        "address": null,
        "created_at": "2025-09-24T09:35:06.393Z",
        "metadata": {
            "udf1": "value1",
            "new_customer": "true",
            "login_date": "2019-09-10T10:11:12Z"
        },
        "default_payment_method_id": null,
        "tax_registration_id": null
    },
    {
        "customer_id": "cus_128",
        "name": "venu 3",
        "email": "guest2@example.com",
        "phone": "999999999",
        "phone_country_code": "+65",
        "description": "First customer",
        "address": null,
        "created_at": "2025-09-24T09:35:15.853Z",
        "metadata": {
            "udf1": "value1",
            "new_customer": "true",
            "login_date": "2019-09-10T10:11:12Z"
        },
        "default_payment_method_id": null,
        "tax_registration_id": null
    },
    {
        "customer_id": "cus_129",
        "name": "venu 4",
        "email": "guest3@example.com",
        "phone": "999999999",
        "phone_country_code": "+65",
        "description": "First customer",
        "address": null,
        "created_at": "2025-09-24T09:35:21.549Z",
        "metadata": {
            "udf1": "value1",
            "new_customer": "true",
            "login_date": "2019-09-10T10:11:12Z"
        },
        "default_payment_method_id": null,
        "tax_registration_id": null
    },
    {
        "customer_id": "cus_129",
        "name": "venu 5",
        "email": "guest4@example.com",
        "phone": "999999999",
        "phone_country_code": "+65",
        "description": "First customer",
        "address": null,
        "created_at": "2025-09-24T09:35:27.178Z",
        "metadata": {
            "udf1": "value1",
            "new_customer": "true",
            "login_date": "2019-09-10T10:11:12Z"
        },
        "default_payment_method_id": null,
        "tax_registration_id": null
    },
    {
        "customer_id": "cus_130",
        "name": "venu 6",
        "email": "guest6@example.com",
        "phone": "999999999",
        "phone_country_code": "+65",
        "description": "First customer",
        "address": null,
        "created_at": "2025-09-24T09:35:51.674Z",
        "metadata": {
            "udf1": "value1",
            "new_customer": "true",
            "login_date": "2019-09-10T10:11:12Z"
        },
        "default_payment_method_id": null,
        "tax_registration_id": null
    },
    {
        "customer_id": "cus_131",
        "name": "venu 7",
        "email": "guest7@example.com",
        "phone": "999999999",
        "phone_country_code": "+65",
        "description": "First customer",
        "address": null,
        "created_at": "2025-09-24T09:35:56.948Z",
        "metadata": {
            "udf1": "value1",
            "new_customer": "true",
            "login_date": "2019-09-10T10:11:12Z"
        },
        "default_payment_method_id": null,
        "tax_registration_id": null
    },
    {
        "customer_id": "cus_132",
        "name": "venu 8",
        "email": "guest8@example.com",
        "phone": "999999999",
        "phone_country_code": "+65",
        "description": "First customer",
        "address": null,
        "created_at": "2025-09-24T09:36:03.017Z",
        "metadata": {
            "udf1": "value1",
            "new_customer": "true",
            "login_date": "2019-09-10T10:11:12Z"
        },
        "default_payment_method_id": null,
        "tax_registration_id": null
    },
    {
        "customer_id": "cus_133",
        "name": "venu 9",
        "email": "guest9@example.com",
        "phone": "999999999",
        "phone_country_code": "+65",
        "description": "First customer",
        "address": null,
        "created_at": "2025-09-24T09:36:08.331Z",
        "metadata": {
            "udf1": "value1",
            "new_customer": "true",
            "login_date": "2019-09-10T10:11:12Z"
        },
        "default_payment_method_id": null,
        "tax_registration_id": null
    },
    {
        "customer_id": "cus_134",
        "name": null,
        "email": "guest@example.com",
        "phone": null,
        "phone_country_code": null,
        "description": null,
        "address": null,
        "created_at": "2025-09-24T10:29:03.242Z",
        "metadata": null,
        "default_payment_method_id": null,
        "tax_registration_id": null
    },
    {
        "customer_id": "cus_135",
        "name": "John Doe",
        "email": "customer@gmail.com",
        "phone": "9999999999",
        "phone_country_code": "+1",
        "description": null,
        "address": null,
        "created_at": "2025-09-24T10:30:14.808Z",
        "metadata": null,
        "default_payment_method_id": "pm_wQqUQeEP9YgJCLpa8NkG",
        "tax_registration_id": null
    },
    {
        "customer_id": "cus_136",
        "name": "John Doe",
        "email": "customer@gmail.com",
        "phone": "9999999999",
        "phone_country_code": "+1234",
        "description": null,
        "address": null,
        "created_at": "2025-09-24T13:49:24.111Z",
        "metadata": null,
        "default_payment_method_id": null,
        "tax_registration_id": null
    },
    {
        "customer_id": "cus_137",
        "name": "venu 10",
        "email": "guest@example.com",
        "phone": "999999999",
        "phone_country_code": "+65",
        "description": "First customer",
        "address": null,
        "created_at": "2025-09-25T13:25:45.670Z",
        "metadata": {
            "udf1": "value1",
            "new_customer": "true",
            "login_date": "2019-09-10T10:11:12Z"
        },
        "default_payment_method_id": null,
        "tax_registration_id": null
    },
    {
        "customer_id": "cus_138",
        "name": null,
        "email": "guest@example.com",
        "phone": null,
        "phone_country_code": null,
        "description": null,
        "address": null,
        "created_at": "2025-09-25T13:39:37.242Z",
        "metadata": null,
        "default_payment_method_id": null,
        "tax_registration_id": null
    },
    {
        "customer_id": "cus_139",
        "name": null,
        "email": "guest@example.com",
        "phone": null,
        "phone_country_code": null,
        "description": null,
        "address": null,
        "created_at": "2025-09-25T13:39:54.772Z",
        "metadata": null,
        "default_payment_method_id": null,
        "tax_registration_id": null
    },
    {
        "customer_id": "cus_140",
        "name": null,
        "email": "guest@example.com",
        "phone": null,
        "phone_country_code": null,
        "description": null,
        "address": null,
        "created_at": "2025-09-25T13:42:24.499Z",
        "metadata": null,
        "default_payment_method_id": null,
        "tax_registration_id": null
    }
]
```

After adding the customer_id search feature
```bash
curl --location 'http://localhost:8080/customers/list?limit=50&offset=0&customer_id=cus_123' \
--header 'Accept: application/json' \
--header 'api-key: <YOUR_API_KEY>' \
--header 'Content-Type: application/json' \
--data-binary '@'

```

Response

```json
[
    {
        "customer_id": "cus_123",
        "name": "John Doe",
        "email": "guest@example.com",
        "phone": "999999999",
        "phone_country_code": "+65",
        "description": "First customer",
        "address": null,
        "created_at": "2025-09-24T08:05:42.489Z",
        "metadata": {
            "udf1": "value1",
            "new_customer": "true",
            "login_date": "2019-09-10T10:11:12Z"
        },
        "default_payment_method_id": null,
        "tax_registration_id": null
    }
]
```

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

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